Tutorial do Xerces-C++ 3 – cz. 1: Instalacja, konfiguracja i pierwszy projekt

Xerces-C++ to darmowa biblioteka do obsługi języka XML rozwijana przez Apache Software Foundation. Tak jak wszystkie produkty spod marki ASF prezentuje bardzo wysoki poziom i szeroki wachlarz funkcjonalności. Jednakże, co dziwne w przypadku takich produktów, ciężko o jakiś prosty tutorial dla początkujących.

Bibliotekę można ściągnąć z oficjalnej strony projektu pod adresem:
http://xerces.apache.org/xerces-c/download.cgi. Dla naszych potrzeb wystarczy wybrać najłatwiejszy wariant, czyli ściągnięcie już skompilowanej wersji umieszczonej pod Binary Distributions. W naszym tutorialu skorzystamy z wersji przeznaczonej na system Linux, ale na pozostałe systemy procedura jest bardzo podobna. Bezpośredni link do wersji, z której jakorzystałem to: http://apache.privatejetscharter.net//xerces/c/3/binaries/xerces-c-3.1.1-x86-linux-gcc-3.4.tar.gz.

Po ściągnięciu i rozpakowaniu otrzymamy kilka folderów, z których najważniejsze dla nas to:

  • include
  • lib

Teraz musimy tylko skonfigurować nasz kompilator i linker, aby wiedziały, gdzie mają szukać potrzebnych plików. Folder include musimy dodać do listy folderów, które przeszukuje kompilator w poszukiwaniu plków nagłówkowych, a folder lib dodajemy do listy folderów, które przeszukuje linker w poszukiwaniu bibliotek. Dodatkowo trzeba dopisać jeszcze parametr dla linkera, aby dołączył bibliotekę do naszego projektu – zazwyczaj to: -l xerces-c. Ostatnią rzeczą, którą musimy zrobić to skopiowanie dynamicznej biblioteki do folderu, gdzie będzie go mógł odnaleść nasz program. Najłatwiej będzie skopiować ją po prostu do folderu z naszą skompilowaną aplikacją. W naszym przypadku ową dynamiczną biblioteką będzie plik w folderze lib o nazwie libxerces-c-3.1.so, ale Windowsie będzie on miał rozszerzenie dll.

Teraz już możemy pisać nasz program. Będzie to bardzo prosty program, aczkolwiek wykorzystujący bardzo zaawansowane mechanizmy. Dokładnie rzecz biorąc, odczytamy z pliku trochę kodu XML, a następnie przejdziemy po wszystkich elementach drugiego poziomu (czyli potomkach elementu nadrzędnego) wypisując przy okazji nazwę i atrybuty tych elementów. Kod XML, którym się posłużymy jest poniżej.

<personnel>
  <person id="Big.Boss" >
    <name><family>Boss</family> <given>Big</given></name>
    <email>chief@foo.com</email>
    <link subordinates="one.worker two.worker three.worker four.worker five.worker"/>
  </person>

  <person id="one.worker">
    <name><family>Worker</family> <given>One</given></name>
    <email>one@foo.com</email>
    <link manager="Big.Boss"/>
  </person>

  <person id="two.worker">
    <name><family>Worker</family> <given>Two</given></name>
    <email>two@foo.com</email>
    <link manager="Big.Boss"/>
  </person>

  <person id="three.worker">
    <name><family>Worker</family> <given>Three</given></name>
    <email>three@foo.com</email>
    <link manager="Big.Boss"/>
  </person>

  <person id="four.worker">
    <name><family>Worker</family> <given>Four</given></name>
    <email>four@foo.com</email>
    <link manager="Big.Boss"/>
  </person>

  <person id="five.worker">
    <name><family>Worker</family> <given>Five</given></name>
    <email>five@foo.com</email>
    <link manager="Big.Boss"/>
  </person>
</personnel>

Jak widać to dość prosty plik XML. To co chcemu uzyskać to przejście po wszystkich elementach person i wypisanie ich nazwy oraz atrybutów. Zaczniemy od dołączenia plików nagłówkowych, z których będziemy korzystać.

#include <iostream>
using namespace std;
#include <xercesc/dom/DOM.hpp>
using namespace xercesc;

Narazie nic nadzwyczajnego. Musimy tylko pamiętać o wyborze odpowiednich przestrzeni nazw. Dalszy kod będziemy umieszczać w funkcji głównej o standardowej postaci:

int main() {
    //TODO:
}

Pierwsze co musimy zrobić to zainicjalizować naszą bibliotekę oraz stworzyć główny obiekt, który da nam dostęp do funkcjonalności związanych z DOM.

XMLPlatformUtils::Initialize();
DOMImplementation* dom_xml = DOMImplementationRegistry::getDOMImplementation(XMLString::transcode(""));

Pierwsza z powyższych funkcji to owa inicjalizacja. Druga to pozyskanie wskaźnika na nasz obiekt. W argumencie użyta jest funkcji transcode, która służy do zmiany zwykłego ciągu znaków na taki, który wykorzystuje kodowanie wewnętrze naszej biblioteki. W efekcie do funkcji przekazany jest łańcuch znaków mówiący bibliotece, jaki zestaw funkcjonalności dotyczących XML potrzebujemy i zwróci nam wskaźnik na obiekt, który takie funkcjonalności posiada. Jeżeli dostaniemy NULL, to znaczy, że biblioteka w obecnej wersji nie dysponuje takową funkcjonalnością. My potrzebujemy tylko podstawowe funkcje, więc przekazujemy pusty string. Idziemy dalej…

