std::string utf16ToUtf8(const StringPiece16& utf16) {
    ssize_t utf8Length = utf16_to_utf8_length(utf16.data(), utf16.length());
    if (utf8Length <= 0) {
        return {};
    }

    std::string utf8;
    utf8.resize(utf8Length);
    utf16_to_utf8(utf16.data(), utf16.length(), &*utf8.begin());
    return utf8;
}
/*
 * 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);
}
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);
}
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);
}
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;
}
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"&lt;") {
                        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;
}