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.html;
16
17 import java.io.PrintWriter;
18
19 import org.htmlunit.SgmlPage;
20 import org.htmlunit.html.impl.SelectionDelegate;
21 import org.htmlunit.html.impl.SimpleSelectionDelegate;
22 import org.htmlunit.util.StringUtils;
23 import org.w3c.dom.DOMException;
24 import org.w3c.dom.Text;
25
26 /**
27 * Representation of a text node in the HTML DOM.
28 *
29 * @author David K. Taylor
30 * @author Christian Sell
31 * @author Rodney Gitzel
32 * @author Ahmed Ashour
33 * @author Sudhan Moghe
34 * @author Philip Graf
35 * @author Ronald Brill
36 */
37 public class DomText extends DomCharacterData implements Text {
38
39 private SelectionDelegate selectionDelegate_;
40 private DoTypeProcessor doTypeProcessor_;
41
42 /** The symbolic node name. */
43 public static final String NODE_NAME = "#text";
44
45 /**
46 * Creates an instance of DomText.
47 *
48 * @param page the Page that contains this element
49 * @param data the string data held by this node
50 */
51 public DomText(final SgmlPage page, final String data) {
52 super(page, data);
53 }
54
55 /**
56 * {@inheritDoc}
57 */
58 @Override
59 public DomText splitText(final int offset) {
60 if (offset < 0 || offset > getLength()) {
61 throw new IllegalArgumentException("offset: " + offset + " data.length: " + getLength());
62 }
63
64 // split text into two separate nodes
65 final DomText newText = createSplitTextNode(offset);
66 setData(getData().substring(0, offset));
67
68 // insert new text node
69 if (getParentNode() != null) {
70 getParentNode().insertBefore(newText, getNextSibling());
71 }
72 return newText;
73 }
74
75 /**
76 * Creates a new text node split from another text node. This method allows
77 * the derived type of the new text node to match the original node type.
78 *
79 * @param offset the character position at which to split the DomText node
80 * @return the newly created Text node
81 */
82 protected DomText createSplitTextNode(final int offset) {
83 return new DomText(getPage(), getData().substring(offset));
84 }
85
86 /**
87 * {@inheritDoc}
88 * Not yet implemented.
89 */
90 @Override
91 public boolean isElementContentWhitespace() {
92 throw new UnsupportedOperationException("DomText.isElementContentWhitespace is not yet implemented.");
93 }
94
95 /**
96 * {@inheritDoc}
97 */
98 @Override
99 public String getWholeText() {
100 // I couldn't find a way to have a nearby EntityReference node (either sibling or parent)
101 // if this is found, have a look at xerces TextImpl.
102 return getNodeValue();
103 }
104
105 /**
106 * {@inheritDoc}
107 * Not yet implemented.
108 */
109 @Override
110 public Text replaceWholeText(final String content) throws DOMException {
111 throw new UnsupportedOperationException("DomText.replaceWholeText is not yet implemented.");
112 }
113
114 /**
115 * @return the node type constant, in this case {@link org.w3c.dom.Node#TEXT_NODE}
116 */
117 @Override
118 public short getNodeType() {
119 return org.w3c.dom.Node.TEXT_NODE;
120 }
121
122 /**
123 * @return the node name, in this case {@link #NODE_NAME}
124 */
125 @Override
126 public String getNodeName() {
127 return NODE_NAME;
128 }
129
130 /**
131 * {@inheritDoc}
132 */
133 @Override
134 protected boolean printXml(final String indent, final boolean tagBefore, final PrintWriter printWriter) {
135 String data = getData();
136 boolean tag = tagBefore;
137 if (StringUtils.isNotBlank(data)) {
138 if (!(getParentNode() instanceof HtmlStyle) || !data.startsWith("<!--") || !data.endsWith("-->")) {
139 data = StringUtils.escapeXmlChars(data);
140 }
141 printWriter.print(data);
142 tag = false;
143 }
144 return printChildrenAsXml(indent, tag, printWriter);
145 }
146
147 /**
148 * Gives a simple representation to facilitate debugging.
149 * @return a simple representation
150 */
151 @Override
152 public String toString() {
153 return asNormalizedText();
154 }
155
156 /**
157 * Performs the effective type action, called after the keyPress event and before the keyUp event.
158 * @param c the character you with to simulate typing
159 * @param htmlElement the element in which typing occurs
160 * @param lastType is this the last character to type
161 */
162 protected void doType(final char c, final HtmlElement htmlElement, final boolean lastType) {
163 initDoTypeProcessor();
164 doTypeProcessor_.doType(getData(), selectionDelegate_, c, htmlElement, lastType);
165 }
166
167 /**
168 * Performs the effective type action, called after the keyPress event and before the keyUp event.
169 *
170 * @param keyCode the key code wish to simulate typing
171 * @param htmlElement the element in which typing occurs
172 * @param lastType is this the last character to type
173 */
174 protected void doType(final int keyCode, final HtmlElement htmlElement, final boolean lastType) {
175 initDoTypeProcessor();
176 doTypeProcessor_.doType(getData(), selectionDelegate_, keyCode, htmlElement, lastType);
177 }
178
179 private void initDoTypeProcessor() {
180 if (selectionDelegate_ == null) {
181 selectionDelegate_ = new SimpleSelectionDelegate();
182 doTypeProcessor_ = new DoTypeProcessor(this);
183 }
184 }
185
186 /**
187 * Indicates if the provided character can be "typed" in the element.
188 * @param c the character
189 * @return {@code true} if it is accepted
190 */
191 protected boolean acceptChar(final char c) {
192 // This range is this is private use area
193 // see http://www.unicode.org/charts/PDF/UE000.pdf
194 return (c < '\uE000' || c > '\uF8FF') && (c == ' ' || !Character.isWhitespace(c));
195 }
196
197 /**
198 * {@inheritDoc}
199 */
200 @Override
201 public DomNode cloneNode(final boolean deep) {
202 final DomText newnode = (DomText) super.cloneNode(deep);
203 selectionDelegate_ = new SimpleSelectionDelegate();
204 doTypeProcessor_ = new DoTypeProcessor(this);
205
206 return newnode;
207 }
208
209 /**
210 * Moves the selection to the end.
211 */
212 public void moveSelectionToEnd() {
213 initDoTypeProcessor();
214 selectionDelegate_.setSelectionStart(getData().length());
215 }
216
217 /**
218 * {@inheritDoc}
219 */
220 @Override
221 public void setPrefix(final String prefix) {
222 // Empty.
223 }
224 }