合并 XML 中的条目

Merge entries in XML

我有一个 XML 包含产品,我需要以某种方式合并到一个条目:

<SHOPITEM>
        <PRODUCT>POINT</PRODUCT>
        <FRAMESIZE>MD</FRAMESIZE>
        <CODE>029,00</CODE>
        <COLOR>black / yellow</COLOR>
</SHOPITEM>
<SHOPITEM>
        <PRODUCT>POINT</PRODUCT>
        <FRAMESIZE>LD</FRAMESIZE>
        <CODE>029,01</CODE>
        <COLOR>black / yellow</COLOR>
</SHOPITEM>
<SHOPITEM>
        <PRODUCT>POINT</PRODUCT>
        <FRAMESIZE>LD</FRAMESIZE>
        <CODE>029,03</CODE>
        <COLOR>green / white</COLOR>
</SHOPITEM>
<SHOPITEM>
        <PRODUCT>POINT</PRODUCT>
        <FRAMESIZE>MD</FRAMESIZE>
        <CODE>029,04</CODE>
        <COLOR>green / white</COLOR>
</SHOPITEM>

<PRODUCT>一样,变化的是<FRAMESIZE>, <CODE>, <COLOR>

有什么方法可以从中获取有用的数据吗?最好是在 PHP 中,但也可以生成一个新的 XML 文件,我可以在 PHP:

中处理该文件
<SHOPITEM>
        <PRODUCT>POINT</PRODUCT>
        <FRAMESIZE1>MD</FRAMESIZE1>
        <CODE1>029,00</CODE1>
        <COLOR1>black / yellow</COLOR2>
        <FRAMESIZE2>LD</FRAMESIZE2>
        <CODE2>029,01</CODE2>
        <COLOR2>black / yellow</COLOR2>
        <FRAMESIZE3>LD</FRAMESIZE3>
        <CODE3>029,03</CODE3>
        <COLOR3>green / white</COLOR3>
        <FRAMESIZE4>MD</FRAMESIZE4>
        <CODE4>029,04</CODE4>
        <COLOR4>green / white</COLOR4>
</SHOPITEM>

我的 XSLT-fu 很弱,但这会产生您想要的输出(在用根标记包装示例 XML 之后):

xmlstarlet sel -t -v '//SHOPITEM[1]/PRODUCT' -n -m '//SHOPITEM' -v FRAMESIZE -n -v CODE -n -v COLOR -n file.xml | 
awk '
  BEGIN {print "<SHOPITEM>"} 
  END   {print "</SHOPITEM>"}
  NR==1 {print "  <PRODUCT>" [=10=] "</PRODUCT>"; next} 
  {
    n++;     t="FRAMESIZE"; printf "  <%s%d>%s</%s%d>\n", t, n, [=10=], t, n
    getline; t="CODE";      printf "  <%s%d>%s</%s%d>\n", t, n, [=10=], t, n
    getline; t="COLOR";     printf "  <%s%d>%s</%s%d>\n", t, n, [=10=], t, n
  }
'
<SHOPITEM>
  <PRODUCT>POINT</PRODUCT>
  <FRAMESIZE1>MD</FRAMESIZE1>
  <CODE1>029,00</CODE1>
  <COLOR1>black / yellow</COLOR1>
  <FRAMESIZE2>LD</FRAMESIZE2>
  <CODE2>029,01</CODE2>
  <COLOR2>black / yellow</COLOR2>
  <FRAMESIZE3>LD</FRAMESIZE3>
  <CODE3>029,03</CODE3>
  <COLOR3>green / white</COLOR3>
  <FRAMESIZE4>MD</FRAMESIZE4>
  <CODE4>029,04</CODE4>
  <COLOR4>green / white</COLOR4>
</SHOPITEM>

事后看来,这种输出格式可能更容易处理:

