Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to turn an XML file into SVG using XSL?

Tags:

xml

svg

xslt

Given

<root>
   <item>
      <detail>100</detail>
      <detail>200</detail>
   </item>
   <item>
      <detail>50</detail>
      <detail>100</detail>
   </item>
</root>

How I would I make this data into a simple SVG bar chart? (nothing fancy, just four bars representing the relation between numbers in some fashion)

Something like this: enter image description here (I know there is no separation between the two items, but lets just say I will make them different colors, the first two bars blue the second red)

I guess I am not sure what the syntax inside the xsl:template would be to generate the SVG code? Best answer gets accepted!

like image 392
antonpug Avatar asked Nov 08 '11 20:11

antonpug


People also ask

Can we convert XML to XSL?

Execute an XSLT transformation from an XML file Associate an XSLT style sheet with the XML document. Add an xml-stylesheet processing instruction to the XML document. For example, add the following line to the document prolog: <? xml-stylesheet type='text/xsl' href='filename.

How does XSL work with XML?

XSLT is used to transform XML document from one form to another form. XSLT uses Xpath to perform matching of nodes to perform these transformation . The result of applying XSLT to XML document could be an another XML document, HTML, text or any another document from technology perspective.

Is XSL the Same as XML?

An XSLT stylesheet is an XML document that consists of a combination of XHTML markup, XSLT template rules, and XPath statements that work together. XHTML markup defines the display environment that XML data is presented in.


2 Answers

Here is an example with some more bells & whistles:

  • it scales automatically to the maximum height
  • it uses CSS to style elements
  • it has configurable parameters for width and spacing of the bars
  • it uses more idiomatic XSLT than just a bunch of nested for-each loops

With your input this code:

<xsl:stylesheet
   version="1.0"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
   xmlns="http://www.w3.org/2000/svg"
>
  <xsl:output indent="yes" cdata-section-elements="style" />

  <xsl:param name="width" select="40" /><!-- width of bars -->
  <xsl:param name="space" select="10" /><!-- space between bars and items -->

  <xsl:variable name="max-y" select="//detail[not(//detail &gt; .)][1]" />

  <xsl:template match="root">
    <svg>
      <defs>
        <style type="text/css"><![CDATA[
          g.bar text {
            font-family: Arial; 
            text-anchor: middle;
            fill: white;
          }
          g.bar rect {
            fill: black;
          }
        ]]></style>
      </defs>
      <g transform="translate(10, 10)">
        <xsl:apply-templates select="item" />
      </g>
    </svg>
  </xsl:template>

  <xsl:template match="item">
    <xsl:variable name="prev-item" select="preceding-sibling::item" />
    <g class="item" id="item-{position()}" transform="translate({
      count($prev-item/detail) * ($width + $space)
      + count($prev-item) * $space
    })">
      <xsl:apply-templates select="detail" />
    </g>
  </xsl:template>

  <xsl:template match="detail">
    <xsl:variable name="idx" select="count(preceding-sibling::detail)" />
    <xsl:variable name="pos" select="$idx * ($width + $space)" />
    <g class="bar">
      <rect x="{$pos}" y="{$max-y - .}" height="{.}" width="{$width}" />
      <text x="{$pos + $width div 2.0}" y="{$max-y - $space}">
        <xsl:value-of select="."/>
      </text>
    </g>
  </xsl:template>
</xsl:stylesheet>

produces

<svg xmlns="http://www.w3.org/2000/svg">
  <defs>
    <style type="text/css"><![CDATA[
              g.bar text {
                font-family: Arial; 
                text-anchor: middle;
                fill: white;
              }
              g.bar rect {
                fill: black;
              }
            ]]></style>
  </defs>
  <g transform="translate(10, 10)">
    <g class="item" id="item-1" transform="translate(0)">
      <g class="bar">
        <rect x="0" y="100" height="100" width="40"/>
        <text x="20" y="190">100</text>
      </g>
      <g class="bar">
        <rect x="50" y="0" height="200" width="40"/>
        <text x="70" y="190">200</text>
      </g>
    </g>
    <g class="item" id="item-2" transform="translate(110)">
      <g class="bar">
        <rect x="0" y="150" height="50" width="40"/>
        <text x="20" y="190">50</text>
      </g>
      <g class="bar">
        <rect x="50" y="100" height="100" width="40"/>
        <text x="70" y="190">100</text>
      </g>
    </g>
  </g>
</svg>

which renders like this

SVG rendering result

on my machine.

like image 84
Tomalak Avatar answered Oct 30 '22 17:10

Tomalak


SVG is just a special kind of xml, check the reference at http://www.w3.org/TR/SVG/intro.html

You start with the <svg> tag and start nesting.

XSL is a whole different world. I used a reference example at http://www.carto.net/svg/samples/xslt/#basic and modified it to work with your xml to demonstrate how to use xsl for your nested data. Basically, I just added an inner for-each loop.

Here is an example starting point:

<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="root">
    <svg width="200px" height="500px" xmlns="http://www.w3.org/2000/svg">
      <g id="bar" transform="translate(0,200)">
        <xsl:for-each select="item">
          <xsl:variable name="item_position" select="(position()-1) * 100"/>
          <xsl:for-each select="detail">
            <xsl:variable name="val" select="."/>
            <rect x="{$item_position + position()*40}" y="-{$val}" height="{$val}" width="35" style="fill:{@fill};"/>
            <text x="{$item_position + position()*40 + 15}" y="-{($val div 2.0) - 5}" style="font-family:arial;text-anchor:middle;baseline-shift:-15;fill:white">
              <xsl:value-of select="."/>
            </text>
          </xsl:for-each>
        </xsl:for-each>
      </g>
    </svg>
  </xsl:template>
</xsl:transform>
like image 31
3martini Avatar answered Oct 30 '22 17:10

3martini