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