11using System ;
2- using System . Collections ;
32using System . Collections . Generic ;
43using System . Linq ;
54using System . Text . RegularExpressions ;
6- using Newtonsoft . Json . Linq ;
7- using PuppeteerSharp . Helpers ;
85
96namespace PuppeteerSharp
107{
118 internal class CustomQueriesManager
129 {
1310 private static readonly string [ ] _customQuerySeparators = new [ ] { "=" , "/" } ;
14- private readonly Dictionary < string , PuppeteerQueryHandler > _internalQueryHandlers ;
15- private readonly Dictionary < string , PuppeteerQueryHandler > _queryHandlers = new ( ) ;
16- private readonly PuppeteerQueryHandler _pierceHandler = CreatePuppeteerQueryHandler ( new CustomQueryHandler
11+ private readonly Dictionary < string , PuppeteerQueryHandler > _internalQueryHandlers = new ( )
1712 {
18- QueryOne = @"(element, selector) => {
19- let found = null;
20- const search = (root) => {
21- const iter = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
22- do {
23- const currentNode = iter.currentNode;
24- if (currentNode.shadowRoot) {
25- search(currentNode.shadowRoot);
26- }
27- if (currentNode instanceof ShadowRoot) {
28- continue;
29- }
30- if (currentNode !== root && !found && currentNode.matches(selector)) {
31- found = currentNode;
32- }
33- } while (!found && iter.nextNode());
34- };
35- if (element instanceof Document) {
36- element = element.documentElement;
37- }
38- search(element);
39- return found;
40- }" ,
41- QueryAll = @"(element, selector) => {
42- const result = [];
43- const collect = (root) => {
44- const iter = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
45- do {
46- const currentNode = iter.currentNode;
47- if (currentNode.shadowRoot) {
48- collect(currentNode.shadowRoot);
49- }
50- if (currentNode instanceof ShadowRoot) {
51- continue;
52- }
53- if (currentNode !== root && currentNode.matches(selector)) {
54- result.push(currentNode);
55- }
56- } while (iter.nextNode());
57- };
58- if (element instanceof Document) {
59- element = element.documentElement;
60- }
61- collect(element);
62- return result;
63- }" ,
64- } ) ;
13+ [ "aria" ] = AriaQueryHandlerFactory . Create ( ) ,
14+ [ "pierce" ] = CreatePierceHandler ( ) ,
15+ [ "text" ] = CreateTextQueryHandler ( ) ,
16+ [ "xpath" ] = CreateXpathHandler ( ) ,
17+ } ;
18+
19+ private readonly Dictionary < string , PuppeteerQueryHandler > _queryHandlers = new ( ) ;
6520
66- private readonly PuppeteerQueryHandler _ariaHandler = AriaQueryHandlerFactory . Create ( ) ;
6721 private readonly Regex _customQueryHandlerNameRegex = new ( "[a-zA-Z]+$" , RegexOptions . Compiled ) ;
6822 private readonly PuppeteerQueryHandler _defaultHandler = CreatePuppeteerQueryHandler ( new CustomQueryHandler
6923 {
7024 QueryOne = "(element, selector) => element.querySelector(selector)" ,
7125 QueryAll = "(element, selector) => element.querySelectorAll(selector)" ,
7226 } ) ;
7327
74- private readonly PuppeteerQueryHandler _textQueryHandler = CreatePuppeteerQueryHandler ( new CustomQueryHandler
75- {
76- QueryOne = @"(element, selector, {createTextContent}) => {
77- const search = (root)=> {
78- for (const node of root.childNodes) {
79- if (node instanceof Element) {
80- let matchedNode;
81- if (node.shadowRoot) {
82- matchedNode = search(node.shadowRoot);
83- } else {
84- matchedNode = search(node);
85- }
86- if (matchedNode) {
87- return matchedNode;
88- }
89- }
90- }
91- const textContent = createTextContent(root);
92- if (textContent.full.includes(selector)) {
93- return root;
94- }
95- return null;
96- };
97- return search(element);
98- }" ,
99- QueryAll = @"(element, selector, {createTextContent}) => {
100- const search = (root) => {
101- let results = [];
102- for (const node of root.childNodes) {
103- if (node instanceof Element) {
104- let matchedNodes;
105- if (node.shadowRoot) {
106- matchedNodes = search(node.shadowRoot);
107- } else {
108- matchedNodes = search(node);
109- }
110- results = results.concat(matchedNodes);
111- }
112- }
113- if (results.length > 0) {
114- return results;
115- }
116-
117- const textContent = createTextContent(root);
118- if (textContent.full.includes(selector)) {
119- return [root];
120- }
121- return [];
122- };
123- return search(element);
124- }" ,
125- } ) ;
126-
127- private readonly PuppeteerQueryHandler _xpathHandler = CreatePuppeteerQueryHandler ( new CustomQueryHandler
128- {
129- QueryOne = @"(element, selector) => {
130- const doc = element.ownerDocument || document;
131- const result = doc.evaluate(
132- selector,
133- element,
134- null,
135- XPathResult.FIRST_ORDERED_NODE_TYPE
136- );
137- return result.singleNodeValue;
138- }" ,
139- QueryAll = @"(element, selector) => {
140- const doc = element.ownerDocument || document;
141- const iterator = doc.evaluate(
142- selector,
143- element,
144- null,
145- XPathResult.ORDERED_NODE_ITERATOR_TYPE
146- );
147- const array = [];
148- let item;
149- while ((item = iterator.iterateNext())) {
150- array.push(item);
151- }
152- return array;
153- },
154- })" ,
155- } ) ;
156-
157- public CustomQueriesManager ( )
158- {
159- _internalQueryHandlers = new ( )
160- {
161- [ "aria" ] = _ariaHandler ,
162- [ "pierce" ] = _pierceHandler ,
163- [ "text" ] = _textQueryHandler ,
164- [ "xpath" ] = _xpathHandler ,
165- } ;
166- }
167-
16828 internal void RegisterCustomQueryHandler ( string name , CustomQueryHandler queryHandler )
16929 {
17030 if ( _internalQueryHandlers . ContainsKey ( name ) )
@@ -194,16 +54,16 @@ internal void RegisterCustomQueryHandler(string name, CustomQueryHandler queryHa
19454
19555 foreach ( var kv in handlers )
19656 {
197- foreach ( var separator in _customQuerySeparators )
198- {
199- var prefix = $ "{ kv . Key } { separator } ";
200-
201- if ( selector . StartsWith ( prefix , StringComparison . OrdinalIgnoreCase ) )
57+ foreach ( var separator in _customQuerySeparators )
20258 {
203- selector = selector . Substring ( prefix . Length ) ;
204- return ( selector , kv . Value ) ;
59+ var prefix = $ "{ kv . Key } { separator } ";
60+
61+ if ( selector . StartsWith ( prefix , StringComparison . OrdinalIgnoreCase ) )
62+ {
63+ selector = selector . Substring ( prefix . Length ) ;
64+ return ( selector , kv . Value ) ;
65+ }
20566 }
206- }
20767 }
20868
20969 return ( selector , _defaultHandler ) ;
@@ -223,6 +83,142 @@ internal void ClearCustomQueryHandlers()
22383 }
22484 }
22585
86+ private static PuppeteerQueryHandler CreatePierceHandler ( ) =>
87+ CreatePuppeteerQueryHandler ( new CustomQueryHandler
88+ {
89+ QueryOne = @"(element, selector) => {
90+ let found = null;
91+ const search = (root) => {
92+ const iter = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
93+ do {
94+ const currentNode = iter.currentNode;
95+ if (currentNode.shadowRoot) {
96+ search(currentNode.shadowRoot);
97+ }
98+ if (currentNode instanceof ShadowRoot) {
99+ continue;
100+ }
101+ if (currentNode !== root && !found && currentNode.matches(selector)) {
102+ found = currentNode;
103+ }
104+ } while (!found && iter.nextNode());
105+ };
106+ if (element instanceof Document) {
107+ element = element.documentElement;
108+ }
109+ search(element);
110+ return found;
111+ }" ,
112+ QueryAll = @"(element, selector) => {
113+ const result = [];
114+ const collect = (root) => {
115+ const iter = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
116+ do {
117+ const currentNode = iter.currentNode;
118+ if (currentNode.shadowRoot) {
119+ collect(currentNode.shadowRoot);
120+ }
121+ if (currentNode instanceof ShadowRoot) {
122+ continue;
123+ }
124+ if (currentNode !== root && currentNode.matches(selector)) {
125+ result.push(currentNode);
126+ }
127+ } while (iter.nextNode());
128+ };
129+ if (element instanceof Document) {
130+ element = element.documentElement;
131+ }
132+ collect(element);
133+ return result;
134+ }" ,
135+ } ) ;
136+
137+ private static PuppeteerQueryHandler CreateTextQueryHandler ( ) =>
138+ CreatePuppeteerQueryHandler ( new CustomQueryHandler
139+ {
140+ QueryOne = @"(element, selector, {createTextContent}) => {
141+ const search = (root)=> {
142+ for (const node of root.childNodes) {
143+ if (node instanceof Element) {
144+ let matchedNode;
145+ if (node.shadowRoot) {
146+ matchedNode = search(node.shadowRoot);
147+ } else {
148+ matchedNode = search(node);
149+ }
150+ if (matchedNode) {
151+ return matchedNode;
152+ }
153+ }
154+ }
155+ const textContent = createTextContent(root);
156+ if (textContent.full.includes(selector)) {
157+ return root;
158+ }
159+ return null;
160+ };
161+ return search(element);
162+ }" ,
163+ QueryAll = @"(element, selector, {createTextContent}) => {
164+ const search = (root) => {
165+ let results = [];
166+ for (const node of root.childNodes) {
167+ if (node instanceof Element) {
168+ let matchedNodes;
169+ if (node.shadowRoot) {
170+ matchedNodes = search(node.shadowRoot);
171+ } else {
172+ matchedNodes = search(node);
173+ }
174+ results = results.concat(matchedNodes);
175+ }
176+ }
177+ if (results.length > 0) {
178+ return results;
179+ }
180+
181+ const textContent = createTextContent(root);
182+ if (textContent.full.includes(selector)) {
183+ return [root];
184+ }
185+ return [];
186+ };
187+ return search(element);
188+ }" ,
189+ } ) ;
190+
191+ private static PuppeteerQueryHandler CreateXpathHandler ( ) =>
192+ CreatePuppeteerQueryHandler ( new CustomQueryHandler
193+ {
194+ QueryOne = @"(element, selector) => {
195+ const doc = element.ownerDocument || document;
196+ const result = doc.evaluate(
197+ selector,
198+ element,
199+ null,
200+ XPathResult.FIRST_ORDERED_NODE_TYPE
201+ );
202+ return result.singleNodeValue;
203+ }" ,
204+ QueryAll = @"(element, selector) => {
205+ const doc = element.ownerDocument || document;
206+ const iterator = doc.evaluate(
207+ selector,
208+ element,
209+ null,
210+ XPathResult.ORDERED_NODE_ITERATOR_TYPE
211+ );
212+ const array = [];
213+ let item;
214+ while ((item = iterator.iterateNext())) {
215+ array.push(item);
216+ }
217+ return array;
218+ },
219+ })" ,
220+ } ) ;
221+
226222 private static PuppeteerQueryHandler CreatePuppeteerQueryHandler ( CustomQueryHandler handler )
227223 {
228224 var internalHandler = new PuppeteerQueryHandler ( ) ;
0 commit comments