Использование ast.literal_eval(s)
прекрасно работает, если нет неэкранированных кавычек.Мы можем исправить это с помощью регулярных выражений.К сожалению, это не так просто.Тем не менее, я получил серебряную пулю для вас.
Сначала давайте определим все цитируемые подстроки, которые имеют внутренние кавычки одного типа:
(?<=[\[,])\s*(['\"])(?:(\1)|.)*?\1(?=\s*[,\]])
Пояснение
(?<=[\[,])
передний якорь с использованием lookbehind: начало списка или предыдущий элемент списка: [
или ,
\s*
ноль или более пробелов (['\"])
открывающая кавычка в группе захвата (?:(\1)|.)*?
ленивое совпадение с любым символом, если та же кавычка, что и в $1
, вставьте его в $2
\1
закрывающая кавычка, используя обратнуюссылка (?=\s*[,\]])
задний якорь с использованием указателя: ,
или ]
Демо
Теперь начинается настоящее веселье.Далее, ищите подстроки, которые имеют внутренние кавычки одного и того же типа для неэкранированных кавычек.
Для этого я использую подход Skip_what_to_avoid с некоторой продвинутой техникой регулярных выражений, такой как развернутый цикл .
^"[^"\\]*(?:\\.[^\"\\]*)*"$|^'[^'\\]*(?:\\.[^'\\]*)*'$|^\s*["'][^"'\\]*|["']\s*$|\\.|(["'])[^"'\\\n]*
Ключевой идеей здесь является полное игнорирование общих совпадений, возвращаемых механизмом регулярных выражений: $0
- это мусорное ведро.Вместо этого нам нужно только проверить группу захвата $1
, которая, когда установлена, содержит то, что мы ищем.По сути, все эти незафиксированные чередования соответствуют обычным, правильно экранированным подстрокам, заключенным в кавычки.Нам нужны только неэкранированные.
Demo2
Я решил собрать исходную строку с необходимыми \
, добавленными для правильного экранирования техне спасенные цитаты.Конечно, есть и другие способы.
Наконец, собираем вместе весь пример кода ( онлайн-демонстрация ):
import re
import ast
test = ("[ \"A\", \"\"B\"\",'C' , \" D\"]\n"
"[ \"A\", \"'B'\",'C' , \" D\"]\n"
"[ \"A\", ''B'','C' , \" D\"]\n"
"[ \"A\", '\"B\"','C' , \" D\"]\n"
"[ \"A\", '8 o'clock','C' , \" D\"]\n"
"[ \"A\", \"Ol' 8 o'clock\",'C' , \" D\"]\n"
"[\"Some Text\"]\n"
"[\"Some more Text\"]\n"
"[\"Even more text about \\\"this text\\\"\"]\n"
"[\"Ol' 8 o'clock\"]\n"
"['8 o'clock']\n"
"[ '8 o'clock']\n"
"['Ol' 8 o'clock']\n"
"[\"\"B\"]\n"
"[\"\\\"B\"]\n"
"[\"\\\\\"B\"]\n"
"[\"\\\\\\\"B\"]\n"
"[\"\\\\\\\\\"B\"]")
result = u''
last_index = 0
regex1 = r"(?<=[\[,])\s*(['\"])(?:(\1)|.)*?\1(?=\s*[,\]])" #nested quotes of the same type
regex2 = r'''^"[^"\\]*(?:\\.[^\"\\]*)*"$|^'[^'\\]*(?:\\.[^'\\]*)*'$|^\s*["'][^"'\\]*|["']\s*$|\\.|(["'])[^"'\\\n]*''' # unescaped quotes in $1
matches = re.finditer(regex1, test, re.MULTILINE)
for match in matches:
if match.groups()[1] is not None: #nested quotes of the same type present
print(match.group())
inner = re.finditer(regex2, match.group())
for m in inner:
if m.groups()[0] is not None: # unescaped quotes in $1 present
result += test[last_index:match.start() + m.start()] + '\\' + m.group()
last_index = match.start()+m.end()
result += test[last_index:len(test)]
print(result)
for test_str in result.split("\n"):
List = ast.literal_eval(test_str)
print(List)
print(type(List))
У нас есть тестовая строка, которая содержит несколько списков:строковый литерал со всеми видами особых случаев: разные и одинаковые типы кавычек, экранированные кавычки, экранированные экранирующие последовательности и т. д. Тестовая строка очищается с использованием описанного выше решения split
, а затем индивидуально преобразуется в список с использованием literal_eval
.
Это, пожалуй, одно из самых совершенных решений регулярных выражений, которое я когда-либо создавал.