Разбор текста, структурированного как дерево с колонками фиксированной ширины, с использованием parslet в ruby
Я застрял. Уже пару дней пытаюсь разобрать этот текст (смотри внизу). Но не могу понять некоторые вещи. Сначала текст форматируется в древовидной структуре со столбцами фиксированной ширины, но точная ширина столбца зависит от самого широкого поля.
Я использую ruby, сначала я попробовал гем Treetop и добился некоторого прогресса, но затем решил попробовать Parslet, поэтому я использую его сейчас, и, кажется, с ним должно быть проще, но трудно найти подробную документацию для него.
в настоящее время я анализирую каждую строку отдельно и создаю массив с проанализированными записями, но это не правильно, так как я теряю структуру. Мне нужно разобрать его рекурсивно и обработать глубину.
Буду очень признателен за любые советы, идеи, предложения.
Вот мой текущий код, он работает, но все данные сведены. Моя текущая идея состоит в том, чтобы рекурсивно анализировать, если текущая начальная позиция строки больше, чем предыдущие (т.е. ширина), таким образом, это означает, что мы должны перейти на более глубокий уровень. На самом деле мне удалось сделать это так, но потом я не смог выбраться наружу должным образом, поэтому я удалил этот код.
require 'pp'
require 'parslet'
require 'parslet/convenience'
class TextParser < Parslet::Parser
@@width = 5
root :text
rule(:text) { (line >> newline).repeat }
rule(:line) { left >> ( topline | subline ).as(:entry) }
rule(:topline) {
float.as(:number) >> str('%') >> space >> somestring.as(:string1) >> space >> specialstring.as(:string2) >> space >> specialstring.as(:string3)
}
rule(:subline) {
dynamic { |source, context|
width = context.captures[:width].to_s.length
width = width-1 if context.captures[:width].to_s[-1] == '|'
if width > @@width
# should be recursive
result = ( specialline | lastline | otherline | empty )
else
result = ( specialline | lastline | otherline | empty )
end
@@width = width
result
}
}
rule(:otherline) {
somestring.as(:string1)
}
rule(:specialline) {
float.as(:number) >> str('%') >> dash >> space? >> specialstring.as(:string1)
}
rule(:lastline) {
float.as(:number) >> str('%') >> dash >> space? >> str('[...]')
}
rule(:empty) {
space?
}
rule(:left) { seperator.capture(:width) >> dash?.capture(:dash) >> space? }
rule(:somestring) { match['0-9A-Za-z\.\-'].repeat(1) }
rule(:specialstring) { match['0-9A-Za-z&()*,\.:<>_~'].repeat(1) }
rule(:space) { match('[ \t]').repeat(1) }
rule(:space?) { space.maybe }
rule(:newline) { space? >> match('[\r\n]').repeat(1) }
rule(:seperator) { space >> (str('|') >> space?).repeat }
rule(:dash) { space? >> str('-').repeat(1) }
rule(:dash?) { dash.maybe }
rule(:float) { (digits >> str('.') >> digits) }
rule(:digits) { match['0-9'].repeat(1) }
end
parser = TextParser.new
file = File.open("text.txt", "rb")
contents = file.read.to_s
file.close
pp parser.parse_with_debug(contents)
Текст выглядит следующим образом ( https://gist.github.com/davispuh/4726538)
1.23% somestring specialstring specialstring
|
--- specialstring
|
|--12.34%-- specialstring
| specialstring
| |
| |--12.34%-- specialstring
| | specialstring
| | |
| | |--12.34%-- specialstring
| | --1.12%-- [...]
| |
| --2.23%-- specialstring
| |
| |--12.34%-- specialstring
| | specialstring
| | specialstring
| | |
| | |--12.34%-- specialstring
| | | specialstring
| | | specialstring
| | --1.23%-- [...]
| |
| --1.23%-- [...]
|
--1.05%-- [...]
1.23% somestring specialstring specialstring
2.34% somestring specialstring specialstring
|
--- specialstring
specialstring
specialstring
|
|--23.34%-- specialstring
| specialstring
| specialstring
--34.56%-- [...]
|
--- specialstring
specialstring
|
|--12.34%-- specialstring
| |
| |--100.00%-- specialstring
| | specialstring
| --0.00%-- [...]
--23.34%-- [...]
Спасибо:)
1 ответ
Я собирался сказать то же самое, что и "Оловянный человечек". Должен быть другой формат, в котором вы можете генерировать данные.
Однако, если вы хотите разобрать это... Парслет работает как карта / алгоритм уменьшения. Ваш первый проход (разбор) не предназначен для того, чтобы дать вам окончательный вывод, а просто для того, чтобы собрать всю необходимую вам информацию из исходного документа.
Как только вы сохранили это в дереве, вы можете преобразовать его, чтобы получить желаемый результат.
Итак... Я бы написал парсер, который записывает каждое пустое пространство как узел, а также сопоставляет текст и проценты, которые вам нужны. Я бы сгруппировал узлы пробелов в узле "отступ".
Затем я использовал бы преобразование, чтобы заменить узлы пробелов числом узлов для вычисления отступов.
Помните: Parslet генерирует стандартный хэш рубина. Затем вы можете написать любой код, который вам нравится, чтобы понять это дерево.
Парсер просто конвертирует текстовый файл в структуру данных, которой вы можете манипулировать.
Просто чтобы повторить, хотя. Я думаю, что у "Жестянщика" есть правильный ответ... вместо этого генерируйте данные машиночитаемым способом.
Обновить:
Для альтернативного подхода вы можете проверить: чувствительный к отступам парсер с использованием Parslet в Ruby?