LINUX.ORG.RU

Распарсить текстовый файл. Задача** с двумя звездочками

 


0

2

Дано:

Тысячи текстовых файлов в которых могут содержаться, помимо текста, куски yaml,json,toml документов, base64 и прочего

Наглядный пример:

$ cat 1.txt
some amazing text data
another cool text line

spec:
  container:
     name: abc
another cool text line with many words
yeah, 42 42 42
{
   "foo": "bar",
   "user": "alice"
}
oh wait, here is another cool text line
and here! another text line
и еще немного текста, а потом

0J/QvtCy0YHRgtGA0LXRh9Cw0LLRiNC40YHRjCDRgdC70YPRh9Cw0LnQvdC+INC90LAg0LLQtdGH0L3QvtC5INC00L7RgNC+0LPQtQrQkdC10Lcg0YHQu9C+0LIg0YEg0YLQvtCx0L7QuSDQvtGB0L7Qt9C90LDQu9C4Cg==

и еще текст

задача* со звездочкой: я ищу готовые решения которые помогли бы определить что в файле есть кроме текста, json, yaml, toml, base64, etc документы

$ cat 1.txt | magicfile
application/yaml
application/json
application/base64

задача** с двумя звездочками: «вычленить» эти документы из текстового файла, что-то в духе

$ cat 1.txt | magicextract 
json: | 
{
   "foo": "bar",
   "user": "alice"
}
     
yaml: |
  spec:
    container:
       name: abc

base64: |
   0J/QvtCy0YHRgtGA0LXRh9Cw0LLRiNC40YHRjCDRgdC70YPRh9Cw0LnQvdC+INC90LAg0LLQtdGH0L3QvtC5INC00L7RgNC+0LPQtQrQkdC10Lcg0YHQu9C+0LIg0YEg0YLQvtCx0L7QuSDQvtGB0L7Qt9C90LDQu9C4Cg==

я смотрю в сторону ast-grep и написания кастомных правил, либо запуск neovim, и через remote API подсовывать ему эти файлики и через LSP сервера попробовать вычленить куски документов.

В идеале нужно что-то в духе apache-tika, которому можно скармливать документ(1.txt,2.txt) и на выходе получать распарсенные куски json,yaml,etc документов

Всезнающий ЛОР в какую сторону копать? последним вариантом я рассматриваю написание очередной неинтересной программы которая бы их парсила, но я уверен что есть готовые решения

Написать сценарий, который поделит файл на части по символу переноса строки \r\n, за исключением }\r\n, а также исключать строки, начинающиеся с пробелов. Дальше куски проверять на соответствие yaml, json, либо base64

Сделается за час

r0ck3r ★★★★★
()
Последнее исправление: r0ck3r (всего исправлений: 2)

Эта задача в принципе не решаема, ибо нет стопроцентного строгого правила определяющего, что строка, условно, «helloworld» является обычным текстом, а не текстом в кодировке base64. То есть, сделать что-то такое, наверное, можно, но в общем случае вы получите некоторое количество промахов на случаях, когда строка валидна для более чем одного формата.

FishHook
()

написать скрипт на перле, который бы анализировал строки 1..1, 1..2, 1..3, 1..a, на предмет формата, до тех пор, пока одна из магий сработает, и останавливаемся на том месте, где перестанет действовать магия. Когда сработала магия, и стало ясно что за формат, и он распарсен и выведен в выходной файл, делаем следующий шаг a+1...a+2, a+1...a+3, a+1...a+4, a+1...b и так до конца

Infra_HDC ★★★★★
()
Ответ на: комментарий от Infra_HDC

написать скрипт на перле, который бы анализировал строки 1..1, 1..2, 1..3, 1..a, на предмет формата, до тех пор, пока одна из магий сработает, и останавливаемся на том месте, где перестанет действовать магия.

Это сработает если файл начинается с магического текста, а если с обычного? Надо же как-то понять, где магия начинается, а не только где она заканчивается. То есть, боюсь вычислительная сложность будет что-то в районе N^2

FishHook
()
Ответ на: комментарий от FishHook

Эта задача в принципе не решаема, ибо нет стопроцентного строгого правила определяющего, что строка, условно, «helloworld» является обычным текстом, а не текстом в кодировке base64.

