spec-up-t 1.3.1 → 1.4.0

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 (130) hide show
  1. package/.github/copilot-instructions.md +13 -0
  2. package/assets/compiled/body.js +17 -11
  3. package/assets/compiled/head.css +6 -4
  4. package/assets/css/collapse-definitions.css +0 -1
  5. package/assets/css/create-pdf.css +4 -2
  6. package/assets/css/create-term-filter.css +4 -4
  7. package/assets/css/definition-buttons-container.css +60 -0
  8. package/assets/css/insert-trefs.css +7 -0
  9. package/assets/css/sidebar-toc.css +2 -1
  10. package/assets/css/terms-and-definitions.css +73 -22
  11. package/assets/js/add-href-to-snapshot-link.js +16 -9
  12. package/assets/js/addAnchorsToTerms.js +1 -1
  13. package/assets/js/charts.js +10 -0
  14. package/assets/js/collapse-definitions.js +13 -2
  15. package/assets/js/collapse-meta-info.js +11 -9
  16. package/assets/js/definition-button-container-utils.js +82 -0
  17. package/assets/js/edit-term-buttons.js +77 -20
  18. package/assets/js/github-issues.js +35 -0
  19. package/assets/js/github-repo-info.js +144 -0
  20. package/assets/js/highlight-heading-plus-sibling-nodes.test.js +18 -0
  21. package/assets/js/insert-trefs.js +62 -13
  22. package/assets/js/mermaid-diagrams.js +11 -0
  23. package/assets/js/terminology-section-utility-container/README.md +107 -0
  24. package/assets/js/terminology-section-utility-container/create-alphabet-index.js +17 -0
  25. package/assets/js/{create-term-filter.js → terminology-section-utility-container/create-term-filter.js} +11 -44
  26. package/assets/js/terminology-section-utility-container/hide-show-utility-container.js +21 -0
  27. package/assets/js/terminology-section-utility-container/search.js +203 -0
  28. package/assets/js/terminology-section-utility-container.js +203 -0
  29. package/assets/js/tooltips.js +283 -0
  30. package/config/asset-map.json +24 -16
  31. package/index.js +57 -390
  32. package/package.json +5 -2
  33. package/src/add-remove-xref-source.js +20 -21
  34. package/src/collect-external-references.js +8 -337
  35. package/src/collect-external-references.test.js +440 -33
  36. package/src/configure.js +8 -109
  37. package/src/create-docx.js +7 -6
  38. package/src/create-pdf.js +15 -14
  39. package/src/freeze-spec-data.js +46 -0
  40. package/src/git-info.test.js +76 -0
  41. package/src/health-check/destination-gitignore-checker.js +5 -3
  42. package/src/health-check/external-specs-checker.js +5 -4
  43. package/src/health-check/specs-configuration-checker.js +2 -1
  44. package/src/health-check/term-references-checker.js +5 -3
  45. package/src/health-check/terms-intro-checker.js +2 -1
  46. package/src/health-check/tref-term-checker.js +8 -7
  47. package/src/health-check.js +8 -7
  48. package/src/init.js +3 -2
  49. package/src/install-from-boilerplate/add-gitignore-entries.js +3 -2
  50. package/src/install-from-boilerplate/add-scripts-keys.js +5 -4
  51. package/src/install-from-boilerplate/boilerplate/README.md +1 -1
  52. package/src/install-from-boilerplate/boilerplate/spec/example-markup-in-markdown.md +1 -1
  53. package/src/install-from-boilerplate/boilerplate/spec/spec-head.md +1 -1
  54. package/src/install-from-boilerplate/boilerplate/specs.json +2 -1
  55. package/src/install-from-boilerplate/config-scripts-keys.js +3 -3
  56. package/src/install-from-boilerplate/copy-boilerplate.js +2 -1
  57. package/src/install-from-boilerplate/copy-system-files.js +4 -3
  58. package/src/install-from-boilerplate/custom-update.js +12 -1
  59. package/src/install-from-boilerplate/help.txt +1 -1
  60. package/src/install-from-boilerplate/menu.sh +6 -6
  61. package/src/json-key-validator.js +17 -11
  62. package/src/markdown-it/README.md +207 -0
  63. package/src/markdown-it/definition-lists.js +397 -0
  64. package/src/markdown-it/index.js +83 -0
  65. package/src/markdown-it/link-enhancement.js +98 -0
  66. package/src/markdown-it/plugins.js +118 -0
  67. package/src/markdown-it/table-enhancement.js +97 -0
  68. package/src/markdown-it/template-tag-syntax.js +152 -0
  69. package/src/parsers/index.js +16 -0
  70. package/src/parsers/spec-parser.js +152 -0
  71. package/src/parsers/spec-parser.test.js +109 -0
  72. package/src/parsers/template-tag-parser.js +277 -0
  73. package/src/parsers/template-tag-parser.test.js +107 -0
  74. package/src/pipeline/configuration/configure-starterpack.js +200 -0
  75. package/src/{create-external-specs-list.js → pipeline/configuration/create-external-specs-list.js} +13 -12
  76. package/src/{create-term-index.js → pipeline/configuration/create-term-index.js} +19 -18
  77. package/src/{create-versions-index.js → pipeline/configuration/create-versions-index.js} +4 -3
  78. package/src/{insert-term-index.js → pipeline/configuration/insert-term-index.js} +2 -2
  79. package/src/pipeline/configuration/prepare-spec-configuration.js +70 -0
  80. package/src/pipeline/parsing/apply-markdown-it-extensions.js +35 -0
  81. package/src/pipeline/parsing/create-markdown-parser.js +94 -0
  82. package/src/pipeline/parsing/create-markdown-parser.test.js +49 -0
  83. package/src/{html-dom-processor.js → pipeline/postprocessing/definition-list-postprocessor.js} +69 -10
  84. package/src/{escape-handler.js → pipeline/preprocessing/escape-processor.js} +3 -1
  85. package/src/{fix-markdown-files.js → pipeline/preprocessing/normalize-terminology-markdown.js} +41 -31
  86. package/src/pipeline/references/collect-external-references.js +307 -0
  87. package/src/pipeline/references/external-references-service.js +231 -0
  88. package/src/pipeline/references/fetch-terms-from-index.js +198 -0
  89. package/src/pipeline/references/match-term.js +34 -0
  90. package/src/{collectExternalReferences/matchTerm.test.js → pipeline/references/match-term.test.js} +8 -2
  91. package/src/pipeline/references/process-xtrefs-data.js +94 -0
  92. package/src/pipeline/references/xtref-utils.js +166 -0
  93. package/src/pipeline/rendering/render-spec-document.js +146 -0
  94. package/src/pipeline/rendering/render-utils.js +154 -0
  95. package/src/utils/LOGGER.md +81 -0
  96. package/src/utils/{doesUrlExist.js → does-url-exist.js} +4 -3
  97. package/src/utils/fetch.js +5 -4
  98. package/src/utils/file-opener.js +3 -2
  99. package/src/utils/git-info.js +77 -0
  100. package/src/utils/logger.js +74 -0
  101. package/src/utils/regex-patterns.js +471 -0
  102. package/src/utils/regex-patterns.test.js +281 -0
  103. package/templates/template.html +56 -21
  104. package/assets/js/create-alphabet-index.js +0 -60
  105. package/assets/js/hide-show-utility-container.js +0 -16
  106. package/assets/js/index.js +0 -87
  107. package/assets/js/search.js +0 -365
  108. package/src/collectExternalReferences/fetchTermsFromIndex.js +0 -284
  109. package/src/collectExternalReferences/matchTerm.js +0 -32
  110. package/src/collectExternalReferences/processXTrefsData.js +0 -108
  111. package/src/freeze.js +0 -90
  112. package/src/markdown-it-extensions.js +0 -395
  113. package/src/references.js +0 -114
  114. /package/assets/css/{bootstrap.min.css → embedded-libraries/bootstrap.min.css} +0 -0
  115. /package/assets/css/{prism.css → embedded-libraries/prism.css} +0 -0
  116. /package/assets/css/{prism.dark.css → embedded-libraries/prism.dark.css} +0 -0
  117. /package/assets/css/{prism.default.css → embedded-libraries/prism.default.css} +0 -0
  118. /package/assets/js/{bootstrap.bundle.min.js → embedded-libraries/bootstrap.bundle.min.js} +0 -0
  119. /package/assets/js/{chart.js → embedded-libraries/chart.js} +0 -0
  120. /package/assets/js/{diff.min.js → embedded-libraries/diff.min.js} +0 -0
  121. /package/assets/js/{font-awesome.js → embedded-libraries/font-awesome.js} +0 -0
  122. /package/assets/js/{mermaid.js → embedded-libraries/mermaid.js} +0 -0
  123. /package/assets/js/{notyf.js → embedded-libraries/notyf.js} +0 -0
  124. /package/assets/js/{popper.js → embedded-libraries/popper.js} +0 -0
  125. /package/assets/js/{prism.dark.js → embedded-libraries/prism.dark.js} +0 -0
  126. /package/assets/js/{prism.default.js → embedded-libraries/prism.default.js} +0 -0
  127. /package/assets/js/{prism.js → embedded-libraries/prism.js} +0 -0
  128. /package/assets/js/{tippy.js → embedded-libraries/tippy.js} +0 -0
  129. /package/src/{escape-mechanism.js → pipeline/preprocessing/escape-placeholder-utils.js} +0 -0
  130. /package/src/utils/{isLineWithDefinition.js → is-line-with-definition.js} +0 -0
@@ -1,108 +0,0 @@
1
- const fs = require('fs');
2
- const { fetchTermsFromIndex, fetchAllTermsFromIndex } = require('./fetchTermsFromIndex.js');
3
- const { matchTerm } = require('./matchTerm.js');
4
- const { addPath, getPath, getAllPaths } = require('../../config/paths');
5
- const path = require('path');
6
-
7
- // Directory to store fetched data files
8
- const CACHE_DIR = getPath('githubcache');
9
-
10
- async function processXTrefsData(allXTrefs, GITHUB_API_TOKEN, outputPathJSON, outputPathJS, outputPathJSTimeStamped) {
11
- try {
12
- // Ensure the directory exists, so that we can store the fetched data
13
- if (!fs.existsSync(CACHE_DIR)) {
14
- fs.mkdirSync(CACHE_DIR, { recursive: true });
15
- }
16
-
17
- // Filter out incomplete xtrefs that don't have proper repository information
18
- allXTrefs.xtrefs = allXTrefs.xtrefs.filter(xtref => {
19
- if (!xtref.owner || !xtref.repo || !xtref.repoUrl) {
20
- console.log(`⚠️ Removing incomplete reference: ${xtref.externalSpec}, ${xtref.term}`);
21
- return false;
22
- }
23
- return true;
24
- });
25
-
26
- // Group xtrefs by repository to avoid multiple downloads of the same index.html
27
- const xrefsByRepo = allXTrefs.xtrefs.reduce((groups, xtref) => {
28
- const repoKey = `${xtref.owner}/${xtref.repo}`;
29
- if (!groups[repoKey]) {
30
- groups[repoKey] = {
31
- owner: xtref.owner,
32
- repo: xtref.repo,
33
- xtrefs: []
34
- };
35
- }
36
- groups[repoKey].xtrefs.push(xtref);
37
- return groups;
38
- }, {});
39
-
40
- console.log(`✅ Grouped ${allXTrefs.xtrefs.length} terms into ${Object.keys(xrefsByRepo).length} repositories`);
41
-
42
- // Process each repository once
43
- for (const repoKey of Object.keys(xrefsByRepo)) {
44
- const repoGroup = xrefsByRepo[repoKey];
45
- console.log(`Processing repository: ${repoKey} (${repoGroup.xtrefs.length} terms)`);
46
-
47
- // Get the GitHub Pages URL from the first xtref in this repo group
48
- const ghPageUrl = repoGroup.xtrefs[0]?.ghPageUrl;
49
-
50
- // First, fetch all terms from this repository
51
- const allTermsData = await fetchAllTermsFromIndex(
52
- GITHUB_API_TOKEN,
53
- repoGroup.owner,
54
- repoGroup.repo,
55
- {
56
- ghPageUrl: ghPageUrl // Pass the GitHub Pages URL
57
- }
58
- );
59
-
60
- if (!allTermsData) {
61
- console.log(`❌ Could not fetch terms from repository ${repoKey}`);
62
- // Mark all terms from this repo as not found
63
- repoGroup.xtrefs.forEach(xtref => {
64
- xtref.commitHash = "not found";
65
- xtref.content = "This term was not found in the external repository.";
66
- xtref.avatarUrl = null;
67
- });
68
- continue; // Skip to next repository
69
- }
70
-
71
- // Now process each term in this repository
72
- for (const xtref of repoGroup.xtrefs) {
73
- // Find the term in the pre-fetched data
74
- const foundTerm = allTermsData.terms.find(
75
- t => t.term.toLowerCase() === xtref.term.toLowerCase()
76
- );
77
-
78
- if (foundTerm) {
79
- xtref.commitHash = allTermsData.sha;
80
- xtref.content = foundTerm.definition;
81
- xtref.avatarUrl = allTermsData.avatarUrl;
82
- console.log(`✅ Match found for term: ${xtref.term} in ${xtref.externalSpec}`);
83
- } else {
84
- xtref.commitHash = "not found";
85
- xtref.content = "This term was not found in the external repository.";
86
- xtref.avatarUrl = null;
87
- console.log(`ℹ️ No match found for term: ${xtref.term} in ${xtref.externalSpec}`);
88
- }
89
- }
90
-
91
- console.log(`✅ Finished processing repository: ${repoKey}`);
92
- console.log("============================================\n\n");
93
- }
94
-
95
- const allXTrefsStr = JSON.stringify(allXTrefs, null, 2);
96
- fs.writeFileSync(outputPathJSON, allXTrefsStr, 'utf8');
97
- const stringReadyForFileWrite = `const allXTrefs = ${allXTrefsStr};`;
98
- fs.writeFileSync(outputPathJS, stringReadyForFileWrite, 'utf8');
99
- fs.writeFileSync(outputPathJSTimeStamped, stringReadyForFileWrite, 'utf8');
100
-
101
- // This will run index.js
102
- require('../../index.js')({ nowatch: true });
103
- } catch (error) {
104
- console.error("An error occurred:", error);
105
- }
106
- }
107
-
108
- module.exports.processXTrefsData = processXTrefsData;
package/src/freeze.js DELETED
@@ -1,90 +0,0 @@
1
- /**
2
- * @file freeze.js
3
- * @description This script reads the output path from a specs.json file, finds the highest versioned directory in the destination path, and copies the index.html file to a new versioned directory with an incremented version number.
4
- *
5
- * @requires fs-extra - Module for file system operations with additional features.
6
- * @requires path - Module for handling and transforming file paths.
7
- *
8
- * @example
9
- * // Assuming specs.json contains:
10
- * // {
11
- * // "specs": [
12
- * // {
13
- * // "output_path": "path/to/output"
14
- * // }
15
- * // ]
16
- * // }
17
- *
18
- * // And the directory structure is:
19
- * // path/to/output/versions/v1/index.html
20
- * // path/to/output/versions/v2/index.html
21
- *
22
- * // Running this script will create:
23
- * // path/to/output/versions/v3/index.html
24
- *
25
- * @version 1.0.0
26
- * @license MIT
27
- */
28
-
29
- const fs = require('fs-extra'); // Import the fs-extra module for file system operations
30
- const path = require('path'); // Import the path module for handling file paths
31
-
32
- // Read and parse the specs.json file
33
- const config = fs.readJsonSync('specs.json');
34
-
35
- // Extract the output_path from the specs.json file
36
- const outputPath = config.specs[0].output_path;
37
-
38
- // Define the source file path
39
- const sourceFile = path.join(outputPath, 'index.html');
40
-
41
- // Define the destination directory path
42
- const destDir = path.join(outputPath, 'versions');
43
-
44
- // Ensure the destination directory exists, create it if it doesn't
45
- if (!fs.existsSync(destDir)) {
46
- fs.mkdirSync(destDir, { recursive: true });
47
- }
48
-
49
- // Get all directories in the destination directory
50
- const dirs = fs.readdirSync(destDir).filter(file => fs.statSync(path.join(destDir, file)).isDirectory());
51
-
52
- // Initialize the highest version number to 0
53
- let highestVersion = 0;
54
-
55
- // Define the pattern to match versioned directories
56
- const versionPattern = /^v(\d+)$/;
57
-
58
- // Iterate over each directory in the destination directory
59
- dirs.forEach(dir => {
60
- // Check if the directory matches the version pattern
61
- const match = dir.match(versionPattern);
62
- if (match) {
63
- // Extract the version number from the directory name
64
- const version = parseInt(match[1], 10);
65
- // Update the highest version number if the latest version is higher
66
- if (version > highestVersion) {
67
- highestVersion = version;
68
- }
69
- }
70
- });
71
-
72
- // Calculate the new version number
73
- const newVersion = highestVersion + 1;
74
-
75
- // Define the new version directory path
76
- const newVersionDir = path.join(destDir, `v${newVersion}`);
77
-
78
- // Ensure the new version directory exists, create it if it doesn't
79
- if (!fs.existsSync(newVersionDir)) {
80
- fs.mkdirSync(newVersionDir, { recursive: true });
81
- }
82
-
83
- // Define the destination file path within the new version directory
84
- const destFile = path.join(newVersionDir, 'index.html');
85
-
86
- // Copy the source file to the destination file
87
- fs.copyFileSync(sourceFile, destFile);
88
-
89
- // Log a message indicating the file has been copied
90
- console.log(`✅ Created a freezed specification version in ${destFile}`);
@@ -1,395 +0,0 @@
1
- 'use strict';
2
-
3
- const { ESCAPED_PLACEHOLDER } = require('./escape-handler');
4
-
5
- /**
6
- * Configuration for custom template syntax [[example]] used throughout the markdown parsing
7
- * These constants define how template markers are identified and processed
8
- */
9
- const levels = 2; // Number of bracket characters used for template markers
10
- const openString = '['.repeat(levels); // Opening delimiter for template markers, e.g., '[['
11
- const closeString = ']'.repeat(levels); // Closing delimiter for template markers, e.g., ']]'
12
- // Regular expression to extract template type and arguments from content between delimiters
13
- // Captures: 1st group = template type (e.g., "ref", "tref"), 2nd group = optional arguments
14
- const contentRegex = /\s*([^\s\[\]:]+):?\s*([^\]\n]+)?/i;
15
-
16
- module.exports = function (md, templates = {}) {
17
-
18
- // Add table renderer to apply Bootstrap classes to all tables by default
19
- const originalTableRender = md.renderer.rules.table_open || function (tokens, idx, options, env, self) {
20
- return self.renderToken(tokens, idx, options);
21
- };
22
-
23
- // Save the original table_close renderer
24
- const originalTableCloseRender = md.renderer.rules.table_close || function (tokens, idx, options, env, self) {
25
- return self.renderToken(tokens, idx, options);
26
- };
27
-
28
- // Override table_open to add both the classes and open a wrapper div
29
- md.renderer.rules.table_open = function (tokens, idx, options, env, self) {
30
- // Add Bootstrap classes to the table element
31
- const token = tokens[idx];
32
- const classIndex = token.attrIndex('class');
33
- const tableClasses = 'table table-striped table-bordered table-hover';
34
-
35
- if (classIndex < 0) {
36
- token.attrPush(['class', tableClasses]);
37
- } else {
38
- // If a class attribute already exists, append our classes
39
- const existingClasses = token.attrs[classIndex][1];
40
- // Only add classes that aren't already present
41
- const classesToAdd = tableClasses
42
- .split(' ')
43
- .filter(cls => !existingClasses.includes(cls))
44
- .join(' ');
45
-
46
- if (classesToAdd) {
47
- token.attrs[classIndex][1] = existingClasses + ' ' + classesToAdd;
48
- }
49
- }
50
-
51
- // Add the responsive wrapper div before the table
52
- return '<div class="table-responsive-md">' + originalTableRender(tokens, idx, options, env, self);
53
- };
54
-
55
- // Override table_close to close the wrapper div
56
- md.renderer.rules.table_close = function (tokens, idx, options, env, self) {
57
- // Close the table and add the closing div
58
- return originalTableCloseRender(tokens, idx, options, env, self) + '</div>';
59
- };
60
-
61
- /**
62
- * Custom template syntax rule for markdown-it
63
- * Processes template markers like [[template-type:arg1,arg2]] in markdown content
64
- * and converts them to tokens that can be processed by template renderers
65
- */
66
- md.inline.ruler.after('emphasis', 'templates', function templates_ruler(state, silent) {
67
- // Get the current parsing position
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
-
75
- // Check if we're at a template opening marker
76
- let prefix = state.src.slice(start, start + levels);
77
- if (prefix !== openString) return false;
78
- // Find the matching closing marker
79
- var indexOfClosingBrace = state.src.indexOf(closeString, start);
80
-
81
- if (indexOfClosingBrace > 0) {
82
- // Extract the template content using regex
83
- let match = contentRegex.exec(state.src.slice(start + levels, indexOfClosingBrace));
84
- if (!match) return false;
85
-
86
- // Get template type and find a matching template handler
87
- let type = match[1];
88
- let template = templates.find(t => t.filter(type) && t);
89
- if (!template) return false;
90
-
91
- // Parse template arguments (comma-separated)
92
- let args = match[2] ? match[2].trim().split(/\s*,+\s*/) : [];
93
- // Create a template token to be processed during rendering
94
- let token = state.push('template', '', 0);
95
- token.content = match[0];
96
- token.info = { type, template, args };
97
-
98
- // If the template has a parse function, use it to preprocess the token
99
- if (template.parse) {
100
- token.content = template.parse(token, type, ...args) || token.content;
101
- }
102
-
103
- // Advance the parser position past the template
104
- state.pos = indexOfClosingBrace + levels;
105
- return true;
106
- }
107
-
108
- return false;
109
- });
110
-
111
- /**
112
- * Renderer for template tokens
113
- * Takes template tokens created during parsing and renders them using their associated template handler
114
- */
115
- md.renderer.rules.template = function (tokens, idx, options, env, renderer) {
116
- let token = tokens[idx];
117
- let template = token.info.template;
118
- if (template.render) {
119
- return template.render(token, token.info.type, ...token.info.args) || (openString + token.content + closeString);
120
- }
121
- return token.content;
122
- }
123
-
124
- /**
125
- * Regular expression to extract domains and path segments from URLs
126
- * Used to add path-related attributes to links for styling and behavior
127
- */
128
- let pathSegmentRegex = /(?:http[s]*:\/\/([^\/]*)|(?:\/([^\/?]*)))/g;
129
-
130
- /**
131
- * Custom link_open renderer that adds path attributes for styling and behavior
132
- * Extracts domain and path segments from href attributes and adds them as path-X attributes
133
- */
134
- md.renderer.rules.link_open = function (tokens, idx, options, env, renderer) {
135
- let token = tokens[idx];
136
- let attrs = token.attrs.reduce((str, attr) => {
137
- let name = attr[0];
138
- let value = attr[1];
139
- if (name === 'href') {
140
- let index = 0;
141
- value.replace(pathSegmentRegex, (m, domain, seg) => {
142
- str += `path-${index++}="${domain || seg}"`;
143
- });
144
- }
145
- return str += name + '="' + value + '" ';
146
- }, '');
147
- let anchor = `<a ${attrs}>`;
148
- // Special handling for auto-detected links (linkify)
149
- return token.markup === 'linkify' ? anchor + '<span>' : anchor;
150
- }
151
-
152
- md.renderer.rules.link_close = function (tokens, idx, options, env, renderer) {
153
- return tokens[idx].markup === 'linkify' ? '</span></a>' : '</a>';
154
- }
155
-
156
- // Add class to <dl> and the last <dd> in each series after a <dt>
157
- const originalRender = md.renderer.rules.dl_open || function (tokens, idx, options, env, self) {
158
- return self.renderToken(tokens, idx, options);
159
- };
160
-
161
- // Variable to keep track of whether the class has been added to the first <dl> after the target HTML
162
- let classAdded = false;
163
-
164
- /**
165
- * Helper function to locate a specific marker in the token stream
166
- * Used to identify the terminology section in the document
167
- *
168
- * @param {Array} tokens - The token array to search through
169
- * @param {String} targetHtml - The HTML string to look for in token content
170
- * @return {Number} The index of the token containing targetHtml, or -1 if not found
171
- */
172
- function findTargetIndex(tokens, targetHtml) {
173
- for (let i = 0; i < tokens.length; i++) {
174
- if (tokens[i].content && tokens[i].content.includes(targetHtml)) {
175
- return i;
176
- }
177
- }
178
- return -1;
179
- }
180
-
181
- /**
182
- * Helper function to identify and mark empty definition term elements
183
- * Empty dt elements cause rendering and styling issues, so we mark them for special handling
184
- *
185
- * @param {Array} tokens - The token array to process
186
- * @param {Number} startIdx - The index in the token array to start processing from
187
- */
188
- function markEmptyDtElements(tokens, startIdx) {
189
- for (let i = startIdx; i < tokens.length; i++) {
190
- if (tokens[i].type === 'dl_close') {
191
- break; // Stop when we reach the end of this definition list
192
- }
193
-
194
- // An empty dt element is one where dt_open is immediately followed by dt_close
195
- // with no content in between
196
- if (tokens[i].type === 'dt_open' &&
197
- i + 1 < tokens.length &&
198
- tokens[i + 1].type === 'dt_close') {
199
- // Mark both opening and closing tokens so they can be skipped during rendering
200
- tokens[i].isEmpty = true;
201
- tokens[i + 1].isEmpty = true;
202
- }
203
- }
204
- }
205
-
206
-
207
- /**
208
- * Helper function to process definition description elements
209
- * Identifies and marks the last dd element in each dt/dd group for special styling
210
- *
211
- * @param {Array} tokens - The token array to process
212
- * @param {Number} startIdx - The index in the token array to start processing from
213
- */
214
- function processLastDdElements(tokens, startIdx) {
215
- let lastDdIndex = -1; // Tracks the most recent dd_open token
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) {
228
- for (let i = startIdx; i < tokens.length; i++) {
229
- if (tokens[i].type === 'dl_close') {
230
- break; // Stop when we reach the end of this definition list
231
- }
232
- if (isDtRef(tokens[i])) {
233
- return true;
234
- }
235
- if (isHtmlRef(tokens[i])) {
236
- return true;
237
- }
238
- if (isInlineRef(tokens[i])) {
239
- return true;
240
- }
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:');
258
- }
259
-
260
- /**
261
- * Custom renderer for definition list opening tags
262
- * Handles special styling for terminology sections and processes definition terms and descriptions
263
- * This function was refactored to reduce cognitive complexity by extracting helper functions
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
- *
269
- * @param {Array} tokens - The token array being processed
270
- * @param {Number} idx - The index of the current token
271
- * @param {Object} options - Rendering options
272
- * @param {Object} env - Environment variables
273
- * @param {Object} self - Reference to the renderer
274
- * @return {String} The rendered HTML output
275
- */
276
- md.renderer.rules.dl_open = function (tokens, idx, options, env, self) {
277
- const targetHtml = 'terminology-section-start';
278
- let targetIndex = findTargetIndex(tokens, targetHtml);
279
-
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) {
293
- tokens[idx].attrPush(['class', 'terms-and-definitions-list']);
294
- classAdded = true;
295
- }
296
-
297
- // First pass - mark empty dt elements
298
- markEmptyDtElements(tokens, idx + 1);
299
-
300
- // Second pass - process last dd elements
301
- processLastDdElements(tokens, idx + 1);
302
-
303
- return originalRender(tokens, idx, options, env, self);
304
- };
305
-
306
- /**
307
- * Helper function to determine if a definition term is transcluded from another source
308
- * Transcluded terms require special styling and handling
309
- *
310
- * @param {Array} tokens - The token array to process
311
- * @param {Number} dtOpenIndex - The index of the dt_open token to check
312
- * @return {Boolean} True if the term is transcluded, false otherwise
313
- */
314
- function isTermTranscluded(tokens, dtOpenIndex) {
315
- for (let i = dtOpenIndex + 1; i < tokens.length; i++) {
316
- if (tokens[i].type === 'dt_close') {
317
- break; // Only examine tokens within this definition term
318
- }
319
-
320
- // Look for inline content that contains template tokens of type 'tref'
321
- // These are transcluded term references
322
- if (tokens[i].type === 'inline' && tokens[i].children) {
323
- for (let child of tokens[i].children) {
324
- if (child.type === 'template' &&
325
- child.info &&
326
- child.info.type === 'tref') {
327
- return true;
328
- }
329
- }
330
- }
331
- }
332
- return false;
333
- }
334
-
335
- // Override the rendering of dt elements to properly handle transcluded terms
336
- const originalDtRender = md.renderer.rules.dt_open || function (tokens, idx, options, env, self) {
337
- return self.renderToken(tokens, idx, options);
338
- };
339
-
340
- /**
341
- * Custom renderer for definition term opening tags
342
- * Handles special cases like empty terms and transcluded terms
343
- *
344
- * @param {Array} tokens - The token array being processed
345
- * @param {Number} idx - The index of the current token
346
- * @param {Object} options - Rendering options
347
- * @param {Object} env - Environment variables
348
- * @param {Object} self - Reference to the renderer
349
- * @return {String} The rendered HTML output or empty string for skipped elements
350
- */
351
- md.renderer.rules.dt_open = function (tokens, idx, options, env, self) {
352
- // Skip rendering empty dt elements that were marked during preprocessing
353
- if (tokens[idx].isEmpty) {
354
- return '';
355
- }
356
-
357
- // Check if this dt is part of a transcluded term and add appropriate class
358
- if (isTermTranscluded(tokens, idx)) {
359
- const classIndex = tokens[idx].attrIndex('class');
360
- if (classIndex < 0) {
361
- tokens[idx].attrPush(['class', 'transcluded-xref-term']);
362
- } else {
363
- tokens[idx].attrs[classIndex][1] += ' transcluded-xref-term';
364
- }
365
- }
366
-
367
- return originalDtRender(tokens, idx, options, env, self);
368
- };
369
-
370
- // Similarly override dt_close to skip empty dts
371
- const originalDtCloseRender = md.renderer.rules.dt_close || function (tokens, idx, options, env, self) {
372
- return self.renderToken(tokens, idx, options);
373
- };
374
-
375
- /**
376
- * Custom renderer for definition term closing tags
377
- * Ensures empty terms are not rendered in the final output
378
- *
379
- * @param {Array} tokens - The token array being processed
380
- * @param {Number} idx - The index of the current token
381
- * @param {Object} options - Rendering options
382
- * @param {Object} env - Environment variables
383
- * @param {Object} self - Reference to the renderer
384
- * @return {String} The rendered HTML output or empty string for skipped elements
385
- */
386
- md.renderer.rules.dt_close = function (tokens, idx, options, env, self) {
387
- // Skip rendering the closing </dt> tag for empty dt elements
388
- // This completes the fix for empty dt elements by ensuring neither
389
- // the opening nor closing tags are rendered
390
- if (tokens[idx].isEmpty) {
391
- return '';
392
- }
393
- return originalDtCloseRender(tokens, idx, options, env, self);
394
- };
395
- };