DOMLSParser* dom_file = dom_xml->createLSParser(DOMImplementationLS::MODE_SYNCHRONOUS, 0);
DOMDocument* dom_doc  = dom_file->parseURI("file.xml");
DOMElement*  dom_root = dom_doc->getDocumentElement();

Pierwsza linia kodu tworzy nam obiekt do odczytywania danych z pliku – tzw. parser. Druga linia to wywołanie metody parsera do odczytu danych z konkretnego pliku. Trzecia metoda to pozyskanie wskaźnika na główny obiekt w naszym pliku XML – czyli w naszym przypadku element personnel Dalszy kod umieścimy w pętli for, która będzie iterować po wszystkich bezpośrednich potomkach elementu głównego. Pętla ma postać, jak poniżej.

for (DOMNode* dom_child=dom_root->getFirstChild(); dom_child != 0; dom_child=dom_child->getNextSibling()) {
    //TODO:
}

W inicjalizacji przypisujemy do licznika wskaźnik na pierwszego potomka elementu głównego. Warunek to zwykłe porównanie z NULLem. Inkrementacja licznika to po prostu przypisanie mu wskaźnika na następnego z rodzeństwa elementu potomnego. W pętli umieścimy poniższy kod. Zaczniemy od sprawdzenia, czy pobrany element potomny jest węzłem, czy jakimś innym typem elementu.

if (dom_child->getNodeType() == DOMNode::ELEMENT_NODE) {
    //TODO::
}

Gdybyśmy nie zrobili powyższego testowania to zwracane nam byłyby niepotrzebne nam w ogóle elemnty tekstowe odpowiadające spacjom i znakom nowej linii. Cały dalszy kod umieścimy w powyższym warunku.

char *name = XMLString::transcode(dom_child->getNodeName());
cout << "Node name: " << name << endl;

Powyższy kod pobiera nazwę z pobranego elementu i ją wypisuje. Jak widać użyta jest tu znowu funkcja transcode, ale teraz wykorzystujemy jej działanie w drugą stronę - zmieniamy tekst z wewnętrzenego kodowania na zwykłe.

DOMNamedNodeMap *dom_attrs = dom_child->getAttributes();
int num= dom_attrs->getLength();
cout << "Attributes number: " << num << endl;

Tutaj pobraliśmy listę atrybutów węzła i wypisaliśmy ich ilość.

for(int i=0; i<num; i++) {
    //TODO:
}

Powyższa pętla iteruje po wszystkich atrybutach z listy, aby je wypisać. Wewnątrz tej pętli dajemy, co poniżej.

cout << "Attribute no. " << i+1 << endl;
DOMAttr* dom_attr = (DOMAttr*) dom_attrs->item(i);

char* name = XMLString::transcode(dom_attr->getName());
cout << "Name: " << name << endl;

char* value = XMLString::transcode(dom_attr->getValue());
cout << "Value: " << value << endl;

Pierwsza para linii to tylko wypisanie numeru atrybutu. Druga para linii to wypisanie nazwy atrybutu. Trzecia para linii to natomiast wypisanie wartości atrybutu. Na zakończenie damy sobie tylko linie rozdzielającą nasze elementy.

cout << "----------------------------------------" << endl;

I już. Cały kod wygląda następująco.

#include <iostream>
using namespace std;
#include <xercesc/dom/DOM.hpp>
using namespace xercesc;

int main()
{
    ///Inicjalizacja

    XMLPlatformUtils::Initialize();
    DOMImplementation* dom_xml = DOMImplementationRegistry::getDOMImplementation(XMLString::transcode(""));

    ///Odczyt z pliku

    DOMLSParser* dom_file = dom_xml->createLSParser(DOMImplementationLS::MODE_SYNCHRONOUS, 0);
    DOMDocument* dom_doc  = dom_file->parseURI("file.xml");
    DOMElement*  dom_root = dom_doc->getDocumentElement();

    ///Iteracja po elementach

    for (DOMNode* dom_child=dom_root->getFirstChild(); dom_child != 0; dom_child=dom_child->getNextSibling()) {
        if (dom_child->getNodeType() == DOMNode::ELEMENT_NODE) {
            char *name = XMLString::transcode(dom_child->getNodeName());
            cout << "Node name: " << name << endl;

            DOMNamedNodeMap *dom_attrs = dom_child->getAttributes();
            int num= dom_attrs->getLength();
            cout << "Attributes number: " << num << endl;

            for(int i=0; iitem(i);

                char* name = XMLString::transcode(dom_attr->getName());
                cout << "Name: " << name << endl;

                char* value = XMLString::transcode(dom_attr->getValue());
                cout << "Value: " << value << endl;
            }

            cout << "----------------------------------------" << endl;
        }
    }
}

W efekcie program wypisze na konsoli:

Node name: person
Attributes number: 1
Attribute no. 1
Name: id
Value: Big.Boss
----------------------------------------
Node name: person
Attributes number: 1
Attribute no. 1
Name: id
Value: one.worker
----------------------------------------
Node name: person
Attributes number: 1
Attribute no. 1
Name: id
Value: two.worker
----------------------------------------
Node name: person
Attributes number: 1
Attribute no. 1
Name: id
Value: three.worker
----------------------------------------
Node name: person
Attributes number: 1
Attribute no. 1
Name: id
Value: four.worker
----------------------------------------
Node name: person
Attributes number: 1
Attribute no. 1
Name: id
Value: five.worker
----------------------------------------