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.javascript.host.css;
16
17 import org.htmlunit.corejs.javascript.Scriptable;
18 import org.htmlunit.javascript.HtmlUnitScriptable;
19 import org.htmlunit.javascript.configuration.JsxClass;
20 import org.htmlunit.javascript.configuration.JsxStaticFunction;
21 import org.htmlunit.util.StringUtils;
22
23 /**
24 * A JavaScript object for {@code CSS}.
25 *
26 * @author Ahmed Ashour
27 * @author Ronald Brill
28 */
29 @JsxClass
30 public class CSS extends HtmlUnitScriptable {
31
32 /**
33 * {@inheritDoc}
34 */
35 @Override
36 public Object get(final String name, final Scriptable start) {
37 if ("prototype".equals(name)) {
38 return NOT_FOUND;
39 }
40 return super.get(name, start);
41 }
42
43 /**
44 * @return a Boolean value indicating if the browser supports a given CSS feature, or not
45 */
46 @JsxStaticFunction
47 public static boolean supports() {
48 // for the moment we support everything :-)
49 return true;
50 }
51
52 /**
53 * @param ident the string to be escaped
54 * @return a string containing the escaped string passed as parameter,
55 * mostly for use as part of a CSS selector
56 */
57 @JsxStaticFunction
58 public static String escape(final String ident) {
59 if (StringUtils.isEmptyOrNull(ident)) {
60 return ident;
61 }
62
63 final int length = ident.length();
64 final StringBuilder escaped = new StringBuilder();
65 for (int i = 0; i < length; i++) {
66 final char c = ident.charAt(i);
67
68 // If the character is NULL (U+0000), then the REPLACEMENT CHARACTER (U+FFFD).
69 if (c == '\u0000') {
70 escaped.append('\ufffd');
71 }
72
73 // If the character is in the range [\1-\1f] (U+0001 to U+001F) or is U+007F,
74 // then the character escaped as code point.
75 else if (('\u0001' <= c && c <= '\u001f') || c == '\u007f') {
76 escaped.append('\\').append(Integer.toHexString(c)).append(' ');
77 }
78
79 // If the character is the first character and is in the range [0-9] (U+0030 to U+0039),
80 // then the character escaped as code point.
81 else if (i == 0 && ('\u0030' <= c && c <= '\u0039')) {
82 escaped.append('\\').append(Integer.toHexString(c)).append(' ');
83 }
84
85 // If the character is the second character and is in the range [0-9] (U+0030 to U+0039)
86 // and the first character is a "-" (U+002D), then the character escaped as code point.
87 else if (i == 1 && ('\u0030' <= c && c <= '\u0039') && ident.charAt(0) == '-') {
88 escaped.append('\\').append(Integer.toHexString(c)).append(' ');
89 }
90
91 // If the character is the first character and is a "-" (U+002D),
92 // and there is no second character, then the escaped character.
93 else if (i == 0 && c == '-' && length == 1) {
94 escaped.append('\\').append(c);
95 }
96
97 // If the character is not handled by one of the above rules
98 // and is greater than or equal to U+0080, is "-" (U+002D) or "_" (U+005F),
99 // or is in one of the ranges [0-9] (U+0030 to U+0039),
100 // [A-Z] (U+0041 to U+005A), or [a-z] (U+0061 to U+007A),
101 // then the character itself.
102 else if (c >= '\u0080'
103 || c == '\u002d'
104 || c == '\u005f'
105 || ('\u0030' <= c && c <= '\u0039')
106 || ('\u0041' <= c && c <= '\u005A')
107 || ('\u0061' <= c && c <= '\u007A')) {
108 escaped.append(c);
109 }
110
111 // Otherwise, the escaped character
112 else {
113 escaped.append('\\').append(c);
114 }
115 }
116
117 return escaped.toString();
118 }
119 }