2009-02-28 4 views
14

Próbuję parsować HTML z XPath. Po poniższym uproszczonym przykładzie XML chcę dopasować ciąg "Tekst 1", a następnie pobrać zawartość odpowiedniego węzła content.Jak dopasować węzeł tekstowy, a następnie wykonaj węzły nadrzędne za pomocą XPath

<doc> 
    <block> 
     <title>Text 1</title> 
     <content>Stuff I want</content> 
    </block> 

    <block> 
     <title>Text 2</title> 
     <content>Stuff I don't want</content> 
    </block> 
</doc> 

kod Mój Python rzuca chwiejne:

>>> from lxml import etree 
>>> 
>>> tree = etree.XML("<doc><block><title>Text 1</title><content>Stuff 
I want</content></block><block><title>Text 2</title><content>Stuff I d 
on't want</content></block></doc>") 
>>> 
>>> # get all titles 
... tree.xpath('//title/text()') 
['Text 1', 'Text 2'] 
>>> 
>>> # match 'Text 1' 
... tree.xpath('//title/text()="Text 1"') 
True 
>>> 
>>> # Follow parent from selected nodes 
... tree.xpath('//title/text()/../..//text()') 
['Text 1', 'Stuff I want', 'Text 2', "Stuff I don't want"] 
>>> 
>>> # Follow parent from selected node 
... tree.xpath('//title/text()="Text 1"/../..//text()') 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "lxml.etree.pyx", line 1330, in lxml.etree._Element.xpath (src/ 
lxml/lxml.etree.c:14542) 
    File "xpath.pxi", line 287, in lxml.etree.XPathElementEvaluator.__ca 
ll__ (src/lxml/lxml.etree.c:90093) 
    File "xpath.pxi", line 209, in lxml.etree._XPathEvaluatorBase._handl 
e_result (src/lxml/lxml.etree.c:89446) 
    File "xpath.pxi", line 194, in lxml.etree._XPathEvaluatorBase._raise 
_eval_error (src/lxml/lxml.etree.c:89281) 
lxml.etree.XPathEvalError: Invalid type 

Jest to możliwe w XPath? Czy muszę wyrażać to, co chcę robić w inny sposób?

Odpowiedz

22

Czy tego chcesz?

//title[text()='Text 1']/../content/text() 
+0

Duh, naprawdę proste! Kinda ma sens, że wybieram teraz atrybut text(). – Mat

+2

możesz również użyć // bloku [title = 'Text 1']/content, aby uzyskać odpowiednią treść węzła – Dror

+0

@Dror: Teraz to jest przydatne. – Mat

16

Zastosowanie:

string(/*/*/title[. = 'Text 1']/following-sibling::content) 

ten stanowi co najmniej dwa ulepszenia, w porównaniu z obecnie przyjętego rozwiązania Johannes Weiß:

  1. bardzo drogie skrót " // " (zwykle powoduje to, że kto dokument XML do zeskanowania) unika się unika się tak jak powinno to być zawsze, gdy struktura dokumentu XML jest znana z góry.

  2. Nie ma powrócić do macierzystej (w kroku lokalizacji "/ .." unika)

+0

Uczciwy postęp, moim prawdziwym dokumentem jest HTML, a część "tytułowa" jest zagnieżdżona na pięciu poziomach, więc muszę wrócić do pięciu rodziców, aby dostać się do obszaru "treści". Będę o tym pamiętać, choć nie będzie to miało większego znaczenia dla brudnego hacka. – Mat

+2

Co robi '/ */* /'? Próbuję go na dość dużym dokumencie i wydaje się tak powolny jak '//'. – dentarg

+2

@dentarg: '/ */*' wybiera wszystkie elementy, które są elementami podrzędnymi górnego elementu dokumentu. Jest o wiele szybszy niż '// someName', który przechodzi przez kompletny dokument i wybiera każdy element o nazwie' "someName" '. W tej odpowiedzi możemy użyć jeszcze bardziej wydajnego wyrażenia: 'string (/ */*/title [. = 'Text 1'] [1]/following-sibling :: content)' Wyrażenie w odpowiedzi nie powinno być mniej wydajne, biorąc pod uwagę dobrze optymalizujący procesor XPath - ponieważ zawsze, gdy funkcja 'string()' jest dostarczana jako argument będący zbiorem węzłów, tworzy ona tylko wartość ciągu pierwszego węzła tego zestawu węzłów. –