使用XSLT基于双重条件创建元素

时间:2017-04-18 15:06:49

标签: xml xslt xpath xslt-1.0 xslt-2.0

我很难绕过我需要的语法来创建一个我想要的元素。 XML / XSLT的新手,并不确定这是否是正确的方法。

我试图将我的XML数据文件解析为以元素为中心,这样我就可以将数据格式化为访问数据库中的可读结构。

我试图根据两个条件从元素内部提取数据值。

数据位于名为' 阅读'的元素内。并且数据标记为' value'。在阅读'之上element是名为' ConsumptionSpec '。

的定义元素

我尝试测试的是当前' 消费规格'的衡量单位( UOM )。然后,测试另一个名为' TouBucket '的属性。具有“TierA'”,“TierB' / C / D”或“总计”的值。 UOM 可以保持" kWh,kW,kVAh或kVA"。我试图得到第一个布局,因为我将重复这个测试,为每个层(A到D)和Total制作元素。 (尽量给出尽可能明确的解释)

目前,我尝试使用 xsl:for-each 在Reading上方选择消费规格,然后使用 xsl:when 来测试 UOM TouBucket 分开。测试结束后,我创建了一个元素,并尝试提取当前读取元素的值。

以下是我的XML摘录,以便您了解我在测试过程中试图尝试的值。

<MeterReadings Irn="Null" Source="Remote" SourceName="Null" SourceIrn="Null" Initiator="Schedule" Purpose="Null" CollectionTime="2017-04-01 09:00:00" >
    <Meter MeterIrn="Null" MeterName="Null" IsActive="true" SerialNumber="Null" MeterType="A3_ILN" Description="" InstallDate="2017-01-21 05:00:00" RemovalDate="" AccountIdent="Null" AccountName="" SdpIdent="" Location="Null" TimeZoneIndex="Null" Timezone="Null" TimeZoneOffset="300" ObservesDaylightSavings="false" MediaType="900 MHz" />

    <ReadingQualityIndicator Name="Tamper Alert" Value="true" />

    <ConsumptionData >

        <ConsumptionSpec UOM="kWh" Direction="Delivered" TouBucket="Total" MeasurementPeriod="Current" Multiplier="1" />

        <Reading TimeStamp="2017-04-01 03:08:00" Value="902" />

    </ConsumptionData>

    <ConsumptionData >

        <ConsumptionSpec UOM="kWh" Direction="Delivered" TouBucket="TierA" MeasurementPeriod="Current" Multiplier="1" />

        <Reading TimeStamp="2017-04-01 03:08:00" Value="0" />

    </ConsumptionData>

    <ConsumptionData >

        <ConsumptionSpec UOM="kWh" Direction="Delivered" TouBucket="TierB" MeasurementPeriod="Current" Multiplier="1" />

        <Reading TimeStamp="2017-04-01 03:08:00" Value="0" />

    </ConsumptionData>

    <ConsumptionData >

        <ConsumptionSpec UOM="kWh" Direction="Delivered" TouBucket="TierC" MeasurementPeriod="Current" Multiplier="1" />

        <Reading TimeStamp="2017-04-01 03:08:00" Value="902" />

    </ConsumptionData>

    <ConsumptionData >

        <ConsumptionSpec UOM="kWh" Direction="Delivered" TouBucket="TierD" MeasurementPeriod="Current" Multiplier="1" />

        <Reading TimeStamp="2017-04-01 03:08:00" Value="0" />

    </ConsumptionData>

这是我目前的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="*"/>

      <!-- BY DEFAULT, elements and text nodes are copied,
           and elements' attributes and contents are transformed as child nodes
           of the output element -->
      <xsl:template match="node()">
        <xsl:copy>
          <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
      </xsl:template>

      <!-- By default, attributes are transformed to elements -->
      <xsl:template match="@*">
        <xsl:element name="{name()}">
          <xsl:value-of select="."/>
        </xsl:element>
      </xsl:template>


      <!-- Certain elements have only their contents transformed -->
      <xsl:template match="
            Meter | Status | ConsumptionData |
            Statuses | MaxDemandData | MaxDemandSpec |
            InstrumentationValue | IntervalData | IntervalSpec">
        <!-- no xsl:copy, and attribute children, if any, are ignored -->
        <xsl:apply-templates select="@* | node()"/>
      </xsl:template>


      <!-- 
      Applies an extra element tag to the selected match


      and pulls the value from the MeterReading ancestor it's
      tagged under.
      -->

   <xsl:template match="Reading">

        <xsl:copy>

            <xsl:element name="MeterReadingIRN">
                <xsl:value-of select="ancestor::MeterReadings/@Irn"/>
            </xsl:element>

              <!-- 
              Trying to get into the ConsumptionSpec tag it's related to,
              then test what the unit of measurement is (UOM),
              and then test what 'TouBucket' it is a part of (TierA/B/C/D or Total),
              and THEN create a new element so that I can hold the 'value' that is inside
              the Reading element, so that it will be referenced to that specific UOM.
              -->
            <xsl:for-each select="ancestor::ConsumptionSpec">
                <xsl:choose>
                    <xsl:when test="@UOM='kWh'">
                        <xsl:when test="@TouBucket='Total'">

                            <xsl:element name="kWhTotal">
                                <xsl:value-of select="Reading/@Value"/>
                            </xsl:element>

                        </xsl:when>
                    </xsl:when>
                    <!-- Not sure how I can make my otherwise into a useful element here -->
                    <xsl:otherwise>

                        <xsl:element name="BlankTest">
                            <xsl:value-of select="ancestor::MeterReadings/@Irn"/>
                        </xsl:element>

                    </xsl:otherwise>
                </xsl:choose>
            </xsl:for-each>  
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

  <xsl:template match="Channel">
  <xsl:copy>
  <xsl:element name="MeterReadingIRN">
     <xsl:value-of select="ancestor::MeterReadings/@Irn"/>
   </xsl:element>
   <xsl:apply-templates select="@*|node()"/>
  </xsl:copy>
 </xsl:template>

