Получить все вложенные текстовые элементы в Google Doc, используя RangeElements в Selection - PullRequest
0 голосов
/ 20 февраля 2019

В документе, аналогичном приведенному выше, я могу получить все абзацы со следующим кодом:

var paras = body.getParagraphs();

Обратите внимание, что приведенный выше код не только возвращает абзацы верхнего уровня, но также возвращает все абзацы подуровня внутри ListItem s, Table s и т. Д.

Как я могу сделать то же самое ввыбранный диапазон?Следующий код возвращает только элементы верхнего уровня.

const selection = DocumentApp.getActiveDocument().getSelection();
var rangeElements = selection.getRangeElements();

Например, приведенная выше таблица содержит 9 непустых абзацев, и я хотел бы обработать их один за другим, если они выбраны.

Что я пытаюсь сделатьФункция достижение аналогична переводу текста в выделенном фрагменте путем максимально возможного сохранения форматирования, таблиц, элементов списка и т. д.

1 Ответ

0 голосов
/ 20 февраля 2019

.getRangeElements() возвращает массив RangeElements .Элемент range - это объект-обертка, который используется, чтобы помочь нам справиться с частичным выбором.Мы можем вызвать .getElement() для каждого элемента в этом массиве, чтобы получить объект Element , который является очень универсальным объектом, который может представлять практически любой фрагмент Google Doc.Elements имеет метод .getType(), который возвращает ElementType enum;и их много !


Давайте использовать то, что нам известно, чтобы увидеть, какие возможные типы есть в Google Doc (я создал )похож на ваш (img) в качестве примера):

function selectionHasWhichTypes() {
  var doc = DocumentApp.getActiveDocument();
  var selection = doc.getSelection();
  var rangeElems = selection.getRangeElements();

  rangeElems.forEach(function(elem){
    var elem = elem.getElement();

    Logger.log(elem.getType());
  });
}

//Logger OUTPUT:
PARAGRAPH
PARAGRAPH
PARAGRAPH
PARAGRAPH
PARAGRAPH
LIST_ITEM
LIST_ITEM
LIST_ITEM
PARAGRAPH
PARAGRAPH
PARAGRAPH
TABLE
PARAGRAPH

А-а-а! Похоже, нам нужно иметь дело только с PARAGRAPH , LIST_ITEM и TABLE ElementTypes на данный момент , но давайте также будем помнить об их дочерних элементах (мы выясним, что это 3 из 5что может иметь детей).Это похоже на работу для рекурсивной функции , которая будет постоянно копаться в дочерних элементах, пока мы не найдем и не разберемся со всеми ними.


Так что давайте попробуем это. Следующая часть может показаться запутанной, но, по сути, она находит элемент, проверяет, есть ли у него дочерние элементы, затем смотрит на них, чтобы узнать, есть ли у у них дочерние элементы, и так далее.Мы также хотим проверить, получаем ли мы new ElementTypes для работы с ...

function selectionHasWhichTypes() {
  var doc = DocumentApp.getActiveDocument();
  var selection = doc.getSelection();
  var rangeElems = selection.getRangeElements();

  rangeElems.forEach(function(elem){
    var elem = elem.getElement();

    elemsHaveWhatChildElems(elem, elem.getType());

  });
}

function elemsHaveWhatChildElems(elem, typeChain){
  var elemType = elem.getType();
  if(elemType == "TABLE" || elemType == "LIST_ITEM" || elemType == "PARAGRAPH"){ //Lets see if element is one of our basic 3. If so they could have children.
    var numChildren = elem.getNumChildren(); //How many children are there?
    if(numChildren > 0){
      for(var i = 0; i < numChildren; i++){ //Let's go through them.
        var child = elem.getChild(i);
        elemsHaveWhatChildElems(child, typeChain + "." + child.getType()); //Recursion step to look for more children.
      }
    }else{
       Logger.log(typeChain); //Let's log the chain of Parent to Child elements.
    }
  }else{
    Logger.log("*" + typeChain); //Let's mark the new elemTypeChains we have not seen.
  }
}

//Logger OUTPUT:
*PARAGRAPH.TEXT
PARAGRAPH
*PARAGRAPH.HORIZONTAL_RULE
PARAGRAPH
*PARAGRAPH.TEXT
*LIST_ITEM.TEXT
*LIST_ITEM.TEXT
*LIST_ITEM.TEXT
PARAGRAPH
*PARAGRAPH.TEXT
PARAGRAPH
*TABLE.TABLE_ROW
*TABLE.TABLE_ROW
PARAGRAPH

Хорошо, поэтому каждая строка журналацепочка стихий и их дети.У нас есть новых ElementTypes ( HORIZONTAL_RULE , TABLE_ROW и TEXT ).Если цепочка имеет значение Paragraph и не имеет дочерних элементов, это обозначается как «ПАРАГРАФ».мы можем игнорировать его , поскольку это пустая строка.Мы также можем игнорировать HORIZONTAL_RULE, поскольку , очевидно, не будет содержать текст.

Если мы получили элемент TEXT , это означает, что мы можем выполнять нашу функцию (т. Е. Для OP это был бы перевод), как мы это делали с LIST_ITEMs и PARAGRAPH.Тем не менее, нам все же приходится иметь дело с TableRow Объектами (которые регистрируются следующим образом: TABLE.TABLE_ROW).Это аналогично нашим основным 3 элементам и может использоваться с нашим if(elemType == "TABLE" || elemType == "LIST_ITEM" || elemType == "PARAGRAPH"), который меняется на if(elemType == "TABLE" || elemType == "LIST_ITEM" || elemType == "PARAGRAPH" || elemType == "TABLE_ROW").

Это дает нам еще один новый элемент в нашей цепочке; TableCell (журналы как: TABLE.TABLE_ROW.TABLE_CELL), которые мы можем снова добавить к нашему заявлению if, сделав это: if(elemType == "TABLE" || elemType == "LIST_ITEM" || elemType == "PARAGRAPH" || elemType == "TABLE_ROW" || elemType == "TABLE_CELL")


Времячтобы увидеть, что происходит , когда мы имеем дело с Table ElementTypes.

function selectionHasWhichtypeChains() {
  var doc = DocumentApp.getActiveDocument();
  var selection = doc.getSelection();
  var rangeElems = selection.getRangeElements();

  rangeElems.forEach(function(elem){
    var elem = elem.getElement();

    elemsHaveWhatChildElems(elem, elem.getType());

  });
}

function elemsHaveWhatChildElems(elem, typeChain){
  var elemType = elem.getType();
  if(elemType == "TABLE" || elemType == "LIST_ITEM" || elemType == "PARAGRAPH" || elemType == "TABLE_ROW" || elemType == "TABLE_CELL"){ //Lets see if element is one of our basic 5 if so they could have children.
    var numChildren = elem.getNumChildren(); //How many children are there?
    if(numChildren > 0){
      for(var i = 0; i < numChildren; i++){ //Let's go through them.
        var child = elem.getChild(i);
        elemsHaveWhatChildElems(child, typeChain + "." + child.getType()); //Recursion step to look for more children.
      }
    }else{
       Logger.log(typeChain); //Let's log the chain of Parent to Child elements.
    }
  }else{
    Logger.log("*" + typeChain); //Let's mark the new elemTypeChains we have not seen.
  }
}

//Logger OUTPUT:
*PARAGRAPH.TEXT
PARAGRAPH
*PARAGRAPH.HORIZONTAL_RULE
PARAGRAPH
*PARAGRAPH.TEXT
*LIST_ITEM.TEXT
*LIST_ITEM.TEXT
*LIST_ITEM.TEXT
PARAGRAPH
*PARAGRAPH.TEXT
PARAGRAPH
*TABLE.TABLE_ROW.TABLE_CELL.PARAGRAPH.TEXT
*TABLE.TABLE_ROW.TABLE_CELL.TABLE.TABLE_ROW.TABLE_CELL.PARAGRAPH.TEXT
*TABLE.TABLE_ROW.TABLE_CELL.TABLE.TABLE_ROW.TABLE_CELL.PARAGRAPH.TEXT
*TABLE.TABLE_ROW.TABLE_CELL.TABLE.TABLE_ROW.TABLE_CELL.PARAGRAPH.TEXT
*TABLE.TABLE_ROW.TABLE_CELL.TABLE.TABLE_ROW.TABLE_CELL.PARAGRAPH.TEXT
TABLE.TABLE_ROW.TABLE_CELL.PARAGRAPH
*TABLE.TABLE_ROW.TABLE_CELL.PARAGRAPH.TEXT
*TABLE.TABLE_ROW.TABLE_CELL.PARAGRAPH.HORIZONTAL_RULE
*TABLE.TABLE_ROW.TABLE_CELL.PARAGRAPH.TEXT
*TABLE.TABLE_ROW.TABLE_CELL.PARAGRAPH.TEXT
PARAGRAPH

Это замечательно! Мы достигли глубины каждого родительского элемента идостиг либо a текстового элемента , либо пустого абзаца !Отсюда мы можем немного изменить наш код, чтобы добавить функции, которые мы хотим выполнять при сохранении структуры документа:

function myFunction() {
  var doc = DocumentApp.getActiveDocument();
  var selection = doc.getSelection();
  var rangeElems = selection.getRangeElements(); //Get main Elements of selection

  rangeElems.forEach(function(elem){ //Let's rn through each to find ALL of their children.
    var elem = elem.getElement(); //We have an ElementType. Let's get the full element.
    getNestedTextElements(elem, elem.getType()); //Time to go down the rabbit hole.
  });
}

function getNestedTextElements(elem, typeChain){
  var elemType = elem.getType();
  if(elemType == "TABLE" || elemType == "LIST_ITEM" || elemType == "PARAGRAPH" || elemType == "TABLE_ROW" || elemType == "TABLE_CELL"){ //Lets see if element is one of our basic 5, if so they could have children.
    var numChildren = elem.getNumChildren(); //How many children are there?
    if(numChildren > 0){
      for(var i = 0; i < numChildren; i++){ //Let's go through them.
        var child = elem.getChild(i);
        getNestedTextElements(child, typeChain + "." + child.getType()); //Recursion step to look for more children.
      }
    }
  }else if(elemType == "TEXT"){
    //THIS IS WHERE WE CAN PERFORM OUR OPERATIONS ON THE TEXT ELEMENT
    var text = elem.getText();


  }else{
    Logger.log("*" + typeChain); //Let's log the new elem we dont deal with now - for future proofing.
  }
}

BOOM!Готово. Я знаю, что это действительно длинный пост, но я разбил каждый раздел решения на части, чтобы помочь новым кодировщикам Apps Script понять структуру Выделения (и, я полагаю, Тела документа) и какизменить его, когда структура очень сложна (много вложенных элементов). Я действительно надеюсь, что это было полезно .Если кто-нибудь увидит кусок, который можно улучшить, дайте мне знать.


В качестве примечания к OP: Имейте в виду, что это не обязательно относится к частичному выбору элемента, но этоможно легко разобраться, немного изменив первую функцию для проверки isPartial() на RangeElement .

...