Improve readability of version string
[TortoiseGit.git] / src / Utils / StringUtils.cpp
bloba1d9277722daa093123e4f54fac35e7ec9e4fe2f
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2015-2016 - TortoiseGit
4 // Copyright (C) 2003-2011, 2015 - TortoiseSVN
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "stdafx.h"
21 #include "UnicodeUtils.h"
22 #include "StringUtils.h"
23 #include "ClipboardHelper.h"
24 #include "SmartHandle.h"
26 int strwildcmp(const char *wild, const char *string)
28 const char* cp = nullptr;
29 const char* mp = nullptr;
30 while ((*string) && (*wild != '*'))
32 if ((*wild != *string) && (*wild != '?'))
33 return 0;
34 ++wild;
35 ++string;
37 while (*string)
39 if (*wild == '*')
41 if (!*++wild)
42 return 1;
43 mp = wild;
44 cp = string+1;
46 else if ((*wild == *string) || (*wild == '?'))
48 ++wild;
49 ++string;
51 else
53 wild = mp;
54 string = cp++;
58 while (*wild == '*')
59 ++wild;
60 return !*wild;
63 int wcswildcmp(const wchar_t *wild, const wchar_t *string)
65 const wchar_t* cp = nullptr;
66 const wchar_t* mp = nullptr;
67 while ((*string) && (*wild != '*'))
69 if ((*wild != *string) && (*wild != '?'))
70 return 0;
71 ++wild;
72 ++string;
74 while (*string)
76 if (*wild == '*')
78 if (!*++wild)
79 return 1;
80 mp = wild;
81 cp = string+1;
83 else if ((*wild == *string) || (*wild == '?'))
85 ++wild;
86 ++string;
88 else
90 wild = mp;
91 string = cp++;
95 while (*wild == '*')
96 ++wild;
97 return !*wild;
100 #ifdef _MFC_VER
102 void CStringUtils::RemoveAccelerators(CString& text)
104 int pos = 0;
105 while ((pos=text.Find('&',pos))>=0)
107 if (text.GetLength() > (pos-1))
109 if (text.GetAt(pos+1)!=' ')
110 text.Delete(pos);
112 ++pos;
116 TCHAR CStringUtils::GetAccellerator(const CString& text)
118 int pos = 0;
119 while ((pos = text.Find('&', pos)) >= 0)
121 if (text.GetLength() > (pos - 1))
123 if (text.GetAt(pos + 1) != ' ' && text.GetAt(pos + 1) != '&')
124 return towupper(text.GetAt(pos + 1));
126 ++pos;
128 return L'\0';
131 bool CStringUtils::WriteAsciiStringToClipboard(const CStringA& sClipdata, LCID lcid, HWND hOwningWnd)
133 CClipboardHelper clipboardHelper;
134 if (!clipboardHelper.Open(hOwningWnd))
135 return false;
137 EmptyClipboard();
138 HGLOBAL hClipboardData = CClipboardHelper::GlobalAlloc(sClipdata.GetLength() + 1);
139 if (!hClipboardData)
140 return false;
142 char* pchData = (char*)GlobalLock(hClipboardData);
143 if (!pchData)
144 return false;
146 strcpy_s(pchData, sClipdata.GetLength() + 1, (LPCSTR)sClipdata);
147 GlobalUnlock(hClipboardData);
148 if (!SetClipboardData(CF_TEXT, hClipboardData))
149 return false;
151 HANDLE hlocmem = CClipboardHelper::GlobalAlloc(sizeof(LCID));
152 if (!hlocmem)
153 return false;
155 PLCID plcid = (PLCID)GlobalLock(hlocmem);
156 if (plcid)
158 *plcid = lcid;
159 SetClipboardData(CF_LOCALE, static_cast<HANDLE>(plcid));
161 GlobalUnlock(hlocmem);
163 return true;
166 bool CStringUtils::WriteAsciiStringToClipboard(const CStringW& sClipdata, HWND hOwningWnd)
168 CClipboardHelper clipboardHelper;
169 if (!clipboardHelper.Open(hOwningWnd))
170 return false;
172 EmptyClipboard();
173 HGLOBAL hClipboardData = CClipboardHelper::GlobalAlloc((sClipdata.GetLength() + 1) * sizeof(WCHAR));
174 if (!hClipboardData)
175 return false;
177 WCHAR* pchData = (WCHAR*)GlobalLock(hClipboardData);
178 if (!pchData)
179 return false;
181 _tcscpy_s(pchData, sClipdata.GetLength()+1, (LPCWSTR)sClipdata);
182 GlobalUnlock(hClipboardData);
183 if (!SetClipboardData(CF_UNICODETEXT, hClipboardData))
184 return false;
186 // no need to also set CF_TEXT : the OS does this
187 // automatically.
188 return true;
191 bool CStringUtils::WriteDiffToClipboard(const CStringA& sClipdata, HWND hOwningWnd)
193 UINT cFormat = RegisterClipboardFormat(_T("TGIT_UNIFIEDDIFF"));
194 if (cFormat == 0)
195 return false;
196 CClipboardHelper clipboardHelper;
197 if (!clipboardHelper.Open(hOwningWnd))
198 return false;
200 EmptyClipboard();
201 HGLOBAL hClipboardData = CClipboardHelper::GlobalAlloc(sClipdata.GetLength() + 1);
202 if (!hClipboardData)
203 return false;
205 char* pchData = (char*)GlobalLock(hClipboardData);
206 if (!pchData)
207 return false;
209 strcpy_s(pchData, sClipdata.GetLength()+1, (LPCSTR)sClipdata);
210 GlobalUnlock(hClipboardData);
211 if (!SetClipboardData(cFormat,hClipboardData))
212 return false;
213 if (!SetClipboardData(CF_TEXT, hClipboardData))
214 return false;
216 CString sClipdataW = CUnicodeUtils::GetUnicode(sClipdata);
217 auto hClipboardDataW = CClipboardHelper::GlobalAlloc(sClipdataW.GetLength()*sizeof(wchar_t) + 1);
218 if (!hClipboardDataW)
219 return false;
221 wchar_t* pchDataW = (wchar_t*)GlobalLock(hClipboardDataW);
222 if (!pchDataW)
223 return false;
225 wcscpy_s(pchDataW, sClipdataW.GetLength() + 1, (LPCWSTR)sClipdataW);
226 GlobalUnlock(hClipboardDataW);
227 if (!SetClipboardData(CF_UNICODETEXT, hClipboardDataW))
228 return false;
230 return true;
233 bool CStringUtils::ReadStringFromTextFile(const CString& path, CString& text)
235 if (!PathFileExists(path))
236 return false;
239 CStdioFile file;
240 // w/o typeBinary for some files \r gets dropped
241 if (!file.Open(path, CFile::typeBinary | CFile::modeRead | CFile::shareDenyWrite))
242 return false;
244 CStringA filecontent;
245 UINT filelength = (UINT)file.GetLength();
246 int bytesread = (int)file.Read(filecontent.GetBuffer(filelength), filelength);
247 filecontent.ReleaseBuffer(bytesread);
248 text = CUnicodeUtils::GetUnicode(filecontent);
249 file.Close();
251 catch (CFileException* pE)
253 text.Empty();
254 pE->Delete();
256 return true;
259 #endif // #ifdef _MFC_VER
261 #if defined(CSTRING_AVAILABLE) || defined(_MFC_VER)
262 BOOL CStringUtils::WildCardMatch(const CString& wildcard, const CString& string)
264 return _tcswildcmp(wildcard, string);
267 CString CStringUtils::LinesWrap(const CString& longstring, int limit /* = 80 */, bool bCompactPaths /* = true */)
269 CString retString;
270 if ((longstring.GetLength() < limit) || (limit == 0))
271 return longstring; // no wrapping needed.
272 // now start breaking the string into lines
274 int linepos = 0;
275 int lineposold = 0;
276 CString temp;
277 while ((linepos = longstring.Find('\n', linepos)) >= 0)
279 temp = longstring.Mid(lineposold, linepos-lineposold);
280 if ((linepos+1)<longstring.GetLength())
281 ++linepos;
282 else
283 break;
284 lineposold = linepos;
285 if (!retString.IsEmpty())
286 retString += _T("\n");
287 retString += WordWrap(temp, limit, bCompactPaths, false, 4);
289 temp = longstring.Mid(lineposold);
290 if (!temp.IsEmpty())
291 retString += _T("\n");
292 retString += WordWrap(temp, limit, bCompactPaths, false, 4);
293 retString.Trim();
294 return retString;
297 CString CStringUtils::WordWrap(const CString& longstring, int limit, bool bCompactPaths, bool bForceWrap, int tabSize)
299 int nLength = longstring.GetLength();
300 CString retString;
302 if (limit < 0)
303 limit = 0;
305 int nLineStart = 0;
306 int nLineEnd = 0;
307 int tabOffset = 0;
308 for (int i = 0; i < nLength; ++i)
310 if (i-nLineStart+tabOffset >= limit)
312 if (nLineEnd == nLineStart)
314 if (bForceWrap)
315 nLineEnd = i;
316 else
318 while ((i < nLength) && (longstring[i] != ' ') && (longstring[i] != '\t'))
319 ++i;
320 nLineEnd = i;
323 if (bCompactPaths)
325 CString longline = longstring.Mid(nLineStart, nLineEnd-nLineStart).Left(MAX_PATH-1);
326 if ((bCompactPaths)&&(longline.GetLength() < MAX_PATH))
328 if (((!PathIsFileSpec(longline))&&longline.Find(':')<3)||(PathIsURL(longline)))
330 TCHAR buf[MAX_PATH] = {0};
331 PathCompactPathEx(buf, longline, limit+1, 0);
332 longline = buf;
335 retString += longline;
337 else
338 retString += longstring.Mid(nLineStart, nLineEnd-nLineStart);
339 retString += L"\n";
340 tabOffset = 0;
341 nLineStart = nLineEnd;
343 if (longstring[i] == ' ')
344 nLineEnd = i;
345 if (longstring[i] == '\t')
347 tabOffset += (tabSize - i % tabSize);
348 nLineEnd = i;
351 if (bCompactPaths)
353 CString longline = longstring.Mid(nLineStart).Left(MAX_PATH-1);
354 if ((bCompactPaths)&&(longline.GetLength() < MAX_PATH))
356 if (((!PathIsFileSpec(longline))&&longline.Find(':')<3)||(PathIsURL(longline)))
358 TCHAR buf[MAX_PATH] = {0};
359 PathCompactPathEx(buf, longline, limit+1, 0);
360 longline = buf;
363 retString += longline;
365 else
366 retString += longstring.Mid(nLineStart);
368 return retString;
370 int CStringUtils::GetMatchingLength (const CString& lhs, const CString& rhs)
372 int lhsLength = lhs.GetLength();
373 int rhsLength = rhs.GetLength();
374 int maxResult = min (lhsLength, rhsLength);
376 LPCTSTR pLhs = lhs;
377 LPCTSTR pRhs = rhs;
379 for (int i = 0; i < maxResult; ++i)
380 if (pLhs[i] != pRhs[i])
381 return i;
383 return maxResult;
386 int CStringUtils::FastCompareNoCase (const CStringW& lhs, const CStringW& rhs)
388 // attempt latin-only comparison
390 INT_PTR count = min (lhs.GetLength(), rhs.GetLength()+1);
391 const wchar_t* left = lhs;
392 const wchar_t* right = rhs;
393 for (const wchar_t* last = left + count+1; left < last; ++left, ++right)
395 int leftChar = *left;
396 int rightChar = *right;
398 int diff = leftChar - rightChar;
399 if (diff != 0)
401 // case-sensitive comparison found a difference
403 if ((leftChar | rightChar) >= 0x80)
405 // non-latin char -> fall back to CRT code
406 // (full comparison required as we might have
407 // skipped special chars / UTF plane selectors)
409 return _wcsicmp (lhs, rhs);
412 // normalize to lower case
414 if ((leftChar >= 'A') && (leftChar <= 'Z'))
415 leftChar += 'a' - 'A';
416 if ((rightChar >= 'A') && (rightChar <= 'Z'))
417 rightChar += 'a' - 'A';
419 // compare again
421 diff = leftChar - rightChar;
422 if (diff != 0)
423 return diff;
427 // must be equal (both ended with a 0)
429 return 0;
432 bool CStringUtils::IsPlainReadableASCII(const CString& text)
434 for (int i = 0; i < text.GetLength(); ++i)
436 if (text[i] < 32 || text[i] >= 127)
437 return false;
439 return true;
442 static void cleanup_space(CString& string)
444 for (int pos = 0; pos < string.GetLength(); ++pos)
446 if (_istspace(string[pos])) {
447 string.SetAt(pos ,_T(' '));
448 int cnt;
449 for (cnt = 0; _istspace(string[pos + cnt + 1]); ++cnt);
450 string.Delete(pos + 1, cnt);
455 static void get_sane_name(CString* out, const CString* name, const CString& email)
457 const CString* src = name;
458 if (name->GetLength() < 3 || 60 < name->GetLength() || _tcschr(*name, _T('@')) || _tcschr(*name, _T('<')) || _tcschr(*name, _T('>')))
459 src = &email;
460 else if (name == out)
461 return;
462 *out = *src;
465 static void parse_bogus_from(const CString& mailaddress, CString& parsedAddress, CString* parsedName)
467 /* John Doe <johndoe> */
469 int bra = mailaddress.Find(L"<");
470 if (bra < 0)
471 return;
472 int ket = mailaddress.Find(L">");
473 if (ket < 0)
474 return;
476 parsedAddress = mailaddress.Mid(bra + 1, ket - bra - 1);
478 if (parsedName)
480 *parsedName = mailaddress.Left(bra).Trim();
481 get_sane_name(parsedName, parsedName, parsedAddress);
485 void CStringUtils::ParseEmailAddress(CString mailaddress, CString& parsedAddress, CString* parsedName)
487 if (parsedName)
488 parsedName->Empty();
490 mailaddress = mailaddress.Trim();
491 if (mailaddress.IsEmpty())
493 parsedAddress.Empty();
494 return;
497 if (mailaddress.Left(1) == L'"')
499 mailaddress = mailaddress.TrimLeft();
500 bool escaped = false;
501 bool opened = true;
502 for (int i = 1; i < mailaddress.GetLength(); ++i)
504 if (mailaddress[i] == L'"')
506 if (!escaped)
508 opened = !opened;
509 if (!opened)
511 if (parsedName)
512 *parsedName = mailaddress.Mid(1, i - 1);
513 mailaddress = mailaddress.Mid(i);
514 break;
517 else
519 escaped = false;
520 mailaddress.Delete(i - 1);
521 --i;
524 else if (mailaddress[i] == L'\\')
525 escaped = !escaped;
529 auto buf = mailaddress.GetBuffer();
530 auto at = _tcschr(buf, _T('@'));
531 if (!at)
533 parse_bogus_from(mailaddress, parsedAddress, parsedName);
534 return;
537 /* Pick up the string around '@', possibly delimited with <>
538 * pair; that is the email part.
540 while (at > buf)
542 auto c = at[-1];
543 if (_istspace(c))
544 break;
545 if (c == _T('<')) {
546 at[-1] = _T(' ');
547 break;
549 at--;
552 mailaddress.ReleaseBuffer();
553 size_t el = _tcscspn(at, _T(" \n\t\r\v\f>"));
554 parsedAddress = mailaddress.Mid((int)(at - buf), (int)el);
555 mailaddress.Delete((int)(at - buf), (int)(el + (at[el] ? 1 : 0)));
557 /* The remainder is name. It could be
559 * - "John Doe <john.doe@xz>" (a), or
560 * - "john.doe@xz (John Doe)" (b), or
561 * - "John (zzz) Doe <john.doe@xz> (Comment)" (c)
563 * but we have removed the email part, so
565 * - remove extra spaces which could stay after email (case 'c'), and
566 * - trim from both ends, possibly removing the () pair at the end
567 * (cases 'b' and 'c').
569 cleanup_space(mailaddress);
570 mailaddress.Trim();
571 if (!mailaddress.IsEmpty() && ((mailaddress[0] == _T('(') && mailaddress[mailaddress.GetLength() - 1] == _T(')')) || (mailaddress[0] == _T('"') && mailaddress[mailaddress.GetLength() - 1] == _T('"'))))
572 mailaddress = mailaddress.Mid(1, mailaddress.GetLength() - 2);
574 if (parsedName && parsedName->IsEmpty())
575 get_sane_name(parsedName, &mailaddress, parsedAddress);
577 #endif // #if defined(CSTRING_AVAILABLE) || defined(_MFC_VER)
579 bool CStringUtils::WriteStringToTextFile(const std::wstring& path, const std::wstring& text, bool bUTF8 /* = true */)
581 DWORD dwWritten = 0;
582 CAutoFile hFile = CreateFile(path.c_str(), GENERIC_WRITE, FILE_SHARE_DELETE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
583 if (!hFile)
584 return false;
586 if (bUTF8)
588 std::string buf = CUnicodeUtils::StdGetUTF8(text);
589 if (!WriteFile(hFile, buf.c_str(), (DWORD)buf.length(), &dwWritten, nullptr))
591 return false;
594 else
596 if (!WriteFile(hFile, text.c_str(), (DWORD)text.length(), &dwWritten, nullptr))
598 return false;
601 return true;
604 inline static void PipeToNull(TCHAR* ptr)
606 if (*ptr == '|')
607 *ptr = '\0';
610 void CStringUtils::PipesToNulls(TCHAR* buffer, size_t length)
612 TCHAR* ptr = buffer + length;
613 while (ptr != buffer)
615 PipeToNull(ptr);
616 ptr--;
620 void CStringUtils::PipesToNulls(TCHAR* buffer)
622 TCHAR* ptr = buffer;
623 while (*ptr != 0)
625 PipeToNull(ptr);
626 ++ptr;