summaryrefslogtreecommitdiffstats
path: root/tools/localize/XLIFFFile.cpp
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2008-10-21 07:00:00 -0700
committerThe Android Open Source Project <initial-contribution@android.com>2008-10-21 07:00:00 -0700
commit54b6cfa9a9e5b861a9930af873580d6dc20f773c (patch)
tree35051494d2af230dce54d6b31c6af8fc24091316 /tools/localize/XLIFFFile.cpp
downloadframeworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.zip
frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.tar.gz
frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.tar.bz2
Initial Contribution
Diffstat (limited to 'tools/localize/XLIFFFile.cpp')
-rw-r--r--tools/localize/XLIFFFile.cpp608
1 files changed, 608 insertions, 0 deletions
diff --git a/tools/localize/XLIFFFile.cpp b/tools/localize/XLIFFFile.cpp
new file mode 100644
index 0000000..5da05a2
--- /dev/null
+++ b/tools/localize/XLIFFFile.cpp
@@ -0,0 +1,608 @@
+#include "XLIFFFile.h"
+
+#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;
+}