【问题标题】:How to preserve whitespace indentation of text enclosed in HTML <pre> tags excluding the current indentation level of the <pre> tag in the document?如何保留 HTML <pre> 标签中包含的文本的空格缩进,不包括文档中 <pre> 标签的当前缩进级别?
【发布时间】:2011-01-08 00:48:56
【问题描述】:

我正在尝试在网站上显示我的代码,但我在正确保留空白缩进时遇到问题。

例如给定以下sn-p:

<html>
 <body>
   Here is my code:
   <pre>
     def some_funtion
       return 'Hello, World!'
     end
   </pre>
 <body>
</html>

这在浏览器中显示为:

Here is my code:

     def some_funtion
       return 'Hello, World!'
     end

当我希望它显示为:

Here is my code:

def some_funtion
 return 'Hello, World!'
end

不同之处在于 HTML pre 标记的当前缩进级别被添加到代码的缩进中。我使用 nanoc 作为静态网站生成器,并且使用 google prettify 来添加语法高亮。

任何人都可以提供任何建议吗?

【问题讨论】:

标签: html whitespace indentation pre


【解决方案1】:

PRE 旨在保留空白,与它看起来完全一样(除非在 CSS 中被 white-space 更改,它没有足够的灵活性来支持格式化代码)。

之前

格式被保留,但PRE 标记之外的所有缩进也是如此。保留以标签位置为起点的空白保留会很好。

之后

内容仍然按照声明的格式进行格式化,但是由于PRE 标记在文档中的位置导致的多余的前导空格被删除。

我想出了以下插件来解决由于文档大纲缩进而想要删除多余空格的问题。此代码使用 PRE 标记内的第一行来确定它缩进了多少纯粹是由于文档的缩进。

此代码适用于 IE7、IE8、IE9、Firefox 和 Chrome。我已经使用Prettify 库对其进行了简要测试,以将保留的格式与漂亮的打印相结合。确保PRE 中的第一行实际上代表了您想要忽略的缩进基线级别(或者,您可以修改插件以使其更智能)。

这是粗略的代码。如果您发现错误或无法按您想要的方式工作,请修复/评论;不要只是投反对票。我编写这段代码是为了解决我遇到的一个问题,并且我正在积极使用它,所以我希望它尽可能可靠!

/*!
*** prettyPre ***/

(function( $ ) {

    $.fn.prettyPre = function( method ) {

        var defaults = {
            ignoreExpression: /\s/ // what should be ignored?
        };

        var methods = {
            init: function( options ) {
                this.each( function() {
                    var context = $.extend( {}, defaults, options );
                    var $obj = $( this );
                    var usingInnerText = true;
                    var text = $obj.get( 0 ).innerText;

                    // some browsers support innerText...some don't...some ONLY work with innerText.
                    if ( typeof text == "undefined" ) {
                        text = $obj.html();
                        usingInnerText = false;
                    }

                    // use the first line as a baseline for how many unwanted leading whitespace characters are present
                    var superfluousSpaceCount = 0;
                    var currentChar = text.substring( 0, 1 );

                    while ( context.ignoreExpression.test( currentChar ) ) {
                        currentChar = text.substring( ++superfluousSpaceCount, superfluousSpaceCount + 1 );
                    }

                    // split
                    var parts = text.split( "\n" );
                    var reformattedText = "";

                    // reconstruct
                    var length = parts.length;
                    for ( var i = 0; i < length; i++ ) {
                        // cleanup, and don't append a trailing newline if we are on the last line
                        reformattedText += parts[i].substring( superfluousSpaceCount ) + ( i == length - 1 ? "" : "\n" );
                    }

                    // modify original
                    if ( usingInnerText ) {
                        $obj.get( 0 ).innerText = reformattedText;
                    }
                    else {
                        // This does not appear to execute code in any browser but the onus is on the developer to not 
                        // put raw input from a user anywhere on a page, even if it doesn't execute!
                        $obj.html( reformattedText );
                    }
                } );
            }
        }

        if ( methods[method] ) {
            return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ) );
        }
        else if ( typeof method === "object" || !method ) {
            return methods.init.apply( this, arguments );
        }
        else {
            $.error( "Method " + method + " does not exist on jQuery.prettyPre." );
        }
    }
} )( jQuery );

然后可以使用标准 jQuery 选择器应用此插件:

<script>
    $( function() { $("PRE").prettyPre(); } );
</script>

