1 2 /* ==================================================================== 3 * The Apache Software License, Version 1.1 4 * 5 * Copyright (c) 2002 The Apache Software Foundation. All rights 6 * 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 following disclaimer in 17 * the documentation and/or other materials provided with the 18 * distribution. 19 * 20 * 3. The end-user documentation included with the redistribution, 21 * if any, must include the following acknowledgment: 22 * "This product includes software developed by the 23 * Apache Software Foundation (http://www.apache.org/)." 24 * Alternately, this acknowledgment may appear in the software itself, 25 * if and wherever such third-party acknowledgments normally appear. 26 * 27 * 4. The names "Apache" and "Apache Software Foundation" and 28 * "Apache POI" must not be used to endorse or promote products 29 * derived from this software without prior written permission. For 30 * written permission, please contact apache@apache.org. 31 * 32 * 5. Products derived from this software may not be called "Apache", 33 * "Apache POI", nor may "Apache" appear in their name, without 34 * prior written permission of the Apache Software Foundation. 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 APACHE SOFTWARE FOUNDATION OR 40 * ITS 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 * 50 * This software consists of voluntary contributions made by many 51 * individuals on behalf of the Apache Software Foundation. For more 52 * information on the Apache Software Foundation, please see 53 * <http://www.apache.org/>. 54 */ 55 56 package org.apache.poi.hssf.record; 57 58 import org.apache.poi.util.LittleEndian; 59 import org.apache.poi.util.StringUtil; 60 61 /** 62 * Title: Unicode String<P> 63 * Description: Unicode String record. We implement these as a record, although 64 * they are really just standard fields that are in several records. 65 * It is considered more desirable then repeating it in all of them.<P> 66 * REFERENCE: PG 264 Microsoft Excel 97 Developer's Kit (ISBN: 1-57231-498-2)<P> 67 * @author Andrew C. Oliver 68 * @author Marc Johnson (mjohnson at apache dot org) 69 * @author Glen Stampoultzis (glens at apache.org) 70 * @version 2.0-pre 71 */ 72 73 public class UnicodeString 74 extends Record 75 implements Comparable 76 { 77 public final static short sid = 0xFFF; 78 private short field_1_charCount; // = 0; 79 private byte field_2_optionflags; // = 0; 80 private String field_3_string; // = null; 81 private final int RICH_TEXT_BIT = 8; 82 private final int EXT_BIT = 4; 83 84 public UnicodeString() 85 { 86 } 87 88 89 public int hashCode() 90 { 91 int stringHash = 0; 92 if (field_3_string != null) 93 stringHash = field_3_string.hashCode(); 94 return field_1_charCount + stringHash; 95 } 96 97 /** 98 * Our handling of equals is inconsistent with compareTo. The trouble is because we don't truely understand 99 * rich text fields yet it's difficult to make a sound comparison. 100 * 101 * @param o The object to compare. 102 * @return true if the object is actually equal. 103 */ 104 public boolean equals(Object o) 105 { 106 if ((o == null) || (o.getClass() != this.getClass())) 107 { 108 return false; 109 } 110 UnicodeString other = ( UnicodeString ) o; 111 112 return ((field_1_charCount == other.field_1_charCount) 113 && (field_2_optionflags == other.field_2_optionflags) 114 && field_3_string.equals(other.field_3_string)); 115 } 116 117 /** 118 * construct a unicode string record and fill its fields, ID is ignored 119 * @param id - ignored 120 * @param size - size of the data 121 * @param data - the bytes of the string/fields 122 */ 123 124 public UnicodeString(short id, short size, byte [] data) 125 { 126 super(id, size, data); 127 } 128 129 /** 130 * construct a unicode string from a string fragment + data 131 */ 132 133 public UnicodeString(short id, short size, byte [] data, String prefix) 134 { 135 this(id, size, data); 136 field_3_string = prefix + field_3_string; 137 setCharCount(); 138 } 139 140 /** 141 * NO OP 142 */ 143 144 protected void validateSid(short id) 145 { 146 147 // included only for interface compliance 148 } 149 150 protected void fillFields(byte [] data, short size) 151 { 152 field_1_charCount = LittleEndian.getShort(data, 0); 153 field_2_optionflags = data[ 2 ]; 154 if ((field_2_optionflags & 1) == 0) 155 { 156 field_3_string = new String(data, 3, getCharCount()); 157 } 158 else 159 { 160 char[] array = new char[ getCharCount() ]; 161 162 for (int j = 0; j < array.length; j++) 163 { 164 array[ j ] = ( char ) LittleEndian.getShort(data, 165 3 + (j * 2)); 166 } 167 field_3_string = new String(array); 168 } 169 } 170 171 /** 172 * get the number of characters in the string 173 * 174 * 175 * @return number of characters 176 * 177 */ 178 179 public short getCharCount() 180 { 181 return field_1_charCount; 182 } 183 184 /** 185 * set the number of characters in the string 186 * @param cc - number of characters 187 */ 188 189 public void setCharCount(short cc) 190 { 191 field_1_charCount = cc; 192 } 193 194 /** 195 * sets the number of characters to whaterver number of characters is in the string 196 * currently. effectively setCharCount(getString.length()). 197 * @see #setString(String) 198 * @see #getString() 199 */ 200 201 public void setCharCount() 202 { 203 field_1_charCount = ( short ) field_3_string.length(); 204 } 205 206 /** 207 * get the option flags which among other things return if this is a 16-bit or 208 * 8 bit string 209 * 210 * @return optionflags bitmask 211 * 212 */ 213 214 public byte getOptionFlags() 215 { 216 return field_2_optionflags; 217 } 218 219 /** 220 * set the option flags which among other things return if this is a 16-bit or 221 * 8 bit string 222 * 223 * @param of optionflags bitmask 224 * 225 */ 226 227 public void setOptionFlags(byte of) 228 { 229 field_2_optionflags = of; 230 } 231 232 /** 233 * get the actual string this contains as a java String object 234 * 235 * 236 * @return String 237 * 238 */ 239 240 public String getString() 241 { 242 return field_3_string; 243 } 244 245 /** 246 * set the actual string this contains 247 * @param string the text 248 */ 249 250 public void setString(String string) 251 { 252 field_3_string = string; 253 if (getCharCount() < field_3_string.length()) 254 { 255 setCharCount(); 256 } 257 } 258 259 /** 260 * unlike the real records we return the same as "getString()" rather than debug info 261 * @see #getDebugInfo() 262 * @return String value of the record 263 */ 264 265 public String toString() 266 { 267 return getString(); 268 } 269 270 /** 271 * return a character representation of the fields of this record 272 * 273 * 274 * @return String of output for biffviewer etc. 275 * 276 */ 277 278 public String getDebugInfo() 279 { 280 StringBuffer buffer = new StringBuffer(); 281 282 buffer.append("[UNICODESTRING]\n"); 283 buffer.append(" .charcount = ") 284 .append(Integer.toHexString(getCharCount())).append("\n"); 285 buffer.append(" .optionflags = ") 286 .append(Integer.toHexString(getOptionFlags())).append("\n"); 287 buffer.append(" .string = ").append(getString()) 288 .append("\n"); 289 buffer.append("[/UNICODESTRING]\n"); 290 return buffer.toString(); 291 } 292 293 public int serialize(int offset, byte [] data) 294 { 295 int charsize = 1; 296 297 if (getOptionFlags() == 1) 298 { 299 charsize = 2; 300 } 301 302 // byte[] retval = new byte[ 3 + (getString().length() * charsize)]; 303 LittleEndian.putShort(data, 0 + offset, getCharCount()); 304 data[ 2 + offset ] = getOptionFlags(); 305 306 // System.out.println("Unicode: We've got "+retval[2]+" for our option flag"); 307 try { 308 String unicodeString = new 309 String(getString().getBytes("Unicode"),"Unicode"); 310 if (getOptionFlags() == 0) 311 { 312 StringUtil.putCompressedUnicode(unicodeString, data, 0x3 + 313 offset); 314 } 315 else 316 { 317 StringUtil.putUncompressedUnicode(unicodeString, data, 318 0x3 + offset); 319 } 320 } 321 catch (Exception e) { 322 if (getOptionFlags() == 0) 323 { 324 StringUtil.putCompressedUnicode(getString(), data, 0x3 + 325 offset); 326 } 327 else 328 { 329 StringUtil.putUncompressedUnicode(getString(), data, 330 0x3 + offset); 331 } 332 } 333 return getRecordSize(); 334 } 335 336 private boolean isUncompressedUnicode() 337 { 338 return (getOptionFlags() & 0x01) == 1; 339 } 340 341 public int getRecordSize() 342 { 343 int charsize = isUncompressedUnicode() ? 2 : 1; 344 return 3 + (getString().length() * charsize); 345 } 346 347 public short getSid() 348 { 349 return this.sid; 350 } 351 352 /** 353 * called by the constructor, should set class level fields. Should throw 354 * runtime exception for bad/icomplete data. 355 * 356 * @param data raw data 357 * @param size size of data 358 * @param offset of the records data (provided a big array of the file) 359 */ 360 361 protected void fillFields(byte [] data, short size, int offset) 362 { 363 } 364 365 public int compareTo(Object obj) 366 { 367 UnicodeString str = ( UnicodeString ) obj; 368 369 return this.getString().compareTo(str.getString()); 370 } 371 372 public boolean isRichText() 373 { 374 return (getOptionFlags() & RICH_TEXT_BIT) != 0; 375 } 376 377 int maxBrokenLength(final int proposedBrokenLength) 378 { 379 int rval = proposedBrokenLength; 380 381 if (isUncompressedUnicode()) 382 { 383 int proposedStringLength = proposedBrokenLength - 3; 384 385 if ((proposedStringLength % 2) == 1) 386 { 387 proposedStringLength--; 388 } 389 rval = proposedStringLength + 3; 390 } 391 return rval; 392 } 393 394 public boolean isExtendedText() 395 { 396 return (getOptionFlags() & EXT_BIT) != 0; 397 } 398 399 } 400