2011-01-06 11 views
30

Come in questo Stack Overflow answer immagina di dover selezionare una tabella particolare e quindi tutte le righe di essa. A causa della permissività di HTML, tutti e tre i seguenti sono markup legale:XPath con elemento facoltativo nella gerarchia

<table id="foo"><tr>...</tr></table> 
<table id="foo"><tbody><tr>...</tr></tbody></table> 
<table id="foo"><tr>...</tr><tbody><tr>...</tr></tbody></table> 

siete preoccupati per tabelle nidificate nelle tabelle, e così non si desidera utilizzare un XPath come
table[@id="foo"]//tr.

Se si potesse specificare il XPath desiderato come una regex, potrebbe essere simile:
table[@id="foo"](/tbody)?/tr

In generale, come è possibile specificare un'espressione XPath che permette un elemento facoltativo nella gerarchia di un selettore?

Per essere chiari, non sto cercando di risolvere un problema del mondo reale o selezionare un elemento specifico di un documento specifico. Sto chiedendo tecniche per risolvere una classe di problemi.

+0

Oh, e per ragioni di semplicità e generalità sto ignorando il '' thead' e tfoot' elementi giuridici in HTML. – Phrogz

risposta

19

non vedo il motivo per cui non è possibile utilizzare questo:

//table[@id='foo']/tr|//table[@id='foo']/tbody/tr 

Se si desidera un'espressione senza set di nodi unione:

//tr[(.|parent::tbody)[1]/parent::table[@id='foo']] 
+0

La tua prima risposta è ciò che suggeriva Dimitre. Ho cambiato accettazione per la tua seconda espressione, tuttavia, poiché questo è più ASCIUTTO – Phrogz

+1

@Phrogz: espressione contiene asce arretrate ed è meno efficiente di un'espressione che contiene solo assi in avanti.Per quanto riguarda "DRY", puoi anche considerare la comprensibilità di un'espressione, che è certamente interconnessa con la sua mantenibilità. :) –

+0

@Dimitre Grazie per il tuo commento sull'efficienza, hai ragione che la semplice alternanza (la tua risposta e la prima espressione in questa risposta) è più facile da capire, anche se è leggermente più incline a correggere errori e più difficile da mantenere – Phrogz

7

Uso:

//table[@id="foo"]/*[self::tbody or self::thead or self::tfoot]/tr 
    | 
    //table[@id="foo"]/tr 

Selezionare ogni elemento tr che è un bambino di qualsiasi table che ha un attributo id "pippo" o qualsiasi elemento tr che è figlio di un tbody che è un bambino qualsiasi table.

+0

Apprezzo la tua esperienza in questo settore, ma è davvero il meglio che si possa fare? Se la prima e l'ultima parte di xpath sono solo "table" e "tr" questo non è male, ma con qualcosa come 'div [@ id =" contents] // table [@ class = "comments"] (/ tbody)?/tr/[td // text() [contains (., 'targetString')]] 'diventa molto non- [DRY] (http://en.wikipedia.org/wiki/Don't_repeat_yourself) per duplicare l'espressione attorno alla variazione – Phrogz

+0

@Phrogz: No, è quasi la mia espressione iniziale: vedere la modifica. Può essere molto più elegante con XPath 2.0 e persino molto più elegante con un documento XML con uno schema XML noto (che è il caso di XHTML) –

+0

Qual è la versione più elegante di XPath 2.0? Il meglio che ho potuto immaginare è stato un passaggio alternato "."e la parte opzionale. Con Saxon su TEI, questo ha funzionato per me: /TEI.2/text/(.|group/text)/body/div1 –

3

In XPath 2.0, la fase opzionale può essere espresso come (tbody|.).

//table[@id="foo"]/(tbody|.)/tr 

XPathTester.com demo

Il tubo (|) denota union (di due nodi-set), la dot (.) denota passo identità (ritornando proprio quello che il passo precedente fatto).

Questo può essere ampliato per includere più elementi facoltativi in ​​una sola volta:

//table[@id="foo"]/(thead|tbody|tfoot|.)/tr 
+0

Non sono sicuro del perché questo non funzioni in XPath 1.0, però. Sembra che dovrebbe, perché '(tbody |.)' Sembra un token 'FilterExpr' valido (' PrimaryExpr' → ''(' Expr ')'' → 'OrExpr' →' AndExpr' → 'EqualityExpr' →' RelationalExpr '→' AdditiveExpr' → 'MultiplicativeExpr' →' UnaryExpr' → 'UnionExpr'). – Palec

Problemi correlati