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.w3c.dom.DOMException;
21 import org.w3c.dom.ProcessingInstruction;
22
23 /**
24 * Wrapper for the DOM node ProcessingInstruction.
25 * <p>
26 * <em>Note:</em> There is an architectural difference between the Java DOM layer and the JavaScript host layer:
27 * This Java class extends {@link DomNode} directly rather than a character data class, whereas the corresponding
28 * JavaScript host class inherits from {@link org.htmlunit.javascript.host.dom.CharacterData} to match browser behavior.
29 * </p>
30 *
31 * @author Ahmed Ashour
32 * @author Ronald Brill
33 */
34 public class DomProcessingInstruction extends DomNode implements ProcessingInstruction {
35
36 private final String target_;
37 private String data_;
38
39 /**
40 * Creates a new instance.
41 *
42 * @param page the Page that contains this element
43 * @param target the target
44 * @param data the data
45 */
46 public DomProcessingInstruction(final SgmlPage page, final String target, final String data) {
47 super(page);
48 target_ = target;
49 setData(data);
50 }
51
52 /**
53 * {@inheritDoc}
54 * @return the node type constant, in this case {@link org.w3c.dom.Node#PROCESSING_INSTRUCTION_NODE}
55 */
56 @Override
57 public short getNodeType() {
58 return org.w3c.dom.Node.PROCESSING_INSTRUCTION_NODE;
59 }
60
61 /**
62 * {@inheritDoc}
63 */
64 @Override
65 public String getNodeName() {
66 return target_;
67 }
68
69 /**
70 * {@inheritDoc}
71 */
72 @Override
73 public String getTarget() {
74 return getNodeName();
75 }
76
77 /**
78 * {@inheritDoc}
79 */
80 @Override
81 public String getData() {
82 return getNodeValue();
83 }
84
85 /**
86 * {@inheritDoc}
87 */
88 @Override
89 public void setData(final String data) throws DOMException {
90 setNodeValue(data);
91 }
92
93 /**
94 * Returns the number of characters in the character data.
95 * @return the number of characters
96 */
97 public int getLength() {
98 return data_ == null ? 0 : data_.length();
99 }
100
101 /**
102 * Appends a string to character data.
103 * @param newData the string to be appended to the character data
104 */
105 public void appendData(final String newData) {
106 if (newData != null) {
107 setData(data_ == null ? newData : data_ + newData);
108 }
109 }
110
111 /**
112 * Deletes characters from character data.
113 * @param offset the position of the first character to be deleted (can't be
114 * less than zero)
115 * @param count the number of characters to be deleted, if less than zero
116 * leaves the first offset chars
117 */
118 public void deleteData(final int offset, final int count) {
119 if (offset < 0) {
120 throw new IllegalArgumentException("Provided offset: " + offset + " is less than zero.");
121 }
122 if (data_ == null) {
123 return;
124 }
125
126 final String data = data_.substring(0, Math.min(offset, data_.length()));
127 if (count >= 0) {
128 final int fromLeft = offset + count;
129 if (fromLeft < data_.length()) {
130 setData(data + data_.substring(fromLeft));
131 return;
132 }
133 }
134 setData(data);
135 }
136
137 /**
138 * Inserts a string into character data.
139 * @param offset the position within the first character at which the string is to be inserted
140 * @param arg the string to insert
141 */
142 public void insertData(final int offset, final String arg) {
143 if (data_ == null) {
144 setData(arg);
145 return;
146 }
147
148 setData(new StringBuilder(data_).insert(offset, arg).toString());
149 }
150
151 /**
152 * Replaces characters of character data with a string.
153 * @param offset the position within the first character at which the string is to be replaced
154 * @param count the number of characters to be replaced
155 * @param arg the string that replaces the count characters beginning at the character at offset
156 */
157 public void replaceData(final int offset, final int count, final String arg) {
158 if (offset < 0 || count < 0) {
159 throw new IllegalArgumentException("offset: " + offset + " count: " + count);
160 }
161 if (data_ == null) {
162 setData(arg);
163 return;
164 }
165
166 final int end = Math.min(offset + count, data_.length());
167 final StringBuilder sb = new StringBuilder(data_);
168 sb.replace(offset, end, arg != null ? arg : "");
169 setData(sb.toString());
170 }
171
172 /**
173 * Extracts a substring from character data.
174 * @param offset the position of the first character to be extracted
175 * @param count the number of characters to be extracted
176 * @return a string that consists of the count characters of the character data starting
177 * from the character at position offset
178 */
179 public String substringData(final int offset, final int count) {
180 if (data_ == null) {
181 throw new IllegalArgumentException("offset: " + offset + " count: " + count);
182 }
183 final int length = data_.length();
184 if (count < 0 || offset < 0 || offset > length - 1) {
185 throw new IllegalArgumentException("offset: " + offset + " count: " + count);
186 }
187
188 final int tailIndex = Math.min(offset + count, length);
189 return data_.substring(offset, tailIndex);
190 }
191
192 /**
193 * {@inheritDoc}
194 */
195 @Override
196 public void setNodeValue(final String value) {
197 data_ = value;
198 }
199
200 /**
201 * {@inheritDoc}
202 */
203 @Override
204 public String getNodeValue() {
205 return data_;
206 }
207
208 /**
209 * {@inheritDoc}
210 */
211 @Override
212 public void setTextContent(final String textContent) {
213 setNodeValue(textContent);
214 }
215
216 /**
217 * {@inheritDoc}
218 */
219 @Override
220 protected boolean printXml(final String indent, final boolean indentBefore, final PrintWriter printWriter) {
221 printWriter.print("<?");
222 printWriter.print(getTarget());
223 printWriter.print(" ");
224 printWriter.print(getData());
225 printWriter.print("?>");
226 return true;
227 }
228
229 /**
230 * {@inheritDoc}
231 */
232 @Override
233 public void setPrefix(final String prefix) {
234 // Empty.
235 }
236 }