【讨论】:

    【解决方案2】:

    注释缩进

    由于浏览器会忽略 cmets,因此您可以使用它们来缩进您的 pre 标记内容。

    解决方案

    <html>
      <body>
        <main>
          Here is my code with hack:
          <pre>
    <!-- -->def some_function
    <!-- -->  return 'Hello, World!'
    <!-- -->end
          </pre>
          Here is my code without hack:
          <pre>
            def some_function
              return 'Hello, World!'
            end
          </pre>
        </main>
      <body>
    </html>

    注意:添加了一个主包装器以为 cmets 提供足够的空间。

    优势

    • 无需 JavaScript
    • 可以静态添加
    • 缩小不会影响缩进并减小文件大小

    缺点

    • cmets 需要最少的空间
    • 除非使用构建工具,否则不是很优雅

    使用节点删除缩进

    更好的解决方案是使用构建过程或后端渲染过程删除前导空白。如果您使用的是 node.js,那么您可以使用我编写的名为 predentation 的流。您可以使用任何您想要的语言来构建类似的工具。

    之前

    <html>
     <body>
       Here is my code:
       <pre>
         def some_function
           return 'Hello, World!'
         end
       </pre>
     </body>
    </html>
    

    之后

    <html>
     <body>
       Here is my code:
       <pre>
    def some_function
      return 'Hello, World!'
    end
       </pre>
     </body>
    </html>
    

    优势

    • 无缝编写pre标签
    • 输出文件更小

    缺点

    • 在您的工作流程中需要一个构建步骤
    • 不处理由 CSS 添加的 white-space: pre 的非 pre 元素

    使用 JavaScript 删除缩进

    See this answer to remove indentation with JavaScript

    优势

    • 可以使用white-space: pre 定位元素

    缺点

    • JavaScript 可以被禁用
    • 空白会增加文件大小

    【讨论】:

    • 目前为止不是最漂亮但最好的解决方案。我希望对 data-* 属性做同样的事情有一个修复(ZeroClipboard 用它来存储要复制的文本,我用它来存储代码 sn-ps)。但在这种情况下不会忽略 cmets。
    【解决方案3】:

    设法用 JavaScript 做到这一点。它适用于 Internet Explorer 9 和 Chrome 15,我还没有测试过旧版本。添加对 outerHTML 的支持后,它应该可以在 Firefox 11 中运行(请参阅 here),同时网络上有一些自定义实现可用。读者的练习是摆脱尾随缩进(直到我有时间完成它并更新此答案)。

    我还会将此标记为社区 wiki,以便于编辑。

    请注意,您必须重新格式化示例以使用制表符作为缩进,或更改正则表达式以使用空格。

    <!DOCTYPE html>
    <html>
        <head>
            <title>Hello, World!</title>
        </head>
        <body>
            <pre>
                &lt;html&gt;
                    &lt;head&gt;
                        &lt;title&gt;Hello World Example&lt;/title&gt;
                    &lt;/head&gt;
                    &lt;body&gt;
                        Hello, World!
                    &lt;/body&gt;
                &lt;/html&gt;
            </pre>
            <pre>
                class HelloWorld
                {
                    public static int Main(String[] args)
                    {
                        Console.WriteLine(&amp;quot;Hello, World!&amp;quot;);
                        return 0;
                    }
                }
            </pre>
            <script language="javascript">
                var pre_elements = document.getElementsByTagName('pre');
    
                for (var i = 0; i < pre_elements.length; i++)
                {
                    var content = pre_elements[i].innerHTML;
    
                    var tabs_to_remove = '';
                    while (content.indexOf('\t') == '0')
                    {
                      tabs_to_remove += '\t';
                      content = content.substring(1);
                    }
    
                    var re = new RegExp('\n' + tabs_to_remove, 'g');
                    content = content.replace(re, '\n');
                    pre_elements[i].outerHTML = '<pre>' + content + '</pre>';
                }
            </script>
        </body>
    </html>
    

    【讨论】:

      【解决方案4】:

      这可以用四行 JavaScript 来完成:

      var pre= document.querySelector('pre');
      
      //insert a span in front of the first letter.  (the span will automatically close.)
      pre.innerHTML= pre.textContent.replace(/(\w)/, '<span>$1');
      
      //get the new span's left offset:
      var left= pre.querySelector('span').getClientRects()[0].left;
      
      //move the code to the left, taking into account the body's margin:
      pre.style.marginLeft= (-left + pre.getClientRects()[0].left)+'px';
       <body>
         Here is my code:
         <pre>
           def some_funtion
             return 'Hello, World!'
           end
         </pre>
       <body>

      【讨论】:

      • 这对我来说似乎是最优雅的解决方案。我注意到脚本需要放在底部才能工作。
      【解决方案5】:

      如果您可以更改元素的innerHTML

      给定:

      <pre>
        <code id="the-code">
          def some_funtion
            return 'Hello, World!'
          end
        </code
      </pre>
      

      呈现为:

      
          def some_funtion
            return 'Hello, World!'
          end
      
      

      以下香草JS:

      // get block however you want.
      var block = document.getElementById("the-code");
      
      // remove leading and trailing white space.
      var code = block.innerHTML
                      .split('\n')
                      .filter(l => l.trim().length > 0)
                      .join('\n');
      
      // find the first non-empty line and use its
      // leading whitespace as the amount that needs to be removed
      var firstNonEmptyLine = block.textContent
                                   .split('\n')
                                   .filter(l => l.trim().length > 0)[0];
      
      // using regex get the first capture group
      var leadingWhiteSpace = firstNonEmptyLine.match(/^([ ]*)/);
      
      // if the capture group exists, then use that to
      // replace all subsequent lines.
      if(leadingWhiteSpace && leadingWhiteSpace[0]) {
        var whiteSpace = leadingWhiteSpace[0];
        code = code.split('\n')
                   .map(l => l.replace(new RegExp('^' + whiteSpace + ''), ''))
                   .join('\n');
      }
      
      // update the inner HTML with the edited code
      block.innerHTML = code;
      

      将导致:

      <pre>
        <code id="the-code">def some_funtion
        return 'Hello, World!'
      end</code>
      </pre>
      

      并将呈现为:

      def some_funtion
        return 'Hello, World!'
      end
      

      【讨论】:

        【解决方案6】:

        我还发现,如果您使用的是haml,您可以使用preserve 方法。例如:

        preserve yield
        

        这将保留生成的yield 中的空白,这通常是包含代码块的降价。

        【讨论】:

          【解决方案7】:

          我决定想出一些比改变precode 工作方式更具体的方法。所以我做了一些正则表达式来获得第一个换行符 \n (前面可能有空格 - \s* 用于清理代码行末尾和换行符之前的额外空格(我注意到你的) ) 并找到它后面的制表符或空白字符[\t\s]*(这意味着制表符、空白字符(0 或更多)并将该值设置为一个变量。然后在正则表达式替换函数中使用该变量来查找它的所有实例并用\n(换行符)替换它。由于第二行(设置pattern)没有全局标志(正则表达式后的g),它将找到@987654329的第一个实例@换行符并将pattern变量设置为该值。因此,在换行符后跟2个制表符的情况下,pattern的值在技术上将是\n\t\t,它将被替换为每个\n在该 pre code 元素中找到字符(因为它正在运行通过每个函数)并替换为\n

          $("pre code").each(function(){
              var html = $(this).html();
              var pattern = html.match(/\s*\n[\t\s]*/);
              $(this).html(html.replace(new RegExp(pattern, "g"),'\n'));
          });
          <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
          <body>
              Here is some code:
          
              <pre><code>
                  Here is some fun code!
                  More code
                    One tab
                      One more tab
                      
                      Two tabs and an extra newline character precede me
              </code></pre>
          </body>

          【讨论】:

            【解决方案8】:

            <script>
                $("pre[name='pre']").each(function () {
                    var html = $(this).html()
                    var blankLen = (html.split('\n')[0].match(/^\s+/)[0]).length
                    $(this).html($.trim(html.replace(eval("/^ {" + blankLen + "}/gm"), "")))
                })
            </script>
            <div>
            	<pre name="pre">
            		1
            			2
            				3
            	</pre>
            </div>

            【讨论】:

            • 真的很有用!谢谢!
            【解决方案9】:

            这很麻烦,但如果代码折叠对您很重要,它就可以:

                    <pre>def some_funtion</pre>
                    <pre>    return 'Hello, World!'</pre>
                    <pre>end</pre>
            

            在你的 CSS 中,

                pre { margin:0 }
            

            在vim中,正常写你的代码然后执行:

                :s/\t\t\([^\n]\+\)/<pre>\1<\/pre>/
            

            每一行都可以工作。

            【讨论】:

              【解决方案10】:

              如果你在这样的代码块上使用它:

              <pre>
                <code>
                  ...
                </code>
              </pre>
              

              您可以像这样使用 css 来抵消前面的大量空白。

              pre code {
                position: relative;
                left: -95px; // or whatever you want
              }
              

              【讨论】:

                【解决方案11】:

                pre 标记保留您在正文中写入时使用的所有空格。通常,如果您不使用 pre 它会正常显示文本...(HTML 将使浏览器忽略那些空格)这里试试这个我使用了段落标签。 输出:-

                这是我的代码:

                def some_function

                 return 'Hello, World!'

                结束

                <html> <body> Here is my code: <p> def some_function<br> <pre> return 'Hello, World!'<br></pre> end </p> </body> </html>

                【讨论】:

                  猜你喜欢
                  • 2018-10-07
                  • 1970-01-01
                  • 2020-04-13
                  • 1970-01-01
                  • 2019-08-08
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多