Initial Contribution
diff --git a/tools/localize/ValuesFile.cpp b/tools/localize/ValuesFile.cpp
new file mode 100644
index 0000000..bd6f494
--- /dev/null
+++ b/tools/localize/ValuesFile.cpp
@@ -0,0 +1,266 @@
+#include "ValuesFile.h"
+
+#include "XMLHandler.h"
+
+#include <algorithm>
+#include <fcntl.h>
+#include <expat.h>
+#include <unistd.h>
+#include <errno.h>
+
+using namespace std;
+
+const char* const ANDROID_XMLNS = "http://schemas.android.com/apk/res/android";
+const char* const XLIFF_XMLNS = "urn:oasis:names:tc:xliff:document:1.2";
+
+const char *const NS_MAP[] = {
+    "android", ANDROID_XMLNS,
+    "xliff", XLIFF_XMLNS,
+    NULL, NULL
+};
+
+const XMLNamespaceMap ANDROID_NAMESPACES(NS_MAP);
+
+
+// =====================================================================================
+class ArrayHandler : public XMLHandler
+{
+public:
+    ArrayHandler(ValuesFile* vf, int version, const string& versionString, const string& id);
+
+    virtual int OnStartElement(const SourcePos& pos, const string& ns, const string& name,
+                                const vector<XMLAttribute>& attrs, XMLHandler** next);
+    virtual int OnText(const SourcePos& pos, const string& text);
+    virtual int OnComment(const SourcePos& pos, const string& text);
+
+private:
+    ValuesFile* m_vf;
+    int m_version;
+    int m_index;
+    string m_versionString;
+    string m_id;
+    string m_comment;
+};
+
+ArrayHandler::ArrayHandler(ValuesFile* vf, int version, const string& versionString,
+                            const string& id)
+    :m_vf(vf),
+     m_version(version),
+     m_index(0),
+     m_versionString(versionString),
+     m_id(id)
+{
+}
+
+int
+ArrayHandler::OnStartElement(const SourcePos& pos, const string& ns, const string& name,
+                                const vector<XMLAttribute>& attrs, XMLHandler** next)
+{
+    if (ns == "" && name == "item") {
+        XMLNode* node = XMLNode::NewElement(pos, ns, name, attrs, XMLNode::EXACT);
+        m_vf->AddString(StringResource(pos, pos.file, m_vf->GetConfiguration(),
+                                            m_id, m_index, node, m_version, m_versionString,
+                                            trim_string(m_comment)));
+        *next = new NodeHandler(node, XMLNode::EXACT);
+        m_index++;
+        m_comment = "";
+        return 0;
+    } else {
+        pos.Error("invalid <%s> element inside <array>\n", name.c_str());
+        return 1;
+    }
+}
+
+int
+ArrayHandler::OnText(const SourcePos& pos, const string& text)
+{
+    return 0;
+}
+
+int
+ArrayHandler::OnComment(const SourcePos& pos, const string& text)
+{
+    m_comment += text;
+    return 0;
+}
+
+// =====================================================================================
+class ValuesHandler : public XMLHandler
+{
+public:
+    ValuesHandler(ValuesFile* vf, int version, const string& versionString);
+
+    virtual int OnStartElement(const SourcePos& pos, const string& ns, const string& name,
+                                const vector<XMLAttribute>& attrs, XMLHandler** next);
+    virtual int OnText(const SourcePos& pos, const string& text);
+    virtual int OnComment(const SourcePos& pos, const string& text);
+
+private:
+    ValuesFile* m_vf;
+    int m_version;
+    string m_versionString;
+    string m_comment;
+};
+
+ValuesHandler::ValuesHandler(ValuesFile* vf, int version, const string& versionString)
+    :m_vf(vf),
+     m_version(version),
+     m_versionString(versionString)
+{
+}
+
+int
+ValuesHandler::OnStartElement(const SourcePos& pos, const string& ns, const string& name,
+                                const vector<XMLAttribute>& attrs, XMLHandler** next)
+{
+    if (ns == "" && name == "string") {
+        string id = XMLAttribute::Find(attrs, "", "name", "");
+        XMLNode* node = XMLNode::NewElement(pos, ns, name, attrs, XMLNode::EXACT);
+        m_vf->AddString(StringResource(pos, pos.file, m_vf->GetConfiguration(),
+                                            id, -1, node, m_version, m_versionString,
+                                            trim_string(m_comment)));
+        *next = new NodeHandler(node, XMLNode::EXACT);
+    }
+    else if (ns == "" && name == "array") {
+        string id = XMLAttribute::Find(attrs, "", "name", "");
+        *next = new ArrayHandler(m_vf, m_version, m_versionString, id);
+    }
+    m_comment = "";
+    return 0;
+}
+
+int
+ValuesHandler::OnText(const SourcePos& pos, const string& text)
+{
+    return 0;
+}
+
+int
+ValuesHandler::OnComment(const SourcePos& pos, const string& text)
+{
+    m_comment += text;
+    return 0;
+}
+
+// =====================================================================================
+ValuesFile::ValuesFile(const Configuration& config)
+    :m_config(config),
+     m_strings(),
+     m_arrays()
+{
+}
+
+ValuesFile::~ValuesFile()
+{
+}
+
+ValuesFile*
+ValuesFile::ParseFile(const string& filename, const Configuration& config,
+                    int version, const string& versionString)
+{
+    ValuesFile* result = new ValuesFile(config);
+
+    TopElementHandler top("", "resources", new ValuesHandler(result, version, versionString));
+    XMLHandler::ParseFile(filename, &top);
+
+    return result;
+}
+
+ValuesFile*
+ValuesFile::ParseString(const string& filename, const string& text, const Configuration& config,
+                    int version, const string& versionString)
+{
+    ValuesFile* result = new ValuesFile(config);
+
+    TopElementHandler top("", "resources", new ValuesHandler(result, version, versionString));
+    XMLHandler::ParseString(filename, text, &top);
+
+    return result;
+}
+
+const Configuration&
+ValuesFile::GetConfiguration() const
+{
+    return m_config;
+}
+
+void
+ValuesFile::AddString(const StringResource& str)
+{
+    if (str.index < 0) {
+        m_strings.insert(str);
+    } else {
+        m_arrays[str.id].insert(str);
+    }
+}
+
+set<StringResource>
+ValuesFile::GetStrings() const
+{
+    set<StringResource> result = m_strings;
+
+    for (map<string,set<StringResource> >::const_iterator it = m_arrays.begin();
+            it != m_arrays.end(); it++) {
+        result.insert(it->second.begin(), it->second.end());
+    }
+
+    return result;
+}
+
+XMLNode*
+ValuesFile::ToXMLNode() const
+{
+    XMLNode* root;
+
+    // <resources>
+    {
+        vector<XMLAttribute> attrs;
+        ANDROID_NAMESPACES.AddToAttributes(&attrs);
+        root = XMLNode::NewElement(GENERATED_POS, "", "resources", attrs, XMLNode::PRETTY);
+    }
+
+    // <array>
+    for (map<string,set<StringResource> >::const_iterator it = m_arrays.begin();
+            it != m_arrays.end(); it++) {
+        vector<XMLAttribute> arrayAttrs;
+        arrayAttrs.push_back(XMLAttribute("", "name", it->first));
+        const set<StringResource>& items = it->second;
+        XMLNode* arrayNode = XMLNode::NewElement(items.begin()->pos, "", "array", arrayAttrs,
+                XMLNode::PRETTY);
+        root->EditChildren().push_back(arrayNode);
+
+        // <item>
+        for (set<StringResource>::const_iterator item = items.begin();
+                item != items.end(); item++) {
+            XMLNode* itemNode = item->value->Clone();
+            itemNode->SetName("", "item");
+            itemNode->EditAttributes().clear();
+            arrayNode->EditChildren().push_back(itemNode);
+        }
+    }
+
+    // <string>
+    for (set<StringResource>::const_iterator it=m_strings.begin(); it!=m_strings.end(); it++) {
+        const StringResource& str = *it;
+        vector<XMLAttribute> attrs;
+        XMLNode* strNode = str.value->Clone();
+        strNode->SetName("", "string");
+        strNode->EditAttributes().clear();
+        strNode->EditAttributes().push_back(XMLAttribute("", "name", str.id));
+        root->EditChildren().push_back(strNode);
+    }
+
+    return root;
+}
+
+string
+ValuesFile::ToString() const
+{
+    XMLNode* xml = ToXMLNode();
+    string s = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
+    s += xml->ToString(ANDROID_NAMESPACES);
+    delete xml;
+    s += '\n';
+    return s;
+}
+