3 * $Date: 2006-03-18 15:59:33 -0600 (Sat, 18 Mar 2006) $
\r
6 * Copyright (C) 2003-2005 Miguel, Jmol Development, www.jmol.org
\r
8 * Contact: hansonr@stolaf.edu
\r
10 * This library is free software; you can redistribute it and/or
\r
11 * modify it under the terms of the GNU Lesser General Public
\r
12 * License as published by the Free Software Foundation; either
\r
13 * version 2.1 of the License, or (at your option) any later version.
\r
15 * This library is distributed in the hope that it will be useful,
\r
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
\r
18 * Lesser General Public License for more details.
\r
20 * You should have received a copy of the GNU Lesser General Public
\r
21 * License along with this library; if not, write to the Free Software
\r
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
\r
24 package javajs.util;
\r
27 import java.io.DataInputStream;
\r
28 import java.io.BufferedInputStream;
\r
31 import java.util.Map;
\r
33 import javajs.api.GenericZipTools;
\r
37 /* a simple compound document reader.
\r
38 * See http://sc.openoffice.org/compdocfileformat.pdf
\r
40 * random access file info:
\r
41 * http://java.sun.com/docs/books/tutorial/essential/io/rafs.html
\r
43 * SHOOT! random access is only for applications, not applets!
\r
45 * With a bit more work, this could be set up to deliver binary files, but
\r
46 * right now I've only implemented it for string-based data. All Jmol is using
\r
51 public class CompoundDocument extends BinaryDocument{
\r
53 // RandomAccessFile file;
\r
54 CompoundDocHeader header = new CompoundDocHeader(this);
\r
55 Lst<CompoundDocDirEntry> directory = new Lst<CompoundDocDirEntry>();
\r
56 CompoundDocDirEntry rootEntry;
\r
61 int shortSectorSize;
\r
62 int nShortSectorsPerStandardSector;
\r
64 int nDirEntriesperSector;
\r
66 // called by reflection
\r
68 public CompoundDocument(){
\r
70 this.isBigEndian = true;
\r
74 public void setStream(GenericZipTools jzt, BufferedInputStream bis, boolean isBigEndian) {
\r
75 // isBigEndian is ignored here; it must be true
\r
77 file = new RandomAccessFile(fileName, "r");
\r
79 } catch (Exception e) {
\r
80 // probably an applet
\r
85 stream = new DataInputStream(bis);
\r
87 stream.mark(Integer.MAX_VALUE);
\r
90 getSectorAllocationTable();
\r
91 getShortSectorAllocationTable();
\r
92 getDirectoryTable();
\r
95 public Lst<CompoundDocDirEntry> getDirectory() {
\r
99 public String getDirectoryListing(String separator) {
\r
101 for (int i = 0; i < directory.size(); i++) {
\r
102 CompoundDocDirEntry thisEntry = directory.get(i);
\r
103 if (!thisEntry.isEmpty)
\r
105 + thisEntry.entryName
\r
107 + thisEntry.lenStream
\r
109 + thisEntry.SIDfirstSector
\r
110 + (thisEntry.isStandard ? "\tfileOffset="
\r
111 + getOffset(thisEntry.SIDfirstSector) : "");
\r
118 public SB getAllData() {
\r
119 return getAllDataFiles(null, null);
\r
123 * reads a compound document directory and saves all data in a Hashtable
\r
124 * so that the files may be organized later in a different order. Also adds
\r
125 * a #Directory_Listing entry.
\r
127 * Files are bracketed by BEGIN Directory Entry and END Directory Entry lines,
\r
128 * similar to ZipUtil.getAllData.
\r
131 * @param binaryFileList |-separated list of files that should be saved
\r
132 * as xx xx xx hex byte strings. The directory listing
\r
133 * is appended with ":asBinaryString"
\r
137 public void getAllDataMapped(String prefix,
\r
138 String binaryFileList, Map<String, String> fileData) {
\r
139 fileData.put("#Directory_Listing", getDirectoryListing("|"));
\r
140 binaryFileList = "|" + binaryFileList + "|";
\r
141 for (int i = 0; i < directory.size(); i++) {
\r
142 CompoundDocDirEntry thisEntry = directory.get(i);
\r
143 if (!thisEntry.isEmpty && thisEntry.entryType != 5) {
\r
144 String name = thisEntry.entryName;
\r
145 System.out.println("CompoundDocument file " + name);
\r
146 boolean isBinary = (binaryFileList.indexOf("|" + name + "|") >= 0);
\r
148 name += ":asBinaryString";
\r
149 SB data = new SB();
\r
150 data.append("BEGIN Directory Entry ").append(name).append("\n");
\r
151 data.appendSB(getEntryAsString(thisEntry, isBinary));
\r
152 data.append("\nEND Directory Entry ").append(name).append("\n");
\r
153 fileData.put(prefix + "/" + name, data.toString());
\r
160 public SB getAllDataFiles(String binaryFileList, String firstFile) {
\r
161 if (firstFile != null) {
\r
162 for (int i = 0; i < directory.size(); i++) {
\r
163 CompoundDocDirEntry thisEntry = directory.get(i);
\r
164 if (thisEntry.entryName.equals(firstFile)) {
\r
165 directory.remove(i);
\r
166 directory.add(1, thisEntry); // after ROOT_ENTRY
\r
172 data.append("Compound Document File Directory: ");
\r
173 data.append(getDirectoryListing("|"));
\r
175 binaryFileList = "|" + binaryFileList + "|";
\r
176 for (int i = 0; i < directory.size(); i++) {
\r
177 CompoundDocDirEntry thisEntry = directory.get(i);
\r
178 //System.out.println("CompoundDocument reading " + thisEntry.entryName);
\r
179 if (!thisEntry.isEmpty && thisEntry.entryType != 5) {
\r
180 String name = thisEntry.entryName;
\r
181 if (name.endsWith(".gz"))
\r
182 name = name.substring(0, name.length() - 3);
\r
183 data.append("BEGIN Directory Entry ").append(name).append("\n");
\r
184 data.appendSB(getEntryAsString(thisEntry, binaryFileList.indexOf("|" + thisEntry.entryName + "|") >= 0));
\r
186 data.append("END Directory Entry ").append(thisEntry.entryName).append("\n");
\r
193 public SB getFileAsString(String entryName) {
\r
194 for (int i = 0; i < directory.size(); i++) {
\r
195 CompoundDocDirEntry thisEntry = directory.get(i);
\r
196 if (thisEntry.entryName.equals(entryName))
\r
197 return getEntryAsString(thisEntry, false);
\r
202 private long getOffset(int SID) {
\r
203 return (SID + 1) * sectorSize;
\r
206 private void gotoSector(int SID) {
\r
207 seek(getOffset(SID));
\r
210 private boolean readHeader() {
\r
211 if (!header.readData())
\r
213 sectorSize = 1 << header.sectorPower;
\r
214 shortSectorSize = 1 << header.shortSectorPower;
\r
215 nShortSectorsPerStandardSector = sectorSize / shortSectorSize; // e.g. 512 / 64 = 8
\r
216 nIntPerSector = sectorSize / 4; // e.g. 512 / 4 = 128
\r
217 nDirEntriesperSector = sectorSize / 128; // e.g. 512 / 128 = 4
\r
218 // System.out.println(
\r
219 // "compound document: revNum=" + header.revNumber +
\r
220 // " verNum=" + header.verNumber + " isBigEndian=" + isBigEndian +
\r
221 // " bytes per standard/short sector=" + sectorSize + "/" + shortSectorSize);
\r
225 private void getSectorAllocationTable() {
\r
228 SAT = new int[header.nSATsectors * nIntPerSector + 109];
\r
231 for (int i = 0; i < 109; i++) {
\r
232 thisSID = header.MSAT0[i];
\r
235 gotoSector(thisSID);
\r
236 for (int j = 0; j < nIntPerSector; j++) {
\r
237 SAT[nSID++] = readInt();
\r
238 //Logger.debug(thisSID+"."+j + "/" + (nSID - 1) + " : " + SAT[nSID - 1]);
\r
241 int nMaster = header.nAdditionalMATsectors;
\r
242 thisSID = header.SID_MSAT_next;
\r
243 int[] MSAT = new int[nIntPerSector];
\r
244 out: while (nMaster-- > 0 && thisSID >= 0) {
\r
245 // read a page of sector identifiers pointing to SAT sectors
\r
246 gotoSector(thisSID);
\r
247 for (int i = 0; i < nIntPerSector; i++)
\r
248 MSAT[i] = readInt();
\r
249 // read each page of SAT sector identifiers
\r
250 // last entry is pointer to next master sector allocation table page
\r
251 for (int i = 0; i < nIntPerSector - 1; i++) {
\r
255 gotoSector(thisSID);
\r
256 for (int j = nIntPerSector; --j >= 0;)
\r
257 SAT[nSID++] = readInt();
\r
259 thisSID = MSAT[nIntPerSector - 1];
\r
261 } catch (Exception e) {
\r
262 System.out.println(e.toString());
\r
266 private void getShortSectorAllocationTable() {
\r
268 int thisSID = header.SID_SSAT_start;
\r
269 int nMax = header.nSSATsectors * nIntPerSector;
\r
270 SSAT = new int[nMax];
\r
272 while (thisSID > 0 && nSSID < nMax) {
\r
273 gotoSector(thisSID);
\r
274 for (int j = 0; j < nIntPerSector; j++) {
\r
275 SSAT[nSSID++] = readInt();
\r
276 //System.out.println("short: " + thisSID+"."+j+" SSID=" +(nSSID-1)+" "+SSAT[nSSID-1]);
\r
278 thisSID = SAT[thisSID];
\r
280 } catch (Exception e) {
\r
281 System.out.println(e.toString());
\r
285 private void getDirectoryTable() {
\r
286 int thisSID = header.SID_DIR_start;
\r
287 CompoundDocDirEntry thisEntry;
\r
290 while (thisSID > 0) {
\r
291 gotoSector(thisSID);
\r
292 for (int j = nDirEntriesperSector; --j >= 0;) {
\r
293 thisEntry = new CompoundDocDirEntry(this);
\r
294 thisEntry.readData();
\r
295 if (thisEntry.lenStream > 0) {
\r
296 directory.addLast(thisEntry);
\r
297 //System.out.println(thisEntry.entryName);
\r
299 if (thisEntry.entryType == 5)
\r
300 rootEntry = thisEntry;
\r
302 thisSID = SAT[thisSID];
\r
304 } catch (Exception e) {
\r
305 System.out.println(e.toString());
\r
307 // System.out.println("CompoundDocument directory entry: \n"
\r
308 // + getDirectoryListing("\n"));
\r
311 private SB getEntryAsString(CompoundDocDirEntry thisEntry, boolean asBinaryString) {
\r
312 if(thisEntry.isEmpty)
\r
314 //System.out.println(thisEntry.entryName + " " + thisEntry.entryType + " " + thisEntry.lenStream + " " + thisEntry.isStandard + " " + thisEntry.SIDfirstSector);
\r
315 return (thisEntry.isStandard ? getStandardStringData(
\r
316 thisEntry.SIDfirstSector, thisEntry.lenStream, asBinaryString)
\r
317 : getShortStringData(thisEntry.SIDfirstSector, thisEntry.lenStream, asBinaryString));
\r
319 private SB getStandardStringData(int thisSID, int nBytes,
\r
320 boolean asBinaryString) {
\r
321 SB data = new SB();
\r
322 byte[] byteBuf = new byte[sectorSize];
\r
323 ZipData gzipData = new ZipData(nBytes);
\r
325 while (thisSID > 0 && nBytes > 0) {
\r
326 gotoSector(thisSID);
\r
327 nBytes = getSectorData(data, byteBuf, sectorSize, nBytes, asBinaryString, gzipData);
\r
328 thisSID = SAT[thisSID];
\r
330 if (nBytes == -9999)
\r
332 } catch (Exception e) {
\r
333 System.out.println(e.toString());
\r
335 if (gzipData.isEnabled)
\r
336 gzipData.addTo(jzt, data);
\r
340 private int getSectorData(SB data, byte[] byteBuf,
\r
341 int nSectorBytes, int nBytes,
\r
342 boolean asBinaryString, ZipData gzipData)
\r
344 readByteArray(byteBuf, 0, byteBuf.length);
\r
345 int n = gzipData.addBytes(byteBuf, nSectorBytes, nBytes);
\r
348 if (asBinaryString) {
\r
349 for (int i = 0; i < nSectorBytes; i++) {
\r
350 data.append(Integer.toHexString(byteBuf[i] & 0xFF)).appendC(' ');
\r
355 for (int i = 0; i < nSectorBytes; i++) {
\r
356 if (byteBuf[i] == 0)
\r
357 return -9999; // don't allow binary data
\r
358 data.appendC((char) byteBuf[i]);
\r
366 private SB getShortStringData(int shortSID, int nBytes, boolean asBinaryString) {
\r
367 SB data = new SB();
\r
368 if (rootEntry == null)
\r
370 int thisSID = rootEntry.SIDfirstSector;
\r
372 byte[] byteBuf = new byte[shortSectorSize];
\r
373 ZipData gzipData = new ZipData(nBytes);
\r
375 //System.out.println("CD shortSID=" + shortSID);
\r
376 // point to correct short data sector, 512/64 = 4 per page
\r
377 while (thisSID >= 0 && shortSID >= 0 && nBytes > 0) {
\r
378 while (shortSID - ptShort >= nShortSectorsPerStandardSector) {
\r
379 ptShort += nShortSectorsPerStandardSector;
\r
380 thisSID = SAT[thisSID];
\r
382 seek(getOffset(thisSID) + (shortSID - ptShort) * shortSectorSize);
\r
383 nBytes = getSectorData(data, byteBuf, shortSectorSize, nBytes, asBinaryString, gzipData);
\r
384 shortSID = SSAT[shortSID];
\r
385 //System.out.println("CD shortSID=" + shortSID);
\r
387 } catch (Exception e) {
\r
388 System.out.println(data.toString());
\r
389 System.out.println("reader error in CompoundDocument " + e.toString());
\r
391 if (gzipData.isEnabled)
\r
392 gzipData.addTo(jzt, data);
\r