【问题标题】:Identify XML element and copy parent node to external XML file using PHP使用 PHP 识别 XML 元素并将父节点复制到外部 XML 文件
【发布时间】:2017-09-29 11:02:13
【问题描述】:

我正在用 PHP 解析一个 XML 文件 (source.xml),需要识别 <property> 节点包含 <rent> 元素的实例。

一旦确定了该条目的整个 <property> 父节点,应将其复制到单独的 XML 文件 (destination.xml)。

在完成复制后,应从 source.xml 文件中删除 <property> 节点。

以下是 source.xml 文件的示例:

<?xml version="1.0" encoding="utf-8"?>
<root>
    <property>
        ...
        <rent>
            <term>long</term>
            <freq>month</freq>
            <price_peak>1234</price_peak>
            <price_high>1234</price_high>
            <price_medium>1234</price_medium>
            <price_low>1234</price_low>
        </rent>
        ...
    </property>
</root>

我已尝试将 DOM 与以下代码一起使用,但是尽管它们有数百个符合上述要求的节点,但我根本没有得到任何结果。这是我目前所拥有的:

$destination = new DOMDocument;
$destination->preserveWhiteSpace = true;
$destination->load('destination.xml');

$source = new DOMDocument;
$source->load('source.xml');

$xp = new DOMXPath($source);

foreach ($xp->query('/root/property/rent[term/freq/price_peak/price_high/price_medium/price_low]') as $item) {

    $newItem = $destination->documentElement->appendChild(

        $destination->createElement('property')

    );

    foreach (array('term', 'freq', 'price_peak', 'price_high', 'price_medium', 'price_low') as $elementName) {

        $newItem->appendChild(
            $destination->importNode(
                $item->getElementsByTagName($elementName)->property(0),
                true
            )
        );
    }
}

$destination->formatOutput = true;
echo $destination->saveXml();

我才刚刚开始学习 DOMDocument 及其用途,所以我显然在某个地方搞砸了,因此感谢任何帮助。非常感谢。

