1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
10 #include <rtl/ustring.hxx>
12 #include <sal/log.hxx>
24 /** Container of po entry
26 Provide all file operations related to LibreOffice specific
27 po entry and store it's attributes.
32 OStringBuffer m_sExtractCom
;
33 std::vector
<OString
> m_sReferences
;
36 OString m_sMsgIdPlural
;
38 std::vector
<OString
> m_sMsgStrPlural
;
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
)
69 void setMsgStr(const OString
& rMsgStr
)
74 void writeToFile(std::ofstream
& rOFStream
) const;
75 void readFromFile(std::ifstream
& rIFStream
);
80 // Convert a normal string to msg/po output string
81 OString
lcl_GenMsgString(const OString
& rString
)
83 if ( rString
.isEmpty() )
88 helper::escapeAll(rString
,"\n""\t""\r""\\""\"","\\n""\\t""\\r""\\\\""\\\"") +
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\"");
101 if ( sResult
.indexOf('\n') != -1 )
102 return "\"\"\n" + sResult
;
107 // Convert msg string to normal form
108 OString
lcl_GenNormString(const OString
& rString
)
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
>() )
127 , m_bCFormat( 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() )
139 << m_sExtractCom
.toString().replaceAll("\n","\n#. ") << std::endl
;
140 for(const auto& rReference
: m_sReferences
)
141 rOFStream
<< "#: " << rReference
<< std::endl
;
143 rOFStream
<< "#, fuzzy" << std::endl
;
145 rOFStream
<< "#, c-format" << std::endl
;
146 if ( !m_sMsgCtxt
.isEmpty() )
147 rOFStream
<< "msgctxt "
148 << lcl_GenMsgString(m_sMsgCtxt
)
150 rOFStream
<< "msgid "
151 << lcl_GenMsgString(m_sMsgId
) << std::endl
;
152 if ( !m_sMsgIdPlural
.isEmpty() )
153 rOFStream
<< "msgid_plural "
154 << lcl_GenMsgString(m_sMsgIdPlural
)
156 if ( !m_sMsgStrPlural
.empty() )
157 for(auto & line
: m_sMsgStrPlural
)
158 rOFStream
<< line
.copy(0,10) << lcl_GenMsgString(line
.copy(10)) << std::endl
;
160 rOFStream
<< "msgstr "
161 << lcl_GenMsgString(m_sMsgStr
) << std::endl
;
164 void GenPoEntry::readFromFile(std::ifstream
& rIFStream
)
166 *this = GenPoEntry();
167 OString
* pLastMsg
= nullptr;
169 getline(rIFStream
,sTemp
);
170 if( rIFStream
.eof() || sTemp
.empty() )
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"))
194 else if (sLine
.startsWith("#, c-format"))
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
)
227 if (!m_sReferences
.empty())
229 sReference
= m_sReferences
.front();
231 if (pLastMsg
!= &m_sMsgCtxt
|| sLine
!= "\"" + sReference
+ "\\n\"")
233 *pLastMsg
+= lcl_GenNormString(sLine
);
238 getline(rIFStream
,sTemp
);
243 : m_bIsInitialized( false )
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() )
255 else if ( rResType
.isEmpty() )
257 else if ( rGroupId
.isEmpty() )
259 else if ( rText
.isEmpty() )
261 else if ( rHelpText
.getLength() == 5 )
264 m_pGenPo
.reset( new GenPoEntry() );
265 OString sReference
= rSourceFile
.copy(rSourceFile
.lastIndexOf('/')+1);
266 m_pGenPo
->setReference(sReference
);
271 (rLocalId
.isEmpty() ? OString() : rLocalId
+ "\n") +
275 sMsgCtxt
+= ".text"; break;
277 sMsgCtxt
+= ".quickhelptext"; break;
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;
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
)
310 *m_pGenPo
= *(rPo
.m_pGenPo
);
314 m_pGenPo
.reset( new GenPoEntry( *(rPo
.m_pGenPo
) ) );
321 m_bIsInitialized
= rPo
.m_bIsInitialized
;
325 PoEntry
& PoEntry::operator=(PoEntry
&& rPo
) noexcept
327 m_pGenPo
= std::move(rPo
.m_pGenPo
);
328 m_bIsInitialized
= std::move(rPo
.m_bIsInitialized
);
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'))
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,'.');
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 );
370 (sType
== "text" || sType
== "quickhelptext" || sType
== "title") );
371 if ( sType
== "text" )
373 else if ( sType
== "quickhelptext" )
374 return TQUICKHELPTEXT
;
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";
424 for( short nKeyInd
= 0; nKeyInd
< 5; ++nKeyInd
)
426 sKeyId
[nKeyInd
] = sSymbols
[(nCRC
& 63) % strlen(sSymbols
)];
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
);
441 strftime( pBuff
, sizeof pBuff
, "%Y-%m-%d %H:%M%z", pNow
);
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
);
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()
483 , m_bIsAfterHeader( false )
487 PoOfstream::PoOfstream(const OString
& rFileName
, OpenMode aMode
)
489 , m_bIsAfterHeader( false )
491 open( rFileName
, aMode
);
494 PoOfstream::~PoOfstream()
502 void PoOfstream::open(const OString
& rFileName
, OpenMode aMode
)
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()
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
);
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()
557 PoIfstream::PoIfstream(const OString
& rFileName
)
564 PoIfstream::~PoIfstream()
572 void PoIfstream::open( const OString
& rFileName
, OString
& rPoHeader
)
575 m_aInPut
.open( rFileName
.getStr(), std::ios_base::in
);
577 // capture header, updating timestamp and generator
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
);
594 void PoIfstream::open( const OString
& rFileName
)
597 m_aInPut
.open( rFileName
.getStr(), std::ios_base::in
);
601 std::getline(m_aInPut
,sTemp
);
602 while( !sTemp
.empty() && !m_aInPut
.eof() )
604 std::getline(m_aInPut
,sTemp
);
609 void PoIfstream::close()
615 void PoIfstream::readEntry( PoEntry
& rPoEntry
)
617 assert( isOpen() && !eof() );
619 aGenPo
.readFromFile( m_aInPut
);
620 if( aGenPo
.isNull() )
623 rPoEntry
= PoEntry();
627 if( lcl_CheckInputEntry(aGenPo
) )
629 if( rPoEntry
.m_pGenPo
)
631 *(rPoEntry
.m_pGenPo
) = aGenPo
;
635 rPoEntry
.m_pGenPo
.reset( new GenPoEntry( aGenPo
) );
637 rPoEntry
.m_bIsInitialized
= true;
641 SAL_WARN("l10ntools", "Parse problem with entry: " << aGenPo
.getMsgStr());
642 throw PoIfstream::Exception();
647 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */