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