Artykuł opisuje skąd pobrać bibliotekę RapidXML oraz jak utworzyć, odczytać i zapisać dokument XML. (artykuł)
Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Zarejestruj się!
dział serwisuArtykuły
kategoriaInne artykuły
artykuł[C++] Obsługa plików XML - biblioteka RapidXML
Autor: Grzegorz 'baziorek' Bazior

[C++] Obsługa plików XML - biblioteka RapidXML

[artykuł] Artykuł opisuje skąd pobrać bibliotekę RapidXML oraz jak utworzyć, odczytać i zapisać dokument XML.

Instalacja biblioteki RapidXML

Zanim rozpoczniesz korzystanie z biblioteki RapidXML, konieczne jest jej wcześniejsze pobranie. Bibliotekę tą możesz pobrać ze strony domowej, po czym będziesz musiał ją rozpakować.

Jak używać biblioteki RapidXML we własnych programach

Rozpakowany folder można umieścić tym samym katalogu co plik z funkcją main() - wówczas wystarczy dołączyć plik rapidxml.hpp za pomocą dyrektywy
#include
. W programie można również dołączyć plik rapidxml_print.hpp, jeśli chcesz mieć dostęp do funkcji wyświetlających dokument XML na różne sposoby. Pliki nagłówkowe możesz dołączyć w następujący sposób:
C/C++
#include "rapidxml/rapidxml.hpp"
#include "rapidxml/rapidxml_print.hpp"

using namespace rapidxml;

Parsowanie dokumentów XML

Aby dokonać parsowania dowolnego tekstu wystarczy posiadać łańcuch znaków, którego zawartością będzie plik w formacie XML. Można to zrobić w następujący sposób:
C/C++
const char * sTrescPlikuXML = "..."; //Tu zawartość pliku XML

xml_document <> dokument;
try
{
    dokument.parse < 0 >( sTrescPlikuXML );
}
catch( parse_error p )
{
    p.what();
}

Struktura pliku XML i dostęp do korzenia

Jak wiemy dokumenty XML mają następującą strukturę:

<?xml version="1.0" encoding="UTF-8"?>
<Korzen>
...
</Korzen>
Struktura ta zawiera deklarację informującą, że mamy do czynienia z plikiem XML, a ponadto znajduje się w niej główny korzeń, w którym umieszcza się pozostałe dane jakie mają być przechowywane w strukturze pliku XML. W związku z tym warto zacząć od dostania się właśnie do tego korzenia:
C/C++
xml_node <>* pKorzen = dokument.first_node();

Metody do obsługi xml_node<>

Na tym etapie można już wykorzystać parę metod charakterystycznych dla xml_node<>. Zobaczmy teraz deklaracje kilku z nich:
C/C++
char * pKorzen->first_node(); // czyli przechodzi na pierwsze dziecko, a dokładniej pierwszy element XML wewnątrz aktualnie "trzymanego", w naszym przypadku korzenia
pKorzen->last_node(); // analogicznie do first_node(), tylko ostatni element
// w xml_node<> mamy zdefiniowaną jeszcze pewna funkcje które dla korzenia nie miały by sensu:
char * pElement->next_sibling(); // czyli najbliższy, następny brat aktualnie "trzymanego" elementu
char * pElement->previous_sibling(); // najbliższy, poprzedni brat aktualnie "trzymanego" elementu
char * pElement->parent(); // przechodzi na rodzica aktualnie "trzymanego" elementu
Wszystkie wymienione wyżej metody zwracają wartość NULL w przypadku gdy szukany element nie zostanie znaleziony w dokumencie XML. Pamiętaj więc, aby zawsze sprawdzać jakie wartości są zwracane przez wywoływane metody.

