Chapter 11. Vertical XSLT Application Recipes

A newcomer wonders if there’s a secret handshake or code required to delve into its riddles . . . such as knowing what to bring to a potluck.

From a book review of Potluck: Stories That Taste Like Hawaii

This chapter differs from the others because the examples represent mini-XSLT applications covering a diverse set of domains (a potluck, if you will). Many examples relate to the use of specific commercial software. As software vendors embrace XML, they provide opportunities for their products to be used in ways they never imagined (or did not get around to implementing).

Microsoft is one vendor that has jumped on the XML bandwagon. The latest versions of Microsoft Visio (Version 10.0) and Excel (Office XP Version 10.0) both support XML output. Visio is a proprietary vector drawing package, and Visio’s XML output (called Visio VDX) is also Visio-specific. John Breen has done an admirable job converting this output to Scalable Vector Graphics (SVG). His code is featured in Recipe 11.1.

Microsoft Excel allows spreadsheets to be saved in XML. Unfortunately, the XML directly models the structure of an Excel spreadsheet. Recipe 11.2 shows how to covert them to a more usable form.

Topic Maps are an up-and-coming XML technology for modeling knowledge in a way that makes information published on the Web more useful to both people and machines. XTM is an open standard for representing topic maps in XML. By analogy, software developers model knowledge about systems by using the Unified Modeling Language (UML). UML has its own standard XML representation known as XML Metadata Interchange (XMI). UML and Topic Maps do not serve the same audience; however, UML is rich enough to capture the concepts addressed by Topic Maps if you follow certain conventions. Since UML has been around longer than Topic Maps, the software tools are more mature. Recipe 11.3 shows how the XMI output of a popular UML authoring tool (Rational Rose) can be converted into XTM Topic Maps.[26]

One of XTM’s most useful features is its ability to generate web sites. Recipe 11.4 addresses this Topic Map application. Nikita Ogievetsky contributed this recipe based on his work on the Cogitative Topic Maps Web Site (CTW) framework

Finally, we clean up the chapter with some SOAP, the W3C’s XML format for implementing web services. The Simple Object Access Protocol is a way for software systems to communicate via standardized XML messages. This section addresses a SOAP-related draft specification called Web Service Definition Language (WSDL). As its name implies, WSDL is an XML specification for documenting a SOAP service. This discussion shows how to convert WSDL into human-readable documentation.

The examples in this chapter are long, but you can find the full source code at http://www.oreilly.com/catalog/xsltckbk/.

Converting Visio VDX Documents to SVG

Problem

You want to convert Microsoft Visio XML files (VDX) into more portable SVG files.

Solution

John Breen implemented the following solution. He maps the major Visio elements to SVG as shown in Table 11-1.

Table 11-1. Visio-to-SVG Mappings

Visio element

SVG element

VisioDocument/Colors/ColorEntry

color value

VisioDocument/Stylesheets/StyleSheet

CSS Style

VisioDocument/Pages/Page

Svg

Page/Shapes/Shape

G

Shapes/Shape/@NameU

@id

Shapes/Shape/XForm

transform

XForm/PinX and XForm/PinX

translate( )

Shapes/Shape/Fill/FillForegnd

@fill (with lookup)

Shapes/Shape/Geom

Path

Shape/Geom/MoveTo

@d "M"

Shape/Geom/LineTo

@d "L"

Shape/Geom/NoFill(0)

@d "z"

This section goes over only the main stylesheet and select portions of the included ones. The entire source code with examples is available at http://sourceforge.net/projects/vdxtosvg/:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:v="urn:schemas-microsoft-com:office:visio"
  xmlns:xlink="http://www.w3.org/1999/xlink"
  xmlns:math="java.lang.Math"
  xmlns:jDouble="java.lang.Double"
  xmlns:saxon="http://icl.com/saxon"
  exclude-result-prefixes="v math saxon jDouble"
  xmlns="http://www.w3.org/2000/svg"
  version="1.0">
   
  <xsl:output method="xml"
    version="1.0"
    omit-xml-declaration="no"
    media-type="image/svg+xml"
    encoding="iso-8859-1"
    indent="yes"
    cdata-section-elements="style"
    doctype-public="-//W3C//DTD SVG 1.0//EN"
    doctype-system="http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"
    />

