spec-up-t 1.2.8 → 1.3.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/.github/copilot-instructions.md +3 -1
  2. package/assets/compiled/body.js +5 -4
  3. package/assets/compiled/head.css +1 -0
  4. package/assets/compiled/refs.json +1 -1
  5. package/assets/css/highlight-heading-plus-sibling-nodes.css +6 -0
  6. package/assets/css/index.css +9 -0
  7. package/assets/js/addAnchorsToTerms.js +13 -5
  8. package/assets/js/collapse-definitions.js +0 -6
  9. package/assets/js/fix-last-dd.js +6 -3
  10. package/assets/js/highlight-heading-plus-sibling-nodes.js +258 -0
  11. package/assets/js/insert-trefs.js +32 -28
  12. package/config/asset-map.json +2 -0
  13. package/gulpfile.js +8 -2
  14. package/index.js +45 -241
  15. package/package.json +2 -1
  16. package/sonar-project.properties +6 -0
  17. package/src/collect-external-references.js +22 -11
  18. package/src/collect-external-references.test.js +153 -2
  19. package/src/collectExternalReferences/fetchTermsFromIndex.js +65 -110
  20. package/src/collectExternalReferences/processXTrefsData.js +9 -11
  21. package/src/create-docx.js +332 -0
  22. package/src/create-pdf.js +243 -122
  23. package/src/escape-handler.js +67 -0
  24. package/src/fix-markdown-files.js +31 -34
  25. package/src/html-dom-processor.js +290 -0
  26. package/src/init.js +3 -0
  27. package/src/install-from-boilerplate/boilerplate/.github/workflows/menu.yml +4 -13
  28. package/src/install-from-boilerplate/boilerplate/spec/example-markup-in-markdown.md +0 -1
  29. package/src/install-from-boilerplate/boilerplate/spec/terms-and-definitions-intro.md +1 -5
  30. package/src/install-from-boilerplate/config-scripts-keys.js +4 -4
  31. package/src/install-from-boilerplate/menu.sh +6 -6
  32. package/src/markdown-it-extensions.js +60 -31
  33. package/src/references.js +18 -6
  34. package/templates/template.html +2 -0
  35. package/test-default-definitions.js +55 -0
  36. package/test-edge-cases.md +20 -0
  37. package/test-fix-markdown.js +11 -0
  38. package/test-no-def.md +22 -0
@@ -0,0 +1,290 @@
1
+ 'use strict';
2
+
3
+ const { JSDOM } = require('jsdom');
4
+
5
+ /**
6
+ * Sorts definition terms in HTML alphabetically (case-insensitive)
7
+ *
8
+ * @param {string} html - The HTML content to process
9
+ * @returns {string} - The HTML with sorted definition terms
10
+ */
11
+ function sortDefinitionTermsInHtml(html) {
12
+ const dom = new JSDOM(html);
13
+ const document = dom.window.document;
14
+
15
+ // Find the terms and definitions list
16
+ const dlElement = document.querySelector('.terms-and-definitions-list');
17
+ if (!dlElement) return html; // If not found, return the original HTML
18
+
19
+ // Collect all dt/dd pairs
20
+ const pairs = [];
21
+ let currentDt = null;
22
+ let currentDds = [];
23
+
24
+ // Process each child of the dl element
25
+ Array.from(dlElement.children).forEach(child => {
26
+ if (child.tagName === 'DT') {
27
+ // If we already have a dt, save the current pair
28
+ if (currentDt) {
29
+ pairs.push({
30
+ dt: currentDt,
31
+ dds: [...currentDds],
32
+ text: currentDt.textContent.trim().toLowerCase() // Use lowercase for sorting
33
+ });
34
+ currentDds = []; // Reset dds for the next dt
35
+ }
36
+ currentDt = child;
37
+ } else if (child.tagName === 'DD' && currentDt) {
38
+ currentDds.push(child);
39
+ }
40
+ });
41
+
42
+ // Add the last pair if exists
43
+ if (currentDt) {
44
+ pairs.push({
45
+ dt: currentDt,
46
+ dds: [...currentDds],
47
+ text: currentDt.textContent.trim().toLowerCase()
48
+ });
49
+ }
50
+
51
+ // Sort pairs case-insensitively
52
+ pairs.sort((a, b) => a.text.localeCompare(b.text));
53
+
54
+ // Clear the dl element
55
+ while (dlElement.firstChild) {
56
+ dlElement.removeChild(dlElement.firstChild);
57
+ }
58
+
59
+ // Re-append elements in sorted order
60
+ pairs.forEach(pair => {
61
+ dlElement.appendChild(pair.dt);
62
+ pair.dds.forEach(dd => {
63
+ dlElement.appendChild(dd);
64
+ });
65
+ });
66
+
67
+ // Return the modified HTML
68
+ return dom.serialize();
69
+ }
70
+
71
+ /**
72
+ * Fixes broken definition list (dl) structures in the HTML output.
73
+ * Specifically, it addresses the issue where transcluded terms (tref tags) break
74
+ * out of the definition list, creating separate lists instead of a continuous one.
75
+ *
76
+ * The strategy:
77
+ * 1. Find all definition lists (dl elements) in the document
78
+ * 2. Use the dl with class 'terms-and-definitions-list' as the main/target list
79
+ * 3. Process each subsequent node after the this main dl:
80
+ * - If another dl is found, merge all its children into the main dl
81
+ * - If a standalone dt is found, move it and its associated dd elements into the main dl
82
+ * - Remove any empty paragraphs that might be breaking the list continuity
83
+ *
84
+ * This ensures all terms appear in one continuous definition list,
85
+ * regardless of how they were originally rendered in the markdown.
86
+ *
87
+ * @param {string} html - The HTML content to fix
88
+ * @returns {string} - The fixed HTML content with merged definition lists
89
+ */
90
+ function fixDefinitionListStructure(html) {
91
+ const dom = new JSDOM(html);
92
+ const document = dom.window.document;
93
+
94
+ // Find all dl elements first
95
+ const allDls = Array.from(document.querySelectorAll('dl'));
96
+
97
+ // Then filter to find the one with the terms-and-definitions-list class
98
+ const dlElements = allDls.filter(dl => {
99
+ return dl?.classList?.contains('terms-and-definitions-list');
100
+ });
101
+
102
+ // Find any transcluded term dt elements anywhere in the document
103
+ const transcludedTerms = document.querySelectorAll('dt.transcluded-xref-term');
104
+
105
+ let mainDl = null;
106
+
107
+ // If we have an existing dl with the terms-and-definitions-list class, use it
108
+ if (dlElements.length > 0) {
109
+ mainDl = dlElements[0]; // Use the first one
110
+ }
111
+ // If we have transcluded terms but no main dl, we need to create one
112
+ else if (transcludedTerms.length > 0) {
113
+ // Create a new dl element with the right class
114
+ mainDl = document.createElement('dl');
115
+ mainDl.className = 'terms-and-definitions-list';
116
+
117
+ // Look for the marker
118
+ const marker = document.getElementById('terminology-section-start');
119
+
120
+ if (marker) {
121
+ // Insert the new dl right after the marker
122
+ if (marker.nextSibling) {
123
+ marker.parentNode.insertBefore(mainDl, marker.nextSibling);
124
+ } else {
125
+ marker.parentNode.appendChild(mainDl);
126
+ }
127
+ } else {
128
+ // Fallback to the original approach if marker isn't found
129
+ const firstTerm = transcludedTerms[0];
130
+ const insertPoint = firstTerm.parentNode;
131
+ insertPoint.parentNode.insertBefore(mainDl, insertPoint);
132
+ }
133
+ }
134
+
135
+ // Safety check - if we still don't have a mainDl, exit early to avoid null reference errors
136
+ if (!mainDl) {
137
+ return html; // Return the original HTML without modifications
138
+ }
139
+
140
+ /**
141
+ * Helper function to collect dt/dd pairs from a node
142
+ * @param {Node} startNode - The node to start collecting from
143
+ * @returns {Array} - Array of elements that are part of the definition group
144
+ */
145
+ function collectDtDdGroup(startNode) {
146
+ const group = [];
147
+ let currentNode = startNode;
148
+
149
+ // Collect the dt and all following dd elements
150
+ while (currentNode && (currentNode.tagName === 'DT' || currentNode.tagName === 'DD')) {
151
+ group.push(currentNode);
152
+ currentNode = currentNode.nextSibling;
153
+
154
+ // Skip text nodes (whitespace) between elements
155
+ while (currentNode && currentNode.nodeType === 3 && !currentNode.textContent.trim()) {
156
+ currentNode = currentNode.nextSibling;
157
+ }
158
+ }
159
+
160
+ return group;
161
+ }
162
+
163
+ // Process all transcluded terms and move them with their dd elements
164
+ transcludedTerms.forEach(dt => {
165
+ // Check if this dt is not already inside our main dl
166
+ if (dt.parentElement !== mainDl) {
167
+ // Collect the dt and its associated dd elements
168
+ const group = collectDtDdGroup(dt);
169
+
170
+ // Move all elements in the group to the main dl
171
+ group.forEach(element => {
172
+ if (element.parentNode) {
173
+ const elementClone = element.cloneNode(true);
174
+ mainDl.appendChild(elementClone);
175
+ element.parentNode.removeChild(element);
176
+ }
177
+ });
178
+ }
179
+ });
180
+
181
+ // Remove any empty dt elements that may exist
182
+ const emptyDts = mainDl.querySelectorAll('dt:empty');
183
+ emptyDts.forEach(emptyDt => {
184
+ emptyDt.parentNode.removeChild(emptyDt);
185
+ });
186
+
187
+ // Process all subsequent content after the main dl
188
+ let currentNode = mainDl.nextSibling;
189
+
190
+ // Process all subsequent content
191
+ while (currentNode) {
192
+ // Save the next node before potentially modifying the DOM
193
+ const nextNode = currentNode.nextSibling;
194
+
195
+ // Handle different node types
196
+ if (currentNode.nodeType === 1) { // 1 = Element node
197
+ if (currentNode.tagName === 'DL') {
198
+ // Check if this is a reference list (contains dt elements with id="ref:...")
199
+ const hasRefIds = currentNode.innerHTML.includes('id="ref:') ||
200
+ currentNode.classList.contains('reference-list');
201
+
202
+ if (!hasRefIds) {
203
+ // Only move non-reference definition lists - move all its children to the main dl
204
+ while (currentNode.firstChild) {
205
+ mainDl.appendChild(currentNode.firstChild);
206
+ }
207
+ // Remove the now-empty dl element
208
+ currentNode.parentNode.removeChild(currentNode);
209
+ }
210
+ // If it's a reference list, leave it alone
211
+ }
212
+ else if (currentNode.tagName === 'DT') {
213
+ // Check if this dt has a ref: id (spec reference)
214
+ const hasRefId = currentNode.id?.startsWith('ref:');
215
+
216
+ if (!hasRefId) {
217
+ // Collect the dt and its associated dd elements
218
+ const group = collectDtDdGroup(currentNode);
219
+
220
+ // Move all elements in the group to the main dl
221
+ group.forEach(element => {
222
+ if (element.parentNode) {
223
+ const elementClone = element.cloneNode(true);
224
+ mainDl.appendChild(elementClone);
225
+ element.parentNode.removeChild(element);
226
+ }
227
+ });
228
+
229
+ // Skip the nodes we just processed
230
+ let skipNodes = group.length - 1; // -1 because currentNode will be advanced anyway
231
+ while (skipNodes > 0 && nextNode) {
232
+ const nodeToSkip = nextNode;
233
+ currentNode = nextNode;
234
+ nextNode = nextNode.nextSibling;
235
+ skipNodes--;
236
+ }
237
+ }
238
+ // If it's a spec reference dt, leave it alone
239
+ }
240
+ else if (currentNode.tagName === 'DD') {
241
+ // Handle orphaned dd elements - move them to the main dl if they don't belong to a reference
242
+ const dtBefore = currentNode.previousSibling;
243
+ let hasAssociatedDt = false;
244
+
245
+ // Check if there's a dt before this dd (walking backwards through siblings)
246
+ let checkNode = currentNode.previousSibling;
247
+ while (checkNode) {
248
+ // Skip text nodes
249
+ if (checkNode.nodeType === 3 && !checkNode.textContent.trim()) {
250
+ checkNode = checkNode.previousSibling;
251
+ continue;
252
+ }
253
+
254
+ if (checkNode.tagName === 'DT') {
255
+ hasAssociatedDt = true;
256
+ break;
257
+ } else if (checkNode.tagName !== 'DD') {
258
+ // Found a non-dt, non-dd element, so this dd is orphaned
259
+ break;
260
+ }
261
+
262
+ checkNode = checkNode.previousSibling;
263
+ }
264
+
265
+ // If this dd doesn't have an associated dt in the same context, move it to main dl
266
+ if (!hasAssociatedDt) {
267
+ const ddClone = currentNode.cloneNode(true);
268
+ mainDl.appendChild(ddClone);
269
+ currentNode.parentNode.removeChild(currentNode);
270
+ }
271
+ }
272
+ else if (currentNode.tagName === 'P' &&
273
+ (!currentNode.textContent || currentNode.textContent.trim() === '')) {
274
+ // Remove empty paragraphs - these break the list structure
275
+ currentNode.parentNode.removeChild(currentNode);
276
+ }
277
+ }
278
+
279
+ // Move to the next node we saved earlier
280
+ currentNode = nextNode;
281
+ }
282
+
283
+ // Return the fixed HTML
284
+ return dom.serialize();
285
+ }
286
+
287
+ module.exports = {
288
+ sortDefinitionTermsInHtml,
289
+ fixDefinitionListStructure
290
+ };
package/src/init.js CHANGED
@@ -5,6 +5,9 @@ const initFlagPath = path.join(outputDir, 'init.flag');
5
5
 
6
6
  async function initialize() {
7
7
  try {
8
+ // Ensure the .cache directory exists
9
+ await fs.ensureDir(outputDir);
10
+
8
11
  // Check if the init script has already run
9
12
  if (await fs.pathExists(initFlagPath)) {
10
13
  return;
@@ -12,8 +12,7 @@ on:
12
12
  # - Edit specification
13
13
  - Render specification
14
14
  # - Develop specification
15
- - Collect external references (cached)
16
- - Collect external references (no cache)
15
+ - Collect external references
17
16
  - Convert to PDF
18
17
  - Freeze specification
19
18
  # - List references
@@ -74,20 +73,12 @@ jobs:
74
73
  # "Develop specification")
75
74
  # node -e "require('spec-up-t')({ dev: true })"
76
75
  # ;;
77
- "Collect external references (cached)")
78
- node --no-warnings -e "require('spec-up-t/src/collect-external-references.js').collectExternalReferences({cache: true})"
76
+ "Collect external references")
77
+ node --no-warnings -e "require('spec-up-t/src/collect-external-references.js').collectExternalReferences({ pat: process.env.MY_PAT })"
79
78
  git config --global user.email "actions@github.com"
80
79
  git config --global user.name "GitHub Actions"
81
80
  git add .
82
- git commit -m "Collect external references (cached)" || echo "No changes to commit"
83
- git push https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git HEAD:main
84
- ;;
85
- "Collect external references (no cache)")
86
- node --no-warnings -e "require('spec-up-t/src/collect-external-references.js').collectExternalReferences({cache: false, pat: process.env.MY_PAT})"
87
- git config --global user.email "actions@github.com"
88
- git config --global user.name "GitHub Actions"
89
- git add .
90
- git commit -m "Collect external references (no cache)" || echo "No changes to commit"
81
+ git commit -m "Collect external references" || echo "No changes to commit"
91
82
  git push https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git HEAD:main
92
83
  ;;
93
84
  "Convert to PDF")
@@ -159,7 +159,6 @@ You can even insert content within more complex blocks, like the JSON object bel
159
159
  | ♙ | ♙ | ♙ | ♙ | | ♙ | ♙ | ♙ |
160
160
  | ♖ | ♘ | ♗ | ♕ | ♔ | | | ♖ |
161
161
 
162
-
163
162
  ### Sequence Diagrams
164
163
 
165
164
  <pre>
@@ -1,5 +1 @@
1
- # Terms and Definitions Intro
2
-
3
- ## Demo terms, definitions and external definitions
4
-
5
- A demo of terms and definitions, and references to external definitions.
1
+ [//]: # (This file, named “terms-and-definitions-intro.md” is mandatory and should not be deleted.)
@@ -2,9 +2,9 @@ const configScriptsKeys = {
2
2
  "edit": "node -e \"require('spec-up-t')()\"",
3
3
  "render": "node --no-warnings -e \"require('spec-up-t/index.js')({ nowatch: true })\"",
4
4
  "dev": "node -e \"require('spec-up-t')({ dev: true })\"",
5
- "collectExternalReferencesCache": "node --no-warnings -e \"require('spec-up-t/src/collect-external-references.js').collectExternalReferences({cache: true})\"",
6
- "collectExternalReferencesNoCache": "node --no-warnings -e \"require('spec-up-t/src/collect-external-references.js').collectExternalReferences({cache: false})\"",
5
+ "collectExternalReferences": "node --no-warnings -e \"require('spec-up-t/src/collect-external-references.js').collectExternalReferences()\"",
7
6
  "topdf": "node -e \"require('spec-up-t/src/create-pdf.js')\"",
7
+ "todocx": "node -e \"require('spec-up-t/src/create-docx.js')\"",
8
8
  "freeze": "node -e \"require('spec-up-t/src/freeze.js')\"",
9
9
  "references": "node -e \"require('spec-up-t/src/references.js')\"",
10
10
  "help": "cat ./node_modules/spec-up-t/src/install-from-boilerplate/help.txt",
@@ -20,9 +20,9 @@ const configOverwriteScriptsKeys = {
20
20
  "edit": true,
21
21
  "render": true,
22
22
  "dev": true,
23
- "collectExternalReferencesCache": true,
24
- "collectExternalReferencesNoCache": true,
23
+ "collectExternalReferences": true,
25
24
  "topdf": true,
25
+ "todocx": true,
26
26
  "freeze": true,
27
27
  "references": true,
28
28
  "help": true,
@@ -6,8 +6,8 @@ function handle_choice() {
6
6
  "Add content" "do_add_content"
7
7
  "Render specification" "do_render"
8
8
  "Export to PDF" "do_topdf"
9
- "Collect external references (cache, faster)" "collect_external_references_cache"
10
- "Collect external references (no cache, slower)" "collect_external_references_no_cache"
9
+ "Export to DOCX" "do_todocx"
10
+ "Collect external references" "collect_external_references"
11
11
  "Add, remove or view xref source" "do_add_remove_xref_source"
12
12
  "Configure" "do_configure"
13
13
  "Run health check" "do_health_check"
@@ -47,8 +47,8 @@ function display_intro() {
47
47
  [0] Add content
48
48
  [1] Render specification
49
49
  [2] Export to PDF
50
- [3] Collect external references (cache, faster)
51
- [4] Collect external references (no cache, slower)
50
+ [3] Export to DOCX
51
+ [4] Collect external references
52
52
  [5] Add, remove or view xref source
53
53
  [6] Configure
54
54
  [7] Run health check
@@ -74,8 +74,8 @@ function do_add_content() {
74
74
 
75
75
  function do_render() { clear; npm run render; }
76
76
  function do_topdf() { clear; npm run topdf; }
77
- function collect_external_references_cache() { clear; npm run collectExternalReferencesCache; }
78
- function collect_external_references_no_cache() { clear; npm run collectExternalReferencesNoCache; }
77
+ function do_todocx() { clear; npm run todocx; }
78
+ function collect_external_references() { clear; npm run collectExternalReferences; }
79
79
  function do_add_remove_xref_source() { clear; npm run addremovexrefsource; }
80
80
  function do_configure() { clear; npm run configure; }
81
81
  function do_health_check() { clear; npm run healthCheck; }
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const { ESCAPED_PLACEHOLDER } = require('./escape-handler');
4
+
3
5
  /**
4
6
  * Configuration for custom template syntax [[example]] used throughout the markdown parsing
5
7
  * These constants define how template markers are identified and processed
@@ -64,6 +66,12 @@ module.exports = function (md, templates = {}) {
64
66
  md.inline.ruler.after('emphasis', 'templates', function templates_ruler(state, silent) {
65
67
  // Get the current parsing position
66
68
  var start = state.pos;
69
+
70
+ // Check if we're at an escaped placeholder - if so, skip processing
71
+ if (state.src.slice(start, start + ESCAPED_PLACEHOLDER.length) === ESCAPED_PLACEHOLDER) {
72
+ return false;
73
+ }
74
+
67
75
  // Check if we're at a template opening marker
68
76
  let prefix = state.src.slice(start, start + levels);
69
77
  if (prefix !== openString) return false;
@@ -195,24 +203,6 @@ module.exports = function (md, templates = {}) {
195
203
  }
196
204
  }
197
205
 
198
- /**
199
- * Helper function to add a 'last-dd' class to a dd token
200
- * This enables special styling for the last definition description in a group
201
- *
202
- * @param {Array} tokens - The token array containing the dd token
203
- * @param {Number} ddIndex - The index of the dd_open token to modify
204
- */
205
- function addLastDdClass(tokens, ddIndex) {
206
- if (ddIndex === -1) return;
207
-
208
- const ddToken = tokens[ddIndex];
209
- const classIndex = ddToken.attrIndex('class');
210
- if (classIndex < 0) {
211
- ddToken.attrPush(['class', 'last-dd']);
212
- } else {
213
- ddToken.attrs[classIndex][1] += ' last-dd';
214
- }
215
- }
216
206
 
217
207
  /**
218
208
  * Helper function to process definition description elements
@@ -224,23 +214,47 @@ module.exports = function (md, templates = {}) {
224
214
  function processLastDdElements(tokens, startIdx) {
225
215
  let lastDdIndex = -1; // Tracks the most recent dd_open token
226
216
 
217
+ }
218
+
219
+ /**
220
+ * Helper function to check if a definition list contains spec references
221
+ * Spec references have dt elements with id attributes starting with "ref:"
222
+ *
223
+ * @param {Array} tokens - The token array to search through
224
+ * @param {Number} startIdx - The index to start searching from (after dl_open)
225
+ * @return {Boolean} True if the dl contains spec references, false otherwise
226
+ */
227
+ function containsSpecReferences(tokens, startIdx) {
227
228
  for (let i = startIdx; i < tokens.length; i++) {
228
229
  if (tokens[i].type === 'dl_close') {
229
- // Add class to the last <dd> before closing the entire <dl>
230
- addLastDdClass(tokens, lastDdIndex);
231
- break;
230
+ break; // Stop when we reach the end of this definition list
232
231
  }
233
-
234
- if (tokens[i].type === 'dt_open' && !tokens[i].isEmpty) {
235
- // When we find a non-empty dt, mark the previous dd as the last one in its group
236
- addLastDdClass(tokens, lastDdIndex);
237
- lastDdIndex = -1; // Reset for the next group
232
+ if (isDtRef(tokens[i])) {
233
+ return true;
238
234
  }
239
-
240
- if (tokens[i].type === 'dd_open') {
241
- lastDdIndex = i; // Track the most recently seen dd_open
235
+ if (isHtmlRef(tokens[i])) {
236
+ return true;
237
+ }
238
+ if (isInlineRef(tokens[i])) {
239
+ return true;
242
240
  }
243
241
  }
242
+ return false;
243
+ }
244
+
245
+ function isDtRef(token) {
246
+ if (token.type !== 'dt_open' || !token.attrs) return false;
247
+ return token.attrs.some(attr => attr[0] === 'id' && attr[1].startsWith('ref:'));
248
+ }
249
+
250
+ function isHtmlRef(token) {
251
+ if (token.type !== 'html_block' && token.type !== 'html_inline') return false;
252
+ return token.content && token.content.includes('id="ref:');
253
+ }
254
+
255
+ function isInlineRef(token) {
256
+ if (token.type !== 'inline') return false;
257
+ return token.content && token.content.includes('id="ref:');
244
258
  }
245
259
 
246
260
  /**
@@ -248,6 +262,10 @@ module.exports = function (md, templates = {}) {
248
262
  * Handles special styling for terminology sections and processes definition terms and descriptions
249
263
  * This function was refactored to reduce cognitive complexity by extracting helper functions
250
264
  *
265
+ * IMPORTANT FIX: This function now checks if a <dl> already has a class attribute OR contains
266
+ * spec references (dt elements with id="ref:...") before adding the 'terms-and-definitions-list'
267
+ * class. This prevents spec reference lists from being incorrectly classified as term definition lists.
268
+ *
251
269
  * @param {Array} tokens - The token array being processed
252
270
  * @param {Number} idx - The index of the current token
253
271
  * @param {Object} options - Rendering options
@@ -259,8 +277,19 @@ module.exports = function (md, templates = {}) {
259
277
  const targetHtml = 'terminology-section-start';
260
278
  let targetIndex = findTargetIndex(tokens, targetHtml);
261
279
 
262
- // Add class to the first <dl> only if it comes after the target HTML
263
- if (targetIndex !== -1 && idx > targetIndex && !classAdded) {
280
+ // Check if the dl already has a class attribute (e.g., reference-list)
281
+ const existingClassIndex = tokens[idx].attrIndex('class');
282
+ const hasExistingClass = existingClassIndex >= 0;
283
+
284
+ // Check if this dl contains spec references (dt elements with id="ref:...")
285
+ const hasSpecReferences = containsSpecReferences(tokens, idx + 1);
286
+
287
+ // Only add terms-and-definitions-list class if:
288
+ // 1. It comes after the target HTML
289
+ // 2. We haven't added the class yet
290
+ // 3. The dl doesn't already have a class (to avoid overriding reference-list)
291
+ // 4. The dl doesn't contain spec references
292
+ if (targetIndex !== -1 && idx > targetIndex && !classAdded && !hasExistingClass && !hasSpecReferences) {
264
293
  tokens[idx].attrPush(['class', 'terms-and-definitions-list']);
265
294
  classAdded = true;
266
295
  }
package/src/references.js CHANGED
@@ -47,25 +47,37 @@ async function fetchExternalSpecs(spec) {
47
47
  try {
48
48
  let results = await Promise.all(
49
49
  spec.external_specs.map(s => {
50
- const url = s["gh_page"]; // Access the "gh_page" URL directly
51
- return axios.get(url);
50
+ const url = s["gh_page"];
51
+ return axios.get(url).catch(error => ({ error, url }));
52
52
  })
53
53
  );
54
54
 
55
+ const failed = results.filter(r => r && r.error);
56
+ if (failed.length > 0) {
57
+ failed.forEach(f => {
58
+ const msg = f.error.response
59
+ ? `HTTP ${f.error.response.status} for ${f.url}`
60
+ : `Network error for ${f.url}: ${f.error.message}`;
61
+ console.error("❌ External spec fetch failed:", msg);
62
+ });
63
+ }
64
+
65
+ // Map results to objects keyed by external_spec if status is 200, otherwise null.
66
+ // This ensures only successful fetches are included, and failed ones are filtered out.
55
67
  results = results
56
68
  .map((r, index) =>
57
- r.status === 200
69
+ r && r.status === 200
58
70
  ? { [spec.external_specs[index].external_spec]: r.data }
59
71
  : null
60
72
  )
61
- .filter(r => r); // Remove null values
73
+ .filter(r => r); // Remove null values (failed fetches)
62
74
 
63
75
  return results.map(r =>
64
76
  createNewDLWithTerms(Object.keys(r)[0], Object.values(r)[0])
65
77
  );
66
78
  } catch (e) {
67
- console.log("❌ " + e);
68
- return []; // Return an empty array in case of errors
79
+ console.error("❌ Unexpected error in fetchExternalSpecs:", e);
80
+ return [];
69
81
  }
70
82
  }
71
83
 
@@ -257,6 +257,8 @@
257
257
  <hr>
258
258
  <p class="ps-3 pe-3">External specifications:</p>
259
259
  ${externalSpecsList}
260
+ <hr>
261
+ <p id="w3c-state">W3C Recommendation, ${currentDate}</p>
260
262
  </div>
261
263
  </div>
262
264
 
@@ -0,0 +1,55 @@
1
+ const { fixMarkdownFiles } = require('./src/fix-markdown-files');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ // Test cases for the default definition functionality
6
+ function testDefaultDefinitions() {
7
+ console.log('Running tests for default definition functionality...');
8
+
9
+ const testContent = [
10
+ '[[def: test-term, test-term]]',
11
+ '',
12
+ '[[tref: another-term, another-term]]',
13
+ '',
14
+ '[[def: term-with-def, term-with-def]]',
15
+ '',
16
+ '~ This term already has a definition',
17
+ '',
18
+ '[[tref: final-term, final-term]]'
19
+ ].join('\n');
20
+
21
+ // Write test content to a temp file
22
+ const tempFile = './temp-test.md';
23
+ fs.writeFileSync(tempFile, testContent, 'utf8');
24
+
25
+ // Process the file
26
+ fixMarkdownFiles('.');
27
+
28
+ // Read and verify results
29
+ const result = fs.readFileSync(tempFile, 'utf8');
30
+ console.log('Test result:');
31
+ console.log(result);
32
+
33
+ // Clean up
34
+ fs.unlinkSync(tempFile);
35
+
36
+ // Verify expected content
37
+ const lines = result.split('\n');
38
+ let hasDefaultDefs = 0;
39
+
40
+ for (let i = 0; i < lines.length; i++) {
41
+ if (lines[i] === '~ No local definition found.') {
42
+ hasDefaultDefs++;
43
+ }
44
+ }
45
+
46
+ console.log(`Found ${hasDefaultDefs} default definitions`);
47
+
48
+ if (hasDefaultDefs === 3) { // Should be 3: test-term, another-term, final-term
49
+ console.log('✅ Test passed: All terms without definitions got default definitions');
50
+ } else {
51
+ console.log('❌ Test failed: Expected 3 default definitions');
52
+ }
53
+ }
54
+
55
+ testDefaultDefinitions();
@@ -0,0 +1,20 @@
1
+ [[def: term1, term1]]
2
+
3
+ ~ No local definition found.
4
+
5
+ [[def: term2, term2]]
6
+
7
+ ~ This term has a definition
8
+
9
+ [[tref: term3, term3]]
10
+
11
+ ~ No local definition found.
12
+
13
+ [[def: term4, term4]]
14
+
15
+ ~ This term also has a definition
16
+ ~ With multiple lines
17
+
18
+ [[tref: term5, term5]]
19
+
20
+ ~ No local definition found.