Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autor: Grzegorz 'baziorek' Bazior
Inne artykuły

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

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

Wprowadzenie do tematyki artykułu

Jeśli jesteś informatykiem doskonale znasz format XML i wiesz czym się charakteryzuje. Wiesz też, że pobierać dane z dokumentów XML można m.in. przy pomocy parserów XML, ciekawym faktem jest to, iż można parsować również niektóre strony internetowe (zgodne z XMLem np. napisane przy użyciu XHTML).

Format XML jest często używany do:
  • konfiguracji programów
  • zapisu danych (np. Microsoft Office w najnowszych wersjach)
  • nawet pisania testów (nie żartuję)
Główną zaletą jest dostępność bardzo wielu parserów w każdym szanującym się języku programowania (w tym nawet w assemblerze). Kolejną zaletą jest to, że format ten jest na ogół czytelny dla człowieka.
Do formatu XML istnieją nawet narzędzia do jego walidacji, takie jak m.in.: XML Scheema, czy XML DTD.

Dla ułatwienia zrozumienia artykułu polecam zapoznanie się ze specyfikacją formatu XML.
Przykładowy plik XML:
<?xml version="1.0" encoding="UTF-8"?>
<root>
...
</root>
Struktura ta zawiera opcjonalną 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 węzły jakie mają być przechowywane w strukturze pliku XML.

Biblioteka RapidXML

Dlatego RapidXML

Dla języka C++ również jest wiele parserów do XMLa. Świetne, graficzne ich porównanie znajduje się na stackoverflow, co ciekawe owa strona wpłynęła na moją decyzję wyboru biblioteki RapidXMLa. Wybór ten argumentuję tym, iż bibliotekę ta jest łatwa w użyciu (zawiera ona jedynie pliki nagłówkowe). Zgodnie z dokumentacją celem biblioteki jest najszybsze możliwe parsowanie plików XML, a głównie dlatego używamy C++, oczywiście kod biblioteki z tego powodu może nie być łatwy w zrozumieniu. W temacie szybkości dodam, że na chwilę obecną nie jest to niestety najszybsza biblioteka do parsowania XMLi. Zalety tej biblioteki zostały zauważone przez programistów biblioteki boost w komponencie Boost Property Tree.
Dodam, że dokumentacja ten portal posiada polską dokumentację poświęconą RapidXML: » DokumentacjaRapidXML!

Instalacja biblioteki RapidXML

Bibliotekę tą możesz pobrać ze strony domowej. Nie trzeba jej kompilować ani instalować, gdyż są to tylko pliki nagłówkowe. Wystarczy je umieścić w odpowiednim miejscu, zależnie gdzie w stosunku do naszego kodu umieścimy bibliotekę może się okazać konieczne podanie ścieżek do kompilatora (np. dla gcc -isystem/sciezka/do/naglowkow).

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

Mając rozpakowaną bibliotekę i podane odpowiednie ścieżki do kompilatora, aby użyć biblioteki wystarczy włączyć plik rapidxml.hpp za pomocą dyrektywy
#include
. Jeśli ponadto chcemy mieć dostęp do funkcji wyświetlających dokument XML na różne sposoby  można również dołączyć plik rapidxml_print.hpp. Jest dostępny jeszcze nagłówek rapidxml_iterators.hpp, udostępniający iteratory oraz rapidxml_utils.hpp oferujący pewne dodatkowe funkcje przydatne podczas korzystania z biblioteki.
rapidxml.hppPodstawowe narzędzia XML. (plik nagłówkowy)
rapidxml_print.hppPrzekazywanie danych XML na wyjście. (plik nagłówkowy)
rapidxml_iterators.hppIteratory węzłów podrzędnych oraz atrybutów. (plik nagłówkowy)
rapidxml_utils.hppWczytywanie danych z wejścia oraz liczenie węzłów i atrybutów. (plik nagłówkowy)
Wszystkie definicje są w przestrzeni nazw
namespace rapidxml { /*...*/ }
.
rapidxmlGłówna przestrzeń nazw tej biblioteki. (przestrzeń nazw)

Parsowanie dokumentów XML

Aby dokonać parsowania dowolnego tekstu należy użyć » RapidXML » rapidxmlxml_document podając do funkcji parsującej ł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++
#include <rapidxml.hpp>
using namespace rapidxml;

int main()
{
    const char * xmlDocument = "...";
   
    xml_document <> document; // domyślnym typem jest char
    try
    {
        document.parse < 0 >( text ); // 0 to domyślny tryb parsowania
    }
    catch( const parse_error & e )
    {
        std::cerr << e.what() << " here: " << e.where < char >() << std::endl;
        return - 1;
    }
}
Jak widać w razie błędu parsowania zostanie wyrzucony » RapidXML » rapidxmlparse_error. O innych flagach parsowania rozpiszę się później, dociekliwi mogą jednak wcześniej zapoznać się z » RapidXML » rapidxmlFlagi.

Kończenie pracy z dokumentem

Po przetworzeniu wg naszej woli dokumentu XML należy wyczyścić zaalokowaną pamięć, można to zrobić przy użyciu metody: » RapidXML » rapidxml » xml_documentclear. Oczywiście destruktor automatycznie zawoła tę metodę.

Nawigacja po danych po parsowaniu XMLa

Dokument XML składa się z węzłów, reprezentowanych przez » RapidXML » rapidxmlxml_node, oraz atrybutów reprezentowanych przez » RapidXML » rapidxmlxml_attribute.

Struktura pliku XML i dostęp do korzenia

Aby po dokonaniu parsowania z naszego dokumentu dostać się do korzenia najprościej zrobić tak:
xml_node <>* root = document.first_node();
. Oczywiście może się okazać, że przy użyciu pewnych flag parsowania trzeba będzie jeszcze poskakać celem dostania się do korzenia (np. przed nim może być deklaracja dokumentu XML).

Metody do nawigowania po węzłach

» RapidXML » rapidxmlxml_node, a co za tym idzie również dziedziczący po nim » RapidXML » rapidxmlxml_document zawierają poniższe metody:
first_nodeZwraca wskaźnik do pierwszego węzła. (metoda)
last_nodeZwraca wskaźnik do ostatniego węzła. (metoda)
C/C++
namespace rapidxml
{
    template < typename Ch = char >
    xml_node < Ch >* xml_node < Ch >::first_node();
    template < typename Ch = char >
    xml_node < Ch >* xml_node < Ch >::last_node();
}
Na uwagę zasługuje metoda:
parentZwraca wskaźnik do nadrzędnego węzła. (metoda)
C/C++
namespace rapidxml
{
    template < typename Ch = char >
    xml_node < Ch >* xml_node < Ch >::parent();
}
Dzięki której można sprawdzić, czy poniższe metody mają sens (jeśli nie ma rodzica, to przechodzenie na rodzeństwo jest niezdefiniowane):
next_siblingZwraca wskaźnik do następnego węzła zawartego w tym samym węźle nadrzędnym. (metoda)
previous_siblingZwraca wskaźnik do poprzedniego węzła zawartego w tym samym węźle nadrzędnym. (metoda)
C/C++
namespace rapidxml
{
    template < typename Ch = char >
    xml_node < Ch >* xml_node < Ch >::next_sibling();
    template < typename Ch = char >
    xml_node < Ch >* xml_node < Ch >::previous_sibling();
}
Wszystkie wymienione wyżej metody zwracają wartość nullptr w przypadku, gdy szukany element nie zostanie znaleziony w dokumencie XML. Należy pamiętać, aby zawsze sprawdzać jaka wartość jest zwracana przez wywoływaniu metody.

Dygresja dla doświadczonych w używaniu parserów XML w innych językach programowania zgodnych ze specyfikacją XML DOM

Mającą taką strukturę:
<nadwezel>
<wezel>Niezwykle wazna tresc</wezel>
</nadwezel>
i mając wskazany w danej chwili <wezel> w RapidXML możemy od razu odczytać zarówno nazwę, jak i wartość. Natomiast w parserach zgodnych ze specyfikacją XML DOM wg W3C, będąc aktualnie na tym węźle, nazwę możemy odczytać od razu, natomiast celem odczytania wartości musimy wejść do wgłąb i dopiero wtedy możemy ją odczytać, z kolei jak wejdziemy wgłąb to już nie możemy odczytać nazwy bez przeskoczenia na rodzica. Wiedza o tym oszczędzi zastanawiania, dlaczego nasz program nie czyta tak jakbyśmy chcieli.

Nawigacja po atrybutach węzła

Chcąc od węzła przejść do atrybutu możemy użyć następujących funkcji:
first_attributeZwraca wskaźnik do pierwszego atrybutu. (metoda)
last_attributeZwraca wskaźnik do ostatniego atrybutu. (metoda)
C/C++
namespace rapidxml
{
    template < typename Ch = char >
    xml_attribute < Ch >* xml_node < Ch >::first_attribute();
    template < typename Ch = char >
    xml_attribute < Ch >* xml_node < Ch >::last_attribute();
}
Między atrybutami przechodzimy przy użyciu funkcji:
next_attributeZwraca wskaźnik do następnego atrybutu. (metoda)
previous_attributeZwraca wskaźnik do poprzedniego atrybutu. (metoda)
C/C++
namespace rapidxml
{
    template < typename Ch = char >
    xml_attribute < Ch >* previous_attribute() const;
    template < typename Ch = char >
    xml_attribute < Ch >* next_attribute() const;
}
Aby przejść po wszystkich atrybutach wystarczy najprostsza pętla:
C/C++
for( xml_attribute <>* a = node->first_attribute(); a; a = a->next_attribute() )
     cout << a->name() << " = " << a->value() << endl;
W ramach wyjaśnienia przypominam, iż w momencie nieznalezienia kolejnego atrybutu do zmiennej a zostanie przypisana wartość nullptr.

Pobieranie nazwy i wartości węzłów i atrybutów

Nasze » RapidXML » rapidxmlxml_node oraz » RapidXML » rapidxmlxml_attribute posiadają jeszcze m.in. następujące metody:
nameUstawia lub pobiera nazwę elementu. (metoda)
valueUstawia lub pobiera wartość elementu. (metoda)

Wyświetlanie dokumentu XML

Chcąc wyświetlić zawartość naszego XML mamy do dyspozycji pewne dodatkowe funkcje, które jednak są dostępne dopiero po włączeniu nagłówka
#include <rapidxml_print.hpp>
:
operator<<Przekazuje dane węzła do strumienia wyjściowego. (operator - funkcja)
printPrzekazuje dane węzła na wyjście. (funkcja)
 można ich użyć w następujący sposób:
C/C++
using namespace rapidxml;

xml_document <> document; // nasz dokument

cout << document; // jest to ładne wyświetlenie dokumentu z wcięciami

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

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

Flagi wyświetlania dokumentu XML

Poruszmy kwestie flag, poza 0, która ładnie wyświetla dokument, mamy flagę odpowiadającą za wyświetlanie bez wcięć, porównajmy je:

Flagaoutput
0
, flaga domyślna przy wyświetlaniu strumieniami
<ceo>
        <manager id="1" name="Jan" working_years="10">
                <developer id="2" name="Stefan" working_years="0">komunikator</developer>
                <developer id="3" name="Zygmunt" working_years="2">komunikator</developer>
        </manager>
        <manager id="2" name="Jan" working_years="10">
                <developer id="4" name="Andrzej" working_years="0">klient poczty</developer>
                <tester id="5" name="Andrzej" working_years="3">klient poczty</tester>
        </manager>
</ceo>
» RapidXML » rapidxmlprint_no_indenting
<ceo><manager id="1" name="Jan" working_years="10"><developer id="2" name="Stefan" working_years="0">komunikator</developer><developer id="3" name="Zygmunt" working_years="2">komunikator</developer></
manager><manager id="2" name="Jan" working_years="10"><developer id="4" name="Andrzej" working_years="0">klient poczty</developer><tester id="5" name="Andrzej" working_years="3">klient poczty</tester>
</manager></ceo>
Jak widać możemy RapidXML użyć zarówno do usunięcia zbędnych białych znaków z naszego pliku XML, jak i do wypisania go w czytelniejszej formie.

Problem z kompilacją nagłówka rapidxml_print.hpp

Czasami kod, który niegdyś się kompilował, po zmianach w kompilatorze przestaje się kompilować. W związku z tym możemy się natknąć na pewien problem włączając nagłówek rapidxml_print.hpp. Problemem są niezadeklarowane przed użyciem funkcje. Dokładniejszy opis problemu oraz rozwiązanie znajduje się na Stack Overflow. W skrócie należy zadeklarować metody w następujący sposób (poniżej cytuje rozwiązanie ze Stack Overflow):
C/C++
template < class OutIt, class Ch >
inline OutIt print_children( OutIt out, const xml_node < Ch > * node, int flags, int indent );
template < class OutIt, class Ch >
inline OutIt print_attributes( OutIt out, const xml_node < Ch > * node, int flags );
template < class OutIt, class Ch >
inline OutIt print_data_node( OutIt out, const xml_node < Ch > * node, int flags, int indent );
template < class OutIt, class Ch >
inline OutIt print_cdata_node( OutIt out, const xml_node < Ch > * node, int flags, int indent );
template < class OutIt, class Ch >
inline OutIt print_element_node( OutIt out, const xml_node < Ch > * node, int flags, int indent );
template < class OutIt, class Ch >
inline OutIt print_declaration_node( OutIt out, const xml_node < Ch > * node, int flags, int indent );
template < class OutIt, class Ch >
inline OutIt print_comment_node( OutIt out, const xml_node < Ch > * node, int flags, int indent );
template < class OutIt, class Ch >
inline OutIt print_doctype_node( OutIt out, const xml_node < Ch > * node, int flags, int indent );
template < class OutIt, class Ch >
inline OutIt print_pi_node( OutIt out, const xml_node < Ch > * node, int flags, int indent );
należy to wkleić niestety wewnątrz pliku nagłówkowego rapidxml_print.hpp tuż przed funkcją » RapidXML » rapidxml » internalprint_node.

Przykład odczytu z dokumentu XML

Wiemy już jak nawigować po węzłach i atrybutach, czas na wykorzystanie tej wiedzy w praktyce.

Przykładowy plik XML

Poniżej znajduje się przykładowy plik XML, którego użyję w najbliższym przykładzie:
<?xml version="1.0" encoding="UTF-8"?>
<ceo>
  <manager id="1" name="Jan" working_years="10">
    <developer id="2" name="Stefan" working_years="0">komunikator</developer>
    <developer id="3" name="Zygmunt" working_years="2">komunikator</developer>
  </manager>
  <manager id="2" name="Jan" working_years="10">
    <developer id="4" name="Andrzej" working_years="0">klient poczty</developer>
    <tester id="5" name="Andrzej" working_years="3">klient poczty</tester>
  </manager>
</ceo>

Przykładowy program czytający plik XML

Poniżej kod odczytujący powyższą treść, demonstrujący użycie możliwości poruszania po węzłach. Pod przykładem jest wyjaśnienie trudniejszych miejsc w programie:
C/C++
#include <iostream>
#include <fstream>
#include <string>
#include <memory> // unique_ptr
using namespace std;

#include <rapidxml.hpp>
#include <rapidxml_print.hpp> // print
using namespace rapidxml;

auto file2char( const char * fileName ) // (1), C++14
{
    ifstream file( fileName );
    if( !file )
    {
        throw runtime_error( "File '" + string( fileName ) + " can't be opened!" );
    }
    file.seekg( 0, file.end );
    const auto fileSize = file.tellg();
    file.seekg( 0 );
   
    unique_ptr < char[] > fileContext( new char[ fileSize + static_cast < decltype( fileSize ) >( 1 ) ] );
    file.read( fileContext.get(), fileSize );
   
    return fileContext;
}

void printAttributes( xml_node <>* node )
{
    for( xml_attribute <>* a = node->first_attribute(); a; a = a->next_attribute() )
    {
        cout << a->name() << "' = '" << a->value() << "'\t";
    }
}

int main()
{
    const char * fileName = "...";
    auto xmlFileContext = file2char( fileName );
   
    xml_document <> document;
    try
    {
        document.parse < 0 >( xmlFileContext.get() ); // (2)
    }
    catch( const parse_error & e )
    {
        std::cerr << e.what() << " here: " << e.where < char >() << std::endl;
        return - 1;
    }
    print( cout, document, 0 );
   
    xml_node <>* nodeCeo = document.first_node();
   
    for( xml_node <>* manager = nodeCeo->first_node(); manager; manager = manager->next_sibling() ) // (3)
    {
        cout << "Manager: '" << manager->name() << "', known as:\t";
        printAttributes( manager );
        cout << "\tfor:\n";
       
        for( xml_node <>* realWorker = manager->first_node(); realWorker; realWorker = realWorker->next_sibling() )
        {
            cout << "\t'" << realWorker->name() << "' id=" << realWorker->first_attribute()->value() << " working on: '" << realWorker->value() << "'\n";
        }
    }
}
Nrfunkcjaopis
1
auto file2char( const char * fileName )
sygnatura tej funkcji skompiluje się jedynie w C++14, w starszych wersjach C++ lepiej jawnie podać typ. Zdecydowałem się na zwrócenie poprzez inteligentny wskaźnik, aby nie doszło do wycieku pamięci.
2
document.parse < 0 >( xmlFileContext.get() );
potrzebujemy surowego wskaźnika, bez
const
, więc musimy go wyjąć z inteligentnego, jeśli używalibyśmy typu
std::string
 i jego metody » standard C++ » stringc_str zwracającej
const char *
, musielibyśmy zdjąć consta używając brzydkiego
const_cast
3
for( xml_node <>* manager = nodeCeo->first_node(); manager; manager = manager->next_sibling() )
przykład iteracji po wszystkich bezpośrednich dzieciach danego węzła
Output powyższego programu:
<?xml version="1.0" encoding="UTF-8"?>
<ceo>
  <manager id="1" name="Jan" working_years="10">
    <developer id="2" name="Stefan" working_years="0">komunikator</developer>
    <developer id="3" name="Zygmunt" working_years="2">komunikator</developer>
  </manager>
  <manager id="2" name="Jan" working_years="10">
    <developer id="4" name="Andrzej" working_years="0">klient poczty</developer>
    <tester id="5" name="Andrzej" working_years="3">klient poczty</tester>
  </manager>
</ceo>
Manager: 'manager', known as:   id' = '1'       name' = 'Jan'   working_years' = '10'           for:
        'developer' id=2 working on: 'komunikator'
        'developer' id=3 working on: 'komunikator'
Manager: 'manager', known as:   id' = '2'       name' = 'Jan'   working_years' = '10'           for:
        'developer' id=4 working on: 'klient poczty'
        'tester' id=5 working on: 'klient poczty'

Funkcje do nawigacji po węzłach i atrybutach -pełna sygnatura

Mamy za sobą już po trochu teorii i praktyki, czas na częściowe skomplikowanie, a zarazem ułatwienie korzystania z parsera, funkcje dla » RapidXML » rapidxmlxml_node oraz » RapidXML » rapidxmlxml_attribute, które wcześniej wymieniłem tak naprawdę mają następującą sygnaturę:
C/C++
template < typename Ch = char >
xml_node < Ch >* xml_node < Ch >::first_node( const Ch * name = 0, std::size_t name_size = 0, bool case_sensitive = true ) const;
template < typename Ch = char >
xml_node < Ch >* xml_node < Ch >::last_node( const Ch * name = 0, std::size_t name_size = 0, bool case_sensitive = true ) const;
template < typename Ch = char >
xml_node < Ch >* xml_node < Ch >::previous_sibling( const Ch * name = 0, std::size_t name_size = 0, bool case_sensitive = true ) const;
template < typename Ch = char >
xml_node < Ch >* xml_node < Ch >::next_sibling( const Ch * name = 0, std::size_t name_size = 0, bool case_sensitive = true ) const;
template < typename Ch = char >
xml_attribute < Ch >* xml_node < Ch >::first_attribute( const Ch * name = 0, std::size_t name_size = 0, bool case_sensitive = true ) const;
template < typename Ch = char >
xml_attribute < Ch >* xml_node < Ch >::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, jeśli podamy 0 zwracane jest pierwsze dziecko
  • name_size - ilość znaków w napisie, przy 0 jest dedukowana automatycznie, jeśli podamy tą wartość >0 łańcuch znaków nie musi się kończyć znakiem null
  • case_sensitive - wrażliwość na wielkość znaków, działa tylko w znakach ASCII
Zapewne wyda się dziwnym podawanie name_size, ale będzie to bardziej zrozumiałe w momencie opisania przeze mnie polityki obsługi własności tej biblioteki.

Inne ciekawe funkcje biblioteki

Biblioteka dostarcza jeszcze wiele drobnych, ale użytecznych funkcjonalności.

Inne użyteczności biblioteki

Użyteczności (ang. utilities, w skrócie utils), aby z nich skorzystać konieczne jest włączenie nagłówka:
#include <rapidxml_utils.hpp>
są to funkcje:
count_childrenLiczy ilość węzłów podrzędnych. (funkcja)
count_attributesLiczy ilość atrybutów. (funkcja)
oraz szablon klasy » RapidXML » rapidxmlfile, posiadająca takie metody jak:
fileWczytuje dane do pamięci. (konstruktor)
dataZwraca wskaźnik do danych. (metoda)
sizeZwraca rozmiar danych. (metoda)

Modyfikacja dokumentu XML

Odczytywanie to jedno, ale potrzebna jest również analogiczna funkcjonalność jaką jest modyfikowanie dokumentu XML, oczywiście biblioteka takową dostarcza (wprawdzie nie przy wszystkich flagach parsowania, ale o tym później).

Funkcje zmieniające węzły i atrybuty

Zmiana nazwy i wartości

Zarówno » RapidXML » rapidxmlxml_node jak i » RapidXML » rapidxmlxml_attribute posiadają nazwę i wartość, co ciekawe do pobierania i modyfikowania tych wartości służą funkcje o tej samej nazwie. Zachowanie zależy od podanych parametrów, jeśli nie podamy żadnego, zostanie zwrócona nazwa/wartość węzła/atrybutu, jeśli podamy, zostanie ona ustawiona. Oprócz nazwy możemy podać długość napisu, co jest przydatne w sytuacji, gdy nie jest on zakończony
'\0'
. Szczegóły na temat tych funkcji:
nameUstawia lub pobiera nazwę elementu. (metoda)
valueUstawia lub pobiera wartość elementu. (metoda)
C/C++
template < typename Ch = char >
Ch * xml_base < Ch >::name() const;
template < typename Ch = char >
Ch * xml_base < Ch >::value() const;

template < typename Ch = char >
void xml_base < Ch >::name( const Ch * name, std::size_t size = 0 );
template < typename Ch = char >
void xml_base < Ch >::value( const Ch * value, std::size_t size = 0 );

template < typename Ch = char >
std::size_t xml_base < Ch >::name_size() const;
template < typename Ch = char >
std::size_t xml_base < Ch >::value_size() const;

Należy zwrócić uwagę, że funkcje te ze względów optymalizacyjnych nie kopiują sobie tekstu, dlatego chcąc dokonać kopii powinniśmy użyć:
allocate_stringPrzydziela pamięć na łańcuch znaków. (metoda)
C/C++
template < class Ch = char >
Ch * memory_pool < Ch >::allocate_string( const Ch * source = 0, std::size_t size = 0 );
» RapidXML » rapidxmlxml_document dziedziczy po » RapidXML » rapidxmlmemory_pool, dlatego najlepszym rozwiązaniem jest użycie instancji dokumentu do wszelakich alokacji, dzięki temu wszystkie zaalokowane napisy będą żyły tak długo jak owa instancja. Podanie 0 jako pierwszego argumentu powoduje zaalokowanie pamięci, bez jej inicjowania.

Funkcje dodające węzły i atrybuty

W tym celu zarówno » RapidXML » rapidxmlxml_node jak i » RapidXML » rapidxmlxml_document zawierają następujące funkcje:
append_nodeWstawia węzeł podrzędny na końcu. (metoda)
prepend_nodeWstawia węzeł podrzędny na początku. (metoda)
insert_nodeWstawia węzeł podrzędny przed wybranym węzłem podrzędnym. (metoda)
Wywołujemy je na rzecz obiektu, do którego chcemy wstawić węzeł/atrybut
C/C++
template < typename Ch = char >
void xml_node < Ch >::append_node( xml_node < Ch >* node );
template < typename Ch = char >
void xml_node < Ch >::prepend_node( xml_node < Ch >* node );
template < typename Ch = char >
void xml_node < Ch >::insert_node( xml_node < Ch >* where, xml_node < Ch >* node );
Anologicznie atrybuty dodajemy przy użyciu następujących funkcji:
append_attributeWstawia atrybut na końcu. (metoda)
prepend_attributeWstawia atrybut na początku. (metoda)
insert_attributeWstawia atrybut przed wybranym atrybutem. (metoda)
C/C++
template < typename Ch = char >
void xml_node < Ch >::prepend_attribute( xml_attribute < Ch >* attribute );
template < typename Ch = char >
void xml_node < Ch >::append_attribute( xml_attribute < Ch >* attribute );
template < typename Ch = char >
void xml_node < Ch >::insert_attribute( xml_attribute < Ch >* where, xml_attribute < Ch >* attribute );

Usuwanie atrybutów oraz węzłów

Funkcje do usuwania węzłów:
remove_first_nodeUsuwa pierwszy węzeł podrzędny. (metoda)
remove_last_nodeUsuwa ostatni węzeł podrzędny. (metoda)
remove_nodeUsuwa wskazany węzeł podrzędny. (metoda)
remove_all_nodesUsuwa wszystkie węzły podrzędne. (metoda)
C/C++
template < typename Ch = char >
void xml_node < Ch >::remove_first_node();
template < typename Ch = char >
void xml_node < Ch >::remove_last_node();
template < typename Ch = char >
void xml_node < Ch >::remove_node( xml_node < Ch >* node );
template < typename Ch = char >
void xml_node < Ch >::remove_all_nodes();
Analogiczne funkcje do usuwania atrybutów:
remove_first_attributeUsuwa pierwszy atrybut. (metoda)
remove_last_attributeUsuwa ostatni atrybut. (metoda)
remove_attributeUsuwa wskazany atrybut. (metoda)
remove_all_attributesUsuwa wszystkie atrybuty. (metoda)
C/C++
template < typename Ch = char >
void xml_node < Ch >::remove_first_attribute();
template < typename Ch = char >
void xml_node < Ch >::remove_last_attribute();
template < typename Ch = char >
void xml_node < Ch >::remove_attribute( xml_attribute < Ch >* attribute );
template < typename Ch = char >
void xml_node < Ch >::remove_all_attributes();

Przykład modyfikacji dokument XML

Teoria bez praktyki nie trafia do serca. Używam tego samego pliku XML co poprzednio. Opis cięższych części programu znajduje się poniżej.
C/C++
#include <iostream>
#include <string>
#include <memory>  // unique_ptr
#include <iomanip> // fill()
using namespace std;

#include <rapidxml.hpp>
#include <rapidxml_print.hpp>
#include <rapidxml_utils.hpp>
using namespace rapidxml;

int main()
{
    const char * fileName = "...";
   
    unique_ptr < rapidxml::file <>> xmlFile; // (1)
   
    xml_document <> document;
    try
    {
        xmlFile.reset( new rapidxml::file <>( fileName ) );
        document.parse < 0 >( xmlFile->data() );
    }
    catch( const parse_error & e )
    {
        std::cerr << e.what() << " here: " << e.where < char >() << std::endl;
        return - 1;
    }
    catch( const exception & e )
    {
        cerr << e.what() << endl;
        return - 2;
    }
   
    cout << document;
    cout << setfill( '_' ) << setw( 60 ) << '\n' << endl;
   
    /***************************** Changing XML: ****************************************/
    xml_node <>* ceo = document.first_node();
    ceo->name( "President" );
   
    xml_node <>* managerBetter = ceo->first_node();
   
    xml_node <>* managerWorse = ceo->last_node();
    xml_node <>* tester = managerWorse->first_node( "tester" );
    managerWorse->remove_node( tester );
    ceo->remove_node( managerWorse );
   
    xml_node <>* developerReportingToWorseManager = document.clone_node( managerWorse->first_node() ); // (2)
    managerBetter->append_node( developerReportingToWorseManager );
    developerReportingToWorseManager->previous_sibling()->value( "synchronizacja miedzy komunikatorem a klientem poczty" );
   
    {
        xml_attribute <>* workingYears = managerBetter->first_attribute( "working_years" );
        workingYears->name( "sizeOfTeam" );
        auto sizeOfTeam = count_children( managerBetter );
        auto sizeOfTeamAsString = to_string( sizeOfTeam );
        workingYears->value( document.allocate_string( sizeOfTeamAsString.c_str() ) );
    }
   
    cout << document;
}
Na komentarz zasługuje (1), gdyż szablon klasy » RapidXML » rapidxmlfile nie posiada domyślnego konstruktora, zamiast tego w konstruktorze przyjmuję nazwę pliku, w razie problemów z odczytem pliku zostaje wyrzucony wyjątek. Z tego powodu konstrukcje obiektu potrzebowałem mieć w bloku try-catch, z drugiej strony metoda » RapidXML » rapidxml » xml_documentparse nie kopiuje tekstu, dlatego obiekt czytający plik musi istnieć tak samo długo jak nasz » RapidXML » rapidxmlxml_document. Jeszcze parę słów o (2), mimo iż tylko chcemy przenieść węzeł z jednego miejsca do drugiego konieczne jest dokonanie kopiowania, nie ma obecnie metody przenoszącej węzeł.

