如何 select 段落中所有带下划线的文本

How to select all underlined text in a paragraph

我正在尝试创建一个 google 应用程序脚本来格式化段落的某些部分。例如,带下划线的文本也将变为 bolded/italicized。

我尝试过的一个文档插件具有类似的功能:https://imgur.com/a/5Cw6Irn(这正是我想要实现的)

我如何编写一个函数来 select 某种类型的文本并对其进行格式化?

**我设法编写了一个脚本来遍历段落中的每个字母并检查它是否有下划线,但是随着段落变长它变得非常慢,所以我正在寻找一个更快的解决方案。

function textUnderline() {
  var selectedText = DocumentApp.getActiveDocument().getSelection();  
  if(selectedText) {
    var elements = selectedText.getRangeElements();
    for (var index = 0; index < elements.length; index++) {
      var element = elements[index];
      if(element.getElement().editAsText) {        
        var text = element.getElement().editAsText();
        var textLength = text.getText().length;        
        //For every single character, check if it's underlined and then format it
        for (var i = 0; i < textLength; i++) {        
          if(text.isUnderline(i)) {          
            text.setBold(i, i, true);
            text.setBackgroundColor(i,i,'#ffff00');               
          } else {            
            text.setFontSize(i, i, 8);             
          } 
        }   
      } 
    }
  }
}

根据 gif 动画中显示的示例,您的程序似乎需要

  • 处理选择
  • 如果所选区域是某种格式(例如带下划线)则设置属性
  • 如果所选区域不是某种格式(例如,没有下划线)则设置属性
  • 尽快完成

并且您的示例代码实现了除最后一个之外的所有这些目标。

问题是您在每个索引位置调用 text.set...() 函数。每次调用都是同步的,并且会在文档更新之前阻塞代码,因此您的 运行 时间会随着选择中的每个字符线性增长。

我的建议是从选择范围建立一个子范围集合,然后对每个子范围使用 text.set...(subrange.start, subrange.end) 来应用格式。现在 运行 时间将取决于字符块,而不是单个字符。也就是说,您只会在格式来回切换时进行更新,在您的示例中,下划线为无下划线。

这里是一些实现这个子范围想法的示例代码。我把具体的谓词函数(text.isUnderline)和具体的格式化效果分离到各自的函数中,以便将总体思路与具体实现分开。

// run this function with selection
function transformUnderlinedToBoldAndYellow() {
  transformSelection("isUnderline", boldYellowOrSmall);
}

function transformSelection(stylePredicateKey, stylingFunction) {
  const selectedText = DocumentApp.getActiveDocument().getSelection();
  if (!selectedText) return;
  const getStyledSubRanges = makeStyledSubRangeReducer(stylePredicateKey);
  selectedText.getRangeElements()
    .reduce(getStyledSubRanges, [])
    .forEach(stylingFunction);
}

function makeStyledSubRangeReducer(stylePredicateKey) {
  return function(ranges, rangeElement) {
    const {text, start, end} = unwrapRangeElement(rangeElement);
    if (start >= end) return ranges; // filter out empty selections
    const range = {
      text, start, end,
      styled: [], notStyled: [] // we will extend our range with subranges
    };
    const getKey = (isStyled) => isStyled ? "styled" : "notStyled";
    let currentKey = getKey(text[stylePredicateKey](start));
    range[currentKey].unshift({start: start});
    for (let index = start + 1; index <= end; ++index) {
      const isStyled = text[stylePredicateKey](index);
      if (getKey(isStyled) !== currentKey) { // we are switching styles
        range[currentKey][0].end = index - 1; // note end of this style
        currentKey = getKey(isStyled);
        range[currentKey].unshift({start: index}); // start new style range
      }
    }
    ranges.push(range);
    return ranges;
  }
}

// a helper function to unwrap a range selection, deals with isPartial,
// maps RangeElement => {text, start, end}
function unwrapRangeElement(rangeElement) {
  const isPartial = rangeElement.isPartial();
  const text = rangeElement.getElement().asText();
  return {
    text: text,
    start: isPartial
      ? rangeElement.getStartOffset()
      : 0,
    end: isPartial
      ? rangeElement.getEndOffsetInclusive()
      : text.getText().length - 1
  };
}

// apply specific formatting to satisfy the example
function boldYellowOrSmall(range) {
  const {text, start, end, styled, notStyled} = range;
  styled.forEach(function setTextBoldAndYellow(range) {
    text.setBold(range.start, range.end || end, true);
    text.setBackgroundColor(range.start, range.end || end, '#ffff00');
  });
   notStyled.forEach(function setTextSmall(range) {
    text.setFontSize(range.start, range.end || end, 8);
  });
}

使用 getTextAttributeIndices:

没有必要检查选择中的每个字符。您可以使用 getTextAttributeIndices() 获取文本格式更改的索引。此方法:

Retrieves the set of text indices that correspond to the start of distinct text formatting runs.

您只需要遍历这些索引(即检查文本格式更改的索引),它们只是所有字符索引的一小部分。这样会大大提高效率。

代码示例:

function textUnderline() {
  var selectedText = DocumentApp.getActiveDocument().getSelection();  
  if(selectedText) {
    var elements = selectedText.getRangeElements();
    for (var index = 0; index < elements.length; index++) {
      var element = elements[index];
      if(element.getElement().editAsText) {        
        var text = element.getElement().editAsText();
        var textRunIndices = text.getTextAttributeIndices();
        var textLength = text.getText().length;
        for (let i = 0; i < textRunIndices.length; i++) {
          const startOffset = textRunIndices[i];
          const endOffset = i + 1 < textRunIndices.length ? textRunIndices[i + 1] - 1 : textLength - 1;
          if (text.isUnderline(textRunIndices[i])) {
            text.setBold(startOffset, endOffset, true);
            text.setBackgroundColor(startOffset, endOffset,'#ffff00');  
          } else {
            text.setFontSize(startOffset, endOffset, 8);
          }
        }
      } 
    }
  }
}

参考: