| 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 } |