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      * {@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 (org.apache.commons.lang3.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 by "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 }