auto import from //depot/cupcake/@135843
diff --git a/tools/localize/XLIFFFile.cpp b/tools/localize/XLIFFFile.cpp
new file mode 100644
index 0000000..51f81de
--- /dev/null
+++ b/tools/localize/XLIFFFile.cpp
@@ -0,0 +1,609 @@
+#include "XLIFFFile.h"
+
+#include <algorithm>
+#include <sys/time.h>
+#include <time.h>
+
+const char* const XLIFF_XMLNS = "urn:oasis:names:tc:xliff:document:1.2";
+
+const char *const NS_MAP[] = {
+    "", XLIFF_XMLNS,
+    "xml", XMLNS_XMLNS,
+    NULL, NULL
+};
+
+const XMLNamespaceMap XLIFF_NAMESPACES(NS_MAP);
+
+int
+XLIFFFile::File::Compare(const XLIFFFile::File& that) const
+{
+    if (filename != that.filename) {
+        return filename < that.filename ? -1 : 1;
+    }
+    return 0;
+}
+
+// =====================================================================================
+XLIFFFile::XLIFFFile()
+{
+}
+
+XLIFFFile::~XLIFFFile()
+{
+}
+
+static XMLNode*
+get_unique_node(const XMLNode* parent, const string& ns, const string& name, bool required)
+{
+    size_t count = parent->CountElementsByName(ns, name);
+    if (count == 1) {
+        return parent->GetElementByNameAt(ns, name, 0);
+    } else {
+        if (required) {
+            SourcePos pos = count == 0
+                                ? parent->Position()
+                                : parent->GetElementByNameAt(XLIFF_XMLNS, name, 1)->Position();
+            pos.Error("<%s> elements must contain exactly one <%s> element",
+                                parent->Name().c_str(), name.c_str());
+        }
+        return NULL;
+    }
+}
+
+XLIFFFile*
+XLIFFFile::Parse(const string& filename)
+{
+    XLIFFFile* result = new XLIFFFile();
+
+    XMLNode* root = NodeHandler::ParseFile(filename, XMLNode::PRETTY);
+    if (root == NULL) {
+        return NULL;
+    }
+
+    // <file>
+    vector<XMLNode*> files = root->GetElementsByName(XLIFF_XMLNS, "file");
+    for (size_t i=0; i<files.size(); i++) {
+        XMLNode* file = files[i];
+
+        string datatype = file->GetAttribute("", "datatype", "");
+        string originalFile = file->GetAttribute("", "original", "");
+
+        Configuration sourceConfig;
+        sourceConfig.locale = file->GetAttribute("", "source-language", "");
+        result->m_sourceConfig = sourceConfig;
+
+        Configuration targetConfig;
+        targetConfig.locale = file->GetAttribute("", "target-language", "");
+        result->m_targetConfig = targetConfig;
+
+        result->m_currentVersion = file->GetAttribute("", "build-num", "");
+        result->m_oldVersion = "old";
+
+        // <body>
+        XMLNode* body = get_unique_node(file, XLIFF_XMLNS, "body", true);
+        if (body == NULL) continue;
+
+        // <trans-unit>
+        vector<XMLNode*> transUnits = body->GetElementsByName(XLIFF_XMLNS, "trans-unit");
+        for (size_t j=0; j<transUnits.size(); j++) {
+            XMLNode* transUnit = transUnits[j];
+
+            string rawID = transUnit->GetAttribute("", "id", "");
+            if (rawID == "") {
+                transUnit->Position().Error("<trans-unit> tag requires an id");
+                continue;
+            }
+            string id;
+            int index;
+
+            if (!StringResource::ParseTypedID(rawID, &id, &index)) {
+                transUnit->Position().Error("<trans-unit> has invalid id '%s'\n", rawID.c_str());
+                continue;
+            }
+
+            // <source>
+            XMLNode* source = get_unique_node(transUnit, XLIFF_XMLNS, "source", false);
+            if (source != NULL) {
+                XMLNode* node = source->Clone();
+                node->SetPrettyRecursive(XMLNode::EXACT);
+                result->AddStringResource(StringResource(source->Position(), originalFile,
+                            sourceConfig, id, index, node, CURRENT_VERSION,
+                            result->m_currentVersion));
+            }
+
+            // <target>
+            XMLNode* target = get_unique_node(transUnit, XLIFF_XMLNS, "target", false);
+            if (target != NULL) {
+                XMLNode* node = target->Clone();
+                node->SetPrettyRecursive(XMLNode::EXACT);
+                result->AddStringResource(StringResource(target->Position(), originalFile,
+                            targetConfig, id, index, node, CURRENT_VERSION,
+                            result->m_currentVersion));
+            }
+
+            // <alt-trans>
+            XMLNode* altTrans = get_unique_node(transUnit, XLIFF_XMLNS, "alt-trans", false);
+            if (altTrans != NULL) {
+                // <source>
+                XMLNode* altSource = get_unique_node(altTrans, XLIFF_XMLNS, "source", false);
+                if (altSource != NULL) {
+                    XMLNode* node = altSource->Clone();
+                    node->SetPrettyRecursive(XMLNode::EXACT);
+                    result->AddStringResource(StringResource(altSource->Position(),
+                                originalFile, sourceConfig, id, index, node, OLD_VERSION,
+                                result->m_oldVersion));
+                }
+
+                // <target>
+                XMLNode* altTarget = get_unique_node(altTrans, XLIFF_XMLNS, "target", false);
+                if (altTarget != NULL) {
+                    XMLNode* node = altTarget->Clone();
+                    node->SetPrettyRecursive(XMLNode::EXACT);
+                    result->AddStringResource(StringResource(altTarget->Position(),
+                                originalFile, targetConfig, id, index, node, OLD_VERSION,
+                                result->m_oldVersion));
+                }
+            }
+        }
+    }
+    delete root;
+    return result;
+}
+
+XLIFFFile*
+XLIFFFile::Create(const Configuration& sourceConfig, const Configuration& targetConfig,
+                                const string& currentVersion)
+{
+    XLIFFFile* result = new XLIFFFile();
+        result->m_sourceConfig = sourceConfig;
+        result->m_targetConfig = targetConfig;
+        result->m_currentVersion = currentVersion;
+    return result;
+}
+
+set<string>
+XLIFFFile::Files() const
+{
+    set<string> result;
+    for (vector<File>::const_iterator f = m_files.begin(); f != m_files.end(); f++) {
+        result.insert(f->filename);
+    }
+    return result;
+}
+
+void
+XLIFFFile::AddStringResource(const StringResource& str)
+{
+    string id = str.TypedID();
+
+    File* f = NULL;
+    const size_t I = m_files.size();
+    for (size_t i=0; i<I; i++) {
+        if (m_files[i].filename == str.file) {
+            f = &m_files[i];
+            break;
+        }
+    }
+    if (f == NULL) {
+        File file;
+        file.filename = str.file;
+        m_files.push_back(file);
+        f = &m_files[I];
+    }
+
+    const size_t J = f->transUnits.size();
+    TransUnit* g = NULL;
+    for (size_t j=0; j<J; j++) {
+        if (f->transUnits[j].id == id) {
+            g = &f->transUnits[j];
+        }
+    }
+    if (g == NULL) {
+        TransUnit group;
+        group.id = id;
+        f->transUnits.push_back(group);
+        g = &f->transUnits[J];
+    }
+
+    StringResource* res = find_string_res(*g, str);
+    if (res == NULL) {
+        return ;
+    }
+    if (res->id != "") {
+        str.pos.Error("Duplicate string resource: %s", res->id.c_str());
+        res->pos.Error("Previous definition here");
+        return ;
+    }
+    *res = str;
+
+    m_strings.insert(str);
+}
+
+void
+XLIFFFile::Filter(bool (*func)(const string&,const TransUnit&,void*), void* cookie)
+{
+    const size_t I = m_files.size();
+    for (size_t ix=0, i=I-1; ix<I; ix++, i--) {
+        File& file = m_files[i];
+
+        const size_t J = file.transUnits.size();
+        for (size_t jx=0, j=J-1; jx<J; jx++, j--) {
+            TransUnit& tu = file.transUnits[j];
+
+            bool keep = func(file.filename, tu, cookie);
+            if (!keep) {
+                if (tu.source.id != "") {
+                    m_strings.erase(tu.source);
+                }
+                if (tu.target.id != "") {
+                    m_strings.erase(tu.target);
+                }
+                if (tu.altSource.id != "") {
+                    m_strings.erase(tu.altSource);
+                }
+                if (tu.altTarget.id != "") {
+                    m_strings.erase(tu.altTarget);
+                }
+                file.transUnits.erase(file.transUnits.begin()+j);
+            }
+        }
+        if (file.transUnits.size() == 0) {
+            m_files.erase(m_files.begin()+i);
+        }
+    }
+}
+
+void
+XLIFFFile::Map(void (*func)(const string&,TransUnit*,void*), void* cookie)
+{
+    const size_t I = m_files.size();
+    for (size_t i=0; i<I; i++) {
+        File& file = m_files[i];
+
+        const size_t J = file.transUnits.size();
+        for (size_t j=0; j<J; j++) {
+            func(file.filename, &(file.transUnits[j]), cookie);
+        }
+    }
+}
+
+TransUnit*
+XLIFFFile::EditTransUnit(const string& filename, const string& id)
+{
+    const size_t I = m_files.size();
+    for (size_t ix=0, i=I-1; ix<I; ix++, i--) {
+        File& file = m_files[i];
+        if (file.filename == filename) {
+            const size_t J = file.transUnits.size();
+            for (size_t jx=0, j=J-1; jx<J; jx++, j--) {
+                TransUnit& tu = file.transUnits[j];
+                if (tu.id == id) {
+                    return &tu;
+                }
+            }
+        }
+    }
+    return NULL;
+}
+
+StringResource*
+XLIFFFile::find_string_res(TransUnit& g, const StringResource& str)
+{
+    int index;
+    if (str.version == CURRENT_VERSION) {
+        index = 0;
+    }
+    else if (str.version == OLD_VERSION) {
+        index = 2;
+    }
+    else {
+        str.pos.Error("Internal Error %s:%d\n", __FILE__, __LINE__);
+        return NULL;
+    }
+    if (str.config == m_sourceConfig) {
+        // index += 0;
+    }
+    else if (str.config == m_targetConfig) {
+        index += 1;
+    }
+    else {
+        str.pos.Error("unknown config for string %s: %s", str.id.c_str(),
+                            str.config.ToString().c_str());
+        return NULL;
+    }
+    switch (index) {
+        case 0:
+            return &g.source;
+        case 1:
+            return &g.target;
+        case 2:
+            return &g.altSource;
+        case 3:
+            return &g.altTarget;
+    }
+    str.pos.Error("Internal Error %s:%d\n", __FILE__, __LINE__);
+    return NULL;
+}
+
+int
+convert_html_to_xliff(const XMLNode* original, const string& name, XMLNode* addTo, int* phID)
+{
+    int err = 0;
+    if (original->Type() == XMLNode::TEXT) {
+        addTo->EditChildren().push_back(original->Clone());
+        return 0;
+    } else {
+        string ctype;
+        if (original->Namespace() == "") {
+            if (original->Name() == "b") {
+                ctype = "bold";
+            }
+            else if (original->Name() == "i") {
+                ctype = "italic";
+            }
+            else if (original->Name() == "u") {
+                ctype = "underline";
+            }
+        }
+        if (ctype != "") {
+            vector<XMLAttribute> attrs;
+            attrs.push_back(XMLAttribute(XLIFF_XMLNS, "ctype", ctype));
+            XMLNode* copy = XMLNode::NewElement(original->Position(), XLIFF_XMLNS, "g",
+                                                attrs, XMLNode::EXACT);
+
+            const vector<XMLNode*>& children = original->Children();
+            size_t I = children.size();
+            for (size_t i=0; i<I; i++) {
+                err |= convert_html_to_xliff(children[i], name, copy, phID);
+            }
+            return err;
+        }
+        else {
+            if (original->Namespace() == XLIFF_XMLNS) {
+                addTo->EditChildren().push_back(original->Clone());
+                return 0;
+            } else {
+                if (original->Namespace() == "") {
+                    // flatten out the tag into ph tags -- but only if there is no namespace
+                    // that's still unsupported because propagating the xmlns attribute is hard.
+                    vector<XMLAttribute> attrs;
+                    char idStr[30];
+                    (*phID)++;
+                    sprintf(idStr, "id-%d", *phID);
+                    attrs.push_back(XMLAttribute(XLIFF_XMLNS, "id", idStr));
+
+                    if (original->Children().size() == 0) {
+                        XMLNode* ph = XMLNode::NewElement(original->Position(), XLIFF_XMLNS,
+                                "ph", attrs, XMLNode::EXACT);
+                        ph->EditChildren().push_back(
+                                XMLNode::NewText(original->Position(),
+                                    original->ToString(XLIFF_NAMESPACES),
+                                    XMLNode::EXACT));
+                        addTo->EditChildren().push_back(ph);
+                    } else {
+                        XMLNode* begin = XMLNode::NewElement(original->Position(), XLIFF_XMLNS,
+                                "bpt", attrs, XMLNode::EXACT);
+                        begin->EditChildren().push_back(
+                                XMLNode::NewText(original->Position(),
+                                    original->OpenTagToString(XLIFF_NAMESPACES, XMLNode::EXACT),
+                                    XMLNode::EXACT));
+                        XMLNode* end = XMLNode::NewElement(original->Position(), XLIFF_XMLNS,
+                                "ept", attrs, XMLNode::EXACT);
+                        string endText = "</";
+                            endText += original->Name();
+                            endText += ">";
+                        end->EditChildren().push_back(XMLNode::NewText(original->Position(),
+                                endText, XMLNode::EXACT));
+
+                        addTo->EditChildren().push_back(begin);
+
+                        const vector<XMLNode*>& children = original->Children();
+                        size_t I = children.size();
+                        for (size_t i=0; i<I; i++) {
+                            err |= convert_html_to_xliff(children[i], name, addTo, phID);
+                        }
+
+                        addTo->EditChildren().push_back(end);
+                    }
+                    return err;
+                } else {
+                    original->Position().Error("invalid <%s> element in <%s> tag\n",
+                                                original->Name().c_str(), name.c_str());
+                    return 1;
+                }
+            }
+        }
+    }
+}
+
+XMLNode*
+create_string_node(const StringResource& str, const string& name)
+{
+    vector<XMLAttribute> attrs;
+    attrs.push_back(XMLAttribute(XMLNS_XMLNS, "space", "preserve"));
+    XMLNode* node = XMLNode::NewElement(str.pos, XLIFF_XMLNS, name, attrs, XMLNode::EXACT);
+
+    const vector<XMLNode*>& children = str.value->Children();
+    size_t I = children.size();
+    int err = 0;
+    for (size_t i=0; i<I; i++) {
+        int phID = 0;
+        err |= convert_html_to_xliff(children[i], name, node, &phID);
+    }
+
+    if (err != 0) {
+        delete node;
+    }
+    return node;
+}
+
+static bool
+compare_id(const TransUnit& lhs, const TransUnit& rhs)
+{
+    string lid, rid;
+    int lindex, rindex;
+    StringResource::ParseTypedID(lhs.id, &lid, &lindex);
+    StringResource::ParseTypedID(rhs.id, &rid, &rindex);
+    if (lid < rid) return true;
+    if (lid == rid && lindex < rindex) return true;
+    return false;
+}
+
+XMLNode*
+XLIFFFile::ToXMLNode() const
+{
+    XMLNode* root;
+    size_t N;
+
+    // <xliff>
+    {
+        vector<XMLAttribute> attrs;
+        XLIFF_NAMESPACES.AddToAttributes(&attrs);
+        attrs.push_back(XMLAttribute(XLIFF_XMLNS, "version", "1.2"));
+        root = XMLNode::NewElement(GENERATED_POS, XLIFF_XMLNS, "xliff", attrs, XMLNode::PRETTY);
+    }
+
+    vector<TransUnit> groups;
+
+    // <file>
+    vector<File> files = m_files;
+    sort(files.begin(), files.end());
+    const size_t I = files.size();
+    for (size_t i=0; i<I; i++) {
+        const File& file = files[i];
+
+        vector<XMLAttribute> fileAttrs;
+        fileAttrs.push_back(XMLAttribute(XLIFF_XMLNS, "datatype", "x-android-res"));
+        fileAttrs.push_back(XMLAttribute(XLIFF_XMLNS, "original", file.filename));
+
+        struct timeval tv;
+        struct timezone tz;
+        gettimeofday(&tv, &tz);
+        fileAttrs.push_back(XMLAttribute(XLIFF_XMLNS, "date", trim_string(ctime(&tv.tv_sec))));
+
+        fileAttrs.push_back(XMLAttribute(XLIFF_XMLNS, "source-language", m_sourceConfig.locale));
+        fileAttrs.push_back(XMLAttribute(XLIFF_XMLNS, "target-language", m_targetConfig.locale));
+        fileAttrs.push_back(XMLAttribute(XLIFF_XMLNS, "build-num", m_currentVersion));
+
+        XMLNode* fileNode = XMLNode::NewElement(GENERATED_POS, XLIFF_XMLNS, "file", fileAttrs,
+                                                XMLNode::PRETTY);
+        root->EditChildren().push_back(fileNode);
+
+        // <body>
+        XMLNode* bodyNode = XMLNode::NewElement(GENERATED_POS, XLIFF_XMLNS, "body",
+                                                vector<XMLAttribute>(), XMLNode::PRETTY);
+        fileNode->EditChildren().push_back(bodyNode);
+
+        // <trans-unit>
+        vector<TransUnit> transUnits = file.transUnits;
+        sort(transUnits.begin(), transUnits.end(), compare_id);
+        const size_t J = transUnits.size();
+        for (size_t j=0; j<J; j++) {
+            const TransUnit& transUnit = transUnits[j];
+
+            vector<XMLAttribute> tuAttrs;
+
+            // strings start with string:
+            tuAttrs.push_back(XMLAttribute(XLIFF_XMLNS, "id", transUnit.id));
+            XMLNode* transUnitNode = XMLNode::NewElement(GENERATED_POS, XLIFF_XMLNS, "trans-unit",
+                                                         tuAttrs, XMLNode::PRETTY);
+            bodyNode->EditChildren().push_back(transUnitNode);
+
+            // <extradata>
+            if (transUnit.source.comment != "") {
+                vector<XMLAttribute> extradataAttrs;
+                XMLNode* extraNode = XMLNode::NewElement(GENERATED_POS, XLIFF_XMLNS, "extradata",
+                                                            extradataAttrs, XMLNode::EXACT);
+                transUnitNode->EditChildren().push_back(extraNode);
+                extraNode->EditChildren().push_back(
+                        XMLNode::NewText(GENERATED_POS, transUnit.source.comment,
+                                         XMLNode::PRETTY));
+            }
+
+            // <source>
+            if (transUnit.source.id != "") {
+                transUnitNode->EditChildren().push_back(
+                                    create_string_node(transUnit.source, "source"));
+            }
+            
+            // <target>
+            if (transUnit.target.id != "") {
+                transUnitNode->EditChildren().push_back(
+                                    create_string_node(transUnit.target, "target"));
+            }
+
+            // <alt-trans>
+            if (transUnit.altSource.id != "" || transUnit.altTarget.id != ""
+                    || transUnit.rejectComment != "") {
+                vector<XMLAttribute> altTransAttrs;
+                XMLNode* altTransNode = XMLNode::NewElement(GENERATED_POS, XLIFF_XMLNS, "alt-trans",
+                                                            altTransAttrs, XMLNode::PRETTY);
+                transUnitNode->EditChildren().push_back(altTransNode);
+
+                // <extradata>
+                if (transUnit.rejectComment != "") {
+                    vector<XMLAttribute> extradataAttrs;
+                    XMLNode* extraNode = XMLNode::NewElement(GENERATED_POS, XLIFF_XMLNS,
+                                                                "extradata", extradataAttrs,
+                                                                XMLNode::EXACT);
+                    altTransNode->EditChildren().push_back(extraNode);
+                    extraNode->EditChildren().push_back(
+                            XMLNode::NewText(GENERATED_POS, transUnit.rejectComment,
+                                             XMLNode::PRETTY));
+                }
+                
+                // <source>
+                if (transUnit.altSource.id != "") {
+                    altTransNode->EditChildren().push_back(
+                                        create_string_node(transUnit.altSource, "source"));
+                }
+                
+                // <target>
+                if (transUnit.altTarget.id != "") {
+                    altTransNode->EditChildren().push_back(
+                                        create_string_node(transUnit.altTarget, "target"));
+                }
+            }
+            
+        }
+    }
+
+    return root;
+}
+
+
+string
+XLIFFFile::ToString() const
+{
+    XMLNode* xml = ToXMLNode();
+    string s = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
+    s += xml->ToString(XLIFF_NAMESPACES);
+    delete xml;
+    s += '\n';
+    return s;
+}
+
+Stats
+XLIFFFile::GetStats(const string& config) const
+{
+    Stats stat;
+    stat.config = config;
+    stat.files = m_files.size();
+    stat.toBeTranslated = 0;
+    stat.noComments = 0;
+
+    for (vector<File>::const_iterator file=m_files.begin(); file!=m_files.end(); file++) {
+        stat.toBeTranslated += file->transUnits.size();
+
+        for (vector<TransUnit>::const_iterator tu=file->transUnits.begin();
+                    tu!=file->transUnits.end(); tu++) {
+            if (tu->source.comment == "") {
+                stat.noComments++;
+            }
+        }
+    }
+
+    stat.totalStrings = stat.toBeTranslated;
+
+    return stat;
+}