编辑:我已添加了适用于此案例的解决方案。
我想从页面中提取一个表,我想(可能)使用DOMDocument和XPath。但如果你有更好的主意,请告诉我。
我的第一次尝试就是这个(显然是错误的,因为它会得到第一个关闭表标签):
<?php
$tableStart = strpos($source, '<table class="schedule"');
$tableEnd = strpos($source, '</table>', $tableStart);
$rawTable = substr($source, $tableStart, ($tableEnd - $tableStart));
?>
我很难,这可能是用DOMDocument和/或xpath解决的......
最后,我希望标签之间的所有内容(在本例中为标签)和标签都是自己的。所有HTML,不仅仅是价值(例如,不只是&#39;价值&#39;但是&#39;价值&#39;)。并且有一个&#39; catch&#39; ...
这可能吗?
这是我想要从另一个网站提取的(简化的)源代码:(我还想显示html标签,而不仅仅是值,所以整个表格带有类&#39; schedule&#39; )
<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>
答案 0 :(得分:8)
首先,请注意XPath基于XML Infopath--一种XML模型,其中没有“起始标记”和“结束标记”,只有节点 强>
因此,不应期望XPath表达式选择“标签” - 它选择节点。
考虑到这一事实,我将问题解释为:
我想获取给定“开始”之间所有元素的集合 元素和给定的“结束元素”,包括开始和结束元素。
在XPath 2.0中,可以使用标准运算符 intersect 方便地完成此操作。
在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>
:
选择了start元素//table[@class = 'target']
//td[. = 'Starting Node']
结束元素由:
选择//table[@class = 'target']
//td[. = Ending Node']
要获得所有想要的节点,我们将以下两组相交:
包含start元素和所有后续元素的集合(我们将此名称命名为$vFollowing
)。
由结束元素和所有前面的元素组成的集合(我们将其命名为$vPreceding
)。
分别通过以下XPath表达式选择:
$ vFollowing:
$vStartNode | $vStartNode/following::*
$ vPreceding:
$vEndNode | $vEndNode/preceding::*
现在我们只需在节点集$vFollowing
和$vPreceding
上应用Kayessian公式:
$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>
答案 1 :(得分:1)
不要使用正则表达式(或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.
}
答案 2 :(得分:0)
如果你有像这样的格式良好的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();
?>
答案 3 :(得分:-3)
这得到了整个表格。但它可以修改为让它抓住另一个标签。这是一个非常具体的解决方案,只能在特定情况下使用。如果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));
}