| #include "XLIFFFile.h" |
| |
| #include <algorithm> |
| #include <sys/time.h> |
| #include <time.h> |
| #include <cstdio> |
| |
| 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; |
| } |