1#include "ValuesFile.h"
2
3#include "XMLHandler.h"
4
5#include <algorithm>
6#include <fcntl.h>
7#include <expat.h>
8#include <unistd.h>
9#include <errno.h>
10
11using namespace std;
12
13const char* const ANDROID_XMLNS = "http://schemas.android.com/apk/res/android";
14const char* const XLIFF_XMLNS = "urn:oasis:names:tc:xliff:document:1.2";
15
16const char *const NS_MAP[] = {
17    "android", ANDROID_XMLNS,
18    "xliff", XLIFF_XMLNS,
19    NULL, NULL
20};
21
22const XMLNamespaceMap ANDROID_NAMESPACES(NS_MAP);
23
24
25// =====================================================================================
26class ArrayHandler : public XMLHandler
27{
28public:
29    ArrayHandler(ValuesFile* vf, int version, const string& versionString, const string& id);
30
31    virtual int OnStartElement(const SourcePos& pos, const string& ns, const string& name,
32                                const vector<XMLAttribute>& attrs, XMLHandler** next);
33    virtual int OnText(const SourcePos& pos, const string& text);
34    virtual int OnComment(const SourcePos& pos, const string& text);
35
36private:
37    ValuesFile* m_vf;
38    int m_version;
39    int m_index;
40    string m_versionString;
41    string m_id;
42    string m_comment;
43};
44
45ArrayHandler::ArrayHandler(ValuesFile* vf, int version, const string& versionString,
46                            const string& id)
47    :m_vf(vf),
48     m_version(version),
49     m_index(0),
50     m_versionString(versionString),
51     m_id(id)
52{
53}
54
55int
56ArrayHandler::OnStartElement(const SourcePos& pos, const string& ns, const string& name,
57                                const vector<XMLAttribute>& attrs, XMLHandler** next)
58{
59    if (ns == "" && name == "item") {
60        XMLNode* node = XMLNode::NewElement(pos, ns, name, attrs, XMLNode::EXACT);
61        m_vf->AddString(StringResource(pos, pos.file, m_vf->GetConfiguration(),
62                                            m_id, m_index, node, m_version, m_versionString,
63                                            trim_string(m_comment)));
64        *next = new NodeHandler(node, XMLNode::EXACT);
65        m_index++;
66        m_comment = "";
67        return 0;
68    } else {
69        pos.Error("invalid <%s> element inside <array>\n", name.c_str());
70        return 1;
71    }
72}
73
74int
75ArrayHandler::OnText(const SourcePos& pos, const string& text)
76{
77    return 0;
78}
79
80int
81ArrayHandler::OnComment(const SourcePos& pos, const string& text)
82{
83    m_comment += text;
84    return 0;
85}
86
87// =====================================================================================
88class ValuesHandler : public XMLHandler
89{
90public:
91    ValuesHandler(ValuesFile* vf, int version, const string& versionString);
92
93    virtual int OnStartElement(const SourcePos& pos, const string& ns, const string& name,
94                                const vector<XMLAttribute>& attrs, XMLHandler** next);
95    virtual int OnText(const SourcePos& pos, const string& text);
96    virtual int OnComment(const SourcePos& pos, const string& text);
97
98private:
99    ValuesFile* m_vf;
100    int m_version;
101    string m_versionString;
102    string m_comment;
103};
104
105ValuesHandler::ValuesHandler(ValuesFile* vf, int version, const string& versionString)
106    :m_vf(vf),
107     m_version(version),
108     m_versionString(versionString)
109{
110}
111
112int
113ValuesHandler::OnStartElement(const SourcePos& pos, const string& ns, const string& name,
114                                const vector<XMLAttribute>& attrs, XMLHandler** next)
115{
116    if (ns == "" && name == "string") {
117        string id = XMLAttribute::Find(attrs, "", "name", "");
118        XMLNode* node = XMLNode::NewElement(pos, ns, name, attrs, XMLNode::EXACT);
119        m_vf->AddString(StringResource(pos, pos.file, m_vf->GetConfiguration(),
120                                            id, -1, node, m_version, m_versionString,
121                                            trim_string(m_comment)));
122        *next = new NodeHandler(node, XMLNode::EXACT);
123    }
124    else if (ns == "" && name == "array") {
125        string id = XMLAttribute::Find(attrs, "", "name", "");
126        *next = new ArrayHandler(m_vf, m_version, m_versionString, id);
127    }
128    m_comment = "";
129    return 0;
130}
131
132int
133ValuesHandler::OnText(const SourcePos& pos, const string& text)
134{
135    return 0;
136}
137
138int
139ValuesHandler::OnComment(const SourcePos& pos, const string& text)
140{
141    m_comment += text;
142    return 0;
143}
144
145// =====================================================================================
146ValuesFile::ValuesFile(const Configuration& config)
147    :m_config(config),
148     m_strings(),
149     m_arrays()
150{
151}
152
153ValuesFile::~ValuesFile()
154{
155}
156
157ValuesFile*
158ValuesFile::ParseFile(const string& filename, const Configuration& config,
159                    int version, const string& versionString)
160{
161    ValuesFile* result = new ValuesFile(config);
162
163    TopElementHandler top("", "resources", new ValuesHandler(result, version, versionString));
164    XMLHandler::ParseFile(filename, &top);
165
166    return result;
167}
168
169ValuesFile*
170ValuesFile::ParseString(const string& filename, const string& text, const Configuration& config,
171                    int version, const string& versionString)
172{
173    ValuesFile* result = new ValuesFile(config);
174
175    TopElementHandler top("", "resources", new ValuesHandler(result, version, versionString));
176    XMLHandler::ParseString(filename, text, &top);
177
178    return result;
179}
180
181const Configuration&
182ValuesFile::GetConfiguration() const
183{
184    return m_config;
185}
186
187void
188ValuesFile::AddString(const StringResource& str)
189{
190    if (str.index < 0) {
191        m_strings.insert(str);
192    } else {
193        m_arrays[str.id].insert(str);
194    }
195}
196
197set<StringResource>
198ValuesFile::GetStrings() const
199{
200    set<StringResource> result = m_strings;
201
202    for (map<string,set<StringResource> >::const_iterator it = m_arrays.begin();
203            it != m_arrays.end(); it++) {
204        result.insert(it->second.begin(), it->second.end());
205    }
206
207    return result;
208}
209
210XMLNode*
211ValuesFile::ToXMLNode() const
212{
213    XMLNode* root;
214
215    // <resources>
216    {
217        vector<XMLAttribute> attrs;
218        ANDROID_NAMESPACES.AddToAttributes(&attrs);
219        root = XMLNode::NewElement(GENERATED_POS, "", "resources", attrs, XMLNode::PRETTY);
220    }
221
222    // <array>
223    for (map<string,set<StringResource> >::const_iterator it = m_arrays.begin();
224            it != m_arrays.end(); it++) {
225        vector<XMLAttribute> arrayAttrs;
226        arrayAttrs.push_back(XMLAttribute("", "name", it->first));
227        const set<StringResource>& items = it->second;
228        XMLNode* arrayNode = XMLNode::NewElement(items.begin()->pos, "", "array", arrayAttrs,
229                XMLNode::PRETTY);
230        root->EditChildren().push_back(arrayNode);
231
232        // <item>
233        for (set<StringResource>::const_iterator item = items.begin();
234                item != items.end(); item++) {
235            XMLNode* itemNode = item->value->Clone();
236            itemNode->SetName("", "item");
237            itemNode->EditAttributes().clear();
238            arrayNode->EditChildren().push_back(itemNode);
239        }
240    }
241
242    // <string>
243    for (set<StringResource>::const_iterator it=m_strings.begin(); it!=m_strings.end(); it++) {
244        const StringResource& str = *it;
245        vector<XMLAttribute> attrs;
246        XMLNode* strNode = str.value->Clone();
247        strNode->SetName("", "string");
248        strNode->EditAttributes().clear();
249        strNode->EditAttributes().push_back(XMLAttribute("", "name", str.id));
250        root->EditChildren().push_back(strNode);
251    }
252
253    return root;
254}
255
256string
257ValuesFile::ToString() const
258{
259    XMLNode* xml = ToXMLNode();
260    string s = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
261    s += xml->ToString(ANDROID_NAMESPACES);
262    delete xml;
263    s += '\n';
264    return s;
265}
266
267