Przerwano korektę tekstu. W tekście znajdują się poważne błędy merytoryczne. Metody nie zwracają typów
char *
.
Nasze xml_node<> dziedziczy z: xml_base, a mówię o tym ze względu na to że atrybuty danego węzła (ang. node) także dziedziczą z tej klasy, a co za tym idzie dysponują podobnie jak xml_node<> poniższymi funkcjami:
C/C++
char * korzen->name(); // zwraca nazwę danego elementu, czyli dla <wezel>bla bla</wezel> będzie to: "wezel"
char * korzen->value(); // zwraca wartość danego elementu, czyli dla <wezel>bla bla</wezel> będzie to: "bla bla"

Dygresja dla doświadczonych w używaniu XML DOM zgodnie ze specyfikacją W3C, reszcie odradzam czytanie poniższego akapitu

XML DOM definiuje standardowy sposób dostępu i manipulacji dokumentami XML.
DOM prezentuje dokument XML jako strukturę drzewiastą
W Document Object Model mając taką strukturę:

<nadwezel>
<wezel>Niezwykle wazna tresc</wezel>
</nadwezel>
i mając wskazany w danej chwili <wezel> odczytać nazwę możemy bez problemu, natomiast aby odczytać wartość musimy wejść: obecny->first_node() i dopiero wtedy możemy pozyskać wartość, ale już nie możemy bez wychodzenia do rodzica pozyskać nazwy. Natomiast w naszym RapidXML będąc w danej chwili na <wezel> możemy odczytać zarówno nazwę jak i wartość; wiedza o tym  oszczędzi zastanawiania dlaczego nie działa ;).

Funkcje charakterystyczne dla nawigacji po atrybutach i pętla for przechodzące przez nie

Aby rozpocząć "zabawę" z atrybutami wystarczy:
C/C++
xml_node <> * wezel =...;
xml_attribute <> * atrybut_pierwszy = wezel->first_attribute(); // w ten sposób kierujemy się na pierwszy atrybut
xml_attribute <> * atrybut_ostatni = wezel->last_attribute(); // w ten sposób kierujemy się na ostatni atrybut

for( atrybut_pierwszy; atrybut_pierwszy; atrybut_pierwszy = atrybut_pierwszy->next_attribute() )
cout << "\tAtrybut: << atrybut_pierwszy->name() << " ma wartosc
        : " << atrybut_pierwszy->value() << endl;
Teraz czas na wyświetlanie tego co mamy. Do dyspozycji dano nam:
C/C++
xml_document <> doc; // nasz dokument

cout << doc; // wiadomo
print( std::cout, doc, 0 ); // wyświetlenie na strumieniu wyjściowym, 0 znaczy że domyślna flaga wypisywania

string s;
print( std::back_inserter( s ), doc, 0 ); // zapisanie do stringa przy użyciu iteratora

char buffer[ 4096 ];
print( buffer, doc, 0 ); // Zapis do tablicy char

Jak zakończyć pracę z dokumentem

Po odczytaniu interesujących nas rzeczy należy wyczyścić zaalokowaną pamięć:
C/C++
xml_document <> doc;
...
doc.clear();

Czas na przykład odczytu z dokumentu XML

Przykładowy plik XML

Dość teorii, czas na praktykę, mamy przykładowy plik XML, nie niosący żadnej wartości, ale mówią że im coś głupsze tym się lepiej zapamiętuje (na własnym przykładzie się z tym zgadzam):

<?xml version="1.0" encoding="UTF-8"?>
<korzen>
  <galazka id="1" kolor="brazowa" grubosc="calkiem spasiona">
    <galazeczka wiek="rok">Przydrzwiowa</galazeczka>
    <galazeczka wiek="2 lata">Siegajaca za plot</galazeczka>
  </galazka>
  <galazka id="3" kolor="brozowawa" grubosc="chuderlawa">
    <galazeczka wiek="miesiac">Kujaca po oczach</galazeczka>
    <galazeczka wiek="pol roku">Usychajaca</galazeczka>
  </galazka>
</korzen>

Kompletny program czytający powyższy plik XML

Teraz kod który w sposób reprezentatywny odczyta powyższą treść, demonstrując wypisane powyżej możliwości, pod przykładem jest wyjaśnienie trudniejszych miejsc w programie (program działa na g++-4.6):
C/C++
#include <iostream>
#include <fstream>
#include <string>
#include <memory> // (1)
#include "rapidxml/rapidxml.hpp"
#include "rapidxml/rapidxml_print.hpp"

using namespace std;
using namespace rapidxml;

inline char * stringToChar( string & s ) {
    long long N = s.length();
    char * out = new char[ N + 1 ];
    ::copy( s.c_str(),( s.c_str() + N - 1 ), out ); // (2)
    return out;
}

char * plikDoChar( const char * nazwa_pliku ) {
    ifstream plik( nazwa_pliku );
    if( plik.bad() ) { // (3)
        cout << "Plik nie istnieje lub nie masz do niego praw!" << endl;
        exit( - 1 );
    }
   
    filebuf * pbuf = plik.rdbuf(); // (4)
    long wielkosc_pliku = pbuf->pubseekoff( 0, ios::end, ios::in );
    plik.seekg( 0 ); // (5)
   
    char * wyjscie = new char[ wielkosc_pliku + 1 ];
   
    plik.read( wyjscie, wielkosc_pliku ); // (6)
   
    return wyjscie;
}

void wyswietlAtrybuty( xml_node <> * wezel ) {
    for( xml_attribute <>* atrybut = wezel->first_attribute(); atrybut; atrybut = atrybut->next_attribute() ) // (7)
         cout << " atrybut '" << atrybut->name() << "' = '" << atrybut->value() << "'\t";
   
    cout << endl;
}

