X-Git-Url: https://git.saurik.com/android/aapt.git/blobdiff_plain/dadd9c1fc18bd05c84a357b56e945b5829b3bd95..4a8f58fa353a64ba332abb12e02bb04b5a5e1091:/XMLNode.cpp diff --git a/XMLNode.cpp b/XMLNode.cpp index 8f45959..c0d7427 100644 --- a/XMLNode.cpp +++ b/XMLNode.cpp @@ -21,6 +21,7 @@ const char* const RESOURCES_ROOT_NAMESPACE = "http://schemas.android.com/apk/res/"; const char* const RESOURCES_ANDROID_NAMESPACE = "http://schemas.android.com/apk/res/android"; +const char* const RESOURCES_ROOT_PRV_NAMESPACE = "http://schemas.android.com/apk/prv/res/"; const char* const XLIFF_XMLNS = "urn:oasis:names:tc:xliff:document:1.2"; const char* const ALLOWED_XLIFF_ELEMENTS[] = { @@ -43,24 +44,142 @@ bool isWhitespace(const char16_t* str) } static const String16 RESOURCES_PREFIX(RESOURCES_ROOT_NAMESPACE); +static const String16 RESOURCES_PRV_PREFIX(RESOURCES_ROOT_PRV_NAMESPACE); -String16 getNamespaceResourcePackage(String16 namespaceUri) +String16 getNamespaceResourcePackage(String16 namespaceUri, bool* outIsPublic) { //printf("%s starts with %s?\n", String8(namespaceUri).string(), // String8(RESOURCES_PREFIX).string()); - if (!namespaceUri.startsWith(RESOURCES_PREFIX)) return String16(); + size_t prefixSize; + bool isPublic = true; + if (namespaceUri.startsWith(RESOURCES_PREFIX)) { + prefixSize = RESOURCES_PREFIX.size(); + } else if (namespaceUri.startsWith(RESOURCES_PRV_PREFIX)) { + isPublic = false; + prefixSize = RESOURCES_PRV_PREFIX.size(); + } else { + if (outIsPublic) *outIsPublic = isPublic; // = true + return String16(); + } + //printf("YES!\n"); - const size_t prefixSize = RESOURCES_PREFIX.size(); //printf("namespace: %s\n", String8(String16(namespaceUri, namespaceUri.size()-prefixSize, prefixSize)).string()); + if (outIsPublic) *outIsPublic = isPublic; 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* outSpans, + bool isFormatted, bool pseudolocalize) { Vector spanStack; @@ -88,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)); @@ -206,10 +329,15 @@ moveon: } spanStack.pop(); - if (empty) { - fprintf(stderr, "%s:%d: WARNING: empty '%s' span found for at text '%s'\n", + /* + * 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(*outString).string(), String8(spanTag).string()); + String8(spanTag).string(), String8(*outString).string()); } } else if (code == ResXMLTree::START_NAMESPACE) { @@ -323,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"); } @@ -460,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; @@ -473,6 +604,7 @@ XMLNode::XMLNode(const String8& filename, const String16& s1, const String16& s2 XMLNode::XMLNode(const String8& filename) : mFilename(filename) { + memset(&mCharsValue, 0, sizeof(mCharsValue)); } XMLNode::type XMLNode::getType() const @@ -511,12 +643,43 @@ const Vector >& XMLNode::getChildren() const return mChildren; } +const String8& XMLNode::getFilename() const +{ + return mFilename; +} + const Vector& XMLNode::getAttributes() const { return mAttributes; } +const XMLNode::attribute_entry* XMLNode::getAttribute(const String16& ns, + const String16& name) const +{ + for (size_t i=0; ins == ns && ae->name == name) { + return ae; + } + } + + return NULL; +} + const String16& XMLNode::getCData() const { return mChars; @@ -537,6 +700,38 @@ int32_t XMLNode::getEndLineNumber() const return mEndLineNumber; } +sp XMLNode::searchElement(const String16& tagNamespace, const String16& tagName) +{ + if (getType() == XMLNode::TYPE_ELEMENT + && mNamespaceUri == tagNamespace + && mElementName == tagName) { + return this; + } + + for (size_t i=0; i found = mChildren.itemAt(i)->searchElement(tagNamespace, tagName); + if (found != NULL) { + return found; + } + } + + return NULL; +} + +sp XMLNode::getChildElement(const String16& tagNamespace, const String16& tagName) +{ + for (size_t i=0; i child = mChildren.itemAt(i); + if (child->getType() == XMLNode::TYPE_ELEMENT + && child->mNamespaceUri == tagNamespace + && child->mElementName == tagName) { + return child; + } + } + + return NULL; +} + status_t XMLNode::addChild(const sp& child) { if (getType() == TYPE_CDATA) { @@ -548,6 +743,17 @@ status_t XMLNode::addChild(const sp& child) return NO_ERROR; } +status_t XMLNode::insertChildAt(const sp& child, size_t index) +{ + if (getType() == TYPE_CDATA) { + SourcePos(mFilename, child->getStartLineNumber()).error("Child to CDATA node."); + return UNKNOWN_ERROR; + } + //printf("Adding child %p to parent %p\n", child.get(), this); + mChildren.insertAt(child, index); + return NO_ERROR; +} + status_t XMLNode::addAttribute(const String16& ns, const String16& name, const String16& value) { @@ -715,15 +921,18 @@ status_t XMLNode::assignResourceIds(const sp& assets, for (size_t i=0; i %s\n", + bool nsIsPublic; + String16 pkg(getNamespaceResourcePackage(e.ns, &nsIsPublic)); + NOISY(printf("Elem %s %s=\"%s\": namespace(%s) %s ===> %s\n", String8(getElementName()).string(), String8(e.name).string(), String8(e.string).string(), - String8(e.ns).string(), String8(pkg).string())); + String8(e.ns).string(), + (nsIsPublic) ? "public" : "private", + String8(pkg).string())); if (pkg.size() <= 0) continue; uint32_t res = table != NULL - ? table->getResId(e.name, &attr, &pkg, &errorMsg) + ? table->getResId(e.name, &attr, &pkg, &errorMsg, nsIsPublic) : assets->getIncludedResources(). identifierForName(e.name.string(), e.name.size(), attr.string(), attr.size(), @@ -754,7 +963,7 @@ status_t XMLNode::assignResourceIds(const sp& assets, status_t XMLNode::flatten(const sp& dest, bool stripComments, bool stripRawValues) const { - StringPool strings; + StringPool strings = StringPool(false, mUTF8); Vector resids; // First collect just the strings for attribute names that have a