Jak widać z powyższego kodu -jest tam opowiedziana pewna historia, której efekt widać na poniższym outpucie:
<ceo>
        <manager id="1" name="Jan" working_years="10">
                <developer id="2" name="Stefan" working_years="0">komunikator</developer>
                <developer id="3" name="Zygmunt" working_years="2">komunikator</developer>
        </manager>
        <manager id="2" name="Jan" working_years="10">
                <developer id="4" name="Andrzej" working_years="0">klient poczty</developer>
                <tester id="5" name="Andrzej" working_years="3">klient poczty</tester>
        </manager>
</ceo>

___________________________________________________________

<President>
        <manager id="1" name="Jan" sizeOfTeam="3">
                <developer id="2" name="Stefan" working_years="0">komunikator</developer>
                <developer id="3" name="Zygmunt" working_years="2">komunikator</developer>
                <developer id="4" name="Andrzej" working_years="0">klient poczty</developer>
        </manager>
</President>

Generowanie od ZERA własnego dokumentu XML

Wiemy jak modyfikować plik XML, ale bywa, że chcemy wygenerować go od zera.

Funkcje alokujące

Do alokacji atrybutów i węzłów służą funkcje:
allocate_nodePrzydziela pamięć na nowy węzeł. (metoda)
allocate_attributePrzydziela pamięć na nowy atrybut. (metoda)
C/C++
template < typename Ch = char >
xml_node < Ch >* memory_pool::allocate_node( node_type type, const Ch * name = 0, const Ch * value = 0, std::size_t name_size = 0, std::size_t value_size = 0 );
template < typename Ch = char >
xml_attribute < Ch >* memory_pool::allocate_attribute( const Ch * name = 0, const Ch * value = 0, std::size_t name_size = 0, std::size_t value_size = 0 );
Jak wcześniej wspomniałem, » RapidXML » rapidxmlxml_document dziedziczy po » RapidXML » rapidxmlmemory_pool oraz do alokacji dla konkretnego dokumentu należy użyć instancji nie innej niż tegoż dokumentu. Alokacja ma to do siebie, że może zostać wyrzucony wyjątek » standard C++bad_alloc.
Przydatna może się okazać jeszcze funkcja klonująca węzeł:
clone_nodeKopiuje węzeł oraz zawarte w nim węzły podrzędne i atrybuty. (metoda)
C/C++
template < typename Ch = char >
xml_node < Ch >* memory_pool < Ch >::clone_node( const xml_node < Ch >* sourceNode, xml_node < Ch >* destinationNode = 0 );
Funkcja 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ć).

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

Opis trudniejszych fragmentów jak zwykle pod kodem.
C/C++
#include <iostream>
#include <string>
using namespace std;

#include <rapidxml.hpp>
#include <rapidxml_print.hpp>
using namespace rapidxml;


int main()
{
    xml_document <> document;
   
    xml_node <>* html = document.allocate_node( node_element, "html" );
    xml_node <>* head = document.allocate_node( node_element, "head" );
   
    /**********************************Head************************************/
    //<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-2" />
    xml_node <>* metaHeader = document.allocate_node( node_element, "meta" );
    xml_attribute <>* attribute = document.allocate_attribute( "http-equiv", "Content-Type" );
    metaHeader->append_attribute( attribute );
    metaHeader->append_attribute( document.allocate_attribute( "content", "text/html; charset=iso-8859-2" ) );
   
    head->append_node( metaHeader );
    head->append_node( document.allocate_node( node_element, "title", "Strona wygenerowana w C++" ) );
   
    html->append_node( head );
   
    document.append_node( html );
   
    /**********************************Body************************************/
    xml_node <>* body = document.allocate_node( node_element, "body" );
   
    xml_node <>* paragraph = document.allocate_node( node_element, "p" );
   
    const char * content1 = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla fringilla vestibulum massa sed euismod.";
    paragraph->value( document.allocate_string( content1 ) );
    paragraph->append_attribute( document.allocate_attribute( "style", "text-align: center; color: red;" ) );
   
    body->append_node( paragraph );
    paragraph = document.clone_node( paragraph );
    const char * content2 = "Phasellus id ante ipsum. Nunc at volutpat neque, vel iaculis metus. ";
    paragraph->value( document.allocate_string( content2 ) );
    body->append_node( paragraph );
   
    xml_node <>* comment = document.allocate_node( node_comment, nullptr, "let's finally create content!" ); // (1)
    body->insert_node( paragraph->previous_sibling(), comment );
   
    html->append_node( body );
   
   
    cout << document;
}
Zacznę od wyjaśnienia nietechnicznego, skąd wziąłem ten tekst -na stronach internetowych zanim pojawi się treść często używa się tzw. Lorem ipsum. W punkcie (1) tworzę nowy węzeł, będący komentarzem, jak wiemy komentarze zawierają jedynie wartość, a nie zawierają nazwy, dlatego jako drugi argument podaję wartość nullptr.

A nasza wygenerowana strona internetowa wygląda tak:
<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;">Phasellus id ante ipsum. Nunc at volutpat neque, vel iaculis metus. </p>
                <p style="text-align: center; color: red;">w tym akapicie</p>
        </body>
</html>
Jeśli ktoś byłby zainteresowany bardziej fachową generacją stron internetowych w C++ odsyłam do innego mojego artykułu: [C++, cgicc] Tworzenie strony Internetowej w C++ jako skrypt CGI.

Inne funkcje biblioteki

Pliki XML mogą być znacznie bardziej skomplikowane niż te, które znamy na codzień, dlatego szkoda byłoby, gdyby nie były obsługiwane dodatkowe aspekty, które jednak rzadko, bo rzadko, ale jednak mogą się pojawiać w dokumentach XML.

Aby poznać możliwości XMLa należałoby przeczytać o nim np. na stronie konsorcium W3C. Czytając warto zwrócić uwagę na następujące tematy:

Tryby parsowania

We wszystkich powyższych przykładach zawsze używałem domyślnego trybu parsowania, jednak mamy ich znacznie więcej. Zależnie od trybu parsowania mamy wpływ na to, co chcemy wyszukiwać w naszym dokumencie XML. Oczywiście im więcej chcemy wykryć, tym wolniej dokonuje się parsowanie, dlatego najlepiej parsować tylko to, co wiemy, że będzie nas interesowało. Dostępne flagi parsowania:
parse_defaultUżywa domyślnych ustawień podczas przetwarzania tekstu. (stała)
parse_fastestPrzetwarza dane tak szybko, jak to możliwe bez utraty istotnych informacji. (stała)
parse_no_data_nodesWyłącza tworzenie węzłów rodzaju
rapidxml::node_data
 podczas przetwarzania tekstu. (stała)
parse_no_element_valuesIgnoruje wartości węzłów rodzaju
rapidxml::node_element
 podczas przetwarzania tekstu. (stała)
parse_no_string_terminatorsNie umieszcza znaków zerowych w przetwarzanym tekście. (stała)
parse_no_entity_translationNie przekształca odwołań znakowych w przetwarzanym tekście. (stała)
parse_no_utf8Wyłącza obsługę UTF-8 i używa ośmiobitowych znaków przy przetwarzaniu tekstu. (stała)
parse_declaration_nodeWłącza tworzenie węzłów rodzaju
rapidxml::node_declaration
 podczas przetwarzania tekstu. (stała)
parse_comment_nodesWłącza tworzenie węzłów rodzaju
rapidxml::node_comment
 podczas przetwarzania tekstu. (stała)
parse_doctype_nodeWłącza tworzenie węzłów rodzaju
rapidxml::node_doctype
 podczas przetwarzania tekstu. (stała)
parse_pi_nodesWłącza tworzenie węzłów rodzaju
rapidxml::node_pi
 podczas przetwarzania tekstu. (stała)
