Сложное регулярное выражение, получающее значение из строки - PullRequest
0 голосов
/ 10 ноября 2009

Вот некоторые входные образцы:

1, 2, 3
'a', 'b', 'c'
«А», «б», «с»
1, 'a', 'b'

Строки заключены в одинарные кавычки, а число - нет. В строках двойная одинарная кавычка '' (это два раза ') является escape-символом для одинарной кавычки'. Следующее также допустимый ввод.

'this' 'is''e string', 1, 2
'' 'this' 'странный', 1, 2
'' '' '' '', 1, 2

после долгой игры у меня получилось что-то вроде:

^(\\d*|(?:(?:')([a-zA-Z0-9]*)(?:')))(?:(?:, *)(\\d*|(?:(?:')([a-zA-Z0-9]*)(?:'))))*$

, который полностью не работает и не завершен:)

Используя Java matcher / group, можно привести пример:
вход: '' 'la''la', 1,3
подходящие группы:

  • la'la
  • 1
  • 2

Обратите внимание, что в выходной строке нет одинарных кавычек, а только экранированные кавычки из ввода.

есть ли гуру Regex? спасибо
PS: я дам вам знать, если я когда-нибудь сам это выясню, все еще пытаясь

Ответы [ 4 ]

2 голосов
/ 10 ноября 2009

Все строки вашего примера удовлетворяют следующему регулярному выражению:

('(''|[^'])*'|\d+)(\s*,\s*('(''|[^'])*'|\d+))*

Значение:

