Line data Source code
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 <rtl/ustring.hxx>
11 :
12 : #include <cstring>
13 : #include <ctime>
14 : #include <cassert>
15 :
16 : #include <vector>
17 : #include <string>
18 :
19 : #ifdef _MSC_VER
20 : #pragma warning (push, 1)
21 : #pragma warning (disable: 4245)
22 : #endif
23 : #include <boost/crc.hpp>
24 : #ifdef _MSC_VER
25 : #pragma warning (pop)
26 : #endif
27 :
28 :
29 : #include "po.hxx"
30 : #include "helper.hxx"
31 :
32 : /** Container of po entry
33 :
34 : Provide all file operations related to LibreOffice specific
35 : po entry and store it's attributes.
36 : */
37 0 : class GenPoEntry
38 : {
39 : private:
40 :
41 : OString m_sExtractCom;
42 : OString m_sReference;
43 : OString m_sMsgCtxt;
44 : OString m_sMsgId;
45 : OString m_sMsgStr;
46 : bool m_bFuzzy;
47 : bool m_bNull;
48 :
49 : public:
50 :
51 : GenPoEntry();
52 : virtual ~GenPoEntry();
53 : // Default copy constructor and copy operator work well
54 :
55 0 : OString getReference() const { return m_sReference; }
56 0 : OString getMsgCtxt() const { return m_sMsgCtxt; }
57 0 : OString getMsgId() const { return m_sMsgId; }
58 0 : OString getMsgStr() const { return m_sMsgStr; }
59 0 : bool isFuzzy() const { return m_bFuzzy; }
60 0 : bool isNull() const { return m_bNull; }
61 :
62 0 : void setExtractCom(const OString& rExtractCom)
63 : {
64 0 : m_sExtractCom = rExtractCom;
65 0 : }
66 0 : void setReference(const OString& rReference)
67 : {
68 0 : m_sReference = rReference;
69 0 : }
70 0 : void setMsgCtxt(const OString& rMsgCtxt)
71 : {
72 0 : m_sMsgCtxt = rMsgCtxt;
73 0 : }
74 0 : void setMsgId(const OString& rMsgId)
75 : {
76 0 : m_sMsgId = rMsgId;
77 0 : }
78 0 : void setMsgStr(const OString& rMsgStr)
79 : {
80 0 : m_sMsgStr = rMsgStr;
81 0 : }
82 :
83 : void writeToFile(std::ofstream& rOFStream) const;
84 : void readFromFile(std::ifstream& rIFStream);
85 : };
86 :
87 : namespace
88 : {
89 : // Convert a normal string to msg/po output string
90 0 : static OString lcl_GenMsgString(const OString& rString)
91 : {
92 0 : if ( rString.isEmpty() )
93 0 : return "\"\"";
94 :
95 : OString sResult =
96 0 : "\"" +
97 0 : helper::escapeAll(rString,"\n""\t""\r""\\""\"","\\n""\\t""\\r""\\\\""\\\"") +
98 0 : "\"";
99 0 : sal_Int32 nIndex = 0;
100 0 : while((nIndex=sResult.indexOf("\\n",nIndex))!=-1)
101 : {
102 0 : if( sResult.copy(nIndex-1,3)!="\\\\n" &&
103 0 : nIndex!=sResult.getLength()-3)
104 : {
105 0 : sResult = sResult.replaceAt(nIndex,2,"\\n\"\n\"");
106 : }
107 0 : ++nIndex;
108 : }
109 :
110 0 : if ( sResult.indexOf('\n') != -1 )
111 0 : return "\"\"\n" + sResult;
112 :
113 0 : return sResult;
114 : }
115 :
116 : // Convert msg string to normal form
117 0 : static OString lcl_GenNormString(const OString& rString)
118 : {
119 : return
120 : helper::unEscapeAll(
121 0 : rString.copy(1,rString.getLength()-2),
122 : "\\n""\\t""\\r""\\\\""\\\"",
123 0 : "\n""\t""\r""\\""\"");
124 : }
125 : }
126 :
127 0 : GenPoEntry::GenPoEntry()
128 : : m_sExtractCom( OString() )
129 : , m_sReference( OString() )
130 : , m_sMsgCtxt( OString() )
131 : , m_sMsgId( OString() )
132 : , m_sMsgStr( OString() )
133 : , m_bFuzzy( false )
134 0 : , m_bNull( false )
135 : {
136 0 : }
137 :
138 0 : GenPoEntry::~GenPoEntry()
139 : {
140 0 : }
141 :
142 0 : void GenPoEntry::writeToFile(std::ofstream& rOFStream) const
143 : {
144 0 : if ( rOFStream.tellp() != std::ofstream::pos_type( 0 ))
145 0 : rOFStream << std::endl;
146 0 : if ( !m_sExtractCom.isEmpty() )
147 : rOFStream
148 0 : << "#. "
149 0 : << m_sExtractCom.replaceAll("\n","\n#. ").getStr() << std::endl;
150 0 : if ( !m_sReference.isEmpty() )
151 0 : rOFStream << "#: " << m_sReference.getStr() << std::endl;
152 0 : if ( m_bFuzzy )
153 0 : rOFStream << "#, fuzzy" << std::endl;
154 0 : if ( !m_sMsgCtxt.isEmpty() )
155 0 : rOFStream << "msgctxt "
156 0 : << lcl_GenMsgString(m_sReference+"\n"+m_sMsgCtxt).getStr()
157 0 : << std::endl;
158 0 : rOFStream << "msgid "
159 0 : << lcl_GenMsgString(m_sMsgId).getStr() << std::endl;
160 0 : rOFStream << "msgstr "
161 0 : << lcl_GenMsgString(m_sMsgStr).getStr() << std::endl;
162 0 : }
163 :
164 0 : void GenPoEntry::readFromFile(std::ifstream& rIFStream)
165 : {
166 0 : *this = GenPoEntry();
167 0 : OString* pLastMsg = 0;
168 0 : std::string sTemp;
169 0 : getline(rIFStream,sTemp);
170 0 : if( rIFStream.eof() || sTemp.empty() )
171 : {
172 0 : m_bNull = true;
173 0 : return;
174 : }
175 0 : while(!rIFStream.eof())
176 : {
177 0 : OString sLine = OString(sTemp.data(),sTemp.length());
178 0 : if (sLine.startsWith("#. "))
179 : {
180 0 : if( !m_sExtractCom.isEmpty() )
181 : {
182 0 : m_sExtractCom += "\n";
183 : }
184 0 : m_sExtractCom += sLine.copy(3);
185 : }
186 0 : else if (sLine.startsWith("#: "))
187 : {
188 0 : m_sReference = sLine.copy(3);
189 : }
190 0 : else if (sLine.startsWith("#, fuzzy"))
191 : {
192 0 : m_bFuzzy = true;
193 : }
194 0 : else if (sLine.startsWith("msgctxt "))
195 : {
196 0 : m_sMsgCtxt = lcl_GenNormString(sLine.copy(8));
197 0 : pLastMsg = &m_sMsgCtxt;
198 : }
199 0 : else if (sLine.startsWith("msgid "))
200 : {
201 0 : m_sMsgId = lcl_GenNormString(sLine.copy(6));
202 0 : pLastMsg = &m_sMsgId;
203 : }
204 0 : else if (sLine.startsWith("msgstr "))
205 : {
206 0 : m_sMsgStr = lcl_GenNormString(sLine.copy(7));
207 0 : pLastMsg = &m_sMsgStr;
208 : }
209 0 : else if (sLine.startsWith("\"") && pLastMsg)
210 : {
211 0 : if (pLastMsg != &m_sMsgCtxt || sLine != "\"" + m_sReference + "\\n\"")
212 : {
213 0 : *pLastMsg += lcl_GenNormString(sLine);
214 : }
215 : }
216 : else
217 0 : break;
218 0 : getline(rIFStream,sTemp);
219 0 : }
220 : }
221 :
222 :
223 : // Class PoEntry
224 :
225 :
226 0 : PoEntry::PoEntry()
227 : : m_pGenPo( 0 )
228 0 : , m_bIsInitialized( false )
229 : {
230 0 : }
231 :
232 0 : PoEntry::PoEntry(
233 : const OString& rSourceFile, const OString& rResType, const OString& rGroupId,
234 : const OString& rLocalId, const OString& rHelpText,
235 : const OString& rText, const TYPE eType )
236 : : m_pGenPo( 0 )
237 0 : , m_bIsInitialized( false )
238 : {
239 0 : if( rSourceFile.isEmpty() )
240 0 : throw NOSOURCFILE;
241 0 : else if ( rResType.isEmpty() )
242 0 : throw NORESTYPE;
243 0 : else if ( rGroupId.isEmpty() )
244 0 : throw NOGROUPID;
245 0 : else if ( rText.isEmpty() )
246 0 : throw NOSTRING;
247 0 : else if ( rHelpText.getLength() == 5 )
248 0 : throw WRONGHELPTEXT;
249 :
250 0 : m_pGenPo = new GenPoEntry();
251 0 : m_pGenPo->setReference(rSourceFile.copy(rSourceFile.lastIndexOf('/')+1));
252 :
253 : OString sMsgCtxt =
254 0 : rGroupId + "\n" +
255 0 : (rLocalId.isEmpty() ? OString( "" ) : rLocalId + "\n") +
256 0 : rResType;
257 0 : switch(eType){
258 : case TTEXT:
259 0 : sMsgCtxt += ".text"; break;
260 : case TQUICKHELPTEXT:
261 0 : sMsgCtxt += ".quickhelptext"; break;
262 : case TTITLE:
263 0 : sMsgCtxt += ".title"; break;
264 : // Default case is unneeded because the type of eType has only three element
265 : }
266 0 : m_pGenPo->setMsgCtxt(sMsgCtxt);
267 0 : m_pGenPo->setMsgId(rText);
268 : m_pGenPo->setExtractCom(
269 0 : ( !rHelpText.isEmpty() ? rHelpText + "\n" : OString( "" )) +
270 0 : genKeyId( m_pGenPo->getReference() + rGroupId + rLocalId + rResType + rText ) );
271 0 : m_bIsInitialized = true;
272 0 : }
273 :
274 0 : PoEntry::~PoEntry()
275 : {
276 0 : delete m_pGenPo;
277 0 : }
278 :
279 0 : PoEntry::PoEntry( const PoEntry& rPo )
280 0 : : m_pGenPo( rPo.m_pGenPo ? new GenPoEntry( *(rPo.m_pGenPo) ) : 0 )
281 0 : , m_bIsInitialized( rPo.m_bIsInitialized )
282 : {
283 0 : }
284 :
285 0 : PoEntry& PoEntry::operator=(const PoEntry& rPo)
286 : {
287 0 : if( this == &rPo )
288 : {
289 0 : return *this;
290 : }
291 0 : if( rPo.m_pGenPo )
292 : {
293 0 : if( m_pGenPo )
294 : {
295 0 : *m_pGenPo = *(rPo.m_pGenPo);
296 : }
297 : else
298 : {
299 0 : m_pGenPo = new GenPoEntry( *(rPo.m_pGenPo) );
300 : }
301 : }
302 : else
303 : {
304 0 : delete m_pGenPo;
305 0 : m_pGenPo = 0;
306 : }
307 0 : m_bIsInitialized = rPo.m_bIsInitialized;
308 0 : return *this;
309 : }
310 :
311 0 : OString PoEntry::getSourceFile() const
312 : {
313 : assert( m_bIsInitialized );
314 0 : return m_pGenPo->getReference();
315 : }
316 :
317 0 : OString PoEntry::getGroupId() const
318 : {
319 : assert( m_bIsInitialized );
320 0 : return m_pGenPo->getMsgCtxt().getToken(0,'\n');
321 : }
322 :
323 0 : OString PoEntry::getLocalId() const
324 : {
325 : assert( m_bIsInitialized );
326 0 : const OString sMsgCtxt = m_pGenPo->getMsgCtxt();
327 0 : if (sMsgCtxt.indexOf('\n')==sMsgCtxt.lastIndexOf('\n'))
328 0 : return OString();
329 : else
330 0 : return sMsgCtxt.getToken(1,'\n');
331 : }
332 :
333 0 : OString PoEntry::getResourceType() const
334 : {
335 : assert( m_bIsInitialized );
336 0 : const OString sMsgCtxt = m_pGenPo->getMsgCtxt();
337 0 : if (sMsgCtxt.indexOf('\n')==sMsgCtxt.lastIndexOf('\n'))
338 0 : return sMsgCtxt.getToken(1,'\n').getToken(0,'.');
339 : else
340 0 : return sMsgCtxt.getToken(2,'\n').getToken(0,'.');
341 : }
342 :
343 0 : PoEntry::TYPE PoEntry::getType() const
344 : {
345 : assert( m_bIsInitialized );
346 0 : const OString sMsgCtxt = m_pGenPo->getMsgCtxt();
347 0 : const OString sType = sMsgCtxt.copy( sMsgCtxt.lastIndexOf('.') + 1 );
348 : assert(
349 : (sType == "text" || sType == "quickhelptext" || sType == "title") );
350 0 : if ( sType == "text" )
351 0 : return TTEXT;
352 0 : else if ( sType == "quickhelptext" )
353 0 : return TQUICKHELPTEXT;
354 : else
355 0 : return TTITLE;
356 : }
357 :
358 0 : bool PoEntry::isFuzzy() const
359 : {
360 : assert( m_bIsInitialized );
361 0 : return m_pGenPo->isFuzzy();
362 : }
363 :
364 : // Get translation string in merge format
365 0 : OString PoEntry::getMsgId() const
366 : {
367 : assert( m_bIsInitialized );
368 0 : return m_pGenPo->getMsgId();
369 : }
370 :
371 : // Get translated string in merge format
372 0 : OString PoEntry::getMsgStr() const
373 : {
374 : assert( m_bIsInitialized );
375 0 : return m_pGenPo->getMsgStr();
376 :
377 : }
378 :
379 0 : bool PoEntry::IsInSameComp(const PoEntry& rPo1,const PoEntry& rPo2)
380 : {
381 : assert( rPo1.m_bIsInitialized && rPo2.m_bIsInitialized );
382 0 : return ( rPo1.getSourceFile() == rPo2.getSourceFile() &&
383 0 : rPo1.getGroupId() == rPo2.getGroupId() &&
384 0 : rPo1.getLocalId() == rPo2.getLocalId() &&
385 0 : rPo1.getResourceType() == rPo2.getResourceType() );
386 : }
387 :
388 0 : OString PoEntry::genKeyId(const OString& rGenerator)
389 : {
390 0 : boost::crc_32_type aCRC32;
391 0 : aCRC32.process_bytes(rGenerator.getStr(), rGenerator.getLength());
392 0 : sal_uInt32 nCRC = aCRC32.checksum();
393 : // Use simple ASCII characters, exclude I, l, 1 and O, 0 to avoid confusing IDs
394 : static const char sSymbols[] =
395 : "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789";
396 : char sKeyId[6];
397 0 : for( short nKeyInd = 0; nKeyInd < 5; ++nKeyInd )
398 : {
399 0 : sKeyId[nKeyInd] = sSymbols[(nCRC & 63) % strlen(sSymbols)];
400 0 : nCRC >>= 6;
401 : }
402 0 : sKeyId[5] = '\0';
403 0 : return OString(sKeyId);
404 : }
405 :
406 :
407 : // Class PoHeader
408 :
409 :
410 : namespace
411 : {
412 : // Get actual time in "YEAR-MO-DA HO:MI+ZONE" form
413 0 : static OString lcl_GetTime()
414 : {
415 0 : time_t aNow = time(NULL);
416 0 : struct tm* pNow = localtime(&aNow);
417 : char pBuff[50];
418 0 : strftime( pBuff, sizeof pBuff, "%Y-%m-%d %H:%M%z", pNow );
419 0 : return pBuff;
420 : }
421 : }
422 :
423 0 : PoHeader::PoHeader( const OString& rExtSrc )
424 : : m_pGenPo( new GenPoEntry() )
425 0 : , m_bIsInitialized( false )
426 : {
427 0 : m_pGenPo->setExtractCom("extracted from " + rExtSrc);
428 : m_pGenPo->setMsgStr(
429 : OString("Project-Id-Version: PACKAGE VERSION\n"
430 : "Report-Msgid-Bugs-To: https://bugs.libreoffice.org/enter_bug.cgi?"
431 : "product=LibreOffice&bug_status=UNCONFIRMED&component=UI\n"
432 0 : "POT-Creation-Date: ") + lcl_GetTime() +
433 : OString("\nPO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
434 : "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
435 : "Language-Team: LANGUAGE <LL@li.org>\n"
436 : "MIME-Version: 1.0\n"
437 : "Content-Type: text/plain; charset=UTF-8\n"
438 : "Content-Transfer-Encoding: 8bit\n"
439 : "X-Generator: LibreOffice\n"
440 0 : "X-Accelerator-Marker: ~\n"));
441 0 : m_bIsInitialized = true;
442 0 : }
443 :
444 0 : PoHeader::~PoHeader()
445 : {
446 0 : delete m_pGenPo;
447 0 : }
448 :
449 :
450 : // Class PoOfstream
451 :
452 :
453 0 : PoOfstream::PoOfstream()
454 : : m_aOutPut()
455 0 : , m_bIsAfterHeader( false )
456 : {
457 0 : }
458 :
459 0 : PoOfstream::PoOfstream(const OString& rFileName, OpenMode aMode )
460 : : m_aOutPut()
461 0 : , m_bIsAfterHeader( false )
462 : {
463 0 : open( rFileName, aMode );
464 0 : }
465 :
466 0 : PoOfstream::~PoOfstream()
467 : {
468 0 : if( isOpen() )
469 : {
470 0 : close();
471 : }
472 0 : }
473 :
474 0 : void PoOfstream::open(const OString& rFileName, OpenMode aMode )
475 : {
476 : assert( !isOpen() );
477 0 : if( aMode == TRUNC )
478 : {
479 : m_aOutPut.open( rFileName.getStr(),
480 0 : std::ios_base::out | std::ios_base::trunc );
481 0 : m_bIsAfterHeader = false;
482 : }
483 0 : else if( aMode == APP )
484 : {
485 : m_aOutPut.open( rFileName.getStr(),
486 0 : std::ios_base::out | std::ios_base::app );
487 0 : m_bIsAfterHeader = m_aOutPut.tellp() != std::ofstream::pos_type( 0 );
488 : }
489 0 : }
490 :
491 0 : void PoOfstream::close()
492 : {
493 : assert( isOpen() );
494 0 : m_aOutPut.close();
495 0 : }
496 :
497 0 : void PoOfstream::writeHeader(const PoHeader& rPoHeader)
498 : {
499 : assert( isOpen() && !m_bIsAfterHeader && rPoHeader.m_bIsInitialized );
500 0 : rPoHeader.m_pGenPo->writeToFile( m_aOutPut );
501 0 : m_bIsAfterHeader = true;
502 0 : }
503 :
504 0 : void PoOfstream::writeEntry( const PoEntry& rPoEntry )
505 : {
506 : assert( isOpen() && m_bIsAfterHeader && rPoEntry.m_bIsInitialized );
507 0 : rPoEntry.m_pGenPo->writeToFile( m_aOutPut );
508 0 : }
509 :
510 :
511 : // Class PoIfstream
512 :
513 :
514 : namespace
515 : {
516 :
517 : // Check the validity of read entry
518 0 : static bool lcl_CheckInputEntry(const GenPoEntry& rEntry)
519 : {
520 0 : const OString sMsgCtxt = rEntry.getMsgCtxt();
521 0 : const sal_Int32 nFirstEndLine = sMsgCtxt.indexOf('\n');
522 0 : const sal_Int32 nLastEndLine = sMsgCtxt.lastIndexOf('\n');
523 0 : const sal_Int32 nLastDot = sMsgCtxt.lastIndexOf('.');
524 0 : const OString sType = sMsgCtxt.copy( nLastDot + 1 );
525 0 : return !rEntry.getReference().isEmpty() &&
526 0 : nFirstEndLine > 0 &&
527 0 : (nLastEndLine == nFirstEndLine || nLastEndLine == sMsgCtxt.indexOf('\n',nFirstEndLine+1)) &&
528 0 : nLastDot - nLastEndLine > 1 &&
529 0 : (sType == "text" || sType == "quickhelptext" || sType == "title")&&
530 0 : !rEntry.getMsgId().isEmpty();
531 : }
532 :
533 : }
534 :
535 0 : PoIfstream::PoIfstream()
536 : : m_aInPut()
537 0 : , m_bEof( false )
538 : {
539 0 : }
540 :
541 0 : PoIfstream::PoIfstream(const OString& rFileName)
542 : : m_aInPut()
543 0 : , m_bEof( false )
544 : {
545 0 : open( rFileName );
546 0 : }
547 :
548 0 : PoIfstream::~PoIfstream()
549 : {
550 0 : if( isOpen() )
551 : {
552 0 : close();
553 : }
554 0 : }
555 :
556 0 : void PoIfstream::open( const OString& rFileName )
557 : {
558 : assert( !isOpen() );
559 0 : m_aInPut.open( rFileName.getStr(), std::ios_base::in );
560 :
561 : // Skip header
562 0 : std::string sTemp;
563 0 : std::getline(m_aInPut,sTemp);
564 0 : while( !sTemp.empty() && !m_aInPut.eof() )
565 : {
566 0 : std::getline(m_aInPut,sTemp);
567 : }
568 0 : m_bEof = false;
569 0 : }
570 :
571 0 : void PoIfstream::close()
572 : {
573 : assert( isOpen() );
574 0 : m_aInPut.close();
575 0 : }
576 :
577 0 : void PoIfstream::readEntry( PoEntry& rPoEntry )
578 : {
579 : assert( isOpen() && !eof() );
580 0 : GenPoEntry aGenPo;
581 0 : aGenPo.readFromFile( m_aInPut );
582 0 : if( aGenPo.isNull() )
583 : {
584 0 : m_bEof = true;
585 0 : rPoEntry = PoEntry();
586 : }
587 : else
588 : {
589 0 : if( lcl_CheckInputEntry(aGenPo) )
590 : {
591 0 : if( rPoEntry.m_pGenPo )
592 : {
593 0 : *(rPoEntry.m_pGenPo) = aGenPo;
594 : }
595 : else
596 : {
597 0 : rPoEntry.m_pGenPo = new GenPoEntry( aGenPo );
598 : }
599 0 : rPoEntry.m_bIsInitialized = true;
600 : }
601 : else
602 : {
603 0 : throw INVALIDENTRY;
604 : }
605 0 : }
606 0 : }
607 :
608 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|