1
votes

I need to:

(1) Generate a unique id attribute for the root element

(2) Append that id to the child elements

(3) Append the name and sequence of any parent element to the id attribute of a child element

**note -- I have an XML editor on my machine that can use XSLT 2.0 but would prefer 1.0 because whenever I run macros with visual basic, I think the Microsoft xml/xslt processor can only handle xslt 1.0. It doesn't seem to like 2.0.

Source XML Example:

<root>
<segment>
<para>Text of the segment here.</para>
</segment>
<segment>
<para>Text of the segment here.</para>
<para>Text of the segment here.</para>
</segment>
<segment>
<para>Text of the segment here.</para>
<sub_segment>
<para>Text of the segment here.</para>
</sub_segment>
</segment>
</root>

Desired Output XML:

<root id="idrootx2x1">
<segment id="idrootx2x1.segment.1">
<para id="idrootx2x1.segment.1.para.1">Text of the segment here.</para>
</segment>
<segment id="idrootx2x1.segment.2">
<para id="idrootx2x1.segment.2.para.1">Text of the segment here.</para>
<para id="idrootx2x1.segment.2.para.2">Text of the segment here.</para>
</segment>
<segment id="idrootx2x1.segment.3">
<para id="idrootx2x1.segment.3.para.1">Text of the segment here.</para>
<sub_segment id="idrootx2x1.segment.3.sub_segment.1">
<para id="idrootx2x1.segment.3.sub_segment.1.para.1">Text of the segment here.</para>
</sub_segment>
</segment>
</root>

Here is the XSLT I have so far:

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

<xsl:template match="root">
<xsl:copy>
    <xsl:attribute name="id"><xsl:value-of select="generate-id()"/></xsl:attribute>
    <xsl:apply-templates select="*|@*|text()"/>
</xsl:copy>
</xsl:template>

<xsl:template match="segment | para | sub_segment">
<xsl:copy>
    <xsl:attribute name="id">
        <xsl:value-of select="name(.)"/>.<xsl:number format="1" level="single"/>
    </xsl:attribute>
    <xsl:apply-templates select="*|@*|text()"/>
</xsl:copy>
</xsl:template>
1
Amazing how much work you made yourself marking up all those tags as code one by one. Next time, maybe use the "format as code" button to mark an entire block at once?Tomalak

1 Answers

1
votes

you can just pass your parent numbering to children like this:

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

<xsl:template match="root">
<xsl:copy>
    <xsl:attribute name="id"><xsl:value-of select="generate-id()"/></xsl:attribute>
    <xsl:apply-templates select="@*|node()">
        <xsl:with-param name="prev_id" select="generate-id()"/>
    </xsl:apply-templates>
</xsl:copy>
</xsl:template>

<xsl:template match="segment|para|sub_segment">
<xsl:param name="prev_id"/>
<xsl:copy>
    <xsl:variable name="cur_id">
        <xsl:value-of select="concat($prev_id,'.',name())"/>.<xsl:number format="1" level="single"/>
    </xsl:variable>
    <xsl:attribute name="id"><xsl:value-of select="$cur_id"/></xsl:attribute>
    <xsl:apply-templates select="@*|node()">
        <xsl:with-param name="prev_id" select="$cur_id"/>
    </xsl:apply-templates>
</xsl:copy>
</xsl:template>

in case there're some other non-numbered elements wrapping numbered ones, change the identity template to

<xsl:template match="@*|node()">
<xsl:param name="prev_id"/>
<xsl:copy>
    <xsl:apply-templates select="@*|node()">
        <xsl:with-param name="prev_id" select="$prev_id"/>
    </xsl:apply-templates>
</xsl:copy>
</xsl:template>

so that it forwards parent numbering