Как сгруппировать XML-элементы в дизайне, добавив их в родительский тег

У меня есть элементы XML, связанные с корневым элементом документа, и есть некоторые элементы, которые следует сгруппировать, а затем связать с проектным документом. Поэтому я хотел бы знать, как создать виртуальную группу и добавить элементы в родительский тег. который, в свою очередь, будет дочерним элементом родительского элемента в сценариях InDesign.

Существующий :

-EL1
-EL2
-EL3
-EL4
-EL5

Ожидал:

-EL1
-EL
--EL2
--EL3
--EL4
-EL5

Где EL — родительский элемент, а EL2,EL3,EL4 — дочерний элемент.


person Sundeep Pidugu    schedule 06.02.2020    source источник
comment
Каковы критерии выбора элементов, которые должны быть добавлены к новому родительскому элементу/тегу - основано ли это на их имени элемента/тега, их положении или чем-то еще? Каковы критерии для именования вновь созданного родительского элемента/тега?   -  person RobC    schedule 07.02.2020
comment
например, это могут быть элементы от 2 до 4, а затем имя родителя — это первые два символа выбранных элементов.   -  person Sundeep Pidugu    schedule 08.02.2020


Ответы (1)


Настройте демонстрационный .indd

Чтобы предоставить некоторый контекст для этого ответа, а также для облегчения объяснения и демонстрации, сначала импортируйте следующий XML-документ в новый документ inDesign:

sample.xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Root>
  <EL1>Para 1</EL1>
  <EL2>Para 2</EL2>

  <EL3>Para 3</EL3>

  <EL4>Para 4</EL4>

  <EL5>Para 5</EL5>
  <EL6>Para 6</EL6>
  <EL7>Para 7</EL7>
  <EL8>Para 8</EL8>
  <EL9>Para 9</EL9>
</Root>

Чтобы импортировать sample.xml в новый документ, выполните следующие действия:

  1. Создайте новый документ InDesign.
  2. Выберите View > Structure > Show Structure в строке меню, чтобы открыть панель Структура XML.
  3. На панели Структура XML выберите элемент Root XML по умолчанию.
  4. На панели Структура XML выберите Import XMl в раскрывающемся меню.
  5. Найдите вышеупомянутый файл sample.xml и откройте его.
  6. Наконец, сохраните документ

Примечание. В этом ответе нам потребуется вернуть файл .indd обратно в исходное состояние, поэтому обязательно сохраните его.

XML-дерево документов теперь должно быть структурировано следующим образом:

Исходная древовидная структура XML:

Root
├── EL1
├── EL2
├── EL3
├── EL4
├── EL5
├── EL6
├── EL7
├── EL8
└── EL9

Как видите, это очень похоже на то, что вы описали в своем вопросе, однако есть еще несколько элементов, а именно; EL6, EL7, EL8, EL9.


Решение А

пример-a.jsx

#target indesign

// 1. Obtain a reference to the active document.
var doc = app.activeDocument;

// 2. Obtain a reference to the root element 
var root = doc.xmlElements.item(0);

// 3. Create a new tag
var newParentTag = doc.xmlTags.add("EL");

// 4. Create a new element node
var parentNode = root.xmlElements.add(newParentTag.name);

// 5. Change the position of the newly created element node
parentNode.move(LocationOptions.before, root.xmlElements.item(1));

// 6. Move elements to be children of the newly created parent element.
root.xmlElements.item(4).move(LocationOptions.atBeginning, root.xmlElements.item(1));
root.xmlElements.item(3).move(LocationOptions.atBeginning, root.xmlElements.item(1));
root.xmlElements.item(2).move(LocationOptions.atBeginning, root.xmlElements.item(1));

Если мы запустим код, представленный в example-a.jsx (выше), он реструктурирует дерево XML следующим образом:

Дерево XML после:

Root
├── EL1
├── EL
│   ├── EL2
│   ├── EL3
│   └── EL4
├── EL5
├── EL6
├── EL7
├── EL8
└── EL9

Примечание. Прежде чем переходить к решению B (ниже), верните демонстрационный документ inDesign в исходное состояние. Выберите File > Revert в строке меню.


Решение Б

Если вы намерены часто реструктурировать XML-дерево, то есть вы намерены часто перемещать различные дочерние элементы в новый родительский элемент, то я бы рассмотрел возможность использования вспомогательной функции, такой как функция childElementsToNewParent, показанная ниже. Делая это, вы можете предоставить более простой интерфейс для выполнения этой задачи.

пример-b.jsx

#target indesign

$.level=0;

//------------------------------------------------------------------------------
// Usage
//------------------------------------------------------------------------------

