Query XPath con caratteri di quotatura (quotes e single quotes)

Se ritieni utile questo articolo, considera la possibilità di effettuare una donazione (il cui importo è a tua completa discrezione) tramite PayPal. Grazie.

Le query XPath di XML, non avendo un modo nativo per effettuare l'escaping dei caratteri di quotatura presentano problemi quando si vuole ricercare del testo che contenga apici doppi ed apici singoli. In presenza di uno dei due la soluzione è piuttosto banale, ovvero adottare l'altro come carattere di qualificazione del testo. Ma come fare quando la stringa da ricercare contiene entrambi i qualificatori di testo?

Introduzione

Proviamo a chiarire meglio con un piccolo esempio: consideriamo il seguente documento XML:

<?xml version="1.0"?>
<root>
    <node>Utente Guest</node>
    <node>Utente "Guest"</node>
    <node>L'utente Guest</node>
    <node>L'utente "Guest"</node>
    <node>L'utente "Guest" e l'utente "Administrator"</node>
</root>

Immaginiamo di aver caricato il suddetto documento in una variabile xml (di tipo XmlDocument) e di voler "recuperare" i singoli nodi che, come possiamo notare, contemplano tutte le casistiche possibili, ovvero:

  1. il testo non contiene nè apici doppi, nè apici singoli

  2. il testo contiene solo apici doppi

  3. il testo contiene solo apici singoli

  4. il testo contiene sia apici doppi che apici singoli

  5. il testo contiene più ricorrenze, sia di apici doppi che di apici singoli

NOTA: le porzioni di codice che seguono presuppongono l'uso del namespace System.Xml, omesso per semplicità

La query per selezionare il primo nodo (1) non presenta alcuna difficoltà e può essere espressa in due modi:

string find = "Utente Guest";
XmlNode n = xml.DocumentElement.SelectSingleNode("node[text()=\"" + find + "\"]");

oppure (usando come qualificatore di testo l'apice singolo):

string find = "Utente Guest";
XmlNode n = xml.DocumentElement.SelectSingleNode("node[text()='" + find + "']");

La query per la selezione del secondo nodo (2) è altrettando semplice:

string find = "Utente \"Guest\"";
XmlNode n = xml.DocumentElement.SelectSingleNode("node[text()='" + find + "']");

Per la selezione del terzo (3) nodo è sufficiente cambiare il carattere di qualificazione della stringa:

string find = "L'utente Guest";
XmlNode n = xml.DocumentElement.SelectSingleNode("node[text()=\"" + find + "\"]");

Per selezionare il quarto nodo (4) - e, analogamente, anche il quinto (5) - non è possibile usare nessuno dei due caratteri di qualificazione (poiché sono entrambi contenuti nel testo stesso). Per risolvere il problema in questo caso possiamo sfruttare la funzione nativa di XPath per la concatenazione di stringhe: concat().
Ad esempio la query per la selezione del quarto nodo risulta:

XmlNode n = xml.DocumentElement.SelectSingleNode("node[text()=concat('L', \"'\", 'utente \"Guest\"')]");

Per il quinto nodo la XPath diventa:

XmlNode n = xml.DocumentElement.SelectSingleNode(&quot;node[text()=concat('L', "'", 'utente "Guest" e l', "'", 'utente "Administrator"')]");

GetXPathString

Considerando quanto detto, possiamo creare una semplicissima funzione di utilità per rendere un testo compatibile con XPath, indipendentemente dagli eventuali caratteri di quotatura contenuti nella stringa:

public static string GetXPathString(string input)
{
    if (input.IndexOf("'") > -1 && input.IndexOf("\"") > -1)
        return "concat('" + input.Replace("'", "', \"'\", '") + "')";
    else if (input.IndexOf("\"") > -1)
        return "'" + input + "'";
    else
        return "\"" + input + "\"";
}

Il codice per la selezione dei nodi del documento XML d'esempio fino ad ora utilizzato risultano dunque molto semplici:

XmlNode n = xml.DocumentElement.SelectSingleNode("node[text()=" + XmlUtils.GetXPathString(find) + "]");

Nel codice sorgente allegato a questo articolo, la funzione GetXPathString è esposta come membro statico della classe XmlUtils.
Utilizzando la funzione XmlUtils.GetXPathString non risulta quindi più necessario preoccuparsi della presenza di eventuali caratteri di quotatura presenti nel testo che desideriamo utilizzare per l'esecuzione di una query XPath.