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="',$new-id,'" persona-generated-id="',@persona-generated-id,'" persona-id="',@persona-id,'"')"/> </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