| #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; | 
 | } |