Gunther Stuhec
Gunther Stuhec

Reputation: 25

Transform adjacent comments into xs:annotations in XSDs

I have a XSD that has a number of comments, which should be moved into the a xs:annotation/xs:documentation part of the preceding xs:simpleTypes or xs:complexTypes. How can I move these comments using XSLT V1.0 or XSLT V2.0?

Example input XSD:

<?xml version="1.0" encoding="UTF-8"?>
<xs:simpleType name="ligula">
    <xs:restriction base="xs:string"/>
</xs:simpleType>
<xs:simpleType name="ultricies">
    <xs:restriction base="xs:integer"/>
</xs:simpleType>
<!--
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. 
Aenean commodo ligula eget dolor. Aenean massa. Cum 
-->
<xs:simpleType name="neque">
    <xs:restriction base="xs:string"/>
</xs:simpleType>
<xs:simpleType name="tincidunt">
    <xs:restriction base="xs:string"/>
</xs:simpleType>
<!-- ligula, porttitor eu, consequat vitae, eleifend ac, enim.  -->
<!-- Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed.  -->
<!-- Integer tincidunt. Cras dapibus. Vivamus elementum  -->
</xs:schema>

The output XSD should be:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:simpleType name="ligula">
        <xs:restriction base="xs:string"/>
    </xs:simpleType>
    <xs:simpleType name="ultricies">
        <xs:annotation>
            <xs:documentation>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. 
            Aenean commodo ligula eget dolor. Aenean massa. Cum</xs:documentation>
        </xs:annotation>
        <xs:restriction base="xs:integer"/>
    </xs:simpleType>
    <xs:simpleType name="neque">
        <xs:restriction base="xs:string"/>
    </xs:simpleType>
    <xs:simpleType name="tincidunt">
        <xs:annotation>
            <xs:documentation>ligula, porttitor eu, consequat vitae, eleifend ac, enim</xs:documentation>
            <xs:documentation>Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed.</xs:documentation>
            <xs:documentation>Integer tincidunt. Cras dapibus. Vivamus elementum</xs:documentation>
        </xs:annotation>
        <xs:restriction base="xs:string"/>
    </xs:simpleType>
</xs:schema>

Upvotes: 2

Views: 172

Answers (2)

Mathias M&#252;ller
Mathias M&#252;ller

Reputation: 22617

The following stylesheet transforms each comment node into an xs:documentation element. More precisely, I store in a variable the sequence of comment nodes that immediately follow an xs:simpleTypeor xs:complexType element:

<xsl:variable name="com" select="following-sibling::comment() except following-sibling::*[local-name() = 'simpleType' or local-name() = 'complexType'][1]/following-sibling::comment()"/>

The expression looks for all following siblings that are comment nodes and subtracts all comment nodes that are a following sibling of the immediately following xs:simpleTypeor xs:complexType element (I know, it sounds complicated).

If this sequence is not empty:

<xsl:if test="$com">

Then, an xs:annotation element is inserted and the string value of each comment node is turned into an xs:documentation element:

<xs:annotation>
  <xsl:for-each select="$com">
     <xs:documentation>
        <xsl:value-of select="normalize-space(.)"/>
     </xs:documentation>
  </xsl:for-each>
</xs:annotation>

Other details:

  • If element nodes are matched by a template, it is necessary to first add all attributes to the result tree. After a child element has been added to the result tree, no more attribute nodes can be added.
  • There is another template to match comment nodes - and do nothing, since they are all processed inside another template.

Stylesheet

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xsl:output method="xml" encoding="UTF-8" indent="yes" />
    <xsl:strip-space elements="*"/>    

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

    <xsl:template match="xs:simpleType|xs:complexType">
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
            <xsl:variable name="com" select="following-sibling::comment() except following-sibling::*[local-name() = 'simpleType' or local-name() = 'complexType'][1]/following-sibling::comment()"/>
            <xsl:if test="$com">
                <xs:annotation>
                    <xsl:for-each select="$com">
                        <xs:documentation>
                            <xsl:value-of select="normalize-space(.)"/>
                        </xs:documentation>
                    </xsl:for-each>
                </xs:annotation>
            </xsl:if>

            <xsl:apply-templates select="node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="comment()"/>

</xsl:transform>

XML Output

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
   <xs:simpleType name="ligula">
      <xs:restriction base="xs:string"/>
   </xs:simpleType>
   <xs:simpleType name="ultricies">
      <xs:annotation>
         <xs:documentation>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum</xs:documentation>
      </xs:annotation>
      <xs:restriction base="xs:integer"/>
   </xs:simpleType>
   <xs:simpleType name="neque">
      <xs:restriction base="xs:string"/>
   </xs:simpleType>
   <xs:simpleType name="tincidunt">
      <xs:annotation>
         <xs:documentation>ligula, porttitor eu, consequat vitae, eleifend ac, enim.</xs:documentation>
         <xs:documentation>Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed.</xs:documentation>
         <xs:documentation>Integer tincidunt. Cras dapibus. Vivamus elementum</xs:documentation>
      </xs:annotation>
      <xs:restriction base="xs:string"/>
   </xs:simpleType>
</xs:schema>

Try this solution online here if you'd like to make changes.

Upvotes: 3

Michael Kay
Michael Kay

Reputation: 163322

Try

<xsl:template match="xs:schema">
  <xsl:copy>
    <xsl:for-each-group group-starting-with="xs:simpleType|xs:complexType">
      <xsl:apply-templates select="." mode="include-comments">
        <xsl:with-param name="comments" select="current-group()/self::comment()"/>
      </
    </
  </
</

<xsl:template match="*" mode="include-comments">
  <xsl:param name="comments" as="comment()*"/>
  <xsl:copy>
    <xs:documentation>
      <xsl:for-each select="$comments">
        <xs:annotation><xsl:value-of select="."/>
      </xsl:for-each>
    </
  </
</

Note: on a typical implementation this is likely to have linear performance, whereas Matthias' solution is likely to have quadratic performance.

Upvotes: 2

Related Questions