Class: Rpdfium::Table::Table
- Inherits:
-
Object
- Object
- Rpdfium::Table::Table
- Defined in:
- lib/rpdfium/table/table.rb
Overview
Rappresenta una tabella trovata su una pagina. Espone celle, righe, colonne, bbox, e il metodo ‘extract` che ritorna i dati testuali.
Ogni cella è una bbox ‘[x0, top, x1, bottom]` (top-down). Una “row” è il gruppo di celle che condividono la stessa `top`. Una “column” è il gruppo che condivide la stessa `x0`.
Instance Attribute Summary collapse
-
#cells ⇒ Object
readonly
Returns the value of attribute cells.
-
#page ⇒ Object
readonly
Returns the value of attribute page.
Instance Method Summary collapse
- #bbox ⇒ Object
- #columns ⇒ Object
-
#extract(x_tolerance: Util::WordExtractor::DEFAULT_X_TOLERANCE, y_tolerance: Util::WordExtractor::DEFAULT_Y_TOLERANCE, keep_blank_chars: false, cell_padding: 0.0) ⇒ Object
Estrai dati: Array<Array<String>>.
-
#initialize(page, cells) ⇒ Table
constructor
A new instance of Table.
-
#rows ⇒ Object
Restituisce le righe come Array<Array<bbox|nil>>.
Constructor Details
#initialize(page, cells) ⇒ Table
Returns a new instance of Table.
14 15 16 17 |
# File 'lib/rpdfium/table/table.rb', line 14 def initialize(page, cells) @page = page @cells = cells end |
Instance Attribute Details
#cells ⇒ Object (readonly)
Returns the value of attribute cells.
12 13 14 |
# File 'lib/rpdfium/table/table.rb', line 12 def cells @cells end |
#page ⇒ Object (readonly)
Returns the value of attribute page.
12 13 14 |
# File 'lib/rpdfium/table/table.rb', line 12 def page @page end |
Instance Method Details
#bbox ⇒ Object
19 20 21 22 23 24 25 26 27 28 |
# File 'lib/rpdfium/table/table.rb', line 19 def bbox @cells.each_with_object( [Float::INFINITY, Float::INFINITY, -Float::INFINITY, -Float::INFINITY] ) do |c, acc| acc[0] = c[0] if c[0] < acc[0] acc[1] = c[1] if c[1] < acc[1] acc[2] = c[2] if c[2] > acc[2] acc[3] = c[3] if c[3] > acc[3] end end |
#columns ⇒ Object
37 38 39 |
# File 'lib/rpdfium/table/table.rb', line 37 def columns rows_or_columns(:col) end |
#extract(x_tolerance: Util::WordExtractor::DEFAULT_X_TOLERANCE, y_tolerance: Util::WordExtractor::DEFAULT_Y_TOLERANCE, keep_blank_chars: false, cell_padding: 0.0) ⇒ Object
Estrai dati: Array<Array<String>>. Per ogni riga, per ogni cella, filtra i char della pagina il cui MIDPOINT è nella bbox della cella, poi ricostruisce il testo via Util::TextExtraction (che a sua volta passa da WordExtractor).
Questo è il path di pdfplumber.Table.extract — per ogni riga prima filtra i char della riga (ottimizzazione: quasi tutti i char delle altre righe vengono scartati subito), poi per ogni cella filtra ancora dentro la sub-bbox.
Ottimizzazione rispetto al path naïve: i char vengono ordinati per midpoint verticale una sola volta; per ogni riga si usa bsearch per trovare in O(log n) i char candidati invece di scansionare tutto l’array O(n) per ogni riga.
NOTA su strategia :text: ‘words_to_edges_h` emette per design DUE edges per riga (top e bottom della bbox del cluster). Significa che una tabella detectata da text-strategy avrà righe “vere” intervallate da righe “vuote” tra il bottom-edge della riga N e il top-edge della riga N+1. Questo è identico al comportamento di pdfplumber. Il caller può filtrare via `result.reject { |row| row.all?(&:empty?) }` se vuole eliminarle. `cell_padding`: estende il bbox di ogni cella verso sinistra e verso l’alto di N punti. Default 0 (= comportamento pdfplumber identico). Utile per PDF dove i char sporgono leggermente dal bordo della cella (es. la “I” maiuscola della cella “Intermediario” in CR Banca d’Italia ha x0=24.0 ma il bordo della cella è a x=25.6 — viene scartata dal filtro midpoint, output “ntermediario:”). Con ‘cell_padding: 2.0` la cella diventa [23.6, …, 100, …] e la “I” viene catturata.
Padding solo sui bordi “interno-sinistro” e “interno-alto” per evitare di duplicare char condivisi tra celle adiacenti (un char tra cella A e cella B finirebbe in entrambe se entrambe paddassero su tutti i lati).
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/rpdfium/table/table.rb', line 75 def extract(x_tolerance: Util::WordExtractor::DEFAULT_X_TOLERANCE, y_tolerance: Util::WordExtractor::DEFAULT_Y_TOLERANCE, keep_blank_chars: false, cell_padding: 0.0) # `lean: true`: salta 5 chiamate FFI per char (font name, weight, # angle, hyphen flag, unicode error) che non servono al pipeline # di estrazione tabelle. Su tabelle con migliaia di char riduce # il tempo di compute_chars del ~30%. chars = @page.chars(lean: true) # Ordina per midpoint verticale una volta sola; costruisce un array # parallelo di vmid per bsearch. Costo: O(n log n) una tantum. sorted_chars = chars.sort_by { |c| (c[:top] + c[:bottom]) / 2.0 } vmids = sorted_chars.map { |c| (c[:top] + c[:bottom]) / 2.0 } # Istanzia WordExtractor UNA volta sola e riusalo per tutte le celle # (può esserci una tabella con decine di celle, evitiamo allocazioni). word_extractor = Util::WordExtractor.new( x_tolerance: x_tolerance, y_tolerance: y_tolerance, keep_blank_chars: keep_blank_chars ) all_rows = rows all_rows.map do |row| row_bbox = row_bounding_box(row) lo = vmids.bsearch_index { |v| v >= row_bbox[1] - cell_padding } || sorted_chars.size hi = vmids.bsearch_index { |v| v >= row_bbox[3] } || sorted_chars.size row_chars = sorted_chars[lo...hi] row.map do |cell| next nil if cell.nil? padded = cell_padding.zero? ? cell : pad_cell_bbox(cell, cell_padding) cell_chars = row_chars.select { |c| char_in_bbox?(c, padded) } if cell_chars.empty? "" else extract_text_with(cell_chars, word_extractor, y_tolerance) end end end end |
#rows ⇒ Object
Restituisce le righe come Array<Array<bbox|nil>>. Le celle “mancanti” in una riga (es. perché la tabella ha una topologia irregolare) sono rappresentate come nil — coerente con pdfplumber.
33 34 35 |
# File 'lib/rpdfium/table/table.rb', line 33 def rows rows_or_columns(:row) end |