【问题讨论】:

    标签: php xml domdocument


    【解决方案1】:

    困难在于当您尝试将节点从一个文档复制到另一个文档时。您可以尝试重新创建节点,复制所有组件,但这是一项艰苦的工作(并且容易出错)。相反,您可以使用importNode 将节点从一个文档导入到另一个文档。第二个参数表示复制所有子元素。

    然后从原始文档中删除元素是让项目“从其父项中删除自身”的情况,这听起来很奇怪,但这就是这段代码的工作原理。

    <?php
    error_reporting ( E_ALL );
    ini_set ( 'display_errors', 1 );
    
    $destination = new DOMDocument;
    $destination->preserveWhiteSpace = true;
    $destination->loadXML('<?xml version="1.0" encoding="utf-8"?><root></root>');
    
    $source = new DOMDocument;
    $source->load('NewFile.xml');
    
    $xp = new DOMXPath($source);
    $destRoot = $destination->getElementsByTagName("root")->item(0);
    foreach ($xp->query('/root/property[rent]') as $item) {
        $newItem = $destination->importNode($item, true);
        $destRoot->appendChild($newItem);
        $item->parentNode->removeChild($item);
    }
    
    echo "Source:".$source->saveXML();
    $destination->formatOutput = true;
    echo "destination:".$destination->saveXml();
    

    对于目的地,我用基本的&lt;root> 元素填充它,然后从那里添加内容。

    【讨论】:

    • 非常感谢。这很好用,除了源输出和目标输出都没有保存到只输出到控制台的文件中。我试过摆弄它来生成文件,但一无所获。如何将 $source 输出到 newsource.xml 并将 $destination 输出到destination.xml?
    • 没关系,明白了:)。我正在尝试 $source->saveXML("final_source.xml") 而不是直接的 $source->save("final_source.xml")。再次感谢。
    【解决方案2】:

    你想获得这样的东西吗?希望这会有所帮助:

    $inXmlFile = getcwd() . "/source.xml";
    $inXmlString = file_get_contents($inXmlFile);
    $outXmlFile = getcwd() . "/destination.xml";
    $outXmlString = file_get_contents($outXmlFile);
    
    $sourceDOMDocument = new DOMDocument;
    $sourceDOMDocument->loadXML($inXmlString);
    
    $sourceRoot = null;
    foreach ($sourceDOMDocument->childNodes as $childNode) {
        if(strcmp($childNode->nodeName, "root") == 0) {
            $sourceRoot = $childNode;
            break;
        }
    }
    
    $destDOMDocument = new DOMDocument;
    $destDOMDocument->loadXML($outXmlString);
    
    $destRoot = null;
    foreach ($destDOMDocument->childNodes as $childNode) {
        if(strcmp($childNode->nodeName, "root") == 0) {
            $destRoot = $childNode;
            break;
        }
    }
    
    $xmlStructure = simplexml_load_string($inXmlString);
    
    $domProperty = dom_import_simplexml($xmlStructure->property);
    
    $rents = $domProperty->getElementsByTagName('rent');
    if(($rents != null) && (count($rents) > 0)) {
        $destRoot->appendChild($destDOMDocument->importNode($domProperty->cloneNode(true), true));
        $destDOMDocument->save($outXmlFile);
        $sourceRoot->removeChild($sourceRoot->getElementsByTagName('property')->item(0));
        $sourceDOMDocument->save($inXmlFile);
    }
    

    【讨论】:

      【解决方案3】:

      考虑运行两种 XSLT 转换:一种在目标中添加 &lt;property&gt;&lt;rent&gt; 节点,另一种从源中删除这些节点。作为背景,XSLT 是一种专用语言,旨在转换 XML 文件,甚至维护一个 document() 函数来解析同一文件夹或子文件夹中的外部 XML 文件。

      PHP 可以使用其php-xsl 类运行 XSLT 1.0 脚本(确保在 .ini 文件中启用扩展名)。使用这种方法,不需要if 逻辑或foreach 循环。

      XSLT 脚本

      PropertyRentAdd.xsl (确保 source.xml 和 XSLT 在同一个文件夹中)

      <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output indent="yes"/>
        <xsl:strip-space elements="*"/>
      
        <!-- IDENTITY TRANSFORM -->
        <xsl:template match="@*|node()">
          <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
          </xsl:copy>
        </xsl:template>
      
        <!-- ADD TEMPLATE -->
        <xsl:template match="root"> 
          <xsl:copy>
              <xsl:copy-of select="*"/>
              <xsl:copy-of select="document('source.xml')/root/property[local-name(*)='rent']"/>
          </xsl:copy>
        </xsl:template>
      
      </xsl:stylesheet>
      

      PropertyRentRemove.xsl

      <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output indent="yes"/>
        <xsl:strip-space elements="*"/>
      
        <!-- IDENTITY TRANSFORM -->
        <xsl:template match="@*|node()">
          <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
          </xsl:copy>
        </xsl:template>
      
        <!-- REMOVE TEMPLATE -->
        <xsl:template match="property[local-name(*)='rent']">    
        </xsl:template>
      
      </xsl:stylesheet>
      

      PHP

      // Set current path
      $cd = dirname(__FILE__);
      
      // Load the XML and XSLT files
      $doc = new DOMDocument();
      $doc->load($cd.'/destination.xml');     
      
      $xsl = new DOMDocument;
      $xsl->load($cd.'/PropertRentAdd.xsl');
      
      // Transform the destination xml
      $proc = new XSLTProcessor;
      $proc->importStyleSheet($xsl);     
      $newXml = $proc->transformToXML($xml);
      
      // Save output to file, overwriting original
      file_put_contents($cd.'/destination.xml', $newXml);
      
      
      // Load the XML and XSLT files
      $doc = new DOMDocument();
      $doc->load($cd.'/source.xml');
      
      $xsl = new DOMDocument;
      $xsl->load($cd.'/PropertRentRemove.xsl');
      
      // Transform the source xml
      $proc = new XSLTProcessor;
      $proc->importStyleSheet($xsl);     
      $newXml = $proc->transformToXML($xml);
      
      // Save output overwriting original file
      file_put_contents($cd.'/source.xml', $newXml);
      

      输入 (举例说明,用其他标签展示内容不受影响)

      source.xml

      <?xml version="1.0" encoding="utf-8"?>
      <root>
          <property>
              <rent>
                  <term>long</term>
                  <freq>month</freq>
                  <price_peak>1234</price_peak>
                  <price_high>1234</price_high>
                  <price_medium>1234</price_medium>
                  <price_low>1234</price_low>
              </rent>
          </property>
          <property>
              <rent>
                  <term>short</term>
                  <freq>month</freq>
                  <price_peak>7890</price_peak>
                  <price_high>7890</price_high>
                  <price_medium>7890</price_medium>
                  <price_low>7890</price_low>
              </rent>
          </property>
          <property>
              <web_site>stackoverflow</web_site>
              <general_purpose>php</general_purpose>
          </property>
          <property>
              <web_site>stackoverflow</web_site>
              <special_purpose>xsl</special_purpose>
          </property>
      </root>
      

      destination.xml

      <?xml version="1.0" encoding="utf-8"?>
      <root>
          <original_data>
              <test1>ABC</test1>
              <test2>123</test2>
          </original_data>
          <original_data>
              <test1>XYZ</test1>
              <test2>789</test2>
          </original_data>
      </root>
      

      输出 (PHP运行后)

      source.xml

      <?xml version="1.0"?>
      <root>
        <property>
          <web_site>stackoverflow</web_site>
          <general_purpose>php</general_purpose>
        </property>
        <property>
          <web_site>stackoverflow</web_site>
          <special_purpose>xsl</special_purpose>
        </property>
      </root>
      

      destination.xml (底部追加新节点)

      <?xml version="1.0"?>
      <root>
        <original_data>
          <test1>ABC</test1>
          <test2>123</test2>
        </original_data>
        <original_data>
          <test1>XYZ</test1>
          <test2>789</test2>
        </original_data>
        <property>
          <rent>
            <term>long</term>
            <freq>month</freq>
            <price_peak>1234</price_peak>
            <price_high>1234</price_high>
            <price_medium>1234</price_medium>
            <price_low>1234</price_low>
          </rent>
        </property>
        <property>
          <rent>
            <term>short</term>
            <freq>month</freq>
            <price_peak>7890</price_peak>
            <price_high>7890</price_high>
            <price_medium>7890</price_medium>
            <price_low>7890</price_low>
          </rent>
        </property>
      </root>
      

      【讨论】:

      • 感谢您的 Parfait 工作,它非常详细,我一定会研究的。但是,就目前而言,Nigel Ren 给出的答案更适合我的工作流程。再次感谢。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-07
      • 2013-12-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多