UNPKG

cordova-plugin-mfp

Version:

IBM MobileFirst Platform Foundation Cordova Plugin

267 lines (229 loc) 8.77 kB
/* * * Copyright 2013 Anis Kadri * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * */ /* jshint sub:true, laxcomma:true */ /** * contains XML utility functions, some of which are specific to elementtree */ var fs = require('fs') , path = require('path') , _ = require('underscore') , et = require('elementtree') ; module.exports = { // compare two et.XML nodes, see if they match // compares tagName, text, attributes and children (recursively) equalNodes: function(one, two) { if (one.tag != two.tag) { return false; } else if (one.text.trim() != two.text.trim()) { return false; } else if (one._children.length != two._children.length) { return false; } var oneAttribKeys = Object.keys(one.attrib), twoAttribKeys = Object.keys(two.attrib), i = 0, attribName; if (oneAttribKeys.length != twoAttribKeys.length) { return false; } for (i; i < oneAttribKeys.length; i++) { attribName = oneAttribKeys[i]; if (one.attrib[attribName] != two.attrib[attribName]) { return false; } } for (i; i < one._children.length; i++) { if (!module.exports.equalNodes(one._children[i], two._children[i])) { return false; } } return true; }, // adds node to doc at selector, creating parent if it doesn't exist graftXML: function(doc, nodes, selector, after) { var parent = resolveParent(doc, selector); if (!parent) { //Try to create the parent recursively if necessary try { var parentToCreate = et.XML('<' + path.basename(selector) + '>'), parentSelector = path.dirname(selector); this.graftXML(doc, [parentToCreate], parentSelector); } catch (e) { return false; } parent = resolveParent(doc, selector); if (!parent) return false; } nodes.forEach(function (node) { // check if child is unique first if (uniqueChild(node, parent)) { var children = parent.getchildren(); var insertIdx = after ? findInsertIdx(children, after) : children.length; //TODO: replace with parent.insert after the bug in ElementTree is fixed parent.getchildren().splice(insertIdx, 0, node); } }); return true; }, // removes node from doc at selector pruneXML: function(doc, nodes, selector) { var parent = resolveParent(doc, selector); if (!parent) return false; nodes.forEach(function (node) { var matchingKid = null; if ((matchingKid = findChild(node, parent)) !== null) { // stupid elementtree takes an index argument it doesn't use // and does not conform to the python lib parent.remove(matchingKid); } }); return true; }, parseElementtreeSync: function (filename) { var contents = fs.readFileSync(filename, 'utf-8'); if(contents) { //Windows is the BOM. Skip the Byte Order Mark. contents = contents.substring(contents.indexOf('<')); } return new et.ElementTree(et.XML(contents)); } }; function findChild(node, parent) { var matchingKids = parent.findall(node.tag) , i, j; for (i = 0, j = matchingKids.length ; i < j ; i++) { if (module.exports.equalNodes(node, matchingKids[i])) { return matchingKids[i]; } } return null; } function uniqueChild(node, parent) { var matchingKids = parent.findall(node.tag) , i = 0; if (matchingKids.length === 0) { return true; } else { for (i; i < matchingKids.length; i++) { if (module.exports.equalNodes(node, matchingKids[i])) { return false; } } return true; } } var ROOT = /^\/([^\/]*)/, ABSOLUTE = /^\/([^\/]*)\/(.*)/; function resolveParent(doc, selector) { var parent, tagName, subSelector; // handle absolute selector (which elementtree doesn't like) if (ROOT.test(selector)) { tagName = selector.match(ROOT)[1]; // test for wildcard "any-tag" root selector if (tagName == '*' || tagName === doc._root.tag) { parent = doc._root; // could be an absolute path, but not selecting the root if (ABSOLUTE.test(selector)) { subSelector = selector.match(ABSOLUTE)[2]; parent = parent.find(subSelector); } } else { return false; } } else { parent = doc.find(selector); } return parent; } // Find the index at which to insert an entry. After is a ;-separated priority list // of tags after which the insertion should be made. E.g. If we need to // insert an element C, and the rule is that the order of children has to be // As, Bs, Cs. After will be equal to "C;B;A". function findInsertIdx(children, after) { var childrenTags = children.map(function(child) { return child.tag; }); var afters = after.split(';'); var afterIndexes = afters.map(function(current) { return childrenTags.lastIndexOf(current); }); var foundIndex = _.find(afterIndexes, function(index) { return index != -1; }); //add to the beginning if no matching nodes are found return typeof foundIndex === 'undefined' ? 0 : foundIndex+1; } var BLACKLIST = ['platform', 'feature','plugin','engine']; var SINGLETONS = ['content', 'author']; function mergeXml(src, dest, platform, clobber) { // Do nothing for blacklisted tags. if (BLACKLIST.indexOf(src.tag) != -1) return; //Handle attributes Object.getOwnPropertyNames(src.attrib).forEach(function (attribute) { if (clobber || !dest.attrib[attribute]) { dest.attrib[attribute] = src.attrib[attribute]; } }); //Handle text if (src.text && (clobber || !dest.text)) { dest.text = src.text; } //Handle platform if (platform) { src.findall('platform[@name="' + platform + '"]').forEach(function (platformElement) { platformElement.getchildren().forEach(mergeChild); }); } //Handle children src.getchildren().forEach(mergeChild); function mergeChild (srcChild) { var srcTag = srcChild.tag, destChild = new et.Element(srcTag), foundChild, query = srcTag + '', shouldMerge = true; if (BLACKLIST.indexOf(srcTag) === -1) { if (SINGLETONS.indexOf(srcTag) !== -1) { foundChild = dest.find(query); if (foundChild) { destChild = foundChild; dest.remove(destChild); } } else { //Check for an exact match and if you find one don't add Object.getOwnPropertyNames(srcChild.attrib).forEach(function (attribute) { query += '[@' + attribute + '="' + srcChild.attrib[attribute] + '"]'; }); var foundChildren = dest.findall(query); for(var i = 0; i < foundChildren.length; i++) { foundChild = foundChildren[i]; if (foundChild && textMatch(srcChild, foundChild) && (Object.keys(srcChild.attrib).length==Object.keys(foundChild.attrib).length)) { destChild = foundChild; dest.remove(destChild); shouldMerge = false; break; } } } mergeXml(srcChild, destChild, platform, clobber && shouldMerge); dest.append(destChild); } } } // Expose for testing. module.exports.mergeXml = mergeXml; function textMatch(elm1, elm2) { var text1 = elm1.text ? elm1.text.replace(/\s+/, '') : '', text2 = elm2.text ? elm2.text.replace(/\s+/, '') : ''; return (text1 === '' || text1 === text2); }