aboutsummaryrefslogtreecommitdiffstats
path: root/manifmerger/src
diff options
context:
space:
mode:
authorRaphael Moll <ralf@android.com>2012-09-28 15:29:21 -0700
committerRaphael Moll <ralf@android.com>2012-09-28 15:46:39 -0700
commit2b426ae27c0be8ebd26bc69ad59007e45487568b (patch)
tree6fb2982a35a1a9a0bd4457d27f00b4857f1ed7e1 /manifmerger/src
parentc439a89ccdcf7123f52cb528d57d5ef78881e0da (diff)
downloadsdk-2b426ae27c0be8ebd26bc69ad59007e45487568b.zip
sdk-2b426ae27c0be8ebd26bc69ad59007e45487568b.tar.gz
sdk-2b426ae27c0be8ebd26bc69ad59007e45487568b.tar.bz2
ManifestMerger: ability to inject attributes.
Change-Id: Icbebe1dd3c8cf51f7d38b585a78264d01977e943
Diffstat (limited to 'manifmerger/src')
-rw-r--r--manifmerger/src/com/android/manifmerger/Main.java7
-rwxr-xr-xmanifmerger/src/com/android/manifmerger/ManifestMerger.java32
-rwxr-xr-xmanifmerger/src/com/android/manifmerger/XmlUtils.java125
3 files changed, 154 insertions, 10 deletions
diff --git a/manifmerger/src/com/android/manifmerger/Main.java b/manifmerger/src/com/android/manifmerger/Main.java
index c48033f..2a6460e 100644
--- a/manifmerger/src/com/android/manifmerger/Main.java
+++ b/manifmerger/src/com/android/manifmerger/Main.java
@@ -20,6 +20,7 @@ import com.android.utils.ILogger;
import com.android.utils.StdLogger;
import java.io.File;
+import java.util.Map;
/**
* Command-line entry point of the Manifest Merger.
@@ -33,7 +34,8 @@ import java.io.File;
* Usage: <br/>
* {@code $ manifmerger merge --main main_manifest.xml --libs lib1.xml lib2.xml --out result.xml}
* <p/>
- * When used as a library, please call {@link ManifestMerger#process(File, File, File[])} directly.
+ * When used as a library, please call {@link ManifestMerger#process(File, File, File[], Map)}
+ * directly.
*/
public class Main {
@@ -68,7 +70,8 @@ public class Main {
boolean ok = mm.process(
new File(mArgvParser.getParamOut()),
new File(mArgvParser.getParamMain()),
- libFiles
+ libFiles,
+ null /*injectAttributes*/
);
System.exit(ok ? 0 : 1);
}
diff --git a/manifmerger/src/com/android/manifmerger/ManifestMerger.java b/manifmerger/src/com/android/manifmerger/ManifestMerger.java
index 688cecd..bbe6e25 100755
--- a/manifmerger/src/com/android/manifmerger/ManifestMerger.java
+++ b/manifmerger/src/com/android/manifmerger/ManifestMerger.java
@@ -46,7 +46,7 @@ import javax.xml.xpath.XPathExpressionException;
* Merges a library manifest into a main application manifest.
* <p/>
* To use, create with {@link ManifestMerger#ManifestMerger(IMergerLog, ICallback)} then
- * call {@link ManifestMerger#process(File, File, File[])}.
+ * call {@link ManifestMerger#process(File, File, File[], Map)}.
* <p/>
* <pre> Merge operations:
* - root manifest: attributes ignored, warn if defined.
@@ -170,15 +170,25 @@ public class ManifestMerger {
* @param outputFile The output path to generate. Can be the same as the main path.
* @param mainFile The main manifest paths to read. What we merge into.
* @param libraryFiles The library manifest paths to read. Must not be null.
+ * @param injectAttributes A map of attributes to inject in the form [pseudo-xpath] => value.
+ * The key is "/manifest/elements...|attribute-ns-uri attribute-local-name",
+ * for example "/manifest/uses-sdk|http://schemas.android.com/apk/res/android minSdkVersion".
+ * (note the space separator between the attribute URI and its local name.)
+ * The elements will be created if they don't exists. Existing attributes will be modified.
+ * The replacement is done on the main document <em>before</em> merging.
* @return True if the merge was completed, false otherwise.
*/
- public boolean process(File outputFile, File mainFile, File[] libraryFiles) {
+ public boolean process(
+ File outputFile,
+ File mainFile,
+ File[] libraryFiles,
+ Map<String, String> injectAttributes) {
Document mainDoc = XmlUtils.parseDocument(mainFile, mLog);
if (mainDoc == null) {
return false;
}
- boolean success = process(mainDoc, libraryFiles);
+ boolean success = process(mainDoc, libraryFiles, injectAttributes);
if (!XmlUtils.printXmlFile(mainDoc, outputFile, mLog)) {
success = false;
@@ -197,13 +207,23 @@ public class ManifestMerger {
* @param mainDoc The document to merge into. Will be modified in-place.
* @param libraryFiles The library manifest paths to read. Must not be null.
* These will be modified in-place.
+ * @param injectAttributes A map of attributes to inject in the form [pseudo-xpath] => value.
+ * The key is "/manifest/elements...|attribute-ns-uri attribute-local-name",
+ * for example "/manifest/uses-sdk|http://schemas.android.com/apk/res/android minSdkVersion".
+ * (note the space separator between the attribute URI and its local name.)
+ * The elements will be created if they don't exists. Existing attributes will be modified.
+ * The replacement is done on the main document <em>before</em> merging.
* @return True on success, false if any error occurred (printed to the {@link IMergerLog}).
*/
- public boolean process(Document mainDoc, File[] libraryFiles) {
+ public boolean process(
+ Document mainDoc,
+ File[] libraryFiles,
+ Map<String, String> injectAttributes) {
boolean success = true;
mMainDoc = mainDoc;
XmlUtils.decorateDocument(mainDoc, IMergerLog.MAIN_MANIFEST);
+ XmlUtils.injectAttributes(mainDoc, injectAttributes, mLog);
String prefix = XmlUtils.lookupNsPrefix(mainDoc, SdkConstants.NS_RESOURCES);
mXPath = AndroidXPathFactory.newXPath(prefix);
@@ -1343,9 +1363,7 @@ public class ManifestMerger {
* @return A new non-null {@link FileAndLine} combining the file name and line number.
*/
private @NonNull FileAndLine xmlFileAndLine(@NonNull Node node) {
- String name = XmlUtils.extractXmlFilename(node);
- int line = XmlUtils.extractLineNumber(node); // 0 in case of error or unknown
- return new FileAndLine(name, line);
+ return XmlUtils.xmlFileAndLine(node);
}
diff --git a/manifmerger/src/com/android/manifmerger/XmlUtils.java b/manifmerger/src/com/android/manifmerger/XmlUtils.java
index 71aac91..b17c7d4 100755
--- a/manifmerger/src/com/android/manifmerger/XmlUtils.java
+++ b/manifmerger/src/com/android/manifmerger/XmlUtils.java
@@ -24,6 +24,7 @@ import com.android.utils.ILogger;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
+import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.xml.sax.ErrorHandler;
@@ -177,6 +178,23 @@ class XmlUtils {
}
/**
+ * Returns a new {@link FileAndLine} structure that identifies
+ * the base filename & line number from which the XML node was parsed.
+ * <p/>
+ * When the line number is unknown (e.g. if a {@link Document} instance is given)
+ * then line number 0 will be used.
+ *
+ * @param node The node or document where the error occurs. Must not be null.
+ * @return A new non-null {@link FileAndLine} combining the file name and line number.
+ */
+ @NonNull
+ static FileAndLine xmlFileAndLine(@NonNull Node node) {
+ String name = extractXmlFilename(node);
+ int line = extractLineNumber(node); // 0 in case of error or unknown
+ return new FileAndLine(name, line);
+ }
+
+ /**
* Extracts the origin {@link File} that {@link #parseDocument(File, IMergerLog)}
* added to the XML document or the string added by
*
@@ -504,6 +522,111 @@ class XmlUtils {
};
}
+ /**
+ * Inject attributes into an existing document.
+ * <p/>
+ * The map keys are "/manifest/elements...|attribute-ns-uri attribute-local-name",
+ * for example "/manifest/uses-sdk|http://schemas.android.com/apk/res/android minSdkVersion".
+ * (note the space separator between the attribute URI and its local name.)
+ * The elements will be created if they don't exists. Existing attributes will be modified.
+ * The replacement is done on the main document <em>before</em> merging.
+ * The value can be null to remove an existing attribute.
+ *
+ * @param doc The document to modify in-place.
+ * @param attributeMap A map of attributes to inject in the form [pseudo-xpath] => value.
+ * @param log A log in case of error.
+ */
+ static void injectAttributes(
+ @Nullable Document doc,
+ @Nullable Map<String, String> attributeMap,
+ @NonNull IMergerLog log) {
+ if (doc == null || attributeMap == null || attributeMap.isEmpty()) {
+ return;
+ }
+
+ // 1=path 2=URI 3=local name
+ final Pattern keyRx = Pattern.compile("^/([^\\|]+)\\|([^ ]*) +(.+)$"); //$NON-NLS-1$
+ final FileAndLine docInfo = xmlFileAndLine(doc);
+
+ nextAttribute: for (Entry<String, String> entry : attributeMap.entrySet()) {
+ String key = entry.getKey();
+ String value = entry.getValue();
+ if (key == null || key.isEmpty()) {
+ continue;
+ }
+
+ Matcher m = keyRx.matcher(key);
+ if (!m.matches()) {
+ log.error(Severity.WARNING, docInfo, "Invalid injected attribute key: %s", key);
+ continue;
+ }
+ String path = m.group(1);
+ String attrNsUri = m.group(2);
+ String attrName = m.group(3);
+
+ String[] segment = path.split(Pattern.quote("/")); //$NON-NLS-1$
+
+ // Get the path elements. Create them as needed if they don't exist.
+ Node element = doc;
+ nextSegment: for (int i = 0; i < segment.length; i++) {
+ // Find a child with the segment's name
+ String name = segment[i];
+ for (Node child = element.getFirstChild();
+ child != null;
+ child = child.getNextSibling()) {
+ if (child.getNodeType() == Node.ELEMENT_NODE &&
+ child.getNamespaceURI() == null &&
+ child.getNodeName().equals(name)) {
+ // Found it. Continue to the next inner segment.
+ element = child;
+ continue nextSegment;
+ }
+ }
+ // No such element. Create it.
+ if (value == null) {
+ // If value is null, we want to remove, not create and if can't find the
+ // element, then we're done: there's no such attribute to remove.
+ break nextAttribute;
+ }
+
+ Element child = doc.createElement(name);
+ element = element.insertBefore(child, element.getFirstChild());
+ }
+
+ if (element == null) {
+ log.error(Severity.WARNING, docInfo, "Invalid injected attribute path: %s", path);
+ return;
+ }
+
+ NamedNodeMap attrs = element.getAttributes();
+ if (attrs != null) {
+
+
+ if (attrNsUri != null && attrNsUri.isEmpty()) {
+ attrNsUri = null;
+ }
+ Node attr = attrs.getNamedItemNS(attrNsUri, attrName);
+
+ if (value == null) {
+ // We want to remove the attribute from the attribute map.
+ if (attr != null) {
+ attrs.removeNamedItemNS(attrNsUri, attrName);
+ }
+
+ } else {
+ // We want to add or replace the attribute.
+ if (attr == null) {
+ attr = doc.createAttributeNS(attrNsUri, attrName);
+ attr.setPrefix(
+ com.android.utils.XmlUtils.lookupNamespacePrefix(element, attrNsUri));
+ attrs.setNamedItemNS(attr);
+ }
+ attr.setNodeValue(value);
+ }
+ }
+ }
+ }
+
// -------
/**
@@ -534,7 +657,7 @@ class XmlUtils {
sb.append(node.getLocalName());
printAttributes(sb, node, nsPrefix, prefix);
sb.append(">\n"); //$NON-NLS-1$
- printChildren(sb, node.getFirstChild(), true, nsPrefix, prefix + " "); //$NON-NLS-1$
+ printChildren(sb, node.getFirstChild(), true, nsPrefix, prefix + " "); //$NON-NLS-1$
sb.append(prefix).append("</"); //$NON-NLS-1$
if (uri != null) {