【问题标题】:Get ANSI color for character at index获取索引处字符的 ANSI 颜色
【发布时间】:2015-01-25 18:00:10
【问题描述】:

我开发了couleurs NPM package,可以设置将rgb方法附加到String.prototype

> console.log("Hello World!".rgb(255, 0, 0)) // "Hello World!" in red
Hello World!
undefined
> "Hello World!".rgb(255, 0, 0)
'\u001b[38;5;196mHello World!\u001b[0m'

这很好用。在索引i 处获取字符的 ANSI 颜色/样式的正确方法是什么?

这可能会被一些正则表达式破解,但我不确定这是否真的很好(但是,如果有正确的实现,我不反对)......我更喜欢原生方式通过访问由 tty 解释的字符来获取颜色/样式。

> function getStyle (input, i) { /* get style at index `i` */ return style; }

> getStyle("Hello World!".rgb(255, 0, 0), 0); // Get style of the first char
{
   start: "\u001b[38;5;196m",
   end: "\u001b[0m",
   char: "H"
}
> getStyle("Hello " + "World!".rgb(255, 0, 0), 0); // Get style of the first char
{
   start: "",
   end: "",
   char: "H"
}

当我们有多种组合样式时,事情会变得复杂:

> console.log("Green and Italic".rgb(0, 255, 0).italic())
Green and Italic
undefined
> getStyle("Green and Italic".rgb(0, 255, 0).italic(), 0);
{
   start: "\u001b[3m\u001b[38;5;46m",
   end: "\u001b[0m\u001b[23m",
   char: "G"
}
> getStyle(("Bold & Red".bold() + " but this one is only red").rgb(255, 0, 0), 0);
{
   start: "\u001b[38;5;196m\u001b[1m",
   end: "\u001b[22m\u001b[0m",
   char: "B"
}
> getStyle(("Bold & Red".bold() + " but this one is only red").rgb(255, 0, 0), 11);
{
   start: "\u001b[38;5;196m",
   end: "\u001b[0m",
   char: "u"
}
> ("Bold & Red".bold() + " but this one is only red").rgb(255, 0, 0)
'\u001b[38;5;196m\u001b[1mBold & Red\u001b[22m but this one is only red\u001b[0m'

就像我说的,我正在寻找一种原生方式(可能使用子进程)。

那么,如何获得索引i处字符的完整ANSI样式?

【问题讨论】:

    标签: javascript regex node.js ansi-colors


    【解决方案1】:

    我无法为您提供完整的解决方案,但这里有一个草图:

    • 维护一个累积当前格式的堆栈
    • 将字符串分割成块espace sequence | just a character
    • 遍历这个块列表
    • 如果它只是一个字符,保存它的索引 + 堆栈的当前状态
    • 如果是转义,要么将相应的格式压入堆栈,要么从中弹出格式

    您也可以使用该算法将转义字符串转换为 html,并使用 XML 方法遍历结果树。

    顺便说一句,后者反过来也不错,怎么样:

    console.log("<font color='red'>hi <b>there</b></font>".toANSI())
    

    【讨论】:

    • 有趣的想法,所以我尝试将它添加到我的解决方案中。标准属性所需要的只是将其名称添加到map 数组中;但我在试图找到一个简单的颜色解决方案时遇到了问题,因为没有明确的“关闭”代码。 (也没有inverse HTML,但可以用span模仿。)
    【解决方案2】:

    有几种方法可以“添加”文本格式,这是其中之一。问题是您将文本和样式混合到同一个对象中——一个文本字符串。类似于RTF

    Here is some \b bold\b0 and {\i italic} text\par
    

    但不同于 Word .DOC 文件的原生格式,后者适用于文本运行

    (text) Here is some bold and italic text\r
    (chp)  13 None
           4  sprmCFBold
           5  None
           6  sprmCFItalic
           6  None
    

    --左边的数字是特定格式的字符数。

    后一种格式是您正在寻找的,因为您想在纯文本中索引 字符。减去格式长度将显示您对哪个格式感兴趣。根据您希望请求格式的次数,您可以只运行一次,或将格式化的文本缓存在某处。

    一次性运行需要检查编码字符串的每个元素,当不在颜色字符串中时增加“文本”索引,如果是,则更新“最后一次看到”颜色字符串。我添加了一个兼容的getCharAt 函数用于调试目的。

    var str = '\u001b[38;5;196m\u001b[1mBo\x1B[22mld & Red\u001b[22m but this one is only red\u001b[0m';
    
    const map = {
        bold: ["\x1B[1m", "\x1B[22m" ]
      , italic: ["\x1B[3m", "\x1B[23m" ]
      , underline: ["\x1B[4m", "\x1B[24m" ]
      , inverse: ["\x1B[7m", "\x1B[27m" ]
      , strikethrough: ["\x1B[9m", "\x1B[29m" ]
    };
    
    String.prototype.getColorAt = function(index)
    {
        var strindex=0, color=[], cmatch, i,j;
    
        while (strindex < this.length)
        {
            cmatch = this.substr(strindex).match(/^(\u001B\[[^m]*m)/);
            if (cmatch)
            {
                // Global reset?
                if (cmatch[0] == '\x1B[0m')
                {
                    color = [];
                } else
                {
                    // Off code?
                    for (i=0; i<map.length; i++)
                    {
                        if (map[i][1] == cmatch[0])
                        {
                            // Remove On code?
                            for (j=color.length-1; j>=0; j--)
                            {
                                if (color[j] == map[i][0])
                                    color.splice (j,1);
                            }
                            break;
                        }
                    }
                    if (j==map.length)
                        color.push (cmatch[0]);
                }
                strindex += cmatch[0].length;
            } else
            {
                /* a regular character! */
                if (!index)
                    break;
                strindex++;
                index--;
            }
        }
        return color.join('');
    }
    
    String.prototype.getCharAt = function(index)
    {
        var strindex=0, cmatch;
    
        while (strindex < this.length)
        {
            cmatch = this.substr(strindex).match(/^(\u001B\[[^m]*m)/);
            if (cmatch)
            {
                strindex += cmatch[0].length;
            } else
            {
                /* a regular character! */
                if (!index)
                    return this.substr(strindex,1);
                strindex++;
                index--;
            }
        }
        return '';
    }
    
    console.log (str);
    
    color = str.getColorAt (1);
    text = str.getCharAt (1);
    console.log ('color is '+color+color.length+', char is '+text);
    

    返回的color 仍是其原始转义编码。您可以通过将它们添加到原始 map 数组中来使其返回某种常量。

    【讨论】:

    • 其实我想解析一个有这种ANSI风格的文本。我测试了代码,但是返回的值不正确。或者是否有任何库已经提供了这样的解析?
    • 据我所见,返回字符串应该是在 index 字符之前设置的最后一个 ANSI 颜色序列。不是这样吗?
    • 我之前的代码在没有进一步检查的情况下连接了所有匹配项,我现在添加了。更好?
    • 很好,为什么你总是使用map.length,而它总是undefined?我想我们可以删除 map 对象和与之相关的 for。
    • 我使用map 使返回的字符串尽可能短。如果没有,您可能会得到“加粗+加粗+加粗+加粗”,因为您必须包含所有颜色代码。我认为length 只是一些替代代码的剩余部分。
    猜你喜欢
    • 2015-01-25
    • 1970-01-01
    • 2012-12-17
    • 1970-01-01
    • 2013-06-12
    • 2021-07-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多