I just switched from using the C++ Xalan to xalan-j.

My current project involves translating certain XML documents into XSL-FO and 
feeding them to Apache FOP, while also allowing for other output types that can 
be selected by importing different stylesheets.  When I started with the Java 
xalan, FOP started complaining about duplicate id attributes. I located the 
place where the offending IDs were being generated, and after toying with the 
code and discovering that a trivial change causes the IDs to suddenly become 
unique, I am thoroughly confused. It may be a bug, but I'd like to see if 
someone here can find another explanation.

Instead of starting with all of the gory details, I think I should give a 
simplified view first, and put the full sturm und drang if anyone wants to see 
it.  It starts like this:  (This, BTW, is ancient code from when I first 
started playing with XSLT and I'm still pretty green at it, so please try no to 
laugh too hard...)

<xsl:template match="play:persona">     <!-- This project is about processing 
scripts for plays. -->
   //stuff//
        <xsl:variable name="foo">       <!-- Not its real name -->

<!-- out:block-container-objects are used to create <fo:block-container> 
objects in the result when the XSL-FO stylesheet is imported. -->

                <xsl:element name="out:block-container-object">
                        <xsl:attribute name="persona-id"><xsl:value-of 
select="@id"/></xsl:attribute>                           <!-- These two are for 
testing.
                        <xsl:attribute 
name="persona-generated-id"><xsl:value-of 
select="generate-id()"/></xsl:attribute>       -->
                        // stuff

<!-- This is the first of three out:block-container-objects that will be 
encapsulated in the parent out:block-container-object.  (BTW, this is a 
workaround; FOP doesn't handle
<fo:inline-container> objects yet.                                              
                                                                                
                -->

                        <xsl:element name="out:block-container-object">
                                <xsl:attribute name="persona-id"><xsl:value-of 
select="@id"/></xsl:attribute>                           <!-- To verify that 
they are the same as the ones
                                <xsl:attribute 
name="persona-generated-id"><xsl:value-of 
select="generate-id()"/></xsl:attribute>       -->  in the parents.
                                // stuff
                        </xsl:element>

                        // Two more...

                </xsl:element>
        </xsl:variable>

<!-- This hands the package to the imported, output-specific stylesheet. -->

        <xsl:apply-templates 
select="xalan:nodeset($foo)/out:block-container-object"/>   <!-- BTW, 
xalan:nodeset seems to work differently in the C++ version. -->
</xsl:template>

The nodeset looks like this:

<out:block-container-object persona-id=" (ID of the play:persona object)" 
persona-generated-id="(generated ID)">
        <out:block-container-object>   <!-- Same attributes -->
                (stuff)
        </out:block-container-object>
        // (two more of the same)
</out:block-container-object>


The XSL-FO imported styesheet turns these into <fo:block-container> objects.

<xsl:template match="out:block-container-object">
        //stuff
                <xsl:element name="fo:block-container">
                                <xsl:variable name="id">

<!-- =================== Here's where the weirdness happens. 
====================== -->

                                        <xsl:choose><xsl:when test="false() and 
string-length(row-id)"><xsl:value-of 
select="concat('row-id',row-id)"/></xsl:when>
                                        <xsl:otherwise><xsl:value-of 
select="generate-id()"/></xsl:otherwise></xsl:choose>
<!--
                                        <xsl:value-of select="generate-id()"/>
-->
                                </xsl:variable>

The <xsl:choose> block is a remnant of some old code which I disabled with the 
"false() and..." rather than chop it out, since I wasn't sure if I wanted to do 
that.   As you
can see, the variable is always set by the <xsl:value-of 
select="generate-id()"/> in <xsl:otherwise>

                                
This is code to dump out the state of this node, created when I started trying 
to troubleshoot this.

<xsl:if test="true()">
        <xsl:message>
                <xsl:value-of select="concat(
                'new-id=&quot;',$new-id,'&quot; 
persona-generated-id=&quot;',@persona-generated-id,'&quot; 
persona-id=&quot;',@persona-id,'&quot;')"/>
        </xsl:message>
</xsl:if>

<!-- Finally, set the id attribute. -->
                                <xsl:attribute name="id">
                                        <xsl:value-of select="$id"/>
                                </xsl:attribute>
// etc.
</xsl:template>


Here's what results.  Here the IDs are all unique and everything is peachy.  (I 
trimmed off the prefixes with the filenames and line numbers).

 new-id="N50092" persona-generated-id="N2092B" persona-id="friar"
 new-id="N50099" persona-generated-id="N2092B" persona-id="friar"
 new-id="N500A4" persona-generated-id="N2092B" persona-id="friar"
 new-id="N500AF" persona-generated-id="N2092B" persona-id="friar"
 out-object id=N50092
 new-id="N500BD" persona-generated-id="N20937" persona-id="drunk"
 new-id="N500C4" persona-generated-id="N20937" persona-id="drunk"
 new-id="N500CF" persona-generated-id="N20937" persona-id="drunk"
 new-id="N500DA" persona-generated-id="N20937" persona-id="drunk"
 out-object id=N500BD
 new-id="N500E8" persona-generated-id="N20943" persona-id="chambermaid"
 new-id="N500EF" persona-generated-id="N20943" persona-id="chambermaid"
 new-id="N500FA" persona-generated-id="N20943" persona-id="chambermaid"
 new-id="N50105" persona-generated-id="N20943" persona-id="chambermaid"
 out-object id=N500E8


But then, if I change this:

                                        <xsl:choose><xsl:when test="false() and 
string-length(row-id)"><xsl:value-of 
select="concat('row-id',row-id)"/></xsl:when>
                                        <xsl:otherwise><xsl:value-of 
select="generate-id()"/></xsl:otherwise></xsl:choose>
<!--
                                        <xsl:value-of select="generate-id()"/>
-->

To this...

<!--
                                        <xsl:choose><xsl:when test="false() and 
string-length(row-id)"><xsl:value-of 
select="concat('row-id',row-id)"/></xsl:when>
                                        <xsl:otherwise><xsl:value-of 
select="generate-id()"/></xsl:otherwise></xsl:choose>
-->
                                        <xsl:value-of select="generate-id()"/>

which should change nothing, since all that's happened is that the 
<xsl:value-of select="generate-id()"/> in <xsl:otherwise>, which was always 
performed because of the
test="false() and ...", has been replaced by the same thing without the 
<xsl:choose> around it, things change.

 new-id="N50011" persona-generated-id="N2092B" persona-id="friar"
 new-id="N50018" persona-generated-id="N2092B" persona-id="friar"
 new-id="N50023" persona-generated-id="N2092B" persona-id="friar"
 new-id="N5002E" persona-generated-id="N2092B" persona-id="friar"
 out-object id=N50011
 new-id="N50011" persona-generated-id="N20937" persona-id="drunk"
 new-id="N50018" persona-generated-id="N20937" persona-id="drunk"
 new-id="N50023" persona-generated-id="N20937" persona-id="drunk"
 new-id="N5002E" persona-generated-id="N20937" persona-id="drunk"
 out-object id=N50011
 new-id="N50011" persona-generated-id="N20943" persona-id="chambermaid"
 new-id="N50018" persona-generated-id="N20943" persona-id="chambermaid"
 new-id="N50023" persona-generated-id="N20943" persona-id="chambermaid"
 new-id="N5002E" persona-generated-id="N20943" persona-id="chambermaid"
 out-object id=N50011

Now the set of IDs for the elements in the nodeset is the same for all the 
nodesets that will be generated.  

<fo:block-container id="N50011" margin-left="0%" width="90%" height="18pt">
<fo:block-container id="N50018" margin-left="0%" position="absolute" top="0%" 
width="30%" left="0%" text-align="justify" text-align-last="justify">
</fo:block-container>
<fo:block-container id="N50023" margin-left="0%" position="absolute" top="0%" 
width="60%" left="30%">
</fo:block-container>
<fo:block-container id="N5002E" margin-left="0%" position="absolute" top="0%" 
width="10%" left="90%">
</fo:block-container>
</fo:block-container>
<fo:block-container id="N50011" margin-left="0%" width="90%" height="18pt">
<fo:block-container id="N50018" margin-left="0%" position="absolute" top="0%" 
width="30%" left="0%" text-align="justify" text-align-last="justify">
</fo:block-container>
<fo:block-container id="N50023" margin-left="0%" position="absolute" top="0%" 
width="60%" left="30%">
</fo:block-container>
<fo:block-container id="N5002E" margin-left="0%" position="absolute" top="0%" 
width="10%" left="90%">
</fo:block-container>

(and so forth for all of the play:persona objects)

This looks like a bug to me.  Can anyone find a different explanation?


-- 
"Under no circumstances will I ever purchase anything offered to me as the 
result of an unsolicited e-mail message. Nor will I forward chain letters, 
petitions, mass mailings, or virus warnings to large numbers of others. This is 
my contribution to the survival of the online community."

The Boulder Pledge,
 created by Roger Ebert



Reply via email to