MilestoneXSLT

Summary
This is a group of XSLT scripts for managing milestones (page breaks, line breaks, hand shifts, etc.) in XSLT 2.0.

These scripts originated from a poster presented at DH2008 by Elena Pierazzo and Raffaele Viglianti: XSLT (2.0) handbook for processing multiple hierarchies. An older version presented after the more efficient new version can also work with XSLT 1.0 Three different approaches are presented, ordered by complexity of the input and of the script as well.

Required Input
Script 1: expanding milestones

 Give me then your house &amp; Grounds  I ask for nothing else 

Script 2: splitting elements, then expanding milestones

 Card room where  nine out of ten had no inclination 

Script 3: looping on textual nodes

   For James Edward Austen  Jane Austen May 6 th 1792 

Expected Output
Script 1

the function would produce the following XML fragment as output:  Give me then your house &amp; Grounds</w></lb> <lb>I ask for nothing else</lb> </TEI>

Script 2

<TEI xmlns="http://www.tei-c.org/ns/1.0"> <lb> Card room where </lb><lb> nine out of ten had no inclination </lb> </TEI>

Script 3

<html xmlns="http://www.w3.org/1999/xhtml"> blvolthird-03 For James Edward Austen Jane Austen May 6 th 1792

Code (XSLT 2.0 only)
Script 1

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" xpath-default-namespace="http://www.tei-c.org/ns/1.0" > <xsl:output method="xml" indent="yes" encoding="UTF-8" exclude-result-prefixes="#all"/> <xsl:template match="node|@*"> <xsl:copy> <xsl:apply-templates select="node|@*"/> </xsl:copy> </xsl:template> <xsl:template match="p"> <p xmlns="http://www.tei-c.org/ns/1.0"> <xsl:for-each-group select="node" group-starting-with="lb"> <lb> <xsl:sequence select="current/ancestor::p[1]/lb/@*"/> <xsl:apply-templates select="current-group[not(self::lb)]"/> </lb> </xsl:for-each-group> </xsl:template> </xsl:stylesheet>

Script 2 <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" xpath-default-namespace="http://www.tei-c.org/ns/1.0"> <xsl:output method="xml" indent="yes" encoding="UTF-8" exclude-result-prefixes="#all"/> <xsl:strip-space elements="*"/>

<xsl:template name="start"> <xsl:apply-templates select="TEI" mode="step1"/> </xsl:template>

<xsl:variable name="step1"> <xsl:call-template name="start"/> </xsl:variable>

<xsl:variable name="step2"> <xsl:apply-templates select="$step1" mode="step2"/> </xsl:variable>

<xsl:template match="/"> <xsl:sequence select="$step2"/> </xsl:template>

<xsl:template match="@*|node" mode="step1"> <xsl:copy> <xsl:apply-templates select="@*|node" mode="step1"/> </xsl:copy> </xsl:template>

<xsl:template match="del" mode="step1"> <xsl:choose> <xsl:when test="lb"> <xsl:for-each-group select="node" group-starting-with="lb"> <xsl:sequence select="current-group/descendant-or-self::lb"/> <del xmlns="http://www.tei-c.org/ns/1.0"> <xsl:sequence select="current/ancestor::matched-element/@*"/> <xsl:sequence select="current-group[not(self::lb)]"/> </xsl:for-each-group> </xsl:when> <xsl:otherwise> <xsl:sequence select="."/> </xsl:otherwise> </xsl:choose> </xsl:template>

<xsl:template match="@*|node" mode="step2"> <xsl:copy> <xsl:apply-templates select="@*|node" mode="step2"/> </xsl:copy> </xsl:template>

<xsl:template match="p" mode="step2"> <xsl:variable name="cur-p" select="generate-id(.)"/> <p xmlns="http://www.tei-c.org/ns/1.0"> <xsl:choose> <xsl:when test="descendant::lb"> <xsl:for-each-group select="node" group-starting-with="lb"> <lb> <xsl:sequence select="current/ancestor::p[1]/lb/@*"/> <xsl:apply-templates select="current-group[not(self::lb)]" mode="step2"/> </lb> </xsl:for-each-group> </xsl:when> <xsl:otherwise> <xsl:apply-templates mode="step2"/> </xsl:otherwise> </xsl:choose> </xsl:template>

<xsl:template match="del" mode="step2"> <del xmlns="http://www.tei-c.org/ns/1.0"> <xsl:apply-templates mode="step2"/> </xsl:template>

</xsl:stylesheet>

Script 3 <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" xpath-default-namespace="http://www.tei-c.org/ns/1.0"> <xsl:output method="xhtml" indent="yes" encoding="UTF-8" doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"/> <xsl:strip-space elements="*"/>

<xsl:template name="start"> <xsl:apply-templates select="TEI" mode="step1"/> </xsl:template>

<xsl:variable name="step1"> <xsl:call-template name="start"/> </xsl:variable>

<xsl:variable name="step2"> <xsl:apply-templates select="$step1" mode="step2"/> </xsl:variable>

<xsl:template match="/"> <xsl:sequence select="$step2"/> </xsl:template>

<xsl:template match="@*|node" mode="step1"> <xsl:copy> <xsl:apply-templates select="@*|node" mode="step1"/> </xsl:copy> </xsl:template>

<xsl:template match="text[not(ancestor::teiHeader)]" mode="step1"> <handShift xmlns="http://www.tei-c.org/ns/1.0"> <xsl:sequence select="preceding::handShift[1]/@new"/> <xsl:value-of select="."/> </handShift> </xsl:template>

<xsl:template match="handShift" mode="step1"/>

<xsl:template match="/" mode="step2"> <html xmlns="http://www.w3.org/1999/xhtml"> <xsl:apply-templates mode="step2"/> </xsl:template>

<xsl:template match="text//p" mode="step2"> <xsl:variable name="cur-p" select="generate-id(.)"/> <p xmlns="http://www.w3.org/1999/xhtml"> <xsl:choose> <xsl:when test="descendant::lb"> <xsl:for-each-group select="node" group-starting-with="lb"> <lb> <xsl:sequence select="current/ancestor::p[1]/lb/@*"/> <xsl:apply-templates select="current-group[not(self::lb)]" mode="step2"/> </lb> </xsl:for-each-group> </xsl:when> <xsl:otherwise> <xsl:apply-templates mode="step2"/> </xsl:otherwise> </xsl:choose> </xsl:template>

<xsl:template match="handShift" mode="step2"> <span xmlns="http://www.w3.org/1999/xhtml"> <xsl:attribute name="class"> <xsl:choose> <xsl:when test="@new='#a2'"> <xsl:text>default</xsl:text> </xsl:when> <xsl:when test="@new='#a1'"> <xsl:text>pencil</xsl:text> </xsl:when> </xsl:choose> </xsl:attribute> <xsl:apply-templates mode="step2"/> </xsl:template>

</xsl:stylesheet>

XSLT 1.0
The code shown below avoids using xsl:for-each-group, which is exclusive of XSLT 2.0. It is still presented in XSLT 2.0 for brevity, as it allows to keep all steps in one script. With XSLT 1 it is possible to emulate each step by using more than one script and by producing intermediate files.

Using xsl:for-each-group and XSLT 2.0 is considerably faster and more efficient and, therefore, recommended. Script 1 (compatible with XSLT 1.0)

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" xpath-default-namespace="http://www.tei-c.org/ns/1.0" xmlns:tei="http://www.tei-c.org/ns/1.0"> <xsl:output method="xml" indent="yes" encoding="UTF-8”/>   <xsl:template match="node|@*">        <xsl:copy>            <xsl:apply-templates select="node|@*"/>        </xsl:copy>    </xsl:template>    <xsl:template match="p">        <xsl:variable name="cur-p" select="generate-id(.)"/>        <xsl:choose>            <xsl:when test="lb">                <xsl:for-each select="lb">                    <lb>                        <xsl:sequence select="parent::p/@*"/>

<xsl:apply-templates select="preceding::*[parent::p[generate-id=$cur-p]]|preceding::text[parent::p[generate-id=$cur-p]]"/> <xsl:apply-templates select="preceding::*/@*[parent::p[generate-id=$cur-p]]"/> </lb> <lb> <xsl:sequence select="parent::p/@*"/> <xsl:sequence select="following::*[parent::p[generate-id=$cur-p]]|following::text[parent::p[generate-id=$cur-p]]"/> <xsl:sequence select="following::*/@*[parent::p[generate-id=$cur-p]]"/> </lb> </xsl:for-each> </xsl:when> <xsl:otherwise> <xsl:sequence select="."/> </xsl:otherwise> </xsl:choose> </xsl:template> </xsl:stylesheet>

Script 2 (Can be split into two XSLT 1.0 scripts) <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" xpath-default-namespace="http://www.tei-c.org/ns/1.0" xmlns="http://www.tei-c.org/ns/1.0"> <xsl:output method="xhtml" indent="yes" encoding="UTF-8" doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"/> <xsl:strip-space elements="*"/>

<xsl:variable name="step1"> <xsl:call-template name="one"/> </xsl:variable>

<xsl:variable name="step2"> <xsl:apply-templates select="$step1" mode="step2"/> </xsl:variable>

<xsl:template match="*" mode="step1"> <xsl:copy> <xsl:sequence select="@*"/> <xsl:apply-templates mode="step1"/> </xsl:copy> </xsl:template>

<xsl:template match="del" mode="step1"> <xsl:variable name="cur-del" select="generate-id(.)"/> <xsl:choose> <xsl:when test="lb"> <xsl:for-each select="lb"> <xsl:choose> <xsl:when test="parent::del/@type"> <xsl:variable name="type" select="generate-id(parent::del/@type)"/> <xsl:sequence select="parent::del/@*[generate-id != $type]"/> </xsl:when> <xsl:otherwise> <xsl:sequence select="parent::del/@*"/> </xsl:otherwise> </xsl:choose> <xsl:apply-templates select="preceding::*[parent::del[generate-id=$cur-del]]|preceding::text[parent::del[generate-id=$cur-del]]"/> <xsl:apply-templates select="preceding::*/@*[parent::del[generate-id=$cur-del]]"/>

<lb> <xsl:sequence select="@*"/> </lb>

<xsl:sequence select="parent::del/@*"/> <xsl:sequence select="following::*[parent::del[generate-id=$cur-del]]|following::text[parent::del[generate-id=$cur-del]]"/> <xsl:sequence select="following::*/@*[parent::del[generate-id=$cur-del]]"/> </xsl:for-each> </xsl:when> <xsl:otherwise> <xsl:sequence select="."/> </xsl:otherwise> </xsl:choose>

</xsl:template>

<xsl:template name="one"> <xsl:apply-templates select="TEI" mode="step1"/> </xsl:template>

<xsl:template match="/" mode="step2"> <xsl:apply-templates mode="step2"/> </xsl:template>

<xsl:template match="p" mode="step2"> <xsl:variable name="cur-p" select="generate-id(.)"/> <xsl:choose> <xsl:when test="exists(descendant::lb)"> <xsl:for-each select="descendant::lb"> <xsl:variable name="cur-lb" select="generate-id(.)"/> <xsl:for-each select="preceding::node[ancestor::p[generate-id=$cur-p]]">

<xsl:choose> <xsl:when test="not(self::lb)"> <xsl:if test="generate-id(following::lb[1])=$cur-lb and generate-id(parent::node[1])=$cur-p"> <xsl:apply-templates select="." mode="step2"/> </xsl:if> </xsl:when> <xsl:otherwise> <xsl:apply-templates select="." mode="step2"/> </xsl:otherwise> </xsl:choose> </xsl:for-each> <xsl:if test="not(following::lb[parent::p[generate-id=$cur-p]])"> <xsl:for-each select="following::*[ancestor::p[generate-id=$cur-p]]"> <xsl:apply-templates select="." mode="step2"/> </xsl:for-each> </xsl:if> </xsl:for-each> </xsl:when> <xsl:otherwise> <xsl:apply-templates mode="step2"/> </xsl:otherwise> </xsl:choose> </xsl:template>

<xsl:template match="del" mode="step2"> <xsl:apply-templates mode="step2"/> </xsl:template>

<xsl:template match="/"> <xsl:sequence select="$step2"/> </xsl:template>

</xsl:stylesheet>

Script 3 (Can be split into three XLST 1.0 scripts) <?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" xpath-default-namespace="http://www.tei-c.org/ns/1.0" xmlns="http://www.tei-c.org/ns/1.0"> <xsl:output method="xhtml" indent="yes" encoding="UTF-8" doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"/> <xsl:strip-space elements="*"/>

<xsl:variable name="step1"> <xsl:call-template name="one"/> </xsl:variable>

<xsl:variable name="step2"> <xsl:apply-templates select="$step1" mode="step2"/> </xsl:variable>

<xsl:template match="*" mode="step1"> <xsl:copy> <xsl:sequence select="@*"/> <xsl:apply-templates mode="step1"/> </xsl:copy> </xsl:template>

<xsl:template match="text[not(ancestor::teiHeader)]" mode="step1"> <handShift> <xsl:sequence select="preceding::handShift[1]/@new"/> <xsl:value-of select="."/> </handShift> </xsl:template>

<xsl:template match="handShift" mode="step1"/>

<xsl:template name="one"> <xsl:apply-templates select="TEI" mode="step1"/> </xsl:template>

<xsl:template match="/" mode="step2"> <xsl:apply-templates mode="step2"/> </xsl:template>

<xsl:template match="p" mode="step2"> <xsl:variable name="cur-p" select="generate-id(.)"/> <xsl:choose> <xsl:when test="exists(descendant::lb)"> <xsl:for-each select="descendant::lb"> <xsl:variable name="cur-lb" select="generate-id(.)"/> <xsl:for-each select="preceding::node[ancestor::p[generate-id=$cur-p]]"> <xsl:choose> <xsl:when test="not(self::lb)"> <xsl:if test="generate-id(following::lb[1])=$cur-lb and generate-id(parent::node[1])=$cur-p"> <xsl:apply-templates select="." mode="step2"/> </xsl:if> </xsl:when> <xsl:otherwise> <xsl:apply-templates select="." mode="step2"/> </xsl:otherwise> </xsl:choose> </xsl:for-each>

<xsl:if test="not(following::lb[parent::p[generate-id=$cur-p]])"> <xsl:for-each select="following::*[ancestor::p[generate-id=$cur-p]]"> <xsl:apply-templates select="." mode="step2"/> </xsl:for-each> </xsl:if>

</xsl:for-each> </xsl:when> <xsl:otherwise> <xsl:apply-templates mode="step2"/> </xsl:otherwise> </xsl:choose> </xsl:template>

<xsl:template match="handShift" mode="step2"> <xsl:attribute name="class"> <xsl:choose> <xsl:when test="@new= '#a2'"> <xsl:text>default</xsl:text> </xsl:when> <xsl:when test="@new='#a1'"> <xsl:text>pencil</xsl:text> </xsl:when> </xsl:choose> </xsl:attribute> <xsl:apply-templates mode="step2"/> </xsl:template>

<xsl:template match="/"> <xsl:sequence select="$step2"/> </xsl:template>

</xsl:stylesheet>