Techniki parsowania XML

11

Zawsze uważałem, że XML jest nieco kłopotliwy w przetwarzaniu. Nie mówię o implementacji analizatora składni XML: mówię o używaniu istniejącego analizatora opartego na strumieniu, takiego jak analizator składni SAX, który przetwarza węzeł XML według węzła.

Tak, naprawdę łatwo jest nauczyć się różnych interfejsów API dla tych parserów, ale kiedy patrzę na kod przetwarzający XML, zawsze uważam, że jest nieco skomplikowany. Zasadniczym problemem wydaje się być to, że dokument XML jest logicznie podzielony na poszczególne węzły, a jednak typy danych i atrybuty są często oddzielone od rzeczywistych danych, czasami przez wiele poziomów zagnieżdżenia. Dlatego podczas indywidualnego przetwarzania dowolnego węzła należy zachować wiele dodatkowych stanów, aby określić, gdzie jesteśmy i co musimy zrobić dalej.

Na przykład biorąc fragment kodu z typowego dokumentu XML:

<book>
  <title>Blah blah</title>
  <author>Blah blah</author>
  <price>15 USD</price>
</book>

... Jak określić, kiedy napotkałem węzeł tekstowy zawierający tytuł książki? Załóżmy, że mamy prosty parser XML, który działa jak iterator, dając nam następny węzeł w dokumencie XML za każdym razem, gdy wywołujemy XMLParser.getNextNode(). Nieuchronnie piszę następujący kod:

boolean insideBookNode = false;
boolean insideTitleNode = false;

while (!XMLParser.finished())
{
    ....
    XMLNode n = XMLParser.getNextNode();

    if (n.type() == XMLTextNode)
    {
        if (insideBookNode && insideTitleNode)
        {
            // We have a book title, so do something with it
        }
    }
    else
    {
        if (n.type() == XMLStartTag)
        {
            if (n.name().equals("book")) insideBookNode = true
            else if (n.name().equals("title")) insideTitleNode = true;
        }
        else if (n.type() == XMLEndTag)
        {
            if (n.name().equals("book")) insideBookNode = false;
            else if (n.name().equals("title")) insideTitleNode = false;
        }
    }
}

Zasadniczo przetwarzanie XML szybko zmienia się w ogromną pętlę sterowaną maszyną stanu, z dużą ilością zmiennych stanu używanych do wskazywania węzłów nadrzędnych, które znaleźliśmy wcześniej. W przeciwnym razie należy zachować obiekt stosu, aby śledzić wszystkie zagnieżdżone znaczniki. Szybko staje się to podatne na błędy i trudne do utrzymania.

Ponownie wydaje się, że problemem jest to, że dane, którymi jesteśmy zainteresowani, nie są bezpośrednio powiązane z pojedynczym węzłem. Jasne, może tak być, gdybyśmy napisali XML w następujący sposób:

<book title="Blah blah" author="blah blah" price="15 USD" />

... ale tak rzadko używa się XML w rzeczywistości. Przeważnie mamy węzły tekstowe jako dzieci węzłów nadrzędnych i musimy śledzić węzły nadrzędne, aby ustalić, do czego odnosi się węzeł tekstowy.

Więc ... robię coś złego? Czy jest lepszy sposób? W którym momencie użycie parsera opartego na strumieniu XML staje się zbyt skomplikowane, więc niezbędny jest w pełni parser DOM? Chciałbym usłyszeć od innych programistów, jakiego rodzaju idiomów używają podczas przetwarzania XML z analizatorami strumieniowymi. Czy parsowanie strumieniowe XML musi zawsze zamieniać się w wielką maszynę stanów?

Channel72
źródło
2
jeśli używasz języka .net, powinieneś spojrzeć na linq to xml alias XLinq.
Muad'Dib,
Dziękuję, myślałem, że tylko ja mam ten problem. Szczerze mówiąc, często uważam, że cały format XML jest bardziej przeszkodą niż pomocą. Tak, pozwala przechowywać wiele ustrukturyzowanych danych w małym pliku tekstowym. Ale jeśli potrzebujesz ponad 20 klas, aby rozpakować i zrozumieć sens tej rzeczy - bez gwarancji, że nie przeoczysz czegoś ważniejszego lub mniej ważnego. To jak króliczek w Świętym Graalu Monty Pythona.
Elise van Looij

Odpowiedzi:

9

Dla mnie pytanie jest odwrotne. W którym momencie dokument XML staje się tak niewygodny, że musisz zacząć używać SAX zamiast DOM?

Używałbym SAX tylko dla bardzo dużego strumienia danych o nieokreślonej wielkości; lub jeśli zachowanie, które ma wywoływać XML, jest w rzeczywistości oparte na zdarzeniach, a zatem podobne do SAX.

Podany przez Ciebie przykład wygląda dla mnie bardzo DOM.

  1. Załaduj XML
  2. Wyodrębnij węzły tytułowe i „zrób coś z nimi”.

EDYCJA: Używałbym również SAX dla strumieni, które mogą być zniekształcone, ale tam, gdzie chcę, najlepiej zgadnąć, jak wyciągnąć dane.

Paul Butcher
źródło
2
Myślę, że to dobra uwaga. Jeśli analizujesz dokumenty, które są zbyt duże dla DOM, musisz rozważyć, czy analizujesz dokumenty, które są zbyt duże dla XML
Dean Harding
1
+1: Biorąc pod uwagę opcję, zawsze wybrałbym DOM. Niestety wydaje się, że nasze wymagania projektowe zawsze obejmują „zdolność do obsługi dokumentów o dowolnym rozmiarze” i „muszą być wydajne”, co w zasadzie wyklucza rozwiązania oparte na DOM.
TMN
3
@TMN, w idealnym świecie te wymagania wykluczyłyby XML.
SK-logika
1
@TMN, to brzmi jak jeden z tych fantomowych wymagań: „Oczywiście wszystkie nasze dokumenty mają tylko około 100 KB, a największy, jaki widzieliśmy, to 1 MB, ale nigdy nie wiesz, co przyniesie przyszłość, więc powinniśmy mieć otwarte opcje i buduj dla nieskończenie dużych dokumentów ”
Paul Butcher,
@Paul Butcher, nigdy nie wiadomo. Mam na myśli, że zrzut Wikipedii jest jak 30 GB XML.
Channel72
7

Nie za bardzo pracuję z XML, moim zdaniem trochę, prawdopodobnie jednym z najlepszych sposobów parsowania XML za pomocą biblioteki jest używanie XPath.

Zamiast przeszukiwać drzewo w celu znalezienia określonego węzła, podajesz ścieżkę do niego. W przypadku twojego przykładu (w pseudokodzie) byłoby to coś takiego:

books = parent.xpath ("/ book") // To da ci wszystkie węzły książki
dla każdej książki w książkach
    title = book.xpath ("/ title / text ()")
    autor = book.xpath ("/ autor / text ()")
    price = book.xpath („/ price / text ()”)

    // Rób rzeczy z danymi

XPath jest znacznie potężniejszy, możesz wyszukiwać przy użyciu warunków (zarówno wartości, jak i atrybutów), wybierać określony węzeł na liście, przenosić poziomy przez drzewo. Polecam poszukać informacji o tym, jak go używać, jest zaimplementowany w wielu bibliotekach parsujących (używam go w wersji .Net Framework i lxml dla Pythona)

Ioachim
źródło
To dobrze, jeśli możesz z góry wiedzieć i ufać, w jaki sposób struktura xml jest zbudowana. Jeśli nie wiesz, czy, powiedzmy, szerokość elementu zostanie określona jako atrybut węzła, czy jako węzeł atrybutu wewnątrz węzła rozmiaru elementu, to XPath nie będzie zbyt pomocny.
Elise van Looij
5

Czy parsowanie strumieniowe XML musi zawsze zamieniać się w wielką maszynę stanów?

Zwykle tak jest.

Dla mnie wskazanie użycia pełnoprawnego analizatora składni DOM jest wtedy, gdy musiałbym naśladować niektóre części hierarchii plików w pamięci, na przykład, aby móc rozpoznać odsyłacze w dokumencie.

Alexander Gessler
źródło
+1: Zacznij od DOM. Unikaj SAX.
S.Lott,
lub z vtd-xml
vtd-xml-autor
4

Ogólnie parsowanie jest po prostu sterowaniem maszyną stanu, a parsowanie XML nie jest niczym innym. Parsowanie strumieniowe jest zawsze kłopotliwe, zawsze kończę budowanie jakiegoś stosu, aby śledzić węzły przodków, i definiowanie wielu zdarzeń oraz jakiegoś programu rozsyłającego zdarzenia, który sprawdza rejestr znaczników lub ścieżek i odpala zdarzenie jeśli jeden pasuje. Kod podstawowy jest dość ciasny, ale kończę z ogromną ilością procedur obsługi zdarzeń, które polegają głównie na przypisaniu wartości następnego węzła tekstowego do pola w jakiejś strukturze. Może stać się dość włochaty, jeśli trzeba tam również mieszać logikę biznesową.

Zawsze używałbym DOM, chyba że problemy z rozmiarem lub wydajnością podyktowałyby inaczej.

TMN
źródło
1

Nie do końca agnostyk językowy, ale zwykle deserializuję XML na obiekty, a nawet nie myślę o analizie. Jedyny czas, aby martwić się samemu analizowaniem strategii, to problem z prędkością.

Wyatt Barnett
źródło
To podlega analizie. Chyba że dany XML jest wynikiem serializacji obiektu i masz gotową bibliotekę deserializacji. Ale to pytanie się nie pojawia.
Wiele języków / stosów ma gotowe biblioteki deserializacji.
Wyatt Barnett,
Tak, a co z tego? Moje uwagi nadal się utrzymują - nie wszystkie dzikie pliki XML są w takim formacie, a jeśli masz taki, nie zadajesz tego pytania, ponieważ po prostu używasz tej biblioteki deserializacji i nie analizujesz niczego samodzielnie, ze strumieni lub w inny sposób.
0

Stanie się znacznie mniej uciążliwe, jeśli można używać XPath. A w .Net land LINQ to XML również streszcza wiele mniej efektownych rzeczy. ( Edycja - wymagają oczywiście podejścia DOM)

Zasadniczo, jeśli używasz podejścia opartego na strumieniu (więc nie możesz używać ładniejszych abstrakcji, które wymagają DOM), myślę, że zawsze będzie to dość kłopotliwe i nie jestem pewien, czy jest na to jakiś sposób.

Steve
źródło
Jeśli używasz XPath, korzystasz z DOM (chyba że używasz go z domowym ewaluatorem XPath).
TMN
tak, stąd mój komentarz na temat abstrakcji wymagających DOM ... ale wyjaśnię, dzięki!
Steve
0

Jeśli potrafisz znaleźć parser, który daje ci iterator, czy myślałeś o potraktowaniu go jak leksykon i użyciu generatora maszyny stanów?

Demi
źródło