2013-04-29 30 views
0

如何做一個PHP simplexml xpath在製表符分隔的ELEMENT中搜索文本值,並在與搜索文本偏移量不同的偏移處從同一元素返回文本?PHP simplexml xpath在包含製表符分隔文本的ELEMENT中搜索值?

假設我想找到包含'2'值的DATA元素並返回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) 

在COLUMNS元件下面的例子中包含三個列名(的longValue的shortValue),其可以是任何順序。

每個數據元素具有3個相應的製表符分隔的文本值,例如低於第一個數據元素包含

LongVlaue = 'Salado' 
    ShortValue = 'Sal' 
    Value = '5' 

下面是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> 

注意:列和數據元素具有文本標籤分隔爲3列,其中每列以標籤後跟文本開頭,然後最後一個標籤末尾

以下是我的想法:

1.)最好從COLUMNS元素中找到名爲'Value'的列的偏移量,然後嘗試從DATA元素中查找相應的文本,因爲'Value'列可以按任意順序排列,但是DATA元素將按該順序排列。

2.)在'Value'列中搜索包含文本的DATA元素並返回'LongValue'中的文本。

下面是一個xpath搜索的例子,其中一些有用但有缺陷,因爲它沒有考慮COLUMNS元素中Value列的偏移量,所以它可以正確地找到'Value '在DATA元素中的列。

下面是一個代碼剪斷,它:

$xml_text = ‘the xml document above’; 
$xml = simplexml_load_string($xml_text); //load the xml document 
$resource = 'Property'; //value for the Resource attribute METADATA. 
$lookup = 'Area'; //value for the Lookup attribute in METADATA 
$value = '2'; //the needle we are looking for 

$find = "\t" . $value . "\t"; 
/* 
adding tabs before and after the $value may be flawed, although each 
column starts with a tab followed by text, only the last column has 
the an extra tab. Not sure this would work properly if the column 
was in the middle, or if the ELEMENT happened to have multiple $value 
in the same element. */ 

    /* 
    Search for a specific METADATA element with matching 
    Resource and Lookup attributes */ 


$node = $this->xml->xpath(
      "//METADATA[@Resource='{$resource}' and @Lookup='{$lookup}']" 
      ."/DATA[contains(., '{$find}')]" 
     ); 

    $x = explode("\t", (string) trim($node[0])); //convert the tab delimited 
               //string to an array 

    echo print_r($x,true); //this shows what the array would look like, 
          //with out the trim there would be empty 
          //first and last array elements 

Array 
(
    [0] => Academy 
    [1] => Aca 
    [2] => 2 
) 


    $LongValue = $x[0]; //assuming the LongValue is in the first column 

    echo $LongValue; //this shows the LongValue retuned 
    Academy 

感謝您的幫助!

更新...發佈後,想出了這個...

//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; 
} 

現在用$指數,這可能從正常返回的longValue偏移

$LongValue = $x[$long_value_index]; 

任何想法

回答

1

你已經很遠了,你已經很好地分析了你需要處理的數據。另外你如何說你想分析數據看起來非常適合我。唯一可能會有所改善的是你要小心,不要一次做太多。

這樣做的一種方法是將問題分成更小的問題。我將告訴你如何將代碼放入多個函數和方法。但是讓我們從一個單一的函數開始,這是一步一步的,所以你可以試着按照例子來構建它。

在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); 

這可能不是一個完美的函數,但它已經是一個例子。在函數旁邊還可以創建對象。對象是具有自己的上下文的函數。這就是爲什麼這些函數被稱爲方法,然後,它們屬於該對象。類似於SimpleXMLElementxpath()方法。

如果看到上述功能,則第一個參數是$xml對象。然後執行xpath方法。最後,這個函數真正做的是根據輸入變量創建和運行xpath查詢。

如果我們可以直接將該函數轉換成$xml對象,那麼我們不需要再將此作爲第一個參數傳遞。這是下一步,它的工作原理是延伸SimpleXMLElement。我們只是添加一個新的方法,搜索和方法幾乎與上面相同。我們也從SimpleXMLElement延長,這意味着我們將創建一個子類的吧:這是所有它已經加上新的方法添加:

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; 

+---------+----------+-----+ 
|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> 

要:

<METADATA Resource="Property" Lookup="Area" transformed="1"> 
    <COLUMNS> LongValue ShortValue Value </COLUMNS> 
    <DATA> 
     <LongValue>Salado</LongValue><ShortValue>Sal</ShortValue><Value>5</Value> 
    </DATA> 

這將允許不僅每一個具體列名搜索也找到了數據元素的其他值。如果搜索返回$data元素:

$xml = simplexml_load_string($xmlString, 'MetadataElement'); 
$data = $xml->search('Property', 'Area', 5); 
echo $data->Value;  # 5 
echo $data->LongValue; # Salado 

如果我們把與元數據元素的附加屬性,而我們的搜索,我們可以將這些元素。如果找到一些數據並且元素尚未轉換,它將被轉換。

因爲我們都在做這個裏面的搜索方法,所以使用搜索方法的代碼一定不能改變太多(即使根本就沒有改變 - 依賴於你有的詳細需求,我可能沒有完全掌握那些,但我認爲你明白了)。所以讓我們把這個工作。因爲我們不想一下子做到這一點,我們創建了多種新的方法:

  1. 變換元數據元素
  2. 搜索原來的元素中(此代碼,我們已經,我們只是將它)

一路上我們也將創造我們認爲有用的方法,順便說一下,你會發現,這也是部分代碼,您已經(在搜索()等)已經寫的,它只是放在現在$xml對象 - 它更自然地屬於它。

然後最後這些新方法將放在現有的search()方法中。

所以首先,我們創建一個輔助方法來將這個標籤行解析成一個數組。這基本上是你的代碼,你不需要在trim前面投射的字符串,這是唯一的區別。因爲這個功能只在裏面需要,所以我們把它設爲私人的:

private function asExplodedString() { 
    return explode("\t", trim($this)); 
} 

它的名字很清楚它的作用。它給出了本身的tab-exploded陣列。如果你還記得,我們在$xml之內,所以現在每個xml元素都有這個方法。如果你不完全明白這一點呢,只是去上,你可以看到它是如何工作的正下方,我們只增加一個方法作爲輔助:

public function getParent() { 
    list($parent) = $this->xpath('..') + array(0 => NULL); 
    return $parent; 
} 

此功能允許我們檢索的父元素元件。這很有用,因爲如果我們找到一個數據元素,我們想要轉換父元數據元素。並且因爲這個功能是通用的,所以我選擇了public。所以它也可以在外部代碼中使用。它解決了一個共同的問題,因此不像爆炸方法那樣具有特定的性質。

所以現在我們要轉換一個元數據元素。雖然上面這兩個輔助方法需要更多的代碼,但由於這些方法並不複雜。

我們假設這個方法被調用的元素是元數據元素。我們不會在這裏添加支票以保持代碼小。由於這是一個私有函數,我們甚至不需要檢查:如果在錯誤的元素上調用此方法,則錯誤在類內部完成 - 而不是從外部代碼執行。這也是爲什麼我在這裏使用私有方法的一個很好的例子,它更具體。

所以我們現在對元數據元素所做的事實際上很簡單:我們獲取列元素,爆炸列名,然後我們遍歷每個數據元素,爆炸數據,然後清空數據-element僅用於將列名稱的子項添加到它。最後,我們添加一個屬性來標記要轉換的元素:

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; 
} 

然後我們輸出爲XML:

$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> 
    ... 

這表明代碼按預期工作。這可能已經解決了你的問題。例如。如果您總是搜索一個數字,而其他列不包含數字,並且您只需要搜索每個元數據塊一個。但可能不是,因此需要更改搜索功能以在內部執行正確的搜索和轉換。

這次我們再次利用$this將方法放在具體的XML元素上。兩個新methhods:一個是基於它的屬性得到一個元數據元素:

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。它看起來如何一目瞭然?

/** 
* 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; 
    } 
} 

也不錯。許多隻有一小段代碼的小方法,即(rel。)易於遵循!

所以我希望這給了一些靈感,我知道這是一個相當一些文字閱讀。玩的開心!

相關問題