@elbstack/xlsx-populate
Version:
Excel XLSX parser/generator written in JavaScript with Node.js and browser support, jQuery/d3-style method chaining, and a focus on keeping existing workbook features and styles in tact.
196 lines (177 loc) • 6.16 kB
JavaScript
;
const _ = require("lodash");
/**
* XML query methods.
* @private
*/
module.exports = {
/**
* Append a child to the node.
* @param {{}} node - The parent node.
* @param {{}} child - The child node.
* @returns {undefined}
*/
appendChild(node, child) {
if (!node.children) node.children = [];
node.children.push(child);
},
/**
* Append a child if one with the given name is not found.
* @param {{}} node - The parent node.
* @param {string} name - The child node name.
* @returns {{}} The child.
*/
appendChildIfNotFound(node, name) {
let child = this.findChild(node, name);
if (!child) {
child = { name, attributes: {}, children: [] };
this.appendChild(node, child);
}
return child;
},
/**
* Find a child with the given name.
* @param {{}} node - The parent node.
* @param {string} name - The name to find.
* @returns {undefined|{}} The child if found.
*/
findChild(node, name) {
return _.find(node.children, { name });
},
/**
* Get an attribute from a child node.
* @param {{}} node - The parent node.
* @param {string} name - The name of the child node.
* @param {string} attribute - The name of the attribute.
* @returns {undefined|*} The value of the attribute if found.
*/
getChildAttribute(node, name, attribute) {
const child = this.findChild(node, name);
if (child) return child.attributes && child.attributes[attribute];
},
/**
* Returns a value indicating whether the node has a child with the given name.
* @param {{}} node - The parent node.
* @param {string} name - The name of the child node.
* @returns {boolean} True if found, false otherwise.
*/
hasChild(node, name) {
return _.some(node.children, { name });
},
/**
* Insert the child after the specified node.
* @param {{}} node - The parent node.
* @param {{}} child - The child node.
* @param {{}} after - The node to insert after.
* @returns {undefined}
*/
insertAfter(node, child, after) {
if (!node.children) node.children = [];
const index = node.children.indexOf(after);
node.children.splice(index + 1, 0, child);
},
/**
* Insert the child before the specified node.
* @param {{}} node - The parent node.
* @param {{}} child - The child node.
* @param {{}} before - The node to insert before.
* @returns {undefined}
*/
insertBefore(node, child, before) {
if (!node.children) node.children = [];
const index = node.children.indexOf(before);
node.children.splice(index, 0, child);
},
/**
* Insert a child node in the correct order.
* @param {{}} node - The parent node.
* @param {{}} child - The child node.
* @param {Array.<string>} nodeOrder - The order of the node names.
* @returns {undefined}
*/
insertInOrder(node, child, nodeOrder) {
const childIndex = nodeOrder.indexOf(child.name);
if (node.children && childIndex >= 0) {
for (let i = childIndex + 1; i < nodeOrder.length; i++) {
const sibling = this.findChild(node, nodeOrder[i]);
if (sibling) {
this.insertBefore(node, child, sibling);
return;
}
}
}
this.appendChild(node, child);
},
/**
* Check if the node is empty (no attributes and no children).
* @param {{}} node - The node.
* @returns {boolean} True if empty, false otherwise.
*/
isEmpty(node) {
return _.isEmpty(node.children) && _.isEmpty(node.attributes);
},
/**
* Remove a child node.
* @param {{}} node - The parent node.
* @param {string|{}} child - The child node or name of node.
* @returns {undefined}
*/
removeChild(node, child) {
if (!node.children) return;
if (typeof child === 'string') {
_.remove(node.children, { name: child });
} else {
const index = node.children.indexOf(child);
if (index >= 0) node.children.splice(index, 1);
}
},
/**
* Set/unset the attributes on the node.
* @param {{}} node - The node.
* @param {{}} attributes - The attributes to set.
* @returns {undefined}
*/
setAttributes(node, attributes) {
_.forOwn(attributes, (value, attribute) => {
if (_.isNil(value)) {
if (node.attributes) delete node.attributes[attribute];
} else {
if (!node.attributes) node.attributes = {};
node.attributes[attribute] = value;
}
});
},
/**
* Set attributes on a child node, creating the child if necessary.
* @param {{}} node - The parent node.
* @param {string} name - The name of the child node.
* @param {{}} attributes - The attributes to set.
* @returns {{}} The child.
*/
setChildAttributes(node, name, attributes) {
let child = this.findChild(node, name);
_.forOwn(attributes, (value, attribute) => {
if (_.isNil(value)) {
if (child && child.attributes) delete child.attributes[attribute];
} else {
if (!child) {
child = { name, attributes: {}, children: [] };
this.appendChild(node, child);
}
if (!child.attributes) child.attributes = {};
child.attributes[attribute] = value;
}
});
return child;
},
/**
* Remove the child node if empty.
* @param {{}} node - The parent node.
* @param {string|{}} child - The child or name of child node.
* @returns {undefined}
*/
removeChildIfEmpty(node, child) {
if (typeof child === 'string') child = this.findChild(node, child);
if (child && this.isEmpty(child)) this.removeChild(node, child);
}
};