2016-08-30 115 views
4

我是字符串來創建一個std::regex(__FILE__)作爲單元測試的一部分,它檢查打印文件名的一些異常輸出。std :: regex轉義文件路徑中的反斜槓

在Windows它失敗:

regex_error(error_escape):表達式包含無效轉義字符,或一個尾隨逃逸。

因爲__FILE__宏擴展包含未轉義的反斜槓。

是否有逃避反斜槓,而不是通過生成的字符串循環更優雅的方式(即用std算法或一些std::string功能)?

+0

'__FILE__'應該只打印文件名。你需要完整的路徑嗎? – Hayt

+2

@Hayt _「'__FILE__'應該只打印文件名。」_不一定 –

+0

是的,如果他不需要他們,他可以看看這裏:https://msdn.microsoft.com/en-us/library/027c4t2s .aspx假設問題不是缺少的引號,你已經回答了。並假設他使用MSVC編譯器 – Hayt

回答

1

文件路徑可以包含許多在正則表達式模式中具有特殊含義的字符。在一般情況下僅僅反轉反斜槓是不夠的。

即使是一個簡單的路徑,如C:\Program Files (x86)\Vendor\Product\app.exe,也包含幾個特殊字符。如果要將其轉換爲正則表達式(或正則表達式的一部分),則不僅需要轉義反斜槓,而且還需要轉義圓括號和句點(點)。

std::string EscapeForRegularExpression(const std::string &s) { 
    static const std::regex metacharacters(R"([\.\^\$\-\+\(\)\[\]\{\}\|\?\*)"); 
    return std::regex_replace(s, metacharacters, "\\$&"); 
} 

(文件路徑不能包含*?,但我將他們保持功能一般)

幸運的是,我們可以有更多的正則表達式解決我們的正則表達式的問題

如果不通過「無生循環」方針遵守,一個可能更快實現將避免正則表達式:

std::string EscapeForRegularExpression(const std::string &s) { 
    static const char metacharacters[] = R"(\.^$-+()[]{}|?*)"; 
    std::string out; 
    out.reserve(s.size()); 
    for (auto ch : s) { 
    if (std::strchr(metacharacters, ch)) 
     out.push_back('\\'); 
    out.push_back(ch); 
    } 
    return out; 
} 

儘管循環增加了一些混亂,但這種方法允許我們在metacharacters的定義上下降一定程度的轉義,這是對regex版本的可讀性的勝利。

+0

沒有「原始循環」?我很高興我們沒有一個風格指南... –

+0

@Nicolas Holthaus:Adobe的肖恩家長提出在這個視頻中的「沒有原始循環」的想法:https://channel9.msdn.com/Events/GoingNative/ 2013/CPP-調味 –

1

EDIT

最後,我切換到@AdrianMcCarthy的more robust approach


下面是我在其中得到解決的情況下,有人在此跌倒的問題不雅方法實際上是尋找一個解決辦法:

std::string escapeBackslashes(const std::string& s) 
{ 
    std::string out; 

    for (auto c : s) 
    { 
     out += c; 
     if (c == '\\') 
      out += c; 
    } 

    return out; 
} 

然後

std::regex(escapeBackslashes(__FILE__)); 

這是O(N)這可能是就像你在這裏可以做到的一樣好,但涉及很多字符串複製,我想這並不是絕對必要的。

+0

所有這一切都是逃避反斜槓,這不足以將Windows文件路徑轉換爲有效的正則表達式模式。它不會對其他正則表達式元字符進行任何操作,這些字符可以在路徑名中,例如圓括號。 –

+0

@AdrianMcCarthy當然,但這就是它打算做的。它意味着一個單元測試,而不是一個通用的「正則表達式」製造商,並解決了我需要的唯一一個問題。 –

1

這裏是polymapper

它需要一個操作來獲取元素並返回一個範圍,即「地圖操作」。

它產生一個函數對象,它接受一個容器,並將「映射操作」應用於每個元素。它返回與容器相同的類型,其中每個元素都被「地圖操作」展開/收縮。

template<class Op> 
auto polymapper(Op&& op) { 
    return [op=std::forward<Op>(op)](auto&& r) { 
    using std::begin; 
    using R=std::decay_t<decltype(r)>; 
    using iterator = decltype(begin(r)); 
    using T = typename std::iterator_traits<iterator>::value_type; 
    std::vector<T> data; 
    for (auto&& e:decltype(r)(r)) { 
     for (auto&& out:op(e)) { 
     data.push_back(out); 
     } 
    } 
    return R{ data.begin(), data.end() }; 
    }; 
} 

這裏是escape_stuff

auto escape_stuff = polymapper([](char c)->std::vector<char> { 
    if (c != '\\') return {c}; 
    else return {c,c}; 
}); 

live example

int main() { 
    std::cout << escape_stuff(std::string(__FILE__)) << "\n"; 
} 

這種方法的優點是,容器的膽量混亂的行爲被分解出來。你編寫的代碼與字符或元素混淆,整體邏輯不是你的問題。

缺點是polymapper有點奇怪,並且不必要的內存分配也完成了。 (那些可以優化出來,但這使得代碼更復雜)。