<h2>Wprowadzenie</h2>Witam wszystkich czytelników artykułu, ta część jest poświęcona DOMowi. Następny będzie poświęcony JAXB i tym samym zakończy się cykl podstaw XML. Najpierw będzie trochę teorii, a potem już tylko praktyka. Zacznijmy. <h2>DOM (Document Object Model) - TEORIA</h2>Model obsługi DOM jest zaprojektowany w taki sposób, że czyta cały XML na raz i zapisuje go, tworząc hierarchię w postaci drzewa, po którym możemy łatwo się poruszać i uzyskaj dostęp do potrzebnych nam elementów. W ten sposób, mając link do górnego elementu, możemy uzyskać wszystkie linki do jego wewnętrznych elementów. Co więcej, elementy znajdujące się wewnątrz elementu są dziećmi tego elementu i jest on ich rodzicem. Kiedy już wczytamy cały plik XML do pamięci, po prostu przejdziemy przez jego strukturę i wykonamy potrzebne działania. Trochę o programistycznej części DOM w Javie: DOM ma wiele interfejsów stworzonych w celu opisywania różnych danych. Wszystkie te interfejsy dziedziczą jeden wspólny interfejs – Node. Ponieważ tak naprawdę najpopularniejszym typem danych w DOM jest Node, którym może być wszystko. Każdy węzeł ma następujące przydatne metody wyszukiwania informacji:
getNodeName
– uzyskaj nazwę hosta.getNodeValue
– uzyskaj wartość węzła.getNodeType
– pobierz typ węzła.getParentNode
– pobierz węzeł, w którym znajduje się dany węzeł.getChildNodes
– pobierz wszystkie węzły pochodne (węzły znajdujące się wewnątrz danego węzła).getAttributes
– pobierz wszystkie atrybuty węzła.getOwnerDocument
– pobierz dokument tego węzła.getFirstChild/getLastChild
– pobierz pierwszy/ostatni węzeł pochodny.getLocalName
– przydatne przy przetwarzaniu przestrzeni nazw w celu uzyskania nazwy bez prefiksu.getTextContent
– zwraca cały tekst w elemencie i wszystkie elementy w obrębie danego elementu, łącznie z podziałami linii i spacjami.
- TYLKO elementy mają atrybuty.
- Elementy NIE MAJĄ znaczenia.
- Nazwa węzła elementu jest taka sama jak nazwa znacznika, a nazwa węzła atrybutu jest taka sama jak nazwa atrybutu.
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.IOException;
Zapewniam importy, żebyście nie pomylili klas :) Zadanie nr 1 - musimy pobrać informacje o wszystkich pracownikach i wyprowadzić je na konsolę z następującego pliku XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<company>
<name>IT-Heaven</name>
<offices>
<office floor="1" room="1">
<employees>
<employee name="Maksim" job="Middle Software Developer" />
<employee name="Ivan" job="Junior Software Developer" />
<employee name="Franklin" job="Junior Software Developer" />
</employees>
</office>
<office floor="1" room="2">
<employees>
<employee name="Herald" job="Middle Software Developer" />
<employee name="Adam" job="Middle Software Developer" />
<employee name="Leroy" job="Junior Software Developer" />
</employees>
</office>
</offices>
</company>
Jak widać wszystkie informacje mamy zapisane w elementach pracowniczych. Aby zapisać to gdzieś w naszym programie, utwórzmy klasę Employee
:
public class Employee {
private String name, job;
public Employee(String name, String job) {
this.name = name;
this.job = job;
}
public String getName() {
return name;
}
public String getJob() {
return job;
}
}
Skoro mamy już opis struktury przechowywania danych, potrzebujemy kolekcji, która będzie przechowywać pracowników. Stworzymy go w samym kodzie. Musimy także utworzyć dokument w oparciu o nasz XML:
public class DOMExample {
// Список для сотрудников из XML plik
private static ArrayList<Employee> employees = new ArrayList<>();
public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
// Получение фабрики, чтобы после получить билдер dokumentов.
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// ПолучLub из фабрики билдер, который парсит XML, создает структуру Document в виде иерархического дерева.
DocumentBuilder builder = factory.newDocumentBuilder();
// ЗапарсLub XML, создав структуру Document. Теперь у нас есть доступ ко всем elementм, Jakим нам нужно.
Document document = builder.parse(new File("resource/xml_file1.xml"));
}
}
Po otrzymaniu dokumentu mamy nieograniczoną władzę nad całą strukturą pliku XML. W każdej chwili możemy pobrać dowolne elementy, wrócić do sprawdzenia dowolnych danych i ogólnie jest to bardziej elastyczne podejście niż mieliśmy w SAX. W kontekście tego zadania wystarczy wyodrębnić wszystkie elementy pracowników, a następnie wydobyć wszystkie informacje na ich temat. To całkiem proste:
public class DOMExample {
// Список для сотрудников из XML plik
private static ArrayList<Employee> employees = new ArrayList<>();
public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
// Получение фабрики, чтобы после получить билдер dokumentов.
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// ПолучLub из фабрики билдер, который парсит XML, создает структуру Document в виде иерархического дерева.
DocumentBuilder builder = factory.newDocumentBuilder();
// ЗапарсLub XML, создав структуру Document. Теперь у нас есть доступ ко всем elementм, Jakим нам нужно.
Document document = builder.parse(new File("resource/xml_file1.xml"));
// Получение списка всех элементов employee внутри корневого element (getDocumentElement возвращает ROOT элемент XML plik).
NodeList employeeElements = document.getDocumentElement().getElementsByTagName("employee");
// Перебор всех элементов employee
for (int i = 0; i < employeeElements.getLength(); i++) {
Node employee = employeeElements.item(i);
// Получение атрибутов каждого element
NamedNodeMap attributes = employee.getAttributes();
// Добавление сотрудника. Атрибут - тоже Node, потому нам нужно получить oznaczający атрибута с помощью метода getNodeValue()
employees.add(new Employee(attributes.getNamedItem("name").getNodeValue(), attributes.getNamedItem("job").getNodeValue()));
}
// Вывод информации о каждом сотруднике
for (Employee employee : employees)
System.out.println(String.format("Информации о сотруднике: Nazwa - %s, должность - %s.", employee.getName(), employee.getJob()));
}
}
Opis tego rozwiązania znajduje się bezpośrednio w rozwiązaniu. Wskazane jest, aby po zapoznaniu się z kodem wrócić do teorii i przeczytać ją ponownie. Tak naprawdę wszystko jest instynktownie jasne. Przeczytaj uważnie komentarze i nie powinno być żadnych pytań, a jeśli jakieś są, możesz napisać w komentarzach, odpowiem, lub w linku, albo po prostu uruchom swój POMYSŁ i spróbuj sam pobawić się kodem, jeśli jeszcze tego nie zrobiłeś. Zatem po uruchomieniu kodu otrzymaliśmy następujący wynik:
Информации о сотруднике: Nazwa - Maksim, должность - Middle Software Developer.
Информации о сотруднике: Nazwa - Ivan, должность - Junior Software Developer.
Информации о сотруднике: Nazwa - Franklin, должность - Junior Software Developer.
Информации о сотруднике: Nazwa - Herald, должность - Middle Software Developer.
Информации о сотруднике: Nazwa - Adam, должность - Middle Software Developer.
Информации о сотруднике: Nazwa - Leroy, должность - Junior Software Developer.
Jak widać zadanie zostało pomyślnie zakończone! Przejdźmy do kolejnego zadania :) Zadanie nr 2 - z konsoli wpisujemy nazwę elementu, o której należy wyświetlić informację o wszystkich znajdujących się w nim elementach oraz ich atrybutach z następującego pliku XML:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<oracle>
<connection value="jdbc:oracle:thin:@10.220.140.48:1521:test1" />
<user value="secretOracleUsername" />
<password value="111" />
</oracle>
<mysql>
<connection value="jdbc:mysql:thin:@10.220.140.48:1521:test1" />
<user value="secretMySQLUsername" />
<password value="222" />
</mysql>
</root>
Wszystko jest dość proste: musimy pobrać element według jego nazwy, którą liczymy, a następnie przejść przez wszystkie węzły potomne. Aby to zrobić, musisz iterować przez wszystkie węzły podrzędne wszystkich węzłów podrzędnych, które są elementami. Rozwiązanie tego problemu:
public class DOMExample {
public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
// Ридер для считывания имени тега из консоли
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
// Получение фабрики, чтобы после получить билдер dokumentов.
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// ПолучLub из фабрики билдер, который парсит XML, создает структуру Document в виде иерархического дерева.
DocumentBuilder builder = factory.newDocumentBuilder();
// ЗапарсLub XML, создав структуру Document. Теперь у нас есть доступ ко всем elementм, Jakим нам нужно.
Document document = builder.parse(new File("resource/xml_file3.xml"));
// Считывание имени тега для поиска его в файле
String element = reader.readLine();
// Получение списка элементов, однако для удобства будем рассматривать только первое совпадение в dokumentе.
// Так же заметьте, что мы ищем элемент внутри dokumentа, а не рут element. Это сделано для того, чтобы рут элемент тоже искался.
NodeList matchedElementsList = document.getElementsByTagName(element);
// Даже если element нет, всегда будет возвращаться список, просто он будет пустым.
// Потому, чтобы утверждать, что element нет в файле, достаточно проверить размер списка.
if (matchedElementsList.getLength() == 0) {
System.out.println("Etykietka " + element + " не был найден в файле.");
} else {
// Получение первого element.
Node foundedElement = matchedElementsList.item(0);
System.out.println("Элемент был найден!");
// Если есть данные внутри, вызов метода для вывода всей информации
if (foundedElement.hasChildNodes())
printInfoAboutAllChildNodes(foundedElement.getChildNodes());
}
}
/**
* Рекурсивный метод, который будет выводить информацию про все узлы внутри всех узлов, которые пришли параметром, пока не будут перебраны все узлы.
* @param list Список узлов.
*/
private static void printInfoAboutAllChildNodes(NodeList list) {
for (int i = 0; i < list.getLength(); i++) {
Node node = list.item(i);
// У элементов есть два вида узлов - другие элементы Lub текстовая информация. Потому нужно разбираться две ситуации отдельно.
if (node.getNodeType() == Node.TEXT_NODE) {
// Фильтрация информации, так Jak пробелы и переносы строчек нам не нужны. Это не информация.
String textInformation = node.getNodeValue().replace("\n", "").trim();
if(!textInformation.isEmpty())
System.out.println("Внутри element найден текст: " + node.getNodeValue());
}
// Если это не текст, а элемент, то обрабатываем его Jak элемент.
else {
System.out.println("Найден элемент: " + node.getNodeName() + ", его атрибуты:");
// Получение атрибутов
NamedNodeMap attributes = node.getAttributes();
// Вывод информации про все атрибуты
for (int k = 0; k < attributes.getLength(); k++)
System.out.println("Nazwa атрибута: " + attributes.item(k).getNodeName() + ", его oznaczający: " + attributes.item(k).getNodeValue());
}
// Если у данного element еще остались узлы, то вывести всю информацию про все его узлы.
if (node.hasChildNodes())
printInfoAboutAllChildNodes(node.getChildNodes());
}
}
}
Cały opis rozwiązania znajduje się w komentarzach, jednak chciałbym trochę graficznie zilustrować podejście, które zastosowaliśmy, na przykładzie z obrazka z teorii. Zakładamy, że musimy wyświetlić informację o znaczniku HTML. Jak widać, musimy przejść od góry do dołu od korzenia drzewa. Wszystkie linie są węzłami. W rozwiązaniu będziemy rekurencyjnie przechodzić od początku żądanego elementu przez wszystkie jego węzły, a jeśli jeden z jego węzłów jest elementem, to iterujemy także po wszystkich węzłach tego elementu. Zatem po uruchomieniu kodu otrzymaliśmy następujące dane wyjściowe dla elementu głównego:
Элемент был найден!
Найден элемент: oracle, его атрибуты:
Найден элемент: connection, его атрибуты:
Nazwa атрибута: value, его oznaczający: jdbc:oracle:thin:@10.220.140.48:1521:test1
Найден элемент: user, его атрибуты:
Nazwa атрибута: value, его oznaczający: secretOracleUsername
Найден элемент: password, его атрибуты:
Nazwa атрибута: value, его oznaczający: 111
Найден элемент: mysql, его атрибуты:
Найден элемент: connection, его атрибуты:
Nazwa атрибута: value, его oznaczający: jdbc:mysql:thin:@10.220.140.48:1521:test1
Найден элемент: user, его атрибуты:
Nazwa атрибута: value, его oznaczający: secretMySQLUsername
Найден элемент: password, его атрибуты:
Nazwa атрибута: value, его oznaczający: 222
Problem został pomyślnie rozwiązany! Zadanie nr 3 – z poniższego pliku XML, w którym zapisane są informacje o studentach, profesorach i pracownikach, należy je odczytać i wyprowadzić do konsoli:
<?xml version="1.0" encoding="UTF-8"?>
<database>
<students>
<student name="Maksim" course="3" specialization="CE" />
<student name="Stephan" course="1" specialization="CS" />
<student name="Irvin" course="2" specialization="CE" />
</students>
<professors>
<professor name="Herald" experience="7 years in University" discipline="Math" />
<professor name="Adam" experience="4 years in University" discipline="Programming" />
<professor name="Anton" experience="6 years in University" discipline="English" />
</professors>
<service>
<member name="John" position="janitor" />
<member name="Jordan" position="janitor" />
<member name="Mike" position="janitor" />
</service>
</database>
Zadanie jest dość proste, ale ciekawe. Najpierw musimy utworzyć 4 klasy: pracownik, profesor i student, a także wspólną klasę abstrakcyjną Human, aby sprowadzić nazwę zmiennej z każdej klasy do wspólnego mianownika: Abstrakcyjna klasa nadrzędna
public abstract class Human {
private String name;
public Human(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Student
public class Student extends Human {
private String course, specialization;
public Student(String name, String course, String specialization) {
super(name);
this.course = course;
this.specialization = specialization;
}
public String getCourse() {
return course;
}
public String getSpecialization() {
return specialization;
}
public String toString() {
return "Голодный студент " + getName() + " " + course + "-го курса, обучающийся по специальности " + specialization;
}
}
Profesor
public class Professor extends Human {
private String experience, discipline;
public Professor(String name, String experience, String discipline) {
super(name);
this.experience = experience;
this.discipline = discipline;
}
public String getExperience() {
return experience;
}
public String getDiscipline() {
return discipline;
}
public String toString() {
return "Профессор " + getName() + ", обладающий опытом: \"" + experience + "\", выкладает дисциплину " + discipline;
}
}
Pracownik
public class Member extends Human {
private String position;
public Member(String name, String position) {
super(name);
this.position = position;
}
public String getPosition() {
return position;
}
public String toString() {
return "Сотрудник обслуживающего персонала " + getName() + ", должность: " + position;
}
}
Teraz, gdy nasze zajęcia są już gotowe, pozostaje nam tylko napisać kod, aby uzyskać wszystkie elementy student, profesor i członek, a następnie uzyskać ich atrybuty. Do przechowywania użyjemy kolekcji, która będzie przechowywać wspólne dla wszystkich obiekty klasy nadrzędnej - Human. I tak rozwiązanie tego problemu:
public class DOMExample {
// Коллекция для хранения всех людей
private static ArrayList<Human> humans = new ArrayList<>();
// Константы для элементов
private static final String PROFESSOR = "professor";
private static final String MEMBER = "member";
private static final String STUDENT = "student";
public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
// Получение фабрики, чтобы после получить билдер dokumentов.
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// ПолучLub из фабрики билдер, который парсит XML, создает структуру Document в виде иерархического дерева.
DocumentBuilder builder = factory.newDocumentBuilder();
// ЗапарсLub XML, создав структуру Document. Теперь у нас есть доступ ко всем elementм, Jakим нам нужно.
Document document = builder.parse(new File("resource/xml_file3.xml"));
// Получение информации про каждый элемент отдельно
collectInformation(document, PROFESSOR);
collectInformation(document, MEMBER);
collectInformation(document, STUDENT);
// Вывод информации
humans.forEach(System.out::println);
}
/**
* Метод ищет информацию про теги по имени element и вносит всю информацию в коллекцию humans.
* @param document Документ, в котором будем искать элементы.
* @param element Nazwa element, теги которого нужно найти. Должна быть одна из констант, которые определяются выше.
*/
private static void collectInformation(Document document, final String element) {
// Получение всех элементов по имени тега.
NodeList elements = document.getElementsByTagName(element);
// Перебор всех найденных элементов
for (int i = 0; i < elements.getLength(); i++) {
// Получение всех атрибутов element
NamedNodeMap attributes = elements.item(i).getAttributes();
String name = attributes.getNamedItem("name").getNodeValue();
// В зависимости от типа element, нам нужно собрать свою дополнительну информацию про каждый подкласс, а после добавить нужные образцы в коллекцию.
switch (element) {
case PROFESSOR: {
String experience = attributes.getNamedItem("experience").getNodeValue();
String discipline = attributes.getNamedItem("discipline").getNodeValue();
humans.add(new Professor(name, experience, discipline));
} break;
case STUDENT: {
String course = attributes.getNamedItem("course").getNodeValue();
String specialization = attributes.getNamedItem("specialization").getNodeValue();
humans.add(new Student(name, course, specialization));
} break;
case MEMBER: {
String position = attributes.getNamedItem("position").getNodeValue();
humans.add(new Member(name, position));
} break;
}
}
}
}
Zauważ, że potrzebujemy tylko nazwy elementu, aby pobrać wszystkie te elementy z dokumentu. Znacząco upraszcza to proces wyszukiwania potrzebnych informacji. Wszystkie informacje dotyczące kodu znajdują się w komentarzach. Nie zastosowano niczego nowego, czego nie było w poprzednich zadaniach. Dane wyjściowe kodu:
Профессор Herald, обладающий опытом: "7 years in University", выкладает дисциплину Math
Профессор Adam, обладающий опытом: "4 years in University", выкладает дисциплину Programming
Профессор Anton, обладающий опытом: "6 years in University", выкладает дисциплину English
Сотрудник обслуживающего персонала John, должность: janitor
Сотрудник обслуживающего персонала Jordan, должность: janitor
Сотрудник обслуживающего персонала Mike, должность: janitor
Голодный студент Maksim 3-го курса, обучающийся по специальности CE
Голодный студент Stephan 1-го курса, обучающийся по специальности CS
Голодный студент Irvin 2-го курса, обучающийся по специальности CE
Problem rozwiązany! Zalecenia, kiedy używać DOM, a kiedy SAX. Różnica pomiędzy tymi narzędziami polega na funkcjonalności i szybkości. Jeśli potrzebujesz bardziej elastycznej funkcjonalności i możesz sobie pozwolić na marnowanie wydajności programu, twoim wyborem jest DOM, ale jeśli twoim głównym celem jest zmniejszenie kosztów pamięci, to DOM nie jest najlepszym wyborem, ponieważ odczytuje wszystkie informacje z pliku XML i przechowuje to. Dlatego metoda odczytu sekwencyjnego SAX jest tańsza. W skrócie: jeśli potrzebujesz wydajności - SAX, funkcjonalności - DOM. <h2>Wnioski</h2>Każdy programista ma swoje własne narzędzia i, w zależności od zadania, musisz użyć określonych narzędzi. W artykułach o SAX i DOM moim celem było nauczenie Cię, jak wydobywać informacje z plików XML i przetwarzać je tak, jak tego potrzebujesz. Jednak nawet jeśli przeczytałeś te artykuły, nie możesz twierdzić, że nauczyłeś się korzystać z tych narzędzi. Powinieneś poćwiczyć, przetestować kod z artykułów, zrozumieć, jak to działa i spróbować napisać coś samodzielnie. W końcu najważniejsza jest praktyka. Ostatni artykuł ukaże się w najbliższych dniach i najprawdopodobniej po zakończeniu konkursu i będzie poświęcony JAXB. JAXB to narzędzie do zapisywania obiektów w programie w formacie XML. To wszystko, mam nadzieję, że ten artykuł był przydatny i życzę powodzenia w programowaniu :) Poprzedni artykuł: [Konkurs] Podstawy XML dla programisty Java - Część 3.1 z 3 - SAX
GO TO FULL VERSION