PHP simplexml xpath在包含制表符分隔文本的元素中搜索值?
如何执行PHP simplexml xpath搜索以制表符分隔的元素中的文本值,并从该元素返回与搜索文本偏移位置不同的文本? 假设我希望找到包含值“2”的数据元素,并返回LongValue“Academy” xml文档的格式如下PHP simplexml xpath在包含制表符分隔文本的元素中搜索值?,php,xpath,simplexml,Php,Xpath,Simplexml,如何执行PHP simplexml xpath搜索以制表符分隔的元素中的文本值,并从该元素返回与搜索文本偏移位置不同的文本? 假设我希望找到包含值“2”的数据元素,并返回LongValue“Academy” xml文档的格式如下 <METADATA Resource="Property" Lookup="Area"> <COLUMNS>->fieldname *(->fieldname)-></COLUMNS> *(
<METADATA Resource="Property" Lookup="Area">
<COLUMNS>->fieldname *(->fieldname)-></COLUMNS>
*(<DATA>->fielddata *(->fielddata)-></DATA>)
</METADATA>
Note: ignore spaces
*() means 1 or more
-> is tab chr(9)
这是XML文档
<METADATA Resource="Property" Lookup="Area">
<COLUMNS> LongValue ShortValue Value </COLUMNS>
<DATA> Salado Sal 5 </DATA>
<DATA> Academy Aca 2 </DATA>
<DATA> Rogers Rog 1 </DATA>
<DATA> Bartlett Bar 4 </DATA>
</METADATA>
谢谢你的帮助
更新。。。发帖后,我想到了这个
//get index of 'Values' column from COLUMNS element
$node = $this->xml->xpath(
"//METADATA[@Resource='{$resource}' and @Lookup='{$lookup}']"
."/COLUMNS");
if($node) {
//array of column names
$columns = explode("\t", strtolower((string) trim($node[0])));
$long_value_index = array_search('longvalue', $columns);
} else {
echo 'not found';
exit;
}
现在使用$index可以从正确的偏移量返回LongValue
$LongValue = $x[$long_value_index];
任何想法你已经走得很远了,你已经很好地分析了需要处理的数据。另外,您所说的要解析数据的方式对我来说也非常合适。唯一可以稍微改进的是,你要注意不要一下子做太多 一种方法是将问题分成几个小问题。我将向您展示如何将代码放入多个函数和方法中。但是让我们从一个函数开始,这是一步一步进行的,因此您可以尝试按照示例来构建它 在PHP中分离问题的一种方法是使用函数。例如,编写一个函数在XML文档中进行搜索,这会使代码看起来更好,更准确:
/**
* search metadata element
*
*
* @param SimpleXMLElement $xml
* @param string $resource metadata attribute
* @param string $lookup metadata attribute
* @param string $value search value
*
* @return SimpleXMLElement
*/
function metadata_search(SimpleXMLElement $xml, $resource, $lookup, $value) {
$xpath = "//METADATA[@Resource='{$resource}' and @Lookup='{$lookup}']"
."/DATA[contains(., '{$find}')]";
list($element)= $xml->xpath($xpath);
return $element;
}
因此,现在您可以轻松地搜索文档,并命名和记录参数。只需调用函数并获取返回值:
$data = metadata_search($xml, 'Property', 'Area', 2);
这可能不是一个完美的函数,但它已经是一个示例。在函数旁边,还可以创建对象。对象是具有自己上下文的函数。这就是为什么这些函数被称为方法,它们属于对象。类似于SimpleXMLElement的xpath()
方法
如果看到上面的函数,第一个参数是$xml
对象。在此基础上,执行xpath方法。最后,这个函数真正做的是基于输入变量创建和运行xpath查询
如果我们可以将该函数直接带到$xml
对象中,我们就不再需要将其作为第一个参数传递。这是下一步,它通过扩展simplexmlement
工作。我们只添加了一个新的方法来进行搜索,该方法与上面的方法基本相同。我们还扩展了simplexmlement
,这意味着我们创建了它的子类型:这就是它已经拥有的全部,再加上您添加的新方法:
class MetadataElement extends SimpleXMLElement
{
/**
* @param string $resource metadata attribute
* @param string $lookup metadata attribute
* @param string $value search value
*
* @return SimpleXMLElement
*/
public function search($resource, $lookup, $value) {
$xpath = "//METADATA[@Resource='{$resource}' and @Lookup='{$lookup}']"
."/DATA[contains(., '{$value}')]";
list($element)= $this->xpath($xpath);
return $element;
}
}
为了实现这一点,我们需要在加载XML字符串时提供此类的名称。然后可以直接调用搜索方法:
$xml = simplexml_load_string($xmlString, 'MetadataElement');
$data = $xml->search('Property', 'Area', 2);
瞧,搜索现在是用SimpleXMLElement
但是如何处理这个$data
?它只是一个XML元素,仍然包含选项卡
更糟糕的是,上下文丢失了:这属于哪个元数据列?这是你的问题。所以我们需要下一步解决这个问题——但如何解决呢
老实说,有很多方法可以做到这一点。我的一个想法是基于元数据元素从XML中创建一个表对象:
list($metadata) = $xml->xpath('//METADATA[1]');
$csv = new CsvTable($metadata);
echo $csv;
private function searchColumn($column, $value) {
return $this->xpath("DATA[{$column}[contains(., '{$value}')]]");
}
即使具有良好的调试输出:
+---------+----------+-----+
|LongValue|ShortValue|Value|
+---------+----------+-----+
|Salado |Sal |5 |
+---------+----------+-----+
|Academ |Aca |2 |
+---------+----------+-----+
|Rogers |Rog |1 |
+---------+----------+-----+
|Bartlett |Bar |4 |
+---------+----------+-----+
但是,如果您可能不熟悉对象的编程,那么这可能是一个很大的工作,因此单独构建一个完整的表模型可能有点太多了
因此我有了一个想法:为什么不继续使用您已经使用的XML对象,并对其中的XML进行一些更改,使其具有更好的格式,以满足您的需要。发件人:
<METADATA Resource="Property" Lookup="Area">
<COLUMNS> LongValue ShortValue Value </COLUMNS>
<DATA> Salado Sal 5 </DATA>
如果我们在元数据元素中留下一个附加属性,我们可以在搜索时转换这些元素。如果找到某些数据,而元素尚未转换,则将转换它
因为我们都是在搜索方法中完成这项工作的,所以使用搜索方法的代码一定不会有太大的变化(如果根本没有变化的话——取决于您的具体需求,我可能还没有完全掌握这些,但我想您已经明白了)。让我们把这个付诸实践。因为我们不想一次完成这一切,我们创建了多个新方法来:
$xml
对象中,它更自然地属于该对象
最后,这些新方法将放在现有的search()
方法中
首先,我们创建一个helper方法来将这个选项卡行解析为一个数组。这基本上是您的代码,您不需要在trim
前面强制转换字符串,这是唯一的区别。由于此功能仅在内部需要,因此我们将其设置为私有:
private function asExplodedString() {
return explode("\t", trim($this));
}
从它的名字来看,它的作用是显而易见的。它将返回自身的制表符分解数组。如果您还记得,我们在$xml
中,所以现在每个xml元素都有这个方法。如果您还没有完全理解这一点,请继续,您可以在下面看到它是如何工作的,我们只添加了一个方法作为帮助器:
public function getParent() {
list($parent) = $this->xpath('..') + array(0 => NULL);
return $parent;
}
此函数允许我们检索元素的父元素。这很有用,因为如果我们找到一个数据元素,我们希望转换作为父元素的元数据元素。因为这个函数是通用的,所以我选择将它公开。因此,它也可以在外部代码中使用。它解决了一个常见的问题,因此不像爆炸方法那样具有特定的性质
现在我们要转换一个元数据元素。虽然上面这两个助手方法需要更多的代码行,但是由于这些,事情不会变得复杂
我们只假设调用此方法的元素是元数据元素。我们不会在这里添加检查来保持代码的小规模。由于这又是一个私有函数,我们甚至不需要检查:是否调用了此方法
$xml = simplexml_load_string($xmlString, 'MetadataElement');
$data = $xml->search('Property', 'Area', 5);
echo $data->Value; # 5
echo $data->LongValue; # Salado
private function asExplodedString() {
return explode("\t", trim($this));
}
public function getParent() {
list($parent) = $this->xpath('..') + array(0 => NULL);
return $parent;
}
private function transform() {
$columns = $this->COLUMNS->asExplodedString();
foreach ($this->DATA as $data) {
$values = $data->asExplodedString();
$data[0] = ''; # set the string of the element (make <DATA></DATA> empty)
foreach ($columns as $index => $name) {
$data->addChild($name, $values[$index]);
}
}
$this['transformed'] = 1;
}
public function search($resource, $lookup, $value) {
$xpath = "//METADATA[@Resource='{$resource}' and @Lookup='{$lookup}']"
. "/DATA[contains(., '{$value}')]";
list($element) = $this->xpath($xpath);
$element->getParent()->transform();
###################################
return $element;
}
$data = $xml->search('Property', 'Area', 2);
echo $data->asXML();
<DATA>
<LongValue>Academ</LongValue>
<ShortValue>Aca</ShortValue>
<Value>2</Value>
</DATA>
echo $data->getParent()->asXML();
<METADATA Resource="Property" Lookup="Area" transformed="1">
<COLUMNS> LongValue ShortValue Value </COLUMNS>
<DATA>
<LongValue>Salado</LongValue>
<ShortValue>Sal</ShortValue>
<Value>5</Value>
</DATA>
...
private function getMetadata($resource, $lookup) {
$xpath = "//METADATA[@Resource='{$resource}' and @Lookup='{$lookup}']";
list($metadata) = $this->xpath($xpath);
return $metadata;
}
private function searchColumn($column, $value) {
return $this->xpath("DATA[{$column}[contains(., '{$value}')]]");
}
public function search($resource, $lookup, $value)
{
$metadata = $this->getMetadata($resource, $lookup);
if (!$metadata['transformed']) {
$metadata->transform();
}
list($element) = $metadata->searchColumn('Value', $value);
return $element;
}
$xml = simplexml_load_string($xmlString, 'MetadataElement');
$data = $xml->search('Property', 'Area', 2);
echo $data->LongValue, "\n"; # Academ
/**
* MetadataElement - Example for extending SimpleXMLElement
*
* @link http://stackoverflow.com/q/16281205/367456
*/
class MetadataElement extends SimpleXMLElement
{
/**
* @param string $resource metadata attribute
* @param string $lookup metadata attribute
* @param string $value search value
*
* @return SimpleXMLElement
*/
public function search($resource, $lookup, $value)
{
$metadata = $this->getMetadata($resource, $lookup);
if (!$metadata['transformed']) {
$metadata->transform();
}
list($element) = $metadata->searchColumn('Value', $value);
return $element;
}
private function getMetadata($resource, $lookup) {
$xpath = "//METADATA[@Resource='{$resource}' and @Lookup='{$lookup}']";
list($metadata) = $this->xpath($xpath);
return $metadata;
}
private function searchColumn($column, $value) {
return $this->xpath("DATA[{$column}[contains(., '{$value}')]]");
}
private function asExplodedString() {
return explode("\t", trim($this));
}
public function getParent() {
list($parent) = $this->xpath('..') + array(0 => NULL);
return $parent;
}
private function transform() {
$columns = $this->COLUMNS->asExplodedString();
foreach ($this->DATA as $data) {
$values = $data->asExplodedString();
$data[0] = ''; # set the string of the element (make <DATA></DATA> empty)
foreach ($columns as $index => $name) {
$data->addChild($name, $values[$index]);
}
}
$this['transformed'] = 1;
}
}