问题
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:
- Create a new InDesign document.
- Select
View
>Structure
>Show Structure
from the Menu Bar to reveal the XML Structure panel. - In the XML Structure panel select the default
Root
XML element. - In the XML Structure panel choose
Import XMl
from the dropdown menu. - Locate the aforementioned
sample.xml
file and open it. - 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:
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 namedparentElement
.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 theEL
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' }
- Anoptions
object which includes a key/property namedtagName
with a value ofsection
.
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