【问题标题】:Parsing XML: Pulling a separate value based on IDREF/ID解析 XML:根据 IDREF/ID 提取单独的值
【发布时间】:2015-11-09 05:06:31
【问题描述】:

我整天都在为此苦苦挣扎,实际上它可能真的很简单......但我是 PHP 和 XML 世界的初学者,所以真的可以在一些帮助下做。

我正在使用 SimpleXML 来解析我的数据并有两个二级组 - (yearlist) 和 (eplist)。我有 (year) 嵌套在 (yearlist) 里面,它有一个属性“yid”,在我的 DTD 中设置为 ID。它还有 (yearname) 嵌套在 (year) 中,其中包含要显示为输出的更详细的描述。我有 (ep) 嵌套在 (eplist) 中,属性“yearid”(与“yid”直接相关),在我的 DTD 中设置为 IDREF。

基本上,当我解析 (eplist) 的数据时,我想使用 (yearname) 作为组头 - 使用 yearid=yid>yearname 作为路径。

我创建了一个数据示例,这可能有助于更好地解释我的问题。

这是我的 DTD:

<?xml encoding="UTF-8"?>

<!ELEMENT besteplist (yearlist,eplist)>

<!ELEMENT yearlist (year)+>
<!ELEMENT year (yearname)>
<!ATTLIST year
            yid ID #REQUIRED>
