如何通过将设计中的 XML 个元素添加到父标签来对它们进行分组

How to group XML elements in in-design by adding them to a parent tag

我有 XML 元素链接到文档的根元素,有些元素应该分组然后链接到设计文档,所以我想知道如何创建虚拟将元素分组并将其添加到父标签,而父标签又将成为 InDesign 脚本中父标签的子标签

现有:

-EL1
-EL2
-EL3
-EL4
-EL5

预计:

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

其中 EL 是父元素,EL2,EL3,EL4 是子元素。

设置演示 .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. Select View > Structure > Show Structure 从菜单栏显示 XML 结构面板.
  3. XML结构面板select默认RootXML元素。
  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

示例-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(下方)之前,请将 demo inDesign 文档恢复为原始状态. Select File > Revert 从菜单栏。


解决方案 B

如果您打算经常重组 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 树重组为与 [= 中所示相同的结果结构217=] "Solution A".

部分

注意:暂时不要还原 indd 文档 - 保持原样,以便与以下使用示例相关。

另一个使用example-b.jsx的例子

假设我们现在要将树从先前显示的 "XML tree structure after" 状态重组为以下状态:

下一个想要的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 - 对我们要更改其 XML 结构的文档的引用。
    • parentElement - 其值是对 EL 元素的引用的变量。
    • 2 - 我们要移动的子元素的第一个位置。
    • 3 - 我们要移动的子元素的最后位置。
    • { tagName: 'section' } - 一个 options 对象,其中包含名为 tagName 的 key/property,其值为 section.

注意:这次我们传递了一个可选的options对象,它指定了新创建的父元素的名称,即section。当在 options 对象中调用 childElementsToNewParent 函数而不为 tagName 提供值时,我们通过使用第一个子元素的前两个字符来推断新父元素的名称移动。根据您的评论:

... the parent name is the first two characters of the elements selected

补充说明

您会注意到,在最后一个示例中,我们获得了对 EL 元素的引用(即对包含我们要移动的子元素的父元素的引用),方法如下:符号;

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

... 当想要获取对深层嵌套元素的引用时,它可能会变得相当长。你最终会做这样的事情:

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

我更喜欢使用 evaluateXPathExpression method instead as this allows us to match the element using an 表达式。例如,要获取对 EL 元素的引用,我们可以这样做

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

如您所见,我们还在 childElementsToNewParent 函数体中使用 evaluateXPathExpression 方法来获取对我们要移动的子元素的引用:

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

var childrenToMove = parent.evaluateXPathExpression(xPath);

这个表达式利用 XPath 的 position() 函数来查找给定位置范围内的子元素。它本质上是您在将 fromto 参数传递给 childElementsToNewParent 函数时定义的位置范围。