XPath

Po přečtení kapitoly o DOM jste si možná řekli, jestli neexistuje náhodou nějaký jednodušší způsob, jak se prokousat k elementu, který vás zajímá. Existuje. A říká se mu XPath.

XPath

XPath, XML Path language, je standardizovaný způsob, jak popsat cestu k nějakému elementu, atributu nebo textu, nebo i třeba počtu elementů nebo součtu hodnot vybranýh elementů.

XPath může vypadat třeba takto:

/sx:config/sx:databases/sx:database

To je cesta k elementu sx:database, který je dítětem elementu sx:databases, který je dítětem kořenového elementu sx:config. Můžete si to představit podobně jako cestu v souborovém systému Souborový systém i XML tvoří stromovou strukturu. Oba mají nějaký kořen (v souborovém systému na linuxu je to /, na Windows např. c:).

V této kapitole nebudu popisovat XPath jako takový (jak se tvoří XPath cesty), ale pouze jak se můžete pomocí knihovny libxml2 dotázat na výsledek této cesty.

Knihovna libxml2 výrazy XPath aplikuje na xmlDoc, se kterým jste se mohli seznámit v kapitole o DOM.

XPath může začínat absolutní cestou (když začíná jedním lomítkem), nebo relativní cestou (když nezačíná lomítkem). Relativní cesta začíná od „aktuálního“ elementu. V XPath můžete/musíte použivat namespace. Tyto a další věci ovlivňují, jak se XPath vyhodnotí. Knihovna libxml2 používá pro uložení těchto informací strukturu xmlXPathContext.

xmlXPathContext vytvoříte voláním funkce xmlXPathNewContext(doc);, kde doc je ukazatel typu xmlDocPtr.

Aktuální element v kontextu můžete nastavit takto:

int ret;
xmlNodePtr node;
xmlXPathContextPtr context;
...
ret = xmlXPathSetContextNode(node, context);
if(ret) {
    fprintf(stderr,"Chyba. Je cur z dokumentu doc?\n");
}

V příkladu aktuální node (uzel) nenastavuji, takže se bere jako aktuální kořenový uzel.

Začátek příkladu vypadá takto:

  1. /*------------------------------------------------*/
  2. /* 27aDom/xpath.c                                 */
  3.  
  4. #include <stdio.h>
  5. #include <libxml/parser.h>
  6. #include <libxml/xpath.h>
  7. #include <libxml/xpathInternals.h>
  8.  
  9. xmlXPathObjectPtr vyhodnotXPath(char *expression, xmlXPathContextPtr context);
  10. void tiskniVysledek(xmlXPathObjectPtr result);
  11. void tiskniNodeSet(xmlXPathObjectPtr result);
  12.  
  13. int main(void)
  14. {
  15.     xmlDocPtr doc;
  16.     xmlXPathContextPtr context;
  17.     xmlXPathObjectPtr result;
  18.     int i;
  19.     char *filename = "config.xml";
  20.     char *expression[] = {
  21.         "/",
  22.         "/sx:config",
  23.         "/sx:config/sx:databases/sx:database",
  24.         "//sx:database/@type",
  25.         "count(/sx:config/sx:databases/node())",
  26.         "//sx:database[@type='mysql']/sx:name/text()",
  27.         "concat("
  28.             "'mysql -u ',"
  29.             "//sx:database[@type='mysql']/sx:name/text(),"
  30.             "' -p'," "//sx:database[@type='mysql']/sx:password/text())",
  31.         "boolean(//sx:database[@type='mysql']/@enabled)",
  32.         "boolean(//sx:database[@type='postgres']/@enabled)",
  33.         NULL
  34.     };
  35.  
  36.     doc = xmlParseFile(filename);
  37.     if (doc == NULL) {
  38.         fprintf(stderr, "Document se nepodařilo naparsovat.\n");
  39.         return 1;
  40.     }
  41.  
  42.     context = xmlXPathNewContext(doc);
  43.     if (context == NULL) {
  44.         fprintf(stderr, "Nepodařilo se vytvořit context\n");
  45.         return 1;
  46.     }
  47.  
  48.     xmlXPathRegisterNs(context, (xmlChar *) "sx", (xmlChar *)
  49.                "http://www.sallyx.org/sally/c/linux/xml");
  50.  
  51.     i = 0;
  52.     while (expression[i]) {
  53.         printf("Expression:\n\t%s\n", expression[i]);
  54.         result = vyhodnotXPath(expression[i], context);
  55.         i++;
  56.         if (!result)
  57.             continue;
  58.         tiskniVysledek(result);
  59.         xmlXPathFreeObject(result);
  60.  
  61.     }
  62.  
  63.     xmlXPathFreeContext(context);
  64.     xmlFreeDoc(doc);
  65.  
  66.     return 0;
  67. }
  68.  

