| /* ==================================================================== |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| ==================================================================== */ |
| |
| package org.apache.poi.hssf.record.common; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.apache.poi.hssf.record.RecordInputStream; |
| import org.apache.poi.hssf.record.cont.ContinuableRecordInput; |
| import org.apache.poi.hssf.record.cont.ContinuableRecordOutput; |
| import org.apache.poi.util.BitField; |
| import org.apache.poi.util.BitFieldFactory; |
| import org.apache.poi.util.LittleEndianInput; |
| import org.apache.poi.util.LittleEndianOutput; |
| import org.apache.poi.util.POILogFactory; |
| import org.apache.poi.util.POILogger; |
| import org.apache.poi.util.StringUtil; |
| |
| /** |
| * Title: Unicode String<p/> |
| * Description: Unicode String - just standard fields that are in several records. |
| * It is considered more desirable then repeating it in all of them.<p/> |
| * This is often called a XLUnicodeRichExtendedString in MS documentation.<p/> |
| * REFERENCE: PG 264 Microsoft Excel 97 Developer's Kit (ISBN: 1-57231-498-2)<p/> |
| * REFERENCE: PG 951 Excel Binary File Format (.xls) Structure Specification v20091214 |
| */ |
| public class UnicodeString implements Comparable<UnicodeString> { |
| // TODO - make this final when the compatibility version is removed |
| private static POILogger _logger = POILogFactory.getLogger(UnicodeString.class); |
| |
| private short field_1_charCount; |
| private byte field_2_optionflags; |
| private String field_3_string; |
| private List<FormatRun> field_4_format_runs; |
| private ExtRst field_5_ext_rst; |
| private static final BitField highByte = BitFieldFactory.getInstance(0x1); |
| // 0x2 is reserved |
| private static final BitField extBit = BitFieldFactory.getInstance(0x4); |
| private static final BitField richText = BitFieldFactory.getInstance(0x8); |
| |
| public static class FormatRun implements Comparable<FormatRun> { |
| final short _character; |
| short _fontIndex; |
| |
| public FormatRun(short character, short fontIndex) { |
| this._character = character; |
| this._fontIndex = fontIndex; |
| } |
| |
| public FormatRun(LittleEndianInput in) { |
| this(in.readShort(), in.readShort()); |
| } |
| |
| public short getCharacterPos() { |
| return _character; |
| } |
| |
| public short getFontIndex() { |
| return _fontIndex; |
| } |
| |
| public boolean equals(Object o) { |
| if (!(o instanceof FormatRun)) { |
| return false; |
| } |
| FormatRun other = ( FormatRun ) o; |
| |
| return _character == other._character && _fontIndex == other._fontIndex; |
| } |
| |
| public int compareTo(FormatRun r) { |
| if (_character == r._character && _fontIndex == r._fontIndex) { |
| return 0; |
| } |
| if (_character == r._character) { |
| return _fontIndex - r._fontIndex; |
| } |
| return _character - r._character; |
| } |
| |
| @Override |
| public int hashCode() { |
| assert false : "hashCode not designed"; |
| return 42; // any arbitrary constant will do |
| } |
| |
| public String toString() { |
| return "character="+_character+",fontIndex="+_fontIndex; |
| } |
| |
| public void serialize(LittleEndianOutput out) { |
| out.writeShort(_character); |
| out.writeShort(_fontIndex); |
| } |
| } |
| |
| // See page 681 |
| public static class ExtRst implements Comparable<ExtRst> { |
| private short reserved; |
| |
| // This is a Phs (see page 881) |
| private short formattingFontIndex; |
| private short formattingOptions; |
| |
| // This is a RPHSSub (see page 894) |
| private int numberOfRuns; |
| private String phoneticText; |
| |
| // This is an array of PhRuns (see page 881) |
| private PhRun[] phRuns; |
| // Sometimes there's some cruft at the end |
| private byte[] extraData; |
| |
| private void populateEmpty() { |
| reserved = 1; |
| phoneticText = ""; |
| phRuns = new PhRun[0]; |
| extraData = new byte[0]; |
| } |
| |
| protected ExtRst() { |
| populateEmpty(); |
| } |
| protected ExtRst(LittleEndianInput in, int expectedLength) { |
| reserved = in.readShort(); |
| |
| // Old style detection (Reserved = 0xFF) |
| if(reserved == -1) { |
| populateEmpty(); |
| return; |
| } |
| |
| // Spot corrupt records |
| if(reserved != 1) { |
| _logger.log(POILogger.WARN, "Warning - ExtRst has wrong magic marker, expecting 1 but found " + reserved + " - ignoring"); |
| // Grab all the remaining data, and ignore it |
| for(int i=0; i<expectedLength-2; i++) { |
| in.readByte(); |
| } |
| // And make us be empty |
| populateEmpty(); |
| return; |
| } |
| |
| // Carry on reading in as normal |
| short stringDataSize = in.readShort(); |
| |
| formattingFontIndex = in.readShort(); |
| formattingOptions = in.readShort(); |
| |
| // RPHSSub |
| numberOfRuns = in.readUShort(); |
| short length1 = in.readShort(); |
| // No really. Someone clearly forgot to read |
| // the docs on their datastructure... |
| short length2 = in.readShort(); |
| // And sometimes they write out garbage :( |
| if(length1 == 0 && length2 > 0) { |
| length2 = 0; |
| } |
| if(length1 != length2) { |
| throw new IllegalStateException( |
| "The two length fields of the Phonetic Text don't agree! " + |
| length1 + " vs " + length2 |
| ); |
| } |
| phoneticText = StringUtil.readUnicodeLE(in, length1); |
| |
| int runData = stringDataSize - 4 - 6 - (2*phoneticText.length()); |
| int numRuns = (runData / 6); |
| phRuns = new PhRun[numRuns]; |
| for(int i=0; i<phRuns.length; i++) { |
| phRuns[i] = new PhRun(in); |
| } |
| |
| int extraDataLength = runData - (numRuns*6); |
| if(extraDataLength < 0) { |
| _logger.log( POILogger.WARN, "Warning - ExtRst overran by " + (0-extraDataLength) + " bytes"); |
| extraDataLength = 0; |
| } |
| extraData = new byte[extraDataLength]; |
| for(int i=0; i<extraData.length; i++) { |
| extraData[i] = in.readByte(); |
| } |
| } |
| /** |
| * Returns our size, excluding our |
| * 4 byte header |
| */ |
| protected int getDataSize() { |
| return 4 + 6 + (2*phoneticText.length()) + |
| (6*phRuns.length) + extraData.length; |
| } |
| protected void serialize(ContinuableRecordOutput out) { |
| int dataSize = getDataSize(); |
| |
| out.writeContinueIfRequired(8); |
| out.writeShort(reserved); |
| out.writeShort(dataSize); |
| out.writeShort(formattingFontIndex); |
| out.writeShort(formattingOptions); |
| |
| out.writeContinueIfRequired(6); |
| out.writeShort(numberOfRuns); |
| out.writeShort(phoneticText.length()); |
| out.writeShort(phoneticText.length()); |
| |
| out.writeContinueIfRequired(phoneticText.length()*2); |
| StringUtil.putUnicodeLE(phoneticText, out); |
| |
| for(int i=0; i<phRuns.length; i++) { |
| phRuns[i].serialize(out); |
| } |
| |
| out.write(extraData); |
| } |
| |
| public boolean equals(Object obj) { |
| if(! (obj instanceof ExtRst)) { |
| return false; |
| } |
| ExtRst other = (ExtRst)obj; |
| return (compareTo(other) == 0); |
| } |
| public int compareTo(ExtRst o) { |
| int result; |
| |
| result = reserved - o.reserved; |
| if (result != 0) { |
| return result; |
| } |
| result = formattingFontIndex - o.formattingFontIndex; |
| if (result != 0) { |
| return result; |
| } |
| result = formattingOptions - o.formattingOptions; |
| if (result != 0) { |
| return result; |
| } |
| result = numberOfRuns - o.numberOfRuns; |
| if (result != 0) { |
| return result; |
| } |
| |
| result = phoneticText.compareTo(o.phoneticText); |
| if (result != 0) { |
| return result; |
| } |
| |
| result = phRuns.length - o.phRuns.length; |
| if (result != 0) { |
| return result; |
| } |
| for(int i=0; i<phRuns.length; i++) { |
| result = phRuns[i].phoneticTextFirstCharacterOffset - o.phRuns[i].phoneticTextFirstCharacterOffset; |
| if (result != 0) { |
| return result; |
| } |
| result = phRuns[i].realTextFirstCharacterOffset - o.phRuns[i].realTextFirstCharacterOffset; |
| if (result != 0) { |
| return result; |
| } |
| result = phRuns[i].realTextLength - o.phRuns[i].realTextLength; |
| if (result != 0) { |
| return result; |
| } |
| } |
| |
| result = Arrays.hashCode(extraData)-Arrays.hashCode(o.extraData); |
| |
| return result; |
| } |
| |
| @Override |
| public int hashCode() { |
| int hash = reserved; |
| hash = 31*hash+formattingFontIndex; |
| hash = 31*hash+formattingOptions; |
| hash = 31*hash+numberOfRuns; |
| hash = 31*hash+phoneticText.hashCode(); |
| |
| if (phRuns != null) { |
| for (PhRun ph : phRuns) { |
| hash = 31*hash+ph.phoneticTextFirstCharacterOffset; |
| hash = 31*hash+ph.realTextFirstCharacterOffset; |
| hash = 31*hash+ph.realTextLength; |
| } |
| } |
| return hash; |
| } |
| |
| protected ExtRst clone() { |
| ExtRst ext = new ExtRst(); |
| ext.reserved = reserved; |
| ext.formattingFontIndex = formattingFontIndex; |
| ext.formattingOptions = formattingOptions; |
| ext.numberOfRuns = numberOfRuns; |
| ext.phoneticText = phoneticText; |
| ext.phRuns = new PhRun[phRuns.length]; |
| for(int i=0; i<ext.phRuns.length; i++) { |
| ext.phRuns[i] = new PhRun( |
| phRuns[i].phoneticTextFirstCharacterOffset, |
| phRuns[i].realTextFirstCharacterOffset, |
| phRuns[i].realTextLength |
| ); |
| } |
| return ext; |
| } |
| |
| public short getFormattingFontIndex() { |
| return formattingFontIndex; |
| } |
| public short getFormattingOptions() { |
| return formattingOptions; |
| } |
| public int getNumberOfRuns() { |
| return numberOfRuns; |
| } |
| public String getPhoneticText() { |
| return phoneticText; |
| } |
| public PhRun[] getPhRuns() { |
| return phRuns; |
| } |
| } |
| public static class PhRun { |
| private int phoneticTextFirstCharacterOffset; |
| private int realTextFirstCharacterOffset; |
| private int realTextLength; |
| |
| public PhRun(int phoneticTextFirstCharacterOffset, |
| int realTextFirstCharacterOffset, int realTextLength) { |
| this.phoneticTextFirstCharacterOffset = phoneticTextFirstCharacterOffset; |
| this.realTextFirstCharacterOffset = realTextFirstCharacterOffset; |
| this.realTextLength = realTextLength; |
| } |
| private PhRun(LittleEndianInput in) { |
| phoneticTextFirstCharacterOffset = in.readUShort(); |
| realTextFirstCharacterOffset = in.readUShort(); |
| realTextLength = in.readUShort(); |
| } |
| private void serialize(ContinuableRecordOutput out) { |
| out.writeContinueIfRequired(6); |
| out.writeShort(phoneticTextFirstCharacterOffset); |
| out.writeShort(realTextFirstCharacterOffset); |
| out.writeShort(realTextLength); |
| } |
| } |
| |
| private UnicodeString() { |
| //Used for clone method. |
| } |
| |
| public UnicodeString(String str) |
| { |
| setString(str); |
| } |
| |
| |
| |
| public int hashCode() |
| { |
| int stringHash = 0; |
| if (field_3_string != null) { |
| stringHash = field_3_string.hashCode(); |
| } |
| return field_1_charCount + stringHash; |
| } |
| |
| /** |
| * Our handling of equals is inconsistent with compareTo. The trouble is because we don't truely understand |
| * rich text fields yet it's difficult to make a sound comparison. |
| * |
| * @param o The object to compare. |
| * @return true if the object is actually equal. |
| */ |
| public boolean equals(Object o) |
| { |
| if (!(o instanceof UnicodeString)) { |
| return false; |
| } |
| UnicodeString other = (UnicodeString) o; |
| |
| //OK lets do this in stages to return a quickly, first check the actual string |
| if (field_1_charCount != other.field_1_charCount |
| || field_2_optionflags != other.field_2_optionflags |
| || !field_3_string.equals(other.field_3_string)) { |
| return false; |
| } |
| |
| //OK string appears to be equal but now lets compare formatting runs |
| if (field_4_format_runs == null) { |
| // Strings are equal, and there are not formatting runs. |
| return (other.field_4_format_runs == null); |
| } else if (other.field_4_format_runs == null) { |
| // Strings are equal, but one or the other has formatting runs |
| return false; |
| } |
| |
| //Strings are equal, so now compare formatting runs. |
| int size = field_4_format_runs.size(); |
| if (size != other.field_4_format_runs.size()) { |
| return false; |
| } |
| |
| for (int i=0;i<size;i++) { |
| FormatRun run1 = field_4_format_runs.get(i); |
| FormatRun run2 = other.field_4_format_runs.get(i); |
| |
| if (!run1.equals(run2)) { |
| return false; |
| } |
| } |
| |
| // Well the format runs are equal as well!, better check the ExtRst data |
| if (field_5_ext_rst == null) { |
| return (other.field_5_ext_rst == null); |
| } else if (other.field_5_ext_rst == null) { |
| return false; |
| } |
| |
| return field_5_ext_rst.equals(other.field_5_ext_rst); |
| } |
| |
| /** |
| * construct a unicode string record and fill its fields, ID is ignored |
| * @param in the RecordInputstream to read the record from |
| */ |
| public UnicodeString(RecordInputStream in) { |
| field_1_charCount = in.readShort(); |
| field_2_optionflags = in.readByte(); |
| |
| int runCount = 0; |
| int extensionLength = 0; |
| //Read the number of rich runs if rich text. |
| if (isRichText()) { |
| runCount = in.readShort(); |
| } |
| //Read the size of extended data if present. |
| if (isExtendedText()) { |
| extensionLength = in.readInt(); |
| } |
| |
| boolean isCompressed = ((field_2_optionflags & 1) == 0); |
| int cc = getCharCount(); |
| field_3_string = (isCompressed) ? in.readCompressedUnicode(cc) : in.readUnicodeLEString(cc); |
| |
| if (isRichText() && (runCount > 0)) { |
| field_4_format_runs = new ArrayList<FormatRun>(runCount); |
| for (int i=0;i<runCount;i++) { |
| field_4_format_runs.add(new FormatRun(in)); |
| } |
| } |
| |
| if (isExtendedText() && (extensionLength > 0)) { |
| field_5_ext_rst = new ExtRst(new ContinuableRecordInput(in), extensionLength); |
| if(field_5_ext_rst.getDataSize()+4 != extensionLength) { |
| _logger.log(POILogger.WARN, "ExtRst was supposed to be " + extensionLength + " bytes long, but seems to actually be " + (field_5_ext_rst.getDataSize() + 4)); |
| } |
| } |
| } |
| |
| |
| |
| /** |
| * get the number of characters in the string, |
| * as an un-wrapped int |
| * |
| * @return number of characters |
| */ |
| public int getCharCount() { |
| if(field_1_charCount < 0) { |
| return field_1_charCount + 65536; |
| } |
| return field_1_charCount; |
| } |
| |
| /** |
| * get the number of characters in the string, |
| * wrapped as needed to fit within a short |
| * |
| * @return number of characters |
| */ |
| public short getCharCountShort() { |
| return field_1_charCount; |
| } |
| |
| /** |
| * set the number of characters in the string |
| * @param cc - number of characters |
| */ |
| |
| public void setCharCount(short cc) |
| { |
| field_1_charCount = cc; |
| } |
| |
| /** |
| * get the option flags which among other things return if this is a 16-bit or |
| * 8 bit string |
| * |
| * @return optionflags bitmask |
| * |
| */ |
| |
| public byte getOptionFlags() |
| { |
| return field_2_optionflags; |
| } |
| |
| /** |
| * set the option flags which among other things return if this is a 16-bit or |
| * 8 bit string |
| * |
| * @param of optionflags bitmask |
| * |
| */ |
| |
| public void setOptionFlags(byte of) |
| { |
| field_2_optionflags = of; |
| } |
| |
| /** |
| * @return the actual string this contains as a java String object |
| */ |
| public String getString() |
| { |
| return field_3_string; |
| } |
| |
| /** |
| * set the actual string this contains |
| * @param string the text |
| */ |
| |
| public void setString(String string) |
| { |
| field_3_string = string; |
| setCharCount((short)field_3_string.length()); |
| // scan for characters greater than 255 ... if any are |
| // present, we have to use 16-bit encoding. Otherwise, we |
| // can use 8-bit encoding |
| boolean useUTF16 = false; |
| int strlen = string.length(); |
| |
| for ( int j = 0; j < strlen; j++ ) { |
| if ( string.charAt( j ) > 255 ) { |
| useUTF16 = true; |
| break; |
| } |
| } |
| if (useUTF16) { |
| //Set the uncompressed bit |
| field_2_optionflags = highByte.setByte(field_2_optionflags); |
| } else { |
| field_2_optionflags = highByte.clearByte(field_2_optionflags); |
| } |
| } |
| |
| public int getFormatRunCount() { |
| return (field_4_format_runs == null) ? 0 : field_4_format_runs.size(); |
| } |
| |
| public FormatRun getFormatRun(int index) { |
| if (field_4_format_runs == null) { |
| return null; |
| } |
| if (index < 0 || index >= field_4_format_runs.size()) { |
| return null; |
| } |
| return field_4_format_runs.get(index); |
| } |
| |
| private int findFormatRunAt(int characterPos) { |
| int size = field_4_format_runs.size(); |
| for (int i=0;i<size;i++) { |
| FormatRun r = field_4_format_runs.get(i); |
| if (r._character == characterPos) { |
| return i; |
| } else if (r._character > characterPos) { |
| return -1; |
| } |
| } |
| return -1; |
| } |
| |
| /** Adds a font run to the formatted string. |
| * |
| * If a font run exists at the current charcter location, then it is |
| * replaced with the font run to be added. |
| */ |
| public void addFormatRun(FormatRun r) { |
| if (field_4_format_runs == null) { |
| field_4_format_runs = new ArrayList<FormatRun>(); |
| } |
| |
| int index = findFormatRunAt(r._character); |
| if (index != -1) { |
| field_4_format_runs.remove(index); |
| } |
| |
| field_4_format_runs.add(r); |
| //Need to sort the font runs to ensure that the font runs appear in |
| //character order |
| Collections.sort(field_4_format_runs); |
| |
| //Make sure that we now say that we are a rich string |
| field_2_optionflags = richText.setByte(field_2_optionflags); |
| } |
| |
| public Iterator<FormatRun> formatIterator() { |
| if (field_4_format_runs != null) { |
| return field_4_format_runs.iterator(); |
| } |
| return null; |
| } |
| |
| public void removeFormatRun(FormatRun r) { |
| field_4_format_runs.remove(r); |
| if (field_4_format_runs.size() == 0) { |
| field_4_format_runs = null; |
| field_2_optionflags = richText.clearByte(field_2_optionflags); |
| } |
| } |
| |
| public void clearFormatting() { |
| field_4_format_runs = null; |
| field_2_optionflags = richText.clearByte(field_2_optionflags); |
| } |
| |
| |
| public ExtRst getExtendedRst() { |
| return this.field_5_ext_rst; |
| } |
| void setExtendedRst(ExtRst ext_rst) { |
| if (ext_rst != null) { |
| field_2_optionflags = extBit.setByte(field_2_optionflags); |
| } else { |
| field_2_optionflags = extBit.clearByte(field_2_optionflags); |
| } |
| this.field_5_ext_rst = ext_rst; |
| } |
| |
| |
| /** |
| * Swaps all use in the string of one font index |
| * for use of a different font index. |
| * Normally only called when fonts have been |
| * removed / re-ordered |
| */ |
| public void swapFontUse(short oldFontIndex, short newFontIndex) { |
| for (FormatRun run : field_4_format_runs) { |
| if(run._fontIndex == oldFontIndex) { |
| run._fontIndex = newFontIndex; |
| } |
| } |
| } |
| |
| /** |
| * unlike the real records we return the same as "getString()" rather than debug info |
| * @see #getDebugInfo() |
| * @return String value of the record |
| */ |
| |
| public String toString() |
| { |
| return getString(); |
| } |
| |
| /** |
| * return a character representation of the fields of this record |
| * |
| * |
| * @return String of output for biffviewer etc. |
| * |
| */ |
| public String getDebugInfo() |
| { |
| StringBuffer buffer = new StringBuffer(); |
| |
| buffer.append("[UNICODESTRING]\n"); |
| buffer.append(" .charcount = ") |
| .append(Integer.toHexString(getCharCount())).append("\n"); |
| buffer.append(" .optionflags = ") |
| .append(Integer.toHexString(getOptionFlags())).append("\n"); |
| buffer.append(" .string = ").append(getString()).append("\n"); |
| if (field_4_format_runs != null) { |
| for (int i = 0; i < field_4_format_runs.size();i++) { |
| FormatRun r = field_4_format_runs.get(i); |
| buffer.append(" .format_run"+i+" = ").append(r.toString()).append("\n"); |
| } |
| } |
| if (field_5_ext_rst != null) { |
| buffer.append(" .field_5_ext_rst = ").append("\n"); |
| buffer.append( field_5_ext_rst.toString() ).append("\n"); |
| } |
| buffer.append("[/UNICODESTRING]\n"); |
| return buffer.toString(); |
| } |
| |
| /** |
| * Serialises out the String. There are special rules |
| * about where we can and can't split onto |
| * Continue records. |
| */ |
| public void serialize(ContinuableRecordOutput out) { |
| int numberOfRichTextRuns = 0; |
| int extendedDataSize = 0; |
| if (isRichText() && field_4_format_runs != null) { |
| numberOfRichTextRuns = field_4_format_runs.size(); |
| } |
| if (isExtendedText() && field_5_ext_rst != null) { |
| extendedDataSize = 4 + field_5_ext_rst.getDataSize(); |
| } |
| |
| // Serialise the bulk of the String |
| // The writeString handles tricky continue stuff for us |
| out.writeString(field_3_string, numberOfRichTextRuns, extendedDataSize); |
| |
| if (numberOfRichTextRuns > 0) { |
| |
| //This will ensure that a run does not split a continue |
| for (int i=0;i<numberOfRichTextRuns;i++) { |
| if (out.getAvailableSpace() < 4) { |
| out.writeContinue(); |
| } |
| FormatRun r = field_4_format_runs.get(i); |
| r.serialize(out); |
| } |
| } |
| |
| if (extendedDataSize > 0 && field_5_ext_rst != null) { |
| field_5_ext_rst.serialize(out); |
| } |
| } |
| |
| public int compareTo(UnicodeString str) { |
| |
| int result = getString().compareTo(str.getString()); |
| |
| //As per the equals method lets do this in stages |
| if (result != 0) { |
| return result; |
| } |
| |
| //OK string appears to be equal but now lets compare formatting runs |
| if (field_4_format_runs == null) { |
| //Strings are equal, and there are no formatting runs. -> 0 |
| //Strings are equal, but one or the other has formatting runs -> 1 |
| return (str.field_4_format_runs == null) ? 0 : 1; |
| } else if (str.field_4_format_runs == null) { |
| //Strings are equal, but one or the other has formatting runs |
| return -1; |
| } |
| |
| //Strings are equal, so now compare formatting runs. |
| int size = field_4_format_runs.size(); |
| if (size != str.field_4_format_runs.size()) { |
| return size - str.field_4_format_runs.size(); |
| } |
| |
| for (int i=0;i<size;i++) { |
| FormatRun run1 = field_4_format_runs.get(i); |
| FormatRun run2 = str.field_4_format_runs.get(i); |
| |
| result = run1.compareTo(run2); |
| if (result != 0) { |
| return result; |
| } |
| } |
| |
| //Well the format runs are equal as well!, better check the ExtRst data |
| if (field_5_ext_rst == null) { |
| return (str.field_5_ext_rst == null) ? 0 : 1; |
| } else if (str.field_5_ext_rst == null) { |
| return -1; |
| } else { |
| return field_5_ext_rst.compareTo(str.field_5_ext_rst); |
| } |
| } |
| |
| private boolean isRichText() { |
| return richText.isSet(getOptionFlags()); |
| } |
| |
| private boolean isExtendedText() { |
| return extBit.isSet(getOptionFlags()); |
| } |
| |
| public Object clone() { |
| UnicodeString str = new UnicodeString(); |
| str.field_1_charCount = field_1_charCount; |
| str.field_2_optionflags = field_2_optionflags; |
| str.field_3_string = field_3_string; |
| if (field_4_format_runs != null) { |
| str.field_4_format_runs = new ArrayList<FormatRun>(); |
| for (FormatRun r : field_4_format_runs) { |
| str.field_4_format_runs.add(new FormatRun(r._character, r._fontIndex)); |
| } |
| } |
| if (field_5_ext_rst != null) { |
| str.field_5_ext_rst = field_5_ext_rst.clone(); |
| } |
| |
| return str; |
| } |
| } |