You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by ra...@apache.org on 2020/03/14 14:22:00 UTC
[cordova-common] branch master updated: refactor(ConfigParser):
cleanup & simplify (#133)
This is an automated email from the ASF dual-hosted git repository.
raphinesse pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cordova-common.git
The following commit(s) were added to refs/heads/master by this push:
new f6e8cab refactor(ConfigParser): cleanup & simplify (#133)
f6e8cab is described below
commit f6e8cabe9b16c992417eceeb08cfd1a1458665f6
Author: Raphael von der GrĂ¼n <ra...@gmail.com>
AuthorDate: Sat Mar 14 15:21:53 2020 +0100
refactor(ConfigParser): cleanup & simplify (#133)
* refactor: ConfigParser
* make use of et API
* factor out CDV_XMLNS_URI
* shorten some setters
* simplify addPlugin
* test: extend preferences tests
* fix & DRY preferences code
- fixes setGlobalPreference to be case-insensitive
- fixes setGlobalPreference to use the last preference if multiple exist
* simplify getCordovaNamespacePrefix
* simplify access methods
* DRY & simplify getFileResources
* simplify getHookScripts
* get rid of superfluous variables
* improve JSDocs
* destructure parseElementtreeSync
* simplify getPlugin
* introduce Resource types
* Add some platform remarks to JSDocs
Co-authored-by: Erisu <el...@gmail.com>
---
spec/ConfigParser/ConfigParser.spec.js | 12 +
src/ConfigParser/ConfigParser.js | 601 ++++++++++++++++-----------------
2 files changed, 303 insertions(+), 310 deletions(-)
diff --git a/spec/ConfigParser/ConfigParser.spec.js b/spec/ConfigParser/ConfigParser.spec.js
index e3058ed..6d9541c 100644
--- a/spec/ConfigParser/ConfigParser.spec.js
+++ b/spec/ConfigParser/ConfigParser.spec.js
@@ -104,6 +104,10 @@ describe('config.xml parser', function () {
it('Test 011 : should return the value of a platform-specific preference', function () {
expect(cfg.getPreference('android-minSdkVersion', 'android')).toEqual('10');
});
+ it('returns the global preference if no platform-specific preference exists', () => {
+ expect(cfg.doc.find('./platform/preference[@name="fullscreen"]')).toBe(null);
+ expect(cfg.getPreference('fullscreen', 'atari')).toEqual('true');
+ });
it('Test 012 : should return an empty string for a non-existing preference', function () {
expect(cfg.getPreference('zimzooo!')).toEqual('');
});
@@ -115,6 +119,14 @@ describe('config.xml parser', function () {
cfg.setPreference('android-minSdkVersion', 'android', '11');
expect(cfg.getPreference('android-minSdkVersion', 'android')).toEqual('11');
});
+ it('should overwrite an existing preference', () => {
+ const children = [...cfg.doc.getroot().getchildren()];
+ expect(cfg.getPreference('fullscreen')).toEqual('true');
+
+ cfg.setPreference('fullscreen', 'false');
+ expect(cfg.getPreference('fullscreen')).toEqual('false');
+ expect(cfg.doc.getroot().getchildren()).toEqual(children);
+ });
});
describe('global preference', function () {
it('Test 013 : should return the value of a global preference', function () {
diff --git a/src/ConfigParser/ConfigParser.js b/src/ConfigParser/ConfigParser.js
index 6bbcf8d..38893dc 100644
--- a/src/ConfigParser/ConfigParser.js
+++ b/src/ConfigParser/ConfigParser.js
@@ -18,26 +18,30 @@
*/
const et = require('elementtree');
-const xml = require('../util/xml-helpers');
+const { parseElementtreeSync } = require('../util/xml-helpers');
const CordovaError = require('../CordovaError');
const fs = require('fs-extra');
const events = require('../events');
+const CDV_XMLNS_URI = 'http://cordova.apache.org/ns/1.0';
+
/** Wraps a config.xml file */
class ConfigParser {
constructor (path) {
this.path = path;
+
try {
- this.doc = xml.parseElementtreeSync(path);
+ this.doc = parseElementtreeSync(path);
this.cdvNamespacePrefix = getCordovaNamespacePrefix(this.doc);
- et.register_namespace(this.cdvNamespacePrefix, 'http://cordova.apache.org/ns/1.0');
+ et.register_namespace(this.cdvNamespacePrefix, CDV_XMLNS_URI);
} catch (e) {
events.emit('error', `Parsing ${path} failed`);
throw e;
}
- const r = this.doc.getroot();
- if (r.tag !== 'widget') {
- throw new CordovaError(`${path} has incorrect root node name (expected "widget", was "${r.tag}")`);
+
+ const root = this.doc.getroot();
+ if (root.tag !== 'widget') {
+ throw new CordovaError(`${path} has incorrect root node name (expected "widget", was "${root.tag}")`);
}
}
@@ -70,8 +74,7 @@ class ConfigParser {
}
setName (name) {
- const el = findOrCreate(this.doc, 'name');
- el.text = name;
+ findOrCreate(this.doc, 'name').text = name;
}
shortName () {
@@ -80,9 +83,7 @@ class ConfigParser {
setShortName (shortname) {
const el = findOrCreate(this.doc, 'name');
- if (!el.text) {
- el.text = shortname;
- }
+ if (!el.text) el.text = shortname;
el.attrib.short = shortname;
}
@@ -91,8 +92,7 @@ class ConfigParser {
}
setDescription (text) {
- const el = findOrCreate(this.doc, 'description');
- el.text = text;
+ findOrCreate(this.doc, 'description').text = text;
}
version () {
@@ -120,49 +120,24 @@ class ConfigParser {
}
getGlobalPreference (name) {
- return findElementAttributeValue(name, this.doc.findall('preference'));
+ return this._getPrefElem(name).attrib.value;
}
setGlobalPreference (name, value) {
- let pref = this.doc.find(`preference[@name="${name}"]`);
- if (!pref) {
- pref = new et.Element('preference');
- pref.attrib.name = name;
- this.doc.getroot().append(pref);
- }
- pref.attrib.value = value;
+ this._getPrefElem(name, { create: true }).attrib.value = value;
}
getPlatformPreference (name, platform) {
- return findElementAttributeValue(name, this.doc.findall(`./platform[@name="${platform}"]/preference`));
+ return this._getPrefElem(name, { platform }).attrib.value;
}
setPlatformPreference (name, platform, value) {
- const platformEl = this.doc.find(`./platform[@name="${platform}"]`);
- if (!platformEl) {
- throw new CordovaError(`platform does not exist (received platform: ${platform})`);
- }
- const elems = this.doc.findall(`./platform[@name="${platform}"]/preference`);
- let pref = elems.filter(elem =>
- elem.attrib.name.toLowerCase() === name.toLowerCase()
- ).pop();
-
- if (!pref) {
- pref = new et.Element('preference');
- pref.attrib.name = name;
- platformEl.append(pref);
- }
- pref.attrib.value = value;
+ this._getPrefElem(name, { platform, create: true }).attrib.value = value;
}
getPreference (name, platform) {
- let platformPreference = '';
-
- if (platform) {
- platformPreference = this.getPlatformPreference(name, platform);
- }
-
- return platformPreference || this.getGlobalPreference(name);
+ return (platform && this.getPlatformPreference(name, platform)) ||
+ this.getGlobalPreference(name);
}
setPreference (name, platform, value) {
@@ -171,85 +146,69 @@ class ConfigParser {
platform = undefined;
}
- if (platform) {
- this.setPlatformPreference(name, platform, value);
- } else {
- this.setGlobalPreference(name, value);
+ this._getPrefElem(name, { platform, create: true }).attrib.value = value;
+ }
+
+ /**
+ * Finds the element that determines the value of preference `name` within `parent`.
+ *
+ * @param {String} name preference name to search for (case insensitive)
+ * @param {{create?: boolean, platform?: string}} [opts]
+ * @return {et.Element} the last matching preference in `parent` (possibly created)
+ */
+ _getPrefElem (name, { create = false, platform } = {}) {
+ const parent = platform
+ ? this.doc.findall(`./platform[@name="${platform}"]`).pop()
+ : this.doc.getroot();
+
+ const makeElem = create ? et.SubElement.bind(null, parent) : et.Element;
+ const getFallBackElem = () => makeElem('preference', { name, value: '' });
+
+ if (!parent) {
+ if (create) {
+ throw new CordovaError(`platform does not exist (received platform: ${platform})`);
+ }
+ return getFallBackElem();
}
+
+ return parent.findall('preference')
+ .filter(elem => elem.attrib.name.toLowerCase() === name.toLowerCase())
+ .pop() || getFallBackElem();
}
/**
* Returns all resources for the platform specified.
- * @param {String} platform The platform.
- * @param {string} resourceName Type of static resources to return.
- * "icon" and "splash" currently supported.
- * @return {Array} Resources for the platform specified.
+ *
+ * @param {string} platform The platform.
+ * @param {string} resourceName Type of static resources to return.
+ * "icon" and "splash" currently supported.
+ * @return {ImageResources} Resources for the platform specified.
*/
getStaticResources (platform, resourceName) {
- const ret = [];
- let staticResources = [];
- if (platform) { // platform specific icons
- this.doc.findall(`./platform[@name="${platform}"]/${resourceName}`).forEach(elt => {
- elt.platform = platform; // mark as platform specific resource
- staticResources.push(elt);
- });
- }
- // root level resources
- staticResources = staticResources.concat(this.doc.findall(resourceName));
- // parse resource elements
- staticResources.forEach(elt => {
- const res = {};
- res.src = elt.attrib.src;
- res.target = elt.attrib.target || undefined;
- res.density = elt.attrib.density || elt.attrib[`${this.cdvNamespacePrefix}:density`] || elt.attrib['gap:density'];
- res.platform = elt.platform || null; // null means icon represents default icon (shared between platforms)
- res.width = +elt.attrib.width || undefined;
- res.height = +elt.attrib.height || undefined;
- res.background = elt.attrib.background || undefined;
- res.foreground = elt.attrib.foreground || undefined;
-
- // default icon
- if (!res.width && !res.height && !res.density) {
- ret.defaultResource = res;
- }
- ret.push(res);
+ const normalizedAttrs = ({ attrib }) => ({
+ density: attrib[`${this.cdvNamespacePrefix}:density`] ||
+ attrib['gap:density'],
+ ...attrib
});
- /**
- * Returns resource with specified width and/or height.
- * @param {number} width Width of resource.
- * @param {number} height Height of resource.
- * @return {Resource} Resource object or null if not found.
- */
- ret.getBySize = (width, height) => {
- return ret.filter(res => {
- if (!res.width && !res.height) {
- return false;
- }
- return ((!res.width || (width === res.width)) &&
- (!res.height || (height === res.height)));
- })[0] || null;
- };
-
- /**
- * Returns resource with specified density.
- * @param {string} density Density of resource.
- * @return {Resource} Resource object or null if not found.
- */
- ret.getByDensity = density => {
- return ret.filter(res => res.density === density)[0] || null;
- };
-
- /** Returns default icons */
- ret.getDefault = () => ret.defaultResource;
-
- return ret;
+ // platform specific icons
+ const platformResources = platform
+ ? this.doc.findall(`./platform[@name="${platform}"]/${resourceName}`)
+ .map(elt => new ImageResource(normalizedAttrs(elt), { platform }))
+ : [];
+
+ // root level resources
+ const commonResources = this.doc.findall(resourceName)
+ .map(elt => new ImageResource(normalizedAttrs(elt)));
+
+ return new ImageResources(...platformResources, ...commonResources);
}
/**
* Returns all icons for specific platform.
+ *
* @param {string} platform Platform name
- * @return {Resource[]} Array of icon objects.
+ * @return {ImageResources} Array of icon objects.
*/
getIcons (platform) {
return this.getStaticResources(platform, 'icon');
@@ -257,8 +216,9 @@ class ConfigParser {
/**
* Returns all splash images for specific platform.
+ *
* @param {string} platform Platform name
- * @return {Resource[]} Array of Splash objects.
+ * @return {ImageResources} Array of Splash objects.
*/
getSplashScreens (platform) {
return this.getStaticResources(platform, 'splash');
@@ -266,61 +226,40 @@ class ConfigParser {
/**
* Returns all resource-files for a specific platform.
+ *
* @param {string} platform Platform name
* @param {boolean} includeGlobal Whether to return resource-files at the
* root level.
- * @return {Resource[]} Array of resource file objects.
+ * @return {FileResource[]} Array of resource file objects.
*/
getFileResources (platform, includeGlobal) {
- let fileResources = [];
-
- if (platform) { // platform specific resources
- fileResources = this.doc.findall(`./platform[@name="${platform}"]/resource-file`).map(tag => ({
- platform,
- src: tag.attrib.src,
- target: tag.attrib.target,
- versions: tag.attrib.versions,
- deviceTarget: tag.attrib['device-target'],
- arch: tag.attrib.arch
- }));
- }
+ const platformResources = platform
+ ? this.doc.findall(`./platform[@name="${platform}"]/resource-file`)
+ : [];
- if (includeGlobal) {
- this.doc.findall('resource-file').forEach(tag => {
- fileResources.push({
- platform: platform || null,
- src: tag.attrib.src,
- target: tag.attrib.target,
- versions: tag.attrib.versions,
- deviceTarget: tag.attrib['device-target'],
- arch: tag.attrib.arch
- });
- });
- }
+ const globalResources = includeGlobal
+ ? this.doc.findall('resource-file')
+ : [];
- return fileResources;
+ return [].concat(platformResources, globalResources)
+ .map(({ attrib }) => new FileResource(attrib, { platform }));
}
/**
* Returns all hook scripts for the hook type specified.
+ *
* @param {String} hook The hook type.
* @param {Array} platforms Platforms to look for scripts into (root scripts will be included as well).
* @return {Array} Script elements.
*/
- getHookScripts (hook, platforms) {
- let scriptElements = this.doc.findall('./hook');
-
- if (platforms) {
- platforms.forEach(platform => {
- scriptElements = scriptElements.concat(this.doc.findall(`./platform[@name="${platform}"]/hook`));
- });
- }
-
- function filterScriptByHookType (el) {
- return el.attrib.src && el.attrib.type && el.attrib.type.toLowerCase() === hook;
- }
-
- return scriptElements.filter(filterScriptByHookType);
+ getHookScripts (hook, platforms = []) {
+ return this.doc.findall('./hook')
+ .concat(...platforms.map(platform =>
+ this.doc.findall(`./platform[@name="${platform}"]/hook`)
+ ))
+ .filter(({ attrib: { src, type } }) =>
+ src && type && type.toLowerCase() === hook
+ );
}
/**
@@ -328,25 +267,24 @@ class ConfigParser {
*
* This function also returns any plugin's that
* were defined using the legacy <feature> tags.
+ *
* @return {string[]} Array of plugin IDs
*/
getPluginIdList () {
const plugins = this.doc.findall('plugin');
const result = plugins.map(plugin => plugin.attrib.name);
const features = this.doc.findall('feature');
+
features.forEach(element => {
const idTag = element.find('./param[@name="id"]');
- if (idTag) {
- result.push(idTag.attrib.value);
- }
+ if (idTag) result.push(idTag.attrib.value);
});
+
return result;
}
getPlugins () {
- return this.getPluginIdList().map(function (pluginId) {
- return this.getPlugin(pluginId);
- }, this);
+ return this.getPluginIdList().map(pluginId => this.getPlugin(pluginId));
}
/**
@@ -357,26 +295,19 @@ class ConfigParser {
*/
addPlugin (attributes, variables) {
if (!attributes && !attributes.name) return;
- const el = new et.Element('plugin');
- el.attrib.name = attributes.name;
- if (attributes.spec) {
- el.attrib.spec = attributes.spec;
- }
// support arbitrary object as variables source
- if (variables && typeof variables === 'object' && !Array.isArray(variables)) {
- variables = Object.keys(variables).map(variableName => ({
- name: variableName,
- value: variables[variableName]
- }));
+ variables = variables || [];
+ if (typeof variables === 'object' && !Array.isArray(variables)) {
+ variables = Object.entries(variables)
+ .map(([name, value]) => ({ name, value }));
}
- if (variables) {
- variables.forEach(variable => {
- el.append(new et.Element('variable', { name: variable.name, value: variable.value }));
- });
- }
- this.doc.getroot().append(el);
+ const el = et.SubElement(this.doc.getroot(), 'plugin', attributes);
+
+ variables.forEach(({ name, value }) => {
+ et.SubElement(el, 'variable', { name, value });
+ });
}
/**
@@ -389,32 +320,30 @@ class ConfigParser {
* @returns {object} plugin including any variables
*/
getPlugin (id) {
- if (!id) {
- return undefined;
- }
+ if (!id) return undefined;
+
const pluginElement = this.doc.find(`./plugin/[@name="${id}"]`);
+
if (pluginElement === null) {
const legacyFeature = this.doc.find(`./feature/param[@name="id"][@value="${id}"]/..`);
+
if (legacyFeature) {
events.emit('log', `Found deprecated feature entry for ${id} in config.xml.`);
return featureToPlugin(legacyFeature);
}
+
return undefined;
}
- const plugin = {};
-
- plugin.name = pluginElement.attrib.name;
- plugin.spec = pluginElement.attrib.spec || pluginElement.attrib.src || pluginElement.attrib.version;
- plugin.variables = {};
- const variableElements = pluginElement.findall('variable');
- variableElements.forEach(varElement => {
- const name = varElement.attrib.name;
- const value = varElement.attrib.value;
- if (name) {
- plugin.variables[name] = value;
- }
- });
- return plugin;
+
+ const { name, spec, src, version } = pluginElement.attrib;
+
+ const varFragments = pluginElement.findall('variable')
+ .map(({ attrib: { name, value } }) =>
+ name ? { [name]: value } : null
+ );
+ const variables = Object.assign({}, ...varFragments);
+
+ return { name, spec: spec || src || version, variables };
}
/**
@@ -423,10 +352,11 @@ class ConfigParser {
* This function also operates on any plugin's that
* were defined using the legacy <feature> tags.
*
- * @param id name of the plugin
+ * @param {string} id name of the plugin
*/
removePlugin (id) {
if (!id) return;
+
const root = this.doc.getroot();
removeChildren(root, `./plugin/[@name="${id}"]`);
removeChildren(root, `./feature/param[@name="id"][@value="${id}"]/..`);
@@ -434,30 +364,25 @@ class ConfigParser {
// Add any element to the root
addElement (name, attributes) {
- const el = et.Element(name);
- for (const a in attributes) {
- el.attrib[a] = attributes[a];
- }
- this.doc.getroot().append(el);
+ et.SubElement(this.doc.getroot(), name, attributes);
}
/**
* Adds an engine. Does not check for duplicates.
+ *
* @param {String} name the engine name
- * @param {String} spec engine source location or version (optional)
+ * @param {String} [spec] engine source location or version
*/
addEngine (name, spec) {
if (!name) return;
- const el = et.Element('engine');
- el.attrib.name = name;
- if (spec) {
- el.attrib.spec = spec;
- }
- this.doc.getroot().append(el);
+
+ const attrs = Object.assign({ name }, spec ? { spec } : null);
+ et.SubElement(this.doc.getroot(), 'engine', attrs);
}
/**
* Removes all the engines with given name
+ *
* @param {String} name the engine name.
*/
removeEngine (name) {
@@ -465,100 +390,100 @@ class ConfigParser {
}
getEngines () {
- const engines = this.doc.findall('./engine');
- return engines.map(engine => {
- const spec = engine.attrib.spec || engine.attrib.version;
- return {
- name: engine.attrib.name,
- spec: spec || null
- };
- });
+ return this.doc.findall('./engine').map(engine => ({
+ name: engine.attrib.name,
+ spec: engine.attrib.spec || engine.attrib.version || null
+ }));
}
- /* Get all the access tags */
+ /**
+ * @typedef {Object} CommonRuleOptions
+ * @prop {string} [minimum_tls_version]
+ * @prop {StringifiedBool} [requires_forward_secrecy]
+ * @prop {StringifiedBool} [requires_certificate_transparency]
+ *
+ * @typedef {string} StringifiedBool has either the value 'true' or 'false'
+ */
+
+ /**
+ * Get all the access tags
+ *
+ * @returns {AccessRule[]}
+ *
+ * @typedef {CommonRuleOptions} AccessRule
+ * @prop {string} origin
+ * @prop {StringifiedBool} [allows_arbitrary_loads_in_web_content]
+ * @prop {StringifiedBool} [allows_arbitrary_loads_in_media] (DEPRECATED)
+ * @prop {StringifiedBool} [allows_arbitrary_loads_for_media]
+ * @prop {StringifiedBool} [allows_local_networking]
+ */
getAccesses () {
- const accesses = this.doc.findall('./access');
- return accesses.map(access => {
- const minimum_tls_version = access.attrib['minimum-tls-version']; /* String */
- const requires_forward_secrecy = access.attrib['requires-forward-secrecy']; /* Boolean */
- const requires_certificate_transparency = access.attrib['requires-certificate-transparency']; /* Boolean */
- const allows_arbitrary_loads_in_web_content = access.attrib['allows-arbitrary-loads-in-web-content']; /* Boolean */
- const allows_arbitrary_loads_in_media = access.attrib['allows-arbitrary-loads-in-media']; /* Boolean (DEPRECATED) */
- const allows_arbitrary_loads_for_media = access.attrib['allows-arbitrary-loads-for-media']; /* Boolean */
- const allows_local_networking = access.attrib['allows-local-networking']; /* Boolean */
-
- return {
- origin: access.attrib.origin,
- minimum_tls_version,
- requires_forward_secrecy,
- requires_certificate_transparency,
- allows_arbitrary_loads_in_web_content,
- allows_arbitrary_loads_in_media,
- allows_arbitrary_loads_for_media,
- allows_local_networking
- };
- });
+ return this.doc.findall('./access').map(element => ({
+ origin: element.attrib.origin,
+ minimum_tls_version: element.get('minimum-tls-version'),
+ requires_forward_secrecy: element.get('requires-forward-secrecy'),
+ requires_certificate_transparency: element.get('requires-certificate-transparency'),
+ allows_arbitrary_loads_in_web_content: element.get('allows-arbitrary-loads-in-web-content'),
+ allows_arbitrary_loads_in_media: element.get('allows-arbitrary-loads-in-media'),
+ allows_arbitrary_loads_for_media: element.get('allows-arbitrary-loads-for-media'),
+ allows_local_networkin: element.get('allows-local-networking')
+ }));
}
- /* Get all the allow-navigation tags */
+ /**
+ * Get all the allow-navigation tags
+ *
+ * @returns {AllowNavigationRule[]}
+ * @typedef {{href: string} & CommonRuleOptions} AllowNavigationRule
+ */
getAllowNavigations () {
- const allow_navigations = this.doc.findall('./allow-navigation');
- return allow_navigations.map(allow_navigation => {
- const minimum_tls_version = allow_navigation.attrib['minimum-tls-version']; /* String */
- const requires_forward_secrecy = allow_navigation.attrib['requires-forward-secrecy']; /* Boolean */
- const requires_certificate_transparency = allow_navigation.attrib['requires-certificate-transparency']; /* Boolean */
-
- return {
- href: allow_navigation.attrib.href,
- minimum_tls_version,
- requires_forward_secrecy,
- requires_certificate_transparency
- };
- });
+ return this.doc.findall('./allow-navigation').map(element => ({
+ href: element.attrib.href,
+ minimum_tls_version: element.get('minimum-tls-version'),
+ requires_forward_secrecy: element.get('requires-forward-secrecy'),
+ requires_certificate_transparenc: element.get('requires-certificate-transparency')
+ }));
}
- /* Get all the allow-intent tags */
+ /**
+ * Get all the allow-intent tags
+ *
+ * @returns {{href: string}[]}
+ */
getAllowIntents () {
- const allow_intents = this.doc.findall('./allow-intent');
- return allow_intents.map(allow_intent => ({
- href: allow_intent.attrib.href
+ return this.doc.findall('./allow-intent').map(element => ({
+ href: element.attrib.href
}));
}
- /* Get all edit-config tags */
+ /** Get all edit-config tags */
getEditConfigs (platform) {
const platform_edit_configs = this.doc.findall(`./platform[@name="${platform}"]/edit-config`);
const edit_configs = this.doc.findall('edit-config').concat(platform_edit_configs);
- return edit_configs.map(tag => {
- const editConfig = {
- file: tag.attrib.file,
- target: tag.attrib.target,
- mode: tag.attrib.mode,
- id: 'config.xml',
- xmls: tag.getchildren()
- };
- return editConfig;
- });
+ return edit_configs.map(tag => ({
+ file: tag.attrib.file,
+ target: tag.attrib.target,
+ mode: tag.attrib.mode,
+ id: 'config.xml',
+ xmls: tag.getchildren()
+ }));
}
- /* Get all config-file tags */
+ /** Get all config-file tags */
getConfigFiles (platform) {
const platform_config_files = this.doc.findall(`./platform[@name="${platform}"]/config-file`);
const config_files = this.doc.findall('config-file').concat(platform_config_files);
- return config_files.map(tag => {
- const configFile = {
- target: tag.attrib.target,
- parent: tag.attrib.parent,
- after: tag.attrib.after,
- xmls: tag.getchildren(),
- // To support demuxing via versions
- versions: tag.attrib.versions,
- deviceTarget: tag.attrib['device-target']
- };
- return configFile;
- });
+ return config_files.map(tag => ({
+ target: tag.attrib.target,
+ parent: tag.attrib.parent,
+ after: tag.attrib.after,
+ xmls: tag.getchildren(),
+ // To support demuxing via versions
+ versions: tag.attrib.versions,
+ deviceTarget: tag.attrib['device-target']
+ }));
}
write () {
@@ -571,49 +496,22 @@ function getNodeTextSafe (el) {
}
function findOrCreate (doc, name) {
- let ret = doc.find(name);
- if (!ret) {
- ret = new et.Element(name);
- doc.getroot().append(ret);
- }
- return ret;
+ const parent = doc.getroot();
+ return parent.find(name) || new et.SubElement(parent, name);
}
function getCordovaNamespacePrefix (doc) {
- const rootAtribs = Object.getOwnPropertyNames(doc.getroot().attrib);
- let prefix = 'cdv';
- for (let j = 0; j < rootAtribs.length; j++) {
- if (rootAtribs[j].startsWith('xmlns:') &&
- doc.getroot().attrib[rootAtribs[j]] === 'http://cordova.apache.org/ns/1.0') {
- const strings = rootAtribs[j].split(':');
- prefix = strings[1];
- break;
- }
- }
- return prefix;
-}
-
-/**
- * Finds the value of an element's attribute
- * @param {String} attributeName Name of the attribute to search for
- * @param {Array} elems An array of ElementTree nodes
- * @return {String}
- */
-function findElementAttributeValue (attributeName, elems) {
- elems = Array.isArray(elems) ? elems : [elems];
+ const attrs = doc.getroot().attrib;
+ const nsAttr = Object.keys(attrs).find(key =>
+ key.startsWith('xmlns:') && attrs[key] === CDV_XMLNS_URI
+ );
- const value = elems.filter(elem =>
- elem.attrib.name.toLowerCase() === attributeName.toLowerCase()
- ).map(filteredElems =>
- filteredElems.attrib.value
- ).pop();
-
- return value || '';
+ return nsAttr ? nsAttr.split(':')[1] : 'cdv';
}
+// remove child from element for each match
function removeChildren (el, selector) {
- const matches = el.findall(selector);
- matches.forEach(child => el.remove(child));
+ el.findall(selector).forEach(child => el.remove(child));
}
function featureToPlugin (featureElement) {
@@ -625,6 +523,7 @@ function featureToPlugin (featureElement) {
nodes.forEach(element => {
const n = element.attrib.name;
const v = element.attrib.value;
+
if (n === 'id') {
plugin.name = v;
} else if (n === 'version') {
@@ -643,4 +542,86 @@ function featureToPlugin (featureElement) {
return plugin;
}
+
+/**
+ * The attribute target is only used for the Windows & Electron platforms
+ */
+class BaseResource {
+ constructor (attrs, { platform = null } = {}) {
+ // null means resource is shared between platforms
+ this.platform = platform || null;
+ this.src = attrs.src;
+ this.target = attrs.target || undefined;
+ }
+}
+
+/**
+ * The attributes density, background, and foreground are only used for the
+ * Android platform
+ */
+class ImageResource extends BaseResource {
+ constructor (attrs, opts) {
+ super(attrs, opts);
+ this.density = attrs.density;
+ this.width = Number(attrs.width) || undefined;
+ this.height = Number(attrs.height) || undefined;
+ this.background = attrs.background || undefined;
+ this.foreground = attrs.foreground || undefined;
+ }
+}
+
+class FileResource extends BaseResource {
+ constructor (attrs, opts) {
+ super(attrs, opts);
+ this.versions = attrs.versions;
+ this.deviceTarget = attrs['device-target'];
+ this.arch = attrs.arch;
+ }
+}
+
+class ImageResources extends Array {
+ constructor (...args) {
+ super(...args);
+
+ // The spread is necessary to avoid infinite recursion
+ this.defaultResource = [...this].filter(res =>
+ !res.width && !res.height && !res.density
+ ).pop();
+ }
+
+ /**
+ * Returns resource with specified width and/or height.
+ * @param {number} width Width of resource.
+ * @param {number} height Height of resource.
+ * @return {ImageResource} Resource object or null if not found.
+ */
+ getBySize (width, height) {
+ return this.find(res =>
+ (res.width || res.height) &&
+ (!res.width || (width === res.width)) &&
+ (!res.height || (height === res.height))
+ ) || null;
+ }
+
+ /**
+ * Returns resource with specified density.
+ *
+ * Only used by Android platform.
+ *
+ * @param {string} density Density of resource.
+ * @return {ImageResource} Resource object or null if not found.
+ */
+ getByDensity (density) {
+ return this.find(res => res.density === density) || null;
+ }
+
+ /**
+ * Returns the default icon
+ * @return {ImageResource} Resource object or null if not found.
+ */
+ getDefault () {
+ return this.defaultResource;
+ }
+}
+
module.exports = ConfigParser;
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org