Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/xpath/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
PHP simplexml xpath在包含制表符分隔文本的元素中搜索值?_Php_Xpath_Simplexml - Fatal编程技术网

PHP simplexml xpath在包含制表符分隔文本的元素中搜索值?

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> *(

如何执行PHP simplexml xpath搜索以制表符分隔的元素中的文本值,并从该元素返回与搜索文本偏移位置不同的文本?

假设我希望找到包含值“2”的数据元素,并返回LongValue“Academy”

xml文档的格式如下

    <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>
如果我们在元数据元素中留下一个附加属性,我们可以在搜索时转换这些元素。如果找到某些数据,而元素尚未转换,则将转换它

因为我们都是在搜索方法中完成这项工作的,所以使用搜索方法的代码一定不会有太大的变化(如果根本没有变化的话——取决于您的具体需求,我可能还没有完全掌握这些,但我想您已经明白了)。让我们把这个付诸实践。因为我们不想一次完成这一切,我们创建了多个新方法来:

  • 转换元数据元素
  • 在原始元素中搜索(我们已经有了这个代码,我们只是移动它)
  • 在此过程中,我们还将创建我们认为有用的方法,您会注意到这也是您已经编写的部分代码(如在search()中),它现在被放置在
    $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;
        }
    }