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 LittleEndian.putShort(data, 0 + offset, getCharCount()); 296 data[ 2 + offset ] = getOptionFlags(); 297 298 if (!isUncompressedUnicode()) 299 { 300 StringUtil.putCompressedUnicode(getString(), data, 0x3 + offset); 301 } 302 else 303 { 304 StringUtil.putUncompressedUnicode(getString(), data, 305 0x3 + offset); 306 } 307 return getRecordSize(); 308 } 309 310 private boolean isUncompressedUnicode() 311 { 312 return (getOptionFlags() & 0x01) == 1; 313 } 314 315 public int getRecordSize() 316 { 317 int charsize = isUncompressedUnicode() ? 2 : 1; 318 return 3 + (getString().length() * charsize); 319 } 320 321 public short getSid() 322 { 323 return this.sid; 324 } 325 326 /** 327 * called by the constructor, should set class level fields. Should throw 328 * runtime exception for bad/icomplete data. 329 * 330 * @param data raw data 331 * @param size size of data 332 * @param offset of the records data (provided a big array of the file) 333 */ 334 335 protected void fillFields(byte [] data, short size, int offset) 336 { 337 } 338 339 public int compareTo(Object obj) 340 { 341 UnicodeString str = ( UnicodeString ) obj; 342 343 return this.getString().compareTo(str.getString()); 344 } 345 346 public boolean isRichText() 347 { 348 return (getOptionFlags() & RICH_TEXT_BIT) != 0; 349 } 350 351 int maxBrokenLength(final int proposedBrokenLength) 352 { 353 int rval = proposedBrokenLength; 354 355 if (isUncompressedUnicode()) 356 { 357 int proposedStringLength = proposedBrokenLength - 3; 358 359 if ((proposedStringLength % 2) == 1) 360 { 361 proposedStringLength--; 362 } 363 rval = proposedStringLength + 3; 364 } 365 return rval; 366 } 367 368 public boolean isExtendedText() 369 { 370 return (getOptionFlags() & EXT_BIT) != 0; 371 } 372 373 } 374