The stylesheet uses a parameter pageNumber for specifying what page should be extracted from the VDX, and the parameter userScale specifies by which amount to scale Visio units to user units:

  <xsl:param name="pageNumber" select="1"/>
  <xsl:param name="userScale"  select="100"/>
   
  <!-- =  =  =  =  Variables (ie, Constants) =  =  =  =  =  =  =  =  =  =  =  =  =  = -->
  <!-- Color map -->
  <xsl:variable name="Colors"
                select="//v:Colors[position(  )=1]/v:ColorEntry"/>
   
  <!-- Page being processed -->
  <xsl:variable name="Page"
            select="/v:VisioDocument/v:Pages/v:Page[number($pageNumber)]"/>
   
  <!-- Template Masters -->
  <xsl:variable name="Masters"
                select="//v:Masters[position(  )=1]/v:Master"/>
   
  <!-- viewBox Master -->
  <xsl:variable name="viewBoxMaster"
                select="$Masters[@NameU='viewBox']"/>
   
  <!-- Ratio of font height to width (fudge factor) -->
  <xsl:variable name="fontRatio"
                select="2"/>
   
  <!-- Pi (SVG uses degrees, Visio uses radians) -->
  <xsl:variable name="pi" select="3.14159265358979323846264338327"/>

The stylesheet is decomposed into several components that are included here. Portions of these modules are discussed later in this section. The stylesheet implements some extensions in JavaScript; however, if your XSLT processor does not support JavaScript, you can still use this code. Some text might not format nicely, however:

  <!-- Included files -->
  <xsl:include href="visio-style.xsl"/>
  <xsl:include href="visio-text.xsl"/>
  <xsl:include href="visio-masters.xsl"/>
  <xsl:include href="visio-nurbs.xsl"/>
   
   <!-- Scripts -->
  <xsl:template name="required-scripts">
    <script xlink:href="wordwrap.js" type="text/ecmascript"/>
  </xsl:template>
   
  <xsl:template match="/v:VisioDocument">
    <xsl:apply-templates
      select="$Page"/>
  </xsl:template>

A Visio page is mapped onto an SVG graphic. Information from the Visio document determines how best to lay out the graphic in a view box:

  <!-- =  =  =  =  =  =  =  = Page =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =-->
  <xsl:template match="v:Page">
    <xsl:message>
      <xsl:value-of select="@NameU"/>
    </xsl:message>
    <svg id="{@NameU}">
      <xsl:attribute name="xml:space">
        <xsl:value-of select="'preserve'"/>
      </xsl:attribute>
      <xsl:choose>
        <!-- Use viewBox with name 'default' if present -->
        <xsl:when test="//v:Shape[@Master=$viewBoxMaster/@ID
                        and @NameU='default'][1]">
          <xsl:for-each
            select="//v:Shape[@Master=$viewBoxMaster/@ID
                    and @NameU='default']">
            <xsl:attribute name="viewBox">
              <xsl:value-of select="concat(
                                    v:XForm/v:PinX*$userScale, ' ',
                                    -v:XForm/v:PinY*$userScale, ' ',
                                    v:XForm/v:Width*$userScale, ' ',
                                    v:XForm/v:Height*$userScale)"/>
            </xsl:attribute>
          </xsl:for-each>
        </xsl:when>
        <!-- Otherwise, center on sheet -->
        <xsl:otherwise>
          <xsl:attribute name="viewBox">
            <xsl:value-of select="concat('0 ', 
                                  -v:PageSheet/v:PageProps/v:PageHeight
                                    *$userScale, ' ', 
                                  v:PageSheet/v:PageProps/v:PageWidth
                                    *$userScale, ' ',
                                  v:PageSheet/v:PageProps/v:PageHeight
                                    *$userScale)"/>
          </xsl:attribute>
        </xsl:otherwise>
      </xsl:choose>
      <xsl:call-template name="required-scripts"/>
      <xsl:call-template name="predefined-pattern-fgnds"/>
      <xsl:call-template name="predefined-markers"/>

The real meat of the conversion begins here. Start by processing Visio stylesheet elements to convert them into equivalent Cascading Style Sheet directives. Then convert all shapes into their SVG representation:

      <xsl:apply-templates select="../../v:StyleSheets"/>
      <xsl:apply-templates select="v:Shapes/v:Shape"/>
    </svg>
  </xsl:template>
   
  <!-- =  =  =  =  =  =  =  = StyleSheets =  =  =  =  =  =  =  =  = -->
  <xsl:template match="v:StyleSheets">
    <defs>
      <xsl:for-each select="v:StyleSheet">
        <!-- Line style -->
        <style id="ss-line-{@ID}" type="text/css">
          <xsl:text>*.ss-line-</xsl:text><xsl:value-of select="@ID"/>
          <xsl:text> { </xsl:text>
          <xsl:call-template name="recursive-line-style">
            <xsl:with-param name="ss" select="."/>
          </xsl:call-template>
          <xsl:text> }</xsl:text>
        </style>
        <!-- Fill style -->
        <style id="ss-fill-{@ID}" type="text/css">
          <xsl:text>*.ss-fill-</xsl:text><xsl:value-of select="@ID"/>
          <xsl:text> { </xsl:text>
          <xsl:call-template name="recursive-fill-style">
            <xsl:with-param name="ss" select="."/>
          </xsl:call-template>
          <xsl:text> }</xsl:text>
        </style>
        <!-- Text style -->
        <style id="ss-text-{@ID}" type="text/css">
          <xsl:text>*.ss-text-</xsl:text><xsl:value-of select="@ID"/>
          <xsl:text> { </xsl:text>
          <xsl:call-template name="recursive-text-style">
            <xsl:with-param name="ss" select="."/>
          </xsl:call-template>
          <xsl:text> } </xsl:text>
        </style>
      </xsl:for-each>
    </defs>
  </xsl:template>
   
  <!-- Recurse through StyleSheet inheritance -->
  <xsl:template name="recursive-line-style">
    <xsl:param name="ss"/>
    <xsl:if test="$ss/@LineStyle">
      <xsl:call-template name="recursive-line-style">
        <xsl:with-param name="ss"
          select="$ss/../v:StyleSheet[@ID=$ss/@LineStyle]"/>
      </xsl:call-template>
    </xsl:if>
    <xsl:apply-templates select="$ss/v:Line" mode="style"/>
  </xsl:template>
   
  <xsl:template name="recursive-fill-style">
    <xsl:param name="ss"/>
    <xsl:if test="$ss/@FillStyle">
      <xsl:call-template name="recursive-fill-style">
        <xsl:with-param name="ss"
          select="$ss/../v:StyleSheet[@ID=$ss/@FillStyle]"/>
      </xsl:call-template>
    </xsl:if>
    <xsl:apply-templates select="$ss/v:Fill" mode="style"/>
  </xsl:template>
   
  <xsl:template name="recursive-text-style">
    <xsl:param name="ss"/>
    <xsl:if test="$ss/@TextStyle">
      <xsl:call-template name="recursive-text-style">
        <xsl:with-param name="ss"
          select="$ss/../v:StyleSheet[@ID=$ss/@TextStyle]"/>
      </xsl:call-template>
    </xsl:if>
    <xsl:apply-templates select="$ss/v:Char|$ss/v:Para" mode="style"/>
  </xsl:template>
   
  <!-- This template returns a string for the line style -->
  <xsl:template match="v:Line" mode="style">
    <xsl:for-each select="v:LineWeight">
      <xsl:text>stroke-width:</xsl:text>
      <xsl:value-of select=". * $userScale"/><xsl:text>;</xsl:text>
    </xsl:for-each>
    <xsl:for-each select="v:LineColor">
      <xsl:choose>
        <xsl:when test="../v:LinePattern > 0">
          <xsl:text>stroke:</xsl:text>
          <xsl:call-template name="lookup-color">
            <xsl:with-param name="c_el" select="."/>
          </xsl:call-template>
        </xsl:when>
        <xsl:when test="../v:LinePattern = 0">
          <xsl:text>stroke:none</xsl:text>
        </xsl:when>
      </xsl:choose>
      <xsl:text>;</xsl:text>
    </xsl:for-each>
    <xsl:for-each select="v:EndArrow">
      <xsl:choose>
        <xsl:when test=". = 0">
          <xsl:value-of select="string('marker-end:none;')"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="concat('marker-end:url(#EndArrow-', .,
                                '-', ../v:EndArrowSize, '),')"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each>
    <xsl:apply-templates select="v:LinePattern[. &gt; 1]" mode="style"/>
  </xsl:template>
   
  <!-- This template returns a string for the fill style -->
  <xsl:template match="v:Fill" mode="style">
    <xsl:for-each select="v:FillForegnd">
      <xsl:choose>
        <xsl:when test="../v:FillPattern = 1">
          <xsl:text>fill:</xsl:text>
          <xsl:call-template name="lookup-color">
            <xsl:with-param name="c_el" select="."/>
          </xsl:call-template>
        </xsl:when>
        <xsl:when test="../v:FillPattern = 0">
          <xsl:text>fill:none</xsl:text>
        </xsl:when>
        <xsl:otherwise>
          <xsl:text>fill:url(#</xsl:text>
          <xsl:value-of select="generate-id(../..)"/>
          <xsl:text>-pat)</xsl:text>
        </xsl:otherwise>
      </xsl:choose>
      <xsl:text>;</xsl:text>
    </xsl:for-each>
  </xsl:template>
   
  <!-- This template returns a string for the text style -->
  <xsl:template match="v:Char|v:Para" mode="style">
    <xsl:for-each select="v:Color">
      <!-- I don't think Visio handles filled characters -->
      <xsl:text>stroke:none</xsl:text>
      <xsl:text>;fill:</xsl:text>
      <xsl:call-template name="lookup-color">
        <xsl:with-param name="c_el" select="."/>
      </xsl:call-template>
      <xsl:text>;</xsl:text>
    </xsl:for-each>
    <xsl:for-each select="v:Size">
      <xsl:text>font-size:</xsl:text>
      <xsl:value-of select=". * $userScale"/><xsl:text>;</xsl:text>
    </xsl:for-each>
    <xsl:for-each select="v:HorzAlign">
      <xsl:text>text-anchor:</xsl:text>
      <xsl:choose>
        <xsl:when test="(. = 0) or (. = 3)">
          <xsl:text>start</xsl:text>
        </xsl:when>
        <xsl:when test=". = 1">
          <xsl:text>middle</xsl:text>
        </xsl:when>
        <xsl:when test=". = 2">
          <xsl:text>end</xsl:text>
        </xsl:when>
      </xsl:choose>
      <xsl:text>;</xsl:text>
    </xsl:for-each>
  </xsl:template>
   
  <!-- Ignore all other StyleSheet elements -->
  <xsl:template match="*[parent::v:StyleSheet]" priority="-100"/>

