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