001: /*--
002:
003: $Id: ProcessingInstruction.java,v 1.1 2005/04/27 09:32:39 wittek Exp $
004:
005: Copyright (C) 2000-2004 Jason Hunter & Brett McLaughlin.
006: All rights reserved.
007:
008: Redistribution and use in source and binary forms, with or without
009: modification, are permitted provided that the following conditions
010: are met:
011:
012: 1. Redistributions of source code must retain the above copyright
013: notice, this list of conditions, and the following disclaimer.
014:
015: 2. Redistributions in binary form must reproduce the above copyright
016: notice, this list of conditions, and the disclaimer that follows
017: these conditions in the documentation and/or other materials
018: provided with the distribution.
019:
020: 3. The name "JDOM" must not be used to endorse or promote products
021: derived from this software without prior written permission. For
022: written permission, please contact <request_AT_jdom_DOT_org>.
023:
024: 4. Products derived from this software may not be called "JDOM", nor
025: may "JDOM" appear in their name, without prior written permission
026: from the JDOM Project Management <request_AT_jdom_DOT_org>.
027:
028: In addition, we request (but do not require) that you include in the
029: end-user documentation provided with the redistribution and/or in the
030: software itself an acknowledgement equivalent to the following:
031: "This product includes software developed by the
032: JDOM Project (http://www.jdom.org/)."
033: Alternatively, the acknowledgment may be graphical using the logos
034: available at http://www.jdom.org/images/logos.
035:
036: THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
037: WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
038: OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
039: DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
040: CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
041: SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
042: LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
043: USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
044: ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
045: OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
046: OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
047: SUCH DAMAGE.
048:
049: This software consists of voluntary contributions made by many
050: individuals on behalf of the JDOM Project and was originally
051: created by Jason Hunter <jhunter_AT_jdom_DOT_org> and
052: Brett McLaughlin <brett_AT_jdom_DOT_org>. For more information
053: on the JDOM Project, please see <http://www.jdom.org/>.
054:
055: */
056:
057: package org.jdom;
058:
059: import java.util.*;
060:
061: /**
062: * An XML processing instruction. Methods allow the user to obtain the target of
063: * the PI as well as its data. The data can always be accessed as a String or,
064: * if the data appears akin to an attribute list, can be retrieved as name/value
065: * pairs.
066: *
067: * @version $Revision: 1.1 $, $Date: 2005/04/27 09:32:39 $
068: * @author Brett McLaughlin
069: * @author Jason Hunter
070: * @author Steven Gould
071: */
072:
073: public class ProcessingInstruction extends Content {
074:
075: private static final String CVS_ID = "@(#) $RCSfile: ProcessingInstruction.java,v $ $Revision: 1.1 $ $Date: 2005/04/27 09:32:39 $ $Name: $";
076:
077: /** The target of the PI */
078: protected String target;
079:
080: /** The data for the PI as a String */
081: protected String rawData;
082:
083: /** The data for the PI in name/value pairs */
084: protected Map mapData;
085:
086: /**
087: * Default, no-args constructor for implementations
088: * to use if needed.
089: */
090: protected ProcessingInstruction() {
091: }
092:
093: /**
094: * This will create a new <code>ProcessingInstruction</code>
095: * with the specified target and data.
096: *
097: * @param target <code>String</code> target of PI.
098: * @param data <code>Map</code> data for PI, in
099: * 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
131: .checkProcessingInstructionTarget(newTarget)) != 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: * This will retrieve the target of the PI.
151: *
152: * @return <code>String</code> - target of PI.
153: */
154: public String getTarget() {
155: return target;
156: }
157:
158: /**
159: * This will return the raw data from all instructions.
160: *
161: * @return <code>String</code> - data of PI.
162: */
163: public String getData() {
164: return rawData;
165: }
166:
167: /**
168: * This will return a <code>List</code> containing the names of the
169: * "attribute" style pieces of name/value pairs in this PI's data.
170: *
171: * @return <code>List</code> - the <code>List</code> containing the
172: * "attribute" names.
173: */
174: public List getPseudoAttributeNames() {
175: Set mapDataSet = mapData.entrySet();
176: List nameList = new ArrayList();
177: for (Iterator i = mapDataSet.iterator(); i.hasNext();) {
178: String wholeSet = (i.next()).toString();
179: String attrName = wholeSet.substring(0, (wholeSet
180: .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: * This will return the value for a specific
227: * name/value pair on the PI. If no such pair is
228: * found for this PI, null is returned.
229: *
230: * @param name <code>String</code> name of name/value pair
231: * to lookup value for.
232: * @return <code>String</code> - value of name/value pair.
233: */
234: public String getPseudoAttributeValue(String name) {
235: return (String) mapData.get(name);
236: }
237:
238: /**
239: * This will set a pseudo attribute with the given name and value.
240: * If the PI data is not already in a pseudo-attribute format, this will
241: * replace the existing data.
242: *
243: * @param name <code>String</code> name of pair.
244: * @param value <code>String</code> value for pair.
245: * @return <code>ProcessingInstruction</code> this PI modified.
246: */
247: public ProcessingInstruction setPseudoAttribute(String name,
248: 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: * This will remove the pseudo attribute with the specified name.
266: *
267: * @param name name of pseudo attribute to remove
268: * @return <code>boolean</code> - whether the requested
269: * instruction was removed.
270: */
271: public boolean removePseudoAttribute(String name) {
272: if ((mapData.remove(name)) != null) {
273: rawData = toString(mapData);
274: return true;
275: }
276:
277: return false;
278: }
279:
280: /**
281: * This will convert the Map to a string representation.
282: *
283: * @param mapData <code>Map</code> PI data to convert
284: * @return a string representation of the Map as appropriate for a PI
285: */
286: private String toString(Map mapData) {
287: StringBuffer rawData = new StringBuffer();
288:
289: Iterator i = mapData.keySet().iterator();
290: while (i.hasNext()) {
291: String name = (String) i.next();
292: String value = (String) mapData.get(name);
293: rawData.append(name).append("=\"").append(value).append(
294: "\" ");
295: }
296: // Remove last space, if we did any appending
297: if (rawData.length() > 0) {
298: rawData.setLength(rawData.length() - 1);
299: }
300:
301: return rawData.toString();
302: }
303:
304: /**
305: * This will parse and load the instructions for the PI.
306: * This is separated to allow it to occur once and then be reused.
307: */
308: private Map parseData(String rawData) {
309: // The parsing here is done largely "by hand" which means the code
310: // gets a little tricky/messy. The following conditions should
311: // now be handled correctly:
312: // <?pi href="http://hi/a=b"?> Reads OK
313: // <?pi href = 'http://hi/a=b' ?> Reads OK
314: // <?pi href\t = \t'http://hi/a=b'?> Reads OK
315: // <?pi href = "http://hi/a=b"?> Reads OK
316: // <?pi?> Empty Map
317: // <?pi id=22?> Empty Map
318: // <?pi id='22?> Empty Map
319:
320: Map data = new HashMap();
321:
322: // System.out.println("rawData: " + rawData);
323:
324: // The inputData variable holds the part of rawData left to parse
325: String inputData = rawData.trim();
326:
327: // Iterate through the remaining inputData string
328: while (!inputData.trim().equals("")) {
329: //System.out.println("parseData() looking at: " + inputData);
330:
331: // Search for "name =", "name=" or "name1 name2..."
332: String name = "";
333: String value = "";
334: int startName = 0;
335: char previousChar = inputData.charAt(startName);
336: int pos = 1;
337: for (; pos < inputData.length(); pos++) {
338: char currentChar = inputData.charAt(pos);
339: if (currentChar == '=') {
340: name = inputData.substring(startName, pos).trim();
341: // Get the boundaries on the quoted string
342: // We use boundaries so we know where to start next
343: int[] bounds = extractQuotedString(inputData
344: .substring(pos + 1));
345: // A null value means a parse error and we return empty!
346: if (bounds == null) {
347: return new HashMap();
348: }
349: value = inputData.substring(bounds[0] + pos + 1,
350: bounds[1] + pos + 1);
351: pos += bounds[1] + 1; // skip past value
352: break;
353: } else if (Character.isWhitespace(previousChar)
354: && !Character.isWhitespace(currentChar)) {
355: startName = pos;
356: }
357:
358: previousChar = currentChar;
359: }
360:
361: // Remove the first pos characters; they have been processed
362: inputData = inputData.substring(pos);
363:
364: // System.out.println("Extracted (name, value) pair: ("
365: // + name + ", '" + value+"')");
366:
367: // If both a name and a value have been found, then add
368: // them to the data Map
369: if (name.length() > 0 && value != null) {
370: //if (data.containsKey(name)) {
371: // A repeat, that's a parse error, so return a null map
372: //return new HashMap();
373: //}
374: //else {
375: data.put(name, value);
376: //}
377: }
378: }
379:
380: return data;
381: }
382:
383: /**
384: * This is a helper routine, only used by parseData, to extract a
385: * quoted String from the input parameter, rawData. A quoted string
386: * can use either single or double quotes, but they must match up.
387: * A singly quoted string can contain an unbalanced amount of double
388: * quotes, or vice versa. For example, the String "JDOM's the best"
389: * is legal as is 'JDOM"s the best'.
390: *
391: * @param rawData the input string from which a quoted string is to
392: * be extracted.
393: * @return the first quoted string encountered in the input data. If
394: * no quoted string is found, then the empty string, "", is
395: * returned.
396: * @see #parseData
397: */
398: private static int[] extractQuotedString(String rawData) {
399: // Remembers whether we're actually in a quoted string yet
400: boolean inQuotes = false;
401:
402: // Remembers which type of quoted string we're in
403: char quoteChar = '"';
404:
405: // Stores the position of the first character inside
406: // the quoted string (i.e. the start of the return string)
407: int start = 0;
408:
409: // Iterate through the input string looking for the start
410: // and end of the quoted string
411: for (int pos = 0; pos < rawData.length(); pos++) {
412: char currentChar = rawData.charAt(pos);
413: if (currentChar == '"' || currentChar == '\'') {
414: if (!inQuotes) {
415: // We're entering a quoted string
416: quoteChar = currentChar;
417: inQuotes = true;
418: start = pos + 1;
419: } else if (quoteChar == currentChar) {
420: // We're leaving a quoted string
421: inQuotes = false;
422: return new int[] { start, pos };
423: }
424: // Otherwise we've encountered a quote
425: // inside a quote, so just continue
426: }
427: }
428:
429: return null;
430: }
431:
432: /**
433: * This returns a <code>String</code> representation of the
434: * <code>ProcessingInstruction</code>, suitable for debugging. If the XML
435: * representation of the <code>ProcessingInstruction</code> is desired,
436: * {@link org.jdom.output.XMLOutputter#outputString(ProcessingInstruction)}
437: * should be used.
438: *
439: * @return <code>String</code> - information about the
440: * <code>ProcessingInstruction</code>
441: */
442: public String toString() {
443: return new StringBuffer().append("[ProcessingInstruction: ")
444: .append(
445: new org.jdom.output.XMLOutputter()
446: .outputString(this )).append("]")
447: .toString();
448: }
449:
450: /**
451: * This will return a clone of this <code>ProcessingInstruction</code>.
452: *
453: * @return <code>Object</code> - clone of this
454: * <code>ProcessingInstruction</code>.
455: */
456: public Object clone() {
457: ProcessingInstruction pi = (ProcessingInstruction) super
458: .clone();
459:
460: // target and rawdata are immutable and references copied by
461: // Object.clone()
462:
463: // Create a new Map object for the clone (since Map isn't Cloneable)
464: if (mapData != null) {
465: pi.mapData = parseData(rawData);
466: }
467: return pi;
468: }
469: }
|