Here is where shapes are mapped onto an SVG equivalent. Notice how shapes can be associated with masters in a Visio document. Think of a master as a template of a shape from which a shape on a page can inherit attributes and behavior.

Each Visio shape is, by default , translated as a <g> element since a Visio shape can contain both graphics and text. Recall that the <g> element is SVG’s way of specifying a group of graphical elements that can share stylistic traits.

SvgElement is a Visio property that the user can attach to a Visio shape to specify special handling by this translator. For example, svgElement can be set to any other SVG container element, such as <defs>. This feature keeps certain shapes from being rendered, such as paths for animateMotion elements. This way, the path can be referenced in the SVG file, but it will not be displayed.

One of the main reasons for using svgElement is to indicate special shapes that are translated to elements that have no correspondence in Visio, such as animate, animateMotion, and viewBox. visio-master.xsl, discussed later, handles these elements:

  <!-- =  =  =  =  =  =  = Shape =  =  =  =  =  =  =  =  =  =  =  =  = -->
  <xsl:template match="v:Shape">
   
    <xsl:variable name="master"
                  select="/v:VisioDocument//v:Masters[1]/
                           v:Master[@ID=current(  )/@Master]"/>
   
    <xsl:variable name="svgElement">
      <xsl:choose>
         <!-- Check for special svgElement property in shape ... -->
        <xsl:when test="./v:Prop/v:Label[.='svgElement']">
          <xsl:value-of
            select="./v:Prop/v:Label[.='svgElement']/../v:Value"/>
        </xsl:when>
         <!-- ... and in master -->
        <xsl:when test="@Master and 
                        $master//v:Prop/v:Label[.='svgElement']">
          <xsl:value-of
            select="$master//v:Prop/v:Label[.='svgElement']/../v:Value"/>
        </xsl:when>
   
         <!-- The simple case maps a shape onto a svg (g)roup -->     
        <xsl:otherwise>
          <xsl:value-of select="'g'"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:variable>
   
    <xsl:choose>
   
      <xsl:when test="@Master and string($svgElement)
                      and contains($specialMasters, $svgElement)">
        <xsl:call-template name="choose-special-master">
          <xsl:with-param name="master" select="$master"/>
          <xsl:with-param name="masterElement" select="$svgElement"/>
        </xsl:call-template>
      </xsl:when>
   
      <xsl:when test="($svgElement = 'defs') or ($svgElement = 'g') or
                      ($svgElement = 'symbol')">
        <xsl:choose>
          <xsl:when test="v:Hyperlink">
            <!-- Surround shape with 'a' element -->
            <!-- This is a minimal implementation.  It doesn't support
                 multiple links, subaddress, etc. -->
            <a xlink:title="{v:Hyperlink/v:Description}"
              xlink:href="{v:Hyperlink/v:Address}">
              <xsl:if test="v:Hyperlink/v:NewWindow">
                <xsl:attribute name="show">
                  <xsl:value-of select="new"/>
                </xsl:attribute>
              </xsl:if>
              <xsl:element name="{$svgElement}">
                <xsl:call-template name="userShape"/>
              </xsl:element>
            </a>
          </xsl:when>
          <xsl:otherwise>
            <xsl:element name="{$svgElement}">
              <xsl:call-template name="userShape"/>
            </xsl:element>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:when>
    </xsl:choose>
  </xsl:template>

