tabular

package module
v0.0.0-...-e8da5e4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 28, 2026 License: Apache-2.0 Imports: 22 Imported by: 0

README

tabular

tabular provides explicit, bounded ingestion for CSV and other delimiters, fixed-width text, legacy XLS, XLSX, and ZIP-backed sources without format auto-detection or implicit data conversion.

Status

The package is pre-v1. Supported behavior is fixture-backed, fuzzed, benchmarked, and held to meaningful 100% production coverage.

Requirements

  • Go 1.26.6 or later

Installation

go get github.com/faustbrian/golib/pkg/tabular

Quickstart

reader, err := tabular.NewDelimitedReader(source, tabular.DelimitedConfig{
    Delimiter: ';',
    MaxRecordBytes: 64 << 10,
    MaxFieldBytes:  16 << 10,
    Header: &tabular.HeaderConfig{
        TrimSpace:        true,
        Case:             tabular.HeaderCaseLower,
        RejectEmpty:      true,
        RejectDuplicates: true,
    },
})
if err != nil {
    return err
}

header, err := reader.Header()
if err != nil {
    return err
}
row, err := reader.Read()

The quickstart covers streaming loops, fixed-width input, spreadsheets, ZIP sources, encodings, and normalization.

Package Guarantees

  • explicit format and encoding selection
  • streaming delimited, fixed-width, ZIP-entry, and XLSX row processing
  • bounded XLS materialization for OLE2/BIFF8 random access
  • archive entry-count, size, compression-ratio, path, link, and duplicate checks
  • opt-in XLSX worksheet-count limits
  • opt-in parsed record and field limits for delimited and spreadsheet rows
  • opt-in absent-versus-stored-empty spreadsheet cell preservation
  • opt-in normalization that does not mutate caller-owned rows
  • stable error kinds with one-based row and field coordinates

See formats and behavior and limits for exact boundaries.

Documentation

Start with the documentation index, quickstart, adoption guide, and API reference. Review performance, security, and hardening before accepting hostile files.

AI tools can use llms.txt and llms-full.txt. Release history is maintained in CHANGELOG.md.

Development

Run make check before submitting a change. This enforces formatting, static analysis, race tests, meaningful 100% coverage, parser fuzz smoke, benchmarks, documentation, and vulnerability scanning.

Contributing

Read CONTRIBUTING.md and follow the code of conduct. Format and normalization changes require explicit compatibility and data-integrity analysis.

Security

Report vulnerabilities privately according to SECURITY.md. Review docs/security.md before ingesting untrusted files.

License

tabular is available under the Apache License 2.0. XLS provenance and third-party attribution are recorded in NOTICE and THIRD_PARTY_NOTICES.md.

Ecosystem

Use the Golib documentation portal to choose companion packages, supported stacks, recipes, and operations guidance.

Documentation

Overview

Package tabular provides explicit, bounded readers for tabular ingest.

CSV and configurable delimited input, fixed-width records, XLS, XLSX, and ZIP-backed sources share deterministic rows and typed errors. Parsers do not auto-detect formats or silently apply normalization. Callers choose the format, limits, header rules, and field transformations in configuration.

Delimited, fixed-width, ZIP entry, and XLSX row processing stream input. Legacy XLS workbooks are materialized up to MaxWorkbookBytes because the OLE2/BIFF8 format requires random access to workbook structures.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func DecodeBytes

func DecodeBytes(source []byte, sourceEncoding Encoding) (string, error)

DecodeBytes validates and converts source bytes to UTF-8.

func DecodeReader

func DecodeReader(source io.Reader, sourceEncoding Encoding) (io.Reader, error)

DecodeReader returns a streaming UTF-8 view of source.

func ExtractBytes

func ExtractBytes(record []byte, start, end int) ([]byte, error)

ExtractBytes returns the requested half-open byte range without copying it.

Types

type DelimitedConfig

type DelimitedConfig struct {
	Delimiter           rune
	Comment             rune
	LazyQuotes          bool
	TrimLeadingSpace    bool
	AllowVariableFields bool
	FieldsPerRecord     int
	// MaxRecordBytes bounds one logical record before parser allocation.
	// Zero preserves the unbounded legacy behavior.
	MaxRecordBytes int
	// MaxFieldBytes bounds one parsed field before normalization.
	// Zero preserves the unbounded legacy behavior.
	MaxFieldBytes int
	Header        *HeaderConfig
	Normalize     NormalizationConfig
}

DelimitedConfig explicitly controls delimited-text parsing behavior.

type DelimitedReader

type DelimitedReader struct {
	// contains filtered or unexported fields
}

DelimitedReader streams records from CSV or another delimited text format.

func NewCSVReader

func NewCSVReader(source io.Reader, config DelimitedConfig) (*DelimitedReader, error)

NewCSVReader constructs a comma-delimited streaming reader.

func NewDelimitedReader

func NewDelimitedReader(source io.Reader, config DelimitedConfig) (*DelimitedReader, error)

NewDelimitedReader constructs a streaming reader for an explicit delimiter.

Example
package main

import (
	"fmt"
	"strings"

	tabular "github.com/faustbrian/golib/pkg/tabular"
)

func main() {
	reader, err := tabular.NewDelimitedReader(strings.NewReader("name;city\nAda;Helsinki\n"), tabular.DelimitedConfig{
		Delimiter: ';',
		Header: &tabular.HeaderConfig{
			Case:             tabular.HeaderCaseLower,
			RejectEmpty:      true,
			RejectDuplicates: true,
		},
	})
	if err != nil {
		panic(err)
	}
	header, err := reader.Header()
	if err != nil {
		panic(err)
	}
	row, err := reader.Read()
	if err != nil {
		panic(err)
	}
	fmt.Println(header)
	fmt.Println(row)
}
Output:
[name city]
[Ada Helsinki]

func (*DelimitedReader) Header

func (reader *DelimitedReader) Header() (Row, error)

Header returns the normalized first row when header handling is configured. The returned row is a copy and is safe for the caller to modify.

func (*DelimitedReader) Read

func (reader *DelimitedReader) Read() (Row, error)

Read returns the next normalized record. io.EOF marks a clean end of input.

type Encoding

type Encoding string

Encoding names a supported source character encoding.

const (
	// EncodingUTF8 selects strict UTF-8 validation.
	EncodingUTF8 Encoding = "utf-8"
	// EncodingISO88591 selects the ISO-8859-1 single-byte encoding.
	EncodingISO88591 Encoding = "iso-8859-1"
	// EncodingWindows1252 selects the Windows-1252 single-byte encoding.
	EncodingWindows1252 Encoding = "windows-1252"
)

type Error

type Error struct {
	Kind   ErrorKind
	Op     string
	Format string
	Row    int
	Field  int
	Err    error
}

Error carries stable classification and optional ingest coordinates. Row and Field are one-based when set.

func (*Error) Error

func (err *Error) Error() string

func (*Error) Is

func (err *Error) Is(target error) bool

Is matches stable ErrorKind values and wrapped causes.

func (*Error) Unwrap

func (err *Error) Unwrap() error

type ErrorKind

type ErrorKind string

ErrorKind identifies a stable class of error that callers can match with errors.Is.

const (
	// ErrorInvalidConfig indicates invalid parser configuration.
	ErrorInvalidConfig ErrorKind = "invalid configuration"
	// ErrorInvalidHeader indicates a missing or invalid header.
	ErrorInvalidHeader ErrorKind = "invalid header"
	// ErrorDuplicateHeader indicates a duplicate normalized header name.
	ErrorDuplicateHeader ErrorKind = "duplicate header"
	// ErrorMalformedRow indicates invalid record syntax or shape.
	ErrorMalformedRow ErrorKind = "malformed row"
	// ErrorInvalidEncoding indicates unsupported or invalid text encoding.
	ErrorInvalidEncoding ErrorKind = "invalid encoding"
	// ErrorInvalidLayout indicates an invalid fixed-width layout.
	ErrorInvalidLayout ErrorKind = "invalid fixed-width layout"
	// ErrorArchive indicates an invalid archive or entry.
	ErrorArchive ErrorKind = "archive error"
	// ErrorEntryNotFound indicates an absent exact archive entry name.
	ErrorEntryNotFound ErrorKind = "archive entry not found"
	// ErrorLimitExceeded indicates a configured resource limit was exceeded.
	ErrorLimitExceeded ErrorKind = "limit exceeded"
	// ErrorSpreadsheet indicates an invalid workbook or worksheet operation.
	ErrorSpreadsheet ErrorKind = "spreadsheet error"
)

