spec-up-t 1.2.9 → 1.3.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 (32) hide show
  1. package/.github/copilot-instructions.md +2 -1
  2. package/assets/compiled/body.js +5 -5
  3. package/assets/compiled/head.css +1 -0
  4. package/assets/css/counter.css +104 -0
  5. package/assets/js/addAnchorsToTerms.js +13 -5
  6. package/assets/js/collapse-definitions.js +0 -3
  7. package/assets/js/custom-elements.js +25 -27
  8. package/assets/js/fix-last-dd.js +6 -3
  9. package/assets/js/highlight-heading-plus-sibling-nodes.js +0 -1
  10. package/assets/js/highlight-heading-plus-sibling-nodes.test.js +120 -0
  11. package/assets/js/insert-trefs.js +32 -28
  12. package/config/asset-map.json +1 -0
  13. package/index.js +33 -227
  14. package/package.json +4 -2
  15. package/sonar-project.properties +7 -0
  16. package/src/collect-external-references.js +22 -11
  17. package/src/collect-external-references.test.js +153 -2
  18. package/src/collectExternalReferences/fetchTermsFromIndex.js +65 -110
  19. package/src/collectExternalReferences/processXTrefsData.js +9 -11
  20. package/src/create-docx.js +332 -0
  21. package/src/create-pdf.js +243 -122
  22. package/src/fix-markdown-files.js +31 -34
  23. package/src/html-dom-processor.js +290 -0
  24. package/src/init.js +3 -0
  25. package/src/install-from-boilerplate/boilerplate/.github/workflows/menu.yml +51 -36
  26. package/src/install-from-boilerplate/boilerplate/spec/example-markup-in-markdown.md +0 -1
  27. package/src/install-from-boilerplate/boilerplate/spec/terms-and-definitions-intro.md +1 -5
  28. package/src/install-from-boilerplate/config-scripts-keys.js +4 -4
  29. package/src/install-from-boilerplate/menu.sh +6 -6
  30. package/src/markdown-it-extensions.js +54 -33
  31. package/src/references.js +18 -6
  32. package/templates/template.html +2 -0
@@ -0,0 +1,332 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const { JSDOM } = require('jsdom');
4
+ const { Document, Packer, Paragraph, TextRun, HeadingLevel, TableOfContents, Table, TableRow, TableCell, WidthType, AlignmentType } = require('docx');
5
+
6
+ /**
7
+ * Creates DOCX metadata from config
8
+ */
9
+ function createDocxMetadata(config) {
10
+ return {
11
+ title: config.specs[0].title || 'Untitled Document',
12
+ subject: config.specs[0].description || '',
13
+ creator: config.specs[0].author || '',
14
+ keywords: config.specs[0].keywords || [],
15
+ description: config.specs[0].description || '',
16
+ lastModifiedBy: 'Spec-Up DOCX Generator',
17
+ revision: 1,
18
+ createdAt: new Date(),
19
+ modifiedAt: new Date()
20
+ };
21
+ }
22
+
23
+ /**
24
+ * Converts HTML heading to DOCX heading level
25
+ */
26
+ function getHeadingLevel(tagName) {
27
+ const levels = {
28
+ 'h1': HeadingLevel.HEADING_1,
29
+ 'h2': HeadingLevel.HEADING_2,
30
+ 'h3': HeadingLevel.HEADING_3,
31
+ 'h4': HeadingLevel.HEADING_4,
32
+ 'h5': HeadingLevel.HEADING_5,
33
+ 'h6': HeadingLevel.HEADING_6
34
+ };
35
+ return levels[tagName.toLowerCase()] || HeadingLevel.HEADING_1;
36
+ }
37
+
38
+ /**
39
+ * Processes HTML node and converts to DOCX paragraphs
40
+ */
41
+ function processNode(node, elements = []) {
42
+ if (node.nodeType === 3) { // Text node
43
+ const text = node.textContent.trim();
44
+ if (text) {
45
+ elements.push(new Paragraph({
46
+ children: [new TextRun(text)]
47
+ }));
48
+ }
49
+ return elements;
50
+ }
51
+
52
+ if (node.nodeType !== 1) return elements; // Skip non-element nodes
53
+
54
+ const tagName = node.tagName.toLowerCase();
55
+
56
+ switch (tagName) {
57
+ case 'h1':
58
+ case 'h2':
59
+ case 'h3':
60
+ case 'h4':
61
+ case 'h5':
62
+ case 'h6':
63
+ elements.push(new Paragraph({
64
+ text: node.textContent.trim(),
65
+ heading: getHeadingLevel(tagName)
66
+ }));
67
+ break;
68
+
69
+ case 'p':
70
+ const textRuns = [];
71
+ processInlineElements(node, textRuns);
72
+ if (textRuns.length > 0) {
73
+ elements.push(new Paragraph({
74
+ children: textRuns
75
+ }));
76
+ }
77
+ break;
78
+
79
+ case 'ul':
80
+ case 'ol':
81
+ const listItems = Array.from(node.children);
82
+ listItems.forEach(li => {
83
+ if (li.tagName.toLowerCase() === 'li') {
84
+ elements.push(new Paragraph({
85
+ text: li.textContent.trim(),
86
+ bullet: { level: 0 }
87
+ }));
88
+ }
89
+ });
90
+ break;
91
+
92
+ case 'table':
93
+ const tableRows = Array.from(node.querySelectorAll('tr'));
94
+ if (tableRows.length > 0) {
95
+ const docxRows = tableRows.map(row => {
96
+ const cells = Array.from(row.querySelectorAll('td, th'));
97
+ return new TableRow({
98
+ children: cells.map(cell => new TableCell({
99
+ children: [new Paragraph({
100
+ text: cell.textContent.trim()
101
+ })],
102
+ width: {
103
+ size: 100 / cells.length,
104
+ type: WidthType.PERCENTAGE
105
+ }
106
+ }))
107
+ });
108
+ });
109
+
110
+ elements.push(new Table({
111
+ rows: docxRows,
112
+ width: {
113
+ size: 100,
114
+ type: WidthType.PERCENTAGE
115
+ }
116
+ }));
117
+ }
118
+ break;
119
+
120
+ case 'blockquote':
121
+ elements.push(new Paragraph({
122
+ text: node.textContent.trim(),
123
+ indent: {
124
+ left: 720 // 0.5 inch in twips
125
+ }
126
+ }));
127
+ break;
128
+
129
+ case 'dl':
130
+ // Process definition lists
131
+ const dlItems = Array.from(node.children);
132
+ dlItems.forEach(item => {
133
+ if (item.tagName.toLowerCase() === 'dt') {
134
+ elements.push(new Paragraph({
135
+ children: [new TextRun({
136
+ text: item.textContent.trim(),
137
+ bold: true
138
+ })]
139
+ }));
140
+ } else if (item.tagName.toLowerCase() === 'dd') {
141
+ elements.push(new Paragraph({
142
+ text: item.textContent.trim(),
143
+ indent: {
144
+ left: 360 // 0.25 inch in twips
145
+ }
146
+ }));
147
+ }
148
+ });
149
+ break;
150
+
151
+ default:
152
+ // For other elements, process their children
153
+ Array.from(node.childNodes).forEach(child => {
154
+ processNode(child, elements);
155
+ });
156
+ break;
157
+ }
158
+
159
+ return elements;
160
+ }
161
+
162
+ /**
163
+ * Processes inline elements within paragraphs
164
+ */
165
+ function processInlineElements(node, textRuns) {
166
+ Array.from(node.childNodes).forEach(child => {
167
+ if (child.nodeType === 3) { // Text node
168
+ const text = child.textContent;
169
+ if (text.trim()) {
170
+ textRuns.push(new TextRun(text));
171
+ }
172
+ } else if (child.nodeType === 1) { // Element node
173
+ const tagName = child.tagName.toLowerCase();
174
+ const text = child.textContent.trim();
175
+
176
+ if (text) {
177
+ switch (tagName) {
178
+ case 'strong':
179
+ case 'b':
180
+ textRuns.push(new TextRun({
181
+ text: text,
182
+ bold: true
183
+ }));
184
+ break;
185
+ case 'em':
186
+ case 'i':
187
+ textRuns.push(new TextRun({
188
+ text: text,
189
+ italics: true
190
+ }));
191
+ break;
192
+ case 'code':
193
+ textRuns.push(new TextRun({
194
+ text: text,
195
+ font: 'Courier New'
196
+ }));
197
+ break;
198
+ case 'a':
199
+ textRuns.push(new TextRun({
200
+ text: text,
201
+ color: '0000FF',
202
+ underline: {}
203
+ }));
204
+ break;
205
+ default:
206
+ textRuns.push(new TextRun(text));
207
+ break;
208
+ }
209
+ }
210
+ }
211
+ });
212
+ }
213
+
214
+ /**
215
+ * Creates a title page
216
+ */
217
+ function createTitlePage(config) {
218
+ const elements = [];
219
+ const spec = config.specs[0];
220
+
221
+ if (spec.title) {
222
+ elements.push(new Paragraph({
223
+ text: spec.title,
224
+ heading: HeadingLevel.TITLE,
225
+ alignment: AlignmentType.CENTER
226
+ }));
227
+ }
228
+
229
+ if (spec.description) {
230
+ elements.push(new Paragraph({
231
+ text: spec.description,
232
+ alignment: AlignmentType.CENTER
233
+ }));
234
+ }
235
+
236
+ if (spec.author) {
237
+ elements.push(new Paragraph({
238
+ text: `Author: ${spec.author}`,
239
+ alignment: AlignmentType.CENTER
240
+ }));
241
+ }
242
+
243
+ // Add some spacing
244
+ elements.push(new Paragraph({ text: '' }));
245
+ elements.push(new Paragraph({ text: '' }));
246
+
247
+ return elements;
248
+ }
249
+
250
+ (async () => {
251
+ try {
252
+ console.log('Starting DOCX generation...');
253
+
254
+ // Read and parse the specs.json file
255
+ const config = fs.readJsonSync('specs.json');
256
+ const metadata = createDocxMetadata(config);
257
+
258
+ // Extract configuration details
259
+ const outputPath = config.specs[0].output_path;
260
+ const filePath = path.resolve(process.cwd(), outputPath, 'index.html');
261
+
262
+ // Check if HTML file exists
263
+ if (!fs.existsSync(filePath)) {
264
+ console.error(`❌ HTML file not found at ${filePath}`);
265
+ console.log('Please run "npm run render" first to generate the HTML file.');
266
+ return;
267
+ }
268
+
269
+ // Read and parse the HTML file
270
+ const htmlContent = fs.readFileSync(filePath, 'utf8');
271
+ const dom = new JSDOM(htmlContent);
272
+ const document = dom.window.document;
273
+
274
+ // Remove unnecessary elements
275
+ document.querySelectorAll('script, style, .d-print-none, [style*="display: none"]').forEach(el => el.remove());
276
+
277
+ // Start building the DOCX document
278
+ const docElements = [];
279
+
280
+ // Add title page
281
+ docElements.push(...createTitlePage(config));
282
+
283
+ // Add table of contents placeholder
284
+ docElements.push(new TableOfContents('Table of Contents', {
285
+ hyperlink: true,
286
+ headingStyleRange: '1-6'
287
+ }));
288
+
289
+ // Add page break after TOC
290
+ docElements.push(new Paragraph({
291
+ text: '',
292
+ pageBreakBefore: true
293
+ }));
294
+
295
+ // Process the main content
296
+ const mainContent = document.querySelector('main') || document.body;
297
+ processNode(mainContent, docElements);
298
+
299
+ // Create the DOCX document
300
+ const doc = new Document({
301
+ properties: {
302
+ title: metadata.title,
303
+ subject: metadata.subject,
304
+ creator: metadata.creator,
305
+ keywords: metadata.keywords.join(', '),
306
+ description: metadata.description,
307
+ lastModifiedBy: metadata.lastModifiedBy,
308
+ revision: metadata.revision,
309
+ createdAt: metadata.createdAt,
310
+ modifiedAt: metadata.modifiedAt
311
+ },
312
+ sections: [{
313
+ children: docElements
314
+ }]
315
+ });
316
+
317
+ // Generate the DOCX file
318
+ const buffer = await Packer.toBuffer(doc);
319
+ const docxPath = path.resolve(process.cwd(), 'docs/index.docx');
320
+
321
+ // Ensure docs directory exists
322
+ fs.ensureDirSync(path.dirname(docxPath));
323
+
324
+ // Write the DOCX file
325
+ fs.writeFileSync(docxPath, buffer);
326
+
327
+ console.log('✅ DOCX generated successfully! Find the DOCX file in the docs directory.');
328
+ } catch (error) {
329
+ console.error('❌ Error generating DOCX:', error);
330
+ process.exit(1);
331
+ }
332
+ })();