【问题标题】:XSLT for transforming XML to JSON (Solr format)XSLT 用于将 XML 转换为 JSON(Solr 格式)
【发布时间】:2019-07-02 22:23:57
【问题描述】:

我正在尝试生成一个 XSLT 以将 XML 文件转换为 JSON 格式,包括在 Solr 中正确索引的所有必要字段。

我当前的 XSLT 文件如下:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text"/>
    <xsl:template match="/">{
        <xsl:apply-templates select="*"/>}
    </xsl:template>

    <!-- Object or Element Property-->
    <xsl:template match="*">
        <xsl:variable name="childDoc" select="name(*[1])"/>
        <xsl:choose>
            <xsl:when test="count(*[name()=$childDoc]) >= 1">
    "Document":"<xsl:value-of select="name()"/>", <xsl:call-template name="Properties"/>
                </xsl:when>
                <xsl:otherwise>
    "<xsl:value-of select="name()"/>": <xsl:call-template name="Properties"/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:template>

        <!-- Array Parent Node -->
        <xsl:template match="*" mode="ArrayParentNode">
            <xsl:call-template name="Path"/>
        </xsl:template>

        <!-- Array Element -->
        <xsl:template match="*" mode="ArrayElement">
            <xsl:call-template name="Properties"/>
        </xsl:template>

        <!-- Object Properties -->
        <xsl:template name="Properties">
            <xsl:variable name="childName" select="name(*[1])"/>
            <xsl:choose>
                <xsl:when test="not(*|@*)">"<xsl:value-of select="."/>"</xsl:when>
                <xsl:when test="count(*[name()=$childName]) > 1">
    "_childDocuments_":[
    { "<xsl:value-of select="$childName"/>" :[<xsl:apply-templates select="*" mode="ArrayElement"/>] 
        }
    ]
                </xsl:when>
                <xsl:otherwise>
    "_childDocuments_":[
        {
                    <xsl:apply-templates select="." mode="ArrayParentNode"/>
                    <xsl:apply-templates select="@*"/>
                    <xsl:apply-templates select="*"/>                   
        }
        ]
                </xsl:otherwise>
            </xsl:choose>
            <xsl:if test="following-sibling::*">,</xsl:if>

        </xsl:template>

        <!-- Attribute Property -->
        <xsl:template match="@*">"<xsl:value-of select="name()"/>" : "<xsl:value-of select="."/>",</xsl:template>

        <!--Path-->
        <xsl:template name = "Path">
            <xsl:variable name="ances" select="ancestor-or-self::node()"/>
            <xsl:variable name="ancestros">
                <xsl:for-each select="$ances">
                    <xsl:value-of select="concat(name(),'.')"/>
                </xsl:for-each>
            </xsl:variable>
        "Path": "<xsl:value-of select = "substring($ancestros,2,string-length($ancestros)-2)"/>",
        </xsl:template>

    </xsl:stylesheet>

举个例子:

<Reporte>
    <id>10</id>
    <Operacion>
        <id>10.1</id>
        <Tipo_Operacion>Ejemplo</Tipo_Operacion>
        <Fecha>10/10/2010</Fecha>
        <Monto>12345</Monto>
    </Operacion>
    <bla>123456ytgfde</bla>
    <Persona_Fisica>
        <id>10.2</id>
        <Nombre>Juan</Nombre>
        <Apellido>Perez</Apellido>
        <Domicilio>
            <id>10.2.1</id>
            <Calle>Yrigoyen</Calle>
            <Numero>123</Numero>
        </Domicilio>
        <Telefono>
            <id>10.2.2</id>
            <Prefijo>11</Prefijo>
            <Numero>12345678</Numero>
        </Telefono>
    </Persona_Fisica>
    <Persona_Fisica>
        <id>10.3</id>
        <Nombre>Roberto Carlos</Nombre>
        <Apellido>De Souza</Apellido>
        <PEP>true</PEP>
        <Domicilio>
            <id>10.3.1</id>
            <Calle>Falsa</Calle>
            <Numero>678</Numero>
        </Domicilio>
        <Telefono>
            <id>10.3.2</id>
            <Prefijo>11</Prefijo>
            <Numero>87654321</Numero>
        </Telefono>
    </Persona_Fisica>
    <Persona_Juridica>
        <id>10.4</id>
        <Denominacion>Lavado SRL</Denominacion>
        <CUIT>23-32480636-9</CUIT>
    </Persona_Juridica>
