解析PHP的XML

时间:2018-06-12 15:21:29

标签: php xml parsing

我需要解析这个包含一些自定义标记的XML文件,如下所示:

    <?xml version="1.0" encoding="utf-8"?>
<glz:Config xmlns:glz="http://www.glizy.org/dtd/1.0/">
    <glz:Import src="config.xml" />

    <glz:Group name="thumbnail">
        <glz:Param name="width" value="200" />
        <glz:Param name="height" value="*" />
    </glz:Group>
</glz:Config>

当它到达标记<glz:Import src="config.xml" />时,它需要解析文件 config.xml ,其中包含如下内容:

    <?xml version="1.0" encoding="utf-8"?>
<glz:Config xmlns:glz="http://www.glizy.org/dtd/1.0/">
    <glz:Group name="folder">
        <glz:Param name="width" value="100" />
        <glz:Param name="height" value="200" />
    </glz:Group>
</glz:Config>

最终结果应该是这样的数组。它包含两个已解析文件的值:

$result['thumbnail/width'] = 200;
$result['thumbnail/height'] = '*';
$result['folder/width'] = 100;
$result['folder/height'] = 200;

这就是我管理XML解析的方法。我的问题是我不知道如何将新结果与已经(旧)解析的结果合并。在这里你可以看到我的代码:

function parseFile(){
            $reader = new XMLReader;
            $reader->open($this->fileName);

            while ($reader->read()){
                if ($reader->name == 'glz:Group')
                {
                    $groupName = $reader->getAttribute('name');
                    $reader->read();
                    $reader->read();

                    while ($reader->name == 'glz:Param')
                    {
                        if (strpos($reader->getAttribute('name'),'[]')  == true)
                        {
                            $arrayGroupName = substr($reader->getAttribute('name'), 0, -2);
                            if(empty($filters[$groupName.'/'.$arrayGroupName]))
                            {
                                $filters[$groupName.'/'.$arrayGroupName] = array();
                                array_push($filters[$groupName.'/'.$arrayGroupName],$this->castValue($reader->getAttribute('value')));
                                $this->result[$groupName."/".$arrayGroupName] = $filters[$groupName.'/'.$arrayGroupName];
                            }
                            else
                            {
                                array_push($filters[$groupName.'/'.$arrayGroupName],$this->castValue($reader->getAttribute('value')));
                                $this->result[$groupName."/".$arrayGroupName] = $filters[$groupName.'/'.$arrayGroupName];
                            }
                        }
                        else
                        {
                            $this->result[$groupName."/".$reader->getAttribute('name')] = $this->castValue($reader->getAttribute('value'));
                        }
                        $reader->read();
                        $reader->read();
                    }
                }
                else if ($reader->name == 'glz:Param')
                {
                    if (strpos($reader->getAttribute('name'),'[]')  == true)
                    {
                        $arrayGroupName = substr($reader->getAttribute('name'), 0, -2);
                        if(empty($filters[$arrayGroupName]))
                        {
                            $filters[$arrayGroupName] = array();
                            array_push($filters[$arrayGroupName],$this->castValue($reader->getAttribute('value')));
                            $this->result[$$arrayGroupName] = $filters[$arrayGroupName];
                        }
                        else
                        {
                            array_push($filters[$arrayGroupName],$this->castValue($reader->getAttribute('value')));
                            $this->result[$arrayGroupName] = $filters[$arrayGroupName];
                        }
                    }
                    else
                    {
                        $this->result[$reader->getAttribute('name')] = $this->castValue($reader->getAttribute('value'));
                    }
                }
                else if ($reader->name == 'glz:Import')
                {
                    $file = $reader->getAttribute('src');
                    $newConfig = new Config($file);
                    $newConfig->parseFile();
                }
            }
            return $this->result;

        }

当我找到标记 时,如何每次合并解析文件时得到的结果?

非常感谢你!

2 个答案:

答案 0 :(得分:1)

据我了解你的问题,你需要稍微重构一下代码。

重写解析器函数,而不引用$ this-&gt; result和$ this-&gt; fileName。

将您函数中的这些变量重新声明为$ result和$ fileName。 将$ fileName添加为函数参数。

在函数中添加另一个变量$ result_config。

当你阅读配置标签时, 以递归方式调用函数而不是创建新类:

 -$file = $reader->getAttribute('src');
 - $newConfig = new Config();

 + $file = $reader->getAttribute('src');
 + $result_config = $this->parseFile($file);

然后在完成两个文件后最终合并两个结果:

if ($result_config) {
    $this->result = array_merge($result_config, $this->result);
}
return $this->result;

答案 1 :(得分:1)

您需要将读取逻辑放入一个以文件名作为参数的函数中,以便在找到Import元素时调用自身。让函数将值作为数组返回并合并结果。

在DOM中,这不太复杂:

function readConfigurationFile($fileName) {
  $document = new DOMDocument();
  $document->load($fileName);
  $xpath = new DOMXpath($document);
  $xpath->registerNamespace('g', 'http://www.glizy.org/dtd/1.0/');

  $result = [];
  foreach ($xpath->evaluate('/g:Config/*[self::g:Import or self::g:Group]') as $node) {
    switch ($node->localName) {
    case 'Import' :
      $result = array_merge($result, readConfigurationfile($node->getAttribute('src')));
      break;
    case 'Group' :
      $groupName = $node->getAttribute('name'); 
      foreach ($xpath->evaluate('g:Param', $node) as $paramNode) {
        $result[
          sprintf('%s/%s', $groupName, $paramNode->getAttribute('name'))
        ] = $paramNode->getAttribute('value');
      } 
      break;
    }
  }
  return $result;
}

var_dump(readConfigurationFile('main.xml'));

输出:

array(4) {
  ["folder/width"]=>
  string(3) "100"
  ["folder/height"]=>
  string(3) "200"
  ["thumbnail/width"]=>
  string(3) "200"
  ["thumbnail/height"]=>
  string(1) "*"
}

XMLReader中的方法相同,但有点复杂。

function readLargeConfigurationFile($fileName) {

  $reader = new XMLReader();
  $reader->open($fileName);

  $xmlns = 'http://www.glizy.org/dtd/1.0/';
  $document = new DOMDocument();
  $xpath = new DOMXpath($document);
  $xpath->registerNamespace('g', $xmlns);

  $result = [];

  // find the first Import or Group in the namespace
  do {
    $found = $reader->read();
  } while(
    $found && 
    !(
       $reader->namespaceURI === $xmlns && 
       ($reader->localName === 'Import' || $reader->localName === 'Group')
    )
  );

  while ($found) {
    switch ($reader->localName) {
    case 'Import' :
      $result = array_merge($result, readLargeConfigurationFile($reader->getAttribute('src')));
      break;
    case 'Group' :
      // expand Group into DOM for easier access
      $groupNode = $reader->expand($document);
      $groupName = $groupNode->getAttribute('name'); 
      foreach ($xpath->evaluate('g:Param', $groupNode) as $paramNode) {
        // read a Param
        $result[
          sprintf('%s/%s', $groupName, $paramNode->getAttribute('name'))
        ] = $paramNode->getAttribute('value');
      } 
      break;
    }

    // iterate sibling nodes to find the next Import or Group
    do {
      $found = $reader->next();
    } while(
      $found && 
      !(
        $reader->namespaceURI === $xmlns && 
        ($reader->localName === 'Import' || $reader->localName === 'Group')
      )
    ); 
  } 
  return $result;
}

var_dump(readLargeConfigurationFile('main.xml'));

请注意,该示例不使用$name属性。它包含名称空间别名/前缀glz。命名空间前缀是可选的,可以更改 - 即使在单个文档中也是如此。使用$localName$namespaceURI属性。

使用XMLReader::expand(),您可以将当前节点扩展为DOM。一种典型的方法是使用XML阅读器仅迭代外部节点。如果您知道节点及其后代足够小,则将它们扩展为DOM以便于访问。