Как стать автором
Обновить

Shiva — Open Source проект на Rust для парсинга и генерации документов любого типа

Уровень сложностиСредний
Время на прочтение3 мин
Количество просмотров10K
Всего голосов 24: ↑21 и ↓3+23
Комментарии31

Комментарии 31

Как текст извлекается из pdf?

Учитывается ли взаимное положение элементов в документе или текст склеивается 'в том порядке что их разместил автор' даже если он редактировал его и верхние части документа добавил последними? Правильно ли распознается многоколоночные документы? Наличие формул и графиков и т.п.

Ну и в догонку, а как быть с pdf без текстовой информации? как картинка или векторный ps? А поддержка русского и не только языка?

Признаюсь, пока с этим сложно. Сделал MVP, чтобы обкатать технологию и модель. Но у меня впереди несколько лет, чтобы это всё закодить. Я настроен оптимистично. Прикручу нейросеть, чтобы картинки в векторе или битмапе распознавать как текст. Есть опыт работы с библиотекой Candle на Rust от Hugging Face — в общем, распознавание текста — это банальная задача. Короче, все решаемо. Не вижу здесь особого R&D. Я бы сказал, что все понятно, нужно только сидеть и терпеливо кодить.

И по поводу всего остального, касаемо формул и так далее, — я знаю, думаю и планирую. Просто нужно с чего-то начать. Сделать минимально жизнеспособный продукт (MVP). А потом, рефакторинг никто не отменял. Учитывая, что это молодая и быстро растущая библиотека, у меня нет миллионов пользователей, чтобы поддерживать старые API, так что пока мои руки развязаны. Вороти и твори, что хочешь :) Это потом, когда достигнешь популярности, становится сложнее — нужно поддерживать старые версии. Нельзя же бросать людей, которые доверились тебе и использовали твою библиотеку. Но это пока всё лишь мечты. Впереди несколько лет кодирования. )

Нельзя же бросать людей, которые доверились тебе и использовали твою библиотеку.

И мона, и нуна (с) из старого детского анекдота

Люди пусть продолжают использовать старую версию, а новая вполне может быть с несовместимым API. Ну или донаты никто не отменял (в размере полной оплаты поддержки старой версии).

Я конечно знаю про эту либу. Но она не на Rust )

Сразу вижу проблему: зачем в один трейт сложено чтение из документа и генерация? Есть много форматов, например DjVu, которые можно прочитать без зависимостей (вся метадата лежит по известному отступу), но хрен сгенерируешь.

И да, pandoc.

Спасибо! Хорошая идея. Когда я дойду до этой проблемы - сделаю рефакторинг.

Дело хорошее, удачи проекту!
По вашему ощущению, разработка на расте вашей библиотеки дает какое-то преимущество: скорость написания кода, проектирования, и т.п.

Просто про безопасность пишут везде, но вот в части повешения эффективности разработки, например на расте тоже самое напишу за месяц, что на плюсах - за три, а на питоне за неделю. Интересен исключительно лично ваша субъективная оценка.

Удачи автору в переписывании pandoc на Rust.

Вместо своего поисковика посмотрите на SoLR. «Все уже украдено»

Поддержка разных типов документов это не тот проект который один человек может потянуть, не говоря о том, что вся проблема имеющихся - invented not here синдром.

Недавно стать читал, про то что «big data” больше не является проблемой.

Поскольку при распаллеливании задачи ваша производительность растёт линейно, а по закону Мура производительность компьютеров - степенная функция.

Пока автор будет парсер pdf писать на Rust-e в смартфоне столика памяти будет, что туда все Java библиотеки можно будет засунуть :)

Закон Мура ограничен сверху законами квантовой физики, а "распаллеливания" законом Амдала.

Вот только мы в него пока не уперлись.

Рекомендую к прочтению стать от автора оригинальной идеи: https://motherduck.com/blog/big-data-is-dead/

fn element_type(&self) -> ElementType;}

pub enum ElementType { Text, Paragraph,

fn paragraph_as_ref(&self) -> anyhow::Result<&ParagraphElement>

В таком подходе тип элементов описывается в трех местах, что во-первых требует больше времени для добавления нового типа, а во-вторых отсуствует статическая проверка типизации - при match element.element_type() нет никакой гарантии компилятора что там будет действительно лежать нужный тип при даункасте. Плюс Vec<Box<dyn Element>> не эффективная по перфомансу, будет постоянный кэш мисс и эвристики подгрузки не работают.

Может быть лучше попробовать, как первое решение, просто алгебраческий тип:

pub enum Element<'a> {

Header { level: u8, /* todo */ },

Paragraph { child: &'a Element<'a> },

...

}

и Vec<Element>

Еще все элементы создаются в куче с динамик трейтами вместо статического полиморфизма, а конструктуры объявлены через Result, хотя ошибку они никогда не кидают. Например

pub fn new(element: &Box<dyn Element>) -> anyhow::Result<ListItemElement> {
Ok(ListItemElement {
element: element.clone(),
})
}

Надеюсь это не будет звучать грубо, но такое решение сильно похоже на попытку использовать ссылочный объектно-ориентированый подход как в java/.net , что смотрится несколько неорганично в расте.

Спасибо. Согласен. Опыт Java давит. Буду делать рефакторинг. А то не комильфо.

Весьма радует, что вы решили не ждать пенсии, а изучить что-то новое.

Я каждые 2-3 года изучаю новый ЯП и новые либы и фреймворки ) У меня психотип новатора ) Все мои 45 летние знакомые старые пни, которые ничему новому не хотят учиться.

Родственная душа :)

