Skip to content
HTML Heading Structure Fixer

HTML Heading Structure Fixer

Automatically fix heading hierarchy issues and improve accessibility

📝 Input HTML

⚙️ Fix Options

\n'; output += ''; return output; }convertBoldToHeadings(doc) { const paragraphs = doc.querySelectorAll('p'); paragraphs.forEach(p => { const strong = p.querySelector('strong, b'); if (!strong) return;const pText = p.textContent.trim(); const strongText = strong.textContent.trim();// Check if the paragraph is short and bold text is significant if (pText.length < 100 && strongText.length > 5 && (pText === strongText || strongText.length / pText.length > 0.7)) { // Check if next sibling has content (indicating this might be a heading) const nextSibling = p.nextElementSibling; if (nextSibling && nextSibling.textContent.trim().length > 0) { // Determine appropriate heading level based on context let level = this.determineHeadingLevel(doc, p); const heading = doc.createElement(`h${level}`); heading.innerHTML = p.innerHTML; this.setAttributes(heading, this.getAttributes(p)); p.parentNode.replaceChild(heading, p); this.stats.converted++; this.stats.changes++; } } }); }determineHeadingLevel(doc, element) { // Look at previous headings to determine appropriate level const allHeadings = Array.from(doc.querySelectorAll('h1, h2, h3, h4, h5, h6')); const elementIndex = Array.from(doc.body.children).indexOf(element); let previousHeading = null; for (let i = 0; i < allHeadings.length; i++) { const headingIndex = Array.from(doc.body.children).indexOf(allHeadings[i]); if (headingIndex < elementIndex) { previousHeading = allHeadings[i]; } else { break; } }if (!previousHeading) { return doc.querySelector('h1') ? 2 : 1; }const prevLevel = parseInt(previousHeading.tagName.substring(1)); return Math.min(prevLevel + 1, 6); }removeEmptyHeadings(doc) { const headings = doc.querySelectorAll('h1, h2, h3, h4, h5, h6'); headings.forEach(heading => { if (heading.textContent.trim() === '') { heading.remove(); this.stats.removed++; this.stats.changes++; } }); }ensureSingleH1(doc) { const h1s = doc.querySelectorAll('h1'); if (h1s.length > 1) { // Keep the first h1, convert others to h2 for (let i = 1; i < h1s.length; i++) { const h2 = doc.createElement('h2'); h2.innerHTML = h1s[i].innerHTML; this.setAttributes(h2, this.getAttributes(h1s[i])); h1s[i].parentNode.replaceChild(h2, h1s[i]); this.stats.changes++; } this.warnings.push({ type: 'multiple-h1', message: `Found ${h1s.length} H1 tags. Kept the first one and converted ${h1s.length - 1} to H2.` }); } }addMissingH1(doc) { const h1 = doc.querySelector('h1'); if (!h1) { const h2 = doc.querySelector('h2'); if (h2) { const newH1 = doc.createElement('h1'); newH1.innerHTML = h2.innerHTML; this.setAttributes(newH1, this.getAttributes(h2)); h2.parentNode.replaceChild(newH1, h2); this.stats.changes++; this.warnings.push({ type: 'missing-h1', message: 'No H1 found. Promoted first H2 to H1.' }); } else { this.warnings.push({ type: 'no-h1', message: 'No H1 found in document. Consider adding one for accessibility.' }); } } }fixSkippedLevels(doc) { const headings = Array.from(doc.querySelectorAll('h1, h2, h3, h4, h5, h6')); let expectedLevel = 1; let hasH1 = false;headings.forEach((heading, index) => { const currentLevel = parseInt(heading.tagName.substring(1)); if (currentLevel === 1) { hasH1 = true; expectedLevel = 2; return; }if (!hasH1) { expectedLevel = currentLevel; hasH1 = true; return; }// Check if level is skipped if (currentLevel > expectedLevel + 1) { const newLevel = expectedLevel + 1; const newHeading = doc.createElement(`h${newLevel}`); newHeading.innerHTML = heading.innerHTML; this.setAttributes(newHeading, this.getAttributes(heading)); heading.parentNode.replaceChild(newHeading, heading); this.stats.changes++; expectedLevel = newLevel; } else { expectedLevel = currentLevel; } }); }flagHeadingOnlySections(doc) { const headings = doc.querySelectorAll('h1, h2, h3, h4, h5, h6'); headings.forEach(heading => { let hasContent = false; let next = heading.nextElementSibling; // Check until we hit another heading while (next && !next.matches('h1, h2, h3, h4, h5, h6')) { if (next.textContent.trim().length > 0) { hasContent = true; break; } next = next.nextElementSibling; } if (!hasContent) { this.warnings.push({ type: 'empty-section', message: `Heading "${heading.textContent.trim()}" has no content following it.` }); } }); }formatHTML(html) { // Simple HTML formatting with indentation let formatted = ''; let indent = 0; const lines = html.split(/>\s* { if (index > 0) line = '<' + line; if (index < lines.length - 1) line = line + '>'; if (line.match(/^<\/\w/) || line.match(/^<\w[^>]*\/>/)) { indent = Math.max(0, indent - 1); } formatted += ' '.repeat(indent) + line.trim() + '\n'; if (line.match(/^<\w[^>]*[^\/]>/) && !line.match(/<\/(h[1-6]|p|span|strong|em|b|i|a)>/)) { indent++; } }); return formatted.trim(); }buildHeadingTree(html) { const doc = this.parseHTML(html); const headings = this.extractHeadings(doc); if (headings.length === 0) { return '

No headings found

'; } let html_output = '
    '; headings.forEach(heading => { const className = `beltab-tree-item beltab-h${heading.level}`; const text = heading.text || '(empty)'; html_output += `
  • H${heading.level}: ${this.escapeHtml(text)}
  • `; }); html_output += '
'; return html_output; }escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } }// Initialize the application const fixer = new HeadingFixer(); document.getElementById('fixButton').addEventListener('click', () => { const input = document.getElementById('inputHtml').value; if (!input.trim()) { alert('Please enter some HTML code to fix.'); return; }const options = { fixSkipped: document.getElementById('fixSkipped').checked, ensureSingleH1: document.getElementById('ensureSingleH1').checked, fixEmpty: document.getElementById('fixEmpty').checked, convertBold: document.getElementById('convertBold').checked, addMissingH1: document.getElementById('addMissingH1').checked, flagEmptySections: document.getElementById('flagEmptySections').checked };const result = fixer.fixHTML(input, options);// Show before/after preview document.getElementById('beforePreview').innerHTML = fixer.buildHeadingTree(input); document.getElementById('afterPreview').innerHTML = fixer.buildHeadingTree(result.html); document.getElementById('previewContainer').style.display = 'block';// Show output document.getElementById('outputHtml').value = result.html; document.getElementById('outputContainer').style.display = 'block';// Show stats const statsHtml = `
${result.stats.original}
Original Headings
${result.stats.fixed}
Final Headings
${result.stats.changes}
Changes Made
${result.stats.removed}
Removed
${result.stats.converted}
Converted
`; document.getElementById('stats').innerHTML = statsHtml; document.getElementById('statsContainer').style.display = 'block';// Show warnings if (result.warnings.length > 0) { const warningsHtml = result.warnings.map(w => `
${w.type} ${w.message}
`).join(''); document.getElementById('warnings').innerHTML = warningsHtml; document.getElementById('warningsContainer').style.display = 'block'; } else { document.getElementById('warningsContainer').style.display = 'none'; }// Scroll to results document.getElementById('previewContainer').scrollIntoView({ behavior: 'smooth', block: 'start' }); });document.getElementById('copyButton').addEventListener('click', () => { const output = document.getElementById('outputHtml'); output.select(); document.execCommand('copy'); const button = document.getElementById('copyButton'); button.textContent = '✅ Copied!'; button.classList.add('copied'); setTimeout(() => { button.textContent = '📋 Copy to Clipboard'; button.classList.remove('copied'); }, 2000); });document.getElementById('clearButton').addEventListener('click', () => { if (confirm('Are you sure you want to clear all fields?')) { document.getElementById('inputHtml').value = ''; document.getElementById('outputHtml').value = ''; document.getElementById('previewContainer').style.display = 'none'; document.getElementById('outputContainer').style.display = 'none'; document.getElementById('statsContainer').style.display = 'none'; document.getElementById('warningsContainer').style.display = 'none'; } });// Add sample HTML button for testing document.addEventListener('DOMContentLoaded', () => { const sampleButton = document.createElement('button'); sampleButton.className = 'beltab-button'; sampleButton.textContent = '📄 Load Sample HTML'; sampleButton.style.background = '#3498db'; sampleButton.addEventListener('click', () => { const sampleHTML = `

Welcome to My Website

This is the introduction.

Another Main Title

This shouldn't be h1.

Skipped from H1 to H4

This heading skipped levels.

This Looks Like a Heading

And it's followed by regular content, so it probably should be a heading.

Proper Heading

Some content here.

Another skip to H5

More content.

Section Without Content

Another Section

This one has content.

`; document.getElementById('inputHtml').value = sampleHTML; }); document.querySelector('.beltab-button-group').appendChild(sampleButton); });