Как отмечается в комментариях, эту задачу невозможно выполнить с помощью регулярных выражений.Regex принципиально не способен обрабатывать вложенные конструкции.Вам нужен парсер.
Одним из способов создания парсера является PEG , который позволяет настроить список токенов и их отношения друг с другом на декларативном языке.Это определение синтаксического анализатора затем превращается в фактический анализатор, который может обрабатывать описанный ввод.При успешном разборе вы получите древовидную структуру со всеми правильно вложенными элементами.
В демонстрационных целях я использовал реализацию JavaScript peg.js, которая имеет интерактивную демонстрационную страницу где вы можете вживую тестировать парсеры против некоторого ввода.Это определение синтаксического анализатора:
{
// [value, [[delimiter, value], ...]] => [value, value, ...]
const list = values => [values[0]].concat(values[1].map(i => i[1]));
}
document
= line*
line "line"
= value:(item (whitespace item)*) whitespace? eol { return list(value) }
item "item"
= number / string / group
group "group"
= "[" value:(item (comma item)*) whitespace? "]" { return list(value) }
comma "comma"
= whitespace? "," whitespace?
number "number"
= value:$[0-9.]+ { return +value }
string "string"
= $([^ 0-9\[\]\r\n,] [^ \[\]\r\n,]*)
whitespace "whitespace"
= $" "+
eol "eol"
= [\r]? [\n] / eof
eof "eof"
= !.
может понять этот тип ввода:
c0 c1 c2 c3 c4 c5
0 10 100.5 [1.5, 2] [[10, 10.4], [c, 10, eee]] [[a , bg], [5.5, ddd, edd]]
1 20 200.5 [2.5, 2] [[20, 20.4], [d, 20, eee]] [[a , bg], [7.5, udd, edd1]]
и создает это дерево объектов (нотация JSON):
[
["c0", "c1", "c2", "c3", "c4", "c5"],
[0, 10, 100.5, [1.5, 2], [[10, 10.4], ["c", 10, "eee"]], [["a", "bg"], [5.5, "ddd", "edd"]]],
[1, 20, 200.5, [2.5, 2], [[20, 20.4], ["d", 20, "eee"]], [["a", "bg"], [7.5, "udd", "edd1"]]]
]
то есть
- массив строк,
- , каждая из которых представляет собой массив значений,
- , каждая из которых может быть либо числом, либо строкой, либо другим массивом.значений
Эта древовидная структура может быть обработана вашей программой.
Вышеописанное будет работать, например, с node.js, чтобы превратить ваш ввод в JSON.Следующая минимальная JS-программа принимает данные из STDIN и записывает проанализированный результат в STDOUT:
// reference the parser.js file, e.g. downloaded from https://pegjs.org/online
const parser = require('./parser');
var chunks = [];
// handle STDIN events to slurp up all the input into one big string
process.stdin.on('data', buffer => chunks.push(buffer.toString()));
process.stdin.on('end', function () {
var text = chunks.join('');
var data = parser.parse(text);
var json = JSON.stringify(data, null, 4);
process.stdout.write(json);
});
// start reading from STDIN
process.stdin.resume();
Сохраните его как text2json.js
или что-то в этом роде и перенаправьте (или передайте) некоторый текст в него:
# input redirection (this works on Windows, too)
node text2json.js < input.txt > output.json
# common alternative, but I'd recommend input redirection over this
cat input.txt | node text2json.js > output.json
Существуют также генераторы синтаксического анализатора PEG для Python, например, https://github.com/erikrose/parsimonious. Язык создания синтаксического анализатора отличается в разных реализациях, поэтому приведенное выше можно использовать только для peg.js, но принцип в точностито же самое.
РЕДАКТИРОВАТЬ Я копался в Parsimonious и воссоздал вышеуказанное решение в коде Python.Подход тот же, грамматика синтаксического анализатора та же, с небольшими синтаксическими изменениями.
from parsimonious.grammar import Grammar
from parsimonious.nodes import NodeVisitor
grammar = Grammar(
r"""
document = line*
line = whitespace? item (whitespace item)* whitespace? eol
item = group / number / boolean / string
group = "[" item (comma item)* whitespace? "]"
comma = whitespace? "," whitespace?
number = "NaN" / ~"[0-9.]+"
boolean = "True" / "False"
string = ~"[^ 0-9\[\]\r\n,][^ \[\]\r\n,]*"
whitespace = ~" +"
eol = ~"\r?\n" / eof
eof = ~"$"
""")
class DataExtractor(NodeVisitor):
@staticmethod
def concat_items(first_item, remaining_items):
""" helper to concat the values of delimited items (lines or goups) """
return first_item + list(map(lambda i: i[1][0], remaining_items))
def generic_visit(self, node, processed_children):
""" in general we just want to see the processed children of any node """
return processed_children
def visit_line(self, node, processed_children):
""" line nodes return an array of their processed_children """
_, first_item, remaining_items, _, _ = processed_children
return self.concat_items(first_item, remaining_items)
def visit_group(self, node, processed_children):
""" group nodes return an array of their processed_children """
_, first_item, remaining_items, _, _ = processed_children
return self.concat_items(first_item, remaining_items)
def visit_number(self, node, processed_children):
""" number nodes return floats (nan is a special value of floats) """
return float(node.text)
def visit_boolean(self, node, processed_children):
""" boolean nodes return return True or False """
return node.text == "True"
def visit_string(self, node, processed_children):
""" string nodes just return their own text """
return node.text
DataExtractor
отвечает за обход дерева и извлечение данных из узлов, возвращая спискистроки, числа, логические значения или NaN.
Функция concat_items()
выполняет ту же задачу, что и функция list()
в приведенном выше коде Javascript, другие функции также имеют свои эквиваленты в подходе peg.js,за исключением того, что peg.js интегрирует их непосредственно в определение синтаксического анализатора, а Parsimonious ожидает определения в отдельном классе, так что это немного сложнее в сравнении, но не так уж плохо.
Использование при условии, что входной файл называется «data».txt ", также отражает код JS:
de = DataExtractor()
with open("data.txt", encoding="utf8") as f:
text = f.read()
tree = grammar.parse(text)
data = de.visit(tree)
print(data)
Ввод:
orig shifted not_equal cumsum lst
0 10 NaN True 1 [[10, 10.4], [c, 10, eee]]
1 10 10.0 False 1 [[10, 10.4], [c, 10, eee]]
2 23 10.0 True 2 [[10, 10.4], [c, 10, eee]]
Ввод:
[
['orig', 'shifted', 'not_equal', 'cumsum', 'lst'],
[0.0, 10.0, nan, True, 1.0, [[10.0, 10.4], ['c', 10.0, 'eee']]],
[1.0, 10.0, 10.0, False, 1.0, [[10.0, 10.4], ['c', 10.0, 'eee']]],
[2.0, 23.0, 10.0, True, 2.0, [[10.0, 10.4], ['c', 10.0, 'eee']]]
]
В долгосрочной перспективе я бы ожидал такой подходбыть более понятным и гибким, чем хакерство регулярных выражений.Например, было легко добавить явную поддержку NaN и логических значений (которых нет в приведенном выше peg.js-Solution - там они анализируются как строки).