You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by be...@apache.org on 2013/08/21 03:12:59 UTC

git commit: [CB-4618] fix for xml parsing on windows, use xml-helpers from now on to parse xml

Updated Branches:
  refs/heads/master 4b77decee -> 0f56fbcda


[CB-4618] fix for xml parsing on windows, use xml-helpers from now on to parse xml


Project: http://git-wip-us.apache.org/repos/asf/cordova-cli/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-cli/commit/0f56fbcd
Tree: http://git-wip-us.apache.org/repos/asf/cordova-cli/tree/0f56fbcd
Diff: http://git-wip-us.apache.org/repos/asf/cordova-cli/diff/0f56fbcd

Branch: refs/heads/master
Commit: 0f56fbcdaacc7c851ee4fd26ca63bea15b10daea
Parents: 4b77dec
Author: Benn Mapes <be...@gmail.com>
Authored: Tue Aug 20 14:39:33 2013 -0700
Committer: Benn Mapes <be...@gmail.com>
Committed: Tue Aug 20 16:01:35 2013 -0700

----------------------------------------------------------------------
 spec/fixtures/plugins/ChildBrowser/plugin.xml   | 142 ++++++++++++++++
 .../ChildBrowser/src/android/ChildBrowser.java  |  19 +++
 .../plugins/ChildBrowser/www/childbrowser.js    |  19 +++
 .../ChildBrowser/www/childbrowser/image.jpg     |   1 +
 .../ChildBrowser/www/childbrowser_file.html     |   1 +
 .../projects/android/AndroidManifest.xml        |  69 ++++++++
 .../projects/android/assets/www/.gitkeep        |   0
 .../projects/android/res/xml/config.xml         |  54 ++++++
 spec/fixtures/projects/android/src/.gitkeep     |   0
 spec/fixtures/projects/windows/bom_test.xml     |  24 +++
 spec/xml-helpers.spec.js                        | 136 +++++++++++++++
 src/cli.js                                      |  19 +++
 src/config_parser.js                            |   3 +-
 src/metadata/android_parser.js                  |   5 +-
 src/metadata/wp7_parser.js                      |  22 +--
 src/metadata/wp8_parser.js                      |  22 +--
 src/plugin_parser.js                            |   6 +-
 src/xml-helpers.js                              | 167 +++++++++++++++++++
 18 files changed, 671 insertions(+), 38 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/0f56fbcd/spec/fixtures/plugins/ChildBrowser/plugin.xml
----------------------------------------------------------------------
diff --git a/spec/fixtures/plugins/ChildBrowser/plugin.xml b/spec/fixtures/plugins/ChildBrowser/plugin.xml
new file mode 100644
index 0000000..512c02f
--- /dev/null
+++ b/spec/fixtures/plugins/ChildBrowser/plugin.xml
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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.
+
+-->
+
+<plugin xmlns="http://cordova.apache.org/ns/plugins/1.0"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    id="com.phonegap.plugins.childbrowser"
+    version="0.6.0">
+
+    <name>Child Browser</name>
+
+    <asset src="www/childbrowser" target="childbrowser" />
+    <asset src="www/childbrowser_file.html" target="childbrowser_file.html" />
+
+    <js-module src="www/childbrowser.js" name="ChildBrowser">
+        <clobbers target="childbrowser" />
+    </js-module>
+
+    <config-file target="config.xml" parent="/*">
+        <access origin="build.phonegap.com" />
+        <access origin="s3.amazonaws.com" />
+    </config-file>
+    
+    <info>No matter what platform you are installing to, this notice is very important.</info>
+
+    <!-- android -->
+    <platform name="android">
+        <config-file target="AndroidManifest.xml" parent="/manifest/application">
+            <activity android:name="com.phonegap.plugins.childBrowser.ChildBrowser"
+                      android:label="@string/app_name">
+                <intent-filter>
+                </intent-filter>
+            </activity>
+        </config-file>
+
+        <!-- CDV < 2.0 -->
+        <config-file target="res/xml/plugins.xml" parent="/plugins">
+            <plugin name="ChildBrowser"
+                value="com.phonegap.plugins.childBrowser.ChildBrowser"/>
+        </config-file>
+
+        <!-- CDV 2.0+ (for now) -->
+        <config-file target="res/xml/config.xml" parent="/cordova/plugins">
+            <plugin name="ChildBrowser"
+                value="com.phonegap.plugins.childBrowser.ChildBrowser"/>
+        </config-file>
+
+        <source-file src="src/android/ChildBrowser.java"
+                target-dir="src/com/phonegap/plugins/childBrowser" />
+        <info>Please make sure you read this because it is very important to complete the installation of your plugin.</info>
+    </platform>
+
+    <!-- ios -->
+    <platform name="ios">
+        <plugins-plist key="com.phonegap.plugins.childbrowser"
+            string="ChildBrowserCommand" />
+
+        <config-file target="config.xml" parent="/widget/plugins">
+            <plugin name="ChildBrowser"
+                value="ChildBrowserCommand" />
+        </config-file>
+
+        <resource-file src="src/ios/ChildBrowser.bundle" />
+        <resource-file src="src/ios/ChildBrowserViewController.xib" />
+
+        <config-file target="*-Info.plist" parent="AppId">
+            <string>$APP_ID</string>
+        </config-file>
+        
+        <config-file target="*-Info.plist" parent="CFBundleURLTypes">
+            <array>
+              <dict>
+                <key>PackageName</key>
+                <string>$PACKAGE_NAME</string>
+              </dict>
+            </array>
+        </config-file>
+
+        <header-file src="src/ios/ChildBrowserCommand.h" />
+        <header-file src="src/ios/ChildBrowserViewController.h" />
+        <header-file src="src/ios/TargetDirTest.h" target-dir="targetDir"/>
+
+        <source-file src="src/ios/ChildBrowserCommand.m" />
+        <source-file src="src/ios/ChildBrowserViewController.m" />
+        <source-file src="src/ios/preserveDirs/PreserveDirsTest.m" preserve-dirs="true" />
+        <header-file src="src/ios/TargetDirTest.m" target-dir="targetDir"/>
+
+        <!-- framework for testing (not actual dependency of ChildBrowser -->
+        <framework src="libsqlite3.dylib" />
+        <framework src="social.framework" weak="true" />
+        <framework src="music.framework" weak="rabbit" />
+    </platform>
+    <!-- wp7 -->
+    <platform name="wp7">
+        <resource-file src="src\wp7\Images\appbar.back.rest.png" />
+        <config-file target="config.xml" parent="/widget/plugins">
+            <plugin name="ChildBrowser"
+                value="ChildBrowser"/>
+        </config-file>
+
+        <source-file src="src\wp7\ChildBrowserCommand.cs"
+                     target-dir="Plugins\" />
+
+        <!-- modify the project file to include the added files -->
+        <config-file target=".csproj" parent=".">  
+        </config-file> 
+
+    </platform>
+
+    <!-- wp8 -->
+    <platform name="wp8">
+        <resource-file src="src\wp7\Images\appbar.back.rest.png" />
+        <config-file target="config.xml" parent="/widget/plugins">
+            <plugin name="ChildBrowser"
+                value="ChildBrowser"/>
+        </config-file>
+
+        <source-file src="src\wp7\ChildBrowserCommand.cs"
+                     target-dir="Plugins\" />
+
+        <!-- modify the project file to include the added files -->
+        <config-file target=".csproj" parent=".">  
+        </config-file> 
+
+    </platform>
+</plugin>

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/0f56fbcd/spec/fixtures/plugins/ChildBrowser/src/android/ChildBrowser.java
----------------------------------------------------------------------
diff --git a/spec/fixtures/plugins/ChildBrowser/src/android/ChildBrowser.java b/spec/fixtures/plugins/ChildBrowser/src/android/ChildBrowser.java
new file mode 100644
index 0000000..5263b0c
--- /dev/null
+++ b/spec/fixtures/plugins/ChildBrowser/src/android/ChildBrowser.java
@@ -0,0 +1,19 @@
+/*
+ *
+ * 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.
+ *
+*/
+

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/0f56fbcd/spec/fixtures/plugins/ChildBrowser/www/childbrowser.js
----------------------------------------------------------------------
diff --git a/spec/fixtures/plugins/ChildBrowser/www/childbrowser.js b/spec/fixtures/plugins/ChildBrowser/www/childbrowser.js
new file mode 100644
index 0000000..5263b0c
--- /dev/null
+++ b/spec/fixtures/plugins/ChildBrowser/www/childbrowser.js
@@ -0,0 +1,19 @@
+/*
+ *
+ * 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.
+ *
+*/
+

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/0f56fbcd/spec/fixtures/plugins/ChildBrowser/www/childbrowser/image.jpg
----------------------------------------------------------------------
diff --git a/spec/fixtures/plugins/ChildBrowser/www/childbrowser/image.jpg b/spec/fixtures/plugins/ChildBrowser/www/childbrowser/image.jpg
new file mode 100644
index 0000000..257cc56
--- /dev/null
+++ b/spec/fixtures/plugins/ChildBrowser/www/childbrowser/image.jpg
@@ -0,0 +1 @@
+foo

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/0f56fbcd/spec/fixtures/plugins/ChildBrowser/www/childbrowser_file.html
----------------------------------------------------------------------
diff --git a/spec/fixtures/plugins/ChildBrowser/www/childbrowser_file.html b/spec/fixtures/plugins/ChildBrowser/www/childbrowser_file.html
new file mode 100644
index 0000000..6de7b8c
--- /dev/null
+++ b/spec/fixtures/plugins/ChildBrowser/www/childbrowser_file.html
@@ -0,0 +1 @@
+This is a test file.

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/0f56fbcd/spec/fixtures/projects/android/AndroidManifest.xml
----------------------------------------------------------------------
diff --git a/spec/fixtures/projects/android/AndroidManifest.xml b/spec/fixtures/projects/android/AndroidManifest.xml
new file mode 100644
index 0000000..0c52803
--- /dev/null
+++ b/spec/fixtures/projects/android/AndroidManifest.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+
+ 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.
+
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:windowSoftInputMode="adjustPan"
+      package="com.alunny.childapp" android:versionName="1.1" android:versionCode="5">
+    <supports-screens
+    	android:largeScreens="true"
+    	android:normalScreens="true"
+    	android:smallScreens="true"
+    	android:xlargeScreens="true"
+    	android:resizeable="true"
+    	android:anyDensity="true"
+    	/>
+
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.VIBRATE" />
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.RECEIVE_SMS" />
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
+    <uses-permission android:name="android.permission.RECORD_VIDEO"/>
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+    <uses-permission android:name="android.permission.READ_CONTACTS" />
+    <uses-permission android:name="android.permission.WRITE_CONTACTS" />   
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />   
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
+    <uses-permission android:name="android.permission.BROADCAST_STICKY" />
+
+    <uses-feature android:name="android.hardware.camera" />
+    <uses-feature android:name="android.hardware.camera.autofocus" />
+
+    <application android:icon="@drawable/icon" android:label="@string/app_name"
+    	android:debuggable="true">
+		<activity android:name="ChildApp" android:label="@string/app_name" 
+				  android:configChanges="orientation|keyboardHidden">
+			<intent-filter>
+				<action android:name="android.intent.action.MAIN" />
+				<category android:name="android.intent.category.LAUNCHER" />
+			</intent-filter>
+        </activity>
+        <activity android:name="com.phonegap.DroidGap" android:label="@string/app_name" 
+            	  android:configChanges="orientation|keyboardHidden">
+        	<intent-filter>
+        	</intent-filter>
+        </activity>
+    </application>
+
+	<uses-sdk android:minSdkVersion="5" />
+</manifest> 

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/0f56fbcd/spec/fixtures/projects/android/assets/www/.gitkeep
----------------------------------------------------------------------
diff --git a/spec/fixtures/projects/android/assets/www/.gitkeep b/spec/fixtures/projects/android/assets/www/.gitkeep
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/0f56fbcd/spec/fixtures/projects/android/res/xml/config.xml
----------------------------------------------------------------------
diff --git a/spec/fixtures/projects/android/res/xml/config.xml b/spec/fixtures/projects/android/res/xml/config.xml
new file mode 100644
index 0000000..d37aba5
--- /dev/null
+++ b/spec/fixtures/projects/android/res/xml/config.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you 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.
+-->
+<cordova>
+    <!--
+    access elements control the Android whitelist.
+    Domains are assumed blocked unless set otherwise
+     -->
+
+    <access origin="http://127.0.0.1*"/> <!-- allow local pages -->
+
+    <!-- <access origin="https://example.com" /> allow any secure requests to example.com -->
+    <!-- <access origin="https://example.com" subdomains="true" /> such as above, but including subdomains, such as www -->
+    <!-- <access origin=".*"/> Allow all domains, suggested development use only -->
+
+    <log level="DEBUG"/>
+    <preference name="useBrowserHistory" value="false" />
+<plugins>
+    <plugin name="App" value="org.apache.cordova.App"/>
+    <plugin name="Geolocation" value="org.apache.cordova.GeoBroker"/>
+    <plugin name="Device" value="org.apache.cordova.Device"/>
+    <plugin name="Accelerometer" value="org.apache.cordova.AccelListener"/>
+    <plugin name="Compass" value="org.apache.cordova.CompassListener"/>
+    <plugin name="Media" value="org.apache.cordova.AudioHandler"/>
+    <plugin name="Camera" value="org.apache.cordova.CameraLauncher"/>
+    <plugin name="Contacts" value="org.apache.cordova.ContactManager"/>
+    <plugin name="File" value="org.apache.cordova.FileUtils"/>
+    <plugin name="NetworkStatus" value="org.apache.cordova.NetworkManager"/>
+    <plugin name="Notification" value="org.apache.cordova.Notification"/>
+    <plugin name="Storage" value="org.apache.cordova.Storage"/>
+    <plugin name="Temperature" value="org.apache.cordova.TempListener"/>
+    <plugin name="FileTransfer" value="org.apache.cordova.FileTransfer"/>
+    <plugin name="Capture" value="org.apache.cordova.Capture"/>
+    <plugin name="Battery" value="org.apache.cordova.BatteryListener"/>
+    <plugin name="SplashScreen" value="org.apache.cordova.SplashScreen"/>
+</plugins>
+</cordova>
+

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/0f56fbcd/spec/fixtures/projects/android/src/.gitkeep
----------------------------------------------------------------------
diff --git a/spec/fixtures/projects/android/src/.gitkeep b/spec/fixtures/projects/android/src/.gitkeep
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/0f56fbcd/spec/fixtures/projects/windows/bom_test.xml
----------------------------------------------------------------------
diff --git a/spec/fixtures/projects/windows/bom_test.xml b/spec/fixtures/projects/windows/bom_test.xml
new file mode 100644
index 0000000..57cadf6
--- /dev/null
+++ b/spec/fixtures/projects/windows/bom_test.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you 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.
+#
+-->
+<widget>
+    <access origin="*"/>
+</widget>

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/0f56fbcd/spec/xml-helpers.spec.js
----------------------------------------------------------------------
diff --git a/spec/xml-helpers.spec.js b/spec/xml-helpers.spec.js
new file mode 100644
index 0000000..140347a
--- /dev/null
+++ b/spec/xml-helpers.spec.js
@@ -0,0 +1,136 @@
+/*
+ *
+ *
+ * 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.
+ *
+*/
+
+var path = require('path')
+  , xml_helpers = require('../src/xml-helpers')
+  , et = require('elementtree')
+
+  , title = et.XML('<title>HELLO</title>')
+  , usesNetworkOne = et.XML('<uses-permission ' +
+            'android:name="PACKAGE_NAME.permission.C2D_MESSAGE"/>')
+  , usesNetworkTwo = et.XML("<uses-permission android:name=\
+            \"PACKAGE_NAME.permission.C2D_MESSAGE\" />")
+  , usesReceive = et.XML("<uses-permission android:name=\
+            \"com.google.android.c2dm.permission.RECEIVE\"/>")
+  , helloTagOne = et.XML("<h1>HELLO</h1>")
+  , goodbyeTag = et.XML("<h1>GOODBYE</h1>")
+  , helloTagTwo = et.XML("<h1>  HELLO  </h1>");
+
+
+describe('xml-helpers', function(){
+    describe('parseElementtreeSync', function() {
+        it('should parse xml with a byte order mark', function() {
+            var xml_path = path.join(__dirname, 'fixtures', 'projects', 'windows', 'bom_test.xml');
+            expect(function() {
+                xml_helpers.parseElementtreeSync(xml_path);
+            }).not.toThrow();
+        })
+    });
+    describe('equalNodes', function() {
+        it('should return false for different tags', function(){
+            expect(xml_helpers.equalNodes(usesNetworkOne, title)).toBe(false);
+        });
+
+        it('should return true for identical tags', function(){
+            expect(xml_helpers.equalNodes(usesNetworkOne, usesNetworkTwo)).toBe(true);
+        });   
+        
+        it('should return false for different attributes', function(){
+            expect(xml_helpers.equalNodes(usesNetworkOne, usesReceive)).toBe(false);
+        });  
+        
+        it('should distinguish between text', function(){
+            expect(xml_helpers.equalNodes(helloTagOne, goodbyeTag)).toBe(false);
+        });  
+        
+        it('should ignore whitespace in text', function(){
+            expect(xml_helpers.equalNodes(helloTagOne, helloTagTwo)).toBe(true);
+        });    
+        
+        describe('should compare children', function(){
+            it('by child quantity', function(){
+                var one = et.XML('<i><b>o</b></i>'),
+                    two = et.XML('<i><b>o</b><u></u></i>');
+        
+                expect(xml_helpers.equalNodes(one, two)).toBe(false);        
+            });
+            
+            it('by child equality', function(){
+                var one = et.XML('<i><b>o</b></i>'),
+                    two = et.XML('<i><u></u></i>'),
+                    uno = et.XML('<i>\n<b>o</b>\n</i>');
+        
+                expect(xml_helpers.equalNodes(one, uno)).toBe(true); 
+                expect(xml_helpers.equalNodes(one, two)).toBe(false);       
+            });
+        }); 
+    });
+    describe('pruneXML', function() {
+        var config_xml;
+
+        beforeEach(function() {
+            config_xml = xml_helpers.parseElementtreeSync(path.join(__dirname, 'fixtures', 'projects', 'android', 'res', 'xml', 'config.xml'));
+        });
+
+        it('should remove any children that match the specified selector', function() {
+            var children = config_xml.findall('plugins/plugin');
+            xml_helpers.pruneXML(config_xml, children, 'plugins');
+            expect(config_xml.find('plugins').getchildren().length).toEqual(0);
+        });
+        it('should do nothing if the children cannot be found', function() {
+            var children = [title];
+            xml_helpers.pruneXML(config_xml, children, 'plugins');
+            expect(config_xml.find('plugins').getchildren().length).toEqual(17);
+        });
+        it('should be able to handle absolute selectors', function() {
+            var children = config_xml.findall('plugins/plugin');
+            xml_helpers.pruneXML(config_xml, children, '/cordova/plugins');
+            expect(config_xml.find('plugins').getchildren().length).toEqual(0);
+        });
+        it('should be able to handle absolute selectors with wildcards', function() {
+            var children = config_xml.findall('plugins/plugin');
+            xml_helpers.pruneXML(config_xml, children, '/*/plugins');
+            expect(config_xml.find('plugins').getchildren().length).toEqual(0);
+        });
+    });
+
+    describe('graftXML', function() {
+        var config_xml, plugin_xml;
+
+        beforeEach(function() {
+            config_xml = xml_helpers.parseElementtreeSync(path.join(__dirname, 'fixtures', 'projects', 'android', 'res', 'xml', 'config.xml'));
+            plugin_xml = xml_helpers.parseElementtreeSync(path.join(__dirname, 'fixtures', 'plugins', 'ChildBrowser', 'plugin.xml'));
+        });
+
+        it('should add children to the specified selector', function() {
+            var children = plugin_xml.find('config-file').getchildren();
+            xml_helpers.graftXML(config_xml, children, 'plugins');
+            expect(config_xml.find('plugins').getchildren().length).toEqual(19);
+        });
+        it('should be able to handle absolute selectors', function() {
+            var children = plugin_xml.find('config-file').getchildren();
+            xml_helpers.graftXML(config_xml, children, '/cordova');
+            expect(config_xml.findall('access').length).toEqual(3);
+        });
+        it('should be able to handle absolute selectors with wildcards', function() {
+            var children = plugin_xml.find('config-file').getchildren();
+            xml_helpers.graftXML(config_xml, children, '/*');
+            expect(config_xml.findall('access').length).toEqual(3);
+        });
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/0f56fbcd/src/cli.js
----------------------------------------------------------------------
diff --git a/src/cli.js b/src/cli.js
index 296ccda..24c9d7d 100755
--- a/src/cli.js
+++ b/src/cli.js
@@ -1,3 +1,22 @@
+/**
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you 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.
+*/
+
 var optimist  = require('optimist'),
     cordova   = require('../cordova'),
     plugman   = require('plugman'),

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/0f56fbcd/src/config_parser.js
----------------------------------------------------------------------
diff --git a/src/config_parser.js b/src/config_parser.js
index 5c5e0dc..6e40054 100644
--- a/src/config_parser.js
+++ b/src/config_parser.js
@@ -17,11 +17,12 @@
     under the License.
 */
 var et = require('elementtree'),
+    xml= require('./xml-helpers'),
     fs = require('fs');
 
 function config_parser(path) {
     this.path = path;
-    this.doc = new et.ElementTree(et.XML(fs.readFileSync(path, 'utf-8')));
+    this.doc = xml.parseElementtreeSync(path);
     this.access = new access(this);
     this.preference = new preference(this);
 }

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/0f56fbcd/src/metadata/android_parser.js
----------------------------------------------------------------------
diff --git a/src/metadata/android_parser.js b/src/metadata/android_parser.js
index 722bd45..6da778a 100644
--- a/src/metadata/android_parser.js
+++ b/src/metadata/android_parser.js
@@ -19,6 +19,7 @@
 var fs            = require('fs'),
     path          = require('path'),
     et            = require('elementtree'),
+    xml           = require('../xml-helpers'),
     util          = require('../util'),
     events        = require('../events'),
     shell         = require('shelljs'),
@@ -81,12 +82,12 @@ module.exports.prototype = {
 
         // Update app name by editing res/values/strings.xml
         var name = config.name();
-        var strings = new et.ElementTree(et.XML(fs.readFileSync(this.strings, 'utf-8')));
+        var strings = xml.parseElementtreeSync(this.strings);
         strings.find('string[@name="app_name"]').text = name;
         fs.writeFileSync(this.strings, strings.write({indent: 4}), 'utf-8');
         events.emit('log', 'Wrote out Android application name to "' + name + '"');
 
-        var manifest = new et.ElementTree(et.XML(fs.readFileSync(this.manifest, 'utf-8')));
+        var manifest = xml.parseElementtreeSync(this.manifest);
         // Update the version by changing the AndroidManifest android:versionName
         var version = config.version();
         manifest.getroot().attrib["android:versionName"] = version;

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/0f56fbcd/src/metadata/wp7_parser.js
----------------------------------------------------------------------
diff --git a/src/metadata/wp7_parser.js b/src/metadata/wp7_parser.js
index 6c81668..bde8770 100644
--- a/src/metadata/wp7_parser.js
+++ b/src/metadata/wp7_parser.js
@@ -19,6 +19,7 @@
 var fs            = require('fs'),
     path          = require('path'),
     et            = require('elementtree'),
+    xml           = require('../xml-helpers'),
     util          = require('../util'),
     events        = require('../events'),
     shell         = require('shelljs'),
@@ -66,9 +67,7 @@ module.exports.prototype = {
         } else throw new Error('update_from_config requires a config_parser object');
 
         //Get manifest file
-        var man = fs.readFileSync(this.manifest_path, 'utf-8');
-        var cleanedMan = man.replace('\ufeff', ''); //Windows is the BOM
-        var manifest = new et.ElementTree(et.XML(cleanedMan));
+        var manifest = xml.parseElementtreeSync(this.manifest_path);
 
         //Update app version
         var version = config.version();
@@ -105,9 +104,7 @@ module.exports.prototype = {
          *  - App.xaml.cs
          */
          var pkg = config.packageName();
-         var raw = fs.readFileSync(this.csproj_path, 'utf-8');
-         var cleanedPage = raw.replace(/^\uFEFF/i, '');
-         var csproj = new et.ElementTree(et.XML(cleanedPage));
+         var csproj = xml.parseElementtreeSync(this.csproj_path);
          prev_name = csproj.find('.//RootNamespace').text;
          if(prev_name != pkg) {
             events.emit('log', "Updating package name from " + prev_name + " to " + pkg);
@@ -118,10 +115,7 @@ module.exports.prototype = {
             csproj.find('.//SilverlightAppEntry').text = pkg + '.App';
             fs.writeFileSync(this.csproj_path, csproj.write({indent: 4}), 'utf-8');
             //MainPage.xaml
-            raw = fs.readFileSync(path.join(this.wp7_proj_dir, 'MainPage.xaml'), 'utf-8');
-            // Remove potential UTF Byte Order Mark
-            cleanedPage = raw.replace(/^\uFEFF/i, '');
-            var mainPageXAML = new et.ElementTree(et.XML(cleanedPage));
+            var mainPageXAML = xml.parseElementtreeSync(path.join(this.wp7_proj_dir, 'MainPage.xaml'));
             mainPageXAML.getroot().attrib['x:Class'] = pkg + '.MainPage';
             fs.writeFileSync(path.join(this.wp7_proj_dir, 'MainPage.xaml'), mainPageXAML.write({indent: 4}), 'utf-8');
             //MainPage.xaml.cs
@@ -129,9 +123,7 @@ module.exports.prototype = {
             var namespaceRegEx = new RegExp('namespace ' + prev_name);
             fs.writeFileSync(path.join(this.wp7_proj_dir, 'MainPage.xaml.cs'), mainPageCS.replace(namespaceRegEx, 'namespace ' + pkg), 'utf-8');
             //App.xaml
-            raw = fs.readFileSync(path.join(this.wp7_proj_dir, 'App.xaml'), 'utf-8');
-            cleanedPage = raw.replace(/^\uFEFF/i, '');
-            var appXAML = new et.ElementTree(et.XML(cleanedPage));
+            var appXAML = xml.parseElementtreeSync(path.join(this.wp7_proj_dir, 'App.xaml'));
             appXAML.getroot().attrib['x:Class'] = pkg + '.App';
             fs.writeFileSync(path.join(this.wp7_proj_dir, 'App.xaml'), appXAML.write({indent: 4}), 'utf-8');
             //App.xaml.cs
@@ -168,9 +160,7 @@ module.exports.prototype = {
     },
     // updates the csproj file to explicitly list all www content.
     update_csproj:function() {
-        var raw = fs.readFileSync(this.csproj_path, 'utf-8');
-        var cleaned = raw.replace(/^\uFEFF/i, '');
-        var csproj_xml = new et.ElementTree(et.XML(cleaned));
+        var csproj_xml = xml.parseElementtreeSync(this.csproj_path);
         // remove any previous references to the www files
         var item_groups = csproj_xml.findall('ItemGroup');
         for (var i = 0, l = item_groups.length; i < l; i++) {

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/0f56fbcd/src/metadata/wp8_parser.js
----------------------------------------------------------------------
diff --git a/src/metadata/wp8_parser.js b/src/metadata/wp8_parser.js
index 00797b7..4a4633f 100644
--- a/src/metadata/wp8_parser.js
+++ b/src/metadata/wp8_parser.js
@@ -24,6 +24,7 @@ var fs            = require('fs'),
     shell         = require('shelljs'),
     events        = require('../events'),
     config_parser = require('../config_parser'),
+    xml           = require('../xml-helpers'),
     config        = require('../config');
 
 module.exports = function wp8_parser(project) {
@@ -66,9 +67,7 @@ module.exports.prototype = {
         } else throw new Error('update_from_config requires a config_parser object');
 
         //Get manifest file
-        var man = fs.readFileSync(this.manifest_path, 'utf-8');
-        var cleanedMan = man.replace('\ufeff', ''); //Windows is the BOM
-        var manifest = new et.ElementTree(et.XML(cleanedMan));
+        var manifest = xml.parseElementtreeSync(this.manifest_path);
 
         //Update app version
         var version = config.version();
@@ -106,9 +105,7 @@ module.exports.prototype = {
          *  - App.xaml.cs
          */
          var pkg = config.packageName();
-         var raw = fs.readFileSync(this.csproj_path, 'utf-8');
-         var cleanedPage = raw.replace(/^\uFEFF/i, '');
-         var csproj = new et.ElementTree(et.XML(cleanedPage));
+         var csproj = xml.parseElementtreeSync(this.csproj_path);
          prev_name = csproj.find('.//RootNamespace').text;
          if(prev_name != pkg) {
             //console.log("Updating package name from " + prev_name + " to " + pkg);
@@ -119,10 +116,7 @@ module.exports.prototype = {
             csproj.find('.//SilverlightAppEntry').text = pkg + '.App';
             fs.writeFileSync(this.csproj_path, csproj.write({indent: 4}), 'utf-8');
             //MainPage.xaml
-            raw = fs.readFileSync(path.join(this.wp8_proj_dir, 'MainPage.xaml'), 'utf-8');
-            // Remove potential UTF Byte Order Mark
-            cleanedPage = raw.replace(/^\uFEFF/i, '');
-            var mainPageXAML = new et.ElementTree(et.XML(cleanedPage));
+            var mainPageXAML = xml.parseElementtreeSync(path.join(this.wp8_proj_dir, 'MainPage.xaml'));
             mainPageXAML.getroot().attrib['x:Class'] = pkg + '.MainPage';
             fs.writeFileSync(path.join(this.wp8_proj_dir, 'MainPage.xaml'), mainPageXAML.write({indent: 4}), 'utf-8');
             //MainPage.xaml.cs
@@ -130,9 +124,7 @@ module.exports.prototype = {
             var namespaceRegEx = new RegExp('namespace ' + prev_name);
             fs.writeFileSync(path.join(this.wp8_proj_dir, 'MainPage.xaml.cs'), mainPageCS.replace(namespaceRegEx, 'namespace ' + pkg), 'utf-8');
             //App.xaml
-            raw = fs.readFileSync(path.join(this.wp8_proj_dir, 'App.xaml'), 'utf-8');
-            cleanedPage = raw.replace(/^\uFEFF/i, '');
-            var appXAML = new et.ElementTree(et.XML(cleanedPage));
+            var appXAML = xml.parseElementtreeSync(path.join(this.wp8_proj_dir, 'App.xaml'));
             appXAML.getroot().attrib['x:Class'] = pkg + '.App';
             fs.writeFileSync(path.join(this.wp8_proj_dir, 'App.xaml'), appXAML.write({indent: 4}), 'utf-8');
             //App.xaml.cs
@@ -178,9 +170,7 @@ module.exports.prototype = {
     },
     // updates the csproj file to explicitly list all www content.
     update_csproj:function() {
-        var raw = fs.readFileSync(this.csproj_path, 'utf-8');
-        var cleaned = raw.replace(/^\uFEFF/i, '');
-        var csproj_xml = new et.ElementTree(et.XML(cleaned));
+        var csproj_xml = xml.parseElementtreeSync(this.csproj_path);
         // remove any previous references to the www files
         var item_groups = csproj_xml.findall('ItemGroup');
         for (var i = 0, l = item_groups.length; i < l; i++) {

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/0f56fbcd/src/plugin_parser.js
----------------------------------------------------------------------
diff --git a/src/plugin_parser.js b/src/plugin_parser.js
index 8b4582b..d3099c0 100644
--- a/src/plugin_parser.js
+++ b/src/plugin_parser.js
@@ -16,12 +16,12 @@
     specific language governing permissions and limitations
     under the License.
 */
-var et = require('elementtree'),
-    fs = require('fs');
+var xml = require('./xml-helpers'),
+    fs  = require('fs');
 
 function plugin_parser(xmlPath) {
     this.path = xmlPath;
-    this.doc = new et.ElementTree(et.XML(fs.readFileSync(xmlPath, 'utf-8')));
+    this.doc = xml.parseElementtreeSync(xmlPath);
     this.platforms = this.doc.findall('platform').map(function(p) {
         return p.attrib.name;
     });

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/0f56fbcd/src/xml-helpers.js
----------------------------------------------------------------------
diff --git a/src/xml-helpers.js b/src/xml-helpers.js
new file mode 100644
index 0000000..1d2a36a
--- /dev/null
+++ b/src/xml-helpers.js
@@ -0,0 +1,167 @@
+/*
+ *
+ * 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.
+ *
+*/
+
+/**
+ * contains XML utility functions, some of which are specific to elementtree
+ */
+
+var fs = require('fs')
+  , path = require('path')
+  , et = require('elementtree');
+
+module.exports = {
+    moveProjFile: function(origFile, projPath, callback) {
+        var src = path.resolve(projPath, origFile)
+          , dest = src.replace('.orig', '');
+
+        fs.createReadStream(src)
+            .pipe(fs.createWriteStream(dest))
+            .on('close', callback);
+    },
+
+    // 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
+    graftXML: function(doc, nodes, selector) {
+        var parent = resolveParent(doc, selector);
+        if (!parent) return false;
+
+        nodes.forEach(function (node) {
+            // check if child is unique first
+            if (uniqueChild(node, parent)) {
+                parent.append(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(0, matchingKid);
+            }
+        });
+
+        return true;
+    },
+
+    parseElementtreeSync: function (filename) {
+        var contents = fs.readFileSync(filename, 'utf-8');
+        if(contents) {
+            contents = contents.replace(/^\uFEFF/, ''); //Windows is the BOM
+        }
+        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;
+}