From: Jean-Baptiste Queru Date: Fri, 5 Nov 2010 21:05:41 +0000 (-0700) Subject: am 5580e44c: Merge "Support changing style parent in overlays" X-Git-Url: https://git.saurik.com/android/aapt.git/commitdiff_plain/034f2c9aac98c8c5d40667f1bf047211ed8f09f0?hp=feea11dc0b8a3ffd908fc6d0a076c8dc575c4de0 am 5580e44c: Merge "Support changing style parent in overlays" * commit '5580e44c250944f5cd011b2682eea5cc2de9706c': Support changing style parent in overlays --- diff --git a/AaptAssets.cpp b/AaptAssets.cpp index efc9619..e4f447e 100644 --- a/AaptAssets.cpp +++ b/AaptAssets.cpp @@ -766,6 +766,11 @@ bool AaptGroupEntry::getScreenLayoutSizeName(const char* name, (out->screenLayout&~ResTable_config::MASK_SCREENSIZE) | ResTable_config::SCREENSIZE_LARGE; return true; + } else if (strcmp(name, "xlarge") == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_SCREENSIZE) + | ResTable_config::SCREENSIZE_XLARGE; + return true; } return false; @@ -1825,6 +1830,16 @@ ssize_t AaptAssets::slurpResourceTree(Bundle* bundle, const String8& srcDir) continue; } + if (bundle->getMaxResVersion() != NULL && group.version.length() != 0) { + int maxResInt = atoi(bundle->getMaxResVersion()); + const char *verString = group.version.string(); + int dirVersionInt = atoi(verString + 1); // skip 'v' in version name + if (dirVersionInt > maxResInt) { + fprintf(stderr, "max res %d, skipping %s\n", maxResInt, entry->d_name); + continue; + } + } + FileType type = getFileType(subdirName.string()); if (type == kFileTypeDirectory) { diff --git a/Bundle.h b/Bundle.h index c198b0b..c5aa573 100644 --- a/Bundle.h +++ b/Bundle.h @@ -45,7 +45,7 @@ public: mRClassDir(NULL), mResourceIntermediatesDir(NULL), mManifestMinSdkVersion(NULL), mMinSdkVersion(NULL), mTargetSdkVersion(NULL), mMaxSdkVersion(NULL), mVersionCode(NULL), mVersionName(NULL), mCustomPackage(NULL), - mDebugMode(false), + mMaxResVersion(NULL), mDebugMode(false), mProduct(NULL), mArgc(0), mArgv(NULL) {} ~Bundle(void) {} @@ -135,8 +135,12 @@ public: void setVersionName(const char* val) { mVersionName = val; } const char* getCustomPackage() const { return mCustomPackage; } void setCustomPackage(const char* val) { mCustomPackage = val; } + const char* getMaxResVersion() const { return mMaxResVersion; } + void setMaxResVersion(const char * val) { mMaxResVersion = val; } bool getDebugMode() { return mDebugMode; } void setDebugMode(bool val) { mDebugMode = val; } + const char* getProduct() const { return mProduct; } + void setProduct(const char * val) { mProduct = val; } /* * Set and get the file specification. @@ -233,7 +237,9 @@ private: const char* mVersionCode; const char* mVersionName; const char* mCustomPackage; + const char* mMaxResVersion; bool mDebugMode; + const char* mProduct; /* file specification */ int mArgc; diff --git a/Command.cpp b/Command.cpp index 83057b8..661ecb1 100644 --- a/Command.cpp +++ b/Command.cpp @@ -141,9 +141,9 @@ int doList(Bundle* bundle) if (bundle->getVerbose()) { printf("Archive: %s\n", zipFileName); printf( - " Length Method Size Ratio Date Time CRC-32 Name\n"); + " Length Method Size Ratio Offset Date Time CRC-32 Name\n"); printf( - "-------- ------ ------- ----- ---- ---- ------ ----\n"); + "-------- ------ ------- ----- ------- ---- ---- ------ ----\n"); } totalUncLen = totalCompLen = 0; @@ -159,12 +159,13 @@ int doList(Bundle* bundle) strftime(dateBuf, sizeof(dateBuf), "%m-%d-%y %H:%M", localtime(&when)); - printf("%8ld %-7.7s %7ld %3d%% %s %08lx %s\n", + printf("%8ld %-7.7s %7ld %3d%% %8zd %s %08lx %s\n", (long) entry->getUncompressedLen(), compressionName(entry->getCompressionMethod()), (long) entry->getCompressedLen(), calcPercent(entry->getUncompressedLen(), entry->getCompressedLen()), + (size_t) entry->getLFHOffset(), dateBuf, entry->getCRC32(), entry->getFileName()); @@ -331,12 +332,15 @@ enum { REQ_FIVE_WAY_NAV_ATTR = 0x01010232, TARGET_SDK_VERSION_ATTR = 0x01010270, TEST_ONLY_ATTR = 0x01010272, - DENSITY_ATTR = 0x0101026c, + ANY_DENSITY_ATTR = 0x0101026c, GL_ES_VERSION_ATTR = 0x01010281, SMALL_SCREEN_ATTR = 0x01010284, NORMAL_SCREEN_ATTR = 0x01010285, LARGE_SCREEN_ATTR = 0x01010286, + XLARGE_SCREEN_ATTR = 0x010102bf, REQUIRED_ATTR = 0x0101028e, + SCREEN_SIZE_ATTR = 0x010102ca, + SCREEN_DENSITY_ATTR = 0x010102cb, }; const char *getComponentName(String8 &pkgName, String8 &componentName) { @@ -353,6 +357,42 @@ const char *getComponentName(String8 &pkgName, String8 &componentName) { return retStr.string(); } +static void printCompatibleScreens(ResXMLTree& tree) { + size_t len; + ResXMLTree::event_code_t code; + int depth = 0; + bool first = true; + printf("compatible-screens:"); + while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { + if (code == ResXMLTree::END_TAG) { + depth--; + if (depth < 0) { + break; + } + continue; + } + if (code != ResXMLTree::START_TAG) { + continue; + } + depth++; + String8 tag(tree.getElementName(&len)); + if (tag == "screen") { + int32_t screenSize = getIntegerAttribute(tree, + SCREEN_SIZE_ATTR, NULL, -1); + int32_t screenDensity = getIntegerAttribute(tree, + SCREEN_DENSITY_ATTR, NULL, -1); + if (screenSize > 0 && screenDensity > 0) { + if (!first) { + printf(","); + } + first = false; + printf("'%d/%d'", screenSize, screenDensity); + } + } + } + printf("\n"); +} + /* * Handle the "dump" command, to extract select data from an archive. */ @@ -569,6 +609,8 @@ int doDump(Bundle* bundle) int smallScreen = 1; int normalScreen = 1; int largeScreen = 1; + int xlargeScreen = 1; + int anyDensity = 1; String8 pkg; String8 activityName; String8 activityLabel; @@ -736,14 +778,6 @@ int doDump(Bundle* bundle) printf(" reqFiveWayNav='%d'", reqFiveWayNav); } printf("\n"); - } else if (tag == "supports-density") { - int32_t dens = getIntegerAttribute(tree, DENSITY_ATTR, &error); - if (error != "") { - fprintf(stderr, "ERROR getting 'android:density' attribute: %s\n", - error.string()); - goto bail; - } - printf("supports-density:'%d'\n", dens); } else if (tag == "supports-screens") { smallScreen = getIntegerAttribute(tree, SMALL_SCREEN_ATTR, NULL, 1); @@ -751,6 +785,10 @@ int doDump(Bundle* bundle) NORMAL_SCREEN_ATTR, NULL, 1); largeScreen = getIntegerAttribute(tree, LARGE_SCREEN_ATTR, NULL, 1); + xlargeScreen = getIntegerAttribute(tree, + XLARGE_SCREEN_ATTR, NULL, 1); + anyDensity = getIntegerAttribute(tree, + ANY_DENSITY_ATTR, NULL, 1); } else if (tag == "uses-feature") { String8 name = getAttribute(tree, NAME_ATTR, &error); @@ -848,6 +886,15 @@ int doDump(Bundle* bundle) error.string()); goto bail; } + } else if (tag == "uses-package") { + String8 name = getAttribute(tree, NAME_ATTR, &error); + if (name != "" && error == "") { + printf("uses-package:'%s'\n", name.string()); + } else { + fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", + error.string()); + goto bail; + } } else if (tag == "original-package") { String8 name = getAttribute(tree, NAME_ATTR, &error); if (name != "" && error == "") { @@ -857,6 +904,18 @@ int doDump(Bundle* bundle) error.string()); goto bail; } + } else if (tag == "uses-gl-texture") { + String8 name = getAttribute(tree, NAME_ATTR, &error); + if (name != "" && error == "") { + printf("uses-gl-texture:'%s'\n", name.string()); + } else { + fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", + error.string()); + goto bail; + } + } else if (tag == "compatible-screens") { + printCompatibleScreens(tree); + depth--; } } else if (depth == 3 && withinApplication) { withinActivity = false; @@ -1079,12 +1138,22 @@ int doDump(Bundle* bundle) if (largeScreen > 0) { largeScreen = targetSdk >= 4 ? -1 : 0; } + if (xlargeScreen > 0) { + // Introduced in Gingerbread. + xlargeScreen = targetSdk >= 9 ? -1 : 0; + } + if (anyDensity > 0) { + anyDensity = targetSdk >= 4 ? -1 : 0; + } printf("supports-screens:"); if (smallScreen != 0) printf(" 'small'"); if (normalScreen != 0) printf(" 'normal'"); if (largeScreen != 0) printf(" 'large'"); + if (xlargeScreen != 0) printf(" 'xlarge'"); printf("\n"); + printf("supports-any-density: '%s'\n", anyDensity ? "true" : "false"); + printf("locales:"); Vector locales; res.getLocales(&locales); diff --git a/Main.cpp b/Main.cpp index 229e13b..739b01f 100644 --- a/Main.cpp +++ b/Main.cpp @@ -62,10 +62,12 @@ void usage(void) " [--rename-manifest-package PACKAGE] \\\n" " [--rename-instrumentation-target-package PACKAGE] \\\n" " [--utf16] [--auto-add-overlay] \\\n" + " [--max-res-version VAL] \\\n" " [-I base-package [-I base-package ...]] \\\n" " [-A asset-source-dir] [-G class-list-file] [-P public-definitions-file] \\\n" " [-S resource-sources [-S resource-sources ...]] " " [-F apk-file] [-J R-file-dir] \\\n" + " [--product product1,product2,...] \\\n" " [raw-files-dir [raw-files-dir] ...]\n" "\n" " Package the android resources. It will read assets and resources that are\n" @@ -131,6 +133,8 @@ void usage(void) " higher, the default encoding for resources will be in UTF-8.\n" " --target-sdk-version\n" " inserts android:targetSdkVersion in to manifest.\n" + " --max-res-version\n" + " ignores versioned resource directories above the given value.\n" " --values\n" " when used with \"dump resources\" also includes resource values.\n" " --version-code\n" @@ -151,6 +155,9 @@ void usage(void) " components target the given package. Useful when used in\n" " conjunction with --rename-manifest-package to fix tests against\n" " a package that has been renamed.\n" + " --product\n" + " Specifies which variant to choose for strings that have\n" + " product variants\n" " --utf16\n" " changes default encoding for resources to UTF-16. Only useful when API\n" " level is set to 7 or higher where the default encoding is UTF-8.\n"); @@ -421,6 +428,15 @@ int main(int argc, char* const argv[]) goto bail; } bundle.setMaxSdkVersion(argv[0]); + } else if (strcmp(cp, "-max-res-version") == 0) { + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '--max-res-version' option\n"); + wantUsage = true; + goto bail; + } + bundle.setMaxResVersion(argv[0]); } else if (strcmp(cp, "-version-code") == 0) { argc--; argv++; @@ -472,6 +488,15 @@ int main(int argc, char* const argv[]) bundle.setInstrumentationPackageNameOverride(argv[0]); } else if (strcmp(cp, "-auto-add-overlay") == 0) { bundle.setAutoAddOverlay(true); + } else if (strcmp(cp, "-product") == 0) { + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '--product' option\n"); + wantUsage = true; + goto bail; + } + bundle.setProduct(argv[0]); } else { fprintf(stderr, "ERROR: Unknown option '-%s'\n", cp); wantUsage = true; diff --git a/Resource.cpp b/Resource.cpp index 7ffd43b..c8ba904 100644 --- a/Resource.cpp +++ b/Resource.cpp @@ -1889,7 +1889,7 @@ addProguardKeepRule(ProguardKeepSet* keep, const String8& inClassName, className.append(inClassName); } } - + String8 rule("-keep class "); rule += className; rule += " { (...); }"; @@ -1964,7 +1964,7 @@ writeProguardForAndroidManifest(ProguardKeepSet* keep, const sp& ass if (tag == "application") { inApplication = true; keepTag = true; - + String8 agent = getAttribute(tree, "http://schemas.android.com/apk/res/android", "backupAgent", &error); if (agent.length() > 0) { @@ -1997,9 +1997,17 @@ writeProguardForAndroidManifest(ProguardKeepSet* keep, const sp& ass return NO_ERROR; } +struct NamespaceAttributePair { + const char* ns; + const char* attr; + + NamespaceAttributePair(const char* n, const char* a) : ns(n), attr(a) {} + NamespaceAttributePair() : ns(NULL), attr(NULL) {} +}; + status_t writeProguardForXml(ProguardKeepSet* keep, const sp& layoutFile, - const char* startTag, const char* altTag) + const char* startTag, const KeyedVector* tagAttrPairs) { status_t err; ResXMLTree tree; @@ -2029,7 +2037,7 @@ writeProguardForXml(ProguardKeepSet* keep, const sp& layoutFile, return NO_ERROR; } } - + while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { if (code != ResXMLTree::START_TAG) { continue; @@ -2040,16 +2048,21 @@ writeProguardForXml(ProguardKeepSet* keep, const sp& layoutFile, if (strchr(tag.string(), '.')) { addProguardKeepRule(keep, tag, NULL, layoutFile->getPrintableSource(), tree.getLineNumber()); - } else if (altTag != NULL && tag == altTag) { - ssize_t classIndex = tree.indexOfAttribute(NULL, "class"); - if (classIndex < 0) { - fprintf(stderr, "%s:%d: does not have class attribute.\n", - layoutFile->getPrintableSource().string(), tree.getLineNumber()); - } else { - size_t len; - addProguardKeepRule(keep, - String8(tree.getAttributeStringValue(classIndex, &len)), NULL, - layoutFile->getPrintableSource(), tree.getLineNumber()); + } else if (tagAttrPairs != NULL) { + ssize_t tagIndex = tagAttrPairs->indexOfKey(tag); + if (tagIndex >= 0) { + const NamespaceAttributePair& nsAttr = tagAttrPairs->valueAt(tagIndex); + ssize_t attrIndex = tree.indexOfAttribute(nsAttr.ns, nsAttr.attr); + if (attrIndex < 0) { + // fprintf(stderr, "%s:%d: <%s> does not have attribute %s:%s.\n", + // layoutFile->getPrintableSource().string(), tree.getLineNumber(), + // tag.string(), nsAttr.ns, nsAttr.attr); + } else { + size_t len; + addProguardKeepRule(keep, + String8(tree.getAttributeStringValue(attrIndex, &len)), NULL, + layoutFile->getPrintableSource(), tree.getLineNumber()); + } } } } @@ -2057,25 +2070,42 @@ writeProguardForXml(ProguardKeepSet* keep, const sp& layoutFile, return NO_ERROR; } +static void addTagAttrPair(KeyedVector* dest, + const char* tag, const char* ns, const char* attr) { + dest->add(String8(tag), NamespaceAttributePair(ns, attr)); +} + status_t writeProguardForLayouts(ProguardKeepSet* keep, const sp& assets) { status_t err; + + // tag:attribute pairs that should be checked in layout files. + KeyedVector kLayoutTagAttrPairs; + addTagAttrPair(&kLayoutTagAttrPairs, "view", NULL, "class"); + addTagAttrPair(&kLayoutTagAttrPairs, "fragment", RESOURCES_ANDROID_NAMESPACE, "name"); + + // tag:attribute pairs that should be checked in xml files. + KeyedVector kXmlTagAttrPairs; + addTagAttrPair(&kXmlTagAttrPairs, "PreferenceScreen", RESOURCES_ANDROID_NAMESPACE, "fragment"); + addTagAttrPair(&kXmlTagAttrPairs, "Header", RESOURCES_ANDROID_NAMESPACE, "fragment"); + const Vector >& dirs = assets->resDirs(); const size_t K = dirs.size(); for (size_t k=0; k& d = dirs.itemAt(k); const String8& dirName = d->getLeaf(); const char* startTag = NULL; - const char* altTag = NULL; + const KeyedVector* tagAttrPairs = NULL; if ((dirName == String8("layout")) || (strncmp(dirName.string(), "layout-", 7) == 0)) { - altTag = "view"; + tagAttrPairs = &kLayoutTagAttrPairs; } else if ((dirName == String8("xml")) || (strncmp(dirName.string(), "xml-", 4) == 0)) { startTag = "PreferenceScreen"; + tagAttrPairs = &kXmlTagAttrPairs; } else { continue; } - + const KeyedVector > groups = d->getFiles(); const size_t N = groups.size(); for (size_t i=0; i& assets) const DefaultKeyedVector >& files = group->getFiles(); const size_t M = files.size(); for (size_t j=0; j spans; err = parseStyledString(bundle, in->getPrintableSource().string(), - block, item16, &str, &spans, + block, item16, &str, &spans, isFormatted, pseudolocalize); if (err != NO_ERROR) { return err; @@ -605,6 +607,32 @@ status_t parseAndAddBag(Bundle* bundle, return err; } +/* + * Returns true if needle is one of the elements in the comma-separated list + * haystack, false otherwise. + */ +bool isInProductList(const String16& needle, const String16& haystack) { + const char16_t *needle2 = needle.string(); + const char16_t *haystack2 = haystack.string(); + size_t needlesize = needle.size(); + + while (*haystack2 != '\0') { + if (strncmp16(haystack2, needle2, needlesize) == 0) { + if (haystack2[needlesize] == '\0' || haystack2[needlesize] == ',') { + return true; + } + } + + while (*haystack2 != '\0' && *haystack2 != ',') { + haystack2++; + } + if (*haystack2 == ',') { + haystack2++; + } + } + + return false; +} status_t parseAndAddEntry(Bundle* bundle, const sp& in, @@ -616,6 +644,8 @@ status_t parseAndAddEntry(Bundle* bundle, const String16& curTag, bool curIsStyled, int32_t curFormat, + bool isFormatted, + const String16& product, bool pseudolocalize, const bool overwrite, ResourceTable* outTable) @@ -626,12 +656,53 @@ status_t parseAndAddEntry(Bundle* bundle, Vector spans; err = parseStyledString(bundle, in->getPrintableSource().string(), block, curTag, &str, curIsStyled ? &spans : NULL, - pseudolocalize); + isFormatted, pseudolocalize); if (err < NO_ERROR) { return err; } + /* + * If a product type was specified on the command line + * and also in the string, and the two are not the same, + * return without adding the string. + */ + + const char *bundleProduct = bundle->getProduct(); + if (bundleProduct == NULL) { + bundleProduct = ""; + } + + if (product.size() != 0) { + /* + * If the command-line-specified product is empty, only "default" + * matches. Other variants are skipped. This is so generation + * of the R.java file when the product is not known is predictable. + */ + + if (bundleProduct[0] == '\0') { + if (strcmp16(String16("default").string(), product.string()) != 0) { + return NO_ERROR; + } + } else { + /* + * The command-line product is not empty. + * If the product for this string is on the command-line list, + * it matches. "default" also matches, but only if nothing + * else has matched already. + */ + + if (isInProductList(product, String16(bundleProduct))) { + ; + } else if (strcmp16(String16("default").string(), product.string()) == 0 && + !outTable->hasBagOrEntry(myPackage, curType, ident)) { + ; + } else { + return NO_ERROR; + } + } + } + NOISY(printf("Adding resource entry l=%c%c c=%c%c orien=%d d=%d id=%s: %s\n", config.language[0], config.language[1], config.country[0], config.country[1], @@ -709,12 +780,18 @@ status_t compileResourceFile(Bundle* bundle, // useful attribute names and special values const String16 name16("name"); const String16 translatable16("translatable"); + const String16 formatted16("formatted"); const String16 false16("false"); const String16 myPackage(assets->getPackage()); bool hasErrors = false; - + + bool fileIsTranslatable = true; + if (strstr(in->getPrintableSource().string(), "donottranslate") != NULL) { + fileIsTranslatable = false; + } + DefaultKeyedVector nextPublicId(0); ResXMLTree::event_code_t code; @@ -751,6 +828,7 @@ status_t compileResourceFile(Bundle* bundle, bool curIsBagReplaceOnOverwrite = false; bool curIsStyled = false; bool curIsPseudolocalizable = false; + bool curIsFormatted = fileIsTranslatable; bool localHasErrors = false; if (strcmp16(block.getElementName(&len), skip16.string()) == 0) { @@ -1136,6 +1214,7 @@ status_t compileResourceFile(Bundle* bundle, String8 locale(rawLocale); String16 name; String16 translatable; + String16 formatted; size_t n = block.getAttributeCount(); for (size_t i = 0; i < n; i++) { @@ -1145,11 +1224,14 @@ status_t compileResourceFile(Bundle* bundle, name.setTo(block.getAttributeStringValue(i, &length)); } else if (strcmp16(attr, translatable16.string()) == 0) { translatable.setTo(block.getAttributeStringValue(i, &length)); + } else if (strcmp16(attr, formatted16.string()) == 0) { + formatted.setTo(block.getAttributeStringValue(i, &length)); } } if (name.size() > 0) { if (translatable == false16) { + curIsFormatted = false; // Untranslatable strings must only exist in the default [empty] locale if (locale.size() > 0) { fprintf(stderr, "aapt: warning: string '%s' in %s marked untranslatable but exists" @@ -1167,6 +1249,10 @@ status_t compileResourceFile(Bundle* bundle, } else { outTable->addLocalization(name, locale); } + + if (formatted == false16) { + curIsFormatted = false; + } } curTag = &string16; @@ -1266,6 +1352,12 @@ status_t compileResourceFile(Bundle* bundle, hasErrors = localHasErrors = true; } + String16 product; + identIdx = block.indexOfAttribute(NULL, "product"); + if (identIdx >= 0) { + product = String16(block.getAttributeStringValue(identIdx, &len)); + } + String16 comment(block.getComment(&len) ? block.getComment(&len) : nulStr); if (curIsBag) { @@ -1356,8 +1448,8 @@ status_t compileResourceFile(Bundle* bundle, block.getPosition(&parserPosition); err = parseAndAddBag(bundle, in, &block, curParams, myPackage, curType, - ident, parentIdent, itemIdent, curFormat, - false, overwrite, outTable); + ident, parentIdent, itemIdent, curFormat, curIsFormatted, + product, false, overwrite, outTable); if (err == NO_ERROR) { if (curIsPseudolocalizable && localeIsDefined(curParams) && bundle->getPseudolocalize()) { @@ -1365,8 +1457,8 @@ status_t compileResourceFile(Bundle* bundle, #if 1 block.setPosition(parserPosition); err = parseAndAddBag(bundle, in, &block, pseudoParams, myPackage, - curType, ident, parentIdent, itemIdent, curFormat, true, - overwrite, outTable); + curType, ident, parentIdent, itemIdent, curFormat, + curIsFormatted, product, true, overwrite, outTable); #endif } } @@ -1389,7 +1481,8 @@ status_t compileResourceFile(Bundle* bundle, block.getPosition(&parserPosition); err = parseAndAddEntry(bundle, in, &block, curParams, myPackage, curType, ident, - *curTag, curIsStyled, curFormat, false, overwrite, outTable); + *curTag, curIsStyled, curFormat, curIsFormatted, + product, false, overwrite, outTable); if (err < NO_ERROR) { // Why err < NO_ERROR instead of err != NO_ERROR? hasErrors = localHasErrors = true; @@ -1400,7 +1493,9 @@ status_t compileResourceFile(Bundle* bundle, // pseudolocalize here block.setPosition(parserPosition); err = parseAndAddEntry(bundle, in, &block, pseudoParams, myPackage, curType, - ident, *curTag, curIsStyled, curFormat, true, overwrite, outTable); + ident, *curTag, curIsStyled, curFormat, + curIsFormatted, product, + true, overwrite, outTable); if (err != NO_ERROR) { hasErrors = localHasErrors = true; } diff --git a/XMLNode.cpp b/XMLNode.cpp index 4c59288..8551b0f 100644 --- a/XMLNode.cpp +++ b/XMLNode.cpp @@ -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* outSpans, + bool isFormatted, bool pseudolocalize) { Vector 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)); diff --git a/XMLNode.h b/XMLNode.h index e9a263b..05624b7 100644 --- a/XMLNode.h +++ b/XMLNode.h @@ -25,6 +25,7 @@ status_t parseStyledString(Bundle* bundle, const String16& endTag, String16* outString, Vector* outSpans, + bool isFormatted, bool isPseudolocalizable); void printXMLBlock(ResXMLTree* block); diff --git a/ZipEntry.h b/ZipEntry.h index 7f721b4..c2f3227 100644 --- a/ZipEntry.h +++ b/ZipEntry.h @@ -71,6 +71,11 @@ public: */ off_t getCompressedLen(void) const { return mCDE.mCompressedSize; } + /* + * Return the offset of the local file header. + */ + off_t getLFHOffset(void) const { return mCDE.mLocalHeaderRelOffset; } + /* * Return the absolute file offset of the start of the compressed or * uncompressed data. @@ -185,11 +190,6 @@ protected: */ void setModWhen(time_t when); - /* - * Return the offset of the local file header. - */ - off_t getLFHOffset(void) const { return mCDE.mLocalHeaderRelOffset; } - /* * Set the offset of the local file header, relative to the start of * the current file.