JavaRush /Blog Java /Random-PL /Podstawy XML dla programisty Java - Część 3.1 z 3 - SAX
Ярослав
Poziom 40
Днепр

Podstawy XML dla programisty Java - Część 3.1 z 3 - SAX

Opublikowano w grupie Random-PL
Wprowadzenie Witam wszystkich czytelników mojego nieostatniego artykułu i chcę pogratulować: skomplikowane rzeczy dotyczące XML mamy już za sobą. Ten artykuł będzie zawierał kod w Javie. Będzie trochę teorii, a potem praktyki. W związku z tym, że jeden materiał na SAX-ie zajmował 10 stron w Wordzie, zdałem sobie sprawę, że nie zmieściłem się w limitach. Dlatego artykuł 3 zostanie podzielony na 3 osobne artykuły, niezależnie od tego, jak dziwnie to może zabrzmieć. Wszystko będzie w następującej kolejności: SAX -> DOM -> JAXB. W tym artykule skupimy się wyłącznie na SAX. PS Gdzieś w kursie było zadanie, w którym konieczne było wyświetlenie wszystkich wewnętrznych elementów w pliku HTML. Po tym artykule będziesz mógł to zrobić bez czytania linia po linii z konwencjonalnymi BufferedReaderi złożonymi algorytmami przetwarzania, a podobne rozwiązanie zostanie podane w ostatnim praktycznym przykładzie. Zaczynamy :) SAX (Simple API for XML) - TEORIA Procedura obsługi SAX została zaprojektowana w taki sposób, że po prostu czyta sekwencyjnie pliki XML i reaguje na różne zdarzenia, po czym przekazuje informację do specjalnego procedury obsługi zdarzeń. Ma sporo wydarzeń, ale najczęstsze i najbardziej przydatne to:
  1. startDocument— początek dokumentu
  2. endDocument- koniec dokumentu
  3. startElement- otwarcie elementu
  4. endElement- zamknięcie elementu
  5. characters— informacje tekstowe wewnątrz elementów.
Wszystkie zdarzenia są przetwarzane w procedurze obsługi zdarzeń , którą należy utworzyć i zastąpić metody . Zalety: wysoka wydajność dzięki „bezpośredniej” metodzie odczytu danych, niskie koszty pamięci. Wady: ograniczona funkcjonalność, co oznacza, że ​​w problemach nieliniowych będziemy musieli ją dopracować. SAX (Simple API for XML) – PRAKTYKA Natychmiastowa lista importów, aby niczego nie przeszukiwać i nie mylić:
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
Teraz najpierw musimy utworzyć SAXParser:
public class SAXExample {
    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        // kreacja фабрики и образца parserа
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser parser = factory.newSAXParser();
    }
}
Jak widać, najpierw trzeba stworzyć fabrykę, a potem w fabryce stworzyć sam parser. Teraz, gdy mamy już sam parser, potrzebujemy procedury obsługi jego zdarzeń. W tym celu potrzebujemy osobnej klasy dla naszej wygody:
public class SAXExample {
    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser parser = factory.newSAXParser();
    }

    private static class XMLHandler extends DefaultHandler {
        @Override
        public void startDocument() throws SAXException {
            // Тут будет логика реакции на начало dokumentа
        }

        @Override
        public void endDocument() throws SAXException {
            // Тут будет логика реакции на конец dokumentа
        }

        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            // Тут будет логика реакции на начало element
        }

        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
            // Тут будет логика реакции на конец element
        }

        @Override
        public void characters(char[] ch, int start, int length) throws SAXException {
            // Тут будет логика реакции на текст между elementми
        }

        @Override
        public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
            // Тут будет логика реакции на пустое пространство внутри элементов (пробелы, переносы строчек и так далее).
        }
    }
}
Stworzyliśmy klasę zawierającą wszystkie metody potrzebne do obsługi zdarzeń wymienionych w teorii. Trochę dodatkowej teorii: Trochę o characters: jeśli element zawiera tekst, na przykład „ cześć ”, to teoretycznie metodę można wywołać 5 razy z rzędu dla każdego pojedynczego znaku, ale to nie jest wielka sprawa, ponieważ wszystko będzie nadal działać. O metodach startElementi endElement:uri - jest to przestrzeń, w której znajduje się element, localName- jest to nazwa elementu bez przedrostka, qName- jest to nazwa elementu z przedrostkiem (jeśli taki występuje, w przeciwnym razie po prostu nazwa elementu). urii localNamezawsze pusty, jeśli w fabryce nie włączyliśmy przetwarzania przestrzeni. Odbywa się to metodą fabryczną setNamespaceAware(true). Wtedy możemy uzyskać spację ( uri) i elementy z przedrostkami ( localName). Zadanie nr 1 — Mamy następujący kod 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>
Nasz cel: uzyskać z tego pliku wszystkie informacje o wszystkich pracownikach. Najpierw musimy stworzyć 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;
    }
}
A w naszej głównej klasie SAXExamplepotrzebujemy listy wszystkich pracowników:
private static ArrayList<Employee> employees = new ArrayList<>();
Przyjrzyjmy się teraz uważnie, gdzie w pliku XML znajdują się potrzebne nam informacje. I jak widzimy, wszystkie potrzebne nam informacje to atrybuty elementów employee. A skoro startElementmamy tak przydatny parametr jak attributes, to mamy dość proste zadanie. Na początek usuńmy niepotrzebne metody, aby nie zaśmiecać naszego kodu. Potrzebujemy tylko startElement. A w samej metodzie musimy zebrać informacje z atrybutów tagu pracownika. Uwaga:
public class SAXExample {
    private static ArrayList<Employee> employees = new ArrayList<>();

    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser parser = factory.newSAXParser();
    }

    private static class XMLHandler extends DefaultHandler {
        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            if (qName.equals("employee")) {
                String name = attributes.getValue("name");
                String job = attributes.getValue("job");
                employees.add(new Employee(name, job));
            }
        }
    }
}
Logika jest prosta: jeśli element będzie nosił nazwę employee, otrzymamy po prostu informację o jego atrybutach. Istnieje attributesprzydatna metoda, dzięki której znając nazwę atrybutu, można uzyskać jego wartość. Tego właśnie używaliśmy. Teraz, gdy stworzyliśmy procedurę obsługi zdarzeń dla początku elementu, musimy przeanalizować nasz plik XML . Aby to zrobić, po prostu wykonaj następujące czynności:
public class SAXExample {
    private static ArrayList<Employee> employees = new ArrayList<>();

    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser parser = factory.newSAXParser();

        XMLHandler handler = new XMLHandler();
        parser.parse(new File("resource/xml_file1.xml"), handler);

        for (Employee employee : employees)
            System.out.println(String.format("Nazwa сотрудника: %s, его должность: %s", employee.getName(), employee.getJob()));
    }

    private static class XMLHandler extends DefaultHandler {
        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            if (qName.equals("employee")) {
                String name = attributes.getValue("name");
                String job = attributes.getValue("job");
                employees.add(new Employee(name, job));
            }
        }
    }
}
W metodzie parse musisz przekazać ścieżkę do pliku xml i utworzonego modułu obsługi. I tak, używając tego kodu, wyodrębniliśmy informacje z tego 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>
I otrzymaliśmy następujące dane wyjściowe:
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
Misja wykonana! Zadanie nr 2 - mamy następujący XML:
<?xml version="1.0" encoding="UTF-8"?>
<company>
    <name>IT-Heaven</name>
    <offices>
        <office floor="1" room="1">
            <employees>
                <employee>
                    <name>Maksim</name>
                    <job>Middle Software Developer</job>
                </employee>
                <employee>
                    <name>Ivan</name>
                    <job>Junior Software Developer</job>
                </employee>
                <employee>
                    <name>Franklin</name>
                    <job>Junior Software Developer</job>
                </employee>
            </employees>
        </office>
        <office floor="1" room="2">
            <employees>
                <employee>
                    <name>Herald</name>
                    <job>Middle Software Developer</job>
                </employee>
                <employee>
                    <name>Adam</name>
                    <job>Middle Software Developer</job>
                </employee>
                <employee>
                    <name>Leroy</name>
                    <job>Junior Software Developer</job>
                </employee>
            </employees>
        </office>
    </offices>
</company>
Nasz cel: uzyskać z tego pliku wszystkie informacje o wszystkich pracownikach. Ten problem dobrze pokazuje, jak źle zorganizowany plik XML może utrudnić pisanie kodu. Jak widać, informacje o nazwie i pozycji są teraz przechowywane jako informacje tekstowe wewnątrz elementów namei job. Aby przeczytać tekst wewnątrz elementów, mamy metodę znaków. Aby to zrobić, musimy utworzyć nową klasę obsługi z ulepszoną logiką. Nie zapominaj, że procedury obsługi to pełnoprawne klasy zdolne do przechowywania logiki o dowolnej złożoności. Dlatego teraz dostroimy nasz procesor. Tak naprawdę wystarczy zauważyć, że zawsze działamy namena jobzmianę i nie ma znaczenia w jakiej kolejności, możemy łatwo zapisać imię i zawód w osobnych zmiennych, a po zapisaniu obu zmiennych stworzyć naszego pracownika. Tylko tutaj wraz z początkiem elementu nie mamy parametru dla tekstu wewnątrz elementu. Musimy użyć metod na tekście. Ale jak uzyskać informacje tekstowe wewnątrz elementu, jeśli są to zupełnie różne metody? Moje rozwiązanie: wystarczy zapamiętać nazwę ostatniego elementu i characterssprawdzić, w którym elemencie czytamy informację. Trzeba też pamiętać, że <codee>znaki czytają wszystkie znaki wewnątrz elementów, co oznacza, że ​​czytane będą wszystkie spacje, a nawet znaki końca linii. I nie potrzebujemy ich. Musimy zignorować te dane, ponieważ są nieprawidłowe.</codee> Kod:
public class SAXExample {
    private static ArrayList<Employee> employees = new ArrayList<>();

    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser parser = factory.newSAXParser();

        AdvancedXMLHandler handler = new AdvancedXMLHandler();
        parser.parse(new File("resource/xml_file2.xml"), handler);

        for (Employee employee : employees)
            System.out.println(String.format("Nazwa сотрудника: %s, его должность: %s", employee.getName(), employee.getJob()));
    }

    private static class AdvancedXMLHandler extends DefaultHandler {
        private String name, job, lastElementName;

        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            lastElementName = qName;
        }

        @Override
        public void characters(char[] ch, int start, int length) throws SAXException {
            String information = new String(ch, start, length);

            information = information.replace("\n", "").trim();

            if (!information.isEmpty()) {
                if (lastElementName.equals("name"))
                    name = information;
                if (lastElementName.equals("job"))
                    job = information;
            }
        }

        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
            if ( (name != null && !name.isEmpty()) && (job != null && !job.isEmpty()) ) {
                employees.add(new Employee(name, job));
                name = null;
                job = null;
            }
        }
    }
}
Jak widać, przez banalną komplikację struktury pliku XML, nasz kod stał się znacznie bardziej skomplikowany. Jednak kod nie jest skomplikowany. Opis: stworzyliśmy zmienne przechowujące dane o pracowniku ( name, job) oraz zmienną lastElementNamerejestrującą w jakim elemencie się znajdujemy. Następnie w metodzie charactersfiltrujemy informacje i jeśli nadal są tam informacje, to znaczy, że jest to tekst, którego potrzebujemy, a następnie ustalamy, czy jest to imię, czy zawód, za pomocą lastElementName. W metodzie endElementsprawdzamy czy wszystkie informacje zostały odczytane i jeśli tak to tworzymy pracownika i resetujemy informacje. Wynik rozwiązania jest równoważny pierwszemu przykładowi:
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
Zatem ten problem został rozwiązany , ale można zauważyć, że złożoność jest większa. Można zatem stwierdzić, że przechowywanie informacji tekstowych w atrybutach będzie najczęściej bardziej poprawne niż w poszczególnych elementach. I jeszcze jedno słodkie zadanie, które częściowo rozwiąże problem w JavaRush z wyświetlaniem informacji o elemencie w HTML, tylko trzeba będzie to trochę edytować, tutaj po prostu wylistujemy wszystkie elementy wewnątrz elementu :) Zadanie nr 3 - biorąc pod uwagę element elementu, wyświetl nazwy i atrybuty wszystkich elementów wewnętrznych; jeśli element nie zostanie znaleziony, wyświetl to. Do tego zadania użyjemy 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>
Jak widać, mamy tutaj trzy możliwe scenariusze: root, mysql, oracle. Następnie program wyświetli wszystkie informacje o wszystkich elementach znajdujących się w środku. Jak możemy to zrobić? A to całkiem proste: wystarczy zadeklarować zmienną logiczną isEntered, która wskaże, czy potrzebujemy elementu w środku, a jeśli jest w środku, odczytać wszystkie dane z startElement. Kod rozwiązania:
public class SAXExample {
    private static boolean isFound;

    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser parser = factory.newSAXParser();

