2020-04-29 13:02:18 -07:00
|
|
|
#include "pch.h"
|
2019-10-17 20:57:19 -07:00
|
|
|
#include "Helpers.h"
|
2025-11-04 09:27:16 +08:00
|
|
|
#include "MetadataTypes.h"
|
2020-07-16 14:24:49 +03:00
|
|
|
#include <regex>
|
2019-10-17 20:57:19 -07:00
|
|
|
#include <ShlGuid.h>
|
2020-07-02 11:52:01 +03:00
|
|
|
#include <cstring>
|
|
|
|
|
#include <filesystem>
|
2025-11-04 09:27:16 +08:00
|
|
|
#include <unordered_map>
|
|
|
|
|
#include <unordered_set>
|
|
|
|
|
#include <algorithm>
|
2020-07-02 11:52:01 +03:00
|
|
|
|
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
|
|
2021-10-25 15:40:19 +02:00
|
|
|
namespace
|
|
|
|
|
{
|
|
|
|
|
const int MAX_INPUT_STRING_LEN = 1024;
|
|
|
|
|
|
|
|
|
|
const wchar_t c_rootRegPath[] = L"Software\\Microsoft\\PowerRename";
|
2025-11-04 09:27:16 +08:00
|
|
|
|
|
|
|
|
// Helper function: Find the longest matching pattern starting at the given position
|
|
|
|
|
// Returns the matched pattern name, or empty string if no match found
|
|
|
|
|
std::wstring FindLongestPattern(
|
|
|
|
|
const std::wstring& input,
|
|
|
|
|
size_t startPos,
|
|
|
|
|
size_t maxPatternLength,
|
|
|
|
|
const std::unordered_set<std::wstring>& validPatterns)
|
|
|
|
|
{
|
|
|
|
|
const size_t remaining = input.length() - startPos;
|
|
|
|
|
const size_t searchLength = std::min(maxPatternLength, remaining);
|
|
|
|
|
|
|
|
|
|
// Try to match from longest to shortest to ensure greedy matching
|
|
|
|
|
// e.g., DATE_TAKEN_YYYY should be matched before DATE_TAKEN_YY
|
|
|
|
|
for (size_t len = searchLength; len > 0; --len)
|
|
|
|
|
{
|
|
|
|
|
std::wstring candidate = input.substr(startPos, len);
|
|
|
|
|
if (validPatterns.find(candidate) != validPatterns.end())
|
|
|
|
|
{
|
|
|
|
|
return candidate;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return L"";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Helper function: Get the replacement value for a pattern
|
|
|
|
|
// Returns the actual metadata value if available; if not, returns the pattern name with $ prefix
|
|
|
|
|
std::wstring GetPatternValue(
|
|
|
|
|
const std::wstring& patternName,
|
|
|
|
|
const PowerRenameLib::MetadataPatternMap& patterns)
|
|
|
|
|
{
|
|
|
|
|
auto it = patterns.find(patternName);
|
|
|
|
|
|
|
|
|
|
// Return actual value if found and valid (non-empty)
|
|
|
|
|
if (it != patterns.end() && !it->second.empty())
|
|
|
|
|
{
|
|
|
|
|
return it->second;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Return pattern name with $ prefix if value is unavailable
|
|
|
|
|
// This provides visual feedback that the field exists but has no data
|
|
|
|
|
return L"$" + patternName;
|
|
|
|
|
}
|
2021-10-25 15:40:19 +02:00
|
|
|
}
|
|
|
|
|
|
2020-07-02 11:52:01 +03:00
|
|
|
HRESULT GetTrimmedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR source)
|
|
|
|
|
{
|
2020-12-14 12:28:12 +03:00
|
|
|
HRESULT hr = E_INVALIDARG;
|
|
|
|
|
if (source)
|
2020-07-02 11:52:01 +03:00
|
|
|
{
|
|
|
|
|
PWSTR newName = nullptr;
|
|
|
|
|
hr = SHStrDup(source, &newName);
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
size_t firstValidIndex = 0, lastValidIndex = wcslen(newName) - 1;
|
|
|
|
|
while (firstValidIndex <= lastValidIndex && iswspace(newName[firstValidIndex]))
|
|
|
|
|
{
|
|
|
|
|
firstValidIndex++;
|
|
|
|
|
}
|
|
|
|
|
while (firstValidIndex <= lastValidIndex && (iswspace(newName[lastValidIndex]) || newName[lastValidIndex] == L'.'))
|
|
|
|
|
{
|
|
|
|
|
lastValidIndex--;
|
|
|
|
|
}
|
|
|
|
|
newName[lastValidIndex + 1] = '\0';
|
|
|
|
|
|
|
|
|
|
hr = StringCchCopy(result, cchMax, newName + firstValidIndex);
|
|
|
|
|
}
|
|
|
|
|
CoTaskMemFree(newName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return hr;
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-14 14:42:37 +01:00
|
|
|
HRESULT GetTransformedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR source, DWORD flags, bool isFolder)
|
2020-07-02 11:52:01 +03:00
|
|
|
{
|
|
|
|
|
std::locale::global(std::locale(""));
|
2020-12-14 12:28:12 +03:00
|
|
|
HRESULT hr = E_INVALIDARG;
|
2024-06-04 04:11:12 -07:00
|
|
|
|
|
|
|
|
auto contractionOrSingleQuotedWordCheck = [](std::wstring stem, size_t i) {
|
|
|
|
|
return !i || stem[i - 1] != '\'' || (i == 1 || iswpunct(stem[i - 2]) || iswspace(stem[i - 2]));
|
|
|
|
|
};
|
|
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
if (source && flags)
|
2020-07-02 11:52:01 +03:00
|
|
|
{
|
|
|
|
|
if (flags & Uppercase)
|
|
|
|
|
{
|
2021-12-14 14:42:37 +01:00
|
|
|
if (isFolder)
|
2020-07-02 11:52:01 +03:00
|
|
|
{
|
2021-12-14 14:42:37 +01:00
|
|
|
hr = StringCchCopy(result, cchMax, source);
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
std::transform(result, result + wcslen(result), result, ::towupper);
|
|
|
|
|
}
|
2020-07-02 11:52:01 +03:00
|
|
|
}
|
2021-12-14 14:42:37 +01:00
|
|
|
else
|
2020-07-02 11:52:01 +03:00
|
|
|
{
|
2021-12-14 14:42:37 +01:00
|
|
|
if (flags & NameOnly)
|
2020-07-02 11:52:01 +03:00
|
|
|
{
|
2021-12-14 14:42:37 +01:00
|
|
|
std::wstring stem = fs::path(source).stem().wstring();
|
|
|
|
|
std::transform(stem.begin(), stem.end(), stem.begin(), ::towupper);
|
|
|
|
|
hr = StringCchPrintf(result, cchMax, L"%s%s", stem.c_str(), fs::path(source).extension().c_str());
|
|
|
|
|
}
|
|
|
|
|
else if (flags & ExtensionOnly)
|
|
|
|
|
{
|
|
|
|
|
std::wstring extension = fs::path(source).extension().wstring();
|
|
|
|
|
if (!extension.empty())
|
|
|
|
|
{
|
|
|
|
|
std::transform(extension.begin(), extension.end(), extension.begin(), ::towupper);
|
|
|
|
|
hr = StringCchPrintf(result, cchMax, L"%s%s", fs::path(source).stem().c_str(), extension.c_str());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
hr = StringCchCopy(result, cchMax, source);
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
std::transform(result, result + wcslen(result), result, ::towupper);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-07-02 11:52:01 +03:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
hr = StringCchCopy(result, cchMax, source);
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
std::transform(result, result + wcslen(result), result, ::towupper);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-12-14 14:42:37 +01:00
|
|
|
}
|
|
|
|
|
else if (flags & Lowercase)
|
|
|
|
|
{
|
|
|
|
|
if (isFolder)
|
2020-07-02 11:52:01 +03:00
|
|
|
{
|
|
|
|
|
hr = StringCchCopy(result, cchMax, source);
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
2021-12-14 14:42:37 +01:00
|
|
|
std::transform(result, result + wcslen(result), result, ::towlower);
|
2020-07-02 11:52:01 +03:00
|
|
|
}
|
|
|
|
|
}
|
2021-12-14 14:42:37 +01:00
|
|
|
else
|
2020-07-02 11:52:01 +03:00
|
|
|
{
|
2021-12-14 14:42:37 +01:00
|
|
|
if (flags & NameOnly)
|
2020-07-02 11:52:01 +03:00
|
|
|
{
|
2021-12-14 14:42:37 +01:00
|
|
|
std::wstring stem = fs::path(source).stem().wstring();
|
|
|
|
|
std::transform(stem.begin(), stem.end(), stem.begin(), ::towlower);
|
|
|
|
|
hr = StringCchPrintf(result, cchMax, L"%s%s", stem.c_str(), fs::path(source).extension().c_str());
|
|
|
|
|
}
|
|
|
|
|
else if (flags & ExtensionOnly)
|
|
|
|
|
{
|
|
|
|
|
std::wstring extension = fs::path(source).extension().wstring();
|
|
|
|
|
if (!extension.empty())
|
|
|
|
|
{
|
|
|
|
|
std::transform(extension.begin(), extension.end(), extension.begin(), ::towlower);
|
|
|
|
|
hr = StringCchPrintf(result, cchMax, L"%s%s", fs::path(source).stem().c_str(), extension.c_str());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
hr = StringCchCopy(result, cchMax, source);
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
std::transform(result, result + wcslen(result), result, ::towlower);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-07-02 11:52:01 +03:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
hr = StringCchCopy(result, cchMax, source);
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
std::transform(result, result + wcslen(result), result, ::towlower);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (flags & Titlecase)
|
|
|
|
|
{
|
|
|
|
|
if (!(flags & ExtensionOnly))
|
|
|
|
|
{
|
|
|
|
|
std::vector<std::wstring> exceptions = { L"a", L"an", L"to", L"the", L"at", L"by", L"for", L"in", L"of", L"on", L"up", L"and", L"as", L"but", L"or", L"nor" };
|
2021-12-14 14:42:37 +01:00
|
|
|
std::wstring stem = isFolder ? source : fs::path(source).stem().wstring();
|
|
|
|
|
std::wstring extension = isFolder ? L"" : fs::path(source).extension().wstring();
|
2020-07-02 11:52:01 +03:00
|
|
|
|
|
|
|
|
size_t stemLength = stem.length();
|
|
|
|
|
bool isFirstWord = true;
|
|
|
|
|
|
|
|
|
|
while (stemLength > 0 && (iswspace(stem[stemLength - 1]) || iswpunct(stem[stemLength - 1])))
|
|
|
|
|
{
|
|
|
|
|
stemLength--;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < stemLength; i++)
|
|
|
|
|
{
|
2024-06-04 04:11:12 -07:00
|
|
|
if (!i || iswspace(stem[i - 1]) || (iswpunct(stem[i - 1]) && contractionOrSingleQuotedWordCheck(stem, i)))
|
2020-07-02 11:52:01 +03:00
|
|
|
{
|
|
|
|
|
if (iswspace(stem[i]) || iswpunct(stem[i]))
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
size_t wordLength = 0;
|
|
|
|
|
while (i + wordLength < stemLength && !iswspace(stem[i + wordLength]) && !iswpunct(stem[i + wordLength]))
|
|
|
|
|
{
|
|
|
|
|
wordLength++;
|
|
|
|
|
}
|
2024-06-04 04:11:12 -07:00
|
|
|
|
|
|
|
|
auto subStr = stem.substr(i, wordLength);
|
|
|
|
|
std::transform(subStr.begin(), subStr.end(), subStr.begin(), ::towlower);
|
|
|
|
|
if (isFirstWord || i + wordLength == stemLength || std::find(exceptions.begin(), exceptions.end(), subStr) == exceptions.end())
|
2020-07-02 11:52:01 +03:00
|
|
|
{
|
|
|
|
|
stem[i] = towupper(stem[i]);
|
|
|
|
|
isFirstWord = false;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
stem[i] = towlower(stem[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
stem[i] = towlower(stem[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
hr = StringCchPrintf(result, cchMax, L"%s%s", stem.c_str(), extension.c_str());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
hr = StringCchCopy(result, cchMax, source);
|
|
|
|
|
}
|
2023-08-14 16:53:49 +02:00
|
|
|
}
|
2021-04-09 08:24:06 -04:00
|
|
|
else if (flags & Capitalized)
|
|
|
|
|
{
|
|
|
|
|
if (!(flags & ExtensionOnly))
|
|
|
|
|
{
|
2021-12-14 14:42:37 +01:00
|
|
|
std::wstring stem = isFolder ? source : fs::path(source).stem().wstring();
|
|
|
|
|
std::wstring extension = isFolder ? L"" : fs::path(source).extension().wstring();
|
2021-04-09 08:24:06 -04:00
|
|
|
|
|
|
|
|
size_t stemLength = stem.length();
|
|
|
|
|
|
|
|
|
|
while (stemLength > 0 && (iswspace(stem[stemLength - 1]) || iswpunct(stem[stemLength - 1])))
|
|
|
|
|
{
|
|
|
|
|
stemLength--;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < stemLength; i++)
|
|
|
|
|
{
|
2024-06-04 04:11:12 -07:00
|
|
|
if (!i || iswspace(stem[i - 1]) || (iswpunct(stem[i - 1]) && contractionOrSingleQuotedWordCheck(stem, i)))
|
2021-04-09 08:24:06 -04:00
|
|
|
{
|
|
|
|
|
if (iswspace(stem[i]) || iswpunct(stem[i]))
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2024-06-04 04:11:12 -07:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
stem[i] = towupper(stem[i]);
|
|
|
|
|
}
|
2021-04-09 08:24:06 -04:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
stem[i] = towlower(stem[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
hr = StringCchPrintf(result, cchMax, L"%s%s", stem.c_str(), extension.c_str());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
hr = StringCchCopy(result, cchMax, source);
|
|
|
|
|
}
|
2020-07-02 11:52:01 +03:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
hr = StringCchCopy(result, cchMax, source);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return hr;
|
|
|
|
|
}
|
2019-10-17 20:57:19 -07:00
|
|
|
|
2023-08-14 16:53:49 +02:00
|
|
|
bool isFileTimeUsed(_In_ PCWSTR source)
|
2020-08-20 02:53:09 +03:00
|
|
|
{
|
|
|
|
|
bool used = false;
|
2025-04-16 07:45:03 +05:30
|
|
|
static const std::array patterns = {
|
|
|
|
|
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$Y" },
|
|
|
|
|
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$M" },
|
|
|
|
|
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$D" },
|
|
|
|
|
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$h" },
|
|
|
|
|
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$m" },
|
|
|
|
|
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$s" },
|
|
|
|
|
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$f" },
|
|
|
|
|
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$H" },
|
|
|
|
|
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$T" },
|
|
|
|
|
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$t" }
|
|
|
|
|
};
|
|
|
|
|
|
2023-08-14 16:53:49 +02:00
|
|
|
for (size_t i = 0; !used && i < patterns.size(); i++)
|
2020-08-20 02:53:09 +03:00
|
|
|
{
|
2023-08-14 16:53:49 +02:00
|
|
|
if (std::regex_search(source, patterns[i]))
|
2020-08-20 02:53:09 +03:00
|
|
|
{
|
|
|
|
|
used = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return used;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-04 09:27:16 +08:00
|
|
|
bool isMetadataUsed(_In_ PCWSTR source, PowerRenameLib::MetadataType metadataType, _In_opt_ PCWSTR filePath, bool isFolder)
|
|
|
|
|
{
|
|
|
|
|
if (!source) return false;
|
|
|
|
|
|
|
|
|
|
// Early exit: If file path is provided, check file type first (fastest checks)
|
|
|
|
|
// This avoids expensive pattern matching for files that don't support metadata
|
|
|
|
|
if (filePath != nullptr)
|
|
|
|
|
{
|
|
|
|
|
// Folders don't support metadata extraction
|
|
|
|
|
if (isFolder)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if file path is valid
|
|
|
|
|
if (wcslen(filePath) == 0)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get file extension
|
|
|
|
|
std::wstring extension = fs::path(filePath).extension().wstring();
|
|
|
|
|
|
|
|
|
|
// Convert to lowercase for case-insensitive comparison
|
|
|
|
|
std::transform(extension.begin(), extension.end(), extension.begin(), ::towlower);
|
|
|
|
|
|
|
|
|
|
// According to the metadata support table, only these formats support metadata extraction:
|
|
|
|
|
// - JPEG (IFD, Exif, XMP, GPS, IPTC) - supports fast metadata encoding
|
|
|
|
|
// - TIFF (IFD, Exif, XMP, GPS, IPTC) - supports fast metadata encoding
|
|
|
|
|
// - PNG (text chunks)
|
|
|
|
|
static const std::unordered_set<std::wstring> supportedExtensions = {
|
|
|
|
|
L".jpg",
|
|
|
|
|
L".jpeg",
|
|
|
|
|
L".png",
|
|
|
|
|
L".tif",
|
|
|
|
|
L".tiff"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// If file type doesn't support metadata, no need to check patterns
|
|
|
|
|
if (supportedExtensions.find(extension) == supportedExtensions.end())
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Now check if any metadata pattern exists in the source string
|
|
|
|
|
// This is the most expensive check, so we do it last
|
|
|
|
|
std::wstring str(source);
|
|
|
|
|
|
|
|
|
|
// Get supported patterns for the specified metadata type
|
|
|
|
|
auto supportedPatterns = PowerRenameLib::MetadataPatternExtractor::GetSupportedPatterns(metadataType);
|
|
|
|
|
|
|
|
|
|
// Check if any metadata pattern exists in the source string
|
|
|
|
|
for (const auto& pattern : supportedPatterns)
|
|
|
|
|
{
|
|
|
|
|
std::wstring searchPattern = L"$" + pattern;
|
|
|
|
|
if (str.find(searchPattern) != std::wstring::npos)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// No metadata pattern found
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
HRESULT GetDatedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR source, SYSTEMTIME fileTime)
|
2020-07-16 14:24:49 +03:00
|
|
|
{
|
2020-08-20 02:53:09 +03:00
|
|
|
std::locale::global(std::locale(""));
|
2023-08-14 16:53:49 +02:00
|
|
|
HRESULT hr = E_INVALIDARG;
|
2020-12-14 12:28:12 +03:00
|
|
|
if (source && wcslen(source) > 0)
|
2020-07-16 14:24:49 +03:00
|
|
|
{
|
|
|
|
|
std::wstring res(source);
|
2020-08-20 02:53:09 +03:00
|
|
|
wchar_t replaceTerm[MAX_PATH] = { 0 };
|
|
|
|
|
wchar_t formattedDate[MAX_PATH] = { 0 };
|
|
|
|
|
|
|
|
|
|
wchar_t localeName[LOCALE_NAME_MAX_LENGTH];
|
|
|
|
|
if (GetUserDefaultLocaleName(localeName, LOCALE_NAME_MAX_LENGTH) == 0)
|
|
|
|
|
{
|
|
|
|
|
StringCchCopy(localeName, LOCALE_NAME_MAX_LENGTH, L"en_US");
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-16 07:45:03 +05:30
|
|
|
int hour12 = (fileTime.wHour % 12);
|
|
|
|
|
if (hour12 == 0)
|
|
|
|
|
{
|
|
|
|
|
hour12 = 12;
|
|
|
|
|
}
|
|
|
|
|
|
[PowerRename] Fix date replacement tokens failing if followed by a capital letter (#44267)
## Summary of the Pull Request
Fixes date/time-related replacement tokens being rejected if they were
followed by capital letters in the user's replacement string.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [x] Closes: #44202
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx
<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
With the addition of image metadata replacement options, a strict
negative lookahead was added to the date replacement regular expressions
to prevent conflicts. This was required because, for example, `$D` would
otherwise match before `$DATE_TAKEN_YYYY`. Metadata and date-related
replacements are executed separately at the moment, so this awareness of
each other is required.
However, the negative lookahead was far too aggressive:
- It used `(?![A-Z])`, meaning any capital letter after the date token
would reject the match entirely. This caused the problem referred to in
the linked issue, where `$DDT` was rejected instead of matching to the
`$DD` replacement followed by a verbatim `T` character.
- It was applied to the majority of fields, whereas it is only actually
needed where date tokens are prefixes of metadata tokens. Only `$D` and
`$H` are affected.
- There was no need to apply negative lookups to catch 'self-matches'
like preventing `$D`, `$DD`, and `$DDD` from matching when `$DDDD` was
in the replacement string. Instead, the order of processing already
matches the longest token first, so this could never happen.
To fix these issues, I:
- Removed the majority of the negative lookaheads.
- Made the remaining negative lookaheads only match actual conflicting
suffixes, e.g. `$D(?!(ATE_TAKEN_|ESCRIPTION|OCUMENT_ID))` instead of
`$D(?![A-Z])`. This makes mistaken rejections of user-supplied
replacement strings much more unlikely.
Please note: there remain inherent issues with the current token
replacement approach. Tackling these will require a more extensive
refactoring PR which separates replacement string tokenization from
matching and replacement, and which tackles both image metadata and file
date metadata in a unified manner.
## Validation Steps Performed
- Corrected unit tests which classified, for example, `$YYY` as invalid,
instead of identifying it as a valid `$YY` token + verbatim capital Y
replacement.
- Wrote new unit tests to exercise the refined negative lookaheads.
- Wrote tests to confirm certain negative lookaheads were not required
because the order of processing guaranteed the longest match happens
before any prefix matches.
- Wrote a unit test to exercise the specific issue raised in #44202.
- Confirmed that all new and pre-existing PowerRename tests pass.
2025-12-17 08:54:58 +00:00
|
|
|
// Order matters. Longer patterns are processed before any prefixes.
|
|
|
|
|
// Years.
|
2020-12-14 12:28:12 +03:00
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%04d"), L"$01", fileTime.wYear);
|
2020-08-20 02:53:09 +03:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$YYYY"), replaceTerm);
|
|
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", (fileTime.wYear % 100));
|
[PowerRename] Fix date replacement tokens failing if followed by a capital letter (#44267)
## Summary of the Pull Request
Fixes date/time-related replacement tokens being rejected if they were
followed by capital letters in the user's replacement string.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [x] Closes: #44202
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx
<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
With the addition of image metadata replacement options, a strict
negative lookahead was added to the date replacement regular expressions
to prevent conflicts. This was required because, for example, `$D` would
otherwise match before `$DATE_TAKEN_YYYY`. Metadata and date-related
replacements are executed separately at the moment, so this awareness of
each other is required.
However, the negative lookahead was far too aggressive:
- It used `(?![A-Z])`, meaning any capital letter after the date token
would reject the match entirely. This caused the problem referred to in
the linked issue, where `$DDT` was rejected instead of matching to the
`$DD` replacement followed by a verbatim `T` character.
- It was applied to the majority of fields, whereas it is only actually
needed where date tokens are prefixes of metadata tokens. Only `$D` and
`$H` are affected.
- There was no need to apply negative lookups to catch 'self-matches'
like preventing `$D`, `$DD`, and `$DDD` from matching when `$DDDD` was
in the replacement string. Instead, the order of processing already
matches the longest token first, so this could never happen.
To fix these issues, I:
- Removed the majority of the negative lookaheads.
- Made the remaining negative lookaheads only match actual conflicting
suffixes, e.g. `$D(?!(ATE_TAKEN_|ESCRIPTION|OCUMENT_ID))` instead of
`$D(?![A-Z])`. This makes mistaken rejections of user-supplied
replacement strings much more unlikely.
Please note: there remain inherent issues with the current token
replacement approach. Tackling these will require a more extensive
refactoring PR which separates replacement string tokenization from
matching and replacement, and which tackles both image metadata and file
date metadata in a unified manner.
## Validation Steps Performed
- Corrected unit tests which classified, for example, `$YYY` as invalid,
instead of identifying it as a valid `$YY` token + verbatim capital Y
replacement.
- Wrote new unit tests to exercise the refined negative lookaheads.
- Wrote tests to confirm certain negative lookaheads were not required
because the order of processing guaranteed the longest match happens
before any prefix matches.
- Wrote a unit test to exercise the specific issue raised in #44202.
- Confirmed that all new and pre-existing PowerRename tests pass.
2025-12-17 08:54:58 +00:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$YY"), replaceTerm);
|
2020-08-20 02:53:09 +03:00
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", (fileTime.wYear % 10));
|
[PowerRename] Fix date replacement tokens failing if followed by a capital letter (#44267)
## Summary of the Pull Request
Fixes date/time-related replacement tokens being rejected if they were
followed by capital letters in the user's replacement string.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [x] Closes: #44202
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx
<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
With the addition of image metadata replacement options, a strict
negative lookahead was added to the date replacement regular expressions
to prevent conflicts. This was required because, for example, `$D` would
otherwise match before `$DATE_TAKEN_YYYY`. Metadata and date-related
replacements are executed separately at the moment, so this awareness of
each other is required.
However, the negative lookahead was far too aggressive:
- It used `(?![A-Z])`, meaning any capital letter after the date token
would reject the match entirely. This caused the problem referred to in
the linked issue, where `$DDT` was rejected instead of matching to the
`$DD` replacement followed by a verbatim `T` character.
- It was applied to the majority of fields, whereas it is only actually
needed where date tokens are prefixes of metadata tokens. Only `$D` and
`$H` are affected.
- There was no need to apply negative lookups to catch 'self-matches'
like preventing `$D`, `$DD`, and `$DDD` from matching when `$DDDD` was
in the replacement string. Instead, the order of processing already
matches the longest token first, so this could never happen.
To fix these issues, I:
- Removed the majority of the negative lookaheads.
- Made the remaining negative lookaheads only match actual conflicting
suffixes, e.g. `$D(?!(ATE_TAKEN_|ESCRIPTION|OCUMENT_ID))` instead of
`$D(?![A-Z])`. This makes mistaken rejections of user-supplied
replacement strings much more unlikely.
Please note: there remain inherent issues with the current token
replacement approach. Tackling these will require a more extensive
refactoring PR which separates replacement string tokenization from
matching and replacement, and which tackles both image metadata and file
date metadata in a unified manner.
## Validation Steps Performed
- Corrected unit tests which classified, for example, `$YYY` as invalid,
instead of identifying it as a valid `$YY` token + verbatim capital Y
replacement.
- Wrote new unit tests to exercise the refined negative lookaheads.
- Wrote tests to confirm certain negative lookaheads were not required
because the order of processing guaranteed the longest match happens
before any prefix matches.
- Wrote a unit test to exercise the specific issue raised in #44202.
- Confirmed that all new and pre-existing PowerRename tests pass.
2025-12-17 08:54:58 +00:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$Y"), replaceTerm);
|
2023-08-14 16:53:49 +02:00
|
|
|
|
[PowerRename] Fix date replacement tokens failing if followed by a capital letter (#44267)
## Summary of the Pull Request
Fixes date/time-related replacement tokens being rejected if they were
followed by capital letters in the user's replacement string.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [x] Closes: #44202
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx
<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
With the addition of image metadata replacement options, a strict
negative lookahead was added to the date replacement regular expressions
to prevent conflicts. This was required because, for example, `$D` would
otherwise match before `$DATE_TAKEN_YYYY`. Metadata and date-related
replacements are executed separately at the moment, so this awareness of
each other is required.
However, the negative lookahead was far too aggressive:
- It used `(?![A-Z])`, meaning any capital letter after the date token
would reject the match entirely. This caused the problem referred to in
the linked issue, where `$DDT` was rejected instead of matching to the
`$DD` replacement followed by a verbatim `T` character.
- It was applied to the majority of fields, whereas it is only actually
needed where date tokens are prefixes of metadata tokens. Only `$D` and
`$H` are affected.
- There was no need to apply negative lookups to catch 'self-matches'
like preventing `$D`, `$DD`, and `$DDD` from matching when `$DDDD` was
in the replacement string. Instead, the order of processing already
matches the longest token first, so this could never happen.
To fix these issues, I:
- Removed the majority of the negative lookaheads.
- Made the remaining negative lookaheads only match actual conflicting
suffixes, e.g. `$D(?!(ATE_TAKEN_|ESCRIPTION|OCUMENT_ID))` instead of
`$D(?![A-Z])`. This makes mistaken rejections of user-supplied
replacement strings much more unlikely.
Please note: there remain inherent issues with the current token
replacement approach. Tackling these will require a more extensive
refactoring PR which separates replacement string tokenization from
matching and replacement, and which tackles both image metadata and file
date metadata in a unified manner.
## Validation Steps Performed
- Corrected unit tests which classified, for example, `$YYY` as invalid,
instead of identifying it as a valid `$YY` token + verbatim capital Y
replacement.
- Wrote new unit tests to exercise the refined negative lookaheads.
- Wrote tests to confirm certain negative lookaheads were not required
because the order of processing guaranteed the longest match happens
before any prefix matches.
- Wrote a unit test to exercise the specific issue raised in #44202.
- Confirmed that all new and pre-existing PowerRename tests pass.
2025-12-17 08:54:58 +00:00
|
|
|
// Months.
|
2020-12-14 12:28:12 +03:00
|
|
|
GetDateFormatEx(localeName, NULL, &fileTime, L"MMMM", formattedDate, MAX_PATH, NULL);
|
2020-08-20 02:53:09 +03:00
|
|
|
formattedDate[0] = towupper(formattedDate[0]);
|
|
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", formattedDate);
|
|
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$MMMM"), replaceTerm);
|
2023-08-14 16:53:49 +02:00
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
GetDateFormatEx(localeName, NULL, &fileTime, L"MMM", formattedDate, MAX_PATH, NULL);
|
2020-08-20 02:53:09 +03:00
|
|
|
formattedDate[0] = towupper(formattedDate[0]);
|
|
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", formattedDate);
|
[PowerRename] Fix date replacement tokens failing if followed by a capital letter (#44267)
## Summary of the Pull Request
Fixes date/time-related replacement tokens being rejected if they were
followed by capital letters in the user's replacement string.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [x] Closes: #44202
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx
<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
With the addition of image metadata replacement options, a strict
negative lookahead was added to the date replacement regular expressions
to prevent conflicts. This was required because, for example, `$D` would
otherwise match before `$DATE_TAKEN_YYYY`. Metadata and date-related
replacements are executed separately at the moment, so this awareness of
each other is required.
However, the negative lookahead was far too aggressive:
- It used `(?![A-Z])`, meaning any capital letter after the date token
would reject the match entirely. This caused the problem referred to in
the linked issue, where `$DDT` was rejected instead of matching to the
`$DD` replacement followed by a verbatim `T` character.
- It was applied to the majority of fields, whereas it is only actually
needed where date tokens are prefixes of metadata tokens. Only `$D` and
`$H` are affected.
- There was no need to apply negative lookups to catch 'self-matches'
like preventing `$D`, `$DD`, and `$DDD` from matching when `$DDDD` was
in the replacement string. Instead, the order of processing already
matches the longest token first, so this could never happen.
To fix these issues, I:
- Removed the majority of the negative lookaheads.
- Made the remaining negative lookaheads only match actual conflicting
suffixes, e.g. `$D(?!(ATE_TAKEN_|ESCRIPTION|OCUMENT_ID))` instead of
`$D(?![A-Z])`. This makes mistaken rejections of user-supplied
replacement strings much more unlikely.
Please note: there remain inherent issues with the current token
replacement approach. Tackling these will require a more extensive
refactoring PR which separates replacement string tokenization from
matching and replacement, and which tackles both image metadata and file
date metadata in a unified manner.
## Validation Steps Performed
- Corrected unit tests which classified, for example, `$YYY` as invalid,
instead of identifying it as a valid `$YY` token + verbatim capital Y
replacement.
- Wrote new unit tests to exercise the refined negative lookaheads.
- Wrote tests to confirm certain negative lookaheads were not required
because the order of processing guaranteed the longest match happens
before any prefix matches.
- Wrote a unit test to exercise the specific issue raised in #44202.
- Confirmed that all new and pre-existing PowerRename tests pass.
2025-12-17 08:54:58 +00:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$MMM"), replaceTerm);
|
2023-08-14 16:53:49 +02:00
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", fileTime.wMonth);
|
[PowerRename] Fix date replacement tokens failing if followed by a capital letter (#44267)
## Summary of the Pull Request
Fixes date/time-related replacement tokens being rejected if they were
followed by capital letters in the user's replacement string.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [x] Closes: #44202
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx
<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
With the addition of image metadata replacement options, a strict
negative lookahead was added to the date replacement regular expressions
to prevent conflicts. This was required because, for example, `$D` would
otherwise match before `$DATE_TAKEN_YYYY`. Metadata and date-related
replacements are executed separately at the moment, so this awareness of
each other is required.
However, the negative lookahead was far too aggressive:
- It used `(?![A-Z])`, meaning any capital letter after the date token
would reject the match entirely. This caused the problem referred to in
the linked issue, where `$DDT` was rejected instead of matching to the
`$DD` replacement followed by a verbatim `T` character.
- It was applied to the majority of fields, whereas it is only actually
needed where date tokens are prefixes of metadata tokens. Only `$D` and
`$H` are affected.
- There was no need to apply negative lookups to catch 'self-matches'
like preventing `$D`, `$DD`, and `$DDD` from matching when `$DDDD` was
in the replacement string. Instead, the order of processing already
matches the longest token first, so this could never happen.
To fix these issues, I:
- Removed the majority of the negative lookaheads.
- Made the remaining negative lookaheads only match actual conflicting
suffixes, e.g. `$D(?!(ATE_TAKEN_|ESCRIPTION|OCUMENT_ID))` instead of
`$D(?![A-Z])`. This makes mistaken rejections of user-supplied
replacement strings much more unlikely.
Please note: there remain inherent issues with the current token
replacement approach. Tackling these will require a more extensive
refactoring PR which separates replacement string tokenization from
matching and replacement, and which tackles both image metadata and file
date metadata in a unified manner.
## Validation Steps Performed
- Corrected unit tests which classified, for example, `$YYY` as invalid,
instead of identifying it as a valid `$YY` token + verbatim capital Y
replacement.
- Wrote new unit tests to exercise the refined negative lookaheads.
- Wrote tests to confirm certain negative lookaheads were not required
because the order of processing guaranteed the longest match happens
before any prefix matches.
- Wrote a unit test to exercise the specific issue raised in #44202.
- Confirmed that all new and pre-existing PowerRename tests pass.
2025-12-17 08:54:58 +00:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$MM"), replaceTerm);
|
2020-08-20 02:53:09 +03:00
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", fileTime.wMonth);
|
[PowerRename] Fix date replacement tokens failing if followed by a capital letter (#44267)
## Summary of the Pull Request
Fixes date/time-related replacement tokens being rejected if they were
followed by capital letters in the user's replacement string.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [x] Closes: #44202
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx
<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
With the addition of image metadata replacement options, a strict
negative lookahead was added to the date replacement regular expressions
to prevent conflicts. This was required because, for example, `$D` would
otherwise match before `$DATE_TAKEN_YYYY`. Metadata and date-related
replacements are executed separately at the moment, so this awareness of
each other is required.
However, the negative lookahead was far too aggressive:
- It used `(?![A-Z])`, meaning any capital letter after the date token
would reject the match entirely. This caused the problem referred to in
the linked issue, where `$DDT` was rejected instead of matching to the
`$DD` replacement followed by a verbatim `T` character.
- It was applied to the majority of fields, whereas it is only actually
needed where date tokens are prefixes of metadata tokens. Only `$D` and
`$H` are affected.
- There was no need to apply negative lookups to catch 'self-matches'
like preventing `$D`, `$DD`, and `$DDD` from matching when `$DDDD` was
in the replacement string. Instead, the order of processing already
matches the longest token first, so this could never happen.
To fix these issues, I:
- Removed the majority of the negative lookaheads.
- Made the remaining negative lookaheads only match actual conflicting
suffixes, e.g. `$D(?!(ATE_TAKEN_|ESCRIPTION|OCUMENT_ID))` instead of
`$D(?![A-Z])`. This makes mistaken rejections of user-supplied
replacement strings much more unlikely.
Please note: there remain inherent issues with the current token
replacement approach. Tackling these will require a more extensive
refactoring PR which separates replacement string tokenization from
matching and replacement, and which tackles both image metadata and file
date metadata in a unified manner.
## Validation Steps Performed
- Corrected unit tests which classified, for example, `$YYY` as invalid,
instead of identifying it as a valid `$YY` token + verbatim capital Y
replacement.
- Wrote new unit tests to exercise the refined negative lookaheads.
- Wrote tests to confirm certain negative lookaheads were not required
because the order of processing guaranteed the longest match happens
before any prefix matches.
- Wrote a unit test to exercise the specific issue raised in #44202.
- Confirmed that all new and pre-existing PowerRename tests pass.
2025-12-17 08:54:58 +00:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$M"), replaceTerm);
|
2020-08-20 02:53:09 +03:00
|
|
|
|
[PowerRename] Fix date replacement tokens failing if followed by a capital letter (#44267)
## Summary of the Pull Request
Fixes date/time-related replacement tokens being rejected if they were
followed by capital letters in the user's replacement string.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [x] Closes: #44202
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx
<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
With the addition of image metadata replacement options, a strict
negative lookahead was added to the date replacement regular expressions
to prevent conflicts. This was required because, for example, `$D` would
otherwise match before `$DATE_TAKEN_YYYY`. Metadata and date-related
replacements are executed separately at the moment, so this awareness of
each other is required.
However, the negative lookahead was far too aggressive:
- It used `(?![A-Z])`, meaning any capital letter after the date token
would reject the match entirely. This caused the problem referred to in
the linked issue, where `$DDT` was rejected instead of matching to the
`$DD` replacement followed by a verbatim `T` character.
- It was applied to the majority of fields, whereas it is only actually
needed where date tokens are prefixes of metadata tokens. Only `$D` and
`$H` are affected.
- There was no need to apply negative lookups to catch 'self-matches'
like preventing `$D`, `$DD`, and `$DDD` from matching when `$DDDD` was
in the replacement string. Instead, the order of processing already
matches the longest token first, so this could never happen.
To fix these issues, I:
- Removed the majority of the negative lookaheads.
- Made the remaining negative lookaheads only match actual conflicting
suffixes, e.g. `$D(?!(ATE_TAKEN_|ESCRIPTION|OCUMENT_ID))` instead of
`$D(?![A-Z])`. This makes mistaken rejections of user-supplied
replacement strings much more unlikely.
Please note: there remain inherent issues with the current token
replacement approach. Tackling these will require a more extensive
refactoring PR which separates replacement string tokenization from
matching and replacement, and which tackles both image metadata and file
date metadata in a unified manner.
## Validation Steps Performed
- Corrected unit tests which classified, for example, `$YYY` as invalid,
instead of identifying it as a valid `$YY` token + verbatim capital Y
replacement.
- Wrote new unit tests to exercise the refined negative lookaheads.
- Wrote tests to confirm certain negative lookaheads were not required
because the order of processing guaranteed the longest match happens
before any prefix matches.
- Wrote a unit test to exercise the specific issue raised in #44202.
- Confirmed that all new and pre-existing PowerRename tests pass.
2025-12-17 08:54:58 +00:00
|
|
|
// Days.
|
2020-12-14 12:28:12 +03:00
|
|
|
GetDateFormatEx(localeName, NULL, &fileTime, L"dddd", formattedDate, MAX_PATH, NULL);
|
2020-08-20 02:53:09 +03:00
|
|
|
formattedDate[0] = towupper(formattedDate[0]);
|
|
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", formattedDate);
|
|
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$DDDD"), replaceTerm);
|
2023-08-14 16:53:49 +02:00
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
GetDateFormatEx(localeName, NULL, &fileTime, L"ddd", formattedDate, MAX_PATH, NULL);
|
2020-08-20 02:53:09 +03:00
|
|
|
formattedDate[0] = towupper(formattedDate[0]);
|
|
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", formattedDate);
|
[PowerRename] Fix date replacement tokens failing if followed by a capital letter (#44267)
## Summary of the Pull Request
Fixes date/time-related replacement tokens being rejected if they were
followed by capital letters in the user's replacement string.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [x] Closes: #44202
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx
<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
With the addition of image metadata replacement options, a strict
negative lookahead was added to the date replacement regular expressions
to prevent conflicts. This was required because, for example, `$D` would
otherwise match before `$DATE_TAKEN_YYYY`. Metadata and date-related
replacements are executed separately at the moment, so this awareness of
each other is required.
However, the negative lookahead was far too aggressive:
- It used `(?![A-Z])`, meaning any capital letter after the date token
would reject the match entirely. This caused the problem referred to in
the linked issue, where `$DDT` was rejected instead of matching to the
`$DD` replacement followed by a verbatim `T` character.
- It was applied to the majority of fields, whereas it is only actually
needed where date tokens are prefixes of metadata tokens. Only `$D` and
`$H` are affected.
- There was no need to apply negative lookups to catch 'self-matches'
like preventing `$D`, `$DD`, and `$DDD` from matching when `$DDDD` was
in the replacement string. Instead, the order of processing already
matches the longest token first, so this could never happen.
To fix these issues, I:
- Removed the majority of the negative lookaheads.
- Made the remaining negative lookaheads only match actual conflicting
suffixes, e.g. `$D(?!(ATE_TAKEN_|ESCRIPTION|OCUMENT_ID))` instead of
`$D(?![A-Z])`. This makes mistaken rejections of user-supplied
replacement strings much more unlikely.
Please note: there remain inherent issues with the current token
replacement approach. Tackling these will require a more extensive
refactoring PR which separates replacement string tokenization from
matching and replacement, and which tackles both image metadata and file
date metadata in a unified manner.
## Validation Steps Performed
- Corrected unit tests which classified, for example, `$YYY` as invalid,
instead of identifying it as a valid `$YY` token + verbatim capital Y
replacement.
- Wrote new unit tests to exercise the refined negative lookaheads.
- Wrote tests to confirm certain negative lookaheads were not required
because the order of processing guaranteed the longest match happens
before any prefix matches.
- Wrote a unit test to exercise the specific issue raised in #44202.
- Confirmed that all new and pre-existing PowerRename tests pass.
2025-12-17 08:54:58 +00:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$DDD"), replaceTerm);
|
2020-08-20 02:53:09 +03:00
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", fileTime.wDay);
|
[PowerRename] Fix date replacement tokens failing if followed by a capital letter (#44267)
## Summary of the Pull Request
Fixes date/time-related replacement tokens being rejected if they were
followed by capital letters in the user's replacement string.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [x] Closes: #44202
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx
<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
With the addition of image metadata replacement options, a strict
negative lookahead was added to the date replacement regular expressions
to prevent conflicts. This was required because, for example, `$D` would
otherwise match before `$DATE_TAKEN_YYYY`. Metadata and date-related
replacements are executed separately at the moment, so this awareness of
each other is required.
However, the negative lookahead was far too aggressive:
- It used `(?![A-Z])`, meaning any capital letter after the date token
would reject the match entirely. This caused the problem referred to in
the linked issue, where `$DDT` was rejected instead of matching to the
`$DD` replacement followed by a verbatim `T` character.
- It was applied to the majority of fields, whereas it is only actually
needed where date tokens are prefixes of metadata tokens. Only `$D` and
`$H` are affected.
- There was no need to apply negative lookups to catch 'self-matches'
like preventing `$D`, `$DD`, and `$DDD` from matching when `$DDDD` was
in the replacement string. Instead, the order of processing already
matches the longest token first, so this could never happen.
To fix these issues, I:
- Removed the majority of the negative lookaheads.
- Made the remaining negative lookaheads only match actual conflicting
suffixes, e.g. `$D(?!(ATE_TAKEN_|ESCRIPTION|OCUMENT_ID))` instead of
`$D(?![A-Z])`. This makes mistaken rejections of user-supplied
replacement strings much more unlikely.
Please note: there remain inherent issues with the current token
replacement approach. Tackling these will require a more extensive
refactoring PR which separates replacement string tokenization from
matching and replacement, and which tackles both image metadata and file
date metadata in a unified manner.
## Validation Steps Performed
- Corrected unit tests which classified, for example, `$YYY` as invalid,
instead of identifying it as a valid `$YY` token + verbatim capital Y
replacement.
- Wrote new unit tests to exercise the refined negative lookaheads.
- Wrote tests to confirm certain negative lookaheads were not required
because the order of processing guaranteed the longest match happens
before any prefix matches.
- Wrote a unit test to exercise the specific issue raised in #44202.
- Confirmed that all new and pre-existing PowerRename tests pass.
2025-12-17 08:54:58 +00:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$DD"), replaceTerm);
|
2020-07-16 14:24:49 +03:00
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", fileTime.wDay);
|
[PowerRename] Fix date replacement tokens failing if followed by a capital letter (#44267)
## Summary of the Pull Request
Fixes date/time-related replacement tokens being rejected if they were
followed by capital letters in the user's replacement string.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [x] Closes: #44202
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx
<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
With the addition of image metadata replacement options, a strict
negative lookahead was added to the date replacement regular expressions
to prevent conflicts. This was required because, for example, `$D` would
otherwise match before `$DATE_TAKEN_YYYY`. Metadata and date-related
replacements are executed separately at the moment, so this awareness of
each other is required.
However, the negative lookahead was far too aggressive:
- It used `(?![A-Z])`, meaning any capital letter after the date token
would reject the match entirely. This caused the problem referred to in
the linked issue, where `$DDT` was rejected instead of matching to the
`$DD` replacement followed by a verbatim `T` character.
- It was applied to the majority of fields, whereas it is only actually
needed where date tokens are prefixes of metadata tokens. Only `$D` and
`$H` are affected.
- There was no need to apply negative lookups to catch 'self-matches'
like preventing `$D`, `$DD`, and `$DDD` from matching when `$DDDD` was
in the replacement string. Instead, the order of processing already
matches the longest token first, so this could never happen.
To fix these issues, I:
- Removed the majority of the negative lookaheads.
- Made the remaining negative lookaheads only match actual conflicting
suffixes, e.g. `$D(?!(ATE_TAKEN_|ESCRIPTION|OCUMENT_ID))` instead of
`$D(?![A-Z])`. This makes mistaken rejections of user-supplied
replacement strings much more unlikely.
Please note: there remain inherent issues with the current token
replacement approach. Tackling these will require a more extensive
refactoring PR which separates replacement string tokenization from
matching and replacement, and which tackles both image metadata and file
date metadata in a unified manner.
## Validation Steps Performed
- Corrected unit tests which classified, for example, `$YYY` as invalid,
instead of identifying it as a valid `$YY` token + verbatim capital Y
replacement.
- Wrote new unit tests to exercise the refined negative lookaheads.
- Wrote tests to confirm certain negative lookaheads were not required
because the order of processing guaranteed the longest match happens
before any prefix matches.
- Wrote a unit test to exercise the specific issue raised in #44202.
- Confirmed that all new and pre-existing PowerRename tests pass.
2025-12-17 08:54:58 +00:00
|
|
|
// $D overlaps with metadata patterns like $DATE_TAKEN_YYYY, so we use negative
|
|
|
|
|
// lookahead to prevent matching those.
|
|
|
|
|
res = regex_replace(
|
|
|
|
|
res,
|
|
|
|
|
std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$D(?!(ATE_TAKEN_|ESCRIPTION|OCUMENT_ID))"), /* #no-spell-check-line */
|
|
|
|
|
replaceTerm);
|
|
|
|
|
|
|
|
|
|
// Time.
|
2025-04-16 07:45:03 +05:30
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", hour12);
|
[PowerRename] Fix date replacement tokens failing if followed by a capital letter (#44267)
## Summary of the Pull Request
Fixes date/time-related replacement tokens being rejected if they were
followed by capital letters in the user's replacement string.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [x] Closes: #44202
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx
<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
With the addition of image metadata replacement options, a strict
negative lookahead was added to the date replacement regular expressions
to prevent conflicts. This was required because, for example, `$D` would
otherwise match before `$DATE_TAKEN_YYYY`. Metadata and date-related
replacements are executed separately at the moment, so this awareness of
each other is required.
However, the negative lookahead was far too aggressive:
- It used `(?![A-Z])`, meaning any capital letter after the date token
would reject the match entirely. This caused the problem referred to in
the linked issue, where `$DDT` was rejected instead of matching to the
`$DD` replacement followed by a verbatim `T` character.
- It was applied to the majority of fields, whereas it is only actually
needed where date tokens are prefixes of metadata tokens. Only `$D` and
`$H` are affected.
- There was no need to apply negative lookups to catch 'self-matches'
like preventing `$D`, `$DD`, and `$DDD` from matching when `$DDDD` was
in the replacement string. Instead, the order of processing already
matches the longest token first, so this could never happen.
To fix these issues, I:
- Removed the majority of the negative lookaheads.
- Made the remaining negative lookaheads only match actual conflicting
suffixes, e.g. `$D(?!(ATE_TAKEN_|ESCRIPTION|OCUMENT_ID))` instead of
`$D(?![A-Z])`. This makes mistaken rejections of user-supplied
replacement strings much more unlikely.
Please note: there remain inherent issues with the current token
replacement approach. Tackling these will require a more extensive
refactoring PR which separates replacement string tokenization from
matching and replacement, and which tackles both image metadata and file
date metadata in a unified manner.
## Validation Steps Performed
- Corrected unit tests which classified, for example, `$YYY` as invalid,
instead of identifying it as a valid `$YY` token + verbatim capital Y
replacement.
- Wrote new unit tests to exercise the refined negative lookaheads.
- Wrote tests to confirm certain negative lookaheads were not required
because the order of processing guaranteed the longest match happens
before any prefix matches.
- Wrote a unit test to exercise the specific issue raised in #44202.
- Confirmed that all new and pre-existing PowerRename tests pass.
2025-12-17 08:54:58 +00:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$HH"), replaceTerm);
|
2025-04-16 07:45:03 +05:30
|
|
|
|
|
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", hour12);
|
[PowerRename] Fix date replacement tokens failing if followed by a capital letter (#44267)
## Summary of the Pull Request
Fixes date/time-related replacement tokens being rejected if they were
followed by capital letters in the user's replacement string.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [x] Closes: #44202
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx
<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
With the addition of image metadata replacement options, a strict
negative lookahead was added to the date replacement regular expressions
to prevent conflicts. This was required because, for example, `$D` would
otherwise match before `$DATE_TAKEN_YYYY`. Metadata and date-related
replacements are executed separately at the moment, so this awareness of
each other is required.
However, the negative lookahead was far too aggressive:
- It used `(?![A-Z])`, meaning any capital letter after the date token
would reject the match entirely. This caused the problem referred to in
the linked issue, where `$DDT` was rejected instead of matching to the
`$DD` replacement followed by a verbatim `T` character.
- It was applied to the majority of fields, whereas it is only actually
needed where date tokens are prefixes of metadata tokens. Only `$D` and
`$H` are affected.
- There was no need to apply negative lookups to catch 'self-matches'
like preventing `$D`, `$DD`, and `$DDD` from matching when `$DDDD` was
in the replacement string. Instead, the order of processing already
matches the longest token first, so this could never happen.
To fix these issues, I:
- Removed the majority of the negative lookaheads.
- Made the remaining negative lookaheads only match actual conflicting
suffixes, e.g. `$D(?!(ATE_TAKEN_|ESCRIPTION|OCUMENT_ID))` instead of
`$D(?![A-Z])`. This makes mistaken rejections of user-supplied
replacement strings much more unlikely.
Please note: there remain inherent issues with the current token
replacement approach. Tackling these will require a more extensive
refactoring PR which separates replacement string tokenization from
matching and replacement, and which tackles both image metadata and file
date metadata in a unified manner.
## Validation Steps Performed
- Corrected unit tests which classified, for example, `$YYY` as invalid,
instead of identifying it as a valid `$YY` token + verbatim capital Y
replacement.
- Wrote new unit tests to exercise the refined negative lookaheads.
- Wrote tests to confirm certain negative lookaheads were not required
because the order of processing guaranteed the longest match happens
before any prefix matches.
- Wrote a unit test to exercise the specific issue raised in #44202.
- Confirmed that all new and pre-existing PowerRename tests pass.
2025-12-17 08:54:58 +00:00
|
|
|
// $H overlaps with metadata's $HEIGHT, so we use negative lookahead to prevent
|
|
|
|
|
// matching that.
|
|
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$H(?!(EIGHT))"), replaceTerm);
|
2025-04-16 07:45:03 +05:30
|
|
|
|
|
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", (fileTime.wHour < 12) ? L"AM" : L"PM");
|
|
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$TT"), replaceTerm);
|
|
|
|
|
|
|
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", (fileTime.wHour < 12) ? L"am" : L"pm");
|
|
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$tt"), replaceTerm);
|
|
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", fileTime.wHour);
|
[PowerRename] Fix date replacement tokens failing if followed by a capital letter (#44267)
## Summary of the Pull Request
Fixes date/time-related replacement tokens being rejected if they were
followed by capital letters in the user's replacement string.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [x] Closes: #44202
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx
<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
With the addition of image metadata replacement options, a strict
negative lookahead was added to the date replacement regular expressions
to prevent conflicts. This was required because, for example, `$D` would
otherwise match before `$DATE_TAKEN_YYYY`. Metadata and date-related
replacements are executed separately at the moment, so this awareness of
each other is required.
However, the negative lookahead was far too aggressive:
- It used `(?![A-Z])`, meaning any capital letter after the date token
would reject the match entirely. This caused the problem referred to in
the linked issue, where `$DDT` was rejected instead of matching to the
`$DD` replacement followed by a verbatim `T` character.
- It was applied to the majority of fields, whereas it is only actually
needed where date tokens are prefixes of metadata tokens. Only `$D` and
`$H` are affected.
- There was no need to apply negative lookups to catch 'self-matches'
like preventing `$D`, `$DD`, and `$DDD` from matching when `$DDDD` was
in the replacement string. Instead, the order of processing already
matches the longest token first, so this could never happen.
To fix these issues, I:
- Removed the majority of the negative lookaheads.
- Made the remaining negative lookaheads only match actual conflicting
suffixes, e.g. `$D(?!(ATE_TAKEN_|ESCRIPTION|OCUMENT_ID))` instead of
`$D(?![A-Z])`. This makes mistaken rejections of user-supplied
replacement strings much more unlikely.
Please note: there remain inherent issues with the current token
replacement approach. Tackling these will require a more extensive
refactoring PR which separates replacement string tokenization from
matching and replacement, and which tackles both image metadata and file
date metadata in a unified manner.
## Validation Steps Performed
- Corrected unit tests which classified, for example, `$YYY` as invalid,
instead of identifying it as a valid `$YY` token + verbatim capital Y
replacement.
- Wrote new unit tests to exercise the refined negative lookaheads.
- Wrote tests to confirm certain negative lookaheads were not required
because the order of processing guaranteed the longest match happens
before any prefix matches.
- Wrote a unit test to exercise the specific issue raised in #44202.
- Confirmed that all new and pre-existing PowerRename tests pass.
2025-12-17 08:54:58 +00:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$hh"), replaceTerm);
|
2020-07-16 14:24:49 +03:00
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", fileTime.wHour);
|
[PowerRename] Fix date replacement tokens failing if followed by a capital letter (#44267)
## Summary of the Pull Request
Fixes date/time-related replacement tokens being rejected if they were
followed by capital letters in the user's replacement string.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [x] Closes: #44202
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx
<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
With the addition of image metadata replacement options, a strict
negative lookahead was added to the date replacement regular expressions
to prevent conflicts. This was required because, for example, `$D` would
otherwise match before `$DATE_TAKEN_YYYY`. Metadata and date-related
replacements are executed separately at the moment, so this awareness of
each other is required.
However, the negative lookahead was far too aggressive:
- It used `(?![A-Z])`, meaning any capital letter after the date token
would reject the match entirely. This caused the problem referred to in
the linked issue, where `$DDT` was rejected instead of matching to the
`$DD` replacement followed by a verbatim `T` character.
- It was applied to the majority of fields, whereas it is only actually
needed where date tokens are prefixes of metadata tokens. Only `$D` and
`$H` are affected.
- There was no need to apply negative lookups to catch 'self-matches'
like preventing `$D`, `$DD`, and `$DDD` from matching when `$DDDD` was
in the replacement string. Instead, the order of processing already
matches the longest token first, so this could never happen.
To fix these issues, I:
- Removed the majority of the negative lookaheads.
- Made the remaining negative lookaheads only match actual conflicting
suffixes, e.g. `$D(?!(ATE_TAKEN_|ESCRIPTION|OCUMENT_ID))` instead of
`$D(?![A-Z])`. This makes mistaken rejections of user-supplied
replacement strings much more unlikely.
Please note: there remain inherent issues with the current token
replacement approach. Tackling these will require a more extensive
refactoring PR which separates replacement string tokenization from
matching and replacement, and which tackles both image metadata and file
date metadata in a unified manner.
## Validation Steps Performed
- Corrected unit tests which classified, for example, `$YYY` as invalid,
instead of identifying it as a valid `$YY` token + verbatim capital Y
replacement.
- Wrote new unit tests to exercise the refined negative lookaheads.
- Wrote tests to confirm certain negative lookaheads were not required
because the order of processing guaranteed the longest match happens
before any prefix matches.
- Wrote a unit test to exercise the specific issue raised in #44202.
- Confirmed that all new and pre-existing PowerRename tests pass.
2025-12-17 08:54:58 +00:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$h"), replaceTerm);
|
2020-07-16 14:24:49 +03:00
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", fileTime.wMinute);
|
[PowerRename] Fix date replacement tokens failing if followed by a capital letter (#44267)
## Summary of the Pull Request
Fixes date/time-related replacement tokens being rejected if they were
followed by capital letters in the user's replacement string.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [x] Closes: #44202
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx
<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
With the addition of image metadata replacement options, a strict
negative lookahead was added to the date replacement regular expressions
to prevent conflicts. This was required because, for example, `$D` would
otherwise match before `$DATE_TAKEN_YYYY`. Metadata and date-related
replacements are executed separately at the moment, so this awareness of
each other is required.
However, the negative lookahead was far too aggressive:
- It used `(?![A-Z])`, meaning any capital letter after the date token
would reject the match entirely. This caused the problem referred to in
the linked issue, where `$DDT` was rejected instead of matching to the
`$DD` replacement followed by a verbatim `T` character.
- It was applied to the majority of fields, whereas it is only actually
needed where date tokens are prefixes of metadata tokens. Only `$D` and
`$H` are affected.
- There was no need to apply negative lookups to catch 'self-matches'
like preventing `$D`, `$DD`, and `$DDD` from matching when `$DDDD` was
in the replacement string. Instead, the order of processing already
matches the longest token first, so this could never happen.
To fix these issues, I:
- Removed the majority of the negative lookaheads.
- Made the remaining negative lookaheads only match actual conflicting
suffixes, e.g. `$D(?!(ATE_TAKEN_|ESCRIPTION|OCUMENT_ID))` instead of
`$D(?![A-Z])`. This makes mistaken rejections of user-supplied
replacement strings much more unlikely.
Please note: there remain inherent issues with the current token
replacement approach. Tackling these will require a more extensive
refactoring PR which separates replacement string tokenization from
matching and replacement, and which tackles both image metadata and file
date metadata in a unified manner.
## Validation Steps Performed
- Corrected unit tests which classified, for example, `$YYY` as invalid,
instead of identifying it as a valid `$YY` token + verbatim capital Y
replacement.
- Wrote new unit tests to exercise the refined negative lookaheads.
- Wrote tests to confirm certain negative lookaheads were not required
because the order of processing guaranteed the longest match happens
before any prefix matches.
- Wrote a unit test to exercise the specific issue raised in #44202.
- Confirmed that all new and pre-existing PowerRename tests pass.
2025-12-17 08:54:58 +00:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$mm"), replaceTerm);
|
2020-07-16 14:24:49 +03:00
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", fileTime.wMinute);
|
[PowerRename] Fix date replacement tokens failing if followed by a capital letter (#44267)
## Summary of the Pull Request
Fixes date/time-related replacement tokens being rejected if they were
followed by capital letters in the user's replacement string.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [x] Closes: #44202
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx
<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
With the addition of image metadata replacement options, a strict
negative lookahead was added to the date replacement regular expressions
to prevent conflicts. This was required because, for example, `$D` would
otherwise match before `$DATE_TAKEN_YYYY`. Metadata and date-related
replacements are executed separately at the moment, so this awareness of
each other is required.
However, the negative lookahead was far too aggressive:
- It used `(?![A-Z])`, meaning any capital letter after the date token
would reject the match entirely. This caused the problem referred to in
the linked issue, where `$DDT` was rejected instead of matching to the
`$DD` replacement followed by a verbatim `T` character.
- It was applied to the majority of fields, whereas it is only actually
needed where date tokens are prefixes of metadata tokens. Only `$D` and
`$H` are affected.
- There was no need to apply negative lookups to catch 'self-matches'
like preventing `$D`, `$DD`, and `$DDD` from matching when `$DDDD` was
in the replacement string. Instead, the order of processing already
matches the longest token first, so this could never happen.
To fix these issues, I:
- Removed the majority of the negative lookaheads.
- Made the remaining negative lookaheads only match actual conflicting
suffixes, e.g. `$D(?!(ATE_TAKEN_|ESCRIPTION|OCUMENT_ID))` instead of
`$D(?![A-Z])`. This makes mistaken rejections of user-supplied
replacement strings much more unlikely.
Please note: there remain inherent issues with the current token
replacement approach. Tackling these will require a more extensive
refactoring PR which separates replacement string tokenization from
matching and replacement, and which tackles both image metadata and file
date metadata in a unified manner.
## Validation Steps Performed
- Corrected unit tests which classified, for example, `$YYY` as invalid,
instead of identifying it as a valid `$YY` token + verbatim capital Y
replacement.
- Wrote new unit tests to exercise the refined negative lookaheads.
- Wrote tests to confirm certain negative lookaheads were not required
because the order of processing guaranteed the longest match happens
before any prefix matches.
- Wrote a unit test to exercise the specific issue raised in #44202.
- Confirmed that all new and pre-existing PowerRename tests pass.
2025-12-17 08:54:58 +00:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$m"), replaceTerm);
|
2020-07-16 14:24:49 +03:00
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", fileTime.wSecond);
|
[PowerRename] Fix date replacement tokens failing if followed by a capital letter (#44267)
## Summary of the Pull Request
Fixes date/time-related replacement tokens being rejected if they were
followed by capital letters in the user's replacement string.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [x] Closes: #44202
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx
<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
With the addition of image metadata replacement options, a strict
negative lookahead was added to the date replacement regular expressions
to prevent conflicts. This was required because, for example, `$D` would
otherwise match before `$DATE_TAKEN_YYYY`. Metadata and date-related
replacements are executed separately at the moment, so this awareness of
each other is required.
However, the negative lookahead was far too aggressive:
- It used `(?![A-Z])`, meaning any capital letter after the date token
would reject the match entirely. This caused the problem referred to in
the linked issue, where `$DDT` was rejected instead of matching to the
`$DD` replacement followed by a verbatim `T` character.
- It was applied to the majority of fields, whereas it is only actually
needed where date tokens are prefixes of metadata tokens. Only `$D` and
`$H` are affected.
- There was no need to apply negative lookups to catch 'self-matches'
like preventing `$D`, `$DD`, and `$DDD` from matching when `$DDDD` was
in the replacement string. Instead, the order of processing already
matches the longest token first, so this could never happen.
To fix these issues, I:
- Removed the majority of the negative lookaheads.
- Made the remaining negative lookaheads only match actual conflicting
suffixes, e.g. `$D(?!(ATE_TAKEN_|ESCRIPTION|OCUMENT_ID))` instead of
`$D(?![A-Z])`. This makes mistaken rejections of user-supplied
replacement strings much more unlikely.
Please note: there remain inherent issues with the current token
replacement approach. Tackling these will require a more extensive
refactoring PR which separates replacement string tokenization from
matching and replacement, and which tackles both image metadata and file
date metadata in a unified manner.
## Validation Steps Performed
- Corrected unit tests which classified, for example, `$YYY` as invalid,
instead of identifying it as a valid `$YY` token + verbatim capital Y
replacement.
- Wrote new unit tests to exercise the refined negative lookaheads.
- Wrote tests to confirm certain negative lookaheads were not required
because the order of processing guaranteed the longest match happens
before any prefix matches.
- Wrote a unit test to exercise the specific issue raised in #44202.
- Confirmed that all new and pre-existing PowerRename tests pass.
2025-12-17 08:54:58 +00:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$ss"), replaceTerm);
|
2020-07-16 14:24:49 +03:00
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", fileTime.wSecond);
|
[PowerRename] Fix date replacement tokens failing if followed by a capital letter (#44267)
## Summary of the Pull Request
Fixes date/time-related replacement tokens being rejected if they were
followed by capital letters in the user's replacement string.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [x] Closes: #44202
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx
<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
With the addition of image metadata replacement options, a strict
negative lookahead was added to the date replacement regular expressions
to prevent conflicts. This was required because, for example, `$D` would
otherwise match before `$DATE_TAKEN_YYYY`. Metadata and date-related
replacements are executed separately at the moment, so this awareness of
each other is required.
However, the negative lookahead was far too aggressive:
- It used `(?![A-Z])`, meaning any capital letter after the date token
would reject the match entirely. This caused the problem referred to in
the linked issue, where `$DDT` was rejected instead of matching to the
`$DD` replacement followed by a verbatim `T` character.
- It was applied to the majority of fields, whereas it is only actually
needed where date tokens are prefixes of metadata tokens. Only `$D` and
`$H` are affected.
- There was no need to apply negative lookups to catch 'self-matches'
like preventing `$D`, `$DD`, and `$DDD` from matching when `$DDDD` was
in the replacement string. Instead, the order of processing already
matches the longest token first, so this could never happen.
To fix these issues, I:
- Removed the majority of the negative lookaheads.
- Made the remaining negative lookaheads only match actual conflicting
suffixes, e.g. `$D(?!(ATE_TAKEN_|ESCRIPTION|OCUMENT_ID))` instead of
`$D(?![A-Z])`. This makes mistaken rejections of user-supplied
replacement strings much more unlikely.
Please note: there remain inherent issues with the current token
replacement approach. Tackling these will require a more extensive
refactoring PR which separates replacement string tokenization from
matching and replacement, and which tackles both image metadata and file
date metadata in a unified manner.
## Validation Steps Performed
- Corrected unit tests which classified, for example, `$YYY` as invalid,
instead of identifying it as a valid `$YY` token + verbatim capital Y
replacement.
- Wrote new unit tests to exercise the refined negative lookaheads.
- Wrote tests to confirm certain negative lookaheads were not required
because the order of processing guaranteed the longest match happens
before any prefix matches.
- Wrote a unit test to exercise the specific issue raised in #44202.
- Confirmed that all new and pre-existing PowerRename tests pass.
2025-12-17 08:54:58 +00:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$s"), replaceTerm);
|
2020-07-16 14:24:49 +03:00
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%03d"), L"$01", fileTime.wMilliseconds);
|
[PowerRename] Fix date replacement tokens failing if followed by a capital letter (#44267)
## Summary of the Pull Request
Fixes date/time-related replacement tokens being rejected if they were
followed by capital letters in the user's replacement string.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [x] Closes: #44202
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx
<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
With the addition of image metadata replacement options, a strict
negative lookahead was added to the date replacement regular expressions
to prevent conflicts. This was required because, for example, `$D` would
otherwise match before `$DATE_TAKEN_YYYY`. Metadata and date-related
replacements are executed separately at the moment, so this awareness of
each other is required.
However, the negative lookahead was far too aggressive:
- It used `(?![A-Z])`, meaning any capital letter after the date token
would reject the match entirely. This caused the problem referred to in
the linked issue, where `$DDT` was rejected instead of matching to the
`$DD` replacement followed by a verbatim `T` character.
- It was applied to the majority of fields, whereas it is only actually
needed where date tokens are prefixes of metadata tokens. Only `$D` and
`$H` are affected.
- There was no need to apply negative lookups to catch 'self-matches'
like preventing `$D`, `$DD`, and `$DDD` from matching when `$DDDD` was
in the replacement string. Instead, the order of processing already
matches the longest token first, so this could never happen.
To fix these issues, I:
- Removed the majority of the negative lookaheads.
- Made the remaining negative lookaheads only match actual conflicting
suffixes, e.g. `$D(?!(ATE_TAKEN_|ESCRIPTION|OCUMENT_ID))` instead of
`$D(?![A-Z])`. This makes mistaken rejections of user-supplied
replacement strings much more unlikely.
Please note: there remain inherent issues with the current token
replacement approach. Tackling these will require a more extensive
refactoring PR which separates replacement string tokenization from
matching and replacement, and which tackles both image metadata and file
date metadata in a unified manner.
## Validation Steps Performed
- Corrected unit tests which classified, for example, `$YYY` as invalid,
instead of identifying it as a valid `$YY` token + verbatim capital Y
replacement.
- Wrote new unit tests to exercise the refined negative lookaheads.
- Wrote tests to confirm certain negative lookaheads were not required
because the order of processing guaranteed the longest match happens
before any prefix matches.
- Wrote a unit test to exercise the specific issue raised in #44202.
- Confirmed that all new and pre-existing PowerRename tests pass.
2025-12-17 08:54:58 +00:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$fff"), replaceTerm);
|
2020-07-16 14:24:49 +03:00
|
|
|
|
2023-08-14 16:53:49 +02:00
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", fileTime.wMilliseconds / 10);
|
[PowerRename] Fix date replacement tokens failing if followed by a capital letter (#44267)
## Summary of the Pull Request
Fixes date/time-related replacement tokens being rejected if they were
followed by capital letters in the user's replacement string.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [x] Closes: #44202
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx
<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
With the addition of image metadata replacement options, a strict
negative lookahead was added to the date replacement regular expressions
to prevent conflicts. This was required because, for example, `$D` would
otherwise match before `$DATE_TAKEN_YYYY`. Metadata and date-related
replacements are executed separately at the moment, so this awareness of
each other is required.
However, the negative lookahead was far too aggressive:
- It used `(?![A-Z])`, meaning any capital letter after the date token
would reject the match entirely. This caused the problem referred to in
the linked issue, where `$DDT` was rejected instead of matching to the
`$DD` replacement followed by a verbatim `T` character.
- It was applied to the majority of fields, whereas it is only actually
needed where date tokens are prefixes of metadata tokens. Only `$D` and
`$H` are affected.
- There was no need to apply negative lookups to catch 'self-matches'
like preventing `$D`, `$DD`, and `$DDD` from matching when `$DDDD` was
in the replacement string. Instead, the order of processing already
matches the longest token first, so this could never happen.
To fix these issues, I:
- Removed the majority of the negative lookaheads.
- Made the remaining negative lookaheads only match actual conflicting
suffixes, e.g. `$D(?!(ATE_TAKEN_|ESCRIPTION|OCUMENT_ID))` instead of
`$D(?![A-Z])`. This makes mistaken rejections of user-supplied
replacement strings much more unlikely.
Please note: there remain inherent issues with the current token
replacement approach. Tackling these will require a more extensive
refactoring PR which separates replacement string tokenization from
matching and replacement, and which tackles both image metadata and file
date metadata in a unified manner.
## Validation Steps Performed
- Corrected unit tests which classified, for example, `$YYY` as invalid,
instead of identifying it as a valid `$YY` token + verbatim capital Y
replacement.
- Wrote new unit tests to exercise the refined negative lookaheads.
- Wrote tests to confirm certain negative lookaheads were not required
because the order of processing guaranteed the longest match happens
before any prefix matches.
- Wrote a unit test to exercise the specific issue raised in #44202.
- Confirmed that all new and pre-existing PowerRename tests pass.
2025-12-17 08:54:58 +00:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$ff"), replaceTerm);
|
2020-07-16 14:24:49 +03:00
|
|
|
|
2023-08-14 16:53:49 +02:00
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", fileTime.wMilliseconds / 100);
|
[PowerRename] Fix date replacement tokens failing if followed by a capital letter (#44267)
## Summary of the Pull Request
Fixes date/time-related replacement tokens being rejected if they were
followed by capital letters in the user's replacement string.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [x] Closes: #44202
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx
<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
With the addition of image metadata replacement options, a strict
negative lookahead was added to the date replacement regular expressions
to prevent conflicts. This was required because, for example, `$D` would
otherwise match before `$DATE_TAKEN_YYYY`. Metadata and date-related
replacements are executed separately at the moment, so this awareness of
each other is required.
However, the negative lookahead was far too aggressive:
- It used `(?![A-Z])`, meaning any capital letter after the date token
would reject the match entirely. This caused the problem referred to in
the linked issue, where `$DDT` was rejected instead of matching to the
`$DD` replacement followed by a verbatim `T` character.
- It was applied to the majority of fields, whereas it is only actually
needed where date tokens are prefixes of metadata tokens. Only `$D` and
`$H` are affected.
- There was no need to apply negative lookups to catch 'self-matches'
like preventing `$D`, `$DD`, and `$DDD` from matching when `$DDDD` was
in the replacement string. Instead, the order of processing already
matches the longest token first, so this could never happen.
To fix these issues, I:
- Removed the majority of the negative lookaheads.
- Made the remaining negative lookaheads only match actual conflicting
suffixes, e.g. `$D(?!(ATE_TAKEN_|ESCRIPTION|OCUMENT_ID))` instead of
`$D(?![A-Z])`. This makes mistaken rejections of user-supplied
replacement strings much more unlikely.
Please note: there remain inherent issues with the current token
replacement approach. Tackling these will require a more extensive
refactoring PR which separates replacement string tokenization from
matching and replacement, and which tackles both image metadata and file
date metadata in a unified manner.
## Validation Steps Performed
- Corrected unit tests which classified, for example, `$YYY` as invalid,
instead of identifying it as a valid `$YY` token + verbatim capital Y
replacement.
- Wrote new unit tests to exercise the refined negative lookaheads.
- Wrote tests to confirm certain negative lookaheads were not required
because the order of processing guaranteed the longest match happens
before any prefix matches.
- Wrote a unit test to exercise the specific issue raised in #44202.
- Confirmed that all new and pre-existing PowerRename tests pass.
2025-12-17 08:54:58 +00:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$f"), replaceTerm);
|
2020-07-16 14:24:49 +03:00
|
|
|
|
|
|
|
|
hr = StringCchCopy(result, cchMax, res.c_str());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return hr;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-04 09:27:16 +08:00
|
|
|
HRESULT GetMetadataFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR source, const PowerRenameLib::MetadataPatternMap& patterns)
|
|
|
|
|
{
|
|
|
|
|
if (!source || wcslen(source) == 0)
|
|
|
|
|
{
|
|
|
|
|
return E_INVALIDARG;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::wstring input(source);
|
|
|
|
|
std::wstring output;
|
|
|
|
|
output.reserve(input.length() * 2); // Reserve space to avoid frequent reallocations
|
|
|
|
|
|
|
|
|
|
// Build pattern lookup table for fast validation
|
|
|
|
|
// Using all possible patterns to recognize valid pattern names even when metadata is unavailable
|
|
|
|
|
auto allPatterns = PowerRenameLib::MetadataPatternExtractor::GetAllPossiblePatterns();
|
|
|
|
|
std::unordered_set<std::wstring> validPatterns;
|
|
|
|
|
validPatterns.reserve(allPatterns.size());
|
|
|
|
|
size_t maxPatternLength = 0;
|
|
|
|
|
for (const auto& pattern : allPatterns)
|
|
|
|
|
{
|
|
|
|
|
validPatterns.insert(pattern);
|
|
|
|
|
maxPatternLength = std::max(maxPatternLength, pattern.length());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t pos = 0;
|
|
|
|
|
while (pos < input.length())
|
|
|
|
|
{
|
|
|
|
|
// Handle regular characters
|
|
|
|
|
if (input[pos] != L'$')
|
|
|
|
|
{
|
|
|
|
|
output += input[pos];
|
|
|
|
|
pos++;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Count consecutive dollar signs
|
|
|
|
|
size_t dollarCount = 0;
|
|
|
|
|
while (pos < input.length() && input[pos] == L'$')
|
|
|
|
|
{
|
|
|
|
|
dollarCount++;
|
|
|
|
|
pos++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Even number of dollars: all are escaped (e.g., $$ -> $, $$$$ -> $$)
|
|
|
|
|
if (dollarCount % 2 == 0)
|
|
|
|
|
{
|
|
|
|
|
output.append(dollarCount / 2, L'$');
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Odd number of dollars: pairs are escaped, last one might be a pattern prefix
|
|
|
|
|
// e.g., $ -> might be pattern, $$$ -> $ + might be pattern
|
|
|
|
|
size_t escapedDollars = dollarCount / 2;
|
|
|
|
|
|
|
|
|
|
// If no more characters, output all dollar signs
|
|
|
|
|
if (pos >= input.length())
|
|
|
|
|
{
|
|
|
|
|
output.append(dollarCount, L'$');
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Try to match a pattern (greedy matching for longest pattern)
|
|
|
|
|
std::wstring matchedPattern = FindLongestPattern(input, pos, maxPatternLength, validPatterns);
|
|
|
|
|
|
|
|
|
|
if (matchedPattern.empty())
|
|
|
|
|
{
|
|
|
|
|
// No pattern matched, output all dollar signs
|
|
|
|
|
output.append(dollarCount, L'$');
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Pattern matched
|
|
|
|
|
output.append(escapedDollars, L'$'); // Output escaped dollars first
|
|
|
|
|
|
|
|
|
|
// Replace pattern with its value or keep pattern name if value unavailable
|
|
|
|
|
std::wstring replacementValue = GetPatternValue(matchedPattern, patterns);
|
|
|
|
|
output += replacementValue;
|
|
|
|
|
|
|
|
|
|
pos += matchedPattern.length();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return StringCchCopy(result, cchMax, output.c_str());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2021-04-02 03:07:12 -07:00
|
|
|
HRESULT GetShellItemArrayFromDataObject(_In_ IUnknown* dataSource, _COM_Outptr_ IShellItemArray** items)
|
2020-07-22 13:27:58 -07:00
|
|
|
{
|
|
|
|
|
*items = nullptr;
|
|
|
|
|
CComPtr<IDataObject> dataObj;
|
|
|
|
|
HRESULT hr;
|
|
|
|
|
if (SUCCEEDED(dataSource->QueryInterface(IID_PPV_ARGS(&dataObj))))
|
|
|
|
|
{
|
|
|
|
|
hr = SHCreateShellItemArrayFromDataObject(dataObj, IID_PPV_ARGS(items));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
hr = dataSource->QueryInterface(IID_PPV_ARGS(items));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return hr;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-20 16:59:20 +03:00
|
|
|
BOOL GetEnumeratedFileName(__out_ecount(cchMax) PWSTR pszUniqueName, UINT cchMax, __in PCWSTR pszTemplate, __in_opt PCWSTR pszDir, unsigned long ulMinLong, __inout unsigned long* pulNumUsed)
|
2019-10-17 20:57:19 -07:00
|
|
|
{
|
|
|
|
|
PWSTR pszName = nullptr;
|
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
|
BOOL fRet = FALSE;
|
|
|
|
|
int cchDir = 0;
|
|
|
|
|
|
|
|
|
|
if (0 != cchMax && pszUniqueName)
|
|
|
|
|
{
|
|
|
|
|
*pszUniqueName = 0;
|
|
|
|
|
if (pszDir)
|
|
|
|
|
{
|
|
|
|
|
hr = StringCchCopy(pszUniqueName, cchMax, pszDir);
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
hr = PathCchAddBackslashEx(pszUniqueName, cchMax, &pszName, nullptr);
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
cchDir = lstrlen(pszDir);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
cchDir = 0;
|
|
|
|
|
pszName = pszUniqueName;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
hr = E_INVALIDARG;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int cchTmp = 0;
|
|
|
|
|
int cchStem = 0;
|
|
|
|
|
PCWSTR pszStem = nullptr;
|
|
|
|
|
PCWSTR pszRest = nullptr;
|
|
|
|
|
wchar_t szFormat[MAX_PATH] = { 0 };
|
|
|
|
|
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
pszStem = pszTemplate;
|
|
|
|
|
|
|
|
|
|
pszRest = StrChr(pszTemplate, L'(');
|
|
|
|
|
while (pszRest)
|
|
|
|
|
{
|
|
|
|
|
PCWSTR pszEndUniq = CharNext(pszRest);
|
|
|
|
|
while (*pszEndUniq && *pszEndUniq >= L'0' && *pszEndUniq <= L'9')
|
|
|
|
|
{
|
|
|
|
|
pszEndUniq++;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-28 15:33:36 +02:00
|
|
|
if (*pszEndUniq == L')' && (*CharNext(pszEndUniq) == L'\0' || CharNext(pszEndUniq) == PathFindExtension(pszTemplate)))
|
2019-10-17 20:57:19 -07:00
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pszRest = StrChr(CharNext(pszRest), L'(');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!pszRest)
|
|
|
|
|
{
|
|
|
|
|
pszRest = PathFindExtension(pszTemplate);
|
2023-02-08 13:10:28 +00:00
|
|
|
cchStem = static_cast<int>(pszRest - pszTemplate);
|
2019-10-17 20:57:19 -07:00
|
|
|
|
|
|
|
|
hr = StringCchCopy(szFormat, ARRAYSIZE(szFormat), L" (%lu)");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
pszRest++;
|
|
|
|
|
|
2023-02-08 13:10:28 +00:00
|
|
|
cchStem = static_cast<int>(pszRest - pszTemplate);
|
2019-10-17 20:57:19 -07:00
|
|
|
|
|
|
|
|
while (*pszRest && *pszRest >= L'0' && *pszRest <= L'9')
|
|
|
|
|
{
|
|
|
|
|
pszRest++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hr = StringCchCopy(szFormat, ARRAYSIZE(szFormat), L"%lu");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unsigned long ulMax = 0;
|
|
|
|
|
unsigned long ulMin = 0;
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
int cchFormat = lstrlen(szFormat);
|
|
|
|
|
if (cchFormat < 3)
|
|
|
|
|
{
|
|
|
|
|
*pszUniqueName = L'\0';
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
ulMin = ulMinLong;
|
|
|
|
|
cchTmp = cchMax - cchDir - cchStem - (cchFormat - 3);
|
|
|
|
|
switch (cchTmp)
|
|
|
|
|
{
|
|
|
|
|
case 1:
|
|
|
|
|
ulMax = 10;
|
|
|
|
|
break;
|
|
|
|
|
case 2:
|
|
|
|
|
ulMax = 100;
|
|
|
|
|
break;
|
|
|
|
|
case 3:
|
|
|
|
|
ulMax = 1000;
|
|
|
|
|
break;
|
|
|
|
|
case 4:
|
|
|
|
|
ulMax = 10000;
|
|
|
|
|
break;
|
|
|
|
|
case 5:
|
|
|
|
|
ulMax = 100000;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
if (cchTmp <= 0)
|
|
|
|
|
{
|
|
|
|
|
ulMax = ulMin;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
ulMax = 1000000;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
hr = StringCchCopyN(pszName, pszUniqueName + cchMax - pszName, pszStem, cchStem);
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
PWSTR pszDigit = pszName + cchStem;
|
|
|
|
|
|
|
|
|
|
for (unsigned long ul = ulMin; ((ul < ulMax) && (!fRet)); ul++)
|
|
|
|
|
{
|
|
|
|
|
wchar_t szTemp[MAX_PATH] = { 0 };
|
|
|
|
|
hr = StringCchPrintf(szTemp, ARRAYSIZE(szTemp), szFormat, ul);
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
hr = StringCchCat(szTemp, ARRAYSIZE(szTemp), pszRest);
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
hr = StringCchCopy(pszDigit, pszUniqueName + cchMax - pszDigit, szTemp);
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
if (!PathFileExists(pszUniqueName))
|
|
|
|
|
{
|
|
|
|
|
(*pulNumUsed) = ul;
|
|
|
|
|
fRet = TRUE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!fRet)
|
|
|
|
|
{
|
|
|
|
|
*pszUniqueName = L'\0';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return fRet;
|
|
|
|
|
}
|
2020-07-22 13:27:58 -07:00
|
|
|
|
2022-06-30 22:10:14 +02:00
|
|
|
// Iterate through the shell items array and checks if at least 1 item has SFGAO_CANRENAME.
|
Updates for check-spelling v0.0.25 (#40386)
## Summary of the Pull Request
- #39572 updated check-spelling but ignored:
> 🐣 Breaking Changes
[Code Scanning action requires a Code Scanning
Ruleset](https://github.com/check-spelling/check-spelling/wiki/Breaking-Change:-Code-Scanning-action-requires-a-Code-Scanning-Ruleset)
If you use SARIF reporting, then instead of the workflow yielding an ❌
when it fails, it will rely on [github-advanced-security
🤖](https://github.com/apps/github-advanced-security) to report the
failure. You will need to adjust your checks for PRs.
This means that check-spelling hasn't been properly doing its job 😦.
I'm sorry, I should have pushed a thing to this repo earlier,...
Anyway, as with most refreshes, this comes with a number of fixes, some
are fixes for typos that snuck in before the 0.0.25 upgrade, some are
for things that snuck in after, some are based on new rules in
spell-check-this, and some are hand written patterns based on running
through this repository a few times.
About the 🐣 **breaking change**: someone needs to create a ruleset for
this repository (see [Code Scanning action requires a Code Scanning
Ruleset: Sample ruleset
](https://github.com/check-spelling/check-spelling/wiki/Breaking-Change:-Code-Scanning-action-requires-a-Code-Scanning-Ruleset#sample-ruleset)).
The alternative to adding a ruleset is to change the condition to not
use sarif for this repository. In general, I think the github
integration from sarif is prettier/more helpful, so I think that it's
the better choice.
You can see an example of it working in:
- https://github.com/check-spelling-sandbox/PowerToys/pull/23
---------
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
Co-authored-by: Mike Griese <migrie@microsoft.com>
Co-authored-by: Dustin L. Howett <dustin@howett.net>
2025-07-08 18:16:52 -04:00
|
|
|
// We do not enumerate child items - only items the user selected.
|
2022-06-30 22:10:14 +02:00
|
|
|
bool ShellItemArrayContainsRenamableItem(_In_ IShellItemArray* shellItemArray)
|
|
|
|
|
{
|
|
|
|
|
bool hasRenamable = false;
|
|
|
|
|
IEnumShellItems* spesi;
|
|
|
|
|
if (SUCCEEDED(shellItemArray->EnumItems(&spesi)))
|
|
|
|
|
{
|
|
|
|
|
ULONG celtFetched;
|
|
|
|
|
IShellItem* spsi;
|
|
|
|
|
while ((S_OK == spesi->Next(1, &spsi, &celtFetched)))
|
|
|
|
|
{
|
|
|
|
|
SFGAOF attrs;
|
|
|
|
|
if (SUCCEEDED(spsi->GetAttributes(SFGAO_CANRENAME, &attrs)) &&
|
|
|
|
|
attrs & SFGAO_CANRENAME)
|
|
|
|
|
{
|
|
|
|
|
hasRenamable = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return hasRenamable;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-22 13:27:58 -07:00
|
|
|
// Iterate through the data source and checks if at least 1 item has SFGAO_CANRENAME.
|
Updates for check-spelling v0.0.25 (#40386)
## Summary of the Pull Request
- #39572 updated check-spelling but ignored:
> 🐣 Breaking Changes
[Code Scanning action requires a Code Scanning
Ruleset](https://github.com/check-spelling/check-spelling/wiki/Breaking-Change:-Code-Scanning-action-requires-a-Code-Scanning-Ruleset)
If you use SARIF reporting, then instead of the workflow yielding an ❌
when it fails, it will rely on [github-advanced-security
🤖](https://github.com/apps/github-advanced-security) to report the
failure. You will need to adjust your checks for PRs.
This means that check-spelling hasn't been properly doing its job 😦.
I'm sorry, I should have pushed a thing to this repo earlier,...
Anyway, as with most refreshes, this comes with a number of fixes, some
are fixes for typos that snuck in before the 0.0.25 upgrade, some are
for things that snuck in after, some are based on new rules in
spell-check-this, and some are hand written patterns based on running
through this repository a few times.
About the 🐣 **breaking change**: someone needs to create a ruleset for
this repository (see [Code Scanning action requires a Code Scanning
Ruleset: Sample ruleset
](https://github.com/check-spelling/check-spelling/wiki/Breaking-Change:-Code-Scanning-action-requires-a-Code-Scanning-Ruleset#sample-ruleset)).
The alternative to adding a ruleset is to change the condition to not
use sarif for this repository. In general, I think the github
integration from sarif is prettier/more helpful, so I think that it's
the better choice.
You can see an example of it working in:
- https://github.com/check-spelling-sandbox/PowerToys/pull/23
---------
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
Co-authored-by: Mike Griese <migrie@microsoft.com>
Co-authored-by: Dustin L. Howett <dustin@howett.net>
2025-07-08 18:16:52 -04:00
|
|
|
// We do not enumerate child items - only items the user selected.
|
2020-07-22 13:27:58 -07:00
|
|
|
bool DataObjectContainsRenamableItem(_In_ IUnknown* dataSource)
|
|
|
|
|
{
|
|
|
|
|
bool hasRenamable = false;
|
|
|
|
|
CComPtr<IShellItemArray> spsia;
|
2023-03-08 14:51:33 +01:00
|
|
|
if (dataSource && SUCCEEDED(GetShellItemArrayFromDataObject(dataSource, &spsia)))
|
2020-07-22 13:27:58 -07:00
|
|
|
{
|
|
|
|
|
CComPtr<IEnumShellItems> spesi;
|
|
|
|
|
if (SUCCEEDED(spsia->EnumItems(&spesi)))
|
|
|
|
|
{
|
|
|
|
|
ULONG celtFetched;
|
|
|
|
|
CComPtr<IShellItem> spsi;
|
|
|
|
|
while ((S_OK == spesi->Next(1, &spsi, &celtFetched)))
|
|
|
|
|
{
|
|
|
|
|
SFGAOF attrs;
|
|
|
|
|
if (SUCCEEDED(spsi->GetAttributes(SFGAO_CANRENAME, &attrs)) &&
|
|
|
|
|
attrs & SFGAO_CANRENAME)
|
|
|
|
|
{
|
|
|
|
|
hasRenamable = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return hasRenamable;
|
|
|
|
|
}
|
2021-04-02 03:07:12 -07:00
|
|
|
|
|
|
|
|
HWND CreateMsgWindow(_In_ HINSTANCE hInst, _In_ WNDPROC pfnWndProc, _In_ void* p)
|
|
|
|
|
{
|
|
|
|
|
WNDCLASS wc = { 0 };
|
|
|
|
|
PCWSTR wndClassName = L"MsgWindow";
|
|
|
|
|
|
|
|
|
|
wc.lpfnWndProc = DefWindowProc;
|
|
|
|
|
wc.cbWndExtra = sizeof(void*);
|
|
|
|
|
wc.hInstance = hInst;
|
2023-02-08 13:10:28 +00:00
|
|
|
wc.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_BTNFACE + 1);
|
2021-04-02 03:07:12 -07:00
|
|
|
wc.lpszClassName = wndClassName;
|
|
|
|
|
|
|
|
|
|
RegisterClass(&wc);
|
|
|
|
|
|
|
|
|
|
HWND hwnd = CreateWindowEx(
|
|
|
|
|
0, wndClassName, nullptr, 0, 0, 0, 0, 0, HWND_MESSAGE, 0, hInst, nullptr);
|
|
|
|
|
if (hwnd)
|
|
|
|
|
{
|
2023-02-08 13:10:28 +00:00
|
|
|
SetWindowLongPtr(hwnd, 0, reinterpret_cast<LONG_PTR>(p));
|
2021-04-02 03:07:12 -07:00
|
|
|
if (pfnWndProc)
|
|
|
|
|
{
|
2023-02-08 13:10:28 +00:00
|
|
|
SetWindowLongPtr(hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(pfnWndProc));
|
2021-04-02 03:07:12 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return hwnd;
|
|
|
|
|
}
|
2021-10-25 15:40:19 +02:00
|
|
|
|
|
|
|
|
std::wstring GetRegString(const std::wstring& valueName, const std::wstring& subPath)
|
|
|
|
|
{
|
|
|
|
|
wchar_t value[MAX_INPUT_STRING_LEN];
|
|
|
|
|
value[0] = L'\0';
|
|
|
|
|
DWORD type = REG_SZ;
|
|
|
|
|
DWORD size = MAX_INPUT_STRING_LEN * sizeof(wchar_t);
|
|
|
|
|
std::wstring completePath = std::wstring(c_rootRegPath) + subPath;
|
|
|
|
|
if (SHGetValue(HKEY_CURRENT_USER, completePath.c_str(), valueName.c_str(), &type, value, &size) == ERROR_SUCCESS)
|
|
|
|
|
{
|
|
|
|
|
return std::wstring(value);
|
|
|
|
|
}
|
|
|
|
|
return std::wstring{};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unsigned int GetRegNumber(const std::wstring& valueName, unsigned int defaultValue)
|
|
|
|
|
{
|
|
|
|
|
DWORD type = REG_DWORD;
|
|
|
|
|
DWORD data = 0;
|
|
|
|
|
DWORD size = sizeof(DWORD);
|
|
|
|
|
if (SHGetValue(HKEY_CURRENT_USER, c_rootRegPath, valueName.c_str(), &type, &data, &size) == ERROR_SUCCESS)
|
|
|
|
|
{
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
return defaultValue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SetRegNumber(const std::wstring& valueName, unsigned int value)
|
|
|
|
|
{
|
|
|
|
|
SHSetValue(HKEY_CURRENT_USER, c_rootRegPath, valueName.c_str(), REG_DWORD, &value, sizeof(value));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool GetRegBoolean(const std::wstring& valueName, bool defaultValue)
|
|
|
|
|
{
|
|
|
|
|
DWORD value = GetRegNumber(valueName.c_str(), defaultValue ? 1 : 0);
|
|
|
|
|
return (value == 0) ? false : true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SetRegBoolean(const std::wstring& valueName, bool value)
|
|
|
|
|
{
|
|
|
|
|
SetRegNumber(valueName, value ? 1 : 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool LastModifiedTime(const std::wstring& filePath, FILETIME* lpFileTime)
|
|
|
|
|
{
|
|
|
|
|
WIN32_FILE_ATTRIBUTE_DATA attr{};
|
|
|
|
|
if (GetFileAttributesExW(filePath.c_str(), GetFileExInfoStandard, &attr))
|
|
|
|
|
{
|
|
|
|
|
*lpFileTime = attr.ftLastWriteTime;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2024-06-20 18:26:31 +03:00
|
|
|
|
|
|
|
|
std::wstring CreateGuidStringWithoutBrackets()
|
|
|
|
|
{
|
|
|
|
|
GUID guid;
|
|
|
|
|
if (CoCreateGuid(&guid) == S_OK)
|
|
|
|
|
{
|
|
|
|
|
OLECHAR* guidString;
|
|
|
|
|
if (StringFromCLSID(guid, &guidString) == S_OK)
|
|
|
|
|
{
|
|
|
|
|
std::wstring guidStr{ guidString };
|
|
|
|
|
CoTaskMemFree(guidString);
|
|
|
|
|
return guidStr.substr(1, guidStr.length() - 2);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return L"";
|
2025-11-04 09:27:16 +08:00
|
|
|
}
|