XPATHS e namespaces padrão
-
08-06-2019 - |
Pergunta
Qual é a história por trás do XPath e do suporte para namespaces?O XPath como especificação precedeu os namespaces?Se eu tiver um documento onde os elementos receberam um namespace padrão:
<foo xmlns="uri" />
Parece que algumas das bibliotecas do processador XPath não reconhecem //foo
por causa do namespace, enquanto outros o farão.A opção que minha equipe pensou é adicionar um prefixo de namespace usando expressões regulares ao XPath (você pode adicionar um prefixo de namespace via XmlNameTable), mas isso parece frágil, já que XPath é uma linguagem muito flexível quando se trata de testes de nó.
Existe um padrão que se aplica a isso?
Minha abordagem é um pouco hackeada, mas parece funcionar bem;Eu removo o xmlns
declaração com uma pesquisa/substituição e, em seguida, aplique XPath.
string readyForXpath = Regex.Replace(xmldocument, "xmlns=\".+\"", String.Empty );
Essa é uma abordagem justa ou alguém resolveu isso de forma diferente?
Solução
Tentei algo semelhante ao que o palehorse propôs e não consegui fazer funcionar.Como estava obtendo dados de um serviço publicado, não consegui alterar o xml.Acabei usando XmlDocument e XmlNamespaceManager assim:
XmlDocument doc = new XmlDocument();
doc.LoadXml(xmlWithBogusNamespace);
XmlNamespaceManager nSpace = new XmlNamespaceManager(doc.NameTable);
nSpace.AddNamespace("myNs", "http://theirUri");
XmlNodeList nodes = doc.SelectNodes("//myNs:NodesIWant",nSpace);
//etc
Outras dicas
Você precisa de nome local():
http://www.w3.org/TR/xpath#function-local-name
Para berço de http://jcooney.net/archive/2005/08/09/6517.aspx:
<foo xmlns='urn:foo'>
<bar>
<asdf/>
</bar>
</foo>
Esta expressão corresponderá ao elemento “bar”:
//*[local-name()='bar']
Este não vai:
//bar
O problema é que um elemento sem um namespace é declarado como estando no namespace NULL - portanto, se //foo correspondesse ao namespace que você considera o 'padrão', não haveria como se referir a um elemento no namespace nulo.
Lembre-se também de que o prefixo de um namespace é apenas uma convenção abreviada, o nome real do elemento (Nome qualificado ou QName, abreviadamente) consiste no namespace completo e no nome local.Alterar o prefixo de um namespace não altera a 'identidade' de um elemento - se estiver no mesmo namespace e no mesmo nome local, então é o mesmo tipo de elemento, mesmo que o prefixo seja diferente.
XPath 2.0 (ou melhor, XSLT 2.0) tem o conceito de 'namespace xpath padrão'.Você pode definir o atributo xpath-default-namespace no elemento xsl:stylesheet.
Se você estiver tentando usar xslt, poderá adicionar o namespace à declaração da folha de estilo.Se você fizer isso, certifique-se de que existe um prefixo ou não funcionará.Se o XML de origem não tiver um prefixo, tudo bem, você adiciona seu próprio prefixo na folha de estilo.
Folha de estilo
<xsl:stylesheet
xmlns:fb="uri"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="fb:foo/bar">
<!-- do stuff here -->
</xsl:template>
</xsl:stylsheet>
Ou algo assim.
Usando libxml parece que funciona:
http://xmlsoft.org/examples/xpath1.c
int
register_namespaces(xmlXPathContextPtr xpathCtx, const xmlChar* nsList) {
xmlChar* nsListDup;
xmlChar* prefix;
xmlChar* href;
xmlChar* next;
assert(xpathCtx);
assert(nsList);
nsListDup = xmlStrdup(nsList);
if(nsListDup == NULL) {
fprintf(stderr, "Error: unable to strdup namespaces list\n");
return(-1);
}
next = nsListDup;
while(next != NULL) {
/* skip spaces */
while((*next) == ' ') next++;
if((*next) == '\0') break;
/* find prefix */
prefix = next;
next = (xmlChar*)xmlStrchr(next, '=');
if(next == NULL) {
fprintf(stderr,"Error: invalid namespaces list format\n");
xmlFree(nsListDup);
return(-1);
}
*(next++) = '\0';
/* find href */
href = next;
next = (xmlChar*)xmlStrchr(next, ' ');
if(next != NULL) {
*(next++) = '\0';
}
/* do register namespace */
if(xmlXPathRegisterNs(xpathCtx, prefix, href) != 0) {
fprintf(stderr,"Error: unable to register NS with prefix=\"%s\" and href=\"%s\"\n", prefix, href);
xmlFree(nsListDup);
return(-1);
}
}
xmlFree(nsListDup);
return(0);
}