UNPKG

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