func (ErrorKind) Error

func (kind ErrorKind) Error() string

type FixedWidthConfig

type FixedWidthConfig struct {
	Fields              []FixedWidthField
	Encoding            Encoding
	AllowShortRecords   bool
	RejectTrailingBytes bool
	MaxRecordBytes      int
	Normalize           NormalizationConfig
}

FixedWidthConfig controls fixed-width record parsing.

type FixedWidthField

type FixedWidthField struct {
	Name      string
	Start     int
	End       int
	TrimSpace bool
}

FixedWidthField identifies a half-open byte range [Start, End) in a source record. Offsets apply before character decoding.

type FixedWidthReader

type FixedWidthReader struct {
	// contains filtered or unexported fields
}

FixedWidthReader streams newline-delimited fixed-width records.

func NewFixedWidthReader

func NewFixedWidthReader(source io.Reader, config FixedWidthConfig) (*FixedWidthReader, error)

NewFixedWidthReader validates a layout and constructs a streaming reader.

Example
package main

import (
	"fmt"
	"strings"

	tabular "github.com/faustbrian/golib/pkg/tabular"
)

func main() {
	reader, err := tabular.NewFixedWidthReader(strings.NewReader("001Ada       Helsinki  \n"), tabular.FixedWidthConfig{
		Fields: []tabular.FixedWidthField{
			{Name: "id", Start: 0, End: 3},
			{Name: "name", Start: 3, End: 13, TrimSpace: true},
			{Name: "city", Start: 13, End: 23, TrimSpace: true},
		},
	})
	if err != nil {
		panic(err)
	}
	row, err := reader.Read()
	if err != nil {
		panic(err)
	}
	fmt.Println(reader.Fields())
	fmt.Println(row)
}
Output:
[id name city]
[001 Ada Helsinki]

func (*FixedWidthReader) Fields

func (reader *FixedWidthReader) Fields() []string

Fields returns the configured field names in source order.

func (*FixedWidthReader) Read

func (reader *FixedWidthReader) Read() (Row, error)

Read returns the next decoded row. io.EOF marks a clean end of input.

type HeaderCase

type HeaderCase uint8

HeaderCase controls header-name case conversion.

const (
	// HeaderCasePreserve retains source header casing.
	HeaderCasePreserve HeaderCase = iota
	// HeaderCaseLower converts headers to lowercase.
	HeaderCaseLower
	// HeaderCaseUpper converts headers to uppercase.
	HeaderCaseUpper
)

type HeaderConfig

type HeaderConfig struct {
	TrimSpace        bool
	Case             HeaderCase
	Replace          map[string]string
	RejectEmpty      bool
	RejectDuplicates bool
}

HeaderConfig describes explicit header normalization and validation.

type NormalizationConfig

type NormalizationConfig struct {
	TrimSpace bool
	EmptyAs   string
}

NormalizationConfig describes explicit field-level data changes.

type Row

type Row []string

Row is one tabular record. Fields retain source order.

func NormalizeHeader

func NormalizeHeader(header Row, config HeaderConfig) (Row, error)

NormalizeHeader returns a normalized copy of header or a typed validation error. A UTF-8 BOM is removed from the first field before other changes.

func NormalizeRow

func NormalizeRow(row Row, config NormalizationConfig) Row

NormalizeRow returns a copy of row with configured transformations applied.

type SpreadsheetCell

type SpreadsheetCell struct {
	// contains filtered or unexported fields
}

SpreadsheetCell is one immutable decoded worksheet cell.

func (SpreadsheetCell) Present

func (cell SpreadsheetCell) Present() bool

Present reports whether the workbook stored a cell at this position.

func (SpreadsheetCell) Value

func (cell SpreadsheetCell) Value() string

Value returns the decoded and normalized cell value.

type SpreadsheetConfig

type SpreadsheetConfig struct {
	Format              SpreadsheetFormat
	Sheet               string
	Header              *HeaderConfig
	Normalize           NormalizationConfig
	FieldsPerRecord     int
	AllowVariableFields bool
	PreserveCellErrors  bool
	// PreserveCellPresence enables ReadCells and its absent-versus-stored-empty
	// distinction. Zero preserves the optimized string-only Read behavior.
	PreserveCellPresence bool
	MaxWorkbookBytes     int64
	// MaxRecordBytes bounds one parsed worksheet row before normalization.
	// Zero preserves the unbounded legacy behavior.
	MaxRecordBytes int
	// MaxFieldBytes bounds one parsed worksheet cell before normalization.
	// Zero preserves the unbounded legacy behavior.
	MaxFieldBytes int
	// MaxSheets bounds the number of worksheets in an XLSX workbook.
	// Zero preserves the unbounded legacy behavior.
	MaxSheets int
	ZIP       ZIPConfig
}

SpreadsheetConfig controls workbook selection and row semantics.

type SpreadsheetFormat

type SpreadsheetFormat string

SpreadsheetFormat identifies an explicitly selected workbook format.

const (
	// FormatXLS selects legacy OLE2/BIFF8 workbooks.
	FormatXLS SpreadsheetFormat = "xls"
	// FormatXLSX selects OOXML workbooks.
	FormatXLSX SpreadsheetFormat = "xlsx"
)

type SpreadsheetReader

type SpreadsheetReader struct {
	// contains filtered or unexported fields
}

SpreadsheetReader presents format-independent workbook rows.

func OpenSpreadsheet

func OpenSpreadsheet(source io.ReaderAt, size int64, config SpreadsheetConfig) (*SpreadsheetReader, error)

OpenSpreadsheet opens an explicitly configured XLS or XLSX workbook.

func (*SpreadsheetReader) Close

func (reader *SpreadsheetReader) Close() error

Close releases iterator resources. It does not close the caller's source.

func (*SpreadsheetReader) Header

func (reader *SpreadsheetReader) Header() (Row, error)

Header returns a normalized copy of the configured first row.

func (*SpreadsheetReader) Read

func (reader *SpreadsheetReader) Read() (Row, error)

Read returns the next worksheet row.

func (*SpreadsheetReader) ReadCells

func (reader *SpreadsheetReader) ReadCells() (SpreadsheetRow, error)

ReadCells returns the next worksheet row while preserving whether each cell was stored or absent. Values follow the same normalization and error policy as Read.

type SpreadsheetRow

type SpreadsheetRow []SpreadsheetCell

SpreadsheetRow preserves workbook cell presence alongside decoded values.

type ZIPArchive

type ZIPArchive struct {
	// contains filtered or unexported fields
}

ZIPArchive is a validated, indexed ZIP source.

func OpenZIP

func OpenZIP(source io.ReaderAt, size int64, config ZIPConfig) (*ZIPArchive, error)

OpenZIP validates and indexes an archive from a random-access source.

func (*ZIPArchive) Entries

func (archive *ZIPArchive) Entries() []ZIPEntry

Entries returns archive metadata in the original central-directory order.

func (*ZIPArchive) Extract

func (archive *ZIPArchive) Extract(name string, destination io.Writer) error

Extract streams one exact entry to destination and verifies its ZIP checksum.

func (*ZIPArchive) Open

func (archive *ZIPArchive) Open(name string) (io.ReadCloser, error)

Open returns a streaming reader for an exact, non-directory entry name.

type ZIPConfig

type ZIPConfig struct {
	MaxEntries          int
	MaxEntryBytes       uint64
	MaxTotalBytes       uint64
	MaxCompressionRatio uint64
	RejectSymlinks      bool
}

ZIPConfig defines archive-bomb safeguards. Zero size values select documented finite defaults. A zero compression ratio and false link policy preserve the prior behavior.

type ZIPEntry

type ZIPEntry struct {
	Name             string
	UncompressedSize uint64
	Directory        bool
}

ZIPEntry is immutable metadata for one archive member.

Directories

Path Synopsis
internal
xls
Package xls implements the bounded OLE2 primitives needed to read BIFF8 workbooks.
Package xls implements the bounded OLE2 primitives needed to read BIFF8 workbooks.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL