1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  */
9 
10 #include <iostream>
11 #include <fstream>
12 #include <cassert>
13 #include <cstring>
14 
15 #include <libxml/tree.h>
16 #include <libxml/parser.h>
17 #include <libxml/xmlmemory.h>
18 #include <libxml/xmlstring.h>
19 
20 #include <export.hxx>
21 #include <helper.hxx>
22 #include <common.hxx>
23 #include <po.hxx>
24 #include <treemerge.hxx>
25 
26 
27 namespace
28 {
29     // Extract strings from nodes on all level recursively
lcl_ExtractLevel(const xmlDocPtr pSource,const xmlNodePtr pRoot,const xmlChar * pNodeName,PoOfstream & rPOStream)30     void lcl_ExtractLevel(
31         const xmlDocPtr pSource, const xmlNodePtr pRoot,
32         const xmlChar* pNodeName, PoOfstream& rPOStream )
33     {
34         if( !pRoot->children )
35         {
36             return;
37         }
38         for( xmlNodePtr pCurrent = pRoot->children->next;
39             pCurrent; pCurrent = pCurrent->next)
40         {
41             if (!xmlStrcmp(pCurrent->name, pNodeName))
42             {
43                 xmlChar* pID = xmlGetProp(pCurrent, reinterpret_cast<const xmlChar*>("id"));
44                 xmlChar* pText =
45                     xmlGetProp(pCurrent, reinterpret_cast<const xmlChar*>("title"));
46 
47                 common::writePoEntry(
48                     "Treex", rPOStream, pSource->name, helper::xmlStrToOString( pNodeName ),
49                     helper::xmlStrToOString( pID ), OString(), OString(), helper::xmlStrToOString( pText ));
50 
51                 xmlFree( pID );
52                 xmlFree( pText );
53 
54                 lcl_ExtractLevel(
55                     pSource, pCurrent, reinterpret_cast<const xmlChar *>("node"),
56                     rPOStream );
57             }
58         }
59     }
60 
61     // Update id and content of the topic
lcl_UpdateTopic(const xmlNodePtr pCurrent,const OString & rXhpRoot)62     xmlNodePtr lcl_UpdateTopic(
63         const xmlNodePtr pCurrent, const OString& rXhpRoot )
64     {
65         xmlNodePtr pReturn = pCurrent;
66         xmlChar* pID = xmlGetProp(pReturn, reinterpret_cast<const xmlChar*>("id"));
67         const OString sID =
68             helper::xmlStrToOString( pID );
69         xmlFree( pID );
70 
71         const sal_Int32 nFirstSlash = sID.indexOf('/');
72         // Update id attribute of topic
73         {
74             OString sNewID =
75                 OString::Concat(sID.subView( 0, nFirstSlash + 1 )) +
76                 rXhpRoot.subView( rXhpRoot.lastIndexOf('/') + 1 ) +
77                 sID.subView( sID.indexOf( '/', nFirstSlash + 1 ) );
78             xmlSetProp(
79                 pReturn, reinterpret_cast<const xmlChar*>("id"),
80                 reinterpret_cast<const xmlChar*>(sNewID.getStr()));
81         }
82 
83         const OString sXhpPath =
84             rXhpRoot +
85             sID.subView(sID.indexOf('/', nFirstSlash + 1));
86         xmlDocPtr pXhpFile = xmlParseFile( sXhpPath.getStr() );
87         // if xhpfile is missing than put this topic into comment
88         if ( !pXhpFile )
89         {
90             xmlNodePtr pTemp = pReturn;
91             xmlChar* sNewID =
92                 xmlGetProp(pReturn, reinterpret_cast<const xmlChar*>("id"));
93             xmlChar* sComment =
94                 xmlStrcat( xmlCharStrdup("removed "), sNewID );
95             pReturn = xmlNewComment( sComment );
96             xmlReplaceNode( pTemp, pReturn );
97             xmlFree( pTemp );
98             xmlFree( sNewID );
99             xmlFree( sComment );
100         }
101         // update topic's content on the basis of xhpfile's title
102         else
103         {
104             xmlNodePtr pXhpNode = xmlDocGetRootElement( pXhpFile );
105             for( pXhpNode = pXhpNode->children;
106                 pXhpNode; pXhpNode = pXhpNode->children )
107             {
108                 while( pXhpNode->type != XML_ELEMENT_NODE )
109                 {
110                     pXhpNode = pXhpNode->next;
111                 }
112                 if(!xmlStrcmp(pXhpNode->name, reinterpret_cast<const xmlChar *>("title")))
113                 {
114                     xmlChar* sTitle =
115                         xmlNodeListGetString(pXhpFile, pXhpNode->children, 1);
116                     OString sNewTitle =
117                         helper::xmlStrToOString( sTitle ).
118                             replaceAll("$[officename]","%PRODUCTNAME").
119                                 replaceAll("$[officeversion]","%PRODUCTVERSION");
120                     xmlNodeSetContent(
121                         pReturn,
122                         xmlEncodeSpecialChars( nullptr,
123                             reinterpret_cast<const xmlChar*>(
124                                 sNewTitle.getStr() )));
125                     xmlFree( sTitle );
126                     break;
127                 }
128             }
129             if( !pXhpNode )
130             {
131                 std::cerr
132                     << "Treex error: Cannot find title in "
133                     << sXhpPath << std::endl;
134                 pReturn = nullptr;
135             }
136             xmlFreeDoc( pXhpFile );
137             xmlCleanupParser();
138         }
139         return pReturn;
140     }
141     // Localize title attribute of help_section and node tags
lcl_MergeLevel(xmlDocPtr io_pSource,const xmlNodePtr pRoot,const xmlChar * pNodeName,MergeDataFile * pMergeDataFile,const OString & rLang,const OString & rXhpRoot)142     void lcl_MergeLevel(
143         xmlDocPtr io_pSource, const xmlNodePtr pRoot,
144         const xmlChar * pNodeName, MergeDataFile* pMergeDataFile,
145         const OString& rLang, const OString& rXhpRoot )
146     {
147         if( !pRoot->children )
148         {
149             return;
150         }
151         for( xmlNodePtr pCurrent = pRoot->children;
152             pCurrent; pCurrent = pCurrent->next)
153         {
154             if( !xmlStrcmp(pCurrent->name, pNodeName) )
155             {
156                 if( rLang != "en-US" )
157                 {
158                     OString sNewText;
159                     xmlChar* pID = xmlGetProp(pCurrent, reinterpret_cast<const xmlChar*>("id"));
160                     ResData  aResData(
161                         helper::xmlStrToOString( pID ),
162                         static_cast<OString>(io_pSource->name) );
163                     xmlFree( pID );
164                     aResData.sResTyp = helper::xmlStrToOString( pNodeName );
165                     if( pMergeDataFile )
166                     {
167                         MergeEntrys* pEntrys =
168                             pMergeDataFile->GetMergeEntrys( &aResData );
169                         if( pEntrys )
170                         {
171                             pEntrys->GetText( sNewText, rLang );
172                         }
173                     }
174                     else if( rLang == "qtz" )
175                     {
176                         xmlChar* pText = xmlGetProp(pCurrent, reinterpret_cast<const xmlChar*>("title"));
177                         const OString sOriginText = helper::xmlStrToOString(pText);
178                         xmlFree( pText );
179                         sNewText = MergeEntrys::GetQTZText(aResData, sOriginText);
180                     }
181                     if( !sNewText.isEmpty() )
182                     {
183                         xmlSetProp(
184                             pCurrent, reinterpret_cast<const xmlChar*>("title"),
185                             reinterpret_cast<const xmlChar*>(sNewText.getStr()));
186                     }
187                 }
188 
189                 lcl_MergeLevel(
190                     io_pSource, pCurrent, reinterpret_cast<const xmlChar *>("node"),
191                     pMergeDataFile, rLang, rXhpRoot );
192             }
193             else if( !xmlStrcmp(pCurrent->name, reinterpret_cast<const xmlChar *>("topic")) )
194             {
195                 pCurrent = lcl_UpdateTopic( pCurrent, rXhpRoot );
196             }
197         }
198     }
199 }
200 
TreeParser(const OString & rInputFile,const OString & rLang)201 TreeParser::TreeParser(
202     const OString& rInputFile, const OString& rLang )
203     : m_pSource( nullptr )
204     , m_sLang( rLang )
205     , m_bIsInitialized( false )
206 {
207     m_pSource = xmlParseFile( rInputFile.getStr() );
208     if ( !m_pSource ) {
209         std::cerr
210             << "Treex error: Cannot open source file: "
211             << rInputFile << std::endl;
212         return;
213     }
214     if( !m_pSource->name )
215     {
216         m_pSource->name = static_cast<char *>(xmlMalloc(strlen(rInputFile.getStr())+1));
217         strcpy( m_pSource->name, rInputFile.getStr() );
218     }
219     m_bIsInitialized = true;
220 }
221 
~TreeParser()222 TreeParser::~TreeParser()
223 {
224     // be sure m_pSource is freed
225     if (m_bIsInitialized)
226         xmlFreeDoc( m_pSource );
227 }
228 
Extract(const OString & rPOFile)229 void TreeParser::Extract( const OString& rPOFile )
230 {
231     assert( m_bIsInitialized );
232     PoOfstream aPOStream( rPOFile, PoOfstream::APP );
233     if( !aPOStream.isOpen() )
234     {
235         std::cerr
236             << "Treex error: Cannot open po file for extract: "
237             << rPOFile << std::endl;
238         return;
239     }
240 
241     xmlNodePtr pRootNode = xmlDocGetRootElement( m_pSource );
242     lcl_ExtractLevel(
243         m_pSource, pRootNode, reinterpret_cast<const xmlChar *>("help_section"),
244         aPOStream );
245 
246     xmlFreeDoc( m_pSource );
247     xmlCleanupParser();
248     aPOStream.close();
249     m_bIsInitialized = false;
250 }
251 
Merge(const OString & rMergeSrc,const OString & rDestinationFile,const OString & rXhpRoot)252 void TreeParser::Merge(
253     const OString &rMergeSrc, const OString &rDestinationFile,
254     const OString &rXhpRoot )
255 {
256     assert( m_bIsInitialized );
257 
258     const xmlNodePtr pRootNode = xmlDocGetRootElement( m_pSource );
259     std::unique_ptr<MergeDataFile> pMergeDataFile;
260     if( m_sLang != "qtz" && m_sLang != "en-US" )
261     {
262         pMergeDataFile.reset(new MergeDataFile(
263             rMergeSrc, static_cast<OString>( m_pSource->name ), false, false ));
264         const std::vector<OString> vLanguages = pMergeDataFile->GetLanguages();
265         if( !vLanguages.empty() && vLanguages[0] != m_sLang )
266         {
267             std::cerr
268                 << ("Treex error: given language conflicts with language of"
269                     " Mergedata file: ")
270                 << m_sLang << " - "
271                 << vLanguages[0] << std::endl;
272             return;
273         }
274     }
275     lcl_MergeLevel(
276         m_pSource, pRootNode, reinterpret_cast<const xmlChar *>("help_section"),
277         pMergeDataFile.get(), m_sLang, rXhpRoot );
278 
279     pMergeDataFile.reset();
280     xmlSaveFile( rDestinationFile.getStr(), m_pSource );
281     xmlFreeDoc( m_pSource );
282     xmlCleanupParser();
283     m_bIsInitialized = false;
284 }
285 
286 
287 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
288