跨浏览器xml漂亮打印替代身份变换

时间:2017-07-19 15:27:59

标签: xml typescript cross-browser

在开发Single Page Application时,我正在生成一些我希望能够在浏览器中阅读的XML。

在开发过程中,我通常在任何最新版本的Safari中工作,这一切都很有效,但是当我想向某人展示一些进展,并且他们使用的是Firefox时,生成的XML都在一条线上。 / p>

我根据我的研究选择了身份转换为漂亮的打印,因为它似乎是我问题的最干净的解决方案,但现在它似乎不适用于基于Gecko的浏览器。

Firefox and Safari side by side

我在Typescript中使用的代码是:

private prettifyXml(unformattedDocument:XMLDocument) : XMLDocument {

    let identityTransformSheet = '\<' +
        'xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">\
            <xsl:output omit-xml-declaration="yes" indent="yes"/>\
            <xsl:template match="node()|@*">\
                <xsl:copy>\
                    <xsl:apply-templates select="node()|@*"/>\
                </xsl:copy>\
            </xsl:template>\
        </xsl:stylesheet>';

    let parser = new DOMParser();
    let processor = new XSLTProcessor();
    processor.importStylesheet(parser.parseFromString(identityTransformSheet, 'text/xml'));
    let result = processor.transformToDocument(unformattedDocument);
    return result;
}

使用它:

    // `xml` holds a document created by DOMParser and filled with a serialized representation of the model.

    let result = (new XMLSerializer()).serializeToString(this.prettifyXml(xml));
    this.xmlEquivalent(result);
    return result;

这在Safari中运行良好,但似乎在Firefox中无效。有没有办法让这个功能跨浏览器,或者有更好的方法可以做到这一点吗?

(我希望在不添加其他库的情况下提供符合标准的解决方案)

1 个答案:

答案 0 :(得分:1)

当您要求替代方案时,您与Dimitre Novatchev提出的讨论使用身份转换相关联的帖子也有一条评论建议使用XPath可视化工具表而不是身份转换。因此,为了证明这是可能的,我对XSLT和CSS以及Javascript做了一些调整,以允许它在另一个HTML文档中使用,而不是创建一个单独的,完整的HTML文档,结果是https://martin-honnen.github.io/js/2017/pretty-print/pretty-print-test1.html,它确实适用于我在Windows 10上使用当前版本的Edge,Chrome和Firefox。我没有Safari测试。它肯定不会像IE一样工作,因为我在Mozilla中首次引入的Javascript中仅使用了XSLTProcessor API,现在除了IE之外的任何东西都支持它;如果您需要IE支持,那么您应该能够通过使用特定于IE的代码来运行XSLT转换。

以下是HTML的代码:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Testing a proof of concept of pretty-printing of an XML DOM tree as a HTML collapsible table structure</title>
        <link rel="stylesheet" type="text/css" href="pretty-print1.css"/>
        <script>
            var prettyPrinter = new XSLTProcessor();
            (function() {
            var req = new XMLHttpRequest();
            req.open('GET', 'pretty-print1.xsl');
            req.onload = function() {
              prettyPrinter.importStylesheet(req.responseXML);
            };
            req.send();
            }())

            function prettyPrint(doc) {
               return prettyPrinter.transformToFragment(doc, document);
            }

            function prettyPrintCollapseExpandHandler(event) {
  try {
    var thisNode = event.target;
    var par = event.target.parentNode;
    if (thisNode.nodeName == 'TD' && thisNode.className == 'expander') {
      if (par.parentNode.className == 'expander-closed') {
        par.parentNode.className = '';
        thisNode.textContent = '-';
      }
      else {
        par.parentNode.className = 'expander-closed';
        thisNode.textContent = '+';
      }
    }
  } catch (e) {
  }
}
        </script>
        <script>
            document.addEventListener('DOMContentLoaded',
              function() {
                var req = new XMLHttpRequest();
                req.open('GET', 'input1.xml');
                req.onload = function() {
                  document.getElementById('result').appendChild(prettyPrint(req.responseXML));
                };
                req.send();
             },
             false
           );
        </script>
    </head>
    <body>
        <section>
            <h1>Testing a proof of concept of pretty-printing of an XML DOM tree as a HTML collapsible table structure</h1>
            <section id="result">
                <h2>Example result</h2>
            </section>
        </section>
    </body>
</html>

XSLT

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">

