Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

UnicodeString.java 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.hssf.record.common;
  16. import java.util.ArrayList;
  17. import java.util.Collections;
  18. import java.util.Iterator;
  19. import java.util.List;
  20. import org.apache.poi.hssf.record.RecordInputStream;
  21. import org.apache.poi.hssf.record.cont.ContinuableRecordOutput;
  22. import org.apache.poi.util.BitField;
  23. import org.apache.poi.util.BitFieldFactory;
  24. import org.apache.poi.util.LittleEndianInput;
  25. import org.apache.poi.util.LittleEndianOutput;
  26. import org.apache.poi.util.StringUtil;
  27. /**
  28. * Title: Unicode String<p/>
  29. * Description: Unicode String - just standard fields that are in several records.
  30. * It is considered more desirable then repeating it in all of them.<p/>
  31. * This is often called a XLUnicodeRichExtendedString in MS documentation.<p/>
  32. * REFERENCE: PG 264 Microsoft Excel 97 Developer's Kit (ISBN: 1-57231-498-2)<p/>
  33. * REFERENCE: PG 951 Excel Binary File Format (.xls) Structure Specification v20091214
  34. */
  35. public final class UnicodeString implements Comparable<UnicodeString> {
  36. private short field_1_charCount;
  37. private byte field_2_optionflags;
  38. private String field_3_string;
  39. private List<FormatRun> field_4_format_runs;
  40. private ExtRst field_5_ext_rst;
  41. private static final BitField highByte = BitFieldFactory.getInstance(0x1);
  42. // 0x2 is reserved
  43. private static final BitField extBit = BitFieldFactory.getInstance(0x4);
  44. private static final BitField richText = BitFieldFactory.getInstance(0x8);
  45. public static class FormatRun implements Comparable<FormatRun> {
  46. final short _character;
  47. short _fontIndex;
  48. public FormatRun(short character, short fontIndex) {
  49. this._character = character;
  50. this._fontIndex = fontIndex;
  51. }
  52. public FormatRun(LittleEndianInput in) {
  53. this(in.readShort(), in.readShort());
  54. }
  55. public short getCharacterPos() {
  56. return _character;
  57. }
  58. public short getFontIndex() {
  59. return _fontIndex;
  60. }
  61. public boolean equals(Object o) {
  62. if (!(o instanceof FormatRun)) {
  63. return false;
  64. }
  65. FormatRun other = ( FormatRun ) o;
  66. return _character == other._character && _fontIndex == other._fontIndex;
  67. }
  68. public int compareTo(FormatRun r) {
  69. if (_character == r._character && _fontIndex == r._fontIndex) {
  70. return 0;
  71. }
  72. if (_character == r._character) {
  73. return _fontIndex - r._fontIndex;
  74. }
  75. return _character - r._character;
  76. }
  77. public String toString() {
  78. return "character="+_character+",fontIndex="+_fontIndex;
  79. }
  80. public void serialize(LittleEndianOutput out) {
  81. out.writeShort(_character);
  82. out.writeShort(_fontIndex);
  83. }
  84. }
  85. // See page 681
  86. public static class ExtRst implements Comparable<ExtRst> {
  87. private short reserved;
  88. // This is a Phs (see page 881)
  89. private short formattingFontIndex;
  90. private short formattingOptions;
  91. // This is a RPHSSub (see page 894)
  92. private int numberOfRuns;
  93. private String phoneticText;
  94. // This is an array of PhRuns (see page 881)
  95. private PhRun[] phRuns;
  96. // Sometimes there's some cruft at the end
  97. private byte[] extraData;
  98. private void populateEmpty() {
  99. reserved = 1;
  100. phoneticText = "";
  101. phRuns = new PhRun[0];
  102. extraData = new byte[0];
  103. }
  104. protected ExtRst() {
  105. populateEmpty();
  106. }
  107. protected ExtRst(LittleEndianInput in, int expectedLength) {
  108. reserved = in.readShort();
  109. // Old style detection (Reserved = 0xFF)
  110. if(reserved == -1) {
  111. populateEmpty();
  112. return;
  113. }
  114. // Spot corrupt records
  115. if(reserved != 1) {
  116. System.err.println("Warning - ExtRst was has wrong magic marker, expecting 1 but found " + reserved + " - ignoring");
  117. // Grab all the remaining data, and ignore it
  118. for(int i=0; i<expectedLength-2; i++) {
  119. in.readByte();
  120. }
  121. // And make us be empty
  122. populateEmpty();
  123. return;
  124. }
  125. // Carry on reading in as normal
  126. short stringDataSize = in.readShort();
  127. formattingFontIndex = in.readShort();
  128. formattingOptions = in.readShort();
  129. // RPHSSub
  130. numberOfRuns = in.readUShort();
  131. short length1 = in.readShort();
  132. // No really. Someone clearly forgot to read
  133. // the docs on their datastructure...
  134. short length2 = in.readShort();
  135. // And sometimes they write out garbage :(
  136. if(length1 == 0 && length2 > 0) {
  137. length2 = 0;
  138. }
  139. if(length1 != length2) {
  140. throw new IllegalStateException(
  141. "The two length fields of the Phonetic Text don't agree! " +
  142. length1 + " vs " + length2
  143. );
  144. }
  145. phoneticText = StringUtil.readUnicodeLE(in, length1);
  146. int runData = stringDataSize - 4 - 6 - (2*phoneticText.length());
  147. int numRuns = (runData / 6);
  148. phRuns = new PhRun[numRuns];
  149. for(int i=0; i<phRuns.length; i++) {
  150. phRuns[i] = new PhRun(in);
  151. }
  152. int extraDataLength = runData - (numRuns*6);
  153. if(extraDataLength < 0) {
  154. System.err.println("Warning - ExtRst overran by " + (0-extraDataLength) + " bytes");
  155. extraDataLength = 0;
  156. }
  157. extraData = new byte[extraDataLength];
  158. for(int i=0; i<extraData.length; i++) {
  159. extraData[i] = in.readByte();
  160. }
  161. }
  162. /**
  163. * Returns our size, excluding our
  164. * 4 byte header
  165. */
  166. protected int getDataSize() {
  167. return 4 + 6 + (2*phoneticText.length()) +
  168. (6*phRuns.length) + extraData.length;
  169. }
  170. protected void serialize(ContinuableRecordOutput out) {
  171. int dataSize = getDataSize();
  172. out.writeContinueIfRequired(8);
  173. out.writeShort(reserved);
  174. out.writeShort(dataSize);
  175. out.writeShort(formattingFontIndex);
  176. out.writeShort(formattingOptions);
  177. out.writeContinueIfRequired(6);
  178. out.writeShort(numberOfRuns);
  179. out.writeShort(phoneticText.length());
  180. out.writeShort(phoneticText.length());
  181. out.writeContinueIfRequired(phoneticText.length()*2);
  182. StringUtil.putUnicodeLE(phoneticText, out);
  183. for(int i=0; i<phRuns.length; i++) {
  184. phRuns[i].serialize(out);
  185. }
  186. out.write(extraData);
  187. }
  188. public boolean equals(Object obj) {
  189. if(! (obj instanceof ExtRst)) {
  190. return false;
  191. }
  192. ExtRst other = (ExtRst)obj;
  193. return (compareTo(other) == 0);
  194. }
  195. public int compareTo(ExtRst o) {
  196. int result;
  197. result = reserved - o.reserved;
  198. if(result != 0) return result;
  199. result = formattingFontIndex - o.formattingFontIndex;
  200. if(result != 0) return result;
  201. result = formattingOptions - o.formattingOptions;
  202. if(result != 0) return result;
  203. result = numberOfRuns - o.numberOfRuns;
  204. if(result != 0) return result;
  205. result = phoneticText.compareTo(o.phoneticText);
  206. if(result != 0) return result;
  207. result = phRuns.length - o.phRuns.length;
  208. if(result != 0) return result;
  209. for(int i=0; i<phRuns.length; i++) {
  210. result = phRuns[i].phoneticTextFirstCharacterOffset - o.phRuns[i].phoneticTextFirstCharacterOffset;
  211. if(result != 0) return result;
  212. result = phRuns[i].realTextFirstCharacterOffset - o.phRuns[i].realTextFirstCharacterOffset;
  213. if(result != 0) return result;
  214. result = phRuns[i].realTextFirstCharacterOffset - o.phRuns[i].realTextLength;
  215. if(result != 0) return result;
  216. }
  217. result = extraData.length - o.extraData.length;
  218. if(result != 0) return result;
  219. // If we get here, it's the same
  220. return 0;
  221. }
  222. protected ExtRst clone() {
  223. ExtRst ext = new ExtRst();
  224. ext.reserved = reserved;
  225. ext.formattingFontIndex = formattingFontIndex;
  226. ext.formattingOptions = formattingOptions;
  227. ext.numberOfRuns = numberOfRuns;
  228. ext.phoneticText = new String(phoneticText);
  229. ext.phRuns = new PhRun[phRuns.length];
  230. for(int i=0; i<ext.phRuns.length; i++) {
  231. ext.phRuns[i] = new PhRun(
  232. phRuns[i].phoneticTextFirstCharacterOffset,
  233. phRuns[i].realTextFirstCharacterOffset,
  234. phRuns[i].realTextLength
  235. );
  236. }
  237. return ext;
  238. }
  239. public short getFormattingFontIndex() {
  240. return formattingFontIndex;
  241. }
  242. public short getFormattingOptions() {
  243. return formattingOptions;
  244. }
  245. public int getNumberOfRuns() {
  246. return numberOfRuns;
  247. }
  248. public String getPhoneticText() {
  249. return phoneticText;
  250. }
  251. public PhRun[] getPhRuns() {
  252. return phRuns;
  253. }
  254. }
  255. public static class PhRun {
  256. private int phoneticTextFirstCharacterOffset;
  257. private int realTextFirstCharacterOffset;
  258. private int realTextLength;
  259. public PhRun(int phoneticTextFirstCharacterOffset,
  260. int realTextFirstCharacterOffset, int realTextLength) {
  261. this.phoneticTextFirstCharacterOffset = phoneticTextFirstCharacterOffset;
  262. this.realTextFirstCharacterOffset = realTextFirstCharacterOffset;
  263. this.realTextLength = realTextLength;
  264. }
  265. private PhRun(LittleEndianInput in) {
  266. phoneticTextFirstCharacterOffset = in.readUShort();
  267. realTextFirstCharacterOffset = in.readUShort();
  268. realTextLength = in.readUShort();
  269. }
  270. private void serialize(ContinuableRecordOutput out) {
  271. out.writeContinueIfRequired(6);
  272. out.writeShort(phoneticTextFirstCharacterOffset);
  273. out.writeShort(realTextFirstCharacterOffset);
  274. out.writeShort(realTextLength);
  275. }
  276. }
  277. private UnicodeString() {
  278. //Used for clone method.
  279. }
  280. public UnicodeString(String str)
  281. {
  282. setString(str);
  283. }
  284. public int hashCode()
  285. {
  286. int stringHash = 0;
  287. if (field_3_string != null)
  288. stringHash = field_3_string.hashCode();
  289. return field_1_charCount + stringHash;
  290. }
  291. /**
  292. * Our handling of equals is inconsistent with compareTo. The trouble is because we don't truely understand
  293. * rich text fields yet it's difficult to make a sound comparison.
  294. *
  295. * @param o The object to compare.
  296. * @return true if the object is actually equal.
  297. */
  298. public boolean equals(Object o)
  299. {
  300. if (!(o instanceof UnicodeString)) {
  301. return false;
  302. }
  303. UnicodeString other = (UnicodeString) o;
  304. //OK lets do this in stages to return a quickly, first check the actual string
  305. boolean eq = ((field_1_charCount == other.field_1_charCount)
  306. && (field_2_optionflags == other.field_2_optionflags)
  307. && field_3_string.equals(other.field_3_string));
  308. if (!eq) return false;
  309. //OK string appears to be equal but now lets compare formatting runs
  310. if ((field_4_format_runs == null) && (other.field_4_format_runs == null))
  311. //Strings are equal, and there are not formatting runs.
  312. return true;
  313. if (((field_4_format_runs == null) && (other.field_4_format_runs != null)) ||
  314. (field_4_format_runs != null) && (other.field_4_format_runs == null))
  315. //Strings are equal, but one or the other has formatting runs
  316. return false;
  317. //Strings are equal, so now compare formatting runs.
  318. int size = field_4_format_runs.size();
  319. if (size != other.field_4_format_runs.size())
  320. return false;
  321. for (int i=0;i<size;i++) {
  322. FormatRun run1 = field_4_format_runs.get(i);
  323. FormatRun run2 = other.field_4_format_runs.get(i);
  324. if (!run1.equals(run2))
  325. return false;
  326. }
  327. // Well the format runs are equal as well!, better check the ExtRst data
  328. if(field_5_ext_rst == null && other.field_5_ext_rst == null) {
  329. // Good
  330. } else if(field_5_ext_rst != null && other.field_5_ext_rst != null) {
  331. int extCmp = field_5_ext_rst.compareTo(other.field_5_ext_rst);
  332. if(extCmp == 0) {
  333. // Good
  334. } else {
  335. return false;
  336. }
  337. } else {
  338. return false;
  339. }
  340. //Phew!! After all of that we have finally worked out that the strings
  341. //are identical.
  342. return true;
  343. }
  344. /**
  345. * construct a unicode string record and fill its fields, ID is ignored
  346. * @param in the RecordInputstream to read the record from
  347. */
  348. public UnicodeString(RecordInputStream in) {
  349. field_1_charCount = in.readShort();
  350. field_2_optionflags = in.readByte();
  351. int runCount = 0;
  352. int extensionLength = 0;
  353. //Read the number of rich runs if rich text.
  354. if ( isRichText() )
  355. {
  356. runCount = in.readShort();
  357. }
  358. //Read the size of extended data if present.
  359. if ( isExtendedText() )
  360. {
  361. extensionLength = in.readInt();
  362. }
  363. boolean isCompressed = ((field_2_optionflags & 1) == 0);
  364. if (isCompressed) {
  365. field_3_string = in.readCompressedUnicode(getCharCount());
  366. } else {
  367. field_3_string = in.readUnicodeLEString(getCharCount());
  368. }
  369. if (isRichText() && (runCount > 0)) {
  370. field_4_format_runs = new ArrayList<FormatRun>(runCount);
  371. for (int i=0;i<runCount;i++) {
  372. field_4_format_runs.add(new FormatRun(in));
  373. }
  374. }
  375. if (isExtendedText() && (extensionLength > 0)) {
  376. field_5_ext_rst = new ExtRst(in, extensionLength);
  377. if(field_5_ext_rst.getDataSize()+4 != extensionLength) {
  378. System.err.println("ExtRst was supposed to be " + extensionLength + " bytes long, but seems to actually be " + (field_5_ext_rst.getDataSize()+4));
  379. }
  380. }
  381. }
  382. /**
  383. * get the number of characters in the string,
  384. * as an un-wrapped int
  385. *
  386. * @return number of characters
  387. */
  388. public int getCharCount() {
  389. if(field_1_charCount < 0) {
  390. return field_1_charCount + 65536;
  391. }
  392. return field_1_charCount;
  393. }
  394. /**
  395. * get the number of characters in the string,
  396. * wrapped as needed to fit within a short
  397. *
  398. * @return number of characters
  399. */
  400. public short getCharCountShort() {
  401. return field_1_charCount;
  402. }
  403. /**
  404. * set the number of characters in the string
  405. * @param cc - number of characters
  406. */
  407. public void setCharCount(short cc)
  408. {
  409. field_1_charCount = cc;
  410. }
  411. /**
  412. * get the option flags which among other things return if this is a 16-bit or
  413. * 8 bit string
  414. *
  415. * @return optionflags bitmask
  416. *
  417. */
  418. public byte getOptionFlags()
  419. {
  420. return field_2_optionflags;
  421. }
  422. /**
  423. * set the option flags which among other things return if this is a 16-bit or
  424. * 8 bit string
  425. *
  426. * @param of optionflags bitmask
  427. *
  428. */
  429. public void setOptionFlags(byte of)
  430. {
  431. field_2_optionflags = of;
  432. }
  433. /**
  434. * @return the actual string this contains as a java String object
  435. */
  436. public String getString()
  437. {
  438. return field_3_string;
  439. }
  440. /**
  441. * set the actual string this contains
  442. * @param string the text
  443. */
  444. public void setString(String string)
  445. {
  446. field_3_string = string;
  447. setCharCount((short)field_3_string.length());
  448. // scan for characters greater than 255 ... if any are
  449. // present, we have to use 16-bit encoding. Otherwise, we
  450. // can use 8-bit encoding
  451. boolean useUTF16 = false;
  452. int strlen = string.length();
  453. for ( int j = 0; j < strlen; j++ )
  454. {
  455. if ( string.charAt( j ) > 255 )
  456. {
  457. useUTF16 = true;
  458. break;
  459. }
  460. }
  461. if (useUTF16)
  462. //Set the uncompressed bit
  463. field_2_optionflags = highByte.setByte(field_2_optionflags);
  464. else field_2_optionflags = highByte.clearByte(field_2_optionflags);
  465. }
  466. public int getFormatRunCount() {
  467. if (field_4_format_runs == null)
  468. return 0;
  469. return field_4_format_runs.size();
  470. }
  471. public FormatRun getFormatRun(int index) {
  472. if (field_4_format_runs == null) {
  473. return null;
  474. }
  475. if (index < 0 || index >= field_4_format_runs.size()) {
  476. return null;
  477. }
  478. return field_4_format_runs.get(index);
  479. }
  480. private int findFormatRunAt(int characterPos) {
  481. int size = field_4_format_runs.size();
  482. for (int i=0;i<size;i++) {
  483. FormatRun r = field_4_format_runs.get(i);
  484. if (r._character == characterPos)
  485. return i;
  486. else if (r._character > characterPos)
  487. return -1;
  488. }
  489. return -1;
  490. }
  491. /** Adds a font run to the formatted string.
  492. *
  493. * If a font run exists at the current charcter location, then it is
  494. * replaced with the font run to be added.
  495. */
  496. public void addFormatRun(FormatRun r) {
  497. if (field_4_format_runs == null) {
  498. field_4_format_runs = new ArrayList<FormatRun>();
  499. }
  500. int index = findFormatRunAt(r._character);
  501. if (index != -1)
  502. field_4_format_runs.remove(index);
  503. field_4_format_runs.add(r);
  504. //Need to sort the font runs to ensure that the font runs appear in
  505. //character order
  506. Collections.sort(field_4_format_runs);
  507. //Make sure that we now say that we are a rich string
  508. field_2_optionflags = richText.setByte(field_2_optionflags);
  509. }
  510. public Iterator<FormatRun> formatIterator() {
  511. if (field_4_format_runs != null) {
  512. return field_4_format_runs.iterator();
  513. }
  514. return null;
  515. }
  516. public void removeFormatRun(FormatRun r) {
  517. field_4_format_runs.remove(r);
  518. if (field_4_format_runs.size() == 0) {
  519. field_4_format_runs = null;
  520. field_2_optionflags = richText.clearByte(field_2_optionflags);
  521. }
  522. }
  523. public void clearFormatting() {
  524. field_4_format_runs = null;
  525. field_2_optionflags = richText.clearByte(field_2_optionflags);
  526. }
  527. public ExtRst getExtendedRst() {
  528. return this.field_5_ext_rst;
  529. }
  530. void setExtendedRst(ExtRst ext_rst) {
  531. if (ext_rst != null) {
  532. field_2_optionflags = extBit.setByte(field_2_optionflags);
  533. } else {
  534. field_2_optionflags = extBit.clearByte(field_2_optionflags);
  535. }
  536. this.field_5_ext_rst = ext_rst;
  537. }
  538. /**
  539. * Swaps all use in the string of one font index
  540. * for use of a different font index.
  541. * Normally only called when fonts have been
  542. * removed / re-ordered
  543. */
  544. public void swapFontUse(short oldFontIndex, short newFontIndex) {
  545. for (FormatRun run : field_4_format_runs) {
  546. if(run._fontIndex == oldFontIndex) {
  547. run._fontIndex = newFontIndex;
  548. }
  549. }
  550. }
  551. /**
  552. * unlike the real records we return the same as "getString()" rather than debug info
  553. * @see #getDebugInfo()
  554. * @return String value of the record
  555. */
  556. public String toString()
  557. {
  558. return getString();
  559. }
  560. /**
  561. * return a character representation of the fields of this record
  562. *
  563. *
  564. * @return String of output for biffviewer etc.
  565. *
  566. */
  567. public String getDebugInfo()
  568. {
  569. StringBuffer buffer = new StringBuffer();
  570. buffer.append("[UNICODESTRING]\n");
  571. buffer.append(" .charcount = ")
  572. .append(Integer.toHexString(getCharCount())).append("\n");
  573. buffer.append(" .optionflags = ")
  574. .append(Integer.toHexString(getOptionFlags())).append("\n");
  575. buffer.append(" .string = ").append(getString()).append("\n");
  576. if (field_4_format_runs != null) {
  577. for (int i = 0; i < field_4_format_runs.size();i++) {
  578. FormatRun r = field_4_format_runs.get(i);
  579. buffer.append(" .format_run"+i+" = ").append(r.toString()).append("\n");
  580. }
  581. }
  582. if (field_5_ext_rst != null) {
  583. buffer.append(" .field_5_ext_rst = ").append("\n");
  584. buffer.append( field_5_ext_rst.toString() ).append("\n");
  585. }
  586. buffer.append("[/UNICODESTRING]\n");
  587. return buffer.toString();
  588. }
  589. /**
  590. * Serialises out the String. There are special rules
  591. * about where we can and can't split onto
  592. * Continue records.
  593. */
  594. public void serialize(ContinuableRecordOutput out) {
  595. int numberOfRichTextRuns = 0;
  596. int extendedDataSize = 0;
  597. if (isRichText() && field_4_format_runs != null) {
  598. numberOfRichTextRuns = field_4_format_runs.size();
  599. }
  600. if (isExtendedText() && field_5_ext_rst != null) {
  601. extendedDataSize = 4 + field_5_ext_rst.getDataSize();
  602. }
  603. // Serialise the bulk of the String
  604. // The writeString handles tricky continue stuff for us
  605. out.writeString(field_3_string, numberOfRichTextRuns, extendedDataSize);
  606. if (numberOfRichTextRuns > 0) {
  607. //This will ensure that a run does not split a continue
  608. for (int i=0;i<numberOfRichTextRuns;i++) {
  609. if (out.getAvailableSpace() < 4) {
  610. out.writeContinue();
  611. }
  612. FormatRun r = field_4_format_runs.get(i);
  613. r.serialize(out);
  614. }
  615. }
  616. if (extendedDataSize > 0) {
  617. field_5_ext_rst.serialize(out);
  618. }
  619. }
  620. public int compareTo(UnicodeString str) {
  621. int result = getString().compareTo(str.getString());
  622. //As per the equals method lets do this in stages
  623. if (result != 0)
  624. return result;
  625. //OK string appears to be equal but now lets compare formatting runs
  626. if ((field_4_format_runs == null) && (str.field_4_format_runs == null))
  627. //Strings are equal, and there are no formatting runs.
  628. return 0;
  629. if ((field_4_format_runs == null) && (str.field_4_format_runs != null))
  630. //Strings are equal, but one or the other has formatting runs
  631. return 1;
  632. if ((field_4_format_runs != null) && (str.field_4_format_runs == null))
  633. //Strings are equal, but one or the other has formatting runs
  634. return -1;
  635. //Strings are equal, so now compare formatting runs.
  636. int size = field_4_format_runs.size();
  637. if (size != str.field_4_format_runs.size())
  638. return size - str.field_4_format_runs.size();
  639. for (int i=0;i<size;i++) {
  640. FormatRun run1 = field_4_format_runs.get(i);
  641. FormatRun run2 = str.field_4_format_runs.get(i);
  642. result = run1.compareTo(run2);
  643. if (result != 0)
  644. return result;
  645. }
  646. //Well the format runs are equal as well!, better check the ExtRst data
  647. if ((field_5_ext_rst == null) && (str.field_5_ext_rst == null))
  648. return 0;
  649. if ((field_5_ext_rst == null) && (str.field_5_ext_rst != null))
  650. return 1;
  651. if ((field_5_ext_rst != null) && (str.field_5_ext_rst == null))
  652. return -1;
  653. result = field_5_ext_rst.compareTo(str.field_5_ext_rst);
  654. if (result != 0)
  655. return result;
  656. //Phew!! After all of that we have finally worked out that the strings
  657. //are identical.
  658. return 0;
  659. }
  660. private boolean isRichText()
  661. {
  662. return richText.isSet(getOptionFlags());
  663. }
  664. private boolean isExtendedText()
  665. {
  666. return extBit.isSet(getOptionFlags());
  667. }
  668. public Object clone() {
  669. UnicodeString str = new UnicodeString();
  670. str.field_1_charCount = field_1_charCount;
  671. str.field_2_optionflags = field_2_optionflags;
  672. str.field_3_string = field_3_string;
  673. if (field_4_format_runs != null) {
  674. str.field_4_format_runs = new ArrayList<FormatRun>();
  675. for (FormatRun r : field_4_format_runs) {
  676. str.field_4_format_runs.add(new FormatRun(r._character, r._fontIndex));
  677. }
  678. }
  679. if (field_5_ext_rst != null) {
  680. str.field_5_ext_rst = field_5_ext_rst.clone();
  681. }
  682. return str;
  683. }
  684. }