Question

I need to modify the attribute of an XML node, based on an attribute of it's parent node.

I think it will be better explained with an example...so:

I have an xml file that looks like this:

<pdf2xml>
    <page number="1">
        <text top="100" />
    </page>
    <page number="2">
        <text top="100" />
        <text top="50" />
    </page>
</pdf2xml>

I'm currently ordering the text nodes by 'top' within each page with the following xslt template:

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

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

    <xsl:template match="pdf2xml/page">
        <xsl:copy>
            <xsl:for-each select=".">
                <xsl:apply-templates select="*">
                    <xsl:sort select="@top" data-type="number"/>
                </xsl:apply-templates>
            </xsl:for-each>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

However, I later refer to all the text nodes as one long array using getElementsByTagName("text") - so what I want to do, is make text@[top] = 5000 * page@[number] - so that all my text top attributes are sequential through the whole document.

I've had a look at this question, which helped, but I'm struggling to see how (if I even can?) apply that to a dynamic value, dependent on the position in the hierachy of each text node.

Was it helpful?

Solution

I'd use a template matching the top attribute itself, from where you can refer to the page number as ../../@number (in XPath an attribute node is not a child of its containing element, but the containing element is the parent of the attribute node):

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

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

    <xsl:template match="pdf2xml/page">
        <xsl:copy>
            <xsl:apply-templates select="@*" />
            <xsl:apply-templates select="*">
                <xsl:sort select="@top" data-type="number"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="text/@top">
        <xsl:attribute name="top">
            <xsl:value-of select=". + (../../@number * 5000)" />
        </xsl:attribute>
    </xsl:template>
</xsl:stylesheet>

You might prefer to make things more explicit by using ancestor::page[1]/@number instead of ../../@number to make it clearer to future readers of the code that it's the nearest containing page you're trying to reach.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top