From 70f1b42af4f60fd39896f757d46b667aa788dbe0 Mon Sep 17 00:00:00 2001
From: "(no author)" <(no author)@unknown>
Date: Mon, 28 Jul 2003 18:45:14 +0000
Subject: [PATCH] This commit was manufactured by cvs2svn to create tag
'REL_2_0_PRE3'.
git-svn-id: https://svn.apache.org/repos/asf/jakarta/poi/tags/REL_2_0_PRE3@353269 13f79535-47bb-0310-9956-ffa450edef68
---
.cvsignore | 1 +
build.xml | 82 ++--
.../xdocs/dtd/book-cocoon-v10.dtd | 0
.../{ => content}/xdocs/dtd/changes-v11.dtd | 0
.../{ => content}/xdocs/dtd/document-v11.dtd | 0
.../{ => content}/xdocs/dtd/faq-v11.dtd | 0
.../xdocs/dtd/javadoc-v04draft.dtd | 0
.../xdocs/dtd/specification-v11.dtd | 0
.../{ => content}/xdocs/dtd/todo-v11.dtd | 0
.../content/xdocs/howtobuild.xml | 4 +
src/documentation/content/xdocs/index.xml | 10 +-
src/documentation/content/xdocs/status.xml | 33 +-
.../resources/images/group-logo.gif | Bin 8146 -> 5258 bytes
src/java/org/apache/poi/hpsf/TypeReader.java | 74 ++--
src/java/org/apache/poi/hpsf/Variant.java | 108 +++---
src/java/org/apache/poi/hssf/model/Sheet.java | 233 +++++++----
.../org/apache/poi/hssf/model/Workbook.java | 142 +++----
.../poi/hssf/record/BottomMarginRecord.java | 188 ++++++++-
.../poi/hssf/record/ContinueRecord.java | 10 +
.../poi/hssf/record/ExtSSTInfoSubRecord.java | 4 +-
.../apache/poi/hssf/record/ExtSSTRecord.java | 26 +-
.../poi/hssf/record/ExternSheetRecord.java | 231 ++++++++++-
.../apache/poi/hssf/record/FormulaRecord.java | 14 +-
.../apache/poi/hssf/record/NameRecord.java | 5 +-
.../poi/hssf/record/PasswordRecord.java | 10 +
.../org/apache/poi/hssf/record/Record.java | 16 -
.../poi/hssf/record/RecordProcessor.java | 5 +
.../org/apache/poi/hssf/record/SSTRecord.java | 68 +++-
.../hssf/record/SSTRecordSizeCalculator.java | 4 +-
.../apache/poi/hssf/record/SSTSerializer.java | 36 +-
.../apache/poi/hssf/record/StyleRecord.java | 45 ++-
.../apache/poi/hssf/record/UnknownRecord.java | 13 +-
.../aggregates/FormulaRecordAggregate.java | 36 +-
.../poi/hssf/record/formula/Area3DPtg.java | 1 +
.../poi/hssf/record/formula/AreaPtg.java | 1 +
.../poi/hssf/record/formula/ExpPtg.java | 5 +-
.../poi/hssf/record/formula/FuncPtg.java | 3 +-
.../poi/hssf/record/formula/FuncVarPtg.java | 1 +
.../poi/hssf/record/formula/NamePtg.java | 25 +-
.../poi/hssf/record/formula/NameXPtg.java | 3 +-
.../apache/poi/hssf/record/formula/Ptg.java | 14 +
.../poi/hssf/record/formula/Ref3DPtg.java | 3 +-
.../poi/hssf/record/formula/ReferencePtg.java | 1 +
.../poi/hssf/record/formula/StringPtg.java | 55 ++-
.../hssf/record/formula/UnaryMinusPtg.java | 127 ++++++
.../poi/hssf/record/formula/UnaryPlusPtg.java | 127 ++++++
.../poi/hssf/usermodel/HSSFPalette.java | 2 +-
.../apache/poi/hssf/usermodel/HSSFSheet.java | 124 +++++-
.../poi/hssf/usermodel/HSSFWorkbook.java | 14 +-
src/java/org/apache/poi/util/HexDump.java | 85 ++--
src/java/org/apache/poi/util/HexRead.java | 226 ++++++++++-
.../org/apache/poi/util/LittleEndian.java | 32 +-
src/java/org/apache/poi/util/StringUtil.java | 364 +++++++++++++++++-
.../org/apache/poi/hpsf/basic/POIFile.java | 15 +-
.../org/apache/poi/hpsf/basic/TestBasic.java | 190 ++++-----
.../poi/hpsf/basic/TestEmptyProperties.java | 189 +++++++++
.../apache/poi/hpsf/basic/TestUnicode.java | 81 ++--
.../org/apache/poi/hpsf/basic/Util.java | 145 ++++---
.../org/apache/poi/hpsf/data/TestCorel.shw | Bin 0 -> 76800 bytes
.../org/apache/poi/hssf/data/15228.xls | Bin 0 -> 692224 bytes
.../poi/hssf/data/EmbeddedChartHeaderTest.xls | Bin 0 -> 15360 bytes
.../apache/poi/hssf/usermodel/TestBugs.java | 98 +++++
.../hssf/usermodel/TestHSSFHeaderFooter.java | 126 ++++++
.../poi/hssf/usermodel/TestHSSFSheet.java | 119 +++++-
.../poi/hssf/usermodel/TestWorkbook.java | 2 +
.../org/apache/poi/util/TestHexDump.java | 3 +
66 files changed, 2951 insertions(+), 628 deletions(-)
rename src/documentation/{ => content}/xdocs/dtd/book-cocoon-v10.dtd (100%)
mode change 100755 => 100644
rename src/documentation/{ => content}/xdocs/dtd/changes-v11.dtd (100%)
rename src/documentation/{ => content}/xdocs/dtd/document-v11.dtd (100%)
rename src/documentation/{ => content}/xdocs/dtd/faq-v11.dtd (100%)
rename src/documentation/{ => content}/xdocs/dtd/javadoc-v04draft.dtd (100%)
mode change 100755 => 100644
rename src/documentation/{ => content}/xdocs/dtd/specification-v11.dtd (100%)
rename src/documentation/{ => content}/xdocs/dtd/todo-v11.dtd (100%)
create mode 100644 src/java/org/apache/poi/hssf/record/formula/UnaryMinusPtg.java
create mode 100644 src/java/org/apache/poi/hssf/record/formula/UnaryPlusPtg.java
create mode 100644 src/testcases/org/apache/poi/hpsf/basic/TestEmptyProperties.java
create mode 100755 src/testcases/org/apache/poi/hpsf/data/TestCorel.shw
create mode 100644 src/testcases/org/apache/poi/hssf/data/15228.xls
create mode 100644 src/testcases/org/apache/poi/hssf/data/EmbeddedChartHeaderTest.xls
create mode 100644 src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java
create mode 100644 src/testcases/org/apache/poi/hssf/usermodel/TestHSSFHeaderFooter.java
diff --git a/.cvsignore b/.cvsignore
index 558342ccee..145be6eafa 100644
--- a/.cvsignore
+++ b/.cvsignore
@@ -1,4 +1,5 @@
dist
+build
scripts
*.el
*.ipr
diff --git a/build.xml b/build.xml
index 0ed314e0bf..b4b7c67741 100644
--- a/build.xml
+++ b/build.xml
@@ -6,7 +6,7 @@
Glen Stampoultzis glens at apache.org
- This build was tested with and 1.5.3 although it will probably work with
+ This build was tested with ant 1.5.3 although it will probably work with
other versions. The following jar files should be installed
into the ant lib directory:
@@ -15,6 +15,7 @@
junit(3.8+) http://www.ibiblio.org/maven/junit/jars/
xerces http://www.ibiblio.org/maven/xerces/jars/
jdepend http://www.ibiblio.org/maven/jdepend/jars/
+ xalan http://www.ibiblio.org/maven/xalan/jars/
The ant jar "optional.jar" should also be available otherwise the
build will fail.
@@ -82,7 +83,7 @@
Just pick the latest versions of these jars and place
diff --git a/src/documentation/content/xdocs/index.xml b/src/documentation/content/xdocs/index.xml
index 274ebf30ef..634745e2df 100644
--- a/src/documentation/content/xdocs/index.xml
+++ b/src/documentation/content/xdocs/index.xml
@@ -18,11 +18,15 @@
The POI project consists of APIs for manipulating various file formats
- based upon Microsoft's OLE 2 Compound Document format using pure Java.
+ based upon Microsoft's OLE 2 Compound Document format using pure Java. In short, you can
+ read and write MS Excel files using Java. Soon, you'll be able to read and write
+ Word files using Java. POI is your Java Excel solution as well as your Word Excel solution.
+ However, we have a complete API for porting other OLE 2 Compound Document formats and welcome
+ others to participate.
OLE 2 Compound Document Format based files include most Microsoft Office
- files such as XLS and DOC.
+ files such as XLS and DOC as well as MFC serialization API based file formats.
As a general policy we try to collaborate as much as possible with other projects to
@@ -46,7 +50,7 @@
we say that POIFS is the most complete and correct port of this file format to date!
- You'd use HSSF if you needed to read or write an XLS (Excel) file using Java. You can also read and modify
+ You'd use HSSF if you needed to read or write an Excel file using Java (XLS). You can also read and modify
spreadsheets using this API, although right now writing is more mature.
mQ0+y{RX1W}+d!PE%Ly-i3ln20ErsHqP?isLuI}^+%*~7Ay+uJ#CbU7gf
zpbp58m)}2*-gir${? eZA!xxSJDwGA@J!)%mT@8Uw*F--__qADLM#;RzeyfR*Yh5da`Ik~HE
z;PgqcXDCmg6m6Qh8459wG5USQdUkr#&=J{?YL%!!DFduZSi [V][P] Nothing. [V][P] SQL style Null. [V][T][P][S] 2 byte signed int. [V][T][P][S] 4 byte signed int. [V][T][P][S] 4 byte real. [V][T][P][S] 8 byte real. [V][T][P][S] currency. How long is this? How is it to be
* interpreted? [V][T][P][S] date. How long is this? How is it to be
* interpreted? [V][T][P][S] OLE Automation string. How long is this? How is it
* to be interpreted? [V][T][P][S] IDispatch *. How long is this? How is it to be
* interpreted? [V][T][S] SCODE. How
* long is this? How is it to be interpreted? [V][T][P][S] True=-1, False=0. [V][T][P][S] VARIANT *. How long is this? How is it to be
* interpreted? [V][T][S] IUnknown *. How long is this? How is it to be
* interpreted? [V][T][S] 16 byte fixed point. [T] signed char. [V][T][P][S] unsigned char. [T][P] unsigned short. [T][P] unsigned int. [T][P] signed 64-bit int. [T][P] unsigned 64-bit int. [T] signed machine int. [T] unsigned machine int. [T] C style void. [T] Standard return type. How long is this? How is it to be
* interpreted? [T] pointer type. How long is this? How is it to be
* interpreted? [T] (use VT_ARRAY in VARIANT). [T] C style array. How long is this? How is it to be
* interpreted? [T] user defined type. How long is this? How is it to be
* interpreted? [T][P] null terminated string. [T][P] wide (Unicode) null terminated string. [P] FILETIME. The FILETIME structure holds a date and time
@@ -256,50 +256,50 @@ public class Variant
* have passed since January 1, 1601. This 64-bit value is split
* into the two dwords stored in the structure. [P] Length prefixed bytes. [P] Name of the stream follows. [P] Name of the storage follows. [P] Stream contains an object. How long is this? How is it
* to be interpreted? [P] Storage contains an object. How long is this? How is it
* to be interpreted? [P] Blob contains an object. How long is this? How is it to be
* interpreted? [P] Clipboard format. How long is this? How is it to be
* interpreted? [P] A Class ID.jdepend
http://www.ibiblio.org/maven/jdepend/jars/
+
+
xalan
+ http://www.ibiblio.org/maven/xalan/jars/
+
[P] simple counted array. How long is this? How is it to be * interpreted?
*/ - public final static int VT_VECTOR = 0x1000; + public static final int VT_VECTOR = 0x1000; /** *[V] SAFEARRAY*. How * long is this? How is it to be interpreted?
*/ - public final static int VT_ARRAY = 0x2000; + public static final int VT_ARRAY = 0x2000; /** *[V] void* for local use. How long is this? How is it to be * interpreted?
*/ - public final static int VT_BYREF = 0x4000; + public static final int VT_BYREF = 0x4000; - public final static int VT_RESERVED = 0x8000; + /** + *FIXME: Document this!
+ */ + public static final int VT_RESERVED = 0x8000; - public final static int VT_ILLEGAL = 0xFFFF; + /** + *FIXME: Document this!
+ */ + public static final int VT_ILLEGAL = 0xFFFF; - public final static int VT_ILLEGALMASKED = 0xFFF; + /** + *FIXME: Document this!
+ */ + public static final int VT_ILLEGALMASKED = 0xFFF; - public final static int VT_TYPEMASK = 0xFFF; + /** + *FIXME: Document this!
+ */ + public static final int VT_TYPEMASK = 0xFFF; -} +} \ No newline at end of file diff --git a/src/java/org/apache/poi/hssf/model/Sheet.java b/src/java/org/apache/poi/hssf/model/Sheet.java index 9a7abadc3f..a14d06a219 100644 --- a/src/java/org/apache/poi/hssf/model/Sheet.java +++ b/src/java/org/apache/poi/hssf/model/Sheet.java @@ -108,7 +108,9 @@ public class Sheet implements Model protected HeaderRecord header = null; protected FooterRecord footer = null; protected PrintGridlinesRecord printGridlines = null; + protected WindowTwoRecord windowTwo = null; protected MergeCellsRecord merged = null; + protected Margin margins[] = null; protected ArrayList mergedRecords = new ArrayList(); protected ArrayList mergedLocs = new ArrayList(); protected int numMergedRegions = 0; @@ -249,22 +251,42 @@ public class Sheet implements Model { retval.printGridlines = (PrintGridlinesRecord) rec; } - else if ( rec.getSid() == HeaderRecord.sid ) + else if ( rec.getSid() == HeaderRecord.sid && bofEofNestingLevel == 1) { retval.header = (HeaderRecord) rec; } - else if ( rec.getSid() == FooterRecord.sid ) + else if ( rec.getSid() == FooterRecord.sid && bofEofNestingLevel == 1) { retval.footer = (FooterRecord) rec; } - else if ( rec.getSid() == PrintSetupRecord.sid ) + else if ( rec.getSid() == PrintSetupRecord.sid && bofEofNestingLevel == 1) { retval.printSetup = (PrintSetupRecord) rec; } + else if ( rec.getSid() == LeftMarginRecord.sid) + { + retval.getMargins()[LeftMargin] = (LeftMarginRecord) rec; + } + else if ( rec.getSid() == RightMarginRecord.sid) + { + retval.getMargins()[RightMargin] = (RightMarginRecord) rec; + } + else if ( rec.getSid() == TopMarginRecord.sid) + { + retval.getMargins()[TopMargin] = (TopMarginRecord) rec; + } + else if ( rec.getSid() == BottomMarginRecord.sid) + { + retval.getMargins()[BottomMargin] = (BottomMarginRecord) rec; + } else if ( rec.getSid() == SelectionRecord.sid ) { retval.selection = (SelectionRecord) rec; } + else if ( rec.getSid() == WindowTwoRecord.sid ) + { + retval.windowTwo = (WindowTwoRecord) rec; + } if (rec != null) { @@ -391,7 +413,7 @@ public class Sheet implements Model retval.dims = ( DimensionsRecord ) retval.createDimensions(); retval.dimsloc = 19; records.add(retval.dims); - records.add(retval.createWindowTwo()); + records.add(retval.windowTwo = retval.createWindowTwo()); retval.setLoc(records.size() - 1); retval.selection = (SelectionRecord) retval.createSelection(); @@ -469,12 +491,21 @@ public class Sheet implements Model numMergedRegions--; if (rec.getNumAreas() == 0) { - mergedRecords.remove(pos); - if (merged == rec) - merged = (MergeCellsRecord) mergedRecords.get(mergedRecords.size() - 1); + mergedRecords.remove(pos); + if (merged == rec) { + //pull up the LAST record for operations when we finally + //support continue records for mergedRegions + if (mergedRecords.size() > 0) { + merged = (MergeCellsRecord) mergedRecords.get(mergedRecords.size() - 1); + } else { + merged = null; + } + } + int removePos = ((Integer) mergedLocs.get(pos)).intValue(); records.remove(removePos); mergedLocs.remove(pos); + } } @@ -2011,7 +2042,7 @@ public class Sheet implements Model * @return record containing a WindowTwoRecord */ - protected Record createWindowTwo() + protected WindowTwoRecord createWindowTwo() { WindowTwoRecord retval = new WindowTwoRecord(); @@ -2312,7 +2343,6 @@ public class Sheet implements Model * @param sel True to select the sheet, false otherwise. */ public void setSelected(boolean sel) { - WindowTwoRecord windowTwo = (WindowTwoRecord) findFirstRecordBySid(WindowTwoRecord.sid); windowTwo.setSelected(sel); } @@ -2321,82 +2351,59 @@ public class Sheet implements Model * @param margin which margin to get * @return the size of the margin */ - public double getMargin(short margin) { - Margin m; - switch ( margin ) - { - case LeftMargin: - m = (Margin) findFirstRecordBySid( LeftMarginRecord.sid ); - if ( m == null ) - return .75; - break; - case RightMargin: - m = (Margin) findFirstRecordBySid( RightMarginRecord.sid ); - if ( m == null ) - return .75; - break; - case TopMargin: - m = (Margin) findFirstRecordBySid( TopMarginRecord.sid ); - if ( m == null ) - return 1.0; - break; - case BottomMargin: - m = (Margin) findFirstRecordBySid( BottomMarginRecord.sid ); - if ( m == null ) - return 1.0; - break; - default : - throw new RuntimeException( "Unknown margin constant: " + margin ); - } - return m.getMargin(); - } + public double getMargin(short margin) { + if (getMargins()[margin] != null) + return margins[margin].getMargin(); + else { + switch ( margin ) + { + case LeftMargin: + return .75; + case RightMargin: + return .75; + case TopMargin: + return 1.0; + case BottomMargin: + return 1.0; + default : + throw new RuntimeException( "Unknown margin constant: " + margin ); + } + } + } /** * Sets the size of the margin in inches. * @param margin which margin to get * @param size the size of the margin */ - public void setMargin(short margin, double size) { - Margin m; - switch ( margin ) - { - case LeftMargin: - m = (Margin) findFirstRecordBySid( LeftMarginRecord.sid ); - if ( m == null ) - { - m = new LeftMarginRecord(); - records.add( getDimsLoc() + 1, m ); - } - break; - case RightMargin: - m = (Margin) findFirstRecordBySid( RightMarginRecord.sid ); - if ( m == null ) - { - m = new RightMarginRecord(); - records.add( getDimsLoc() + 1, m ); - } - break; - case TopMargin: - m = (Margin) findFirstRecordBySid( TopMarginRecord.sid ); - if ( m == null ) - { - m = new TopMarginRecord(); - records.add( getDimsLoc() + 1, m ); - } - break; - case BottomMargin: - m = (Margin) findFirstRecordBySid( BottomMarginRecord.sid ); - if ( m == null ) - { - m = new BottomMarginRecord(); - records.add( getDimsLoc() + 1, m ); - } - break; - default : - throw new RuntimeException( "Unknown margin constant: " + margin ); - } - m.setMargin( size ); - } + public void setMargin(short margin, double size) { + Margin m = getMargins()[margin]; + if (m == null) { + switch ( margin ) + { + case LeftMargin: + m = new LeftMarginRecord(); + records.add( getDimsLoc() + 1, m ); + break; + case RightMargin: + m = new RightMarginRecord(); + records.add( getDimsLoc() + 1, m ); + break; + case TopMargin: + m = new TopMarginRecord(); + records.add( getDimsLoc() + 1, m ); + break; + case BottomMargin: + m = new BottomMarginRecord(); + records.add( getDimsLoc() + 1, m ); + break; + default : + throw new RuntimeException( "Unknown margin constant: " + margin ); + } + margins[margin] = m; + } + m.setMargin( size ); + } public int getEofLoc() { @@ -2434,9 +2441,8 @@ public class Sheet implements Model } records.add(loc+1, pane); - WindowTwoRecord windowRecord = (WindowTwoRecord) records.get(loc); - windowRecord.setFreezePanes(true); - windowRecord.setFreezePanesNoSplit(true); + windowTwo.setFreezePanes(true); + windowTwo.setFreezePanesNoSplit(true); SelectionRecord sel = (SelectionRecord) findFirstRecordBySid(SelectionRecord.sid); // SelectionRecord sel2 = (SelectionRecord) sel.clone(); @@ -2484,9 +2490,8 @@ public class Sheet implements Model r.setActivePane((short) activePane); records.add(loc+1, r); - WindowTwoRecord windowRecord = (WindowTwoRecord) records.get(loc); - windowRecord.setFreezePanes(false); - windowRecord.setFreezePanesNoSplit(false); + windowTwo.setFreezePanes(false); + windowTwo.setFreezePanesNoSplit(false); SelectionRecord sel = (SelectionRecord) findFirstRecordBySid(SelectionRecord.sid); // SelectionRecord sel2 = (SelectionRecord) sel.clone(); @@ -2520,4 +2525,62 @@ public class Sheet implements Model this.selection = selection; } + /** + * Sets whether the gridlines are shown in a viewer. + * @param show whether to show gridlines or not + */ + public void setDisplayGridlines(boolean show) { + windowTwo.setDisplayGridlines(show); + } + + /** + * Returns if gridlines are displayed. + * @return whether gridlines are displayed + */ + public boolean isDisplayGridlines() { + return windowTwo.getDisplayGridlines(); + } + + /** + * Sets whether the formulas are shown in a viewer. + * @param show whether to show formulas or not + */ + public void setDisplayFormulas(boolean show) { + windowTwo.setDisplayFormulas(show); + } + + /** + * Returns if formulas are displayed. + * @return whether formulas are displayed + */ + public boolean isDisplayFormulas() { + return windowTwo.getDisplayFormulas(); + } + + /** + * Sets whether the RowColHeadings are shown in a viewer. + * @param show whether to show RowColHeadings or not + */ + public void setDisplayRowColHeadings(boolean show) { + windowTwo.setDisplayRowColHeadings(show); + } + + /** + * Returns if RowColHeadings are displayed. + * @return whether RowColHeadings are displayed + */ + public boolean isDisplayRowColHeadings() { + return windowTwo.getDisplayRowColHeadings(); + } + + /** + * Returns the array of margins. If not created, will create. + * + * @return the array of marings. + */ + protected Margin[] getMargins() { + if (margins == null) + margins = new Margin[4]; + return margins; + } } diff --git a/src/java/org/apache/poi/hssf/model/Workbook.java b/src/java/org/apache/poi/hssf/model/Workbook.java index cad91171e8..da1aa129e9 100644 --- a/src/java/org/apache/poi/hssf/model/Workbook.java +++ b/src/java/org/apache/poi/hssf/model/Workbook.java @@ -513,7 +513,7 @@ public class Workbook implements Model { for (int k = 0; k < boundsheets.size(); k++) { String sheet = getSheetName(k); - if (sheet.equals(name)) { + if (sheet.equalsIgnoreCase(name)) { retval = k; break; } @@ -686,37 +686,27 @@ public class Workbook implements Model { * * @return byte array containing the HSSF-only portions of the POIFS file. */ - - public byte [] serialize() { - log.log(DEBUG, "Serializing Workbook!"); - byte[] retval = null; - - // ArrayList bytes = new ArrayList(records.size()); - int arraysize = getSize(); - int pos = 0; - - // for (int k = 0; k < records.size(); k++) - // { - // bytes.add((( Record ) records.get(k)).serialize()); - // } - // for (int k = 0; k < bytes.size(); k++) - // { - // arraysize += (( byte [] ) bytes.get(k)).length; - // } - retval = new byte[ arraysize ]; - for (int k = 0; k < records.size(); k++) { - - // byte[] rec = (( byte [] ) bytes.get(k)); - // System.arraycopy(rec, 0, retval, pos, rec.length); - Record record = records.get(k); - // Let's skip RECALCID records, as they are only use for optimization - if(record.getSid() != RecalcIdRecord.sid || ((RecalcIdRecord)record).isNeeded()) { - pos += record.serialize(pos, retval); // rec.length; - } - } - log.log(DEBUG, "Exiting serialize workbook"); - return retval; - } + // GJS: Not used so why keep it. +// public byte [] serialize() { +// log.log(DEBUG, "Serializing Workbook!"); +// byte[] retval = null; +// +//// ArrayList bytes = new ArrayList(records.size()); +// int arraysize = getSize(); +// int pos = 0; +// +// retval = new byte[ arraysize ]; +// for (int k = 0; k < records.size(); k++) { +// +// Record record = records.get(k); +//// Let's skip RECALCID records, as they are only use for optimization +// if(record.getSid() != RecalcIdRecord.sid || ((RecalcIdRecord)record).isNeeded()) { +// pos += record.serialize(pos, retval); // rec.length; +// } +// } +// log.log(DEBUG, "Exiting serialize workbook"); +// return retval; +// } /** * Serializes all records int the worksheet section into a big byte array. Use @@ -725,44 +715,54 @@ public class Workbook implements Model { * @param data array of bytes to write this to */ - public int serialize(int offset, byte [] data) { - log.log(DEBUG, "Serializing Workbook with offsets"); + public int serialize( int offset, byte[] data ) + { + log.log( DEBUG, "Serializing Workbook with offsets" ); - // ArrayList bytes = new ArrayList(records.size()); - // int arraysize = getSize(); // 0; - int pos = 0; + int pos = 0; - // for (int k = 0; k < records.size(); k++) - // { - // bytes.add((( Record ) records.get(k)).serialize()); - // - // } - // for (int k = 0; k < bytes.size(); k++) - // { - // arraysize += (( byte [] ) bytes.get(k)).length; - // } - for (int k = 0; k < records.size(); k++) { + SSTRecord sst = null; + int sstPos = 0; + for ( int k = 0; k < records.size(); k++ ) + { - // byte[] rec = (( byte [] ) bytes.get(k)); - // System.arraycopy(rec, 0, data, offset + pos, rec.length); - Record record = records.get(k); + Record record = records.get( k ); // Let's skip RECALCID records, as they are only use for optimization - if(record.getSid() != RecalcIdRecord.sid || ((RecalcIdRecord)record).isNeeded()) { - pos += record.serialize(pos + offset, data); // rec.length; + if ( record.getSid() != RecalcIdRecord.sid || ( (RecalcIdRecord) record ).isNeeded() ) + { + if (record instanceof SSTRecord) + { + sst = (SSTRecord)record; + sstPos = pos; + } + if (record.getSid() == ExtSSTRecord.sid && sst != null) + { + record = sst.createExtSSTRecord(sstPos + offset); + } + pos += record.serialize( pos + offset, data ); // rec.length; } } - log.log(DEBUG, "Exiting serialize workbook"); + log.log( DEBUG, "Exiting serialize workbook" ); return pos; } - public int getSize() { + public int getSize() + { int retval = 0; - for (int k = 0; k < records.size(); k++) { - Record record = records.get(k); + SSTRecord sst = null; + for ( int k = 0; k < records.size(); k++ ) + { + Record record = records.get( k ); // Let's skip RECALCID records, as they are only use for optimization - if(record.getSid() != RecalcIdRecord.sid || ((RecalcIdRecord)record).isNeeded()) { - retval += record.getRecordSize(); + if ( record.getSid() != RecalcIdRecord.sid || ( (RecalcIdRecord) record ).isNeeded() ) + { + if (record instanceof SSTRecord) + sst = (SSTRecord)record; + if (record.getSid() == ExtSSTRecord.sid && sst != null) + retval += sst.calcExtSSTRecordSize(); + else + retval += record.getRecordSize(); } } return retval; @@ -1729,15 +1729,17 @@ public class Workbook implements Model { } public SheetReferences getSheetReferences() { - SheetReferences refs = new SheetReferences(); - - if (externSheet != null) { - for (int k = 0; k < externSheet.getNumOfREFStructures(); k++) { - String sheetName = findSheetNameFromExternSheet((short)k); - refs.addSheetReference(sheetName, k); - } - } - return refs; + SheetReferences refs = new SheetReferences(); + + if (externSheet != null) { + for (int k = 0; k < externSheet.getNumOfREFStructures(); k++) { + + String sheetName = findSheetNameFromExternSheet((short)k); + refs.addSheetReference(sheetName, k); + + } + } + return refs; } /** finds the sheet name by his extern sheet index @@ -1745,10 +1747,12 @@ public class Workbook implements Model { * @return sheet name */ public String findSheetNameFromExternSheet(short num){ - String result; + String result=""; short indexToSheet = externSheet.getREFRecordAt(num).getIndexToFirstSupBook(); - result = getSheetName(indexToSheet); + if (indexToSheet>-1) { //error check, bail out gracefully! + result = getSheetName(indexToSheet); + } return result; } diff --git a/src/java/org/apache/poi/hssf/record/BottomMarginRecord.java b/src/java/org/apache/poi/hssf/record/BottomMarginRecord.java index a74be0f6c9..fcab41a1f2 100644 --- a/src/java/org/apache/poi/hssf/record/BottomMarginRecord.java +++ b/src/java/org/apache/poi/hssf/record/BottomMarginRecord.java @@ -1 +1,187 @@ -/* ==================================================================== * The Apache Software License, Version 1.1 * * Copyright (c) 2003 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, * if any, must include the following acknowledgment: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowledgment may appear in the software itself, * if and wherever such third-party acknowledgments normally appear. * * 4. The names "Apache" and "Apache Software Foundation" and * "Apache POI" must not be used to endorse or promote products * derived from this software without prior written permission. For * written permission, please contact apache@apache.org. * * 5. Products derived from this software may not be called "Apache", * "Apache POI", nor may "Apache" appear in their name, without * prior written permission of the Apache Software Foundation. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see ** Description: A List of Inndexes to SupBook
* REFERENCE:
* @author Libin Roman (Vista Portal LDT. Developer) * @version 1.0-pre */public class ExternSheetRecord extends Record { public final static short sid = 0x17; private short field_1_number_of_REF_sturcutres; private ArrayList field_2_REF_structures; public ExternSheetRecord() { field_2_REF_structures = new ArrayList(); } /** * Constructs a Extern Sheet record and sets its fields appropriately. * * @param id id must be 0x16 or an exception will be throw upon validation * @param size the size of the data area of the record * @param data data of the record (should not contain sid/len) */ public ExternSheetRecord(short id, short size, byte[] data) { super(id, size, data); } /** * Constructs a Extern Sheet record and sets its fields appropriately. * * @param id id must be 0x16 or an exception will be throw upon validation * @param size the size of the data area of the record * @param data data of the record (should not contain sid/len) * @param offset of the record's data */ public ExternSheetRecord(short id, short size, byte[] data, int offset) { super(id, size, data, offset); } /** * called by constructor, should throw runtime exception in the event of a * record passed with a differing ID. * * @param id alleged id for this record */ protected void validateSid(short id) { if (id != sid) { throw new RecordFormatException("NOT An ExternSheet RECORD"); } } /** * called by the constructor, should set class level fields. Should throw * runtime exception for bad/icomplete data. * * @param data raw data * @param size size of data * @param offset of the record's data (provided a big array of the file) */ protected void fillFields(byte [] data, short size, int offset) { field_2_REF_structures = new ArrayList(); field_1_number_of_REF_sturcutres = LittleEndian.getShort(data, 0 + offset); int pos = 2 + offset; for (int i = 0 ; i < field_1_number_of_REF_sturcutres ; ++i) { ExternSheetSubRecord rec = new ExternSheetSubRecord((short)0, (short)6 , data , pos); pos += 6; field_2_REF_structures.add( rec); } } /** * sets the number of the REF structors , that is in Excel file * @param numStruct number of REF structs */ public void setNumOfREFStructures(short numStruct) { field_1_number_of_REF_sturcutres = numStruct; } /** * return the number of the REF structors , that is in Excel file * @return number of REF structs */ public short getNumOfREFStructures() { return field_1_number_of_REF_sturcutres; } /** * adds REF struct (ExternSheetSubRecord) * @param rec REF struct */ public void addREFRecord(ExternSheetSubRecord rec) { field_2_REF_structures.add(rec); } /** returns the number of REF Records, which is in model * @return number of REF records */ public int getNumOfREFRecords() { return field_2_REF_structures.size(); } /** returns the REF record (ExternSheetSubRecord) * @param elem index to place * @return REF record */ public ExternSheetSubRecord getREFRecordAt(int elem) { ExternSheetSubRecord result = ( ExternSheetSubRecord ) field_2_REF_structures.get(elem); return result; } public String toString() { StringBuffer buffer = new StringBuffer(); buffer.append("[EXTERNSHEET]\n"); buffer.append(" numOfRefs = ").append(getNumOfREFStructures()).append("\n"); for (int k=0; k < this.getNumOfREFRecords(); k++) { buffer.append("refrec #").append(k).append('\n'); buffer.append(getREFRecordAt(k).toString()); buffer.append("----refrec #").append(k).append('\n'); } buffer.append("[/EXTERNSHEET]\n"); return buffer.toString(); } /** * called by the class that is responsible for writing this sucker. * Subclasses should implement this so that their data is passed back in a * byte array. * * @param offset to begin writing at * @param data byte array containing instance data * @return number of bytes written */ public int serialize(int offset, byte [] data) { LittleEndian.putShort(data, 0 + offset, sid); LittleEndian.putShort(data, 2 + offset,(short)(2 + (getNumOfREFRecords() *6))); LittleEndian.putShort(data, 4 + offset, getNumOfREFStructures()); int pos = 6 ; for (int k = 0; k < getNumOfREFRecords(); k++) { ExternSheetSubRecord record = getREFRecordAt(k); System.arraycopy(record.serialize(), 0, data, pos + offset, 6); pos +=6; } return getRecordSize(); } public int getRecordSize() { return 4 + 2 + getNumOfREFRecords() * 6; } /** * return the non static version of the id for this record. */ public short getSid() { return this.sid; }}
\ No newline at end of file
+/* ====================================================================
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 2002 The Apache Software Foundation. All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ * if any, must include the following acknowledgment:
+ * "This product includes software developed by the
+ * Apache Software Foundation (http://www.apache.org/)."
+ * Alternately, this acknowledgment may appear in the software itself,
+ * if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Apache" and "Apache Software Foundation" and
+ * "Apache POI" must not be used to endorse or promote products
+ * derived from this software without prior written permission. For
+ * written permission, please contact apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache",
+ * "Apache POI", nor may "Apache" appear in their name, without
+ * prior written permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ *
+ * Description: A List of Inndexes to SupBook
+ * REFERENCE:
+ * @author Libin Roman (Vista Portal LDT. Developer) + * @version 1.0-pre + */ + +public class ExternSheetRecord extends Record { + public final static short sid = 0x17; + private short field_1_number_of_REF_sturcutres; + private ArrayList field_2_REF_structures; + + public ExternSheetRecord() { + field_2_REF_structures = new ArrayList(); + } + + /** + * Constructs a Extern Sheet record and sets its fields appropriately. + * + * @param id id must be 0x16 or an exception will be throw upon validation + * @param size the size of the data area of the record + * @param data data of the record (should not contain sid/len) + */ + + public ExternSheetRecord(short id, short size, byte[] data) { + super(id, size, data); + } + + /** + * Constructs a Extern Sheet record and sets its fields appropriately. + * + * @param id id must be 0x16 or an exception will be throw upon validation + * @param size the size of the data area of the record + * @param data data of the record (should not contain sid/len) + * @param offset of the record's data + */ + public ExternSheetRecord(short id, short size, byte[] data, int offset) { + super(id, size, data, offset); + } + + /** + * called by constructor, should throw runtime exception in the event of a + * record passed with a differing ID. + * + * @param id alleged id for this record + */ + protected void validateSid(short id) { + if (id != sid) { + throw new RecordFormatException("NOT An ExternSheet RECORD"); + } + } + + /** + * called by the constructor, should set class level fields. Should throw + * runtime exception for bad/icomplete data. + * + * @param data raw data + * @param size size of data + * @param offset of the record's data (provided a big array of the file) + */ + protected void fillFields(byte [] data, short size, int offset) { + field_2_REF_structures = new ArrayList(); + + field_1_number_of_REF_sturcutres = LittleEndian.getShort(data, 0 + offset); + + int pos = 2 + offset; + for (int i = 0 ; i < field_1_number_of_REF_sturcutres ; ++i) { + ExternSheetSubRecord rec = new ExternSheetSubRecord((short)0, (short)6 , data , pos); + + pos += 6; + + field_2_REF_structures.add( rec); + } + } + + /** + * sets the number of the REF structors , that is in Excel file + * @param numStruct number of REF structs + */ + public void setNumOfREFStructures(short numStruct) { + field_1_number_of_REF_sturcutres = numStruct; + } + + /** + * return the number of the REF structors , that is in Excel file + * @return number of REF structs + */ + public short getNumOfREFStructures() { + return field_1_number_of_REF_sturcutres; + } + + /** + * adds REF struct (ExternSheetSubRecord) + * @param rec REF struct + */ + public void addREFRecord(ExternSheetSubRecord rec) { + field_2_REF_structures.add(rec); + } + + /** returns the number of REF Records, which is in model + * @return number of REF records + */ + public int getNumOfREFRecords() { + return field_2_REF_structures.size(); + } + + /** returns the REF record (ExternSheetSubRecord) + * @param elem index to place + * @return REF record + */ + public ExternSheetSubRecord getREFRecordAt(int elem) { + ExternSheetSubRecord result = ( ExternSheetSubRecord ) field_2_REF_structures.get(elem); + + return result; + } + + public String toString() { + StringBuffer buffer = new StringBuffer(); + + buffer.append("[EXTERNSHEET]\n"); + buffer.append(" numOfRefs = ").append(getNumOfREFStructures()).append("\n"); + for (int k=0; k < this.getNumOfREFRecords(); k++) { + buffer.append("refrec #").append(k).append('\n'); + buffer.append(getREFRecordAt(k).toString()); + buffer.append("----refrec #").append(k).append('\n'); + } + buffer.append("[/EXTERNSHEET]\n"); + + + return buffer.toString(); + } + + /** + * called by the class that is responsible for writing this sucker. + * Subclasses should implement this so that their data is passed back in a + * byte array. + * + * @param offset to begin writing at + * @param data byte array containing instance data + * @return number of bytes written + */ + public int serialize(int offset, byte [] data) { + LittleEndian.putShort(data, 0 + offset, sid); + LittleEndian.putShort(data, 2 + offset,(short)(2 + (getNumOfREFRecords() *6))); + + LittleEndian.putShort(data, 4 + offset, getNumOfREFStructures()); + + int pos = 6 ; + + for (int k = 0; k < getNumOfREFRecords(); k++) { + ExternSheetSubRecord record = getREFRecordAt(k); + System.arraycopy(record.serialize(), 0, data, pos + offset, 6); + + pos +=6; + } + return getRecordSize(); + } + + public int getRecordSize() { + return 4 + 2 + getNumOfREFRecords() * 6; + } + + /** + * return the non static version of the id for this record. + */ + public short getSid() { + return this.sid; + } +} diff --git a/src/java/org/apache/poi/hssf/record/FormulaRecord.java b/src/java/org/apache/poi/hssf/record/FormulaRecord.java index ad926be474..050558739b 100644 --- a/src/java/org/apache/poi/hssf/record/FormulaRecord.java +++ b/src/java/org/apache/poi/hssf/record/FormulaRecord.java @@ -557,8 +557,13 @@ public class FormulaRecord .append("\n"); buffer.append(" .xf = ") .append(Integer.toHexString(getXFIndex())).append("\n"); - buffer.append(" .value = ").append(getValue()) - .append("\n"); + if (Double.isNaN(this.getValue()) && value_data != null) + buffer.append(" .value (NaN) = ") + .append(org.apache.poi.util.HexDump.dump(value_data,0,0)) + .append("\n"); + else + buffer.append(" .value = ").append(getValue()) + .append("\n"); buffer.append(" .options = ").append(getOptions()) .append("\n"); buffer.append(" .zero = ").append(field_6_zero) @@ -607,9 +612,10 @@ public class FormulaRecord if (field_8_parsed_expr != null) size = field_8_parsed_expr.size(); for (int i=0; i< size; i++) { - Ptg ptg = (Ptg)((Ptg)field_8_parsed_expr.get(i)).clone(); - rec.field_8_parsed_expr.set(i, ptg); + Ptg ptg = (Ptg)((Ptg)field_8_parsed_expr.get(i)).clone(); + rec.field_8_parsed_expr.add(i, ptg); } + rec.value_data = value_data; rec.all_data = all_data; return rec; } diff --git a/src/java/org/apache/poi/hssf/record/NameRecord.java b/src/java/org/apache/poi/hssf/record/NameRecord.java index 09bf2a416d..01fe777dfc 100644 --- a/src/java/org/apache/poi/hssf/record/NameRecord.java +++ b/src/java/org/apache/poi/hssf/record/NameRecord.java @@ -794,6 +794,8 @@ public class NameRecord extends Record { pos += ptg.getSize(); sizeCounter += ptg.getSize(); stack.push(ptg); + field_13_raw_name_definition=new byte[size]; + System.arraycopy(data,offset,field_13_raw_name_definition,0,size); } } catch (java.lang.UnsupportedOperationException uoe) { System.err.println("[WARNING] Unknown Ptg " @@ -880,7 +882,7 @@ public class NameRecord extends Record { .append("\n"); buffer.append(" .unused = ").append( field_5_index_to_sheet ) .append("\n"); - buffer.append(" .( 0 = Global name, otherwise index to sheet (one-based) ) = ").append( field_6_equals_to_index_to_sheet ) + buffer.append(" .index to sheet (1-based, 0=Global) = ").append( field_6_equals_to_index_to_sheet ) .append("\n"); buffer.append(" .Length of menu text (character count) = ").append( field_7_length_custom_menu ) .append("\n"); @@ -906,6 +908,7 @@ public class NameRecord extends Record { .append("\n"); buffer.append(" .Status bar text (Unicode string without length field) = ").append( field_17_status_bar_text ) .append("\n"); + buffer.append(org.apache.poi.util.HexDump.dump(this.field_13_raw_name_definition,0,0)); buffer.append("[/NAME]\n"); return buffer.toString(); diff --git a/src/java/org/apache/poi/hssf/record/PasswordRecord.java b/src/java/org/apache/poi/hssf/record/PasswordRecord.java index 9feb6a2cbd..ba9cbfc5f0 100644 --- a/src/java/org/apache/poi/hssf/record/PasswordRecord.java +++ b/src/java/org/apache/poi/hssf/record/PasswordRecord.java @@ -166,4 +166,14 @@ public class PasswordRecord { return this.sid; } + + /** + * Clone this record. + */ + public Object clone() { + PasswordRecord clone = new PasswordRecord(); + clone.setPassword(field_1_password); + return clone; + } + } diff --git a/src/java/org/apache/poi/hssf/record/Record.java b/src/java/org/apache/poi/hssf/record/Record.java index 7783026054..bd4e73789f 100644 --- a/src/java/org/apache/poi/hssf/record/Record.java +++ b/src/java/org/apache/poi/hssf/record/Record.java @@ -70,16 +70,6 @@ package org.apache.poi.hssf.record; public abstract class Record { - /** - * The static ID, subclasses should override this value with the id for the - * record type they handle. - */ - - public short sid = 0; - private short id = 0; - private short size = 0; - private byte[] data = null; - /** * instantiates a blank record strictly for ID matching */ @@ -98,9 +88,6 @@ public abstract class Record public Record(short id, short size, byte [] data) { - this.id = id; - this.size = size; - this.data = data; validateSid(id); fillFields(data, size); } @@ -115,9 +102,6 @@ public abstract class Record public Record(short id, short size, byte [] data, int offset) { - this.id = id; - this.size = size; - this.data = data; validateSid(id); fillFields(data, size, offset); } diff --git a/src/java/org/apache/poi/hssf/record/RecordProcessor.java b/src/java/org/apache/poi/hssf/record/RecordProcessor.java index c8d659c259..ba86f678f8 100644 --- a/src/java/org/apache/poi/hssf/record/RecordProcessor.java +++ b/src/java/org/apache/poi/hssf/record/RecordProcessor.java @@ -156,5 +156,10 @@ class RecordProcessor recordOffset += amount; available -= amount; } + + public int getRecordOffset() + { + return recordOffset; + } } diff --git a/src/java/org/apache/poi/hssf/record/SSTRecord.java b/src/java/org/apache/poi/hssf/record/SSTRecord.java index a3f0807021..9cd941121d 100644 --- a/src/java/org/apache/poi/hssf/record/SSTRecord.java +++ b/src/java/org/apache/poi/hssf/record/SSTRecord.java @@ -60,7 +60,6 @@ import org.apache.poi.util.LittleEndianConsts; import java.util.Iterator; import java.util.List; -import java.util.ArrayList; /** * Title: Static String Table Record @@ -73,7 +72,7 @@ import java.util.ArrayList; * @author Andrew C. Oliver (acoliver at apache dot org) * @author Marc Johnson (mjohnson at apache dot org) * @author Glen Stampoultzis (glens at apache.org) - * @version 2.0-pre + * * @see org.apache.poi.hssf.record.LabelSSTRecord * @see org.apache.poi.hssf.record.ContinueRecord */ @@ -112,10 +111,14 @@ public class SSTRecord private List _record_lengths = null; private SSTDeserializer deserializer; + /** Offsets from the beginning of the SST record (even across continuations) */ + int[] bucketAbsoluteOffsets; + /** Offsets relative the start of the current SST or continue record */ + int[] bucketRelativeOffsets; + /** * default constructor */ - public SSTRecord() { field_1_num_strings = 0; @@ -220,7 +223,7 @@ public class SSTRecord field_1_num_strings++; String str = ( string == null ) ? "" : string; - int rval = -1; + int rval; UnicodeString ucs = new UnicodeString(); ucs.setString( str ); @@ -334,7 +337,7 @@ public class SSTRecord for ( int k = 0; k < field_3_strings.size(); k++ ) { buffer.append( " .string_" + k + " = " ) - .append( ( (UnicodeString) field_3_strings + .append( ( field_3_strings .get( new Integer( k ) ) ).toString() ).append( "\n" ); } buffer.append( "[/SST]\n" ); @@ -394,7 +397,7 @@ public class SSTRecord * The data consists of sets of string data. This string data is * arranged as follows: *
- *
+ *
*
+ *
* short string_length; // length of string data
* byte string_flag; // flag specifying special string
* // handling
@@ -407,7 +410,7 @@ public class SSTRecord
* // array is run_count)
* byte[] extension; // optional extension (length of array
* // is extend_length)
- *
* The string_flag is bit mapped as follows: *
@@ -507,14 +510,22 @@ public class SSTRecord * Subclasses should implement this so that their data is passed back in a * byte array. * - * @return byte array containing instance data + * @return size */ public int serialize( int offset, byte[] data ) { SSTSerializer serializer = new SSTSerializer( _record_lengths, field_3_strings, getNumStrings(), getNumUniqueStrings() ); - return serializer.serialize( getRecordSize(), offset, data ); + int bytes = serializer.serialize( getRecordSize(), offset, data ); + bucketAbsoluteOffsets = serializer.getBucketAbsoluteOffsets(); + bucketRelativeOffsets = serializer.getBucketRelativeOffsets(); +// for ( int i = 0; i < bucketAbsoluteOffsets.length; i++ ) +// { +// System.out.println( "bucketAbsoluteOffset = " + bucketAbsoluteOffsets[i] ); +// System.out.println( "bucketRelativeOffset = " + bucketRelativeOffsets[i] ); +// } + return bytes; } @@ -538,6 +549,45 @@ public class SSTRecord { deserializer.processContinueRecord( record ); } + + /** + * Creates an extended string record based on the current contents of + * the current SST record. The offset within the stream to the SST record + * is required because the extended string record points directly to the + * strings in the SST record. + *
+ * NOTE: THIS FUNCTION MUST ONLY BE CALLED AFTER THE SST RECORD HAS BEEN + * SERIALIZED. + * + * @param sstOffset The offset in the stream to the start of the + * SST record. + * @return The new SST record. + */ + public ExtSSTRecord createExtSSTRecord(int sstOffset) + { + if (bucketAbsoluteOffsets == null || bucketAbsoluteOffsets == null) + throw new IllegalStateException("SST record has not yet been serialized."); + + ExtSSTRecord extSST = new ExtSSTRecord(); + extSST.setNumStringsPerBucket((short)8); + int[] absoluteOffsets = (int[]) bucketAbsoluteOffsets.clone(); + int[] relativeOffsets = (int[]) bucketRelativeOffsets.clone(); + for ( int i = 0; i < absoluteOffsets.length; i++ ) + absoluteOffsets[i] += sstOffset; + extSST.setBucketOffsets(absoluteOffsets, relativeOffsets); + return extSST; + } + + /** + * Calculates the size in bytes of the EXTSST record as it would be if the + * record was serialized. + * + * @return The size of the ExtSST record in bytes. + */ + public int calcExtSSTRecordSize() + { + return 4 + 2 + ((field_3_strings.size() / SSTSerializer.DEFAULT_BUCKET_SIZE) + 1) * 8; + } } diff --git a/src/java/org/apache/poi/hssf/record/SSTRecordSizeCalculator.java b/src/java/org/apache/poi/hssf/record/SSTRecordSizeCalculator.java index fbdfba50f3..15a52f9807 100644 --- a/src/java/org/apache/poi/hssf/record/SSTRecordSizeCalculator.java +++ b/src/java/org/apache/poi/hssf/record/SSTRecordSizeCalculator.java @@ -61,7 +61,9 @@ import java.util.List; import java.util.Map; /** - * Used to calculate the record sizes for a particular record. + * Used to calculate the record sizes for a particular record. This kind of + * sucks because it's similar to the SST serialization code. In general + * the SST serialization code needs to be rewritten. * * @author Glen Stampoultzis (glens at apache.org) */ diff --git a/src/java/org/apache/poi/hssf/record/SSTSerializer.java b/src/java/org/apache/poi/hssf/record/SSTSerializer.java index 8239eeb8b4..69e7af87d5 100644 --- a/src/java/org/apache/poi/hssf/record/SSTSerializer.java +++ b/src/java/org/apache/poi/hssf/record/SSTSerializer.java @@ -77,6 +77,14 @@ class SSTSerializer private int numUniqueStrings; private SSTRecordHeader sstRecordHeader; + /** Offsets from the beginning of the SST record (even across continuations) */ + int[] bucketAbsoluteOffsets; + /** Offsets relative the start of the current SST or continue record */ + int[] bucketRelativeOffsets; + int startOfSST, startOfRecord; + /** The default bucket size (this is used for ExternSST) */ + final static int DEFAULT_BUCKET_SIZE = 8; + public SSTSerializer( List recordLengths, BinaryTree strings, int numStrings, int numUniqueStrings ) { this.recordLengths = recordLengths; @@ -84,6 +92,9 @@ class SSTSerializer this.numStrings = numStrings; this.numUniqueStrings = numUniqueStrings; this.sstRecordHeader = new SSTRecordHeader( numStrings, numUniqueStrings ); + + this.bucketAbsoluteOffsets = new int[strings.size()/DEFAULT_BUCKET_SIZE+1]; + this.bucketRelativeOffsets = new int[strings.size()/DEFAULT_BUCKET_SIZE+1]; } /** @@ -133,7 +144,6 @@ class SSTSerializer /** * This case is chosen when an SST record does not span over to a continue record. - * */ private void serializeSingleSSTRecord( byte[] data, int offset, int record_length_index ) { @@ -144,6 +154,11 @@ class SSTSerializer for ( int k = 0; k < strings.size(); k++ ) { + if (k % DEFAULT_BUCKET_SIZE == 0) + { + bucketAbsoluteOffsets[k / DEFAULT_BUCKET_SIZE] = pos; + bucketRelativeOffsets[k / DEFAULT_BUCKET_SIZE] = pos; + } System.arraycopy( getUnicodeString( k ).serialize(), 0, data, pos + offset, getUnicodeString( k ).getRecordSize() ); pos += getUnicodeString( k ).getRecordSize(); } @@ -157,6 +172,8 @@ class SSTSerializer private void serializeLargeRecord( int record_size, int record_length_index, byte[] buffer, int offset ) { + startOfSST = offset; + byte[] stringReminant = null; int stringIndex = 0; boolean lastneedcontinue = false; @@ -170,6 +187,7 @@ class SSTSerializer recordLength, numStrings, numUniqueStrings ); // write the appropriate header + startOfRecord = offset + totalWritten; recordProcessor.writeRecordHeader( offset, totalWritten, recordLength, first_record ); first_record = false; @@ -189,6 +207,12 @@ class SSTSerializer { UnicodeString unistr = getUnicodeString( stringIndex ); + if (stringIndex % DEFAULT_BUCKET_SIZE == 0) + { + bucketAbsoluteOffsets[stringIndex / DEFAULT_BUCKET_SIZE] = offset + totalWritten + recordProcessor.getRecordOffset() - startOfSST; + bucketRelativeOffsets[stringIndex / DEFAULT_BUCKET_SIZE] = offset + totalWritten + recordProcessor.getRecordOffset() - startOfRecord; + } + if ( unistr.getRecordSize() <= recordProcessor.getAvailable() ) { recordProcessor.writeWholeString( unistr, offset, totalWritten ); @@ -235,4 +259,14 @@ class SSTSerializer { return recordLengths; } + + public int[] getBucketAbsoluteOffsets() + { + return bucketAbsoluteOffsets; + } + + public int[] getBucketRelativeOffsets() + { + return bucketRelativeOffsets; + } } diff --git a/src/java/org/apache/poi/hssf/record/StyleRecord.java b/src/java/org/apache/poi/hssf/record/StyleRecord.java index c4f8ee35cc..d6bb482e5c 100644 --- a/src/java/org/apache/poi/hssf/record/StyleRecord.java +++ b/src/java/org/apache/poi/hssf/record/StyleRecord.java @@ -57,12 +57,14 @@ package org.apache.poi.hssf.record; import org.apache.poi.util.LittleEndian; import org.apache.poi.util.StringUtil; +import org.apache.poi.util.BitField; /** * Title: Style Record
* Description: Describes a builtin to the gui or user defined style
* REFERENCE: PG 390 Microsoft Excel 97 Developer's Kit (ISBN: 1-57231-498-2)
* @author Andrew C. Oliver (acoliver at apache dot org) + * @author aviks : string fixes for UserDefined Style * @version 2.0-pre */ @@ -81,8 +83,10 @@ public class StyleRecord private byte field_3_outline_style_level; // only for user defined styles - private byte field_2_name_length; - private String field_3_name; + private short field_2_name_length; //OO doc says 16 bit length, so we believe + private byte field_3_string_options; + private BitField fHighByte; + private String field_4_name; public StyleRecord() { @@ -125,17 +129,24 @@ public class StyleRecord protected void fillFields(byte [] data, short size, int offset) { + fHighByte = new BitField(0x01); //have to init here, since we are being called + //from super, and class level init hasnt been done. field_1_xf_index = LittleEndian.getShort(data, 0 + offset); - if (getType() == 1) + if (getType() == STYLE_BUILT_IN) { field_2_builtin_style = data[ 2 + offset ]; field_3_outline_style_level = data[ 3 + offset ]; } - else if (getType() == 0) + else if (getType() == STYLE_USER_DEFINED) { - field_2_name_length = data[ 2 + offset ]; - field_3_name = StringUtil.getFromCompressedUnicode(data, 3 + offset, - LittleEndian.ubyteToInt(field_2_name_length)); + field_2_name_length = LittleEndian.getShort(data, 2 + offset ); + field_3_string_options = data[4+offset]; + + if (fHighByte.isSet(field_3_string_options)) { + field_4_name= StringUtil.getFromUnicode(data,offset+5,field_2_name_length); + }else { + field_4_name=StringUtil.getFromCompressedUnicode(data,offset+5,field_2_name_length); + } } // todo sanity check exception to make sure we're one or the other @@ -199,7 +210,8 @@ public class StyleRecord public void setName(String name) { - field_3_name = name; + field_4_name = name; + //TODO set name length and string options } // end user defined @@ -273,7 +285,7 @@ public class StyleRecord * @see #getName() */ - public byte getNameLength() + public short getNameLength() { return field_2_name_length; } @@ -286,7 +298,7 @@ public class StyleRecord public String getName() { - return field_3_name; + return field_4_name; } // end user defined @@ -361,7 +373,7 @@ public class StyleRecord else { LittleEndian.putShort(data, 2 + offset, - (( short ) (0x03 + getNameLength()))); + (( short ) (getRecordSize()-4))); } LittleEndian.putShort(data, 4 + offset, getIndex()); if (getType() == STYLE_BUILT_IN) @@ -371,8 +383,9 @@ public class StyleRecord } else { - data[ 6 + offset ] = getNameLength(); - StringUtil.putCompressedUnicode(getName(), data, 7 + offset); + LittleEndian.putShort(data, 6 + offset , getNameLength()); + data[8+offset]=this.field_3_string_options; + StringUtil.putCompressedUnicode(getName(), data, 9 + offset); } return getRecordSize(); } @@ -387,7 +400,11 @@ public class StyleRecord } else { - retval = 7 + getNameLength(); + if (fHighByte.isSet(field_3_string_options)) { + retval= 9+2*getNameLength(); + }else { + retval = 9 + getNameLength(); + } } return retval; } diff --git a/src/java/org/apache/poi/hssf/record/UnknownRecord.java b/src/java/org/apache/poi/hssf/record/UnknownRecord.java index 36b0595498..b88d22f455 100644 --- a/src/java/org/apache/poi/hssf/record/UnknownRecord.java +++ b/src/java/org/apache/poi/hssf/record/UnknownRecord.java @@ -65,16 +65,14 @@ import org.apache.poi.util.LittleEndian; * Company: SuperLink Software, Inc.
* @author Andrew C. Oliver (acoliver at apache dot org)
* @author Jason Height (jheight at chariot dot net dot au)
- * @version 2.0-pre
+ * @author Glen Stampoultzis (glens at apache.org)
*/
public class UnknownRecord
extends Record
{
- private short sid = 0;
- private short size = 0;
- private byte[] thedata = null;
- int offset = 0;
+ private short sid = 0;
+ private byte[] thedata = null;
public UnknownRecord()
{
@@ -91,7 +89,6 @@ public class UnknownRecord
public UnknownRecord(short id, short size, byte [] data)
{
sid = id;
- size = size;
thedata = data;
}
@@ -127,7 +124,7 @@ public class UnknownRecord
protected void fillFields(byte [] data, short sid)
{
- sid = sid;
+ this.sid = sid;
thedata = data;
}
@@ -179,9 +176,7 @@ public class UnknownRecord
/** Unlike the other Record.clone methods this is a shallow clone*/
public Object clone() {
UnknownRecord rec = new UnknownRecord();
- rec.offset = offset;
rec.sid = sid;
- rec.size = size;
rec.thedata = thedata;
return rec;
}
diff --git a/src/java/org/apache/poi/hssf/record/aggregates/FormulaRecordAggregate.java b/src/java/org/apache/poi/hssf/record/aggregates/FormulaRecordAggregate.java
index 7d5b0b048d..e3eb40e201 100644
--- a/src/java/org/apache/poi/hssf/record/aggregates/FormulaRecordAggregate.java
+++ b/src/java/org/apache/poi/hssf/record/aggregates/FormulaRecordAggregate.java
@@ -82,6 +82,19 @@ public class FormulaRecordAggregate
this.stringRecord = stringRecord;
}
+ /**
+ * Used only in the clone
+ * @param formulaRecord
+ * @param stringRecord
+ * @param sharedRecord
+ */
+ public FormulaRecordAggregate( FormulaRecord formulaRecord, StringRecord stringRecord, SharedFormulaRecord sharedRecord)
+ {
+ this.formulaRecord = formulaRecord;
+ this.stringRecord = stringRecord;
+ this.sharedFormulaRecord = sharedRecord;
+ }
+
protected void validateSid( short id )
@@ -106,14 +119,14 @@ public class FormulaRecordAggregate
{
int pos = offset;
pos += formulaRecord.serialize(pos, data);
- if (stringRecord != null)
- {
- pos += stringRecord.serialize(pos, data);
- }
if (this.getSharedFormulaRecord() != null)
{
pos += getSharedFormulaRecord().serialize(pos, data);
}
+ if (stringRecord != null)
+ {
+ pos += stringRecord.serialize(pos, data);
+ }
return pos - offset;
}
@@ -221,7 +234,10 @@ public class FormulaRecordAggregate
* @see java.lang.Object#clone()
*/
public Object clone() {
- return new FormulaRecordAggregate((FormulaRecord) this.formulaRecord.clone(), (StringRecord) this.stringRecord.clone());
+ StringRecord clonedString = (stringRecord == null) ? null : (StringRecord)stringRecord.clone();
+ SharedFormulaRecord clonedShared = (sharedFormulaRecord == null) ? null : (SharedFormulaRecord)sharedFormulaRecord.clone();
+
+ return new FormulaRecordAggregate((FormulaRecord) this.formulaRecord.clone(), clonedString, clonedShared);
}
@@ -241,4 +257,14 @@ public class FormulaRecordAggregate
this.sharedFormulaRecord = sharedFormulaRecord;
}
+ /*
+ * Setting to true so that this value does not abort the whole ValueAggregation
+ * (non-Javadoc)
+ * @see org.apache.poi.hssf.record.Record#isInValueSection()
+ */
+ public boolean isInValueSection() {
+
+ return true;
+ }
+
}
diff --git a/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java b/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java
index 1e425669db..311ba89399 100644
--- a/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java
@@ -318,6 +318,7 @@ public class Area3DPtg extends Ptg
ptg.field_3_last_row = field_3_last_row;
ptg.field_4_first_column = field_4_first_column;
ptg.field_5_last_column = field_5_last_column;
+ ptg.setClass(ptgClass);
return ptg;
}
diff --git a/src/java/org/apache/poi/hssf/record/formula/AreaPtg.java b/src/java/org/apache/poi/hssf/record/formula/AreaPtg.java
index 87ebb2c769..4f994151d6 100644
--- a/src/java/org/apache/poi/hssf/record/formula/AreaPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/AreaPtg.java
@@ -321,6 +321,7 @@ public class AreaPtg
ptg.field_2_last_row = field_2_last_row;
ptg.field_3_first_column = field_3_first_column;
ptg.field_4_last_column = field_4_last_column;
+ ptg.setClass(ptgClass);
return ptg;
}
diff --git a/src/java/org/apache/poi/hssf/record/formula/ExpPtg.java b/src/java/org/apache/poi/hssf/record/formula/ExpPtg.java
index 576bce3316..b100a1a762 100644
--- a/src/java/org/apache/poi/hssf/record/formula/ExpPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/ExpPtg.java
@@ -110,7 +110,10 @@ public class ExpPtg
public byte getDefaultOperandClass() {return Ptg.CLASS_VALUE;}
public Object clone() {
- throw new RuntimeException("NO IDEA SHARED FORMULA EXP PTG");
+ //can't clone one that doesnt have data can we??
+ if (this.existing == null) throw new RuntimeException("NO IDEA SHARED FORMULA EXP PTG");
+
+ return new ExpPtg(this.existing, 0);
}
}
diff --git a/src/java/org/apache/poi/hssf/record/formula/FuncPtg.java b/src/java/org/apache/poi/hssf/record/formula/FuncPtg.java
index 1c230c95a4..713d200b2d 100644
--- a/src/java/org/apache/poi/hssf/record/formula/FuncPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/FuncPtg.java
@@ -63,7 +63,8 @@ public class FuncPtg extends AbstractFunctionPtg{
FuncPtg ptg = new FuncPtg();
//ptg.field_1_num_args = field_1_num_args;
ptg.field_2_fnc_index = field_2_fnc_index;
- return ptg;
+ ptg.setClass(ptgClass);
+ return ptg;
}
public int getSize() {
diff --git a/src/java/org/apache/poi/hssf/record/formula/FuncVarPtg.java b/src/java/org/apache/poi/hssf/record/formula/FuncVarPtg.java
index f333ba694e..6bf4d7f540 100644
--- a/src/java/org/apache/poi/hssf/record/formula/FuncVarPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/FuncVarPtg.java
@@ -52,6 +52,7 @@ public class FuncVarPtg extends AbstractFunctionPtg{
FuncVarPtg ptg = new FuncVarPtg();
ptg.field_1_num_args = field_1_num_args;
ptg.field_2_fnc_index = field_2_fnc_index;
+ ptg.setClass(ptgClass);
return ptg;
}
diff --git a/src/java/org/apache/poi/hssf/record/formula/NamePtg.java b/src/java/org/apache/poi/hssf/record/formula/NamePtg.java
index c926d511a1..3786cdd694 100644
--- a/src/java/org/apache/poi/hssf/record/formula/NamePtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/NamePtg.java
@@ -73,10 +73,10 @@ public class NamePtg
extends Ptg
{
public final static short sid = 0x23;
- private final static int SIZE = 7;
- private short field_1_ixti; // unknown function
- private short field_2_label_index;
- private short field_3_zero; // reserved must be 0
+ private final static int SIZE = 5;
+ private short field_1_label_index;
+ private short field_2_zero; // reserved must be 0
+ boolean xtra=false;
private NamePtg() {
@@ -95,13 +95,17 @@ public class NamePtg
public NamePtg(byte [] data, int offset)
{
offset++;
- field_1_ixti = LittleEndian.getShort(data, offset);
- field_2_label_index = LittleEndian.getShort(data, offset + 2);
- field_3_zero = LittleEndian.getShort(data, offset + 4);
+ //field_1_ixti = LittleEndian.getShort(data, offset);
+ field_1_label_index = LittleEndian.getShort(data, offset );
+ field_2_zero = LittleEndian.getShort(data, offset + 2);
+ //if (data[offset+6]==0) xtra=true;
}
public void writeBytes(byte [] array, int offset)
{
+ array[offset+0]= (byte) (sid + ptgClass);
+ LittleEndian.putShort(array,offset+1,field_1_label_index);
+ LittleEndian.putShort(array,offset+3, field_2_zero);
}
public int getSize()
@@ -111,16 +115,15 @@ public class NamePtg
public String toFormulaString(SheetReferences refs)
{
- return "NO IDEA - NAME";
+ return "NAMED RANGE";
}
public byte getDefaultOperandClass() {return Ptg.CLASS_VALUE;}
public Object clone() {
NamePtg ptg = new NamePtg();
- ptg.field_1_ixti = field_1_ixti;
- ptg.field_2_label_index = field_2_label_index;
- ptg.field_3_zero = field_3_zero;
+ ptg.field_1_label_index = field_1_label_index;
+ ptg.field_2_zero = field_2_zero;
return ptg;
}
}
diff --git a/src/java/org/apache/poi/hssf/record/formula/NameXPtg.java b/src/java/org/apache/poi/hssf/record/formula/NameXPtg.java
index b913f438a4..58fa096866 100644
--- a/src/java/org/apache/poi/hssf/record/formula/NameXPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/NameXPtg.java
@@ -102,7 +102,7 @@ public class NameXPtg extends Ptg
public void writeBytes(byte [] array, int offset)
{
- array[ offset + 0 ] = sid;
+ array[ offset + 0 ] = (byte)(sid + ptgClass);
LittleEndian.putShort(array, offset + 1, field_1_ixals);
LittleEndian.putShort(array,offset+3, field_2_ilbl);
LittleEndian.putShort(array, offset + 5, field_3_reserved);
@@ -125,6 +125,7 @@ public class NameXPtg extends Ptg
ptg.field_1_ixals = field_1_ixals;
ptg.field_3_reserved = field_3_reserved;
ptg.field_2_ilbl = field_2_ilbl;
+ ptg.setClass(ptgClass);
return ptg;
}
}
diff --git a/src/java/org/apache/poi/hssf/record/formula/Ptg.java b/src/java/org/apache/poi/hssf/record/formula/Ptg.java
index 3787d0056e..70b0606134 100644
--- a/src/java/org/apache/poi/hssf/record/formula/Ptg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/Ptg.java
@@ -307,6 +307,12 @@ public abstract class Ptg
case MissingArgPtg.sid:
retval = new MissingArgPtg(data,offset);
break;
+ case UnaryPlusPtg.sid:
+ retval=new UnaryPlusPtg(data,offset);
+ break;
+ case UnaryMinusPtg.sid:
+ retval=new UnaryMinusPtg(data,offset);
+ break;
default :
@@ -357,6 +363,14 @@ public abstract class Ptg
return retval;
}
+ /** Overridden toString method to ensure object hash is not printed.
+ * This helps get rid of gratuitous diffs when comparing two dumps
+ * Subclasses may output more relevant information by overriding this method
+ **/
+ public String toString(){
+ return this.getClass().toString();
+ }
+
public static final byte CLASS_REF = 0x00;
public static final byte CLASS_VALUE = 0x20;
public static final byte CLASS_ARRAY = 0x40;
diff --git a/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java b/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java
index 0d842955b9..13ae2ef238 100644
--- a/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java
@@ -104,7 +104,7 @@ public class Ref3DPtg extends Ptg {
public String toString() {
StringBuffer buffer = new StringBuffer();
- buffer.append("Ref3dPrg\n");
+ buffer.append("Ref3dPtg\n");
buffer.append("Index to Extern Sheet = " + getExternSheetIndex()).append("\n");
buffer.append("Row = " + getRow()).append("\n");
buffer.append("Col = " + getColumn()).append("\n");
@@ -210,6 +210,7 @@ public class Ref3DPtg extends Ptg {
ptg.field_1_index_extern_sheet = field_1_index_extern_sheet;
ptg.field_2_row = field_2_row;
ptg.field_3_column = field_3_column;
+ ptg.setClass(ptgClass);
return ptg;
}
diff --git a/src/java/org/apache/poi/hssf/record/formula/ReferencePtg.java b/src/java/org/apache/poi/hssf/record/formula/ReferencePtg.java
index 90f7d899c8..2ec380a4ad 100644
--- a/src/java/org/apache/poi/hssf/record/formula/ReferencePtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/ReferencePtg.java
@@ -193,6 +193,7 @@ public class ReferencePtg extends Ptg
ReferencePtg ptg = new ReferencePtg();
ptg.field_1_row = field_1_row;
ptg.field_2_col = field_2_col;
+ ptg.setClass(ptgClass);
return ptg;
}
}
diff --git a/src/java/org/apache/poi/hssf/record/formula/StringPtg.java b/src/java/org/apache/poi/hssf/record/formula/StringPtg.java
index 022fffd83c..a66861914e 100644
--- a/src/java/org/apache/poi/hssf/record/formula/StringPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/StringPtg.java
@@ -55,8 +55,9 @@
package org.apache.poi.hssf.record.formula;
import org.apache.poi.util.LittleEndian;
-
+import org.apache.poi.util.BitField;
import org.apache.poi.hssf.util.SheetReferences;
+import org.apache.poi.util.StringUtil;
/**
* Number
@@ -70,7 +71,12 @@ public class StringPtg
{
public final static int SIZE = 9;
public final static byte sid = 0x17;
- private String field_1_value;
+ //NOTE: OO doc says 16bit lenght, but BiffViewer says 8
+ // Book says something totally different, so dont look there!
+ byte field_1_length;
+ byte field_2_options;
+ BitField fHighByte = new BitField(0x01);
+ private String field_3_string;
private StringPtg() {
//Required for clone methods
@@ -79,7 +85,16 @@ public class StringPtg
/** Create a StringPtg from a byte array read from disk */
public StringPtg(byte [] data, int offset)
{
- setValue(new String(data, offset+3, data[offset+1] + 256*data[offset+2]));
+ offset++;
+ field_1_length = data[offset];
+ field_2_options = data[offset+1];
+ if (fHighByte.isSet(field_2_options)) {
+ field_3_string= StringUtil.getFromUnicode(data,offset+2,field_1_length);
+ }else {
+ field_3_string=StringUtil.getFromCompressedUnicode(data,offset+2,field_1_length);
+ }
+
+ //setValue(new String(data, offset+3, data[offset+1] + 256*data[offset+2]));
}
/** Create a StringPtg from a string representation of the number
@@ -88,32 +103,46 @@ public class StringPtg
* @param value : String representation of a floating point number
*/
public StringPtg(String value) {
- setValue(value);
+ if (value.length() >255) {
+ throw new IllegalArgumentException("String literals in formulas cant be bigger than 255 characters ASCII");
+ }
+ this.field_2_options=0;
+ this.fHighByte.setBoolean(field_2_options, false);
+ this.field_3_string=value;
+ this.field_1_length=(byte)value.length(); //for the moment, we support only ASCII strings in formulas we create
}
-
+ /*
public void setValue(String value)
{
field_1_value = value;
- }
+ }*/
public String getValue()
{
- return field_1_value;
+ return field_3_string;
}
public void writeBytes(byte [] array, int offset)
{
array[ offset + 0 ] = sid;
- array[ offset + 1 ] = (byte)(getValue().length() % 256);
- array[ offset + 2 ] = (byte)(getValue().length() / 256);
- System.arraycopy(getValue().getBytes(), 0, array, offset + 3, getValue().length());
+ array[ offset + 1 ] = field_1_length;
+ array[ offset + 2 ] = field_2_options;
+ if (fHighByte.isSet(field_2_options)) {
+ StringUtil.putUncompressedUnicode(getValue(),array,offset+3);
+ }else {
+ StringUtil.putCompressedUnicode(getValue(),array,offset+3);
+ }
}
public int getSize()
{
- return field_1_value.length() + 3;
+ if (fHighByte.isSet(field_2_options)) {
+ return 2*field_1_length+3;
+ }else {
+ return field_1_length+3;
+ }
}
public String toFormulaString(SheetReferences refs)
@@ -126,7 +155,9 @@ public class StringPtg
public Object clone() {
StringPtg ptg = new StringPtg();
- ptg.field_1_value = field_1_value;
+ ptg.field_1_length = field_1_length;
+ ptg.field_2_options=field_2_options;
+ ptg.field_3_string=field_3_string;
return ptg;
}
diff --git a/src/java/org/apache/poi/hssf/record/formula/UnaryMinusPtg.java b/src/java/org/apache/poi/hssf/record/formula/UnaryMinusPtg.java
new file mode 100644
index 0000000000..6cbe9b64d9
--- /dev/null
+++ b/src/java/org/apache/poi/hssf/record/formula/UnaryMinusPtg.java
@@ -0,0 +1,127 @@
+
+/* ====================================================================
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 2003 The Apache Software Foundation. All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ * if any, must include the following acknowledgment:
+ * "This product includes software developed by the
+ * Apache Software Foundation (http://www.apache.org/)."
+ * Alternately, this acknowledgment may appear in the software itself,
+ * if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Apache" and "Apache Software Foundation" and
+ * "Apache POI" must not be used to endorse or promote products
+ * derived from this software without prior written permission. For
+ * written permission, please contact apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache",
+ * "Apache POI", nor may "Apache" appear in their name, without
+ * prior written permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ *
+ * TODO: MODE , this is only row specific + * @param startRow + * @param endRow + * @param n + * @param isRow + */ + protected void shiftMerged(int startRow, int endRow, int n, boolean isRow) { + List shiftedRegions = new ArrayList(); + //move merged regions completely if they fall within the new region boundaries when they are shifted + for (int i = 0; i < this.getNumMergedRegions(); i++) { + Region merged = this.getMergedRegionAt(i); + + boolean inStart = (merged.getRowFrom() >= startRow || merged.getRowTo() >= startRow); + boolean inEnd = (merged.getRowTo() <= endRow || merged.getRowFrom() <= endRow); + + //dont check if it's not within the shifted area + if (! (inStart && inEnd)) continue; + + //only shift if the region outside the shifted rows is not merged too + if (!merged.contains(startRow-1, (short)0) && !merged.contains(endRow+1, (short)0)){ + merged.setRowFrom(merged.getRowFrom()+n); + merged.setRowTo(merged.getRowTo()+n); + //have to remove/add it back + shiftedRegions.add(merged); + this.removeMergedRegion(i); + i = i -1; // we have to back up now since we removed one + + } + + } + + //readd so it doesn't get shifted again + Iterator iterator = shiftedRegions.iterator(); + while (iterator.hasNext()) { + Region region = (Region)iterator.next(); + + this.addMergedRegion(region); + } + + } + /** * Shifts rows between startRow and endRow n number of rows. * If you use a negative number, it will shift rows up. @@ -894,19 +946,25 @@ public class HSSFSheet * * Calls shiftRows(startRow, endRow, n, false, false); * + *
+ * Additionally shifts merged regions that are completely defined in these + * rows (ie. merged 2 cells on a row to be shifted). * @param startRow the row to start shifting * @param endRow the row to end shifting * @param n the number of rows to shift */ public void shiftRows( int startRow, int endRow, int n ) { - shiftRows(startRow, endRow, n, false, false); + shiftRows(startRow, endRow, n, false, false); } /** * Shifts rows between startRow and endRow n number of rows. * If you use a negative number, it will shift rows up. * Code ensures that rows don't wrap around - * + * + *
+ * Additionally shifts merged regions that are completely defined in these
+ * rows (ie. merged 2 cells on a row to be shifted).
* @param startRow the row to start shifting
* @param endRow the row to end shifting
* @param n the number of rows to shift
@@ -928,6 +986,9 @@ public class HSSFSheet
e = startRow;
inc = -1;
}
+
+ shiftMerged(startRow, endRow, n, true);
+
for ( int rowNum = s; rowNum >= startRow && rowNum <= endRow && rowNum >= 0 && rowNum < 65536; rowNum += inc )
{
HSSFRow row = getRow( rowNum );
@@ -937,6 +998,9 @@ public class HSSFSheet
HSSFCell cell;
+
+
+
// Removes the cells before over writting them.
for ( short col = row2Replace.getFirstCellNum(); col <= row2Replace.getLastCellNum(); col++ )
{
@@ -1021,5 +1085,51 @@ public class HSSFSheet
getSheet().createSplitPane( xSplitPos, ySplitPos, topRow, leftmostColumn, activePane );
}
+ /**
+ * Sets whether the gridlines are shown in a viewer.
+ * @param show whether to show gridlines or not
+ */
+ public void setDisplayGridlines(boolean show) {
+ sheet.setDisplayGridlines(show);
+ }
+ /**
+ * Returns if gridlines are displayed.
+ * @return whether gridlines are displayed
+ */
+ public boolean isDisplayGridlines() {
+ return sheet.isDisplayGridlines();
+ }
+
+ /**
+ * Sets whether the formulas are shown in a viewer.
+ * @param show whether to show formulas or not
+ */
+ public void setDisplayFormulas(boolean show) {
+ sheet.setDisplayFormulas(show);
+ }
+
+ /**
+ * Returns if formulas are displayed.
+ * @return whether formulas are displayed
+ */
+ public boolean isDisplayFormulas() {
+ return sheet.isDisplayFormulas();
+ }
+
+ /**
+ * Sets whether the RowColHeadings are shown in a viewer.
+ * @param show whether to show RowColHeadings or not
+ */
+ public void setDisplayRowColHeadings(boolean show) {
+ sheet.setDisplayRowColHeadings(show);
+ }
+
+ /**
+ * Returns if RowColHeadings are displayed.
+ * @return whether RowColHeadings are displayed
+ */
+ public boolean isDisplayRowColHeadings() {
+ return sheet.isDisplayRowColHeadings();
+ }
}
diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
index 8420aa63b0..69f2c99e95 100644
--- a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
+++ b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
@@ -147,6 +147,14 @@ public class HSSFWorkbook
* memory.
*/
private POIFSFileSystem poifs;
+
+ /**
+ * Used to keep track of the data formatter so that all
+ * createDataFormatter calls return the same one for a given
+ * book. This ensures that updates from one places is visible
+ * someplace else.
+ */
+ private HSSFDataFormat formatter;
private static POILogger log = POILogFactory.getLogger(HSSFWorkbook.class);
@@ -945,13 +953,15 @@ public class HSSFWorkbook
}
/**
- * Creates an instance of HSSFDataFormat.
+ * Returns the instance of HSSFDataFormat for this workbook.
* @return the HSSFDataFormat object
* @see org.apache.poi.hssf.record.FormatRecord
* @see org.apache.poi.hssf.record.Record
*/
public HSSFDataFormat createDataFormat() {
- return new HSSFDataFormat(workbook);
+ if (formatter == null)
+ formatter = new HSSFDataFormat(workbook);
+ return formatter;
}
/** remove the named range by his name
diff --git a/src/java/org/apache/poi/util/HexDump.java b/src/java/org/apache/poi/util/HexDump.java
index bc846459d1..74198bc525 100644
--- a/src/java/org/apache/poi/util/HexDump.java
+++ b/src/java/org/apache/poi/util/HexDump.java
@@ -67,6 +67,20 @@ import java.io.*;
public class HexDump
{
+ public static final String EOL =
+ System.getProperty("line.separator");
+// private static final StringBuffer _lbuffer = new StringBuffer(8);
+// private static final StringBuffer _cbuffer = new StringBuffer(2);
+ private static final char _hexcodes[] =
+ {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
+ 'E', 'F'
+ };
+ private static final int _shifts[] =
+ {
+ 28, 24, 20, 16, 12, 8, 4, 0
+ };
+
// all static methods, so no need for a public constructor
private HexDump()
@@ -95,12 +109,14 @@ public class HexDump
throws IOException, ArrayIndexOutOfBoundsException,
IllegalArgumentException
{
- if ((index < 0) || (index >= data.length))
+ if ((index < 0) || (data.length != 0 && index >= data.length))
{
throw new ArrayIndexOutOfBoundsException(
"illegal index: " + index + " into array of length "
+ data.length);
}
+ if (data.length == 0)
+ return; // nothing more to do.
if (stream == null)
{
throw new IllegalArgumentException("cannot write to nullstream");
@@ -241,39 +257,26 @@ public class HexDump
}
- public static final String EOL =
- System.getProperty("line.separator");
- private static final StringBuffer _lbuffer = new StringBuffer(8);
- private static final StringBuffer _cbuffer = new StringBuffer(2);
- private static final char _hexcodes[] =
- {
- '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
- 'E', 'F'
- };
- private static final int _shifts[] =
- {
- 28, 24, 20, 16, 12, 8, 4, 0
- };
-
private static String dump(final long value)
{
- _lbuffer.setLength(0);
+ StringBuffer buf = new StringBuffer();
+ buf.setLength(0);
for (int j = 0; j < 8; j++)
{
- _lbuffer
- .append(_hexcodes[ (( int ) (value >> _shifts[ j ])) & 15 ]);
+ buf.append( _hexcodes[ (( int ) (value >> _shifts[ j ])) & 15 ]);
}
- return _lbuffer.toString();
+ return buf.toString();
}
private static String dump(final byte value)
{
- _cbuffer.setLength(0);
+ StringBuffer buf = new StringBuffer();
+ buf.setLength(0);
for (int j = 0; j < 2; j++)
{
- _cbuffer.append(_hexcodes[ (value >> _shifts[ j + 6 ]) & 15 ]);
+ buf.append(_hexcodes[ (value >> _shifts[ j + 6 ]) & 15 ]);
}
- return _cbuffer.toString();
+ return buf.toString();
}
/**
@@ -294,6 +297,7 @@ public class HexDump
retVal.append(']');
return retVal.toString();
}
+
/**
* Converts the parameter to a hex value.
*
@@ -337,4 +341,41 @@ public class HexDump
}
return result.toString();
}
+
+ /**
+ * Dumps bytesToDump
bytes to an output stream.
+ *
+ * @param in The stream to read from
+ * @param out The output stream
+ * @param start The index to use as the starting position for the left hand side label
+ * @param bytesToDump The number of bytes to output. Use -1 to read until the end of file.
+ */
+ public static void dump( InputStream in, PrintStream out, int start, int bytesToDump ) throws IOException
+ {
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ if (bytesToDump == -1)
+ {
+ int c = in.read();
+ while (c != -1)
+ {
+ buf.write(c);
+ c = in.read();
+ }
+ }
+ else
+ {
+ int bytesRemaining = bytesToDump;
+ while (bytesRemaining-- > 0)
+ {
+ int c = in.read();
+ if (c == -1)
+ break;
+ else
+ buf.write(c);
+ }
+ }
+
+ byte[] data = buf.toByteArray();
+ dump(data, 0, out, start, data.length);
+ }
}
diff --git a/src/java/org/apache/poi/util/HexRead.java b/src/java/org/apache/poi/util/HexRead.java
index 0be7930a7b..6c5eceb125 100644
--- a/src/java/org/apache/poi/util/HexRead.java
+++ b/src/java/org/apache/poi/util/HexRead.java
@@ -1 +1,225 @@
-/* ==================================================================== * The Apache Software License, Version 1.1 * * Copyright (c) 2003 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, * if any, must include the following acknowledgment: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowledgment may appear in the software itself, * if and wherever such third-party acknowledgments normally appear. * * 4. The names "Apache" and "Apache Software Foundation" and * "Apache POI" must not be used to endorse or promote products * derived from this software without prior written permission. For * written permission, please contact apache@apache.org. * * 5. Products derived from this software may not be called "Apache", * "Apache POI", nor may "Apache" appear in their name, without * prior written permission of the Apache Software Foundation. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see *
* [sectioname] ** * @see #readData(String) */ public static byte[] readData(String filename, String section) throws IOException { File file = new File( filename ); FileInputStream stream = new FileInputStream( file ); try { StringBuffer sectionText = new StringBuffer(); boolean inSection = false; int c = stream.read(); while (c != -1) { switch(c) { case '[': inSection = true; break; case '\n': case '\r': inSection = false; sectionText = new StringBuffer(); break; case ']': inSection = false; if (sectionText.toString().equals(section)) return readData(stream, '['); sectionText = new StringBuffer(); break; default: if (inSection) sectionText.append((char)c); } c = stream.read(); } } finally { stream.close(); } throw new IOException("Section '" + section + "' not found"); } static private byte[] readData( FileInputStream stream, int eofChar ) throws IOException { int characterCount = 0; byte b = (byte) 0; List bytes = new ArrayList(); boolean done = false; while ( !done ) { int count = stream.read(); char baseChar = 'a'; if ( count == eofChar) break; switch ( count ) { case '#': readToEOL(stream); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': b <<= 4; b += (byte) ( count - '0' ); characterCount++; if ( characterCount == 2 ) { bytes.add( new Byte( b ) ); characterCount = 0; b = (byte) 0; } break; case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': baseChar = 'A'; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': b <<= 4; b += (byte) ( count + 10 - baseChar ); characterCount++; if ( characterCount == 2 ) { bytes.add( new Byte( b ) ); characterCount = 0; b = (byte) 0; } break; case -1: done = true; break; default : break; } } Byte[] polished = (Byte[]) bytes.toArray( new Byte[0] ); byte[] rval = new byte[polished.length]; for ( int j = 0; j < polished.length; j++ ) { rval[j] = polished[j].byteValue(); } return rval; } static private void readToEOL( InputStream stream ) throws IOException { int c = stream.read(); while ( c != -1 && c != '\n' && c != '\r') { c = stream.read(); } }} \ No newline at end of file +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2002 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" and + * "Apache POI" must not be used to endorse or promote products + * derived from this software without prior written permission. For + * written permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * "Apache POI", nor may "Apache" appear in their name, without + * prior written permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + *
+ * [sectioname] + *+ * + * @see #readData(String) + */ + public static byte[] readData( String filename, String section ) throws IOException + { + File file = new File( filename ); + FileInputStream stream = new FileInputStream( file ); + try + { + StringBuffer sectionText = new StringBuffer(); + boolean inSection = false; + int c = stream.read(); + while ( c != -1 ) + { + switch ( c ) + { + case '[': + inSection = true; + break; + case '\n': + case '\r': + inSection = false; + sectionText = new StringBuffer(); + break; + case ']': + inSection = false; + if ( sectionText.toString().equals( section ) ) return readData( stream, '[' ); + sectionText = new StringBuffer(); + break; + default: + if ( inSection ) sectionText.append( (char) c ); + } + c = stream.read(); + } + } + finally + { + stream.close(); + } + throw new IOException( "Section '" + section + "' not found" ); + } + + static public byte[] readData( InputStream stream, int eofChar ) throws IOException + { + int characterCount = 0; + byte b = (byte) 0; + List bytes = new ArrayList(); + boolean done = false; + while ( !done ) + { + int count = stream.read(); + char baseChar = 'a'; + if ( count == eofChar ) break; + switch ( count ) + { + case '#': + readToEOL( stream ); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + b <<= 4; + b += (byte) ( count - '0' ); + characterCount++; + if ( characterCount == 2 ) + { + bytes.add( new Byte( b ) ); + characterCount = 0; + b = (byte) 0; + } + break; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + baseChar = 'A'; + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + b <<= 4; + b += (byte) ( count + 10 - baseChar ); + characterCount++; + if ( characterCount == 2 ) + { + bytes.add( new Byte( b ) ); + characterCount = 0; + b = (byte) 0; + } + break; + case -1: + done = true; + break; + default : + break; + } + } + Byte[] polished = (Byte[]) bytes.toArray( new Byte[0] ); + byte[] rval = new byte[polished.length]; + for ( int j = 0; j < polished.length; j++ ) + { + rval[j] = polished[j].byteValue(); + } + return rval; + } + + static public byte[] readFromString(String data) throws IOException + { + return readData(new ByteArrayInputStream( data.getBytes() ), -1); + } + + static private void readToEOL( InputStream stream ) throws IOException + { + int c = stream.read(); + while ( c != -1 && c != '\n' && c != '\r' ) + { + c = stream.read(); + } + } +} \ No newline at end of file diff --git a/src/java/org/apache/poi/util/LittleEndian.java b/src/java/org/apache/poi/util/LittleEndian.java index 0f525c156d..4c4b51a011 100644 --- a/src/java/org/apache/poi/util/LittleEndian.java +++ b/src/java/org/apache/poi/util/LittleEndian.java @@ -475,23 +475,10 @@ public class LittleEndian return getLong(readFromStream(stream, LONG_SIZE)); } - - private final static byte[] _short_buffer = new byte[SHORT_SIZE]; - private final static byte[] _int_buffer = new byte[INT_SIZE]; - private final static byte[] _long_buffer = new byte[LONG_SIZE]; - - /** * Read the appropriate number of bytes from the stream and return them to * the caller.
* - * It should be noted that, in an attempt to improve system performance and - * to prevent a transient explosion of discarded byte arrays to be garbage - * collected, static byte arrays are employed for the standard cases of - * reading a short, an int, or a long.
- * - * THIS INTRODUCES A RISK FOR THREADED OPERATIONS!
- *
* However, for the purposes of the POI project, this risk is deemed
* negligible. It is, however, so noted.
*
@@ -510,23 +497,8 @@ public class LittleEndian
public static byte[] readFromStream(final InputStream stream,
final int size)
throws IOException, BufferUnderrunException {
- byte[] buffer = null;
-
- switch (size) {
-
- case SHORT_SIZE:
- buffer = _short_buffer;
- break;
- case INT_SIZE:
- buffer = _int_buffer;
- break;
- case LONG_SIZE:
- buffer = _long_buffer;
- break;
- default:
- buffer = new byte[size];
- break;
- }
+ byte[] buffer = new byte[size];
+
int count = stream.read(buffer);
if (count == -1) {
diff --git a/src/java/org/apache/poi/util/StringUtil.java b/src/java/org/apache/poi/util/StringUtil.java
index 845854e599..a2ef1247da 100644
--- a/src/java/org/apache/poi/util/StringUtil.java
+++ b/src/java/org/apache/poi/util/StringUtil.java
@@ -1 +1,363 @@
-/* * ==================================================================== * The Apache Software License, Version 1.1 * * Copyright (c) 2003 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, * if any, must include the following acknowledgment: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowledgment may appear in the software itself, * if and wherever such third-party acknowledgments normally appear. * * 4. The names "Apache" and "Apache Software Foundation" and * "Apache POI" must not be used to endorse or promote products * derived from this software without prior written permission. For * written permission, please contact apache@apache.org. * * 5. Products derived from this software may not be called "Apache", * "Apache POI", nor may "Apache" appear in their name, without * prior written permission of the Apache Software Foundation. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see *
Runs the test cases stand-alone.
*/ - public static void main(String[] args) throws Throwable + public static void main(final String[] args) throws Throwable { - System.setProperty("HPSF.testdata.path", - "./src/testcases/org/apache/poi/hpsf/data"); - junit.textui.TestRunner.run(TestBasic.class); + System.setProperty("HPSF.testdata.path", + "./src/testcases/org/apache/poi/hpsf/data"); + junit.textui.TestRunner.run(TestBasic.class); } } diff --git a/src/testcases/org/apache/poi/hpsf/basic/TestEmptyProperties.java b/src/testcases/org/apache/poi/hpsf/basic/TestEmptyProperties.java new file mode 100644 index 0000000000..fbe9fbf7f9 --- /dev/null +++ b/src/testcases/org/apache/poi/hpsf/basic/TestEmptyProperties.java @@ -0,0 +1,189 @@ +package org.apache.poi.hpsf.basic; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; + +import junit.framework.Assert; +import junit.framework.TestCase; + +import org.apache.poi.hpsf.HPSFException; +import org.apache.poi.hpsf.MarkUnsupportedException; +import org.apache.poi.hpsf.NoPropertySetStreamException; +import org.apache.poi.hpsf.PropertySet; +import org.apache.poi.hpsf.PropertySetFactory; +import org.apache.poi.hpsf.SummaryInformation; +import org.apache.poi.hpsf.UnexpectedPropertySetTypeException; + +/** + *Test case for OLE2 files with empty properties. An empty property's type + * is {@link Variant.VT_EMPTY}.
+ * + * @author Rainer Klute <klute@rainer-klute.de> + * @since 2003-07-25 + * @version $Id$ + */ +public class TestEmptyProperties extends TestCase +{ + + /** + *This test file's summary information stream contains some empty + * properties.
+ */ + static final String POI_FS = "TestCorel.shw"; + + static final String[] POI_FILES = new String[] + { + "PerfectOffice_MAIN", + "\005SummaryInformation", + "Main" + }; + + POIFile[] poiFiles; + + + + /** + *Constructor
+ * + * @param name The name of the test case + */ + public TestEmptyProperties(final String name) + { + super(name); + } + + + + /** + *Read a the test file from the "data" directory.
+ * + * @exception FileNotFoundException if the file containing the test data + * does not exist + * @exception IOException if an I/O exception occurs + */ + public void setUp() throws FileNotFoundException, IOException + { + final File dataDir = + new File(System.getProperty("HPSF.testdata.path")); + final File data = new File(dataDir, POI_FS); + + poiFiles = Util.readPOIFiles(data); + } + + + + /** + *Checks the names of the files in the POI filesystem. They + * are expected to be in a certain order.
+ * + * @exception IOException if an I/O exception occurs + */ + public void testReadFiles() throws IOException + { + String[] expected = POI_FILES; + for (int i = 0; i < expected.length; i++) + Assert.assertEquals(poiFiles[i].getName(), expected[i]); + } + + + + /** + *Tests whether property sets can be created from the POI + * files in the POI file system. This test case expects the first + * file to be a {@link SummaryInformation}, the second file to be + * a {@link DocumentSummaryInformation} and the rest to be no + * property sets. In the latter cases a {@link + * NoPropertySetStreamException} will be thrown when trying to + * create a {@link PropertySet}.
+ * + * @exception IOException if an I/O exception occurs + */ + public void testCreatePropertySets() throws IOException + { + Class[] expected = new Class[] + { + NoPropertySetStreamException.class, + SummaryInformation.class, + NoPropertySetStreamException.class + }; + for (int i = 0; i < expected.length; i++) + { + InputStream in = new ByteArrayInputStream(poiFiles[i].getBytes()); + Object o; + try + { + o = PropertySetFactory.create(in); + } + catch (NoPropertySetStreamException ex) + { + o = ex; + } + catch (UnexpectedPropertySetTypeException ex) + { + o = ex; + } + catch (MarkUnsupportedException ex) + { + o = ex; + } + in.close(); + Assert.assertEquals(o.getClass(), expected[i]); + } + } + + + + /** + *Tests the {@link PropertySet} methods. The test file has two + * property sets: the first one is a {@link SummaryInformation}, + * the second one is a {@link DocumentSummaryInformation}.
+ * + * @exception IOException if an I/O exception occurs + * @exception HPSFException if an HPSF operation fails + */ + public void testPropertySetMethods() throws IOException, HPSFException + { + byte[] b = poiFiles[1].getBytes(); + PropertySet ps = + PropertySetFactory.create(new ByteArrayInputStream(b)); + SummaryInformation s = (SummaryInformation) ps; + assertNull(s.getTitle()); + assertNull(s.getSubject()); + assertNotNull(s.getAuthor()); + assertNull(s.getKeywords()); + assertNull(s.getComments()); + assertNotNull(s.getTemplate()); + assertNotNull(s.getLastAuthor()); + assertNotNull(s.getRevNumber()); + assertNull(s.getEditTime()); + assertNull(s.getLastPrinted()); + assertNull(s.getCreateDateTime()); + assertNull(s.getLastSaveDateTime()); + assertEquals(s.getPageCount(), 0); + assertEquals(s.getWordCount(), 0); + assertEquals(s.getCharCount(), 0); + assertNull(s.getThumbnail()); + assertNull(s.getApplicationName()); + } + + + + /** + *Runs the test cases stand-alone.
+ * + * @param args the command-line arguments (unused) + * + * @exception Throwable if any exception or error occurs + */ + public static void main(final String[] args) throws Throwable + { + System.setProperty("HPSF.testdata.path", + "./src/testcases/org/apache/poi/hpsf/data"); + junit.textui.TestRunner.run(TestBasic.class); + } + +} diff --git a/src/testcases/org/apache/poi/hpsf/basic/TestUnicode.java b/src/testcases/org/apache/poi/hpsf/basic/TestUnicode.java index b2c7b2ab50..cf3b351cc6 100644 --- a/src/testcases/org/apache/poi/hpsf/basic/TestUnicode.java +++ b/src/testcases/org/apache/poi/hpsf/basic/TestUnicode.java @@ -54,10 +54,18 @@ package org.apache.poi.hpsf.basic; -import java.io.*; -import java.util.*; -import junit.framework.*; -import org.apache.poi.hpsf.*; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; + +import junit.framework.Assert; +import junit.framework.TestCase; + +import org.apache.poi.hpsf.HPSFException; +import org.apache.poi.hpsf.PropertySet; +import org.apache.poi.hpsf.PropertySetFactory; +import org.apache.poi.hpsf.Section; @@ -72,17 +80,22 @@ import org.apache.poi.hpsf.*; public class TestUnicode extends TestCase { - final static String POI_FS = "TestUnicode.xls"; - final static String[] POI_FILES = new String[] - { - "\005DocumentSummaryInformation", - }; + static final String POI_FS = "TestUnicode.xls"; + static final String[] POI_FILES = new String[] + { + "\005DocumentSummaryInformation", + }; File data; POIFile[] poiFiles; - public TestUnicode(String name) + /** + *Constructor
+ * + * @param name the test case's name + */ + public TestUnicode(final String name) { super(name); } @@ -92,11 +105,11 @@ public class TestUnicode extends TestCase /** *Read a the test file from the "data" directory.
*/ - public void setUp() throws FileNotFoundException, IOException + protected void setUp() throws FileNotFoundException, IOException { - final File dataDir = - new File(System.getProperty("HPSF.testdata.path")); - data = new File(dataDir, POI_FS); + final File dataDir = + new File(System.getProperty("HPSF.testdata.path")); + data = new File(dataDir, POI_FS); } @@ -108,23 +121,23 @@ public class TestUnicode extends TestCase */ public void testPropertySetMethods() throws IOException, HPSFException { - POIFile poiFile = Util.readPOIFiles(data, POI_FILES)[0]; - byte[] b = poiFile.getBytes(); - PropertySet ps = - PropertySetFactory.create(new ByteArrayInputStream(b)); - Assert.assertTrue(ps.isDocumentSummaryInformation()); - Assert.assertEquals(ps.getSectionCount(), 2); - Section s = (Section) ps.getSections().get(1); - Assert.assertEquals(s.getProperty(1), - new Integer(1200)); - Assert.assertEquals(s.getProperty(2), - new Long(4198897018l)); - Assert.assertEquals(s.getProperty(3), - "MCon_Info zu Office bei Schreiner"); - Assert.assertEquals(s.getProperty(4), - "petrovitsch@schreiner-online.de"); - Assert.assertEquals(s.getProperty(5), - "Petrovitsch, Wilhelm"); + POIFile poiFile = Util.readPOIFiles(data, POI_FILES)[0]; + byte[] b = poiFile.getBytes(); + PropertySet ps = + PropertySetFactory.create(new ByteArrayInputStream(b)); + Assert.assertTrue(ps.isDocumentSummaryInformation()); + Assert.assertEquals(ps.getSectionCount(), 2); + Section s = (Section) ps.getSections().get(1); + Assert.assertEquals(s.getProperty(1), + new Integer(1200)); + Assert.assertEquals(s.getProperty(2), + new Long(4198897018L)); + Assert.assertEquals(s.getProperty(3), + "MCon_Info zu Office bei Schreiner"); + Assert.assertEquals(s.getProperty(4), + "petrovitsch@schreiner-online.de"); + Assert.assertEquals(s.getProperty(5), + "Petrovitsch, Wilhelm"); } @@ -132,10 +145,10 @@ public class TestUnicode extends TestCase /** *Runs the test cases stand-alone.
*/ - public static void main(String[] args) + public static void main(final String[] args) { - System.setProperty("HPSF.testdata.path", - "./src/testcases/org/apache/poi/hpsf/data"); + System.setProperty("HPSF.testdata.path", + "./src/testcases/org/apache/poi/hpsf/data"); junit.textui.TestRunner.run(TestUnicode.class); } diff --git a/src/testcases/org/apache/poi/hpsf/basic/Util.java b/src/testcases/org/apache/poi/hpsf/basic/Util.java index 87aaf216a7..7afd37194f 100644 --- a/src/testcases/org/apache/poi/hpsf/basic/Util.java +++ b/src/testcases/org/apache/poi/hpsf/basic/Util.java @@ -54,9 +54,24 @@ package org.apache.poi.hpsf.basic; -import java.io.*; -import java.util.*; -import org.apache.poi.poifs.eventfilesystem.*; +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Properties; + +import org.apache.poi.poifs.eventfilesystem.POIFSReader; +import org.apache.poi.poifs.eventfilesystem.POIFSReaderEvent; +import org.apache.poi.poifs.eventfilesystem.POIFSReaderListener; @@ -73,6 +88,9 @@ public class Util /** *Reads bytes from an input stream and writes them to an * output stream until end of file is encountered.
+ * + * @param in the input stream to read from + * @param out the output stream to write to */ public static void copy(final InputStream in, final OutputStream out) throws IOException @@ -88,8 +106,8 @@ public class Util read = in.read(b, 0, BUF_SIZE); if (read > 0) out.write(b, 0, read); - else - eof = true; + else + eof = true; } catch (EOFException ex) { @@ -106,16 +124,16 @@ public class Util * into memory and thus does not cope well with large POI * filessystems. * - * @param file The name of the POI filesystem as seen by the + * @param poiFs The name of the POI filesystem as seen by the * operating system. (This is the "filename".) * * @return The POI files. The elements are ordered in the same way * as the files in the POI filesystem. */ public static POIFile[] readPOIFiles(final File poiFs) - throws FileNotFoundException, IOException + throws FileNotFoundException, IOException { - return readPOIFiles(poiFs, null); + return readPOIFiles(poiFs, null); } @@ -126,7 +144,7 @@ public class Util * files into memory and thus does not cope well with large POI * filessystems. * - * @param file The name of the POI filesystem as seen by the + * @param poiFs The name of the POI filesystem as seen by the * operating system. (This is the "filename".) * * @param poiFiles The names of the POI files to be read. @@ -135,50 +153,49 @@ public class Util * as the files in the POI filesystem. */ public static POIFile[] readPOIFiles(final File poiFs, - final String[] poiFiles) - throws FileNotFoundException, IOException + final String[] poiFiles) + throws FileNotFoundException, IOException { - final List files = new ArrayList(); - POIFSReader r = new POIFSReader(); - POIFSReaderListener pfl = new POIFSReaderListener() - { - public void processPOIFSReaderEvent(POIFSReaderEvent event) - { - try - { - POIFile f = new POIFile(); - f.setName(event.getName()); - f.setPath(event.getPath()); - InputStream in = event.getStream(); - ByteArrayOutputStream out = - new ByteArrayOutputStream(); - Util.copy(in, out); - out.close(); - f.setBytes(out.toByteArray()); - files.add(f); - } - catch (IOException ex) - { - ex.printStackTrace(); - throw new RuntimeException(ex.getMessage()); - } - } - }; - if (poiFiles == null) - /* Register the listener for all POI files. */ - r.registerListener(pfl); - else - /* Register the listener for the specified POI files - * only. */ - for (int i = 0; i < poiFiles.length; i++) - r.registerListener(pfl, poiFiles[i]); - - /* Read the POI filesystem. */ - r.read(new FileInputStream(poiFs)); - POIFile[] result = new POIFile[files.size()]; - for (int i = 0; i < result.length; i++) - result[i] = (POIFile) files.get(i); - return result; + final List files = new ArrayList(); + POIFSReader r = new POIFSReader(); + POIFSReaderListener pfl = new POIFSReaderListener() + { + public void processPOIFSReaderEvent(final POIFSReaderEvent event) + { + try + { + POIFile f = new POIFile(); + f.setName(event.getName()); + f.setPath(event.getPath()); + InputStream in = event.getStream(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Util.copy(in, out); + out.close(); + f.setBytes(out.toByteArray()); + files.add(f); + } + catch (IOException ex) + { + ex.printStackTrace(); + throw new RuntimeException(ex.getMessage()); + } + } + }; + if (poiFiles == null) + /* Register the listener for all POI files. */ + r.registerListener(pfl); + else + /* Register the listener for the specified POI files + * only. */ + for (int i = 0; i < poiFiles.length; i++) + r.registerListener(pfl, poiFiles[i]); + + /* Read the POI filesystem. */ + r.read(new FileInputStream(poiFs)); + POIFile[] result = new POIFile[files.size()]; + for (int i = 0; i < result.length; i++) + result[i] = (POIFile) files.get(i); + return result; } @@ -188,19 +205,19 @@ public class Util */ public static void printSystemProperties() { - Properties p = System.getProperties(); - List names = new LinkedList(); - for (Iterator i = p.keySet().iterator(); i.hasNext();) - names.add(i.next()); - Collections.sort(names); - for (Iterator i = names.iterator(); i.hasNext();) + final Properties p = System.getProperties(); + final List names = new LinkedList(); + for (Iterator i = p.keySet().iterator(); i.hasNext();) + names.add(i.next()); + Collections.sort(names); + for (final Iterator i = names.iterator(); i.hasNext();) { - String name = (String) i.next(); - String value = (String) p.get(name); - System.out.println(name + ": " + value); - } - System.out.println("Current directory: " + - System.getProperty("user.dir")); + String name = (String) i.next(); + String value = (String) p.get(name); + System.out.println(name + ": " + value); + } + System.out.println("Current directory: " + + System.getProperty("user.dir")); } } diff --git a/src/testcases/org/apache/poi/hpsf/data/TestCorel.shw b/src/testcases/org/apache/poi/hpsf/data/TestCorel.shw new file mode 100755 index 0000000000000000000000000000000000000000..e0af1945e89de1496ce534f58017395113c8b819 GIT binary patch literal 76800 zcmeHw3w#vS_5a_6Aucks}yXMOa; z^LvT>;ndDsYx?yVv+}1W#3gS&vEcioca*N#@Z)_qrXDu``9=J?{*B)3Bl9qmLDBhHRo@io;u)-zYoh %5Yat3R5$piPJIo%j23_~*|)^RMroeeklQ1LDxkyZ2ks`p`X}B#v1q&Y4yH zj0n9}kT|9I)PzxM&W$c^AJ}zUUFAE=n=VV-E 0(9a}xMvf_ 1G+|3`1IHOngf->>yi49l0HnZ2{N$*X0adF9n>#V_NeK)XG z?n4bHEC~#-ZaDn-^h0+&lykz5*%x(qd()-~$!qtUQqywXm#d3fSL_*d?bv4?f8vE; z_OWN3a_KQQ4SM;O$G?B&hcAzRXyo)i-F$t`%bTvgWz}(KUp1(A`}ApxyLT+vUNia1 zzQb4a{vxj|^G`Q-{<`Y*C2Ja98ui4XJLYy7+_+%Yh@+>8Yr35K!?w(Gmlo{!YEJzN zUshbbtT_9a3#$7jumAk|hQ3n|IPsZIXWmkF^wp6Sx9r%m>8>mKr2qYfeP=(rX2igE zCVbvu*7VUoJTqZ-*Zo_yz5U7A=T{X>xOc;aw@=-D??0
Tx-5DdUsjffmgxR+pvbk?v)~Lt#{h`$tUC#WX`s;?b&wBr{fBoa3VOzd`{`H-u zm#wLOYu3G)Pv^e#dCK*j8h2K0y?6Aj;T9d ?AuSR2QIs?&Grv=_D=uq znT^lAee3<>797)J )*b6+pNgMi+*UaVbQFIa*rDJ;--I9fAhic*H7$za$rpO>$?x%wzb`(=Y2o> z*g>bxT|9f*Pj_9iqT (%*}o;7pz#jo7C;+-Rgy}9Y;r(P=m({mRdy>9;l zk9+gOZyRqa`DEPFUFIE_F=9un3B9Tw4L*HC;Z=7ZfAOo0m9NZsebl(}Pd^*@r-jpB zKkAW-Rt(v(|E4dyFF5Rtv5BGdUBs+T6ItZ}u}ob0Hz7{v@EIIl&K>tG^gSU~uzxoD zkAQyRNhpD@!CzWX-?mz^AK`v}TA&cTY#G7 s&Hg#; zpUeJv?4Qs61?;b6e-- #727Jjo3sn)?*z1cn;xgew6$tIA#m`PqP0M`%edk;Y#5dghS$4j`=(L z&pk@Hwg!ko?g_NS`if+e93c`Co__jikw`)-2p?BU2_s8EDA`Y8-YFk>l*-aHj!9=f zgJW87xF!3o5&$4FIoz84aS62E7UA$|@KL`BL|RWzxB`2I&fxgv3A?^e7H4w!tb}g? zu!6$YBMAZ2Z v%i`BC)nS@{*&xKm5|jS#M24Sw`nV$Nyyr{ zW6!e*;TdO(zbA-@5YNHiv*&rFT)gLnglr(cm|ztM@lrzJCLvyCgjW(W_Y>mPgqqbT z!-Vx?_Uw6$<6r0aH#mMzLI+F}!9*(VoWvXZ2ShF^7TXUJ>1%xA(uV^gpJPsi-%6ap zG0QpTOb(yL;pw917j#j*l8;t#rTm%07jpQKM0Ad2VbSdi+SrCmh7c|VLfDxRqB8qY zbhn~V;VZ~`@&1UuxUjNH>a6PHEbLOTunI!K{F2gn*3{~%`gu4PU!h{FjoeXWl^_XS zwb`30Fz-Utmtzb#sL#W5rp(NrAOiiwc>F5g5)<$Qo=W^#c%WD{Kapo1!n5#no(eqr zYaX7-Q;r`oSlC=qfxSWHctF$u99g2T^-KK?!ehap&O}xxj~@tBixbhqfxJpp;(1^e zo{dGa`B0_-zJ)8*>2|0ZPb;(Vd?>%1OtNsXR|+&~fCXb6Q!F;wf@~g8Ej)pX9+T7n zpHj=b J+l-6<8wY2jnD>iCyY*cn^ z)R5Syp|MfJVxxw~MvaJ#8W|gP5H_M|+7Hf(6E`?cTy~teA#vh{#)%siCvJG0xDj#U zM#hPYV@GxzJF?^0ksZg5>^OF0$FU B#s?J;@B}HjvYhd z*fAuI9Ycm;O$e)I4drzK%%vEkr&KH`uS4YIy1It)G6bfS)YO%iVL51W6`FY|Z-2xd zOsv3KIdidKjw_*AIF<*PQgDk!?tJZ6SW;VGfz7r&jaaxj)=*bJzr3oxe-AuavbLmV zUPURSn0{)@>kwU1j}4kM_b{v_^^%cT4lYN+`6U%qI3s2v)&$FCg|w}+uD-mgbg@-m zZILBz4y 5J7lN;dz>ay}{6D(lgO$CocCudJ>i9WC6t!6rs1M3dr_lDc~9 z1ZfD*lu)R|nt&`kPonGFM&?ylHq5WWx@7K@X?cfQc~f#GPqn5@&dHrTW%7(8t)l$G zX+<-#aPE27N?KkAODso4>>n+twr!u#Q0Wv~9`^87)T16|@G`X%$aVrlF|+&4w))9s z(Egp+{_@%i6i+$rJ}tNU;cN)>$4Z@C9k-+9l6!x X$cRYX8o-A=VY^r5z|Kb{ZdFUdoNaNmDqre_JGJk0fm=30sP`;mC8R_RoZ* z4dhr5mRu>1_FkhE70UZXScIKkT3%IBTTyLI9*-qnbyQF?zZ~rp7V VT6E&!3Lk zojiT|%>3~POvx#no u)TG8Z**lp_^ z7UfUPnOuN8hmXg+H6>?ami!tlzp~}m5cxG!ehrgf!{yfq`88609YiYTOdOnLe-F05 zv+eI8_V-Zxdzk$_-2NV6e~+}kZM &JX6Z#B?pCHoCV#+g<1v>zNzC%>+~Vm|s~83q8G zRLdJLy`78@5zThGnwAXx)VY p-hu=G5H$BI;z^>ZV({ z`7>ta=NGs=EvI0-#d)Y;rlZeV_)MOWKh+waKRvH#av}B70oe46J=xB%Y~Mk(FAMBz z3ePZ;h90u7Ka0Fx@T5sanh`RM+$BH2B3QbE<+(`7_gthXqq+N2{saB_-D?sa0-cMS z*nAkX3Q^POEBe5V&WCAA{zAk)4NX0A&`0O?Uky!}T#%nrWTBT>(`Qbdnp1S7m3yRH zv|Qw<==jOx6oIjhlVM<0CzdS)4NnewQ%mX_YH_d7ZeJM7rcR)lmG!H$DtKmb27f49 zLE{}Z0QARQDmx8|!Y=xLQ;VkcAA_IMG|Zm^yCATW5VOt0mJuFK?djPby`>ucau&=j zs7C$tpDDxuYbN>trgQ#*1OITq{!t0d&I?K^E6QjVs>J>nCmw0-t^o8JXjx0?>{*;r z*P*D~@qF)HXEYDr {TSK)B>bkoA{X}3q!@JjH^7`2| z{NFvvej+fz)|j5cTLX>DJ(tN7pg&z?(?hiBs@41Fl$`$?laB>LHx|0vWKuSG45E_k z_{b#p1(3R_C5tNNH_W%(UQ$%OunxVdpr}8P=yy@o4QPGXqEwFQS++nL8*sow1Os74 zar$%*#7pwa)U}-2twNFWMjeumZ>Z&22HMF^ry8wg_x?KEfRLx#>(qiJqJBC3&tM @#xpH_S7@Y}Rn>L}%y&}`;Nf$FjYEc9v+ddL (Grr~ z)l?d{OLLMzq)aXiIPg8i2_^{6hOvu&I#*UFPjp-@Ma!{A0+uo-9&{wK=k>SLOsKL| z@U@F$2@!bCcYxJURl-*xm|baLE1~O`1FYkSZF`Z+%UHp@{)f?`!vtP*h`&~q&pm|( z0_s6>^<*-ZeM))hfd&m)zj7Nswa$Jb2R^N2<=WqQ_V;*v9|)KKZ!P%blae`{ }hsK{J fbUFD80w%oTRjK#Bpz5duX-WdZk5v= z4zxJpi?kR~Q(ZyC5&Bw#@km&n0mk>Ii7KD{3G7c~e-itX**}E+L)o9wUtWk$$tmDv zj8UW9v3Ujtyn2i_bh{(=7Kbx5>P$0s#QOQ6auD7Lqefx#Y2(pIKU0c#GzeU5pgXF@ zT2p{2FArK+JlP#+PYBH&QwL$U=FeY=#4PF%5zCv s6Zw36hs2aB+ zS$v1BM9hJVZcx?p?XIzQ^;xKEN-e+ToxA2%Wpm1E5ba6SZ$^=trKm9XUYKmEWI`Pb ziLWwMyrfoi3#vIMr!<3bA5XOz;wE(U lV8A`Ja^FAUx^{iptw-|GuInP=Rja(Y?xe_t2cXjsJ;idn@ZfC`hO*IrPt!B$-4^ znp&vSO1(-KfmK(*fCiMIg?q@djx4;+iUt~D&-=sud+d2{ihJV+;e|*rLf(D+IU{oT zRy7SgbGe<4hbeVv%cQovuk5{CCB^A7fM~zu@ZJ-f!ZZlyl3KDy(nLX5Ahq1yWcmEu zi3j7PvTu7^ ?u57zV&x*vmS;<$3G0^mBkgl-PeWLi?S7 Xg z_22IR`YPP7r?bkS7EftTb5=9#g n8@@BA3DFlO-h=OF@O@{Y5Kq$ml*5FWhob3M zgm(|(R?0Q_z7O9C)A4I1m@x+5MfhHd?+fw$AiiJ3_mB8)gEjW`GqAS}-^qvL9g3*U zC-MCbzRQllyZulwskrp$jvFI4;QJwbcbbiNB(}wyB5;A%fTjHda5b5W-?a@XMX7hj z)6M1zQH#w@FCQ;=%N10jT42|W_`V$9IJNy=xJ=)B;nEYZ5{OR#?hv?ZYVbb8lkv{R zQ-ydRt_5$L%Ya{oU0k%)jeeYlazR=Cc+(dzPMuRY4qGh|KY-)+XMY*TM5rs N~;en`8AY zL{sitkk)s+)py3}41WguQw42AoQ=N;;3+v6aVB6>({zkJMIZ#^&&J48h@UlP;YVm$ zc*WyDq$ F3S8aqP%xYvnx&?lJu! z`ocChC^`R3`k)(Be)QQu@6;P`mNYy$pBkk68MH`{4@jX#FMmrEN|28vNk%i}PoNd9 zc`|`aaw-da>^StOm^f %l+CN3pT9O %z&^>p-0U_B0_ zXG|RZ%w11M1En4ZiV+isQ8*^9dz?56aY0YJ`Ux$RTK_E=B=PcBK1#qVx|XS R!*<)eaW6r#>te4;w?r< zz}au(m*B=;xp}iY6P6+PB0@WW1(JC0-)qJ-%}A!ht p+`7u7i~*6y|jP%r{f+*tt4@tcYsFcqLD-9OXhs^>@&(o^}k*0 z|IH Gt@k+91W(~MW z;-xSiA<)l)w~HOfDUS#boQCzI5liBQ7>}NPMtJlK>g{656a7bB__V~MM+iv$=$Gf) z#f?SnatJTP`hi<99*uSqj~*_zO+@hQGs2@^zHb-xceh9JO1xC1A3Z2yyV!}GgqO;A zG@euW(MT!rsHbh?XP*%swf^nmyhA<-4sE@ITXmY!j~>17G506JqZ(O<3ss3nqpZZE zhl_0!7M^`Zc -j;w6KD3{nMG#^N CJJd++SK^8k7jE8&n7d$rQ b6v5_xd5 zBZKl_ya-Z7w1J^_h|7RMYS5%9)u3mIp*r!XGg5 pxk4;(`u#PiSyuLI-l&ii5Kr%{R5LE+I;K?pCG@#uBucknUkM@&NE z(F4b}33?tH;dNxZhu&}3^~nz CAcPxn^{p z&Yb7L`inXR50`m5t31SVWFC4r7 Stbe^u9CvtCi*?UjOJY7{DVo@>=J;)0! z49^*(Jl!}?V(K~FOFxr&x~V+GvSc25SQpAI@jcGdo%5V|)JqS(-|llR>+UL#rSe#u zr#MCCu{clY)-Fetjh1;V&a)LuWqr{(DicQH+Hyc(D4zRK==-Cq&_cux5NF}}wxev> zP+W(4|LDr12hQzffE3p=PF$}zalPZj^@$VLm*Z###0OU|)V}DvvYdLz`bd*D_h6gn zAAa(>d*(=+d#G|Ey<{G`zC$~1Df9H?JiBh#eCUX?WuBfY59uxQ^pf?_M&{|od1e&t z*Le1NnWvY^Lw3nLy=8rLka>D@p37%meamgH$vnMP9 %};_CMYQYp+&+|DN6l^Vhsll8?Xxe`Ho?g#hjGFW{*p6Gttue9)l7t6}$ zocwpzk;+gof2@zpNus3DRFev(}OBNtlF`3GO;lt4VxqMC(ixKRMMsrmNKp)7;}E zJl4l6?~7L58|irk4D*iDMrBK-kwV72ixdJQ$yBvgw4djip3>2^v>q3X6GuHxPe {tXtdGeh=1#G#I*G|qP= z^ `;#*hNs8TIHboB3)AC>W$AImqVzaoNqQWyAU%#)jvhxWMvt?!IGV+& z{k_L?2KA6>B7$R@C8)O)VC~yEO2pC8>L`e#W7JU+j*eAF$v7%jM=3ZeQAZ&h%~40G zI4V^~X*eoVM|g__R?D5UXW-~Kb<_e!bJbBx9L-Zlt#DMKjxup{ygF))qZ8Co8yr=t zqqaDjua4T`h*tMyNwmjNwL0p6qZ)P85l1JgqfR)gRY&{as7@Vq#! Zl8j z7O11HI9jNVy5VS%I_i$2#p=ky(Gqpk14k#Rqn 6= LcwL@#&^ex&`E%#G-UHT3zt`+c~k6Ts{~5}&~yDhV2K zrTy;e<#rKX{x&Rcx@6b?LHiTg{++h{G`mRqgIxdg4hPiylDFWmAD@_mQds^8mBqO2 zzx%R~^&j-rKh31les_Hp))tes8} _kkg`7{$A7HD!`j=PBLC5^;9J|N6vV=3$$uzH{-In7E6$_vie=>E^V|1j z{fB(@Ps~@=zkA;R{eSWfSQS~z +zl9&UoVoBnc>YVJwh(!g#sU$UOTM&! _=_&2u!xW!DIh;u zOY!)?FJ4M9O(&8sWB%v9w7;dV{%Hk5+V9?BgymOqpYKGiJ-p>b@&^zL5a+SY&(}l| zj-Ep?lTIUF3?*OMU%ZfGW|03Iw7-?F{%N&E+V9>GMV Zhq+uLJR^$ZqlmA)gW{W*BZPTec%D=K4_kq(|4d)~)5?;x-+N~_ z@)GgC#zVe_A6@q<{K#6K`R3k>uxN8T`C0YkXR=lEE~2o-Z9cLvif{qjZ~gY{Z|$pp zT1AugyZ5W1{k5xMPb8P-yl@#^3x>!41HQQ8F7k_M=8v@8Na66& %FfQo-Sq z$X~&|qxd)qi^1e)@`%t>%rWKc^LPO{rTH~|S^sT(^-n8?(th_wIrPY*SuOGb&vx1G zkd_;7CBNtf@}pEbVzAf#DHPKr=e)xy{5+3~(NigGm69)QllJebbz@}wf5DgbxAoOO zt&U3j-MjP9HMj}-MKZr3|7c=?k-^me!@1NJ#IEJ!kKs1IitYT4#{zLM#hWeJuwpN1 zX6z;YmunHy{&v3lr 0rGD=n|zC7c5w|q ze?G YwgrNc-JAC$Q%)cfhKqkzc~kTKrG=#Rn53j&y#S{OhPK zh_3gL|JKXoH G8{Yp9l?1$>AiV>k+7_PdTob${YQ6h zr2X#x8CX92BWM%-ky?Sc@>}?gX`fQ~nH%AUYhR#nd$K?J3EOY$$M(z7Q4FCpGTv*s z9rM4le;;4{(_JcQzq=O*dS~)Hw|NI_*!A2VvZVfF^3(Zhubx<7r19?*bK^?(dHfF_ zLt$y5V9wWgJjF!h=nzx4VU|XVJh@`h*l52*ysZDuzWS#-a?*ZxUlVL=^(u5~97vWo zJwcX-JAVQ{GK6@sSa2Qr&(gRc`Yj_r7`yUev|m~-ZTm0VzptF%Sn-`%4Hdy3Y= zs&E}$|3x>vM;4CTN&Y=gkiVLGfe2j)Kl0N?3TJs%gxB-DXi3gjM$#2GkuSK-H}RMs zVZO9WUN!#`bxZg^ha>C1i?9CaPOG%v-ERh)2ha$Y)sp&u BNhg8aw=_IY)v ziQD{d%Kq-Y`nQz*mZ1B;w5O3Qr0cfGWmNxGJ;fAH+D`FX{z3k3uKy#r6rSf+UBm7F z>(wNcYr%q90FEd|`ZX;56Zw(DIP8`F`e9lBma2c+pSh+N?a%B?H^2YmKiav|12*bA zF?z;{!|7tu^^OzQCr%vp86mISMdaO|Dcf%ku0ML`EXw!fDEy{wG;@i@=O}zG&E?|j zHRR)U!|-dClE30y@<&RxR!HGEHTPSpzaGB!OJywWclVl-Z8SGoMNROdlir0NK9{ay z#1jw0k6gmD_;(jk%nJ6KsJ)BEYVz?>ep6MH;<@FXMtOZwZ_58dShnAus{PXb1!_;y zes|w2w0w-LZTybfLiiHmGUBHv$-k1vtWoz;c>F5zSM!*?s*b|H3;TQd>Yw_Kw7<6; zzdCUH|1Z=A%Im*;7Fqwjef3Y{gtXtkpV`xf|I1qbdgUVR@1yGfVNr;10DG5yslPtF z*ZUc43c(&VH6QTaW~yuvW?y=~z0ExYZ6pa`8bBz?ySJHs1}FD64<6p!&COeG7vhbV zg!t_BKQ=deO8oyh`1}2l{2!Z}V`&p@-O(vmC{ElTo0~KF1+98n{js_EkIl`r>F57% zo0}Ui;!9bK6u%x-`R^l3a@3s7&HtC}Z_eiC@S88;V&{mnld!o-jD3wReEtu#1zG>i z+T6T}Ze?vhnKpNeo!;9Qzvmty?QhoR<{f+!V;$ezol7^j#Q K3BGkMEs=>gc`0|yZ4};4OK0MDYJYP!H;Xf#f`yIgRP)8F z|4HFVACk>i@Xd|YS5ml%m$*d{g-5L?|2N(iWc@d1b8{q*>O5;VSzb7fM#0DcDiN`r zZ)II@GlipkEBkk6e{(iBi_58>NBaH?e)uD<`2xO`z2a%Id^*pFOZg^dm`kB2FPENi zb@aE~3S|8^XLEC86pi`odHfUnLkH3D7+L-qFA+DEP #v|Wfxy}#ak$) H=a+n$+@8McnZr6kj1}M` -!#WBtV*b~3Hie5T$WP;WQEvB%98NJ!W#r?de7Vs%GEa~9rumh^ zvi_U1xw$xtTK=vV1zrCgPwhFfFWVpCF+ZDo`*+L9h9 t3 yKU7>NbNbon$O{1 z-*P&m^W&!GY;G1cH0DLu{S*HB^e;)v0FUa69;0wpO%(q5cTw2h(0??A#bxZvmM?pL zDaWvdjodn-MQ#joAeYUffBjOB_1~P$&EcNJZNi;?gdaJL>v;riZi No3*+5Eo$x2&AZ{Rdi^{2;rCyKFV++PkMLL^PJ57I zA}5nC!sIu}_RM1;;wZig#YdNNn&a3nX1_Q4F?AlTjUR8L#iakY;${6eXLIxOW#2-N zBihi9JHt=BLpp7EnEZpe{*%{IcsAQ_YaC;LF>`@PMfA63e{(iBM@l!s!lrfG;D@{J zA#3lX79rMO4?oi98S 6+KHygH=G_R3j+9137n9%kXYz;sh5Rhphx<}Ewk-aKF|z)f zv$;9kNcG>il4ic>>7SA%Gnmhu#G^oOw!g4};(N2@{UQ|paW(l(vTgrX?QhoR<}7OG zyAIq1zqsr@_~9F#C)>VmBtMgT{us8r_vI8LxBp4bEA8J^L-F^}coC-+gp1?H$7vms zDe}m_s=nE|;>{GY{+qM8Ieg|zI8$WePWZ(~y-R1knMTCOmE7lxx#q 9_oHfhs_ZG7BU zO?{xfYDzvLI2fQu0#m-;{D@$A4883~{sJrEk4OGyqAp{1 ^J0++}cU;4X)|0`5w +~;t+;J$$S67FlbpYXQAf8zTaxNqUUgZm!t2e^O1{RsCH+-|r% zaG28YPiMjhkA0>!^ger1#8LD zDI*DD&%`K zh@H`!fQh38B&mdeNu_)-FeRYZY*2zedp|i6J13Vx*N8-%ia0V(gco%P92rO@61zBl zQc16Ok~j -qR6=gTQKT|a_(^3VUa{-9%RnlT*tN?~DiiVIB0r9SR3fp9<0qAgc=@g$ z$3QBP*v0Xa%0#RW`f&`T5{X?LKdDT_8k!%+Kq`^g#qpEMM7-wDk7FQ}NbKVHNo68l zNax2fkV+(War~q*5ic3^;}}RK61zBlQkf|Hq!LL@?NE_Q =;}}RK61zBlQkjUC-}-S3q!Ni;96zZ{#M1)&I0jOQ z#4e7XR3_q=<$fFksYGHI$4@E~@d8{wj)7Dnv5Vs;m5F#+t{=xhDv{X5@sr9#yeQX? zV<44C?Be)IWg^yw{Wu0viNr3BpHwE|9h`m~1F1w}7spR36ES)FaSWsqiCr8&sZ7L+ zcl|g9Qi;Saj-ON}3O}htQd2)wq!Rf6>Fw`+QVF>UN0G`z;U|@ec(#w (?CLBd7gUm0zIDS$I zxd}&+${_PgFOHv7LT |IYCFCX?Vv>BP zHpu+ai{mGikehH6sSGl|^y2tQCFCX?MJj_DsSM(#wB$d6KaiVn6sbf$RnC%1I;4_r zUQ1Fb-}p+fkwzkw;F5Bs5&|ZbNs3f9;f1i2z(6XI*eO&QbPFR%kxF`{tN^7mtW+Yg zi{mGiNzgzR2ECM)a15jpiCr8&sZ7#HC6eyFP?6Zh@sr9VZKV=PO*o2FA|D{V<>M!n zkehH6sZ7#HWs*iJAvfVDQi*(;K7LXOxd}&+$|Q|cCTS~`kehH6sYE_aA3v#t+=Qb@ zWs*iJleCpe$W1tkR3e|IkDpXRZo*NdGD#zqN!m&! *<0qAnn{X7VOwveYl13^aH{mE!iF}$qeo_g!2}hC2B#l%iX)BeG zn{X7VL_SR)KdFS=gri7hl13_%w3SN8O*o2FBA=#@pHxC_!cn9$Nh6g>+DawlCLBd7 zkx$ddPbwid;V4p>q>;)bZKV=&6OJO4$fxP!CzX(!a1^Ob(nw{Jwo(bX2}hAi C z?fe8dkni0HBzAEq=*4kJrHH)1I0jOQHCY$OPb!lYsf^x4dqt7dq>mz%$fxO}Rw^&Q zlW`2B5+(2I<0qBL?(J`DG2<9WB@(+h{@dTl8mUB5lU<5bBA;fLpHxC_!cn9$StFIn z8mWZbgri6$@@e|`NhRba97QUVHBy rjMUgLT (? zCLBd7lQmMAtdUB{O*o2FBA=#@pHxC_!cn9$StFIn8mWZbgri6$@@e|`NhRba97QUV zHBy tdYuO?e;h1CLBd7kx$ddzfuXg2}hC2WQ|nPyRW<}gOHnW6sbf$O&|YC zCFCX?MJkguQkkr+R6=gTQKS<2ls=M5IxCea!XcF@id0%R5~&22l%x^@CY33QR7M`9 zJ(U36`^k|QXJm#ggDx3NQKV7~ejhlHn{X7VL_TE%{op4>tyG4$ZDkw-sYGH|A3v!~ zQKT|5ZxiDfNF@@xIDS%@f)cSC%dqu~V<44C?Be)IWr{{BkRzCjQ-;kSd z6sb(nNM(xflS;@ rjP&jH{>Q9MJiJ?Qkf$Bq!Mxyjv|%Fr|ILr{SCPZN0G`DjZ~%x zKdFS=gri6$@@e|`Z+}B>!cn9$MI)6d!cQt8H{mE!iF}$q{@dS>n{X7VOwmYXitv+4 z$W1tkR3e|IkN@^J Epls4Y>(Nk;)W}RHg_&sf65w zqevz4Y5Mp{CFCX?MJiJ?QkkNWO2|z(ic}(>rjMUgLT DkV+(WDn|z0$_Xh_8F}<8;6T22 zBaqm|@srAsB9+#5BBw}d!cn9W`BY&LISnaNDHcA r4mU^b}3Sce41T;QVF>UN0G{qMk+(vN+skb97QUTPt(UwDj_%FC{h{HNM%S{ zsf65wqevz4Y5Mp{CFCX?MJhuYsSIf=m5`fo6sbf$O&>q0gxrLqNM%SPl_71V5^@uc zB9+Le>EkDrkehH6sSIhPGNi3kLT naubdsl_8B(hP0JR$W1tkR3e|I zkDpXRZo*NdGNh5pkhW3@xd}&+O61e@@smo(O*o2FhBQ(c(pD-VH{mE!iF}$qeo_g! z2}hC2kVYy)+DawlCLBd7kx$ddPbwid;V4oW(nw`UyZsHh2}hAi GDpQ3+DpM7yjNDG75?oS}N(h)#(sP33 zN?a4(Jw*u&q!NjtIg<++bV*98ODfmWufHMRyAeq2;`m8rsv?zQ9g$olHQ^{yiG037 zO;w~a^7&(oV<44C?CRqumGnqOSw4~1HZzWaR3fp9<0qA=E~&g?4dWO{B@(+heo~o= z8j|{mXf@*)NF@@xIR4w;sVG?+he;)pn#x(NR3aZBz4h*2sf65wqex|{Mk-Ual}gA> zIEqvvpQew0r4n)zjv|$*8mUaxRw^Mk;V4pxe40M~l}gA>IEqxJYNRq%Td9QHgri6$ z@@e|`S1KVl;V4p>s*%c6ZKV=&6OJO4$fxP!U#W!Lgri7hszxePwUtW9O*o2FBA=#@ zf29&~6OJO4sT!$F)mADYH{mE!iF}$q{*_9|O*o2FrfQ@zRa>cq+=Qb@CGu(d_*W_+ zH{mE!nW~Y>RBfdaaubdsmB^>*<6o(S+=Qb@WvWIhQ?->!$W1tkR3e|IkAI~Saubds zm8lx3Ox0E@AvfVDQi*(;KK_+T$W1tkRHkaAGF4ltgxrLqNG0-V`uJBWAvfVDQkkle z%2aKo5^@ucB9+Le>EmCigxrLqNM)) rjLK65^@ucB9*BcsZ7;Y zDj_%FC{l@hN*_rjo!j4O!XcGuic}WgNu&~7Qj$stm{g`IQW-w`c}`#;l}L;;GO3h7 zmt3SNQfXcKIdCA~yAeq2;`m8rnj)2r5mFdQO*o2FBA+S@TB%G^q|$m>;uuIJ61)2N zNoATMm0~QBTqHH=qevz4Y5J%i>b$v{aSWs~%_WsG=q+bODi64vaSWs~%_WsG=*3Z_ zGCGHG45Tv6U8$5oFOEwp3mL~iD$|%r+U>``QkmwG%B8Cq$3QBP*sU)=sZ7&WDv{Jw zUy4*BpH^S~l}gA>IEqxJX{0huTd9QHgri6$@@e|`S1KVl;SiIw%b9}S`tp;?G!v;z zV =ZQVF>UN0Cb8)AaF^O2|z(id3d)q%ut- zm5`fo6sbf$O&>q0gxrLqNM)KvD$_Jl3AqVJkxJy#^zoBQ$W1tkRHkX9GEF0ukehH6 zsYE_aA3v#t+=Qb@Wtv7R(=<{Exd}&+O61e@@smo(O*o2FrfH-yO(T_%n{X7VL_SR) zKdFS=gri7hnno(qG*St<2}hAi q7%5+64t$QW6vPmTbOe)hAscgDNCNPjnB*qzel954ovPySJ PxLuA|D{V_3kH? zkehJSN@cpXQkkxiO2|z(ic}(>rjLK65^@s`F-f~UQ_yRdpH!xsNM$-RNgKz%Qkkxi zN+dPuqevz4Y5Mp{CFCX?MJm&^mCAJO_BZ4v9JNx3e40M~+ux9za1^Ob*GOf$cKaK0 z6OJO4$fxP!zx@rl2}hC2bd6M|Yq!54H{mE!iF}$q{@dS>n{X7VOxH+dx_0{;aubds zmB^>* Epls4Y>(Nk;-(9RHkdUzacl_C{l@hnm+#9-;kSd6sb(tNM*Wq`x|l-jv|%Fr|ILr z{SCPZN0G{OjZ~& n{X7VOxH+dx+0Ys!XcIPra1eJIu8(M1ecU6 zl@Ktgq?g^v+n~|0v{DJsy`LP3p*c^UGU!gi87`^3iykQixd}&+O62nuMusAl#TRdb zK9HMm6sbf$4JXODy?ey{jAI~`87`@mL9boT?eF5TA2N=CRAvZ8DrL}% NlW`2B5{X?LKdH=sCAL0-K1gb^OOZ -0XD;-p>bR(6pM>-0XDChjKBbBg6 zItrC59aOG#BbBg6ItrC2=lroFm9R%T3Y9AzRIYR*m9R%T3Y945{IMgIutz!yl`9=o zu5=@nutz!yl_=-@u_KkRM>-0XD;-p>bR(6pM>-0XDChjKBbBg6ItrC59aOG#BbBg6 zItrC2=lroFm9R%T3Y9AzRIYR*m9R%T3Y93Q{?JI}N`=Y}oK0n($_|Cf#oGuf!IA=% zFfdfoca-HMym{p#oFRZpWJZk)m9m&mo*fw~-|~Ctz}~nK$eeX7RCXv-F1(Yb0z%eE zN1+nsv@_`TcZZHtE_`2f0;ojh><=xL+f|3kw2Vehku~y1p|T^{k;)Dosa*IBJ>v;` zq@yF1DChi9s9bnH>jY5Qk)cu+%k8pI347#^LS=`8%8m?``~4mJ6F?<0=YFQevOoDn zoi(fzKxIc#sFcOBjzT5FAnZ}Q6e>F$RCXXjWjKXB($U-BDCgQ`p%V5;N1?LAL1l-7 zO4uVEg-Voj{#dAlJ~K)o;h+-sNJpU(<(xkjDq)Xw6e>F$RCYM1ggw$xs6;vE zkA+IuBOQgx4hNMT4k}@fbQCI4&iP}Z681<(p|Zn4Wru@G*drZ ~9`D39H_DDyevco}Thl5JkBOQfGlym-AsDwSzQK;;2P}$+2681<(p%UesKNc!s zk8~6&I~-JYIH-g@(ov{HIp>dsO4uVEg~|>Gl^qT$VUKhaDp5}TA*k$7sO(I2sO(gz z-11q1O0c9rB@7IeoeGud*PrGL0aPM0YGkOC#e6dFRH&T)5KW>Ed*enRbJnp?*_om8 zM|ZPM0F}s`bu3hNDpaPuzsouSR3dZMu~6Bmx4+ZRKfpQxR3dZMu~6BmP?>74Sfu_5 zpc0w0j)lt3eEa*tx3WJ0R3dZMvA4fF^X>1nz&Zg`B6HTUx4%0xR4yFDIssH7bJnp? z*_lTw7tiDV37`_0vkonmw?l=>M1RN{ZHEe#oenBHGgNNAXr6Qes6^)MkA=!klq}$M z>x)?@fJ$V}I`;N=r-MpljoPJ9iE^%gEL6fC=_pioI;iY)Pzigaqfm)*&L0bvutz!y zm7NYMI~`QQ9_c7lqMY-`LM7~xjzVRpgUU_^m9R%T3Y945{IO68d!(aK+3BFN(?KQd zk&Z$o$~k{5RKgzVC{%VjsO)r5345fYP>FKR9}AVRM>-0XoenBH9aO>|=_pj9ob$&* zCG3%oLS?6e%1#HButz!yl_=-@u}}$nq@z&T>7cUHK_%>wjzT5MIe#ov!XD`;RCYS3 z>~v5Gd!(aKiE_>#3ze`(ItrDY4k|kxRKgzVC{&`H^T$FZ?2(Q_Wv7G6P6w5+M>-0X zD5w4qRCX# BYzYsQO^0JBbBcw zh=V=SQK;-n7Am_kRNi&&BIyKBiOjiO7Am_kR4%@b{RyBFnX?WpmgA)(l}B96IssI6 zC51{^EbHj)@3a@|1W?(Pp;8vhIyzFxPzig~&kB`Y%$H>yh04t@V}Amu>|&BEwr6|$ zyDLLwI)QZps6^)6&lW1Xut$}l5?P~mDO94Ii FKR zANx!t?2(Q_WtW4>u4JJS_DDye66Ksf_L)l9BOQgxE(eue$wDRUk&Z$o$~k}RGnKGM zItrCt4l28ng-X~X9feAibN<+8Dq)Xw6e_zMRCXl`m9R%T3Y945{ISne!XD`;RCYP2 z>`E3YVUKhaDpAh)W1p#nJ~c`ql`K@k9_c7lqMY-`K2r&Mq@z&T<)E@FS*V0P z(ov{HIp>dkrV{o@N1?LIL1mYFrV{o@N1+nsoIm!NO4uVEg~~1mm0ig~CG3%oLM6&M zf9x}rutz!ym0b=hyOM=U*drZ wXk8~6&yBt(@DO7f+I#hNmRL zWX?JkD!Uab7cQX}Y>+kbN1+nsOmMpMNaY_t%sK&7B6Ie~LS=V`%5UGwIssH7bJnp? z*`1+s`x{v&fJ$V}Iu ~ FKR9}AVRM>-0X-3}_d-AE jY4V%-J6cl|30MuepPD0;ojhtYe|F zCqw1)Kh8P C^H^UzJqfpu7pt2`J<-*GrNhg3xWX}C# zp|U4K hP>FJ`e{`g>Mo
H|sq8^Z3V#y)!5---RHB^o$3i9Sk&Z%TkAun{2bHi#ItrC2=lroy345fYP}$?4 zvd2Ls?2(Q_CCWK}EL6fC=@3a4$7fnB?_YMLvL}Mdo}^GIi)9@Pm9R(tC{*@1sO)i2 z345fYP>FJ`T^1@~k8~6&dmL2uIH-g@(ov{HIp>dsO4uVEg~}cWl|2qBVUKhaDpAh) zW1$lENJpWv$3bO}gG$&V9feAibN*PUggw$xsO)i2+2f!R_DDye66Ksf7Aj$nbQCIk z98~r=sDwSzQK&>Y=Z}R-*drZ<${q)mJq{{ik8~6&QBM6KsO(Xw?8Q-DZh!YGRBrt` zK_ysHpb`d#%3g)abnRuFA%IF`MvZ*XlEr+m=~bv)IGCpJfxU4fkU8sEsO(j!oImXc ztP?;bGG`qNmAwj;^M`()bpohF=B#6(vNuEJ)i<+F0F}s`bu3i&W~kgu^GhOY)ISQ9 zC S?K&M>jY5Qo1sz`%Q|}dd;XcM6F_Be9;uYYvW`M!dLiosP}!TIQWncP z3YA-4!a4y|_A*Hp+o64?vbO}4XR=NJmB^gqWudY+L* &b7 -0XDChjKPziga zqfpuFpt9FNCG3%oLM6&Me=JnO9_c7l_ByESbx;X=q@z%Ya?T$Mm9R%T3YEPMDtjGN z!XD`;RHB^o$3i9Sk&Z%TuY<~72bHi#ItrC2r~VLB_9|5Nr8-phDO4_gi=YxLDNqRm zLuFrv%0p;s8HhHn9GOugAJk YZ3K-}_9;{@?D0d^37`_0 zvp*Ip`xGkEXK!Sk04k9=>sYAl%TRgT^{f*>B{F9n3zdBtDi;afku~Zcg-Voj{Zog^ zwBfU?6F_BOhDup1$IC(`?2$h@QrYK5D*IA~%A?qy04k9=_m6#1r!Of~Zlx)FkTvp0 zM=DXy^-sS2eG=;gP}!HEQWneoqi=uTMK3bJ9{HnC+2=+o`!ZB6zLIqUs6^)6KNc$c zGE~-{%Q^v6B6HTUP}x_4%7i~;joPJ9iE^%=EmXoD=_pk8IjHQz5vPFD?dNg30;ojh z+%5~1eP~G?mZe<*R3dZMu~6CPpb}Z5b`ePy$3t2yZ$B0)p&RKaRQ4r1QrYLA681<( zp%UesKNc!sk8~6&`y5pEB@30XM>-0XDChjK&s4%5=_pk8IjHPQ7Aj$nbQCI4&iP}X zsf0b!QK;;5P}!F(RKgzVC{&`H^T$3@345fYP}%38vM*Vvggw$xs6;vEkA0>R_DDye zvd=+fpL?bf_DDye66Ksf_L)l9BOQgxJ_nV3$wDRUk&Z$o$~k}RGnKGMItrD24l4VS zg-X~X9feAibN<+8Dq)Xw6e{~1RQ4%U_T#WBBbEIMm0P|`Pzjb4sDy!`vOhy*>*qK_ z0F}rL&-oxKi}@hhpP{n*chG^oaU+m9>sYAl&rtbH`Z5o)Mmh?WC}%pgU!ihg13gm- zd!(aKiE>V-{-VzO!8CFTd!(aK*`F*__GhTv{u yg-VivP5_ntd8ASn%Q|}dyU+=svOhzmES7cj_IIHZKxKc1N?9!H z s`!iG)?Fyh0ne%>Op|amWC9+0w zSExiew;kHs->^qI3YGoILS=vH_V)?gKLJ!Cb8eS~%6 hP>FJ`T^1@~k8~6& z`yEvFJE(*`(ov{HIp>dsO4uVEh01;hmHiGXVUKhaDpAh)W1$lENJpWv-$7-+gG$&V z9feAibN*PUggw$xsO)!8+3%nd_DDye66Ksf7Aj$nbQCK49aQ!^sDwSzQK&>Y=Z}R- z*drZ<%6 ~~Pv zuTVLF Z~71D zz#i!+RHB^ei~)tp#XIS3bl4*ug-VojIy(9JwzsfO0F?uIq*4~k?W#j%n*0>&1W-AU z6e?x0tW%%-Jl*hG)(N0;AVZ}rmUVQbvL@{cpmHFORLWvmN1<{%jiA9E^|M0dfP>0` z43&4ifpr3?MCQExSg0JxBb5x5$Qt>hBb6xU;%=c5_DDyea==05K)(H*=nwWtM@K4A z&iP}Z681<(M=A%HFU#?=Pzigaqa&3A?wQH~H&O|Eq@yF1DCgQ`p%V5;M@K3L+(_ks zgG$&V9feAibN<+oO4uVEh01|sp>hC6!19PE?2(Q_CCWK}@<=5?CG3%oLgj!PsT^=n z345fYP>FKR9}AVRM>-0X0}d(&98|&{=_pj9ob$&*CG3%oLgj#i$^i$Jutz!yl_=-@ zu}}$nq@z$d;GlBAK_%>wjzT5MIe#ov!XD`;R1P?(9B@zxd!(aKiE_>#3ze`(ItrBo z4k`y6RKgzVC{&`H^T$FZ?2(Q_<$#0A0SA?^M>-0XDChjKPzigaqfj~EpmM-LCG3%o zLM6&Me=JnO9_c7l4mhYBP^cWlSx}&IP@%GRA3-HpQlJtBhRQ*O%7q7Mq!OZyD@SH{ z&QK|f`J^|fP+6O%kxJMj9feAiGo3N0P&t1cO$7vdq@z%Ya!#iXm5cBFChG)HIhdhR z7R&9bL*>Hyds!!d%E6>iDT`$th00St$vOd44rZv7#j=h<<)$lGCxFVq43)B2)={XO z|6A4xpmLB&vWUBVQD-ni<-(I#CxA+1&fAZL%E1y;(lc?$8pU0q66IXnEmXoD=_phV zI;b3UPzigaqfm)*&L0bvutz!ym4gl{2lMUkM1QbHItrC2=lroFm9R%T3YCM&Lgipm zs4V;mpc0vL-0eu^po2 ulL^; C{zwQs2ofd zDq)Xw6e>~9`D1T?!yf4 7Aj$nbQCI4&iP}Xsf0b!QK%erP&w$Hsf0b!QK&>Y=a0Sp z4SS@cP&w$Jaxht_ggw$xs6;vEkG=g3d!(aKIq0BrFj=UCJpew2`a&o0+lc@R1PUr-t@ZPaE1UXkr_4e!B`ga!EZ>Ra?8SxpaXm3Mj&(6 zu~0drP`Pl_7g#5NN@UJD7AlAI_IIsa{0X2EnX?WpmfKZ_%JkZgvOfV-4kd+3SuE=) zRQ@wTCG1iE= wjzT5M zIe#ov!XD`mNfzz0P&wqFawvjIWX?JkDu-}+ULNs8*2o_nsYE%~E(?{gM>-0XLk=p3 zut!;c0;ojh+%5~1LvExJStEZGDpAh0%R(jWk&Z&;kb}x0H&O|Eq@z%Ya?T$Mm9R%T z3Y9|+Du>)iCG3%oLM6&Me=JnO9_c7l4mqeCawC -0XDChjKPzigaqfj~IpmHc# zsDwSzQK&>Y=Z_t!ggw$xs2p-oIpjtvVUKhaDpAh)W1$lENJpV^$U)_h8>xgn(ov{H zIp>dsO4uVEg~}lZl|u@Z!>JCH!wQv)+XyPbk^+@5FjNjJRHn=SkuwBPiOlev55}^X zPcp*_m0S1wHFRKa+z4dOIu FJoEXUnKCG3%oLglc7%3(KB345fYP>FKRA04UOOmADm z9_c7l4!e=cVK-6 #eNksKy{H3wq@z$d?4WWuDO4u p9tf=aNYKqU+el_Lt3Tle`XX9%DYnc+EaEV7t4x)FuS z#Xr)BC+v+Ify`OQLgk1;WqQ}|Sto!>WX?JkDn}G5w=Dk!>jY4V%vr}m<%mLMy7LCs z37`_0vyO$zkyMAu`SZTcIssH7bJnph>WpNl93ZGf)~KHqDp3xS<#^RcDi?oFPziga zqfj}*d|B4XZ+|EJ!5---RF1fj$`J>Zutz!yl_=-hr6ZO6LKN(gjzZ;#8>t-0P?`4R z{t2KGnRC1>RF1fjN@R`vQK&>Y*Df8YWEg}!(ov`!aZowpMk-;CbQCI4&iP|UDq)Xw z6e>p?RF344N`^| ~9`IAQ~2`XWabQCH_ z+(_k!gG$&V9feAibN*PUggw$xs2p)nIpUxa_DDye66KsfcBB&aNJpV^#6jhV8>xgn z(ov{HIp>cZsf0b!QK%eoP&wj8Dq)Xw6e>~9`C~^aVUKhaDn}etj<}IZ*drZ FKR9}AVRM>-0X zBMvG@98|&{=_pj9ob$&*CG3%oLgk2q$`OUiQJmccDn}J6H*Y7X1WO83!oW~Ds!*A_ zY04akHm)3*;W -0XC}%ojRH1VF*LJW@0F}s`{jpFvs!*BE z{~7B9P>IZ0$3o?3hRPK* =r?@Fvy?pc0w0jvc8S%_EgJ(A(e08nsKI66IX~= `Uq zMb@ZY3Y945`dLRRse8~r0aT7MNf!INh00MkQi-gQKMIv7=ls!;%EF%jDo2?l3x6zB zj^>fddFkf>Dv>$&vmL1%%}}{`+&uM90F}s`bu3hlmPRV2p982w=B#5!Dn}huB5Tyo z3Y945`q@Gy?2(Q_<*0+oQ3sW -0XDChjKPzigaqfj~OpmG!uDvzAP9_c7lqMY-` zLM7~xjzZ NL@(XNIgisNPS5CNCQZNNJB`&NFzw2NMlIjNE1ktNUM-)NK;7D zNHa*YNOMT5k#<7b8EF@!HAuT6?S`}#X?LV`Nb8X{Ank#)C(>R>dn4_Gv@g AQgYuKBDbNFDkIzxb4wiRKOnT=tae-yQmmV_VEYUBOSe{Kso1+y{Lda z($R~GQFl=>>Mkl^k972+0_B`P_M!szNJlR!M%_ilsJp0uJ<`#O3Y2sH*oz9-BOSe{ z7 hAl`gD#lV%dVsg5`G@JE0@o69Q2_&ARE+6G#X{G&IYV$!fz0rnPwKLm zVQx$>Dz>bpividhHv*Znj=iWD(~F9&U!scvWQ}z6q5|bir;h1G#r){cStq!tK<4a^ zy{H(|i;9IkKf^k~MFlcv9eYtRrX$tqrPr}ea8ZHGS;vl4kLg84`pOqrC%C9U=B#5c zD#r3f#m=u{o#3JZnX`_)s2Ix^6|HY(o#3JZnX`_)s2D4aR44o)YZP}KsYW?SmbXKD zQ2~3TqZbup?xJGMT~xpx>F7lT$~k}Z@dv)BfIZUDi;6KfQa$D_DqxRv^r8agoIm!W z0`^EpN2 1z!xn9_i>v^_Ux}9?KUMwE1BB2`(y-8BP?( zF?&%l<}NCbHS$L 6=N8_Dqjr19_i>s1 _r9ak&a$ejJb=7F>Eo`pWvbb zne+BzAAcBg7Zu1F`J)#VDCgQ`FDhV ~Hdz#i!+RE{~Q97`4|VUKha zDpAh)W1$lENJpV^%t7T?vQP WX?JkD#sNn(=A_Ood7D4IqO)c9M4dB89hdUtWp0cRH7Ut%l)j+)V9Bsbpogy z&rm6gWu5xt4-1cemURNC9M4cGi)9^s{9*meSto$X@eGx+Sk}>z>O_CANByHvIqslx zJSkM(L{o0T9_c7lqMYkzeNn4MUoe6_(ov`!cThQ=N2=3ltP?;bGUx5bj#Q6lsJ!I; ztP?;bGG`qNmE)zyALtoYWR2RTP>FJ`fAsN(trxOR0F~oRlErbuLgjc;s9d0rCm?I& zk3uENIe+x=hwXpIIssIUXQ-6L@^)ySsf9iAN1<}uLFKp`sfIn$(UEGDbM3NF345fY zP&w|Pa@;{B?2(Q_CCWK}EL6fC=_pi=JE$CYPzigaqfm)*&L0bvutz!ymE(yP%W=0* z345fYP&w{Ks>iVfm*EulNJpU(<(xkjDq)Xw6e`CZRF2n2s+0V|9_c7lqMY-`LM7~x zjzZ jY4V%-J6cl@kh;izm{g z^~f6eqfm)*&Y${=T4|nO5cWt%p>iTwsGLx!T=@1P>jY4V%(-0_Dkt(t<-?b=P5_n2 zoOSF- PY3n`K%K_ ~9`D39H_DDyea>7C7go8@hBOQfGlym-AsDwSzQK+17P&t9|mNE>&9_c7lqMY-` zLM7~xjzZ;xgUSi)3D%zgDv=pZ6vrPsQaRzE5?LdE6e>~9waY>!?2(Q_<%EOE2?v$1 zM>-0XDChjKPzigaqfj~FpmM@NCG3%oLM6&Me=JnO9_c7lPB^HXa8L<*q@z%Ya?T$M zm9R%T3Y8NMDkl^wCvkKZsGL-&+_Hn95-cfD2?Imrq(WsH{TOEmpc0wkId8nOm=DI2 z3YCkO-US`l8#e-(vyO$zNrlR-KiSGU0aPM$*0E4IsZg1YqDkwKHS$NH66H*ACi6(; z?2W7wKqWF~e=Jl^DpaORwy;hBmB^fREL2WrsQlWktP?;bGG`qNm6I7NxBLz31W<|0 zS;s= Su*Yl!Ih>`%$R8iC`J_NJpV^(m~~9hDsh8ggw$xs6;vEk3!|* zi`kz5Dkn2k%3`@)di#4b!58e2KMIwT4k{<}NadDGSSNr=WX|!jBbAe-k;;TWWR3jM zkxG dsO4uVEh04i9i{-f6 z+uyK9ItrDO4k{<{L{}NUV2^YZDpAh)W1$lENJpV^(m~}UhGncj0aPM0oG7+uJ5o96 zpb}Xle-tWF&b7 C{&`H^T*!)hCR|zsGM|AIq7bH!yf4< zRHB^o$KL*iJ280+9_c7lqMY-`-u{L?(ov|KbWl0zZhylb=_pj9occqz zzb6$cSK;_9P`OH>a-rq^c{=tNPzeJ=<*E#o>*>WGh&HYqnNcI}1hSY9600&)zWp1} zfj!bus6;t!1bzE`l|tqGy6>@00F}s`{jpHFDnsP~pJtr^Dv>$sSg2f;p>lqKbpohF z=B#6(a#e=PbT#V)P>IZ0$3o?*43&G++yBTK^|L}H%0aRmulnupw25E`_DDyeauxGs zSx0YwuephJ0;pWYBw1`f7AjXIh04V WX}CVi)DZG_V>a|Sto$XRZNnFKNc!i zC56g`=d(@#mB^g^u_KkM@<`=Yy4{SdQ9mnGqMU1&j#M&K!XD`;RIXyaEccI&R3`d^ zJ~9`D39H_DDyea+QP1RSqg)k8~6&QO@~ep%V5;N1<|+gUVHIq!RW> zN1+nsoIe&SVUKhaDpxtET;)b8VUKhaDpAh)W1$lENJpV^l^dyC<)9MwNJpU(<(xkj zDq)Xw6e?Fas9c33MH!Z1k8~6&QO@~ep%V5;N1<|+gUVG7Dq)Xw6e>~9`D39H_DDye za+QP1RSqg)k8~6&QO@~ep%V5;N1<|+gUVG7Dq)Xw6e>|p{UNAarBGSJ*+QVQrck-L z?f!W>hZIl=14CskLuI;{GXzkH%&3u1!m^l8RyBpng}2j4CG3qGfy`OQLS;>%vUWZ> zjI5E4LM6&+XAqp$6e>3#v5j>Cs6^)MkA=!whRWN&&pH8AB6HTUP+7}R`6n8wMAoQX z3Y945+NIA_o=k8Gd!(aKS#wZX%OjO{(QQ!JBOQfGlym+lRGv$>zhRGbbfmK8Mk;F= zDj&X#bpohF=G@N~Dr=>Y%1x{jKqWF~9SfDU43+6X)(M~znX`_C%36lXM1RN{^^Za& z%DH}4s4R2>sI29YN?9!Lrx_~GohN?+sI2AN-?CWN$x!(W)(N1pmPaaOv8 FIbUiL*D*drZ<%9?}9TCz|H zd!(aKiE_>#`=So)k&Z%T%|T@?S*V0P(ov{HIp>cZsf0b!QK+mrsI0k>O4uVEg-Voj z{@9U9*drZ<%9?}9nj5KvJVUKhaDpAh)W1$lE zNJpWv=Ag2MBaZbafJ$VB6UBaNZ-3VuR3dBSk3uENxprBoggw$xsH{1ttU0KJJE5j`8k&Z&~l)HUDRf6J#KiDH3g<_O*?b6%#Bm -_`6M8V z<#t&phCT8}M_Q-cNb6MT_B}x{?2(R+w4$7AmxW^3BOM)SopK|sQ^`Ux?2(R+w4$8z z$3ijek&ceEPPviRDF?-{M>-0{DChjKPz-ydqfk8Mpm@qbG3=3!LNUrYe=HQk9_c6) zPdO-_a!?F=q@z%ba?T$M#jr;@3dK_nil-bD!yf4<6r-H;$3ijek&Z&~l!M|a2gR^Q zIts-o=lroy411)bP(0 t4o^nt;l`Is)9_c6)qnz`{ zJ`)Oiq@z$g<)C; uij8Id((kZN0F~2uq*WHn?a~*G zzH}Sw1W-Ah6e?x0tW%$7b>Yg-uucG#(-|sdv8 zP&u8UQWncPI?}q8M(SaY;-yeI?VxfxkF;(%hjjv|MCQC5+L6}j(nxE P? zJqJ*U%( ~9`D39H_DDyea@s-Vw1Z06BOQfGlym-A zsDwSzQK+1DP&w_O681<(p%UesKNc!sk8~6&ryW#IJE(*`(ov{HIp>dsO4uVEh019M zmD3I?VUKhaDpAh)W1$lENJpV^+Ck;CgG$&V9feAibN*PUggw$xsGN3CIgKMu87g6q zbQCI4&iP}Z681<(p>o $Y zq4)lII%XG82?ImrOoqzO3w#AoiOlsz$YMUI&nQ%Gd4wv0y>TOuIqO)coKdKpzmn$t zMAk^BHS5q~*`GR8rboZbIssJ9WT=$IvW`OKyXYBA*du=wDrb@%shr6pm5=*4>jY4V z%(-25q;e*YR37?f)(M~znX`@^shr6pm6y?rJ;)lhOGheE&h@iG<(pr^IssJ9FiG-G zAdBUG){#nvO4uWR6e?%jNac)!O4uVEg-Voj?Xpk_d!(Zyl{0RnawgxtXQ+fd($U-Z zDChjKPzigaqfj~HpmN4RCG3%oLM6&MfAUCW!XNCBjzZ;(gUXpaQb{t<37`_0;Y6|h z*pbSaWT6sSBYzYsQO>o?-oA%D(ov|KaZovvEL6fC=_pj9ob$)tzK1>1QK+18P&tz< zRKgzVC{&`H^T*!4hdt6!sGM<7Ig>0@!XD`;RHB^o$37DZd!(aKIpd&mCRwP2JdsO4uVEg~}NRl`}YUS$_hkL}oZq9LH#}d>*tT zmB1QK&>Y^@nbH&nQ&Vt#g6OS%u2YgZIzVF}r|D7#J#NGgP7w zWeA`Wnd_Y)i@7sq6)IT $s&| +PN?9!H=tyPZPXLv(87gJ5tfM29g-!sKvw5Ua z7Rx#cm4!|Km9rTtWwETIP+8~%P&u1NDrK>(lW%{M5bROB6e?#ORL;8F->^qI3Y945 zwjUj-EZP-7A8vi)EesOsMov0F|?Oq*4~kIu Y=Z}R-*drZ<%2@}Mvu>mk z_DDye66Ksf7Aj$nbQCIQ9aPS`kxJMj9feAibN*PUggw$xsGN0BIqOC$VUKhaDpAh) zW1$lENJpV^) xgn(ov{HIp>dsO4uVEh00k6m9uW7681<(p%UesKNc!sk8~6& zXB|||x{*rQBOQfGlym-AsDwSzQK+1CP&w;HDq)Xw6e>~9`D39H_DDyea@LJh&f<(! zhEv!h9feAibN<+oO4uVEh00k6m9q+!^z5ZT<(xugZS?+mI>$EssVzOd6&x#2Ip?4f zqKzv@W_ZpgD_P7ZnK=iQutz!yl_+OAW6nV(?2(Q_CCWJ+9jT;qC)yi8 `3KY9;qyJ0;ojhtYb$i z=aPj=WR3bqZ-1kli (ov{HIp>eP{SAAh zqfj~LpmNTQRKgzVC{&`H^T&=-0Xa}Fx! z+(;$tk&Z$o$~k}RNG0r%jzZ;}gUUHKQVDycqfm)*&L2Bc345fYP&wzIa?XuZ!XD`; zRHB^o$BtCO9_c7l&N--@b0d|oM>-0XDChjKBbBg6ItrC@4l3u|NG0r%jzT5MIe+X( zCG3%oLgk!;$~iYu345fYP>FKRA3IVBd!(aKIp?5q4#RO}7=%62nSm1pN6})LH!M^_ zH_}n4oO4h)=SC`Fk8~6&QBM7#k;*xS%G=T={J$yv_W9>@(dm6P3RrT)8D`&`n#q12>fnsqTf_5tvmuvmfy{=%q}~E zdW!8WVEoy(Y^NTaZO^}{`;P3&zn4O3c18Z(1_nN|BmZuPA1~_9zn7sCslG(tL2m9O zZxZz=zJH8FFQn0$!^2^yBF75D%Mf&`Fyl}_;nO~zpkxf9Yx0HLkk|W zj(qp|R#dE`c=&uB73(@H)^$~^qkY5c^;E3utytGrv5vMluSZ*;UpH8>Zm442aK*Zj ziglwE>&7b9(O&3%p$|^@b(0nAXu3C_ujba#(U7*k`_rSyOZ&>P=>+_iU(NV+qW)^Z zuaopwD}Fsye=WhUll9k9{Cb-HYQwLm>#uhFdIo+;&CAkHc!{?6AEkf6U@RR=9!Tl( z`{uW$boXWR&8Y=@4gSTM=;F%{x%8|jK6U 5qhy0uQ{vTfPf=$;IYskMJ z9{YwX|NRT%U#t2@#~JaDPBjmtg)QRW687)!2fzE(m;JT)w}kz>^sEQ|^ZhS>8tX3M zzB_>`ziGZ$L;g{oT`m!Cmau>1&CfqwUqk*q>z|KY`KnKfe@i&7hoabDA9oe^-xBqY zj(OrAZQ~E5`D?{LivPCsYn-FHzbF1tuZwQ;kEH)_w!j*R_Msf@r8p04OK 8W8bd(qWIRvzI~x~(|I>ci*Iy}Dq_D{d~0Lh z{^J!dxb@*Li*Ie}8=V8hw|4d|-6+1bvv1q4|KgKB{sr-^oqg+E`_Xs(>f=vj-`d$X z-h##tEY?uJVXryuKktz!wzGfV{p# -{=S= zz3{2OzmmP7BhI$;q?OIP{{1n}D%Q|8_mjJ(zyF7CpH!@&zWV@h>b*O}zvb-TD_?+C zeo*{d&i>IJ@TptHzvb*7ny~Jf>)*kBw_N?BbF}!kLbkh)i+?L}-yL`*j<3CG1^ajL zi65WemYTkK>wIgX?S2LK*xxSRam{Of^giz673|T%nc~d~_J$6;9p{TTE7+S|>2R<` zyjh{%5FOA_pQilgqyDGvLv87iE4a=Z>E8$bsi=7+*ZlCKcVBqsm!vONa?N~{Un{k* z Kx5_77REKYaL|9IFoX zkC;gOBWipgZTYPDM`y!riMQjQ$+kr2t8Iz4 >tz3ayJt5azvu|I zEg?*A_)e1?NjurMqxOC6#w}ylb7VW&w=dtcl+T%+>KAdDoU1X}0Uh_hNKyOD?adqI zT#aiFXg`Neb5|X~O1Qeg8j2-tLtX62Noc?iJ}Ldu#oq7{d6jt6#oj#llut~4;ehLl zQglSv^oA?Xe(ST}#XZ%f9uXCaM?}*&(%-?`(VbV$i$~q;5uMHNzG^>K>t>JYK9X16 z>=k0(e&Z$LRX2O}?q6O1#Y-O$is)vq9{pVp_aR;DY)j|#BA!=@N8RcXF{yae!yeVX z%6+t~rRbv`_DGK5r*I$jNFUvE4g1%_-cU5Q9w0r^!`?g(f2+@^uc@D{*Suc(sE0kG zBh#|G-_3p0qaG0vi$}fe(blhXADwYk(MP@PQTdGA%N|_;