【问题标题】:Format text with footnote by regular expression通过正则表达式格式化带脚注的文本
【发布时间】:2014-09-06 15:56:25
【问题描述】:

我想将文本的注释转换为脚注的形式。这是文本的一个最小示例。

第一段。这是第一段的第一位[1]。这是第一段的第二位[2]。

[1]第一段注释之一

[2]第一段注释二

第二段。这是第二段的第一位[1]。这是第二段的第二位[2]。

[1]第二段注释之一

[2]第二段注释二

在每个段落的末尾,会有几个以标签[1]开头的注释。每个注释将形成一个段落。

我想要做的是使用乳胶语法将这些注释插入到文本中。示例文本的期望输出是,

第一段。这是第一段的第一个位置\footnote{annotation one of paragraph one}。这是第一段的第二位\footnote{annotation two of paragraph one}

第二段。这是第二段的第一名\footnote{annotation one of parantwo}。这是第二段的第二位\footnote{annotation one of paragraph}

这不仅仅是通过匹配模式进行的简单替换。它可能必须以段落为基础执行。您认为最简单的方法是什么?

编辑:我想出了一个可能的解决方案来使用 sed。

去掉注解前面的换行符,

第一段。这是第一段的第一位[1]。这是第一段的第二位[2]。 [1] 第一段注释之一 [2] 第一段注释二

第二段。这是第二段的第一位[1]。这是第二段的第二位[2]。 [1] 第二段注释之一 [2] 第二段注释二

匹配模式

[1] 文本1 [1] 文本2 [2]

替换成

文本2文本1 [2]

基本上第一个 [1] 是应该插入注释的位置; [1] 和 [2] 之间的东西是要重定位的注解。

这些问题是相关的:Remove new line / line break characters only for specific linesHow can I remove a line-feed/newline BEFORE a pattern using sed,但由于缺乏正则表达式知识,我无法让这些代码为我工作。

【问题讨论】:

  • 闻起来像一个太宽泛的问题..
  • “最简单的方法是什么?”:绝对不是sed。查看一些awk 问题,然后在此帖子中添加解决您的问题的尝试。 StackOverflow 旨在帮助人们解决编程问题,而不是提供免费的编码服务。祝你好运。
  • 如果您没有在每个“段落”之后重置脚注计数,这会更容易
  • 从根本上说,sed 是这个工作的错误工具。您也许可以编写一个 sed 脚本来预处理文件并生成一个新的 sed 脚​​本来处理该文件,但是当有许多更好的工具可以完成这项任务时,您就只能手忙脚乱了。我会接触 Perl(但我在 20 多年前学习了 Perl,而 Python 才几年前),但 Python 也能够处理它。部分麻烦是您必须保存第一段的所有文本,直到到达第二段的开头;只有这样你才能开始为第一段生成实际的文本。
  • 我认为我之前的评论仍然有效,即使sed 脚本捕获了保留空间中的段落内容。这些将是不以方括号开头的行。问题是,当您遇到带有方括号的行时,您需要编写一个正则表达式,将行尾替换为保留空间来代替方括号的内容。这需要一种“动态正则表达式”。如果您知道一个段落中的脚注永远不会超过 9 个,那么您可以考虑某种将代码写出 9 次的 hack,但仍然存在问题。

标签: shell perl sed footnotes


【解决方案1】:

从根本上说,sed 不适合这项工作。您也许可以编写一个 sed 脚本来预处理文件并生成一个新的 sed 脚本来处理该文件,但是当有许多更好的工具可以完成这项任务时,您就手足无措了。我会接触 Perl(但我在 20 多年前学习了 Perl,而 Python 才几年前),但 Python 也能够处理它,小心你甚至可以使用 awk。部分麻烦是您必须保存第一段的所有文本,直到到达第二段的开头;只有这样你才能开始为第一段生成实际的文本。

我认为“sed 是错误的工具”注释仍然有效,即使 sed 脚本捕获了保留空间中的段落内容。这些将是不以方括号开头的行。问题是,当您遇到带有方括号的行时,您需要编写一个正则表达式,将行尾替换为保留空间来代替方括号的内容。这需要一种“动态正则表达式”。即使您知道一个段落中的脚注永远不会超过 9 个,因此您可以考虑某种将代码写出 9 次的 hack,在正确的位置编写替换字符串仍然存在问题。

这是一个简单的 Perl 脚本——好吧,Perl 中的一个不太复杂的脚本——可以完成这项工作。 “旋转循环”(三个嵌套循环)使其有点难以理解。

#!/usr/bin/env perl
use strict;
use warnings;

my $para = "";

TEXT:
while (<>)
{
NOTES:
    while (m/^\s*\[(\d+)]\s+(.*)/)
    {
        my $tag = $1;
        my $note = $2;
        $para =~ s/\[$tag]/\\footnote{$note}/m;
        while (<>)
        {
            last if $_ =~ m/^\s*\[/;
            if ($_ !~ m/^\s*$/)
            {
            print $para;
            $para = "";
            last NOTES;
            }
        }
        last TEXT if eof;
    }

    $para .= $_;
}

print "$para";

给定输入文件:

Paragraph one. This is the first place [1] of paragraph one. This is the second place [2] of paragraph one.

[1] annotation one of paragraph one

[2] annotation two of paragraph one

Paragraph two. This is the first place [1] of paragraph two. This is the second place [2] of paragraph two.

[1] annotation one of paragraph two

[2] annotation two of paragraph two

该文件中该脚本的输出是:

Paragraph one. This is the first place \footnote{annotation one of paragraph one} of paragraph one. This is the second place \footnote{annotation two of paragraph one} of paragraph one.

Paragraph two. This is the first place \footnote{annotation one of paragraph two} of paragraph two. This is the second place \footnote{annotation two of paragraph two} of paragraph two.

脚本的作用是什么?

外部循环(标记为TEXT)将行读入$_,直到EOF。

标记为NOTES 的循环处理段落之后的材料,直到下一个段落的开头。它知道这是一个脚注行,因为它以方括号中的数字开头(可能用空格缩进,并且肯定在右方括号后有一个空格)。当它找到这样的一行时,数字保存在$tag 中,替换文本(必须是单行——此处没有扩展的多行脚注)保存在$note 中。然后,保存的段落中方括号内第一次出现的标记被脚注符号和注释文本替换(这是在sed 的单次运行中几乎不可能的部分,并且假设脚注编号在段落中重复,甚至导致两次运行 sed 有问题)。完成替换后(不在乎是否没有匹配项可替换),它读取下一行,这就是循环(和头部)开始旋转的地方。如果新读取的行是注释行,则最初的last 退出最里面的while 并返回到NOTES 循环的下一次迭代。如果该行与空白行不匹配,那么我们必须刚刚阅读了下一段的第一行,所以打印上一段(现在有与要进行的替换一样多的替换),清空保存的段落,并退出NOTES 循环。否则,请忽略注释中间的空行。

循环结束后,检查是否有 EOF,如果有则退出主循环。否则,将刚刚读取的段落行添加到保存的段落中。

最后,打印最后保存的段落。

这还没有经过详尽的测试。我没有生成引用缺失注释的段落,或者没有引用的注释,或者没有顺序的注释。我认为它会通过忽略问题来“处理”这些问题;仍然会有对丢失注释的引用,并且未引用的注释根本不会出现在输出中。如果相同的注释编号引用在一个段落中出现两次,但该段落之后只有一个注释编号,则忽略第二个和后续的注释编号。如果相同的注释编号出现两次('text[1] more[1]')并且段落后面的注释重复编号('[1] note 1A','[1] note 1B'),那么第一个将替换为“note 1A”,第二个替换为“note 1B”。我没有测试过多行段落(但我不希望有麻烦)。替换正则表达式不需要多行限定符,因为对标签的引用不能被分割成行,也不能锚定在一行上。

处理多行脚注是读者的一项练习(并非完全无关紧要)。除此之外,在找到一个空行、另一个脚注行或下一段的开头之前,您不能开始替换多行脚注。

【讨论】:

  • 没想到有人会为我写代码,谢谢。我不知道 Perl,但是通过谷歌搜索 perl 正则表达式和你的 cmets 关于该程序,我想我了解其中的大部分。
  • m/^\s*[(\d+)]\s+(.*)/, m 用于多行搜索,^\s* 用于以 0 或多个空格开头,[( \d+)] 表示 [一个或多个整数] 加上任意字符,大概是 "+" 是将字符串切割成 $1 和 $2 的分隔符。
  • “s/[$tag]/\\footnote{$note}/m;”后面的“m”是什么?它可能不是多行修饰符。同样在最近的编辑中,您将~添加到“$_ =~ m/^\s*[/”,只是找不到“~”代表什么。
  • m// 运算符是匹配运算符;它可以由结尾的m(以及其他字母)限定,以指示多行搜索:m/^\s*\[(\d+)]\s+(.*)/m 将是多行搜索。你可以去掉前面的m,所以//m//的简写。 s///m 命令末尾的m 是多行修饰符;这可能没有必要,但它不会造成积极的伤害。 =~ 运算符是一个正则表达式绑定运算符;它将右侧的正则表达式操作应用于左侧的操作数。该操作可以是m//s///tr///y///(可能还有其他)。
  • while (&lt;&gt;) 表示法自动分配给$_(整个循环从命令行中指定的文件读取,如果没有指定文件,则从标准输入读取)。在引用$_ 的测试中,我可以省略$_ 并使测试工作(即last if m/^\s*\[/;if (!m/^\s*$/))。 while (m/^\s*\[(\d+)]\s+(.*)/) 循环可以写成while ($_ =~ m/^\s*\[(\d+)]\s+(.*)/);我并不完全一致。由于 Perl 不是您的母语之一,我大部分(但不是完全)避免使用 $_
【解决方案2】:

一个不那么冗长(并且记录较少)的 perl 版本

perl -00 -pe '
    @markers = m{(\[\d+\])}g;
    for $i (0..$#markers) {
        $footnote = <>;
        ($marker, $text) = $footnote =~ m{(\[\d+\])\s+(.*)};
        s{\Q$marker\E}{\\footnote{$text}};
    }
' file

这假设如果一个段落中有 5 个脚注标记,则该段落后面将有 5 个脚注。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-09-15
    • 1970-01-01
    • 1970-01-01
    • 2023-02-04
    • 2019-08-05
    • 2017-09-29
    • 2011-11-17
    • 2020-09-14
    相关资源
    最近更新 更多