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.
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
!= '?'))
46 else if ((*wild
== *string
) || (*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
!= '?'))
83 else if ((*wild
== *string
) || (*wild
== '?'))
102 void CStringUtils::RemoveAccelerators(CString
& text
)
105 while ((pos
=text
.Find('&',pos
))>=0)
107 if (text
.GetLength() > (pos
-1))
109 if (text
.GetAt(pos
+1)!=' ')
116 TCHAR
CStringUtils::GetAccellerator(const CString
& text
)
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));
131 bool CStringUtils::WriteAsciiStringToClipboard(const CStringA
& sClipdata
, LCID lcid
, HWND hOwningWnd
)
133 CClipboardHelper clipboardHelper
;
134 if (!clipboardHelper
.Open(hOwningWnd
))
138 HGLOBAL hClipboardData
= CClipboardHelper::GlobalAlloc(sClipdata
.GetLength() + 1);
142 char* pchData
= (char*)GlobalLock(hClipboardData
);
146 strcpy_s(pchData
, sClipdata
.GetLength() + 1, (LPCSTR
)sClipdata
);
147 GlobalUnlock(hClipboardData
);
148 if (!SetClipboardData(CF_TEXT
, hClipboardData
))
151 HANDLE hlocmem
= CClipboardHelper::GlobalAlloc(sizeof(LCID
));
155 PLCID plcid
= (PLCID
)GlobalLock(hlocmem
);
159 SetClipboardData(CF_LOCALE
, static_cast<HANDLE
>(plcid
));
161 GlobalUnlock(hlocmem
);
166 bool CStringUtils::WriteAsciiStringToClipboard(const CStringW
& sClipdata
, HWND hOwningWnd
)
168 CClipboardHelper clipboardHelper
;
169 if (!clipboardHelper
.Open(hOwningWnd
))
173 HGLOBAL hClipboardData
= CClipboardHelper::GlobalAlloc((sClipdata
.GetLength() + 1) * sizeof(WCHAR
));
177 WCHAR
* pchData
= (WCHAR
*)GlobalLock(hClipboardData
);
181 _tcscpy_s(pchData
, sClipdata
.GetLength()+1, (LPCWSTR
)sClipdata
);
182 GlobalUnlock(hClipboardData
);
183 if (!SetClipboardData(CF_UNICODETEXT
, hClipboardData
))
186 // no need to also set CF_TEXT : the OS does this
191 bool CStringUtils::WriteDiffToClipboard(const CStringA
& sClipdata
, HWND hOwningWnd
)
193 UINT cFormat
= RegisterClipboardFormat(_T("TGIT_UNIFIEDDIFF"));
196 CClipboardHelper clipboardHelper
;
197 if (!clipboardHelper
.Open(hOwningWnd
))
201 HGLOBAL hClipboardData
= CClipboardHelper::GlobalAlloc(sClipdata
.GetLength() + 1);
205 char* pchData
= (char*)GlobalLock(hClipboardData
);
209 strcpy_s(pchData
, sClipdata
.GetLength()+1, (LPCSTR
)sClipdata
);
210 GlobalUnlock(hClipboardData
);
211 if (!SetClipboardData(cFormat
,hClipboardData
))
213 if (!SetClipboardData(CF_TEXT
, hClipboardData
))
216 CString sClipdataW
= CUnicodeUtils::GetUnicode(sClipdata
);
217 auto hClipboardDataW
= CClipboardHelper::GlobalAlloc(sClipdataW
.GetLength()*sizeof(wchar_t) + 1);
218 if (!hClipboardDataW
)
221 wchar_t* pchDataW
= (wchar_t*)GlobalLock(hClipboardDataW
);
225 wcscpy_s(pchDataW
, sClipdataW
.GetLength() + 1, (LPCWSTR
)sClipdataW
);
226 GlobalUnlock(hClipboardDataW
);
227 if (!SetClipboardData(CF_UNICODETEXT
, hClipboardDataW
))
233 bool CStringUtils::ReadStringFromTextFile(const CString
& path
, CString
& text
)
235 if (!PathFileExists(path
))
240 // w/o typeBinary for some files \r gets dropped
241 if (!file
.Open(path
, CFile::typeBinary
| CFile::modeRead
| CFile::shareDenyWrite
))
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
);
251 catch (CFileException
* pE
)
258 #endif // #ifdef _MFC_VER
260 #if defined(CSTRING_AVAILABLE) || defined(_MFC_VER)
261 BOOL
CStringUtils::WildCardMatch(const CString
& wildcard
, const CString
& string
)
263 return _tcswildcmp(wildcard
, string
);
266 CString
CStringUtils::LinesWrap(const CString
& longstring
, int limit
/* = 80 */, bool bCompactPaths
/* = true */)
269 if ((longstring
.GetLength() < limit
) || (limit
== 0))
270 return longstring
; // no wrapping needed.
271 // now start breaking the string into lines
276 while ((linepos
= longstring
.Find('\n', linepos
)) >= 0)
278 temp
= longstring
.Mid(lineposold
, linepos
-lineposold
);
279 if ((linepos
+1)<longstring
.GetLength())
283 lineposold
= linepos
;
284 if (!retString
.IsEmpty())
285 retString
+= _T("\n");
286 retString
+= WordWrap(temp
, limit
, bCompactPaths
, false, 4);
288 temp
= longstring
.Mid(lineposold
);
290 retString
+= _T("\n");
291 retString
+= WordWrap(temp
, limit
, bCompactPaths
, false, 4);
296 CString
CStringUtils::WordWrap(const CString
& longstring
, int limit
, bool bCompactPaths
, bool bForceWrap
, int tabSize
)
298 int nLength
= longstring
.GetLength();
307 for (int i
= 0; i
< nLength
; ++i
)
309 if (i
-nLineStart
+tabOffset
>= limit
)
311 if (nLineEnd
== nLineStart
)
317 while ((i
< nLength
) && (longstring
[i
] != ' ') && (longstring
[i
] != '\t'))
324 CString longline
= longstring
.Mid(nLineStart
, nLineEnd
-nLineStart
).Left(MAX_PATH
-1);
325 if ((bCompactPaths
)&&(longline
.GetLength() < MAX_PATH
))
327 if (((!PathIsFileSpec(longline
))&&longline
.Find(':')<3)||(PathIsURL(longline
)))
329 TCHAR buf
[MAX_PATH
] = {0};
330 PathCompactPathEx(buf
, longline
, limit
+1, 0);
334 retString
+= longline
;
337 retString
+= longstring
.Mid(nLineStart
, nLineEnd
-nLineStart
);
340 nLineStart
= nLineEnd
;
342 if (longstring
[i
] == ' ')
344 if (longstring
[i
] == '\t')
346 tabOffset
+= (tabSize
- i
% tabSize
);
352 CString longline
= longstring
.Mid(nLineStart
).Left(MAX_PATH
-1);
353 if ((bCompactPaths
)&&(longline
.GetLength() < MAX_PATH
))
355 if (((!PathIsFileSpec(longline
))&&longline
.Find(':')<3)||(PathIsURL(longline
)))
357 TCHAR buf
[MAX_PATH
] = {0};
358 PathCompactPathEx(buf
, longline
, limit
+1, 0);
362 retString
+= longline
;
365 retString
+= longstring
.Mid(nLineStart
);
369 int CStringUtils::GetMatchingLength (const CString
& lhs
, const CString
& rhs
)
371 int lhsLength
= lhs
.GetLength();
372 int rhsLength
= rhs
.GetLength();
373 int maxResult
= min (lhsLength
, rhsLength
);
378 for (int i
= 0; i
< maxResult
; ++i
)
379 if (pLhs
[i
] != pRhs
[i
])
385 int CStringUtils::FastCompareNoCase (const CStringW
& lhs
, const CStringW
& rhs
)
387 // attempt latin-only comparison
389 INT_PTR count
= min (lhs
.GetLength(), rhs
.GetLength()+1);
390 const wchar_t* left
= lhs
;
391 const wchar_t* right
= rhs
;
392 for (const wchar_t* last
= left
+ count
+1; left
< last
; ++left
, ++right
)
394 int leftChar
= *left
;
395 int rightChar
= *right
;
397 int diff
= leftChar
- rightChar
;
400 // case-sensitive comparison found a difference
402 if ((leftChar
| rightChar
) >= 0x80)
404 // non-latin char -> fall back to CRT code
405 // (full comparison required as we might have
406 // skipped special chars / UTF plane selectors)
408 return _wcsicmp (lhs
, rhs
);
411 // normalize to lower case
413 if ((leftChar
>= 'A') && (leftChar
<= 'Z'))
414 leftChar
+= 'a' - 'A';
415 if ((rightChar
>= 'A') && (rightChar
<= 'Z'))
416 rightChar
+= 'a' - 'A';
420 diff
= leftChar
- rightChar
;
426 // must be equal (both ended with a 0)
431 bool CStringUtils::IsPlainReadableASCII(const CString
& text
)
433 for (int i
= 0; i
< text
.GetLength(); ++i
)
435 if (text
[i
] < 32 || text
[i
] >= 127)
441 static void cleanup_space(CString
& string
)
443 for (int pos
= 0; pos
< string
.GetLength(); ++pos
)
445 if (_istspace(string
[pos
])) {
446 string
.SetAt(pos
,_T(' '));
448 for (cnt
= 0; _istspace(string
[pos
+ cnt
+ 1]); ++cnt
);
449 string
.Delete(pos
+ 1, cnt
);
454 static void get_sane_name(CString
* out
, const CString
* name
, const CString
& email
)
456 const CString
* src
= name
;
457 if (name
->GetLength() < 3 || 60 < name
->GetLength() || _tcschr(*name
, _T('@')) || _tcschr(*name
, _T('<')) || _tcschr(*name
, _T('>')))
459 else if (name
== out
)
464 static void parse_bogus_from(const CString
& mailaddress
, CString
& parsedAddress
, CString
* parsedName
)
466 /* John Doe <johndoe> */
468 int bra
= mailaddress
.Find(L
"<");
471 int ket
= mailaddress
.Find(L
">");
475 parsedAddress
= mailaddress
.Mid(bra
+ 1, ket
- bra
- 1);
479 *parsedName
= mailaddress
.Left(bra
).Trim();
480 get_sane_name(parsedName
, parsedName
, parsedAddress
);
484 void CStringUtils::ParseEmailAddress(CString mailaddress
, CString
& parsedAddress
, CString
* parsedName
)
489 mailaddress
= mailaddress
.Trim();
490 if (mailaddress
.IsEmpty())
492 parsedAddress
.Empty();
496 if (mailaddress
.Left(1) == L
'"')
498 mailaddress
= mailaddress
.TrimLeft();
499 bool escaped
= false;
501 for (int i
= 1; i
< mailaddress
.GetLength(); ++i
)
503 if (mailaddress
[i
] == L
'"')
511 *parsedName
= mailaddress
.Mid(1, i
- 1);
512 mailaddress
= mailaddress
.Mid(i
);
519 mailaddress
.Delete(i
- 1);
523 else if (mailaddress
[i
] == L
'\\')
528 auto buf
= mailaddress
.GetBuffer();
529 auto at
= _tcschr(buf
, _T('@'));
532 parse_bogus_from(mailaddress
, parsedAddress
, parsedName
);
536 /* Pick up the string around '@', possibly delimited with <>
537 * pair; that is the email part.
551 mailaddress
.ReleaseBuffer();
552 size_t el
= _tcscspn(at
, _T(" \n\t\r\v\f>"));
553 parsedAddress
= mailaddress
.Mid((int)(at
- buf
), (int)el
);
554 mailaddress
.Delete((int)(at
- buf
), (int)(el
+ (at
[el
] ? 1 : 0)));
556 /* The remainder is name. It could be
558 * - "John Doe <john.doe@xz>" (a), or
559 * - "john.doe@xz (John Doe)" (b), or
560 * - "John (zzz) Doe <john.doe@xz> (Comment)" (c)
562 * but we have removed the email part, so
564 * - remove extra spaces which could stay after email (case 'c'), and
565 * - trim from both ends, possibly removing the () pair at the end
566 * (cases 'b' and 'c').
568 cleanup_space(mailaddress
);
570 if (!mailaddress
.IsEmpty() && ((mailaddress
[0] == _T('(') && mailaddress
[mailaddress
.GetLength() - 1] == _T(')')) || (mailaddress
[0] == _T('"') && mailaddress
[mailaddress
.GetLength() - 1] == _T('"'))))
571 mailaddress
= mailaddress
.Mid(1, mailaddress
.GetLength() - 2);
573 if (parsedName
&& parsedName
->IsEmpty())
574 get_sane_name(parsedName
, &mailaddress
, parsedAddress
);
577 bool CStringUtils::StartsWith(const wchar_t* heystack
, const CString
& needle
)
579 return wcsncmp(heystack
, needle
, needle
.GetLength()) == 0;
581 #endif // #if defined(CSTRING_AVAILABLE) || defined(_MFC_VER)
583 bool CStringUtils::StartsWith(const wchar_t* heystack
, const wchar_t* needle
)
585 return wcsncmp(heystack
, needle
, wcslen(needle
)) == 0;
588 bool CStringUtils::WriteStringToTextFile(const std::wstring
& path
, const std::wstring
& text
, bool bUTF8
/* = true */)
591 CAutoFile hFile
= CreateFile(path
.c_str(), GENERIC_WRITE
, FILE_SHARE_DELETE
, nullptr, CREATE_ALWAYS
, FILE_ATTRIBUTE_NORMAL
, nullptr);
597 std::string buf
= CUnicodeUtils::StdGetUTF8(text
);
598 if (!WriteFile(hFile
, buf
.c_str(), (DWORD
)buf
.length(), &dwWritten
, nullptr))
605 if (!WriteFile(hFile
, text
.c_str(), (DWORD
)text
.length(), &dwWritten
, nullptr))
613 inline static void PipeToNull(TCHAR
* ptr
)
619 void CStringUtils::PipesToNulls(TCHAR
* buffer
, size_t length
)
621 TCHAR
* ptr
= buffer
+ length
;
622 while (ptr
!= buffer
)
629 void CStringUtils::PipesToNulls(TCHAR
* buffer
)