Skip to main content

XML i XPath

Jeśli myślałeś kiedykolwiek nad stworzeniem aplikacji mobilnej korzystającej z dostępu do Internetu, prawdopodobnie musiałeś użyć także XML.

Wiele usług sieciowych za standard wymiany danych uznaje właśnie XML, np. protokół SOAP (ang. Simple Object Access Protocol).

XML (ang. Extensible Markup Language, w wolnym tłumaczeniu Rozszerzalny Język Znaczników) – uniwersalny język formalny przeznaczony do reprezentowania różnych danych w strukturalizowany sposób.

http://pl.wikipedia.org/wiki/XML

Ten artykuł nie będzie omawiał samych usług sieciowych, ale sposób przetwarzania XML i pokrótce język XPath.

 XPath (ang. XML Path Language, w wolnym tłumaczeniu Język ścieżek XML, Język ścieżek rozszerzalnego języka znaczników) – język służący do adresowania części dokumentu XML.

http://pl.wikipedia.org/wiki/XPath

W bada za przetwarzanie XML odpowiada biblioteka Libxml2, której pełna dokumentacja znajduje się na stronie projektu:
http://xmlsoft.org/

Uprawnienia:
Brak

Plik nagłówkowy:
FXml.h

Przestrzeń nazw:
Osp::Xml

Format XML

Czyli nasze przykładowe dane, które będziemy przetwarzali. Postanowiłem, że będzie to lista użytkowników z dodatkowymi informacjami opisującymi użytkownika.

<users>
  <user>
    <username>markac</username>
    <password>1234</password>
    <www>BadaDev.pl</www>
  </user>
  <user>
    <username>jozek</username>
    <password>5678</password>
    <www></www>
  </user>
</users>

Jest to bardzo prosta struktura danych i o to chodzi. Nie zależy nam przecież na komplikowaniu.
Na tym etapie nie będę zagłębiał się za bardzo w temat, czy lepiej użyć elementów czy atrybutów.
Ta sama struktura danych mogłaby wyglądać również tak:

<users>
  <user username="markac" password="1234" www="BadaDev.pl" />
  <user username="jozek" password="5678" www="" />
</users>

Generalnie atrybutów używam do zapisywania meta-informacji opisujących dany Node, czyli informacje o informacji 🙂

Rozszerzmy naszą strukturę danych o meta-informacje, aby przykład był bardziej z życia i jak najbardziej wyczerpujący:

<users version="2.0">
  <user created_at="2010-01-01" modified_at="2010-05-28">
    <username>markac</username>
    <password>1234</password>
    <www>BadaDev.pl</www>
  </user>
  <user created_at="2011-03-20" modified_at="2011-03-29">
    <username>jozek</username>
    <password>5678</password>
    <www></www>
  </user>
</users>

Atrybuty, które zostały dodane to:

  • version – wersja drzewa (np. do rozpoznania formatu XML lub sprawdzenia aktualności danych),
  • created_at – data utworzenia,
  • modified_at – data modyfikacji.

Innymi atrybutami mogą być np.:

  • removed – użytkownik usunięty.

Implementacja – inicjalizacja

String xmlData(L"
  <users version="2.0">
    <user created_at="2010-01-01" modified_at="2010-05-28">
      <username>markac</username>
      <password>1234</password>
      <www>BadaDev.pl</www>
    </user>
    <user created_at="2011-03-20" modified_at="2011-03-29">
      <username>jozek</username>
      <password>5678</password>
      <www></www>
    </user>
  </users>");

xmlDocPtr pDocument = NULL;
xmlXPathContextPtr xpathCtx = NULL;
xmlXPathObjectPtr xpathObj = NULL;

ByteBuffer* pBuffer = Utility::StringUtil::StringToUtf8N(xmlData);
pDocument = xmlParseDoc((unsigned char*)pBuffer->GetPointer());

// Utworzenie kontekstu dla wyrażenia xpath
xpathCtx = xmlXPathNewContext(pDocument);
if(xpathCtx == NULL) {
  AppLog("Error: unable to create new XPath context");
  // Zwolnij zajęte zasoby
}

Implementacja – odczyt atrybutu version

// Odczyt atrybutu version elementu users z kontekstu xpathCtx
xpathObj = xmlXPathEvalExpression((xmlChar*)"string(/users/@version)", xpathCtx);
if(xpathObj != NULL) {
  String verStr = String((char*)xpathObj->stringval);
  AppLogDebug("Version: %S", verStr.GetPointer());
  xmlXPathFreeObject(xpathObj);
}

Wyrażenia XPath

Na chwilę obecną odczytaliśmy jedynie atrybut version z elementu users.
Odpowiada za to linia:

xpathObj = xmlXPathEvalExpression((xmlChar*)"string(/users/@version)", xpathCtx);

i wyrażenie string(/users/@version), które oznacza:

  • znajdź atrybut version w głównym elemencie users (ścieżka absolutna),
  • rzutuj zawartość atrybutu na String.

Jeśli zwrócony wynik to tablica, należy zastosować indeks, np.:

string(/users/@version[1])

co oznacza: pobierz tylko pierwszy element ze znalezionych atrybutów.

Możliwe typy danych:

  • string,
  • number,
  • boolean.

Ścieżki XPath

Oto najważniejsze informacje na temat lokalizacji danych.

Ścieżka XPath Znaczenie
namenode Wszyscy potomkowie o nazwie nodename
/ Główny element (ścieżka absolutna)
// Wszystkie elementy z dokumentu
. Aktualny element
.. Element rodzica
@ Atrybut

Przykłady:

Ścieżka XPath Znaczenie
/users/user Wszystkie elementy user z elementu users
/users/user[2] Tylko user o indeksie 2 (numeracja od 1) z elementu users
//user[@created_at] Wszystkie elementy w dokumencie o nazwie user, które zawierają atrybut createrd_at

 

Implementacja – odczyt użytkowników

Pozostało nam jeszcze pobrać cała listę użytkowników i wyświetlić dane w oknie konsoli Output.

xpathObj = xmlXPathEvalExpression((xmlChar*)"/users/user", xpathCtx);

if (xpathObj != NULL && xpathObj->nodesetval != NULL) // Są wyniki
{
  // Ilość znalezionych użytkowników
  AppLogDebug("Users count: %d", xpathObj->nodesetval->nodeNr);

  xmlNodePtr pCurrentElement = NULL;
  for(int n = 0; n < xpathObj->nodesetval->nodeNr; n++)
  {
    pCurrentElement = xpathObj->nodesetval->nodeTab[n]->children;
    while (pCurrentElement != null)
    {
      if (pCurrentElement->type == XML_ELEMENT_NODE)
      {
        String key = String((const char*)pCurrentElement->name).GetPointer();
        String val = pCurrentElement->children == null ? "" : String((const char*)pCurrentElement->children->content);

        AppLogDebug("%ls = %ls",
          key.GetPointer(),
          val.GetPointer()
        );

        if (xmlStrcmp(pCurrentElement->name, (const xmlChar*) "username") == 0)
        {
          // jeśli element to username, wykonaj jakąś akcję...
        }
      }
      pCurrentElement = pCurrentElement->next;
    }
  }

  xmlXPathFreeObject(xpathObj);
}

Zwalnianie zasobów

Po wystąpieniu błędu lub zakończeniu przetwarzania danych XML powinieneś zadbać o zwolnienie zajętych zasobów.

xmlFreeDoc(pDocument);
xmlCleanupParser();
delete pBuffer;

Wynik

Zapraszam do dyskusji 🙂

markac

Full-stack Web Developer