Come trovare la corrispondenza più vicina dal nodo di contesto corrente

c# html-agility-pack xpath

Domanda

Ho un file XML piuttosto grande che sto cercando di analizzare usando un'applicazione C # e HtmlAgilityPack. L'XML ha un aspetto simile al seguente:

...
<tr>
<td><b>ABC-123</b></td>
<td>15</td>
<td>4</td>
</tr>
<tr>
<td>AB-4-320</td>
<td>11</td>
<td>2</td>
</tr>
<tr>
<td><b>ABC-123</b></td>
<td>15</td>
<td>4</td>
</tr>
<tr>
<td>AB-4-320</td>
<td>11</td>
<td>2</td>
</tr>
<tr>
<td>CONTROLLER1</td>
<td>4</td>
<td>3</td>
</tr>
<td>CONTROLLER2</td>
<td>4</td>
<td>3</td>
</tr>
...

Fondamentalmente una serie di righe e colonne di tabelle che si ripetono. Sto prima cercando un controller usando:

string xPath = @"//tr/td[starts-with(.,'CONTROLLER2')]";
HtmlNodeCollection nodes = doc.DocumentNode.SelectNodes(xPath);
foreach (HtmlNode link in nodes) { ... }

Che restituisce il nodo corretto. Ora voglio cercare indietro (su) per il primo (più vicino) matching <td> nodo che inizia con il testo "ABC":

string xPath = @link.XPath + @"/parent::tr/preceding-sibling::tr/td[starts-with(.,'ABC-')]";

Ciò restituisce tutti i nodi corrispondenti, non solo quello più vicino. Quando ho tentato di aggiungere [1] alla fine di questa stringa XPath, non sembrava funzionare e non ho trovato esempi che mostrassero un predicato utilizzato con una funzione di assi come questo. O, più probabilmente, sto sbagliando. Eventuali suggerimenti?

Risposta accettata

Puoi usare questo XPath:

/parent::tr/preceding-sibling::tr[td[starts-with(.,'ABC-')]][1]/td[starts-with(.,'ABC-')]

Quello cercherà il più vicino precedente <tr> che ha un figlio <td> inizia con 'ABC-'. Quindi prendi quel particolare elemento <td> .

Ci sono almeno due approcci che puoi scegliere quando usi HtmlAgilityPack:

foreach (HtmlNode link in nodes)
{
    //approach 1 : notice dot(.) at the beginning of the XPath
    string xPath1 = 
        @"./parent::tr/preceding-sibling::tr[td[starts-with(.,'ABC-')]][1]/td[starts-with(.,'ABC-')]";
    var n1 = node.SelectSingleNode(xPath1);
    Console.WriteLine(n1.InnerHtml);

    //approach 2 : appending to XPath of current link
    string xPath2 = 
        @"/parent::tr/preceding-sibling::tr[td[starts-with(.,'ABC-')]][1]/td[starts-with(.,'ABC-')]";
    var n2 = node.SelectSingleNode(link.XPath + xPath2);
    Console.WriteLine(n2.InnerHtml);
}

Risposta popolare

Se sei in grado di utilizzare LINQ-to-XML anziché HAP, allora questo funziona:

var node = xml.Root.Elements("tr")
    .TakeWhile(tr => !tr.Elements("td")
        .Any(td => td.Value.StartsWith("CONTROLLER2")))
    .SelectMany(tr => tr.Elements("td"))
    .Where(td => td.Value.StartsWith("ABC-"))
    .Last();

Ho ottenuto questo risultato:

<td>
  <b>ABC-123</b>
</td>

(Che ho controllato era il secondo nodo corrispondente nel tuo campione, non il primo.)



Related

Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché