<!--
     XSLT 1.0 serializer for XML

     remarks:
     - generates output nearly identical to <xsl:copy-of select="/"/>
       - all attributes before namespace declarations
       - attribute values might be different because of AVN 
      
     - since stylesheet does not have access to CDATA sections
       it has to use template escapeLtGtAmp to ensure correct
       escaping; overhead of 1 x call-template + 3 x contains()
       for text output not containing any of &lt; , &gt; and &amp;

     - because of "Attribute-Value Normalization" no newlines in 
       attribute values; this might change visual presentation
       as can be seen in first <xsl:when>'s test attribute

     - entity references like &#10; and &quot; in the XML file are not
       accessible by the stylesheet and are displayed as non-Entity


          serialize.xsl: XML serializer

     serialize-demo.xml: demonstration file (open in browser)
     serialize-demo.xsl: referenced demonstration

            copy-of.xsl: for comparison with serialize-test.xsl output
     serialize-test.xsl: for comparison with copy-of.xsl output;
                         view output in browser for comparing
-->
<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
  <xsl:template name="doOutput">
    <xsl:choose>
      <xsl:when test="count(. | ../namespace::*) !=
                      count(../namespace::*)">
        <xsl:apply-templates select="." mode="output"/>
      </xsl:when>

      <xsl:otherwise>
        <xsl:value-of select= "concat('xmlns',
                                       substring(':',1 div boolean(name())),
                                       name(),'=&quot;',.,'&quot;')" />
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:template match="@*" mode="output">
    <xsl:value-of select="concat(' ',name(),'=&quot;',.,'&quot;')"/>
  </xsl:template>

  <xsl:template match="node()" mode="output">
    <!-- for xsl:copy-of behavior of xsltproc;
         Saxon, xalan and DataPower XSLT processors do not do this.
    <xsl:if test="(.=/) and 
                  (preceding::comment()|preceding::processing-instruction())">
      <xsl:text>&#10;</xsl:text>
    </xsl:if>
    -->

    <xsl:value-of select="concat('&lt;',name())"/>

    <xsl:apply-templates select="@*" mode="output"/>

    <xsl:for-each select="namespace::*">
      <xsl:if test="not(.=../../namespace::*) and name()!='xml'">
        <xsl:value-of select= "concat(' xmlns',
                                       substring(':',1 div boolean(name())),
                                       name(),'=&quot;',.,'&quot;')" />
      </xsl:if>
    </xsl:for-each>
  
    <xsl:choose>
      <xsl:when test="*|text()|comment()|processing-instruction()">
        <xsl:text>></xsl:text>

        <xsl:apply-templates 
          select="*|text()|comment()|processing-instruction()" mode="output"/>
  
        <xsl:value-of select="concat('&lt;/',name(),'>')"/>
      </xsl:when>

      <xsl:otherwise>
        <xsl:text>/></xsl:text>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:template match="comment()" mode="output">
    <xsl:value-of select="concat('&lt;!--',.,'-->')"/>
  </xsl:template>

  <xsl:template match="processing-instruction()" mode="output">
    <xsl:value-of select="concat('&lt;?',name(),' ',.,'?>')"/>
  </xsl:template>

  <xsl:template match="text()" mode="output">
    <!-- 
         overhead: 1 x call-template + 3 x contains() for text without CDATA
    -->
    <xsl:call-template name="escapeLtGtAmp">
      <xsl:with-param name="str" select="."/>
    </xsl:call-template>
  </xsl:template>


  <xsl:template name="escapeLtGtAmp">
    <xsl:param name="str"/>

    <xsl:choose>
      <xsl:when test="contains($str,'&lt;') or 
                      contains($str,'&gt;') or
                      contains($str,'&amp;')">
        <xsl:variable name="lt" 
          select="substring-before(concat($str,'&lt;'),'&lt;')"/>
        <xsl:variable name="gt" 
          select="substring-before(concat($str,'&gt;'),'&gt;')"/>
        <xsl:variable name="amp" 
          select="substring-before(concat($str,'&amp;'),'&amp;')"/>

        <xsl:choose>
          <xsl:when test="string-length($gt) > string-length($amp)">
            <xsl:choose>
              <xsl:when test="string-length($amp) > string-length($lt)">
                <xsl:value-of 
                  select="concat(substring-before($str,'&lt;'),'&amp;lt;')"/>
    
                <xsl:call-template name="escapeLtGtAmp">
                  <xsl:with-param name="str" 
                    select="substring-after($str,'&lt;')"/>
                </xsl:call-template>
              </xsl:when>
    
              <xsl:otherwise>
                <xsl:value-of 
                  select="concat(substring-before($str,'&amp;'),'&amp;amp;')"/>
    
                <xsl:call-template name="escapeLtGtAmp">
                  <xsl:with-param name="str" 
                    select="substring-after($str,'&amp;')"/>
                </xsl:call-template>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:when>

          <xsl:otherwise>
            <xsl:choose>
              <xsl:when test="string-length($gt) > string-length($lt)">
                <xsl:value-of 
                  select="concat(substring-before($str,'&lt;'),'&amp;lt;')"/>
    
                <xsl:call-template name="escapeLtGtAmp">
                  <xsl:with-param name="str" 
                    select="substring-after($str,'&lt;')"/>
                </xsl:call-template>
              </xsl:when>
    
              <xsl:otherwise>
                <xsl:value-of 
                  select="concat(substring-before($str,'&gt;'),'&amp;gt;')"/>
    
                <xsl:call-template name="escapeLtGtAmp">
                  <xsl:with-param name="str" 
                    select="substring-after($str,'&gt;')"/>
                </xsl:call-template>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:when>

      <xsl:otherwise>
        <xsl:value-of select="$str"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

