UNPKG

29.7 kBJavaScriptView Raw
1/**
2 * Titanium SDK Library for Node.js
3 * Copyright (c) 2012-Present by Appcelerator, Inc. All Rights Reserved.
4 * Please see the LICENSE file for information about licensing.
5 */
6/* eslint no-loop-func: "off" */
7'use strict';
8
9const appc = require('node-appc');
10const DOMParser = require('xmldom').DOMParser;
11const fs = require('fs-extra');
12const path = require('path');
13
14const plist = appc.plist;
15const version = appc.version;
16const xml = appc.xml;
17const __ = appc.i18n(__dirname).__;
18
19const defaultDOMParserArgs = { errorHandler: function () {} };
20
21module.exports = tiapp;
22
23function toXml(dom, parent, name, value) {
24 // properties is a super special case
25 if (name === 'properties') {
26 Object.keys(value).forEach(function (v) {
27 dom.create('property', {
28 name: v,
29 type: value[v].type || 'string',
30 nodeValue: value[v].value
31 }, parent);
32 });
33 return;
34 }
35
36 var node = dom.create(name, null, parent);
37
38 switch (name) {
39 case 'deployment-targets':
40 Object.keys(value).forEach(function (v) {
41 dom.create('target', {
42 device: v,
43 nodeValue: value[v]
44 }, node);
45 });
46 break;
47
48 case 'code-processor':
49 Object.keys(value).forEach(function (key) {
50 if (key === 'plugins') {
51 if (Array.isArray(value[key]) && value[key].length) {
52 dom.create('plugins', null, node, function (plugins) {
53 value[key].forEach(function (p) {
54 dom.create('plugin', { nodeValue: p }, plugins);
55 });
56 });
57 }
58 } else if (key === 'options') {
59 if (Object.prototype.toString.call(value[key]) === '[object Object]') {
60 dom.create('options', null, node, function (options) {
61 Object.keys(value[key]).forEach(function (opt) {
62 dom.create(opt, { nodeValue: value[key][opt] }, options);
63 });
64 });
65 }
66 } else {
67 dom.create(key, { nodeValue: value[key] }, node);
68 }
69 });
70 break;
71
72 case 'ios':
73 if (Object.prototype.hasOwnProperty.call(value, 'exclude-dir-from-asset-catalog')) {
74 dom.create('exclude-dir-from-asset-catalog', { nodeValue: !!value['exclude-dir-from-asset-catalog'] }, node);
75 }
76
77 if (Object.prototype.hasOwnProperty.call(value, 'enable-launch-screen-storyboard')) {
78 dom.create('enable-launch-screen-storyboard', { nodeValue: !!value['enable-launch-screen-storyboard'] }, node);
79 }
80
81 if (Object.prototype.hasOwnProperty.call(value, 'enablecoverage')) {
82 dom.create('enablecoverage', { nodeValue: !!value['enablecoverage'] }, node);
83 }
84
85 if (Object.prototype.hasOwnProperty.call(value, 'enablemdfind')) {
86 dom.create('enablemdfind', { nodeValue: !!value['enablemdfind'] }, node);
87 }
88
89 if (Object.prototype.hasOwnProperty.call(value, 'min-ios-ver')) {
90 dom.create('min-ios-ver', { nodeValue: version.format(value['min-ios-ver'], 2) }, node);
91 }
92
93 if (Object.prototype.hasOwnProperty.call(value, 'default-background-color')) {
94 dom.create('default-background-color', { nodeValue: value['default-background-color'] }, node);
95 }
96
97 if (Object.prototype.hasOwnProperty.call(value, 'team-id')) {
98 dom.create('team-id', { nodeValue: value['team-id'] }, node);
99 }
100
101 if (Object.prototype.hasOwnProperty.call(value, 'use-jscore-framework')) {
102 dom.create('use-jscore-framework', { nodeValue: !!value['use-jscore-framework'] }, node);
103 }
104
105 if (Object.prototype.hasOwnProperty.call(value, 'run-on-main-thread')) {
106 dom.create('run-on-main-thread', { nodeValue: !!value['run-on-main-thread'] }, node);
107 }
108
109 if (Object.prototype.hasOwnProperty.call(value, 'use-autolayout')) {
110 dom.create('use-autolayout', { nodeValue: !!value['use-autolayout'] }, node);
111 }
112
113 if (Object.prototype.hasOwnProperty.call(value, 'use-new-build-system')) {
114 dom.create('use-new-build-system', { nodeValue: !!value['use-new-build-system'] }, node);
115 }
116
117 if (Object.prototype.hasOwnProperty.call(value, 'use-app-thinning')) {
118 dom.create('use-app-thinning', { nodeValue: !!value['use-app-thinning'] }, node);
119 }
120
121 if (Object.prototype.hasOwnProperty.call(value, 'log-server-port')) {
122 dom.create('log-server-port', { nodeValue: value['log-server-port'] }, node);
123 }
124
125 if (value.capabilities) {
126 var capNode = dom.create('capabilities', null, node);
127 Object.keys(value.capabilities).forEach(function (cap) {
128 if (cap === 'app-groups') {
129 var appGroupNode = dom.create(cap, null, capNode);
130 value.capabilities[cap].forEach(function (group) {
131 dom.create('group', { nodeValue: group }, appGroupNode);
132 });
133 appGroupNode.appendChild(dom.createTextNode('\r\n\t\t\t'));
134 capNode.appendChild(dom.createTextNode('\r\n\t\t'));
135 }
136 });
137 }
138
139 if (value.entitlements) {
140 const enNode = dom.create('entitlements', null, node);
141 const pl = new plist();
142 appc.util.mix(pl, value.entitlements);
143 const doc = pl.toXml(3);
144 enNode.appendChild(dom.createTextNode('\r\n\t\t\t'));
145 xml.forEachElement(doc, function (elem) {
146 enNode.appendChild(elem);
147 });
148 enNode.appendChild(dom.createTextNode('\r\n\t\t'));
149 }
150
151 if (value.plist) {
152 const plNode = dom.create('plist', null, node);
153 const pl = new plist();
154 appc.util.mix(pl, value.plist);
155 const doc = pl.toXml(3);
156 plNode.appendChild(dom.createTextNode('\r\n\t\t\t'));
157 xml.forEachElement(doc, function (elem) {
158 plNode.appendChild(elem);
159 });
160 plNode.appendChild(dom.createTextNode('\r\n\t\t'));
161 }
162
163 if (Array.isArray(value.extensions)) {
164 var extsNode = dom.create('extentions', null, node);
165 value.extensions.forEach(function (ext) {
166 var extNode = dom.create('extention', { projectPath: ext.projectPath }, extsNode);
167 Array.isArray(ext.targets) && ext.targets.forEach(function (target) {
168 var targetNode = dom.create('target', { name: target.name }, extNode);
169 if (target.ppUUIDs && typeof target.ppUUIDs === 'object' && Object.keys(target.ppUUIDs).length) {
170 var ppUUIDsNode = dom.create('provisioning-profiles', null, targetNode);
171 Object.keys(target.ppUUIDs).forEach(function (type) {
172 dom.create(type, { nodeValue: target.ppUUIDs[type] }, ppUUIDsNode);
173 });
174 ppUUIDsNode.appendChild(dom.createTextNode('\r\n\t\t\t\t\t'));
175 }
176 targetNode.appendChild(dom.createTextNode('\r\n\t\t\t\t'));
177 });
178 extNode.appendChild(dom.createTextNode('\r\n\t\t\t'));
179 });
180 extsNode.appendChild(dom.createTextNode('\r\n\t\t'));
181 }
182 break;
183
184 case 'iphone':
185 value.orientations && Object.keys(value.orientations).forEach(function (o) {
186 dom.create('orientations', { device: o }, node, function (orientations) {
187 value.orientations[o].forEach(function (p) {
188 dom.create('orientation', { nodeValue: p }, orientations);
189 });
190 });
191 });
192
193 value.backgroundModes && dom.create('background', null, node, function (background) {
194 value.backgroundModes.forEach(function (mode) {
195 dom.create('mode', { nodeValue: mode }, background);
196 });
197 });
198
199 value.requiredFeatures && dom.create('requires', null, node, function (requires) {
200 value.requiredFeatures.forEach(function (feature) {
201 dom.create('feature', { nodeValue: feature }, requires);
202 });
203 });
204
205 value.types && dom.create('types', null, node, function (types) {
206 value.types.forEach(function (typeObj) {
207 dom.create('type', null, types, function (typeNode) {
208 dom.create('name', { nodeValue: typeObj.name }, typeNode);
209 dom.create('icon', { nodeValue: typeObj.icon }, typeNode);
210 dom.create('uti', { nodeValue: typeObj.uti.join(',') }, typeNode);
211 dom.create('owner', { nodeValue: !!typeObj.owner }, typeNode);
212 });
213 });
214 });
215 break;
216
217 case 'android':
218 node.setAttribute('xmlns:android', 'http://schemas.android.com/apk/res/android');
219
220 if (value.manifest) {
221 node.appendChild(dom.createTextNode('\r\n' + new Array(3).join('\t')));
222 const opts = defaultDOMParserArgs;
223 opts.xmlns = { android: 'http://schemas.android.com/apk/res/android' };
224 node.appendChild(new DOMParser(opts).parseFromString(value.manifest));
225 }
226
227 if (Object.prototype.hasOwnProperty.call(value, 'tool-api-level')) {
228 dom.create('tool-api-level', { nodeValue: value['tool-api-level'] }, node);
229 }
230
231 if (Object.prototype.hasOwnProperty.call(value, 'abi')) {
232 dom.create('abi', { nodeValue: Array.isArray(value.abi) ? value.abi.join(',') : value.abi }, node);
233 }
234
235 if (value.activities) {
236 dom.create('activities', null, node, function (node) {
237 Object.keys(value.activities).forEach(function (url) {
238 var attrs = {};
239 Object.keys(value.activities[url]).forEach(function (attr) {
240 attr !== 'classname' && (attrs[attr] = value.activities[url][attr]);
241 });
242 dom.create('activity', attrs, node);
243 });
244 });
245 }
246
247 if (value.services) {
248 dom.create('services', null, node, function (node) {
249 Object.keys(value.services).forEach(function (url) {
250 var attrs = {};
251 Object.keys(value.services[url]).forEach(function (attr) {
252 attr !== 'classname' && (attrs[attr] = value.services[url][attr]);
253 });
254 dom.create('service', attrs, node);
255 });
256 });
257 }
258 break;
259
260 case 'mobileweb':
261 Object.keys(value).forEach(function (prop) {
262 switch (prop) {
263 case 'build':
264 dom.create('build', null, node, function (build) {
265 Object.keys(value.build).forEach(function (name) {
266 dom.create(name, null, build, function (deployment) {
267 Object.keys(value.build[name]).forEach(function (d) {
268 var val = value.build[name][d];
269 switch (d) {
270 case 'js':
271 case 'css':
272 case 'html':
273 dom.create(d, null, deployment, function (type) {
274 Object.keys(val).forEach(function (v) {
275 dom.create(v, { nodeValue: val[v] }, type);
276 });
277 });
278 break;
279
280 default:
281 dom.create(d, { nodeValue: val }, deployment);
282 }
283 });
284 });
285 });
286 });
287 break;
288
289 case 'analytics':
290 case 'filesystem':
291 case 'map':
292 case 'splash':
293 case 'unsupported-platforms':
294 dom.create(prop, null, node, function (section) {
295 Object.keys(value[prop]).forEach(function (key) {
296 dom.create(key, { nodeValue: value[prop][key] }, section);
297 });
298 });
299 break;
300
301 case 'precache':
302 dom.create('precache', null, node, function (precache) {
303 Object.keys(value[prop]).forEach(function (type) {
304 value[prop][type].forEach(function (n) {
305 dom.create(type, { nodeValue: n }, precache);
306 });
307 });
308 });
309 break;
310
311 default:
312 dom.create(prop, { nodeValue: value[prop] }, node);
313 }
314 });
315 break;
316
317 case 'tizen':
318 node.setAttribute('xmlns:tizen', 'http://ti.appcelerator.org');
319 // use default and generated values if appid and configXml are empty
320 value.appid && node.setAttribute('appid', value.appid);
321 // creating nodes from tizen specific entries
322 var tizenSection = new DOMParser(defaultDOMParserArgs).parseFromString('<?xml version="1.0" encoding="UTF-8"?>\n<tizen xmlns:tizen="http://ti.appcelerator.org" appid="' + value.appid + '"> ' + value.configXml + ' </tizen>', 'text/xml').documentElement,
323 child = tizenSection.firstChild,
324 nextSibl;
325 while (child) {
326 // store next sibling before calling nextSibling().
327 // Becaus after appendChild() nextSibling() will return node from other tree
328 nextSibl = child.nextSibling;
329 node.appendChild(child);
330 child = nextSibl;
331 }
332 break;
333
334 case 'windows':
335 Object.keys(value).forEach(function (prop) {
336 switch (prop) {
337 default:
338 dom.create(prop, { nodeValue: value[prop] }, node);
339 }
340 });
341 break;
342
343 case 'windows-phone':
344 Object.keys(value).forEach(function (prop) {
345 switch (prop) {
346 default:
347 dom.create(prop, { nodeValue: value[prop] }, node);
348 }
349 });
350 break;
351
352 case 'webpack':
353 if (value.type) {
354 dom.create('type', { nodeValue: value.type }, node);
355 }
356 if (Array.isArray(value.transpileDependencies)) {
357 dom.create('transpile-dependencies', null, node, depsNode => {
358 for (const dep of value.transpileDependencies) {
359 dom.create('dep', { nodeValue: dep }, depsNode);
360 }
361 });
362 }
363 break;
364
365 case 'modules':
366 value.forEach(function (mod) {
367 dom.create('module', {
368 platform: mod.platform,
369 version: mod.version ? version.format(mod.version, 2) : null,
370 'deploy-type': mod.deployType || null,
371 nodeValue: mod.id
372 }, node);
373 });
374 break;
375
376 case 'plugins':
377 value.forEach(function (plugin) {
378 dom.create('plugin', {
379 version: version.format(plugin.version, 2),
380 nodeValue: plugin.id
381 }, node);
382 });
383 break;
384
385 default:
386 node.appendChild(dom.createTextNode(value));
387 return;
388 }
389
390 node.appendChild(dom.createTextNode('\r\n' + new Array(2).join('\t')));
391}
392
393function toJS(obj, doc) {
394 var node = doc.firstChild;
395 while (node) {
396 if (node.nodeType === xml.ELEMENT_NODE) {
397 switch (node.tagName) {
398 case 'property':
399 var name = xml.getAttr(node, 'name'),
400 type = xml.getAttr(node, 'type') || 'string',
401 value = xml.getValue(node);
402 if (name) {
403 obj.properties || (obj.properties = {});
404 obj.properties[name] = {
405 type: type,
406 value: type === 'bool' ? !!value
407 : type === 'int' ? (parseInt(value) || 0)
408 : type === 'double' ? (parseFloat(value) || 0)
409 : '' + value
410 };
411 }
412 break;
413
414 case 'deployment-targets':
415 var targets = obj['deployment-targets'] = {};
416 xml.forEachElement(node, function (elem) {
417 var dev = xml.getAttr(elem, 'device');
418 dev && (targets[dev] = xml.getValue(elem));
419 });
420 break;
421
422 case 'code-processor':
423 var codeProcessor = obj['code-processor'] = {};
424 xml.forEachElement(node, function (elem) {
425 switch (elem.tagName) {
426 case 'plugins':
427 codeProcessor.plugins = [];
428 xml.forEachElement(elem, function (elem) {
429 if (elem.tagName === 'plugin') {
430 codeProcessor.plugins.push(xml.getValue(elem));
431 }
432 });
433 break;
434 case 'options':
435 codeProcessor.options = {};
436 xml.forEachElement(elem, function (elem) {
437 codeProcessor.options[elem.tagName] = xml.getValue(elem);
438 });
439 break;
440 default:
441 codeProcessor[elem.tagName] = xml.getValue(elem);
442 }
443 });
444 break;
445
446 case 'ios':
447 var ios = obj.ios = {};
448 xml.forEachElement(node, function (elem) {
449 switch (elem.tagName) {
450 case 'exclude-dir-from-asset-catalog':
451 case 'enable-launch-screen-storyboard':
452 case 'enablecoverage':
453 case 'enablemdfind':
454 case 'default-background-color':
455 case 'team-id':
456 case 'use-jscore-framework':
457 case 'run-on-main-thread':
458 case 'use-autolayout':
459 case 'use-app-thinning':
460 case 'use-new-build-system':
461 case 'log-server-port':
462 ios[elem.tagName] = xml.getValue(elem);
463 break;
464
465 case 'min-ios-ver':
466 if (elem.firstChild) {
467 ios['min-ios-ver'] = parseFloat(elem.firstChild.data) || 0;
468 }
469 break;
470
471 case 'capabilities':
472 ios.capabilities = {};
473 xml.forEachElement(elem, function (elem) {
474 if (elem.tagName === 'app-groups') {
475 var appGroups = ios.capabilities[elem.tagName] = [];
476 xml.forEachElement(elem, function (elem) {
477 if (elem.tagName === 'group') {
478 var group = xml.getValue(elem);
479 group && appGroups.push(group);
480 }
481 });
482 }
483 });
484 break;
485
486 case 'entitlements':
487 ios.entitlements = {};
488 xml.forEachElement(elem, function (elem) {
489 if (elem.tagName === 'dict') {
490 const pl = new plist().parse('<plist version="1.0">' + elem.toString() + '</plist>');
491 Object.keys(pl).forEach(prop => ios.entitlements[prop] = pl[prop]);
492 }
493 });
494 break;
495
496 case 'plist':
497 ios.plist = {};
498 xml.forEachElement(elem, function (elem) {
499 if (elem.tagName === 'dict') {
500 var pl = new plist().parse('<plist version="1.0">' + elem.toString() + '</plist>');
501 Object.keys(pl).forEach(function (prop) {
502 if (!/^CFBundle(DisplayName|Executable|IconFile|Identifier|InfoDictionaryVersion|Name|PackageType|Signature)|LSRequiresIPhoneOS$/.test(prop)) {
503 ios.plist[prop] = pl[prop];
504 }
505 });
506 }
507 });
508 break;
509
510 case 'extensions':
511 var extensions = ios.extensions = [];
512 xml.forEachElement(elem, function (elem) {
513 if (elem.tagName !== 'extension') {
514 return;
515 }
516
517 var ext = {
518 projectPath: elem.getAttribute('projectPath') || null,
519 targets: []
520 };
521 extensions.push(ext);
522
523 xml.forEachElement(elem, function (elem) {
524 if (elem.tagName !== 'target') {
525 return;
526 }
527
528 var target = {
529 name: elem.getAttribute('name'),
530 ppUUIDs: {}
531 };
532 ext.targets.push(target);
533
534 xml.forEachElement(elem, function (elem) {
535 if (elem.tagName === 'provisioning-profiles') {
536 xml.forEachElement(elem, function (elem) {
537 target.ppUUIDs[elem.tagName] = xml.getValue(elem);
538 });
539 }
540 });
541 });
542 });
543 break;
544 }
545 });
546 break;
547
548 case 'blackberry':
549 var blackberry = obj.blackberry = {};
550 obj.blackberry.other = '';
551 xml.forEachElement(node, function (elem) {
552 switch (elem.tagName) {
553 case 'permissions':
554 const permissions = blackberry.permissions = {};
555 xml.forEachElement(elem, (e) => permissions[xml.getValue(e)] = true);
556 break;
557
558 case 'build-id':
559 case 'orientation':
560 blackberry[elem.tagName] = xml.getValue(elem);
561 break;
562
563 default:
564 obj.blackberry.other += elem.toString() + '\n';
565 }
566 });
567 break;
568
569 case 'iphone':
570 var iphone = obj.iphone = {},
571 dev;
572 xml.forEachElement(node, function (elem) {
573 switch (elem.tagName) {
574 case 'orientations':
575 iphone.orientations || (iphone.orientations = {});
576 if (dev = xml.getAttr(elem, 'device')) {
577 iphone.orientations[dev] || (iphone.orientations[dev] = []);
578 xml.forEachElement(elem, function (elem) {
579 iphone.orientations[dev].push(xml.getValue(elem));
580 });
581 }
582 break;
583
584 case 'background':
585 xml.forEachElement(elem, function (elem) {
586 if (elem.tagName === 'mode') {
587 iphone.backgroundModes || (iphone.backgroundModes = []);
588 iphone.backgroundModes.push(xml.getValue(elem));
589 }
590 });
591 break;
592
593 case 'requires':
594 xml.forEachElement(elem, function (elem) {
595 if (elem.tagName === 'feature') {
596 iphone.requiredFeatures || (iphone.requiredFeatures = []);
597 iphone.requiredFeatures.push(xml.getValue(elem));
598 }
599 });
600 break;
601
602 case 'types':
603 xml.forEachElement(elem, function (elem) {
604 if (elem.tagName === 'type') {
605 iphone.types || (iphone.types = []);
606 var type = {
607 name: '',
608 icon: '',
609 uti: [],
610 owner: false
611 };
612 xml.forEachElement(elem, function (elem) {
613 var v = xml.getValue(elem);
614 type[elem.tagName] = elem.tagName === 'uti' ? v.split(',').map(s => s.trim()) : v;
615 });
616 iphone.types.push(type);
617 }
618 });
619 break;
620 }
621 });
622 break;
623
624 case 'android':
625 var android = obj.android = {},
626 formatUrl = (url) => {
627 return appc.string.capitalize(url.replace(/^app:\/\//, '').replace(/\.js$/, '').replace(/\//g, '_')).replace(/[/ .$&@]/g, '_');
628 };
629
630 xml.forEachElement(node, function (elem) {
631 switch (elem.tagName) {
632 case 'manifest':
633 // the <manifest> tag is an XML document and we're just gonna
634 // defer the parsing to whoever wants its data
635 // Strip the 'android' XML namespace on the uses-sdk tag! It's already defined at <android> tag level!
636 android.manifest = elem.toString().replace(/ xmlns:android="http:\/\/schemas\.android\.com\/apk\/res\/android"/, '');
637 break;
638
639 case 'abi':
640 android[elem.tagName] = xml.getValue(elem).split(',').map(s => s.trim());
641 break;
642
643 case 'tool-api-level':
644 android[elem.tagName] = xml.getValue(elem);
645 break;
646
647 case 'activities':
648 case 'services':
649 var type = elem.tagName,
650 dest = android[type] = {};
651
652 xml.forEachElement(elem, function (elem) {
653 if ((type === 'activities' && elem.tagName === 'activity') || (type === 'services' && elem.tagName === 'service')) {
654 var url = xml.getAttr(elem, 'url') || xml.getValue(elem) || '';
655 if (url) {
656 var a = dest[url] = {};
657 xml.forEachAttr(elem, function (attr) {
658 a[attr.name] = xml.parse(attr.value);
659 });
660 a['classname'] = formatUrl(url) + (type === 'activities' ? 'Activity' : 'Service');
661 if (type === 'services') {
662 a['type'] = xml.getAttr(elem, 'type') || 'standard';
663 }
664 a['url'] = url;
665 xml.forEachElement(elem, function (elem) {
666 if (elem.tagName === 'intent-filter') {
667 var intentFilter = null;
668 xml.forEachElement(elem, function (elem) {
669 if (elem.tagName === 'action' || elem.tagName === 'category' || elem.tagName === 'data') {
670 intentFilter || (intentFilter = {});
671 intentFilter[elem.tagName] || (intentFilter[elem.tagName] = []);
672 if (elem.tagName === 'data') {
673 var a = {};
674 xml.forEachAttr(elem, function (attr) {
675 a[attr.name.replace(/^android:/, '')] = xml.parse(attr.value);
676 });
677 intentFilter[elem.tagName].push(a);
678 } else {
679 intentFilter[elem.tagName].push(xml.getAttr(elem, 'android:name'));
680 }
681 }
682 });
683 if (intentFilter) {
684 a['intent-filter'] || (a['intent-filter'] = []);
685 a['intent-filter'].push(intentFilter);
686 }
687 } else if (elem.tagName === 'meta-data') {
688 var obj = {};
689 xml.forEachAttr(elem, function (attr) {
690 obj[attr.name.replace(/^android:/, '')] = xml.parse(attr.value);
691 });
692 if (obj.name) {
693 a['meta-data'] || (a['meta-data'] = {});
694 a['meta-data'][obj.name] = obj;
695 }
696 }
697 });
698 }
699 }
700 });
701 break;
702 }
703 });
704 break;
705
706 case 'mobileweb':
707 var mobileweb = obj.mobileweb = {};
708 xml.forEachElement(node, function (elem) {
709 switch (elem.tagName) {
710 case 'build':
711 var build = mobileweb.build = {};
712 xml.forEachElement(elem, function (elem) {
713 var profile = build[elem.tagName] = {};
714 xml.forEachElement(elem, function (elem) {
715 switch (elem.tagName) {
716 case 'js':
717 case 'css':
718 case 'html':
719 var filetype = profile[elem.tagName] = {};
720 xml.forEachElement(elem, function (elem) {
721 filetype[elem.tagName] = xml.getValue(elem);
722 });
723 break;
724
725 default:
726 profile[elem.tagName] = xml.getValue(elem);
727 }
728 });
729 });
730 break;
731
732 case 'analytics':
733 case 'filesystem':
734 case 'map':
735 case 'splash':
736 case 'unsupported-platforms':
737 mobileweb[elem.tagName] = {};
738 xml.forEachElement(elem, function (subelem) {
739 mobileweb[elem.tagName][subelem.tagName] = xml.getValue(subelem);
740 });
741 break;
742
743 case 'precache':
744 var precache = mobileweb.precache = {};
745 xml.forEachElement(elem, function (elem) {
746 precache[elem.tagName] || (precache[elem.tagName] = []);
747 precache[elem.tagName].push(xml.getValue(elem));
748 });
749 break;
750
751 default:
752 mobileweb[elem.tagName] = xml.getValue(elem);
753 }
754 });
755 break;
756
757 case 'tizen':
758 var tizen = obj.tizen = {
759 appid: undefined,
760 configXml: undefined
761 };
762
763 tizen.appid = xml.getAttr(node, 'appid');
764 xml.forEachElement(node, function (elem) {
765 tizen.configXml ? tizen.configXml = tizen.configXml + '\n' + elem.toString() : tizen.configXml = elem.toString();
766 });
767 break;
768
769 case 'windows':
770 var windows = obj.windows = {};
771 xml.forEachElement(node, function (elem) {
772 windows[elem.tagName] = xml.getValue(elem);
773 });
774 break;
775
776 case 'windows-phone':
777 var wp = obj['windows-phone'] = {};
778 xml.forEachElement(node, function (elem) {
779 switch (elem.tagName) {
780 case 'target-sdk':
781 wp[elem.tagName] = elem.firstChild && elem.firstChild.data.replace(/\n/g, '').trim() || '';
782 break;
783 default:
784 wp[elem.tagName] = xml.getValue(elem);
785 }
786 });
787 break;
788
789 case 'modules':
790 var modules = obj.modules = [];
791 xml.forEachElement(node, function (elem) {
792 var opts = {
793 id: xml.getValue(elem),
794 platform: xml.getAttr(elem, 'platform')
795 },
796 version = elem.getAttribute('version'),
797 deployType = xml.getAttr(elem, 'deploy-type');
798 version && (opts.version = version);
799 deployType && (opts.deployType = deployType);
800 modules.push(opts);
801 });
802 break;
803
804 case 'plugins':
805 var plugins = obj.plugins = [];
806 xml.forEachElement(node, function (elem) {
807 var opts = {
808 id: xml.getValue(elem)
809 },
810 version = elem.getAttribute('version');
811 version && (opts.version = version);
812 plugins.push(opts);
813 });
814 break;
815
816 case 'version':
817 obj[node.tagName] = node.firstChild && node.firstChild.data.replace(/\n/g, '').trim() || '';
818 break;
819
820 case 'name':
821 case 'guid':
822 case 'id':
823 case 'icon':
824 // need to strip out line returns which shouldn't be there in the first place
825 obj[node.tagName] = '' + xml.getValue(node);
826 if (typeof obj[node.tagName] === 'string') {
827 obj[node.tagName] = obj[node.tagName].replace(/\n/g, '');
828 }
829 break;
830
831 case 'webpack':
832 const webpack = obj.webpack = {};
833 xml.forEachElement(node, elem => {
834 switch (elem.tagName) {
835 case 'type': {
836 webpack[elem.tagName] = xml.getValue(elem);
837 break;
838 }
839 case 'transpile-dependencies': {
840 const transpileDependencies = webpack.transpileDependencies = [];
841 xml.forEachElement(elem, dep => {
842 transpileDependencies.push(xml.getValue(dep));
843 });
844 break;
845 }
846 }
847 });
848 break;
849
850 default:
851 obj[node.tagName] = xml.getValue(node);
852 }
853 }
854 node = node.nextSibling;
855 }
856}
857
858function tiapp(filename) {
859 Object.defineProperty(this, 'load', {
860 value: function (file) {
861 if (!fs.existsSync(file)) {
862 throw new Error(__('tiapp.xml file does not exist'));
863 }
864 toJS(this, (new DOMParser(defaultDOMParserArgs).parseFromString(fs.readFileSync(file).toString(), 'text/xml')).documentElement);
865 return this;
866 }
867 });
868
869 Object.defineProperty(this, 'parse', {
870 value: function (str) {
871 toJS(this, (new DOMParser(defaultDOMParserArgs).parseFromString(str, 'text/xml')).documentElement);
872 return this;
873 }
874 });
875
876 Object.defineProperty(this, 'toString', {
877 value: function (fmt) {
878 if (fmt === 'xml') {
879 var dom = new DOMParser(defaultDOMParserArgs).parseFromString('<ti:app xmlns:ti="http://ti.appcelerator.org"/>', 'text/xml');
880
881 dom.create = function (tag, attrs, parent, callback) {
882 var node = dom.createElement(tag),
883 i = 0,
884 p = parent;
885
886 attrs && Object.keys(attrs).forEach(function (attr) {
887 if (attr === 'nodeValue') {
888 node.appendChild(dom.createTextNode('' + attrs[attr]));
889 } else {
890 attrs[attr] != undefined && node.setAttribute(attr, '' + attrs[attr]); // eslint-disable-line eqeqeq
891 }
892 });
893
894 if (p) {
895 while (p.parentNode) {
896 i++;
897 p = p.parentNode;
898 }
899 parent.appendChild(dom.createTextNode('\r\n' + new Array(i + 1).join('\t')));
900 }
901
902 parent && parent.appendChild(node);
903 if (callback) {
904 callback(node);
905 node.appendChild(dom.createTextNode('\r\n' + new Array(i + 1).join('\t')));
906 }
907 return node;
908 };
909
910 Object.keys(this).forEach(function (key) {
911 toXml(dom, dom.documentElement, key, this[key]);
912 }, this);
913
914 dom.documentElement.appendChild(dom.createTextNode('\r\n'));
915
916 var xml = dom.documentElement.toString();
917 return '<?xml version="1.0" encoding="UTF-8"?>\n' + xml.replace(/uses-sdk xmlns:android="http:\/\/schemas\.android\.com\/apk\/res\/android"/, 'uses-sdk');
918 } else if (fmt === 'pretty-json') {
919 return JSON.stringify(this, null, '\t');
920 } else if (fmt === 'json') {
921 return JSON.stringify(this);
922 }
923 return Object.prototype.toString.call(this);
924 }
925 });
926
927 Object.defineProperty(this, 'save', {
928 value: function (file) {
929 if (file) {
930 fs.ensureDirSync(path.dirname(file));
931 fs.writeFileSync(file, this.toString('xml'));
932 }
933 return this;
934 }
935 });
936
937 filename && this.load(filename);
938}