你已经很远了,你已经很好地分析了你需要处理的数据。另外,您说要解析数据的方式对我来说看起来非常好。唯一可能会有所改进的是你注意不要一次做太多事情。
这样做的一种方法是将问题分成更小的问题。我将向您展示如何将代码放入多个函数和方法中。但是让我们从一个函数开始,这是一步一步的,所以你可以尝试按照示例来构建它。
在 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
如果我们为元数据元素保留一个附加属性,我们可以在搜索时转换这些元素。如果找到一些数据并且元素尚未转换,它将被转换。
因为我们都是在 search 方法里面做的,所以使用 search 方法的代码应该不会有太大的变化(如果甚至没有 - 取决于你的详细需求,我可能没有完全掌握这些,但我认为你明白了)。所以让我们开始工作吧。因为我们不想一次全部完成,所以我们创建了多个新方法来:
- 转换元数据元素
- 在原始元素中搜索(我们已经有了这段代码,我们只是移动它)
在此过程中,我们还将创建我们认为有用的方法,您会注意到这也是您已经编写的部分代码(如在 search() 中),它现在只是放置在对象内部$xml
- 它更自然地属于。
然后最后将这些新方法放在现有search()
方法中。
所以首先,我们创建一个辅助方法来将这个选项卡行解析成一个数组。它基本上是你的代码,你不需要前面的字符串trim
,这是唯一的区别。因为这个函数只在内部需要,所以我们将其设为私有:
private function asExplodedString() {
return explode("\t", trim($this));
}
从它的名字可以清楚地看出它的作用。它返回本身的标签爆炸数组。如果你还记得,我们在里面$xml
,所以现在每个 xml 元素都有这个方法。如果您还没有完全理解这一点,请继续,您可以在下面看到它是如何工作的,我们只添加了一个方法作为帮助程序:
public function getParent() {
list($parent) = $this->xpath('..') + array(0 => NULL);
return $parent;
}
此函数允许我们检索元素的父元素。这很有用,因为如果我们找到一个数据元素,我们想要转换作为父元素的元数据元素。而且因为这个功能是通用的,所以我选择将其公开。所以它也可以在外部代码中使用。它解决了一个常见问题,因此不像explode方法那样具有特定性质。
所以现在我们要转换一个元数据元素。虽然上面这两个辅助方法需要更多的代码行,但是由于这些事情并不复杂。
我们只是假设调用此方法的元素是元数据元素。我们不在这里添加检查以保持代码小。由于这又是一个私有函数,我们甚至不需要检查:如果在错误的元素上调用此方法,则错误已在类本身内部完成 - 而不是来自外部代码。这也是我在这里使用私有方法的一个很好的例子,它更具体。
所以我们现在对元数据元素所做的实际上非常简单:我们获取里面的列元素,分解列名,然后我们遍历每个数据元素,也分解数据,然后清空数据元素将列命名的子项添加到它。最后,我们添加一个属性来将元素标记为已转换:
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 元素上。两种新方法:一种根据其属性获取元数据元素:
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}')]]");
}
然后在主搜索方法中使用这两种方法。首先通过其属性查找元数据元素将对其进行轻微更改。然后将检查是否需要转换,然后按 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;
}
}
也不算太坏。许多只有几行代码的小方法,即(相对)易于遵循!
所以我希望这能给你一些启发,我知道这是一个相当值得阅读的文本。玩得开心!