【问题标题】:replaceText() RegEx "not followed by"replaceText() 正则表达式“不跟随”
【发布时间】:2015-06-21 19:23:13
【问题描述】:

知道为什么 Google Docs 脚本似乎不支持这个简单的 RegEx 吗?

foo(?!bar)

我假设 Google Apps 脚本使用与 JavaScript 相同的 RegEx。不是这样吗?

我正在使用 RegEx:

DocumentApp.getActiveDocument().getBody().replaceText('foo(?!bar)', 'hello');

这会产生错误:

ScriptError: 无效的正则表达式模式 foo(?!bar)

【问题讨论】:

  • 文档中提到“不完全支持JavaScript正则表达式功能的子集,例如捕获组和模式修饰符。”,看来确实不支持此功能.developers.google.com/apps-script/reference/document/…
  • 值得一提的是,它在字符串操作中得到完全支持……这些限制仅适用于 DocumentApp 服务。因此,应该可以在较低级别操作您的文档(从段落中提取文本等),但它很快就会变得非常麻烦。直到现在我才找到一个干净的解决方案。
  • 是的,尽管 Google 声称脚本支持“大多数 JavaScript 的正则表达式功能”,但它看起来确实非常有限。如上所述的负前瞻非常有用且难以解决。此外,能够捕获文本对于许多查找和替换需求至关重要。就我而言,我能够通过使用 getText()、Javascript 的 .replace() 方法和 setText() 来解决我的需求。
  • 不应该有两个斜线来告诉它是一个正则表达式吗? /foo(?!bar)/(这里是新手)

标签: javascript google-apps-script


【解决方案1】:

正如 cmets 关于这个问题所讨论的,这是一个记录在案的限制; replaceText() 方法不支持反向前瞻或任何其他捕获组。

不完全支持 JavaScript 正则表达式功能的子集,例如捕获组和模式修饰符。ref

Serge 提出了一种解决方法,“应该可以在较低级别操作您的文档(从段落中提取文本等),但很快就会变得非常麻烦。”

这就是它的样子。如果您不介意丢失所有格式,此示例将应用捕获组、RegExp 标志(i 表示不区分大小写)和反向前瞻来更改:

小兔子Foo Foo,跑过foobar

到:

小兔子 Fred Fred,跑过 foobar。

代码:

function myFunction() {
  var body = DocumentApp.getActiveDocument().getBody();
  var paragraphs = body.getParagraphs();
  for (var i=0; i<paragraphs.length; i++) {
    var text = paragraphs[i].getText();
    paragraphs[i].replaceText(".*", text.replace(/(f)oo(?!bar)/gi, '$1red') );
  }
}

