XSLT XML 到 JSON 问题

XSLT XML to JSON issue

我正在尝试使用 XSLT 将 XML 更改为 JSON。它实际上是一个非常简单的模式,但我修改使用的 XSLT 比我需要的更复杂,因为除了顶级的之外,我不需要漂亮的打印或数组。

来源XML:

<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<r xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <GR CO="B" Date="2022-03-15" Zone="ASDF" Truck="1TR"></GR>
    <BS CO="A" Date="2022-03-14" Zone="ASDF" Truck="BT1"/>
    <GR CO="A" Date="2022-03-14" Zone="QWER" Truck="2TK"></GR>
</r>

所有属性都将成为一个 JSON 字符串元素,后跟一个逗号。然后在名为 Line 的末尾附加一个原始元素名称的值。最好也按日期属性排序。

所需输出的漂亮打印,但不需要白色 space:

{

    "Routes": [

        {
            "CO": "A",
            "Date": "2022-03-14",
            "Zone": "QWER",
            "Truck": "BT1",
            "Line": "BS"
        },
        {
            "CO": "A",
            "Date": "2022-03-14",
            "Zone": "ASDF",
            "Truck": "2TK",
            "Line": "GR"
        },
        {
            "CO": "B",
            "Date": "2022-03-15",
            "Zone": "ASDF",
            "Truck": "1TR",
            "Line": "GR"
        }

    ]
}

这就是我正在尝试的方法,但我无法让它工作:

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


    <xsl:template match="/r">
        <xsl:text>{"Routes": [</xsl:text>
        <xsl:apply-templates select="." mode="detect">
        <xsl:sort select="./@Date" />
        </xsl:apply-templates>
        <xsl:text>}</xsl:text>
    </xsl:template>

    <xsl:template match="@*" priority="1">
        <xsl:text>Found Attrib</xsl:text>
    </xsl:template>

    <xsl:template match="*" priority="2" mode="detect">
        <xsl:text>{</xsl:text>
        <xsl:choose>
            <xsl:when test="name(preceding-sibling::*[1]) = name(current()) and name(following-sibling::*[1]) != name(current())">
                <xsl:apply-templates select="." mode="obj-content" />
                <xsl:text>]</xsl:text>
                <xsl:if test="count(following-sibling::*[name() != name(current())]) &gt; 0">, </xsl:if>
            </xsl:when>
            <xsl:when test="name(preceding-sibling::*[1]) = name(current())">
                    <xsl:apply-templates select="." mode="obj-content" />
                    <xsl:if test="name(following-sibling::*) = name(current())">, </xsl:if>
            </xsl:when>
            <xsl:when test="following-sibling::*[1][name() = name(current())]">
                <xsl:text>"</xsl:text><xsl:value-of select="name()"/><xsl:text>" : [</xsl:text>
                    <xsl:apply-templates select="." mode="obj-content" /><xsl:text>, </xsl:text> 
            </xsl:when>
            <xsl:when test="count(./child::*) > 0 or count(@*) > 0">
                <xsl:text>"</xsl:text><xsl:value-of select="name()"/>" : <xsl:apply-templates select="." mode="obj-content" />
                <xsl:if test="count(following-sibling::*) &gt; 0">, </xsl:if>
            </xsl:when>
            <xsl:when test="count(./child::*) = 0">
                <xsl:text>"</xsl:text><xsl:value-of select="name()"/>" : "<xsl:apply-templates select="."/><xsl:text>"</xsl:text>
                <xsl:if test="count(following-sibling::*) &gt; 0">, </xsl:if>
            </xsl:when>
        </xsl:choose>
        <xsl:text>"Line": "</xsl:text><xsl:value-of select="local-name()"/><xsl:text>"
    </xsl:text>

    </xsl:template>

    <xsl:template match="*" mode="obj-content">
        <xsl:text>{</xsl:text>
            <xsl:apply-templates select="@*" mode="attr" />
            <xsl:if test="count(@*) &gt; 0 and (count(child::*) &gt; 0 or text())">, </xsl:if>
            <xsl:apply-templates select="./*" mode="detect" />
            <xsl:if test="count(child::*) = 0 and text() and not(@*)">
                <xsl:text>"</xsl:text><xsl:value-of select="name()"/>" : "<xsl:value-of select="text()"/><xsl:text>"</xsl:text>
            </xsl:if>
            <xsl:if test="count(child::*) = 0 and text() and @*">
                <xsl:text>"text" : "</xsl:text><xsl:value-of select="text()"/><xsl:text>"</xsl:text>
            </xsl:if>
        <xsl:text>}</xsl:text>
        <xsl:if test="position() &lt; last()">, </xsl:if>
    </xsl:template>
    
    <xsl:template match="@*" mode="attr">
        <xsl:text>"</xsl:text><xsl:value-of select="name()"/>" : "<xsl:value-of select="."/><xsl:text>"</xsl:text>
        <xsl:if test="position() &lt; last()">,</xsl:if>
    </xsl:template>

    <xsl:template match="node/@TEXT | text()" name="removeBreaks">
        <xsl:param name="pText" select="normalize-space(.)"/>
        <xsl:choose>
            <xsl:when test="not(contains($pText, '&#xA;'))"><xsl:copy-of select="$pText"/></xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="concat(substring-before($pText, '&#xD;&#xA;'), ' ')"/>
                <xsl:call-template name="removeBreaks">
                    <xsl:with-param name="pText" select="substring-after($pText, '&#xD;&#xA;')"/>
                </xsl:call-template>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    
</xsl:stylesheet>

首先,您没有说明要使用哪个版本的 XSLT,所以这是一个 XSLT 3.0 解决方案:

<xsl:stylesheet version="3.0"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="json" indent="yes"/>
    <xsl:strip-space elements="*"/>
    <xsl:template match="/r">
        <xsl:map>
            <xsl:variable name="content" as="map(*)*">
                <xsl:apply-templates>
                   <xsl:sort select="xs:date(@Date)"/>
                </xsl:apply-templates>
            </xsl:variable>
            <xsl:map-entry key="'Routes'" select="array{$content}"/>
        </xsl:map>
    </xsl:template>
    <xsl:template match="r/*">
        <xsl:sequence select="map{
            'CO': string(@CO), 
            'Date': string(@Date), 
            'Zone': string(@Zone), 
            'Truck': string(@Truck), 
            'Line':local-name()}"/>
    </xsl:template>
</xsl:stylesheet>

对于 Saxon,这会产生输出

{ "Routes": [
    { "CO": "A","Zone": "ASDF","Date": "2022-03-14","Line": "BS","Truck": "BT1" },
    { "CO": "A","Zone": "QWER","Date": "2022-03-14","Line": "GR","Truck": "2TK" },
    { "CO": "B","Zone": "ASDF","Date": "2022-03-15","Line": "GR","Truck": "1TR" }
  ] }

使用 XSLT 1.0 几乎没有任何困难,而且比你做的要容易得多(我真的完全无法理解你的复杂逻辑)。

试试这个:

<xsl:stylesheet version="1.0"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text"/>
    <xsl:strip-space elements="*"/>
    <xsl:template match="/r">
        { "Routes": [
           <xsl:for-each select="*">
            <xsl:sort select="@Date"/>
            <xsl:if test="position() != 1">, </xsl:if>
            {"CO": "<xsl:value-of select="@CO"/>", 
            "Date": "<xsl:value-of select="@Date"/>",
            "Zone": "<xsl:value-of select="@Zone"/>",
            "Truck": "<xsl:value-of select="@Truck"/>", 
            "Line": "<xsl:value-of select="local-name()"/>"}            
           </xsl:for-each>
       ]}
    </xsl:template>

</xsl:stylesheet>

输出是:

    { "Routes": [
       
        {"CO": "A", 
        "Date": "2022-03-14",
        "Zone": "ASDF",
        "Truck": "BT1", 
        "Line": "BS"}           
       , 
        {"CO": "A", 
        "Date": "2022-03-14",
        "Zone": "QWER",
        "Truck": "2TK", 
        "Line": "GR"}           
       , 
        {"CO": "B", 
        "Date": "2022-03-15",
        "Zone": "ASDF",
        "Truck": "1TR", 
        "Line": "GR"}
]}