编辑:我添加了一个在这种情况下有效的解决方案。
我想
从页面中提取一个表,我想(可能(使用 DOMDocument 和 XPath 来做到这一点。但如果你有更好的主意,告诉我。
我的第一次尝试是这样的(显然是错误的,因为它会得到第一个关闭表标签(:
<?php
$tableStart = strpos($source, '<table class="schedule"');
$tableEnd = strpos($source, '</table>', $tableStart);
$rawTable = substr($source, $tableStart, ($tableEnd - $tableStart));
?>
我很难,这可能可以通过 DOMDocument 和/或 xpath 来解决......
最后,我想要标签(在本例中为标签(和标签本身之间的所有内容。所以所有的HTML,而不仅仅是值(例如,不仅仅是"值",而是"值"(。还有一个"陷阱"...
- 该表中有其他表。因此,如果您只是搜索表的末尾(' tag'(,您可能会得到错误的标签。
- 开始标签有一个类,你可以用它来识别它(类名='schedule'(。
这可能吗?
这是我想从另一个网站提取的(简化的(源部分:(我还想显示 html 标签,而不仅仅是值,所以整个表都带有类"schedule"(
<table class="schedule">
<table class="annoying nested table">
Lots of table rows, etc.
</table> <-- The problematic tag...
<table class="annoying nested table">
Lots of table rows, etc.
</table> <-- The problematic tag...
<table class="annoying nested table">
Lots of table rows, etc.
</table> <-- a problematic tag...
This could even be variable content. =O =S
</table>
首先,请注意XPath是基于XML Infopath的 - 一个XML模型,其中没有"开始标记"和"结束标记",只有节点
因此,不应期望 XPath 表达式选择"标记"——它选择节点。
考虑到这一事实,我将这个问题解释为:
我想获取给定"开始"之间的所有元素的集合 元素和给定的"结束元素",包括开始和结束元素。
在 XPath 2.0 中,这可以通过标准运算符相交方便地完成。
在XPath 1.0(我假设您正在使用(中,这并不容易。解决方案是使用 Kayessian(通过 @Michael Kay(公式进行节点集交集:
通过计算以下 XPath 表达式来选择两个节点集的交集:$ns1
和 $ns2
:
$ns1[count(.|$ns2) = count($ns2)]
假设我们有以下 XML 文档(因为您从未提供过(:
<html>
<body>
<table>
<tr valign="top">
<td>
<table class="target">
<tr>
<td>Other Node</td>
<td>Other Node</td>
<td>Starting Node</td>
<td>Inner Node</td>
<td>Inner Node</td>
<td>Inner Node</td>
<td>Ending Node</td>
<td>Other Node</td>
<td>Other Node</td>
<td>Other Node</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
起始元素由以下方式选择:
//table[@class = 'target']
//td[. = 'Starting Node']
结束元素由以下方法选择:
//table[@class = 'target']
//td[. = Ending Node']
为了获得所有想要的节点,我们将以下两个集合相交:
由开始元素和所有后续元素组成的集合(我们将其命名为
$vFollowing
(。由结束元素和所有前面元素组成的集合(我们将其命名为
$vPreceding
(。
它们分别由以下 XPath 表达式选择:
$vFollowing:
$vStartNode | $vStartNode/following::*
$vPreceding:
$vEndNode | $vEndNode/preceding::*
现在我们可以简单地将 Kayessian 公式应用于节点集$vFollowing
和$vPreceding
:
$vFollowing
[count(.|$vPreceding)
=
count($vPreceding)
]
剩下的就是用它们各自的表达式替换所有变量。
基于 XSLT 的验证:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vStartNode" select=
"//table[@class = 'target']//td[. = 'Starting Node']"/>
<xsl:variable name="vEndNode" select=
"//table[@class = 'target']//td[. = 'Ending Node']"/>
<xsl:variable name="vFollowing" select=
"$vStartNode | $vStartNode/following::*"/>
<xsl:variable name="vPreceding" select=
"$vEndNode | $vEndNode/preceding::*"/>
<xsl:template match="/">
<xsl:copy-of select=
"$vFollowing
[count(.|$vPreceding)
=
count($vPreceding)
]"/>
</xsl:template>
</xsl:stylesheet>
当应用于上面的 XML 文档时,将计算 XPath 表达式,并输出所需的、正确的结果选择节点集:
<td>Starting Node</td>
<td>Inner Node</td>
<td>Inner Node</td>
<td>Inner Node</td>
<td>Ending Node</td>
不要使用正则表达式(或strpos
...(来解析 HTML!
这个问题对你来说很困难的部分原因是你在思考"标签"而不是"节点"或"元素"。标记是序列化的项目。(HTML 具有可选的结束标记。节点是实际的数据结构。DOMDocument没有"标签",只有以正确的树结构排列的"节点"。
以下是使用 XPath 获取表的方法:
// This is a simple solution, but only works if the value of "class" attribute is exactly "schedule"
// $xpath = '//table[@class="schedule"]';
// This is what you want. It is equivalent to the "table.schedule" css selector:
$xpath = "//table[contains(concat(' ',normalize-space(@class),' '),' schedule ')]";
$d = new DOMDocument();
$d->loadHTMLFile('http://example.org');
$xp = new DOMXPath($d);
$tables = $xp->query($xpath);
foreach ($tables as $table) {
$table; // this is a DOMElement of a table with class="schedule"; It includes all nodes which are children of it.
}
如果你有像这样的格式良好的HTML
<html>
<body>
<table>
<tr valign='top'>
<td>
<table class='inner'>
<tr><td>Inner Table</td></tr>
</table>
</td>
<td>
<table class='second inner'>
<tr><td>Second Inner</td></tr>
</table>
</td>
</tr>
</table>
</body>
</html>
使用此pho代码输出节点(在xml包装器中(
<?php
$xml = new DOMDocument();
$strFileName = "t.xml";
$xml->load($strFileName);
$xmlCopy = new DOMDocument();
$xmlCopy->loadXML( "<xml/>" );
$xpath = new domxpath( $xml );
$strXPath = "//table[@class='inner']";
$elements = $xpath->query( $strXPath, $xml );
foreach( $elements as $element ) {
$ndTemp = $xmlCopy->importNode( $element, true );
$xmlCopy->documentElement->appendChild( $ndTemp );
}
echo $xmlCopy->saveXML();
?>
这将获取整个表。但是可以对其进行修改以使其获取另一个标签。这是一个非常特定于案例的解决方案,只能在特定情况下使用。如果 html、php 或 css 注释包含开始或结束标记,则中断。请谨慎使用。
功能:
// **********************************************************************************
// Gets a whole html tag with its contents.
// - Source should be a well formatted html string (get it with file_get_contents or cURL)
// - You CAN provide a custom startTag with in it e.g. an id or something else (<table style='border:0;')
// This is recommended if it is not the only p/table/h2/etc. tag in the script.
// - Ignores closing tags if there is an opening tag of the same sort you provided. Got it?
function getTagWithContents($source, $tag, $customStartTag = false)
{
$startTag = '<'.$tag;
$endTag = '</'.$tag.'>';
$startTagLength = strlen($startTag);
$endTagLength = strlen($endTag);
// *****************************
if ($customStartTag)
$gotStartTag = strpos($source, $customStartTag);
else
$gotStartTag = strpos($source, $startTag);
// Can't find it?
if (!$gotStartTag)
return false;
else
{
// *****************************
// This is the hard part: finding the correct closing tag position.
// <table class="schedule">
// <table>
// </table> <-- Not this one
// </table> <-- But this one
$foundIt = false;
$locationInScript = $gotStartTag;
$startPosition = $gotStartTag;
// Checks if there is an opening tag before the start tag.
while ($foundIt == false)
{
$gotAnotherStart = strpos($source, $startTag, $locationInScript + $startTagLength);
$endPosition = strpos($source, $endTag, $locationInScript + $endTagLength);
// If it can find another opening tag before the closing tag, skip that closing tag.
if ($gotAnotherStart && $gotAnotherStart < $endPosition)
{
$locationInScript = $endPosition;
}
else
{
$foundIt = true;
$endPosition = $endPosition + $endTagLength;
}
}
// *****************************
// cut the piece from its source and return it.
return substr($source, $startPosition, ($endPosition - $startPosition));
}
}
功能的应用:
$gotTable = getTagWithContents($tableData, 'table', '<table class="schedule"');
if (!$gotTable)
{
$error = 'Faild to log in or to get the tag';
}
else
{
//Do something you want to do with it, e.g. display it or clean it...
$cleanTable = preg_replace('|href=''(.*)''|', '', $gotTable);
$cleanTable = preg_replace('|TITLE="(.*)"|', '', $cleanTable);
}
上面你可以找到我问题的最终解决方案。在旧解决方案下面,我从中制作了一个通用功能。
旧解决方案:
// Try to find the table and remember its starting position. Check for succes.
// No success means the user is not logged in.
$gotTableStart = strpos($source, '<table class="schedule"');
if (!$gotTableStart)
{
$err = 'Can''t find the table start';
}
else
{
// *****************************
// This is the hard part: finding the closing tag.
$foundIt = false;
$locationInScript = $gotTableStart;
$tableStart = $gotTableStart;
while ($foundIt == false)
{
$innerTablePos = strpos($source, '<table', $locationInScript + 6);
$tableEnd = strpos($source, '</table>', $locationInScript + 7);
// If it can find '<table' before '</table>' skip that closing tag.
if ($innerTablePos != false && $innerTablePos < $tableEnd)
{
$locationInScript = $tableEnd;
}
else
{
$foundIt = true;
$tableEnd = $tableEnd + 8;
}
}
// *****************************
// Clear the table from links and popups...
$rawTable = substr($tableData, $tableStart, ($tableEnd - $tableStart));
}