Gabo Bonanno
Gabo Bonanno

Reputation: 21

Generate IDs for XML element with XSLT

I have an XML file with the following structure:

<Operation>
    <id_operation>10</id_operation>
    <operation_type>blablabla</operation_type>
    <Person>
        <surname>Doe</surname>
        <name>John</name>
        <Address>
            <Street>Fake</Street>
            <Number>123</Number>
        </Address>
    </Person>
    <Person>
        <surname>Smith</surname>
        <name>Paul</name>
    </Person>
</Operation>

I need to make an XSLT to get the following (adding the IDs for each element based on the operation ID and the element hierarchy):

<Operation>
    <id>10</id>
    <operation_type>blablabla</operation_type>
    <Person>
        <id>10.1</id>
        <surname>Doe</surname>
        <name>John</name>
        <Address>
            <id>10.1.1</id>
            <Street>Fake</Street>
            <Number>123</Number>
        </Address>
    </Person>
    <Person>
        <id>10.2</id>
        <surname>Smith</surname>
        <name>Paul</name>
    </Person>
</Operation>

Any ideas?

Upvotes: 0

Views: 596

Answers (3)

Gabo Bonanno
Gabo Bonanno

Reputation: 21

Finally, taking ideas from you and modifying it a little bit, here is what I got.

Original XML:

<Reporte>
    <idoperacion>10</idoperacion>
    <Operacion>
        <Tipo>Lavado</Tipo>
        <Fecha>01/02/2018</Fecha>
    </Operacion>
    <Persona_Fisica>
        <Nombre>Juan</Nombre>
        <Apellido>Perez</Apellido>
        <Domicilio>
            <Calle>Falsa</Calle>
            <Nro>123</Nro>
        </Domicilio>
    </Persona_Fisica>
    <Persona_Juridica>
        <Denominacion>Lavado SRL</Denominacion>
    </Persona_Juridica>
    <Persona_Fisica>
        <Nombre>Jose</Nombre>
        <Apellido>Lopez</Apellido>
        <Tipo_Documento>CUIT</Tipo_Documento>
        <Nro_Documento>12345678</Nro_Documento>
        <Domicilio>
            <Calle>Sarasa</Calle>
            <Nro>1234</Nro>
        </Domicilio>
        <Telefono>
            <Numero>1234-5678</Numero>
        </Telefono>
    </Persona_Fisica>
</Reporte>

XSLT:

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

    <!-- Object or Element Property-->
    <xsl:template match="*">
            <xsl:text disable-output-escaping="yes">&lt;</xsl:text>
            <xsl:value-of select="name()"/>
            <xsl:text disable-output-escaping="yes">&gt;</xsl:text>
            <xsl:call-template name="Properties"/>
            <xsl:text disable-output-escaping="yes">&lt;</xsl:text>/<xsl:value-of select="name()"/>
            <xsl:text disable-output-escaping="yes">&gt;</xsl:text>

    </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"><xsl:value-of select="$childName"/>[<xsl:apply-templates select="*" mode="ArrayElement"/>]</xsl:when>
            <xsl:otherwise>
                <xsl:apply-templates select="@*"/>
                <xsl:apply-templates select="*"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

    <!--ID Reporte-->
    <xsl:template match="idoperacion">
         <id><xsl:value-of select="."/></id>
    </xsl:template>

    <!--ID-->
    <xsl:template match="Operacion | Juzgado | Causa_Penal | Delito | Inmueble | Automotor | Producto | Persona_Fisica | Telefono | Domicilio | Persona_Juridica">
        <xsl:copy>
            <id><xsl:value-of select="ancestor::Reporte/idoperacion"/><xsl:text>.</xsl:text><xsl:number level="multiple" count="*" from="Reporte"/></id>
            <xsl:apply-templates/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

New XML:

<Reporte><id>10</id>
<Operacion>
   <id>10.2</id>
        <Tipo>Lavado</Tipo>
        <Fecha>01/02/2018</Fecha>
    </Operacion>
<Persona_Fisica>
   <id>10.3</id>
        <Nombre>Juan</Nombre>
        <Apellido>Perez</Apellido>
        <Domicilio>
      <id>10.3.3</id>
            <Calle>Falsa</Calle>
            <Nro>123</Nro>
        </Domicilio>
    </Persona_Fisica>
<Persona_Juridica>
   <id>10.4</id>
        <Denominacion>Lavado SRL</Denominacion>
    </Persona_Juridica>
<Persona_Fisica>
   <id>10.5</id>
        <Nombre>Jose</Nombre>
        <Apellido>Lopez</Apellido>
        <Tipo_Documento>CUIT</Tipo_Documento>
        <Nro_Documento>12345678</Nro_Documento>
        <Domicilio>
      <id>10.5.5</id>
            <Calle>Sarasa</Calle>
            <Nro>1234</Nro>
        </Domicilio>
        <Telefono>
      <id>10.5.6</id>
            <Numero>1234-5678</Numero>
        </Telefono>
    </Persona_Fisica></Reporte>

This has some obvious bugs:

  1. Indentation: it's clear it's not indenting properly, but it's not so important at this point.
  2. ID sequence: it's considering initial ID as the first element, so the second ID is "original_id.2" instead of "original_id.1". Also, it doesn't restart sequence properly when adding a new element (for example, 10.5.5 goes after 10.5 instead of 10.1). However, it accomplishes the goal of a hierarchical ID (all numbers are different and you can determine which elements are father and son).

Thanks all for your help!

Upvotes: 0

Parfait
Parfait

Reputation: 107567

Consider this 1.0 solution using concatenation of node positions by ancestor and preceding-sibling counts:

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

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

    <xsl:template match="Person">
        <xsl:copy>
            <id><xsl:value-of select="concat('10.', (count(preceding-sibling::*[name()='Person']) + 1))"/></id>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="Address">
        <xsl:copy>
            <id><xsl:value-of select="concat('10.', (count(ancestor::Person/preceding-sibling::*[name()='Person']) + 1),
                                             '.', (count(preceding-sibling::*[name()='Address']) + 1))"/></id>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

XSLT Demo (extended input XML for fuller demo)

Upvotes: 0

Michael Kay
Michael Kay

Reputation: 163262

Generating the top-level id is trivial:

<xsl:template match="id_operation">
  <id><xsl:value-of select="."/></id>
</xsl:template>

For the deeper ids you can use xsl:number:

<xsl:template match="Address">
  <xsl:copy>
    <id>
      <xsl:value-of select="ancestor::operation/id"/>
      <xsl:text>.</xsl:text>
      <xsl:number level="multiple" count="*" from="operation"/>
    </id>
    <xsl:apply-templates/>
  </xsl:copy>
</xsl:template>

Not tested and you may need to tweak the xsl:number attributes.

Upvotes: 1

Related Questions