</xsl:stylesheet>

任何和所有的建议都值得赞赏,在复活节周末的大部分时间里都在敲打我的头!如果有更多信息可以让我更容易理解,请告诉我。

2 个答案:

答案 0 :(得分:1)

而不是试图嵌套xsl:when语句,这是不允许的,你想要的语法就是这个......

<xsl:when test="@UOM='kWh' and @TouBucket='Total'">

但是,即使在此之前,您使用xsl:for-each

也会遇到问题
<xsl:for-each select="ancestor::ConsumptionSpec">

此时您正在匹配Reading元素,因此您当前将定位在该元素上。 ConsumptionSpec不是当前Reading元素的祖先。这是一个兄弟姐妹。

<ConsumptionData >
    <ConsumptionSpec UOM="kWh" Direction="Delivered" TouBucket="TierA" MeasurementPeriod="Current" Multiplier="1" />
    <Reading TimeStamp="2017-04-01 03:08:00" Value="0" />
</ConsumptionData>

这里ConsumptionDataConsumptionSpec的父亲都是Reading

另请注意,如果<xsl:for-each select="ancestor::ConsumptionSpec">确实选择了某个元素,则会将您的排名从Reading更改为ConsumptionSpec

我假设每ConsumptionSpec只有一个Reading?在这种情况下,使用变量可能更好。

请尝试使用此模板匹配:

<xsl:template match="Reading">
    <xsl:copy>
        <MeterReadingIRN>
            <xsl:value-of select="ancestor::MeterReadings/@Irn"/>
        </MeterReadingIRN>
         <xsl:variable name="spec" select="../ConsumptionSpec" />
         <xsl:choose>
           <xsl:when test="$spec/@UOM='kWh' and $spec/@TouBucket='Total'">
              <kWhTotal>
                 <xsl:value-of select="@Value"/>
               </kWhTotal>
              </xsl:when>
              <xsl:otherwise>
                <BlankTest>
                  <xsl:value-of select="ancestor::MeterReadings/@Irn"/>
                </BlankTest>
              </xsl:otherwise>
            </xsl:choose>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

注意,在这种情况下,没有必要使用xsl:element来创建新元素。只需直接写出你想要创建的元素。

XPath中的语法..表示选择父级,因此../ConsumptionSpec将选择与ConsumptionSpec相同的父级的Reading

答案 1 :(得分:1)

XSL的迭代和条件元素有很好的用途,但它们有点不常见。每当您考虑使用xsl:for-eachxsl:choosexsl:if构造时,您应该暂停一下,思考这是否真的是最好的工作方式。

此外,在开发样式表时,尝试将事物拆分为并行案例并一次处理一个案例并不一定有利。 XSL不是一种过程编程语言,它不适合这种思维。 XSLT倾向于通过一种整体的,自上而下的方法提供更好的服务 - 实际上,这本身就是XML本身。

<Reading>元素的模板为例,您似乎正准备为一堆符合常见模式的个案编写规则。具体来说,您似乎可能尝试根据适用的ConsumptionSpec的<Reading>UOM动态命名已转换的TouBucket元素的元素子元素。 这个<xsl:element>有用的东西;对于名称固定的输出元素,你真的不需要它,因为文字输出元素可以正常工作。因此,您可以使用简单的模板覆盖所有案例:

<xsl:template match="Reading">
  <xsl:copy>

    <!-- literal output element: -->
    <MeterReadingIRN>
      <xsl:value-of select="ancestor::MeterReadings/@Irn"/>
    </MeterReadingIRN>

    <xsl:element name="{concat(../ConsumptionSpec/@UOM, ../ConsumptionSpec/@TouBucket)}">
      <xsl:value-of select="@Value"/>
    </xsl:element>

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

即使您需要Reading元素的转换之间存在更多差异,您也应该考虑构建样式表,以便在模板上使用更具体的match表达式,更具体{{1表达式选择要应用模板的节点,和/或不同的模板模式来表达和指导转换。