View Javadoc
1   /*
2    * Copyright (c) 2002-2026 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.html;
16  
17  import static org.htmlunit.BrowserVersionFeatures.HTML_LAYER_TAG;
18  
19  import java.util.ArrayList;
20  import java.util.List;
21  import java.util.Locale;
22  import java.util.Map;
23  
24  import org.apache.commons.logging.Log;
25  import org.apache.commons.logging.LogFactory;
26  import org.htmlunit.SgmlPage;
27  import org.htmlunit.cyberneko.xerces.util.XMLAttributesImpl;
28  import org.htmlunit.cyberneko.xerces.xni.QName;
29  import org.htmlunit.util.OrderedFastHashMap;
30  import org.xml.sax.Attributes;
31  
32  /**
33   * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br>
34   *
35   * Element factory which creates elements by calling the constructor on a
36   * given {@link HtmlElement} subclass.
37   * The constructor is expected to take 2 arguments of type
38   * {@link HtmlPage} and {@link java.util.Map}
39   * where the first one is the owning page of the element, the second one is a map
40   * holding the initial attributes for the element.
41   *
42   * @author Christian Sell
43   * @author Marc Guillemot
44   * @author Ahmed Ashour
45   * @author David K. Taylor
46   * @author Dmitri Zoubkov
47   * @author Ronald Brill
48   * @author Frank Danek
49   */
50  public class DefaultElementFactory implements ElementFactory {
51  
52      /** Logging support. */
53      private static final Log LOG = LogFactory.getLog(DefaultElementFactory.class);
54  
55      /**
56       * You can generate your own test cases by looking into
57       * org.htmlunit.source.ElementTestSource#generateTestForHtmlElements(String, String).
58       */
59      public static final List<String> SUPPORTED_TAGS_ = List.of(
60              HtmlAbbreviated.TAG_NAME, HtmlAcronym.TAG_NAME,
61              HtmlAnchor.TAG_NAME, HtmlAddress.TAG_NAME, HtmlArea.TAG_NAME,
62              HtmlArticle.TAG_NAME, HtmlAside.TAG_NAME, HtmlAudio.TAG_NAME,
63              HtmlBase.TAG_NAME, HtmlBaseFont.TAG_NAME,
64              HtmlBidirectionalIsolation.TAG_NAME, HtmlBidirectionalOverride.TAG_NAME, HtmlBig.TAG_NAME,
65              HtmlBlockQuote.TAG_NAME, HtmlBody.TAG_NAME, HtmlBold.TAG_NAME,
66              HtmlBreak.TAG_NAME, HtmlButton.TAG_NAME, HtmlCanvas.TAG_NAME, HtmlCaption.TAG_NAME,
67              HtmlCenter.TAG_NAME, HtmlCitation.TAG_NAME, HtmlCode.TAG_NAME,
68              HtmlData.TAG_NAME, HtmlDataList.TAG_NAME,
69              HtmlDefinition.TAG_NAME, HtmlDefinitionDescription.TAG_NAME,
70              HtmlDeletedText.TAG_NAME, HtmlDetails.TAG_NAME, HtmlDialog.TAG_NAME, HtmlDirectory.TAG_NAME,
71              HtmlDivision.TAG_NAME, HtmlDefinitionList.TAG_NAME,
72              HtmlDefinitionTerm.TAG_NAME, HtmlEmbed.TAG_NAME,
73              HtmlEmphasis.TAG_NAME,
74              HtmlFieldSet.TAG_NAME, HtmlFigureCaption.TAG_NAME, HtmlFigure.TAG_NAME,
75              HtmlFont.TAG_NAME, HtmlForm.TAG_NAME, HtmlFooter.TAG_NAME,
76              HtmlFrame.TAG_NAME, HtmlFrameSet.TAG_NAME,
77              HtmlHead.TAG_NAME, HtmlHeader.TAG_NAME,
78              HtmlHeading1.TAG_NAME, HtmlHeading2.TAG_NAME, HtmlHeading3.TAG_NAME,
79              HtmlHeading4.TAG_NAME, HtmlHeading5.TAG_NAME, HtmlHeading6.TAG_NAME,
80              HtmlHeadingGroup.TAG_NAME, HtmlHorizontalRule.TAG_NAME,
81              HtmlHtml.TAG_NAME,
82              HtmlInlineFrame.TAG_NAME, HtmlInlineQuotation.TAG_NAME,
83              HtmlImage.TAG_NAME, HtmlImage.TAG_NAME2,
84              HtmlInput.TAG_NAME,
85              HtmlInsertedText.TAG_NAME,
86              HtmlItalic.TAG_NAME,
87              HtmlKeyboard.TAG_NAME, HtmlLabel.TAG_NAME, HtmlLayer.TAG_NAME,
88              HtmlLegend.TAG_NAME, HtmlListing.TAG_NAME, HtmlListItem.TAG_NAME,
89              HtmlLink.TAG_NAME, HtmlMain.TAG_NAME, HtmlMap.TAG_NAME, HtmlMark.TAG_NAME, HtmlMarquee.TAG_NAME,
90              HtmlMenu.TAG_NAME, HtmlMeta.TAG_NAME, HtmlMeter.TAG_NAME,
91              HtmlNav.TAG_NAME,
92              HtmlNoBreak.TAG_NAME, HtmlNoEmbed.TAG_NAME, HtmlNoFrames.TAG_NAME,
93              HtmlNoLayer.TAG_NAME,
94              HtmlNoScript.TAG_NAME, HtmlObject.TAG_NAME, HtmlOrderedList.TAG_NAME,
95              HtmlOptionGroup.TAG_NAME, HtmlOption.TAG_NAME, HtmlOutput.TAG_NAME,
96              HtmlParagraph.TAG_NAME,
97              HtmlParameter.TAG_NAME, HtmlPicture.TAG_NAME, HtmlPlainText.TAG_NAME, HtmlPreformattedText.TAG_NAME,
98              HtmlProgress.TAG_NAME,
99              HtmlRb.TAG_NAME, HtmlRp.TAG_NAME, HtmlRt.TAG_NAME, HtmlRtc.TAG_NAME, HtmlRuby.TAG_NAME,
100             HtmlS.TAG_NAME, HtmlSample.TAG_NAME,
101             HtmlScript.TAG_NAME, HtmlSection.TAG_NAME, HtmlSelect.TAG_NAME, HtmlSlot.TAG_NAME, HtmlSmall.TAG_NAME,
102             HtmlSource.TAG_NAME, HtmlSpan.TAG_NAME,
103             HtmlStrike.TAG_NAME, HtmlStrong.TAG_NAME, HtmlStyle.TAG_NAME,
104             HtmlSubscript.TAG_NAME, HtmlSummary.TAG_NAME, HtmlSuperscript.TAG_NAME,
105             HtmlSvg.TAG_NAME,
106             HtmlTable.TAG_NAME, HtmlTableColumn.TAG_NAME, HtmlTableColumnGroup.TAG_NAME,
107             HtmlTableBody.TAG_NAME, HtmlTableDataCell.TAG_NAME, HtmlTableHeaderCell.TAG_NAME,
108             HtmlTableRow.TAG_NAME, HtmlTextArea.TAG_NAME, HtmlTableFooter.TAG_NAME,
109             HtmlTableHeader.TAG_NAME, HtmlTeletype.TAG_NAME, HtmlTemplate.TAG_NAME, HtmlTime.TAG_NAME,
110             HtmlTitle.TAG_NAME, HtmlTrack.TAG_NAME, HtmlUnderlined.TAG_NAME, HtmlUnorderedList.TAG_NAME,
111             HtmlVariable.TAG_NAME, HtmlVideo.TAG_NAME, HtmlWordBreak.TAG_NAME, HtmlExample.TAG_NAME);
112 
113     // for performance optimization
114     static final class OrderedFastHashMapWithLowercaseKeys<K, V> extends OrderedFastHashMap<K, V> {
115         OrderedFastHashMapWithLowercaseKeys(final int size) {
116             super(size);
117         }
118     }
119 
120     /**
121      * @param page the owning page
122      * @param tagName the HTML tag name
123      * @param attributes initial attributes, possibly {@code null}
124      * @return the newly created element
125      */
126     @Override
127     public HtmlElement createElement(final SgmlPage page, final String tagName, final Attributes attributes) {
128         return createElementNS(page, null, tagName, attributes);
129     }
130 
131     /**
132      * @param page the owning page
133      * @param namespaceURI the URI that identifies an XML namespace
134      * @param qualifiedName the qualified name of the element type to instantiate
135      * @param attributes initial attributes, possibly {@code null}
136      * @return the newly created element
137      */
138     @Override
139     public HtmlElement createElementNS(final SgmlPage page, final String namespaceURI,
140             final String qualifiedName, final Attributes attributes) {
141         final Map<String, DomAttr> attributeMap = toMap(page, attributes);
142 
143         final HtmlElement element;
144         final String tagName;
145         final int colonIndex = qualifiedName.indexOf(':');
146         if (colonIndex == -1) {
147             tagName = qualifiedName.toLowerCase(Locale.ROOT);
148         }
149         else {
150             tagName = qualifiedName.substring(colonIndex + 1).toLowerCase(Locale.ROOT);
151         }
152 
153         switch (tagName) {
154             case HtmlAbbreviated.TAG_NAME:
155                 element = new HtmlAbbreviated(qualifiedName, page, attributeMap);
156                 break;
157 
158             case HtmlAcronym.TAG_NAME:
159                 element = new HtmlAcronym(qualifiedName, page, attributeMap);
160                 break;
161 
162             case HtmlAddress.TAG_NAME:
163                 element = new HtmlAddress(qualifiedName, page, attributeMap);
164                 break;
165 
166             case HtmlAnchor.TAG_NAME:
167                 element = new HtmlAnchor(qualifiedName, page, attributeMap);
168                 break;
169 
170             case HtmlArea.TAG_NAME:
171                 element = new HtmlArea(qualifiedName, page, attributeMap);
172                 break;
173 
174             case HtmlArticle.TAG_NAME:
175                 element = new HtmlArticle(qualifiedName, page, attributeMap);
176                 break;
177 
178             case HtmlAside.TAG_NAME:
179                 element = new HtmlAside(qualifiedName, page, attributeMap);
180                 break;
181 
182             case HtmlAudio.TAG_NAME:
183                 element = new HtmlAudio(qualifiedName, page, attributeMap);
184                 break;
185 
186             case HtmlBase.TAG_NAME:
187                 element = new HtmlBase(qualifiedName, page, attributeMap);
188                 break;
189 
190             case HtmlBaseFont.TAG_NAME:
191                 element = new HtmlBaseFont(qualifiedName, page, attributeMap);
192                 break;
193 
194             case HtmlBidirectionalIsolation.TAG_NAME:
195                 element = new HtmlBidirectionalIsolation(qualifiedName, page, attributeMap);
196                 break;
197 
198             case HtmlBidirectionalOverride.TAG_NAME:
199                 element = new HtmlBidirectionalOverride(qualifiedName, page, attributeMap);
200                 break;
201 
202             case HtmlBig.TAG_NAME:
203                 element = new HtmlBig(qualifiedName, page, attributeMap);
204                 break;
205 
206             case HtmlBlockQuote.TAG_NAME:
207                 element = new HtmlBlockQuote(qualifiedName, page, attributeMap);
208                 break;
209 
210             case HtmlBody.TAG_NAME:
211                 element = new HtmlBody(qualifiedName, page, attributeMap, false);
212                 // Force script object creation now to forward onXXX handlers to window.
213                 if (page instanceof HtmlPage) {
214                     if (page.getWebClient().isJavaScriptEngineEnabled()) {
215                         element.getScriptableObject();
216                     }
217                 }
218                 break;
219 
220             case HtmlBold.TAG_NAME:
221                 element = new HtmlBold(qualifiedName, page, attributeMap);
222                 break;
223 
224             case HtmlBreak.TAG_NAME:
225                 element = new HtmlBreak(qualifiedName, page, attributeMap);
226                 break;
227 
228             case HtmlButton.TAG_NAME:
229                 element = new HtmlButton(qualifiedName, page, attributeMap);
230                 break;
231 
232             case HtmlCanvas.TAG_NAME:
233                 element = new HtmlCanvas(qualifiedName, page, attributeMap);
234                 break;
235 
236             case HtmlCaption.TAG_NAME:
237                 element = new HtmlCaption(qualifiedName, page, attributeMap);
238                 break;
239 
240             case HtmlCenter.TAG_NAME:
241                 element = new HtmlCenter(qualifiedName, page, attributeMap);
242                 break;
243 
244             case HtmlCitation.TAG_NAME:
245                 element = new HtmlCitation(qualifiedName, page, attributeMap);
246                 break;
247 
248             case HtmlCode.TAG_NAME:
249                 element = new HtmlCode(qualifiedName, page, attributeMap);
250                 break;
251 
252             case HtmlData.TAG_NAME:
253                 element = new HtmlData(qualifiedName, page, attributeMap);
254                 break;
255 
256             case HtmlDataList.TAG_NAME:
257                 element = new HtmlDataList(qualifiedName, page, attributeMap);
258                 break;
259 
260             case HtmlDefinition.TAG_NAME:
261                 element = new HtmlDefinition(qualifiedName, page, attributeMap);
262                 break;
263 
264             case HtmlDefinitionDescription.TAG_NAME:
265                 element = new HtmlDefinitionDescription(qualifiedName, page, attributeMap);
266                 break;
267 
268             case HtmlDefinitionList.TAG_NAME:
269                 element = new HtmlDefinitionList(qualifiedName, page, attributeMap);
270                 break;
271 
272             case HtmlDefinitionTerm.TAG_NAME:
273                 element = new HtmlDefinitionTerm(qualifiedName, page, attributeMap);
274                 break;
275 
276             case HtmlDeletedText.TAG_NAME:
277                 element = new HtmlDeletedText(qualifiedName, page, attributeMap);
278                 break;
279 
280             case HtmlDetails.TAG_NAME:
281                 element = new HtmlDetails(qualifiedName, page, attributeMap);
282                 break;
283 
284             case HtmlDialog.TAG_NAME:
285                 element = new HtmlDialog(qualifiedName, page, attributeMap);
286                 break;
287 
288             case HtmlDirectory.TAG_NAME:
289                 element = new HtmlDirectory(qualifiedName, page, attributeMap);
290                 break;
291 
292             case HtmlDivision.TAG_NAME:
293                 element = new HtmlDivision(qualifiedName, page, attributeMap);
294                 break;
295 
296             case HtmlEmbed.TAG_NAME:
297                 element = new HtmlEmbed(qualifiedName, page, attributeMap);
298                 break;
299 
300             case HtmlEmphasis.TAG_NAME:
301                 element = new HtmlEmphasis(qualifiedName, page, attributeMap);
302                 break;
303 
304             case HtmlExample.TAG_NAME:
305                 element = new HtmlExample(qualifiedName, page, attributeMap);
306                 break;
307 
308             case HtmlFieldSet.TAG_NAME:
309                 element = new HtmlFieldSet(qualifiedName, page, attributeMap);
310                 break;
311 
312             case HtmlFigure.TAG_NAME:
313                 element = new HtmlFigure(qualifiedName, page, attributeMap);
314                 break;
315 
316             case HtmlFigureCaption.TAG_NAME:
317                 element = new HtmlFigureCaption(qualifiedName, page, attributeMap);
318                 break;
319 
320             case HtmlFont.TAG_NAME:
321                 element = new HtmlFont(qualifiedName, page, attributeMap);
322                 break;
323 
324             case HtmlForm.TAG_NAME:
325                 element = new HtmlForm(qualifiedName, page, attributeMap);
326                 break;
327 
328             case HtmlFooter.TAG_NAME:
329                 element = new HtmlFooter(qualifiedName, page, attributeMap);
330                 break;
331 
332             case HtmlFrame.TAG_NAME:
333                 if (attributeMap != null) {
334                     final DomAttr srcAttribute = attributeMap.get(DomElement.SRC_ATTRIBUTE);
335                     if (srcAttribute != null) {
336                         srcAttribute.setValue(srcAttribute.getValue().trim());
337                     }
338                 }
339                 element = new HtmlFrame(qualifiedName, page, attributeMap);
340                 break;
341 
342             case HtmlFrameSet.TAG_NAME:
343                 element = new HtmlFrameSet(qualifiedName, page, attributeMap);
344                 break;
345 
346             case HtmlHead.TAG_NAME:
347                 element = new HtmlHead(qualifiedName, page, attributeMap);
348                 break;
349 
350             case HtmlHeader.TAG_NAME:
351                 element = new HtmlHeader(qualifiedName, page, attributeMap);
352                 break;
353 
354             case HtmlHeading1.TAG_NAME:
355                 element = new HtmlHeading1(qualifiedName, page, attributeMap);
356                 break;
357 
358             case HtmlHeading2.TAG_NAME:
359                 element = new HtmlHeading2(qualifiedName, page, attributeMap);
360                 break;
361 
362             case HtmlHeading3.TAG_NAME:
363                 element = new HtmlHeading3(qualifiedName, page, attributeMap);
364                 break;
365 
366             case HtmlHeading4.TAG_NAME:
367                 element = new HtmlHeading4(qualifiedName, page, attributeMap);
368                 break;
369 
370             case HtmlHeading5.TAG_NAME:
371                 element = new HtmlHeading5(qualifiedName, page, attributeMap);
372                 break;
373 
374             case HtmlHeading6.TAG_NAME:
375                 element = new HtmlHeading6(qualifiedName, page, attributeMap);
376                 break;
377 
378             case HtmlHeadingGroup.TAG_NAME:
379                 element = new HtmlHeadingGroup(qualifiedName, page, attributeMap);
380                 break;
381 
382             case HtmlHorizontalRule.TAG_NAME:
383                 element = new HtmlHorizontalRule(qualifiedName, page, attributeMap);
384                 break;
385 
386             case HtmlHtml.TAG_NAME:
387                 element = new HtmlHtml(qualifiedName, page, attributeMap);
388                 break;
389 
390             case HtmlImage.TAG_NAME:
391             case HtmlImage.TAG_NAME2:
392                 element = new HtmlImage(qualifiedName, page, attributeMap);
393                 break;
394 
395             case HtmlInlineFrame.TAG_NAME:
396                 if (attributeMap != null) {
397                     final DomAttr srcAttribute = attributeMap.get(DomElement.SRC_ATTRIBUTE);
398                     if (srcAttribute != null) {
399                         srcAttribute.setValue(srcAttribute.getValue().trim());
400                     }
401                 }
402                 element = new HtmlInlineFrame(qualifiedName, page, attributeMap);
403                 break;
404 
405             case HtmlInlineQuotation.TAG_NAME:
406                 element = new HtmlInlineQuotation(qualifiedName, page, attributeMap);
407                 break;
408 
409             case HtmlInput.TAG_NAME:
410                 element = createInputElement(qualifiedName, page, attributeMap);
411                 break;
412 
413             case HtmlInsertedText.TAG_NAME:
414                 element = new HtmlInsertedText(qualifiedName, page, attributeMap);
415                 break;
416 
417             case HtmlItalic.TAG_NAME:
418                 element = new HtmlItalic(qualifiedName, page, attributeMap);
419                 break;
420 
421             case HtmlKeyboard.TAG_NAME:
422                 element = new HtmlKeyboard(qualifiedName, page, attributeMap);
423                 break;
424 
425             case HtmlLabel.TAG_NAME:
426                 element = new HtmlLabel(qualifiedName, page, attributeMap);
427                 break;
428 
429             case HtmlLayer.TAG_NAME:
430                 if (page.getWebClient().getBrowserVersion().hasFeature(HTML_LAYER_TAG)) {
431                     element = new HtmlLayer(qualifiedName, page, attributeMap);
432                 }
433                 else {
434                     element = UnknownElementFactory.INSTANCE
435                             .createElementNS(page, namespaceURI, qualifiedName, attributes);
436                 }
437                 break;
438 
439             case HtmlLegend.TAG_NAME:
440                 element = new HtmlLegend(qualifiedName, page, attributeMap);
441                 break;
442 
443             case HtmlLink.TAG_NAME:
444                 element = new HtmlLink(qualifiedName, page, attributeMap);
445                 break;
446 
447             case HtmlListing.TAG_NAME:
448                 element = new HtmlListing(qualifiedName, page, attributeMap);
449                 break;
450 
451             case HtmlListItem.TAG_NAME:
452                 element = new HtmlListItem(qualifiedName, page, attributeMap);
453                 break;
454 
455             case HtmlMain.TAG_NAME:
456                 element = new HtmlMain(qualifiedName, page, attributeMap);
457                 break;
458 
459             case HtmlMap.TAG_NAME:
460                 element = new HtmlMap(qualifiedName, page, attributeMap);
461                 break;
462 
463             case HtmlMark.TAG_NAME:
464                 element = new HtmlMark(qualifiedName, page, attributeMap);
465                 break;
466 
467             case HtmlMarquee.TAG_NAME:
468                 element = new HtmlMarquee(qualifiedName, page, attributeMap);
469                 break;
470 
471             case HtmlMenu.TAG_NAME:
472                 element = new HtmlMenu(qualifiedName, page, attributeMap);
473                 break;
474 
475             case HtmlMeta.TAG_NAME:
476                 element = new HtmlMeta(qualifiedName, page, attributeMap);
477                 break;
478 
479             case HtmlMeter.TAG_NAME:
480                 element = new HtmlMeter(qualifiedName, page, attributeMap);
481                 break;
482 
483             case HtmlNav.TAG_NAME:
484                 element = new HtmlNav(qualifiedName, page, attributeMap);
485                 break;
486 
487             case HtmlNoBreak.TAG_NAME:
488                 element = new HtmlNoBreak(qualifiedName, page, attributeMap);
489                 break;
490 
491             case HtmlNoEmbed.TAG_NAME:
492                 element = new HtmlNoEmbed(qualifiedName, page, attributeMap);
493                 break;
494 
495             case HtmlNoFrames.TAG_NAME:
496                 element = new HtmlNoFrames(qualifiedName, page, attributeMap);
497                 break;
498 
499             case HtmlNoLayer.TAG_NAME:
500                 if (page.getWebClient().getBrowserVersion().hasFeature(HTML_LAYER_TAG)) {
501                     element = new HtmlNoLayer(qualifiedName, page, attributeMap);
502                 }
503                 else {
504                     element = UnknownElementFactory.INSTANCE
505                             .createElementNS(page, namespaceURI, qualifiedName, attributes);
506                 }
507                 break;
508 
509             case HtmlNoScript.TAG_NAME:
510                 element = new HtmlNoScript(qualifiedName, page, attributeMap);
511                 break;
512 
513             case HtmlObject.TAG_NAME:
514                 element = new HtmlObject(qualifiedName, page, attributeMap);
515                 break;
516 
517             case HtmlOption.TAG_NAME:
518                 element = new HtmlOption(qualifiedName, page, attributeMap);
519                 break;
520 
521             case HtmlOptionGroup.TAG_NAME:
522                 element = new HtmlOptionGroup(qualifiedName, page, attributeMap);
523                 break;
524 
525             case HtmlOrderedList.TAG_NAME:
526                 element = new HtmlOrderedList(qualifiedName, page, attributeMap);
527                 break;
528 
529             case HtmlOutput.TAG_NAME:
530                 element = new HtmlOutput(qualifiedName, page, attributeMap);
531                 break;
532 
533             case HtmlParagraph.TAG_NAME:
534                 element = new HtmlParagraph(qualifiedName, page, attributeMap);
535                 break;
536 
537             case HtmlParameter.TAG_NAME:
538                 element = new HtmlParameter(qualifiedName, page, attributeMap);
539                 break;
540 
541             case HtmlPicture.TAG_NAME:
542                 element = new HtmlPicture(qualifiedName, page, attributeMap);
543                 break;
544 
545             case HtmlPlainText.TAG_NAME:
546                 element = new HtmlPlainText(qualifiedName, page, attributeMap);
547                 break;
548 
549             case HtmlPreformattedText.TAG_NAME:
550                 element = new HtmlPreformattedText(qualifiedName, page, attributeMap);
551                 break;
552 
553             case HtmlProgress.TAG_NAME:
554                 element = new HtmlProgress(qualifiedName, page, attributeMap);
555                 break;
556 
557             case HtmlRb.TAG_NAME:
558                 element = new HtmlRb(qualifiedName, page, attributeMap);
559                 break;
560 
561             case HtmlRp.TAG_NAME:
562                 element = new HtmlRp(qualifiedName, page, attributeMap);
563                 break;
564 
565             case HtmlRt.TAG_NAME:
566                 element = new HtmlRt(qualifiedName, page, attributeMap);
567                 break;
568 
569             case HtmlRtc.TAG_NAME:
570                 element = new HtmlRtc(qualifiedName, page, attributeMap);
571                 break;
572 
573             case HtmlRuby.TAG_NAME:
574                 element = new HtmlRuby(qualifiedName, page, attributeMap);
575                 break;
576 
577             case HtmlS.TAG_NAME:
578                 element = new HtmlS(qualifiedName, page, attributeMap);
579                 break;
580 
581             case HtmlSample.TAG_NAME:
582                 element = new HtmlSample(qualifiedName, page, attributeMap);
583                 break;
584 
585             case HtmlScript.TAG_NAME:
586                 element = new HtmlScript(qualifiedName, page, attributeMap);
587                 break;
588 
589             case HtmlSection.TAG_NAME:
590                 element = new HtmlSection(qualifiedName, page, attributeMap);
591                 break;
592 
593             case HtmlSelect.TAG_NAME:
594                 element = new HtmlSelect(qualifiedName, page, attributeMap);
595                 break;
596 
597             case HtmlSmall.TAG_NAME:
598                 element = new HtmlSmall(qualifiedName, page, attributeMap);
599                 break;
600 
601             case HtmlSlot.TAG_NAME:
602                 element = new HtmlSlot(qualifiedName, page, attributeMap);
603                 break;
604 
605             case HtmlSource.TAG_NAME:
606                 element = new HtmlSource(qualifiedName, page, attributeMap);
607                 break;
608 
609             case HtmlSpan.TAG_NAME:
610                 element = new HtmlSpan(qualifiedName, page, attributeMap);
611                 break;
612 
613             case HtmlStrike.TAG_NAME:
614                 element = new HtmlStrike(qualifiedName, page, attributeMap);
615                 break;
616 
617             case HtmlStrong.TAG_NAME:
618                 element = new HtmlStrong(qualifiedName, page, attributeMap);
619                 break;
620 
621             case HtmlStyle.TAG_NAME:
622                 element = new HtmlStyle(qualifiedName, page, attributeMap);
623                 break;
624 
625             case HtmlSubscript.TAG_NAME:
626                 element = new HtmlSubscript(qualifiedName, page, attributeMap);
627                 break;
628 
629             case HtmlSummary.TAG_NAME:
630                 element = new HtmlSummary(qualifiedName, page, attributeMap);
631                 break;
632 
633             case HtmlSuperscript.TAG_NAME:
634                 element = new HtmlSuperscript(qualifiedName, page, attributeMap);
635                 break;
636 
637             case HtmlSvg.TAG_NAME:
638                 element = new HtmlSvg(qualifiedName, page, attributeMap);
639                 break;
640 
641             case HtmlTable.TAG_NAME:
642                 element = new HtmlTable(qualifiedName, page, attributeMap);
643                 break;
644 
645             case HtmlTableBody.TAG_NAME:
646                 element = new HtmlTableBody(qualifiedName, page, attributeMap);
647                 break;
648 
649             case HtmlTableColumn.TAG_NAME:
650                 element = new HtmlTableColumn(qualifiedName, page, attributeMap);
651                 break;
652 
653             case HtmlTableColumnGroup.TAG_NAME:
654                 element = new HtmlTableColumnGroup(qualifiedName, page, attributeMap);
655                 break;
656 
657             case HtmlTableDataCell.TAG_NAME:
658                 element = new HtmlTableDataCell(qualifiedName, page, attributeMap);
659                 break;
660 
661             case HtmlTableFooter.TAG_NAME:
662                 element = new HtmlTableFooter(qualifiedName, page, attributeMap);
663                 break;
664 
665             case HtmlTableHeader.TAG_NAME:
666                 element = new HtmlTableHeader(qualifiedName, page, attributeMap);
667                 break;
668 
669             case HtmlTableHeaderCell.TAG_NAME:
670                 element = new HtmlTableHeaderCell(qualifiedName, page, attributeMap);
671                 break;
672 
673             case HtmlTableRow.TAG_NAME:
674                 element = new HtmlTableRow(qualifiedName, page, attributeMap);
675                 break;
676 
677             case HtmlTeletype.TAG_NAME:
678                 element = new HtmlTeletype(qualifiedName, page, attributeMap);
679                 break;
680 
681             case HtmlTemplate.TAG_NAME:
682                 element = new HtmlTemplate(qualifiedName, page, attributeMap);
683                 break;
684 
685             case HtmlTextArea.TAG_NAME:
686                 element = new HtmlTextArea(qualifiedName, page, attributeMap);
687                 break;
688 
689             case HtmlTime.TAG_NAME:
690                 element = new HtmlTime(qualifiedName, page, attributeMap);
691                 break;
692 
693             case HtmlTitle.TAG_NAME:
694                 element = new HtmlTitle(qualifiedName, page, attributeMap);
695                 break;
696 
697             case HtmlTrack.TAG_NAME:
698                 element = new HtmlTrack(qualifiedName, page, attributeMap);
699                 break;
700 
701             case HtmlUnderlined.TAG_NAME:
702                 element = new HtmlUnderlined(qualifiedName, page, attributeMap);
703                 break;
704 
705             case HtmlUnorderedList.TAG_NAME:
706                 element = new HtmlUnorderedList(qualifiedName, page, attributeMap);
707                 break;
708 
709             case HtmlVariable.TAG_NAME:
710                 element = new HtmlVariable(qualifiedName, page, attributeMap);
711                 break;
712 
713             case HtmlVideo.TAG_NAME:
714                 element = new HtmlVideo(qualifiedName, page, attributeMap);
715                 break;
716 
717             case HtmlWordBreak.TAG_NAME:
718                 element = new HtmlWordBreak(qualifiedName, page, attributeMap);
719                 break;
720 
721             default:
722                 throw new IllegalStateException("Cannot find HtmlElement for " + qualifiedName);
723         }
724 
725         return element;
726     }
727 
728     /**
729      * Converts {@link Attributes} into the map needed by {@link HtmlElement}s.
730      *
731      * @param page the page which contains the specified attributes
732      * @param attributes the SAX attributes
733      * @return the map of attribute values for {@link HtmlElement}s
734      */
735     static Map<String, DomAttr> toMap(final SgmlPage page, final Attributes attributes) {
736         // it is ok to return null here, the element ctor's are able to deal with that
737         if (attributes == null) {
738             return null;
739         }
740 
741         final int length = attributes.getLength();
742         if (length == 0) {
743             return null;
744         }
745 
746         final Map<String, DomAttr> attributeMap = new OrderedFastHashMapWithLowercaseKeys<>(length);
747 
748         // small performance optimization if we know the attributes we can avoid some index lookups
749         if (attributes instanceof XMLAttributesImpl impl) {
750             final ArrayList<XMLAttributesImpl.Attribute> attribs = impl.getAttributes();
751             for (final XMLAttributesImpl.Attribute attribute : attribs) {
752                 final QName qName = attribute.getQName();
753                 final String name = qName.getRawname();
754 
755                 String namespaceURI = qName.getUri();
756                 if (namespaceURI != null && namespaceURI.isEmpty()) {
757                     namespaceURI = null;
758                 }
759 
760                 DomAttr attr = new DomAttr(page, namespaceURI, name, attribute.getValue(), true);
761                 attr = attributeMap.put(name, attr);
762 
763                 // browsers consider only first attribute (ex: <div id='foo' id='something'>...</div>)
764                 // for performance reasons we do not check for the existence of the key first
765                 // because this is the unusual case
766                 if (attr != null) {
767                     attributeMap.put(name, attr);
768                 }
769             }
770 
771             return attributeMap;
772         }
773 
774         for (int i = 0; i < length; i++) {
775             final String qName = attributes.getQName(i);
776 
777             String namespaceURI = attributes.getURI(i);
778 
779             if (namespaceURI != null && namespaceURI.isEmpty()) {
780                 namespaceURI = null;
781             }
782 
783             DomAttr attr = new DomAttr(page, namespaceURI, qName, attributes.getValue(i), true);
784             attr = attributeMap.put(qName, attr);
785 
786             // browsers consider only first attribute (ex: <div id='foo' id='something'>...</div>)
787             // for performance reasons we do not check for the existence of the key first
788             // because this is the unusual case
789             if (attr != null) {
790                 attributeMap.put(qName, attr);
791             }
792         }
793 
794         return attributeMap;
795     }
796 
797     private static HtmlElement createInputElement(final String qualifiedName, final SgmlPage page,
798                                                 final Map<String, DomAttr> attributeMap) {
799         String type = "";
800         if (attributeMap != null) {
801             for (final Map.Entry<String, DomAttr> entry : attributeMap.entrySet()) {
802                 if (DomElement.TYPE_ATTRIBUTE.equalsIgnoreCase(entry.getKey())) {
803                     type = entry.getValue().getValue();
804                 }
805             }
806         }
807 
808         final HtmlInput result = switch (type.toLowerCase(Locale.ROOT)) {
809             // This not an illegal value, as it defaults to "text"
810             // cf http://www.w3.org/TR/REC-html40/interact/forms.html#adef-type-INPUT
811             // and the common browsers seem to treat it as a "text" input so we will as well.
812             case "", "text" -> new HtmlTextInput(qualifiedName, page, attributeMap);
813             case "submit" -> new HtmlSubmitInput(qualifiedName, page, attributeMap);
814             case "checkbox" -> new HtmlCheckBoxInput(qualifiedName, page, attributeMap);
815             case "radio" -> new HtmlRadioButtonInput(qualifiedName, page, attributeMap);
816             case "hidden" -> new HtmlHiddenInput(qualifiedName, page, attributeMap);
817             case "password" -> new HtmlPasswordInput(qualifiedName, page, attributeMap);
818             case "image" -> new HtmlImageInput(qualifiedName, page, attributeMap);
819             case "reset" -> new HtmlResetInput(qualifiedName, page, attributeMap);
820             case "button" -> new HtmlButtonInput(qualifiedName, page, attributeMap);
821             case "file" -> new HtmlFileInput(qualifiedName, page, attributeMap);
822             case "color" -> new HtmlColorInput(qualifiedName, page, attributeMap);
823             case "date" -> new HtmlDateInput(qualifiedName, page, attributeMap);
824             case "datetime-local" -> new HtmlDateTimeLocalInput(qualifiedName, page, attributeMap);
825             case "email" -> new HtmlEmailInput(qualifiedName, page, attributeMap);
826             case "month" -> new HtmlMonthInput(qualifiedName, page, attributeMap);
827             case "number" -> new HtmlNumberInput(qualifiedName, page, attributeMap);
828             case "range" -> new HtmlRangeInput(qualifiedName, page, attributeMap);
829             case "search" -> new HtmlSearchInput(qualifiedName, page, attributeMap);
830             case "tel" -> new HtmlTelInput(qualifiedName, page, attributeMap);
831             case "time" -> new HtmlTimeInput(qualifiedName, page, attributeMap);
832             case "url" -> new HtmlUrlInput(qualifiedName, page, attributeMap);
833             case "week" -> new HtmlWeekInput(qualifiedName, page, attributeMap);
834             default -> {
835                 if (LOG.isInfoEnabled()) {
836                     LOG.info("Bad input type: \"" + type + "\", creating a text input");
837                 }
838                 yield new HtmlTextInput(qualifiedName, page, attributeMap);
839             }
840         };
841         return result;
842     }
843 }