Enhance DOM extension methods with additional pseudo-selector handlers and improve nth-child logic

This commit is contained in:
Moustapha Kodjo Amadou 2025-10-30 14:24:43 +01:00
parent 1361315e3e
commit 93a17cc797

View file

@ -4,14 +4,86 @@ import 'package:mangayomi/utils/reg_exp_matcher.dart';
import 'package:xpath_selector_html_parser/xpath_selector_html_parser.dart';
void _initPseudoSelector() {
bool nthChild(Element element, String? args) {
if (int.tryParse(args!) != null) {
final parent = element.parentNode;
return parent != null &&
(int.parse(args) as num) > 0 &&
parent.nodes.indexOf(element) == int.parse(args);
(int, int) parseNth(String arg) {
arg = arg.toLowerCase().replaceAll(' ', '');
if (arg == 'odd') return (2, 1);
if (arg == 'even') return (2, 0);
final reg = RegExp(r'^(\d*)n([+-]?\d+)?$');
final match = reg.firstMatch(arg);
if (match != null) {
final aStr = match.group(1);
final a = aStr == null || aStr.isEmpty ? 1 : int.parse(aStr);
final bStr = match.group(2);
final b = bStr == null ? 0 : int.parse(bStr);
return (a, b);
}
return true;
final n = int.tryParse(arg);
if (n != null) return (0, n);
return (0, 0);
}
bool matchesNth(int index, int a, int b) {
if (a == 0) return index == b;
final diff = index - b;
return diff % a == 0 && diff ~/ a >= 0;
}
String getWholeText(Element element) {
return element.nodes.map((node) {
if (node is Text) return node.text;
if (node is Element) return getWholeText(node);
return '';
}).join();
}
String getWholeOwnText(Element element) {
return element.nodes.whereType<Text>().map((t) => t.text).join();
}
bool nthChild(Element element, String? args) {
if (args == null) return false;
final parent = element.parent;
if (parent == null) return false;
final siblings = parent.children;
final index = siblings.indexOf(element) + 1; // 1-based
final (a, b) = parseNth(args);
return matchesNth(index, a, b);
}
bool nthLastChild(Element element, String? args) {
if (args == null) return false;
final parent = element.parent;
if (parent == null) return false;
final siblings = parent.children;
final index =
siblings.length - siblings.indexOf(element); // 1-based from end
final (a, b) = parseNth(args);
return matchesNth(index, a, b);
}
bool nthOfType(Element element, String? args) {
if (args == null) return false;
final parent = element.parent;
if (parent == null) return false;
final siblings = parent.children
.where((e) => e.localName == element.localName)
.toList();
final index = siblings.indexOf(element) + 1; // 1-based
final (a, b) = parseNth(args);
return matchesNth(index, a, b);
}
bool nthLastOfType(Element element, String? args) {
if (args == null) return false;
final parent = element.parent;
if (parent == null) return false;
final siblings = parent.children
.where((e) => e.localName == element.localName)
.toList();
final index =
siblings.length - siblings.indexOf(element); // 1-based from end
final (a, b) = parseNth(args);
return matchesNth(index, a, b);
}
bool has(Element element, String? args) {
@ -34,7 +106,74 @@ void _initPseudoSelector() {
bool contains(Element element, String? args) {
final text = args ?? '';
return element.text.contains(text);
return element.text.toLowerCase().contains(text.toLowerCase());
}
bool containsOwn(Element element, String? args) {
final text = args ?? '';
final ownText = element.nodes.whereType<Text>().map((t) => t.text).join();
return ownText.toLowerCase().contains(text.toLowerCase());
}
bool matches(Element element, String? args) {
if (args == null) return false;
try {
final reg = RegExp(args, caseSensitive: false);
return reg.hasMatch(element.text);
} catch (e) {
return false;
}
}
bool containsData(Element element, String? args) {
final data = args ?? '';
// For script and style elements
if (element.localName == 'script' || element.localName == 'style') {
return element.text.toLowerCase().contains(data.toLowerCase());
}
return false;
}
bool containsWholeText(Element element, String? args) {
final text = args ?? '';
return getWholeText(element).contains(text);
}
bool containsWholeOwnText(Element element, String? args) {
final text = args ?? '';
return getWholeOwnText(element).contains(text);
}
bool matchesWholeText(Element element, String? args) {
if (args == null) return false;
try {
final reg = RegExp(args);
return reg.hasMatch(getWholeText(element));
} catch (e) {
return false;
}
}
bool matchesWholeOwnText(Element element, String? args) {
if (args == null) return false;
try {
final reg = RegExp(args);
return reg.hasMatch(getWholeOwnText(element));
} catch (e) {
return false;
}
}
bool isSelector(Element element, String? args) {
if (args == null) return false;
final selectors = args.split(',').map((s) => s.trim()).toList();
for (final sel in selectors) {
try {
final parsed = pseudom.parse(sel);
if (parsed.selectFirst(element) != null) return true;
} catch (_) {}
}
return false;
}
bool firstChild(Element element, String? args) {
@ -45,17 +184,103 @@ void _initPseudoSelector() {
return element.nextElementSibling == null;
}
bool firstOfType(Element element, String? args) {
final parent = element.parent;
if (parent == null) return false;
final siblings = parent.children.where(
(e) => e.localName == element.localName,
);
return siblings.first == element;
}
bool lastOfType(Element element, String? args) {
final parent = element.parent;
if (parent == null) return false;
final siblings = parent.children.where(
(e) => e.localName == element.localName,
);
return siblings.last == element;
}
bool onlyChild(Element element, String? args) {
return element.nextElementSibling == null;
return element.previousElementSibling == null &&
element.nextElementSibling == null;
}
bool onlyOfType(Element element, String? args) {
final parent = element.parent;
if (parent == null) return false;
final siblings = parent.children.where(
(e) => e.localName == element.localName,
);
return siblings.length == 1;
}
bool empty(Element element, String? args) {
return element.children.isEmpty && element.text.trim().isEmpty;
}
bool root(Element element, String? args) {
return element.parent == null;
}
bool lt(Element element, String? args) {
if (args == null) return false;
final n = int.tryParse(args);
if (n == null) return false;
final parent = element.parent;
if (parent == null) return false;
final index = parent.children.indexOf(element);
return index < n;
}
bool gt(Element element, String? args) {
if (args == null) return false;
final n = int.tryParse(args);
if (n == null) return false;
final parent = element.parent;
if (parent == null) return false;
final index = parent.children.indexOf(element);
return index > n;
}
bool eq(Element element, String? args) {
if (args == null) return false;
final n = int.tryParse(args);
if (n == null) return false;
final parent = element.parent;
if (parent == null) return false;
final index = parent.children.indexOf(element);
return index == n;
}
pseudom.PseudoSelector.handlers['nth-child'] = nthChild;
pseudom.PseudoSelector.handlers['nth-last-child'] = nthLastChild;
pseudom.PseudoSelector.handlers['nth-of-type'] = nthOfType;
pseudom.PseudoSelector.handlers['nth-last-of-type'] = nthLastOfType;
pseudom.PseudoSelector.handlers['has'] = has;
pseudom.PseudoSelector.handlers['inot'] = inot;
pseudom.PseudoSelector.handlers['contains'] = contains;
pseudom.PseudoSelector.handlers['containsOwn'] = containsOwn;
pseudom.PseudoSelector.handlers['containsData'] = containsData;
pseudom.PseudoSelector.handlers['containsWholeText'] = containsWholeText;
pseudom.PseudoSelector.handlers['containsWholeOwnText'] =
containsWholeOwnText;
pseudom.PseudoSelector.handlers['matches'] = matches;
pseudom.PseudoSelector.handlers['matchesWholeText'] = matchesWholeText;
pseudom.PseudoSelector.handlers['matchesWholeOwnText'] = matchesWholeOwnText;
pseudom.PseudoSelector.handlers['is'] = isSelector;
pseudom.PseudoSelector.handlers['last-child'] = lastChild;
pseudom.PseudoSelector.handlers['first-child'] = firstChild;
pseudom.PseudoSelector.handlers['first-of-type'] = firstOfType;
pseudom.PseudoSelector.handlers['last-of-type'] = lastOfType;
pseudom.PseudoSelector.handlers['only-child'] = onlyChild;
pseudom.PseudoSelector.handlers['only-of-type'] = onlyOfType;
pseudom.PseudoSelector.handlers['empty'] = empty;
pseudom.PseudoSelector.handlers['root'] = root;
pseudom.PseudoSelector.handlers['lt'] = lt;
pseudom.PseudoSelector.handlers['gt'] = gt;
pseudom.PseudoSelector.handlers['eq'] = eq;
}
String _fixSelector(String selector) {