Repository navigation
features custom mappers
Active contributors: Artem
A caller-supplied KiOptions.map callback replaces both built-in profiles. It validates an application schema and emits incremental text through KiDoc, while the library retains XML parsing, fonts, wrapping, PDF generation and publication.
CatalogMap mapper;
catalog_map_init(&mapper);
KiOptions opt;
KiResult result;
ki_options_init(&opt);
opt.map = catalog_map_event;
opt.map_user = &mapper;
int status = ki_convert_file("catalog.xml", "catalog.pdf", &opt, &result);This uses examples/catalog_map.h and examples/catalog_map.c. The callback signature is int map(void *user, KiDoc *doc, const KiEvent *ev). Return zero for success, nonzero to stop. src/convert.c always dispatches custom map before a built-in profile; the ordinary options/profile fields must still pass validation.
- Keep current state bounded. The example holds one state, one value-started flag and one counter, not field/item/document buffers.
- Handle START, TEXT and END; there is no mapper EOF event. Validate required children at parent closure and emit summaries at root closure.
- Use
text_lenin bytes, notstrlenassumptions for arbitrary fragments. Send TEXT and CDATA through the same emission path and preserve significant spaces. - Check every
ki_doc_text/ki_doc_breakreturn and propagate failures immediately. Replacing them withki_doc_failhides font/resource/I/O causes. - Use
ki_doc_fail(doc, "schema: rule")for schema errors. The library appends the current consumedat line L, column C, byte B; do not append a second offset yourself.
KiEvent and its name/text/attribute pointers are borrowed only during that callback (include/ki_xml_pdf.h). Consume attributes immediately or copy only bounded metadata; never save an event pointer.
Text is incremental, not a whole field. The current parser emits scalar-complete chunks, and ki_doc_text also accepts caller emission split within UTF-8 sequences. The pending word survives separate calls and TEXT/CDATA boundaries; no application word buffer is needed. See Layout and fonts.
| Element | Attributes | Content |
|---|---|---|
catalog |
None | Zero or more items |
item |
Optional nonblank id
|
Exactly one name, then one price
|
name, price
|
None | Text/CDATA/references, no nested elements |
examples/catalog_map.c has eight states: before catalog, in catalog, need/in name, need/in price, item done, after catalog. It rejects namespaces, unknown attributes/elements, invalid child order and structural text.
Output is Catalog, Item <id or original number>, Name: value, Price: value, then Items: N. Empty fields use a bare colon, duplicate IDs are allowed, empty catalogs are valid, and prices are lexical strings rather than parsed amounts. Attribute values retain the parser's 255-byte bound; field values stream without a mapper buffer limit.
The example is two C implementation files (mapper and driver), one private header and one dedicated Python verifier, tests/catalog_verify.py. This is an example-level 1:2 test-to-implementation-file ratio, not a coverage metric. Both files include only the public library interface.
make example
./examples/catalog examples/catalog.xml catalog.pdf
./examples/catalog examples/catalog.xml one-byte.pdf 1
python3 tests/catalog_verify.pyThe driver accepts catalog [--max-pages N] INPUT OUTPUT [CHUNK_BYTES]. No chunk uses file input; a positive chunk uses a caller-owned FILE reader through ki_convert_to_file. Zero/negative/invalid explicit chunks are usage errors.
Against an installation, compile mapper sources in the application:
prefix="$HOME/.local"
cc -std=c99 -Wall -Wextra -I"$prefix/include" \
examples/catalog_driver.c examples/catalog_map.c \
"$prefix/lib/libki_xml_pdf.a" -lz -o catalogUse the same prefix at build/install, or set an explicit font. Installed CMake/pkg-config targets manage zlib; see Packaging.
Report offset/limit and KiResult.report_* belong to the built-in report mapper. The custom catalog renders all items and keeps its count in CatalogMap.items; it does not inherit report selection.
ki_doc_fail produces KI_CAUSE_PROFILE. Its position is after the event, not the beginning of the tag; empty-element END matches START. Long rule text is shortened at a UTF-8 boundary while retaining the whole position suffix.
Use separate mapper state for concurrent conversions. Callback resources remain caller-owned. The file APIs preserve old destinations on late schema/XML/quota errors; callback output cannot be rolled back.
Adapt state transitions/labels in examples/catalog_map.c, define bounded state in examples/catalog_map.h, and wire I/O in examples/catalog_driver.c. Add independent positive/negative fixtures for your schema. Sorting, joins and document-wide lookup sets need a different resource contract.
tests/catalog_verify.py checks full expected output, duplicate/empty/significant-whitespace cases, long fields with file/one-byte/ordinary reads, consumed positions, distinct causes and preserved destination bytes/inode with temporary-file cleanup.
| File | Role |
|---|---|
include/ki_xml_pdf.h |
Map/event/document and ownership contract |
src/convert.c |
Mapper precedence, emission calls and positioned failures |
examples/catalog_map.c, examples/catalog_map.h
|
Fixed-state schema adapter |
examples/catalog_driver.c |
File/callback driver |
examples/catalog.xml, examples/catalog.expected.txt
|
Original fixture and independent oracle |
tests/catalog_verify.py |
Public-interface positive/negative suite |
docs/mapper-guide.md |
Full integration and adaptation guide |
Related: C API, Streaming I/O, Atomic output.