Maybe<std::u16string> extractPackageFromNamespace(const std::u16string& namespaceUri) { if (stringStartsWith<char16_t>(namespaceUri, kSchemaPrefix)) { StringPiece16 schemaPrefix = kSchemaPrefix; StringPiece16 package = namespaceUri; return package.substr(schemaPrefix.size(), package.size() - schemaPrefix.size()) .toString(); } else if (namespaceUri == kSchemaAuto) { return std::u16string(); } return {}; }
/* * Style parent's are a bit different. We accept the following formats: * * @[[*]package:][style/]<entry> * ?[[*]package:]style/<entry> * <[*]package>:[style/]<entry> * [[*]package:style/]<entry> */ Maybe<Reference> parseStyleParentReference(const StringPiece16& str, std::string* outError) { if (str.empty()) { return {}; } StringPiece16 name = str; bool hasLeadingIdentifiers = false; bool privateRef = false; // Skip over these identifiers. A style's parent is a normal reference. if (name.data()[0] == u'@' || name.data()[0] == u'?') { hasLeadingIdentifiers = true; name = name.substr(1, name.size() - 1); } if (name.data()[0] == u'*') { privateRef = true; name = name.substr(1, name.size() - 1); } ResourceNameRef ref; ref.type = ResourceType::kStyle; StringPiece16 typeStr; extractResourceName(name, &ref.package, &typeStr, &ref.entry); if (!typeStr.empty()) { // If we have a type, make sure it is a Style. const ResourceType* parsedType = parseResourceType(typeStr); if (!parsedType || *parsedType != ResourceType::kStyle) { std::stringstream err; err << "invalid resource type '" << typeStr << "' for parent of style"; *outError = err.str(); return {}; } } if (!hasLeadingIdentifiers && ref.package.empty() && !typeStr.empty()) { std::stringstream err; err << "invalid parent reference '" << str << "'"; *outError = err.str(); return {}; } Reference result(ref); result.privateReference = privateRef; return result; }
std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece16& str) { android::Res_value value; if (!android::ResTable::stringToFloat(str.data(), str.size(), &value)) { return {}; } return util::make_unique<BinaryPrimitive>(value); }
std::u16string Pseudolocalizer::text(const StringPiece16& text) { std::u16string out; size_t depth = mLastDepth; size_t lastpos, pos; const size_t length = text.size(); const char16_t* str = text.data(); bool escaped = false; for (lastpos = pos = 0; pos < length; pos++) { char16_t c = str[pos]; if (escaped) { escaped = false; continue; } if (c == '\'') { escaped = true; continue; } if (c == k_arg_start) { depth++; } else if (c == k_arg_end && depth) { depth--; } if (mLastDepth != depth || pos == length - 1) { bool pseudo = ((mLastDepth % 2) == 0); size_t nextpos = pos; if (!pseudo || depth == mLastDepth) { nextpos++; } size_t size = nextpos - lastpos; if (size) { std::u16string chunk = text.substr(lastpos, size).toString(); if (pseudo) { chunk = mImpl->text(chunk); } else if (str[lastpos] == k_arg_start && str[nextpos - 1] == k_arg_end) { chunk = mImpl->placeholder(chunk); } out.append(chunk); } if (pseudo && depth < mLastDepth) { // End of message out.append(mImpl->end()); } else if (!pseudo && depth > mLastDepth) { // Start of message out.append(mImpl->start()); } lastpos = nextpos; mLastDepth = depth; } } return out; }
/* * The device ResXMLParser in libandroidfw differentiates between empty namespace and null * namespace. */ TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) { std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom("<View package=\"android\"/>"); android::ResXMLTree tree; ASSERT_TRUE(flatten(doc.get(), &tree)); while (tree.next() != android::ResXMLTree::START_TAG) { ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); } const StringPiece16 kPackage = u"package"; EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0); }
bool BindingXmlPullParser::readExpressions() { mOverride = true; std::vector<XmlPullParser::Attribute> expressions; std::string idValue; const auto endAttrIter = mParser->endAttributes(); for (auto attr = mParser->beginAttributes(); attr != endAttrIter; ++attr) { if (attr->namespaceUri == kAndroidNamespaceUri && attr->name == u"id") { idValue = util::utf16ToUtf8(attr->value); } else { StringPiece16 value = util::trimWhitespace(attr->value); if (util::stringStartsWith<char16_t>(value, u"@{") && util::stringEndsWith<char16_t>(value, u"}")) { // This is attribute's value is an expression of the form // @{expression}. We need to capture the expression inside. expressions.push_back(XmlPullParser::Attribute{ attr->namespaceUri, attr->name, value.substr(2, value.size() - 3).toString() }); } else { // This is a normal attribute, use as is. mAttributes.emplace_back(*attr); } } } // Check if we have any expressions. if (!expressions.empty()) { // We have expressions, so let's assign the target a tag number // and add it to our targets list. int32_t targetId = mNextTagId++; mTargets.push_back(Target{ util::utf16ToUtf8(mParser->getElementName()), idValue, targetId, std::move(expressions) }); std::stringstream numGen; numGen << kBindingTagPrefix << targetId; mAttributes.push_back(XmlPullParser::Attribute{ std::u16string(kAndroidNamespaceUri), std::u16string(u"tag"), util::utf8ToUtf16(numGen.str()) }); } return true; }
Maybe<std::u16string> getFullyQualifiedClassName(const StringPiece16& package, const StringPiece16& className) { if (className.empty()) { return {}; } if (util::isJavaClassName(className)) { return className.toString(); } if (package.empty()) { return {}; } std::u16string result(package.data(), package.size()); if (className.data()[0] != u'.') { result += u'.'; } result.append(className.data(), className.size()); if (!isJavaClassName(result)) { return {}; } return result; }
StringPiece16 trimWhitespace(const StringPiece16& str) { if (str.size() == 0 || str.data() == nullptr) { return str; } const char16_t* start = str.data(); const char16_t* end = str.data() + str.length(); while (start != end && util::isspace16(*start)) { start++; } while (end != start && util::isspace16(*(end - 1))) { end--; } return StringPiece16(start, end - start); }
bool parseResourceName(const StringPiece16& str, ResourceNameRef* outRef, bool* outPrivate) { if (str.empty()) { return false; } size_t offset = 0; bool priv = false; if (str.data()[0] == u'*') { priv = true; offset = 1; } StringPiece16 package; StringPiece16 type; StringPiece16 entry; if (!extractResourceName(str.substr(offset, str.size() - offset), &package, &type, &entry)) { return false; } const ResourceType* parsedType = parseResourceType(type); if (!parsedType) { return false; } if (entry.empty()) { return false; } if (outRef) { outRef->package = package; outRef->type = *parsedType; outRef->entry = entry; } if (outPrivate) { *outPrivate = priv; } return true; }
bool extractResourceName(const StringPiece16& str, StringPiece16* outPackage, StringPiece16* outType, StringPiece16* outEntry) { bool hasPackageSeparator = false; bool hasTypeSeparator = false; const char16_t* start = str.data(); const char16_t* end = start + str.size(); const char16_t* current = start; while (current != end) { if (outType->size() == 0 && *current == u'/') { hasTypeSeparator = true; outType->assign(start, current - start); start = current + 1; } else if (outPackage->size() == 0 && *current == u':') { hasPackageSeparator = true; outPackage->assign(start, current - start); start = current + 1; } current++; } outEntry->assign(start, end - start); return !(hasPackageSeparator && outPackage->empty()) && !(hasTypeSeparator && outType->empty()); }
std::u16string PseudoMethodBidi::text(const StringPiece16& source) { const char16_t* s = source.data(); std::u16string result; bool lastspace = true; bool space = true; for (size_t i = 0; i < source.size(); i++) { char16_t c = s[i]; space = util::isspace16(c); if (lastspace && !space) { // Word start result += k_rlm + k_rlo; } else if (!lastspace && space) { // Word end result += k_pdf + k_rlm; } lastspace = space; result.append(&c, 1); } if (!lastspace) { // End of last word result += k_pdf + k_rlm; } return result; }
static bool lessThanEntry(const std::unique_ptr<ResourceEntry>& lhs, const StringPiece16& rhs) { return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0; }
/** * Converts characters so they look like they've been localized. * * Note: This leaves placeholder syntax untouched. */ std::u16string PseudoMethodAccent::text(const StringPiece16& source) { const char16_t* s = source.data(); std::u16string result; const size_t I = source.size(); bool lastspace = true; for (size_t i = 0; i < I; i++) { char16_t c = s[i]; if (c == '%') { // Placeholder syntax, no need to pseudolocalize std::u16string chunk; bool end = false; chunk.append(&c, 1); while (!end && i < I) { ++i; c = s[i]; chunk.append(&c, 1); if (isPossibleNormalPlaceholderEnd(c)) { end = true; } else if (c == 't') { ++i; c = s[i]; chunk.append(&c, 1); end = true; } } // Treat chunk as a placeholder unless it ends with %. result += ((c == '%') ? chunk : placeholder(chunk)); } else if (c == '<' || c == '&') { // html syntax, no need to pseudolocalize bool tag_closed = false; while (!tag_closed && i < I) { if (c == '&') { std::u16string escapeText; escapeText.append(&c, 1); bool end = false; size_t htmlCodePos = i; while (!end && htmlCodePos < I) { ++htmlCodePos; c = s[htmlCodePos]; escapeText.append(&c, 1); // Valid html code if (c == ';') { end = true; i = htmlCodePos; } // Wrong html code else if (!((c == '#' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')))) { end = true; } } result += escapeText; if (escapeText != u"<") { tag_closed = true; } continue; } if (c == '>') { tag_closed = true; result.append(&c, 1); continue; } result.append(&c, 1); i++; c = s[i]; } } else { // This is a pure text that should be pseudolocalized const char16_t* p = pseudolocalizeChar(c); if (p != nullptr) { result += p; } else { bool space = util::isspace16(c); if (lastspace && !space) { mWordCount++; } lastspace = space; result.append(&c, 1); } // Count only pseudolocalizable chars and delimiters mLength++; } } return result; }