File-Raw-Separated

 view release on metacpan or  search on metacpan

include/separated_parser.h  view on Meta::CPAN

     * Lenient mode (default): best-effort recovery. */
    int strict;
    /* Line ending mode. AUTO locks to first detected LF/CRLF/CR. */
    separated_eol_t eol_mode;
    /* Strip leading/trailing ASCII whitespace from unquoted fields only. */
    int trim;
    /* Empty unquoted field becomes the sentinel SEPARATED_FIELD_NULL
     * (length == SEPARATED_FIELD_NULL_LEN, ptr == NULL).
     * Quoted empty ("") stays a real zero-length field. */
    int empty_is_undef;
    /* Skip UTF-8 BOM detection / stripping. */
    int binary;
    /* Header mode: when set, the dispatcher (XS layer) consumes the
     * first emitted row as field names and emits subsequent rows as
     * hashrefs keyed by those names. The C parser itself is not
     * affected — it always emits fields the same way. */
    int header;
    /* Maximum field length. 0 = use SEPARATED_FIELD_DEFAULT_CAP. */
    size_t max_field_len;
} separated_options_t;

lib/File/Raw/Separated.pm  view on Meta::CPAN

Strip leading/trailing ASCII space and tab from B<unquoted> fields
only. Quoted fields preserve all bytes. Default false.

=item C<empty_is_undef>

Empty unquoted field becomes C<undef>. Quoted empty (C<"">) stays the
empty string. Default false (always returns C<"">).

=item C<binary>

Skip UTF-8 BOM stripping and skip C<sv_utf8_decode> on each field.
Default false.

=item C<header>

Controls whether rows are emitted as arrayrefs (default) or hashrefs.
Two forms:

=over 4

=item C<< header => 1 >>

separated_parser.c  view on Meta::CPAN

    void *ud;

    /* Field buffer (geometric growth). */
    char  *buf;
    size_t buf_len;
    size_t buf_cap;

    /* State. */
    parse_state_t state;
    int  field_was_quoted;     /* 1 if current field began with a quote */
    int  bom_checked;          /* 1 once we've decided about the BOM */
    int  any_field_in_row;     /* 1 if at least one field started in this row */

    /* Auto-detected / pinned EOL. */
    separated_eol_t detected_eol;
    int  pending_cr;           /* 1 if last byte was CR awaiting LF/data (CRLF detect) */

    /* Diagnostics. */
    size_t bytes_consumed;
    size_t rows_emitted;
    size_t err_offset;

separated_parser.c  view on Meta::CPAN

    ctx->field_was_quoted = 0;
    ctx->any_field_in_row = 1;
    if (end_of_row) {
        ctx->rows_emitted++;
        ctx->any_field_in_row = 0;
    }
    return SEPARATED_OK;
}

/* ------------------------------------------------------------
 * BOM stripping (UTF-8 only, when binary=0).
 * Called once before any byte is interpreted. Caller passes the
 * incoming buffer pointer + length pair through bom_skip; on return
 * any leading 3-byte BOM has been advanced past.
 * ------------------------------------------------------------ */

static void
bom_check(separated_ctx_t *ctx, const char **pp, size_t *plen)
{
    if (ctx->bom_checked) return;
    ctx->bom_checked = 1;

    if (ctx->opts.binary) return;

t/06-bom.t  view on Meta::CPAN

use strict;
use warnings;
use Test::More;
use File::Raw::Separated qw(import);

my $bom = "\xEF\xBB\xBF";

is_deeply(
    file_csv_parse_buf("${bom}a,b\n"),
    [['a', 'b']],
    'UTF-8 BOM stripped from first field by default',
);

# binary => 1 keeps BOM bytes literal
my $rows = file_csv_parse_buf("${bom}a,b\n", { binary => 1 });
is($rows->[0][0], "${bom}a", 'binary mode preserves BOM bytes literally')
    or diag(explain($rows));
is($rows->[0][1], 'b', 'second field unaffected');

# BOM in the middle of input is data, not stripped. In default
# (non-binary) mode the field gets sv_utf8_decode'd, so the 3-byte BOM
# decodes to the single Unicode codepoint U+FEFF.
is_deeply(
    file_csv_parse_buf("a,${bom}b\n"),
    [['a', "\x{feff}b"]],
    'BOM in middle of input is data (decoded to U+FEFF)',
);

# In binary mode it stays as raw bytes.
is_deeply(
    file_csv_parse_buf("a,${bom}b\n", { binary => 1 }),
    [['a', "${bom}b"]],
    'BOM in middle, binary mode keeps bytes',
);

done_testing;



( run in 0.693 second using v1.01-cache-2.11-cpan-9581c071862 )