使用XPath查询C#中的某些HTML文件时遇到了一个小问题。
好的,首先是HTML示例:
<table id="theTable">
<tbody>
<tr class="theClass">A</tr>
<tr class="theClass">B</tr>
<tr>1</tr>
<tr>2</tr>
<tr>3</tr>
<tr>4</tr>
<tr>5</tr>
<tr class="theClass">C</tr>
<tr class="theClass">D</tr>
<tr>6</tr>
<tr>7</tr>
<tr>8</tr>
<tr>9</tr>
<tr>10</tr>
<tr>11</tr>
<tr>12</tr>
<tr>13</tr>
<tr>14</tr>
<tr>15</tr>
<tr class="theClass">E</tr>
<tr class="theClass">F</tr>
<tr>16</tr>
<tr>17</tr>
<tr>18</tr>
<tr>19</tr>
<tr>20</tr>
<tr>21</tr>
<tr>22</tr>
</tbody>
</table>
现在,我要做的是只获取B和C节点之间的元素(1,2,3,4,5)。
这是我到目前为止所尝试的内容:
using System;
using System.Xml.XPath;
namespace Test
{
class Test
{
static void Main(string[] args)
{
XPathDocument doc = new XPathDocument("Test.xml");
XPathNavigator nav = doc.CreateNavigator();
Console.WriteLine(nav.Select("//table[@id='theTable']/tbody/tr[preceding-sibling::tr[@class='theClass'] and following-sibling::tr[@class='theClass']]").Count);
Console.WriteLine(nav.Select("//table[@id='theTable']/tbody/tr[preceding-sibling::tr[@class='theClass'][2] and following-sibling::tr[@class='theClass'][4]]").Count);
Console.ReadKey(true);
}
}
}
此代码在上面的HTML上运行,输出19和5。
所以只有第二个XPath表达式可以工作,但这只是因为它搜索的元素有两个元素,前面有class=theClass
,后面有4个。
我的问题现在就开始了。我想编写一个表达式,它只返回<td class="theClass"></td>
标记之后的第一组元素,无论有多少组跟随它。
如果我在此HTML上运行我的代码
<table id="theTable">
<tbody>
<tr class="theClass">A</tr>
<tr class="theClass">B</tr>
<tr>1</tr>
<tr>2</tr>
<tr>3</tr>
<tr>4</tr>
<tr>5</tr>
<tr>6</tr>
</tbody>
</table>
它将输出0和0。
所以这不好。
有人有什么想法吗?
谢谢!
答案 0 :(得分:6)
现在,我要做的是只获得那些元素 在
B
和C
个节点之间
使用此单个XPath表达式:
/*/*/tr[.='B']
/following-sibling::*
[count(.|/*/*/tr[. ='C']/preceding-sibling::*)
=
count(/*/*/tr[. ='C']/preceding-sibling::*)
]
以下是基于XSLT的验证:
<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=
"/*/*/tr[.='B']
/following-sibling::*
[count(.|/*/*/tr[. ='C']/preceding-sibling::*)
=
count(/*/*/tr[. ='C']/preceding-sibling::*)
]
"/>
</xsl:template>
</xsl:stylesheet>
在第一个提供的XML文档上应用此转换时:
<table id="theTable">
<tbody>
<tr class="theClass">A</tr>
<tr class="theClass">B</tr>
<tr>1</tr>
<tr>2</tr>
<tr>3</tr>
<tr>4</tr>
<tr>5</tr>
<tr class="theClass">C</tr>
<tr class="theClass">D</tr>
<tr>6</tr>
<tr>7</tr>
<tr>8</tr>
<tr>9</tr>
<tr>10</tr>
<tr>11</tr>
<tr>12</tr>
<tr>13</tr>
<tr>14</tr>
<tr>15</tr>
<tr class="theClass">E</tr>
<tr class="theClass">F</tr>
<tr>16</tr>
<tr>17</tr>
<tr>18</tr>
<tr>19</tr>
<tr>20</tr>
<tr>21</tr>
<tr>22</tr>
</tbody>
</table>
评估XPath表达式并将选定的节点复制到输出中:
<tr>1</tr>
<tr>2</tr>
<tr>3</tr>
<tr>4</tr>
<tr>5</tr>
<强>解释强>:
这里我们只使用 Kayessian公式进行节点集交集:
$ns1[count(.|$ns2) = count($ns2)]
我们将$ns1
替换为:
/*/*/tr[.='B']
/following-sibling::*
我们将$ns2
替换为:
/*/*/tr[. ='C']/preceding-sibling::*
第二个问题:
我的问题现在就开始了。我想写一个表达式 只返回
<td class="theClass"></td>
标记之后的第一组元素,无论有多少组 跟随它。
再次选择那些元素的单个XPath表达式:
/*/*/tr[@class='theClass'
and
following-sibling::*[1][self::tr[not(@*)] ]
][1]
/following-sibling::tr
[not(@*)
and
count(preceding-sibling::tr
[@class='theClass'
and
following-sibling::*[1][self::tr[not(@*)] ]
]
)
= 1
]
<强>解释强>:
这将选择tr
个*/*/tr
元素的所有后续兄弟class
个元素(满足多个条件),其"theClass"
属性的字符串值为tr
且其第一个跟随element sibling是一个没有属性的tr
。
这些选定的tr
元素也满足的条件是两个:1)它们没有任何属性; 2)他们只有一个前面的兄弟class
元素,其"theClass"
属性的字符串值为<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=
"/*/*/tr[@class='theClass'
and
following-sibling::*[1][self::tr[not(@*)] ]
][1]
/following-sibling::tr
[not(@*)
and
count(preceding-sibling::tr
[@class='theClass'
and
following-sibling::*[1][self::tr[not(@*)] ]
]
)
= 1
]
"/>
</xsl:template>
</xsl:stylesheet>
。
以下是基于XSLT的验证:
<table id="theTable">
<tbody>
<tr class="theClass">A</tr>
<tr class="theClass">B</tr>
<tr>1</tr>
<tr>2</tr>
<tr>3</tr>
<tr>4</tr>
<tr>5</tr>
<tr>6</tr>
</tbody>
</table>
应用于第二个提供的XML文档:
<tr>1</tr>
<tr>2</tr>
<tr>3</tr>
<tr>4</tr>
<tr>5</tr>
<tr>6</tr>
再次输出所需和正确选择的元素:
{{1}}
答案 1 :(得分:1)
如果你不必使用XPath,那么一些LINQ可能更容易正确并且更易读。
在你的情况下,Skip和TakeWhile的组合类似于以下伪代码可以起作用:
nav.Select("//table[@id='theTable']/tbody/tr") // whatever to get list of all TR
.Skip("theClass is B") // some condition to skip up to first node
.TakeWhile("theClass is C"); // some condition to take upto second node.