nss: upgrade to release 3.73
[LibreOffice.git] / l10ntools / source / po.cxx
blob3d001f28e3f4015528a7e54e8b20d4c8ed06ba9d
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>
11 #include <rtl/crc.h>
12 #include <sal/log.hxx>
14 #include <cstring>
15 #include <ctime>
16 #include <cassert>
18 #include <vector>
19 #include <string>
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:
32 OStringBuffer m_sExtractCom;
33 std::vector<OString> m_sReferences;
34 OString m_sMsgCtxt;
35 OString m_sMsgId;
36 OString m_sMsgIdPlural;
37 OString m_sMsgStr;
38 std::vector<OString> m_sMsgStrPlural;
39 bool m_bFuzzy;
40 bool m_bCFormat;
41 bool m_bNull;
43 public:
44 GenPoEntry();
46 const std::vector<OString>& getReference() const { return m_sReferences; }
47 const OString& getMsgCtxt() const { return m_sMsgCtxt; }
48 const OString& getMsgId() const { return m_sMsgId; }
49 const OString& getMsgStr() const { return m_sMsgStr; }
50 bool isFuzzy() const { return m_bFuzzy; }
51 bool isNull() const { return m_bNull; }
53 void setExtractCom(const OString& rExtractCom)
55 m_sExtractCom = rExtractCom;
57 void setReference(const OString& rReference)
59 m_sReferences.push_back(rReference);
61 void setMsgCtxt(const OString& rMsgCtxt)
63 m_sMsgCtxt = rMsgCtxt;
65 void setMsgId(const OString& rMsgId)
67 m_sMsgId = rMsgId;
69 void setMsgStr(const OString& rMsgStr)
71 m_sMsgStr = rMsgStr;
74 void writeToFile(std::ofstream& rOFStream) const;
75 void readFromFile(std::ifstream& rIFStream);
78 namespace
80 // Convert a normal string to msg/po output string
81 OString lcl_GenMsgString(const OString& rString)
83 if ( rString.isEmpty() )
84 return "\"\"";
86 OString sResult =
87 "\"" +
88 helper::escapeAll(rString,"\n""\t""\r""\\""\"","\\n""\\t""\\r""\\\\""\\\"") +
89 "\"";
90 sal_Int32 nIndex = 0;
91 while((nIndex=sResult.indexOf("\\n",nIndex))!=-1)
93 if( !sResult.match("\\\\n", nIndex-1) &&
94 nIndex!=sResult.getLength()-3)
96 sResult = sResult.replaceAt(nIndex,2,"\\n\"\n\"");
98 ++nIndex;
101 if ( sResult.indexOf('\n') != -1 )
102 return "\"\"\n" + sResult;
104 return sResult;
107 // Convert msg string to normal form
108 OString lcl_GenNormString(const OString& rString)
110 return
111 helper::unEscapeAll(
112 rString.copy(1,rString.getLength()-2),
113 "\\n""\\t""\\r""\\\\""\\\"",
114 "\n""\t""\r""\\""\"");
118 GenPoEntry::GenPoEntry()
119 : m_sExtractCom( OString() )
120 , m_sReferences( std::vector<OString>() )
121 , m_sMsgCtxt( OString() )
122 , m_sMsgId( OString() )
123 , m_sMsgIdPlural( OString() )
124 , m_sMsgStr( OString() )
125 , m_sMsgStrPlural( std::vector<OString>() )
126 , m_bFuzzy( false )
127 , m_bCFormat( false )
128 , m_bNull( false )
132 void GenPoEntry::writeToFile(std::ofstream& rOFStream) const
134 if ( rOFStream.tellp() != std::ofstream::pos_type( 0 ))
135 rOFStream << std::endl;
136 if ( !m_sExtractCom.isEmpty() )
137 rOFStream
138 << "#. "
139 << m_sExtractCom.toString().replaceAll("\n","\n#. ") << std::endl;
140 for(const auto& rReference : m_sReferences)
141 rOFStream << "#: " << rReference << std::endl;
142 if ( m_bFuzzy )
143 rOFStream << "#, fuzzy" << std::endl;
144 if ( m_bCFormat )
145 rOFStream << "#, c-format" << std::endl;
146 if ( !m_sMsgCtxt.isEmpty() )
147 rOFStream << "msgctxt "
148 << lcl_GenMsgString(m_sMsgCtxt)
149 << std::endl;
150 rOFStream << "msgid "
151 << lcl_GenMsgString(m_sMsgId) << std::endl;
152 if ( !m_sMsgIdPlural.isEmpty() )
153 rOFStream << "msgid_plural "
154 << lcl_GenMsgString(m_sMsgIdPlural)
155 << std::endl;
156 if ( !m_sMsgStrPlural.empty() )
157 for(auto & line : m_sMsgStrPlural)
158 rOFStream << line.copy(0,10) << lcl_GenMsgString(line.copy(10)) << std::endl;
159 else
160 rOFStream << "msgstr "
161 << lcl_GenMsgString(m_sMsgStr) << std::endl;
164 void GenPoEntry::readFromFile(std::ifstream& rIFStream)
166 *this = GenPoEntry();
167 OString* pLastMsg = nullptr;
168 std::string sTemp;
169 getline(rIFStream,sTemp);
170 if( rIFStream.eof() || sTemp.empty() )
172 m_bNull = true;
173 return;
175 while(!rIFStream.eof())
177 OString sLine(sTemp.data(),sTemp.length());
178 if (sLine.startsWith("#. "))
180 if( !m_sExtractCom.isEmpty() )
182 m_sExtractCom.append("\n");
184 m_sExtractCom.append(sLine.copy(3));
186 else if (sLine.startsWith("#: "))
188 m_sReferences.push_back(sLine.copy(3));
190 else if (sLine.startsWith("#, fuzzy"))
192 m_bFuzzy = true;
194 else if (sLine.startsWith("#, c-format"))
196 m_bCFormat = true;
198 else if (sLine.startsWith("msgctxt "))
200 m_sMsgCtxt = lcl_GenNormString(sLine.copy(8));
201 pLastMsg = &m_sMsgCtxt;
203 else if (sLine.startsWith("msgid "))
205 m_sMsgId = lcl_GenNormString(sLine.copy(6));
206 pLastMsg = &m_sMsgId;
208 else if (sLine.startsWith("msgid_plural "))
210 m_sMsgIdPlural = lcl_GenNormString(sLine.copy(13));
211 pLastMsg = &m_sMsgIdPlural;
213 else if (sLine.startsWith("msgstr "))
215 m_sMsgStr = lcl_GenNormString(sLine.copy(7));
216 pLastMsg = &m_sMsgStr;
218 else if (sLine.startsWith("msgstr["))
220 // assume there are no more than 10 plural forms...
221 // and that plural strings are never split to multi-line in po
222 m_sMsgStrPlural.push_back(sLine.subView(0,10) + lcl_GenNormString(sLine.copy(10)));
224 else if (sLine.startsWith("\"") && pLastMsg)
226 OString sReference;
227 if (!m_sReferences.empty())
229 sReference = m_sReferences.front();
231 if (pLastMsg != &m_sMsgCtxt || sLine != "\"" + sReference + "\\n\"")
233 *pLastMsg += lcl_GenNormString(sLine);
236 else
237 break;
238 getline(rIFStream,sTemp);
242 PoEntry::PoEntry()
243 : m_bIsInitialized( false )
247 PoEntry::PoEntry(
248 const OString& rSourceFile, const OString& rResType, const OString& rGroupId,
249 const OString& rLocalId, const OString& rHelpText,
250 const OString& rText, const TYPE eType )
251 : m_bIsInitialized( false )
253 if( rSourceFile.isEmpty() )
254 throw NOSOURCFILE;
255 else if ( rResType.isEmpty() )
256 throw NORESTYPE;
257 else if ( rGroupId.isEmpty() )
258 throw NOGROUPID;
259 else if ( rText.isEmpty() )
260 throw NOSTRING;
261 else if ( rHelpText.getLength() == 5 )
262 throw WRONGHELPTEXT;
264 m_pGenPo.reset( new GenPoEntry() );
265 OString sReference = rSourceFile.copy(rSourceFile.lastIndexOf('/')+1);
266 m_pGenPo->setReference(sReference);
268 OString sMsgCtxt =
269 sReference + "\n" +
270 rGroupId + "\n" +
271 (rLocalId.isEmpty() ? OString() : rLocalId + "\n") +
272 rResType;
273 switch(eType){
274 case TTEXT:
275 sMsgCtxt += ".text"; break;
276 case TQUICKHELPTEXT:
277 sMsgCtxt += ".quickhelptext"; break;
278 case TTITLE:
279 sMsgCtxt += ".title"; break;
280 // Default case is unneeded because the type of eType has only three element
282 m_pGenPo->setMsgCtxt(sMsgCtxt);
283 m_pGenPo->setMsgId(rText);
284 m_pGenPo->setExtractCom(
285 ( !rHelpText.isEmpty() ? rHelpText + "\n" : OString()) +
286 genKeyId( m_pGenPo->getReference().front() + rGroupId + rLocalId + rResType + rText ) );
287 m_bIsInitialized = true;
290 PoEntry::~PoEntry()
294 PoEntry::PoEntry( const PoEntry& rPo )
295 : m_pGenPo( rPo.m_pGenPo ? new GenPoEntry( *(rPo.m_pGenPo) ) : nullptr )
296 , m_bIsInitialized( rPo.m_bIsInitialized )
300 PoEntry& PoEntry::operator=(const PoEntry& rPo)
302 if( this == &rPo )
304 return *this;
306 if( rPo.m_pGenPo )
308 if( m_pGenPo )
310 *m_pGenPo = *(rPo.m_pGenPo);
312 else
314 m_pGenPo.reset( new GenPoEntry( *(rPo.m_pGenPo) ) );
317 else
319 m_pGenPo.reset();
321 m_bIsInitialized = rPo.m_bIsInitialized;
322 return *this;
325 PoEntry& PoEntry::operator=(PoEntry&& rPo) noexcept
327 m_pGenPo = std::move(rPo.m_pGenPo);
328 m_bIsInitialized = std::move(rPo.m_bIsInitialized);
329 return *this;
332 OString const & PoEntry::getSourceFile() const
334 assert( m_bIsInitialized );
335 return m_pGenPo->getReference().front();
338 OString PoEntry::getGroupId() const
340 assert( m_bIsInitialized );
341 return m_pGenPo->getMsgCtxt().getToken(0,'\n');
344 OString PoEntry::getLocalId() const
346 assert( m_bIsInitialized );
347 const OString sMsgCtxt = m_pGenPo->getMsgCtxt();
348 if (sMsgCtxt.indexOf('\n')==sMsgCtxt.lastIndexOf('\n'))
349 return OString();
350 else
351 return sMsgCtxt.getToken(1,'\n');
354 OString PoEntry::getResourceType() const
356 assert( m_bIsInitialized );
357 const OString sMsgCtxt = m_pGenPo->getMsgCtxt();
358 if (sMsgCtxt.indexOf('\n')==sMsgCtxt.lastIndexOf('\n'))
359 return sMsgCtxt.getToken(1,'\n').getToken(0,'.');
360 else
361 return sMsgCtxt.getToken(2,'\n').getToken(0,'.');
364 PoEntry::TYPE PoEntry::getType() const
366 assert( m_bIsInitialized );
367 const OString sMsgCtxt = m_pGenPo->getMsgCtxt();
368 const OString sType = sMsgCtxt.copy( sMsgCtxt.lastIndexOf('.') + 1 );
369 assert(
370 (sType == "text" || sType == "quickhelptext" || sType == "title") );
371 if ( sType == "text" )
372 return TTEXT;
373 else if ( sType == "quickhelptext" )
374 return TQUICKHELPTEXT;
375 else
376 return TTITLE;
379 bool PoEntry::isFuzzy() const
381 assert( m_bIsInitialized );
382 return m_pGenPo->isFuzzy();
385 // Get message context
386 const OString& PoEntry::getMsgCtxt() const
388 assert( m_bIsInitialized );
389 return m_pGenPo->getMsgCtxt();
393 // Get translation string in merge format
394 OString const & PoEntry::getMsgId() const
396 assert( m_bIsInitialized );
397 return m_pGenPo->getMsgId();
400 // Get translated string in merge format
401 const OString& PoEntry::getMsgStr() const
403 assert( m_bIsInitialized );
404 return m_pGenPo->getMsgStr();
408 bool PoEntry::IsInSameComp(const PoEntry& rPo1,const PoEntry& rPo2)
410 assert( rPo1.m_bIsInitialized && rPo2.m_bIsInitialized );
411 return ( rPo1.getSourceFile() == rPo2.getSourceFile() &&
412 rPo1.getGroupId() == rPo2.getGroupId() &&
413 rPo1.getLocalId() == rPo2.getLocalId() &&
414 rPo1.getResourceType() == rPo2.getResourceType() );
417 OString PoEntry::genKeyId(const OString& rGenerator)
419 sal_uInt32 nCRC = rtl_crc32(0, rGenerator.getStr(), rGenerator.getLength());
420 // Use simple ASCII characters, exclude I, l, 1 and O, 0 to avoid confusing IDs
421 static const char sSymbols[] =
422 "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789";
423 char sKeyId[6];
424 for( short nKeyInd = 0; nKeyInd < 5; ++nKeyInd )
426 sKeyId[nKeyInd] = sSymbols[(nCRC & 63) % strlen(sSymbols)];
427 nCRC >>= 6;
429 sKeyId[5] = '\0';
430 return sKeyId;
433 namespace
435 // Get actual time in "YEAR-MO-DA HO:MI+ZONE" form
436 OString lcl_GetTime()
438 time_t aNow = time(nullptr);
439 struct tm* pNow = localtime(&aNow);
440 char pBuff[50];
441 strftime( pBuff, sizeof pBuff, "%Y-%m-%d %H:%M%z", pNow );
442 return pBuff;
446 // when updating existing files (pocheck), reuse provided po-header
447 PoHeader::PoHeader( const OString& rExtSrc, const OString& rPoHeaderMsgStr )
448 : m_pGenPo( new GenPoEntry() )
449 , m_bIsInitialized( false )
451 m_pGenPo->setExtractCom("extracted from " + rExtSrc);
452 m_pGenPo->setMsgStr(rPoHeaderMsgStr);
453 m_bIsInitialized = true;
456 PoHeader::PoHeader( const OString& rExtSrc )
457 : m_pGenPo( new GenPoEntry() )
458 , m_bIsInitialized( false )
460 m_pGenPo->setExtractCom("extracted from " + rExtSrc);
461 m_pGenPo->setMsgStr(
462 "Project-Id-Version: PACKAGE VERSION\n"
463 "Report-Msgid-Bugs-To: https://bugs.libreoffice.org/enter_bug.cgi?"
464 "product=LibreOffice&bug_status=UNCONFIRMED&component=UI\n"
465 "POT-Creation-Date: " + lcl_GetTime() +
466 "\nPO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
467 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
468 "Language-Team: LANGUAGE <LL@li.org>\n"
469 "MIME-Version: 1.0\n"
470 "Content-Type: text/plain; charset=UTF-8\n"
471 "Content-Transfer-Encoding: 8bit\n"
472 "X-Accelerator-Marker: ~\n"
473 "X-Generator: LibreOffice\n");
474 m_bIsInitialized = true;
477 PoHeader::~PoHeader()
481 PoOfstream::PoOfstream()
482 : m_aOutPut()
483 , m_bIsAfterHeader( false )
487 PoOfstream::PoOfstream(const OString& rFileName, OpenMode aMode )
488 : m_aOutPut()
489 , m_bIsAfterHeader( false )
491 open( rFileName, aMode );
494 PoOfstream::~PoOfstream()
496 if( isOpen() )
498 close();
502 void PoOfstream::open(const OString& rFileName, OpenMode aMode )
504 assert( !isOpen() );
505 if( aMode == TRUNC )
507 m_aOutPut.open( rFileName.getStr(),
508 std::ios_base::out | std::ios_base::trunc );
509 m_bIsAfterHeader = false;
511 else if( aMode == APP )
513 m_aOutPut.open( rFileName.getStr(),
514 std::ios_base::out | std::ios_base::app );
515 m_bIsAfterHeader = m_aOutPut.tellp() != std::ofstream::pos_type( 0 );
519 void PoOfstream::close()
521 assert( isOpen() );
522 m_aOutPut.close();
525 void PoOfstream::writeHeader(const PoHeader& rPoHeader)
527 assert( isOpen() && !m_bIsAfterHeader && rPoHeader.m_bIsInitialized );
528 rPoHeader.m_pGenPo->writeToFile( m_aOutPut );
529 m_bIsAfterHeader = true;
532 void PoOfstream::writeEntry( const PoEntry& rPoEntry )
534 assert( isOpen() && m_bIsAfterHeader && rPoEntry.m_bIsInitialized );
535 rPoEntry.m_pGenPo->writeToFile( m_aOutPut );
538 namespace
541 // Check the validity of read entry
542 bool lcl_CheckInputEntry(const GenPoEntry& rEntry)
544 return !rEntry.getReference().empty() &&
545 !rEntry.getMsgCtxt().isEmpty() &&
546 !rEntry.getMsgId().isEmpty();
551 PoIfstream::PoIfstream()
552 : m_aInPut()
553 , m_bEof( false )
557 PoIfstream::PoIfstream(const OString& rFileName)
558 : m_aInPut()
559 , m_bEof( false )
561 open( rFileName );
564 PoIfstream::~PoIfstream()
566 if( isOpen() )
568 close();
572 void PoIfstream::open( const OString& rFileName, OString& rPoHeader )
574 assert( !isOpen() );
575 m_aInPut.open( rFileName.getStr(), std::ios_base::in );
577 // capture header, updating timestamp and generator
578 std::string sTemp;
579 std::getline(m_aInPut,sTemp);
580 while( !sTemp.empty() && !m_aInPut.eof() )
582 std::getline(m_aInPut,sTemp);
583 OString sLine(sTemp.data(),sTemp.length());
584 if (sLine.startsWith("\"PO-Revision-Date"))
585 rPoHeader += "PO-Revision-Date: " + lcl_GetTime() + "\n";
586 else if (sLine.startsWith("\"X-Generator"))
587 rPoHeader += "X-Generator: LibreOffice\n";
588 else if (sLine.startsWith("\""))
589 rPoHeader += lcl_GenNormString(sLine);
591 m_bEof = false;
594 void PoIfstream::open( const OString& rFileName )
596 assert( !isOpen() );
597 m_aInPut.open( rFileName.getStr(), std::ios_base::in );
599 // Skip header
600 std::string sTemp;
601 std::getline(m_aInPut,sTemp);
602 while( !sTemp.empty() && !m_aInPut.eof() )
604 std::getline(m_aInPut,sTemp);
606 m_bEof = false;
609 void PoIfstream::close()
611 assert( isOpen() );
612 m_aInPut.close();
615 void PoIfstream::readEntry( PoEntry& rPoEntry )
617 assert( isOpen() && !eof() );
618 GenPoEntry aGenPo;
619 aGenPo.readFromFile( m_aInPut );
620 if( aGenPo.isNull() )
622 m_bEof = true;
623 rPoEntry = PoEntry();
625 else
627 if( lcl_CheckInputEntry(aGenPo) )
629 if( rPoEntry.m_pGenPo )
631 *(rPoEntry.m_pGenPo) = aGenPo;
633 else
635 rPoEntry.m_pGenPo.reset( new GenPoEntry( aGenPo ) );
637 rPoEntry.m_bIsInitialized = true;
639 else
641 SAL_WARN("l10ntools", "Parse problem with entry: " << aGenPo.getMsgStr());
642 throw PoIfstream::Exception();
647 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */