diff options
author | Raphael Moll <ralf@android.com> | 2012-09-28 15:29:21 -0700 |
---|---|---|
committer | Raphael Moll <ralf@android.com> | 2012-09-28 15:46:39 -0700 |
commit | 2b426ae27c0be8ebd26bc69ad59007e45487568b (patch) | |
tree | 6fb2982a35a1a9a0bd4457d27f00b4857f1ed7e1 /manifmerger/src | |
parent | c439a89ccdcf7123f52cb528d57d5ef78881e0da (diff) | |
download | sdk-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.java | 7 | ||||
-rwxr-xr-x | manifmerger/src/com/android/manifmerger/ManifestMerger.java | 32 | ||||
-rwxr-xr-x | manifmerger/src/com/android/manifmerger/XmlUtils.java | 125 |
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) { |