Да можно решить, просто там могут быть ложные срабатывания - но задача, очевидно, синтетическая, так что такое можно опустить.

Сначала проверяем строку на yaml/json/ещёчто (и принадлежность предыдущей конструкции, если есть), потом на base64. Всё, что не подходит к вышеперечисленному, это плейнтекст.

Bfgeshka ★★★★★
()
Последнее исправление: Bfgeshka (всего исправлений: 1)
Ответ на: комментарий от Infra_HDC

по одной строке вы не определите магию.

Например, вот это невалидный JSON?

["arstastastarstarst

А хрен его знает, для того чтобы ответить на этот вопрос надо проитерироваться по всему файлу до конца пытаясь провалидировать результат для каждой новой строки. А для форматов типа yaml алгорит будет еще жаднее, потому что вы не знаете где он заканчивается в виду отсутствия операторных скобок

FishHook
()
Последнее исправление: FishHook (всего исправлений: 1)

Тысячи текстовых файлов в которых могут содержаться, помимо текста, куски yaml,json,toml документов, base64 и прочего

в какую сторону копать?

Найти виновного повара такой каши.

dataman ★★★★★
()

Написать сценарий, который поделит файл на части по символу переноса строки \r\n Это последний вариант, к которому я бы прибегнул

Эта задача в принципе не решаема Мне достаточно, если бы алгоритм работал бы (условно) в 80% случаев

Мне кажется, что эта задача уже решена в LSP.

Один из вариантов, это запустить $EDITOR (nvim), подключить к нему LSP плагины требуемых мне «языков», и через remote api https://neovim.io/doc/user/api.html запихивать в neovim эти 1.txt, 2.txt, 3.txt и каким-либо образом, на языке lua опрашивать LSP сервера по имеющимся codesnippet в редактируемом файле и получать ответ.

gagarin0
() автор топика
Ответ на: комментарий от ugoday

Просто из любопытства: как вы дожили до такой жизни, что нужно решать такие задачи?

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

Сценарий:

  1. пользователь открывает gnome-terminal
  2. в шелле набирает cat foobar.json
  3. при помощи GUI у пользователя в «доке» появляется кнопочка, нажав на которую он откроет терминал и запихнет foobar.json в программу jnv https://github.com/ynqa/jnv

вместо тысячи слов

gagarin0
() автор топика

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

Начните с конца. Я сомневаюсь, что есть готовый мульти анализатор синтаксиса.

dmitry237 ★★★★
()
Ответ на: комментарий от gruy

Тренировать уже и не надо.

Chatgpt справляется с этим вполне уверенно, но медленно, у меня этих документов много, и на каждый уходит примерно по 7 секунд, а мне хотелось бы «on the fly»

gagarin0
() автор топика
Ответ на: комментарий от sin_a

Цитата из первого поста

spec:
  container:
     name: abc
another cool text line with many words
yeah, 42 42 42
{
   "foo": "bar",
   "user": "alice"
}
oh wait, here is another cool text line
and here! another text line
и еще немного текста, а потом

Отсутсвует разделитель, есть 4 «codesnippet»:

  1. application/yaml
  2. application/text
  3. application/json
  4. application/text
gagarin0
() автор топика

Валидный JSON является валидным YAML. Более того, простая строка текста тоже является валидным YAML. И даже вот это валидный YAML:

Дама сдавала в багаж:
- диван,
- чемодан,
- саквояж.
static_lab ★★★★★
()
Ответ на: комментарий от ya-betmen

Это yaml, какие могут быт сомнения?

$ echo "['a:"0LIg0YEg0YLQvtCx0L7QuSDQvtGB0L7Qt9C90LDQu9C4Cg=="]']" | jq
jq: parse error: Invalid numeric literal at line 1, column 4
$ echo "['a:"0LIg0YEg0YLQvtCx0L7QuSDQvtGB0L7Qt9C90LDQu9C4Cg=="]']" | yq
[
  "a:0LIg0YEg0YLQvtCx0L7QuSDQvtGB0L7Qt9C90LDQu9C4Cg==]"
]
$ echo "['a:"0LIg0YEg0YLQvtCx0L7QuSDQvtGB0L7Qt9C90LDQu9C4Cg=="]']" | base64 -d
base64: invalid input
gagarin0
() автор топика
Ответ на: комментарий от gagarin0

в которых могут содержаться, помимо текста, куски

Давай для понятности

 
Blahblahblah ['a:"0LIg0YEg0YLQvtCx0L7QuSDQvtGB0L7Qt9C90LDQu9C4Cg=="]'] 
ya-betmen ★★★★★
()
Последнее исправление: ya-betmen (всего исправлений: 1)
Ответ на: комментарий от FishHook

а следовательно не может быть непротиворечиво реализовано.

Мне достаточно будет дешевого, быстрого, решения которое срабатывало бы в 80% случаев

gagarin0
() автор топика
Ответ на: комментарий от gagarin0

в шелле набирает cat foobar.json

Так может вам стоит cat подменить? Тогда можно сделать предположение, что весь текст относится к одному из доступных типов и не нужно границы между yaml’ом и json’ом определять.

ugoday ★★★★★
()
Ответ на: комментарий от ugoday

Подмена cat не подойдет, по следующим причинам:

  1. json может прилететь из другой cli утилиты, например aws cli или kubectl

  2. это должно работать на «удаленных» окружениях

$ ssh remote-host cat foobar.json
  1. это не будет работать при подключение к условному сетевому оборудованию (cisco, dlink, etc)
gagarin0
() автор топика

Подскажите, я создал этот топик в Development форуме или его сюда перенесли из General? Вроде, я создавал топик в General

В Development понабижали программисты которые мне говорят что у меня данные какие-то не такие.

gagarin0
() автор топика
Ответ на: комментарий от gagarin0

Тогда жопа. Либо сразу свой эмулятор терминала писать (можно взять st за основу, там всего около 5к кодострок на православном С), либо ваять хитрый конечный автомат. Иначе, если бить файл на куски и запускать внешний проверятор, то простой файл на тысячу строк может быть разбит на подстроки 10715086071862673209484250490600018105614048117055336074437503883703510511249361224931983788156958581275946729175531468251871452856923140435984577574698574803934567774824230985421074605062371141877954182153046474983581941267398767559165543946077062914571196477686542167660429831652624386837205668069376-ю способами и удачи вам жить с этим.

ugoday ★★★★★
()
Ответ на: комментарий от ugoday

Либо сразу свой эмулятор терминала писать

Не нужно. есть asciinema, есть tmux pipe-open

Иначе, если бить файл на куски и запускать внешний проверятор то простой файл на тысячу строк может быть разбит на подстроки

За этим я сюда и пришел

удачи вам жить с этим.

Вашими молитвами

я ловлю такие вот cloudevent сообщения

{
  "id": "string",
  "source": "mindwm.",
  "specversion": "string",
  "type": "IoDocument",
  "datacontenttype": "string",
  "dataschema": "string",
  "subject": "IoDocument",
  "time": "2019-08-24T14:15:22Z",
  "data": {
    "input": "uptime",
    "output": "16:37:51 up 19 days,  2:15, 71 users,  load average: 2.01, 2.85, 2.95",
    "ps1": "user@mindwm-dev1:~/rnd/asyncapi$"
  },
  "knativebrokerttl": "255"
}

в .data.output как раз прилетают такие вот данные, как в 1.txt мне нужно понять сколько там сниппетов json, yaml и другие форматов (hcl, css, etc)

пока лучшее что у меня получилось, это начать решать проблему через nvim-treesitter-context плагин, выглядит это так:

require'nvim-treesitter.configs'.setup {
  ensure_installed = { "yaml", "json", "bash", "hcl", "ini", "css"}, 
}

function extract_code_snippets()
  snippets = {}
  local language_snippets = require'nvim-treesitter.configs'.get_ensure_installed_parsers()
  for idx, language in ipairs(language_snippets) do
      print(language)
      local bufnr = vim.api.nvim_get_current_buf()
      local parser = vim.treesitter.get_parser(bufnr, language)
      local syntax_tree = parser:parse()[1]
      local root_node = syntax_tree:root()
      for node in root_node:iter_children() do
         print(vim.inspect(node:type()))
         if node:type() ~= "ERROR" then
           local snippet_text = vim.treesitter.get_node_text(node, bufnr)
           table.insert(snippets, { lanauge = language, snippet = snippet_text })
        end
      end
  end
  print(vim.inspect(snippets))
  return snippets
end

с горем пополам оно работает

вот таким образом этот nvim может работать как api:

NVIM_LISTEN_ADDRESS=127.0.0.1:7450 nvim

и при помощи вот этой питонячки можно сделать запрос в nvim и получить структуру от плагина nvim-treesitter-context

import neovim
nvim = neovim.attach('tcp', path='127.0.0.1', port=7450)
file_path = "1.txt"
# Read buffer content from the file
with open(file_path, 'r') as file:
    buffer_content = file.readlines()

nvim.command('enew')  # Creates a new buffer
buffer = nvim.current.buffer
buffer[:] = [line.rstrip('\n') for line in buffer_content]

result = nvim.exec_lua('return extract_code_snippets()', [])
print(result)

Если все получится, то nvim нужно будет выкинуть, и дергать nvim-treesitter-context прямо из lua, если такое вообще возможно

gagarin0
() автор топика
Ответ на: комментарий от ugoday

а какова производительность такого решения? Ваши вопросы преждевременны.

Если, допустим, 10 тысяч раз в цикле дёрнуть ваш 1.txt, сколько секунд понадобится?

Зависит от того насколько криво написан nvim-treesitter-context кодец на Lua (nvim в итоге будет выкинуть, если это возможно).

В целом, у меня к «производительности» стека lua + regex никогда не возникало, а у Вас?

Для начала я хотел бы получить рабочий прототип из говна и палок, и полном «замерять метрики»

gagarin0
() автор топика

Уважаемые модераторы, перенесите пожалуйста этот топик из Development куда-нибудь в другое место, на меня тут напали программисты с упреками в неправильных исходных данных и метриками на 10000 запросов

Я особо разрабатывать ничего не хочу, я хочу использовать готовые решения.

gagarin0
() автор топика
Ответ на: комментарий от ugoday

Держу в курсе:

Спросил в комьюнити tree-sitter по поводу этой задачи, ответ был такой: tree-sitter предназначен в первую очередь для подсветки синтаксиса, и то что мне нужно сделать можно, но потребуются большие трудозатраты.

В принципе tree-sitter json хорошо выдирает из мусора:

function extract_json()
  local snippets = {}
  local bufnr = vim.api.nvim_get_current_buf()
  local parser = vim.treesitter.get_parser(bufnr, "json", {})
  local syntax_tree = parser:parse()[1]
  local root_node = syntax_tree:root()
  for node in root_node:iter_children() do
    if node:type() == "object" then
        local snippet_text = vim.treesitter.get_node_text(node, bufnr)
        table.insert(snippets, { language = "json", snippet = snippet_text })
    end
  end
  print(vim.inspect(snippets))
  return snippets
end

забавная «метрика», посылаем тысячу раз в запущенный neovim заданный текст и вызываем lua функцию для парсинга текста, в 50 потоков, цифры такие:

$ time (seq 1 1000 | xargs -I{} -P50 sh -c 'python ./buffer.py') >/dev/null

real    0m9.603s
user    2m56.619s
sys     0m23.156s

Открыл для себя возможность использовать neovim как бекенд для парсинга и обработки текста.

Сейчас буду пробовать из LSP серверов вытащить контекст.

@gruy chatgpt справляется с этой задачей довольно уверенно, можно сделать прототип с бекендом chatgpt, дешево в реализации.

snippet_type: YAML, line_start: 5, line_end: 7
snippet_type: JSON, line_start: 9, line_end: 12
snippet_type: Terraform, line_start: 14, line_end: 18
gagarin0
() автор топика

Вот эксперимент в LSP серверами, для истории neovim нужно запускать вот так:

neovim --listen 
$ cat requirements.txt
pynvim
pyyaml
$ python3 -m venv .venv
$ . venv/bin/activate
$ pip3 install -r requirements.txt 
mkdir -p tmp variable

Нижеприведенный код, подключается к neovim, запускает три LSP сервера для hcl, yaml, json

$ ps wwwx | grep -i mason 
  92857 ?        Ssl    0:00 /home/gagarin0/.local/share/nvim/mason/bin/terraform-ls serve
  92880 ?        Ssl    0:00 node /home/gagarin0/.local/share/nvim/mason/bin/vscode-json-language-server --stdio
  92881 ?        Ssl    0:00 node /home/gagarin0/.local/share/nvim/mason/bin/yaml-language-server --stdio

И посылает содержимео указанного в файла в neovim буффер, и далее для каждого подключенного LSP сервера дергает все методы которые есть в LSP спецификации https://neovim.io/doc/user/lsp.html и сохраняет в директорию variable/{lang}_{method_name}

В итоге, у меня сложилось впечатление что LSP «ломается» первой же нераспарсенной строки. Во всяком случаее это было с terraform-ls LSP сервером

(python код, который вызывает удаленно lua код внутри neovim инстанса)

import neovim
import pprint
import sys
nvim = neovim.attach('tcp', path='127.0.0.1:7450')
file_path = sys.argv[1]
# Read buffer content from the file
with open(file_path, 'r') as file:
    buffer_content = file.readlines()

nvim.command('w! tmp/xxx.tf')
nvim.command('set filetype=terraform')
nvim.command('set filetype=json')
nvim.command('set filetype=yaml')

buffer = nvim.current.buffer
buffer[:] = [line.rstrip('\n') for line in buffer_content]

result = nvim.exec_lua('''
local function dump_variable_to_file(variable_name, variable_value)
  local file_path = "variable/" .. variable_name
  local file = io.open(file_path, "w")

  if not file then
    --print("Error: Could not open file " .. file_path)
    return
  end

  file:write(variable_value)

  file:close()

 
local function request_from_all_lsp(request_type)
    local bufnr = vim.api.nvim_get_current_buf()
    local position = vim.api.nvim_win_get_cursor(0)
    local params = vim.lsp.util.make_position_params()
    local results = vim.lsp.buf_request_sync(bufnr, request_type, params)
    local lsp = vim.lsp
    -- Print the results from all clients
    if results then
        for client_id, res in pairs(results) do
            client = lsp.get_active_clients({ id = client_id })[1]
            lsp_name = client["config"]["name"]
            output_name = lsp_name .. "_" .. request_type:gsub("/", "_")
            --print('Client ID: ', client_id)
            if res.result then
                dump_variable_to_file(output_name, vim.inspect(res.result))
            else
               --print('No result from client ID:', client_id)
                dump_variable_to_file(output_name, "err")
            end
        end
    else
       --print('No results received from LSP clients.')
    end
end

-- Run the function to get the LSP responses
local lsp_methods = {
    "callHierarchy/incomingCalls",
    "callHierarchy/outgoingCalls",
    "textDocument/codeAction",
    "textDocument/completion",
    "textDocument/declaration",
    "textDocument/definition",
    "textDocument/diagnostic",
    "textDocument/documentHighlight",
    "textDocument/documentSymbol",
    "textDocument/formatting",
    "textDocument/hover",
    "textDocument/implementation",
    "textDocument/inlayHint",
    "textDocument/prepareTypeHierarchy",
    "textDocument/publishDiagnostics",
    "textDocument/rangeFormatting",
    "textDocument/rangesFormatting",
    "textDocument/references",
    "textDocument/rename",
    "textDocument/semanticTokens/full",
    "textDocument/semanticTokens/full/delta",
    "textDocument/signatureHelp",
    "textDocument/typeDefinition",
    "typeHierarchy/subtypes",
    "typeHierarchy/supertypes",
    "window/logMessage",
    "window/showMessage",
    "window/showDocument",
    "window/showMessageRequest",
    "workspace/applyEdit",
    "workspace/configuration",
    "workspace/executeCommand",
    "workspace/inlayHint/refresh",
    "workspace/symbol",
    "workspace/workspaceFolders"
}

-- Loop over the array and call the function for each element
for _, method in ipairs(lsp_methods) do
    request_from_all_lsp(method)
end
''', [])
#
pprint.pprint(result)
gagarin0
() автор топика
Ответ на: комментарий от FishHook

Чисто теоретически скорее всего лучше всего эту задачу решит скармливать текст какой-то нейронке вроде чатагпт. Но понятно что и там без галлюцинаций не обойтись.

peregrine ★★★★★
()
Ответ на: комментарий от gagarin0

Всё остальное будет сильно дороже и затратнее, т.к. задачу даже сложно формализовать. Ты же даже не знаешь всё что может быть этим сниппетом. Может вообще какой-то ini файл юзер через cat смотрит.

peregrine ★★★★★
()