2015-01-30 2 views
3

Я пишу общий проводник HTML, который может выполнять список операций, таких как страница посещения, найти таблицу, находить строки, хранить данные и т. Д. Он использует Goutte/Guzzle внутренне и, следовательно, может использовать селектора CSS и XPath , У меня есть интересная проблема, с которой я столкнулся в выборе нового набора результатов по сравнению с существующим набором результатов.Как получить текст родительского элемента относительно определенных найденных узлов HTML?

Рассмотрим демо HTML:

<h2>Burrowing</h2> 
    <ul> 
     <li> 
      <a href="/jobs/junior-mole">Junior Mole</a> 
     </li> 
     <li> 
      <a href="/jobs/head-of-badger-partnerships">Head of Badger Partnerships</a> 
     </li> 
     <li> 
      <a href="/jobs/trainee-worm">Trainee Worm</a> 
     </li> 
    </ul> 

    <h2>Tree Surgery</h2> 
    <ul> 
     <li> 
      <a href="/jobs/senior-woodpecker">Senior Woodpecker</a> 
     </li> 
     <li> 
      <a href="/jobs/owl-supervisor">Owl Supervisor</a> 
     </li> 
    </ul> 

    <h2>Grass maintenance</h2> 
    <ul> 
     <li> 
      <a href="/jobs/trainee-sheep">Trainee sheep</a> 
     </li> 
     <li> 
      <a href="/jobs/sheep-shearer">Sheep shearer</a> 
     </li> 
    </ul> 

    <h2>Aerial supervision</h2> 
    <ul> 
     <li> 
      <a href="/jobs/head-magpie-ops">Head of Magpie Operations</a> 
     </li> 
    </ul> 

Я бегу этот CSS запрос, чтобы получить роль в связях (это правильно получает восемь элементов):

ul li a 

Для каждого из них, я бы как получить категорию, которая является <h2>, непосредственно предшествующей <ul> в каждом случае. Теперь я мог бы сделать это с абсолютным селектором CSS, таким образом:

h2 

Однако это получает четыре результата, так что я не знаю, к какой категории (h2) идет с какой работой (по ссылке). Мне нужно получить восемь результатов: три лота первой категории, две из вторых, две из третьей и одна четвертая, поэтому каждая категория отображается на каждую роль.

Интересно, если я должен был бы родительский селектор для этого, поэтому я перешел из CSS в XPath, и первым попытался это, который получает каждый h2, имеющий сразу после элемента списка:

//h2[(following-sibling::ul)[1]/li/a] 

Это находит H2S имея указанную родительскую структуру, но снова возвращается с четырьмя результатами - ничего хорошего.

Следующая попытка:

//ul/li[../preceding-sibling::h2[1]] 

Это получает нужное количество результатов (на основе получения элемента списка с непосредственно предшествующим названием), но получает текст ссылки, а не категория текста.

Я думал о выполнении цикла - я знаю, что у меня есть восемь результатов, поэтому я мог бы это сделать (X - это впрыскиваемая переменная, петляющая от 1 до 8). Это работает, но я считаю добавление ручного контура здесь довольно безвкусным - Я стараюсь, чтобы мои правила, как общие, как это возможно:

//li[X]/../preceding-sibling::h2[1] 

Есть ли операция XPath, которая может вернуть требуемые результаты? Во избежании недоразумений я ищу следующий (или только текстовые элементы были бы отлично):

<h2>Burrowing</h2> 
<h2>Burrowing</h2> 
<h2>Burrowing</h2> 
<h2>Tree Surgery</h2> 
<h2>Tree Surgery</h2> 
<h2>Grass maintenance</h2> 
<h2>Grass maintenance</h2> 
<h2>Aerial supervision</h2> 

CSS будет тоже хорошо, но я предполагаю, что это не возможно, потому что CSS не имеет родителя оператор (в любом случае Goutte просто преобразует селектора CSS в селектора XPath).

Поскольку я на PHP (5.5), я считаю, что я должен придерживаться XPath 1.0.

+0

Незаконченное тему ... то, что случилось с вашей репутации? – prodigitalson

+0

Возможно, вы другой пользователь? «Халфер», который раньше видел, был в высоких десятках тысяч ... Мне так очень не повезло ... – prodigitalson

+0

@prodigitalson: Не я, еще не сломал линию 10K! Почти там ... – halfer

ответ

2

