185 lines
4.8 KiB
JavaScript
185 lines
4.8 KiB
JavaScript
// Make sure regex patterns are only parsed once
|
|
const $whiteSpacePattern = /^\s+$/ig;
|
|
const $inputTagPattern = /<(input)(.*?)>/ig;
|
|
const $optionClosingTagPattern = />(?:\s+)?<\/option>/ig;
|
|
const $htmlAttributeSpacingPattern = /(\s+)([^in\s][a-z_\-]+=(?:"(?:.*?)"|[^"'`=<>\s]+))/ig;
|
|
const $labelWrapPattern = /<label.*?>(?:.*?)?(?:\s+)?(<.*?>)(?:.*?)?(?:\s+)?<\/label>/ig;
|
|
|
|
const ELEMENT_NODE = 1;
|
|
const TEXT_NODE = 3;
|
|
|
|
class Format {
|
|
/**
|
|
* Format the display of an input element, and align the attributes
|
|
* on separate lines
|
|
*
|
|
* @param {HTMLInputElement} formElement
|
|
* @return {string}
|
|
*/
|
|
static input(formElement) {
|
|
// Let's do some ugly DOM manipulation to make the output of the
|
|
// highlighted have lined up attributes.
|
|
// For the spacing, we replace normal whitespace characters with
|
|
// {x} placeholders so the highlighter doesn't mangle them
|
|
const formElementSpaces = '{s}'.repeat(formElement.tagName.length + 2);
|
|
const formElementReplace = '{n}' + formElementSpaces;
|
|
return formElement.outerHTML
|
|
.trim()
|
|
.replace($inputTagPattern, `<$1$2 />`)
|
|
.replace($htmlAttributeSpacingPattern, formElementReplace + '$2');
|
|
}
|
|
|
|
/**
|
|
* Format the display of an option element
|
|
*
|
|
* @param {HTMLOptionElement} formElement
|
|
* @return {string}
|
|
*/
|
|
static option(formElement) {
|
|
let raw = Format.generic(formElement);
|
|
|
|
if (Format.hasHtmlChildren(formElement) && formElement.childNodes.length > 0) {
|
|
return raw;
|
|
}
|
|
|
|
raw = raw.replace($whiteSpacePattern, '');
|
|
return raw.replace($optionClosingTagPattern, ' />');
|
|
}
|
|
|
|
/**
|
|
* Format the display of elements without specific formatting methods
|
|
*
|
|
* @param {HTMLElement} formElement
|
|
* @return {string}
|
|
*/
|
|
static generic(formElement) {
|
|
return formElement.outerHTML.trim();
|
|
}
|
|
|
|
/**
|
|
* Format the current element
|
|
*
|
|
* @param {HTMLElement} formElement
|
|
* @param {number} i
|
|
* @return string
|
|
*/
|
|
static formatElement(formElement, i) {
|
|
const elementPrefix = (i > 0)
|
|
? '{n}' + '{t}'.repeat(i)
|
|
: '';
|
|
|
|
const formattingMethods = ['input', 'option'];
|
|
|
|
// Attempt to indent text nodes
|
|
if (formElement.nodeType === TEXT_NODE) {
|
|
//alert('Attempting to format a text node');
|
|
return elementPrefix + formElement.nodeValue;
|
|
} else if (formElement.nodeType === ELEMENT_NODE) {
|
|
const tagName = formElement.nodeName.toLowerCase();
|
|
const formattingMethod = (formattingMethods.includes(tagName))
|
|
? Format[tagName]
|
|
: Format.generic;
|
|
|
|
return elementPrefix + formattingMethod(formElement);
|
|
} else if (formElement.nodeValue) {
|
|
alert('What am I?');
|
|
return formElement.nodeValue;
|
|
}
|
|
|
|
console.error('Empty form element :(');
|
|
console.error(formElement);
|
|
}
|
|
|
|
/**
|
|
* Format a label-wrapped element
|
|
*
|
|
* @param {HTMLElement} formElement
|
|
* @returns {string}
|
|
*/
|
|
static formatLabelWrappedElement(formElement) {
|
|
return formElement.outerHTML + '{n}';
|
|
}
|
|
|
|
/**
|
|
* Check whether an element has an other elements as children
|
|
*
|
|
* @param {HTMLElement} element
|
|
* @return {boolean}
|
|
*/
|
|
static hasHtmlChildren(element) {
|
|
const numChildren = element.childNodes.length;
|
|
for (let x = 0; x < numChildren; x++) {
|
|
const node = element.childNodes.item(x);
|
|
|
|
// Only count as a child if the node is an element
|
|
if (node.nodeType === ELEMENT_NODE) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Recursively format the form elements for better alignment
|
|
*
|
|
* @param {HTMLElement} formElement
|
|
* @param {number} level
|
|
* @return {string}
|
|
*/
|
|
static formatHtml(formElement, level = 0) {
|
|
const hasChildren = Format.hasHtmlChildren(formElement);
|
|
const isLabelWrapped = Format.isLabelWrapped(formElement);
|
|
|
|
let formattedHTML = (isLabelWrapped)
|
|
? Format.formatLabelWrappedElement(formElement)
|
|
: Format.formatElement(formElement, level);
|
|
|
|
// If there are no children, just return the formatted element
|
|
if (!hasChildren) {
|
|
return formattedHTML;
|
|
}
|
|
|
|
let children = Array.from(formElement.childNodes);
|
|
const rawChildren = formElement.innerHTML;
|
|
|
|
// Discard text nodes if they only contain whitespace
|
|
children = children.filter(node => (!(node.nodeType === TEXT_NODE || $whiteSpacePattern.test(node.nodeValue))));
|
|
|
|
level++;
|
|
let newChildrenHTML = children.reduce((prevHTML, node) => {
|
|
return prevHTML + Format.formatHtml(node, level);
|
|
}, '');
|
|
|
|
|
|
// Format those closing tags
|
|
if (level > 0 && hasChildren) {
|
|
newChildrenHTML += '{n}' + '{t}'.repeat(level - 1);
|
|
}
|
|
|
|
formattedHTML = formattedHTML.replace(rawChildren, newChildrenHTML);
|
|
|
|
return formattedHTML;
|
|
}
|
|
|
|
/**
|
|
* Check whether an element
|
|
*
|
|
* @param {Element|Element[]} labelElements
|
|
* @returns {boolean}
|
|
*/
|
|
static isLabelWrapped(labelElements) {
|
|
if (labelElements.length === 0) {
|
|
return false;
|
|
}
|
|
|
|
const labelElement = (Array.isArray(labelElements))
|
|
? labelElements[0]
|
|
: labelElements;
|
|
|
|
return $labelWrapPattern.test(labelElement.outerHTML);
|
|
}
|
|
}
|
|
|
|
export default Format;
|