Creating and maintaining display models with hundreds of layers is labor intensive. It is almost a mandatory requirement to be able to do tricks like bulk editing of the XML layer files to do global changes. Things like altering the scale of symbols for plot display models or changing color lookup formulas can take a long time without automation.
For a lot of these operations a powerful editor suffices. For example one can use the Visual Studio Replace in Files function using regular expressions. However, search and replace for patterns spanning multiple lines is difficult, the replacement text is fixed and operations aren't easily scripted to allow them to be repeated often. This is where the ability to use XSLT transforms can come in handy. It isn't a replacement for working with the user interface or power editing, but it's another arrow in your quiver of tools that can open up alternate possibilities.
XSLT is a way of transforming one file into another using a template-driven match and replace process. The XSLT language is abundantly described on the web. You might like to start with the W3C XSLT Version 1.0 specification. For our purposes, since we need not change the layer file too much, our transforms can be relatively minor. Let's start with an identity transform like:
<?xml version="1.0" encoding="ISO-8859-15"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
which will match everything using the pattern @*|node() and copies all tags and attributes from input to output with the xsl:copy instruction and a recursive xsl:apply-templates instruction. With suitable programming the transform can be applied to multiple files, for example all layer files in a directory.
The Transformer program, available below, is a command line program that transforms XML files. It takes several parameters. The mandatory parameters are the name of the XSLT file to use and the file (or file pattern) to operate on. For example, to transform all layer files in the MyDataModel directory using the identity.xslt file (containing the above content), you could use:
transformer identity.xslt MyDataModel\*.layer
A third optional argument can specify an output file or directory, otherwise it replaces the existing files with the transformed ones.
Even an identity transformation is useful, because we can pretty print (format) the output XML at the same time. The indenting of XML to match the nesting level of the tags is easily accomplished with a fragment of code that initializes an XmlWriterSettings
object and sets the Indent
property to true
in the Transformer program:
settings = new XmlWriterSettings ();
settings.OmitXmlDeclaration = false;
settings.Indent = true;
settings.Encoding = Encoding.UTF8;
Further down in the program, creation of the transform is done once outside of the loop over all files. This same compiled transform is used repeatedly to process each file.
transform = new XslCompiledTransform ();
transform.Load (xslt_filename);
The actual processing step is relatively simple.
input = new XmlTextReader (new StreamReader (source_file));
source_doc = new XPathDocument (input);
destination_doc = XmlTextWriter.Create (target_file, settings);
transform.Transform (source_doc, arguments, destination_doc);
destination_doc.Close ();
input.Close ();
Using XSLT transforms we can, for example, strip out the provider parameter information from the layer file. This would be used to anonymize the layer files in your display model before shipping them - after all, who needs to know the Topobase system user name and Oracle SID for the development machine. The transform could look like this:
<?xml version="1.0" encoding="ISO-8859-15"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<!-- empty out the feature source parameters -->
<xsl:template match="FeatureSource/Parameter/Value">
<Value/>
</xsl:template>
</xsl:stylesheet>
which adds one more template (rule) that matches a Value nested in a Parameter element which is itself nested in a FeatureSource element, and replaces the Value element with an empty one. The effect of this transform is shown on the right as the output of a diff program (WinMerge). The same result could be obtained with edit and replace, but it would take three operations.
It's also possible to pass arguments to the transform that can be used as parameters in the process. For example, if you wanted to change all ScaleX and ScaleY values such that they are multiplied by a factor, the factor could be a parameter passed into the transform so you could try various scale increases or decreases quite easily. The following transform does this processing:
<?xml version="1.0" encoding="ISO-8859-15"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<!-- use a parameter named factor, 1.0 by default-->
<xsl:param name="factor">1.0</xsl:param>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<!-- multiply scale X and Y values by a factor -->
<xsl:template match="SymbolInstance/ScaleX|SymbolInstance/ScaleY">
<xsl:copy>
<xsl:choose>
<xsl:when test="number(child::text())">
<xsl:value-of select="string (number(child::text()) * number ($factor))"/>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="@*|node()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This template matches all ScaleX and ScaleY elements that have a SymbolInstance parent element. It also performs a xsl:copy operation, but first tests if the value is a number, and if it is, it will multiply the value by the factor. If the value isn't a number, for example it's a formula, the value is copied as is. You will need to scale these manually, sorry.
To use this transform, the command line must specify an output file or directory and the factor as the fourth parameter:
transformer scale.xslt MyDataModel\*.layer MyDataModel factor=2.0
The output of the program is shown to the right. Contrary to a global search and replace with a text editor, you'll see this XSLT transform is smart enough to change different scale values by the same factor, rather than make them all the same. The equivalent operation with a text editor would take two search and replace operations, and over multiple files with various scale values could become quite tedious.
The final example shows how to strip out unused calculated properties. The FeatureSource element has all calculated properties ever created, and if these are not used any more they can be tough to remove because every layer has a copy. The following transform only copies calculated properties used by the layer:
<?xml version="1.0" encoding="ISO-8859-15"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<!-- eliminate unused calculated properties -->
<xsl:template match="Extension">
<xsl:variable name="extension_name" select="Name/node()" />
<xsl:if test="//FeatureName/text()=$extension_name">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
This template matches Extension elements, but only copies them if their name can be found as the contents of a FeatureName element somewhere in the XML file - hence only used within the layer being processed. It would be used like so:
transformer removeunusedcalculatedproperties.xslt MyDataModel\*.layer
This has the added benefit of reducing the disk space used by your layer files, but as far as I can tell has no appreciable effect on layer generation times.
These examples and the source code (of course) are available for download below. You can combine these XSLT transforms into one larger file or create your own to handle your own requirements.
AUTODESK DOES NOT GUARANTEE THAT YOU WILL BE ABLE TO SUCCESSFULLY DOWNLOAD OR IMPLEMENT ANY SAMPLE CODE. SAMPLE CODE IS SUBJECT TO CHANGE WITHOUT NOTICE TO YOU.
AUTODESK PROVIDES SAMPLE CODE "AS IS" WITHOUT WARRANTY OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE. IN NO EVENT SHALL AUTODESK OR ITS SUPPLIERS BE LIABLE FOR ANY DAMAGES WHATSOEVER INCLUDING DIRECT, INDIRECT, INCIDENTAL, CONSEQUENTIAL, LOSS
OF DATA, OR LOSS OF BUSINESS PROFITS OR SPECIAL DAMAGES, THAT MAY OCCUR AS A RESULT OF IMPLEMENTING OR USING ANY SAMPLE CODE, EVEN IF AUTODESK OR ITS SUPPLIERS
HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
Comments