1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit.html.parser.neko;
16
17 import java.io.IOException;
18 import java.io.InputStream;
19 import java.io.StringReader;
20 import java.net.URL;
21 import java.nio.charset.Charset;
22 import java.util.ArrayList;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.concurrent.ConcurrentHashMap;
26
27 import org.htmlunit.ObjectInstantiationException;
28 import org.htmlunit.Page;
29 import org.htmlunit.SgmlPage;
30 import org.htmlunit.WebAssert;
31 import org.htmlunit.WebClient;
32 import org.htmlunit.WebResponse;
33 import org.htmlunit.cyberneko.HTMLScanner;
34 import org.htmlunit.cyberneko.HTMLTagBalancer;
35 import org.htmlunit.cyberneko.xerces.util.DefaultErrorHandler;
36 import org.htmlunit.cyberneko.xerces.xni.QName;
37 import org.htmlunit.cyberneko.xerces.xni.XNIException;
38 import org.htmlunit.cyberneko.xerces.xni.parser.XMLErrorHandler;
39 import org.htmlunit.cyberneko.xerces.xni.parser.XMLInputSource;
40 import org.htmlunit.cyberneko.xerces.xni.parser.XMLParseException;
41 import org.htmlunit.html.DefaultElementFactory;
42 import org.htmlunit.html.DomNode;
43 import org.htmlunit.html.ElementFactory;
44 import org.htmlunit.html.Html;
45 import org.htmlunit.html.HtmlPage;
46 import org.htmlunit.html.HtmlSvg;
47 import org.htmlunit.html.UnknownElementFactory;
48 import org.htmlunit.html.XHtmlPage;
49 import org.htmlunit.html.parser.HTMLParser;
50 import org.htmlunit.html.parser.HTMLParserListener;
51 import org.htmlunit.svg.SvgElementFactory;
52 import org.htmlunit.svg.SvgForeignObject;
53 import org.htmlunit.util.StringUtils;
54 import org.w3c.dom.Node;
55 import org.xml.sax.SAXException;
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72 public final class HtmlUnitNekoHtmlParser implements HTMLParser {
73
74
75
76
77 public static final SvgElementFactory SVG_FACTORY = new SvgElementFactory();
78
79 private static final Map<String, ElementFactory> ELEMENT_FACTORIES = new ConcurrentHashMap<>();
80
81 static {
82 final DefaultElementFactory defaultElementFactory = new DefaultElementFactory();
83 for (final String tagName : DefaultElementFactory.SUPPORTED_TAGS_) {
84 ELEMENT_FACTORIES.put(tagName, defaultElementFactory);
85 }
86 }
87
88
89
90
91 @Override
92 public void parseFragment(final WebClient webClient, final DomNode parent, final DomNode context,
93 final String source, final boolean createdByJavascript)
94 throws SAXException, IOException {
95 final Page page = parent.getPage();
96 if (!(page instanceof HtmlPage htmlPage)) {
97 return;
98 }
99 final URL url = htmlPage.getUrl();
100
101 final HtmlUnitNekoDOMBuilder domBuilder =
102 new HtmlUnitNekoDOMBuilder(this, webClient, parent, url, source, createdByJavascript);
103 domBuilder.setFeature("http://cyberneko.org/html/features/balance-tags/document-fragment", true);
104
105 domBuilder.setInsideSvg(isSvgContext(context));
106
107 final boolean xhtml = page instanceof XHtmlPage;
108 domBuilder.setFeature(HTMLScanner.ALLOW_SELFCLOSING_TAGS, xhtml);
109 domBuilder.setFeature(HTMLScanner.ALLOW_SELFCLOSING_IFRAME, xhtml);
110 domBuilder.setFeature(HTMLScanner.ALLOW_SELFCLOSING_SCRIPT, xhtml);
111
112
113 DomNode node = context;
114 final List<QName> ancestors = new ArrayList<>();
115 while (node != null && node.getNodeType() != Node.DOCUMENT_NODE) {
116 ancestors.add(0, new QName(null, node.getNodeName(), null, null));
117 node = node.getParentNode();
118 }
119 if (ancestors.isEmpty() || !"html".equals(ancestors.get(0).getLocalpart())) {
120 ancestors.add(new QName(null, "html", null, null));
121 ancestors.add(new QName(null, "body", null, null));
122 }
123 else if (ancestors.size() == 1
124 || (!"body".equals(ancestors.get(1).getLocalpart())
125 && !"head".equals(ancestors.get(1).getLocalpart()))) {
126 ancestors.add(new QName(null, "body", null, null));
127 }
128
129 domBuilder.setProperty(HTMLTagBalancer.FRAGMENT_CONTEXT_STACK, ancestors.toArray(new QName[0]));
130
131 final XMLInputSource in = new XMLInputSource(null, url.toString(), null, new StringReader(source), null);
132
133 htmlPage.registerParsingStart();
134 htmlPage.registerSnippetParsingStart();
135 try {
136 domBuilder.parse(in);
137 }
138 finally {
139 htmlPage.registerParsingEnd();
140 htmlPage.registerSnippetParsingEnd();
141 }
142 }
143
144
145
146
147 @Override
148 public void parse(final WebClient webClient, final WebResponse webResponse, final HtmlPage page,
149 final boolean xhtml, final boolean createdByJavascript) throws IOException {
150 final URL url = webResponse.getWebRequest().getUrl();
151 final HtmlUnitNekoDOMBuilder domBuilder =
152 new HtmlUnitNekoDOMBuilder(this, webClient, page, url, null, createdByJavascript);
153
154 final Charset charset = webResponse.getContentCharset();
155 try {
156 if (!webResponse.wasContentCharsetTentative()) {
157
158 domBuilder.setFeature(HTMLScanner.IGNORE_SPECIFIED_CHARSET, true);
159 }
160
161
162 if (xhtml) {
163 domBuilder.setFeature(HTMLScanner.ALLOW_SELFCLOSING_TAGS, true);
164 domBuilder.setFeature(HTMLScanner.ALLOW_SELFCLOSING_IFRAME, true);
165 domBuilder.setFeature(HTMLScanner.ALLOW_SELFCLOSING_SCRIPT, true);
166
167 domBuilder.setFeature(HTMLScanner.SCRIPT_STRIP_CDATA_DELIMS, true);
168 domBuilder.setFeature(HTMLScanner.STYLE_STRIP_CDATA_DELIMS, true);
169 domBuilder.setFeature(HTMLScanner.CDATA_SECTIONS, true);
170 domBuilder.setFeature(HTMLScanner.CDATA_EARLY_CLOSING, false);
171 }
172
173 final int bufferSize = webClient.getOptions().getNekoReaderBufferSize();
174 if (bufferSize > 0) {
175 domBuilder.setProperty(HTMLScanner.READER_BUFFER_SIZE, bufferSize);
176 }
177 }
178 catch (final Exception e) {
179 throw new ObjectInstantiationException("Error setting HTML parser feature", e);
180 }
181
182 try (InputStream content = webResponse.getContentAsStream()) {
183 final String encoding = charset.name();
184 final XMLInputSource in = new XMLInputSource(null, url.toString(), null, content, encoding);
185
186 page.registerParsingStart();
187 try {
188 domBuilder.parse(in);
189 }
190 catch (final XNIException e) {
191
192 final Throwable origin = extractNestedException(e);
193 throw new RuntimeException("Failed parsing content from " + url, origin);
194 }
195 }
196 finally {
197 page.registerParsingEnd();
198 }
199 }
200
201
202
203
204
205
206
207 static Throwable extractNestedException(final Throwable e) {
208 Throwable originalException;
209 Throwable cause = e;
210 do {
211 originalException = cause;
212
213 if (cause instanceof XNIException) {
214 cause = cause.getCause();
215 }
216 else {
217 cause = null;
218 }
219 }
220 while (cause != null);
221
222 return originalException;
223 }
224
225
226
227
228 @Override
229 public ElementFactory getSvgFactory() {
230 return SVG_FACTORY;
231 }
232
233
234
235
236 @Override
237 public ElementFactory getFactory(final String tagName) {
238 final ElementFactory result = ELEMENT_FACTORIES.get(tagName);
239
240 if (result != null) {
241 return result;
242 }
243 return UnknownElementFactory.INSTANCE;
244 }
245
246
247
248
249
250
251
252
253 private static boolean isSvgContext(final DomNode context) {
254 DomNode node = context;
255 while (node != null) {
256 if (node instanceof SvgForeignObject) {
257 return false;
258 }
259 if (node instanceof HtmlSvg) {
260 return true;
261 }
262 node = node.getParentNode();
263 }
264 return false;
265 }
266
267
268
269
270
271
272
273
274
275
276
277
278 @Override
279 public ElementFactory getElementFactory(final SgmlPage page, final String namespaceURI,
280 final String qualifiedName, final boolean insideSvg, final boolean svgSupport) {
281 if (insideSvg) {
282 return SVG_FACTORY;
283 }
284
285 if (namespaceURI == null || namespaceURI.isEmpty()
286 || Html.XHTML_NAMESPACE.equals(namespaceURI)
287 || Html.SVG_NAMESPACE.equals(namespaceURI)
288 || !qualifiedName.contains(":")) {
289
290 String tagName = qualifiedName;
291 final int index = tagName.indexOf(':');
292 if (index == -1) {
293 tagName = StringUtils.toRootLowerCase(tagName);
294 }
295 else {
296 tagName = tagName.substring(index + 1);
297 }
298 final ElementFactory factory;
299 if (svgSupport && !"svg".equals(tagName) && Html.SVG_NAMESPACE.equals(namespaceURI)) {
300 factory = SVG_FACTORY;
301 }
302 else {
303 factory = ELEMENT_FACTORIES.get(tagName);
304 }
305
306 if (factory != null) {
307 return factory;
308 }
309 }
310 return UnknownElementFactory.INSTANCE;
311 }
312 }
313
314
315
316
317 class HtmlUnitNekoHTMLErrorHandler implements XMLErrorHandler {
318 private final HTMLParserListener listener_;
319 private final URL url_;
320 private final String html_;
321
322 HtmlUnitNekoHTMLErrorHandler(final HTMLParserListener listener, final URL url, final String htmlContent) {
323 WebAssert.notNull("listener", listener);
324 WebAssert.notNull("url", url);
325 listener_ = listener;
326 url_ = url;
327 html_ = htmlContent;
328 }
329
330
331
332
333
334 @Override
335 public void error(final String domain, final String key,
336 final XMLParseException exception) throws XNIException {
337 listener_.error(exception.getMessage(),
338 url_,
339 html_,
340 exception.getLineNumber(),
341 exception.getColumnNumber(),
342 key);
343 }
344
345
346
347
348
349 @Override
350 public void warning(final String domain, final String key,
351 final XMLParseException exception) throws XNIException {
352 listener_.warning(exception.getMessage(),
353 url_,
354 html_,
355 exception.getLineNumber(),
356 exception.getColumnNumber(),
357 key);
358 }
359
360 @Override
361 public void fatalError(final String domain, final String key,
362 final XMLParseException exception) throws XNIException {
363 listener_.error(exception.getMessage(),
364 url_,
365 html_,
366 exception.getLineNumber(),
367 exception.getColumnNumber(),
368 key);
369 }
370 }