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

ぃ、小莉子 提交于 2020-03-04 05:53:09

问题


I have XML elements linked to the root element of the document and there are some elements that should be grouped and then linked to the design document, So I would like to know how I can create a virtual group and add the elements to a parent tag that will, in turn, be the child of the parent in InDesign scripting

Existing :

-EL1
-EL2
-EL3
-EL4
-EL5

Expected:

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

Where EL is the parent and EL2,EL3,EL4 are the child elements.


回答1:


Setup a demo .indd

To provide some context for this answer, and to aid explanation and demonstration, firstly import the following XML document into a new inDesign document:

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>

To import the sample.xml into a new document follow these steps:

  1. Create a new InDesign document.
  2. Select View > Structure > Show Structure from the Menu Bar to reveal the XML Structure panel.
  3. In the XML Structure panel select the default Root XML element.
  4. In the XML Structure panel choose Import XMl from the dropdown menu.
  5. Locate the aforementioned sample.xml file and open it.
  6. Finally save the document

Note: Throughout this answer we'll need to revert the .indd file back to this starting state - so please ensure you save it.

The documents XML tree should now be structured as follows:

Initial XML tree structure:

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

As you can see it's very similar to what you described in your question, however there's just a few more elements, namely; EL6, EL7,EL8, EL9.


Solution A

example-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));

If we run the code provided in example-a.jsx (above) it will restructure the XML tree to the following:

XML tree structure after:

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

Note: Before proceeding to Solution B (below) please revert the demo inDesign document to it's original state. Select File > Revert from the Menu Bar.


Solution B

If you intend to restructure the XML tree often, i.e. you intent to move various child elements to a new parent element often, then I would consider utilizing a helper function such as the childElementsToNewParent function shown below. By doing this you can provide a simpler interface for performing this task.

example-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;
}

If we run the code provided in example-a.jsx (above) it will restructure the XML tree to the same resultant structure as shown in the "XML tree structure after" section of "Solution A".

Note: Don't revert the indd document just yet - leave it as-is for the following usage example to be relevant.

Another example of using example-b.jsx

Let's say we now want to restructure the tree from the previously shown "XML tree structure after" state to the following state:

The next desired XML tree structure:

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

To achieve this structure we need to replace the the following line:

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

...that is currently defined in example-b.jsx, i.e. the line that invokes the childElementsToNewParent function, with the following two lines instead:

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

This time we are essentially:

  1. Obtaining a reference to the EL element (as that's the parent element containing the child elements we want to move) and assigning it to a variable named parentElement.

  2. Invoking the childElementsToNewParent function with the following arguments:

    • doc - A reference to the document that we want to change its XML structure.
    • parentElement - The variable whose value is a reference to the EL element.
    • 2 - The first position of the child element that we want to move.
    • 3 - The last position of the child element that we want to move.
    • { tagName: 'section' } - An options object which includes a key/property named tagName with a value of section.

Note: This time we passed an optional options object that specified the name for the newly created parent element, namely section. When invoking the childElementsToNewParent function without providing a value for tagName in the the options object we infer the name for the new parent element by using the first two characters of the first child element to be move. As per your comment:

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

Additional note

You'll have noticed in that last example we obtained a reference to the EL element, (i.e. the reference to the parent element containing the child elements that we wanted to move), by utilizing the following notation;

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

... which can get get rather long when wanting to obtain a reference to a deeply nested element. You'll end up doing something like this:

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

I prefer utilizing the evaluateXPathExpression method instead as this allows us to match the element using an xpath expression. For example to obtain the reference to the EL element we could do this instead

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

As you can see, we're also utilizing the evaluateXPathExpression method in the body of the childElementsToNewParent function to obtain a reference to the child elements that we want to move:

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

var childrenToMove = parent.evaluateXPathExpression(xPath);

This expression utilizes XPath's position() function to find the child elements within the given positional range. It's essentially the positional range you define when passing the from and to arguments to the childElementsToNewParent function.



来源:https://stackoverflow.com/questions/60096112/how-to-group-xml-elements-in-in-design-by-adding-them-to-a-parent-tag

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!