How to add Named Ranges to sub-paragraph elements in Google apps-script

664 views Asked by At

I would like to implement a 'html-span-like' feature inside Google Documents but whenever I try to add a NamedRange to a substring of text inside a Google Document, the range is merged with previous text in the same paragraph.

In result, the NamedRange is applied to the whole paragraph.

Here's my test case :

function createTextNamedRange(){
  // Retrieve the current document's body
  var doc = DocumentApp.getActiveDocument();
  var docBody = doc.getBody();

  // Add a new paragraph with text {NotNamed}
  var para = docBody.appendParagraph('{NotNamed}');

  // Append some text that will be named ( .appendText() method returns a Text element )
  var textElem = para.appendText('{NamedText}');

  // Build a range for the Text element
  var range=doc.newRange().addElement(textElem).build();
  // Name the range and append it to the document
  doc.addNamedRange('myNamedRange',range);
}

I then display the content of the NamedRange using the Logger class in this function:

function getTextNamedRange(){
  // Retrieve the named range
  var namedRanges =    DocumentApp.getActiveDocument().getNamedRanges();

  // Iterate through each instance of name 'myNamedRange' (there's only one)
  for (var nr in namedRanges){
    var namedRange = namedRanges[nr];

    // A range may contain several RangeElements, iterate through them
    var rangeElements = namedRange.getRange().getRangeElements();
    for(var re in rangeElements) {

      // Get the text of each RangeElement and display it in the logger
      var text = rangeElements[re].getElement().asText().getText();
      Logger.log('Text with namedRange (' + namedRange.getName() + ') : "' + text +'"');
    }
  }
}

I would assume to get {NamedText} as an output but the Log Window tells this:

Text with namedRange (myNamedRange) : "{NotNamed}{NamedText}"

As it can be seen, the range was applied to the unmarked as well as the named text.

I found a workaround detailed below but it's definitely not to my taste: it consists of adding empty inline images between the .appendText() calls. This way the Text elements are not merged.

I'm still looking for a better solution.

1

There are 1 answers

1
Laurent' On

The following is for WORKAROUND ONLY but appears to work.

It relies on InlineImage insertion before and after the Text chunk that has to be Named.

This prevent the NamedRange to merge when applied to chunk of Text inside a Paragraph

function createTextNamedRangeWorkaround(){
  // WORKAROUND: prevent the Text Merging (and hence the NamedRange merging)
  // by separating Text Elements with blank Inline Images

  // Import a blank PNG file
  // NOTE: This shouldn't be fetched each time 
  //       but only once and passed as an argument
  var blankImage = UrlFetchApp.fetch('http://upload.wikimedia.org/wikipedia/commons/d/d2/Blank.png');

  // Retrieve the current document's body
  var doc = DocumentApp.getActiveDocument();
  var docBody = doc.getBody();

  // Add a new paragraph with text {NotNamed}
  var para = docBody.appendParagraph('{NotNamed}');

  // WORKAROUND HERE : 
  para.appendInlineImage(blankImage.getBlob());
  // Append some text
  var textElem1 = para.appendText('{NamedText1}');

  // WORKAROUND HERE : 
  para.appendInlineImage(blankImage.getBlob());
  // Append some text
  var textElem2 = para.appendText('{NamedText2}');

  // Build Named ranges after text insertion (see notice below)
  var range1=doc.newRange().addElement(textElem1).build();
  // Name the range and append it to the document
  doc.addNamedRange('Range1',range1);
  var range2=doc.newRange().addElement(textElem2).build();
  // Name the range and append it to the document
  doc.addNamedRange('Range2',range2);
}

After execution the document is appended with a new paragraph containing three words separated by two blank inline PNGs

{NotNamed}{NamedText1}{NamedText2}

And the execution of my getTextNamedRange() shows the desired behaviour:

Text with namedRange (Range1) : "{NamedText1}"
Text with namedRange (Range2) : "{NamedText2}"

Important notices:

  • Named ranges must be applied alltogether after the last Text insertion, otherwise, previous NamedRanges adopt new insertions and appendings.

  • InlineImages can't be removed from parent Paragraph using image.removeFromParent() because this causes the Text elements (and the NamedRange) to be merged again