aboutsummaryrefslogtreecommitdiffhomepage
path: root/org/ocda_get.org
blob: 40effc5460db4712439f5d246182d20ae7826809 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
-*- mode: org -*-
#+TITLE:       sisudoc spine (doc_reform) object-centric document abstraction
#+DESCRIPTION: documents - structuring, publishing in multiple formats & search
#+FILETAGS:    :spine:abstraction:
#+AUTHOR:      Ralph Amissah
#+EMAIL:       [[mailto:ralph.amissah@gmail.com][ralph.amissah@gmail.com]]
#+COPYRIGHT:   Copyright (C) 2015 (continuously updated, current 2026) Ralph Amissah
#+LANGUAGE:    en
#+STARTUP:     content hideblocks hidestars noindent entitiespretty
#+PROPERTY:    header-args+ :eval never-export :exports code
#+PROPERTY:    header-args+ :noweb yes :padline no
#+PROPERTY:    header-args+ :results silent :cache no
#+PROPERTY:    header-args+ :mkdirp yes
#+OPTIONS:     H:3 num:nil toc:t \n:t ::t |:t ^:nil -:t f:t *:t
- magic single double-quote → " ← FIX changes hilighting behavior (occuring
  after it) in org document. INVESTIGATE (org-mode CONFIG?) FIND & FIX

- [[./doc-reform.org][doc-reform.org]]  [[./][org/]]

* Get OCD

get abstracted objects for downstream processing

** _module template_

#+HEADER: :tangle "../src/sisudoc/ocda/abstraction/doc_has.d"
#+HEADER: :noweb yes
#+BEGIN_SRC d
<<doc_header_including_copyright_and_license>>
module sisudoc.ocda.abstraction.doc_has;
@safe:
/+ ↓ ST_DocHas from a loaded abstraction

     the parser builds ST_DocHas as it goes, with the markup in front of it.
     A document loaded from a .ssp or a .ocda.db has to arrive at the same
     value from what the artefact carries, and this is where that is done.

     Every one of these is an *index over properties the artefact already
     holds*, not a re-derivation of something it does not:

       imagelist            the .image records' filenames
       segnames_lv4         .anchor of each body heading at level 4
       segnames_lv_0_to_4   .segment_epub of each heading at level 4 or less
       tag_associations     .anchor, .anchor_tag and .segment_* per object
       section_keys_sequenced  which sections are non-empty, plus the run's
                            own output flags

     That distinction is the line the .ssp skill draws: assembling an index
     is fine and cannot drift, because a wrong index would not survive the
     round trip that produced its inputs; recomputing dom_status or ancestors
     would be re-derivation and is exactly what ocda exists to remove. If a
     value is not in the file and cannot be indexed out of what is, it must
     be added to the artefact instead of reconstructed here.

     The counts come from the @doc_has block rather than being recounted, for
     the same reason.
+/
template spineDocHasFromAbstraction() {
  import std.algorithm : sort, uniq;
  import std.array;
  import std.conv : to;
  import std.json : JSONValue;
  import std.regex;
  import sisudoc.ocda.meta.metadoc_object_setter;
  import sisudoc.ocda.meta.rgx;
  mixin ObjectSetter;
  mixin spineRgxIn;
  /+ ↓ the sections, in the order a document holds them +/
  enum string[] doc_sections = [
    "head", "toc", "body", "endnotes",
    "glossary", "bibliography", "bookindex", "blurb", "tail",
  ];
  /+ ↓ the tail sections that become segments of their own, in the order
       after_doc_determine_segnames appends them +/
  enum string[] tail_sections = [
    "endnotes", "glossary", "bibliography", "bookindex", "blurb",
  ];
  private uint _count(string[string] _doc_has, string _key) {
    if (auto _v = _key in _doc_has) {
      try { return (*_v).to!uint; } catch (Exception ex) { return 0; }
    }
    return 0;
  }
  private bool _has_section(A)(A _abst, string _section) {
    if (auto _s = _section in _abst) { return (*_s).length > 1; }
    return false;
  }
  /+ ↓ which document sections each output format walks, and in what order.

       the same rule the parser applies: the three that are always there,
       then each tail section that the document actually has, then "tail"
       for the formats that close with one. It depends on the run as well as
       on the document, since only html and epub take the tail.
  +/
  string[][string] sectionKeysSequenced(A,O)(A _abst, O _opt_action) {
    string[][string] _keys = [
      "scroll": ["head", "toc", "body",],
      "seg":    ["head", "toc", "body",],
      "sql":    ["head", "body",],
      "latex":  ["head", "toc", "body",],
      "text":   ["head", "toc", "body",],
    ];
    if (_has_section(_abst, "endnotes")) {
      _keys["scroll"] ~= "endnotes";
      _keys["seg"]    ~= "endnotes";
      _keys["latex"]  ~= "endnotes";
      _keys["text"]   ~= "endnotes";
    }
    if (_has_section(_abst, "glossary")) {
      foreach (_k; ["scroll", "seg", "sql", "latex", "text"]) { _keys[_k] ~= "glossary"; }
    }
    if (_has_section(_abst, "bibliography")) {
      foreach (_k; ["scroll", "seg", "sql", "latex", "text"]) { _keys[_k] ~= "bibliography"; }
    }
    if (_has_section(_abst, "bookindex")) {
      foreach (_k; ["scroll", "seg", "sql", "latex", "text"]) { _keys[_k] ~= "bookindex"; }
    }
    if (_has_section(_abst, "blurb")) {
      foreach (_k; ["scroll", "seg", "sql", "latex", "text"]) { _keys[_k] ~= "blurb"; }
    }
    if (_opt_action.html_scroll || _opt_action.html_seg || _opt_action.epub) {
      _keys["scroll"] ~= "tail";
      _keys["seg"]    ~= "tail";
    }
    return _keys;
  }
  /+ ↓ the whole of it +/
  ST_DocHas docHasFromAbstraction(A,O)(
    A               _abst,
    string[string]  _doc_has_block,
    O               _opt_action,
  ) {
    static auto rgx = RgxI();
    ST_DocHas _out;
    _out.inline_links      = _count(_doc_has_block, "inline_links");
    _out.inline_notes_reg  = _count(_doc_has_block, "inline_notes_reg");
    _out.inline_notes_star = _count(_doc_has_block, "inline_notes_star");
    _out.codeblocks        = _count(_doc_has_block, "codeblocks");
    _out.tables            = _count(_doc_has_block, "tables");
    _out.blocks            = _count(_doc_has_block, "blocks");
    _out.groups            = _count(_doc_has_block, "groups");
    _out.poems             = _count(_doc_has_block, "poems");
    _out.quotes            = _count(_doc_has_block, "quotes");
    string[] _images;
    string[string][string] _tag_assoc;
    /+ ↓ segnames["html"] is seeded with the toc, which is a segment of its
         own before any level 4 heading opens one +/
    _out.segnames_lv4 ~= "toc";
    foreach (_section; doc_sections) {
      if (_section !in _abst) { continue; }
      /+ ↓ head is always walked; every other section only when it holds
           more than the placeholder object, which is the guard the parser
           puts on each of its section loops
      +/
      if (_section != "head" && !_has_section(_abst, _section)) { continue; }
      foreach (obj; _abst[_section]) {
        /+ ↓ the images this object names, as the abstraction recorded them +/
        foreach (_img; obj.metainfo.sha256.images) { _images ~= _img.fileName; }
        /+ ↓ every object: its html anchor is in a segment, and its epub
             segment anchor stands for itself +/
        if (obj.tags.anchor_tag_html.length > 0) {
          _tag_assoc[obj.tags.anchor_tag_html]["seg_lv4"] = obj.tags.in_segment_html;
        }
        if (obj.tags.segment_anchor_tag_epub.length > 0) {
          _tag_assoc[obj.tags.segment_anchor_tag_epub]["seg_lv1to4"]
            = obj.tags.segment_anchor_tag_epub;
        }
        /+ ↓ a heading's own anchor tags, and the anchors declared inline in
             an object's text, both point at the segment the object is in +/
        /+ ↓ a heading above level 4 opens no html segment of its own, so a
             cross reference to it has to land on the level 4 segment that
             follows it. .segment is exactly that (the segment a heading
             opens or falls into), where .segment_html_is is the segment an
             object sits in; epub segments go down to level 1, so there
             .segment_epub stands for the heading itself
        +/
        if (obj.tags.heading_lev_anchor_tag.length > 0
          && obj.tags.in_segment_html.length > 0
          && obj.metainfo.heading_lev_markup >= 4
        ) {
          _tag_assoc[obj.tags.heading_lev_anchor_tag]["seg_lv4"]
            = obj.tags.in_segment_html;
          if (obj.tags.segment_anchor_tag_epub.length > 0) {
            _tag_assoc[obj.tags.heading_lev_anchor_tag]["seg_lv1to4"]
              = obj.tags.segment_anchor_tag_epub;
          }
        }
        foreach (m; obj.text.matchAll(rgx.inline_link_anchor)) {
          string _a = m["anchor"].to!string;
          if (_a.length == 0 || _a in _tag_assoc) { continue; }
          _tag_assoc[_a]["seg_lv4"]    = obj.tags.html_segment_anchor_tag_is;
          _tag_assoc[_a]["seg_lv1to4"] = obj.tags.epub_segment_anchor_tag_is;
        }
        /+ ↓ the segment name lists, in document order +/
        if (obj.metainfo.is_a == "heading") {
          if (obj.metainfo.heading_lev_markup <= 4) {
            _out.segnames_lv_0_to_4 ~= obj.tags.segment_anchor_tag_epub;
          }
          if (_section == "body" && obj.metainfo.heading_lev_markup == 4) {
            _out.segnames_lv4 ~= obj.tags.anchor_tag_html;
          }
          /+ ↓ a heading above level 4 opens no html segment of its own, so
               a cross reference to it lands on the level 4 segment that
               follows. That is .segment_lv4_is, resolved in ocda where the
               finished sections are to hand and carried by the artefact, so
               there is nothing to work out here
          +/
          if (obj.tags.segment_lv4_is.length > 0) {
            /+ ↓ seg_lv4 only, and not through heading_lev_anchor_tag: that
                 names the level 4 heading itself, whose own entry says it
                 opens that segment rather than merely sits in it. epub
                 segments go down to level 1, so seg_lv1to4 is untouched
            +/
            foreach (_k; [obj.metainfo.identifier, obj.tags.anchor_tag_html]) {
              if (_k.length > 0) { _tag_assoc[_k]["seg_lv4"] = obj.tags.segment_lv4_is; }
            }
          }
        }
      }
    }
    foreach (_section; tail_sections) {
      if (_has_section(_abst, _section)) { _out.segnames_lv4 ~= _section; }
    }
    /+ ↓ the body's objects, in a pass of their own after every anchor above
         has been recorded, because the parser does it in that order and the
         order is what decides two of these entries.
         .
         each body object's identifier maps to the segment it is in. seg_lv4
         is set only where it is not already, an anchor of the same name
         having said it *names* a segment rather than sits in one; seg_lv1to4
         is set always. A heading's own anchor can be a bare number taken
         from its text ("2. Shorter Terms" gives the anchor "2"), which then
         collides with the ocn of an unrelated object, and this is the order
         that resolves the collision the way the parser resolves it: in
         free_culture, ocn 2 keeps seg_lv4 "them" from the anchor and takes
         seg_lv1to4 "_part_1" from itself.
    +/
    if (_has_section(_abst, "body")) {
      foreach (obj; _abst["body"]) {
        if (obj.metainfo.identifier.length == 0) { continue; }
        if (!((obj.metainfo.identifier in _tag_assoc)
          && ("seg_lv4" in _tag_assoc[obj.metainfo.identifier]))
        ) {
          _tag_assoc[obj.metainfo.identifier]["seg_lv4"]
            = obj.tags.html_segment_anchor_tag_is;
        }
        _tag_assoc[obj.metainfo.identifier]["seg_lv1to4"]
          = obj.tags.epub_segment_anchor_tag_is;
      }
    }
    _out.imagelist              = _images.sort.uniq.array;
    _out.tag_associations       = _tag_assoc;
    _out.section_keys_sequenced = sectionKeysSequenced(_abst, _opt_action);
    return _out;
  }
}
#+END_SRC

* org includes
** project version

#+NAME: spine_version
#+HEADER: :noweb yes
#+BEGIN_SRC emacs-lisp
<<./sisudoc_spine_version_info_and_doc_header_including_copyright_and_license.org:spine_project_version()>>
#+END_SRC

** year

#+NAME: year
#+HEADER: :noweb yes
#+BEGIN_SRC emacs-lisp
<<./sisudoc_spine_version_info_and_doc_header_including_copyright_and_license.org:year()>>
#+END_SRC

** document header including copyright & license

#+NAME: doc_header_including_copyright_and_license
#+HEADER: :noweb yes
#+BEGIN_SRC emacs-lisp
<<./sisudoc_spine_version_info_and_doc_header_including_copyright_and_license.org:spine_doc_header_including_copyright_and_license()>>
#+END_SRC

* __END__