V proměnné expression jsou XPath výrazy, které budu v programu testovat oproti souboru config.xml.

Nejdříve vytvořím DOM voláním funkce doc = xmlParseFile(filename).
Pak vytvořím kontext voláním context = xmlXpathNewContext(doc);.
Poslední důležitá věc před testováním XPath výrazu je registorvání namespace funkcí xmlXPathRegisterNs().

Můžete se oprávněně ptát, proč si nenačte libxml2 informace o namespace z XML souboru. Kdo ví. Zajímavostí může být, že si můžete pro namespace vymyslet jinou zkratku – místo sx třeba xx. Pak se v XPath výrazech můžete odkazovat na xx:config místo sx:config. Důležité totiž je jen to, aby si odpovídalo URI z XML souboru a URI ze zaregistrovaného namespace (tj. http://www.sallyx.org/sally/c/linux/xml)

Po této krátké přípravě se v programu projde pole expression, vyhodnotí se, vypíše se výsledek a uvolní se paměť proměnné s výsledkem (na kterou odkazuje result) pomocí funkce xmlXPathFreeObject().

Na konec se ještě uvolní paměť kontextu a DOM dokumentu (xmlXPathFreeContext() a xmlFreeDoc()).

K vyhodnocení XPath výrazu jsem si napsal tuto funkci:

  1. xmlXPathObjectPtr vyhodnotXPath(char *expression, xmlXPathContextPtr context)
  2. {
  3.     xmlXPathCompExprPtr comp;
  4.     xmlXPathObjectPtr result;
  5.  
  6.     /* result = xmlXPathEvalExpression(expression, context); */
  7.     comp = xmlXPathCompile((xmlChar *) expression);
  8.     if (comp == NULL) {
  9.         fprintf(stderr, "Chyba v xmlXPathCompile()\n");
  10.         return NULL;
  11.     }
  12.     result = xmlXPathCompiledEval(comp, context);
  13.  
  14.     if (result == NULL) {
  15.         fprintf(stderr, "Chyba v xmlXPathEvalExpression()\n");
  16.         return NULL;
  17.     }
  18.  
  19.     return result;
  20. }
  21.  

Funkce nejdřív výraz zkompiluje pomocí xmlXPathCompile() a pak získá výsledek pomocí xmlXPathCompiledEval(). Mohl jsem taky získat výsledek rovnou, bez kompilace, voláním funkce xmlXPathEvalExpression(). Asi by to v tomto příkladu dávalo i větší smysl. Kompilace se totiž používá tehdy, když hodláte XPath výraz použít několikrát. Pak kompilace ušetří čas. (xmlXPathEvalExpression() si výraz uvnitř sebe taky musí zkompilovat a to vždy, když ji voláte. xmlXPathCompiledEval() dostane už výraz zkompilovaný, takže její opakované volání ušetří dost času.)

Při tištění výsledku se nejdříve musím podívat, jaký typ výsledku result obsahuje. Ve funkci tiskniVysledek() zpracovám jen ty nejčastější možné typy výsledků. Podle typu výsledku pak rozhoduji, která položka struktury xmlXPathObject mě zajímá:

  1. void tiskniVysledek(xmlXPathObjectPtr result)
  2. {
  3.     /* http://xmlsoft.org/html/libxml-xpath.html#xmlXPathObjectType */
  4.     switch (result->type) {
  5.     case XPATH_BOOLEAN:
  6.         printf("\t = %s\n", result->boolval ? "True" : "False");
  7.         break;
  8.     case XPATH_STRING:
  9.         printf("\t = '%s'\n", result->stringval);
  10.         break;
  11.     case XPATH_NUMBER:
  12.         printf("\t = %f\n", result->floatval);
  13.         break;
  14.     case XPATH_NODESET:
  15.         tiskniNodeSet(result);
  16.         break;
  17.     default:
  18.         printf("not implemented\n");
  19.     }
  20. }
  21.  

Pro tisk node setu (tedy seznamu uzlů) jsem napsal extra funkci. Pokud není node set prázdný, procházím všechny jeho uzly a podle typu uzlu rozhoduju, jak uzel vytisknu:

void tiskniNodeSet(xmlXPathObjectPtr result)
{
    xmlNodePtr node;
    int i;
    if (xmlXPathNodeSetIsEmpty(result->nodesetval)) {
        printf("\t = NodeSet JE PRÁZDNÁ");
        return;
    }
    for (i = 0; i < result->nodesetval->nodeNr; i++) {
        node = result->nodesetval->nodeTab[i];
        /* http://xmlsoft.org/html/libxml-tree.html#xmlElementType */
        switch (node->type) {
        case XML_DOCUMENT_NODE:
            printf("\t = /\n");
            break;
        case XML_ELEMENT_NODE:
            if ((long int)node->ns > 0)
                printf("\t = <%s:%s/>\n",
                       node->ns->prefix ? node->
                       ns->prefix : node->ns->href,
                       node->
                       name ? (char *)node->name : "(null)");
            else
                printf("\t = <%s/>\n",
                       node->
                       name ? (char *)node->name : "(null)");
            break;
        case XML_ATTRIBUTE_NODE:
            printf("\t = @%s=%s\n", node->name,
                   node->children[0].content);
            break;
        case XML_TEXT_NODE:
            printf("\t = '%s'\n", node->content);
            break;
        case XML_CDATA_SECTION_NODE:
            printf("\t = <![CDATA[%s]]>'\n", node->content);
            break;
        default:
            printf("?????\n");
        }
    }
}

/*------------------------------------------------*/

Zase jsem vybral jen ty nejčastější typy uzlů. Myslím ale, že si s mým výběrem vystačíte :-).

Nainstalujte si libxml2-devel. Bez toho tento příklad nepřeložíte. Překlad může vypadat takto:

$ gcc -o xpath -g -Wall `xml2-config --cflags --libs` xpath.c

Výstup z programu je následující:

./xpath
Expression:
        /
         = /
Expression:
        /sx:config
         = <sx:config/>
Expression:
        /sx:config/sx:databases/sx:database
         = <http://www.sallyx.org/sally/c/linux/xml:database/>
         = <http://www.sallyx.org/sally/c/linux/xml:database/>
Expression:
        //sx:database/@type
         = @type=mysql
         = @type=postgres
Expression:
        count(/sx:config/sx:databases/node())
         = 5.000000
Expression:
        //sx:database[@type='mysql']/sx:name/text()
         = <![CDATA[petr]]>'
Expression:
        concat('mysql -u ',//sx:database[@type='mysql']/sx:name/text(),' -p',//sx:database[@type='mysql']/sx:password/text())
         = 'mysql -u petr -p*****'
Expression:
        boolean(//sx:database[@type='mysql']/@enabled)
         = True
Expression:
        boolean(//sx:database[@type='postgres']/@enabled)
         = False

A tím je příklad u konce. Pokud XPath znáte a umíte, určitě víte, jak jsou užitečné a jak usnadňují práci s XML dokumentem. Pokud je pro vás XPath novinkou, možná v něm trochu tápete. Budete mi tedy muset věřit, že jsou XPath výrazy užitečné a velmi usnadňují práci s XML dokumentem :-).

Použité funkce

Oproti kapitole o DOM přibyli tyto funkce:

/**
* Create a new xmlXPathContext
* @param doc the XML document
* @return the xmlXPathContext just allocated. The caller will need to free it.
*/

xmlXPathContextPtr xmlXPathNewContext (xmlDocPtr doc);

Dokumentace: xmlXPathNewContext

/**
* Sets 'node' as the context node.
* The node must be in the same document as that associated with the context.
* @param node   the node to to use as the context node
* @param ctx    the XPath context
* @return -1 in case of error or 0 if successful
*/

int xmlXPathSetContextNode (xmlNodePtr node, xmlXPathContextPtr ctx);

Dokumentace: xmlXPathSetContextNode

/**
* Register a new namespace. If @ns_uri is NULL it unregisters the namespace
* @param ctxt: the XPath context
* @param prefix: the namespace prefix cannot be NULL or empty string
* @param ns_uri: the namespace name
* @return 0 in case of success, -1 in case of error
*/

int xmlXPathRegisterNs (xmlXPathContextPtr ctxt, const xmlChar * prefix, const xmlChar * ns_uri);

Dokumentace: xmlXPathRegisterNs

/**
* Free up an xmlXPathObjectPtr object.
* @param obj the object to free
*/

void xmlXPathFreeObject (xmlXPathObjectPtr obj);

Dokumentace: xmlXPathFreeObject

/**
* Free up an xmlXPathContext
* @param ctxt the context to free
*/

void xmlXPathFreeContext (xmlXPathContextPtr ctxt);

Dokumentace: xmlXPathFreeContext

/**
* Evaluate the XPath expression in the given context.
* @param str the XPath expression
* @param ctxt the XPath context
* @return the xmlXPathObjectPtr resulting from the evaluation or NULL. the caller has to free the object.
*/

xmlXPathObjectPtr xmlXPathEvalExpression (const xmlChar * str, xmlXPathContextPtr ctxt);

Dokumentace: xmlXPathEvalExpression

/**
* Compile an XPath expression
* @param str the XPath expression
* @return the xmlXPathCompExprPtr resulting from the compilation or NULL. the caller has to free the object.
*/

xmlXPathCompExprPtr xmlXPathCompile (const xmlChar * str);

Dokumentace: xmlXPathCompile

/**
* Evaluate the Precompiled XPath expression in the given context.
* @param comp the compiled XPath expression
* @param ctx the XPath context
* @return the xmlXPathObjectPtr resulting from the evaluation or NULL. the caller has to free the object.
*/

xmlXPathObjectPtr xmlXPathCompiledEval (xmlXPathCompExprPtr comp, xmlXPathContextPtr ctx);

Dokumentace: xmlXPathCompiledEval

/**
* Checks whether @ns is empty or not.
* @param nodeset
* @return TRUE if @ns is an empty node-set.
*/

bool xmlXPathNodeSetIsEmpty(xmlNodeSetPtr nodeset);

Dokumentace: xmlXPathNodeSetIsEmpty

Kromě toho jsem používal atributy struktur:

Komentář Hlášení chyby
Created: 6.9.2014
Last updated: 18.11.2014
Tato stánka používá ke svému běhu cookies, díky kterým je možné monitorovat, co tu provádíte (ne že bych to bez cookies nezvládl). Také vás tu bude špehovat google analytics. Jestli si myslíte, že je to problém, vypněte si cookies ve vašem prohlížeči, nebo odejděte a už se nevracejte :-). Prohlížením tohoto webu souhlasíte s používáním cookies. Dozvědět se více..