将纯文本渲染为HTML并保持空白 – 不使用<pre>标签

10 投票
4 回答
14444 浏览
提问于 2025-04-16 11:53

给定一个包含可打印字符的任意文本文件,怎样才能把它转换成HTML,并且在浏览器中显示效果完全一样(满足以下要求)呢?

  • 不依赖于除了默认的HTML空白规则以外的任何规则
    • 不能使用<pre>标签
    • 不能使用CSS的white-space规则
  • 可以使用<p>标签,但不是必须的(<br />和/或<div>标签也可以)
  • 空白字符必须完全保留。

    给定以下输入行(忽略错误的自动语法高亮):

    Line one
        Line two, indented    four spaces
    

    浏览器应该完全一样地渲染输出,保持第二行的缩进和“indented”和“spaces”之间的间隔。当然,我并不是真的想要等宽的输出,字体与算法/标记无关。

    假设这两行是完整的输入文件,正确的输出示例应该是:

    Line one<br />&nbsp;&nbsp;&nbsp;&nbsp;Line two, 
    indented&nbsp;&nbsp;&nbsp; four spaces
    
  • 浏览器中希望有软换行。也就是说,生成的HTML不应该强迫用户滚动,即使输入行的宽度超过了他们的视口(假设单个单词的宽度仍然小于视口的宽度)。

我在寻找一个完整定义的算法。 如果能用pythonjavascript实现,那就更好了。

(请不要只是回答我应该使用<pre>标签或CSS的white-space规则,因为我的要求使这些选项不可行。也请不要发布未经测试和/或天真的建议,比如“把所有空格替换成&nbsp;。”毕竟,我相信技术上是可以找到解决方案的——这确实是个有趣的问题,你不觉得吗?)

4 个回答

2

虽然这个方法可能不能完全满足你的所有需求——比如它不支持制表符,但我之前用过一个叫做 gem 的工具,它给 Javascript 的 String 添加了一个 wordWrap() 方法,能做一些类似你描述的事情。所以这可能是一个不错的起点,帮助你实现你想要的其他功能。

//+ Jonas Raoni Soares Silva
//@ http://jsfromhell.com/string/wordwrap [rev. #2]

// String.wordWrap(maxLength: Integer,
//                 [breakWith: String = "\n"],
//                 [cutType: Integer = 0]): String
//
//   Returns an string with the extra characters/words "broken".
//
//     maxLength  maximum amount of characters per line
//     breakWith  string that will be added whenever one is needed to
//                break the line
//     cutType    0 = words longer than "maxLength" will not be broken
//                1 = words will be broken when needed
//                2 = any word that trespasses the limit will be broken

String.prototype.wordWrap = function(m, b, c){
    var i, j, l, s, r;
    if(m < 1)
        return this;
    for(i = -1, l = (r = this.split("\n")).length; ++i < l; r[i] += s)
        for(s = r[i], r[i] = ""; s.length > m; r[i] += s.slice(0, j) + ((s = s.slice(j)).length ? b : ""))
            j = c == 2 || (j = s.slice(0, m + 1).match(/\S*(\s)?$/))[1] ? m : j.input.length - j[0].length
            || c == 1 && m || j.input.length + (j = s.slice(m).match(/^\S*/)).input.length;
    return r.join("\n");
};

我还想补充一点,通常来说,如果涉及到制表符的话,使用等宽字体会比较好,因为不同的字体宽度会不一样(这会导致使用制表符的效果跟字体类型有很大关系)。

更新:这里有一个稍微更易读的版本,感谢一个在线的 javascript 美化工具

String.prototype.wordWrap = function(m, b, c) {
    var i, j, l, s, r;
    if (m < 1)
        return this;
    for (i = -1, l = (r = this.split("\n")).length; ++i < l; r[i] += s)
        for (s = r[i], r[i] = ""; s.length > m; r[i] += s.slice(0, j) + ((s =
                s.slice(j)).length ? b : ""))
            j = c == 2 || (j = s.slice(0, m + 1).match(/\S*(\s)?$/))[1] ? m :
            j.input.length - j[0].length || c == 1 && m || j.input.length +
            (j = s.slice(m).match(/^\S*/)).input.length;
    return r.join("\n");
};
11

使用一个叫做零宽空格&#8203;)来保持空格,并让文本能够换行。基本的想法是把每个空格或一串空格和一个零宽空格配对。然后把每个空格替换成一个不换行的空格。你还需要对html进行编码,并添加换行符。

如果你不在乎unicode字符,那就简单多了。你只需要用string.replace()就可以了:

function textToHTML(text)
{
    return ((text || "") + "")  // make sure it is a string;
        .replace(/&/g, "&amp;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;")
        .replace(/\t/g, "    ")
        .replace(/ /g, "&#8203;&nbsp;&#8203;")
        .replace(/\r\n|\r|\n/g, "<br />");
}

如果可以让空格换行,就像上面那样把每个空格和零宽空格配对。否则,如果想保持空格在一起,就要把每一串空格和零宽空格配对:

    .replace(/ /g, "&nbsp;")
    .replace(/((&nbsp;)+)/g, "&#8203;$1&#8203;")

要对unicode字符进行编码,就稍微复杂一些。你需要遍历这个字符串:

var charEncodings = {
    "\t": "&nbsp;&nbsp;&nbsp;&nbsp;",
    " ": "&nbsp;",
    "&": "&amp;",
    "<": "&lt;",
    ">": "&gt;",
    "\n": "<br />",
    "\r": "<br />"
};
var space = /[\t ]/;
var noWidthSpace = "&#8203;";
function textToHTML(text)
{
    text = (text || "") + "";  // make sure it is a string;
    text = text.replace(/\r\n/g, "\n");  // avoid adding two <br /> tags
    var html = "";
    var lastChar = "";
    for (var i in text)
    {
        var char = text[i];
        var charCode = text.charCodeAt(i);
        if (space.test(char) && !space.test(lastChar) && space.test(text[i + 1] || ""))
        {
            html += noWidthSpace;
        }
        html += char in charEncodings ? charEncodings[char] :
        charCode > 127 ? "&#" + charCode + ";" : char;
        lastChar = char;
    }
    return html;
}  

现在,补充一点。没有使用等宽字体的话,你会失去一些格式。想想看,这些用等宽字体的文本是如何形成列的:

ten       seven spaces
eleven    four spaces

没有等宽字体的话,你就会失去这些列:

 十个       七个空格
 十一个    四个空格

看起来,修复这个问题的算法会非常复杂。

15

要实现这个功能,同时让浏览器能够自动换行长的文本,我们可以把每两个空格替换成一个普通空格和一个不换行的空格。

这样,浏览器就能正确显示所有的空格(包括普通空格和不换行的空格),同时也能根据普通空格来换行长的文本。

下面是用Javascript实现的代码:

text = html_escape(text); // dummy function
text = text.replace(/\t/g, '    ')
           .replace(/  /g, '&nbsp; ')
           .replace(/  /g, ' &nbsp;') // second pass
                                      // handles odd number of spaces, where we 
                                      // end up with "&nbsp;" + " " + " "
           .replace(/\r\n|\n|\r/g, '<br />');

撰写回答