Introduction
As a Sinologist, my main interest for the past 35 years lay in the digital representation
of premodern Chinese texts for use in research environments. Encountering the TEI
and SGML at
that time was a life changing experience and has shaped my activities for many years;
one of
the outcomes is a large corpus of Buddhist texts[1]. Among colleagues the uptake was mixed, and looking for
more approachable ways to encode text, I embraced an approach of
making the markup as implicit as possible with a simple data format
derived from Emacs org-mode, called the Kanseki Repository[2]. This went on for a dozen years, then a different
project brought me back to TEI – by this time I had given up the
idea of having researchers edit the source directly and concentrated
on the creation of a collaborative platform, hiding the technical
complexity and angled brackets[3]. Ten years into this project, out of an attempt to bring the Kanseki Repository
and the TLS on the same page, I happened on the idea to stop trying to contain all
features of
a text in one container and instead embracing an approach which bears some similarity
to
Docker images: Composable layers of a text as separate components, drawn together
according to
a recipe (think Docker file
) by a composer that performs the steps outlined in
the recipe and produces the fleeting shape of a text according to the wishes of the
reader:
with or without annotations, translations or facsimile images, and in the format needed
at the
moment: PDF for printing, HTML for the web, ePub for a ebook reader.
As always, getting the details right is where the rubber hits the road. JSON is the native language of most modern communication, but not really usable as an archival file format; YAML fits this bill better. A few weeks of experimenting resulted in a shape of data that is both fit for the purpose and has interesting properties inherited from thousands of years of writing Chinese: Texts written on scrolls, kept in bundles with minimal metadata attached. So in this presentation, I would like to introduce this text format, which is specialized for this specific use of premodern Chinese texts, and probably not for much else, but might still contain some ideas that inspire imitation. A first realization of an interface is now online[4], and a first version of the texts is published.
A short look at the shape of Chinese texts
Before we dive in, I will give a very short introduction to premodern Chinese texts. The earliest surfaces used for writing in China have been bones of cattle or turtle plastrons, followed by bronze vessels. These could only be used for short inscriptions, so bamboo strips and silk came also to be used. Before woodblock printing started in the 9th century (see Fig 1), texts have been written on paper since the Eastern Han period (25 - 220 AD). To make handling of longer texts convenient, sheets of paper were glued together to form a long surface of several meters (ready to hold several thousand characters), these were rolled for storage and put in boxes. For convenience, the outside usually had the title and sequential number and the beginning would start with some metadata for housekeeping, including a table of contents if there were several shorter texts on a scroll.
Figure 1: Woodblock print of the Diamond Sutra, ca 868

The beginning section of a woodblock print, discovered in Central Asia, now held in the British Library, shelfmark Or. 8210.
Paper made writing much more convenient and marked the beginning of the production of larger amounts of texts; some conventions that arose around this time carried over to the period of woodblock printing, when even larger numbers of texts could be produced conveniently. Sheets of paper would still be glued together, but only after the printing, so now the sheets themselves also needed some metadata, which included in addition to a short title and a scroll number, also the sheet number and in many cases the number of characters on the sheet and the name of the carver – the latter was necessary to calculate the pay.
So a scroll, juan in Chinese, continued to be a basic unit for the production of texts. Since the length could be adjusted to a certain degree, the juan came also to resemble not just a physical, but also a logical unit of a text and it became customary to refer to specific locations in a text by title and scroll number. Within a scroll though, there was very little structure, apart from very few paragraph indications – starting a new line before the space was used up – the text would flow without interruption, hundreds and hundreds of characters.
There are, of course, texts that have more internal structure, for example, with a commentary interlaced with the main text. In this case, the commentary might be in smaller characters occupying a quarter of the square a normal character would take up, thus slicing the text up in (at least) two separate logical streams within a text. And for texts that had been commented on several times over the centuries (the way to make a new statement in Chinese thinking was to make it a comment to a classic, thus making it look like it was not new at all), sometimes these commentaries would be collected, and such a text could have six or more additional voices layered on the source text. As an example for a woodblock print, Fig 2 shows a text with commentary.
Figure 2: History of the later Han, first part of first juan

Beginning of the main text, with the frontmatter marked in yellow – this includes the title and name of the author, as well of the main commentator. The first heading is marked in orange, while the root text is in blue, and the commentary is in black. I preserved some of the text unmarked to show the original shape.
Now this handling is not yet implemented for the new data format, but Fig. 3 shows how it looks like in the upstream source, displayed in the web interface.
Figure 3: The same location as Fig 2, but a different source

This is a screenshot of a display of this same text location in the Kanseki Repository, with the smaller print text rendered as pink to make the distinction more obvious. The right hand side shows the optionally activated digital facsimile of the edition used here.
Digital representation and system design
What about TEI?
TEI/XML is of course perfectly capable of representing Chinese text and has been used for this for more than 30 years. Probably the largest, at least the largest publicly available, collection is the CBETA 電子佛典集成 (CBETA Collection of Buddhist Scriptures[5]), but there might be others. In another project I have spend many years developing a platform for collaborative research on such texts, proving that it is indeed possible.
However, over the years I also encountered problems, not so much with the representation itself, but with the overall system design with the affordances provided by this structure.
One of the aims of the collaborative platform was to provide full support for the research workflow for a text, which starts from establishing the exact characters of the text, including comparison of variant editions or parallel occurrences while referencing a digital facsimile of the text. The next step then is identifying parts of a texts, such as front matter, body or back matter, as well as internal structure, as far as it is visible[6]. This is followed by adding sections, paragraphs and punctuation to the text. At this point the text is ready to be annotated and translated, which are the main tasks for the project. It might be blamed on the way TEI was applied to this project, but this workflow essentially required to prepare the texts outside the collaborative platform and upload them once they reached a satisfying state, thus resulting in a waterfall workflow rather than a circular one[7].
So in a way of speaking, the text format introduced here might not so much be seen as replacing TEI, but rather providing a scaffolding for the production of a text that can then be interchanged with others using TEI.
So then, what exactly is a text?
One of the main tasks scheduled for our project this year was the integration of a large additional trove of texts into the existing platform, which would result in a tenfold expansion of the number of covered texts. The main catch was that the new corpus is unstructured and raw, and could thus not easily be added to the existing system. In a number of research meetings, we ended up questioning some of the fundamental assumptions about what a text is, and how it should be treated. Coming from the raw representation of a text in the scrolls of the first millenium, everything that was added later could be seen as an interpretative addition to a text, not an essential part. So we started to see the text not as a fixed publication, but rather as a composite artefact that could be made up from various ingredients at the convenience of the producer (or even of the reader).
Right, but how do we tell the computer?
This encouraged me to look for a solution that would not rely on TEI. It still needed to be fit for markup statements, but rather standoff than inline. One experiment tried to use JSON for text representation, but that is both awful to edit and to manage in a git based environment. YAML then seemed a better fit, and so the current implementation and data format took shape. It is still in a somewhat experimental state, but with help from today's coding agents, it could be quickly turned into a viable implementation and tested on the whole corpus. However, there are still some components completely missing.
Bunkankun format
The building housing my office in Kyoto is the branch office of our
Institute, Bunkan
in Japanese, so I nicknamed my
coding agent Bunkankun
=~ the Bunkan
guy
. Somehow the name stuck; a domain could be registered,
and no clashes with other names were discovered by a few casual
searches on the Internet and on GitHub (bkk
was
already taken, unfortunately), so that became the name for this endeavor.
The texts we are dealing with vary considerably in length, from a few hundred characters
to many millions, with the largest text running to 1000 juan in the order of 20000
characters. For convenience, this is used as a handy unit of division for the texts,
so each juan will
reside in its own YAML file. These are then collected in text
bundles
, with a manifest file declaring the assets in a
bundle and adding some administrative metadata. There will be at least a
surface edition – the curated text we establish – and possibly an unlimited number
of additional
editions, residing in a similar way under an edition
subdirectory.
Archival format
All text fields, no matter their location or
type, are accompanied by a hash field whose
value audits the content. This will allow the standoff references to the content by
offset to be pinned to discoverable versions and allow detection of drift.
A juan file has a
front, a body, and a
back. Only the body must be non-empty; the
others are optional and need not be present if empty. Additional
metadata fields are available. A typical front contains
an opening line that locates the juan in a larger collection, the
title of the text, the sequential number of the juan, and an
attribution naming persons and roles with respect to the body. The
back contains a closing line. The placement of prefaces,
postfaces, colophons, and similar paratextual material is open:
such material may go into the body or be separated out into front
or back, at the discretion of the project applying the format.
The body has one text element that holds the canonical character content of the whole juan. Space characters, punctuation, line breaks, and similar content are not present in this stream — they are extracted into the juan's logical marker collection. A marker has at minimum a type and an offset; further fields are optional and typically include id, content, and additional structured information appropriate to the marker's type. The set of marker types is open; a small core vocabulary is defined separately.
Markers may be stored in several physical locations. A bucket may
carry an inline markers list for structural
markers that are useful when inspecting the juan by itself. Most
markers may instead be stored in a per-juan marker asset under the
bundle's assets/ directory and pinned from the
manifest. Consumers treat inline markers and marker-asset markers
as one logical collection, sorted by offset. This keeps juan files
compact while preserving the same text-addressing model.
One text can be represented in several editions. Additional editions in a format that
parallels the main structure are storied in a subdirectory editions. In addition, they can be made
accessible through the master
edition of the text through markers that indicate at which offsets the text differs
from the surface content.
Canonicalization
The markers link to their associated text files by raw offset.
Since this is a potential fragile mechanism, extra care needs to
be taken to ensure the integrity of the text fields. As has been mentioned, a hash
is used to make changes to the text discoverable. For Chinese
text, there are additional steps required to make this process robust. For this purpose,
we define a set of
canonical characters. These
characters are drawn from Unicode, but due to the history of Unicode, there are many
codepoints that essentially encode the same character. For this reason, we need to
exclude some, while providing a mapping of the excluded characters to corresponding
characters in the canonical set. On the other hand, there are rare characters which
do not (yet?) have an assigned codepoint in Unicode. For these cases, we designate
an area within the large block of PUA (private use area) characters for the purpose
of refering to these characters within our texts, while maintaining external information
as part of the text format, which provides ways to render the characters, similar
to the g element in the TEI. This constitutes a reference character set,
which is part of the file format together with other reference
assets.
Source material is brought into this canonical form through a deterministic procedure; every step that alters the source is recorded as a marker, so that the source can be reconstructed exactly from the canonical text and its markers.
The procedure is applied in order:
-
Source. Input is treated as Unicode encoded in UTF-8.
-
Entity expansion. Entity references in the source — for example,
&KRxxxx;style references in Kanripo material, or TEI<g/>elements — that point to characters not available in current Unicode are expanded to codepoints in the Supplementary Private Use Area (SPUA-A, U+F0000–U+FFFFD) using the bundle's declared entity encoding. The expansion is deterministic and produces no markers: the PUA codepoint, taken together with the declared encoding, is sufficient to recover the entity reference. The PUA codepoints produced this way are first-class members of the canonical text stream and participate in offsets, the text hash, and downstream marker logic like any other character. -
Unicode normalization. NFC is applied so that base characters are in precomposed form.
-
Extraction of layout features. Characters carrying layout, structural, or paratextual information — whitespace, punctuation, indent characters, page and line breaks, register dividers, and similar — are removed from the text element and emitted as markers, each with an offset into the remaining text stream.
-
Substitution to canonical characters. Any character in the post-extraction stream that is not a member of the canonical character set is replaced by its canonical equivalent. Each such substitution is emitted as a marker recording the offset, the character or sequence replaced, and the reason for the substitution.
-
Hash. The resulting text stream, encoded as UTF-8, is the input to the hash field that accompanies the text element.
Offsets count Unicode codepoints into the post-substitution text stream. Variation selectors and similar combining characters are not independent offset targets; they remain attached to the preceding base character.
Characters that fall outside the declared set are not forbidden in a source — they are replaced during canonicalization and the replacement is recorded as a substitution marker. Common reasons for exclusion include compatibility characters with a documented preferred form, deprecated codepoints, ad-hoc Private Use Area assignments from third-party encoding schemes, and blocks deliberately not yet supported.
Markers
Markers are the mechanism by which information that has been removed from the text stream during canonicalization, or that annotates the text stream from outside it, is reattached to specific positions in the canonical text.
The logical marker collection for a bucket is assembled from the
bucket's inline markers list, if present,
plus the matching list in the juan's marker asset, if the
manifest declares one. Inline markers are therefore a storage
convenience, not a separate semantic class. When both locations
contain markers, consumers merge them by offset and preserve
source order for markers with the same offset.
Markers come in two shapes. Point
markers apply at a single position; they carry an
offset alone. Range
markers apply to a slice of the text stream; they
carry an additional length field, and apply
to the slice [offset, offset + length). The
shape is fixed by the marker type. The length
field mirrors the codepoint-slice form also used in the
recipe-selection vocabulary.
Every marker carries the following required fields:
-
type: the kind of marker.
-
offset: the codepoint position in the canonical text stream where the marker begins.
-
length: the codepoint span of the marker. Present on range-typed markers, absent on point-typed.
Optional fields are defined per marker type and may include
id,
content,
note,
responds-to (an id referring
to another marker, used by markers that comment on or extend
another marker), and structured fields specific to the type. A
marker carrying an id may be referenced from
elsewhere; ids are local to the juan unless qualified. Id
uniqueness is checked over the assembled logical marker
collection, not merely over one physical file.
The set of marker types is open. A small core vocabulary is defined for layout, structural, voicing, and substitution markers: page break, line break, indent, punctuation, paragraph break, register divider, head, comment, and substitution are point-typed; voice (§"Voices") is range-typed. Projects may extend the set for their own purposes; a marker type that is not in the core vocabulary should be namespaced by the project introducing it.
XML-based imports may emit a generic point marker for source
elements whose presence should be preserved but whose semantics
are not otherwise modeled by the core importer. This marker has
type
xml-element and carries the required fields
offset,
name, and
role. The
name field records the source
element name, using the same prefix form used elsewhere in
source metadata, and role is
normally open or close.
Additional fields may be present, for example,
id or an
attrs mapping of source
attributes. Page and line breaks, tls:head,
and tls:seg remain represented by their
specialized markers and are not emitted as
xml-element markers[8].
By default, structural and navigation-critical markers remain
inline: tls:head,
tls:div-start,
tls:div-end,
cbeta:juan-start,
cbeta:juan-end,
cbeta:mulu, and any marker directly
referenced by the table of contents. Layout markers,
punctuation, page and line breaks, voice markers, variant
markers, substitution markers, comments, and other bulky project
markers normally live in the marker asset. This default is a
packaging policy rather than a semantic restriction.
Voice markers
A voice marker records that a slice of the canonical text stream belongs to a named textual layer. It is the mechanism by which interleaved threads — a root text and its commentaries, for example — are disentangled so that a consumer can extract any one layer, present several in parallel, or reproduce the interleaved source as published.
A voice marker is range-typed. It carries the required marker fields together with:
-
type: the literal value
voice. -
length: the slice length, in codepoints.
-
name: the textual layer this slice belongs to.
The name vocabulary is open. A small recognized set is defined so that cross-project work stays legible:
-
root— the primary, commented-upon text. -
commentary— a layer of comment on a root text. -
subcommentary— a layer of comment on a commentary. -
gloss— a short interlinear annotation (typical layout: between the lines, in smaller characters). -
note— a bracketed span whose specific role (commentary, gloss, alternate reading, citation, …) is left unresolved. Used when the only available signal is a punctuation fence such as(…=)= and the deriver can't claim anything stronger.
Projects extend by prefixing —
kr:apparatus, tls:swl,
and so on — consistent with the rest of the marker
vocabulary.
A voice marker may carry optional fields including
id,
note, and
responds-to — the id of
another voice marker that this voice answers to. A
subcommentary voice typically
responds-to a
commentary voice; a
commentary voice
responds-to a root
voice. The relationship is many-to-one: a single root span
may attract several commentary spans, each of which names
that root span as its responds-to target.
Voices coexist with the single text stream of the juan;
there is no separate per-voice text element. The juan's
canonical text contains every voice's content in source
order, and voice markers carve it into named slices. A
consumer extracting only the root
filters by
name=root and reads the slices in offset
order; one reproducing the interleaved form walks all voice
markers in offset order. Both presentations are derivations
of the same canonical text and guarded by the same hash.
The lighter-weight point markers comment
and head (which carry out-of-stream
content) remain available for sparse
interlinear material — short editor's notes, occasional
headings — where promoting the inserted text into a
first-class slice of the text stream would be overkill.
Example. A short commentary passage alternates root lines and commentary on those lines. The canonical text of the juan, rendered as a single stream, reads:
色不異空謂色相虛幻不離空性空不異色謂空性遍周不離色相
The juan carries four voice markers:
markers:
- {type: voice, offset: 0, length: 4, name: root, id: r1}
- {type: voice, offset: 4, length: 9, name: commentary, responds-to: r1}
- {type: voice, offset: 13, length: 4, name: root, id: r2}
- {type: voice, offset: 17, length: 9, name: commentary, responds-to: r2}
A consumer extracting name=root walks the
marker list in offset order and concatenates the slices
[0,4) and [13,17),
yielding 色不異空空不異色. A consumer
reproducing the interleaved source walks every voice marker
in offset order and emits the slices in sequence, producing
the original stream. Both presentations are derivations of
the same canonical text and the same hash; neither
presentation requires altering the underlying juan.
Overlay bundles
An overlay bundle is a bundle that contributes additional markers to another bundle's text stream without modifying it. Its manifest declares a target — the canonical identifier and hash of a target bundle — and its juan files carry marker collections that address offsets in the corresponding juans of the target. The target is unaffected: its hash does not change, and existing pins to it remain valid. The overlay is independently addressable, hashed, and versioned per the usual bundle pattern.
The motivation is straightforward. Useful markers can be authored after a bundle has been published, by parties who do not own the target, for purposes its original author may not have considered: segmentation introduced for the sake of a translation, modern punctuation added to a classical source, voice markers added to disentangle a layout the source did not mark machine-readably, scholarly annotation distributed independently. A consumer composes the target with whichever overlays they want via a recipe.
These distributed layers are what makes this system adaptable to a variety of needs and use cases. A research paper could, for example, keep the passages it quotes as an overlay and could then use a recipe to pull the source and an accompanying translation from the corpus, in a similar way to the handling of bibliographies. Readers could then use such an overlay to inspect the quotations in their original context, comment or add their own translations.
Conclusions
The text format introduced here is just a few weeks old and still in a very early
phase of experimentation and implementation. After initial testing with a small subset
of texts, a more thorough conversion of texts from three different sources is now
live at the GitHub account bkkbooks, with almost 12500 titles. Code for a variety of maintenance tasks, including import,
export, a web server and the frontend UI are all available at bunkankun/bkk.
The next step will involve adding proper segmentation to the digital text, this will also be added as markers. Text sections in our sources that start with a separate header line are easily recognized from analyzing the existing markers. Paragraphs in the modern sense are not existing and thus not indicated, but preliminary experiments show that large language models, with the right prompting and harness, can be pressed into service here. The same also holds for punctuation. And here is where this data format shines: Responses from different models can easily be overlaid, compared and further processed, without affecting the base text at all.
At the moment, this looks like a real opportunity to take care of the first few steps of the research workflow, the establishing of a well understood text, with the option of downstream paths to either the Kanseki Repository or the TEI based TLS format. Time will show how these three projects will evolve.
[1] CBETA, live at https://cbetaonline.dila.edu.tw/en/, XML source at https://github.com/cbeta-org/xml-p5.
[2] A text repository based on this is live at https://kanripo.org, source at https://github.com/kanripo.
[3] The platform, called Thesaurus Linguae Sericae (TLS), also known as Hanxue Wendian (HXWD), is live at https://hxwd.org; the source is at https://github.com/tls-kr.
[4] To visit at https://bunkankun.org, supporting material and code at https://github.com/bunkankun; the texts are available at https://github.com/bkkbooks.
[5] Accessible in TEI P5 XML source at https://github.com/cbeta-org/xml-p5
[6] Ideally, these steps would be applied as needed and in a repetitive, circular manner, so that only parts of the text that were needed immediately had to be treated, rather than completing every step completely before moving on to the next step.
[7] At some point, user interface elements for most of the text critical work to establish a text were implemented, but they proved to be hard to use and brittle and saw little actual use.
[8] In the reference implementation bkk, a list of elements to be preserved in this way can be supplied.