Parser refactor
This commit is contained in:
@@ -54,11 +54,10 @@ fixtures.forEach(fixtureId => {
|
|||||||
metadata[key.trim()] = valueParts.join(':').trim();
|
metadata[key.trim()] = valueParts.join(':').trim();
|
||||||
});
|
});
|
||||||
|
|
||||||
const parsedIssues = parseIssuesFromHtmlTable(inputHtml);
|
|
||||||
|
|
||||||
// Extract type from folder name (e.g., "PAA-25.6.2-known" → "Known")
|
// Extract type from folder name (e.g., "PAA-25.6.2-known" → "Known")
|
||||||
const typeFromFolder = fixtureId.split('-').pop().toLowerCase();
|
const typeFromFolder = fixtureId.split('-').pop().toLowerCase();
|
||||||
const capitalizedType = typeFromFolder.charAt(0).toUpperCase() + typeFromFolder.slice(1);
|
const capitalizedType = typeFromFolder.charAt(0).toUpperCase() + typeFromFolder.slice(1);
|
||||||
|
const parsedIssues = parseIssuesFromHtmlTable(inputHtml, { type: capitalizedType });
|
||||||
|
|
||||||
const markdown = buildIssueMarkdownDocument({
|
const markdown = buildIssueMarkdownDocument({
|
||||||
type: capitalizedType,
|
type: capitalizedType,
|
||||||
|
|||||||
+63
-58
@@ -14,7 +14,7 @@ if (typeof document !== 'undefined') {
|
|||||||
|
|
||||||
let activeDownloadUrl = null;
|
let activeDownloadUrl = null;
|
||||||
const PROCESS_FORM_STATE_KEY = 'bugmedley.process.formState.v1';
|
const PROCESS_FORM_STATE_KEY = 'bugmedley.process.formState.v1';
|
||||||
const ISSUE_ID_PATTERN = /\b((?:[A-Z]{2,6}|WF500)-\d{4,8})\b/i;
|
const ISSUE_ID_PATTERN = /((?:[A-Z]{2,6}|WF500)-\d{4,8})/i;
|
||||||
const BLOCK_CONTAINER_TAGS = new Set(['ARTICLE', 'ASIDE', 'BLOCKQUOTE', 'DIV', 'P', 'PRE', 'SECTION']);
|
const BLOCK_CONTAINER_TAGS = new Set(['ARTICLE', 'ASIDE', 'BLOCKQUOTE', 'DIV', 'P', 'PRE', 'SECTION']);
|
||||||
const RESOLVED_LINE_PATTERN = /^(?:resolved|fixed)\s+in\b/i;
|
const RESOLVED_LINE_PATTERN = /^(?:resolved|fixed)\s+in\b/i;
|
||||||
const TEXT_NODE = 3;
|
const TEXT_NODE = 3;
|
||||||
@@ -69,7 +69,7 @@ function generateJSON() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsedIssues = parseIssuesFromHtmlTable(inputText);
|
const parsedIssues = parseIssuesFromHtmlTable(inputText, { type: issueType });
|
||||||
|
|
||||||
if (parsedIssues.length === 0) {
|
if (parsedIssues.length === 0) {
|
||||||
document.getElementById('markdownOutput').value = '';
|
document.getElementById('markdownOutput').value = '';
|
||||||
@@ -97,7 +97,7 @@ function generateJSON() {
|
|||||||
setParseStatus(`Parsed ${issues.length} issues from input.`);
|
setParseStatus(`Parsed ${issues.length} issues from input.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseIssuesFromHtmlTable(htmlText) {
|
export function parseIssuesFromHtmlTable(htmlText, options = {}) {
|
||||||
const parser = new DOMParser();
|
const parser = new DOMParser();
|
||||||
const doc = parser.parseFromString(htmlText, 'text/html');
|
const doc = parser.parseFromString(htmlText, 'text/html');
|
||||||
const table = doc.querySelector('table');
|
const table = doc.querySelector('table');
|
||||||
@@ -109,6 +109,10 @@ export function parseIssuesFromHtmlTable(htmlText) {
|
|||||||
const rows = Array.from(table.querySelectorAll('tr'));
|
const rows = Array.from(table.querySelectorAll('tr'));
|
||||||
const issues = [];
|
const issues = [];
|
||||||
|
|
||||||
|
const parsedType = normalizeWhitespace(String(options.type || '')).toLowerCase();
|
||||||
|
const includeDescriptionCaveats = parsedType !== 'addressed';
|
||||||
|
const includeInlineCodeFormatting = parsedType !== 'addressed';
|
||||||
|
|
||||||
rows.forEach(row => {
|
rows.forEach(row => {
|
||||||
const cells = Array.from(row.children).filter(cell => {
|
const cells = Array.from(row.children).filter(cell => {
|
||||||
const tag = (cell.tagName || '').toUpperCase();
|
const tag = (cell.tagName || '').toUpperCase();
|
||||||
@@ -119,8 +123,11 @@ export function parseIssuesFromHtmlTable(htmlText) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const leftCellText = extractCellText(cells[0]);
|
const leftCellText = extractCellPlainText(cells[0]);
|
||||||
const issueDetails = extractIssueDetails(cells[1]);
|
const issueDetails = extractIssueDetails(cells[1], {
|
||||||
|
includeCaveat: includeDescriptionCaveats,
|
||||||
|
includeInlineCode: includeInlineCodeFormatting
|
||||||
|
});
|
||||||
const rightCellText = issueDetails.description;
|
const rightCellText = issueDetails.description;
|
||||||
const rightCellCaveat = issueDetails.caveat;
|
const rightCellCaveat = issueDetails.caveat;
|
||||||
|
|
||||||
@@ -134,13 +141,9 @@ export function parseIssuesFromHtmlTable(htmlText) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const id = issueIdMatch[1].toUpperCase();
|
const id = issueIdMatch[1].toUpperCase();
|
||||||
const leftCellTrailingText = normalizeWhitespace(
|
const leftCellTrailingText = normalizeWhitespace(leftCellText.replace(issueIdMatch[0], ''));
|
||||||
leftCellText.replace(issueIdMatch[0], '').replace(/^[-:;,.\s]+/, '')
|
const metadataText = leftCellTrailingText.replace(/^[-:;,.\s]+/, '');
|
||||||
);
|
const resolved = RESOLVED_LINE_PATTERN.test(metadataText) ? metadataText : '';
|
||||||
|
|
||||||
// Strip markdown formatting to check if this is resolved text
|
|
||||||
const plainTrailingText = stripMarkdownFormatting(leftCellTrailingText);
|
|
||||||
const resolved = RESOLVED_LINE_PATTERN.test(plainTrailingText) ? plainTrailingText : '';
|
|
||||||
const description = rightCellText;
|
const description = rightCellText;
|
||||||
|
|
||||||
if (!description) {
|
if (!description) {
|
||||||
@@ -151,39 +154,29 @@ export function parseIssuesFromHtmlTable(htmlText) {
|
|||||||
id,
|
id,
|
||||||
description,
|
description,
|
||||||
resolved,
|
resolved,
|
||||||
caveat: resolved ? '' : (rightCellCaveat || plainTrailingText)
|
caveat: resolved ? '' : (rightCellCaveat || metadataText)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return issues;
|
return issues;
|
||||||
}
|
}
|
||||||
|
|
||||||
function stripMarkdownFormatting(text) {
|
function extractIssueDetails(cell, options = {}) {
|
||||||
return String(text || '')
|
|
||||||
.replace(/\*\*(.*?)\*\*/g, '$1')
|
|
||||||
.replace(/\*(.*?)\*/g, '$1')
|
|
||||||
.replace(/`(.*?)`/g, '$1')
|
|
||||||
.replace(/~~(.*?)~~/g, '$1');
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractIssueDetails(cell) {
|
|
||||||
const caveatNode = cell.querySelector('tt');
|
|
||||||
const caveat = caveatNode ? normalizeWhitespace(caveatNode.textContent || '') : '';
|
|
||||||
|
|
||||||
if (!caveatNode) {
|
|
||||||
return {
|
|
||||||
description: extractCellText(cell),
|
|
||||||
caveat: ''
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const clone = cell.cloneNode(true);
|
const clone = cell.cloneNode(true);
|
||||||
const cloneCaveatNode = clone.querySelector('tt');
|
const includeCaveat = options.includeCaveat !== false;
|
||||||
if (cloneCaveatNode) {
|
const includeInlineCode = options.includeInlineCode !== false;
|
||||||
cloneCaveatNode.remove();
|
|
||||||
|
let caveat = '';
|
||||||
|
const firstTtNode = clone.querySelector('tt');
|
||||||
|
|
||||||
|
if (firstTtNode && includeCaveat) {
|
||||||
|
caveat = normalizeWhitespace(firstTtNode.textContent || '');
|
||||||
|
firstTtNode.remove();
|
||||||
|
} else if (firstTtNode) {
|
||||||
|
Array.from(clone.querySelectorAll('tt')).forEach(node => node.remove());
|
||||||
}
|
}
|
||||||
|
|
||||||
let description = extractCellText(clone);
|
let description = extractCellText(clone, { includeInlineCode });
|
||||||
description = description.replace(/^\(\s*\)\s*/, '').trim();
|
description = description.replace(/^\(\s*\)\s*/, '').trim();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -192,15 +185,19 @@ function extractIssueDetails(cell) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractCellText(cell) {
|
function extractCellPlainText(cell) {
|
||||||
|
return normalizeWhitespace(cell.textContent || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractCellText(cell, options = {}) {
|
||||||
const clone = cell.cloneNode(true);
|
const clone = cell.cloneNode(true);
|
||||||
return collectMarkdownBlocks(clone)
|
return collectMarkdownBlocks(clone, 0, options)
|
||||||
.join('\n\n')
|
.join('\n\n')
|
||||||
.replace(/\n{3,}/g, '\n\n')
|
.replace(/\n{3,}/g, '\n\n')
|
||||||
.trim();
|
.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
function collectMarkdownBlocks(root, nestedListDepth = 0) {
|
function collectMarkdownBlocks(root, nestedListDepth = 0, options = {}) {
|
||||||
const blocks = [];
|
const blocks = [];
|
||||||
let paragraphBuffer = '';
|
let paragraphBuffer = '';
|
||||||
|
|
||||||
@@ -235,7 +232,7 @@ function collectMarkdownBlocks(root, nestedListDepth = 0) {
|
|||||||
|
|
||||||
if (tagName === 'UL' || tagName === 'OL') {
|
if (tagName === 'UL' || tagName === 'OL') {
|
||||||
flushParagraph();
|
flushParagraph();
|
||||||
const listMarkdown = convertHtmlListToMarkdown(node, nestedListDepth);
|
const listMarkdown = convertHtmlListToMarkdown(node, nestedListDepth, options);
|
||||||
if (listMarkdown) {
|
if (listMarkdown) {
|
||||||
blocks.push(listMarkdown);
|
blocks.push(listMarkdown);
|
||||||
}
|
}
|
||||||
@@ -244,18 +241,18 @@ function collectMarkdownBlocks(root, nestedListDepth = 0) {
|
|||||||
|
|
||||||
if (BLOCK_CONTAINER_TAGS.has(tagName)) {
|
if (BLOCK_CONTAINER_TAGS.has(tagName)) {
|
||||||
flushParagraph();
|
flushParagraph();
|
||||||
blocks.push(...collectMarkdownBlocks(node, nestedListDepth));
|
blocks.push(...collectMarkdownBlocks(node, nestedListDepth, options));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
paragraphBuffer = appendInlineText(paragraphBuffer, convertInlineNodeToMarkdown(node));
|
paragraphBuffer = appendInlineText(paragraphBuffer, convertInlineNodeToMarkdown(node, options));
|
||||||
});
|
});
|
||||||
|
|
||||||
flushParagraph();
|
flushParagraph();
|
||||||
return blocks;
|
return blocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertInlineNodeToMarkdown(node) {
|
function convertInlineNodeToMarkdown(node, options = {}) {
|
||||||
if (!node || node.nodeType !== ELEMENT_NODE) {
|
if (!node || node.nodeType !== ELEMENT_NODE) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@@ -270,25 +267,33 @@ function convertInlineNodeToMarkdown(node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (/\buicontrol\b/i.test(className)) {
|
if (/\buicontrol\b/i.test(className)) {
|
||||||
const content = normalizeInlineMarkdown(renderInlineChildren(node));
|
const content = normalizeInlineMarkdown(renderInlineChildren(node, options));
|
||||||
return content ? `**${content}**` : '';
|
return content ? `**${content}**` : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (/\buserinput\b/i.test(className)) {
|
if (/\buserinput\b/i.test(className)) {
|
||||||
const content = normalizeInlineMarkdown(renderInlineChildren(node));
|
const content = normalizeInlineMarkdown(renderInlineChildren(node, options));
|
||||||
return content ? `\`${content}\`` : '';
|
if (!content) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return options.includeInlineCode === false ? content : `\`${content}\``;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (/\bsystemoutput\b/i.test(className)) {
|
if (/\bsystemoutput\b/i.test(className)) {
|
||||||
const content = normalizeInlineMarkdown(renderInlineChildren(node));
|
const content = normalizeInlineMarkdown(renderInlineChildren(node, options));
|
||||||
return content ? `\`${content}\`` : '';
|
if (!content) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return options.includeInlineCode === false ? content : `\`${content}\``;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tagName = (node.tagName || '').toUpperCase();
|
const tagName = (node.tagName || '').toUpperCase();
|
||||||
|
|
||||||
if (tagName === 'A' && /\bxref\b/i.test(className)) {
|
if (tagName === 'A' && /\bxref\b/i.test(className)) {
|
||||||
const href = node.getAttribute('href') || '';
|
const href = node.getAttribute('href') || '';
|
||||||
const content = normalizeInlineMarkdown(renderInlineChildren(node));
|
const content = normalizeInlineMarkdown(renderInlineChildren(node, options));
|
||||||
return content && href ? `[${content}](${href})` : content;
|
return content && href ? `[${content}](${href})` : content;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,19 +302,19 @@ function convertInlineNodeToMarkdown(node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (tagName === 'STRONG' || tagName === 'B') {
|
if (tagName === 'STRONG' || tagName === 'B') {
|
||||||
const content = normalizeInlineMarkdown(renderInlineChildren(node));
|
const content = normalizeInlineMarkdown(renderInlineChildren(node, options));
|
||||||
return content ? `**${content}**` : '';
|
return content ? `**${content}**` : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tagName === 'EM' || tagName === 'I') {
|
if (tagName === 'EM' || tagName === 'I') {
|
||||||
const content = normalizeInlineMarkdown(renderInlineChildren(node));
|
const content = normalizeInlineMarkdown(renderInlineChildren(node, options));
|
||||||
return content ? `*${content}*` : '';
|
return content ? `*${content}*` : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
return renderInlineChildren(node);
|
return renderInlineChildren(node, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderInlineChildren(node) {
|
function renderInlineChildren(node, options = {}) {
|
||||||
let output = '';
|
let output = '';
|
||||||
|
|
||||||
Array.from(node.childNodes).forEach(child => {
|
Array.from(node.childNodes).forEach(child => {
|
||||||
@@ -322,7 +327,7 @@ function renderInlineChildren(node) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
output = appendInlineText(output, convertInlineNodeToMarkdown(child));
|
output = appendInlineText(output, convertInlineNodeToMarkdown(child, options));
|
||||||
});
|
});
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
@@ -340,7 +345,7 @@ function extractMenuCascadeParts(node) {
|
|||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertHtmlListToMarkdown(list, depth = 0) {
|
function convertHtmlListToMarkdown(list, depth = 0, options = {}) {
|
||||||
const items = Array.from(list.children).filter(child => child.tagName && child.tagName.toUpperCase() === 'LI');
|
const items = Array.from(list.children).filter(child => child.tagName && child.tagName.toUpperCase() === 'LI');
|
||||||
if (items.length === 0) {
|
if (items.length === 0) {
|
||||||
return '';
|
return '';
|
||||||
@@ -348,13 +353,13 @@ function convertHtmlListToMarkdown(list, depth = 0) {
|
|||||||
|
|
||||||
const isOrdered = list.tagName.toUpperCase() === 'OL';
|
const isOrdered = list.tagName.toUpperCase() === 'OL';
|
||||||
return items
|
return items
|
||||||
.map((item, index) => convertListItemToMarkdown(item, isOrdered ? `${index + 1}.` : '-', depth))
|
.map((item, index) => convertListItemToMarkdown(item, isOrdered ? `${index + 1}.` : '-', depth, options))
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join('\n');
|
.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertListItemToMarkdown(item, marker, depth) {
|
function convertListItemToMarkdown(item, marker, depth, options = {}) {
|
||||||
const blocks = collectMarkdownBlocks(item, depth + 1);
|
const blocks = collectMarkdownBlocks(item, depth + 1, options);
|
||||||
const indent = ' '.repeat(depth);
|
const indent = ' '.repeat(depth);
|
||||||
const continuationIndent = `${indent} `;
|
const continuationIndent = `${indent} `;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user