Есть ли стандартная (желательно Apache Commons или аналогичная невирусная) библиотека для сопоставления типов "glob" в Java? Когда мне однажды пришлось сделать подобное в Perl, я просто поменял все « .
» на « \.
», « *
« на » .*
», « ?
« на .
»и тому подобное, но мне интересно, делал ли кто-нибудь работать на меня.
Аналогичный вопрос: создать регулярное выражение из выражения glob
Ответы:
Нет ничего встроенного, но довольно просто преобразовать что-то похожее на глобус в регулярное выражение:
public static String createRegexFromGlob(String glob) { String out = "^"; for(int i = 0; i < glob.length(); ++i) { final char c = glob.charAt(i); switch(c) { case '*': out += ".*"; break; case '?': out += '.'; break; case '.': out += "\\."; break; case '\\': out += "\\\\"; break; default: out += c; } } out += '$'; return out; }
это работает для меня, но я не уверен, что он охватывает глобус "стандарт", если он есть :)
Обновление от Пола Томблина: я нашел программу на perl, которая выполняет преобразование глобусов, и адаптируя ее к Java, я получил:
private String convertGlobToRegEx(String line) { LOG.info("got line [" + line + "]"); line = line.trim(); int strLen = line.length(); StringBuilder sb = new StringBuilder(strLen); // Remove beginning and ending * globs because they're useless if (line.startsWith("*")) { line = line.substring(1); strLen--; } if (line.endsWith("*")) { line = line.substring(0, strLen-1); strLen--; } boolean escaping = false; int inCurlies = 0; for (char currentChar : line.toCharArray()) { switch (currentChar) { case '*': if (escaping) sb.append("\\*"); else sb.append(".*"); escaping = false; break; case '?': if (escaping) sb.append("\\?"); else sb.append('.'); escaping = false; break; case '.': case '(': case ')': case '+': case '|': case '^': case '$': case '@': case '%': sb.append('\\'); sb.append(currentChar); escaping = false; break; case '\\': if (escaping) { sb.append("\\\\"); escaping = false; } else escaping = true; break; case '{': if (escaping) { sb.append("\\{"); } else { sb.append('('); inCurlies++; } escaping = false; break; case '}': if (inCurlies > 0 && !escaping) { sb.append(')'); inCurlies--; } else if (escaping) sb.append("\\}"); else sb.append("}"); escaping = false; break; case ',': if (inCurlies > 0 && !escaping) { sb.append('|'); } else if (escaping) sb.append("\\,"); else sb.append(","); break; default: escaping = false; sb.append(currentChar); } } return sb.toString(); }
Я редактирую этот ответ, а не делаю свой, потому что этот ответ поставил меня на правильный путь.
источник
Глобббинг
также запланирован нареализовать в Java 7.См.
FileSystem.getPathMatcher(String)
И учебник «Поиск файлов» .источник
Спасибо всем присутствующим за их вклад. Я написал более полное преобразование, чем любой из предыдущих ответов:
/** * Converts a standard POSIX Shell globbing pattern into a regular expression * pattern. The result can be used with the standard {@link java.util.regex} API to * recognize strings which match the glob pattern. * <p/> * See also, the POSIX Shell language: * http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_13_01 * * @param pattern A glob pattern. * @return A regex pattern to recognize the given glob pattern. */ public static final String convertGlobToRegex(String pattern) { StringBuilder sb = new StringBuilder(pattern.length()); int inGroup = 0; int inClass = 0; int firstIndexInClass = -1; char[] arr = pattern.toCharArray(); for (int i = 0; i < arr.length; i++) { char ch = arr[i]; switch (ch) { case '\\': if (++i >= arr.length) { sb.append('\\'); } else { char next = arr[i]; switch (next) { case ',': // escape not needed break; case 'Q': case 'E': // extra escape needed sb.append('\\'); default: sb.append('\\'); } sb.append(next); } break; case '*': if (inClass == 0) sb.append(".*"); else sb.append('*'); break; case '?': if (inClass == 0) sb.append('.'); else sb.append('?'); break; case '[': inClass++; firstIndexInClass = i+1; sb.append('['); break; case ']': inClass--; sb.append(']'); break; case '.': case '(': case ')': case '+': case '|': case '^': case '$': case '@': case '%': if (inClass == 0 || (firstIndexInClass == i && ch == '^')) sb.append('\\'); sb.append(ch); break; case '!': if (firstIndexInClass == i) sb.append('^'); else sb.append('!'); break; case '{': inGroup++; sb.append('('); break; case '}': inGroup--; sb.append(')'); break; case ',': if (inGroup > 0) sb.append('|'); else sb.append(','); break; default: sb.append(ch); } } return sb.toString(); }
И модульные тесты, чтобы доказать, что это работает:
/** * @author Neil Traft */ public class StringUtils_ConvertGlobToRegex_Test { @Test public void star_becomes_dot_star() throws Exception { assertEquals("gl.*b", StringUtils.convertGlobToRegex("gl*b")); } @Test public void escaped_star_is_unchanged() throws Exception { assertEquals("gl\\*b", StringUtils.convertGlobToRegex("gl\\*b")); } @Test public void question_mark_becomes_dot() throws Exception { assertEquals("gl.b", StringUtils.convertGlobToRegex("gl?b")); } @Test public void escaped_question_mark_is_unchanged() throws Exception { assertEquals("gl\\?b", StringUtils.convertGlobToRegex("gl\\?b")); } @Test public void character_classes_dont_need_conversion() throws Exception { assertEquals("gl[-o]b", StringUtils.convertGlobToRegex("gl[-o]b")); } @Test public void escaped_classes_are_unchanged() throws Exception { assertEquals("gl\\[-o\\]b", StringUtils.convertGlobToRegex("gl\\[-o\\]b")); } @Test public void negation_in_character_classes() throws Exception { assertEquals("gl[^a-n!p-z]b", StringUtils.convertGlobToRegex("gl[!a-n!p-z]b")); } @Test public void nested_negation_in_character_classes() throws Exception { assertEquals("gl[[^a-n]!p-z]b", StringUtils.convertGlobToRegex("gl[[!a-n]!p-z]b")); } @Test public void escape_carat_if_it_is_the_first_char_in_a_character_class() throws Exception { assertEquals("gl[\\^o]b", StringUtils.convertGlobToRegex("gl[^o]b")); } @Test public void metachars_are_escaped() throws Exception { assertEquals("gl..*\\.\\(\\)\\+\\|\\^\\$\\@\\%b", StringUtils.convertGlobToRegex("gl?*.()+|^$@%b")); } @Test public void metachars_in_character_classes_dont_need_escaping() throws Exception { assertEquals("gl[?*.()+|^$@%]b", StringUtils.convertGlobToRegex("gl[?*.()+|^$@%]b")); } @Test public void escaped_backslash_is_unchanged() throws Exception { assertEquals("gl\\\\b", StringUtils.convertGlobToRegex("gl\\\\b")); } @Test public void slashQ_and_slashE_are_escaped() throws Exception { assertEquals("\\\\Qglob\\\\E", StringUtils.convertGlobToRegex("\\Qglob\\E")); } @Test public void braces_are_turned_into_groups() throws Exception { assertEquals("(glob|regex)", StringUtils.convertGlobToRegex("{glob,regex}")); } @Test public void escaped_braces_are_unchanged() throws Exception { assertEquals("\\{glob\\}", StringUtils.convertGlobToRegex("\\{glob\\}")); } @Test public void commas_dont_need_escaping() throws Exception { assertEquals("(glob,regex),", StringUtils.convertGlobToRegex("{glob\\,regex},")); } }
источник
Есть несколько библиотек, которые выполняют сопоставление шаблонов по типу Glob, которые более современны, чем перечисленные:
Сканер каталога муравьев и источники AntPathMatcher
Я рекомендую оба решения по сравнению с другими решениями, так как Ant Style Globbing в значительной степени стал стандартным синтаксисом glob в мире Java (Hudson, Spring, Ant и, я думаю, Maven).
источник
Мне недавно пришлось это сделать и использовал
\Q
и,\E
чтобы избежать шаблона глобуса:private static Pattern getPatternFromGlob(String glob) { return Pattern.compile( "^" + Pattern.quote(glob) .replace("*", "\\E.*\\Q") .replace("?", "\\E.\\Q") + "$"); }
источник
glob
переменную с помощью glob = Pattern.quote (glob), которая, как мне кажется, обрабатывает такие крайние случаи. Однако в этом случае вам не нужно добавлять первый и последний \\ Q и \\ E в начале и в конце.Это простая реализация Glob, которая обрабатывает * и? в шаблоне
public class GlobMatch { private String text; private String pattern; public boolean match(String text, String pattern) { this.text = text; this.pattern = pattern; return matchCharacter(0, 0); } private boolean matchCharacter(int patternIndex, int textIndex) { if (patternIndex >= pattern.length()) { return false; } switch(pattern.charAt(patternIndex)) { case '?': // Match any character if (textIndex >= text.length()) { return false; } break; case '*': // * at the end of the pattern will match anything if (patternIndex + 1 >= pattern.length() || textIndex >= text.length()) { return true; } // Probe forward to see if we can get a match while (textIndex < text.length()) { if (matchCharacter(patternIndex + 1, textIndex)) { return true; } textIndex++; } return false; default: if (textIndex >= text.length()) { return false; } String textChar = text.substring(textIndex, textIndex + 1); String patternChar = pattern.substring(patternIndex, patternIndex + 1); // Note the match is case insensitive if (textChar.compareToIgnoreCase(patternChar) != 0) { return false; } } // End of pattern and text? if (patternIndex + 1 >= pattern.length() && textIndex + 1 >= text.length()) { return true; } // Go on to match the next character in the pattern return matchCharacter(patternIndex + 1, textIndex + 1); } }
источник
Подобно Тони Edgecombe «s ответ , вот короткий и простой globber , что поддерживает
*
и?
без использования регулярных выражений, если кто -то нуждается.public static boolean matches(String text, String glob) { String rest = null; int pos = glob.indexOf('*'); if (pos != -1) { rest = glob.substring(pos + 1); glob = glob.substring(0, pos); } if (glob.length() > text.length()) return false; // handle the part up to the first * for (int i = 0; i < glob.length(); i++) if (glob.charAt(i) != '?' && !glob.substring(i, i + 1).equalsIgnoreCase(text.substring(i, i + 1))) return false; // recurse for the part after the first *, if any if (rest == null) { return glob.length() == text.length(); } else { for (int i = glob.length(); i <= text.length(); i++) { if (matches(text.substring(i), rest)) return true; } return false; } }
источник
Это может быть немного хакерский подход. Я понял это из
Files.newDirectoryStream(Path dir, String glob)
кода NIO2 . Обратите внимание, что при каждом совпаденииPath
создается новый объект. Пока мне удалось протестировать это только на Windows FS, однако я считаю, что это должно работать и на Unix.// a file system hack to get a glob matching PathMatcher matcher = ("*".equals(glob)) ? null : FileSystems.getDefault().getPathMatcher("glob:" + glob); if ("*".equals(glob) || matcher.matches(Paths.get(someName))) { // do you stuff here }
ОБНОВЛЕНИЕ Работает как на Mac, так и на Linux.
источник
Я не знаю о «стандартной» реализации, но мне известен проект sourceforge, выпущенный под лицензией BSD, в котором реализовано глобальное сопоставление для файлов. Он реализован в одном файле , возможно, вы сможете адаптировать его под свои требования.
источник
Давным-давно я выполнял массовую фильтрацию текста с помощью глобусов, поэтому написал небольшой фрагмент кода (15 строк кода, никаких зависимостей, кроме JDK). Он обрабатывает только '*' (мне было достаточно), но его можно легко расширить на '?'. Это в несколько раз быстрее, чем предварительно скомпилированное регулярное выражение, не требует предварительной компиляции (по сути, это сравнение строки и строки каждый раз, когда сопоставляется шаблон).
Код:
public static boolean miniglob(String[] pattern, String line) { if (pattern.length == 0) return line.isEmpty(); else if (pattern.length == 1) return line.equals(pattern[0]); else { if (!line.startsWith(pattern[0])) return false; int idx = pattern[0].length(); for (int i = 1; i < pattern.length - 1; ++i) { String patternTok = pattern[i]; int nextIdx = line.indexOf(patternTok, idx); if (nextIdx < 0) return false; else idx = nextIdx + patternTok.length(); } if (!line.endsWith(pattern[pattern.length - 1])) return false; return true; } }
Применение:
public static void main(String[] args) { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); try { // read from stdin space separated text and pattern for (String input = in.readLine(); input != null; input = in.readLine()) { String[] tokens = input.split(" "); String line = tokens[0]; String[] pattern = tokens[1].split("\\*+", -1 /* want empty trailing token if any */); // check matcher performance long tm0 = System.currentTimeMillis(); for (int i = 0; i < 1000000; ++i) { miniglob(pattern, line); } long tm1 = System.currentTimeMillis(); System.out.println("miniglob took " + (tm1-tm0) + " ms"); // check regexp performance Pattern reptn = Pattern.compile(tokens[1].replace("*", ".*")); Matcher mtchr = reptn.matcher(line); tm0 = System.currentTimeMillis(); for (int i = 0; i < 1000000; ++i) { mtchr.matches(); } tm1 = System.currentTimeMillis(); System.out.println("regexp took " + (tm1-tm0) + " ms"); // check if miniglob worked correctly if (miniglob(pattern, line)) { System.out.println("+ >" + line); } else { System.out.println("- >" + line); } } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
Скопируйте / вставьте отсюда
источник
Предыдущее решение по Vincent Robert / dimo414 зависит от
Pattern.quote()
реализуются в терминах\Q
...\E
, которые не описаны в API и , следовательно , не может быть для других / будущих реализаций Java. Следующее решение устраняет эту зависимость реализации путем экранирования всех вхождений\E
вместо использованияquote()
. Он также активируетDOTALL
mode ((?s)
) в случае, если строка для сопоставления содержит символы новой строки.public static Pattern globToRegex(String glob) { return Pattern.compile( "(?s)^\\Q" + glob.replace("\\E", "\\E\\\\E\\Q") .replace("*", "\\E.*\\Q") .replace("?", "\\E.\\Q") + "\\E$" ); }
источник
Кстати, похоже, что вы сделали это на собственном горьком опыте в Perl.
Это трюк в Perl:
my @files = glob("*.html") # Or, if you prefer: my @files = <*.html>
источник