<feed xmlns='http://www.w3.org/2005/Atom'>
<title>sisudoc-ocda-lib/gleam/src/ocda/model.gleam, branch orgify</title>
<subtitle>SiSU Spine: tools</subtitle>
<link rel='alternate' type='text/html' href='https://git.sisudoc.org/projects/sisudoc-ocda-lib/'/>
<entry>
<title>gleam sisudoc-ocda-lib</title>
<updated>2026-10-03T01:39:08+00:00</updated>
<author>
<name>Ralph Amissah</name>
<email>ralph.amissah@gmail.com</email>
</author>
<published>2026-10-01T21:23:00+00:00</published>
<link rel='alternate' type='text/html' href='https://git.sisudoc.org/projects/sisudoc-ocda-lib/commit/?id=919942b1b5222cf00a5f68605bc383462770fa22'/>
<id>919942b1b5222cf00a5f68605bc383462770fa22</id>
<content type='text'>
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
</pre>
</div>
</content>
</entry>
<entry>
<title>model 2.1: ocn_range, the verses a poem holds</title>
<updated>2026-10-03T01:38:40+00:00</updated>
<author>
<name>Ralph Amissah</name>
<email>ralph.amissah@gmail.com</email>
</author>
<published>2026-09-29T18:07:32+00:00</published>
<link rel='alternate' type='text/html' href='https://git.sisudoc.org/projects/sisudoc-ocda-lib/commit/?id=597d73db60679b8ed13524f9dd3832bb7476038f'/>
<id>597d73db60679b8ed13524f9dd3832bb7476038f</id>
<content type='text'>
The format now states, for a container, the first and last ocn of
what it holds: .ocn_range in the .ssp, ocn_range_first and
ocn_range_last in the .ocda.db. A poem is the one container, and since
2.1 it carries ocn 0 and comes before its verses.

Both fields in the model, read by both loaders, in both languages, and
written by the dump. A 2.0 database has neither column, so each
loader asks sqlite whether it is there and reads a 2.0 file as having
no ranges, which is what 2.0 meant; checked on a published 2.0
database, D and Gleam giving the same dump.

Over the 36 document-languages written by spine at 2.1, the four
dumps (D and Gleam, .ssp and .ocda.db) are byte identical, 36 of 36.

(assisted by Claude-Code)
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
The format now states, for a container, the first and last ocn of
what it holds: .ocn_range in the .ssp, ocn_range_first and
ocn_range_last in the .ocda.db. A poem is the one container, and since
2.1 it carries ocn 0 and comes before its verses.

Both fields in the model, read by both loaders, in both languages, and
written by the dump. A 2.0 database has neither column, so each
loader asks sqlite whether it is there and reads a 2.0 file as having
no ranges, which is what 2.0 meant; checked on a published 2.0
database, D and Gleam giving the same dump.

Over the 36 document-languages written by spine at 2.1, the four
dumps (D and Gleam, .ssp and .ocda.db) are byte identical, 36 of 36.

(assisted by Claude-Code)
</pre>
</div>
</content>
</entry>
<entry>
<title>images from the source, and the epub segment fields</title>
<updated>2026-10-03T01:38:33+00:00</updated>
<author>
<name>Ralph Amissah</name>
<email>ralph.amissah@gmail.com</email>
</author>
<published>2026-09-26T13:47:07+00:00</published>
<link rel='alternate' type='text/html' href='https://git.sisudoc.org/projects/sisudoc-ocda-lib/commit/?id=6c14c162bdec65934fbda3b14295efcfc12d28d2'/>
<id>6c14c162bdec65934fbda3b14295efcfc12d28d2</id>
<content type='text'>
Two additions the epub writer needed and could not have without.

ocda.images: an image's bytes, from whichever input form the document came
from. The model names images, with digest, byte size and pixel size, and
does not hold them - which is right, an abstraction is not a bundle of
assets - so something has to know where they are, and the answer depends
on the input form rather than on the output. Hence the library.

  .ocda.db   the files table, role 'image', by name
  .sisupod   the archive entry whose path ends in /image/&lt;name&gt;
  .zip       the same
  .ssp       beside it: a pod puts the abstraction in media/abstraction/
             and its images in media/image/, so the path is documented
             rather than guessed

canGiveImages says whether a source can answer at all, because a writer
has to know before it emits an &lt;img&gt;: a manifest item whose file is absent
and an &lt;img&gt; whose file is not in the manifest are both epubcheck errors.
A .ssp outside a pod has nowhere to look, and that is absence rather than
error - the document is still readable.

Reading a blob needed sqlite3_column_blob rather than _column_text: an
image read as text stops at the first zero byte, and a png's ninth byte
is zero.

And two more model fields: segment_epub and segment_epub_is. The epub
segments at heading levels 1 to 4 where the html segments at level 4
alone, so the two are different files and the abstraction records both.
Nine of the 36 documents have images; the four-way agreement still holds
at 36 of 36 each way.

(assisted by Claude-Code)
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Two additions the epub writer needed and could not have without.

ocda.images: an image's bytes, from whichever input form the document came
from. The model names images, with digest, byte size and pixel size, and
does not hold them - which is right, an abstraction is not a bundle of
assets - so something has to know where they are, and the answer depends
on the input form rather than on the output. Hence the library.

  .ocda.db   the files table, role 'image', by name
  .sisupod   the archive entry whose path ends in /image/&lt;name&gt;
  .zip       the same
  .ssp       beside it: a pod puts the abstraction in media/abstraction/
             and its images in media/image/, so the path is documented
             rather than guessed

canGiveImages says whether a source can answer at all, because a writer
has to know before it emits an &lt;img&gt;: a manifest item whose file is absent
and an &lt;img&gt; whose file is not in the manifest are both epubcheck errors.
A .ssp outside a pod has nowhere to look, and that is absence rather than
error - the document is still readable.

Reading a blob needed sqlite3_column_blob rather than _column_text: an
image read as text stops at the first zero byte, and a png's ninth byte
is zero.

And two more model fields: segment_epub and segment_epub_is. The epub
segments at heading levels 1 to 4 where the html segments at level 4
alone, so the two are different files and the abstraction records both.
Nine of the 36 documents have images; the four-way agreement still holds
at 36 of 36 each way.

(assisted by Claude-Code)
</pre>
</div>
</content>
</entry>
<entry>
<title>two fields the model was missing, found by writing an html writer</title>
<updated>2026-10-03T01:38:29+00:00</updated>
<author>
<name>Ralph Amissah</name>
<email>ralph.amissah@gmail.com</email>
</author>
<published>2026-09-26T04:50:16+00:00</published>
<link rel='alternate' type='text/html' href='https://git.sisudoc.org/projects/sisudoc-ocda-lib/commit/?id=8fd585f659c5dfc7c822cb8b7d66d2c5f9e901a1'/>
<id>8fd585f659c5dfc7c822cb8b7d66d2c5f9e901a1</id>
<content type='text'>
anchor_tags and heading_lev_anchor. Both are in the specification's
property table and neither was read, so they went to the unknown counts
and a writer over the library had no way to emit a link target.

The cost was visible the moment an html writer existed: 154 of its
internal links pointed at nothing, because a note reference points at
the endnote's anchor and the endnote's anchor is an anchor_tag.
heading_lev_anchor is the name a document's own cross references use,
1,854 of them in the reference collection.

The .ssp carries them as repeated .anchor_tag and as
.heading_lev_anchor; the database as the object_anchors child table and
the heading_lev_anchor column. Both loaders read both now, the dump
reports them, and the four-way agreement still holds: 36 of 36 each way.

One thing to watch when adding a column to the database query: the read
is by position, so a column inserted in the middle shifts every one
after it. Adding heading_lev_anchor after segment_html_is broke all 36
documents until it moved to the end of the select.

(assisted by Claude-Code)
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
anchor_tags and heading_lev_anchor. Both are in the specification's
property table and neither was read, so they went to the unknown counts
and a writer over the library had no way to emit a link target.

The cost was visible the moment an html writer existed: 154 of its
internal links pointed at nothing, because a note reference points at
the endnote's anchor and the endnote's anchor is an anchor_tag.
heading_lev_anchor is the name a document's own cross references use,
1,854 of them in the reference collection.

The .ssp carries them as repeated .anchor_tag and as
.heading_lev_anchor; the database as the object_anchors child table and
the heading_lev_anchor column. Both loaders read both now, the dump
reports them, and the four-way agreement still holds: 36 of 36 each way.

One thing to watch when adding a column to the database query: the read
is by position, so a column inserted in the middle shifts every one
after it. Adding heading_lev_anchor after segment_html_is broke all 36
documents until it moved to the end of the select.

(assisted by Claude-Code)
</pre>
</div>
</content>
</entry>
<entry>
<title>ocda-dump, and the four disagreements it found</title>
<updated>2026-10-03T01:38:08+00:00</updated>
<author>
<name>Ralph Amissah</name>
<email>ralph.amissah@gmail.com</email>
</author>
<published>2026-09-26T01:02:39+00:00</published>
<link rel='alternate' type='text/html' href='https://git.sisudoc.org/projects/sisudoc-ocda-lib/commit/?id=64571ec14438e85d740ede5810a6f3a5cca88fe3'/>
<id>64571ec14438e85d740ede5810a6f3a5cca88fe3</id>
<content type='text'>
The agreement check compared ten fields of each object. That was enough
to find the blank-first-line defect and not enough for anything
subtler, so this adds ocda-dump: the whole loaded model written out
canonically, every field, sorted dictionaries, sections in format
order, defaults omitted. It is an oracle, not an output format. Two
implementations of this model can now be compared with diff.

Run against the reference collection it found four disagreements
between the two loaders, none of which the ten-field check could see.

1. .identifier and .anchor. The .ssp emits a property only when it
   carries information, so the identifier is absent when it is simply
   the ocn and the anchor is absent when it is the segment name; the
   database stores both unconditionally. Over one document that is
   1,314 identifiers and 69 anchors. The loader now derives them, which
   is what the model means: an object's identifier is its ocn unless it
   says otherwise.

2. make and doc_has. The database has one metadata table where the
   text form has three blocks, and tells them apart by a key prefix.
   The loader now unprefixes them, so a writer asks doc.make["footer"]
   whichever serialisation it was handed and never
   doc.metadata["make.footer"].

3. The JSON columns were read by a character scan that stripped
   brackets and quotes, which is wrong the moment a value holds an
   escape: JSONValue.toString escapes "/" as "\/", so every heading
   containing a slash came back with a backslash in it. All 36
   document-languages differed on heading_ancestors_text. Now parsed as
   JSON.

4. translation.ocn_aligned is in the database and not in the .ssp,
   legitimately: it says whether the languages share one numbering, and
   a .ssp is one language and cannot know. It is a fact about the file
   rather than the document, so the loader leaves it out rather than
   letting a writer tell which serialisation it was given.

Metadata values are also stripped now, as the text loader strips them:
one topic_register value carried surrounding spaces in the database
that the text form cannot represent.

After all four: 36 of 36 document-languages give byte identical dumps
from their .ssp and from their .ocda.db.

(assisted by Claude-Code)
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
The agreement check compared ten fields of each object. That was enough
to find the blank-first-line defect and not enough for anything
subtler, so this adds ocda-dump: the whole loaded model written out
canonically, every field, sorted dictionaries, sections in format
order, defaults omitted. It is an oracle, not an output format. Two
implementations of this model can now be compared with diff.

Run against the reference collection it found four disagreements
between the two loaders, none of which the ten-field check could see.

1. .identifier and .anchor. The .ssp emits a property only when it
   carries information, so the identifier is absent when it is simply
   the ocn and the anchor is absent when it is the segment name; the
   database stores both unconditionally. Over one document that is
   1,314 identifiers and 69 anchors. The loader now derives them, which
   is what the model means: an object's identifier is its ocn unless it
   says otherwise.

2. make and doc_has. The database has one metadata table where the
   text form has three blocks, and tells them apart by a key prefix.
   The loader now unprefixes them, so a writer asks doc.make["footer"]
   whichever serialisation it was handed and never
   doc.metadata["make.footer"].

3. The JSON columns were read by a character scan that stripped
   brackets and quotes, which is wrong the moment a value holds an
   escape: JSONValue.toString escapes "/" as "\/", so every heading
   containing a slash came back with a backslash in it. All 36
   document-languages differed on heading_ancestors_text. Now parsed as
   JSON.

4. translation.ocn_aligned is in the database and not in the .ssp,
   legitimately: it says whether the languages share one numbering, and
   a .ssp is one language and cannot know. It is a fact about the file
   rather than the document, so the loader leaves it out rather than
   letting a writer tell which serialisation it was given.

Metadata values are also stripped now, as the text loader strips them:
one topic_register value carried surrounding spaces in the database
that the text form cannot represent.

After all four: 36 of 36 document-languages give byte identical dumps
from their .ssp and from their .ocda.db.

(assisted by Claude-Code)
</pre>
</div>
</content>
</entry>
</feed>