Нет, нет ни одного выражения XPath 1.0, которая возвращает то, что вы хотите. Во-первых, потому что XPath 1.0 не позволяет выполнять итерацию по промежуточным результатам, а во-вторых, потому что последовательность элементов - defined as a node-set - в которых не может быть дубликатов.

Я вижу два возможных решения вашей проблемы. Либо напишите PHP-код, что

  • сначала извлекает все соответствующие узлы a, например. с выражением, как //a
  • применяет второе выражение XPath для каждого из них в свою очередь: preceding::h2[1]

Вы должны были бы написать, что PHP код самостоятельно, учитывая мои слабые навыки в нем. Но я могу внести альтернативу: вы также можете использовать трансформацию XSLT 1.0, there are XSLT 1.0 processors в PHP.

стилевых

<?xml version="1.0" encoding="UTF-8" ?> 
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> 
    <xsl:output method="xml" omit-xml-declaration="yes" indent="yes" /> 

    <xsl:template match="/"> 
     <xsl:for-each select="//a"> 
      <xsl:copy-of select="preceding::h2[1]"/> 
     </xsl:for-each> 
    </xsl:template> 

</xsl:transform> 

Прикладной к введенному (после добавления корневого элемента), результат

<h2>Burrowing</h2> 
<h2>Burrowing</h2> 
<h2>Burrowing</h2> 
<h2>Tree Surgery</h2> 
<h2>Tree Surgery</h2> 
<h2>Grass maintenance</h2> 
<h2>Grass maintenance</h2> 
<h2>Aerial supervision</h2> 

Попробуйте онлайн here. Кстати, если вы заинтересованы в том, как сделать это с помощью XPath 2.0, используя for, как вы упомянули в комментарии, см this version instead:

for $a in //a return $a/preceding::h2[1] 
+0

Ах, две хорошие новые идеи, большое спасибо. 'For' XPath наиболее расстраивает, так как он идеально подходит, не требует изменений дизайна в моем приложении, но синтаксис недоступен! Ба. XSLT заслуживает некоторого рассмотрения: согласно моему комментарию к prodigitalson, я делаю общий синтаксический анализатор, чтобы я мог сканировать любую структуру без написания нового PHP, и общий шаг трансформатора был бы полезным дополнением. – halfer

+0

(Возможно, у меня есть рыба, чтобы посмотреть, есть ли у кого-нибудь XPath 2.0 для работы с PHP в некотором роде, может быть, есть какой-то приемлемый взлом. Я буду замечать это на этой странице, если найду что-то. в некотором роде] (http://stackoverflow.com/questions/2085632/will-xpath-2-0-and-or-xslt-2-0-be-implemented-in-php)). – halfer

1

Так что я не знаю, как вы пытаетесь использовать это, но я хотел бы попробовать что-то вроде:

$links = $cralwer->filter('ul li a'); 
foreach ($links as $link) { 
    // do stuff with the link 
    // ... 
    // get the H2 
    $header = $link->parents()->filter('ul[../preceding-sibling::h2]'); 
    // do stuff with the header 
} 

Примечание это непроверенный, и я пришел с ним из глядя на Symfony\Component\DomCrawler API непосредственно, но Я думаю, что это должно сработать на основе этого (если у меня не будет XPath), но если я это сделаю, вам будет очень легко работать).

Конечно, вы можете также использовать Symfony\Component\DomCrawler::each и сделать это внутри крышки вместо того, чтобы делать Еогеасп ...

+0

Спасибо за предложение! Тем не менее, я стараюсь как можно больше обобщать мои шаги обработки - «строки захвата» из «ul li a» в порядке, а второе выражение, которое у вас есть, - это, фактически, операция «захват строк». Однако свойство 'parents()' делает его менее общим и в идеале я хотел бы заставить его работать без него (т. Е. При разборе новой страницы я просто добавляю различные предопределенные типы шагов и не должен писать любой PHP вообще). Я полагаю, что «родители()» могут быть самим собой, поэтому процесс будет «захватить эти строки [xpath], переходить к родителям, захватить эти столбцы [xpath]». – halfer

+0

Интересно, что только в эту минуту выяснилось, что XPath 2.0 [имеет операцию 'for') (http://www.xml.com/pub/a/2002/03/20/xpath2.html?page=2), поэтому я предполагаю, что это было бы тривиально в этой версии! Тем не менее, я застрял на 1.0, если не найду времени, чтобы получить парсер 2.0, работающий на консоли, и взломать его в Goutte (на самом деле не стоит беспокоиться, IMO). – halfer