"a floating point value, such as \"<code>1.2</code>\"."},
{ dimensionArray, sizeof(dimensionArray)/2, ResTable_map::TYPE_DIMENSION,
"a dimension value, which is a floating point number appended with a unit such as \"<code>14.5sp</code>\".\n"
- "Available units are: px (pixels), db (density-independent pixels), sp (scaled pixels based on preferred font size),\n"
+ "Available units are: px (pixels), dp (density-independent pixels), sp (scaled pixels based on preferred font size),\n"
"in (inches), mm (millimeters)." },
{ fractionArray, sizeof(fractionArray)/2, ResTable_map::TYPE_FRACTION,
"a fractional value, which is a floating point number appended with either % or %p, such as \"<code>14.5%</code>\".\n"
const String16& itemIdent,
int32_t curFormat,
bool pseudolocalize,
+ const bool overwrite,
ResourceTable* outTable)
{
status_t err;
err = outTable->addBag(SourcePos(in->getPrintableSource(), block->getLineNumber()),
myPackage, curType, ident, parentIdent, itemIdent, str,
- &spans, &config, false, false, curFormat);
+ &spans, &config, overwrite, false, curFormat);
return err;
}
bool curIsStyled,
int32_t curFormat,
bool pseudolocalize,
+ const bool overwrite,
ResourceTable* outTable)
{
status_t err;
err = outTable->addEntry(SourcePos(in->getPrintableSource(), block->getLineNumber()),
myPackage, curType, ident, str, &spans, &config,
- false, curFormat);
+ false, curFormat, overwrite);
return err;
}
const sp<AaptAssets>& assets,
const sp<AaptFile>& in,
const ResTable_config& defParams,
+ const bool overwrite,
ResourceTable* outTable)
{
ResXMLTree block;
const String16 string16("string");
const String16 drawable16("drawable");
const String16 color16("color");
+ const String16 bool16("bool");
const String16 integer16("integer");
const String16 dimen16("dimen");
+ const String16 fraction16("fraction");
const String16 style16("style");
const String16 plurals16("plurals");
const String16 array16("array");
const String16 many16("many");
const String16 quantityMany16("^many");
+ // useful attribute names and special values
+ const String16 name16("name");
+ const String16 translatable16("translatable");
+ const String16 false16("false");
const String16 myPackage(assets->getPackage());
}
curIsStyled = true;
} else if (strcmp16(block.getElementName(&len), string16.string()) == 0) {
+ // Note the existence and locale of every string we process
+ char rawLocale[16];
+ curParams.getLocale(rawLocale);
+ String8 locale(rawLocale);
+ String16 name;
+ String16 translatable;
+
+ size_t n = block.getAttributeCount();
+ for (size_t i = 0; i < n; i++) {
+ size_t length;
+ const uint16_t* attr = block.getAttributeName(i, &length);
+ if (strcmp16(attr, name16.string()) == 0) {
+ name.setTo(block.getAttributeStringValue(i, &length));
+ } else if (strcmp16(attr, translatable16.string()) == 0) {
+ translatable.setTo(block.getAttributeStringValue(i, &length));
+ }
+ }
+
+ if (name.size() > 0) {
+ if (translatable == false16) {
+ // 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"
+ " in locale '%s'\n", String8(name).string(),
+ bundle->getResourceSourceDirs()[0],
+ locale.string());
+ // hasErrors = localHasErrors = true;
+ } else {
+ // Intentionally empty block:
+ //
+ // Don't add untranslatable strings to the localization table; that
+ // way if we later see localizations of them, they'll be flagged as
+ // having no default translation.
+ }
+ } else {
+ outTable->addLocalization(name, locale);
+ }
+ }
+
curTag = &string16;
curType = string16;
curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_STRING;
curTag = &color16;
curType = color16;
curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_COLOR;
+ } else if (strcmp16(block.getElementName(&len), bool16.string()) == 0) {
+ curTag = &bool16;
+ curType = bool16;
+ curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_BOOLEAN;
} else if (strcmp16(block.getElementName(&len), integer16.string()) == 0) {
curTag = &integer16;
curType = integer16;
curTag = &dimen16;
curType = dimen16;
curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_DIMENSION;
+ } else if (strcmp16(block.getElementName(&len), fraction16.string()) == 0) {
+ curTag = &fraction16;
+ curType = fraction16;
+ curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_FRACTION;
} else if (strcmp16(block.getElementName(&len), bag16.string()) == 0) {
curTag = &bag16;
curIsBag = true;
block.getPosition(&parserPosition);
err = parseAndAddBag(bundle, in, &block, curParams, myPackage, curType,
- ident, parentIdent, itemIdent, curFormat, false, outTable);
+ ident, parentIdent, itemIdent, curFormat,
+ false, overwrite, outTable);
if (err == NO_ERROR) {
if (curIsPseudolocalizable && localeIsDefined(curParams)
&& bundle->getPseudolocalize()) {
block.setPosition(parserPosition);
err = parseAndAddBag(bundle, in, &block, pseudoParams, myPackage,
curType, ident, parentIdent, itemIdent, curFormat, true,
- outTable);
+ overwrite, outTable);
#endif
}
}
block.getPosition(&parserPosition);
err = parseAndAddEntry(bundle, in, &block, curParams, myPackage, curType, ident,
- *curTag, curIsStyled, curFormat, false, outTable);
+ *curTag, curIsStyled, curFormat, false, overwrite, outTable);
if (err < NO_ERROR) { // Why err < NO_ERROR instead of err != NO_ERROR?
hasErrors = localHasErrors = true;
// pseudolocalize here
block.setPosition(parserPosition);
err = parseAndAddEntry(bundle, in, &block, pseudoParams, myPackage, curType,
- ident, *curTag, curIsStyled, curFormat, true, outTable);
+ ident, *curTag, curIsStyled, curFormat, true, false, outTable);
if (err != NO_ERROR) {
hasErrors = localHasErrors = true;
}
const Vector<StringPool::entry_style_span>* style,
const ResTable_config* params,
const bool doSetIndex,
- const int32_t format)
+ const int32_t format,
+ const bool overwrite)
{
// Check for adding entries in other packages... for now we do
// nothing. We need to do the right thing here to support skinning.
if (e == NULL) {
return UNKNOWN_ERROR;
}
- status_t err = e->setItem(sourcePos, value, style, format);
+ status_t err = e->setItem(sourcePos, value, style, format, overwrite);
if (err == NO_ERROR) {
mNumLocal++;
}
uint32_t ResourceTable::getResId(const String16& package,
const String16& type,
- const String16& name) const
+ const String16& name,
+ bool onlyPublic) const
{
sp<Package> p = mPackages.valueFor(package);
if (p == NULL) return 0;
// First look for this in the included resources...
+ uint32_t specFlags = 0;
uint32_t rid = mAssets->getIncludedResources()
.identifierForName(name.string(), name.size(),
type.string(), type.size(),
- package.string(), package.size());
+ package.string(), package.size(),
+ &specFlags);
if (rid != 0) {
+ if (onlyPublic) {
+ if ((specFlags & ResTable_typeSpec::SPEC_PUBLIC) == 0) {
+ return 0;
+ }
+ }
+
if (Res_INTERNALID(rid)) {
return rid;
}
uint32_t ResourceTable::getResId(const String16& ref,
const String16* defType,
const String16* defPackage,
- const char** outErrorMsg) const
+ const char** outErrorMsg,
+ bool onlyPublic) const
{
String16 package, type, name;
if (!ResTable::expandResourceRef(
String8(name).string()));
return 0;
}
- uint32_t res = getResId(package, type, name);
+ uint32_t res = getResId(package, type, name, onlyPublic);
NOISY(printf("Expanded resource: p=%s, t=%s, n=%s, res=%d\n",
String8(package).string(), String8(type).string(),
String8(name).string(), res));
}
+void
+ResourceTable::addLocalization(const String16& name, const String8& locale)
+{
+ mLocalizations[name].insert(locale);
+}
+
+
+/*!
+ * Flag various sorts of localization problems. '+' indicates checks already implemented;
+ * '-' indicates checks that will be implemented in the future.
+ *
+ * + A localized string for which no default-locale version exists => warning
+ * + A string for which no version in an explicitly-requested locale exists => warning
+ * + A localized translation of an translateable="false" string => warning
+ * - A localized string not provided in every locale used by the table
+ */
+status_t
+ResourceTable::validateLocalizations(void)
+{
+ status_t err = NO_ERROR;
+ const String8 defaultLocale;
+
+ // For all strings...
+ for (map<String16, set<String8> >::iterator nameIter = mLocalizations.begin();
+ nameIter != mLocalizations.end();
+ nameIter++) {
+ const set<String8>& configSet = nameIter->second; // naming convenience
+
+ // Look for strings with no default localization
+ if (configSet.count(defaultLocale) == 0) {
+ fprintf(stdout, "aapt: warning: string '%s' has no default translation in %s; found:",
+ String8(nameIter->first).string(), mBundle->getResourceSourceDirs()[0]);
+ for (set<String8>::iterator locales = configSet.begin();
+ locales != configSet.end();
+ locales++) {
+ fprintf(stdout, " %s", (*locales).string());
+ }
+ fprintf(stdout, "\n");
+ // !!! TODO: throw an error here in some circumstances
+ }
+
+ // Check that all requested localizations are present for this string
+ if (mBundle->getConfigurations() != NULL && mBundle->getRequireLocalization()) {
+ const char* allConfigs = mBundle->getConfigurations();
+ const char* start = allConfigs;
+ const char* comma;
+
+ do {
+ String8 config;
+ comma = strchr(start, ',');
+ if (comma != NULL) {
+ config.setTo(start, comma - start);
+ start = comma + 1;
+ } else {
+ config.setTo(start);
+ }
+
+ // don't bother with the pseudolocale "zz_ZZ"
+ if (config != "zz_ZZ") {
+ if (configSet.find(config) == configSet.end()) {
+ // okay, no specific localization found. it's possible that we are
+ // requiring a specific regional localization [e.g. de_DE] but there is an
+ // available string in the generic language localization [e.g. de];
+ // consider that string to have fulfilled the localization requirement.
+ String8 region(config.string(), 2);
+ if (configSet.find(region) == configSet.end()) {
+ if (configSet.count(defaultLocale) == 0) {
+ fprintf(stdout, "aapt: error: "
+ "*** string '%s' has no default or required localization "
+ "for '%s' in %s\n",
+ String8(nameIter->first).string(),
+ config.string(),
+ mBundle->getResourceSourceDirs()[0]);
+ err = UNKNOWN_ERROR;
+ }
+ }
+ }
+ }
+ } while (comma != NULL);
+ }
+ }
+
+ return err;
+}
+
+
status_t
ResourceFilter::parse(const char* arg)
{
}
const size_t N = c->getEntries().size();
for (size_t ei=0; ei<N; ei++) {
+ ConfigDescription config = c->getEntries().keyAt(ei);
+ if (!filter.match(config)) {
+ continue;
+ }
sp<Entry> e = c->getEntries().valueAt(ei);
if (e == NULL) {
continue;
status_t ResourceTable::Entry::setItem(const SourcePos& sourcePos,
const String16& value,
const Vector<StringPool::entry_style_span>* style,
- int32_t format)
+ int32_t format,
+ const bool overwrite)
{
Item item(sourcePos, false, value, style);
item.sourcePos.file.string(), item.sourcePos.line);
return UNKNOWN_ERROR;
}
- if (mType != TYPE_UNKNOWN) {
+ if ( (mType != TYPE_UNKNOWN) && (overwrite == false) ) {
sourcePos.error("Resource entry %s is already defined.\n"
"%s:%d: Originally defined here.\n",
String8(mName).string(),
}
return res;
}
-