Que código eu usaria para converter uma expressão semelhante a SQL em um regex em tempo real?
Pergunta
Estou procurando converter uma instrução semelhante a SQL instantaneamente para o regex equivalente, ou seja,
LIKE '%this%'
LIKE 'Sm_th'
LIKE '[C-P]arsen'
Qual é a melhor abordagem para fazer isso?
P.S.Estou tentando fazer isso no .Net Framework (C#).
Solução
O Regex a seguir converte um padrão semelhante ao SQL em um padrão Regex com a ajuda de um MatchEvaluator
delegar.Ele lida corretamente com blocos de colchetes e escapa de caracteres Regex especiais.
string regexPattern = Regex.Replace(
likePattern,
@"[%_]|\[[^]]*\]|[^%_[]+",
match =>
{
if (match.Value == "%")
{
return ".*";
}
if (match.Value == "_")
{
return ".";
}
if (match.Value.StartsWith("[") && match.Value.EndsWith("]"))
{
return match.Value;
}
return Regex.Escape(match.Value);
});
Outras dicas
Além da solução de @Nathan-Baulch, você pode usar o código abaixo para também lidar com o caso em que um caractere de escape personalizado foi definido usando o LIKE '!%' ESCAPE '!'
sintaxe.
public Regex ConvertSqlLikeToDotNetRegex(string regex, char? likeEscape = null)
{
var pattern = string.Format(@"
{0}[%_]|
[%_]|
\[[^]]*\]|
[^%_[{0}]+
", likeEscape);
var regexPattern = Regex.Replace(
regex,
pattern,
ConvertWildcardsAndEscapedCharacters,
RegexOptions.IgnorePatternWhitespace);
regexPattern = "^" + regexPattern + "$";
return new Regex(regexPattern,
!m_CaseSensitive ? RegexOptions.IgnoreCase : RegexOptions.None);
}
private string ConvertWildcardsAndEscapedCharacters(Match match)
{
// Wildcards
switch (match.Value)
{
case "%":
return ".*";
case "_":
return ".";
}
// Remove SQL defined escape characters from C# regex
if (StartsWithEscapeCharacter(match.Value, likeEscape))
{
return match.Value.Remove(0, 1);
}
// Pass anything contained in []s straight through
// (These have the same behaviour in SQL LIKE Regex and C# Regex)
if (StartsAndEndsWithSquareBrackets(match.Value))
{
return match.Value;
}
return Regex.Escape(match.Value);
}
private static bool StartsAndEndsWithSquareBrackets(string text)
{
return text.StartsWith("[", StringComparison.Ordinal) &&
text.EndsWith("]", StringComparison.Ordinal);
}
private bool StartsWithEscapeCharacter(string text, char? likeEscape)
{
return (likeEscape != null) &&
text.StartsWith(likeEscape.ToString(), StringComparison.Ordinal);
}
Pelo seu exemplo acima, eu atacaria assim (falo em termos gerais porque não conheço C#):
Separe-o por COMO '...', coloque o ... peças em uma matriz.Substituir sem escape % sinais por .*, sublinhado por ., e neste caso o [C-P]arsen traduz diretamente em regex.
Junte as peças da matriz novamente com um tubo e coloque o resultado entre parênteses e bits de regex padrão.
O resultado seria:
/^(.*this.*|Sm.th|[C-P]arsen)$/
O mais importante aqui é ter cuidado com todas as maneiras pelas quais você pode escapar dos dados e quais curingas são traduzidos para quais expressões regulares.
% becomes .*
_ becomes .
Encontrei um módulo Perl chamado Regexp::Cartões curinga.Você pode tentar portá-lo ou tentar o Perl.NET.Tenho a sensação de que você também pode escrever algo.