Dump binary Android xml files as plain text
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.
-
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>
-
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>
-
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>
-
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: <APK>#<XML_Path></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>
-
src/com/android/xmltool/DumpXml.javapackage 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(); } } }
- Screenshot
- Pre-built APK can be downloaded from: http://code.google.com/p/android-binxml-dump/