%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% %% :DOM %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{DOM} DOM (Document Object Model) je nástroj pro čtení a~zpracování, ale i~pro vytváření XML dokumentů. XML si lze představit jako strom a~přesně takto ho také DOM modeluje. Kořen pomyslného XML stromu představuje kořenový XML element. Uzly ve stromu reprezentují jednotlivé XML elementy. Uzel \emph{B} je ve stromu potomkem uzlu \emph{A} právě když XML element \emph{B} je dceřiným elementem elementu \emph{A}. Modelování XML dokumentu jako stromu ukazuje obrázek \ref{DOM Tree 1} \begin{figure}[ht] \centerline{\includegraphics[height=40mm]{images/dom_tree1.png}} \caption{XML dokument jako strom.} \label{DOM Tree 1} \end{figure} Aby byl návrh tříd DOMu jednodušší, udělali jeho autoři jednu úpravu. Kořenovému uzlu reprezentujícímu kořenový XML element předsadili uzel reprezentující celý dokument. Přitom tento nový kořen smí mít pouze jednoho potomka. Obrázek \ref{DOM Tree 2} ukazuje finální podobu stromu. \begin{figure}[ht] \centerline{\includegraphics[height=60mm]{images/dom_tree2.png}} \caption{XML dokument modelován pomocí DOMu.} \label{DOM Tree 2} \end{figure} Nástroj DOM je pouze obecný návrh od W3C, který má svou konkrétní podobu v~mnoha programovacích jazycích. V~této kapitole si představíme jeho implementaci v~jazyce Java. \subsection{Document} Celý XML dokument je v~DOMu reprezentovaný objektem typu \texttt{Document} z~balíčku \texttt{org.w3c.dom}. K~vytvoření jeho instance potřebujeme nejdříve získat instanci tovární třídy \texttt{DocumentBuilderFactory} a~z~ní následně získat objekt typu \texttt{DocumentBuilder} (oba dva z~balíčku \texttt{javax.xml.parsers}) zavoláním metody \texttt{newDocumentBuilder}. Z~výrobního objektu pak již získáme potřebný dokument zavoláním metody \texttt{newDocument}. Tento postup se může zdát poněkud komplikovaný, jedná se však o~implementaci návrhového vzoru Stavitel (Builder, popsán v~\cite{Navrhove vzory}). Následující kód ukazuje, jak získat instanci typu \texttt{Document}. \begin{Verbatim} try \{ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.newDocument(); \} catch (ParserConfigurationException ex) \{ System.err.println(ex.getMessage()); ex.printStackTrace(); \} \end{Verbatim} \subsection{Element} Uzel v~DOMu se jmenuje \texttt{Element} a~představuje XML element z~XML dokumentu. Nachází se v~balíčku \texttt{org.w3c.dom}. Instanci tohoto uzlu vytvoříme zavoláním tovární metody \texttt{createElement} na objektu typu \texttt{Document}. Jako parametr metodě \texttt{createElement} zadáme název XML elementu. Bohužel v~DOMu neexistuje přetížená verze této metody a~tak pro vytvoření uzlu, který náleží do nějakého jmenného prostoru potřebujeme zavolat metodu \texttt{createElementNS}, která bere jako parametry jmenný prostor a~jméno XML elementu. Následující příklad ukazuje, jak vytvořit uzel reprezentující zvíře z~aplikace Animalia. \begin{Verbatim} try \{ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.newDocument(); \codeHighlight {Element animal = document.createElement("animal");} \} catch (ParserConfigurationException ex) \{ System.err.println(ex.getMessage()); ex.printStackTrace(); \} \end{Verbatim} \subsection{Zadání hodnoty elementu} Hodnotu elementu vyplníme pomocí metody \texttt{setTextContent} objektu typu \texttt{Element}. Jako parametr bere tato metoda řetězec reprezentující hodnotou XML elementu. V~DOMu neexistují přetížené verze pro čísla nebo logické hodnoty, takovéto typy je proto třeba vždy ručně převést na řetězec statickou metodou \texttt{valueOf} ze třídy \texttt{String}. Příklad rozšíříme o~další dva uzly reprezentující rod (genus) a~druh (species) zvířete. Zároveň vyplníme jejich hodnoty na lišku obecnou (\emph{Vulpes vulpes}). \begin{Verbatim} try \{ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.newDocument(); Element animal = document.createElement("animal"); \codeHighlight {Element genus = document.createElement("genus");} \codeHighlight {genus.setTextContent("Vulpes");} \codeHighlight {Element species = document.createElement("species");} \codeHighlight {species.setTextContent("vulpes");} \} catch (ParserConfigurationException ex) \{ System.err.println(ex.getMessage()); ex.printStackTrace(); \} \end{Verbatim} \subsection{Napojení uzlů} Jednotlivé uzly je možné napojit pomocí metody \texttt{appendChild} na objektu typu \texttt{Document}, nebo \texttt{Element} (oba implementují stejné rozhraní, které tuto metodu definuje). Metoda bere jako parametr objekt typu \texttt{Element} a~napojí ho jakožto potomka uzlu, na kterém jsme metodu zavolali. Příklad ukazuje, jak uzly \texttt{genus} a~\texttt{species} napojit jako děti uzlu \texttt{animal} a~jak uzel \texttt{animal} napojit jako kořenový uzel dokumentu. \begin{Verbatim} try \{ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.newDocument(); Element animal = document.createElement("animal"); \codeHighlight{document.appendChild(animal);} Element genus = document.createElement("genus"); genus.setTextContent("Vulpes"); \codeHighlight{animal.appendChild(genus);} Element species = document.createElement("species"); species.setTextContent("vulpes"); \codeHighlight{animal.appendChild(species);} \} catch (ParserConfigurationException ex) \{ System.err.println(ex.getMessage()); ex.printStackTrace(); \} \end{Verbatim} Pokud vás napadlo, co se stane pokud se pokusíme napojit na uzel dokumentu více uzlů a~tím porušit pravidlo, že XML dokument má právě jeden kořenový uzel, pak vězte že dojde k~vyvolání běhové výjimky \texttt{DOMException}. Stejné by to bylo i~v~případě, kdybychom se snažili udělat z~uzlů cyklus. Pokud to ale nehrozí, není potřeba výjimku \texttt{DOMException} zachytávat. \fcolorbox{boxout}{boxin}{ \parbox{0.98\linewidth}{ Ve skutečnosti bere metoda \texttt{appendChild} jako parametr objekt typu \texttt{Node}. Jedná se o~rozhraní, které v~DOMu implementují dvě nám známé třídy --- \texttt{Element} a~\texttt{Document}. Pokud byste se ale snažili jako potomka nějakého uzlu napojit instanci typu \texttt{Document}, dojde k~vyvolání běhové výjimky \texttt{DOMException}. }} \subsection{Transformace do XML} Jakmile máme sestavený celý strom, zbývá ho přetransformovat do XML. Postup, který nabízí jazyk Java je poměrně komplikovaný. V~naší verzi si ukážeme, jak výsledné XML zapsat do řetězcové proměnné, pro proudy je řešení obdobné pouze z~drobnou změnou. Nejdříve je potřeba získat instanci třídy \texttt{Transformer}. Jelikož se jedná o~obecný transformátor, je třeba zde nastavit vlastnosti, že výstupem má být XML dokument v~kódování UTF-8. Dále je potřeba zavolat na transformátoru metodu \texttt{transform}. Celý postup se obtížně vysvětluje, z~příkladu je ale pochopitelný. Výsledný XML dokument je zde uchován v~proměnné \texttt{xmlResult} \begin{Verbatim} try \{ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.newDocument(); Element animal = document.createElement("animal"); document.appendChild(animal); Element genus = document.createElement("genus"); genus.setTextContent("Vulpes"); animal.appendChild(genus); Element species = document.createElement("species"); species.setTextContent("vulpes"); animal.appendChild(species); \codeHighlight{Transformer transformer = TransformerFactory.newInstance().newTransformer();} \codeHighlight{transformer.setOutputProperty(OutputKeys.METHOD, "xml");} \codeHighlight{transformer.setOutputProperty(OutputKeys.ENCODING, "utf-8");} \codeHighlight{transformer.setOutputProperty(OutputKeys.INDENT, "yes");} \codeHighlight{StringWriter writer = new StringWriter();} \codeHighlight{StreamResult result = new StreamResult(writer);} \codeHighlight{DOMSource source = new DOMSource(document);} \codeHighlight{transformer.transform(source, result);} \codeHighlight{String xmlResult = writer.toString();} \} catch (ParserConfigurationException ex) \{ System.err.println(ex.getMessage()); ex.printStackTrace(); \} \codeHighlight{catch (TransformerConfigurationException ex) \{} \codeHighlight{System.err.println(ex.getMessage());} \codeHighlight{ex.printStackTrace();} \codeHighlight{\}} \codeHighlight{catch (TransformerException ex) \{} \codeHighlight{System.err.println(ex.getMessage());} \codeHighlight{ex.printStackTrace();} \codeHighlight{\}} \end{Verbatim} \subsection{Zpracování XML dokumentu} DOM umožňuje také XML dokument zpracovat zavoláním metody \texttt{parse} na instanci třídy \texttt{DocumentBuilder}. V~tom případě DOM celý XML dokument načte a~vymodeluje jako strom. Následnou stromovou strukturu pak vrátí. Příklad ukazuje, jak pomocí DOMu načíst XML dokument \emph{liska\_obecna.xml}: \begin{Verbatim} DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.parse(new File("liska\_obecna.xml")); \end{Verbatim} V~proměnné \texttt{document} je teď uložen kompletně sestavený strom. Na něm se lze pohybovat intuitivním způsobem. Vždy máme k~dispozici nějaký uzel (ze začátku uzel dokumentu), na kterém můžeme zavolat metodu \texttt{getChildNodes}. Zde se projevuje fakt, že DOM je obecný návrh. Metoda nevrací klasikou kolekci nebo pole v~jazyce Java, nýbrž vlastní objekt typu \texttt{NodeList}. Tento objekt navíc nemá vestavěný iterátor, musíme se proto po něm pohybovat cyklem a~voláním metod \texttt{getLength} a~\texttt{item}. Druhá zmíněná metoda bere jako parametr index prvku ze seznamu a~vrací tento prvek. Bohužel návratová hodnota je typu \texttt{Node} (vzpomeňte si, že uzel může být element ale i~dokument) a~je nutné ji přetypovat. Příklad ukazuje jak to udělat: \begin{Verbatim} DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.parse(new File("liska\_obecna.xml")); \codeHighlight{NodeList list = document.getChildNodes();} \codeHighlight{for (int i = 0; i < list.getLength(); i++) \{} \codeHighlight{ Node node = list.item(i);} \codeHighlight{ if (node instanceof Element ) \{} \codeHighlight{ Element element = (Element) node;} \codeHighlight{ \}} \codeHighlight{\}} \end{Verbatim} Uvnitř cyklu máme odkaz na uzel v~proměnné \texttt{element}. V~tuto chvíli můžeme zjistit jeho jméno, hodnotu nebo seznam atributů. Pokud ale zpracováváme XML dokument, většinou víme jakou má strukturu (jinak by nebylo možné s~ním rozumně pracovat). Pohybování se po obsáhlejším stromu pomocí několikanásobně zanořených cyklů je ovšem značně nepraktické. V~úvahu proto připadá lepší řešení pomocí výrazů XPath. \subsection{XPath} XPath je technologie pro parametrizované vyhledávání v~XML stromu. Umožňuje nám vyhledávat XML elementy na základě jejich jména, hodnoty, názvu atributů nebo hodnot atributů. Syntaxe jazyka XPath není komplikovaná, nabízí však velmi bohaté možnosti parametrizace. Celý tento jazyk je dobře popsán v~\cite{XML v kostce}. V~následujícím příkladu si uvedeme vyhledání uzlu s~názvem \texttt{animal}. \begin{Verbatim} DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.parse(new File("liska\_obecna.xml")); \codeHighlight{try \{} \codeHighlight{ XPath xpath = XPathFactory.newInstance().newXPath();} \codeHighlight{ Element result = (Element) xpath.evaluate(".//animal", } \codeHighlight{ document, XPathConstants.NODE);} \codeHighlight{\}} \codeHighlight{catch (XPathExpressionException ex) \{} \codeHighlight{ System.err.println(ex.getMessage());} \codeHighlight{ ex.printStackTrace();} \codeHighlight{\}} \end{Verbatim} Třídy \texttt{XPath}, \texttt{XPathFactory}, \texttt{XPathConstants} a~\texttt{XPathExpressionException} jsou všechny z~balíčku \texttt{javax.xml.xpath}. Jako první parametr metody \texttt{evaluate} zadáváme výraz v~jazyce XPath. Druhým parametrem je uzel od kterého vyhledávání začínáme (připomeňme si, že v~DOMu je kořenovým uzlem dokument). Jako třetí parametr musíme zadat, jaký způsob hledání vlastně chceme realizovat. Jak již bylo zmíněno, možnosti XPath jsou velmi bohaté. V~našel příkladě jsme hledali jeden samostatný uzel. Pokud výraz XPath nelze vyhodnotit, dojde k~vyvolání výjimky \texttt{XPathExpressionException}. Bohužel metoda \texttt{evaluate} neobsahuje šablonu a~vrací tedy vždy objekt typu \texttt{Object}, který je potřeba následně přetypovat. \subsection{Srovnání nástrojů DOM a~SAX} DOM a~SAX jsou oba dva určené pro jak pro zpracování XML dokumentu, tak pro jeho vytvoření. Zajímavé je, že oba dva nástroje používají stejný přístup a~to že mají stejné rozhraní pro čtení jako pro zpracování. Zatímco u~SAXu toto představuje při tvorbě XML dokumentu značný handicap, u~DOMu se jedná o~příjemnou vlastnost. Nevýhodou DOMu však je, že při čtení je celý dokument nejdříve natažen do paměti. To DOM diskvalifikuje u~některých problémů, například když potřebujeme hledat ve slovníku uloženém v~XML souboru. Takovýto slovník má řádově deseti tisíce záznamů a~soubor by zabíral přes 15 MB. Celé jeho načtení do paměti by bylo zdlouhavé a~navíc by značně ohrozilo výkonnost aplikace. Pro řešení takového problému je značně lepší nástroj SAX, který pracuje velmi rychle a~k~paměti šetrně. Závěrem jde říct, že DOM se hodí lépe pro tvorbu XML dokumentu. SAX je zase většinou lepší pro čtení a~následné zpracování. \subsection{Závěr} DOM představuje pohodlný nástroj pro vytváření XML dokumentu. Jeho rozhraní je intuitivní a~vytvoření XML dokumentu požaduje téměř minimální možný počet příkazů. U~zpracování XML dokumentu ovšem narážíme na těžkopádné rozhraní které navíc nedodržuje standardy jazyka Java. Lepší je použít výrazy XPath pro parametrizované vyhledávání. Ty jsou velkou výhodou, pokud potřebujeme v~XML dokumentu najít nějaký konkrétní element (většinou vyhledáváme podle jména elementu). Příkladem může být konfigurační soubor \texttt{web.xml}, kde v~danou chvíli můžeme potřebovat najít například název nasazeného servletu. XPath nám to umožní realizovat velmi efektivně. Použitá literatura:\\ \cite{XML v kostce} \emph{XML v kostce}\\ \cite{XML Reference} \emph{XML -- pohotová referenční příručka}\\ \cite{DOM Wikipedia} \emph{DOM na Wikipedii}\\