// 1. Obtain a reference to the active document.
var doc = app.activeDocument;


// 2. Obtain a reference to the root element
var rootElement = doc.xmlElements.item(0);


// 3. Restructure the XML tree.
childElementsToNewParent(doc, rootElement, 2, 4);


//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

/**
 * Moves child element(s) at a given position within a given parent element to
 * a new parent element.
 *
 * @param {Object} doc - A document reference for changing its XML structure.
 * @param {Object} parent - The parent XMlElement whose children need to move.
 * @param {Number} from - The position of the first child element to move.
 * @param {Number} to - The position of the last child element to move.
 * @param {Object} options - The configuration options.
 * @param {String} [options.tagName=undefined] - A custom name for the newly
 *  created parent XML element.
 */
function childElementsToNewParent(doc, parent, from, to, options) {

  // Default options
  var opts = {
    tagName: undefined
  }

  // Override the default opts with any user defined options.
  opts = assign(options, opts);

  var xPath = '*[position() >= ' + from + ' and position() <= ' + to + ']';

  var childrenToMove = parent.evaluateXPathExpression(xPath);

  // XMLElements matched by the `evaluateXPathExpression` method are returned
  // in any order. We sort each element object in the array by it positional
  // index to ensure that when we move them to a new parent element their
  // positional order is preserved as-is.
  childrenToMove = sortArrayOfObjects(childrenToMove, 'index');

  var firstChildToMove = childrenToMove[0];
  var firstChildToMoveIndex = firstChildToMove.index;

  var xmlTagName = opts.tagName
      ? opts.tagName
      : firstChildToMove.markupTag.name.substring(0, 2);

  createXmlTag(doc, xmlTagName);

  // Move the newly created parent XMLElement to
  // before the first child element to be moved.
  parent.xmlElements.add(xmlTagName).move(
    LocationOptions.before,
    parent.xmlElements.item(firstChildToMoveIndex)
  );

  // Move each the matched child XMLElement(s) into their new parent XMLElement.
  for (var i = 0, max = childrenToMove.length; i < max; i++) {
    childrenToMove[i].move(
      LocationOptions.atEnd,
      parent.xmlElements.item(firstChildToMoveIndex)
    );
  }
}

/**
 * Enumerates own properties of a 'source' object and copies them to a 'target'
 * object. Properties in the 'target' object are overwritten by properties in
 * the 'source' object if they have the same key.
 *
 * @param {Object} source - The object containing the properties to apply.
 * @param {Object} target - The object to apply the source object properties to.
 * @returns {Object} - The target object.
 */
function assign(source, target) {
  if (typeof source === 'object') {
    for (key in source) {
      if (source.hasOwnProperty(key) && target.hasOwnProperty(key)) {
        target[key] = source[key];
      }
    }
  }
  return target;
}

/**
 * Sorts array of objects by value of property name in ascending order.
 *
 * @param {Array} arr - The array of objects to sort.
 * @param {String} prop - The name of the object property to sort by.
 * @returns {Array} - Array of objects sorted by value of property name.
 */
function sortArrayOfObjects(arr, prop) {
  return arr.sort(function sortByPropertyValue(a, b) {
    if (a[prop] < b[prop]) {
      return -1;
    }
    if (a[prop] > b[prop]) {
      return 1;
    }
    return 0;
  });
}

/**
 * Creates a new XML tag if the given name does not already exist.
 *
 * @param {String} tagName - The name of the XML tag.
 * @param {Object} doc - A reference to the document to add the XMl tag to.
 */
function createXmlTag(doc, tagName) {
  var hasTag = inArray(tagName, doc.xmlTags.everyItem().name);
  if (! hasTag) {
    doc.xmlTags.add(tagName);
  }
}


/**
 * Determines whether an array includes a certain value among its elements.
 *
 * @param {String} valueToFind - The value to search for.
 * @param {Array} arrayToSearch - The array to search in.
 * @returns {Boolean} true if valueToFind is found within the array.
 */
function inArray(valueToFind, arrayToSearch) {
  for (var i = 0, max = arrayToSearch.length; i < max; i++) {
    if (arrayToSearch[i] === valueToFind) {
      return true;
    }
  }
  return false;
}

Если мы запустим код, представленный в example-a.jsx (выше), он реструктурирует дерево XML до такой же результирующей структуры, как показано в разделе "Структура дерева XML после" раздела "Решение A".

Примечание. Пока не меняйте документ indd — оставьте его как есть, чтобы следующий пример использования был актуален.

Другой пример использования example-b.jsx

Допустим, теперь мы хотим реструктурировать дерево из ранее показанного состояния "Структура дерева XML после" в следующее состояние:

Следующая желаемая древовидная структура XML:

Root
├── EL1
├── EL
│   ├── EL2
│   └── section
│       ├── EL3
│       └── EL4
├── EL5
├── EL6
├── EL7
├── EL8
└── EL9

Чтобы получить эту структуру, нам нужно заменить следующую строку:

// 3. Restructure the XML tree.
childElementsToNewParent(doc, rootElement, 2, 4);

...которая в настоящее время определена в example-b.jsx, то есть в строке, которая вызывает функцию childElementsToNewParent, со следующими двумя строками:

var parentElement = rootElement.xmlElements.item(1);
childElementsToNewParent(doc, parentElement, 2, 3, { tagName: 'section' });

На этот раз мы по существу:

  1. Получение ссылки на элемент EL (поскольку это родительский элемент, содержащий дочерние элементы, которые мы хотим переместить) и присвоение его переменной с именем parentElement.

  2. Вызов функции childElementsToNewParent со следующими аргументами:

    • doc - A reference to the document that we want to change its XML structure.
    • parentElement — переменная, значение которой является ссылкой на элемент EL.
    • 2 — первая позиция дочернего элемента, который мы хотим переместить.
    • 3 — Последняя позиция дочернего элемента, который мы хотим переместить.
    • { tagName: 'section' } — объект options, который включает в себя ключ/свойство с именем tagName со значением section.

Примечание. На этот раз мы передали необязательный объект options, в котором указано имя вновь созданного родительского элемента, а именно section. При вызове функции childElementsToNewParent без предоставления значения для tagName в объекте options мы выводим имя для нового родительского элемента, используя первые два символа первого дочернего элемента, который нужно переместить. Согласно вашему комментарию:

... родительское имя - это первые два символа выбранных элементов

Дополнительное примечание

Вы заметили, что в последнем примере мы получили ссылку на элемент EL (т. е. ссылку на родительский элемент, содержащий дочерние элементы, которые мы хотели переместить), используя следующую нотацию;

var parentElement = rootElement.xmlElements.item(1);

... который может стать довольно длинным, если вы хотите получить ссылку на глубоко вложенный элемент. В итоге вы сделаете что-то вроде этого:

var parentElement = rootElement.xmlElements.item(1).xmlElements.item(3).xmlElements.item(1) ....

Вместо этого я предпочитаю использовать метод evaluateXPathExpression, так как это позволяет нам сопоставлять элемент, используя выражение xpath. Например, чтобы получить ссылку на элемент EL, мы могли бы сделать это вместо этого

var parentElement = rootElement.evaluateXPathExpression('EL')[0];

Как видите, мы также используем метод evaluateXPathExpression в теле функции childElementsToNewParent для получения ссылки на дочерние элементы, которые мы хотим переместить:

var xPath = '*[position() >= ' + from + ' and position() <= ' + to + ']';

var childrenToMove = parent.evaluateXPathExpression(xPath);

Это выражение использует функцию XPath position() для найти дочерние элементы в заданном позиционном диапазоне. По сути, это позиционный диапазон, который вы определяете при передаче аргументов from и to функции childElementsToNewParent.

person RobC    schedule 10.02.2020
comment
Я пробовал второй подход, и я хотел бы знать, как сохранить разрывы абзаца и стили после связывания/группировки (как вы сделали в примере) дочерних элементов с родительским элементом - person Sundeep Pidugu; 12.02.2020
comment
Когда вы помечаете абзацы с помощью inDesign, вам необходимо убедиться, что скрытый символ разрыва абзаца () находится внутри закрывающего тега, и тогда вы сохраните разрывы абзаца и стиль при реструктуризации дерева XML. Например, если сначала выбрать TypeShow Hidden Characters в строке меню, а затем EditEdit in Layout в строке меню. Вы должны убедиться, что видите скрытый символ , расположенный примерно так: <EL2>Some text¶</EL2>, а не этот ‹EL2›Some text‹/EL2›¶` перед запуском скрипта. Если ваша разметка указывает на последнее, это становится очень... - person RobC; 12.02.2020
comment
сложно автоматизировать. Вам потребуется использовать в коде метод insertTextAsContent, что-то вроде этого : childrenToMove[i].insertTextAsContent('\r', XMLElementPosition.ELEMENT_END) для добавления новых разрывов абзаца. Но, как я уже сказал, определить правильную логику, когда и не вставлять новые разрывы абзаца, становится очень сложно. Кстати. вы заметите, что inDesign теряет разрывы абзацев и стили, когда ваша разметка равна <EL2>Some text</EL2>¶, и вы выполняете эту реструктуризацию вручную. - person RobC; 12.02.2020