als-document 0.12.0 → 1.0.0-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,30 @@
1
+ class SingleNode extends Node {
2
+ constructor(tagName, attributes = {}, parent = null) {
3
+ if(attributes['?'] && tagName === '?xml') delete attributes['?']
4
+ super(tagName, attributes, parent);
5
+ this.isSingle = true
6
+ }
7
+
8
+ get outerHTML() { // Переопределение outerHTML для одиночного узла
9
+ if (this.tagName === "#cdata-section") return `<![CDATA[${this.textContent}]]>`;
10
+ const attrs = Object.entries(this.attributes).map(([key, val]) => `${key}="${val}"`).join(" ");
11
+ return `<${this.tagName} ${attrs}${this.tagName === '?xml' ? '?' : ''}>`;
12
+ }
13
+ // Удаляем методы и свойства, которые не имеют смысла для одиночных узлов
14
+ get innerHTML() { return ""; }
15
+ set innerHTML(_) { }
16
+ $(_) {return null}
17
+ $$(_) {return []}
18
+ querySelectorAll(_) { return []; }
19
+ querySelector(_) { return null; }
20
+ getElementsByClassName(_) { return []; }
21
+ getElementsByTagName(_) { return []; }
22
+ getElementById(_) { return null; }
23
+ get children() { return []; }
24
+ insertAdjacentElement(_, __) { }
25
+ insertAdjacentHTML(_, __) { }
26
+ insertAdjacentText(_, __) { }
27
+ appendChild(_) { }
28
+ get textContent() { return ""; }
29
+ set textContent(_) { }
30
+ }
@@ -0,0 +1,26 @@
1
+ function buildStyle(attributes) {
2
+ const styles = attributes.style || "";
3
+
4
+ const camelToKebab = str => str.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase();
5
+ const kebabToCamel = str => str.replace(/-([a-z])/g, g => g[1].toUpperCase());
6
+
7
+ const baseStyleObj = styles.split(";").reduce((acc, style) => {
8
+ const [key, value] = style.split(":").map(s => s.trim());
9
+ if (key && value) acc[kebabToCamel(key)] = value;
10
+ return acc;
11
+ }, {});
12
+
13
+ return new Proxy(baseStyleObj, {
14
+ get: (obj, prop) => obj[camelToKebab(prop)] || obj[prop],
15
+ set: (obj, prop, value) => {
16
+ obj[camelToKebab(prop)] = value;
17
+ attributes.style = Object.entries(obj).map(([k, v]) => `${camelToKebab(k)}: ${v}`).join("; ");
18
+ return true;
19
+ },
20
+ deleteProperty: (obj, prop) => {
21
+ delete obj[camelToKebab(prop)];
22
+ attributes.style = Object.entries(obj).map(([k, v]) => `${camelToKebab(k)}: ${v}`).join("; ");
23
+ return true;
24
+ }
25
+ });
26
+ }
@@ -0,0 +1,9 @@
1
+ class TextNode {
2
+ constructor(data) {
3
+ this.nodeName = '#text';
4
+ this.parent = null;
5
+ this.textContent = data;
6
+ }
7
+ get nodeValue() {return this.textContent}
8
+ get parentNode() {return this.parent}
9
+ }
@@ -0,0 +1,36 @@
1
+ function parseAttributes(str) {
2
+ const attrs = {};
3
+ let key = "";
4
+ let value = "";
5
+ let isKey = true;
6
+ let quoteChar = null;
7
+
8
+ for (let i = 0; i < str.length; i++) {
9
+ const char = str[i];
10
+
11
+ if (isKey && (char === '=' || char === ' ')) {
12
+ if (char === '=') isKey = false;
13
+ else if (key.trim()) { // We've found an attribute without a value
14
+ attrs[key.trim()] = true; // Set the value to true for boolean attributes
15
+ key = ""; // Reset the key
16
+ }
17
+ continue;
18
+ }
19
+
20
+ if (!quoteChar && (char === '"' || char === "'")) {
21
+ quoteChar = char;
22
+ continue; // we skip the quote itself
23
+ } else if (quoteChar && char === quoteChar) {
24
+ quoteChar = null;
25
+ attrs[key.trim()] = value.trim();
26
+ key = ""; value = ""; isKey = true;
27
+ continue;
28
+ }
29
+
30
+ if (isKey) key += char;
31
+ else value += char;
32
+ }
33
+
34
+ if (key.trim() && !value) attrs[key.trim()] = true; // After the loop, check if there's a leftover key, which would be an attribute without a value
35
+ return attrs;
36
+ }
@@ -0,0 +1,74 @@
1
+ function parseHTML(html) {
2
+ const root = new Node("ROOT");
3
+ const stack = [root];
4
+ let currentText = "", i = 0;
5
+
6
+ function parseSpecial(startStr, endStr, n1, n2, tag) {
7
+ if (!html.startsWith(startStr, i)) return false
8
+ const end = html.indexOf(endStr, i + n1);
9
+ const strNode = new Node(tag, {}, stack[stack.length - 1]);
10
+ strNode.childNodes.push(html.substring(i + n1, end));
11
+ i = end + n2;
12
+ return true
13
+ }
14
+
15
+ while (i < html.length) {
16
+ if (parseSpecial("<!--", "-->", 4, 3, '#comment')) continue
17
+ if (parseSpecial("<script", "</script>", 8, 9, 'script')) continue
18
+ if (parseSpecial("<style", "</style>", 7, 8, 'style')) continue
19
+
20
+ if (html.startsWith("<![CDATA[", i)) {
21
+ const end = html.indexOf("]]>", i + 9);
22
+ if (end === -1) break; // Убедитесь, что существует закрывающий тег
23
+ const content = html.substring(i + 9, end); // это содержимое CDATA
24
+ const cdataNode = new SingleNode("#cdata-section", {}, stack[stack.length - 1]);
25
+ cdataNode.nodeValue = content;
26
+ i = end + 3;
27
+ continue;
28
+ }
29
+
30
+ if (html.startsWith("<", i)) {
31
+ if (currentText.trim()) {
32
+ stack[stack.length - 1].childNodes.push(new TextNode(currentText.trim()));
33
+ currentText = "";
34
+ }
35
+
36
+ let tagEnd = i + 1;
37
+ let insideQuotes = false;
38
+ let quoteChar = null;
39
+
40
+ while (tagEnd < html.length) {
41
+ const char = html[tagEnd];
42
+ if (!insideQuotes && (char === '"' || char === "'")) {
43
+ insideQuotes = true;
44
+ quoteChar = char;
45
+ } else if (insideQuotes && char === quoteChar) {
46
+ insideQuotes = false;
47
+ quoteChar = null;
48
+ }
49
+
50
+ if (!insideQuotes && char === '>') break;
51
+ tagEnd++;
52
+ }
53
+
54
+ const tagContent = html.substring(i + 1, tagEnd);
55
+
56
+ if (tagContent.startsWith("/")) stack.pop(); // It is close tag
57
+ else { // It is open tag
58
+ let isSelfClosing = tagContent.endsWith('/');
59
+ const tagNameEnd = tagContent.search(/\s|>|\//); // add a check for "/"
60
+ const tagName = tagContent.substring(0, tagNameEnd > 0 ? tagNameEnd : tagEnd - i - 1);
61
+ const attributesString = tagContent.substring(tagName.length, isSelfClosing ? tagContent.length - 1 : tagContent.length).trim();
62
+ const attributes = parseAttributes(attributesString);
63
+ if (VOID_TAGS.has(tagName.toLowerCase()) || isSelfClosing) new SingleNode(tagName, attributes, stack[stack.length - 1])
64
+ else stack.push(new Node(tagName, attributes, stack[stack.length - 1]));
65
+ }
66
+ i = tagEnd + 1;
67
+ } else {
68
+ currentText += html[i];
69
+ i++;
70
+ }
71
+ }
72
+ if (currentText.trim()) stack[stack.length - 1].childNodes.push(new TextNode(currentText.trim()));
73
+ return root;
74
+ }
@@ -0,0 +1,5 @@
1
+ const VOID_TAGS = new Set([
2
+ "area", "base", "br", "col", "command", "embed", "hr", "img", "input",
3
+ "keygen", "link", "meta", "param", "source", "track", "wbr",
4
+ "!doctype",'?xml'
5
+ ]);
@@ -0,0 +1,84 @@
1
+ function checkElement(el,selector) {
2
+ if(selector == undefined) return true
3
+ if(el == null) return false
4
+ let {tag,classList,attributes,id,prev,ancestors,parents,prevAny} = selector
5
+ if(typeof el === 'string') return false
6
+ if(el.isSpecial) return false
7
+ if(id !== undefined && el.id === null) return false
8
+ if(id && id !== el.id) return false
9
+ if(tag && el.tagName === undefined) return false
10
+ else if(tag && tag !== el.tagName) return false
11
+ const clas = el.attributes.class
12
+ if(classList !== undefined && (clas === undefined || clas === '')) return false
13
+ else if(classList !== undefined) {
14
+ if(classList.every(e => el.classList.contains(e)) === false) return false
15
+ }
16
+ if(checkattributes(attributes,el) === false) return false
17
+ if(checkElement(el.prev,prev) === false) return false
18
+ if(checkAncestors(el.ancestors,ancestors) === false) return false
19
+ if(checkParents(el.ancestors,parents) === false) return false
20
+ if(el.parent) {
21
+ if(checkPrevAny(el.parent.children,el.childIndex,prevAny) == false) return false
22
+ }
23
+ return true
24
+ }
25
+
26
+ function checkattributes(attributes=[],el) {
27
+ let elattributes = el.attributes
28
+ let names = Object.keys(elattributes)
29
+ let passedTests = 0
30
+ if(attributes) for(let i=0; i<attributes.length; i++) {
31
+ let {name,value,check} = attributes[i]
32
+ if(name == 'inner' && value !== undefined && check && el.inner) {
33
+ if(check(el.inner)) passedTests++
34
+ }
35
+
36
+ if(!names.includes(name)) continue
37
+ else if(value == undefined) passedTests++
38
+ else if(value && elattributes[name]) {
39
+ if(check(elattributes[name]) == false) continue
40
+ else passedTests++
41
+ }
42
+ }
43
+ if(passedTests == attributes.length) return true
44
+ else return false
45
+ }
46
+
47
+ function checkPrevAny(children=[],index,prevAny) {
48
+ let size = children.length
49
+ if((size == 0 || index == 0) && prevAny) return false
50
+ for(let i=index; i>=0; i--) {
51
+ if(checkElement(children[i],prevAny)) return true
52
+ }
53
+ return false
54
+ }
55
+
56
+ function checkAncestors(ancestors=[],selectorAncestors=[]) {
57
+ let count = 0
58
+ if(selectorAncestors.length == 0) return true
59
+ let endIndex = ancestors.length-1
60
+ let selectorIndex = selectorAncestors.length-1
61
+ while(selectorIndex>=0) {
62
+ for(let i=endIndex; i>=0; i--) {
63
+ endIndex=i-1
64
+ if(checkElement(ancestors[i],selectorAncestors[selectorIndex]) == true) {
65
+ count++
66
+ break
67
+ }
68
+ }
69
+ selectorIndex--
70
+ }
71
+ if(count == selectorAncestors.length) return true
72
+ else return false
73
+ }
74
+
75
+ function checkParents(ancestors=[],selectorParents=[]) {
76
+ if(selectorParents.length === 0) return true
77
+ if(ancestors.length < selectorParents.length) return false
78
+ let index = ancestors.length-1
79
+ for(let i=selectorParents.length-1; i>=0; i--) {
80
+ if(checkElement(ancestors[index],selectorParents[i]) === false) return false
81
+ index--
82
+ }
83
+ return true
84
+ }
@@ -0,0 +1,142 @@
1
+ class Query {
2
+ static get(query) {
3
+ let q = new Query(query)
4
+ return q.selectors
5
+ }
6
+
7
+ constructor(query) {
8
+ this.query = query
9
+ this.selectors = []
10
+ this.stringValues = [];
11
+ this.parseSelectors(query.split(','))
12
+ }
13
+
14
+ parseSelectors(selectors) {
15
+ selectors.forEach(selector => {
16
+ let originalSelector = selector.trim()
17
+ selector = this.removeSpaces(selector)
18
+ this.stringValues = []
19
+ selector = selector.replace(/\[.*?\]/g, (value) => {
20
+ this.stringValues.push(value)
21
+ return `[${this.stringValues.length - 1}]`
22
+ })
23
+ let [element, ancestors] = this.splitAndCutLast(selector, ' ') // \s - ancestor
24
+ element = this.getFamily(element)
25
+ if (ancestors.length > 0)
26
+ element.ancestors = ancestors.map(ancestor => this.getFamily(ancestor))
27
+ element.group = originalSelector
28
+ this.selectors.push(element)
29
+ });
30
+ }
31
+
32
+ splitAndCutLast(string, splitBy) {
33
+ const array = string.split(splitBy);
34
+ const last = array.pop(); // pop() удаляет и возвращает последний элемент массива
35
+ return [last, array];
36
+ }
37
+
38
+ getFamily(group, element, prev, prevAny, sign) {
39
+ if (group.match(/\~|\+/) !== null) {
40
+ let [last, prevBrothers] = this.splitAndCutLast(group, /\~|\+/)
41
+ let signs = group.replace(last, '')
42
+ prevBrothers.forEach(el => signs = signs.replace(el, ''))
43
+ signs = signs.match(/\~|\+/g)
44
+ if (signs.length == 1) {
45
+ sign = signs[0]
46
+ } else if (signs.length > 1) {
47
+ sign = signs.splice(signs.length - 1, signs.length - 1)[0]
48
+ prevBrothers[0] = prevBrothers.map((b, i) => {
49
+ if (i < prevBrothers.length - 1) b += signs[i]
50
+ return b
51
+ }).join('')
52
+ prevBrothers[0] = this.getFamily(prevBrothers[0])
53
+ }
54
+ if (sign == '~') prevAny = prevBrothers[0] // ~ - any prev brother
55
+ else if (sign == '+') prev = prevBrothers[0] // + - prev brother
56
+ element = last
57
+ } else element = group
58
+ let family
59
+ if (prev || prevAny) {
60
+ family = this.getParents(element)
61
+ if (prev) family.prev = this.getParents(prev)
62
+ if (prevAny) family.prevAny = this.getParents(prevAny)
63
+ } else family = this.getParents(element)
64
+ if (family.query !== group) family.group = group
65
+ return family
66
+ }
67
+
68
+ getParents(selector) {
69
+ if (typeof selector == 'string') {
70
+ let [element, parents] = this.splitAndCutLast(selector, '>')
71
+ element = this.buildElement(element)
72
+ parents = parents.map(parent => this.buildElement(parent))
73
+ if (parents.length > 0) element.parents = parents
74
+ return element
75
+ } else return selector
76
+ }
77
+
78
+ buildElement(element, id = null, tag = null, classList = []) {
79
+ let query = element
80
+ element = element.replace(/\#(\w-?)*/, $id => {
81
+ id = $id.replace(/^\#/, ''); return ''
82
+ })
83
+ element = element.replace(/\.(\w-?)*/, $class => {
84
+ classList.push($class.replace(/^\./, '')); return ''
85
+ })
86
+ element = element.replace(/(\w\:?-?)*/, $tag => {
87
+ tag = $tag == '' ? null : $tag; return ''
88
+ })
89
+ let attribs = this.getAttributes(element)
90
+ element = { query }
91
+ if (id) element.id = id
92
+ if (tag) element.tag = tag
93
+ if (classList.length > 0) element.classList = classList
94
+ if (attribs.length > 0) element.attribs = attribs
95
+ return element
96
+ }
97
+
98
+ getAttributes(element) {
99
+ let attribs = this.stringValues.filter((value, index) => {
100
+ let searchValue = `[${index}]`
101
+ if (element.match(searchValue)) return true
102
+ else return false
103
+ })
104
+ attribs = attribs.map(attrib => {
105
+ let query = attrib
106
+ attrib = attrib.replace('[', '').replace(']', '')
107
+ let [name, value] = attrib.split(/[\~\|\^\$\*]?\=/)
108
+ let sign = attrib.replace(name, '').replace(value, '')
109
+ attrib = { query }
110
+ if (name) attrib.name = name
111
+ if (value) attrib.value = value.trim().replace(/^\"/, '').replace(/\"$/, '')
112
+ if (sign) {
113
+ attrib.sign = sign
114
+ attrib.check = this.getAttribFn(sign).bind(attrib)
115
+ }
116
+ return attrib
117
+ });
118
+ return attribs
119
+ }
120
+
121
+ getAttribFn(sign) {
122
+ if (sign == '=') return function (value) { return value === this.value }
123
+ if (sign == '*=') return function (value) { return value.includes(this.value) }
124
+ if (sign == '^=') return function (value) { return value.startsWith(this.value) }
125
+ if (sign == '$=') return function (value) { return value.endsWith(this.value) }
126
+ if (sign == '|=') return function (value) {
127
+ return value.trim().split(' ').length == 1
128
+ && (value.startsWith(this.value) || value.startsWith(this.value + '-'))
129
+ ? true : false
130
+ }
131
+ if (sign == '~=') return function (value) { // includes whole word only
132
+ return this.value.trim().split(' ').length == 1 && value.includes(this.value) ? true : false
133
+ }
134
+ }
135
+
136
+ removeSpaces(selector) {
137
+ selector = selector.replace(/\s{2}/g, ' ') // remove double spaces
138
+ selector = selector.replace(/\s?\^?\$?\|?\~?\*?\=\s*/g, (m) => m.trim()) // remove spaces inside []
139
+ selector = selector.replace(/\s?(\+|\~|\>)\s?/g, (m) => m.trim()) // remove spaces around +|~|>
140
+ return selector
141
+ }
142
+ }