Demerge the merged cell in XSLT

2019-06-14 17:42发布

问题:

Can someone help me with the below xslt question.

<?xml version="1.0" encoding="UTF-8"?>
 <catalog>
<cd>
    <title rowmerge="f">Title 1</title>
    <artist rowmerge="f">sample 1</artist>
    <price rowmerge="T">1</price>
    <year >1985</year>
</cd>
<cd>
    <title rowmerge="F">Title 2</title>
    <artist rowmerge="F">Sample 2</artist>
    <price rowmerge="T"></price>
    <year>1988</year>
</cd>
<cd>
    <title rowmerge="F">Title 3</title>
    <artist rowmerge="F">Sample 3</artist>
    <price rowmerge="F">3</price>
    <year>1988</year>
</cd>
<cd>
    <title rowmerge="T">Title 4</title>
    <artist rowmerge="F">sample 4</artist>
    <price rowmerge="T">4</price>
    <year >1985</year>
</cd>
<cd>
    <title rowmerge="T"></title>
    <artist rowmerge="F">Sample 5</artist>
    <price rowmerge="T"></price>
    <year>1988</year>
</cd>
</catalog>

Expected output:

<?xml version="1.0" encoding="UTF-8"?>
<catalog>
<cd>
    <title rowmerge="f">Title 1</title>
    <artist rowmerge="f">sample 1</artist>
    <price rowmerge="f">1</price>
    <year >1985</year>
</cd>
<cd>
    <title rowmerge="F">Title 2</title>
    <artist rowmerge="F">Sample 2</artist>
    <price rowmerge="f">1</price>
    <year>1988</year>
</cd>
<cd>
    <title rowmerge="F">Title 3</title>
    <artist rowmerge="F">Sample 3</artist>
    <price rowmerge="F">3</price>
    <year>1988</year>
</cd>
<cd>
    <title rowmerge="F">Title 4</title>
    <artist rowmerge="F">sample 4</artist>
    <price rowmerge="F">4</price>
    <year >1985</year>
</cd>
<cd>
    <title rowmerge="F">Title 4</title>
    <artist rowmerge="F">Sample 5</artist>
    <price rowmerge="F">4</price>
    <year>1988</year>
</cd>
</catalog>

If rowmerge attribute is 'T' in the first cd for any tag (title/artist/price)then I need to copy the price value from first cd to next cd. I am new to xslt.

回答1:

You first should read up on the XSLT Identity Template, which on its own will copy nodes from the source document to the output.

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

What this means is that you only need to write templates for the nodes you wish to transform. Considering just the price elements for now, you are trying to amend price elements which have a rowmerge set to "T" and which are empty. In this case, you want to copy the price from the first most preceding 'cd'. This is achieved like so:

<xsl:template match="price[@rowmerge='T'][not(normalize-space())]">
    <price>
        <xsl:apply-templates select="@*|node()"/>
        <xsl:value-of select="../preceding-sibling::*[1]/price" />
    </price>
</xsl:template> 

So, it is very similar to the identity template, but it has the extra xsl:value-of statement to copy the value from the preceding node. Or rather the price value from the preceding node of the parent cd element.

Of course, you could repeat this template for each of the possible child elements of cd, but this would be a lot of repetitive coding. Better would be to have a more generic template to cover all cases:

<xsl:template match="*[@rowmerge='T'][not(normalize-space())]">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
        <xsl:value-of select="../preceding-sibling::*[1]/*[name() = name(current())]" />
    </xsl:copy>
</xsl:template>

Try this XSLT

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

    <xsl:template match="*[@rowmerge='T'][not(normalize-space())]">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
            <xsl:value-of select="../preceding-sibling::*[1]/*[name() = name(current())]" />
        </xsl:copy>
    </xsl:template>


    <xsl:template match="@rowmerge[. = 'T']">
        <xsl:attribute name="rowmerge">F</xsl:attribute>
    </xsl:template>

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

Do note there is also a template to convert the rowmerge attributes from having a value of T to F.

EDIT: In answer to your comment, if you have to look more than one sibling back (that is to say, you have two consecutive elements that are empty), then try one of these two expressions

<xsl:value-of select="../preceding-sibling::*[*[name() = name(current())][normalize-space()]][1]/*[name() = name(current())]" />

<xsl:value-of select="(../preceding-sibling::*/*[name() = name(current())][normalize-space()])[last()]" />

EDIT 2: If the nodes contain more than just text, then to copy all the child elements, you use xsl:copy-of instead of xsl:value-of. For example...

<xsl:copy-of select="../preceding-sibling::*[1]/*[name() = name(current())]/node()" />

Note the use of node() on the end, to ensure only the child nodes are copied, not the price (for example) element itself.



标签: xslt