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:
V příkladu aktuální node (uzel) nenastavuji, takže se bere jako aktuální kořenový uzel.
Začátek příkladu vypadá takto:
- /*------------------------------------------------*/
- /* 27aDom/xpath.c */
- #include <stdio.h>
- #include <libxml/parser.h>
- #include <libxml/xpath.h>
- #include <libxml/xpathInternals.h>
- {
- xmlDocPtr doc;
- xmlXPathContextPtr context;
- xmlXPathObjectPtr result;
- int i;
- "/",
- "/sx:config",
- "/sx:config/sx:databases/sx:database",
- "//sx:database/@type",
- "count(/sx:config/sx:databases/node())",
- "//sx:database[@type='mysql']/sx:name/text()",
- "concat("
- "'mysql -u ',"
- "//sx:database[@type='mysql']/sx:name/text(),"
- "' -p'," "//sx:database[@type='mysql']/sx:password/text())",
- "boolean(//sx:database[@type='mysql']/@enabled)",
- "boolean(//sx:database[@type='postgres']/@enabled)",
- NULL
- };
- doc = xmlParseFile(filename);
- }
- context = xmlXPathNewContext(doc);
- }
- xmlXPathRegisterNs(context, (xmlChar *) "sx", (xmlChar *)
- "http://www.sallyx.org/sally/c/linux/xml");
- i = 0;
- result = vyhodnotXPath(expression[i], context);
- i++;
- tiskniVysledek(result);
- xmlXPathFreeObject(result);
- }
- xmlXPathFreeContext(context);
- xmlFreeDoc(doc);
- }
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:
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á:
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:
{
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:
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: