0
|
1 /*--
|
|
2
|
|
3 $Id: ProcessingInstruction.java,v 1.47 2007/11/10 05:28:59 jhunter Exp $
|
|
4
|
|
5 Copyright (C) 2000-2007 Jason Hunter & Brett McLaughlin.
|
|
6 All rights reserved.
|
|
7
|
|
8 Redistribution and use in source and binary forms, with or without
|
|
9 modification, are permitted provided that the following conditions
|
|
10 are met:
|
|
11
|
|
12 1. Redistributions of source code must retain the above copyright
|
|
13 notice, this list of conditions, and the following disclaimer.
|
|
14
|
|
15 2. Redistributions in binary form must reproduce the above copyright
|
|
16 notice, this list of conditions, and the disclaimer that follows
|
|
17 these conditions in the documentation and/or other materials
|
|
18 provided with the distribution.
|
|
19
|
|
20 3. The name "JDOM" must not be used to endorse or promote products
|
|
21 derived from this software without prior written permission. For
|
|
22 written permission, please contact <request_AT_jdom_DOT_org>.
|
|
23
|
|
24 4. Products derived from this software may not be called "JDOM", nor
|
|
25 may "JDOM" appear in their name, without prior written permission
|
|
26 from the JDOM Project Management <request_AT_jdom_DOT_org>.
|
|
27
|
|
28 In addition, we request (but do not require) that you include in the
|
|
29 end-user documentation provided with the redistribution and/or in the
|
|
30 software itself an acknowledgement equivalent to the following:
|
|
31 "This product includes software developed by the
|
|
32 JDOM Project (http://www.jdom.org/)."
|
|
33 Alternatively, the acknowledgment may be graphical using the logos
|
|
34 available at http://www.jdom.org/images/logos.
|
|
35
|
|
36 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
|
|
37 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
38 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
39 DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
|
|
40 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
41 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
42 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
|
43 USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
44 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
45 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
|
46 OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
47 SUCH DAMAGE.
|
|
48
|
|
49 This software consists of voluntary contributions made by many
|
|
50 individuals on behalf of the JDOM Project and was originally
|
|
51 created by Jason Hunter <jhunter_AT_jdom_DOT_org> and
|
|
52 Brett McLaughlin <brett_AT_jdom_DOT_org>. For more information
|
|
53 on the JDOM Project, please see <http://www.jdom.org/>.
|
|
54
|
|
55 */
|
|
56
|
|
57 package org.jdom;
|
|
58
|
|
59 import java.util.*;
|
|
60
|
|
61 /**
|
|
62 * An XML processing instruction. Methods allow the user to obtain the target of
|
|
63 * the PI as well as its data. The data can always be accessed as a String or,
|
|
64 * if the data appears akin to an attribute list, can be retrieved as name/value
|
|
65 * pairs.
|
|
66 *
|
|
67 * @version $Revision: 1.47 $, $Date: 2007/11/10 05:28:59 $
|
|
68 * @author Brett McLaughlin
|
|
69 * @author Jason Hunter
|
|
70 * @author Steven Gould
|
|
71 */
|
|
72
|
|
73 public class ProcessingInstruction extends Content {
|
|
74
|
|
75 private static final String CVS_ID =
|
|
76 "@(#) $RCSfile: ProcessingInstruction.java,v $ $Revision: 1.47 $ $Date: 2007/11/10 05:28:59 $ $Name: jdom_1_1_1 $";
|
|
77
|
|
78 /** The target of the PI */
|
|
79 protected String target;
|
|
80
|
|
81 /** The data for the PI as a String */
|
|
82 protected String rawData;
|
|
83
|
|
84 /** The data for the PI in name/value pairs */
|
|
85 protected Map mapData;
|
|
86
|
|
87 /**
|
|
88 * Default, no-args constructor for implementations
|
|
89 * to use if needed.
|
|
90 */
|
|
91 protected ProcessingInstruction() { }
|
|
92
|
|
93 /**
|
|
94 * This will create a new <code>ProcessingInstruction</code>
|
|
95 * with the specified target and data.
|
|
96 *
|
|
97 * @param target <code>String</code> target of PI.
|
|
98 * @param data <code>Map</code> data for PI, in
|
|
99 * name/value pairs
|
|
100 * @throws IllegalTargetException if the given target is illegal
|
|
101 * as a processing instruction name.
|
|
102 */
|
|
103 public ProcessingInstruction(String target, Map data) {
|
|
104 setTarget(target);
|
|
105 setData(data);
|
|
106 }
|
|
107
|
|
108 /**
|
|
109 * This will create a new <code>ProcessingInstruction</code>
|
|
110 * with the specified target and data.
|
|
111 *
|
|
112 * @param target <code>String</code> target of PI.
|
|
113 * @param data <code>String</code> data for PI.
|
|
114 * @throws IllegalTargetException if the given target is illegal
|
|
115 * as a processing instruction name.
|
|
116 */
|
|
117 public ProcessingInstruction(String target, String data) {
|
|
118 setTarget(target);
|
|
119 setData(data);
|
|
120 }
|
|
121
|
|
122 /**
|
|
123 * This will set the target for the PI.
|
|
124 *
|
|
125 * @param newTarget <code>String</code> new target of PI.
|
|
126 * @return <code>ProcessingInstruction</code> - this PI modified.
|
|
127 */
|
|
128 public ProcessingInstruction setTarget(String newTarget) {
|
|
129 String reason;
|
|
130 if ((reason = Verifier.checkProcessingInstructionTarget(newTarget))
|
|
131 != null) {
|
|
132 throw new IllegalTargetException(newTarget, reason);
|
|
133 }
|
|
134
|
|
135 target = newTarget;
|
|
136 return this;
|
|
137 }
|
|
138
|
|
139 /**
|
|
140 * Returns the XPath 1.0 string value of this element, which is the
|
|
141 * data of this PI.
|
|
142 *
|
|
143 * @return the data of this PI
|
|
144 */
|
|
145 public String getValue() {
|
|
146 return rawData;
|
|
147 }
|
|
148
|
|
149
|
|
150 /**
|
|
151 * This will retrieve the target of the PI.
|
|
152 *
|
|
153 * @return <code>String</code> - target of PI.
|
|
154 */
|
|
155 public String getTarget() {
|
|
156 return target;
|
|
157 }
|
|
158
|
|
159 /**
|
|
160 * This will return the raw data from all instructions.
|
|
161 *
|
|
162 * @return <code>String</code> - data of PI.
|
|
163 */
|
|
164 public String getData() {
|
|
165 return rawData;
|
|
166 }
|
|
167
|
|
168 /**
|
|
169 * This will return a <code>List</code> containing the names of the
|
|
170 * "attribute" style pieces of name/value pairs in this PI's data.
|
|
171 *
|
|
172 * @return <code>List</code> - the <code>List</code> containing the
|
|
173 * "attribute" names.
|
|
174 */
|
|
175 public List getPseudoAttributeNames() {
|
|
176 Set mapDataSet = mapData.entrySet();
|
|
177 List nameList = new ArrayList();
|
|
178 for (Iterator i = mapDataSet.iterator(); i.hasNext();) {
|
|
179 String wholeSet = (i.next()).toString();
|
|
180 String attrName = wholeSet.substring(0,(wholeSet.indexOf("=")));
|
|
181 nameList.add(attrName);
|
|
182 }
|
|
183 return nameList;
|
|
184 }
|
|
185
|
|
186 /**
|
|
187 * This will set the raw data for the PI.
|
|
188 *
|
|
189 * @param data <code>String</code> data of PI.
|
|
190 * @return <code>ProcessingInstruction</code> - this PI modified.
|
|
191 */
|
|
192 public ProcessingInstruction setData(String data) {
|
|
193 String reason = Verifier.checkProcessingInstructionData(data);
|
|
194 if (reason != null) {
|
|
195 throw new IllegalDataException(data, reason);
|
|
196 }
|
|
197
|
|
198 this.rawData = data;
|
|
199 this.mapData = parseData(data);
|
|
200 return this;
|
|
201 }
|
|
202
|
|
203 /**
|
|
204 * This will set the name/value pairs within the passed
|
|
205 * <code>Map</code> as the pairs for the data of
|
|
206 * this PI. The keys should be the pair name
|
|
207 * and the values should be the pair values.
|
|
208 *
|
|
209 * @param data new map data to use
|
|
210 * @return <code>ProcessingInstruction</code> - modified PI.
|
|
211 */
|
|
212 public ProcessingInstruction setData(Map data) {
|
|
213 String temp = toString(data);
|
|
214
|
|
215 String reason = Verifier.checkProcessingInstructionData(temp);
|
|
216 if (reason != null) {
|
|
217 throw new IllegalDataException(temp, reason);
|
|
218 }
|
|
219
|
|
220 this.rawData = temp;
|
|
221 this.mapData = data;
|
|
222 return this;
|
|
223 }
|
|
224
|
|
225
|
|
226 /**
|
|
227 * This will return the value for a specific
|
|
228 * name/value pair on the PI. If no such pair is
|
|
229 * found for this PI, null is returned.
|
|
230 *
|
|
231 * @param name <code>String</code> name of name/value pair
|
|
232 * to lookup value for.
|
|
233 * @return <code>String</code> - value of name/value pair.
|
|
234 */
|
|
235 public String getPseudoAttributeValue(String name) {
|
|
236 return (String)mapData.get(name);
|
|
237 }
|
|
238
|
|
239 /**
|
|
240 * This will set a pseudo attribute with the given name and value.
|
|
241 * If the PI data is not already in a pseudo-attribute format, this will
|
|
242 * replace the existing data.
|
|
243 *
|
|
244 * @param name <code>String</code> name of pair.
|
|
245 * @param value <code>String</code> value for pair.
|
|
246 * @return <code>ProcessingInstruction</code> this PI modified.
|
|
247 */
|
|
248 public ProcessingInstruction setPseudoAttribute(String name, String value) {
|
|
249 String reason = Verifier.checkProcessingInstructionData(name);
|
|
250 if (reason != null) {
|
|
251 throw new IllegalDataException(name, reason);
|
|
252 }
|
|
253
|
|
254 reason = Verifier.checkProcessingInstructionData(value);
|
|
255 if (reason != null) {
|
|
256 throw new IllegalDataException(value, reason);
|
|
257 }
|
|
258
|
|
259 this.mapData.put(name, value);
|
|
260 this.rawData = toString(mapData);
|
|
261 return this;
|
|
262 }
|
|
263
|
|
264
|
|
265 /**
|
|
266 * This will remove the pseudo attribute with the specified name.
|
|
267 *
|
|
268 * @param name name of pseudo attribute to remove
|
|
269 * @return <code>boolean</code> - whether the requested
|
|
270 * instruction was removed.
|
|
271 */
|
|
272 public boolean removePseudoAttribute(String name) {
|
|
273 if ((mapData.remove(name)) != null) {
|
|
274 rawData = toString(mapData);
|
|
275 return true;
|
|
276 }
|
|
277
|
|
278 return false;
|
|
279 }
|
|
280
|
|
281 /**
|
|
282 * This will convert the Map to a string representation.
|
|
283 *
|
|
284 * @param mapData <code>Map</code> PI data to convert
|
|
285 * @return a string representation of the Map as appropriate for a PI
|
|
286 */
|
|
287 private String toString(Map mapData) {
|
|
288 StringBuffer rawData = new StringBuffer();
|
|
289
|
|
290 Iterator i = mapData.keySet().iterator();
|
|
291 while (i.hasNext()) {
|
|
292 String name = (String)i.next();
|
|
293 String value = (String)mapData.get(name);
|
|
294 rawData.append(name)
|
|
295 .append("=\"")
|
|
296 .append(value)
|
|
297 .append("\" ");
|
|
298 }
|
|
299 // Remove last space, if we did any appending
|
|
300 if (rawData.length() > 0) {
|
|
301 rawData.setLength(rawData.length() - 1);
|
|
302 }
|
|
303
|
|
304 return rawData.toString();
|
|
305 }
|
|
306
|
|
307 /**
|
|
308 * This will parse and load the instructions for the PI.
|
|
309 * This is separated to allow it to occur once and then be reused.
|
|
310 */
|
|
311 private Map parseData(String rawData) {
|
|
312 // The parsing here is done largely "by hand" which means the code
|
|
313 // gets a little tricky/messy. The following conditions should
|
|
314 // now be handled correctly:
|
|
315 // <?pi href="http://hi/a=b"?> Reads OK
|
|
316 // <?pi href = 'http://hi/a=b' ?> Reads OK
|
|
317 // <?pi href\t = \t'http://hi/a=b'?> Reads OK
|
|
318 // <?pi href = "http://hi/a=b"?> Reads OK
|
|
319 // <?pi?> Empty Map
|
|
320 // <?pi id=22?> Empty Map
|
|
321 // <?pi id='22?> Empty Map
|
|
322
|
|
323 Map data = new HashMap();
|
|
324
|
|
325 // System.out.println("rawData: " + rawData);
|
|
326
|
|
327 // The inputData variable holds the part of rawData left to parse
|
|
328 String inputData = rawData.trim();
|
|
329
|
|
330 // Iterate through the remaining inputData string
|
|
331 while (!inputData.trim().equals("")) {
|
|
332 //System.out.println("parseData() looking at: " + inputData);
|
|
333
|
|
334 // Search for "name =", "name=" or "name1 name2..."
|
|
335 String name = "";
|
|
336 String value = "";
|
|
337 int startName = 0;
|
|
338 char previousChar = inputData.charAt(startName);
|
|
339 int pos = 1;
|
|
340 for (; pos<inputData.length(); pos++) {
|
|
341 char currentChar = inputData.charAt(pos);
|
|
342 if (currentChar == '=') {
|
|
343 name = inputData.substring(startName, pos).trim();
|
|
344 // Get the boundaries on the quoted string
|
|
345 // We use boundaries so we know where to start next
|
|
346 int[] bounds = extractQuotedString(
|
|
347 inputData.substring(pos+1));
|
|
348 // A null value means a parse error and we return empty!
|
|
349 if (bounds == null) {
|
|
350 return new HashMap();
|
|
351 }
|
|
352 value = inputData.substring(bounds[0]+pos+1,
|
|
353 bounds[1]+pos+1);
|
|
354 pos += bounds[1] + 1; // skip past value
|
|
355 break;
|
|
356 }
|
|
357 else if (Character.isWhitespace(previousChar)
|
|
358 && !Character.isWhitespace(currentChar)) {
|
|
359 startName = pos;
|
|
360 }
|
|
361
|
|
362 previousChar = currentChar;
|
|
363 }
|
|
364
|
|
365 // Remove the first pos characters; they have been processed
|
|
366 inputData = inputData.substring(pos);
|
|
367
|
|
368 // System.out.println("Extracted (name, value) pair: ("
|
|
369 // + name + ", '" + value+"')");
|
|
370
|
|
371 // If both a name and a value have been found, then add
|
|
372 // them to the data Map
|
|
373 if (name.length() > 0 && value != null) {
|
|
374 //if (data.containsKey(name)) {
|
|
375 // A repeat, that's a parse error, so return a null map
|
|
376 //return new HashMap();
|
|
377 //}
|
|
378 //else {
|
|
379 data.put(name, value);
|
|
380 //}
|
|
381 }
|
|
382 }
|
|
383
|
|
384 return data;
|
|
385 }
|
|
386
|
|
387 /**
|
|
388 * This is a helper routine, only used by parseData, to extract a
|
|
389 * quoted String from the input parameter, rawData. A quoted string
|
|
390 * can use either single or double quotes, but they must match up.
|
|
391 * A singly quoted string can contain an unbalanced amount of double
|
|
392 * quotes, or vice versa. For example, the String "JDOM's the best"
|
|
393 * is legal as is 'JDOM"s the best'.
|
|
394 *
|
|
395 * @param rawData the input string from which a quoted string is to
|
|
396 * be extracted.
|
|
397 * @return the first quoted string encountered in the input data. If
|
|
398 * no quoted string is found, then the empty string, "", is
|
|
399 * returned.
|
|
400 * @see #parseData
|
|
401 */
|
|
402 private static int[] extractQuotedString(String rawData) {
|
|
403 // Remembers whether we're actually in a quoted string yet
|
|
404 boolean inQuotes = false;
|
|
405
|
|
406 // Remembers which type of quoted string we're in
|
|
407 char quoteChar = '"';
|
|
408
|
|
409 // Stores the position of the first character inside
|
|
410 // the quoted string (i.e. the start of the return string)
|
|
411 int start = 0;
|
|
412
|
|
413 // Iterate through the input string looking for the start
|
|
414 // and end of the quoted string
|
|
415 for (int pos=0; pos < rawData.length(); pos++) {
|
|
416 char currentChar = rawData.charAt(pos);
|
|
417 if (currentChar=='"' || currentChar=='\'') {
|
|
418 if (!inQuotes) {
|
|
419 // We're entering a quoted string
|
|
420 quoteChar = currentChar;
|
|
421 inQuotes = true;
|
|
422 start = pos+1;
|
|
423 }
|
|
424 else if (quoteChar == currentChar) {
|
|
425 // We're leaving a quoted string
|
|
426 inQuotes = false;
|
|
427 return new int[] { start, pos };
|
|
428 }
|
|
429 // Otherwise we've encountered a quote
|
|
430 // inside a quote, so just continue
|
|
431 }
|
|
432 }
|
|
433
|
|
434 return null;
|
|
435 }
|
|
436
|
|
437 /**
|
|
438 * This returns a <code>String</code> representation of the
|
|
439 * <code>ProcessingInstruction</code>, suitable for debugging. If the XML
|
|
440 * representation of the <code>ProcessingInstruction</code> is desired,
|
|
441 * {@link org.jdom.output.XMLOutputter#outputString(ProcessingInstruction)}
|
|
442 * should be used.
|
|
443 *
|
|
444 * @return <code>String</code> - information about the
|
|
445 * <code>ProcessingInstruction</code>
|
|
446 */
|
|
447 public String toString() {
|
|
448 return new StringBuffer()
|
|
449 .append("[ProcessingInstruction: ")
|
|
450 .append(new org.jdom.output.XMLOutputter().outputString(this))
|
|
451 .append("]")
|
|
452 .toString();
|
|
453 }
|
|
454
|
|
455 /**
|
|
456 * This will return a clone of this <code>ProcessingInstruction</code>.
|
|
457 *
|
|
458 * @return <code>Object</code> - clone of this
|
|
459 * <code>ProcessingInstruction</code>.
|
|
460 */
|
|
461 public Object clone() {
|
|
462 ProcessingInstruction pi = (ProcessingInstruction) super.clone();
|
|
463
|
|
464 // target and rawdata are immutable and references copied by
|
|
465 // Object.clone()
|
|
466
|
|
467 // Create a new Map object for the clone (since Map isn't Cloneable)
|
|
468 if (mapData != null) {
|
|
469 pi.mapData = parseData(rawData);
|
|
470 }
|
|
471 return pi;
|
|
472 }
|
|
473 }
|