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.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      * {@inheritDoc}
203      */
204     @Override
205     public DomNode cloneNode(final boolean deep) {
206         final DomText newnode = (DomText) super.cloneNode(deep);
207         selectionDelegate_ = new SimpleSelectionDelegate();
208         doTypeProcessor_ = new DoTypeProcessor(this);
209 
210         return newnode;
211     }
212 
213     /**
214      * Moves the selection to the end.
215      */
216     public void moveSelectionToEnd() {
217         initDoTypeProcessor();
218         selectionDelegate_.setSelectionStart(getData().length());
219     }
220 
221     /**
222      * {@inheritDoc}
223      */
224     @Override
225     public void setPrefix(final String prefix) {
226         // Empty.
227     }
228 }