xmlstarlet ... file.xml | awk '
      BEGIN {print "<SHOPITEM>"; fmt="\t\t<%s>%s</%s>\n"} 
      END   {print "</SHOPITEM>"}
      NR==1 {print "\t<PRODUCT>" [=12=] "</PRODUCT>"; next} 
      {
        n++
        printf "\t<PRODUCT_ITEM id=\"%d\">\n", n
        t="FRAMESIZE"; printf fmt, t, [=12=], t; getline
        t="CODE";      printf fmt, t, [=12=], t; getline
        t="COLOR";     printf fmt, t, [=12=], t
        print "\t</PRODUCT_ITEM>"
      }
    '
<SHOPITEM>
    <PRODUCT>POINT</PRODUCT>
    <PRODUCT_ITEM id="1">
        <FRAMESIZE>MD</FRAMESIZE>
        <CODE>029,00</CODE>
        <COLOR>black / yellow</COLOR>
    </PRODUCT_ITEM>
    <PRODUCT_ITEM id="2">
        <FRAMESIZE>LD</FRAMESIZE>
        <CODE>029,01</CODE>
        <COLOR>black / yellow</COLOR>
    </PRODUCT_ITEM>
    <PRODUCT_ITEM id="3">
        <FRAMESIZE>LD</FRAMESIZE>
        <CODE>029,03</CODE>
        <COLOR>green / white</COLOR>
    </PRODUCT_ITEM>
    <PRODUCT_ITEM id="4">
        <FRAMESIZE>MD</FRAMESIZE>
        <CODE>029,04</CODE>
        <COLOR>green / white</COLOR>
    </PRODUCT_ITEM>
</SHOPITEM>

I strongly recommend you figure out an XSLT solution - glenn jackman

我只能支持。所以,这是您的 XSLT 解决方案。但是,问题是:您是否展示了 代表 XML 示例,或者您的 真实 中是否有几个不同的 PRODUCT 元素] XML数据?

此外,命名元素 CODE1CODE2 等也可以,但我(再次强烈)建议不要这样做。我很高兴添加这个细节,但首先要澄清您是否 真的 需要这种严重的命名约定,或者您是否可以改用属性:

<CODE n="1"/>

XML 输入

正如 Glenn 已经建议的那样,必须有一个最外层元素才能使您的输入格式正确 XML。

<root>
    <SHOPITEM>
            <PRODUCT>POINT</PRODUCT>
            <FRAMESIZE>MD</FRAMESIZE>
            <CODE>029,00</CODE>
            <COLOR>black / yellow</COLOR>
    </SHOPITEM>
    <SHOPITEM>
            <PRODUCT>POINT</PRODUCT>
            <FRAMESIZE>LD</FRAMESIZE>
            <CODE>029,01</CODE>
            <COLOR>black / yellow</COLOR>
    </SHOPITEM>
    <SHOPITEM>
            <PRODUCT>POINT</PRODUCT>
            <FRAMESIZE>LD</FRAMESIZE>
            <CODE>029,03</CODE>
            <COLOR>green / white</COLOR>
    </SHOPITEM>
    <SHOPITEM>
            <PRODUCT>POINT</PRODUCT>
            <FRAMESIZE>MD</FRAMESIZE>
            <CODE>029,04</CODE>
            <COLOR>green / white</COLOR>
    </SHOPITEM>
</root>

XSLT 样式表 (1.0)

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="xml" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />

    <xsl:strip-space elements="*"/>

    <xsl:template match="/root">
        <SHOPITEM>
            <xsl:copy-of select="SHOPITEM[1]/PRODUCT"/>
            <xsl:copy-of select="SHOPITEM/*[not(self::PRODUCT)]"/>
        </SHOPITEM>
    </xsl:template>

</xsl:transform>

XML输出

<SHOPITEM>
   <PRODUCT>POINT</PRODUCT>
   <FRAMESIZE>MD</FRAMESIZE>
   <CODE>029,00</CODE>
   <COLOR>black / yellow</COLOR>
   <FRAMESIZE>LD</FRAMESIZE>
   <CODE>029,01</CODE>
   <COLOR>black / yellow</COLOR>
   <FRAMESIZE>LD</FRAMESIZE>
   <CODE>029,03</CODE>
   <COLOR>green / white</COLOR>
   <FRAMESIZE>MD</FRAMESIZE>
   <CODE>029,04</CODE>
   <COLOR>green / white</COLOR>
</SHOPITEM>

编辑:

What I missed too, that there is many different elements as Mathias asked.

XML 输入

更合理的测试样本,有多个PRODUCT:

<root>
    <SHOPITEM>
            <PRODUCT>POINT</PRODUCT>
            <FRAMESIZE>MD</FRAMESIZE>
            <CODE>029,00</CODE>
            <COLOR>black / yellow</COLOR>
    </SHOPITEM>
    <SHOPITEM>
            <PRODUCT>POINT</PRODUCT>
            <FRAMESIZE>LD</FRAMESIZE>
            <CODE>029,01</CODE>
            <COLOR>black / yellow</COLOR>
    </SHOPITEM>
    <SHOPITEM>
            <PRODUCT>OTHER</PRODUCT>
            <FRAMESIZE>LD</FRAMESIZE>
            <CODE>029,03</CODE>
            <COLOR>green / white</COLOR>
    </SHOPITEM>
    <SHOPITEM>
            <PRODUCT>OTHER</PRODUCT>
            <FRAMESIZE>MD</FRAMESIZE>
            <CODE>029,04</CODE>
            <COLOR>green / white</COLOR>
    </SHOPITEM>
</root>

样式表

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="xml" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />

    <xsl:strip-space elements="*"/>

    <xsl:key name="prod" match="SHOPITEM" use="PRODUCT"/>

    <xsl:template match="/root">
        <xsl:copy>
            <xsl:for-each select="SHOPITEM[generate-id() = generate-id(key('prod',PRODUCT)[1])]">
                <SHOPITEM>
                    <xsl:copy-of select="PRODUCT"/>
                    <xsl:copy-of select="/root/SHOPITEM[PRODUCT = current()/PRODUCT]/*[not(self::PRODUCT)]"/>
                </SHOPITEM>
            </xsl:for-each>
        </xsl:copy>
    </xsl:template>

</xsl:transform>

XML输出

<root>
   <SHOPITEM>
      <PRODUCT>POINT</PRODUCT>
      <FRAMESIZE>MD</FRAMESIZE>
      <CODE>029,00</CODE>
      <COLOR>black / yellow</COLOR>
      <FRAMESIZE>LD</FRAMESIZE>
      <CODE>029,01</CODE>
      <COLOR>black / yellow</COLOR>
   </SHOPITEM>
   <SHOPITEM>
      <PRODUCT>OTHER</PRODUCT>
      <FRAMESIZE>LD</FRAMESIZE>
      <CODE>029,03</CODE>
      <COLOR>green / white</COLOR>
      <FRAMESIZE>MD</FRAMESIZE>
      <CODE>029,04</CODE>
      <COLOR>green / white</COLOR>
   </SHOPITEM>
</root>

这是 XSLT 1.0 中的另一个解决方案 - 它假设可以有多个 <SHOPTITEM> 元素。

我添加了一个根元素 (<root>),因为您的输入 XML 格式不正确。您也可以在此处 see/test 解决方案:http://xsltransform.net/pPqsHTk

请注意,有一个模板匹配第一个 PRODUCT,它根据 PRODUCT 的名称对数据进行分组。另一个模板处理同一产品的所有出现,这不是第一个,什么都不做。

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

    <xsl:template match="root">
        <xsl:copy>
            <xsl:apply-templates />
        </xsl:copy>
    </xsl:template>

    <xsl:template match="SHOPITEM[not(PRODUCT = preceding::SHOPITEM/PRODUCT)]">
        <SHOPITEM>
            <xsl:copy-of select="*"/>
            <xsl:copy-of select="following-sibling::SHOPITEM[PRODUCT = current()/PRODUCT]/*[not(self::PRODUCT)]"/>
        </SHOPITEM>
    </xsl:template>

    <xsl:template match="SHOPITEM[PRODUCT = preceding::SHOPITEM/PRODUCT]"/>
</xsl:transform>

这不是最快的解决方案,但如果您的输入 xml 不是太大,它应该会运行得相当快。