Newer
Older
XML / extract_svg_layers.xsl
<?xml version="1.0" encoding="utf-8"?>

<!--
	Extract named layers of an SVG file into an new SVG file. This is particularly useful for images that have a base layer of objects with various different overlays. The entire image can be created as one file with each overlay on a separate layer, then only the required overlay layers plus the base layer are output.
	
	The base layer is assumed to be named "Base Layer" (case-insensitive).
	
	Arguments
		$base-layer: The case-insensitive name of the base layer, which will always be included. Omit if there is no base layer.
		
		$layers: A list of case-insensitive layer names. You can use any character as a separator, as long as it doesn't appear in any of the layer names. Comma would be usual. Pass "*" to include all layers (this obviously rules out "*" as an layer name!).
		
		$items: A list of case-insensitive SVG item IDs to extract from the base layer. ONLY these items are extracted from the base layer; everything else in the base layer is ignored. Pass "*" to include all items (this obviously rules out "*" as an item ID!).
-->
	
<xsl:stylesheet
	version="2.0"
	xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
	xmlns:svg="http://www.w3.org/2000/svg"
	xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">


	<xsl:output method="xml" encoding="UTF-8" standalone="no" />


	<!--
		The case-insensitive name of the base layer. Everything in the base layer will be included in the output, unless the $items parameter is set (see below).
	-->
	<xsl:param name="base-layer" />
	
	<!--
		The names of the layers to extract. Normally comma-separated, but any separator should work, as long as it doesn't appear in any of the layer names. Watch out for blanks in layer names when passing this from the command line! Note that the base layer is ALWAYS included in the output. Pass "*" to include all layers (default).
	-->
	<xsl:param name="layers">*</xsl:param>
	
	<!--
		A list of the *base layer* item IDs to extract. ONLY items that appear in this list will be extracted. Pass "*" to include all items (default).
	-->
	<xsl:param name="items">*</xsl:param>
	

	<!-- Identity transformation. -->
	<xsl:template match="@*|node()">
		<!-- Copy the current node -->
		<xsl:copy>
			<!-- Including any attributes it has and any child nodes -->
			<xsl:apply-templates select="@*|node()" />
		</xsl:copy>
	</xsl:template>
	
	
	<!--
		Always output the base layer.
		
		XPath explanation
			@inkscape:groupmode = 'layer'
				This is a layer.
			lower-case( @inkscape:label ) = lower-case( $base-layer )
				This is the base layer.
	-->
	<xsl:template name="output-base-layer"
		match="svg:g[ ( @inkscape:groupmode = 'layer' ) and
					  ( lower-case( @inkscape:label ) = lower-case( $base-layer ) )
					]" priority="10">
		<!-- Copy the current node -->
		<xsl:copy>
			<!-- Including any attributes it has and any child nodes -->
			<xsl:apply-templates select="@*|node()" />
		</xsl:copy>
	</xsl:template>
	
	
	<!--
		Ignore base layer items not included in the items list. The basic list of item types appears to be svg:rect, svg:text, svg:path (anything other than a rect or text) and svg:g (group). We only consider "top-level" elements within the layer, otherwise the number of potential element IDs could explode massively. This could will be tricky if we ever decide that we want to omit an item that appears inside a group.
		
		XPath explanation
			@inkscape:groupmode = 'layer'
				See output-base-layer template above.
			( lower-case( @inkscape:label ) = lower-case( $base-layer ) ) or ( $layers = '*' )
				Either this is the base layer, or we're outputting all layers anyway.
			
			.../element()[...]
				All immediate child elements of the base layer.
			
			contains( 'svg:g|svg:path|svg:rect|svg:text', string( node-name ( . ) ) )
				The current child element is one those listed.
			not( contains( lower-case( $items ), lower-case( @id ) ) )
				The current child node's ID isn't in the items list.
			$items != '*'
				We're not outputting all items.
	-->
	<xsl:template name="ignore-items"
		match="svg:g[ ( @inkscape:groupmode = 'layer' ) and
					  ( ( lower-case( @inkscape:label ) = lower-case( $base-layer ) ) or
					    ( $layers = '*' )
					  )
					]/element()[ contains( 'svg:g|svg:path|svg:rect|svg:text', string( node-name ( . ) ) ) and
					             not( contains( lower-case( $items ), lower-case( @id ) ) ) and
					             ( $items != '*' )
					           ]" />
	
	
	<!--
		Ignore layers that aren't in the list of layers.
		
		XPath explanation
			@inkscape:groupmode = 'layer'
				See output-base-layer template above.
			$layers != '*'
				We're not outputting all layers
			not( contains ( $layers, @inkscape:label ) )
				The layer's label isn't in the list of layers.
	-->
	<xsl:template name="ignore-layers"
		match="svg:g[ ( @inkscape:groupmode = 'layer' ) and
					  ( $layers != '*') and
					  not( contains ( lower-case( $layers ), lower-case( @inkscape:label ) ) )
					]" />
	
	
	<!--
		Ensure that extracted layers are always visible, regardless of their setting in the original file.
		
		XPath explanation
			@inkscape:groupmode = 'layer'
				See output-base-layer template above.
			@style[. = 'display:none']
				This layer is invisible.
	-->
	<xsl:template name="make-layers-visible" match="svg:g[@inkscape:groupmode = 'layer']/@style[. = 'display:none']">
		<xsl:attribute name="style">
			<xsl:text>display:inline</xsl:text>
		</xsl:attribute>
	</xsl:template>
	
	
</xsl:stylesheet>