|
|
#!/usr/bin/env node |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function extractReferences(content) { |
|
|
const references = { |
|
|
labels: new Set(), |
|
|
refs: new Set(), |
|
|
cites: new Set() |
|
|
}; |
|
|
|
|
|
|
|
|
const labelMatches = content.matchAll(/\\label\{([^}]+)\}/g); |
|
|
for (const match of labelMatches) { |
|
|
references.labels.add(match[1]); |
|
|
} |
|
|
|
|
|
|
|
|
const refMatches = content.matchAll(/\\ref\{([^}]+)\}/g); |
|
|
for (const match of refMatches) { |
|
|
references.refs.add(match[1]); |
|
|
} |
|
|
|
|
|
|
|
|
const citeMatches = content.matchAll(/\\cite[tp]?\{([^}]+)\}/g); |
|
|
for (const match of citeMatches) { |
|
|
|
|
|
const citations = match[1].split(',').map(cite => cite.trim()); |
|
|
citations.forEach(cite => references.cites.add(cite)); |
|
|
} |
|
|
|
|
|
return references; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function createCleanMapping(references) { |
|
|
const mapping = new Map(); |
|
|
|
|
|
|
|
|
const allIdentifiers = new Set([ |
|
|
...references.labels, |
|
|
...references.refs |
|
|
]); |
|
|
|
|
|
for (const id of allIdentifiers) { |
|
|
|
|
|
let cleanId = id |
|
|
.replace(/^(sec|section|ch|chapter|fig|figure|eq|equation|tab|table|lst|listing|app|appendix):/gi, '') |
|
|
.replace(/:/g, '-') |
|
|
.replace(/[^a-zA-Z0-9_-]/g, '-') |
|
|
.replace(/-+/g, '-') |
|
|
.replace(/^-|-$/g, ''); |
|
|
|
|
|
|
|
|
if (!cleanId) { |
|
|
cleanId = id.replace(/:/g, '-'); |
|
|
} |
|
|
|
|
|
mapping.set(id, cleanId); |
|
|
} |
|
|
|
|
|
return mapping; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function convertLabelsToAnchors(content, mapping) { |
|
|
let processedContent = content; |
|
|
let anchorsCreated = 0; |
|
|
|
|
|
|
|
|
for (const [original, clean] of mapping) { |
|
|
|
|
|
if (original.startsWith('eq:')) { |
|
|
continue; |
|
|
} |
|
|
|
|
|
const labelRegex = new RegExp(`\\\\label\\{${escapeRegex(original)}\\}`, 'g'); |
|
|
const labelMatches = processedContent.match(labelRegex); |
|
|
|
|
|
if (labelMatches) { |
|
|
|
|
|
processedContent = processedContent.replace(labelRegex, `\n\n<span id="${clean}" style="position: absolute;"></span>\n\n`); |
|
|
anchorsCreated += labelMatches.length; |
|
|
} |
|
|
} |
|
|
|
|
|
return { content: processedContent, anchorsCreated }; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function convertHighlightCommands(content) { |
|
|
let processedContent = content; |
|
|
let highlightsConverted = 0; |
|
|
|
|
|
|
|
|
processedContent = processedContent.replace(/\\highlight\{([^}]+)\}/g, (match, text) => { |
|
|
highlightsConverted++; |
|
|
return `<span class="highlight">${text}</span>`; |
|
|
}); |
|
|
|
|
|
return { content: processedContent, highlightsConverted }; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function applyMapping(content, mapping) { |
|
|
let cleanedContent = content; |
|
|
let changesCount = 0; |
|
|
|
|
|
|
|
|
const anchorResult = convertLabelsToAnchors(cleanedContent, mapping); |
|
|
cleanedContent = anchorResult.content; |
|
|
const anchorsCreated = anchorResult.anchorsCreated; |
|
|
|
|
|
|
|
|
const highlightResult = convertHighlightCommands(cleanedContent); |
|
|
cleanedContent = highlightResult.content; |
|
|
const highlightsConverted = highlightResult.highlightsConverted; |
|
|
|
|
|
|
|
|
for (const [original, clean] of mapping) { |
|
|
if (original !== clean) { |
|
|
|
|
|
const refRegex = new RegExp(`\\\\ref\\{${escapeRegex(original)}\\}`, 'g'); |
|
|
const refMatches = cleanedContent.match(refRegex); |
|
|
if (refMatches) { |
|
|
cleanedContent = cleanedContent.replace(refRegex, `\\ref{${clean}}`); |
|
|
changesCount += refMatches.length; |
|
|
} |
|
|
|
|
|
|
|
|
if (original.startsWith('eq:')) { |
|
|
const labelRegex = new RegExp(`\\\\label\\{${escapeRegex(original)}\\}`, 'g'); |
|
|
const labelMatches = cleanedContent.match(labelRegex); |
|
|
if (labelMatches) { |
|
|
cleanedContent = cleanedContent.replace(labelRegex, `\\label{${clean}}`); |
|
|
changesCount += labelMatches.length; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
return { |
|
|
content: cleanedContent, |
|
|
changesCount: changesCount + anchorsCreated, |
|
|
highlightsConverted: highlightsConverted |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function escapeRegex(string) { |
|
|
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function preprocessLatexReferences(latexContent) { |
|
|
console.log('π§ Preprocessing LaTeX references for MDX compatibility...'); |
|
|
|
|
|
|
|
|
const references = extractReferences(latexContent); |
|
|
|
|
|
console.log(` π Found: ${references.labels.size} labels, ${references.refs.size} refs`); |
|
|
|
|
|
|
|
|
const mapping = createCleanMapping(references); |
|
|
|
|
|
|
|
|
const result = applyMapping(latexContent, mapping); |
|
|
|
|
|
if (result.changesCount > 0) { |
|
|
console.log(` β
Processed ${result.changesCount} reference(s) and created anchor spans`); |
|
|
|
|
|
|
|
|
let exampleCount = 0; |
|
|
for (const [original, clean] of mapping) { |
|
|
if (original !== clean && exampleCount < 3) { |
|
|
console.log(` ${original} β ${clean} (span + refs)`); |
|
|
exampleCount++; |
|
|
} |
|
|
} |
|
|
if (mapping.size > 3) { |
|
|
console.log(` ... and ${mapping.size - 3} more anchor spans created`); |
|
|
} |
|
|
} else { |
|
|
console.log(' βΉοΈ No reference cleanup needed'); |
|
|
} |
|
|
|
|
|
if (result.highlightsConverted > 0) { |
|
|
console.log(` β¨ Converted ${result.highlightsConverted} \\highlight{} command(s) to <span class="highlight">`); |
|
|
} |
|
|
|
|
|
return { |
|
|
content: result.content, |
|
|
changesCount: result.changesCount, |
|
|
mapping: mapping, |
|
|
references: references |
|
|
}; |
|
|
} |
|
|
|