Вообще-то у автора уже проблема в реализации типа Document.

Тип назван документ, но поскольку у него есть pagewidth, … page header и page footer,

То это скорее всего должен быть Page а не Документ.

Элементы которые могут быть в Page/Document. Это параграф, … table of context

Table of Context не имеет смысла в отрыве от документа, который состоит из набора страниц.

способ объединения Page/Document в «нормальный документ» состоящий из набора страниц не виден.

Далее, есть table header, но отсутствует table footer, если уж работать по аналогии с html table,

Зачем-то появился елемент PageBreak, который в рамках поиска, для которого автор делает парсер, вообще не имеет смысла.

Как уже говорилось в у автора не Document а Page, для которой тип PageBreak тоже не имеет смысла.

В общем похоже ещё один фанат Rust, готовый переписать весь мир на новом языке.

Советую обратиться к первоисточникам - перечитать Маркса, т.е. Бруста «Мифический человекомесяц» и его главное утверждение «серебряной пули не существует»

:)

Спасибо за замечание. Долго думал над архитектурой. Дело в том, что у меня много идей, куда и как применить мою библиотеку. В частности, одна из многих идей — это создание генератора отчетов, на входе которого данные и шаблон отчета. Я всегда иду по пути наименьшего сопротивления и решил принять за аксиому, что документ имеет все страницы одинакового формата. Просто я не видел отчетов, у которых каждая страница имеет разные отступы, ориентацию и т.д.

У меня, к сожалению, всего две руки и 24 часа в сутки. Я иду на жертвы с точки зрения универсальности, иду на упрощение. В целом, я с вами не спорю. Надо, возможно, для каждой страницы делать и отступы, и т. д. Надо подумать. Может, пока не поздно, дойти до такой универсальности, как вы сказали.

Генератор отчетов: json, minijinja, latex, pdf

Вы оказались правы! Спасибо! Поигрался в песочнеце:

#[derive(Debug)]
pub enum Element<'a> {

    Header { level: u8, text: &'a str },

    Paragraph { elements: Vec<Element<'a>> },

    List { ordered: bool, text: &'a crate::Element<'a> },

    Text { text: &'a str, size: u8 },

}


fn main() {
    let header = Element::Header { level: 1, text: "Hello, World!" };
    let paragraph = Element::Paragraph { elements: vec![header] };
    let list = Element::List { ordered: false, text: &paragraph };
    println!("{:?}", list);
}

про fn new(element: &Box<dyn Element>) -> anyhow::Result<ListItemElement> тут вообще все очевидно - излишний Rusult конечно

Буду все рефакторить, пока кода мало!

Вы оказались правы! Спасибо! Поигрался в песочнеце:

#[derive(Debug)]
pub enum Element<'a> {
    Header { level: u8, text: &'a str },
    Paragraph { elements: Vec<Element<'a>> },
    List { ordered: bool, text: &'a crate::Element<'a> },
    Text { text: &'a str, size: u8 },
}

fn main() {
    let header = Element::Header { level: 1, text: "Hello, World!" };
    let paragraph = Element::Paragraph { elements: vec![header] };
    let list = Element::List { ordered: false, text: &paragraph };
    println!("{:?}", list);
}

про fn new(element: &Box<dyn Element>) -> anyhow::Result<ListItemElement> тут вообще все очевидно - излишний Rusult конечно

Буду все рефакторить, пока кода мало!

Может пригодится - синтаксис паттерн матчинга позволяет в таких случаях делать вайлкард

let x = Element::Header { level: 8, text: "hello" } };

match x { Element::Header { level, .. } => { println!("{level}") } Element::Paragraph { .. } => { println!("Paragraph") }

};

Плюс Vec<Box<dyn Element>> не эффективная по перфомансу

Где-то у matklad в блоге было про что-то похожее. Запихивание всего в Box<dyn Element> позволяет этому лежать в памяти выровнено по размеру Box, а не Element, что наоборот ускоряет обработку отдельных токенов и уменьшает количество дыр из-за выравнивания, экономя память. Даже не смотря на пару уровней индирекции это всё равно может быть быстрее голых Element в векторе. Так что спешу поставить под сомнение это утверждение и посоветую автору прогонять подобные штуки через профайлер (flamegraph, hyperfine) до и после рефакторинга.

Похожим образом Zig оптимизирует свои Енамы, но уже на уровне компилятора.

Даже не смотря на пару уровней индирекции это всё равно может быть быстрее голых Element в векторе. 

Индирекции в общем случае будут медленее, так как соседние элементы банально могут не обладать пространственной когерентностью и даже могут быть на разных страницах. А вы приводите зачем-то узкий случай оптимизации под конкретный алгоритм, "If your access pattern does not require blind iteration (which can be the case for flattened, index-based tree structures)", при том что в исходном коде буквально делается итерация по всему вектору: document.elements.iter()Так что спешу поставить под сомнение(с) ваши советы запихивать все в Вox<dyn Element> , хотя использовать профайл дело вполне полезное.

Так что спешу поставить под сомнение(с) ваши советы запихивать все в Вox<dyn Element>

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

Большое спасибо за Вашу мотивацию. Мне определённо нравится Ваш проект. У меня и самого вертелась схожая идея в голове.

Совершенно соглашусь. Задача по созданию такой библиотеки вполне интересна

Хотите присоединиться к кодированию проекта?

Я такую задачу решил на С# в проекте Pullenti Unitext с конвертацией в Java, Javascript и Python. Пытался конвертировать на Rust, но это не получилось. Понимаю всю сложность этой задачи и желаю автору удачи!

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории