如何根据缩进标记文本?

如何根据缩进标记文本?

我想根据文本内容的缩进深度来标记文本内容(使用类似 XML 的标记)。应保留空行。下面的示例内容缩进 1、2 或 3 个制表符。

输入

aaa
  bbb
  bbb

aaa

      ccc
      ccc
  bbb
  bbb

我想将相同缩进级别的行分组,并将这些缩进级别转换为标签xyz,如下所示:

输出

<x>aaa</x>
     <y>bbb
        bbb</y>

 <x>aaa</x>

        <z>ccc
           ccc</z>
    <y>bbb
       bbb</y>

我怎样才能做到这一点?

答案1

问题描述:

输入文件包含文本,以零个或多个制表符缩进。具体来说,输入中的每一行都是以下之一:

  • 空白,或
  • 零个或多个制表符(最多有限制;见下文),后跟一个既不是空格也不是制表符的字符(后跟零个或多个任意字符)。

  • 以零个或多个制表符开头,后跟一个空格。(这意味着没有以空格开头的行。)
  • 完全由一个或多个标签组成(没有其他内容)。
  • 从超过指定数量的标签开始。

输入应按逻辑分解为多组行,这些行要么

  • 空白,或
  • 缩进相同数量的制表符。

空行应不加修改地传递到输出。

应指定标签列表;例如,xyzz。一组(非空白)行如果缩进的标签为零(即不缩进),则应使用 和 括起来<x></x>一组缩进一个标签的行应使用 和 括起来<y></y>一组缩进两个标签的行应使用 和 括起来<zz></zz>(行不会使用超过两个标签进行缩进。)

一组(非空白行)的第一行应在制表符和文本之间插入开始标记。一组的最后一行应在文本末尾附加结束标记。一个组可能由一行组成,因此第一行也可能是最后一行。除第一行之外,组中所有行都应额外缩进(在制表符和文本之间插入空格),缩进宽度与开始标记相同。

例如(使用 ―→ 表示选项卡),这个输入

aaa
 ―→ Once upon a midnight dreary,
 ―→ while I pondered, weak and weary,

Quoth the Raven, “Nevermore.”

 ―→  ―→ The quick brown fox
 ―→  ―→ jumps over the lazy dog.
 ―→ It was a dark and stormy night.
 ―→ Suddenly a shot rang out.

应翻译成输出

<x>aaa</x>
 ―→ <y>Once upon a midnight dreary,
 ―→    while I pondered, weak and weary,</y>

<x>Quoth the Raven, “Nevermore.”</x>

 ―→  ―→ <zz>The quick brown fox
 ―→  ―→     jumps over the lazy dog.</zz>
 ―→ <y>It was a dark and stormy night.
 ―→    Suddenly a shot rang out.</y>

解决方案:

显然,在读完下一行之前,我们并不知道该如何处理输入的一行。解决这个问题的方法是保存上一行的内容,等读完下一行后再处理。

因此,如下所示:

awk '
  BEGIN {
        num_tags = split("x y zz", tags)
        for (i=1; i<=num_tags; i++)
            {
                len = length(tags[i]) + 2
                tag_pad[i] = ""
                for (j=1; j<=len; j++) tag_pad[i] = tag_pad[i] " "
            }
    }
    {
        if (NF == 0)
                indent_num = 0
        else
            {
                indent_num = index($0, $1)
                indent_str = substr($0, 1, indent_num-1)
                restOfLine = substr($0, indent_num)
            }
        if (indent_num != saved_indent_num  &&  saved != "")
            {
                print saved "</" tags[saved_indent_num] ">"
                saved = ""
            }
        if (NF == 0)
                print
        else if (indent_num > num_tags)
            {
                errmsg = "Error: line %d has an indent level of %d.\n"
                printf errmsg, NR, indent_num > "/dev/stderr"
                exit 1
            }
        else if (indent_num == saved_indent_num)
            {
                print saved
                saved = indent_str   tag_pad[indent_num]    restOfLine
            }
        else
                saved = indent_str "<" tags[indent_num] ">" restOfLine
        saved_indent_num = indent_num
    }
   END {
        if (saved != "")
                print saved "</" tags[saved_indent_num] ">"
    }
    '

BEGIN 块通过拆分空格分隔的字符串来初始化标签(xy和)。数组包含足够的空格以匹配标签的宽度(包括和): 和是三个空格;是四个空格。zztag_pad<>tag_pad[1]tag_pad[2]tag_pad[3]

读取一行输入后,我们会对其进行解析。如果它没有字段(NF == 0),则它必须是空白的(因为我们已经指定没有一行完全由空格和制表符组成),因此设置为 0。否则,通过查找(整个行)  中(第一个单词)indent_num的位置来测量缩进。返回一个从 1 开始的值,因此这实际上比第一个非空白字符之前的空白字符数多一个(请记住,我们假设这些都是制表符)。这很幸运,因为现在 对应于和数组中的条目。然后我们将该行分成(空白)和(缩进后的所有内容)。$1$0indexindent_numtagstag_padindent_strrestOfLine

现在我们依赖已保存的信息。如果此行的缩进与前一行不同,则我们将开始一个新组。如果有保存的行,将其写出,并在行末添加适当的结束标记。

如果当前行为空行,则直接打印。检查当前缩进级别是否过高,如果过高则放弃。如果当前缩进与上一个缩进相同,则这是已开始组的续行,因此只需打印已保存的(上一个)行,并使用以下命令构建一个作为saved当前行的新字符串宽度当前标签插入在缩进和文本之间。否则,我们将开始一个新组,因此构建一个saved字符串,该字符串是当前行,起始标签(本身)插入在缩进和文本之间。

当我们到达输入的末尾时,像之前一样结束当前组。

相关内容