</Reporte>

我的输出如下:

{

                "Document":"Reporte", 
    "_childDocuments_":[
        {

        "Path": "Reporte",

                "id": "11",
                "Document":"Operacion", 
    "_childDocuments_":[
        {

        "Path": "Reporte.Operacion",

                "id": "10.1",
                "Tipo_Operacion": "Ejemplo",
                "Fecha": "10/10/2010",
                "Monto": "12345"                    
        }
        ]
                ,
                "bla": "123456ytgfde",
                "Document":"Persona_Fisica", 
    "_childDocuments_":[
        {

        "Path": "Reporte.Persona_Fisica",

                "id": "10.2",
                "Nombre": "Juan",
                "Apellido": "Perez",
                "Document":"Domicilio", 
    "_childDocuments_":[
        {

        "Path": "Reporte.Persona_Fisica.Domicilio",

                "id": "10.2.1",
                "Calle": "Yrigoyen",
                "Numero": "123"                 
        }
        ]
                ,
                "Document":"Telefono", 
    "_childDocuments_":[
        {

        "Path": "Reporte.Persona_Fisica.Telefono",

                "id": "10.2.2",
                "Prefijo": "11",
                "Numero": "12345678"                    
        }
        ]

        }
        ]
                ,
                "Document":"Persona_Fisica", 
    "_childDocuments_":[
        {

        "Path": "Reporte.Persona_Fisica",

                "id": "10.3",
                "Nombre": "Roberto Carlos",
                "Apellido": "De Souza",
                "PEP": "true",
                "Document":"Domicilio", 
    "_childDocuments_":[
        {

        "Path": "Reporte.Persona_Fisica.Domicilio",

                "id": "10.3.1",
                "Calle": "Falsa",
                "Numero": "678"                 
        }
        ]
                ,
                "Document":"Telefono", 
    "_childDocuments_":[
        {

        "Path": "Reporte.Persona_Fisica.Telefono",

                "id": "10.3.2",
                "Prefijo": "11",
                "Numero": "87654321"                    
        }
        ]

        }
        ]
                ,
                "Document":"Persona_Juridica", 
    "_childDocuments_":[
        {

        "Path": "Reporte.Persona_Juridica",

                "id": "10.4",
                "Denominacion": "Lavado SRL",
                "CUIT": "23-32480636-9"                 
        }
        ]

        }
        ]
                }

不考虑缩进(因为它是可取的但不是必需的),我期望如下所示:

{
    "Document": "Reporte",
    "Path": "Reporte",
    "id": "12",
    "bla": "123456ytgfde",
    "_childDocuments_": [
        {
            "Document": "Operacion",
            "Path": "Reporte.Operacion",
            "id": "10.1",
            "Tipo_Operacion": "Ejemplo",
            "Fecha": "10/10/2010",
            "Monto": "12345"
        },
        {
            "Document": "Persona_Fisica",
            "Path": "Reporte.Persona_Fisica",
            "id": "10.2",
            "Nombre": "Juan",
            "Apellido": "Perez",
            "_childDocuments_": [
                {
                    "Document": "Domicilio",
                    "Path": "Reporte.Persona_Fisica.Domicilio",
                    "id": "10.2.1",
                    "Calle": "Yrigoyen",
                    "Numero": "123"
                },
                {
                    "Document": "Telefono",
                    "Path": "Reporte.Persona_Fisica.Telefono",
                    "id": "10.2.2",
                    "Prefijo": "11",
                    "Numero": "12345678"
                }
            ]
        },
        {
            "Document": "Persona_Fisica",
            "Path": "Reporte.Persona_Fisica",
            "id": "10.3",
            "Nombre": "Roberto Carlos",
            "Apellido": "De Souza",
            "PEP": "true",
            "_childDocuments_": [
                {
                    "Document": "Domicilio",
                    "Path": "Reporte.Persona_Fisica.Domicilio",
                    "id": "10.3.1",
                    "Calle": "Falsa",
                    "Numero": "678"
                },
                {
                    "Document": "Telefono",
                    "Path": "Reporte.Persona_Fisica.Telefono",
                    "id": "10.3.2",
                    "Prefijo": "11",
                    "Numero": "87654321"
                }
            ]
        },
        {
            "Document": "Persona_Juridica",
            "Path": "Reporte.Persona_Juridica",
            "id": "10.4",
            "Denominacion": "Lavado SRL",
            "CUIT": "23-32480636-9"
        }
    ]
}

总结一下,我的 XSLT 还缺少什么:

  1. “Reporte”中没有任何子元素的元素(如 “文档”:“报告”,“路径”:“报告”,“id”:“10”,“bla”: "123456ytgfde") 应该在第一个 childDocuments 之外 元素。
  2. 只有一个 childDocuments 元素具有多个 应创建子文档,而不是每个子文档一个。
  3. “文档”元素应该在相应的之后 childDocuments 而不是之前(与“路径”元素并排,这没关系)。
  4. 漂亮的缩进(恰到好处)。

【问题讨论】:

  • 您是否仅限于 XSLT 1? JSON 中的属性顺序是否重要? XSLT 3(自 2017 年起可用)内置 JSON 支持和序列化,因此您可以轻松地在其中编写模板,创建 XSLT 3/XPath 3.1 映射和数组并将它们序列化为 json,例如,请参阅 xsltfiddle.liberty-development.net/94rmq5S
  • 我在 Nifi 中使用了 TransformXML 处理器,但我找不到它无法运行的 XSLT 版本。但是,我尝试运行您附加的转换并以错误结束,所以我猜它不支持 XSLT 3。
  • 如果可能的话,最好用 XSLT 1 来解决。
  • “我在 Nifi 中使用了 TransformXML 处理器,但我找不到它在任何地方运行的 XSLT 版本”:任何 XSLT 处理器都允许您运行输出例如&lt;xsl:value-of select="system-property('xsl:version')"/&gt; 找出答案。
  • gitbox.apache.org/repos/… 似乎暗示他们包括 Saxon HE,至少看起来是 9.6,所以你应该支持 XSLT 2。如果您将 Saxon 9.9 或 9.8 HE 从 Maven 或 Sourceforge 放到 Nifi 类路径中,则可能更容易使用 XSLT 3。

标签: json xml xslt solr


【解决方案1】:

如果您可以迁移到 XSLT 3,则可以使用对 XML 到 JSON 转换的内置支持 (https://www.w3.org/TR/xslt-30/#json),例如通过创建 XSLT 3 规范中指定的 JSON 的 XML 表示,然后调用 @987654325 @函数(https://www.w3.org/TR/xpath-functions/#func-xml-to-json):

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:fn="http://www.w3.org/2005/xpath-functions"
    exclude-result-prefixes="#all"
    expand-text="yes"
    version="3.0">

  <xsl:output method="text"/>

  <xsl:mode name="json" on-no-match="shallow-skip"/>

  <xsl:template match="*[*]" mode="json">
      <fn:map>
          <fn:string key="Document">{local-name()}</fn:string>
          <fn:string key="Path">{string-join(ancestor-or-self::*/local-name(), '.')}</fn:string>
          <xsl:apply-templates select="*[not(*)]" mode="#current"/>
          <xsl:where-populated>
              <fn:array key="_childDocument_">
                  <xsl:apply-templates select="*[*]" mode="#current"/>
              </fn:array>
          </xsl:where-populated>
      </fn:map>
  </xsl:template>

  <xsl:template match="*[not(*)]" mode="json">
      <fn:string key="{local-name()}">{.}</fn:string>
  </xsl:template>

  <xsl:variable name="json-xml">
      <xsl:apply-templates mode="json"/>
  </xsl:variable>

  <xsl:template match="/">
      <xsl:sequence select="xml-to-json($json-xml, map { 'indent' : true() })"/>
  </xsl:template>

</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/94rmq5S/2

正如您在https://xsltfiddle.liberty-development.net/94rmq5S/8 上还要求提供 XSLT 1 解决方案一样,我尝试重用 XSLT 3 模板将您的 XML 转换为 XSLT 3 规范中指定的 XML 格式以表示 JSON,只是这次使用纯 XSLT 1,然后我添加了一些模板来尝试将 XSLT 1 中的 XML 序列化为 JSON 文本,使用 exsl:node-set 将中间结果树片段转换为节点集:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:fn="http://www.w3.org/2005/xpath-functions"
    xmlns:exsl="http://exslt.org/common"
    exclude-result-prefixes="fn exsl"
    version="1.0">

  <xsl:param name="indent" select="'  '"/>

  <xsl:output method="text"/>

  <xsl:template match="*[*]" mode="json">
      <xsl:param name="path" select="''"/>
      <xsl:variable name="current-path">
          <xsl:if test="ancestor::*">
              <xsl:value-of select="concat($path, '.')"/>
          </xsl:if>
          <xsl:value-of select="local-name()"/>
      </xsl:variable>
      <fn:map>
          <fn:string key="Document">
              <xsl:value-of select="local-name()"/>
          </fn:string>
          <fn:string key="Path">
              <xsl:value-of select="$current-path"/>
          </fn:string>
          <xsl:apply-templates select="*[not(*)]" mode="json"/>
          <xsl:if test="*[*]">
              <fn:array key="_childDocument_">
                  <xsl:apply-templates select="*[*]" mode="json">
                      <xsl:with-param name="path" select="$current-path"/>
                  </xsl:apply-templates>
              </fn:array>
          </xsl:if>
      </fn:map>
  </xsl:template>

  <xsl:template match="*[not(*)]" mode="json">
      <fn:string key="{local-name()}">
          <xsl:value-of select="."/>
      </fn:string>
  </xsl:template>

  <xsl:variable name="json-xml">
      <xsl:apply-templates mode="json"/>
  </xsl:variable>

  <xsl:template match="/">
      <!--
      <xsl:copy-of select="exsl:node-set($json-xml)/node()"/>
      <hr/>
      -->
      <xsl:apply-templates select="exsl:node-set($json-xml)/node()"/>
  </xsl:template>

  <xsl:template match="fn:map">
      <xsl:param name="current-indent" select="''"/>
      <xsl:if test="position() > 1">,&#10;</xsl:if>
      <xsl:value-of select="concat($current-indent, '{&#10;')"/>
      <xsl:apply-templates>
          <xsl:with-param name="current-indent" select="concat($current-indent, $indent)"/>
      </xsl:apply-templates>
      <xsl:value-of select="concat('&#10;', $current-indent, '}')"/>
  </xsl:template>

  <xsl:template match="fn:array[@key]">
      <xsl:param name="current-indent"/>
      <xsl:if test="position() > 1">,&#10;</xsl:if>
      <xsl:value-of select="concat($current-indent, '&quot;', @key, '&quot; : [&#10;')"/>
      <xsl:apply-templates>
          <xsl:with-param name="current-indent" select="concat($current-indent, $indent)"/>
      </xsl:apply-templates>      
      <xsl:value-of select="concat('&#10;', $current-indent, ']')"/>
  </xsl:template>

  <xsl:template match="fn:string">
      <xsl:param name="current-indent"/>
      <xsl:if test="position() > 1">,&#10;</xsl:if>
      <xsl:value-of select="concat($current-indent, '&quot;', @key, '&quot; : &quot;', ., '&quot;')"/>
  </xsl:template>

</xsl:stylesheet>

考虑到 XSLT 3 方法,这有几个缺点,我没有尝试实现所有不同的 JSON 数据类型,如数字、布尔值,只有字符串模板,我没有尝试确保任何字符在需要转义的 JSON 字符串中正确转义。而且我确信还有其他缺陷,因为它需要更广泛和更严格的测试,但作为处理数据或方法的一个步骤,它也许可以帮助您。

【讨论】:

  • 谢谢,马丁!我已经测试了最后一个 XSLT,它工作正常。只是一点评论。能够使用 Solr 处理文档的正确转换是使用 childDocuments(复数形式)而不是 childDocument
猜你喜欢
  • 2022-11-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-26
  • 2023-03-30
  • 1970-01-01
相关资源
最近更新 更多