解析XML:根据IDREF/ID提取一个单独的值


Parsing XML: Pulling a separate value based on IDREF/ID

我一整天都在为这个问题而挣扎,事实上这可能真的很简单。。。但我是PHP和XML世界的完全初学者,所以需要一些帮助。

我使用SimpleXML来解析我的数据,并有两个二级组——(yearlist)和(eplist)。我在(yearlist)中嵌套了(year),它有一个属性"yid",在我的DTD中设置为ID。它还在(year)中嵌套了(渴望名称),其中包含将作为输出显示的更详细的描述。我在(eplist)中嵌套了(ep),属性为"yearid"(与"yid"直接相关),在DTD中设置为IDREF。

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

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

这是我的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没有任何依恋,所以如果有人能提出一种更有效的做事方式,我会洗耳恭听的。

提前非常感谢任何花时间帮助我的人

Sam


作为对@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>"; 
    } 
}
?>

我希望你能引导我朝着正确的方向前进!

谢谢Sam


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

我想办法只显示一次季名。。。遇到了迭代和数组,但它们对我来说太复杂了。有可能在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;
    }
}
?>

再次感谢!

您可以使用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>

你掌握了SimpleXml的基本技术,做得很好。现在让我们来研究一下:

  1. 我建议在<eplist>上迭代,并仅回显所有<ep>

    $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生成跨不同平台的新行,请参阅"我什么时候使用PHP常量";PHP_EOL";?

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

    这看起来确实和你想要的很相似,不是吗?

  2. 使用<ep> yearid属性作为密钥访问并回显相应的<yearname>,使用xpath()

    您的xpath表达式基本正确,但需要进行一些更改:

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

    交换"',以便对$episode进行评估。请注意,我在代码中将其名称从$episodes更改为$episode
    请参阅PHP中单引号和双引号字符串之间的区别是什么?

    xpath()返回SimpleXml元素的array,为了访问1st值,我们需要用[0]取消引用数组。

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

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

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

  3. 下一步:将具有相同季节的剧集分组=仅为属于该季节的第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

    进一步讨论:代码想当然地认为<ep>节点已经在XML中分组。如果你有一个<ep>,在y3之后有y1…