View Javadoc
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 <a href="mailto:cse@dynabean.de">Christian Sell</a>
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      * Recursively writes the XML data for the node tree starting at <code>node</code>.
132      *
133      * @param indent white space to indent child nodes
134      * @param printWriter writer where child nodes are written
135      */
136     @Override
137     protected void printXml(final String indent, final PrintWriter printWriter) {
138         String data = getData();
139         if (org.apache.commons.lang3.StringUtils.isNotBlank(data)) {
140             printWriter.print(indent);
141             if (!(getParentNode() instanceof HtmlStyle) || !data.startsWith("<!--") || !data.endsWith("-->")) {
142                 data = StringUtils.escapeXmlChars(data);
143             }
144             printWriter.print(data);
145             printWriter.print("\r\n");
146         }
147         printChildrenAsXml(indent, printWriter);
148     }
149 
150     /**
151      * Gives a simple representation to facilitate debugging.
152      * @return a simple representation
153      */
154     @Override
155     public String toString() {
156         return asNormalizedText();
157     }
158 
159     /**
160      * Performs the effective type action, called after the keyPress event and before the keyUp event.
161      * @param c the character you with to simulate typing
162      * @param htmlElement the element in which typing occurs
163      * @param lastType is this the last character to type
164      */
165     protected void doType(final char c, final HtmlElement htmlElement, final boolean lastType) {
166         initDoTypeProcessor();
167         doTypeProcessor_.doType(getData(), selectionDelegate_, c, htmlElement, lastType);
168     }
169 
170     /**
171      * Performs the effective type action, called after the keyPress event and before the keyUp event.
172      *
173      * @param keyCode the key code wish to simulate typing
174      * @param htmlElement the element in which typing occurs
175      * @param lastType is this the last character to type
176      */
177     protected void doType(final int keyCode, final HtmlElement htmlElement, final boolean lastType) {
178         initDoTypeProcessor();
179         doTypeProcessor_.doType(getData(), selectionDelegate_, keyCode, htmlElement, lastType);
180     }
181 
182     private void initDoTypeProcessor() {
183         if (selectionDelegate_ == null) {
184             selectionDelegate_ = new SimpleSelectionDelegate();
185             doTypeProcessor_ = new DoTypeProcessor(this);
186         }
187     }
188 
189     /**
190      * Indicates if the provided character can by "typed" in the element.
191      * @param c the character
192      * @return {@code true} if it is accepted
193      */
194     protected boolean acceptChar(final char c) {
195         // This range is this is private use area
196         // see http://www.unicode.org/charts/PDF/UE000.pdf
197         return (c < '\uE000' || c > '\uF8FF') && (c == ' ' || !Character.isWhitespace(c));
198     }
199 
200     /**
201      * {@inheritDoc}
202      */
203     @Override
204     public DomNode cloneNode(final boolean deep) {
205         final DomText newnode = (DomText) super.cloneNode(deep);
206         selectionDelegate_ = new SimpleSelectionDelegate();
207         doTypeProcessor_ = new DoTypeProcessor(this);
208 
209         return newnode;
210     }
211 
212     /**
213      * Moves the selection to the end.
214      */
215     public void moveSelectionToEnd() {
216         initDoTypeProcessor();
217         selectionDelegate_.setSelectionStart(getData().length());
218     }
219 
220     /**
221      * {@inheritDoc}
222      */
223     @Override
224     public void setPrefix(final String prefix) {
225         // Empty.
226     }
227 }