Altreus
Altreus

Reputation: 6239

Convert XML tags to attributes in arbitrary locations with XSLT

We have an XML format that is actually just HTML with some extra gubbins. It defines which HTML elements are editable, and which attributes are editable thereof.

Here's an example

<img src="images/placeholder.jpg" 
     alt="Placeholder" 
     width="600" 
     height="250" 
     border="0">
  <var attr="src" />
  <var attr="height" ok="150-300" />
</img>

The XML is so the templates are easier to write but I need to convert it to valid(ish) HTML. To do this I want to collapse it into:

<img src="images/placeholder.jpg" 
     alt="Placeholder" 
     width="600" 
     height="250" 
     border="0"
     editable="src height" 
     constraints="height:150-300">

I can do this for <img>, but the problem is that the <var> tags can actually appear as a child of any element in the page. In all cases the algorithm for converting it to attributes is the same but what I can't work out how to do is specify an XSLT template that can match the parent element of a <var> tag. I tried match="var/.." but this turned out to be invalid.

The alternative would be to match the <var>s and add them as attributes to their parents, but I believe that at this point in the processing that particular ship would already have sailed.

Is this doable?

Thankies

Answer!

I combined the more complete answer with Dimitre's usefully accurate answer to form the following:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:template match="*[var]">
        <xsl:copy >
            <xsl:attribute name="editable">
                <xsl:for-each select="var[@attr]">
                    <xsl:value-of
                        select="concat(@attr,
                            substring(' ',
                                1 div (position()!=last())))"/>
                </xsl:for-each>
            </xsl:attribute>
            <xsl:attribute name="constraints">
                <xsl:for-each select="var[@ok]">
                    <xsl:value-of
                        select="concat(@attr,':',@ok,
                            substring(';',
                                1 div (position()!=last())))"/>
                </xsl:for-each>
            </xsl:attribute>
            <xsl:apply-templates select="@*"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="var">
    </xsl:template>

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

Unfortunately the exact answer Alejandro gave didn't work straight away (still not sure why),but this combination of his answer and Dmitri's answer seems to have done the job nicely :)

Upvotes: 2

Views: 885

Answers (3)

user357812
user357812

Reputation:

Besides Dimitre's exact answer to your question, other approach would be:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|var"/>
            <xsl:apply-templates select="node()[not(self::var)]"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="var"/>
    <xsl:template match="var[1]">
        <xsl:attribute name="editable">
            <xsl:for-each select="../var/@attr">
                <xsl:value-of
                     select="concat(.,
                                    substring(' ',
                                              1 div (position()!=last())))"/>
            </xsl:for-each>
        </xsl:attribute>
        <xsl:attribute name="constraints">
            <xsl:for-each select="../var/@ok">
                <xsl:value-of
                     select="concat(../@attr,':',.,
                                    substring(';',
                                              1 div (position()!=last())))"/>
            </xsl:for-each>
        </xsl:attribute>
    </xsl:template>
</xsl:stylesheet>

Output:

<img src="images/placeholder.jpg" 
     alt="Placeholder" 
     width="600" 
     height="250" 
     border="0" 
     editable="src height" 
     constraints="height:150-300"></img>

Upvotes: 1

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243549

what I can't work out how to do is specify an XSLT template that can match the parent element of a <var> tag

Use:

<xsl:template match="*[var]">

Upvotes: 4

Aidanapword
Aidanapword

Reputation: 298

I would consider building a local variable (<xsl:variable>) as the parser gets to the <img> tag and construct a NEW <img ...> explicitly within that variable, walking through any <var> children it finds (<xsl:foreach ...> anyone?) close the variable when they are exhausted ... then just write the variable out?

Upvotes: 1

Related Questions