        SearchingXMLHandler handler = new SearchingXMLHandler("root");
        parser.parse(new File("resource/xml_file3.xml"), handler);

        if (!isFound)
            System.out.println("Элемент не был найден.");
    }

    private static class SearchingXMLHandler extends DefaultHandler {
        private String element;
        private boolean isEntered;

        public SearchingXMLHandler(String element) {
            this.element = element;
        }

        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            if (isEntered) {
                System.out.println(String.format("Найден элемент <%s>, его атрибуты:", qName));

                int length = attributes.getLength();
                for(int i = 0; i < length; i++)
                    System.out.println(String.format("Nazwa атрибута: %s, его oznaczający: %s", attributes.getQName(i), attributes.getValue(i)));
            }

            if (qName.equals(element)) {
                isEntered = true;
                isFound = true;
            }
        }

        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
            if (qName.equals(element))
                isEntered = false;
        }
    }
}
W tym kodzie wpisując element, o którym potrzebujemy informacji, ustawiamy flagę isEnteredna wartość true, co oznacza, że ​​jesteśmy wewnątrz elementu. A gdy tylko znajdziemy się wewnątrz elementu, po prostu przetwarzamy każdy nowy element startElement, wiedząc, że jest to dokładnie wewnętrzny element naszego elementu. Wypisujemy więc nazwę elementu i jego tytuł. Jeżeli elementu nie odnaleziono w pliku to mamy zmienną isFound, która jest ustawiana w momencie odnalezienia elementu, a jeżeli ma wartość false, wyświetli się komunikat, że elementu nie odnaleziono. I jak widać, w przykładzie SearchingXMLHandlerprzekazaliśmy rootelement konstruktorowi. Wniosek dla niego:
Найден элемент <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
W ten sposób otrzymaliśmy wszelkie informacje o elementach wewnętrznych i ich atrybutach. Problem jest rozwiązany. <h2>Epilog</h2>Widziałeś, że SAX jest całkiem ciekawym narzędziem i dość skutecznym, że można go używać na różne sposoby, do różnych celów itd., wystarczy tylko spojrzeć na problem od prawej strony strony, jak pokazano w zadaniu nr 2 i nr 3, gdzie SAX nie podał bezpośrednich metod rozwiązania problemu, ale dzięki naszej pomysłowości udało nam się znaleźć wyjście z sytuacji. Dalsza część artykułu będzie w całości poświęcona DOMowi. Mam nadzieję, że podobało Ci się poznanie SAX. Eksperymentuj, ćwicz, a zrozumiesz, że wszystko jest całkiem proste. I to wszystko, życzę powodzenia w programowaniu i z niecierpliwością czekam na część o DOM-ie. Powodzenia na studiach :) Poprzedni artykuł: [Konkurs] Podstawy XML dla programisty Java - część 2 z 3 Następny artykuł: [Konkurs] Podstawy XML dla programisty Java - część 3.2 z 3 - DOM
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION