Files
htmlTemplate/xsd_xml_viewer.html
2025-09-07 12:03:21 -06:00

1267 lines
42 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>XSD-Based XML Viewer</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 1400px;
margin: 0 auto;
padding: 80px 20px;
line-height: 1.6;
}
/* Fixed top banner */
.top-banner {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 60px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
z-index: 1000;
font-size: 18px;
font-weight: bold;
}
/* Fixed bottom banner */
.bottom-banner {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 50px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 -2px 10px rgba(0,0,0,0.1);
z-index: 1000;
font-size: 14px;
}
.upload-container {
display: flex;
gap: 20px;
margin-bottom: 20px;
}
.upload-area {
flex: 1;
border: 2px dashed #ccc;
border-radius: 8px;
padding: 40px;
text-align: center;
background-color: #f9f9f9;
}
.upload-area.dragover {
border-color: #007bff;
background-color: #e3f2fd;
}
.upload-area.has-file {
border-color: #28a745;
background-color: #e8f5e9;
}
.file-status {
margin-top: 10px;
font-weight: bold;
}
.file-status.loaded {
color: #28a745;
}
.file-status.error {
color: #dc3545;
}
.group-container {
margin: 20px 0;
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
}
.group-header {
background: linear-gradient(135deg, #f5f5f5 0%, #e0e0e0 100%);
padding: 15px;
font-size: 18px;
font-weight: bold;
color: #333;
border-bottom: 2px solid #007bff;
}
.element-card {
background: #fff;
padding: 20px;
margin: 0;
border-bottom: 3px solid #ddd;
position: relative;
}
.element-card:last-child {
border-bottom: none;
}
.element-card:not(:last-child)::after {
content: '';
position: absolute;
bottom: -3px;
left: 50%;
transform: translateX(-50%);
width: 50px;
height: 3px;
background: linear-gradient(90deg, transparent, #007bff, transparent);
}
.field-row {
display: flex;
margin: 8px 0;
padding: 8px 0;
align-items: flex-start;
border-bottom: 1px dotted #e0e0e0;
}
.field-row:last-child {
border-bottom: none;
}
.field-label {
min-width: 180px;
font-weight: bold;
color: #5c6bc0;
padding-right: 15px;
flex-shrink: 0;
}
.field-value {
flex: 1;
color: #333;
word-wrap: break-word;
}
/* Style for multi-value fields */
.field-value-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.field-value-item {
padding: 4px 8px;
background: #f5f5f5;
border-left: 2px solid #007bff;
border-radius: 2px;
}
.element-pair {
position: relative;
}
.element-pair.show-xml .element-xml {
display: block;
}
.element-xml {
display: none;
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 15px;
margin-top: 15px;
font-family: 'Courier New', Consolas, monospace;
font-size: 12px;
white-space: pre;
overflow-x: auto;
max-height: 400px;
overflow-y: auto;
}
.element-index {
position: absolute;
top: 20px;
right: 20px;
background: #5c6bc0;
color: white;
padding: 4px 12px;
border-radius: 15px;
font-size: 14px;
font-weight: bold;
}
.attribute-list {
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid #ddd;
}
.attribute-item {
display: flex;
gap: 10px;
margin: 5px 0;
font-size: 14px;
}
.attribute-name {
font-weight: bold;
color: #666;
min-width: 100px;
}
.attribute-value {
color: #333;
}
/* Custom formatting tags */
code, kbd, samp, var, .code {
font-family: 'Courier New', Consolas, monospace;
background-color: #f0f0f0;
padding: 2px 4px;
border-radius: 3px;
font-size: 0.9em;
}
kbd {
background-color: #333;
color: #fff;
border: 1px solid #666;
border-radius: 3px;
box-shadow: 0 1px 0 rgba(0,0,0,0.2);
}
samp {
background-color: #e8f5e9;
color: #2e7d32;
border-left: 3px solid #4caf50;
padding-left: 6px;
}
var {
background-color: #fff3e0;
color: #e65100;
font-style: italic;
}
mark, .highlight {
background-color: #ffeb3b;
padding: 2px 4px;
}
.critical {
color: #d32f2f;
font-weight: bold;
background-color: #ffebee;
padding: 2px 4px;
border-radius: 3px;
}
.optional {
color: #9e9e9e;
font-style: italic;
}
.deprecated {
text-decoration: line-through;
color: #757575;
}
.todo {
background-color: #fff9c4;
color: #f57f17;
padding: 2px 6px;
border-left: 3px solid #fbc02d;
font-weight: bold;
}
.value {
color: #1976d2;
font-weight: bold;
}
.unit {
color: #666;
font-size: 0.9em;
}
.term {
color: #7b1fa2;
font-weight: 600;
border-bottom: 1px dotted #7b1fa2;
}
.acronym {
text-transform: uppercase;
font-size: 0.9em;
letter-spacing: 0.5px;
}
.version {
background-color: #e3f2fd;
color: #1565c0;
padding: 2px 6px;
border-radius: 3px;
font-family: monospace;
}
blockquote {
margin: 10px 0;
padding-left: 15px;
border-left: 4px solid #ccc;
background-color: #f9f9f9;
font-style: italic;
color: #555;
}
pre {
background-color: #f5f5f5;
padding: 10px;
border-radius: 4px;
overflow-x: auto;
font-family: 'Courier New', Consolas, monospace;
}
.ref, .url {
color: #1976d2;
text-decoration: underline;
cursor: pointer;
}
.controls {
text-align: center;
margin: 20px 0;
}
.controls button {
background-color: #007bff;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
margin: 0 5px;
}
.controls button:hover {
background-color: #0056b3;
}
.controls button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.error {
color: #d32f2f;
background-color: #ffebee;
padding: 10px;
border-radius: 4px;
margin: 10px 0;
}
.info-box {
background: #e3f2fd;
padding: 15px;
border-radius: 4px;
margin: 20px 0;
}
.schema-info {
background: #f0f8ff;
padding: 10px;
border-radius: 4px;
margin: 10px 0;
font-size: 14px;
}
.schema-info h3 {
margin-top: 0;
color: #007bff;
}
/* Dark mode styles */
@media (prefers-color-scheme: dark) {
body {
background-color: #1a1a1a;
color: #e0e0e0;
}
.upload-area {
background-color: #2d2d2d;
border-color: #555;
color: #e0e0e0;
}
.upload-area.dragover {
border-color: #4fc3f7;
background-color: #1e3a8a;
}
.upload-area.has-file {
border-color: #4caf50;
background-color: #1b5e20;
}
.group-container {
border-color: #555;
}
.group-header {
background: linear-gradient(135deg, #2d2d2d 0%, #1a1a1a 100%);
color: #e0e0e0;
border-bottom-color: #4fc3f7;
}
.element-card {
background: #2d2d2d;
border-bottom-color: #555;
}
.element-card:not(:last-child)::after {
background: linear-gradient(90deg, transparent, #4fc3f7, transparent);
}
.field-row {
border-bottom-color: #444;
}
.field-label {
color: #64b5f6;
}
.field-value {
color: #e0e0e0;
}
.field-value-item {
background: #3d3d3d;
border-left-color: #4fc3f7;
}
.element-index {
background: #4fc3f7;
}
.element-name {
color: #4fc3f7;
}
.element-path {
color: #999;
}
.attribute-name {
color: #999;
}
.attribute-value {
color: #e0e0e0;
}
.element-xml {
background: #2d2d2d;
border-color: #555;
color: #e0e0e0;
}
/* Dark mode formatting */
code, kbd, samp, var, .code {
background-color: #3d3d3d;
color: #e0e0e0;
}
kbd {
background-color: #1a1a1a;
color: #fff;
border-color: #555;
}
samp {
background-color: #1a4d1a;
color: #90ee90;
}
var {
background-color: #4d3d1a;
color: #ffd700;
}
mark, .highlight {
background-color: #665500;
color: #ffeb3b;
}
.critical {
color: #ff6b6b;
background-color: #4d1f1f;
}
.optional {
color: #999;
}
.deprecated {
color: #888;
}
.todo {
background-color: #4d3d1f;
color: #ffb74d;
}
.value {
color: #64b5f6;
}
.term {
color: #ba68c8;
}
.version {
background-color: #1a2332;
color: #90caf9;
}
blockquote {
background-color: #2d2d2d;
border-left-color: #555;
color: #ccc;
}
pre {
background-color: #2d2d2d;
color: #e0e0e0;
}
.ref, .url {
color: #64b5f6;
}
.error {
color: #ff6b6b;
background-color: #4d1f1f;
}
.info-box {
background: #1e3a8a;
}
.schema-info {
background: #2d2d2d;
}
.controls button {
background-color: #4fc3f7;
}
.controls button:hover {
background-color: #29b6f6;
}
.controls button:disabled {
background-color: #555;
}
/* Dark mode banner styling */
.top-banner {
background: linear-gradient(135deg, #1e3a8a 0%, #581c87 100%);
}
.bottom-banner {
background: linear-gradient(135deg, #1e3a8a 0%, #581c87 100%);
}
}
</style>
</head>
<body>
<!-- Fixed top banner -->
<div class="top-banner">
XSD-Based XML Viewer
</div>
<!-- Fixed bottom banner -->
<div class="bottom-banner">
© 2024 XSD XML Viewer | Version 1.0
</div>
<h1>XSD-Based XML Viewer</h1>
<p>Load an XSD schema file and an XML file to view formatted content based on the schema structure.</p>
<div class="upload-container">
<div class="upload-area" id="xsd-upload-area">
<p><strong>Step 1: Load XSD Schema</strong></p>
<p>Drag and drop your XSD file here, or click to select</p>
<input type="file" id="xsd-input" accept=".txt" style="display: none;">
<button onclick="document.getElementById('xsd-input').click()">Choose XSD File</button>
<div id="xsd-status" class="file-status"></div>
</div>
<div class="upload-area" id="xml-upload-area">
<p><strong>Step 2: Load XML Data</strong></p>
<p>Drag and drop your XML file here, or click to select</p>
<input type="file" id="xml-input" accept=".xml" style="display: none;">
<button onclick="document.getElementById('xml-input').click()" id="xml-button" disabled>Choose XML File</button>
<div id="xml-status" class="file-status"></div>
</div>
</div>
<div id="schema-info" class="schema-info" style="display: none;">
<h3>Schema Information</h3>
<div id="schema-details"></div>
</div>
<div class="controls" id="controls" style="display: none;">
<button id="toggle-view-btn" onclick="toggleXMLView()">Show XML</button>
<button onclick="clearAll()">Clear All</button>
</div>
<div id="content-container"></div>
<script>
// Global variables
let xsdDoc = null;
let xmlDoc = null;
let showXML = false;
let elementDefinitions = new Map();
// DOM elements
const xsdUploadArea = document.getElementById('xsd-upload-area');
const xmlUploadArea = document.getElementById('xml-upload-area');
const xsdInput = document.getElementById('xsd-input');
const xmlInput = document.getElementById('xml-input');
const xmlButton = document.getElementById('xml-button');
const xsdStatus = document.getElementById('xsd-status');
const xmlStatus = document.getElementById('xml-status');
const container = document.getElementById('content-container');
const controls = document.getElementById('controls');
const toggleBtn = document.getElementById('toggle-view-btn');
const schemaInfo = document.getElementById('schema-info');
const schemaDetails = document.getElementById('schema-details');
// XSD Upload handlers
xsdUploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
xsdUploadArea.classList.add('dragover');
});
xsdUploadArea.addEventListener('dragleave', () => {
xsdUploadArea.classList.remove('dragover');
});
xsdUploadArea.addEventListener('drop', (e) => {
e.preventDefault();
xsdUploadArea.classList.remove('dragover');
const files = e.dataTransfer.files;
if (files.length > 0) {
handleXSDFile(files[0]);
}
});
xsdInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) {
handleXSDFile(e.target.files[0]);
}
});
// XML Upload handlers
xmlUploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
if (xsdDoc) {
xmlUploadArea.classList.add('dragover');
}
});
xmlUploadArea.addEventListener('dragleave', () => {
xmlUploadArea.classList.remove('dragover');
});
xmlUploadArea.addEventListener('drop', (e) => {
e.preventDefault();
xmlUploadArea.classList.remove('dragover');
if (xsdDoc) {
const files = e.dataTransfer.files;
if (files.length > 0) {
handleXMLFile(files[0]);
}
}
});
xmlInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) {
handleXMLFile(e.target.files[0]);
}
});
function handleXSDFile(file) {
if (!file.name.toLowerCase().endsWith('.txt')) {
showError('Please select an XSD file.', xsdStatus);
return;
}
const reader = new FileReader();
reader.onload = (e) => {
try {
parseXSD(e.target.result);
xsdStatus.textContent = `${file.name} loaded`;
xsdStatus.className = 'file-status loaded';
xsdUploadArea.classList.add('has-file');
// Enable XML upload
xmlButton.disabled = false;
xmlStatus.textContent = 'Ready for XML file';
} catch (error) {
showError('Error parsing XSD file: ' + error.message, xsdStatus);
xsdUploadArea.classList.remove('has-file');
}
};
reader.readAsText(file);
}
function handleXMLFile(file) {
if (!file.name.toLowerCase().endsWith('.xml')) {
showError('Please select an XML file.', xmlStatus);
return;
}
if (!xsdDoc) {
showError('Please load an XSD file first.', xmlStatus);
return;
}
const reader = new FileReader();
reader.onload = (e) => {
try {
parseXML(e.target.result);
xmlStatus.textContent = `${file.name} loaded`;
xmlStatus.className = 'file-status loaded';
xmlUploadArea.classList.add('has-file');
} catch (error) {
showError('Error parsing XML file: ' + error.message, xmlStatus);
xmlUploadArea.classList.remove('has-file');
}
};
reader.readAsText(file);
}
function parseXSD(xsdString) {
const parser = new DOMParser();
xsdDoc = parser.parseFromString(xsdString, 'text/xml');
// Check for parsing errors
const parseError = xsdDoc.querySelector('parsererror');
if (parseError) {
throw new Error('XSD parsing error: ' + parseError.textContent);
}
// Extract element definitions from XSD
extractElementDefinitions();
displaySchemaInfo();
}
function extractElementDefinitions() {
elementDefinitions.clear();
// Find all element definitions in the XSD
const elements = xsdDoc.querySelectorAll('element, xs\\:element, xsd\\:element');
elements.forEach(element => {
const name = element.getAttribute('name');
const type = element.getAttribute('type');
if (name) {
const definition = {
name: name,
type: type || 'string',
minOccurs: element.getAttribute('minOccurs') || '1',
maxOccurs: element.getAttribute('maxOccurs') || '1',
documentation: extractDocumentation(element),
attributes: extractAttributes(element),
children: extractChildElements(element)
};
elementDefinitions.set(name, definition);
}
});
// Also extract complex types
const complexTypes = xsdDoc.querySelectorAll('complexType, xs\\:complexType, xsd\\:complexType');
complexTypes.forEach(complexType => {
const name = complexType.getAttribute('name');
if (name) {
const definition = {
name: name,
type: 'complex',
attributes: extractAttributes(complexType),
children: extractChildElements(complexType)
};
elementDefinitions.set(name, definition);
}
});
}
function extractDocumentation(element) {
const doc = element.querySelector('documentation, xs\\:documentation, xsd\\:documentation');
return doc ? doc.textContent.trim() : '';
}
function extractAttributes(element) {
const attributes = [];
const attrElements = element.querySelectorAll('attribute, xs\\:attribute, xsd\\:attribute');
attrElements.forEach(attr => {
attributes.push({
name: attr.getAttribute('name'),
type: attr.getAttribute('type') || 'string',
use: attr.getAttribute('use') || 'optional'
});
});
return attributes;
}
function extractChildElements(element) {
const children = [];
const sequences = element.querySelectorAll('sequence, xs\\:sequence, xsd\\:sequence');
sequences.forEach(seq => {
const childElements = seq.querySelectorAll('element, xs\\:element, xsd\\:element');
childElements.forEach(child => {
children.push({
name: child.getAttribute('name'),
type: child.getAttribute('type') || 'string',
minOccurs: child.getAttribute('minOccurs') || '1',
maxOccurs: child.getAttribute('maxOccurs') || '1'
});
});
});
return children;
}
function displaySchemaInfo() {
schemaInfo.style.display = 'block';
const rootElements = Array.from(elementDefinitions.entries())
.filter(([name, def]) => def.type !== 'complex')
.map(([name]) => name);
schemaDetails.innerHTML = `
<p><strong>Elements defined:</strong> ${elementDefinitions.size}</p>
<p><strong>Root elements:</strong> ${rootElements.join(', ') || 'None identified'}</p>
`;
}
function parseXML(xmlString) {
const parser = new DOMParser();
xmlDoc = parser.parseFromString(xmlString, 'text/xml');
// Check for parsing errors
const parseError = xmlDoc.querySelector('parsererror');
if (parseError) {
throw new Error('XML parsing error: ' + parseError.textContent);
}
displayXMLContent();
controls.style.display = 'block';
}
function displayXMLContent() {
container.innerHTML = '';
// Find the root element
const root = xmlDoc.documentElement;
// Find the first-level repeating element type (e.g., "Rule" in software-rules)
const topLevelElements = findTopLevelRepeatingElements(root);
if (topLevelElements.length > 0) {
// Group by the repeating element type
const elementGroups = new Map();
topLevelElements.forEach(element => {
const tagName = element.tagName;
if (!elementGroups.has(tagName)) {
elementGroups.set(tagName, []);
}
elementGroups.get(tagName).push(element);
});
// Display each group
elementGroups.forEach((elements, tagName) => {
displayElementGroup(tagName, elements);
});
} else {
// Fallback to original processing if no repeating pattern found
const processedElements = new Set();
processElement(xmlDoc.documentElement, '', processedElements);
}
}
function findTopLevelRepeatingElements(root) {
// Get all direct children of root
const children = Array.from(root.children);
// Find which tag names appear multiple times
const tagCounts = new Map();
children.forEach(child => {
const tag = child.tagName;
tagCounts.set(tag, (tagCounts.get(tag) || 0) + 1);
});
// Find the most common repeating element
let maxCount = 0;
let repeatingTag = null;
tagCounts.forEach((count, tag) => {
if (count > maxCount) {
maxCount = count;
repeatingTag = tag;
}
});
// Return all elements with the repeating tag
if (repeatingTag && maxCount > 1) {
return children.filter(child => child.tagName === repeatingTag);
}
// If only single instances, return all children
return children;
}
function displayElementGroup(tagName, elements) {
// Create group container
const groupDiv = document.createElement('div');
groupDiv.className = 'group-container';
// Add group header
const headerDiv = document.createElement('div');
headerDiv.className = 'group-header';
headerDiv.textContent = `${tagName} Elements (${elements.length})`;
groupDiv.appendChild(headerDiv);
// Display each element in compact format
elements.forEach((element, index) => {
displayCompactElement(element, index + 1, groupDiv);
});
container.appendChild(groupDiv);
}
function displayCompactElement(element, index, parentContainer) {
// Create element pair container
const pairDiv = document.createElement('div');
pairDiv.className = 'element-pair';
// Create formatted element card
const cardDiv = document.createElement('div');
cardDiv.className = 'element-card';
// Add index badge
const indexDiv = document.createElement('div');
indexDiv.className = 'element-index';
indexDiv.textContent = `#${index}`;
cardDiv.appendChild(indexDiv);
// Process all child elements as fields
const fields = extractElementFields(element);
fields.forEach(field => {
const rowDiv = document.createElement('div');
rowDiv.className = 'field-row';
const labelDiv = document.createElement('div');
labelDiv.className = 'field-label';
labelDiv.textContent = formatFieldName(field.name) + ':';
const valueDiv = document.createElement('div');
valueDiv.className = 'field-value';
valueDiv.innerHTML = field.value;
rowDiv.appendChild(labelDiv);
rowDiv.appendChild(valueDiv);
cardDiv.appendChild(rowDiv);
});
// Add attributes as fields if present
if (element.attributes.length > 0) {
Array.from(element.attributes).forEach(attr => {
const rowDiv = document.createElement('div');
rowDiv.className = 'field-row';
const labelDiv = document.createElement('div');
labelDiv.className = 'field-label';
labelDiv.textContent = `@${attr.name}:`;
const valueDiv = document.createElement('div');
valueDiv.className = 'field-value';
valueDiv.textContent = attr.value;
rowDiv.appendChild(labelDiv);
rowDiv.appendChild(valueDiv);
cardDiv.appendChild(rowDiv);
});
}
// Add show XML button
if (showXML) {
const xmlDiv = document.createElement('div');
xmlDiv.className = 'element-xml';
xmlDiv.textContent = formatXMLElement(element);
cardDiv.appendChild(xmlDiv);
pairDiv.classList.add('show-xml');
}
pairDiv.appendChild(cardDiv);
parentContainer.appendChild(pairDiv);
}
function extractElementFields(element) {
const fields = [];
const processedTags = new Set();
// Process each child element as a field
Array.from(element.children).forEach(child => {
const fieldName = child.tagName;
// Skip if we've already processed this tag
if (processedTags.has(fieldName)) {
return;
}
// Check if this is a multi-value field (appears multiple times)
const siblings = Array.from(element.children).filter(c => c.tagName === fieldName);
if (siblings.length > 1) {
// For multi-value fields, create a list
const values = siblings.map(s => formatElementContent(s)).filter(v => v && v !== '<em>(empty)</em>');
if (values.length > 0) {
// Create a formatted list of values
const listHtml = '<div class="field-value-list">' +
values.map(v => `<div class="field-value-item">${v}</div>`).join('') +
'</div>';
fields.push({
name: fieldName,
value: listHtml
});
}
processedTags.add(fieldName);
} else {
const fieldValue = formatElementContent(child);
if (fieldValue && fieldValue !== '<em>(empty)</em>') {
fields.push({
name: fieldName,
value: fieldValue
});
}
processedTags.add(fieldName);
}
});
return fields;
}
function formatFieldName(name) {
// Convert camelCase or PascalCase to Title Case
return name
.replace(/([A-Z])/g, ' $1')
.replace(/^./, str => str.toUpperCase())
.trim();
}
function processElement(element, path, processedElements) {
if (!element || element.nodeType !== Node.ELEMENT_NODE) return;
const elementPath = path ? `${path}/${element.tagName}` : element.tagName;
// Get all elements with this tag name at this level
const siblings = element.parentNode ?
Array.from(element.parentNode.children).filter(child => child.tagName === element.tagName) :
[element];
siblings.forEach((elem, index) => {
const uniquePath = siblings.length > 1 ? `${elementPath}[${index + 1}]` : elementPath;
// Check if this element has text content or attributes worth displaying
const hasContent = hasDisplayableContent(elem);
if (hasContent) {
displayElement(elem, uniquePath);
}
// Process child elements
Array.from(elem.children).forEach(child => {
processElement(child, uniquePath, processedElements);
});
});
}
function hasDisplayableContent(element) {
// Check if element has direct text content
const textNodes = Array.from(element.childNodes)
.filter(node => node.nodeType === Node.TEXT_NODE)
.map(node => node.textContent.trim())
.join('');
// Check if element has attributes
const hasAttributes = element.attributes.length > 0;
// Check if element contains formatted content (any child elements that might be formatting)
const hasFormattingChildren = Array.from(element.children).some(child => {
const tagName = child.tagName.toLowerCase();
return ['strong', 'em', 'code', 'kbd', 'critical', 'value', 'unit', 'term',
'sup', 'sub', 'mark', 'todo', 'optional', 'deprecated', 'version',
'ref', 'url', 'br', 'hr', 'ul', 'ol', 'li', 'blockquote', 'pre'].includes(tagName);
});
return textNodes.length > 0 || hasAttributes || hasFormattingChildren;
}
function displayElement(element, path) {
// Create element pair container
const pairDiv = document.createElement('div');
pairDiv.className = showXML ? 'element-pair' : 'element-pair xml-hidden';
// Create formatted element card
const cardDiv = document.createElement('div');
cardDiv.className = 'element-card';
// Add element path
const pathDiv = document.createElement('div');
pathDiv.className = 'element-path';
pathDiv.textContent = path;
cardDiv.appendChild(pathDiv);
// Add element name
const nameDiv = document.createElement('div');
nameDiv.className = 'element-name';
nameDiv.textContent = element.tagName;
cardDiv.appendChild(nameDiv);
// Add element content with formatting
const contentDiv = document.createElement('div');
contentDiv.className = 'element-content';
contentDiv.innerHTML = formatElementContent(element);
cardDiv.appendChild(contentDiv);
// Add attributes if present
if (element.attributes.length > 0) {
const attrDiv = document.createElement('div');
attrDiv.className = 'attribute-list';
Array.from(element.attributes).forEach(attr => {
const itemDiv = document.createElement('div');
itemDiv.className = 'attribute-item';
const nameSpan = document.createElement('span');
nameSpan.className = 'attribute-name';
nameSpan.textContent = attr.name + ':';
const valueSpan = document.createElement('span');
valueSpan.className = 'attribute-value';
valueSpan.textContent = attr.value;
itemDiv.appendChild(nameSpan);
itemDiv.appendChild(valueSpan);
attrDiv.appendChild(itemDiv);
});
cardDiv.appendChild(attrDiv);
}
// Create XML display div
const xmlDiv = document.createElement('div');
xmlDiv.className = 'element-xml';
xmlDiv.textContent = formatXMLElement(element);
pairDiv.appendChild(cardDiv);
pairDiv.appendChild(xmlDiv);
container.appendChild(pairDiv);
}
function formatElementContent(element) {
// Simply get the inner HTML of the element, which preserves all HTML formatting
const innerHTML = element.innerHTML;
// If there's content, return it as-is (browser will handle HTML tags)
// If empty, return the (empty) indicator
return innerHTML ? innerHTML.trim() : '<em>(empty)</em>';
}
function formatXMLElement(element) {
const serializer = new XMLSerializer();
const xmlString = serializer.serializeToString(element);
// Basic formatting with indentation
return xmlString
.replace(/></g, '>\n<')
.split('\n')
.map((line, index) => {
const indent = ' '.repeat(Math.min(index, 4));
return indent + line;
})
.join('\n');
}
function toggleXMLView() {
showXML = !showXML;
// Refresh the display with the new setting
displayXMLContent();
toggleBtn.textContent = showXML ? 'Hide XML' : 'Show XML';
}
function clearAll() {
xsdDoc = null;
xmlDoc = null;
elementDefinitions.clear();
container.innerHTML = '';
controls.style.display = 'none';
schemaInfo.style.display = 'none';
xsdStatus.textContent = '';
xsdStatus.className = 'file-status';
xsdUploadArea.classList.remove('has-file');
xmlStatus.textContent = '';
xmlStatus.className = 'file-status';
xmlUploadArea.classList.remove('has-file');
xmlButton.disabled = true;
xsdInput.value = '';
xmlInput.value = '';
}
function showError(message, statusElement) {
if (statusElement) {
statusElement.textContent = message;
statusElement.className = 'file-status error';
} else {
container.innerHTML = `<div class="error">${message}</div>`;
}
}
// Display initial info
container.innerHTML = `
<div class="info-box">
<h3>How to use this viewer:</h3>
<ol>
<li>Load an XSD schema file first - this defines the structure of your XML</li>
<li>Load an XML file that conforms to the schema</li>
<li>All text content will be displayed with HTML formatting support</li>
</ol>
<p><strong>Supported formatting tags:</strong> strong, em, code, kbd, critical, optional, todo, value, unit, term, version, and more!</p>
</div>
`;
</script>
</body>
</html>