3. Namensraum `text::` — der Text
25 Funktionen, um den Text eines Dokuments zu extrahieren, zu normalisieren, zu durchsuchen und zu prüfen.
Bei Funktionen, die mit
[text]gekennzeichnet sind, ist das Argument optional: ohne es arbeitet die Funktion auf dem ganzen Dokument, mit ihm auf der übergebenen Zeichenkette.
3.1 Extraktion
Abschnitt betitelt „3.1 Extraktion“| Funktion | Zweck |
|---|---|
text::extract_all() |
Der gesamte Text des Dokuments (Seiten durch Umbrüche verbunden) |
text::extract_from_page(page) |
Der Text einer Seite (ab 1) |
text::extract_from_region(page, region) |
Der Text eines Bereichs (leere Zeichenkette, wenn keiner da ist) |
text::extract_with_normalization() |
Der bereits normalisierte Text des Dokuments |
check "Extraction" { inhalt = text::extract_all() assert inhalt.trim() != "", "PDF has no extractable text"
titelseite = text::extract_from_page(1) assert titelseite.contains("User Manual"), "cover lacks the expected title"
// Produktionsfußzeilen (InDesign-Dateiname, Exportdatum) überleben // manchmal bis in die fertige Datei fuss = region(0, 0, 467, 40, "footer") doc.pages.each { |page| zeile = text::extract_from_region(page.number, fuss) assert !zeile.contains(".indd"), "page #{page.number} has a production mark in the footer: #{zeile.trim()}" }}3.2 Normalisierung und Zerlegung
Abschnitt betitelt „3.2 Normalisierung und Zerlegung“| Funktion | Zweck |
|---|---|
text::normalize([text]) |
Kleinschreibung + zusammengefasste Leerzeichen |
text::split_words([text]) |
Zerlegt in Wörter (Satzzeichen an den Rändern entfernt) |
text::split_sentences([text]) |
Zerlegt in Sätze |
text::split_paragraphs([text]) |
Zerlegt in Absätze (Leerzeile) |
text::count_words([text]) |
Anzahl der Wörter |
text::count_characters([text]) |
Anzahl der Zeichen |
text::detect_language([text]) |
"pt", "en", "es" oder "unknown" |
check "Normalization and splitting" { require text::normalize(" HELLO World ") == "hello world"
woerter = text::split_words("Hello, world! (test)") require woerter.length == 3 require woerter.first() == "Hello"
// Beipackzettel und Verträge haben eine praktische Lesbarkeitsgrenze text::split_sentences().each { |satz| assert satz.length < 400, "sentence with #{satz.length} characters — hard to read" }
require text::count_words() > 100 assert text::detect_language() == "en", "document should be in English, detected: #{text::detect_language()}"}3.3 Suche und Pflichtinhalte
Abschnitt betitelt „3.3 Suche und Pflichtinhalte“| Funktion | Zweck |
|---|---|
text::require_text(begriff) |
Wahr, wenn der Begriff vorkommt |
text::forbid_text(begriff) |
Wahr, wenn er nicht vorkommt |
text::require_match(regex) |
Wahr, wenn der reguläre Ausdruck etwas findet |
text::forbid_match(regex) |
Wahr, wenn er nichts findet |
text::fuzzy_match(a, b) |
Ähnlichkeit zweier Zeichenketten (0.0 bis 1.0) |
Der Vergleich ignoriert Groß-/Kleinschreibung und Leerzeichen.
profile "contract" { check "Mandatory clauses" { assert text::require_text("governing law"), "contract has no governing-law clause" assert text::require_match("\d{4}/\d{4}"), "contract number not found" }
check "Forbidden terms" { assert text::forbid_text("DRAFT"), "document still marked as draft" assert text::forbid_text("lorem ipsum"), "placeholder text was not replaced" assert text::forbid_match("\d{2}-\d{2}-\d{4}"), "US-format date found" }
check "Name with tolerance" { // Nützlich, wenn Tippfehler oder OCR-Rauschen zu erwarten sind gefunden = text::extract_from_region(1, region(50, 700, 300, 40)) aehnlichkeit = text::fuzzy_match("Paracetamol 750mg", gefunden) assert aehnlichkeit > 0.9, "product name differs from expected (#{round(aehnlichkeit * 100)}% similar)" }}3.4 Personenbezogene Daten
Abschnitt betitelt „3.4 Personenbezogene Daten“text::detect_personal_data([text]) und text::detect_pii([text]) sind gleichbedeutend.
Sie geben die Liste der gefundenen personenbezogenen Daten zurück: CPF, CNPJ
(brasilianische Steuernummern), E-Mail-Adresse und Telefonnummer.
CPF und CNPJ kommen nur in die Liste, wenn die Prüfziffer stimmt. Eine Nummer, die nur wie ein CPF aussieht (
111.111.111-12), löst keinen Alarm aus.
check "Public document must carry no personal data" { gefunden = text::detect_personal_data() assert gefunden.length == 0, "personal data exposed: #{gefunden.join("; ")}"
// Jeder Eintrag sieht aus wie "CPF: 529.982.247-25" text::detect_pii().each { |eintrag| print("found:", eintrag) }}3.5 Formatprüfungen
Abschnitt betitelt „3.5 Formatprüfungen“| Funktion | Zweck |
|---|---|
text::validate_cpf(text) |
Prüfziffer des CPF (mod 11) |
text::validate_cnpj(text) |
Prüfziffer des CNPJ |
text::validate_date_format(text [, format]) |
Tatsächlich gültiges Kalenderdatum |
text::validate_phone_format(text) |
Brasilianisches Telefonformat |
text::validate_format(text, regex) |
Passt die ganze Zeichenkette? |
Akzeptierte Datumsformate: "dd/mm/aaaa" und "aaaa-mm-dd"; ohne zweites
Argument werden beide akzeptiert.
check "Format validation" { require text::validate_cpf("529.982.247-25") require !text::validate_cpf("111.111.111-11") // lauter gleiche Ziffern require text::validate_cnpj("11.222.333/0001-81")
require text::validate_date_format("29/02/2024") // 2024 ist ein Schaltjahr require !text::validate_date_format("29/02/2023") // 2023 nicht require !text::validate_date_format("31/04/2026") // April hat 30 Tage
require text::validate_phone_format("(11) 98765-4321")
// Chargencode im Werksformat charge = text::extract_from_region(1, region(400, 50, 150, 20)).trim() assert text::validate_format(charge, "L\d{4}-\d{2}"), "batch code does not follow the L0000-00 pattern: #{charge}"}3.6 Vergleich und Diagnose
Abschnitt betitelt „3.6 Vergleich und Diagnose“text::diff(a, b) listet die geänderten Zeilen (- entfernt, + ergänzt).
text::detect_rasterized_text() ist wahr, wenn Text in ein Bild verwandelt
wurde.
check "Comparison and diagnostics" { aenderungen = text::diff(text::extract_from_page(1), text::extract_from_page(2)) print("changed lines:", aenderungen.length)
// Eine gescannte oder in Pfade umgewandelte Seite ist weder durchsuchbar // noch für Screenreader lesbar assert !text::detect_rasterized_text(), "there are pages with rasterized text (scanned or outlined)"}Um zwei Dateien zu vergleichen, nehmen Sie den Befehl
pdfl compare: Er ordnet die Seiten selbstständig zu. Siehe Kapitel 11.
3.7 Vollständiges Beispiel
Abschnitt betitelt „3.7 Vollständiges Beispiel“// rechtsdokument.pdfl — Prüfung eines Vertragsprofile "standard-contract" {
check "Required content" tags: ["legal"] { assert text::require_text("governing law"), "no governing-law clause" assert text::require_text("term of agreement"), "no term clause" assert text::require_match("\d{4}/\d{4}"), "no contract number" }
check "No drafts" tags: ["legal"] { assert text::forbid_text("DRAFT"), "marked as draft" assert text::forbid_text("lorem ipsum"), "placeholder text present" assert text::forbid_match("XXX+"), "unfilled fields (XXX)" }
check "Privacy" tags: ["compliance"] { gefunden = text::detect_personal_data() assert gefunden.length == 0, "personal data in a public document: #{gefunden.join("; ")}" }
check "Text quality" tags: ["text"] { assert text::detect_language() == "en", "document is not in English" assert !text::detect_rasterized_text(), "rasterized text blocks search" require text::count_words() > 200 }}