XPath, чтобы найти всех следующих братьев до следующего брата определенного типа - PullRequest
4 голосов
/ 13 декабря 2011

Учитывая этот XML / HTML:

<dl>
  <dt>Label1</dt><dd>Value1</dd>
  <dt>Label2</dt><dd>Value2</dd>
  <dt>Label3</dt><dd>Value3a</dd><dd>Value3b</dd>
  <dt>Label4</dt><dd>Value4</dd>
</dl>

Я хочу найти все <dt>, а затем для каждого найти следующее <dd> до следующего <dt>.

Использование Ruby's Nokogiri Я могу сделать это следующим образом:

dl.xpath('dt').each do |dt|
  ct  = dt.xpath('count(following-sibling::dt)')
  dds = dt.xpath("following-sibling::dd[count(following-sibling::dt)=#{ct}]")
  puts "#{dt.text}: #{dds.map(&:text).join(', ')}"
end
#=> Label1: Value1
#=> Label2: Value2
#=> Label3: Value3a, Value3b
#=> Label4: Value4

Однако, как вы можете видеть, я создаю переменную в Ruby и затем создаю XPath, используя ее,Как я могу написать одно выражение XPath, которое эквивалентно?

Я догадался:

following-sibling::dd[count(following-sibling::dt)=count(self/following-sibling::dt)]

, но, видимо, я не понимаю, что там означает self.

Этот вопрос похож на XPath: выбирайте всех следующих братьев и сестер, пока не появится другой брат , за исключением того, что нет уникального идентификатора для узла 'stop'.

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

Ответы [ 2 ]

5 голосов
/ 14 декабря 2011

Это интересный вопрос.Большинство проблем уже упоминалось в ответе @ lwburk и в его комментариях.Просто чтобы раскрыть немного больше сложностей, скрытых в этом вопросе для случайного читателя, мой ответ, вероятно, более сложный или более подробный, чем необходим ОП.

Особенности XPath 1.0, связанные с этой проблемой

В XPath каждый шаг и каждый узел в наборе выбранных узлов работают независимо.Это означает, что

  1. подвыражение не имеет общего способа доступа к данным, которые были вычислены в предыдущем подвыражении, или обмениваться данными, вычисленными в этом подвыражении, с другими подвыражениями
  2. узел не имеет общего способадля ссылки на узел, который использовался в качестве узла контекста в предыдущем подвыражении
  3. , узел не имеет общего способа ссылаться на другие узлы, которые выбраны в данный момент.
  4. , если каждый из выбранных узловнеобходимо сравнить с тем же определенным узлом, тогда этот узел должен быть однозначно определен способом, который является общим для всех выбранных узлов

(Ну, на самом деле, я не уверен на 100%, если этот списокАбсолютно правильно в каждом случае. Если кто-то лучше знает особенности XPath, пожалуйста, прокомментируйте или исправьте этот ответ, отредактировав его.)

Несмотря на отсутствие общих решений, некоторые из этих ограничений можно преодолеть, если естьправильное знание структуры документа, и / или ранее использованная ось может быть «обращена»другая ось, которая служит в качестве обратной ссылки, то есть соответствует только узлам, которые использовались в качестве узла контекста в предыдущем выражении.Типичным примером этого является случай, когда ось parent используется после первого использования оси child (противоположный случай, от дочернего элемента к родительскому, не может быть однозначно обратимым без дополнительной информации).В таких случаях информация с предыдущих шагов более точно воссоздается на более позднем этапе (вместо доступа к ранее известной информации).

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

XPath определяет синтаксис для ссылки на переменную, но не определяет синтаксис для определения переменных, способ определения переменных зависит от среды, гдеXPath используется.На самом деле, поскольку в рекомендации говорится, что «привязки переменных, используемые для оценки подвыражения, всегда совпадают с привязками переменных, используемыми для оценки содержащего выражения», вы также можете утверждать, что XPath явно запрещает определение переменных внутри выражения XPath.

Проблема переформулирована

В вашем вопросе проблема будет заключаться в том, чтобы, когда задано <dt>, идентифицировать следующие элементы <dd> или первоначально заданный узел после переключения узла контекста.Определение первоначально заданного <dt> имеет решающее значение, поскольку для каждого узла в наборе узлов, подлежащего фильтрации, выражение предиката оценивается с этим узлом в качестве узла контекста;поэтому нельзя ссылаться на оригинал <dt> в предикате, если нет способа идентифицировать его после изменения контекста.То же самое относится к <dd> элементам, которые следуют за братьями и сестрами данного <dt>.

Если вы используете переменные, можно спорить, есть ли существенное различие между 1) использованием синтаксиса переменных XPath и специфическим для Nokogiriспособ объявления этой переменной или 2) использование расширенного синтаксиса XPath Nokogiri, который позволяет использовать переменные Ruby в выражении XPath.В обоих случаях переменная определяется специфическим для среды способом, и значение XPath ясно только в том случае, если также доступно определение переменной.Аналогичный случай можно увидеть с XSLT, где в некоторых случаях вы можете сделать выбор между 1) определением переменной с помощью <xsl:variable> перед использованием вашего выражения XPath или 2) использованием current() (внутри вашего выражения XPath), которое является расширением XSLT.

Решение с использованием переменных набора узлов и метода Кайсиана

Вы можете выбрать все элементы <dd> после элемента current <dt> с помощью following-sibling::dd (набор A).Также вы можете выбрать все элементы <dd>, следующие за элементом next <dt> с помощью following-sibling::dt[1]/following-sibling::dd (набор B).Теперь разница в множестве A\B оставляет элементы <dd>, которые вы действительно хотели (элементы, которые находятся в наборе A, но не в наборе B).Если переменная $setA содержит набор узлов A, а переменная $setB содержит набор узлов B, разность наборов может быть получена с помощью (модификации) метода Кайсиана:

dds = $setA[count(.|$setB) != count($setB)]

Простой обходной путь без каких-либо переменных

В настоящее время ваш метод состоит в том, чтобы выбрать все элементы <dt> и затем попытаться связать значение каждого такого элемента со значениями соответствующих элементов <dd> в одной операции.Можно ли было бы преобразовать эту логику связи, чтобы она работала наоборот?Таким образом, вы сначала должны выбрать все <dd> элементов, а затем для каждого <dd> найти соответствующий <dt>.Это будет означать, что вы получите доступ к одним и тем же элементам <dt> несколько раз, и при каждой операции вы добавляете только одно новое значение <dd>.Это может повлиять на производительность, а код Ruby может быть более сложным.

Хорошая сторона - это простота требуемого XPath.При наличии элемента <dd> найти соответствующий <dt> удивительно просто: preceding-sibling::dt[1]

Применительно к вашему текущему коду Ruby

dl.xpath('dd').each do |dd|
  dt = dd.xpath("preceding-sibling::dt[1]")
  ## Insert new Ruby magic here ##
end
3 голосов
/ 14 декабря 2011

Одно из возможных решений:

dl.xpath('dt').each_with_index do |dt, i|
  dds = dt.xpath("following-sibling::dd[not(../dt[#{i + 2}]) or " +
                     "following-sibling::dt[1]=../dt[#{i + 2}]]")
  puts "#{dt.text}: #{dds.map(&:text).join(', ')}"
end

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

following-sibling::dd[not(../dt[$n]) or 
    (following-sibling::dt[1] and count(following-sibling::dt[1]|../dt[$n])=1)]

Примечание: Сбой использования self из-за неправильного его использованиякак ось (self::).Кроме того, self всегда содержит только узел контекста, поэтому он будет ссылаться на каждый dd, проверенный выражением, а не на исходную dt

...