feature/validaciones-clarion-agentes-aduanales
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
"""
|
||||
Lectura de CSV con detección de delimitador (compartida por layouts_csv).
|
||||
Si se pasa fieldnames, no se usa la primera fila como cabecera y se toma como dato (CSV sin cabeceras).
|
||||
Si fieldnames es None, cabeceras vacías se normalizan a _COL_0_, _COL_1_, ... para no colapsar columnas.
|
||||
Si se pasa headerless_first_cell_values, se detecta si la primera fila es cabecera o dato por el valor de la primera celda.
|
||||
"""
|
||||
import csv
|
||||
import io
|
||||
from typing import Iterator, Tuple, Dict, Any, Optional, List
|
||||
from typing import Iterator, Tuple, Dict, Any, Optional, List, Set
|
||||
|
||||
|
||||
def _normalize_empty_headers(headers: List[str]) -> List[str]:
|
||||
@@ -24,12 +24,15 @@ def _normalize_empty_headers(headers: List[str]) -> List[str]:
|
||||
def iter_csv_rows(
|
||||
file_path: str,
|
||||
fieldnames: Optional[List[str]] = None,
|
||||
headerless_first_cell_values: Optional[Set[str]] = None,
|
||||
headerless_second_cell_key_pattern: Optional[str] = None,
|
||||
) -> Iterator[Tuple[int, Dict[str, Any]]]:
|
||||
"""
|
||||
Abre el CSV, detecta dialecto y devuelve (line_num, row_dict) por cada fila.
|
||||
line_num empieza en 1 (primera fila de datos).
|
||||
Si fieldnames es None: la primera fila del archivo se usa como cabecera (comportamiento por defecto).
|
||||
Si fieldnames es una lista: no se usa cabecera; la primera fila se considera dato y se usan fieldnames como columnas.
|
||||
Si fieldnames y headerless_first_cell_values se pasan: si la primera celda de la primera fila
|
||||
(quitando BOM, strip, upper) está en headerless_first_cell_values, se trata como dato y se usan fieldnames.
|
||||
headerless_second_cell_key_pattern se ignora si no se usa (reservado para otros layouts).
|
||||
"""
|
||||
with open(file_path, "r", encoding="utf-8-sig") as f:
|
||||
sample = f.read(2048)
|
||||
@@ -38,7 +41,37 @@ def iter_csv_rows(
|
||||
dialect = csv.Sniffer().sniff(sample, delimiters=",;\t")
|
||||
except Exception:
|
||||
dialect = "excel"
|
||||
if fieldnames:
|
||||
if fieldnames and headerless_first_cell_values is not None:
|
||||
first_line = f.readline()
|
||||
if not first_line:
|
||||
return
|
||||
row_reader = csv.reader(io.StringIO(first_line), dialect=dialect)
|
||||
first_cells = next(row_reader, None)
|
||||
if not first_cells:
|
||||
return
|
||||
first_cell_clean = (first_cells[0] or "").lstrip("\ufeff").strip().upper()
|
||||
use_headerless = first_cell_clean in headerless_first_cell_values
|
||||
if use_headerless:
|
||||
pad = len(fieldnames) - len(first_cells)
|
||||
cells = first_cells[: len(fieldnames)] + ([""] * pad if pad > 0 else [])
|
||||
yield 1, dict(zip(fieldnames, cells))
|
||||
reader = csv.DictReader(f, fieldnames=fieldnames, dialect=dialect, restval="")
|
||||
for i, row in enumerate(reader, start=2):
|
||||
yield i, dict(row)
|
||||
return
|
||||
f.seek(0)
|
||||
first_line = f.readline()
|
||||
if not first_line:
|
||||
return
|
||||
row_reader = csv.reader(io.StringIO(first_line), dialect=dialect)
|
||||
raw_headers = next(row_reader, None)
|
||||
if not raw_headers:
|
||||
return
|
||||
normalized = _normalize_empty_headers(raw_headers)
|
||||
reader = csv.DictReader(f, fieldnames=normalized, dialect=dialect, restval="")
|
||||
for i, row in enumerate(reader, start=1):
|
||||
yield i, row
|
||||
elif fieldnames:
|
||||
reader = csv.DictReader(f, fieldnames=fieldnames, dialect=dialect)
|
||||
for i, row in enumerate(reader, start=1):
|
||||
yield i, dict(row)
|
||||
|
||||
Reference in New Issue
Block a user