XPath in SimpleXML per spazi dei nomi predefiniti senza bisogno di prefissi

Ho un documento XML a cui è associato uno spazio dei nomi predefinito, ad es

<foo xmlns="http://www.example.com/ns/1.0"> ... </foo> 

In realtà questo è un documento XML complesso che si conforma a uno schema complesso. Il mio task è quello di analizzare alcuni dati da esso. Per aiutarmi, ho un foglio di calcolo di XPath. L'XPath è piuttosto profondamente annidato, ad es

 level1/level2/level3[@foo="bar"]/level4[@foo="bar"]/level5/level6[2] 

La persona che genera XPath è un esperto nello schema, quindi partecipo all'assunzione che non riesco a semplificarlo o che utilizzo le scorciatoie traversali dell'object.

Sto usando SimpleXML per analizzare tutto. Il mio problema ha a che fare con il modo in cui viene gestito lo spazio dei nomi predefinito.

Dato che c'è un namespace predefinito sull'elemento radice, non posso farlo

 $xml = simplexml_load_file($somepath); $node = $xml->xpath('level1/level2/level3[@foo="bar"]/level4[@foo="bar"]/level5/level6[2]'); 

Devo registrare lo spazio dei nomi , assegnarlo a un prefisso e quindi usare il prefisso nel mio XPath, ad es

 $xml = simplexml_load_file($somepath); $xml->registerXPathNamespace('myns', 'http://www.example.com/ns/1.0'); $node = $xml->xpath('myns:level1/myns:level2/myns:level3[@foo="bar"]/myns:level4[@foo="bar"]/myns:level5/myns:level6[2]'); 

Aggiungere i prefissi non sarà gestibile a lungo termine.

Esiste un modo corretto per gestire gli spazi dei nomi predefiniti senza la necessità di utilizzare prefissi con XPath?

L'uso di un prefisso vuoto non funziona ( $xml->registerXPathNamespace('', 'http://www.example.com/ns/1.0'); ). Posso estrapolare lo spazio dei nomi predefinito, ad es

 $xml = file_get_contents($somepath); $xml = str_replace('xmlns="http://www.example.com/ns/1.0"', '', $xml); $xml = simplexml_load_string($xml); 

ma questo sta aggirando il problema.

Da un po 'di lettura online, questo non è limitato ad alcun particolare PHP o altra libreria, ma a XPath stesso – alless in XPath versione 1.0

XPath 1.0 non include alcun concetto di spazio dei nomi "predefinito", quindi indipendentemente da come i nomi degli elementi appaiono nel sorgente XML, se hanno uno spazio dei nomi ad essi associato, i selettori per essi devono essere prefissati nei selettori XPath di base del module ns:name . Si noti che ns è un prefisso definito all'interno del processre XPath, non dal documento in elaborazione, quindi non ha alcuna relazione con il modo in cui gli attributi xmlns vengono utilizzati nella rappresentazione XML.

Vedi ad esempio questa pagina "errori XSLT comuni" , che parla dell'XSLT 1.0 strettamente correlato:

Per accedere agli elementi dello spazio dei nomi in XPath, devi definire un prefisso per il loro spazio dei nomi. […] Sfortunatamente, la versione 1.0 di XSLT non ha alcun concetto simile a uno spazio dei nomi predefinito; pertanto, è necessario ripetere i prefissi dello spazio dei nomi ancora e ancora.

Secondo una risposta a una domanda simile , XPath 2.0 include una nozione di "namespace di default", e la pagina XSLT collegata sopra menziona questo anche nel context di XSLT 2.0.

Sfortunatamente, tutte le estensioni XML incorporate in PHP sono basate sulle librerie libxml2 e libxslt , che supportno solo la versione 1.0 di XPath e XSLT.

Quindi, oltre a pre-elaborare il documento per non utilizzare gli spazi dei nomi, la tua unica opzione sarebbe quella di trovare un processre XPath 2.0 che potresti colbind a PHP.

