Home > Mobile >  Use tags from input as enclosing tags in XSLT XML output
Use tags from input as enclosing tags in XSLT XML output

Time:12-23

I have some XML (over which I have no control) which has some "rdf" annotations included. I am trying to convert this into actual usable RDF/XML (using XSLT), but there is an issue I don't quite know how to resolve. First, in the XML there are long sections like this:

<rdf:Description>
  <some:tag>
    <rdf:Bag>
      <rdf:li rdf:resource="resource1" />
    </rdf:Bag>
  </some:tag>
  <some:other-tag>
    <rdf:Bag>
      <rdf:li rdf:resource="resource2" />
      <rdf:li rdf:resource="resource3" />
    </rdf:Bag>
  </some:other-tag>
</rdf:Description>

the goal is to create RDF/XML like this:

<rdf:Description>
  <some:tag>resource1</some:tag>
  <some:other-tag>resource2<some:other-tag>
  <some:other-tag>resource3<some:other-tag>
</rdf:Description>

The number of different tags however is potentially unbounded, so I can't go and enumerate different cases here. I need some generic way to use the tag surrounding the bag as enclosing tags for the individual resource attributes.

Unfortunately, I really don't know much XSLT, so I'm at a loss here.

What I've done so far unfortunately does not work for the case of some:other-tag, because I only get one triple, where the objects are concatenated: <some:other-tag>resource2resource3</some:other-tag>

  <xsl:template match="rdf:Description/*">
    <xsl:copy>
      <xsl:for-each select="rdf:Bag/rdf:li/@rdf:resource">
        <xsl:value-of select="."/>
      </xsl:for-each>
    </xsl:copy>
  </xsl:template>

CodePudding user response:

I would start with the identity transformation template (e.g. in XSLT 3 simply by declaring <xsl:mode on-no-match="shallow-copy"/>) and then add two templates

  <xsl:template match="*[rdf:Bag]| rdf:Bag">
    <xsl:apply-templates/>
  </xsl:template>
  
  <xsl:template match="rdf:Bag/rdf:*">
    <xsl:element name="{name(../..)}" namespace="{namespace-uri(../..)}">{@rdf:resource}</xsl:element>
  </xsl:template>

That should give e.g.

<rdf:Description xmlns:rdf="http://example.com/rdf" xmlns:some="http://example.com/some">
   <some:tag>resource1</some:tag>
   <some:other-tag>resource2</some:other-tag>
   <some:other-tag>resource3</some:other-tag>
</rdf:Description>

for an input like

<rdf:Description xmlns:rdf="http://example.com/rdf" xmlns:some="http://example.com/some">
  <some:tag>
    <rdf:Bag>
      <rdf:li rdf:resource="resource1" />
    </rdf:Bag>
  </some:tag>
  <some:other-tag>
    <rdf:Bag>
      <rdf:li rdf:resource="resource2" />
      <rdf:li rdf:resource="resource3" />
    </rdf:Bag>
  </some:other-tag>
</rdf:Description>

You haven't really shown the namespaces nor told us whether there can be other elements than the rdf:Bar and if so, how they would need to be transformed.

CodePudding user response:

Okay, I figured out a way writing this as a recursive procedure. I'd still be happy if someone came along and told me there's a cleaner, more idiomatic version, but, on the off-chance this could help someone, here you go:


  <xsl:template name="bonkers">
    <xsl:param name="rs" required="yes"/>
    <xsl:copy copy-namespaces="no">
      <xsl:value-of select="subsequence($rs, 1, 1)"/>
    </xsl:copy>
    <xsl:if test="false() = empty($rs)">
      <xsl:call-template name="bonkers">
        <xsl:with-param name="rs" select="subsequence($rs, 2)"/>
      </xsl:call-template>
    </xsl:if>
  </xsl:template>

  <xsl:template match="rdf:Description/*">
    <xsl:variable name="resources" select="rdf:Bag/rdf:li/@rdf:resource"/>
    <xsl:call-template name="bonkers">
      <xsl:with-param name="rs" select="$resources"/>
    </xsl:call-template>
  </xsl:template>
  • Related