Coverage Report - org.apache.maven.shared.utils.xml.XMLEncode
 
Classes in this File Line Coverage Branch Coverage Complexity
XMLEncode
36%
39/108
24%
30/122
7.077
 
 1  
 package org.apache.maven.shared.utils.xml;
 2  
 
 3  
 /*
 4  
  * Licensed to the Apache Software Foundation (ASF) under one
 5  
  * or more contributor license agreements.  See the NOTICE file
 6  
  * distributed with this work for additional information
 7  
  * regarding copyright ownership.  The ASF licenses this file
 8  
  * to you under the Apache License, Version 2.0 (the
 9  
  * "License"); you may not use this file except in compliance
 10  
  * with the License.  You may obtain a copy of the License at
 11  
  *
 12  
  *  http://www.apache.org/licenses/LICENSE-2.0
 13  
  *
 14  
  * Unless required by applicable law or agreed to in writing,
 15  
  * software distributed under the License is distributed on an
 16  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 17  
  * KIND, either express or implied.  See the License for the
 18  
  * specific language governing permissions and limitations
 19  
  * under the License.
 20  
  */
 21  
 
 22  
 
 23  
 /**
 24  
  * Collection of XML encoding/decoding helpers. <br>
 25  
  * This is all about the special characters &amp; and &lt;, and for attributes
 26  
  * &quot; and &apos;. These must be encoded/decoded from/to XML.
 27  
  */
 28  0
 final class XMLEncode
 29  
 {
 30  
 
 31  
     private final static int CDATA_BLOCK_THRESHOLD_LENGTH = 12;
 32  
     private final static char DEFAULT_QUOTE_CHAR = '"';
 33  
 
 34  
     /**
 35  
      * Checks if this text purely consists of the white space characters
 36  
      * ' ',  TAB, NEWLINE.
 37  
      */
 38  
     public static boolean isWhiteSpace( String text )
 39  
     {
 40  0
         for( int i = 0; i < text.length(); i++ )
 41  
         {
 42  0
             char c = text.charAt( i );
 43  0
             if (!Character.isWhitespace( c )) {
 44  0
                 return false;
 45  
             }
 46  
         }
 47  0
         return true;
 48  
     }
 49  
 
 50  
     /**
 51  
      * Makes any text fit into XML attributes.
 52  
      */
 53  
     public static String xmlEncodeTextForAttribute( String text, char quoteChar )
 54  
     {
 55  19
         if( text == null )
 56  
         {
 57  0
             return null;
 58  
         }
 59  19
         return xmlEncodeTextAsPCDATA( text, true, quoteChar );
 60  
     }
 61  
 
 62  
     /**
 63  
      * Encodes text as XML in the most suitable way, either CDATA block or PCDATA.
 64  
      */
 65  
     public static String xmlEncodeText( String text )
 66  
     {
 67  21
         if( text == null )
 68  
         {
 69  0
             return null;
 70  
         }
 71  21
         if( !needsEncoding( text ) )
 72  
         {
 73  19
             return text;
 74  
         }
 75  
         else
 76  
         {
 77  
             // only encode as cdata if is is longer than CDATA block overhead:
 78  2
             if( text.length() > CDATA_BLOCK_THRESHOLD_LENGTH )
 79  
             {
 80  0
                 String cdata = xmlEncodeTextAsCDATABlock( text );
 81  0
                 if( cdata != null )
 82  
                 {
 83  0
                     return cdata;
 84  
                 }
 85  
             }
 86  
         }
 87  
         // if every thing else fails, do it the save way...
 88  2
         return xmlEncodeTextAsPCDATA( text );
 89  
     }
 90  
 
 91  
     /**
 92  
      * Encodes any text as PCDATA.
 93  
      */
 94  
     public static String xmlEncodeTextAsPCDATA( String text )
 95  
     {
 96  2
         if( text == null )
 97  
         {
 98  0
             return null;
 99  
         }
 100  2
         return xmlEncodeTextAsPCDATA( text, false );
 101  
     }
 102  
 
 103  
     /**
 104  
      * Encodes any text as PCDATA.
 105  
      *
 106  
      * @param forAttribute if you want
 107  
      *                     quotes and apostrophes specially treated for attributes
 108  
      */
 109  
     public static String xmlEncodeTextAsPCDATA( String text, boolean forAttribute )
 110  
     {
 111  2
         return xmlEncodeTextAsPCDATA( text, forAttribute, DEFAULT_QUOTE_CHAR );
 112  
     }
 113  
 
 114  
     /**
 115  
      * Encodes any text as PCDATA.
 116  
      *
 117  
      * @param forAttribute if you want
 118  
      *                     quotes and apostrophes specially treated for attributes
 119  
      * @param quoteChar    if this is for attributes this <code>char</code> is used to quote the attribute value
 120  
      */
 121  
     public static String xmlEncodeTextAsPCDATA( String text, boolean forAttribute, char quoteChar )
 122  
     {
 123  21
         if( text == null )
 124  
         {
 125  0
             return null;
 126  
         }
 127  
         char c;
 128  21
         StringBuilder n = new StringBuilder( text.length() * 2 );
 129  156
         for( int i = 0; i < text.length(); i++ )
 130  
         {
 131  135
             c = text.charAt( i );
 132  135
             switch( c )
 133  
             {
 134  
                 case '&':
 135  0
                     n.append( "&amp;" );
 136  0
                     break;
 137  
                 case '<':
 138  5
                     n.append( "&lt;" );
 139  5
                     break;
 140  
                 case '>': // FIX for sourceforge bug #802520 ("]]>" needs encoding)
 141  5
                     n.append( "&gt;" );
 142  5
                     break;
 143  
                 case '"':
 144  4
                     if( forAttribute )
 145  
                     {
 146  0
                         n.append( "&quot;" );
 147  
                     }
 148  
                     else
 149  
                     {
 150  4
                         n.append( c );
 151  
                     }
 152  4
                     break;
 153  
                 case '\'':
 154  0
                     if( forAttribute )
 155  
                     {
 156  0
                         n.append( "&apos;" );
 157  
                     }
 158  
                     else
 159  
                     {
 160  0
                         n.append( c );
 161  
                     }
 162  0
                     break;
 163  
                 case '\r':
 164  2
                     if ( forAttribute )
 165  
                     {
 166  2
                         if ( i == text.length() || text.charAt( i + 1 ) != '\n')
 167  
                         {
 168  1
                             n.append( "&#13;" );
 169  
                         }
 170  
                     }
 171  
                     else
 172  
                     {
 173  0
                         n.append( c );
 174  
                     }
 175  
                     // but skip the \r in \r\n
 176  
 
 177  
 
 178  0
                     break;
 179  
                 case '\n':
 180  2
                     if ( forAttribute )
 181  
                     {
 182  2
                         n.append( "&#10;" );
 183  
                     }
 184  
                     break;
 185  
 
 186  
                 default:
 187  
                 {
 188  117
                     n.append( c );
 189  
                     break;
 190  
                 }
 191  
             }
 192  
         }
 193  
 
 194  21
         if( forAttribute )
 195  
         {
 196  19
             n.append( quoteChar );
 197  19
             n.insert( 0, quoteChar );
 198  
         }
 199  
 
 200  21
         return n.toString();
 201  
     }
 202  
 
 203  
     /**
 204  
      * Returns string as CDATA block if possible, otherwise null.
 205  
      */
 206  
     public static String xmlEncodeTextAsCDATABlock( String text )
 207  
     {
 208  0
         if( text == null )
 209  
         {
 210  0
             return null;
 211  
         }
 212  0
         if( isCompatibleWithCDATABlock( text ) )
 213  
         {
 214  0
             return "<![CDATA[" + text + "]]>";
 215  
         }
 216  
         else
 217  
         {
 218  0
             return null;
 219  
         }
 220  
     }
 221  
 
 222  
     /**
 223  
      * Checks if this text needs encoding in order to be represented in XML.
 224  
      */
 225  
     public static boolean needsEncoding( String text )
 226  
     {
 227  21
         return needsEncoding( text, false );
 228  
     }
 229  
 
 230  
     /**
 231  
      * Checks if this text needs encoding in order to be represented in XML.
 232  
      * <p/>
 233  
      * Set <code>checkForAttr</code> if you want to check for storability in
 234  
      * an attribute.
 235  
      */
 236  
     public static boolean needsEncoding( String data, boolean checkForAttr )
 237  
     {
 238  21
         if( data == null )
 239  
         {
 240  0
             return false;
 241  
         }
 242  
         char c;
 243  238
         for( int i = 0; i < data.length(); i++ )
 244  
         {
 245  219
             c = data.charAt( i );
 246  219
             if( c == '&' || c == '<' || (checkForAttr && (c == '"' || c == '\'')) )
 247  
             {
 248  2
                 return true;
 249  
             }
 250  
         }
 251  19
         return false;
 252  
     }
 253  
 
 254  
     /**
 255  
      * Can this text be stored into a CDATA block?
 256  
      */
 257  
     public static boolean isCompatibleWithCDATABlock( String text )
 258  
     {
 259  0
         return text != null && (!text.contains("]]>"));
 260  
     }
 261  
 
 262  
     /**
 263  
      * Make CDATA out of possibly encoded PCDATA. <br>
 264  
      * E.g. make '&amp;' out of '&amp;amp;'
 265  
      */
 266  
     public static String xmlDecodeTextToCDATA( String pcdata )
 267  
     {
 268  0
         if( pcdata == null )
 269  
         {
 270  0
             return null;
 271  
         }
 272  
         char c, c1, c2, c3, c4, c5;
 273  0
         StringBuilder n = new StringBuilder( pcdata.length() );
 274  0
         for( int i = 0; i < pcdata.length(); i++ )
 275  
         {
 276  0
             c = pcdata.charAt( i );
 277  0
             if( c == '&' )
 278  
             {
 279  0
                 c1 = lookAhead( 1, i, pcdata );
 280  0
                 c2 = lookAhead( 2, i, pcdata );
 281  0
                 c3 = lookAhead( 3, i, pcdata );
 282  0
                 c4 = lookAhead( 4, i, pcdata );
 283  0
                 c5 = lookAhead( 5, i, pcdata );
 284  
 
 285  0
                 if( c1 == 'a' && c2 == 'm' && c3 == 'p' && c4 == ';' )
 286  
                 {
 287  0
                     n.append( "&" );
 288  0
                     i += 4;
 289  
                 }
 290  0
                 else if( c1 == 'l' && c2 == 't' && c3 == ';' )
 291  
                 {
 292  0
                     n.append( "<" );
 293  0
                     i += 3;
 294  
                 }
 295  0
                 else if( c1 == 'g' && c2 == 't' && c3 == ';' )
 296  
                 {
 297  0
                     n.append( ">" );
 298  0
                     i += 3;
 299  
                 }
 300  0
                 else if( c1 == 'q' && c2 == 'u' && c3 == 'o' && c4 == 't' && c5 == ';' )
 301  
                 {
 302  0
                     n.append( "\"" );
 303  0
                     i += 5;
 304  
                 }
 305  0
                 else if( c1 == 'a' && c2 == 'p' && c3 == 'o' && c4 == 's' && c5 == ';' )
 306  
                 {
 307  0
                     n.append( "'" );
 308  0
                     i += 5;
 309  
                 }
 310  
                 else
 311  
                 {
 312  0
                     n.append( "&" );
 313  
                 }
 314  
             }
 315  
             else
 316  
             {
 317  0
                 n.append( c );
 318  
             }
 319  
         }
 320  0
         return n.toString();
 321  
     }
 322  
 
 323  
     private static char lookAhead( int la, int offset, String data )
 324  
     {
 325  
         try
 326  
         {
 327  0
             return data.charAt( offset + la );
 328  
         }
 329  0
         catch( StringIndexOutOfBoundsException e )
 330  
         {
 331  0
             return 0x0;
 332  
         }
 333  
     }
 334  
 
 335  
     // combine multiple checks in one methods for speed
 336  
     private static boolean contains( String text, char[] chars )
 337  
     {
 338  0
         if( text == null || chars == null || chars.length == 0 )
 339  
         {
 340  0
             return false;
 341  
         }
 342  0
         for( int i = 0; i < text.length(); i++ )
 343  
         {
 344  0
             char c = text.charAt( i );
 345  0
             for (char aChar : chars) {
 346  0
                 if (aChar == c) {
 347  0
                     return true;
 348  
                 }
 349  
             }
 350  
         }
 351  0
         return false;
 352  
     }
 353  
 
 354  
 }