[cstdstring] demise Format, replacing with StringUtils::Format
[vuplus_xbmc] / xbmc / utils / RegExp.cpp
1 /*
2  *      Copyright (C) 2005-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 <stdlib.h>
22 #include <string.h>
23 #include <algorithm> 
24 #include "RegExp.h"
25 #include "StdString.h"
26 #include "log.h"
27 #include "utils/StringUtils.h"
28
29 using namespace PCRE;
30
31 #ifndef PCRE_UCP
32 #define PCRE_UCP 0
33 #endif // PCRE_UCP
34
35 #ifdef PCRE_CONFIG_JIT
36 #define PCRE_HAS_JIT_CODE 1
37 #endif
38
39 #ifndef PCRE_STUDY_JIT_COMPILE
40 #define PCRE_STUDY_JIT_COMPILE 0
41 #endif
42 #ifndef PCRE_INFO_JIT
43 // some unused number
44 #define PCRE_INFO_JIT 2048
45 #endif
46 #ifndef PCRE_HAS_JIT_CODE
47 #define pcre_free_study(x) pcre_free((x))
48 #endif
49
50 int CRegExp::m_Utf8Supported = -1;
51 int CRegExp::m_UcpSupported  = -1;
52 int CRegExp::m_JitSupported  = -1;
53
54
55 CRegExp::CRegExp(bool caseless /*= false*/, bool utf8 /*= false*/)
56 {
57   InitValues(caseless, utf8);
58 }
59
60 void CRegExp::InitValues(bool caseless /*= false*/, bool utf8 /*= false*/)
61 {
62   m_re          = NULL;
63   m_sd          = NULL;
64   m_iOptions    = PCRE_DOTALL | PCRE_NEWLINE_ANY;
65   if(caseless)
66     m_iOptions |= PCRE_CASELESS;
67   if (utf8)
68   {
69     if (IsUtf8Supported())
70       m_iOptions |= PCRE_UTF8;
71     if (AreUnicodePropertiesSupported())
72       m_iOptions |= PCRE_UCP;
73   }
74
75   m_offset      = 0;
76   m_jitCompiled = false;
77   m_bMatched    = false;
78   m_iMatchCount = 0;
79   m_jitStack    = NULL;
80
81   memset(m_iOvector, 0, sizeof(m_iOvector));
82 }
83
84 CRegExp::CRegExp(bool caseless, bool utf8, const char *re, studyMode study /*= NoStudy*/)
85 {
86   InitValues(caseless, utf8);
87   RegComp(re, study);
88 }
89
90 CRegExp::CRegExp(const CRegExp& re)
91 {
92   m_re = NULL;
93   m_sd = NULL;
94   m_jitStack = NULL;
95   m_iOptions = re.m_iOptions;
96   *this = re;
97 }
98
99 const CRegExp& CRegExp::operator=(const CRegExp& re)
100 {
101   size_t size;
102   Cleanup();
103   m_jitCompiled = false;
104   m_pattern = re.m_pattern;
105   if (re.m_re)
106   {
107     if (pcre_fullinfo(re.m_re, NULL, PCRE_INFO_SIZE, &size) >= 0)
108     {
109       if ((m_re = (pcre*)malloc(size)))
110       {
111         memcpy(m_re, re.m_re, size);
112         memcpy(m_iOvector, re.m_iOvector, OVECCOUNT*sizeof(int));
113         m_offset = re.m_offset;
114         m_iMatchCount = re.m_iMatchCount;
115         m_bMatched = re.m_bMatched;
116         m_subject = re.m_subject;
117         m_iOptions = re.m_iOptions;
118       }
119       else
120         CLog::Log(LOGSEVERE, "%s: Failed to allocate memory", __FUNCTION__);
121     }
122   }
123   return *this;
124 }
125
126 CRegExp::~CRegExp()
127 {
128   Cleanup();
129 }
130
131 bool CRegExp::RegComp(const char *re, studyMode study /*= NoStudy*/)
132 {
133   if (!re)
134     return false;
135
136   m_offset           = 0;
137   m_jitCompiled      = false;
138   m_bMatched         = false;
139   m_iMatchCount      = 0;
140   const char *errMsg = NULL;
141   int errOffset      = 0;
142
143   Cleanup();
144
145   m_re = pcre_compile(re, m_iOptions, &errMsg, &errOffset, NULL);
146   if (!m_re)
147   {
148     m_pattern.clear();
149     CLog::Log(LOGERROR, "PCRE: %s. Compilation failed at offset %d in expression '%s'",
150               errMsg, errOffset, re);
151     return false;
152   }
153
154   m_pattern = re;
155
156   if (study)
157   {
158     const bool jitCompile = (study == StudyWithJitComp) && IsJitSupported();
159     const int studyOptions = jitCompile ? PCRE_STUDY_JIT_COMPILE : 0;
160
161     m_sd = pcre_study(m_re, studyOptions, &errMsg);
162     if (errMsg != NULL)
163     {
164       CLog::Log(LOGWARNING, "%s: PCRE error \"%s\" while studying expression", __FUNCTION__, errMsg);
165       if (m_sd != NULL)
166       {
167         pcre_free_study(m_sd);
168         m_sd = NULL;
169       }
170     }
171     else if (jitCompile)
172     {
173       int jitPresent = 0;
174       m_jitCompiled = (pcre_fullinfo(m_re, m_sd, PCRE_INFO_JIT, &jitPresent) == 0 && jitPresent == 1);
175     }
176   }
177
178   return true;
179 }
180
181 int CRegExp::RegFind(const char *str, unsigned int startoffset /*= 0*/, int maxNumberOfCharsToTest /*= -1*/)
182 {
183   return PrivateRegFind(strlen(str), str, startoffset, maxNumberOfCharsToTest);
184 }
185
186 int CRegExp::PrivateRegFind(size_t bufferLen, const char *str, unsigned int startoffset /* = 0*/, int maxNumberOfCharsToTest /*= -1*/)
187 {
188   m_offset      = 0;
189   m_bMatched    = false;
190   m_iMatchCount = 0;
191
192   if (!m_re)
193   {
194     CLog::Log(LOGERROR, "PCRE: Called before compilation");
195     return -1;
196   }
197
198   if (!str)
199   {
200     CLog::Log(LOGERROR, "PCRE: Called without a string to match");
201     return -1;
202   } 
203
204   if (startoffset > bufferLen)
205   {
206     CLog::Log(LOGERROR, "%s: startoffset is beyond end of string to match", __FUNCTION__);
207     return -1;
208   }
209
210 #ifdef PCRE_HAS_JIT_CODE
211   if (m_jitCompiled && !m_jitStack)
212   {
213     m_jitStack = pcre_jit_stack_alloc(32*1024, 512*1024);
214     if (m_jitStack == NULL)
215       CLog::Log(LOGWARNING, "%s: can't allocate address space for JIT stack", __FUNCTION__);
216
217     pcre_assign_jit_stack(m_sd, NULL, m_jitStack);
218   }
219 #endif
220
221   if (maxNumberOfCharsToTest >= 0)
222     bufferLen = std::min<size_t>(bufferLen, startoffset + maxNumberOfCharsToTest);
223
224   m_subject.assign(str + startoffset, bufferLen - startoffset);
225   int rc = pcre_exec(m_re, NULL, m_subject.c_str(), m_subject.length(), 0, 0, m_iOvector, OVECCOUNT);
226
227   if (rc<1)
228   {
229     switch(rc)
230     {
231     case PCRE_ERROR_NOMATCH:
232       return -1;
233
234     case PCRE_ERROR_MATCHLIMIT:
235       CLog::Log(LOGERROR, "PCRE: Match limit reached");
236       return -1;
237
238 #ifdef PCRE_ERROR_SHORTUTF8 
239     case PCRE_ERROR_SHORTUTF8:
240 #endif
241     case PCRE_ERROR_BADUTF8:
242       CLog::Log(LOGERROR, "PCRE: Bad UTF-8 character");
243       return -1;
244
245     case PCRE_ERROR_BADUTF8_OFFSET:
246       CLog::Log(LOGERROR, "PCRE: Offset (%d) is pointing to the middle of UTF-8 character", startoffset);
247       return -1;
248
249     default:
250       CLog::Log(LOGERROR, "PCRE: Unknown error: %d", rc);
251       return -1;
252     }
253   }
254   m_offset = startoffset;
255   m_bMatched = true;
256   m_iMatchCount = rc;
257   return m_iOvector[0] + m_offset;
258 }
259
260 int CRegExp::GetCaptureTotal() const
261 {
262   int c = -1;
263   if (m_re)
264     pcre_fullinfo(m_re, NULL, PCRE_INFO_CAPTURECOUNT, &c);
265   return c;
266 }
267
268 std::string CRegExp::GetReplaceString(const std::string& sReplaceExp) const
269 {
270   if (!m_bMatched || sReplaceExp.empty())
271     return "";
272
273   const char* const expr = sReplaceExp.c_str();
274
275   size_t pos = sReplaceExp.find_first_of("\\&");
276   std::string result(sReplaceExp, 0, pos);
277   result.reserve(sReplaceExp.size()); // very rough estimate
278
279   while(pos != std::string::npos)
280   {
281     if (expr[pos] == '\\')
282     {
283       // string is null-terminated and current char isn't null, so it's safe to advance to next char
284       pos++; // advance to next char
285       const char nextChar = expr[pos];
286       if (nextChar == '&' || nextChar == '\\')
287       { // this is "\&" or "\\" combination
288         result.push_back(nextChar); // add '&' or '\' to result 
289         pos++; 
290       }
291       else if (isdigit(nextChar))
292       { // this is "\0" - "\9" combination
293         int subNum = nextChar - '0';
294         pos++; // advance to second next char
295         const char secondNextChar = expr[pos];
296         if (isdigit(secondNextChar))
297         { // this is "\00" - "\99" combination
298           subNum = subNum * 10 + (secondNextChar - '0');
299           pos++;
300         }
301         result.append(GetMatch(subNum));
302       }
303     }
304     else
305     { // '&' char
306       result.append(GetMatch(0));
307       pos++;
308     }
309
310     const size_t nextPos = sReplaceExp.find_first_of("\\&", pos);
311     result.append(sReplaceExp, pos, nextPos - pos);
312     pos = nextPos;
313   }
314
315   return result;
316 }
317
318 int CRegExp::GetSubStart(int iSub) const
319 {
320   if (!IsValidSubNumber(iSub))
321     return -1;
322
323   return m_iOvector[iSub*2] + m_offset;
324 }
325
326 int CRegExp::GetSubStart(const std::string& subName) const
327 {
328   return GetSubStart(GetNamedSubPatternNumber(subName.c_str()));
329 }
330
331 int CRegExp::GetSubLength(int iSub) const
332 {
333   if (!IsValidSubNumber(iSub))
334     return -1;
335
336   return m_iOvector[(iSub*2)+1] - m_iOvector[(iSub*2)];
337 }
338
339 int CRegExp::GetSubLength(const std::string& subName) const
340 {
341   return GetSubLength(GetNamedSubPatternNumber(subName.c_str()));
342 }
343
344 std::string CRegExp::GetMatch(int iSub /* = 0 */) const
345 {
346   if (!IsValidSubNumber(iSub))
347     return "";
348
349   int pos = m_iOvector[(iSub*2)];
350   int len = m_iOvector[(iSub*2)+1] - pos;
351   if (pos < 0 || len <= 0)
352     return "";
353
354   return m_subject.substr(pos, len);
355 }
356
357 std::string CRegExp::GetMatch(const std::string& subName) const
358 {
359   return GetMatch(GetNamedSubPatternNumber(subName.c_str()));
360 }
361
362 bool CRegExp::GetNamedSubPattern(const char* strName, std::string& strMatch) const
363 {
364   strMatch.clear();
365   int iSub = pcre_get_stringnumber(m_re, strName);
366   if (!IsValidSubNumber(iSub))
367     return false;
368   strMatch = GetMatch(iSub);
369   return true;
370 }
371
372 int CRegExp::GetNamedSubPatternNumber(const char* strName) const
373 {
374   return pcre_get_stringnumber(m_re, strName);
375 }
376
377 void CRegExp::DumpOvector(int iLog /* = LOGDEBUG */)
378 {
379   if (iLog < LOGDEBUG || iLog > LOGNONE)
380     return;
381
382   CStdString str = "{";
383   int size = GetSubCount(); // past the subpatterns is junk
384   for (int i = 0; i <= size; i++)
385   {
386     CStdString t = StringUtils::Format("[%i,%i]", m_iOvector[(i*2)], m_iOvector[(i*2)+1]);
387     if (i != size)
388       t += ",";
389     str += t;
390   }
391   str += "}";
392   CLog::Log(iLog, "regexp ovector=%s", str.c_str());
393 }
394
395 void CRegExp::Cleanup()
396 {
397   if (m_re)
398   {
399     pcre_free(m_re); 
400     m_re = NULL; 
401   }
402
403   if (m_sd)
404   {
405     pcre_free_study(m_sd);
406     m_sd = NULL;
407   }
408
409 #ifdef PCRE_HAS_JIT_CODE
410   if (m_jitStack)
411   {
412     pcre_jit_stack_free(m_jitStack);
413     m_jitStack = NULL;
414   }
415 #endif
416 }
417
418 inline bool CRegExp::IsValidSubNumber(int iSub) const
419 {
420   return iSub >= 0 && iSub <= m_iMatchCount && iSub <= m_MaxNumOfBackrefrences;
421 }
422
423
424 bool CRegExp::IsUtf8Supported(void)
425 {
426   if (m_Utf8Supported == -1)
427   {
428     if (pcre_config(PCRE_CONFIG_UTF8, &m_Utf8Supported) != 0)
429       m_Utf8Supported = 0;
430   }
431
432   return m_Utf8Supported == 1;
433 }
434
435 bool CRegExp::AreUnicodePropertiesSupported(void)
436 {
437 #if defined(PCRE_CONFIG_UNICODE_PROPERTIES) && PCRE_UCP != 0
438   if (m_UcpSupported == -1)
439   {
440     if (pcre_config(PCRE_CONFIG_UNICODE_PROPERTIES, &m_UcpSupported) != 0)
441       m_UcpSupported = 0;
442   }
443 #endif
444
445   return m_UcpSupported == 1;
446 }
447
448 bool CRegExp::LogCheckUtf8Support(void)
449 {
450   bool utf8FullSupport = true;
451
452   if (!CRegExp::IsUtf8Supported())
453   {
454     utf8FullSupport = false;
455     CLog::Log(LOGWARNING, "UTF-8 is not supported in PCRE lib, support for national symbols is limited!");
456   }
457
458   if (!CRegExp::AreUnicodePropertiesSupported())
459   {
460     utf8FullSupport = false;
461     CLog::Log(LOGWARNING, "Unicode properties are not enabled in PCRE lib, support for national symbols may be limited!");
462   }
463
464   if (!utf8FullSupport)
465   {
466     CLog::Log(LOGNOTICE, "Consider installing PCRE lib version 8.10 or later with enabled Unicode properties and UTF-8 support. Your PCRE lib version: %s", PCRE::pcre_version());
467 #if PCRE_UCP == 0
468     CLog::Log(LOGNOTICE, "You will need to rebuild XBMC after PCRE lib update.");
469 #endif
470   }
471
472   return utf8FullSupport;
473 }
474
475 bool CRegExp::IsJitSupported(void)
476 {
477   if (m_JitSupported == -1)
478   {
479 #ifdef PCRE_HAS_JIT_CODE
480     if (pcre_config(PCRE_CONFIG_JIT, &m_JitSupported) != 0)
481 #endif
482       m_JitSupported = 0;
483   }
484
485   return m_JitSupported == 1;
486 }