Jak odczytywać i zapisywać pliki XML w Javie
Pliki XML mogą służyć do różnych celów, w tym do przechowywania danych. Zanim JSON stał się popularny, XML był preferowanym formatem reprezentowania, przechowywania i transportu danych strukturalnych.
Pomimo spadku jego popularności we współczesnych czasach, nadal można od czasu do czasu natknąć się na XML. Kluczowe jest zatem nabycie biegłości w pracy z tym formatem. Poznaj zawiłości wykorzystania interfejsu programowania aplikacji (API) Document Object Model (DOM) do odczytu i zapisu dokumentów XML przy użyciu języka Java jako podstawowego narzędzia.
Wymagania dotyczące przetwarzania XML w Javie
Java Standard Edition (SE) zawiera Java API for XML Processing, powszechnie określany jako JAXP, kompleksowy framework obejmujący różne aspekty obsługi XML. Ten rozległy repertuar składa się z kilku integralnych komponentów, w tym:
Obiektowy model dokumentu (DOM) obejmuje zestaw klas, które ułatwiają manipulowanie komponentami XML, w tym elementami, węzłami i atrybutami. Jednakże, biorąc pod uwagę jego konstrukcję do ładowania całego pliku XML do pamięci w celu przetwarzania, nie jest on idealnie przystosowany do wydajnej obsługi dokumentów o znacznych rozmiarach.
Simple API for XML (SAX) to lekki mechanizm parsowania zaprojektowany specjalnie do obsługi dokumentów XML. W przeciwieństwie do Document Object Model (DOM), który buduje całą strukturę drzewa podczas przetwarzania, SAX działa poprzez uruchamianie zdarzeń w oparciu o zawartość XML napotkaną podczas parsowania pliku. Takie podejście zmniejsza zużycie pamięci i pozwala na większą elastyczność w zakresie obsługi dużych ilości danych. Jednak korzystanie z SAX może być nieco trudniejsze niż korzystanie z DOM ze względu na jego zależność od paradygmatów programowania opartych na zdarzeniach.
StAX, czyli Streaming API for XML, stanowi nowszy dodatek do sfery przetwarzania XML. Szczycące się imponującą wydajnością w zakresie filtrowania, przetwarzania i modyfikacji strumieni, to potężne narzędzie osiąga swoje cele bez konieczności hurtowego ładowania dokumentów XML do pamięci. W przeciwieństwie do architektury sterowanej zdarzeniami preferowanej przez SAX API, StAX wykorzystuje podejście typu pull, które czyni go prostszym i bardziej przyjaznym dla użytkownika, jeśli chodzi o kodowanie.
Aby obsługiwać dane XML w aplikacji Java, konieczne jest włączenie pewnych pakietów, które ułatwiają tę funkcjonalność. Pakiety te zapewniają różne metody i klasy do analizowania, manipulowania i generowania dokumentów XML.
import javax.xml.parsers.*;
import javax.xml.transform.*;
import org.w3c.dom.*;
Przygotowanie przykładowego pliku XML
Aby zrozumieć przykładowy kod i koncepcje za nim stojące, użyj tego przykładowego pliku XML firmy Microsoft . Oto jego fragment:
<?xml version="1.0"?>
<catalog>
<book id="bk101">
<author>Gambardella, Matthew</author>
<title>XML Developer's Guide</title>
<genre>Computer</genre>
<price>44.95</price>
<publish_date>2000-10-01</publish_date>
<description>An in-depth look at creating applications
with XML.</description>
</book>
<book id="bk102">
<author>Ralls, Kim</author>
...snipped...
Odczytywanie pliku XML za pomocą interfejsu API DOM
Aby skutecznie przetwarzać plik XML przy użyciu interfejsu programowania aplikacji Document Object Model (DOM), musimy najpierw utworzyć instancję klasy DocumentBuilder
, która ułatwi analizowanie dokumentu XML.
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Można teraz zdecydować się na przechowywanie całego dokumentu w pamięci, zaczynając od głównego elementu XML, który w tym przypadku odpowiada elementowi “katalog”.
// XML file to read
File file = "<path_to_file>";
Document document = builder.parse(file);
Element catalog = document.getDocumentElement();
Rzeczywiście, stosując to podejście, uzyskuje się pełny dostęp do całego dokumentu XML, zaczynając od jego głównego węzła, a mianowicie elementu “katalog”.
Wyodrębnianie informacji za pomocą interfejsu API DOM
Po uzyskaniu elementu głównego za pomocą parsera XML można użyć interfejsu API DOM (Document Object Model), aby uzyskać dostęp do cennych informacji w nim zawartych. Praktycznym podejściem jest na przykład pobranie wszystkich bezpośrednich elementów potomnych elementu głównego i iterowanie po nich. Należy jednak pamiętać, że metoda getChildNodes()
zwraca wszystkie typy elementów potomnych, w tym węzły tekstowe i węzły komentarzy, które nie są istotne dla naszego obecnego zadania. Dlatego podczas przetwarzania tych wyników powinniśmy kierować się wyłącznie na elementy podrzędne.
NodeList books = catalog.getChildNodes();
for (int i = 0, ii = 0, n = books.getLength() ; i < n ; i\\+\\+) {
Node child = books.item(i);
if ( child.getNodeType() != Node.ELEMENT_NODE )
continue;
Element book = (Element)child;
// work with the book Element here
}
Aby zlokalizować określony element podrzędny pod jego rodzicem w dokumencie XML przy użyciu języka C#, można utworzyć metodę statyczną, która iteruje po kolekcji węzłów podrzędnych w celu zidentyfikowania żądanego elementu na podstawie jego nazwy. Jeśli element zostanie wykryty podczas tego procesu, zostanie zwrócony; w przeciwnym razie wynik będzie zerowy.
static private Node findFirstNamedElement(Node parent,String tagName)
{
NodeList children = parent.getChildNodes();
for (int i = 0, in = children.getLength() ; i < in ; i\\+\\+) {
Node child = children.item(i);
if (child.getNodeType() != Node.ELEMENT_NODE)
continue;
if (child.getNodeName().equals(tagName))
return child;
}
return null;
}
Należy pamiętać, że DOM API klasyfikuje zawartość tekstową zawartą w elemencie jako indywidualny węzeł kategorii TEXT\_NODE. Zawartość tekstowa może składać się z kilku sąsiadujących węzłów tekstowych, co wymaga specjalnej obsługi w celu pobrania tekstu danego elementu:
static private String getCharacterData(Node parent)
{
StringBuilder text = new StringBuilder();
if ( parent == null )
return text.toString();
NodeList children = parent.getChildNodes();
for (int k = 0, kn = children.getLength() ; k < kn ; k\\+\\+) {
Node child = children.item(k);
if (child.getNodeType() != Node.TEXT_NODE)
break;
text.append(child.getNodeValue());
}
return text.toString();
}
Korzystając z dostarczonych funkcji narzędziowych, przeanalizujmy przykład, który wyodrębnia istotne dane z dokumentu XML reprezentującego katalog książek.Poniższy kod wyświetla szczegółowe informacje dotyczące każdej publikacji zawartej we wspomnianym katalogu:
NodeList books = catalog.getChildNodes();
for (int i = 0, ii = 0, n = books.getLength() ; i < n ; i\\+\\+) {
Node child = books.item(i);
if (child.getNodeType() != Node.ELEMENT_NODE)
continue;
Element book = (Element)child;
ii\\+\\+;
String id = book.getAttribute("id");
String author = getCharacterData(findFirstNamedElement(child, "author"));
String title = getCharacterData(findFirstNamedElement(child, "title"));
String genre = getCharacterData(findFirstNamedElement(child, "genre"));
String price = getCharacterData(findFirstNamedElement(child, "price"));
String pubdate = getCharacterData(findFirstNamedElement(child, "pubdate"));
String descr = getCharacterData(findFirstNamedElement(child, "description"));
System.out.printf("%3d. book id = %s\n" \\+
" author: %s\n" \\+
" title: %s\n" \\+
" genre: %s\n" \\+
" price: %s\n" \\+
" pubdate: %s\n" \\+
" descr: %s\n",
ii, id, author, title, genre, price, pubdate, descr);
}
Oto wyjaśnienie kodu krok po kroku:
Kod przegląda młode potomstwo lub komponent główny, który służy jako podstawa dla całej struktury.
Dla każdego pojedynczego węzła potomnego, który odpowiada konkretnej książce, program sprawdza, czy jego podstawowa struktura danych posiada cechę bycia ELEMENT\_NODE. Jeśli warunek ten nie jest spełniony, proces przechodzi do kolejnej iteracji.
Jeśli węzeł podrzędny typu ELEMENT\_NODE zostanie napotkany w procesie przechodzenia drzewa DOM, właściwość child
zostanie przekształcona w instancję interfejsu Element
.
Późniejsze wykonanie programu obejmuje wyodrębnienie wielu atrybutów i danych znakowych związanych z określonym elementem książki, takich jak jego unikalny identyfikator (“id”), nazwisko autora, tytuł, gatunek, cena, data publikacji i informacje opisowe. Te wyodrębnione informacje są następnie drukowane na konsoli przy użyciu metody System.out.printf() do celów prezentacji.
Oto jak wyglądają dane wyjściowe:
Pisanie danych wyjściowych XML przy użyciu Transform API
Java oferuje XML Transformation API jako środek do manipulowania danymi XML. Ten interfejs API jest wykorzystywany w połączeniu z transformacją tożsamości w celu uzyskania danych wyjściowych. Aby to zilustrować, rozważmy rozszerzenie poprzedniego przykładowego katalogu o nowy element book.
Informacje dotyczące dzieła literackiego, w tym autora i tytułu, można uzyskać z zewnętrznego zasobu, takiego jak baza danych lub plik właściwości. Dostarczony plik właściwości służy jako model do tego celu.
id=bk113
author=Jane Austen
title=Pride and Prejudice
genre=Romance
price=6.99
publish_date=2010-04-01
description="It is a truth universally acknowledged, that a single man in possession of a good fortune must be in want of a wife." So begins Pride and Prejudice, Jane Austen's witty comedy of manners-one of the most popular novels of all time-that features splendidly civilized sparring between the proud Mr. Darcy and the prejudiced Elizabeth Bennet as they play out their spirited courtship in a series of eighteenth-century drawing-room intrigues.
Aby przetworzyć dokument XML, konieczne jest wykorzystanie techniki parsowania opisanej wcześniej. Obejmuje to rozbicie tekstowej struktury pliku i wyodrębnienie odpowiednich informacji do dalszej analizy lub manipulacji.
File file = ...; // XML file to read
Document document = builder.parse(file);
Element catalog = document.getDocumentElement();
Wykorzystując dostarczoną klasę Properties w języku programowania Java, można wydajnie pobierać i przetwarzać informacje przechowywane w oddzielnym zewnętrznym pliku konfiguracyjnym, zwanym plikiem “właściwości”. Proces ten wymaga minimalnej złożoności kodowania, usprawniając integrację preferencji lub ustawień zdefiniowanych przez użytkownika z ogólną logiką aplikacji.
String propsFile = "<path_to_file>";
Properties props = new Properties();
try (FileReader in = new FileReader(propsFile)) {
props.load(in);
}
Po załadowaniu pliku właściwości można wyodrębnić z niego żądane wartości do dodania.
String id = props.getProperty("id");
String author = props.getProperty("author");
String title = props.getProperty("title");
String genre = props.getProperty("genre");
String price = props.getProperty("price");
String publish_date = props.getProperty("publish_date");
String descr = props.getProperty("description");
Teraz utwórz pusty elementbookelement.
Element book = document.createElement("book");
book.setAttribute("id", id);
Włączenie poszczególnych elementów książki do korpusu tekstowego jest nieskomplikowanym przedsięwzięciem. Aby ułatwić ten proces, można skompilować katalog niezbędnych oznaczeń, organizując je w kolekcję znaną jako “Lista”. Wykonując powtarzającą się operację na tej liście, odpowiednie wpisy można skutecznie dołączyć do szerszych ram narracyjnych.
List<String> elnames =Arrays.asList("author", "title", "genre", "price",
"publish_date", "description");
for (String elname : elnames) {
Element el = document.createElement(elname);
Text text = document.createTextNode(props.getProperty(elname));
el.appendChild(text);
book.appendChild(el);
}
catalog.appendChild(book);
Wspomniany komponent katalogu posiada obecnie dodatkową, niedawno wprowadzoną jednostkę książki. Jedynym pozostałym zadaniem jest sformułowanie poprawionego dokumentu Extensible Markup Language (XML) obejmującego wspomniane aktualizacje.
Aby wygenerować dokument XML przy użyciu transformatora, należy najpierw uzyskać instancję wspomnianego transformatora. Można to osiągnąć poprzez implementację niezbędnego kodu w języku programowania lub środowisku programistycznym. Na przykład w języku Python można wykorzystać bibliotekę transformers
i powiązane z nią funkcje do skonstruowania instancji transformatora do wykorzystania w zadaniach przetwarzania języka naturalnego.
TransformerFactory tfact = TransformerFactory.newInstance();
Transformer tform = tfact.newTransformer();
tform.setOutputProperty(OutputKeys.INDENT, "yes");
tform.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "3");
Możesz użyć metody setOutputProperty()
, aby określić pożądany poziom wcięcia w wygenerowanych danych wyjściowych.
Ostatni etap obejmuje wykonanie procesu konwersji. Wynik jest wyświetlany za pośrednictwem strumienia wyjściowego, który można obserwować, monitorując konsolę lub terminal, na którym działa program.
tform.transform(new DOMSource(document), new StreamResult(System.out));
Aby zapisać dane wyjściowe programu do pliku zamiast drukowania go na konsoli, można zastosować następujące podejście:
tform.transform(new DOMSource(document), new StreamResult(new File("output.xml")));
Aby wykonać zarówno odczyt, jak i zapis plików Extensible Markup Language (XML) przy użyciu języka programowania Java, należy sekwencyjnie wykonać szereg działań proceduralnych. Obejmują one definiowanie obiektu dokumentu XML, tworzenie węzłów w tym dokumencie, dołączanie elementów podrzędnych do węzłów nadrzędnych, określanie atrybutów elementów, dołączanie lub wstawianie nowych węzłów w różnych pozycjach w dokumencie, a na koniec zamykanie wszelkich otwartych znaczników przed zakończeniem procesu.
Teraz wiesz, jak odczytywać i zapisywać pliki XML za pomocą języka Java
Wykorzystanie języka Java do analizowania i manipulowania rozszerzalnym językiem znaczników (XML) jest niezbędną umiejętnością często spotykaną w praktycznych zastosowaniach. W tym celu szczególnie przydatne są interfejsy API Document Object Model (DOM) i Transformation.
Kompleksowe zrozumienie modelu DOM (Document Object Model) jest niezbędne dla programistów, którzy chcą tworzyć skrypty po stronie klienta dla aplikacji internetowych lub stron internetowych. Na szczęście architektura DOM jest ustandaryzowana w różnych językach programowania, umożliwiając spójną manipulację za pomocą kodu napisanego w językach takich jak Java i JavaScript.