Here the normal shapes created by the user are mapped to SVG, as specified in Table 11-1:

  <!-- This does the processing for normal 'user' shapes -->
  <xsl:template name="userShape">
    <xsl:variable name="master"
      select="/v:VisioDocument/v:Masters
              /v:Master[(@ID=current(  )/@Master)
                        and (current(  )/@Type != 'Group')]
              /v:Shapes/v:Shape |
              /v:VisioDocument/v:Masters
              /v:Master[@ID=current(  )
              /ancestor::v:Shape[@Master]/@Master]
              //v:Shape[@ID=current(  )/@MasterShape] | ."/>
   
    <xsl:call-template name="setIdAttribute"/>
   
    <xsl:attribute name="class">
      <xsl:for-each select="($master[@LineStyle])[last(  )]">
        <xsl:text> ss-line-</xsl:text>
        <xsl:value-of select="@LineStyle"/>
      </xsl:for-each>
      <xsl:for-each select="($master[@FillStyle])[last(  )]">
        <xsl:text> ss-fill-</xsl:text>
        <xsl:value-of select="@FillStyle"/>
      </xsl:for-each>
    </xsl:attribute>
    <xsl:attribute name="style">
      <xsl:for-each select="$master">
        <xsl:apply-templates select="./v:Line" mode="style"/>
        <xsl:apply-templates select="./v:Fill" mode="style"/>
      </xsl:for-each>
    </xsl:attribute>
    <xsl:for-each select="v:XForm">
      <xsl:call-template name="transformAttribute">
      </xsl:call-template>
    </xsl:for-each>
    <!-- This is to create the custom pattern -->
    <xsl:apply-templates select="v:Fill" mode="Shape"/>
    <xsl:for-each select="v:Geom">
      <xsl:apply-templates select="v:Ellipse"/>
      <xsl:if test="v:MoveTo or v:LineTo">
        <xsl:call-template name="pathElement"/>
      </xsl:if>
    </xsl:for-each>
    <xsl:for-each select="($master/v:Text)[last(  )]">
      <xsl:apply-templates select="."/>
    </xsl:for-each>
   
    <xsl:apply-templates select="v:Shapes/v:Shape"/>
   
    <!-- Add elements from properties -->
    <xsl:for-each select="v:Prop">
      <xsl:choose>
        <xsl:when test="starts-with(v:Label, 'svg-element')">
          <!-- This is sort of ugly - it may disappear some day -->
          <xsl:value-of disable-output-escaping="yes" select="v:Value"/>
        </xsl:when>
      </xsl:choose>
    </xsl:for-each>
  </xsl:template>
   
  <xsl:template match="v:Ellipse">
    <!-- This is a somewhat limited translation.  It assumes that the
         axes are parallel to the x & y axes, and the lower-left corner
         of the bounding box is at the origin (which appears to be the
         way Visio draws them by default). -->
    <ellipse id="ellipse-{generate-id(ancestor::v:Shape[1])}"
      cx="{v:X*$userScale}" cy="{-v:Y*$userScale}"
      rx="{v:X*$userScale}" ry="{v:Y*$userScale}"/>
  </xsl:template>
   
  <!-- =  =  =  =  =  =  =  = Utility templates =  =  =  =  =  =  =  =  =  =  =  =  =  = -->
   
  <!-- Lookup color value in Colors element -->
  <xsl:template name="lookup-color">
    <xsl:param name="c_el"/>
    <xsl:choose>
      <xsl:when test="starts-with($c_el, '#')">
        <xsl:value-of select="$c_el"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$Colors[@IX=string($c_el)]/@RGB"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

If a Visio element has a name, use it as the shape ID; otherwise, use generate-id( ):

  <xsl:template name="setIdAttribute">
    <xsl:attribute name="id">
      <xsl:choose>
        <xsl:when test="@NameU">
          <xsl:value-of select="@NameU"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="generate-id(.)"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:attribute>
  </xsl:template>
   
  <!-- Translate XForm element into transform attribute -->
  <xsl:template name="transformAttribute">
    <xsl:attribute name="transform">
      <xsl:text>translate(</xsl:text>
      <xsl:value-of select="concat((v:PinX - v:LocPinX)*$userScale,
                            ',', -(v:PinY - v:LocPinY)*$userScale)"/>
      <xsl:if test="v:Angle != 0">
        <xsl:text>) rotate(</xsl:text>
        <xsl:value-of select="-v:Angle*180 div $pi"/>
        <xsl:value-of select="concat(',', v:LocPinX*$userScale,
                              ',', -v:LocPinY*$userScale)"/>
      </xsl:if>
      <xsl:text>)</xsl:text>
    </xsl:attribute>
  </xsl:template>

Visio Geom elements are translated to paths. Most of the mapping is straightforward, except for the handling of Non-Uniform Rational B-Splines (NURBS), which is delegated to a special set of templates in visio-nurbs.xsl, which you can peruse in the full distribution:

  <!-- Translate Geom element into path element -->
  <xsl:template name="pathElement">
    <xsl:variable name="pathID">
      <xsl:text>path-</xsl:text>
      <xsl:choose>
        <xsl:when test="ancestor::v:Shape[1]/@NameU">
          <xsl:value-of select="ancestor::v:Shape[1]/@NameU"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="generate-id(ancestor::v:Shape[1])"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:variable>
    <path id="{$pathID}">
      <xsl:attribute name="d">
        <xsl:for-each select="v:*">
          <xsl:choose>
            <xsl:when test="name(  ) = 'MoveTo'">
              <xsl:value-of select="concat('M', v:X*$userScale,
                                    ',', -v:Y*$userScale, ' ')"/>
            </xsl:when>
            <xsl:when test="name(  ) = 'LineTo'">
              <xsl:value-of select="concat('L', v:X*$userScale,
                                    ',', -v:Y*$userScale, ' ')"/>
            </xsl:when>
            <xsl:when test="name(  ) = 'EllipticalArcTo'">
              <!-- If we don't have access to trig functions, the
                   arc will just be represented by two line segments-->
              <xsl:choose>
                <xsl:when test="function-available('math:atan2')">
                  <xsl:call-template name="ellipticalArcPath"/>
                </xsl:when>
                <xsl:otherwise>
                  <xsl:value-of select="concat('L', v:A*$userScale,
                                        ',', -v:B*$userScale,
                                        ' L', v:X*$userScale,
                                        ',', -v:Y*$userScale, ' ')"/>
                </xsl:otherwise>
              </xsl:choose>
            </xsl:when>
            <xsl:when test="(name(  ) = 'NoFill') or (name(  ) = 'NoLine') or
                            (name(  ) = 'NoShow') or (name(  ) = 'NoSnap')">
              <!-- Ignore these -->
            </xsl:when>
            <xsl:when test="name(  ) = 'NURBSTo'">
              <xsl:call-template name="NURBSPath"/>
            </xsl:when>
            <xsl:otherwise>
              <xsl:message>
                <xsl:text>Warning: unsupported path command found:</xsl:text>
                <xsl:value-of select="name(  )"/>
                <xsl:text>; replacing with LineTo</xsl:text>
              </xsl:message>
              <xsl:value-of select="concat('L', v:X*$userScale,
                                    ',', -v:Y*$userScale, ' ')"/>
            </xsl:otherwise>
          </xsl:choose>
        </xsl:for-each>
      </xsl:attribute>
      <xsl:if test="v:NoFill = 1">
        <xsl:attribute name="fill"><xsl:text>none</xsl:text></xsl:attribute>
      </xsl:if>
    </path>
  </xsl:template>
   
  <!-- This template calculates the path string for an elliptical arc -->
   
  <xsl:template name="ellipticalArcPath">
   
  <!-- Figure sweep based on angle from current point -->
  <!-- to (X,Y) and (A,B) -->
   
  <!-- TODO: figure a better way to make sure the preceding
       sibling is a drawing element -->
   
    <xsl:variable name="lastX"
      select="preceding-sibling::*[1]/v:X"/>
    <xsl:variable name="lastY"
      select="preceding-sibling::*[1]/v:Y"/>
    <xsl:variable name="angle"
      select="math:atan2(v:Y - $lastY, v:X - $lastX)
              - math:atan2(v:B - $lastY, v:A - $lastX)"/>
    <xsl:variable name="sweep">
      <xsl:choose>
        <xsl:when test="$angle &gt; 0
                        and math:abs($angle) &lt; 180">
          <xsl:value-of select='0'/>
        </xsl:when>
        <xsl:when test="$angle &lt; 0
                        and math:abs($angle) &gt; 180">
          <xsl:value-of select='0'/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select='1'/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:variable>
    <xsl:value-of select="concat('A',
                   (preceding-sibling::*[1]/v:X - v:X)*$userScale, ',',
                   (preceding-sibling::*[1]/v:Y - v:Y)*$userScale, ' ',
                   v:C,  ' 0,', $sweep, ' ', v:X*$userScale, ',',
                   -v:Y*$userScale, ' ')"/>
  </xsl:template>
   
</xsl:stylesheet>

Discussion

Visio is a powerful vector graphics editor that, in its current version, does not support SVG. Needless to say, SVG is not powerful enough to represent every Visio construct accurately, but it can come close. Simple Visio diagrams can be translated to SVG almost exactly. More complex SVG may need some touch up in a native SVG editor. The release notes that come with the full distribution identify missing features. Figure 11-1 is a sample of the SVG generated from a Visio file and rendered almost flawlessly.

Basic SVG shapes and text converted from Visio

Figure 11-1. Basic SVG shapes and text converted from Visio

The important lesson you should learn from this example goes beyond the details of Visio VDX , SVG, or any special tricks or techniques the author uses. It is simply the fact that this complex transformation was the author’s first experience with XSLT. This says something very positive about the power of XSLT’s transformational paradigm.

See Also

The source code and some demonstrations are available on Source Forge at http://sourceforge.net/projects/vdxtosvg/.



[26] You might call this recipe Acronym Conversion Software (ACS).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset