PageSourceSearch

https://romchip.org/plugins/themes/streetcat/js/jats-render.js?v=3.5.0.3

js romchip.org collected 2026-10-02 07:18:46 UTC 12,584 bytes, 395 lines download raw bytes

1/**
2 * JATS XML to HTML renderer for the Streetcat theme.
3 *
4 * Converts JATS (Journal Article Tag Suite) XML into readable HTML,
5 * handling sections, paragraphs, figures, tables, footnotes, block quotes,
6 * lists, and inline formatting.
7 */
8(function () {
9  'use strict';
10
11  // ---- Inline element conversion ----
12
13  function convertInline(node) {
14    var html = '';
15    if (!node || !node.childNodes) return html;
16
17    for (var i = 0; i < node.childNodes.length; i++) {
18      var child = node.childNodes[i];
19
20      if (child.nodeType === 3) { // text node
21        html += child.textContent;
22        continue;
23      }
24
25      if (child.nodeType !== 1) continue; // skip non-element nodes
26
27      var tag = child.nodeName.toLowerCase();
28
29      switch (tag) {
30        case 'italic':
31          html += '<em>' + convertInline(child) + '</em>';
32          break;
33        case 'bold':
34          html += '<strong>' + convertInline(child) + '</strong>';
35          break;
36        case 'sup':
37          html += '<sup>' + convertInline(child) + '</sup>';
38          break;
39        case 'sub':
40          html += '<sub>' + convertInline(child) + '</sub>';
41          break;
42        case 'underline':
43          html += '<u>' + convertInline(child) + '</u>';
44          break;
45        case 'monospace':
46          html += '<code>' + convertInline(child) + '</code>';
47          break;
48        case 'sc':
49          html += '<span style="font-variant:small-caps">' + convertInline(child) + '</span>';
50          break;
51        case 'break':
52          html += '<br>';
53          break;
54        case 'xref':
55          var refType = child.getAttribute('ref-type');
56          var rid = child.getAttribute('rid');
57          if (refType === 'fn' && rid) {
58            html += '<a class="fn-ref" href="#' + rid + '" id="' + rid + '-ref"><sup>' + convertInline(child) + '</sup></a>';
59          } else if (refType === 'bibr' && rid) {
60            html += '<a class="bibr-ref" href="#' + rid + '">' + convertInline(child) + '</a>';
61          } else if (refType === 'fig' && rid) {
62            html += '<a class="fig-ref" href="#' + rid + '">' + convertInline(child) + '</a>';
63          } else if (refType === 'table' && rid) {
64            html += '<a class="table-ref" href="#' + rid + '">' + convertInline(child) + '</a>';
65          } else {
66            html += convertInline(child);
67          }
68          break;
69        case 'ext-link':
70          var href = child.getAttribute('xlink:href') || child.getAttribute('href') || '#';
71          html += '<a href="' + href + '" target="_blank" rel="noopener">' + convertInline(child) + '</a>';
72          break;
73        case 'uri':
74          var uri = child.getAttribute('xlink:href') || child.textContent;
75          html += '<a href="' + uri + '" target="_blank" rel="noopener">' + convertInline(child) + '</a>';
76          break;
77        case 'named-content':
78          html += '<span class="named-content">' + convertInline(child) + '</span>';
79          break;
80        case 'inline-formula':
81        case 'inline-graphic':
82          var src = child.getAttribute('xlink:href');
83          if (src) {
84            html += '<img class="inline-graphic" src="' + src + '" alt="">';
85          } else {
86            html += convertInline(child);
87          }
88          break;
89        case 'disp-formula':
90          html += '<div class="disp-formula">' + convertInline(child) + '</div>';
91          break;
92        default:
93          html += convertInline(child);
94          break;
95      }
96    }
97    return html;
98  }
99
100  // ---- Block element conversion ----
101
102  function convertFigure(fig) {
103    var id = fig.getAttribute('id') || '';
104    var html = '<figure class="article-figure" id="' + id + '">';
105
106    var graphic = fig.querySelector('graphic');
107    if (graphic) {
108      var src = graphic.getAttribute('xlink:href') || '';
109      html += '<img src="' + src + '" alt="">';
110    }
111
112    var label = fig.querySelector(':scope > label');
113    var caption = fig.querySelector('caption');
114    if (label || caption) {
115      html += '<figcaption>';
116      if (label) {
117        html += '<strong>' + label.textContent + '.</strong> ';
118      }
119      if (caption) {
120        var captionPs = caption.querySelectorAll('p');
121        captionPs.forEach(function (p) {
122          html += convertInline(p);
123        });
124        if (captionPs.length === 0) {
125          var captionTitle = caption.querySelector('title');
126          if (captionTitle && captionTitle.textContent.trim()) {
127            html += convertInline(captionTitle);
128          }
129        }
130      }
131      html += '</figcaption>';
132    }
133
134    html += '</figure>';
135    return html;
136  }
137
138  function convertTable(tableWrap) {
139    var id = tableWrap.getAttribute('id') || '';
140    var html = '<div class="article-table" id="' + id + '">';
141
142    var label = tableWrap.querySelector(':scope > label');
143    var caption = tableWrap.querySelector(':scope > caption');
144    if (label || caption) {
145      html += '<div class="table-caption">';
146      if (label) html += '<strong>' + label.textContent + '.</strong> ';
147      if (caption) {
148        var ps = caption.querySelectorAll('p');
149        ps.forEach(function (p) { html += convertInline(p); });
150        if (ps.length === 0) html += convertInline(caption);
151      }
152      html += '</div>';
153    }
154
155    var table = tableWrap.querySelector('table');
156    if (table) {
157      html += '<div class="table-responsive"><table class="table">';
158      // Serialize the table children, converting any JATS inline markup
159      var sections = table.querySelectorAll('thead, tbody, tfoot');
160      if (sections.length > 0) {
161        sections.forEach(function (section) {
162          html += '<' + section.nodeName.toLowerCase() + '>';
163          section.querySelectorAll('tr').forEach(function (tr) {
164            html += convertTableRow(tr);
165          });
166          html += '</' + section.nodeName.toLowerCase() + '>';
167        });
168      } else {
169        table.querySelectorAll('tr').forEach(function (tr) {
170          html += convertTableRow(tr);
171        });
172      }
173      html += '</table></div>';
174    }
175
176    html += '</div>';
177    return html;
178  }
179
180  function convertTableRow(tr) {
181    var html = '<tr>';
182    for (var i = 0; i < tr.children.length; i++) {
183      var cell = tr.children[i];
184      var cellTag = cell.nodeName.toLowerCase() === 'th' ? 'th' : 'td';
185      var attrs = '';
186      if (cell.getAttribute('colspan')) attrs += ' colspan="' + cell.getAttribute('colspan') + '"';
187      if (cell.getAttribute('rowspan')) attrs += ' rowspan="' + cell.getAttribute('rowspan') + '"';
188      html += '<' + cellTag + attrs + '>' + convertInline(cell) + '</' + cellTag + '>';
189    }
190    html += '</tr>';
191    return html;
192  }
193
194  function convertList(list) {
195    var listType = list.getAttribute('list-type');
196    var tag = (listType === 'order') ? 'ol' : 'ul';
197    var html = '<' + tag + '>';
198    var items = list.querySelectorAll(':scope > list-item');
199    items.forEach(function (item) {
200      html += '<li>' + convertBlockChildren(item) + '</li>';
201    });
202    html += '</' + tag + '>';
203    return html;
204  }
205
206  function convertDispQuote(quote) {
207    var html = '<blockquote class="disp-quote">';
208    html += convertBlockChildren(quote);
209    var attrib = quote.querySelector('attrib');
210    if (attrib) {
211      html += '<footer>' + convertInline(attrib) + '</footer>';
212    }
213    html += '</blockquote>';
214    return html;
215  }
216
217  function convertSection(sec, depth) {
218    depth = depth || 1;
219    var id = sec.getAttribute('id') || '';
220    var html = '<section id="' + id + '">';
221
222    var title = sec.querySelector(':scope > title');
223    if (title && title.textContent.trim()) {
224      var headingLevel = Math.min(depth + 1, 6); // h2 to h6
225      html += '<h' + headingLevel + '>' + convertInline(title) + '</h' + headingLevel + '>';
226    }
227
228    html += convertBlockChildren(sec, depth + 1);
229    html += '</section>';
230    return html;
231  }
232
233  // Process direct block-level children of a container
234  function convertBlockChildren(container, depth) {
235    depth = depth || 1;
236    var html = '';
237    for (var i = 0; i < container.children.length; i++) {
238      var child = container.children[i];
239      var tag = child.nodeName.toLowerCase();
240
241      switch (tag) {
242        case 'title': // already handled by section
243          break;
244        case 'label': // already handled by parent
245          break;
246        case 'p':
247          html += '<p>' + convertInline(child) + '</p>';
248          break;
249        case 'sec':
250          html += convertSection(child, depth);
251          break;
252        case 'fig':
253          html += convertFigure(child);
254          break;
255        case 'table-wrap':
256          html += convertTable(child);
257          break;
258        case 'list':
259          html += convertList(child);
260          break;
261        case 'disp-quote':
262          html += convertDispQuote(child);
263          break;
264        case 'verse-group':
265          html += '<div class="verse">';
266          child.querySelectorAll('verse-line').forEach(function (line) {
267            html += convertInline(line) + '<br>';
268          });
269          html += '</div>';
270          break;
271        case 'media':
272          var mediaSrc = child.getAttribute('xlink:href');
273          if (mediaSrc) {
274            html += '<div class="article-media"><a href="' + mediaSrc + '" target="_blank">' + (child.textContent || 'Media') + '</a></div>';
275          }
276          break;
277        case 'disp-formula':
278          html += '<div class="disp-formula">' + convertInline(child) + '</div>';
279          break;
280        default:
281          // For unrecognized block elements, try inline conversion
282          var inner = convertInline(child);
283          if (inner.trim()) html += inner;
284          break;
285      }
286    }
287    return html;
288  }
289
290  // ---- Footnotes ----
291
292  function convertFootnotes(xml) {
293    var fnGroup = xml.querySelector('fn-group');
294    if (!fnGroup) {
295      // Also check inside <back>
296      var back = xml.querySelector('back');
297      if (back) fnGroup = back.querySelector('fn-group');
298    }
299    if (!fnGroup) return '';
300
301    var fns = fnGroup.querySelectorAll('fn');
302    if (fns.length === 0) return '';
303
304    var html = '<section class="article-footnotes"><h2>Notes</h2><ol class="footnotes-list">';
305    fns.forEach(function (fn) {
306      var id = fn.getAttribute('id') || '';
307      var label = fn.querySelector(':scope > label');
308      var content = '';
309      fn.querySelectorAll('p').forEach(function (p) {
310        content += convertInline(p);
311      });
312      html += '<li id="' + id + '">';
313      if (id) {
314        html += '<a class="fn-back" href="#' + id + '-ref" aria-label="Back to text">&uarr;</a> ';
315      }
316      html += content + '</li>';
317    });
318    html += '</ol></section>';
319    return html;
320  }
321
322  // ---- References ----
323
324  function convertReferences(xml) {
325    var refList = xml.querySelector('ref-list');
326    if (!refList) {
327      var back = xml.querySelector('back');
328      if (back) refList = back.querySelector('ref-list');
329    }
330    if (!refList) return '';
331
332    var refs = refList.querySelectorAll('ref');
333    if (refs.length === 0) return '';
334
335    var html = '<section class="article-references"><h2>References</h2><ol class="references-list">';
336    refs.forEach(function (ref) {
337      var id = ref.getAttribute('id') || '';
338      html += '<li id="' + id + '">' + convertInline(ref) + '</li>';
339    });
340    html += '</ol></section>';
341    return html;
342  }
343
344  // ---- Main entry point ----
345
346  function renderJatsXml(xml, container) {
347    var body = xml.querySelector('body');
348    if (!body) return;
349
350    var html = '';
351
352    // Convert body content
353    var topLevel = body.children;
354    for (var i = 0; i < topLevel.length; i++) {
355      var el = topLevel[i];
356      var tag = el.nodeName.toLowerCase();
357
358      switch (tag) {
359        case 'sec':
360          html += convertSection(el, 1);
361          break;
362        case 'p':
363          html += '<p>' + convertInline(el) + '</p>';
364          break;
365        case 'fig':
366          html += convertFigure(el);
367          break;
368        case 'table-wrap':
369          html += convertTable(el);
370          break;
371        case 'list':
372          html += convertList(el);
373          break;
374        case 'disp-quote':
375          html += convertDispQuote(el);
376          break;
377        default:
378          html += convertBlockChildren(el, 1);
379          break;
380      }
381    }
382
383    // Add footnotes
384    html += convertFootnotes(xml);
385
386    // Add references (if present in XML, not all articles have them)
387    html += convertReferences(xml);
388
389    container.innerHTML = html;
390  }
391
392  // Expose globally
393  window.renderJatsXml = renderJatsXml;
394
395})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.