Files

PDF text

The text of a PDF, whole or by page, and the lines holding a word: for search, imports and reading invoices.

Version 1.0.0 · 3 functions · 1 crate

#pdf#text#search#import

Functions

What use pdf_text.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use pdf_text, pdf_text_pages from pdf_text (Quillo 0.6).

pdf_text(path: str) → stror an error

All the text of a PDF, pages parted by a blank line. Scanned pages (pictures of text) give nothing: they need OCR.

pdf_text_pages(path: str) → listor an error

The text of each page, in order: the first item is page 1.

Get it

$ quillo install pdf_text               # pdf_text.qwer into this folder$ quillo install pdf_text --example     # and its example, pdf_text.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/pdf_text.qwer$ curl -O https://quillolang.org/plugins/files/pdf_text.qwe$ curl -O https://quillolang.org/plugins/files/pdf_text_sample.pdf

Then, in your program, all of it or only what you call:

main.qwe
use pdf_text.qweruse pdf_text, pdf_text_pages from pdf_text      // Quillo 0.6: only these

It uses this crate, which cargo fetches the first time the plugin is built:

[dependencies]
pdf-extract = "0.9"

Example

pdf_text.qwe
// An invoice's text read from its PDF, page by page, and searched.////   quillo run pdf_text.qwe      then open http://localhost:8080/invoiceuse "pdf_text.qwer"let INVOICE = "pdf_text_sample.pdf"route GET /invoice {    let pages = pdf_text_pages(INVOICE)?    let total = re.find(pages[0], "Total\\s+([\\d.]+)")    { pages: len(pages), first_page: pages[0], total: total?.groups[0] }}route GET /text = pdf_text(INVOICE)route GET /search ? { q: str = "due" } = pdf_text_search(INVOICE, q, 20)
$ quillo run pdf_text.qwe$ curl 'localhost:8080/invoice'$ curl 'localhost:8080/search?q=due'$ curl 'localhost:8080/text'

Source

pdf_text.qwer
//! The text of a PDF, whole or page by page, and the lines that hold a word: for search, imports and reading invoices.//! version: 1.0.0//!//! [dependencies]//! pdf-extract = "0.9"use serde_json::{json, Value};const MAX_BYTES: u64 = 100 * 1024 * 1024;// Blank lines at the ends dropped, runs of them kept to one, spaces at line ends trimmed.fn tidy(s: &str) -> String {    let mut out: Vec<&str> = vec![];    for line in s.lines().map(str::trim_end) {        if line.trim().is_empty() && out.last().map_or(true, |l| l.is_empty()) {            continue;        }        out.push(if line.trim().is_empty() { "" } else { line });    }    while out.last() == Some(&"") {        out.pop();    }    out.join("\n")}fn pages(path: &str) -> Result<Vec<String>, String> {    let size = std::fs::metadata(path).map_err(|e| format!("{}: {}", path, e))?.len();    if size > MAX_BYTES {        return Err(format!("{}: larger than 100 MB", path));    }    // A malformed PDF can make the reader panic: that is an error value here.    let p = path.to_string();    let r = std::panic::catch_unwind(move || pdf_extract::extract_text_by_pages(&p));    match r {        Ok(Ok(pages)) => Ok(pages.iter().map(|t| tidy(t)).collect()),        Ok(Err(e)) => Err(format!("{}: {}", path, e)),        Err(_) => Err(format!("{}: the PDF could not be read", path)),    }}/// All the text of a PDF, pages parted by a blank line. Scanned pages (pictures of text) give nothing: they need OCR.pub fn pdf_text(path: &str) -> Result<String, String> {    Ok(pages(path)?.into_iter().filter(|p| !p.is_empty()).collect::<Vec<_>>().join("\n\n"))}/// The text of each page, in order: the first item is page 1.pub fn pdf_text_pages(path: &str) -> Result<Vec<String>, String> {    pages(path)}/// The lines holding `query` (in any case), as `{ page, line, text }`, pages and lines counted from 1; at most `limit` of them (up to 1,000).pub fn pdf_text_search(path: &str, query: &str, limit: i64) -> Result<Vec<Value>, String> {    let q = query.to_lowercase();    if q.trim().is_empty() {        return Ok(vec![]);    }    let limit = limit.clamp(1, 1000) as usize;    let mut hits = vec![];    for (p, text) in pages(path)?.iter().enumerate() {        for (l, line) in text.lines().enumerate() {            if line.to_lowercase().contains(&q) {                hits.push(json!({ "page": p + 1, "line": l + 1, "text": line.trim() }));                if hits.len() >= limit {                    return Ok(hits);                }            }        }    }    Ok(hits)}
Thread
76Useful? Like it, or add to its thread below.

Thread 0 posts

On PDF text: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.