【讨论】:

    【解决方案2】:

    你有一个序列,你可以用正则表达式匹配,但是这个正则表达式也会匹配一个或多个你不想改变的东西。这种情况的通用解决方案是:

    1. 更改文本,使您知道绝对不使用的字符序列。实际上,这为您提供了用作变量的字符序列,以保存您不想更改的值。就个人而言,我会使用:
      body.replaceText('Q','Qz');
      这将使您的文档中没有与/Q[^z]/ 匹配的序列。这使您能够使用像Qa 这样的序列来表示您不想更改的一些文本。我使用Q,因为它在英语中的使用频率很低。您可以使用任何字符。为提高效率,请选择一个在您影响的文本中产生少量更改的字符。
    2. 将您不想最终更改的内容更改为您现在知道未使用的字符序列之一。例如:
      body.replaceText('foobar','Qa');
      对您不想更改的任何其他项目重复此操作。
    3. 更改您真正想要更改的文本。在这个例子中: body.replaceText('foo','hello'.replace(/Q/g,'Qz'));
      请注意,您需要将用于打开已知未使用序列的第一个替换应用于新的替换文本。
    4. 将您不想更改的所有内容恢复到原始状态:
      body.replaceText('Qa','foobar');
    5. 恢复用于打开未使用的字符序列的文本:
      body.replaceText('Qz','Q');

    所有这些将是:

    var body = DocumentApp.getActiveDocument().getBody();
    body.replaceText('Q','Qz');      //Open up unused character sequences
    body.replaceText('foobar','Qa'); //Save the things you don't want to change.
    
    //In the general case, you need to apply to the new text the same substitution
    //  which you used to open up unused character sequences.  If you don't you
    //  may end up with those sequences being changed in the new text.
    body.replaceText('foo','hello'.replace(/Q/g,'Qz')); //Make the change you desire.
    
    body.replaceText('Qa','foobar'); //Restore the things you saved.
    body.replaceText('Qz','Q');      //Restore the original sequence.
    

    虽然以这种方式解决问题不允许您使用 JavaScript RegExp 的所有功能(例如捕获组、前瞻断言和标志),但它应该保留文档中的格式。

    您可以选择不执行上述步骤 1 和 5,方法是选择更长的字符序列来表示您不想匹配的文本(例如 kNoWn1UnUsEd)。但是,必须根据您对文档中已经存在的内容的了解来选择这样一个较长的序列。这样做可以节省几个步骤,但您要么必须搜索未使用的字符串,要么接受您使用的字符串可能已经在文档中,这会导致不希望的替换。

    【讨论】:

      【解决方案3】:

      我想出了一种方法来获得大部分 JS 的 str.replace() 功能,包括在 Apps 脚本中捕获组和智能替换器,而不会弄乱样式。诀窍是使用 Javascript 的 regex.exec() 函数和 Apps Script 的 text.deleteText()text.insertText() 函数。

      function replaceText(body, regex, replacer, attribute){
        var content = body.getText();
        const text = body.editAsText();
        var match = "";
        while (true){
          content = body.getText();
          var oldLength = content.length;
          match = regex.exec(content);
          if (match === null){
              break;
          }
          var start = match.index;
          var end = regex.lastIndex - 1;
          text.deleteText(start, end);
          text.insertText(start, replacer(match, regex));
          var newLength = body.getText().length;
          var replacedLength = oldLength - newLength;
          var newEnd = end - replacedLength;
          text.setAttributes(start, newEnd, attribute);
          regex.lastIndex -= replacedLength;
        }
      }
      

      参数说明:

      1. body: 你要操作的文档正文
      2. regex:普通的JS正则表达式对象,用作搜索模式
      3. replacer:replacer 函数用于返回要替换的字符串,replacer 自动接收两个参数:
        我。 match:匹配由regex.exec()生成的对象和
        二。 regex: 用作搜索模式的正则表达式对象
      4. attribute: 一个 Apps 脚本 attribute 对象 例如,如果您想将粗体样式应用于替换旧字符串的新字符串,您可以创建一个boldStyle 属性对象:
      var boldStyle = {};
      boldStyle[DocumentApp.Attribute.BOLD] = true;
      

      提示:

      1. 如何在replaceText() 中使用捕获组?

        您可以从replacer 函数访问所有捕获组,match[0] 是匹配的整个字符串,match[1] 是第一个捕获组,match[2] 是第二个,等等。

      2. 如何访问replaceText() 中匹配的索引和位置?

        您可以通过 replacer 函数访问匹配的开始索引 (match.index) 和匹配的结束索引 (regex.lastIndex)。

      有关 JS RegExp 的更深入参考,请参阅来自 Javascript.info 的 excellent tutorial

      示例:

      这是replaceText() 函数的示例用例。这是一个降价到谷歌文档转换脚本的简单实现:

       function markdownToDocs() {
        const body = DocumentApp.getActiveDocument().getBody();
      
        // Use editAsText to obtain a single text element containing
        // all the characters in the document.
        const text = body.editAsText();
      
        // e.g. replace "**string**" with "string" (bolded)
        var boldStyle = {};
        boldStyle[DocumentApp.Attribute.BOLD] = true;
        replaceDeliminaters(body, "\\*\\*", boldStyle, false);
      
        // e.g. replace multiline "```line 1\nline 2\nline 3```" with "line 1\nline 2\nline 3" (with gray background highlight)
        var blockHighlightStyle = {};
        blockHighlightStyle[DocumentApp.Attribute.BACKGROUND_COLOR] = "#EEEEEE";
        replaceDeliminaters(body, "```", blockHighlightStyle, true);
      
        // e.g. replace inline "`console.log("hello world")`" with "console.log("hello world")" (in "Times New Roman" font and italic)
        var inlineStyle = {};
        inlineStyle[DocumentApp.Attribute.FONT_FAMILY] = "Times New Roman";
        inlineStyle[DocumentApp.Attribute.ITALIC] = true;
        replaceDeliminaters(body, "`", inlineStyle, false);
      
        // feel free to change all the styling and markdown deliminaters as you wish.
      }
      
      // replace markdown deliminaters like "**", "`", and "```"
      function replaceDeliminaters(body, deliminator, attributes, multiline){
        var capture;
        if (multiline){
          capture = "([\\s\\S]+?)"; // capture newline characters as well
        } else{
          capture = "(.+?)"; // do not capture newline characters
        }
        const regex = new RegExp(deliminator + capture + deliminator, "g");
        const replacer = function(match, regex){
          return match[1]; // return the first capture group
        }
        replaceText(body, regex, replacer, attributes);
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-03-01
        • 1970-01-01
        • 2015-04-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-12-14
        • 1970-01-01
        相关资源
        最近更新 更多