XSLT串联元素

时间:2018-10-03 10:00:41

标签: xml xslt-1.0

我希望像下面那样转换一些XML,在此示例中,只有2个元素可以组合在一起,但是可以更多:

<file>
    <patient>
        <Lab_No_Spec_No>12345</Lab_No_Spec_No>
        <Patient_Number>ABC</Patient_Number>
        <Albumin_g_L>48 </Albumin_g_L>
        <Calcium_mmol_L>
        <Phosphate_mmol_L>100 </Phosphate_mmol_L>
    </patient>
    <patient>
        <Lab_No_Spec_No>12345</Lab_No_Spec_No>
        <Patient_Number>ABC</Patient_Number>
        <Albumin_g_L>92 </Albumin_g_L>
        <Calcium_mmol_L>50 </Calcium_mmol_L>
        <Phosphate_mmol_L/>
    </patient>
</file>

得到以下结果:

<file>
    <patient>
        <Lab_No_Spec_No>12345</Lab_No_Spec_No>
        <Patient_Number>ABC</Patient_Number>
        <Albumin_g_L>48,92</Albumin_g_L>
        <Calcium_mmol_L>50</Calcium_mmol_L>
        <Phosphate_mmol_L>100</Phosphate_mmol_L>
    </patient>
</file>

到目前为止,这是我的转型:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
    <xsl:strip-space elements="*"/>
    <xsl:key name="specimen" match="patient" use="Lab_No_Spec_No" />
    <xsl:template match="file">
        <file>
            <xsl:for-each select="patient[count(. | key('specimen', Lab_No_Spec_No)[1]) = 1]">
                <patient>
                    <xsl:copy-of select="Lab_No_Spec_No" />
                    <xsl:copy-of select="Patient_Number" />
                    <Albumin_g_L>
                        <xsl:for-each select="key('specimen', Lab_No_Spec_No)">
                            <xsl:value-of select="normalize-space(Albumin_g_L)" />
                            <xsl:if test="position() != last()">
                                <xsl:text>,</xsl:text>
                            </xsl:if>
                        </xsl:for-each>
                    </Albumin_g_L>
                    <Calcium_mmol_L>
                        <xsl:for-each select="key('specimen', Lab_No_Spec_No)">
                            <xsl:value-of select="normalize-space(Calcium_mmol_L)" />
                            <xsl:if test="position() != last()">
                                <xsl:text>,</xsl:text>
                            </xsl:if>
                        </xsl:for-each>
                    </Calcium_mmol_L>
                    <Phosphate_mmol_L>
                        <xsl:for-each select="key('specimen', Lab_No_Spec_No)">
                            <xsl:value-of select="normalize-space(Phosphate_mmol_L)" />
                            <xsl:if test="position() != last()">
                                <xsl:text>,</xsl:text>
                            </xsl:if>
                        </xsl:for-each>
                    </Phosphate_mmol_L>
                </patient>
            </xsl:for-each>
        </file>
    </xsl:template>
</xsl:stylesheet>

我正在寻求有关上述内容的一些问题:

  1. <xsl:for-each select="key('specimen', Lab_No_Spec_No)">串联期间包含空元素,我想省略这些元素,以免得到类似<Calcium_mmol_L>50,</Calcium_mmol_L>的结果。什么需要更改我的for-each选择,以便不选择空元素?

  2. 真正的源XML文件包含30多个元素,我需要对其进行串联。我在示例中对3个元素重复了相同的转换,但是对于Patient_Number元素之后的元素是否有一种简便的方法?还是必须重复进行转换?类似于:

<xsl:for-each select="following-sibling::Patient_Number">
  <local-name(.)>
      <xsl:for-each select="key('specimen', Lab_No_Spec_No)">
          <xsl:value-of select="normalize-space(.)" />
          <xsl:if test="position() != last()">
              <xsl:text>,</xsl:text>
          </xsl:if>
      </xsl:for-each>
  </local-name(.)>
</xsl:for-each>

2 个答案:

答案 0 :(得分:1)

要排除空元素,只需在您的xsl:for-each中添加一个条件

<xsl:for-each select="key('specimen', Lab_No_Spec_No)[normalize-space(Albumin_g_L)]">

为避免重复,您将变得很聪明,并对每个Lab_No_Spec_No的列名称进行第二级分组。

尝试使用此XSLT

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
    <xsl:strip-space elements="*"/>
    <xsl:key name="specimen" match="patient" use="Lab_No_Spec_No" />
    <xsl:key name="cols" match="patient/*" use="concat(../Lab_No_Spec_No, '|', local-name())" />

    <xsl:template match="file">
        <file>
            <xsl:for-each select="patient[count(. | key('specimen', Lab_No_Spec_No)[1]) = 1]">
                <patient>
                    <xsl:copy-of select="Lab_No_Spec_No" />
                    <xsl:copy-of select="Patient_Number" />
                    <xsl:apply-templates select="*[not(self::Lab_No_Spec_No) and not(self::Patient_Number)]
                                                  [count(. | key('cols', concat(../Lab_No_Spec_No, '|', local-name()))[1]) = 1]" />
                </patient>
            </xsl:for-each>
        </file>
    </xsl:template>

    <xsl:template match="patient/*">
      <xsl:copy>
        <xsl:for-each select="key('cols', concat(../Lab_No_Spec_No, '|', local-name()))[normalize-space()]">
            <xsl:value-of select="normalize-space(.)" />
            <xsl:if test="position() != last()">
                <xsl:text>,</xsl:text>
            </xsl:if>
        </xsl:for-each>
      </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

尽管如此,在XSLT 2.0中这一切要容易得多。...

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="file">
        <file>
            <xsl:for-each-group select="patient" group-by="Lab_No_Spec_No">
                <patient>
                    <xsl:copy-of select="Lab_No_Spec_No,Patient_Number" />
                    <xsl:for-each-group select="current-group()/(* except (Lab_No_Spec_No, Patient_Number))" group-by="local-name()">
                        <xsl:copy>
                            <xsl:value-of select="current-group()[normalize-space()]/normalize-space()" separator="," />
                        </xsl:copy>
                    </xsl:for-each-group>
                </patient>
            </xsl:for-each-group>
        </file>
    </xsl:template>
</xsl:stylesheet>

答案 1 :(得分:1)

Tim已经提供了一个很好的答案,但是我认为,从身份转换模板开始,然后仅为需要转换的那些元素添加模板,这些问题将受益匪浅。另外,只要输入中的每个patient都具有相同的child元素,我认为您可以简单地按组进行分组,然后只处理每个组中第一个patient元素的子元素,然后在需要连接数据的那些元素的模板中,可以使用建议的键将其推入另一种模式以输出列表:

<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">

  <xsl:output method="xml" indent="yes" version="5"/>

  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>

  <xsl:key name="specimen" match="patient" use="Lab_No_Spec_No" />

  <xsl:key name="child" match="patient/*" use="concat(../Lab_No_Spec_No, '|', local-name())"/>

  <xsl:template match="patient[count(. | key('specimen', Lab_No_Spec_No)[1]) = 1]">
      <xsl:copy>
          <xsl:apply-templates/>
      </xsl:copy>
  </xsl:template>

  <xsl:template match="patient[not(count(. | key('specimen', Lab_No_Spec_No)[1]) = 1)]"/>

  <xsl:template match="patient/*[not(self::Lab_No_Spec_No|self::Patient_Number)]">
      <xsl:copy>
          <xsl:apply-templates select="key('child', concat(../Lab_No_Spec_No, '|', local-name()))[normalize-space()]" mode="value"/>
      </xsl:copy>
  </xsl:template>

  <xsl:template match="patient/*" mode="value">
      <xsl:if test="position() > 1">,</xsl:if>
      <xsl:value-of select="normalize-space()"/>      
  </xsl:template>

</xsl:stylesheet>

这并不能使您摆脱在注释中提出的问题,即必须在某个位置列出所有不想连接的元素,但是只有一个match属性谓词需要在哪里做那个。

https://xsltfiddle.liberty-development.net/gWmuiJX/2