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