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.javascript.host.dom;
16  
17  import static org.htmlunit.html.DomElement.ATTRIBUTE_NOT_DEFINED;
18  
19  import org.htmlunit.corejs.javascript.Scriptable;
20  import org.htmlunit.html.HtmlElement;
21  import org.htmlunit.javascript.HtmlUnitScriptable;
22  import org.htmlunit.javascript.JavaScriptEngine;
23  import org.htmlunit.javascript.configuration.JsxClass;
24  import org.htmlunit.javascript.configuration.JsxConstructor;
25  
26  /**
27   * A JavaScript object for {@code DOMStringMap}.
28   *
29   * @author Ahmed Ashour
30   * @author Ronald Brill
31   */
32  @JsxClass
33  public final class DOMStringMap extends HtmlUnitScriptable {
34  
35      private static final String DATA_PREFIX = "data-";
36  
37      /**
38       * Creates an instance.
39       */
40      public DOMStringMap() {
41          super();
42      }
43  
44      /**
45       * JavaScript constructor.
46       */
47      @JsxConstructor
48      public void jsConstructor() {
49          // nothing to do
50      }
51  
52      /**
53       * Creates an instance.
54       * @param node the node which contains the underlying string
55       */
56      public DOMStringMap(final Node node) {
57          super();
58          setDomNode(node.getDomNodeOrDie(), false);
59          setParentScope(node.getParentScope());
60          setPrototype(getPrototype(getClass()));
61      }
62  
63      /**
64       * {@inheritDoc}
65       */
66      @Override
67      public Object get(final String name, final Scriptable start) {
68          final HtmlElement e = (HtmlElement) getDomNodeOrNull();
69          if (e != null) {
70              final String value = e.getAttribute(DATA_PREFIX + deCamelize(name));
71              if (ATTRIBUTE_NOT_DEFINED != value) {
72                  return value;
73              }
74          }
75          return super.get(name, start);
76      }
77  
78      /**
79       * {@inheritDoc}
80       */
81      @Override
82      public void put(final String name, final Scriptable start, final Object value) {
83          final HtmlElement e = (HtmlElement) getDomNodeOrNull();
84          if (e == null) {
85              super.put(name, start, value);
86          }
87          else {
88              e.setAttribute("data-" + deCamelize(name), JavaScriptEngine.toString(value));
89          }
90      }
91  
92      /**
93       * {@inheritDoc}
94       */
95      @Override
96      public void delete(final String name) {
97          final HtmlElement e = (HtmlElement) getDomNodeOrNull();
98          if (e == null) {
99              super.delete(name);
100         }
101         else {
102             e.removeAttribute("data-" + deCamelize(name));
103         }
104     }
105 
106     /**
107      * Transforms the specified string from camel-cased to dash separated.
108      *
109      * @param string the string to decamelize
110      * @return the transformed string
111      * @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset#name_conversion">
112      *      MDN - HTMLElement.dataset - Name conversion</a>
113      */
114     private static String deCamelize(final String string) {
115         if (string == null || string.isEmpty()) {
116             return string;
117         }
118 
119         final StringBuilder builder = new StringBuilder();
120         for (int i = 0; i < string.length(); i++) {
121             final char ch = string.charAt(i);
122             if (Character.isUpperCase(ch)) {
123                 builder.append('-').append(Character.toLowerCase(ch));
124             }
125             else {
126                 builder.append(ch);
127             }
128         }
129         return builder.toString();
130     }
131 
132 }