<!ELEMENT yearname (#PCDATA)>

<!ELEMENT eplist (ep)+>
<!ELEMENT ep (eptitle,eptnumber)>
<!ATTLIST ep
            eid ID #REQUIRED
            yearid IDREF #IMPLIED>
<!ELEMENT eptitle (#PCDATA)>
<!ELEMENT eptnumber (#PCDATA)>

这是我的 XML:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE besteplist SYSTEM "example.dtd">
<besteplist>
    <yearlist>
        <year yid="y1">
            <yearname>1995, Season 1</yearname>
        </year>
        <year yid="y2">
            <yearname>1996, Season 2</yearname>
        </year>
        <year yid="y3">
            <yearname>1997, Season 3</yearname>
        </year>
    </yearlist>
    <eplist>
        <ep yearid="y1" eid="e1">
            <eptitle>The First Episode</eptitle>
            <eptnumber>1</eptnumber>
        </ep>
        <ep yearid="y2" eid="e2">
            <eptitle>Bla bla bla</eptitle>
            <eptnumber>21</eptnumber>
        </ep>
        <ep yearid="y2" eid="e3">
            <eptitle>Rar rar rar</eptitle>
            <eptnumber>39</eptnumber>
        </ep>
        <ep yearid="y2" eid="e4">
            <eptitle>Tra la la</eptitle>
            <eptnumber>45</eptnumber>
        </ep>
        <ep yearid="y3" eid="e5">
            <eptitle>Donkey</eptitle>
            <eptnumber>126</eptnumber>
        </ep>
    </eplist>
</besteplist>

这是我希望输出的示例:

SEASON: 1995, Season 1

    EPISODE TITLE: The First Episode
    EPISODE NUMBER: 1

SEASON: 1996, Season 2

    EPISODE TITLE: Bla bla bla
    EPISODE NUMBER: 21

    EPISODE TITLE: Rar rar rar
    EPISODE NUMBER: 39

    EPISODE TITLE: Tra la la
    EPISODE NUMBER: 45

SEASON: 1997, Season 3

    EPISODE TITLE: Donkey
    EPISODE NUMBER: 126

我认为发布我已经尝试过的代码没有多大用处,因为它可能相当无用......我已经设法做的是非常基础的。一旦我完成了这个,我就可以进入下一个阶段......格式化......

我不以任何方式依附于 SimpleXML,所以如果有人能建议一种更有效的做事方式,我会全力以赴。

非常感谢任何花时间帮助我的人。 :)

山姆


作为对@michi 的回应,我一直在努力研究 xpath 并在线阅读各种语法/教程,但似乎无法理解它。这是我到目前为止所拥有的......但我已经注释掉了 xpath,因为它显然是错误的。

<?php
$xml=simplexml_load_file("example.xml") or die("Error: Cannot create object");

foreach($xml->yearlist->children() as $years) { 
    $xyid=$years[yid];
    echo "_____________________________________________<br>";
    echo "(yid= " . $xyid . " )<br>";
    echo "SEASON: " . $years->yearname . "<br>"; 
    echo "_____________________________________________<br>";
    foreach($xml->eplist->children() as $episodes) { 
    echo "EPISODE TITLE: " . $episodes->eptitle . "<br>"; 
    echo "EPISODE NUMBER: " . $episodes->eptnumber . "<br>"; 
    $xyearid=$episodes[yearid];
    echo "(yearid= " . $xyearid . " )<br>";
    // echo $xml->xpath('//year[@yid="$episodes[yearid]"]/yearname');
    echo "</p>"; 
    } 
}

?>

希望你能指引我正确的方向!

谢谢 山姆


感谢 michi 的帮助 - 这绝对是朝着正确方向迈出的一步!

我正在想办法只显示一次季节名称...遇到了迭代和数组,但它们对我来说都太复杂了。是否可以在 foreach 命令中包含 xpath?我想也许如果我在 foreach 季节中嵌套 foreach 剧集并使用 xpath 来匹配它可以工作的 ID,但我似乎无法让它显示元素。我在正确的轨道上吗?

<?php
$xml=simplexml_load_file("example.xml") or die("Error: Cannot create object");

foreach ($xml->yearlist->year as $season) {
    echo "SEASON: " . $season->yearname . PHP_EOL;
    foreach ($xml->xpath("//ep[@yearid='$season[yid]']")[0] as $episode) { 
        echo "EPISODE TITLE: " . $episode->eptitle . PHP_EOL;
        echo "EPISODE NUMBER: " . $episode->eptnumber . PHP_EOL; 
        echo PHP_EOL;
    }
}

?>

再次感谢!

【问题讨论】:

  • 你做到了,只需删除 foreach 中的 [0],请参阅 eval.in/465031。我一直在考虑另一种方法,因为经常有很多方法可以实现目标。我将在我的回答中详细说明替代方案。

标签: php xml xml-parsing simplexml dtd


【解决方案1】:

您可以使用 XSLT 将您的 XML 重构为您需要的格式。作为信息,XSLT 是一种特殊用途的声明性编程语言,用于重组、重新设计、重新格式化 XML 文档以用于各种最终用途。几乎所有通用语言都维护 XSLT 处理器:Java、C#、Python、Perl、VB,甚至 PHP。

XSLT 脚本 (单独保存为 .xsl 文件以供下文使用)

<?xml version="1.0" ?> 
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">  
<xsl:output method="xml" indent="yes"/>

<xsl:template match="besteplist">
  <besteplist>

     <xsl:for-each select="yearlist/year">
        <xsl:variable name="yearvar" select="@yid"/>
        SEASON: <xsl:value-of select="yearname"/>
        <xsl:for-each select="../../eplist/ep[@yearid=$yearvar]">      
            EPISODE TITLE: <xsl:value-of select="eptitle"/>
            EPISODE NUMEBR: <xsl:value-of select="eptnumber"/>
            <xsl:text>&#xa;</xsl:text>
        </xsl:for-each>
      </xsl:for-each>

  </besteplist>
</xsl:template>

</xsl:stylesheet>

PHP 脚本

<?php   

// Set current directory
$cd = dirname(__FILE__);

// Load the XML source and XSLT file
$xml = new DOMDocument('1.0', 'UTF-8');
$xml->formatOutput = true;
$xml->preserveWhiteSpace = false;
$xml->load($cd.'/SeasonEpisodes.xml');

$xsl = new DOMDocument;
$xsl->load($cd.'/SeasonEpisodes.xsl');

// Configure transformer
$proc = new XSLTProcessor;
$proc->importStyleSheet($xsl);

// Transform XML source
$newXML = new DOMDocument;
$newXML = $proc->transformToXML($xml);

// Save output to file
$xmlfile = $cd.'/NewSeasonEpisodes.xml';
file_put_contents($xmlfile, $newXML);

?>

新的 XML 输出 (现在只需解析根节点数据)

<?xml version="1.0"?>
<besteplist>
        SEASON: 1995, Season 1      
            EPISODE TITLE: The First Episode
            EPISODE NUMEBR: 1

        SEASON: 1996, Season 2      
            EPISODE TITLE: Bla bla bla
            EPISODE NUMEBR: 21

            EPISODE TITLE: Rar rar rar
            EPISODE NUMEBR: 39

            EPISODE TITLE: Tra la la
            EPISODE NUMEBR: 45

        SEASON: 1997, Season 3      
            EPISODE TITLE: Donkey
            EPISODE NUMEBR: 126
</besteplist>

【讨论】:

    【解决方案2】:

    您掌握了基本的SimpleXml 技术,干得好。现在让我们开始吧:

    1. 我建议迭代 &lt;eplist&gt; 并仅回显所有 &lt;ep&gt;

      $xml = simplexml_load_string($x); // assume XML in $x
      
      foreach ($xml->eplist->ep as $episode) { 
          echo $episode['yearid'] . PHP_EOL;
          echo "EPISODE TITLE: " . $episode->eptitle . PHP_EOL;
          echo "EPISODE NUMBER: " . $episode->eptnumber . PHP_EOL; 
          echo PHP_EOL;
      }
      

      PHP_EOL跨不同平台生成新行,见When do I use the PHP constant "PHP_EOL"?

      查看实际操作:https://eval.in/464970

      这看起来确实与您想要的相似,不是吗?

    2. 使用&lt;ep&gt;yearid属性作为键来访问和回显对应的&lt;yearname&gt;,使用xpath()

      你的xpath表达式基本正确,但需要修改:

      // old:
      echo $xml->xpath('//year[@yid="$episode[yearid]"]/yearname');
      
      // new:
      echo $xml->xpath("//year[@yid='$episode[yearid]']/yearname")[0];
      

      交换"',这样$episode 将被评估。请注意,我在代码中将其名称从 $episodes 更改为 $episode
      What is the difference between single-quoted and double-quoted strings in PHP?

      xpath() 返回一个arraySimpleXml 元素,要访问第一个st 值,我们需要用[0] 取消对数组的引用。

      当然,这段代码不是防错的,它不会检查数组是否为空等。您需要添加它以进行生产,但这会使这些示例中的要点复杂化。

      echo $episode['yearid'] (...) 替换为正确的xpath

      看到它的工作:https://eval.in/464992

    3. 下一个:将具有相同 SEASON 的剧集分组 = echo SEASON 仅用于属于该季节的第 1st 集。 (你的工作)

      更新:

      您发布了几乎完美的代码,请参阅我的评论。

      基本上,您有两个按 yearid 链接的表。 1 集与 1 年相关联,1 年与许多集相关联。您可以通过迭代年份并选择链接的剧集(=您的最后一个代码示例)或迭代剧集并选择链接的年份(=我的代码示例)来实现它。

      以下是在前面示例的基础上进行分组构建的方法:

      $xml = simplexml_load_string($x); // assume XML in $x
      $yid = "";
      
      foreach ($xml->eplist->ep as $episode) { 
      
          // check if last yearid is different from current yearid
          // only if yes, echo the yearname 
          if ($yid != (string)$episode['yearid']) {
              echo "SEASON: " . $xml->xpath("//year[@yid='$episode[yearid]']/yearname")[0] . PHP_EOL . PHP_EOL;
          }
          echo "  EPISODE TITLE: " . $episode->eptitle . PHP_EOL;
          echo "  EPISODE NUMBER: " . $episode->eptnumber . PHP_EOL . PHP_EOL; 
      
          // store current yearid in $yid for next iteration
          $yid = (string)$episode['yearid'];
      }
      

      注意:(string) 注意评估是字符串而不是SimpleXml 对象。

      输出:

      SEASON: 1995, Season 1
      
        EPISODE TITLE: The First Episode
        EPISODE NUMBER: 1
      
      SEASON: 1996, Season 2
      
        EPISODE TITLE: Bla bla bla
        EPISODE NUMBER: 21
      
        EPISODE TITLE: Rar rar rar
        EPISODE NUMBER: 39
      
        EPISODE TITLE: Tra la la
        EPISODE NUMBER: 45
      
      SEASON: 1997, Season 3
      
        EPISODE TITLE: Donkey
        EPISODE NUMBER: 126
      

      看到它的工作:https://eval.in/465044

      进一步讨论:代码理所当然地认为&lt;ep&gt; 节点已经在您的XML 中分组。如果你在 y3 之后有一个 &lt;ep&gt; 和 y1...

    【讨论】:

    • 谢谢 - 我已按要求编辑了我的答案。希望您能引导这位初学者朝着正确的方向前进!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-09
    • 2019-10-25
    • 1970-01-01
    • 2017-04-11
    • 1970-01-01
    相关资源
    最近更新 更多