Index: src/scratchpad/src/org/apache/poi/hwpf/model/SavedByTable.java =================================================================== --- src/scratchpad/src/org/apache/poi/hwpf/model/SavedByTable.java (revision 0) +++ src/scratchpad/src/org/apache/poi/hwpf/model/SavedByTable.java (revision 0) @@ -0,0 +1,121 @@ +/* ==================================================================== + Copyright 2002-2004 Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + + +package org.apache.poi.hwpf.model; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.StringUtil; + +import org.apache.poi.hwpf.model.io.HWPFOutputStream; + +/** + * String table containing the history of the last few revisions ("saves") of the document. + * Read-only for the time being. + * + * @author Daniel Noll + */ +public class SavedByTable +{ + /** + * A value that I don't know what it does, but is maintained for accuracy. + */ + private short unknownValue = -1; + + /** + * Array of entries. + */ + private SavedByEntry[] entries; + + /** + * Constructor to read the table from the table stream. + * + * @param tableStream the table stream. + * @param offset the offset into the byte array. + * @param size the size of the table in the byte array. + */ + public SavedByTable(byte[] tableStream, int offset, int size) + { + // Read the value that I don't know what it does. :-) + unknownValue = LittleEndian.getShort(tableStream, offset); + offset += 2; + + // The stored int is the number of strings, and there are two strings per entry. + int numEntries = LittleEndian.getInt(tableStream, offset) / 2; + offset += 4; + + entries = new SavedByEntry[numEntries]; + for (int i = 0; i < numEntries; i++) + { + int len = LittleEndian.getShort(tableStream, offset); + offset += 2; + String userName = StringUtil.getFromUnicodeLE(tableStream, offset, len); + offset += len * 2; + len = LittleEndian.getShort(tableStream, offset); + offset += 2; + String saveLocation = StringUtil.getFromUnicodeLE(tableStream, offset, len); + offset += len * 2; + + entries[i] = new SavedByEntry(userName, saveLocation); + } + } + + /** + * Gets the entries. The returned list cannot be modified. + * + * @return the list of entries. + */ + public List getEntries() + { + return Collections.unmodifiableList(Arrays.asList(entries)); + } + + /** + * Writes this table to the table stream. + * + * @param tableStream the table stream to write to. + * @throws IOException if an error occurs while writing. + */ + public void writeTo(HWPFOutputStream tableStream) + throws IOException + { + byte[] header = new byte[6]; + LittleEndian.putShort(header, 0, unknownValue); + LittleEndian.putInt(header, 2, entries.length * 2); + tableStream.write(header); + + for (int i = 0; i < entries.length; i++) + { + writeStringValue(tableStream, entries[i].getUserName()); + writeStringValue(tableStream, entries[i].getSaveLocation()); + } + } + + private void writeStringValue(HWPFOutputStream tableStream, String value) + throws IOException + { + byte[] buf = new byte[value.length() * 2 + 2]; + LittleEndian.putShort(buf, 0, (short) value.length()); + StringUtil.putUnicodeLE(value, buf, 2); + tableStream.write(buf); + } +} + Property changes on: src/scratchpad/src/org/apache/poi/hwpf/model/SavedByTable.java ___________________________________________________________________ Name: svn:executable + * Index: src/scratchpad/src/org/apache/poi/hwpf/model/SavedByEntry.java =================================================================== --- src/scratchpad/src/org/apache/poi/hwpf/model/SavedByEntry.java (revision 0) +++ src/scratchpad/src/org/apache/poi/hwpf/model/SavedByEntry.java (revision 0) @@ -0,0 +1,85 @@ +/* ==================================================================== + Copyright 2002-2004 Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + + +package org.apache.poi.hwpf.model; + + +/** + * A single entry in the {@link SavedByTable}. + * + * @author Daniel Noll + */ +public class SavedByEntry +{ + private String userName; + private String saveLocation; + + public SavedByEntry(String userName, String saveLocation) + { + this.userName = userName; + this.saveLocation = saveLocation; + } + + public String getUserName() + { + return userName; + } + + public String getSaveLocation() + { + return saveLocation; + } + + /** + * Compares this object with another, for equality. + * + * @param other the object to compare to this one. + * @return true iff the other object is equal to this one. + */ + public boolean equals(Object other) + { + if (other == this) return true; + if (!(other instanceof SavedByEntry)) return false; + SavedByEntry that = (SavedByEntry) other; + return that.userName.equals(userName) && + that.saveLocation.equals(saveLocation); + } + + /** + * Generates a hash code for consistency with {@link #equals(Object)}. + * + * @return the hash code. + */ + public int hashCode() + { + int hash = 29; + hash = hash * 13 + userName.hashCode(); + hash = hash * 13 + saveLocation.hashCode(); + return hash; + } + + /** + * Returns a string for display. + * + * @return the string. + */ + public String toString() + { + return "SavedByEntry[userName=" + getUserName() + + ",saveLocation=" + getSaveLocation() + "]"; + } +} Property changes on: src/scratchpad/src/org/apache/poi/hwpf/model/SavedByEntry.java ___________________________________________________________________ Name: svn:executable + * Index: src/scratchpad/src/org/apache/poi/hwpf/model/FileInformationBlock.java =================================================================== --- src/scratchpad/src/org/apache/poi/hwpf/model/FileInformationBlock.java (revision 431273) +++ src/scratchpad/src/org/apache/poi/hwpf/model/FileInformationBlock.java (working copy) @@ -61,6 +61,7 @@ fieldSet.add(new Integer(FIBFieldHandler.PLFLFO)); fieldSet.add(new Integer(FIBFieldHandler.PLCFFLDMOM)); fieldSet.add(new Integer(FIBFieldHandler.STTBFFFN)); + fieldSet.add(new Integer(FIBFieldHandler.STTBSAVEDBY)); fieldSet.add(new Integer(FIBFieldHandler.MODIFIED)); @@ -251,6 +252,26 @@ _fieldHandler.setFieldSize(FIBFieldHandler.STTBFFFN, lcbSttbFffn); } + public int getFcSttbSavedBy() + { + return _fieldHandler.getFieldOffset(FIBFieldHandler.STTBSAVEDBY); + } + + public int getLcbSttbSavedBy() + { + return _fieldHandler.getFieldSize(FIBFieldHandler.STTBSAVEDBY); + } + + public void setFcSttbSavedBy(int fcSttbSavedBy) + { + _fieldHandler.setFieldOffset(FIBFieldHandler.STTBSAVEDBY, fcSttbSavedBy); + } + + public void setLcbSttbSavedBy(int fcSttbSavedBy) + { + _fieldHandler.setFieldSize(FIBFieldHandler.STTBSAVEDBY, fcSttbSavedBy); + } + public int getModifiedLow() { return _fieldHandler.getFieldOffset(FIBFieldHandler.PLFLFO); Index: src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java =================================================================== --- src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java (revision 431273) +++ src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java (working copy) @@ -86,6 +86,9 @@ /** Hold list tables */ protected ListTables _lt; + /** Holds the save history for this document. */ + protected SavedByTable _sbt; + protected HWPFDocument() { @@ -212,6 +215,13 @@ _lt = new ListTables(_tableStream, _fib.getFcPlcfLst(), _fib.getFcPlfLfo()); } + int sbtOffset = _fib.getFcSttbSavedBy(); + int sbtLength = _fib.getLcbSttbSavedBy(); + if (sbtOffset != 0 && sbtLength != 0) + { + _sbt = new SavedByTable(_tableStream, sbtOffset, sbtLength); + } + PlexOfCps plc = new PlexOfCps(_tableStream, _fib.getFcPlcffldMom(), _fib.getLcbPlcffldMom(), 2); for (int x = 0; x < plc.length(); x++) { @@ -267,7 +277,18 @@ { return _lt; } + /** + * Gets a reference to the saved -by table, which holds the save history for the document. + * + * @return the saved-by table. + */ + public SavedByTable getSavedByTable() + { + return _sbt; + } + + /** * Writes out the word file that is represented by an instance of this class. * * @param out The OutputStream to write to. @@ -347,6 +368,16 @@ tableOffset = tableStream.getOffset(); } + // write out the saved-by table. + if (_sbt != null) + { + _fib.setFcSttbSavedBy(tableOffset); + _sbt.writeTo(tableStream); + _fib.setLcbSttbSavedBy(tableStream.getOffset() - tableOffset); + + tableOffset = tableStream.getOffset(); + } + // write out the FontTable. _fib.setFcSttbfffn(tableOffset); _ft.writeTo(docSys); Index: src/scratchpad/testcases/org/apache/poi/hwpf/model/TestSavedByTable.java =================================================================== --- src/scratchpad/testcases/org/apache/poi/hwpf/model/TestSavedByTable.java (revision 0) +++ src/scratchpad/testcases/org/apache/poi/hwpf/model/TestSavedByTable.java (revision 0) @@ -0,0 +1,91 @@ + +/* ==================================================================== + Copyright 2002-2004 Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + + +package org.apache.poi.hwpf.model; + +import java.io.*; +import java.util.*; +import junit.framework.*; + +import org.apache.poi.hwpf.*; +import org.apache.poi.hwpf.model.*; +import org.apache.poi.util.*; + +/** + * Unit test for {@link SavedByTable} and {@link SavedByEntry}. + * + * @author Daniel Noll + */ +public class TestSavedByTable + extends TestCase +{ + /** Data dir */ + private File testFile = new File(new File(System.getProperty("HWPF.testdata.path")), "saved-by-table.doc"); + + /** The expected entries in the test document. */ + private List expected = Arrays.asList(new Object[] { + new SavedByEntry("cic22", "C:\\DOCUME~1\\phamill\\LOCALS~1\\Temp\\AutoRecovery save of Iraq - security.asd"), + new SavedByEntry("cic22", "C:\\DOCUME~1\\phamill\\LOCALS~1\\Temp\\AutoRecovery save of Iraq - security.asd"), + new SavedByEntry("cic22", "C:\\DOCUME~1\\phamill\\LOCALS~1\\Temp\\AutoRecovery save of Iraq - security.asd"), + new SavedByEntry("JPratt", "C:\\TEMP\\Iraq - security.doc"), + new SavedByEntry("JPratt", "A:\\Iraq - security.doc"), + new SavedByEntry("ablackshaw", "C:\\ABlackshaw\\Iraq - security.doc"), + new SavedByEntry("ablackshaw", "C:\\ABlackshaw\\A;Iraq - security.doc"), + new SavedByEntry("ablackshaw", "A:\\Iraq - security.doc"), + new SavedByEntry("MKhan", "C:\\TEMP\\Iraq - security.doc"), + new SavedByEntry("MKhan", "C:\\WINNT\\Profiles\\mkhan\\Desktop\\Iraq.doc") + }); + + /** + * Tests reading in the entries, comparing them against the expected entries. + * Then tests writing the document out and reading the entries yet again. + * + * @throws Exception if an unexpected error occurs. + */ + public void testReadWrite() + throws Exception + { + // This document is widely available on the internet as "blair.doc". + // I tried stripping the content and saving the document but my version + // of Word (from Office XP) strips this table out. + InputStream stream = new BufferedInputStream(new FileInputStream(testFile)); + HWPFDocument doc; + try + { + doc = new HWPFDocument(stream); + } + finally + { + stream.close(); + } + + // Check what we just read. + assertEquals("List of saved-by entries was not as expected", + expected, doc.getSavedByTable().getEntries()); + + // Now write the entire document out, and read it back in... + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + doc.write(byteStream); + InputStream copyStream = new ByteArrayInputStream(byteStream.toByteArray()); + HWPFDocument copy = new HWPFDocument(copyStream); + + // And check again. + assertEquals("List of saved-by entries was incorrect after writing", + expected, copy.getSavedByTable().getEntries()); + } +} Property changes on: src/scratchpad/testcases/org/apache/poi/hwpf/model/TestSavedByTable.java ___________________________________________________________________ Name: svn:executable + * Index: src/scratchpad/testcases/org/apache/poi/hwpf/data/saved-by-table.doc =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: src/scratchpad/testcases/org/apache/poi/hwpf/data/saved-by-table.doc ___________________________________________________________________ Name: svn:executable + * Name: svn:mime-type + application/octet-stream