(Per inciso, vale la pena notare che se si hanno attributi non prefissati nel documento XML, non sono tecnicamente nello spazio dei nomi predefinito, ma piuttosto in nessuno spazio dei nomi, vedere XML Namespace e Attributi non prefissati per la discussione di questa stranezza dello spazio dei nomi spec.)

Esiste un modo corretto per gestire gli spazi dei nomi predefiniti senza la necessità di utilizzare prefissi con XPath?

No. Il modo corretto per gestire qualsiasi spazio dei nomi consiste nell'associare qualche valore (un prefisso) a quello spazio dei nomi in modo che possa essere esplicitamente selezionato nell'espressione XPath. Lo spazio dei nomi predefinito non è diverso.

Pensaci in questo modo: un elemento in qualche spazio dei nomi e un altro elemento con lo stesso nome in qualche altro spazio dei nomi (o nessun spazio dei nomi) sono elementi diversi . Potrebbero significare (cioè rappresentare) cose diverse . Questo è il punto. Devi dire a XPath quale vuoi select. Senza di esso, XPath non sa cosa stai chiedendo.

Aggiungere i prefissi non sarà gestibile a lungo termine.

Non vedo davvero perché. Qualunque cosa crei l'espressione XPath dovrebbe essere in grado di specificare un'espressione XPath corretta (o è uno strumento rotto).

Potresti pensare, " perché non posso semplicemente ignorare lo spazio dei nomi e get tutti gli elementi che corrispondono a quel nome? " Ci sono modi davvero hacky per farlo (come la risposta basata su XSLT già pubblicata), ma sono stati progettati in modo errato. Un elemento in XML è identificato dalla combinazione del suo spazio dei nomi e del nome locale, proprio come la tua casa può essere identificata con un numero civico (il nome locale) in alcune città e stati (lo spazio dei nomi). Se ti dico che vivo a 422 Main St, non hai ancora idea di where vivo finché non ti dico in quale città e stato.

Potresti ancora pensare: " Basta con le stupide analogie, voglio davvero farlo davvero comunque ." Puoi select gli elementi con un nome dato in tutti gli spazi dei nomi facendo corrispondere solo la parte del nome locale dell'elemento, in questo modo:

 *[local-name()='level1']/*[local-name()='level2'] /*[local-name()='level3' and @foo="bar"]/*[local-name()='level4' and @foo="bar"]/*[local-name()='level5']/*[local-name()='level6'][2]'); 

Si noti che questo non si limita allo spazio dei nomi predefinito. Ignora completamente gli spazi dei nomi. È brutto e non lo consiglio, ma a volte vuoi solo ignorare ciò che è meglio e fare qualcosa.

A proposito, questa non è colpa di PHP. Questo è ciò che richiede la specifica XPath. Devi specificare un prefisso per select un nodo in un namespace. Se PHP ti permettesse di farlo in un altro modo, allora qualunque cosa lo chiamassero, non sarebbe più XPath (secondo le specifiche).

Per evitare gli hack come quello di str_replace che hai lì (e ti consiglio di evitarlo), puoi eseguire i file XML attraverso un XSLT per rimuovere lo spazio dei nomi:

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:myns="http://www.example.com/ns/1.0"> <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/> <xsl:template match="@* | node()"> <xsl:copy> <xsl:apply-templates select="@* | node()" /> </xsl:copy> </xsl:template> <xsl:template match="myns:*"> <xsl:element name="{local-name()}"> <xsl:apply-templates select="@* | node()" /> </xsl:element> </xsl:template> </xsl:stylesheet> 

Quando si esegue su uno di questi ingressi:

 <foo xmlns="http://www.example.com/ns/1.0"> <a> <child attr="5"></child> </a> </foo> <ex:foo xmlns:ex="http://www.example.com/ns/1.0"> <ex:a> <ex:child attr="5"></ex:child> </ex:a> </ex:foo> 

L'output è lo stesso:

 <foo> <a> <child attr="5" /> </a> </foo> 

Questo ti permetterebbe di usare i tuoi XPath senza prefisso sul risultato.