View Javadoc
1   /*
2    * Copyright (c) 2002-2025 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.svg;
16  
17  import java.util.HashMap;
18  import java.util.LinkedHashMap;
19  import java.util.Map;
20  
21  import org.htmlunit.SgmlPage;
22  import org.htmlunit.html.DomAttr;
23  import org.htmlunit.html.DomElement;
24  import org.htmlunit.html.ElementFactory;
25  import org.htmlunit.util.StringUtils;
26  import org.xml.sax.Attributes;
27  
28  /**
29   * Element factory which creates elements by calling the constructor on a
30   * given {@link org.htmlunit.svg.SvgElement} subclass.
31   *
32   * @author Ahmed Ashour
33   * @author Frank Danek
34   * @author Ronald Brill
35   */
36  public class SvgElementFactory implements ElementFactory {
37  
38      private static final Class<?>[] CLASSES_ = {SvgAltGlyph.class, SvgAltGlyphDef.class, SvgAltGlyphItem.class,
39          SvgAnchor.class, SvgAnimate.class, SvgAnimateColor.class, SvgAnimateMotion.class, SvgAnimateTransform.class,
40          SvgCircle.class, SvgClipPath.class, SvgColorProfile.class, SvgCursor.class, SvgDefs.class, SvgDesc.class,
41          SvgEllipse.class, SvgFeBlend.class, SvgFeColorMatrix.class, SvgFeComponentTransfer.class,
42          SvgFeComposite.class, SvgFeConvolveMatrix.class, SvgFeDiffuseLighting.class, SvgFeDisplacementMap.class,
43          SvgFeDistantLight.class, SvgFeFlood.class, SvgFeFuncA.class, SvgFeFuncB.class, SvgFeFuncG.class,
44          SvgFeFuncR.class, SvgFeGaussianBlur.class, SvgFeImage.class, SvgFeMerge.class, SvgFeMergeNode.class,
45          SvgFeMorphology.class, SvgFeOffset.class, SvgFePointLight.class, SvgFeSpecularLighting.class,
46          SvgFeSpotLight.class, SvgFeTile.class, SvgFeTurbulence.class, SvgFilter.class, SvgFont.class,
47          SvgFontFace.class, SvgFontFaceFormat.class, SvgFontFaceName.class, SvgFontFaceSrc.class,
48          SvgFontFaceURI.class, SvgForeignObject.class, SvgGlyph.class, SvgGlyphRef.class, SvgGroup.class,
49          SvgHKern.class, SvgImage.class, SvgLine.class, SvgLinearGradient.class, SvgMarker.class, SvgMask.class,
50          SvgMetadata.class, SvgMissingGlyph.class, SvgMPath.class, SvgPath.class, SvgPattern.class, SvgPolygon.class,
51          SvgPolyline.class, SvgRadialGradient.class, SvgRect.class, SvgScript.class, SvgSet.class, SvgStop.class,
52          SvgStyle.class, SvgSwitch.class, SvgSymbol.class, SvgText.class, SvgTextPath.class,
53          SvgTitle.class, SvgTRef.class, SvgTSpan.class, SvgUse.class, SvgView.class, SvgVKern.class
54      };
55  
56      private static final Map<String, Class<?>> ELEMENTS_ = new HashMap<>();
57  
58      static {
59          try {
60              for (final Class<?> klass : CLASSES_) {
61                  final String key = klass.getField("TAG_NAME").get(null).toString();
62                  ELEMENTS_.put(StringUtils.toRootLowerCase(key), klass);
63              }
64          }
65          catch (final Exception e) {
66              throw new IllegalStateException(e);
67          }
68      }
69  
70      /**
71       * {@inheritDoc}
72       */
73      @Override
74      public DomElement createElement(final SgmlPage page, final String tagName, final Attributes attributes) {
75          throw new IllegalStateException("SVG.createElement not yet implemented!");
76      }
77  
78      /**
79       * {@inheritDoc}
80       */
81      @Override
82      public DomElement createElementNS(final SgmlPage page, final String namespaceURI, String qualifiedNameLC,
83              final Attributes attributes) {
84  
85          final Map<String, DomAttr> attributeMap = toMap(page, attributes);
86          qualifiedNameLC = StringUtils.toRootLowerCase(qualifiedNameLC);
87          String tagNameLC = qualifiedNameLC;
88          if (tagNameLC.indexOf(':') != -1) {
89              tagNameLC = tagNameLC.substring(tagNameLC.indexOf(':') + 1);
90          }
91          DomElement element = null;
92  
93          final Class<?> klass = ELEMENTS_.get(tagNameLC);
94          if (klass != null) {
95              try {
96                  element = (DomElement) klass.getDeclaredConstructors()[0]
97                          .newInstance(namespaceURI, qualifiedNameLC, page, attributeMap);
98              }
99              catch (final Exception e) {
100                 throw new IllegalStateException(e);
101             }
102         }
103         if (element == null) {
104             element = new SvgElement(namespaceURI, qualifiedNameLC, page, attributeMap);
105         }
106         return element;
107     }
108 
109     private static Map<String, DomAttr> toMap(final SgmlPage page, final Attributes attributes) {
110         Map<String, DomAttr> attributeMap = null;
111         if (attributes != null) {
112             attributeMap = new LinkedHashMap<>(attributes.getLength());
113             for (int i = 0; i < attributes.getLength(); i++) {
114                 final String qName = attributes.getQName(i);
115                 // browsers consider only first attribute (ex: <div id='foo' id='something'>...</div>)
116                 if (!attributeMap.containsKey(qName)) {
117                     String namespaceURI = attributes.getURI(i);
118                     if (namespaceURI != null && namespaceURI.isEmpty()) {
119                         namespaceURI = null;
120                     }
121                     final DomAttr newAttr = new DomAttr(page, namespaceURI, qName, attributes.getValue(i), true);
122                     attributeMap.put(qName, newAttr);
123                 }
124             }
125         }
126         return attributeMap;
127     }
128 
129     /**
130      * Returns whether the specified name is a valid SVG tag name.
131      * @param tagNameLowerCase the tag name in lower case
132      * @return whether the specified name is a valid SVG tag name or not
133      */
134     public boolean isSupported(final String tagNameLowerCase) {
135         return ELEMENTS_.containsKey(tagNameLowerCase);
136     }
137 }