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
)
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 */)
270 if ((longstring
.GetLength() < limit
) || (limit
== 0))
271 return longstring
; // no wrapping needed.
272 // now start breaking the string into lines
277 while ((linepos
= longstring
.Find('\n', linepos
)) >= 0)
279 temp
= longstring
.Mid(lineposold
, linepos
-lineposold
);
280 if ((linepos
+1)<longstring
.GetLength())
284 lineposold
= linepos
;
285 if (!retString
.IsEmpty())
286 retString
+= _T("\n");
287 retString
+= WordWrap(temp
, limit
, bCompactPaths
, false, 4);
289 temp
= longstring
.Mid(lineposold
);
291 retString
+= _T("\n");
292 retString
+= WordWrap(temp
, limit
, bCompactPaths
, false, 4);
297 CString
CStringUtils::WordWrap(const CString
& longstring
, int limit
, bool bCompactPaths
, bool bForceWrap
, int tabSize
)
299 int nLength
= longstring
.GetLength();
308 for (int i
= 0; i
< nLength
; ++i
)
310 if (i
-nLineStart
+tabOffset
>= limit
)
312 if (nLineEnd
== nLineStart
)
318 while ((i
< nLength
) && (longstring
[i
] != ' ') && (longstring
[i
] != '\t'))
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);
335 retString
+= longline
;
338 retString
+= longstring
.Mid(nLineStart
, nLineEnd
-nLineStart
);
341 nLineStart
= nLineEnd
;
343 if (longstring
[i
] == ' ')
345 if (longstring
[i
] == '\t')
347 tabOffset
+= (tabSize
- i
% tabSize
);
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);
363 retString
+= longline
;
366 retString
+= longstring
.Mid(nLineStart
);
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
);
379 for (int i
= 0; i
< maxResult
; ++i
)
380 if (pLhs
[i
] != pRhs
[i
])
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
;
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';
421 diff
= leftChar
- rightChar
;
427 // must be equal (both ended with a 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)
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(' '));
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('>')))
460 else if (name
== out
)
465 static void parse_bogus_from(const CString
& mailaddress
, CString
& parsedAddress
, CString
* parsedName
)
467 /* John Doe <johndoe> */
469 int bra
= mailaddress
.Find(L
"<");
472 int ket
= mailaddress
.Find(L
">");
476 parsedAddress
= mailaddress
.Mid(bra
+ 1, ket
- bra
- 1);
480 *parsedName
= mailaddress
.Left(bra
).Trim();
481 get_sane_name(parsedName
, parsedName
, parsedAddress
);
485 void CStringUtils::ParseEmailAddress(CString mailaddress
, CString
& parsedAddress
, CString
* parsedName
)
490 mailaddress
= mailaddress
.Trim();
491 if (mailaddress
.IsEmpty())
493 parsedAddress
.Empty();
497 if (mailaddress
.Left(1) == L
'"')
499 mailaddress
= mailaddress
.TrimLeft();
500 bool escaped
= false;
502 for (int i
= 1; i
< mailaddress
.GetLength(); ++i
)
504 if (mailaddress
[i
] == L
'"')
512 *parsedName
= mailaddress
.Mid(1, i
- 1);
513 mailaddress
= mailaddress
.Mid(i
);
520 mailaddress
.Delete(i
- 1);
524 else if (mailaddress
[i
] == L
'\\')
529 auto buf
= mailaddress
.GetBuffer();
530 auto at
= _tcschr(buf
, _T('@'));
533 parse_bogus_from(mailaddress
, parsedAddress
, parsedName
);
537 /* Pick up the string around '@', possibly delimited with <>
538 * pair; that is the email part.
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
);
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 */)
582 CAutoFile hFile
= CreateFile(path
.c_str(), GENERIC_WRITE
, FILE_SHARE_DELETE
, nullptr, CREATE_ALWAYS
, FILE_ATTRIBUTE_NORMAL
, nullptr);
588 std::string buf
= CUnicodeUtils::StdGetUTF8(text
);
589 if (!WriteFile(hFile
, buf
.c_str(), (DWORD
)buf
.length(), &dwWritten
, nullptr))
596 if (!WriteFile(hFile
, text
.c_str(), (DWORD
)text
.length(), &dwWritten
, nullptr))
604 inline static void PipeToNull(TCHAR
* ptr
)
610 void CStringUtils::PipesToNulls(TCHAR
* buffer
, size_t length
)
612 TCHAR
* ptr
= buffer
+ length
;
613 while (ptr
!= buffer
)
620 void CStringUtils::PipesToNulls(TCHAR
* buffer
)