164 lines
4.9 KiB
JavaScript
164 lines
4.9 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Generate output HTML from template + data for space-route documents.
|
|
*
|
|
* Usage: node generate.mjs <docId>
|
|
* Example: node generate.mjs space-route-1
|
|
*
|
|
* Reads: docs/<docId>.template.html
|
|
* Reads: docs/<docId>.data.json (optional)
|
|
* Writes: docs/<docId>.output.html
|
|
*
|
|
* data.json format (from space-route editor):
|
|
* {
|
|
* pages: [{
|
|
* page: 1,
|
|
* objects: [{ nodeId, type, src, left, top, w, h, rotate, flipH }],
|
|
* nodes: [{ nodeId, left, top }]
|
|
* }]
|
|
* }
|
|
*/
|
|
|
|
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
import { join, dirname } from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
import { postGenerate } from '../../../src/scripts/post-generate.mjs';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const docsDir = join(__dirname, '..', 'docs');
|
|
|
|
const docId = process.argv[2];
|
|
if (!docId) {
|
|
console.error('Usage: node generate.mjs <docId>');
|
|
process.exit(1);
|
|
}
|
|
|
|
const templatePath = join(docsDir, `${docId}.template.html`);
|
|
const dataPath = join(docsDir, `${docId}.data.json`);
|
|
const outputPath = join(docsDir, `${docId}.output.html`);
|
|
|
|
if (!existsSync(templatePath)) {
|
|
console.error(`Template not found: ${templatePath}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
let html = readFileSync(templatePath, 'utf-8');
|
|
|
|
if (existsSync(dataPath)) {
|
|
const data = JSON.parse(readFileSync(dataPath, 'utf-8'));
|
|
html = applyData(html, data);
|
|
console.log(`Applied data from ${data.pages?.length || 0} pages`);
|
|
}
|
|
|
|
writeFileSync(outputPath, html);
|
|
console.log(`Generated: ${outputPath}`);
|
|
await postGenerate(outputPath);
|
|
|
|
function applyData(html, data) {
|
|
if (!data.pages) return html;
|
|
|
|
// Split by page divs
|
|
const pageRegex = /<div class="w-\[210mm\] h-\[297mm\]"/g;
|
|
const starts = [];
|
|
let match;
|
|
while ((match = pageRegex.exec(html)) !== null) {
|
|
starts.push(match.index);
|
|
}
|
|
|
|
if (starts.length === 0) return html;
|
|
|
|
for (let i = data.pages.length - 1; i >= 0; i--) {
|
|
const pageData = data.pages[i];
|
|
const pageNum = pageData.page || (i + 1);
|
|
const pageIdx = pageNum - 1;
|
|
|
|
if (pageIdx >= starts.length) continue;
|
|
|
|
const pageStart = starts[pageIdx];
|
|
const pageEnd = pageIdx + 1 < starts.length ? starts[pageIdx + 1] : html.length;
|
|
let pageHtml = html.slice(pageStart, pageEnd);
|
|
|
|
// Apply object positions
|
|
if (pageData.objects) {
|
|
for (const obj of pageData.objects) {
|
|
pageHtml = applyObject(pageHtml, obj);
|
|
}
|
|
}
|
|
|
|
// Apply node positions and update edges
|
|
if (pageData.nodes) {
|
|
for (const node of pageData.nodes) {
|
|
pageHtml = applyNode(pageHtml, node);
|
|
}
|
|
}
|
|
|
|
html = html.slice(0, pageStart) + pageHtml + html.slice(pageEnd);
|
|
}
|
|
|
|
return html;
|
|
}
|
|
|
|
function applyObject(pageHtml, obj) {
|
|
// Match: <img class="space-obj" data-node-id="N" data-type="..." style="..."
|
|
const nodeId = obj.nodeId;
|
|
const regex = new RegExp(
|
|
`(<img class="space-obj"[^>]*data-node-id="${nodeId}"[^>]*style=")([^"]*)(")`
|
|
);
|
|
|
|
return pageHtml.replace(regex, (match, prefix, oldStyle, suffix) => {
|
|
const parts = [];
|
|
parts.push(`left: ${obj.left}`);
|
|
parts.push(`top: ${obj.top}`);
|
|
if (obj.w) parts.push(`width: ${obj.w}`);
|
|
if (obj.h) parts.push(`height: ${obj.h}`);
|
|
|
|
const transforms = [];
|
|
if (obj.rotate) transforms.push(`rotate(${obj.rotate}deg)`);
|
|
if (obj.flipH) transforms.push('scaleX(-1)');
|
|
if (transforms.length) parts.push(`transform: ${transforms.join(' ')}`);
|
|
|
|
// Preserve margin if present in old style
|
|
const marginMatch = oldStyle.match(/margin[^;]+;/);
|
|
if (marginMatch) parts.push(marginMatch[0].replace(/;$/, ''));
|
|
|
|
return prefix + parts.join('; ') + ';' + suffix;
|
|
});
|
|
}
|
|
|
|
function applyNode(pageHtml, node) {
|
|
// Update node div position
|
|
const nodeRegex = new RegExp(
|
|
`(<div[^>]*data-node-id="${node.nodeId}"[^>]*style=")([^"]*)(")`
|
|
);
|
|
|
|
pageHtml = pageHtml.replace(nodeRegex, (match, prefix, oldStyle, suffix) => {
|
|
const newStyle = oldStyle
|
|
.replace(/left:\s*[^;]+/, `left: ${node.left}`)
|
|
.replace(/top:\s*[^;]+/, `top: ${node.top}`);
|
|
return prefix + newStyle + suffix;
|
|
});
|
|
|
|
// Update edges connected to this node
|
|
const leftMm = parseFloat(node.left);
|
|
const topMm = parseFloat(node.top);
|
|
|
|
// Edges where this node is first: <line data-edge="N-M" x1="..." y1="..."
|
|
const edgeRegex1 = new RegExp(
|
|
`(<line[^>]*data-edge="${node.nodeId}-\\d+"[^>]*)(x1=")[^"]*("\\s*y1=")[^"]*(")`,'g'
|
|
);
|
|
pageHtml = pageHtml.replace(edgeRegex1, (match, prefix, x1p, y1p, suffix) => {
|
|
return `${prefix}${x1p}${leftMm}mm${y1p}${topMm}mm${suffix}`;
|
|
});
|
|
|
|
// Edges where this node is second: <line data-edge="M-N" ... x2="..." y2="..."
|
|
const edgeRegex2 = new RegExp(
|
|
`(<line[^>]*data-edge="\\d+-${node.nodeId}"[^>]*)(x2=")[^"]*("\\s*y2=")[^"]*(")`,'g'
|
|
);
|
|
pageHtml = pageHtml.replace(edgeRegex2, (match, prefix, x2p, y2p, suffix) => {
|
|
return `${prefix}${x2p}${leftMm}mm${y2p}${topMm}mm${suffix}`;
|
|
});
|
|
|
|
return pageHtml;
|
|
}
|