Czym są pliki XML?
XML (eXtensive Markup Language) czyli rozszerzalny język znaczników, to uniwersalny język formalny przeznaczony do reprezentowania różnych danych z strukturalizowany sposób. XML jest niezależny od platformy. XML-e umożliwiają tworzenie hierarchicznych danych w formie przejrzystej zarówno dla komputera jak i dla człowieka.
Przykładowy plik XML wygląda następująco:
<!--?xml version="1.0" encoding="UTF-8"--> <ksiazka-telefoniczna kategoria="bohaterowie książek"> <!-- komentarz --> <osoba charakter="dobry"> <imie>Ambroży</imie> <nazwisko>Kleks</nazwisko> <telefon>123-456-789</telefon> </osoba> <osoba charakter="zły"> <imie>Alojzy</imie> <nazwisko>Bąbel</nazwisko> <telefon> </telefon></osoba><telefon> </telefon></ksiazka-telefoniczna><telefon> </telefon> <!--?xml-->
Dokument zaczyna się instrukcją sterującą, która zamienia informacje o wersji XML, oraz kodowaniu. Następnie tworzony jest korzeń dokumentu książka-telefoniczna ma on atrybut kategoria i wartości bohaterowie książek. Korzeń jest rodzicem dwóch innych elementów oba mają nazwę osoba i atrybut charakter.
Tutaj dwie ważne uwagi odnośnie plików XML, otóż nie zawsze ROOT instaluje się z wsparciem dla XMLa (trzeba uważać przy ustawianiu flag instalacji), druga root-config nie zwraca linkowania dla bibliotek xml dlatego często w makefile'u trzeba dopisywać -lxml2 -lXMLParser jako linkowane biblioteki.
Pierwsze kroki w XML w ROOT
Aby poruszać się po XML-u najpierw trzeba utworzyć instancję klasy TXMLEngine która inicjalizuje obsługę XMLa, następnie tworzy się klasy XMLDocPointer, która już służy do odczytu, bądź zapisu konkretnego pliku. Aby dobrać się do korzenia wystarczy użyć metody DocGetRootElement która zwraca XMLNodePointer do korzenia.
Poniżej kod tworzący plik XML-owy (z tutorialu ROOT'a)
// Example to read and parse any xml file, supported by TXMLEngine class // The input file, produced by xmlnewfile.C macro is used // If you need full xml syntax support, use TXMLParser instead //Author: Sergey Linev #include "TXMLEngine.h" void DisplayNode(TXMLEngine* xml, XMLNodePointer_t node, Int_t level); void xmlreadfile(const char* filename = "example.xml") { // First create engine TXMLEngine* xml = new TXMLEngine; // Now try to parse xml file // Only file with restricted xml syntax are supported XMLDocPointer_t xmldoc = xml->ParseFile(filename); if (xmldoc==0) { delete xml; return; } // take access to main node XMLNodePointer_t mainnode = xml->DocGetRootElement(xmldoc); // display recursively all nodes and subnodes DisplayNode(xml, mainnode, 1); // Release memory before exit xml->FreeDoc(xmldoc); delete xml; } void DisplayNode(TXMLEngine* xml, XMLNodePointer_t node, Int_t level) { // this function display all accessible information about xml node and its childs printf("%*c node: %s\n",level,' ', xml->GetNodeName(node)); // display namespace XMLNsPointer_t ns = xml->GetNS(node); if (ns!=0) printf("%*c namespace: %s refer: %s\n",level+2,' ', xml->GetNSName(ns), xml->GetNSReference(ns)); // display attributes XMLAttrPointer_t attr = xml->GetFirstAttr(node); while (attr!=0) { printf("%*c attr: %s value: %s\n",level+2,' ', xml->GetAttrName(attr), xml->GetAttrValue(attr)); attr = xml->GetNextAttr(attr); } // display content (if exists) const char* content = xml->GetNodeContent(node); if (content!=0) printf("%*c cont: %s\n",level+2,' ', content); // display all child nodes XMLNodePointer_t child = xml->GetChild(node); while (child!=0) { DisplayNode(xml, child, level+2); child = xml->GetNext(child); } }
Oraz kod czytający plik XML-owy (również z tutorialu ROOT'a)
// Example to read and parse any xml file, supported by TXMLEngine class // The input file, produced by xmlnewfile.C macro is used // If you need full xml syntax support, use TXMLParser instead //Author: Sergey Linev #include "TXMLEngine.h" void DisplayNode(TXMLEngine* xml, XMLNodePointer_t node, Int_t level); void xmlreadfile(const char* filename = "example.xml") { // First create engine TXMLEngine* xml = new TXMLEngine; // Now try to parse xml file // Only file with restricted xml syntax are supported XMLDocPointer_t xmldoc = xml->ParseFile(filename); if (xmldoc==0) { delete xml; return; } // take access to main node XMLNodePointer_t mainnode = xml->DocGetRootElement(xmldoc); // display recursively all nodes and subnodes DisplayNode(xml, mainnode, 1); // Release memory before exit xml->FreeDoc(xmldoc); delete xml; } void DisplayNode(TXMLEngine* xml, XMLNodePointer_t node, Int_t level) { // this function display all accessible information about xml node and its childs printf("%*c node: %s\n",level,' ', xml->GetNodeName(node)); // display namespace XMLNsPointer_t ns = xml->GetNS(node); if (ns!=0) printf("%*c namespace: %s refer: %s\n",level+2,' ', xml->GetNSName(ns), xml->GetNSReference(ns)); // display attributes XMLAttrPointer_t attr = xml->GetFirstAttr(node); while (attr!=0) { printf("%*c attr: %s value: %s\n",level+2,' ', xml->GetAttrName(attr), xml->GetAttrValue(attr)); attr = xml->GetNextAttr(attr); } // display content (if exists) const char* content = xml->GetNodeContent(node); if (content!=0) printf("%*c cont: %s\n",level+2,' ', content); // display all child nodes XMLNodePointer_t child = xml->GetChild(node); while (child!=0) { DisplayNode(xml, child, level+2); child = xml->GetNext(child); } }
Kilka uwag praktycznych
Niestety obecnie poruszanie się po XML-ach przy użyciu tych surowych klas jest ciężkie, gdyż przy nawigacji możemy się poruszać poprzez metody GetChild, GetParent, GetNextNode. Co więcej nie możemy sprawdzić ile jest pod-elementów w danym elemencie. Oznacza to że za każdym razem jeśli chce się sprawdzić ich ilość trzeba skakać po całej liście. Załóżmy jednak że chcemy wyświetlić drzewo genealogiczne jakieś rodziny, plik XML opisujący tę rodzinę wygląda następująco:
<!--?xml version="1.0" encoding="UTF-8"--> <ksiazka-telefoniczna kategoria="bohaterowie książek"> <!-- komentarz --> <osoba charakter="dobry"> <imie>Ambroży</imie> <nazwisko>Kleks</nazwisko> <telefon>123-456-789</telefon> </osoba> <osoba charakter="zły"> <imie>Alojzy</imie> <nazwisko>Bąbel</nazwisko> <telefon> </telefon></osoba><telefon> </telefon></ksiazka-telefoniczna><telefon> </telefon> <!--?xml-->
Teraz natomiast pokażę jak wygląda odczyt takiego drzewa:
#include "TXMLEngine.h" TXMLNode *GetNextNode(TXMLNode* in){ if(in->HasNextNode()){ in=in->GetNextNode(); } if(in->HasNextNode()){ in=in->GetNextNode(); } return in; } TString GetAtribute(TXMLNode *node){ TList* attrList = node->GetAttributes(); TXMLAttr *attr; attr =(TXMLAttr*)attrList->First(); return attr->GetValue(); } TXMLNode *GetChildren(TXMLNode *parent){ TXMLNode *child = parent->GetChildren(); return child->GetNextNode(); } void FindLara(){ TDOMParser* xml = new TDOMParser; xml->SetValidate(false); xml->ParseFile("babka.xml"); //znajduję babkę TXMLNode *mainnode = xml->GetXMLDocument()->GetRootNode(); //znajduję matkę czyli dziecko babki TXMLNode *matki = GetChildren(mainnode); cout<<GetAtribute(mainnode)<<endl; while(matki->HasNextNode()){ cout<<" "<<GetAtribute(matki)<<endl; corki=GetChildren(matki); while(corki->HasNextNode()){ cout<<" "<<GetAtribute(corki)<<endl; corki=GetNextNode(corki); } matki = GetNextNode(matki); } }
Tutaj mała uwaga odnośnie TDOMParser - jest one nieco lepszy niż te stosowane w dwóch pierwszych przykładach, gdyż umożliwia zapis i odczyt obiektów w XML. Jednak XML w ROOT ma sporo wad przez co trzeba uważać na kod.
- Metoda GetNextNode zwraca tak naprawdę nie następny element ale kolejno "spacje" "element" "spacje" element, dlatego stworzyłem własną metodę GetNextNode która tak naprawdę wykorzystuje "oryginalne" GetNextNode 2 razy aby ominąć problem spacji (a właściwie pustego noda który chętnie nam "zsegfauci" makro)
- Metoda GetChildren też zwraca najpierw pusty element, toteż zamiast użyć GetChildren trzeba użyć GetChilder i potem GetNextNode (patrz linia 19 i 20)
- GetAttribute zwraca całą listę, i niestety trzeba ją potem odpowiednio zrzutować do XMLAtrr, ale tutaj na szczęście przy pomocy GetEntries da się znaleźć liczbę atrybutów i nie ma problemu z pustymi atrybutami
Zapis histogramów
Co ciekawe ROOT'owe obiekty można bardzo łatwo zapisywać i odczytywać w XML, co umożliwia ich używanie nawet bez środowiska ROOT. poniżej przykładowy kod tworzący histogram:
void write(){ TFile *f = TFile::Open("example.xml","recreate"); TH1F *h = new TH1F("histo","histogram",1000,-2.5,2.5); h->SetLineColor(kRed); h->FillRandom("gaus"); h->Write(); delete f; }
Oraz kod odczytujący, warto zauważyć że histogram zapisany w XML zawieranie tylko dane histogramu ale także jego atrybuty np. kolor linii.
void read(){ TFile *f = TFile::Open("example.xml"); TH1F *h = (TH1F*)f->Get("histo"); h->Draw(); }