Как перебирать элементы DOM, соответствующие классу css, с помощью xpath?

Я обрабатываю HTML-страницу с переменным количеством элементов p с классом css «myclass», используя Python + Selenium RC.

Когда я пытаюсь выбрать каждый узел с помощью этого xpath:

//p[@class='myclass'][n]

(где n - натуральное число)

Я получаю только первый элемент p с этим классом css для каждого n, в отличие от ситуации, когда я повторяю выбор ВСЕХ элементов p с помощью:

//p[n]

Есть ли способ перебирать элементы по классу css с помощью xpath?


person GJ.    schedule 17.07.2010    source источник
comment
Хороший вопрос (+1). См. мой ответ для объяснения и примера итерации в наборе узлов, выбранном выражением XPath.   -  person Dimitre Novatchev    schedule 17.07.2010
comment
Вопрос действительно не полный. Какие технологии/язык вы используете для обработки своей страницы? Что именно вы хотите повторить в XPath? (XPath — это язык выбора, а не язык обработки. Итерация как концепция здесь не имеет большого значения.) Пожалуйста, объясните.   -  person Tomalak    schedule 17.07.2010
comment
@Gj это может помочь показать код Python или Selenium RC, который пытается выполнить итерацию по набору узлов, выбранному выражением XPath.   -  person LarsH    schedule 13.09.2010
comment
@Gj Я опубликовал новый ответ, который, я думаю, может решить вашу настоящую проблему. Посмотрите.   -  person Dimitre Novatchev    schedule 15.09.2010


Ответы (5)


XPath 1.0 не предоставляет повторяющуюся конструкцию.

Итерация может выполняться для выбранного набора узлов на языке, на котором размещен XPath.

Примеры:

В XSLT 1.0:

   <xsl:for-each select="someExpressionSelectingNodes">
     <!-- Do something with the current node -->
   </xsl:for-each>

В C#:

using System;
using System.IO;
using System.Xml;

public class Sample {

  public static void Main() {

    XmlDocument doc = new XmlDocument();
    doc.Load("booksort.xml");

    XmlNodeList nodeList;
    XmlNode root = doc.DocumentElement;

    nodeList=root.SelectNodes("descendant::book[author/last-name='Austen']");

    //Change the price on the books.
    foreach (XmlNode book in nodeList)
    {
      book.LastChild.InnerText="15.95";
    }

    Console.WriteLine("Display the modified XML document....");
    doc.Save(Console.Out);

  }
}

XPath 2.0 имеет собственную конструкцию итерации:

   for $varname1 in someExpression1,
       $varname2 in someExpression2, 
      .  .  .  .  .  .  .  .  .  .  .
       $varnameN in someExpressionN 
    return
        SomeExpressionUsingTheVarsAbove
person Dimitre Novatchev    schedule 17.07.2010
comment
возможно, мой вопрос был недостаточно ясен, но я не понимаю, как ваш ответ связан с ним. Я могу использовать окончание [n], чтобы выбрать элемент из нескольких простых совпадений, например. //p[n] для перебора ВСЕХ элементов p. моя проблема начинается при попытке перебрать только те элементы p, которые имеют определенный класс. - person GJ.; 17.07.2010
comment
Кто бы ни проголосовал за этот ответ, пожалуйста, подойдите и укажите причины? это было из-за плохой погоды или из-за того, что ты некомпетентный трус? Я предполагаю, что это было последнее... - person Dimitre Novatchev; 18.07.2010
comment
@Gj: Почему, просто замените некоторыеExpressionSelectingNodes из моего ответа своим выражением (//p[@class='myclass']), которое выбирает узлы, которые вы хотите перебрать. Я привел два примера организации итерации — на двух разных языках хостинга. Это должно быть что-то похожее на языке хостинга, который вы используете. - person Dimitre Novatchev; 18.07.2010

Теперь, когда я снова смотрю на этот вопрос, я думаю, что настоящая проблема не в итерации, а в использовании //.

Это часто задаваемые вопросы:

//p[@class='myclass'][1] 

выбирает каждый элемент p, который имеет атрибут class со значением "myclass" и является первым дочерним элементом своего родителя. Следовательно, это выражение может выбрать много элементов p, ни один из которых не является первым таким элементом p в документе.

Когда мы хотим получить первый элемент p в документе, который удовлетворяет приведенному выше предикату, одно правильное выражение:

(//p)[@class='myclass'][1] 

Помните: оператор [] имеет более высокий приоритет (приоритет), чем аббревиатура //. Всякий раз, когда вам нужно индексировать узлы, выбранные //, всегда помещайте индексируемое выражение в скобки.

Вот демонстрация:

<nums>
 <a>
  <n x="1"/>
  <n x="2"/>
  <n x="3"/>
  <n x="4"/>
 </a>
 <b>
  <n x="5"/>
  <n x="6"/>
  <n x="7"/>
  <n x="8"/>
 </b>
</nums>

Выражение XPath:

//n[@x mod 2 = 0][1]

выбирает следующие два узла:

<n x="2" />
<n x="6" />

Выражение XPath:

(//n)[@x mod 2 = 0][1]

выбирает ровно первый n элемент в документе с требуемым свойством:

<n x="2" />

Сначала попробуйте следующее преобразование:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="/">
  <xsl:copy-of select="//n[@x mod 2 = 0][1]"/>
 </xsl:template>
</xsl:stylesheet>

и в результате получается два узла.

<n x="2" />
<n x="6" />

Теперь измените выражение XPath, как показано ниже, и повторите попытку:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="/">
  <xsl:copy-of select="(//n)[@x mod 2 = 0][1]"/>
 </xsl:template>
</xsl:stylesheet>

И в результате получилось то, что мы действительно хотели — первый такой элемент n в документе:

<n x="2" />
person Dimitre Novatchev    schedule 15.09.2010

Возможно, все ваши div с этим классом находятся на одном уровне, поэтому по //p[@class='myclass'] вы получаете массив абзацев с указанным классом. Таким образом, вы должны перебирать его с помощью индексов, т.е. //p[@class='myclass'][1], //p[@class='myclass'][2],...,//p[@class ='мой класс'][последний()]

person Sergii Pozharov    schedule 19.07.2010

Я не думаю, что вы используете «индекс» по прямому назначению. Синтаксис //p[selection][index] в этом выборе на самом деле говорит вам, каким элементом в его родительском элементе он должен быть... Итак, //p[selection][1] говорит, что выбранный вами p должен быть первым дочерним элементом его родителя. //p[selection][2] говорит, что это должен быть второй ребенок. В зависимости от вашего html, скорее всего, это не то, что вам нужно.

Учитывая, что вы используете Selenium и Python, есть несколько способов сделать то, что вы хотите, и вы можете посмотреть этот вопрос, чтобы увидеть их (там есть два варианта: один в селении Javascript, другой с использованием вызовов селена на стороне сервера).

person Ryley    schedule 19.07.2010
comment
В XPath предикат [n] (сокращение от [position() = n]) означает выбор только n узла контекстной группы. Группа контекста — это набор узлов, указанный выражением XPath, предшествующим предикату. Это может относиться или не относиться к его порядку среди братьев и сестер конкретного родителя. В данном случае это не так. - person LarsH; 13.09.2010
comment
@LarsH - да, ты меня понял ... Я вообще не смог это хорошо объяснить. Согласны ли вы с тем, что связанные ответы SO предлагают правильный тип ответа (также очень похожий на то, что говорит Димитре) ... если нет, я, вероятно, просто удалю этот ответ. - person Ryley; 13.09.2010
comment
Я не уверен, актуальны ли связанные ответы. На самом деле, я, кажется, припоминаю из своего ограниченного и давнего опыта работы с Selenium, что Selenium не выполняет настоящий XPath, а ограниченное подмножество, и даже тогда, возможно, не совсем правильно. Так что, возможно, это была проблема ОП. Насколько я знаю, [n] в Selenium работает так, как вы сказали, а не так, как говорит спецификация XPath. Как я сказал в своем комментарии к вопросу, если бы мы увидели контекст, в котором @Gj выполняет итерацию, мы могли бы решить проблему. - person LarsH; 13.09.2010

Вот фрагмент кода C#, который может вам помочь.

Ключевым здесь является функция Selenium GetXpathCount(). Он должен вернуть количество вхождений выражения Xpath, которое вы ищете.

Вы можете ввести //p[@class='myclass'] в XPather или любом другом инструменте анализа Xpath, чтобы убедиться, что возвращаются несколько результатов. Затем вы просто перебираете результаты в своем коде.

В моем случае это были все элементы списка в UL, которые необходимо было повторить, т.е. //li[@class='myclass']/ul/li - поэтому в зависимости от ваших требований должно быть что-то вроде:

int numProductsInLeftNav = Convert.ToInt32(selenium.GetXpathCount("//p[@class='myclass']"));

List<string> productsInLeftNav = new List<string>();
for (int i = 1; i <= numProductsInLogOutLeftNav; i++) {
    string productName = selenium.GetText("//p[@class='myclass'][" + i + "]");
    productsInLogoutLeftNav.Add(productName);
}
person Hector M    schedule 29.07.2010