parse_validate_closing_tagsWłącza sprawdzanie nazw znaczników zamykających podczas przetwarzania tekstu. (stała)
parse_trim_whitespaceUsuwa wszystkie białe znaki znajdujące się przed oraz za danymi węzłów. (stała)
parse_normalize_whitespaceZamienia ciąg białych znaków na jedną spację podczas przetwarzania tekstu. (stała)
parse_non_destructiveNie zmienia danych źródłowych podczas przetwarzania tekstu. (stała)
parse_fullDąży do uzyskania jak największej ilości danych podczas przetwarzania tekstu. (stała)
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;
Jak widać można je ze sobą łączyć.

Typy węzłów

Każdy element ma swój typ, który można sprawdzić, zmienić przy użyciu: » RapidXML » rapidxml » xml_nodetype. Dostępne typy węzłów znajdują się tutaj: » RapidXML » rapidxmlnode_type.

Przykład na bardziej wyszukane parsowanie

Czymże byłoby powyższe wypisanie flag bez przykładu? Weźmy sobie przykładowy plik XML:
<?xml version="1.0" encoding="UTF-8"?>
<root>
  <!-- Nobody likes ceo -->
  <![CDATA[
    In the CDATA section we can use all sorts of reserved characters, even <, >, ", &and the document is still correct!
  ]]>
 
  <!DOCTYPE note
[
<!ELEMENT manager (developer,tester)>
<!ELEMENT developer (#PCDATA)>
<!ELEMENT tester (#PCDATA)>
  ]>
 
  <?processing instruction, named PI?>
  <?xml-stylesheet type="text/xsl" href="style.xsl"?>
 
  <node>
    <subnode>context</subnode>
  </node>
  <specialNode/>
</root>

Czas na przykłady parsowania przy użyciu paru wybranych flag parsowania:
Użyta flagawydruk dokumentu dla danej flagi
rapidxml::parse_full
jak wyżej
rapidxml::parse_fastest
<root>
        <node>
                <subnode>context</subnode>
        </node>
        <specialNode/>
</root>
rapidxml::parse_default
,
znana jako:
0
<root>
        <![CDATA[
    In the CDATA section we can use all sorts of reserved characters, even <, >, ", &and the document is still correct!
  ]]>
        <node>
                <subnode>context</subnode>
        </node>
        <specialNode/>
</root>
rapidxml::parse_declaration_node
<?xml version="1.0" encoding="UTF-8"?>
<root>
        <![CDATA[
    In the CDATA section we can use all sorts of reserved characters, even <, >, ", &and the document is still correct!
  ]]>
        <node>
                <subnode>context</subnode>
        </node>
        <specialNode/>
</root>
rapidxml::parse_comment_nodes
<root>
        <!-- Nobody likes ceo -->
        <![CDATA[
    In the CDATA section we can use all sorts of reserved characters, even <, >, ", &and the document is still correct!
  ]]>
        <node>
                <subnode>context</subnode>
        </node>
        <specialNode/>
</root>
rapidxml::parse_doctype_node
<root>
        <![CDATA[
    In the CDATA section we can use all sorts of reserved characters, even <, >, ", &and the document is still correct!
  ]]>
        <!DOCTYPE note
        [
        <!ELEMENT manager (developer,tester)>
        <!ELEMENT developer (#PCDATA)>
        <!ELEMENT tester (#PCDATA)>
  ]>
        <node>
                <subnode>context</subnode>
        </node>
        <specialNode/>
</root>
rapidxml::parse_pi_nodes
<root>
        <![CDATA[
    In the CDATA section we can use all sorts of reserved characters, even <, >, ", &and the document is still correct!
  ]]>
        <?processing instruction, named PI?>
        <?xml-stylesheet type="text/xsl" href="style.xsl"?>
        <node>
                <subnode>context</subnode>
        </node>
        <specialNode/>
</root>

Iteratory

Parser zawiera także iteratory dla węzłów i atrybutów, ale ich użycie nie jest wcale potrzebne. Dla zainteresowanych znajdują się one w pliku: rapidxml_iterators.hpp. Są to iteratory dwukierunkowe: » RapidXML » rapidxmlnode_iterator i » RapidXML » rapidxmlattribute_iterator. Przykład prostego ich użycia:
C/C++
#include <iostream>
#include <string>
#include <algorithm> // for_each
using namespace std;

#include <rapidxml.hpp>
#include <rapidxml_print.hpp>
#include <rapidxml_utils.hpp>
#include <rapidxml_iterators.hpp>
using namespace rapidxml;

int main()
{
    const char * fileName = "...";
   
    unique_ptr < rapidxml::file <>> xmlFile;
   
    xml_document <> document;
    try
    {
        xmlFile.reset( new rapidxml::file <>( fileName ) );
        document.parse < 0 >( xmlFile->data() );
    }
    catch( const parse_error & e )
    {
        std::cerr << e.what() << " here: " << e.where < char >() << std::endl;
        return - 1;
    }
    catch( const exception & e )
    {
        cerr << e.what() << endl;
        return - 2;
    }
   
    xml_node <>* root = document.first_node();
   
    for_each( node_iterator < char >( root ), node_iterator < char >(),[]( auto & node )
    {
        cout << node.name() << " ";
        for( attribute_iterator < char > it( & node ); it != attribute_iterator < char >(); ++it )
        {
            cout << it->name() << " = " << it->value() << ", ";
        }
        cout << endl;
    } );
}
wydruk:
manager id = 1, name = Jan, working_years = 10,
manager id = 2, name = Jan, working_years = 10,

Dodatkowe informacje na temat biblioteki RapidXML

Prawa własności

Zostały jeszcze prawa własności do opisania. Sprawa jest prosta, sprowadza się do następujących rzeczy:
W momencie parsowania parser dąży do jak najszybszego parsowania, dlatego nie alokuje ani nie kopiuje tekstu, a jedynie trzyma do niego wskaźniki. Ponadto tekst, jeśli nie jest włączona flaga » RapidXML » rapidxmlparse_non_destructive, jest modyfikowany przez parser (są wstawiane w odpowiednie miejsca znaki końca tekstu). Węzły i atrybuty nie roszczą sobie prawa własności do wskazywanego tekstu, dlatego go nie niszczą w obliczu własnej destrukcji.

Wyłączenie wyrzucania wyjątków

Jest to możliwe poprzez makro: » RapidXMLRAPIDXML_NO_EXCEPTIONS, w razie błędu zostanie wtedy wywołana funkcja: » RapidXML » rapidxmlparse_error_handler.

Dla chcących dowiedzieć się więcej

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

Mankamenty biblioteki RapidXML

Wszystko co informatyczne ma pewne wady. Nie jest od nich wolna biblioteka RapidXML, część z nich poznaliśmy w tym artykule. Poważną niedogodnością jest fakt, iż biblioteka nie jest rozwijana od paru lat, czego konsekwencją jest np. konieczność modyfikacji pliku
rapidxml / rapidxml_print.hpp
.
Lekką niedogodnością jest brak funkcji do wyjmowania wartości w innej postaci niż
char *
, ale od czego mamy m.in.funkcję std::stoi().
Jeszcze jednym mankamentem jest brak specjalnej obsługi przestrzeni nazw, na szczęście dla łaknących takiej funkcjonalności powstał fork biblioteki z dodaną obsługą przestrzenią nazw zwanym RapidXMLns, do pobrania na swojej stronie domowej. Biblioteka ta posiada m.in. następujące funkcje (i analogiczne do nich):
C/C++
namespace rapidxml_ns
{
    template < typename Ch = char >
    Ch * xml_base < Ch >::prefix() const;
   
    template < typename Ch = char >
    std::size_t xml_base < Ch >::prefix_size() const;
   
    template < typename Ch = char >
    Ch const * xml_base::namespace_uri() const;
   
    template < typename Ch = char >
    xml_attribute < Ch >* xml_attribute::next_attribute_ns( const Ch * namespace_uri, const Ch * local_name, bool local_name_case_sensitive = true ) const;
   
    template < typename Ch = char >
    xml_node < Ch >* xml_node::first_node_ns( const Ch * namespace_uri, const Ch * local_name, bool local_name_case_sensitive = true ) const;
}
Co ciekawe używanie przestrzeni nazw można wyłączyć przy użyciu flagi:
const int parse_no_namespace = 0x1000;
Jeszcze jedną zaletą tej biblioteki jest fakt wprowadzenia poprawek (m.in. pliku rapidxml/rapidxml_print.hpp).

Bonus na zakończenie -parsowanie stron internetowych

Jak wcześniej wspominałem niektóre strony internetowe da się parsować parserem XML, są takie, które wymagają tylko drobnych modyfikacji treści, aby dokonać parsowania (przykładowo strona Niepodam.pl daje się parsować po wyrzuceniu
<script>...</script>
). Co jeśli proste zabiegi nie pomogą, a bardzo zależy nam na parsowaniu strony?

Parsowanie danych przy użyciu API

Jeśli dana strona nie daje się parsować, a nie chcemy robić skomplikowanych operacji na tekście, warto sprawdzić, czy dana strona oferuje API. Używanie API jest znacznie lepszym pomysłem niż bezpośrednie parsowanie strony, gdyż strona może się w każdej chwili zmienić, a API z założenia rzadziej się zmienia, a nawet gdyby się zmieniło znacznie łatwiej jest dostosować się do zmian API niż strony internetowej.
Bywa, że dostępu do API wymaga rejestracji, na szczęście nie na każdej stronie.
Wzorem dla większości stron internetowych jest wg mnie Wikipedia.pl, która udostępnia rozbudowane https://www.mediawiki.org/wiki​/API:Main_page. Co fajne dla nas oferuje ona wiele formatów przesyłania odpowiedzi, w tym format XML.

Pobieranie strony internetowej

Tutaj musimy się zaopatrzyć w odpowiednią bibliotekę, dobry interfejs do pobierania stron internetowych z poziomu C++ oferują m.in. https://stackoverflow.com​/questions/4383864​/downloading-file-in-qt-from-url, https://pocoproject.org/docs​/Poco.Net.html. Jednak zdecydowałem się na użycie libCurl.
Dla Windowsa na tym serwisie jest opis jak instalować biblioteki w Code:Block [C++] Instalacja bibliotek w Code::Blocks, jeśli ktoś by się zdecydował na VS, oto instrukcje. Dla Linuxa wystarczy
sudo apt-get install curl
.

Przykładowy kod parsujący tekst zwrócony przez API Wikipedii

C/C++
#include <iostream>
#include <memory> // unique_ptr
#include <string>
using namespace std;

#include <rapidxml.hpp>
#include <rapidxml_print.hpp>
using namespace rapidxml;

#include <curl/curl.h>

////////////// functions to download page:
int bufferMoreData( 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;
}

unique_ptr < char[] > downloadPage( const char * page2Download )
{
    string buffer; // (1)
    buffer.reserve( 10000 );
   
    bool problemOccured = false;
    do
    {
        CURL * curl;
        CURLcode result;
       
        curl = curl_easy_init();
        if( curl )
        {
            curl_easy_setopt( curl, CURLOPT_URL, page2Download );
            curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, bufferMoreData );
            curl_easy_setopt( curl, CURLOPT_WRITEDATA, & buffer );
           
            result = curl_easy_perform( curl );
            if( CURLE_OK == result )
            {
                problemOccured = false;
            }
            else
            {
                cout << "!Problem occured during downloading website: " << page2Download << "!\n";
                problemOccured = true;
            }
            curl_easy_cleanup( curl );
        }
    }
    while( problemOccured );
   
    long long N = buffer.length();
    unique_ptr < char[] > out( new char[ N + 1 ] );
    buffer.copy( out.get(), N + 1 );
    out[ N ] = '\0';
   
    return out;
}

////////////// parsing helper functions:
template < typename Ch = char >
char * getPageContent( xml_document < Ch >& document )
{
    xml_node < Ch >* nodeApi = document.first_node();
    xml_node < Ch >* nodeParse = nodeApi->first_node();
    xml_node < Ch >* nodeText = nodeParse->first_node();
   
    return nodeText->value();
}

template < typename Ch = char >
string getTextWithoutHiperlinks( xml_node < Ch >* node ) // (2)
{
    string outputText;
    for( auto child = node->first_node(); child; child = child->next_sibling() )
    {
        if( rapidxml::node_data == child->type() )
        {
            outputText += child->value();
        }
        else if( rapidxml::node_element == child->type() )
        {
            outputText += child->value();
        }
    }
    return outputText;
}

template < typename Ch = char >
xml_node < Ch >* getNodeWithSententias( xml_document < Ch >& document ) // (3)
{
    xml_node < Ch >* nodeDiv = document.first_node( "div" );
   
    return nodeDiv->first_node( "ul" );
}

////////////// main()
int main()
{
    const char * pageUrlAddress = "https://pl.wikipedia.org/w/api.php?action=parse&format=xml&page=Izydor%20z%20Sewilli&prop=text";
   
    unique_ptr < char[] > wikipediaApiXmlPage = downloadPage( pageUrlAddress );
   
    xml_document <> dokument;
    try
    {
        dokument.parse < 0 >( wikipediaApiXmlPage.get() );
    }
    catch( const parse_error & p )
    {
        cerr << p.where < char >() << ": " << p.what() << endl;
        return - 1;
    }
   
    ////////////// now we reach inner content of the page:
    char * pageContent = getPageContent( dokument );
   
    xml_document <> documentInner;
    try
    {
        documentInner.parse < 0 >( pageContent );
    }
    catch( const parse_error & p )
    {
        cerr << p.where < char >() << ": " << p.what() << endl;
        return - 2;
    }
   
    const auto nodeWithSententias = getNodeWithSententias( documentInner );
    for( auto nodeLi = nodeWithSententias->first_node( "li" ); nodeLi; nodeLi = nodeLi->next_sibling( "li" ) )
    {
        cout << getTextWithoutHiperlinks( nodeLi ) << endl;
    }
} // g++ -lcurl -o wikipediaapi.exe  wikipediaapi.cc && ./wikipediaapi.exe
Output trzeba pozyskać na własną rękę, ale warto.
Aby zrozumieć ten kod trzeba wiedzieć, jak wygląda tekst zwrócony dla powyższego https://pl.wikipedia.org/w​/api.php?action=parse​&format=xml​&page=Izydor%20z%20Sewilli​&prop=text. Opiszę jednak dwie rzeczy (1) można było uniknąć jednego kopiowania tablicy znaków, jeśli nie używałbym
std::string
 kosztem czytelności.
W tekście przeszkadzały mi hiperłącza, dlatego w taki (2) sposób się ich pozbywam, oczywiście można też było uniknąć kopiowania.
Patrząc na strukturę kodu strony powinienem skakać do każdego elementu
<h2>
, następnie sprawdzać wartość pierwszego
<span>
 i w razie natrafienia na Sentencje z dzieł dopiero wtedy przeskoczyć na element
<ul>
 -to byłoby bezpieczniejsze w perspektywie przyszłości, ja poszedłem na skróty.
Pojawia się jeszcze pytanie, czemu parsuję dwukrotnie? Proszę zobaczyć na wygląd kodu zwróconego przez API Wikipedii -tam zamiast < i > są encje takie jak odpowiednio: &lt; i &gt;, natomiast w momencie zawołania » RapidXML » rapidxml » xml_basevalue zwrócony tekst posiada zamienione encje na właściwe znaki.

Parsowanie danych gdy nie ma ani API ani kod strony nie daje się parsować

Innymi słowy, jeśli strona nie jest zgodna z formatem XML. Wydaje się, że nie da się nic zrobić, a jednak jest wyjście -istnieje biblioteka w języku C, która konwertuje strony na format XHTML, nazywa się HTML Tidy.

Instalacja biblioteki Tidy HTML

Instrukcje jak zainstalować bibliotekę znajduje się w pliku README/BUILD.md, wcześniej jednak trzeba sobie pobrać i rozpakować.

Oto przykład wypisujący artykuły serwisu Cpp0x.pl, oczywiście przed dokonaniem parsowania należy stronę naprawić przy użyciu Tidy.
C/C++
#include <iostream>
#include <fstream>
#include <memory> // unique_ptr
#include <string>
#include <algorithm> // replace_if()
using namespace std;

#include <rapidxml.hpp>
#include <rapidxml_print.hpp>
using namespace rapidxml;

#include <cstdio>
#include <cerrno>

#include <tidy/tidy.h>
#include <tidy/tidybuffio.h>

#include <curl/curl.h>

int bufferMoreData( 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;
}

unique_ptr < char[] > downloadPage( const char * page2Download )
{
    string buffer; // (1)
    buffer.reserve( 10000 );
   
    bool problemOccured = false;
    do
    {
        CURL * curl;
        CURLcode result;
       
        curl = curl_easy_init();
        if( curl )
        {
            curl_easy_setopt( curl, CURLOPT_URL, page2Download );
            curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, bufferMoreData );
            curl_easy_setopt( curl, CURLOPT_WRITEDATA, & buffer );
           
            result = curl_easy_perform( curl );
            if( CURLE_OK == result )
            {
                problemOccured = false;
            }
            else
            {
                cout << "!Problem occured during downloading website: " << page2Download << "!\n";
                problemOccured = true;
            }
            curl_easy_cleanup( curl );
        }
    }
    while( problemOccured );
   
    long long N = buffer.length();
    unique_ptr < char[] > out( new char[ N + 1 ] );
    buffer.copy( out.get(), N + 1 );
    out[ N ] = '\0';
   
    return out;
}

// example from: http://tidy.sourceforge.net/libintro.html with my few modyfications
unique_ptr < char[] > websiteSource2Xhtml( const char * websiteContent )
{
    TidyBuffer output = { }, errbuf = { };
   
    TidyDoc tdoc = tidyCreate(); // Initialize "document"
   
    int rc = - 1;
   
    Bool ok = tidyOptSetBool( tdoc, TidyXhtmlOut, yes ); // Convert to XHTML
    if( ok )
         rc = tidySetErrorBuffer( tdoc, & errbuf ); // Capture diagnostics
   
    if( rc >= 0 )
         rc = tidyParseString( tdoc, websiteContent ); // Parse the input
   
    if( rc >= 0 )
         rc = tidyCleanAndRepair( tdoc ); // Tidy it up!
   
    if( rc >= 0 )
         rc = tidyRunDiagnostics( tdoc ); // Kvetch
   
    if( rc > 1 ) // If error, force output.
         rc =( tidyOptSetBool( tdoc, TidyForceOutput, yes ) ? rc
        : - 1 );
   
    if( rc >= 0 )
         rc = tidySaveBuffer( tdoc, & output ); // Pretty Print
   
    if( rc < 0 )
    {
        tidyRelease( tdoc );
        tidyBufFree( & errbuf );
        tidyBufFree( & output );
        throw runtime_error( "A severe error occurred with code: " + to_string( rc ) );
    }
   
    unique_ptr < char[] > websiteXhtml( new char[ output.size + 1 ] );
   
    strncpy( websiteXhtml.get(), reinterpret_cast < char *>( output.bp ), output.size + 1 ); // (2)
   
    tidyRelease( tdoc );
    tidyBufFree( & errbuf );
    tidyBufFree( & output );
   
    return websiteXhtml;
}

template < typename Ch = char >
auto getDivWithArticles( xml_document < Ch >& document )
{
    auto nodeHtml = document.first_node( "html" );
    auto nodeBody = nodeHtml->first_node( "body" );
    auto nodeForm = nodeBody->first_node( "form" );
    auto nodeDiv = nodeForm->first_node( "div" );
   
    for( auto n = nodeDiv->first_node( "div" ); n; n = n->next_sibling( "div" ) )
    {
        auto classAttribute = n->first_attribute( "class" );
        if( classAttribute && 0 == strcmp( "Article", classAttribute->value() ) )
        {
            return n;
        }
    }
    throw invalid_argument( "No articles" );
}

char * removeNewlines( char * text )
{
    const auto textSize = strlen( text );
   
    std::replace_if( text, text + textSize,[]( auto letter )
    {
        return '\n' == letter || '\r' == letter;
    }, ' ' );
   
    return text;
}

template < typename Ch = char >
bool hasH2ClassStatic( const xml_node < Ch >* node )
{
    if( strcmp( "h2", node->name() ) )
         return false;
   
    const auto attributeClass = node->first_attribute( "class" );
    return attributeClass && 0 == strcmp( "Static", attributeClass->value() );
}

template < typename Ch = char >
void printAllHiperlinkValues( xml_node < Ch >* node )
{
    if( nullptr == node || hasH2ClassStatic( node ) )
    {
        return;
    }
   
    for( auto n = node->first_node(); n; n = n->next_sibling() )
    {
        if( rapidxml::node_element == n->type() )
        {
            if( 0 == strcmp( "a", n->name() ) )
            {
                static unsigned articleCounter;
                char * articleTitle = n->value();
               
                cout << ++articleCounter << ")\t" << removeNewlines( n->value() ) << '\n';
            }
            else
            {
                printAllHiperlinkValues( n );
            }
        }
    }
}

int main( int argc, char ** argv )
{
    unique_ptr < char[] > pageSource, fixedPageSource;
   
    xml_document <> document;
    try
    {
        pageSource = downloadPage( "http://cpp0x.pl/artykuly/" );
       
        fixedPageSource = websiteSource2Xhtml( pageSource.get() );
       
        document.parse < 0 >( fixedPageSource.get() );
    }
    catch( const parse_error & e )
    {
        std::cerr << e.what() << " here: " << e.where < char >() << std::endl;
        return - 1;
    }
    catch( const std::exception & e )
    {
        std::cerr << e.what() << std::endl;
        return - 2;
    }
   
    auto nodeArticles = getDivWithArticles( document );
   
    for( auto divWithArticleCategory = nodeArticles->first_node( "div" ); divWithArticleCategory; divWithArticleCategory = divWithArticleCategory->next_sibling( "div" ) )
    {
        auto attributeClass = divWithArticleCategory->first_attribute( "class" );
        if( attributeClass && 0 == strcmp( "DispCateg", attributeClass->value() ) )
        {
            printAllHiperlinkValues( divWithArticleCategory );
        }
    }
}
Czas na wyjaśnienie, oczywiście nie będę uzasadniał pewnego przechodzenia, które wynika z obecnej struktury strony Cpp0x.pl. Jeśli chodzi o (1) typ jest
byte *
, dlatego konieczne jest rzutowanie.
W trybie DEBUG niestety biblioteka Tidy bardzo pluje po ekranie wszystkie logi, na moje oko nie da się tego wyłączyć bez modyfikacji kodu tejże biblioteki.