]> git.saurik.com Git - android/aapt.git/blobdiff - XMLNode.cpp
Normalize output from aapt d
[android/aapt.git] / XMLNode.cpp
index 6daa0d206da62065111102bf20c68b21d4fb6b17..c0d74275a8a6a6883fedeed008ab6e993e4658cc 100644 (file)
@@ -68,12 +68,118 @@ String16 getNamespaceResourcePackage(String16 namespaceUri, bool* outIsPublic)
     return String16(namespaceUri, namespaceUri.size()-prefixSize, prefixSize);
 }
 
+status_t hasSubstitutionErrors(const char* fileName,
+                               ResXMLTree* inXml,
+                               String16 str16)
+{
+    const char16_t* str = str16.string();
+    const char16_t* p = str;
+    const char16_t* end = str + str16.size();
+
+    bool nonpositional = false;
+    int argCount = 0;
+
+    while (p < end) {
+        /*
+         * Look for the start of a Java-style substitution sequence.
+         */
+        if (*p == '%' && p + 1 < end) {
+            p++;
+
+            // A literal percent sign represented by %%
+            if (*p == '%') {
+                p++;
+                continue;
+            }
+
+            argCount++;
+
+            if (*p >= '0' && *p <= '9') {
+                do {
+                    p++;
+                } while (*p >= '0' && *p <= '9');
+                if (*p != '$') {
+                    // This must be a size specification instead of position.
+                    nonpositional = true;
+                }
+            } else if (*p == '<') {
+                // Reusing last argument; bad idea since it can be re-arranged.
+                nonpositional = true;
+                p++;
+
+                // Optionally '$' can be specified at the end.
+                if (p < end && *p == '$') {
+                    p++;
+                }
+            } else {
+                nonpositional = true;
+            }
+
+            // Ignore flags and widths
+            while (p < end && (*p == '-' ||
+                    *p == '#' ||
+                    *p == '+' ||
+                    *p == ' ' ||
+                    *p == ',' ||
+                    *p == '(' ||
+                    (*p >= '0' && *p <= '9'))) {
+                p++;
+            }
+
+            /*
+             * This is a shortcut to detect strings that are going to Time.format()
+             * instead of String.format()
+             *
+             * Comparison of String.format() and Time.format() args:
+             *
+             * String: ABC E GH  ST X abcdefgh  nost x
+             *   Time:    DEFGHKMS W Za  d   hkm  s w yz
+             *
+             * Therefore we know it's definitely Time if we have:
+             *     DFKMWZkmwyz
+             */
+            if (p < end) {
+                switch (*p) {
+                case 'D':
+                case 'F':
+                case 'K':
+                case 'M':
+                case 'W':
+                case 'Z':
+                case 'k':
+                case 'm':
+                case 'w':
+                case 'y':
+                case 'z':
+                    return NO_ERROR;
+                }
+            }
+        }
+
+        p++;
+    }
+
+    /*
+     * If we have more than one substitution in this string and any of them
+     * are not in positional form, give the user an error.
+     */
+    if (argCount > 1 && nonpositional) {
+        SourcePos(String8(fileName), inXml->getLineNumber()).error(
+                "Multiple substitutions specified in non-positional format; "
+                "did you mean to add the formatted=\"false\" attribute?\n");
+        return NOT_ENOUGH_DATA;
+    }
+
+    return NO_ERROR;
+}
+
 status_t parseStyledString(Bundle* bundle,
                            const char* fileName,
                            ResXMLTree* inXml,
                            const String16& endTag,
                            String16* outString,
                            Vector<StringPool::entry_style_span>* outSpans,
+                           bool isFormatted,
                            bool pseudolocalize)
 {
     Vector<StringPool::entry_style_span> spanStack;
@@ -101,7 +207,11 @@ status_t parseStyledString(Bundle* bundle,
                 std::string pseudo = pseudolocalize_string(orig);
                 curString.append(String16(String8(pseudo.c_str())));
             } else {
-                curString.append(text);
+                if (isFormatted && hasSubstitutionErrors(fileName, inXml, text) != NO_ERROR) {
+                    return UNKNOWN_ERROR;
+                } else {
+                    curString.append(text);
+                }
             }
         } else if (code == ResXMLTree::START_TAG) {
             const String16 element16(inXml->getElementName(&len));
@@ -219,7 +329,12 @@ moveon:
             }
             spanStack.pop();
 
-            if (empty) {
+            /*
+             * This warning seems to be just an irritation to most people,
+             * since it is typically introduced by translators who then never
+             * see the warning.
+             */
+            if (0 && empty) {
                 fprintf(stderr, "%s:%d: warning: empty '%s' span found in text '%s'\n",
                         fileName, inXml->getLineNumber(),
                         String8(spanTag).string(), String8(*outString).string());
@@ -336,13 +451,15 @@ void printXMLBlock(ResXMLTree* block)
                     printf("=?0x%x", (int)value.data);
                 } else if (value.dataType == Res_value::TYPE_STRING) {
                     printf("=\"%s\"",
-                           String8(block->getAttributeStringValue(i, &len)).string());
+                            ResTable::normalizeForOutput(String8(block->getAttributeStringValue(i,
+                                        &len)).string()).string());
                 } else {
                     printf("=(type 0x%x)0x%x", (int)value.dataType, (int)value.data);
                 }
                 const char16_t* val = block->getAttributeStringValue(i, &len);
                 if (val != NULL) {
-                    printf(" (Raw: \"%s\")", String8(val).string());
+                    printf(" (Raw: \"%s\")", ResTable::normalizeForOutput(String8(val).string()).
+                            string());
                 }
                 printf("\n");
             }
@@ -473,6 +590,7 @@ XMLNode::XMLNode(const String8& filename, const String16& s1, const String16& s2
     , mFilename(filename)
     , mStartLineNumber(0)
     , mEndLineNumber(0)
+    , mUTF8(false)
 {
     if (isNamespace) {
         mNamespacePrefix = s1;
@@ -549,6 +667,19 @@ const XMLNode::attribute_entry* XMLNode::getAttribute(const String16& ns,
     return NULL;
 }
 
+XMLNode::attribute_entry* XMLNode::editAttribute(const String16& ns,
+        const String16& name)
+{
+    for (size_t i=0; i<mAttributes.size(); i++) {
+        attribute_entry * ae = &mAttributes.editItemAt(i);
+        if (ae->ns == ns && ae->name == name) {
+            return ae;
+        }
+    }
+
+    return NULL;
+}
+
 const String16& XMLNode::getCData() const
 {
     return mChars;
@@ -832,7 +963,7 @@ status_t XMLNode::assignResourceIds(const sp<AaptAssets>& assets,
 status_t XMLNode::flatten(const sp<AaptFile>& dest,
         bool stripComments, bool stripRawValues) const
 {
-    StringPool strings;
+    StringPool strings = StringPool(false, mUTF8);
     Vector<uint32_t> resids;
     
     // First collect just the strings for attribute names that have a