main() {
    const char * nazwa_pliku = "xml.xml";
    auto_ptr < char > zawartosc_pliku( plikDoChar( nazwa_pliku ) );
   
    xml_document <> dokument;
    try {
        dokument.parse < 0 >( zawartosc_pliku.get() ); // (8)
    } catch( parse_error p ) {
        p.what();
    }
    cout << dokument; // (9)
    xml_node <> * korzen = dokument.first_node(); // (10)
   
    for( xml_node <> * galazka = korzen->first_node(); galazka; galazka = galazka->next_sibling() ) { // (11)
        cout << "Mamy teraz wezel: '" << galazka->name() << "', ma atrybuty:\t";
        wyswietlAtrybuty( galazka );
       
        for( xml_node <> * galazeczka = galazka->first_node(); galazeczka; galazeczka = galazeczka->next_sibling() )
             cout << "\tteraz '" << galazeczka->name() << "' co ma " << galazeczka->first_attribute()->value() << "lat\ti niesie przeslanie '" << galazeczka->value() << "'\n";
       
    }
    dokument.clear(); // (12)
}
// g++-4.6 -std=c++0x -o przykladowy.exe  przykladowy.cc && ./przykladowy.exe
(W poniższej tabeli niektóre ";" zastąpiłem ":" ze względu na formatowanie
nrfunkcjaopis
1#include <memory>potrzebne do użycia auto_ptr
2::copy(s.c_str(), (s.c_str() + N-1), out)c_str() zwraca const char*, wiec musimy to niestety przepisac na char*
3if(plik.bad())jeśli plik się nie otworzył dalsze działanie programu nie ma sensu
4filebuf *pbuf = plik.rdbuf()potrzebne do odczytania wielkości pliku
5plik.seekg(0)po odczytaniu wielkości pliku trzeba przesunąć wskaźnik czytania na poczatek
6plik.read(wyjscie, wielkosc_pliku)wczytanie całego pliku do tablicy
7for( xml_attribute<>* atrybut = wezel->first_attribute(): atrybut: atrybut=atrybut->next_attribute() )iteracja po wszystkich atrybutach
8dokument.parse<0>( zawartosc_pliku.get() )parsowanie XMLa znajdującego się na tablicy char[]
9cout << dokumentwyswietlenie naszego dokumentu
10xml_node<> *korzen = dokument.first_node()nasz pierwszy/główny element
11for(xml_node<> *galazka=korzen->first_node(): galazka: galazka=galazka->next_sibling())iteracja po całym rodzeństwie
12dokument.clear()czyszczenie dokumentu po pracy

Celowo zatajona wcześniej teoria

Mamy za sobą już po trochu teorii i praktyki, czas na częściowe skomplikowanie, a zarazem ułatwienie korzystania z parsera, funkcje dla xml_node<>, które wcześniej wymieniłem tak naprawdę mają następującą postać:
C/C++
xml_node <>* first_node( const Ch * name = 0, std::size_t name_size = 0, bool case_sensitive = true ) const;
xml_node <>* last_node( const Ch * name = 0, std::size_t name_size = 0, bool case_sensitive = true ) const;
xml_node <>* previous_sibling( const Ch * name = 0, std::size_t name_size = 0, bool case_sensitive = true ) const;
xml_node <>* next_sibling( const Ch * name = 0, std::size_t name_size = 0, bool case_sensitive = true ) const;
xml_attribute <>* first_attribute( const Ch * name = 0, std::size_t name_size = 0, bool case_sensitive = true ) const;
xml_attribute <>* last_attribute( const Ch * name = 0, std::size_t name_size = 0, bool case_sensitive = true ) const;
Parametry mają następujące znaczenie:
  • name - nazwa dziecka do znalezienia, zwracany jest pierwszy o danej nazwie, gdy 0 zwracane jest pierwsze dziecko
  • name_size - ilosc znakow w napisie, przy 0 jest dedukowana automatycznie, ponadto łańcuch znaków nie musi się kończyć znakiem null jeśli wartość name_size jest >0
  • case_sensitive - wrażliwość na wielkość znaków, działa tylko w znakach ASCII
pełna wersja tych funkcji może nam ułatwić wydobywanie informacji.

Modyfikacja dokumentu XML

Jako że format XML jest bardzo popularny, mamy wiele narzędzi obsługujących go, a ponadto jest bardzo czytelny zarówno dla ludzi jak i komputera, dlatego szkoda byłoby nie móc edytować raz umieszczonej treści w łatwy sposób. Na szczęście nasz parser umożliwia i coś takiego (wprawdzie nie przy wszystkich flagach parsowania).

Funkcje zmieniające atrybuty, oraz węzły

Dwie pierwsze funkcje nadają węzłom tylko wskaźnik do napisu, dlatego warto sobie zaalokować dany napis:
C/C++
void name( const Ch * nowa_nazwa, size_t rozmiar_nazwy = 0 );
void value( const Ch * nowa_wartosc, size_t rozmiar_wartosci );

Ch * allocate_string( const Ch * zrodlowy_tekst = 0, size_t rozmiar_tekstu = 0 ); // allokuje napis

Funkcje usuwające atrybuty, oraz węzły

C/C++
void remove_first_node(); // usuwa pierwszy wezel
void remove_last_node(); // usuwa ostatni wezel
void remove_node( xml_node < Ch > * ktory_wezel ); // usuwa wskazany wezel
void remove_all_nodes(); // usuwa wszystkie wezly

void remove_first_attribute(); // usuwa pierwszy atrybut
void remove_last_attribute(); // usuwa ostatni atrybut
void remove_attribute( xml_attribute < Ch > * ktory_atrybut ); // usuwa wskazany atrybut
void remove_all_attributes(); // usuwa wszystkie atrybuty
Powyższe funkcje są funkcjami składowymi tylko xml_node<> i jego pochodnej xml_document<>

Kompletny programu demonstrujący modyfikację dokument XML

Teoria bez praktyki nie trafia do serca. Używam tego samego pliku źródłowego co poprzednio. Opis cięższych części programu znajduje się poniżej, a funkcja char* plikDoChar(const char* nazwa_pliku) była już omówiona w poprzednim przykładzie


C/C++
#include <iostream>
#include <fstream>
#include <iomanip>
#include <string>
#include <memory>
#include "rapidxml-1.13/rapidxml.hpp"
#include "rapidxml-1.13/rapidxml_print.hpp"

using namespace std;
using namespace rapidxml;

inline char * plikDoChar( const char * nazwa_pliku ) {
    ifstream plik( nazwa_pliku );
    if( plik.bad() ) {
        cout << "Plik nie istnieje lub nie masz do niego praw!" << endl;
        exit( - 1 );
    }
   
    filebuf * pbuf = plik.rdbuf();
    long wielkosc_pliku = pbuf->pubseekoff( 0, ios::end, ios::in );
    plik.seekg( 0 );
   
    char * wyjscie = new char[ wielkosc_pliku + 1 ];
   
    plik.read( wyjscie, wielkosc_pliku );
   
    return wyjscie;
}

main() {
    const char * nazwa_pliku = "xml.xml";
    auto_ptr < char > zawartosc_pliku( plikDoChar( nazwa_pliku ) );
   
    xml_document <> dokument;
    try {
        dokument.parse < parse_fastest >( zawartosc_pliku.get() ); // (1)
    } catch( parse_error p ) {
        p.what();
    }
    cout << dokument << setfill( '_' ) << setw( 60 ) << '\n' << endl; // (2)
   
    xml_node <> * korzen = dokument.first_node();
    korzen->name( "owoce" ); // (3)
   
    /*****************************Jablka:****************************************/
    xml_node <> * jablka = korzen->first_node();
    jablka->name( "jablka" );
    jablka->remove_first_attribute();
    jablka->remove_last_attribute();
    xml_attribute <>* atrybut_jablka = jablka->first_attribute();
    atrybut_jablka->name( "rok_wyprodukowania jabluszka", 18 ); // (4)
    atrybut_jablka->value( "2013, w grudniu po poludniu", 4 ); // (5)
   
    xml_node <> * gatunek_jablka = jablka->first_node( "galazeczka" ); // (6)
    gatunek_jablka->name( "jonatany" );
    gatunek_jablka->value( "najtansze" );
    gatunek_jablka->remove_all_attributes();
   
    gatunek_jablka = gatunek_jablka->next_sibling( "GaLaZeCzKa", 10, false ); // (7)
    gatunek_jablka->name( "zimowki" );
   
    if( gatunek_jablka->first_attribute( "w" ) ) { // (8)
        atrybut_jablka = gatunek_jablka->first_attribute( "w" );
        cout << "first_attribute('w')\n";
    }
    else
         atrybut_jablka = gatunek_jablka->first_attribute( 0, gatunek_jablka->first_attribute()->name_size() + 10 ); // (9)
   
    gatunek_jablka->remove_attribute( atrybut_jablka ); // (10)
    gatunek_jablka->value( "niedojrzale" );
   
    /*****************************Marchewki:****************************************/
    xml_node <> * marchewki = gatunek_jablka->parent()->next_sibling();
    marchewki->name( "marchewki" );
    marchewki->remove_all_nodes();
    marchewki->remove_all_attributes();
    marchewki->value( " (jeszcze nie przywiezli) " );
   
   
    cout << dokument;
    dokument.clear();
}
// g++-4.6 -std=c++0x -o przykladowy2.exe  przykladowy2.cc && ./przykladowy2.exe
Czas na wyjaśnienie pewnych fragmentów kodu:
nrfunkcjaopis
1dokument.parse < parse_fastest >( zawartosc_pliku.get() )zmieniłem flagę parsowania
2cout << dokument << setfill('_') << setw(60)wyświetlam poziomą kreskę o długości 60 znaków
3korzen->name(owoce)zmiana nazwy
4atrybut_jablka->name(rok_wyprodukowania jabluszka, 18)zmiana nazwy atrybutu na pierwsze 18 znaków tekstu
5atrybut_jablka->value(2013, w grudniu po poludniu, 4)zmiana wartości atrybutu
6xml_node <> * gatunek_jablka = jablka -> first_node(galazeczka)chcę pierwszy o takiej nazwie
7gatunek_jablka->next_sibling( GaLaZeCzKa, 10, false )tutaj biorę następny element o takiej nazwie bez rozróżniania wielkości liter
8if( gatunek_jablka->first_attribute(w) )sprawdzam czy musi być pełna nazwa chcianego atrybutu, czy wystarczy początek
9gatunek_jablka->first_attribute(0, gatunek_jablka->first_attribute()->name_size()+10 )czy zadziała podanie dłuższej nazwy przy wyszukiwaniu i nie wysypie nam programu?
10gatunek_jablka -> remove_attribute( atrybut_jablka )usuwam wskazany atrybut
Tekst wyświetlony po modyfikacjach dokument

<owoce>
        <jablka rok_wyprodukowania="2013">
                <jonatany>najtansze</jonatany>
                <zimowki>niedojrzale</zimowki>
        </jablka>
        <marchewki> (jeszcze nie przywiezli) </marchewki>
</owoce>

Generowanie od ZERA własnego dokumentu XML

Funkcje alokujące

C/C++
xml_node < Ch >* allocate_node( node_type typ_wezla, const Ch * nazwa = 0, const Ch * wartosc = 0, size_t dlugosc_nazwy = 0, size_t dlogosc_wartosci = 0 ) throw( bad_alloc );
xml_attribute < Ch >* allocate_attribute( const Ch * nazwa = 0, const Ch * wartosc = 0, size_t dlugosc_nazwy = 0, size_t dlogosc_wartosci = 0 ) throw( bad_alloc );

xml_node < Ch >* clone_node( const xml_node < Ch > * zrodo, xml_node < Ch > * ewentualny_cel = 0 );
Ostatnia z funkcji kopiuje dany węzeł ze wszystkimi potomkami i atrybutami, natomiast nazwy i wartości są współdzielone wskaźnikiem (jak ktoś chce to zmienić musi je osobno alokować).

Funkcje dodające węzły i atrybuty

Wywołujemy je na rzecz obiektu do którego chcemy wstawić węzeł/atrybut
C/C++
void append_node( xml_node < Ch > * wezel ); // dorzucanie węzła na koniec
void prepend_node( xml_node < Ch > * wezel ); // dodawanie węzła na początek
void insert_node( xml_node < Ch > * gdzie = 0, xml_node < Ch > * wezel ); // wstawianie węzła na konkretne miejsce, gdzie=0 wstawia jako ostatni węzeł

void prepend_attribute( xml_attribute < Ch > * atrybut ); // dorzucanie atrybutu na koniec
void append_attribute( xml_attribute < Ch > * atrybut ); // dodawanie atrybutu na początek
void insert_attribute( xml_attribute < Ch > * gdzie = 0, xml_attribute < Ch > * atrybut ); // wstawianie atrybutu na konkretne miejsce, gdzie=0 wstawia jako ostatni atrybut
(Usuwanie węzłów i atrybutów opisałem wcześniej)

Przykład programu generującego własny dokument XML

Opis trudniejszych fragmentów jak zwykle pod kodem
C/C++
#include <iostream>
#include <string>
#include "rapidxml-1.13/rapidxml.hpp"
#include "rapidxml-1.13/rapidxml_print.hpp"

using namespace std;
using namespace rapidxml;

main() {
    xml_document <> dokument;
   
    xml_node <> * html = dokument.allocate_node( node_element, "html" );
    xml_node <> * head = dokument.allocate_node( node_element, "head" );
   
    /**********************************Head************************************/
    //<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-2" />
    xml_node <> * naglowek = dokument.allocate_node( node_element, "meta" );
    xml_attribute <> * atrybut = dokument.allocate_attribute( "http-equiv", "Content-Type" );
    naglowek->append_attribute( atrybut );
    naglowek->append_attribute( dokument.allocate_attribute( "content", "text/html; charset=iso-8859-2" ) ); // (1)
   
    head->append_node( naglowek );
    head->append_node( dokument.allocate_node( node_element, "title", "Strona wygenerowana w C++" ) ); // (2)
   
    html->append_node( head );
    dokument.append_node( html );
   
   
    /**********************************Body************************************/
    xml_node <> * body = dokument.allocate_node( node_element, "body" );
   
    string tresc1 = "zaczne od...", tresc2 = "w tym akapicie";
    xml_node <> * akapit = dokument.allocate_node( node_element, "p", tresc1.c_str() );
    akapit->append_attribute( dokument.allocate_attribute( "style", "text-align: center; color: red;" ) );
   
    body->append_node( akapit );
    akapit = dokument.clone_node( akapit ); // (3)
    akapit->value( tresc2.c_str() );
    body->append_node( akapit );
   
    xml_node <> * komentarz = dokument.allocate_node( node_element, "dwukropekP", "jeszcze fajnie byloby dopisac 2 akapity" );
    komentarz->type( node_comment ); // (4)
    body->insert_node( akapit->previous_sibling(), komentarz ); // (5)
   
    html->append_node( body );
   
    cout << dokument;
    dokument.clear();
}
// g++-4.6 -std=c++0x -o przykladowy3.exe  przykladowy3.cc && ./przykladowy3.exe
Słowem wyjaśnienia:
nrfunkcjaopis
1naglowek->append_attribute(dokument.allocate_attribute(content, text/html; charset=iso-8859-2))dodaje dopiero co zaalokowany atrybut
2head->append_node(dokument.allocate_node(node_element, title, Strona wygenerowana w C++))dodaje dopiero co zaalokowany węzeł
3akapit = dokument.clone_node(akapit)kopiuje węzeł będący akapitem
4komentarz->type(node_comment)zmieniam typ węzła na komentarz
5body->insert_node(akapit->previous_sibling(), komentarz)wrzucam dany komentarz, chce to zrobić przed wszystkimi akapitami

<html>
        <head>
                <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-2"/>
                <title>Strona wygenerowana w C++</title>
        </head>
        <body>
                <!--jeszcze fajnie byloby dopisac 2 akapity-->
                <p style="text-align: center; color: red;">zaczne od...</p>
                <p style="text-align: center; color: red;">w tym akapicie</p>
        </body>
</html>

Dodatkowe informacje na koniec

Flagi parsowania

C/C++
int parse_default = 0;
int parse_no_data_nodes = 0x1;
int parse_no_element_values = 0x2;
int parse_no_string_terminators = 0x4;
int parse_no_entity_translation = 0x8;
int parse_no_utf8 = 0x10;
int parse_declaration_node = 0x20;
int parse_comment_nodes = 0x40;
int parse_doctype_node = 0x80;
int parse_pi_nodes = 0x100;
int parse_validate_closing_tags = 0x200;
int parse_trim_whitespace = 0x400;
int parse_normalize_whitespace = 0x800;
int parse_non_destructive = parse_no_string_terminators | parse_no_entity_translation;
int parse_fastest = parse_non_destructive | parse_no_data_nodes;
int parse_full = parse_declaration_node | parse_comment_nodes | parse_doctype_node | parse_pi_nodes | parse_validate_closing_tags;
Opis zarówno w dokumentacji, jak i w kodzie źródłowym, na uwagę zasługuje fakt że szybsze zaniedbują pewne funkcje uwzględnione w wolniejszych trybach

Typy węzłów

C/C++
enum node_type {
    node_document, node_element, node_data, node_cdata, node_comment, node_declaration, node_doctype, node_pi
}; // opis typów węzłów jest dostępny w dokumentacji, oraz w pliku "rapidxml-1.13/rapidxml.hpp"

node_type type() const; // zwraca typ węzła
void type( node_type type ); // zmienia typ węzła

Iteratory

Parser zawiera także iteratory dla węzłów i atrybutów, ale ich użycie nie jest wcale potrzebne, dla ciekawych znajdują się one w pliku: rapidxml_iterators.hpp.

#include <rapidxml_utils.hpp> czyli dodatkowe możliwości

C/C++
size_t count_children( xml_node < Ch > * wezel ); // liczy dzieci węzła
size_t count_attributes( xml_node < Ch > * wezel ); // liczy atrybuty węzła

template < class Ch = char > class file; // szablon reprezentujący dane załadowane z pliku

Dla chcących dowiedzieć się więcej

Dla dociekliwych zachęcam do obejrzenia dokumentacji, a także na tej właśnie stronie jest porównanie szybkości parsowania , niestety sprzed paru lat

Bonus na zakończenie

Czas teraz na praktyczne wykorzystanie nabytej w tym artykule wiedzy. Zdarza się że chcemy pobrać kod strony internetowej i coś z niego wyciągnąć. Do pobrania kodu użyję darmowej biblioteki cURL ( instalacja: na linuxie sudo apt-get install curl, w Visual Studio,
w Code:Block i w DevC++ opisana w tym serwisie).
Zajmę się pobraniem informacji z pliku XML z popularnej gry, zawierającego informacje o liczbie osób grających w danych krajach
C/C++
#include <iomanip>
#include <iostream>
#include <memory>
#include <string>
#include <curl/curl.h>
#include "rapidxml/rapidxml.hpp"
#include "rapidxml/rapidxml_print.hpp"
#include "rapidxml/rapidxml_utils.hpp"

using namespace std;
using namespace rapidxml;

static string buffer;

inline static int string_writer( char * data, size_t size, size_t nmemb, string * buffer ) {
    int result = 0;
    if( buffer != NULL ) {
        buffer->append( data, size * nmemb );
        result = size * nmemb;
    }
    return result;
}

inline static char * pobierzStrone( char * strona ) {
    bool byl_problem = false;
   
    do {
        buffer.reserve( 10000 );
        buffer.clear();
        CURL * curl;
        CURLcode result;
       
        curl = curl_easy_init();
        if( curl ) {
            curl_easy_setopt( curl, CURLOPT_URL, strona );
            curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, string_writer );
            curl_easy_setopt( curl, CURLOPT_WRITEDATA, & buffer );
           
            result = curl_easy_perform( curl );
            if( result == CURLE_OK )
                 byl_problem = false;
            else {
                cout << "!Wystapil blad podczas pobierania strony, powtarzam czynnosc!\n";
                byl_problem = true;
            }
            curl_easy_cleanup( curl );
        }
    } while( byl_problem );
   
    long long N = buffer.length();
    char * out = new char[ N + 1 ];
    ::copy( buffer.c_str(),( buffer.c_str() + N - 1 ), out );
    out[ N - 1 ] = '\0';
    return out;
}

main() {
    char countries_address[] = "http://api.erepublik.com/v2/feeds/countries";
   
    auto_ptr < char > tresc_strony( pobierzStrone( countries_address ) );
   
    xml_document <> dokument;
    try {
        dokument.parse < 0 >( tresc_strony.get() );
    } catch( parse_error p ) {
        p.what();
    }
   
    xml_node <> * kraje = dokument.first_node();
    unsigned ilosc_krajow = count_children( kraje );
    cout << "Mamy w grze: " << ilosc_krajow << " krajow\n";
    for( xml_node <> * kraj = kraje->first_node(); kraj; kraj = kraj->next_sibling() )
         cout << " * w kraju: " << setw( 30 ) << left << kraj->first_node( "name" )->value() << " jest graczy: " << setw( 6 ) << right << kraj->first_node( "citizen-count" )->value() << '\n';
   
    dokument.clear();
} // g++-4.6 -std=c++0x -lcurl -o erepy.exe  erepy.cc && ./erepy.exe