<!-- The following is not used because of a bug in Mozilla :( -->
<!--
  <xsl:key name="kattPref" match="@*" 
   use="concat(generate-id(..), '|', substring-before(., ':'))"/>
-->  
  <xsl:output method="html"/>  
  <xsl:template match="/">                

      <div class="pretty-print" onclick="prettyPrintCollapseExpandHandler(event);">        
        <xsl:apply-templates/>      
      </div>    

  </xsl:template>  

  <xsl:template match="*">        
    <div class="indent">      
      <span class="markup">&lt;</span>      

      <xsl:variable name="class" select="'elemname'"/>


      <span class="{$class}">        
        <xsl:value-of select="name(.)"/>      
      </span>

      <xsl:call-template name="findNamespace"/>

      <xsl:apply-templates select="@*"/>      
      <span class="markup">/></span>    
    </div>  
  </xsl:template>  

  <xsl:template match="*[text()]">    

    <xsl:variable name="class" select="'elemname'"/>   

    <div class="indent">      
      <span class="markup">&lt;</span>      
      <span class="{$class}">        
        <xsl:value-of select="name(.)"/>      
      </span>

      <xsl:call-template name="findNamespace"/>

      <xsl:apply-templates select="@*"/>      
      <span class="markup">></span>      
      <!--<span class="text">        
        <xsl:value-of select="."/>      -->
        <xsl:apply-templates/>
      <!--</span>-->      
      <span class="markup">&lt;/</span>      
      <span class="elemname">        
        <xsl:value-of select="name(.)"/>      
      </span>      
      <span class="markup">></span>    
    </div>  
  </xsl:template>  

  <xsl:template match="*[* or processing-instruction() or comment() 
                         or string-length(text()) > 50]" priority="10">    

    <xsl:variable name="class" select="'elemname'"/>    

    <table>      
      <tr>        
        <td class="expander">
          -
          <div/>        
        </td>        
        <td>          
          <span class="markup">&lt;</span>          
          <span class="{$class}">            
            <xsl:value-of select="name(.)"/>          
          </span>          
          <xsl:call-template name="findNamespace"/>
          <xsl:apply-templates select="@*"/>          
          <span class="markup">></span>          
          <div class="expander-content">            
            <xsl:apply-templates/>          
          </div>          
          <span class="markup">&lt;/</span>          
          <span class="elemname">            
            <xsl:value-of select="name(.)"/>          
          </span>          
          <span class="markup">></span>        
        </td>      
      </tr>    
    </table>  
  </xsl:template>  
  <xsl:template match="@*">        

    <xsl:variable name="vPos" select="position()"/>

    <xsl:variable name="vPref" select="substring-before(name(), ':')"/>

    <xsl:if test="$vPref 
               and 
                  not(../@*[position() &lt; $vPos]
                           [substring-before(name(), ':') 
                           = $vPref]
                      )">
      <xsl:call-template name="findNamespace"/>
    </xsl:if>

<!-- The following is not used because of a bug in Mozilla :( -->

<!--
    <xsl:if test=
    "generate-id() 
    = 
     generate-id(key('kattPref', 
                      concat(generate-id(..), '|', substring-before(., ':'))
                     )[1]
                )">
      <xsl:call-template name="findNamespace"/>
    </xsl:if>
-->
    <xsl:variable name="class" select="'attrname'"/>

    <xsl:variable name="class2" select="'markup'"/>

    <xsl:variable name="class3" select="'attrvalue'"/>

    <xsl:text> </xsl:text>    
    <span class="{$class}">      
      <xsl:value-of select="name(.)"/>    
    </span>    
    <span class="{$class2}">="</span>    
    <span class="{$class3}">      
      <!-- <xsl:value-of select="."/> -->    
      <xsl:call-template name="replaceAmpersands">
        <xsl:with-param name="vString" select="string(.)"/>
      </xsl:call-template>
    </span>    
    <span class="{$class2}">"</span>  
  </xsl:template>  

  <xsl:template match="text()">    

    <xsl:variable name="class" select="'text'"/>


    <span class="{$class}">        
      <!-- <xsl:value-of select="."/>       -->
      <xsl:call-template name="replaceAmpersands">
        <xsl:with-param name="vString" select="string(.)"/>
      </xsl:call-template>
    </span>    
  </xsl:template>  

  <xsl:template match="processing-instruction()">    

    <xsl:variable name="class" select="'indent pi'"/>

    <div class="{$class}">

      &lt;?
      <xsl:value-of select="name(.)"/>      
      <xsl:text> </xsl:text>      
      <xsl:value-of select="."/>
?>

    </div>  
  </xsl:template>  

  <xsl:template match="processing-instruction()[string-length(.) > 50]">    

    <xsl:variable name="class" select="'pi'"/>

    <xsl:variable name="class2" select="'indent expander-content'"/>

    <table>      
      <tr>        
        <td class="expander">
          -          
          <div/>        
        </td>        
        <td class="{$class}">

          &lt;?
          <xsl:value-of select="name(.)"/>          
          <div class="{$class2}">            
            <xsl:value-of select="."/>          
          </div>          
          <xsl:text>?></xsl:text>        
        </td>      
      </tr>    
    </table>  
  </xsl:template>  

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

    <xsl:variable name="class" select="'comment indent'"/>

    <div class="{$class}">      
      &lt;!--
      <xsl:value-of select="."/>
      -->    
    </div>  
  </xsl:template>  

  <xsl:template match="comment()[string-length(.) > 50]">    

    <xsl:variable name="class" select="'comment'"/>

    <xsl:variable name="class2" select="'indent expander-content'"/>

    <table>      
      <tr>        
        <td class="expander">
          -          
          <div/>        
        </td>        
        <td class="{$class}">          
          &lt;!--            
          <div class="{$class2}">              
            <xsl:value-of select="."/>            
          </div>          
          -->        
        </td>      
      </tr>    
    </table>  
  </xsl:template>

  <xsl:template name="findNamespace">

    <xsl:variable name="vName" select="substring-before(name(), ':')"/>
    <xsl:variable name="vUri" select="namespace-uri(.)"/>

    <xsl:variable name="vAncestNamespace">
      <xsl:call-template name="findAncNamespace">
        <xsl:with-param name="pName" select="$vName"/>
        <xsl:with-param name="pUri" select="$vUri"/>
      </xsl:call-template>
    </xsl:variable>

    <xsl:if test="not(number($vAncestNamespace))">
      <xsl:if test="namespace-uri()
                  or
                    not(generate-id() 
                       = 
                        generate-id(../@*[name() 
                                         = 
                                          name(current())]
                                    )
                        )">
        <xsl:if test="parent::* or namespace-uri() or contains(name(), ':')">
          <xsl:text> </xsl:text>    
          <span class="namespace">      
            <xsl:value-of select="'xmlns'"/>
            <xsl:if test="contains(name(), ':')">
              <xsl:value-of select="concat(':', $vName)"/>
            </xsl:if>
          </span>    
          <span class="markup">="</span>    
          <span class="namespace">      
            <xsl:value-of select="namespace-uri()"/>    
          </span>    
          <span class="markup">"</span> 
        </xsl:if> 
      </xsl:if>
    </xsl:if>
  </xsl:template>

  <xsl:template name="findAncNamespace">
    <xsl:param name="pNode" select="."/>
    <xsl:param name="pName" select="substring-before(name(), ':')"/>
    <xsl:param name="pUri" select="namespace-uri(.)"/>

     <xsl:choose>
      <xsl:when test="not($pNode/parent::*) 
                     and not($pName) and not($pUri)">1</xsl:when>
      <xsl:when test="not($pNode/parent::*)">0</xsl:when>
      <xsl:otherwise>
        <xsl:variable name="vSamePrefs" 
        select="number($pName
                      = substring-before(name($pNode/..), ':')
                      )"/>

        <xsl:variable name="vSameUris" 
         select="number($pUri  = namespace-uri($pNode/..))"/>

        <xsl:choose>
          <xsl:when test="$vSamePrefs and not($vSameUris)">0</xsl:when>
          <xsl:when test="not($vSamePrefs)">
            <xsl:call-template name="findAncNamespace">
              <xsl:with-param name="pNode" select="$pNode/.."/>
              <xsl:with-param name="pName" select="$pName"/>
              <xsl:with-param name="pUri" select="$pUri"/>
            </xsl:call-template>
          </xsl:when>
           <xsl:otherwise>1</xsl:otherwise>
        </xsl:choose>
      </xsl:otherwise>
    </xsl:choose>

  </xsl:template>

  <xsl:template name="replaceAmpersands">
    <xsl:param name="vString"/>

   <xsl:variable name="vAmp">&amp;</xsl:variable>

   <xsl:choose>
   <xsl:when test="contains($vString, $vAmp)">
     <xsl:value-of select="substring-before($vString, $vAmp)"/>
     <xsl:value-of select="concat($vAmp, 'amp;')"/>
     <xsl:call-template name="replaceAmpersands">
       <xsl:with-param name="vString" 
       select="substring-after($vString, $vAmp)"/>
     </xsl:call-template>
   </xsl:when>
   <xsl:otherwise>
     <xsl:value-of select="$vString"/>
   </xsl:otherwise>
   </xsl:choose>

  </xsl:template>
</xsl:stylesheet>

https://martin-honnen.github.io/js/2017/pretty-print/pretty-print1.css获取CSS,它无论如何都意味着作为概念的证明,而不是抛光的完成代码。

这些天我宁愿建议一个像https://github.com/pgfearo/xmlspectrum这样可以在浏览器中使用Saxon-CE或Saxon-JS的解决方案。