我很难绕过我需要的语法来创建一个我想要的元素。 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>
任何和所有的建议都值得赞赏,在复活节周末的大部分时间里都在敲打我的头!如果有更多信息可以让我更容易理解,请告诉我。
答案 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>
这里ConsumptionData
和ConsumptionSpec
的父亲都是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-each
,xsl:choose
或xsl: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表达式选择要应用模板的节点,和/或不同的模板模式来表达和指导转换。