Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/jdom/ProcessingInstruction.java


1   /*--
2   
3    $Id: ProcessingInstruction.java,v 1.46 2004/02/27 11:32:57 jhunter Exp $
4   
5    Copyright (C) 2000-2004 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.46 $, $Date: 2004/02/27 11:32:57 $
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.46 $ $Date: 2004/02/27 11:32:57 $ $Name: jdom_1_0 $";
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 }