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();
}