Как перебрать элементы DOM, которые соответствуют классу CSS, используя xpath? - PullRequest
9 голосов
/ 17 июля 2010

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

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

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

(с натуральным числом n)

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

//p[n]

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

Ответы [ 5 ]

1 голос
/ 15 сентября 2010

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

Это FAQ :

//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 элемент в документе со свойством wanted:

<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" />
1 голос
/ 17 июля 2010

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
0 голосов
/ 29 июля 2010

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

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

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

В моем случае это были все элементы списка в UL, которые нужно было повторить, -ie //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);
}
0 голосов
/ 19 июля 2010

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

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

0 голосов
/ 19 июля 2010

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

...