Проблемы с регулярным выражением, кавычки - PullRequest
5 голосов
/ 17 мая 2011

По сути, мне передают строку, и мне нужно ее токенизировать почти так же, как параметры командной строки - * nix shell

Скажите, у меня есть следующая строка

"Hello\" World" "Hello Universe" Hi

Как я могу превратить его в список из 3 элементов

  • Hello "World
  • Привет Вселенная
  • Привет

Следующая моя первая попытка, но есть ряд проблем

  • оставляет кавычки
  • Не уловил сбежавшей цитаты

Код:

public void test() {
    String str = "\"Hello\\\" World\" \"Hello Universe\" Hi";
    List<String> list = split(str);
}

public static List<String> split(String str) {
    Pattern pattern = Pattern.compile(
        "\"[^\"]*\"" + /* double quoted token*/
        "|'[^']*'" + /*single quoted token*/
        "|[A-Za-z']+" /*everything else*/
    );

    List<String> opts = new ArrayList<String>();
    Scanner scanner = new Scanner(str).useDelimiter(pattern);

    String token;
    while ((token = scanner.findInLine(pattern)) != null) {
        opts.add(token);
    }
    return opts;
}

Таким образом, неправильный вывод следующего кода

  • "Hello \"
  • Мир
  • ""
  • Hello
  • Вселенная
  • Привет

РЕДАКТИРОВАТЬ Я полностью открыт для решения без регулярных выражений. Это просто первое решение, которое пришло на ум

Ответы [ 5 ]

2 голосов
/ 17 мая 2011

Если вы решите, что хотите отказаться от регулярных выражений, и вместо этого выполните синтаксический анализ, есть несколько вариантов.Если вы хотите, чтобы в качестве вашей цитаты использовалась только двойная или одинарная (но не обе), то вы можете легко использовать StreamTokenizer:

public static List<String> tokenize(String s) throws IOException {
    List<String> opts = new ArrayList<String>();
    StreamTokenizer st = new StreamTokenizer(new StringReader(s));
    st.quoteChar('\"');
    while (st.nextToken() != StreamTokenizer.TT_EOF) {
        opts.add(st.sval);
    }

    return opts;
}

Если вы должны поддерживать обе цитаты, здесьявляется наивной реализацией, которая должна работать (предупреждаем, что строка типа '"бла \" бла "бла" даст что-то вроде "бла" бла-бла. Если это не так, вам нужно будет внести некоторые изменения):*

   public static List<String> splitSSV(String in) throws IOException {
        ArrayList<String> out = new ArrayList<String>();

        StringReader r = new StringReader(in);
        StringBuilder b = new StringBuilder();
        int inQuote = -1;
        boolean escape = false;
        int c;
        // read each character
        while ((c = r.read()) != -1) {
            if (escape) {  // if the previous char is escape, add the current char
                b.append((char)c);
                escape = false;
                continue;
            }
            switch (c) {
            case '\\':   // deal with escape char
                escape = true;
                break;
            case '\"':
            case '\'':  // deal with quote chars
                if (c == '\"' || c == '\'') {
                    if (inQuote == -1) {  // not in a quote
                        inQuote = c;  // now we are
                    } else {
                        inQuote = -1;  // we were in a quote and now we aren't
                    }
                }
                break;
            case ' ':
                if (inQuote == -1) {  // if we aren't in a quote, then add token to list
                    out.add(b.toString());
                    b.setLength(0);
                } else {
                    b.append((char)c); // else append space to current token
                }
                break;
            default:
                b.append((char)c);  // append all other chars to current token
            }
        }
        if (b.length() > 0) {
            out.add(b.toString()); // add final token to list
        }
        return out;
    }
2 голосов
/ 17 мая 2011

Напомним, что вы хотите разделить пробел, кроме случаев, когда они заключены в двойные кавычки, которым не предшествует обратная косая черта.

Шаг 1: токенизация ввода: /([ \t]+)|(\\")|(")|([^ \t"]+)/

Это дает вам последовательность токенов SPACE, ESCAPED_QUOTE, QUOTE и TEXT.

Шаг 2: построить конечный автомат, сопоставляющий и реагирующий на токены:

Состояние: START

  • ПРОБЕЛ -> вернуть пустую строку
  • ESCAPED_QUOTE -> Ошибка (?)
  • QUOTE -> State: = WITHIN_QUOTES
  • ТЕКСТ -> возвращаемый текст

Состояние: WITHIN_QUOTES

  • ПРОБЕЛ -> добавить значение к аккумулятору
  • ESCAPED_QUOTE -> добавить цитату в аккумулятор
  • QUOTE -> возврат и сброс аккумулятора; Состояние: = НАЧАЛО
  • TEXT -> добавить текст в аккумулятор

Шаг 3: Прибыль !!

2 голосов
/ 17 мая 2011

Я почти уверен, что вы не можете сделать это, просто используя токены для регулярных выражений.Если вам нужно иметь дело с вложенными и экранированными разделителями, вам нужно написать парсер.Например, http://kore -nordmann.de / blog / do_NOT_parse_using_regexp.html

Будут парсеры с открытым исходным кодом, которые могут делать то, что вы хотите, хотя я не знаю ни одного.Вам также следует проверить класс StreamTokenizer.

1 голос
/ 17 мая 2011

Я думаю, что если вы используете шаблон так:

Pattern pattern = Pattern.compile("\".*?(?<!\\\\)\"|'.*?(?<!\\\\)'|[A-Za-z']+");

Тогда это даст вам желаемый результат. Когда я побежал с вашими входными данными, я получил этот список:

["Hello\" World", "Hello Universe", Hi]


Я использовал [A-Za-z']+ из вашего собственного вопроса, но разве это не должно быть просто: [A-Za-z]+

EDIT

Измените opts.add(token); строку на:

opts.add(token.replaceAll("^\"|\"$|^'|'$", ""));
0 голосов
/ 17 мая 2011

Первое, что вам нужно сделать, это перестать думать о работе в терминах split(). split() предназначен для разбиения простых строк, таких как this/that/the other, где / всегда является разделителем. Но вы пытаетесь разделить пробел, , если пробел не находится в кавычках, за исключением , если кавычки экранированы обратным слэшем (и если обратный слэш экранирует кавычки, возможно, они избегают других вещей, как и другие обратные слеши).

Со всеми этими исключениями просто невозможно создать регулярное выражение для сопоставления со всеми возможными разделителями, даже с такими причудливыми уловками, как обходные пути, условные выражения, неохотные и притяжательные квантификаторы. То, что вы хотите сделать, это сопоставить токены , а не разделители.

В следующем коде токен, заключенный в двойные или одинарные кавычки, может содержать пробел, а также символ кавычки, если ему предшествует обратный слеш. Все, кроме заключающих в кавычки, записывается в группу № 1 (для двойных кавычек) или группу № 2 (в одинарных кавычках). Любой символ может быть экранирован обратной косой чертой, даже в токенах без кавычек; «убегающие» обратные слеши удаляются в отдельном шаге.

public static void test()
{
  String str = "\"Hello\\\" World\" 'Hello Universe' Hi";
  List<String> commands = parseCommands(str);
  for (String s : commands)
  {
    System.out.println(s);
  }
}

public static List<String> parseCommands(String s)
{
  String rgx = "\"((?:[^\"\\\\]++|\\\\.)*+)\""  // double-quoted
             + "|'((?:[^'\\\\]++|\\\\.)*+)'"    // single-quoted
             + "|\\S+";                         // not quoted
  Pattern p = Pattern.compile(rgx);
  Matcher m = p.matcher(s);
  List<String> commands = new ArrayList<String>();
  while (m.find())
  {
    String cmd = m.start(1) != -1 ? m.group(1) // strip double-quotes
               : m.start(2) != -1 ? m.group(2) // strip single-quotes
               : m.group();
    cmd = cmd.replaceAll("\\\\(.)", "$1");  // remove escape characters
    commands.add(cmd);
  }
  return commands;
}

выход:

Hello" World
Hello Universe
Hi

Это примерно так же просто, как и для решения на основе регулярных выражений - и на самом деле оно не имеет дело с некорректным вводом, таким как несбалансированные кавычки. Если вы не владеете регулярными выражениями, возможно, вам лучше использовать решение, написанное вручную, или, что еще лучше, выделенную библиотеку интерпретатора командной строки (CLI).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...