Dump binary Android xml files as plain text 

Joined:
07/27/2010
Posts:
128

May 13, 2011 15:45:29    Last update: September 20, 2011 08:08:12
This is an Android app that dumps any binarized xml file as plain text - to the sdcard on the device or emulator.
  1. build.xml:
    <?xml version="1.0" encoding="UTF-8"?>
    <project name="DumpXml" default="help">
        <property file="local.properties" />
        <property file="build.properties" />
        <property file="default.properties" />
    
        <path id="android.antlibs">
            <pathelement path="${sdk.dir}/tools/lib/anttasks.jar" />
            <pathelement path="${sdk.dir}/tools/lib/sdklib.jar" />
            <pathelement path="${sdk.dir}/tools/lib/androidprefs.jar" />
            <pathelement path="${sdk.dir}/tools/lib/apkbuilder.jar" />
            <pathelement path="${sdk.dir}/tools/lib/jarutils.jar" />
        </path>
    
        <path id="android.target.classpath">
        </path>
    
        <taskdef name="setup"
            classname="com.android.ant.SetupTask"
            classpathref="android.antlibs" />
    
        <setup />
    
        <property name="original.boot.classpath" refid="android.target.classpath"/>
        <path id="android.target.classpath">
            <pathelement path="${sdk.dir}/platforms/android-8/data/layoutlib.jar" />
    	<pathelement path="${original.boot.classpath}"/>
        </path>
    
    </project>
    

  2. AndroidManifest.xml
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android" 
        package="com.android.xmltool" 
        android:versionCode="1" 
        android:versionName="1.0">
        <application android:label="@string/app_name" android:icon="@drawable/icon">
    	<activity android:label="@string/app_name" 
    		  android:name="DumpXml">
    	    <intent-filter>
    		<action android:name="android.intent.action.MAIN"/>
    		<category android:name="android.intent.category.LAUNCHER"/>
    	    </intent-filter>
    	</activity>
        </application>
    </manifest>
    

  3. res/layout/main.xml
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        >
    <TextView  
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content" 
        android:text="@string/prompt"
        />
    
    <EditText
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content" 
        android:id="@+id/path"
        />
    
    <Button
         android:layout_height="wrap_content"
         android:layout_width="wrap_content"
         android:text="@string/dumpXml"
         android:onClick="dumpXml" />
    
    <TextView  
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content" 
        android:id="@+id/status"
        />
    </LinearLayout>
    

  4. res/values/strings.xml:
    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <string name="app_name">Dump XML</string>
        <string name="prompt">Enter XML File Path:</string>
        <string name="dumpXml">Dump!</string>
        <string name="status">Dumped %1$s to %2$s</string>
        <string name="path_needed">Please enter the path to the XML file</string>
        <string name="path_format">The expected path format is: &lt;APK&gt;#&lt;XML_Path&gt;</string>
        <string name="package_not_found">Package %s not found</string>
        <string name="cannot_find">Cannot find XML file: %s</string>
        <string name="failed">Failed to dump XML: %s</string>
    </resources>
    

  5. src/com/android/xmltool/DumpXml.java
    package com.android.xmltool;
    
    import java.util.*;
    import java.util.zip.*;
    import java.io.*;
    
    import org.xmlpull.v1.*;
    
    import android.app.Activity;
    import android.content.Context;
    import android.content.pm.PackageManager;
    import android.content.res.AssetManager;
    import android.content.res.XmlResourceParser;
    import android.os.Bundle;
    import android.os.Environment;
    import android.util.Log;
    import android.view.View;
    import android.widget.TextView;
    
    public class DumpXml extends Activity {
        private static final String TAG = "DumpXml";
        private TextView pathField;
        private TextView statusField;
    
        private Map<String,String> namespaces;
        private Stack<NamespaceUriDepthPair> namespaceStack;
    
        @Override
        public void onCreate(Bundle savedInstanceState)  {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
    	pathField = (TextView) findViewById(R.id.path);
    	statusField = (TextView) findViewById(R.id.status);
        }
    
        public void dumpXml(View view) {
    	String path = pathField.getText().toString();
    	if (path.trim().equals("")) {
    	    statusField.setText(getString(R.string.path_needed));
    	    return;
    	}
    
    	String[] pathParts = path.split("#");
    	if (pathParts.length != 2) {
    	    statusField.setText(getString(R.string.path_format));
    	    return;
    	}
    
    	String apkPath = pathParts[0];
    	String xmlPath = pathParts[1];
    	if (!apkPath.startsWith("/")) { // package name
    	    try {
    		Context ctx = createPackageContext(apkPath, Context.CONTEXT_IGNORE_SECURITY);
    		apkPath = ctx.getPackageCodePath();
    	    }
    	    catch (PackageManager.NameNotFoundException e) {
    		statusField.setText(String.format(
    			getString(R.string.package_not_found),
    			apkPath)
    		);
    		return;
    	    }
    	}
    
    	AssetManager am = getAssets();
    	int cookie = am.addAssetPath(apkPath);
    	if (cookie == 0) {
    	    statusField.setText(String.format(getString(R.string.cannot_find), path));
    	    return;
    	}
    
    	Log.d(TAG, "cookie = " + cookie);
    	XmlResourceParser parser = null;
    	try {
    	    parser = am.openXmlResourceParser(cookie, xmlPath);
    	}
    	catch (Exception e) {
    	    statusField.setText(String.format(getString(R.string.cannot_find), path));
    	    return;
    	}
    
    	String dumpPath = null;
    	try {
    	    dumpPath = dumpXmlToSdCard(parser, xmlPath);
    	}
    	catch (Exception e) {
    	    ByteArrayOutputStream o = new ByteArrayOutputStream();
    	    e.printStackTrace(new PrintStream(o));
    	    Log.e(TAG, o.toString());
    	    statusField.setText(String.format(getString(R.string.failed), e.getMessage()));
    	    return;
    	}
    	finally {
    	    parser.close();
    	}
    
    	statusField.setText(String.format(getString(R.string.status), path, dumpPath));
        }
    
        private String dumpXmlToSdCard(XmlResourceParser p, String xmlPath) throws Exception {
    	Log.d(TAG, "Parser: " + p);
    
    	namespaces = new HashMap<String,String>();
    	namespaces.put("http://schemas.android.com/apk/res/android", "android");
    	namespaceStack = new Stack<NamespaceUriDepthPair>();
    
    	File f = new File(Environment.getExternalStorageDirectory(), xmlPath);
    	Log.d(TAG, "Output file: " + f.getPath());
    
    	PrintWriter out = new PrintWriter(new FileWriter(f));
    
    	int nextEvent = p.next();
    	String version = (String) p.getProperty("http://xmlpull.org/v1/doc/properties.html#xmldecl-version");
    	String standalone = (String) p.getProperty("http://xmlpull.org/v1/doc/features.html#xmldecl-standalone");
    	String encoding = p.getInputEncoding();
    
    	out.println(String.format("<?xml%s%s%s?>",
    			(version == null) ? "" : " version=\"" + version + "\"",
    			(standalone == null) ? "" : " standalone=\"" + standalone + "\"",
    			(encoding == null) ? "" : " encoding=\"" + encoding + "\""));
    
    	int depth = 1;
    	boolean extraBlankLine = false;
    	while (nextEvent != XmlPullParser.END_DOCUMENT) {
    	    if (nextEvent == XmlPullParser.START_TAG) {
    		depth = p.getDepth();
    		String uri = p.getNamespace();
    		pushNamespaceStack(uri, depth);
    		String prefix = getNamespacePrefix(uri); 
    		StringBuilder sb = new StringBuilder(getIndent(depth-1) + "<" + 
    					(prefix.equals("") ? p.getName() : prefix + ":" + p.getName()));
    		int attrCnt = p.getAttributeCount();
    		if (attrCnt > 0) {
    		    for (int i = 0; i < attrCnt; i++) {
    			uri = p.getAttributeNamespace(i);
    			pushNamespaceStack(uri, depth);
    			prefix = getNamespacePrefix(uri);
    			sb.append(String.format(
    				(i == 0) ? " %s=\"%s\"" : "\n" + getIndent(depth) + "%s=\"%s\"",
    				prefix.equals("") ? p.getAttributeName(i) : prefix + ":" + p.getAttributeName(i),
    				p.getAttributeValue(i)
    			));
    		    }
    		}
    
    		for (NamespaceUriDepthPair pair: namespaceStack) {
    		    if (pair.depth == depth) {
    			sb.append(String.format("\n" + getIndent(depth) +"xmlns:%s=\"%s\"", 
    				    getNamespacePrefix(pair.uri), 
    				    pair.uri
    				 )
    			);
    		    }
    		}
    
    		if (p.isEmptyElementTag()) {
    		    sb.append("/>\n");
    		    nextEvent = p.next();
    		    popNamespaceStack(depth);
    		    depth--;
    		}
    		else {
    		    sb.append(">\n");
    		}
    
    		if (extraBlankLine) {
    		    out.println();
    		    extraBlankLine = false;
    		}
    
    		out.print(sb.toString());
    	    }
    	    else if (nextEvent == XmlPullParser.END_TAG) {
    		out.print(getIndent(depth-1) + "</" + p.getName() + ">\n");
    		extraBlankLine = true;
    		popNamespaceStack(depth);
    		depth--;
    	    }
    	    else if (nextEvent == XmlPullParser.TEXT) {
    		out.print(p.getText());
    	    }
    
    	    nextEvent = p.next();
    	}
    
    	out.close();
    
    	return f.getPath();
        }
    
        private String getIndent(int depth) {
    	StringBuilder sb = new StringBuilder();
    	for (int i = 0; i < depth; i++) {
    	    sb.append("    ");
    	}
    	return sb.toString();
        }
    
        private char currentPrefix = 'a' - 1;
        private String getNextPrefix() {
    	return new String(new char[] {++currentPrefix});
        }
    
        private String getNamespacePrefix(String namespaceUri) {
    	String prefix = "";
    	if ((namespaceUri != null) && !"".equals(namespaceUri)) {
    	    prefix = namespaces.get(namespaceUri);
    	    if (prefix == null) {
    		prefix = getNextPrefix();
    		namespaces.put(namespaceUri, prefix);
    	    }
    	}
    	return prefix;
        }
    
        private static class NamespaceUriDepthPair {
    	public String uri;
    	public int depth;
    
    	public NamespaceUriDepthPair(String uri, int depth) {
    	    this.uri = uri;
    	    this.depth = depth;
    	}
        }
    
        private void pushNamespaceStack(String uri, int depth) {
    	if (uri == null || uri.equals("")) {
    	    return;
    	}
    
    	for (NamespaceUriDepthPair p: namespaceStack) {
    	    if (p.uri.equals(uri)) {
    		return;
    	    }
    	}
    
    	namespaceStack.push(new NamespaceUriDepthPair(uri, depth));
        }
    
        private void popNamespaceStack(int depth) {
    	while (!namespaceStack.empty() && namespaceStack.peek().depth == depth) {
    	    namespaceStack.pop();
    	}
        }
    }
    

  6. Screenshot

  7. Pre-built APK can be downloaded from: http://code.google.com/p/android-binxml-dump/
Share |
| Comment  | Tags