Zapis do pliku XML – część V
Dzisiaj wreszcie zajmę się zapisem do pliku XML. Na samym końcu spróbuję wygenerować poprawną zawartość dokumentu XHTML.
Klasa XMLSaver
Zacznę od klasy zapisującej dane do pliku:
package xml;
import java.io.PrintStream;
import java.util.Map.Entry;
import xml.annotation.XML;
import static xml.XMLException.*;
import static xml.XMLTools.*;
public class XMLSaver {
}
Na początek utworzę kilka stałych do formatowania znaczników i atrybutów:
private static final String XML_HEADER_FORMAT =
"<?xml version=\"%s\" encoding=\"%s\"?>";
private static final String OPEN_TAG_START_FORMAT = "<%s";
private static final String ATTRIBUTE_FORMAT = " %s=\"%s\"";
private static final String OPEN_TAG_END_FORMAT = ">";
private static final String OPEN_CLOSE_TAG_END_FORMAT = "/>";
private static final String CLOSE_TAG_FORMAT = "</%s>";
Zapis do pliku XML można podzielić na zapis nagłówka oraz głównego elementu:
public static synchronized void saveXML(
PrintStream stream, Object xml) throws XMLException {
saveXMLHeader(stream, xml);
saveXMLElement(stream, getContents(xml).iterator().next());
stream.flush();
}
W definicji metody widać słowo kluczowe synchronized, które (w przypadku metod statycznych) oznacza czasową wyłączność dla wątku na wykonywanie tak oznaczonych metod danej klasy. W praktyce – jeżeli jeden wątek wykonuje statyczną metodę zsynchronizowaną określonej klasy, a drugi wątek potrzebuje tej samej lub innej zsynchronizowanej metody statycznej tej samej klasy, to drugie wywołanie zostanie zawieszone do czasu zakończenia pierwszego wywołania.
Ostatnia instrukcja wewnątrz powyższej metody (stream.flush()) nakazuje zakończenie przesyłania danych do strumienia przed kontynuacją działania programu.
Zapisanie nagłówka pliku XML zawiera sprawdzenie, czy klasa obiektu posiada odpowiednią adnotację, oraz sam zapis:
private static void saveXMLHeader(
PrintStream stream, Object xml) throws XMLException {
XML xmlMeta = xml.getClass().getAnnotation(XML.class);
if (xmlMeta == null) {
throw new XMLException(String.format(
ANNOTATION_NOT_FOUND, XML.class.getName()));
}
stream.println(String.format(XML_HEADER_FORMAT,
xmlMeta.version(), xmlMeta.encoding()));
stream.println(xmlMeta.doctype());
}
Podczas zapisywania elementu podejmowana jest decyzja co do sposobu jego zamknięcia. Jeżeli element posiada jakąś zawartość, to jesto on zamykany przy pomocy drugiego znacznika. W przeciwnym wypadku używany jest pojedynczy znacznik, który odrazu się zamyka:
private static void saveXMLElement(
PrintStream stream, Object element) throws XMLException {
stream.print(String.format(OPEN_TAG_START_FORMAT,
getElementTagName(element)));
saveXMLAttributes(stream, element);
Iterable<Object> contents = getContents(element);
if (contents.iterator().hasNext()) {
stream.println(OPEN_TAG_END_FORMAT);
saveXMLElementContents(stream, contents);
stream.println(String.format(CLOSE_TAG_FORMAT,
getElementTagName(element)));
} else {
stream.println(OPEN_CLOSE_TAG_END_FORMAT);
}
}
Zapisanie atrybutów elementu opiera się o prostą iterację po zbiorze wpisów do mapy atrybutów:
private static void saveXMLAttributes(
PrintStream stream, Object element) throws XMLException {
for (Entry<String, Object> attribute : getAttributes(
element).entrySet()) {
stream.print(String.format(ATTRIBUTE_FORMAT,
attribute.getKey(), attribute.getValue()));
}
}
Podobnie przedstawia się zapis tekstu pobranego z obiektu klasy z adnotacją @Text. Tutaj jednak kolekcją jest lista łańcuchów znaków:
private static void saveXMLText(
PrintStream stream, Object text) throws XMLException {
for (String textContent : getTextContents(text)) {
stream.println(textContent);
}
}
Na koniec zostawiłem zapisywanie zawartości elementu. Dla każdego obiektu dochodzi do sprawdzenia, czy jest on elementem, czy też zawiera tylko tekst. W zależności od adnotacji podejmowane jest stosowne działanie:
private static void saveXMLElementContents(
PrintStream stream, Iterable<Object> contents)
throws XMLException {
for (Object content : contents) {
if (isElement(content)) {
saveXMLElement(stream, content);
} else if (isText(content)) {
saveXMLText(stream, content);
}
}
}
To już ostatnia metoda. Czas już sprawdzić działanie całości.
Uruchomienie programu
Utworzyłem jeszcze jedną klasę, a w niej metodę main(String[] args):
import static xml.xhtml.elements.Meta.HTTPEquiv.*;
import static xml.xhtml.elements.Meta.*;
import static xml.xhtml.XHTML.DTD.*;
import xml.XMLException;
import xml.XMLSaver;
import xml.xhtml.XHTML;
import xml.xhtml.elements.Meta;
public class Main {
public static void main(String[] args) {
XHTML xhtml = XHTML.create(STRICT);
xhtml.getHTML().getHead().getTitle().setTitle(
"Hello World!");
xhtml.getHTML().getHead().addMeta(new Meta(
CONTENT_TYPE, "text/html;charset=utf-8"));
xhtml.getHTML().getHead().addMeta(new Meta(
AUTHOR, "Sławomir Czerwiński"));
xhtml.getHTML().getHead().addMeta(new Meta(
DESCRIPTION, "Simple \'Hello World!\' example."));
xhtml.getHTML().getHead().addMeta(new Meta(
KEYWORDS, "Hello, World"));
try {
XMLSaver.saveXML(System.out, xhtml);
} catch (XMLException e) {
e.printStackTrace();
}
}
}
Powinienem uzyskać dokument o tytule „Hello World!”. Ustawiam też kodowanie znaków, autora, opis i słowa kluczowe. Uruchamiam aplikację i otrzymuję wynik:
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta content="text/html;charset=utf-8" http-equiv="content-type"/> <meta content="Sławomir Czerwiński" name="author"/> <meta content="Simple 'Hello World!' example." name="description"/> <meta content="Hello, World" name="keywords"/> <title> Hello World! </title> </head> <body/> </html>
Wygląda w porządku… co na to W3C Markup Validation Service?
This document was successfully checked as XHTML 1.0 Strict!
Świetnie!
Podsumowanie
Udowodniłem, że można wykorzystać adnotacje do mapowania obiektów do pliku XML. Co prawda można też utworzyć abstrakcyjną klasę zawierającą mapę atrybutów i listę zawartości. Lecz adnotacje mają pewne zalety:
- brak konieczności dziedziczenia – zawsze można wykorzystać inną klasę bazową,
- brak konieczności definiowania atrybutów i zawartości w kolekcjach,
- oddzielenie mapowania od implementacji klasy.
Na koniec jeszcze zamieszczam odnośniki do poprzednich części zapisu do pliku XML:



