mirror of
https://github.com/tiennm99/thptqg.git
synced 2026-08-15 01:25:11 +00:00
Comments across the tree justified the code by pointing at a Rust implementation that is no longer in the repository, citing files and line numbers (config.rs:132, schema.rs:26-54, reader.rs:42) that cannot be opened, plus crates and datasets that are equally gone. A reader could not check any of it. Every invariant those comments carried is kept and restated so it stands on its own: the bytewise sort that decides which row survives a duplicate exam number, the literal U+0300..U+036F range that must match the site's toAscii, the trailing space in "SINH ", the BIFF and shared-string corrections, the VACUUM-after-COMMIT rule, the deploy-from-main guard. The reader's contract is now anchored to the frozen oracle in parser/testdata, which still exists and is still checked, rather than to the tool that originally produced it. TestDDLMatchesRust becomes TestDDLIsFrozen: it compares against a copy of the DDL inside the test and never read schema.rs, so both the name and the failure message were misleading. ToAscii no longer claims the d-replacement must precede lowercasing. Both cases map to 'd' and ToLower runs last, so the order has no effect.
84 lines
2.6 KiB
Go
84 lines
2.6 KiB
Go
// Package reader wraps the two spreadsheet libraries behind one contract.
|
|
//
|
|
// Cell rendering and sheet geometry are pinned by the frozen fidelity oracle in
|
|
// parser/testdata/reader-fidelity-hashes.tsv. "The reference" below means that
|
|
// pinned rendering: whenever an underlying library disagrees with it, this
|
|
// package corrects the library, never the oracle.
|
|
//
|
|
// The *contract* is row-at-a-time: callers receive one row per callback and
|
|
// never hold the workbook. The two implementations do not honour that in
|
|
// memory — both decode the whole workbook into [][][]Cell up front, because
|
|
// neither underlying library exposes a usable row cursor. data/2017/ha-noi.xls
|
|
// alone holds 72k rows across two sheets, so a caller that assumed streaming
|
|
// memory here would be wrong.
|
|
package reader
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// Cell is one spreadsheet cell rendered as reference text.
|
|
//
|
|
// IsEmpty marks a cell with no value, as opposed to a string cell that happens
|
|
// to be empty. Both render "" and both count as blank in IsAllBlank and in
|
|
// transform, so the flag is diagnostic only — never compare on it.
|
|
type Cell struct {
|
|
Str string
|
|
IsEmpty bool
|
|
}
|
|
|
|
// Sheet carries a sheet's identity and geometry. Height and Width describe the
|
|
// reference used range; every used range in the corpus starts at (0,0),
|
|
// verified across every input file.
|
|
type Sheet struct {
|
|
Index int
|
|
Name string
|
|
Height int
|
|
Width int
|
|
}
|
|
|
|
// RowFunc receives each row of each sheet. Rows are padded to the sheet's used
|
|
// width — width is load-bearing because every column read downstream is
|
|
// positional and falls back to "" when the index is out of range, so a short
|
|
// row silently NULLs its tail columns.
|
|
type RowFunc func(sheet Sheet, rowIdx int, row []Cell) error
|
|
|
|
// Workbook is one opened spreadsheet.
|
|
type Workbook interface {
|
|
// Sheets returns sheet identity and geometry in workbook order.
|
|
Sheets() []Sheet
|
|
// EachRow streams every row of the given sheet in order.
|
|
EachRow(sheetIdx int, fn RowFunc) error
|
|
Close() error
|
|
}
|
|
|
|
// Open dispatches on file extension.
|
|
func Open(path string) (Workbook, error) {
|
|
switch strings.ToLower(filepath.Ext(path)) {
|
|
case ".xls":
|
|
return openXLS(path)
|
|
case ".xlsx", ".xlsm":
|
|
return openXLSX(path)
|
|
default:
|
|
return nil, fmt.Errorf("unsupported extension: %s", path)
|
|
}
|
|
}
|
|
|
|
// padRow extends row to width with empty cells, and truncates if longer.
|
|
func padRow(row []Cell, width int) []Cell {
|
|
if len(row) == width {
|
|
return row
|
|
}
|
|
if len(row) > width {
|
|
return row[:width]
|
|
}
|
|
out := make([]Cell, width)
|
|
copy(out, row)
|
|
for i := len(row); i < width; i++ {
|
|
out[i] = Cell{IsEmpty: true}
|
|
}
|
|
return out
|
|
}
|