(               # open group 1
  '             #   match a single quote
  (''|[^'])*    #   match two single quotes OR a single character other than a single quote, zero or more times
  '             #   match a single quote
  |             #   OR
  \d+           #   match one or more digits
)               # close group 1
(               # open group 3
  \s*,\s*       #   match a comma possibly surrounded my white space characters
  (             #   open group 4
    '           #     match a single quote
    (''|[^'])*  #     match two single quotes OR a single character other than a single quote, zero or more times
    '           #     match a single quote
    |           #     OR
    \d+         #     match one or more digits
  )             #   close group 4
)*              # close group 3 and repeat it zero or more times

Небольшая демонстрация:

import java.util.*;
import java.util.regex.*;

public class Main { 

    public static List<String> tokens(String line) {
        if(!line.matches("('(''|[^'])*'|\\d+)(\\s*,\\s*('(''|[^'])*'|\\d+))*")) {
            return null;
        }
        Matcher m = Pattern.compile("'(''|[^'])*+'|\\d++").matcher(line);
        List<String> tok = new ArrayList<String>();
        while(m.find()) tok.add(m.group());
        return tok;
    }

    public static void main(String[] args) {
        String[] tests = {
                "1, 2, 3",
                "'a', 'b',    'c'",
                "'a','b','c'",
                "1, 'a', 'b'",
                "'this''is''one string', 1, 2",
                "'''this'' is a weird one', 1, 2",
                "'''''''', 1, 2",
                /* and some invalid ones */
                "''', 1, 2",
                "1 2, 3, 4, 'aaa'",
                "'a', 'b', 'c"
        };
        for(String t : tests) {
            System.out.println(t+" --tokens()--> "+tokens(t));
        }
    }
}

Выход:

1, 2, 3 --tokens()--> [1, 2, 3]
'a', 'b',    'c' --tokens()--> ['a', 'b', 'c']
'a','b','c' --tokens()--> ['a', 'b', 'c']
1, 'a', 'b' --tokens()--> [1, 'a', 'b']
'this''is''one string', 1, 2 --tokens()--> ['this''is''one string', 1, 2]
'''this'' is a weird one', 1, 2 --tokens()--> ['''this'' is a weird one', 1, 2]
'''''''', 1, 2 --tokens()--> ['''''''', 1, 2]
''', 1, 2 --tokens()--> null
1 2, 3, 4, 'aaa' --tokens()--> null
'a', 'b', 'c --tokens()--> null

Но разве вы не можете просто использовать вместо этого существующий (и проверенный) парсер CSV? Анализатор CSV Остермиллера приходит на ум.

1 голос
/ 10 ноября 2009

Возможно, вам лучше сделать это в два этапа; сначала разбейте его на поля, а затем постобработайте содержимое каждого поля.

\s*('(?:''|[^'])*'|\d+)\s*(?:,|$)

Должно соответствовать одному полю. Затем просто повторяйте каждое совпадение (чередуя .find() и затем .group(1)), чтобы захватить каждое поле по порядку. Вы можете преобразовать двойные апострофы в синглы после извлечения значения поля; просто замените простую строку на '' -> '.

1 голос
/ 10 ноября 2009

Ваша проблема в том, что у вас есть список ввода, который гарантированно будет в формате, который вы здесь показали, и вам просто нужно разделить его на отдельные элементы? Для этого вам, вероятно, вообще не нужно регулярное выражение.

Если строки не могут содержать запятые, просто разделите их запятыми, чтобы получить индивидуальные токены. Затем для токенов, которые не являются числами, удалите начальную / конечную кавычку. Затем замените «на». Проблема решена, регулярное выражение не требуется.

0 голосов
/ 10 ноября 2009

Сопоставление строк в кавычках с RegExp является сложным предложением. Для вас полезно, чтобы ваш текст разделителя был не просто одинарной кавычкой, а на самом деле это одинарная кавычка плюс одна из следующих: запятая, начало строки, конец строки. Это означает, что единственный раз, когда одинарные кавычки появляются в допустимой записи, как часть экранирования строки.

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

Возможно, в ваших же интересах очистить текст перед тем, как сопоставить его. Замените все \ экземпляры литералом \u005c, а затем все '' экземпляры литералом \u0027 (в этом порядке). Вы предоставляете здесь уровень экранирования, который оставляет строку без особых специальных символов.

Теперь вы можете использовать простой шаблон, такой как (?:(?:^\s*|\s*,\s*)(?:'([^']*)'|[^,]*?)))*\s*$

Вот разбивка этого шаблона (для ясности я использую терминологию «набор», чтобы указать группировку без захвата, и «группу», чтобы указать группировку захвата):

(?:               Open a non-capturing / alternation set 1
  (?:             Open a non-capturing / alternation set 2
    ^\s*          Match the start of the line and any amount of white space.
    |             alternation (or) for alternation set 2
    \s*,\s*       A comma surrounded by optional whitespace
  )               Close non-capturing group 2 (we don't care about the commas once we've used them to split our data)
  (?:             Open non-capturing set 3
    '([^']*)'     Capturing group #1 matching the quoted string value option.
    |             alternation for set 3.
    ([^,]*?)      Capturing group #2 matching non-quoted entries but not including a comma (you might refine this part of the expression if for example you only want to allow numbers to be non-quoted).  This is a non-greedy match so that it'll stop at the first comma rather than the last comma.
  )               Close non-capturing set 3
)                 Close non-capturing set 1
*                 Repeat the whole set as many times as it takes (the first match will trigger the ^ start of line, the subsequent matches will trigger the ,comma delimiters)
\s*$              Consume trailing spaces until the end of line.

Ваши параметры в кавычках будут в группе захвата 1, параметры в кавычках будут в группе 2 захвата. Все остальное будет отброшено.

Затем переберите соответствующие записи и измените кодировку (замените \u0027 на ' и \u005c на \ в указанном порядке), и все готово.

Это должно быть достаточно отказоустойчиво и правильно анализировать некоторые тупые технически некорректные, но восстанавливаемые сценарии, такие как 1, a''b, 2, но при этом не работать с невосстановимыми значениями, такими как 1, a'b, 2, при успешном выполнении технически правильной (но, вероятно, непреднамеренной) записи 1, 'ab, 2'

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