forked from dequelabs/axe-core
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathget-element-source.js
More file actions
101 lines (91 loc) · 3.48 KB
/
Copy pathget-element-source.js
File metadata and controls
101 lines (91 loc) · 3.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import getNodeAttributes from './get-node-attributes';
import isXHTML from './is-xhtml';
/**
* Gets the truncated HTML source of an element, or nodeValue for non-element nodes
* @param {Node} node the DOM node (element, text, comment, etc.)
* @param {Object} options truncation options
* @param {Number} [options.maxLength=300] maximum length of the output
* @param {Number} [options.attrLimit=20] maximum length for attribute names and values
* @returns {String} The outerHTML, truncated representation, or nodeValue for non-elements
*/
export default function getElementSource(
node,
{ maxLength = 300, attrLimit = 20 } = {}
) {
if (!node) {
return '';
}
// non-element nodes
if (node.nodeType !== 1) {
const value = node.nodeValue ?? '';
return truncate(value, maxLength);
}
const deepStr = getOuterHtml(node);
if (deepStr.length > maxLength) {
return getTruncatedElementSource(node, { maxLength, attrLimit });
}
return deepStr;
}
/**
* Gets the outerHTML of an element, using XMLSerializer as fallback for SVG/MathML
* @param {Element} element the DOM element
* @returns {String} The serialized HTML or empty string
*/
function getOuterHtml(element) {
let source = element.outerHTML;
if (!source && typeof window.XMLSerializer === 'function') {
source = new window.XMLSerializer().serializeToString(element);
}
return source || '';
}
/**
* Builds a truncated HTML representation of an element when outerHTML exceeds maxLength.
* Note: attribute order may differ from the original source as node.attributes order is not guaranteed.
* @param {Element} elm the DOM element
* @param {Object} options truncation options
* @param {Number} options.maxLength maximum length of the output
* @param {Number} options.attrLimit maximum length for attribute names and values
* @returns {String} Truncated opening tag (e.g. '<div id="foo" ...>')
*/
function getTruncatedElementSource(elm, { maxLength, attrLimit }) {
const nodeName = isXHTML(elm.ownerDocument || document)
? elm.nodeName
: elm.nodeName.toLowerCase();
// Get a mutable attribute map, and work out their rendered length
const nodeAttrs = Array.from(getNodeAttributes(elm)).map(
({ name, value }) => ({ name, value })
);
const attrsLength = nodeAttrs.reduce((acc, { name, value }) => {
// 4 = space before name + equals sign + opening quote + closing quote
return acc + name.length + value.length + 4;
}, 0);
// 2 = opening "<" + space before first attribute
if (2 + nodeName.length + attrsLength > maxLength) {
nodeAttrs.forEach(attr => {
attr.name = truncate(attr.name, attrLimit);
attr.value = truncate(attr.value, attrLimit);
});
}
let source = `<${nodeName}`;
let tagEnd = '>';
const truncateEnd = ' ...>';
// Must check every attribute: an attr that doesn't fit may be followed by one that does
for (const attr of nodeAttrs) {
const attrStr = ` ${attr.name}="${attr.value}"`;
if (source.length + attrStr.length > maxLength - truncateEnd.length) {
tagEnd = truncateEnd;
continue;
}
source += attrStr;
}
return source + tagEnd;
}
/**
* Truncates a string to max length, appending '...' when truncated
* @param {String} str the string to truncate
* @param {Number} attrLimit maximum length before truncation
* @returns {String} The original string or truncated version with '...' suffix
*/
function truncate(str, attrLimit) {
return str.length <= attrLimit ? str : str.substring(0, attrLimit) + '...';
}