Merge pull request #4676 from jmarshallnz/dont_set_scraper_on_tvshow_on_nfo
[vuplus_xbmc] / xbmc / utils / POUtils.cpp
1 /*
2  *      Copyright (C) 2012-2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "utils/POUtils.h"
22 #include "filesystem/File.h"
23 #include "utils/log.h"
24 #include <stdlib.h>
25
26 CPODocument::CPODocument()
27 {
28   m_CursorPos = 0;
29   m_nextEntryPos = 0;
30   m_POfilelength = 0;
31   m_Entry.msgStrPlural.clear();
32   m_Entry.msgStrPlural.resize(1);
33 }
34
35 CPODocument::~CPODocument() {}
36
37 bool CPODocument::LoadFile(const std::string &pofilename)
38 {
39   XFILE::CFile file;
40   if (!file.Open(pofilename))
41     return false;
42
43   int64_t fileLength = file.GetLength();
44   if (fileLength < 18) // at least a size of a minimalistic header
45   {
46     file.Close();
47     CLog::Log(LOGERROR, "POParser: non valid length found for string file: %s", pofilename.c_str());
48     return false;
49   }
50
51   m_POfilelength = static_cast<size_t> (fileLength);
52
53   m_strBuffer.resize(m_POfilelength+1);
54   m_strBuffer[0] = '\n';
55
56   unsigned int readBytes = file.Read(&m_strBuffer[1], m_POfilelength);
57   file.Close();
58
59   if (readBytes != m_POfilelength)
60   {
61     CLog::Log(LOGERROR, "POParser: actual read data differs from file size, for string file: %s",
62               pofilename.c_str());
63     return false;
64   }
65
66   ConvertLineEnds(pofilename);
67
68   // we make sure, to have an LF at the end of buffer
69   if (*m_strBuffer.rbegin() != '\n')
70   {
71     m_strBuffer += "\n";
72   }
73
74   m_POfilelength = m_strBuffer.size();
75
76   if (GetNextEntry() && m_Entry.Type == MSGID_FOUND)
77     return true;
78
79   CLog::Log(LOGERROR, "POParser: unable to read PO file header from file: %s", pofilename.c_str());
80   return false;
81 }
82
83 bool CPODocument::GetNextEntry()
84 {
85   do
86   {
87     // if we don't find LFLF, we reached the end of the buffer and the last entry to check
88     // we indicate this with setting m_nextEntryPos to the end of the buffer
89     if ((m_nextEntryPos = m_strBuffer.find("\n\n", m_CursorPos)) == std::string::npos)
90       m_nextEntryPos = m_POfilelength-1;
91
92     // now we read the actual entry into a temp string for further processing
93     m_Entry.Content.assign(m_strBuffer, m_CursorPos, m_nextEntryPos - m_CursorPos +1);
94     m_CursorPos = m_nextEntryPos+1; // jump cursor to the second LF character
95
96     if (FindLineStart ("\nmsgid ", m_Entry.msgID.Pos))
97     {
98       if (FindLineStart ("\nmsgctxt \"#", m_Entry.xIDPos) && ParseNumID())
99       {
100         m_Entry.Type = ID_FOUND; // we found an entry with a valid numeric id
101         return true;
102       }
103
104       size_t plurPos;
105       if (FindLineStart ("\nmsgid_plural ", plurPos))
106       {
107         m_Entry.Type = MSGID_PLURAL_FOUND; // we found a pluralized entry
108         return true;
109       }
110
111       m_Entry.Type = MSGID_FOUND; // we found a normal entry, with no numeric id
112       return true;
113     }
114   }
115   while (m_nextEntryPos != m_POfilelength-1);
116   // we reached the end of buffer AND we have not found a valid entry
117
118   return false;
119 }
120
121 void CPODocument::ParseEntry(bool bisSourceLang)
122 {
123   if (bisSourceLang)
124   {
125     if (m_Entry.Type == ID_FOUND)
126       GetString(m_Entry.msgID);
127     else
128       m_Entry.msgID.Str.clear();
129     return;
130   }
131
132   if (m_Entry.Type != ID_FOUND)
133   {
134     GetString(m_Entry.msgID);
135     if (FindLineStart ("\nmsgctxt ", m_Entry.msgCtxt.Pos))
136       GetString(m_Entry.msgCtxt);
137     else
138       m_Entry.msgCtxt.Str.clear();
139   }
140
141   if (m_Entry.Type != MSGID_PLURAL_FOUND)
142   {
143     if (FindLineStart ("\nmsgstr ", m_Entry.msgStr.Pos))
144     {
145       GetString(m_Entry.msgStr);
146       GetString(m_Entry.msgID);
147     }
148     else
149     {
150       CLog::Log(LOGERROR, "POParser: missing msgstr line in entry. Failed entry: %s",
151                 m_Entry.Content.c_str());
152       m_Entry.msgStr.Str.clear();
153     }
154     return;
155   }
156
157   // We found a plural form entry. We read it into a vector of CStrEntry types
158   m_Entry.msgStrPlural.clear();
159   std::string strPattern = "\nmsgstr[0] ";
160   CStrEntry strEntry;
161
162   for (int n=0; n<7 ; n++)
163   {
164     strPattern[8] = static_cast<char>(n+'0');
165     if (FindLineStart (strPattern, strEntry.Pos))
166     {
167       GetString(strEntry);
168       if (strEntry.Str.empty())
169         break;
170       m_Entry.msgStrPlural.push_back(strEntry);
171     }
172     else
173       break;
174   }
175
176   if (m_Entry.msgStrPlural.size() == 0)
177   {
178     CLog::Log(LOGERROR, "POParser: msgstr[] plural lines have zero valid strings. "
179                         "Failed entry: %s", m_Entry.Content.c_str());
180     m_Entry.msgStrPlural.resize(1); // Put 1 element with an empty string into the vector
181   }
182
183   return;
184 }
185
186 const std::string& CPODocument::GetPlurMsgstr(size_t plural) const
187 {
188   if (m_Entry.msgStrPlural.size() < plural+1)
189   {
190     CLog::Log(LOGERROR, "POParser: msgstr[%i] plural field requested, but not found in PO file. "
191                         "Failed entry: %s", static_cast<int>(plural), m_Entry.Content.c_str());
192     plural = m_Entry.msgStrPlural.size()-1;
193   }
194   return m_Entry.msgStrPlural[plural].Str;
195 }
196
197 std::string CPODocument::UnescapeString(const std::string &strInput)
198 {
199   std::string strOutput;
200   if (strInput.empty())
201     return strOutput;
202
203   char oescchar;
204   strOutput.reserve(strInput.size());
205   std::string::const_iterator it = strInput.begin();
206   while (it < strInput.end())
207   {
208     oescchar = *it++;
209     if (oescchar == '\\')
210     {
211       if (it == strInput.end())
212       {
213         CLog::Log(LOGERROR,
214                   "POParser: warning, unhandled escape character "
215                   "at line-end. Problematic entry: %s",
216                   m_Entry.Content.c_str());
217         break;
218       }
219       switch (*it++)
220       {
221         case 'a':  oescchar = '\a'; break;
222         case 'b':  oescchar = '\b'; break;
223         case 'v':  oescchar = '\v'; break;
224         case 'n':  oescchar = '\n'; break;
225         case 't':  oescchar = '\t'; break;
226         case 'r':  oescchar = '\r'; break;
227         case '"':  oescchar = '"' ; break;
228         case '0':  oescchar = '\0'; break;
229         case 'f':  oescchar = '\f'; break;
230         case '?':  oescchar = '\?'; break;
231         case '\'': oescchar = '\''; break;
232         case '\\': oescchar = '\\'; break;
233
234         default: 
235         {
236           CLog::Log(LOGERROR,
237                     "POParser: warning, unhandled escape character. Problematic entry: %s",
238                     m_Entry.Content.c_str());
239           continue;
240         }
241       }
242     }
243     strOutput.push_back(oescchar);
244   }
245   return strOutput;
246 }
247
248 bool CPODocument::FindLineStart(const std::string &strToFind, size_t &FoundPos)
249 {
250
251   FoundPos = m_Entry.Content.find(strToFind);
252
253   if (FoundPos == std::string::npos || FoundPos + strToFind.size() + 2 > m_Entry.Content.size())
254     return false; // if we don't find the string or if we don't have at least one char after it
255
256   FoundPos += strToFind.size(); // to set the pos marker to the exact start of the real data
257   return true;
258 }
259
260 bool CPODocument::ParseNumID()
261 {
262   if (isdigit(m_Entry.Content.at(m_Entry.xIDPos))) // verify if the first char is digit
263   {
264     // we check for the numeric id for the fist 10 chars (uint32)
265     m_Entry.xID = strtol(&m_Entry.Content[m_Entry.xIDPos], NULL, 10);
266     return true;
267   }
268
269   CLog::Log(LOGERROR, "POParser: found numeric id descriptor, but no valid id can be read, "
270                       "entry was handled as normal msgid entry");
271   CLog::Log(LOGERROR, "POParser: The problematic entry: %s",
272             m_Entry.Content.c_str());
273   return false;
274 }
275
276 void CPODocument::GetString(CStrEntry &strEntry)
277 {
278   size_t nextLFPos;
279   size_t startPos = strEntry.Pos;
280   strEntry.Str.clear();
281
282   while (startPos < m_Entry.Content.size())
283   {
284     nextLFPos = m_Entry.Content.find("\n", startPos);
285     if (nextLFPos == std::string::npos)
286       nextLFPos = m_Entry.Content.size();
287
288     // check syntax, if it really is a valid quoted string line
289     if (nextLFPos-startPos < 2 ||  m_Entry.Content[startPos] != '\"' ||
290         m_Entry.Content[nextLFPos-1] != '\"')
291       break;
292
293     strEntry.Str.append(m_Entry.Content, startPos+1, nextLFPos-2-startPos);
294     startPos = nextLFPos+1;
295   }
296
297   strEntry.Str = UnescapeString(strEntry.Str);
298 }
299
300 void CPODocument::ConvertLineEnds(const std::string &filename)
301 {
302   size_t foundPos = m_strBuffer.find_first_of("\r");
303   if (foundPos == std::string::npos)
304     return; // We have only Linux style line endings in the file, nothing to do
305
306   if (foundPos+1 >= m_strBuffer.size() || m_strBuffer[foundPos+1] != '\n')
307     CLog::Log(LOGDEBUG, "POParser: PO file has Mac Style Line Endings. "
308               "Converted in memory to Linux LF for file: %s", filename.c_str());
309   else
310     CLog::Log(LOGDEBUG, "POParser: PO file has Win Style Line Endings. "
311               "Converted in memory to Linux LF for file: %s", filename.c_str());
312
313   std::string strTemp;
314   strTemp.reserve(m_strBuffer.size());
315   for (std::string::const_iterator it = m_strBuffer.begin(); it < m_strBuffer.end(); it++)
316   {
317     if (*it == '\r')
318     {
319       if (it+1 == m_strBuffer.end() || *(it+1) != '\n')
320         strTemp.push_back('\n'); // convert Mac style line ending and continue
321       continue; // we have Win style line ending so we exclude this CR now
322     }
323     strTemp.push_back(*it);
324   }
325   m_strBuffer.swap(strTemp);
326   m_POfilelength = m_strBuffer.size();
327 }