update credits
[LibreOffice.git] / l10ntools / source / po.cxx
blob7b4a94c0e8b3f7ccac70e8ab83c8eab09e0c115c
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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 */
10 #include <rtl/ustring.hxx>
12 #include <cstring>
13 #include <ctime>
14 #include <cassert>
16 #include <vector>
17 #include <string>
19 #include <boost/crc.hpp>
21 #include "po.hxx"
22 #include "helper.hxx"
24 /** Container of po entry
26 Provide all file operations related to LibreOffice specific
27 po entry and store it's attributes.
29 class GenPoEntry
31 private:
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;
41 public:
43 GenPoEntry();
44 virtual ~GenPoEntry();
45 // Default copy constructor and copy operator work well
47 virtual OString getExtractCom() const { return m_sExtractCom; }
48 virtual OString getReference() const { return m_sReference; }
49 virtual OString getMsgCtxt() const { return m_sMsgCtxt; }
50 virtual OString getMsgId() const { return m_sMsgId; }
51 virtual OString getMsgStr() const { return m_sMsgStr; }
52 virtual bool isFuzzy() const { return m_bFuzzy; }
53 virtual bool isNull() const { return m_bNull; }
55 virtual void setExtractCom(const OString& rExtractCom)
57 m_sExtractCom = rExtractCom;
59 virtual void setReference(const OString& rReference)
61 m_sReference = rReference;
63 virtual void setMsgCtxt(const OString& rMsgCtxt)
65 m_sMsgCtxt = rMsgCtxt;
67 virtual void setMsgId(const OString& rMsgId)
69 m_sMsgId = rMsgId;
71 virtual void setMsgStr(const OString& rMsgStr)
73 m_sMsgStr = rMsgStr;
75 virtual void setFuzzy(const bool bFuzzy)
77 m_bFuzzy = bFuzzy;
80 virtual void writeToFile(std::ofstream& rOFStream) const;
81 virtual void readFromFile(std::ifstream& rIFStream);
84 namespace
86 // Convert a normal string to msg/po output string
87 static OString lcl_GenMsgString(const OString& rString)
89 if ( rString.isEmpty() )
90 return "\"\"";
92 OString sResult =
93 "\"" +
94 helper::escapeAll(rString,"\n""\t""\r""\\""\"","\\n""\\t""\\r""\\\\""\\\"") +
95 "\"";
96 sal_Int32 nIndex = 0;
97 while((nIndex=sResult.indexOf("\\n",nIndex))!=-1)
99 if( sResult.copy(nIndex-1,3)!="\\\\n" &&
100 nIndex!=sResult.getLength()-3)
102 sResult = sResult.replaceAt(nIndex,2,"\\n\"\n\"");
104 ++nIndex;
107 if ( sResult.indexOf('\n') != -1 )
108 return "\"\"\n" + sResult;
110 return sResult;
113 // Convert msg string to normal form
114 static OString lcl_GenNormString(const OString& rString)
116 return
117 helper::unEscapeAll(
118 rString.copy(1,rString.getLength()-2),
119 "\\n""\\t""\\r""\\\\""\\\"",
120 "\n""\t""\r""\\""\"");
124 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 , m_bNull( false )
135 GenPoEntry::~GenPoEntry()
139 void GenPoEntry::writeToFile(std::ofstream& rOFStream) const
141 if ( rOFStream.tellp() != std::ofstream::pos_type( 0 ))
142 rOFStream << std::endl;
143 if ( !m_sExtractCom.isEmpty() )
144 rOFStream
145 << "#. "
146 << m_sExtractCom.replaceAll("\n","\n#. ").getStr() << std::endl;
147 if ( !m_sReference.isEmpty() )
148 rOFStream << "#: " << m_sReference.getStr() << std::endl;
149 if ( m_bFuzzy )
150 rOFStream << "#, fuzzy" << std::endl;
151 if ( !m_sMsgCtxt.isEmpty() )
152 rOFStream << "msgctxt "
153 << lcl_GenMsgString(m_sReference+"\n"+m_sMsgCtxt).getStr()
154 << std::endl;
155 rOFStream << "msgid "
156 << lcl_GenMsgString(m_sMsgId).getStr() << std::endl;
157 rOFStream << "msgstr "
158 << lcl_GenMsgString(m_sMsgStr).getStr() << std::endl;
161 void GenPoEntry::readFromFile(std::ifstream& rIFStream)
163 *this = GenPoEntry();
164 OString* pLastMsg = 0;
165 std::string sTemp;
166 getline(rIFStream,sTemp);
167 if( rIFStream.eof() || sTemp.empty() )
169 m_bNull = true;
170 return;
172 while(!rIFStream.eof())
174 OString sLine = OString(sTemp.data(),sTemp.length());
175 if (sLine.startsWith("#. "))
177 if( !m_sExtractCom.isEmpty() )
179 m_sExtractCom += "\n";
181 m_sExtractCom += sLine.copy(3);
183 else if (sLine.startsWith("#: "))
185 m_sReference = sLine.copy(3);
187 else if (sLine.startsWith("#, fuzzy"))
189 m_bFuzzy = true;
191 else if (sLine.startsWith("msgctxt "))
193 m_sMsgCtxt = lcl_GenNormString(sLine.copy(8));
194 pLastMsg = &m_sMsgCtxt;
196 else if (sLine.startsWith("msgid "))
198 m_sMsgId = lcl_GenNormString(sLine.copy(6));
199 pLastMsg = &m_sMsgId;
201 else if (sLine.startsWith("msgstr "))
203 m_sMsgStr = lcl_GenNormString(sLine.copy(7));
204 pLastMsg = &m_sMsgStr;
206 else if (sLine.startsWith("\"") && pLastMsg)
208 if (pLastMsg != &m_sMsgCtxt || sLine != "\"" + m_sReference + "\\n\"")
210 *pLastMsg += lcl_GenNormString(sLine);
213 else
214 break;
215 getline(rIFStream,sTemp);
220 // Class PoEntry
223 PoEntry::PoEntry()
224 : m_pGenPo( 0 )
225 , m_bIsInitialized( false )
229 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 , m_bIsInitialized( false )
236 if( rSourceFile.isEmpty() )
237 throw NOSOURCFILE;
238 else if ( rResType.isEmpty() )
239 throw NORESTYPE;
240 else if ( rGroupId.isEmpty() )
241 throw NOGROUPID;
242 else if ( rText.isEmpty() )
243 throw NOSTRING;
244 else if ( rHelpText.getLength() == 5 )
245 throw WRONGHELPTEXT;
247 m_pGenPo = new GenPoEntry();
248 m_pGenPo->setReference(rSourceFile.copy(rSourceFile.lastIndexOf("/")+1));
250 OString sMsgCtxt =
251 rGroupId + "\n" +
252 (rLocalId.isEmpty() ? OString( "" ) : rLocalId + "\n") +
253 rResType;
254 switch(eType){
255 case TTEXT:
256 sMsgCtxt += ".text"; break;
257 case TQUICKHELPTEXT:
258 sMsgCtxt += ".quickhelptext"; break;
259 case TTITLE:
260 sMsgCtxt += ".title"; break;
261 // Default case is unneeded because the type of eType has only three element
263 m_pGenPo->setMsgCtxt(sMsgCtxt);
264 m_pGenPo->setMsgId(rText);
265 m_pGenPo->setExtractCom(
266 ( !rHelpText.isEmpty() ? rHelpText + "\n" : OString( "" )) +
267 genKeyId( m_pGenPo->getReference() + rGroupId + rLocalId + rResType + rText ) );
268 m_bIsInitialized = true;
271 PoEntry::~PoEntry()
273 delete m_pGenPo;
276 PoEntry::PoEntry( const PoEntry& rPo )
277 : m_pGenPo( rPo.m_pGenPo ? new GenPoEntry( *(rPo.m_pGenPo) ) : 0 )
278 , m_bIsInitialized( rPo.m_bIsInitialized )
282 PoEntry& PoEntry::operator=(const PoEntry& rPo)
284 if( this == &rPo )
286 return *this;
288 if( rPo.m_pGenPo )
290 if( m_pGenPo )
292 *m_pGenPo = *(rPo.m_pGenPo);
294 else
296 m_pGenPo = new GenPoEntry( *(rPo.m_pGenPo) );
299 else
301 delete m_pGenPo;
302 m_pGenPo = 0;
304 m_bIsInitialized = rPo.m_bIsInitialized;
305 return *this;
308 OString PoEntry::getSourceFile() const
310 assert( m_bIsInitialized );
311 return m_pGenPo->getReference();
314 OString PoEntry::getGroupId() const
316 assert( m_bIsInitialized );
317 return m_pGenPo->getMsgCtxt().getToken(0,'\n');
320 OString PoEntry::getLocalId() const
322 assert( m_bIsInitialized );
323 const OString sMsgCtxt = m_pGenPo->getMsgCtxt();
324 if (sMsgCtxt.indexOf('\n')==sMsgCtxt.lastIndexOf('\n'))
325 return OString();
326 else
327 return sMsgCtxt.getToken(1,'\n');
330 OString PoEntry::getResourceType() const
332 assert( m_bIsInitialized );
333 const OString sMsgCtxt = m_pGenPo->getMsgCtxt();
334 if (sMsgCtxt.indexOf('\n')==sMsgCtxt.lastIndexOf('\n'))
335 return sMsgCtxt.getToken(1,'\n').getToken(0,'.');
336 else
337 return sMsgCtxt.getToken(2,'\n').getToken(0,'.');
340 PoEntry::TYPE PoEntry::getType() const
342 assert( m_bIsInitialized );
343 const OString sMsgCtxt = m_pGenPo->getMsgCtxt();
344 const OString sType = sMsgCtxt.copy( sMsgCtxt.lastIndexOf('.') + 1 );
345 assert(
346 (sType == "text" || sType == "quickhelptext" || sType == "title") );
347 if ( sType == "text" )
348 return TTEXT;
349 else if ( sType == "quickhelptext" )
350 return TQUICKHELPTEXT;
351 else
352 return TTITLE;
355 bool PoEntry::isFuzzy() const
357 assert( m_bIsInitialized );
358 return m_pGenPo->isFuzzy();
361 // Get translation string in merge format
362 OString PoEntry::getMsgId() const
364 assert( m_bIsInitialized );
365 return m_pGenPo->getMsgId();
368 // Get translated string in merge format
369 OString PoEntry::getMsgStr() const
371 assert( m_bIsInitialized );
372 return m_pGenPo->getMsgStr();
376 bool PoEntry::IsInSameComp(const PoEntry& rPo1,const PoEntry& rPo2)
378 assert( rPo1.m_bIsInitialized && rPo2.m_bIsInitialized );
379 return ( rPo1.getSourceFile() == rPo2.getSourceFile() &&
380 rPo1.getGroupId() == rPo2.getGroupId() &&
381 rPo1.getLocalId() == rPo2.getLocalId() &&
382 rPo1.getResourceType() == rPo2.getResourceType() );
385 OString PoEntry::genKeyId(const OString& rGenerator)
387 boost::crc_32_type aCRC32;
388 aCRC32.process_bytes(rGenerator.getStr(), rGenerator.getLength());
389 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 "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789";
393 char sKeyId[6];
394 for( short nKeyInd = 0; nKeyInd < 5; ++nKeyInd )
396 sKeyId[nKeyInd] = sSymbols[(nCRC & 63) % sSymbols.getLength()];
397 nCRC >>= 6;
399 sKeyId[5] = '\0';
400 return OString(sKeyId);
404 // Class PoHeader
407 namespace
409 // Get actual time in "YEAR-MO-DA HO:MI+ZONE" form
410 static OString lcl_GetTime()
412 time_t aNow = time(NULL);
413 struct tm* pNow = localtime(&aNow);
414 char pBuff[50];
415 strftime( pBuff, sizeof pBuff, "%Y-%m-%d %H:%M%z", pNow );
416 return pBuff;
420 PoHeader::PoHeader( const OString& rExtSrc )
421 : m_pGenPo( new GenPoEntry() )
422 , m_bIsInitialized( false )
424 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.freedesktop.org/enter_bug.cgi?"
428 "product=LibreOffice&bug_status=UNCONFIRMED&component=UI\n"
429 "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 "X-Accelerator-Marker: ~\n"));
438 m_bIsInitialized = true;
441 PoHeader::~PoHeader()
443 delete m_pGenPo;
447 // Class PoOfstream
450 PoOfstream::PoOfstream()
451 : m_aOutPut()
452 , m_bIsAfterHeader( false )
456 PoOfstream::PoOfstream(const OString& rFileName, OpenMode aMode )
457 : m_aOutPut()
458 , m_bIsAfterHeader( false )
460 open( rFileName, aMode );
463 PoOfstream::~PoOfstream()
465 if( isOpen() )
467 close();
471 void PoOfstream::open(const OString& rFileName, OpenMode aMode )
473 assert( !isOpen() );
474 if( aMode == TRUNC )
476 m_aOutPut.open( rFileName.getStr(),
477 std::ios_base::out | std::ios_base::trunc );
478 m_bIsAfterHeader = false;
480 else if( aMode == APP )
482 m_aOutPut.open( rFileName.getStr(),
483 std::ios_base::out | std::ios_base::app );
484 m_bIsAfterHeader = m_aOutPut.tellp() != std::ofstream::pos_type( 0 );
488 void PoOfstream::close()
490 assert( isOpen() );
491 m_aOutPut.close();
494 void PoOfstream::writeHeader(const PoHeader& rPoHeader)
496 assert( isOpen() && !m_bIsAfterHeader && rPoHeader.m_bIsInitialized );
497 rPoHeader.m_pGenPo->writeToFile( m_aOutPut );
498 m_bIsAfterHeader = true;
501 void PoOfstream::writeEntry( const PoEntry& rPoEntry )
503 assert( isOpen() && m_bIsAfterHeader && rPoEntry.m_bIsInitialized );
504 rPoEntry.m_pGenPo->writeToFile( m_aOutPut );
508 // Class PoIfstream
511 namespace
514 // Check the validity of read entry
515 static bool lcl_CheckInputEntry(const GenPoEntry& rEntry)
517 const OString sMsgCtxt = rEntry.getMsgCtxt();
518 const sal_Int32 nFirstEndLine = sMsgCtxt.indexOf('\n');
519 const sal_Int32 nLastEndLine = sMsgCtxt.lastIndexOf('\n');
520 const sal_Int32 nLastDot = sMsgCtxt.lastIndexOf('.');
521 const OString sType = sMsgCtxt.copy( nLastDot + 1 );
522 return !rEntry.getReference().isEmpty() &&
523 nFirstEndLine > 0 &&
524 (nLastEndLine == nFirstEndLine || nLastEndLine == sMsgCtxt.indexOf('\n',nFirstEndLine+1)) &&
525 nLastDot - nLastEndLine > 1 &&
526 (sType == "text" || sType == "quickhelptext" || sType == "title")&&
527 !rEntry.getMsgId().isEmpty();
532 PoIfstream::PoIfstream()
533 : m_aInPut()
534 , m_bEof( false )
538 PoIfstream::PoIfstream(const OString& rFileName)
539 : m_aInPut()
540 , m_bEof( false )
542 open( rFileName );
545 PoIfstream::~PoIfstream()
547 if( isOpen() )
549 close();
553 void PoIfstream::open( const OString& rFileName )
555 assert( !isOpen() );
556 m_aInPut.open( rFileName.getStr(), std::ios_base::in );
558 // Skip header
559 std::string sTemp;
560 std::getline(m_aInPut,sTemp);
561 while( !sTemp.empty() && !m_aInPut.eof() )
563 std::getline(m_aInPut,sTemp);
565 m_bEof = false;
568 void PoIfstream::close()
570 assert( isOpen() );
571 m_aInPut.close();
574 void PoIfstream::readEntry( PoEntry& rPoEntry )
576 assert( isOpen() && !eof() );
577 GenPoEntry aGenPo;
578 aGenPo.readFromFile( m_aInPut );
579 if( aGenPo.isNull() )
581 m_bEof = true;
582 rPoEntry = PoEntry();
584 else
586 if( lcl_CheckInputEntry(aGenPo) )
588 if( rPoEntry.m_pGenPo )
590 *(rPoEntry.m_pGenPo) = aGenPo;
592 else
594 rPoEntry.m_pGenPo = new GenPoEntry( aGenPo );
596 rPoEntry.m_bIsInitialized = true;
598 else
600 throw INVALIDENTRY;
605 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */