/* $RCSfile$ * $Author: egonw $ * $Date: 2006-03-18 15:59:33 -0600 (Sat, 18 Mar 2006) $ * $Revision: 4652 $ * * Copyright (C) 2003-2005 Miguel, Jmol Development, www.jmol.org * * Contact: hansonr@stolaf.edu * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ package javajs.util; import java.io.DataInputStream; import java.io.BufferedInputStream; import java.util.Map; import javajs.api.GenericZipTools; /* a simple compound document reader. * See http://sc.openoffice.org/compdocfileformat.pdf * * random access file info: * http://java.sun.com/docs/books/tutorial/essential/io/rafs.html * * SHOOT! random access is only for applications, not applets! * * With a bit more work, this could be set up to deliver binary files, but * right now I've only implemented it for string-based data. All Jmol is using * is getAllData(). * */ public class CompoundDocument extends BinaryDocument{ // RandomAccessFile file; CompoundDocHeader header = new CompoundDocHeader(this); Lst directory = new Lst(); CompoundDocDirEntry rootEntry; int[] SAT; int[] SSAT; int sectorSize; int shortSectorSize; int nShortSectorsPerStandardSector; int nIntPerSector; int nDirEntriesperSector; // called by reflection public CompoundDocument(){ super(); this.isBigEndian = true; } @Override public void setStream(GenericZipTools jzt, BufferedInputStream bis, boolean isBigEndian) { // isBigEndian is ignored here; it must be true /* try { file = new RandomAccessFile(fileName, "r"); isRandom = true; } catch (Exception e) { // probably an applet } */ this.jzt = jzt; if (!isRandom) { stream = new DataInputStream(bis); } stream.mark(Integer.MAX_VALUE); if (!readHeader()) return; getSectorAllocationTable(); getShortSectorAllocationTable(); getDirectoryTable(); } public Lst getDirectory() { return directory; } public String getDirectoryListing(String separator) { String str = ""; for (int i = 0; i < directory.size(); i++) { CompoundDocDirEntry thisEntry = directory.get(i); if (!thisEntry.isEmpty) str += separator + thisEntry.entryName + "\tlen=" + thisEntry.lenStream + "\tSID=" + thisEntry.SIDfirstSector + (thisEntry.isStandard ? "\tfileOffset=" + getOffset(thisEntry.SIDfirstSector) : ""); } return str; } SB data; public SB getAllData() { return getAllDataFiles(null, null); } /** * reads a compound document directory and saves all data in a Hashtable * so that the files may be organized later in a different order. Also adds * a #Directory_Listing entry. * * Files are bracketed by BEGIN Directory Entry and END Directory Entry lines, * similar to ZipUtil.getAllData. * * @param prefix * @param binaryFileList |-separated list of files that should be saved * as xx xx xx hex byte strings. The directory listing * is appended with ":asBinaryString" * @param fileData */ @Override public void getAllDataMapped(String prefix, String binaryFileList, Map fileData) { fileData.put("#Directory_Listing", getDirectoryListing("|")); binaryFileList = "|" + binaryFileList + "|"; for (int i = 0; i < directory.size(); i++) { CompoundDocDirEntry thisEntry = directory.get(i); if (!thisEntry.isEmpty && thisEntry.entryType != 5) { String name = thisEntry.entryName; System.out.println("CompoundDocument file " + name); boolean isBinary = (binaryFileList.indexOf("|" + name + "|") >= 0); if (isBinary) name += ":asBinaryString"; SB data = new SB(); data.append("BEGIN Directory Entry ").append(name).append("\n"); data.appendSB(getEntryAsString(thisEntry, isBinary)); data.append("\nEND Directory Entry ").append(name).append("\n"); fileData.put(prefix + "/" + name, data.toString()); } } close(); } @Override public SB getAllDataFiles(String binaryFileList, String firstFile) { if (firstFile != null) { for (int i = 0; i < directory.size(); i++) { CompoundDocDirEntry thisEntry = directory.get(i); if (thisEntry.entryName.equals(firstFile)) { directory.remove(i); directory.add(1, thisEntry); // after ROOT_ENTRY break; } } } data = new SB(); data.append("Compound Document File Directory: "); data.append(getDirectoryListing("|")); data.append("\n"); binaryFileList = "|" + binaryFileList + "|"; for (int i = 0; i < directory.size(); i++) { CompoundDocDirEntry thisEntry = directory.get(i); //System.out.println("CompoundDocument reading " + thisEntry.entryName); if (!thisEntry.isEmpty && thisEntry.entryType != 5) { String name = thisEntry.entryName; if (name.endsWith(".gz")) name = name.substring(0, name.length() - 3); data.append("BEGIN Directory Entry ").append(name).append("\n"); data.appendSB(getEntryAsString(thisEntry, binaryFileList.indexOf("|" + thisEntry.entryName + "|") >= 0)); data.append("\n"); data.append("END Directory Entry ").append(thisEntry.entryName).append("\n"); } } close(); return data; } public SB getFileAsString(String entryName) { for (int i = 0; i < directory.size(); i++) { CompoundDocDirEntry thisEntry = directory.get(i); if (thisEntry.entryName.equals(entryName)) return getEntryAsString(thisEntry, false); } return new SB(); } private long getOffset(int SID) { return (SID + 1) * sectorSize; } private void gotoSector(int SID) { seek(getOffset(SID)); } private boolean readHeader() { if (!header.readData()) return false; sectorSize = 1 << header.sectorPower; shortSectorSize = 1 << header.shortSectorPower; nShortSectorsPerStandardSector = sectorSize / shortSectorSize; // e.g. 512 / 64 = 8 nIntPerSector = sectorSize / 4; // e.g. 512 / 4 = 128 nDirEntriesperSector = sectorSize / 128; // e.g. 512 / 128 = 4 // System.out.println( // "compound document: revNum=" + header.revNumber + // " verNum=" + header.verNumber + " isBigEndian=" + isBigEndian + // " bytes per standard/short sector=" + sectorSize + "/" + shortSectorSize); return true; } private void getSectorAllocationTable() { int nSID = 0; int thisSID; SAT = new int[header.nSATsectors * nIntPerSector + 109]; try { for (int i = 0; i < 109; i++) { thisSID = header.MSAT0[i]; if (thisSID < 0) break; gotoSector(thisSID); for (int j = 0; j < nIntPerSector; j++) { SAT[nSID++] = readInt(); //Logger.debug(thisSID+"."+j + "/" + (nSID - 1) + " : " + SAT[nSID - 1]); } } int nMaster = header.nAdditionalMATsectors; thisSID = header.SID_MSAT_next; int[] MSAT = new int[nIntPerSector]; out: while (nMaster-- > 0 && thisSID >= 0) { // read a page of sector identifiers pointing to SAT sectors gotoSector(thisSID); for (int i = 0; i < nIntPerSector; i++) MSAT[i] = readInt(); // read each page of SAT sector identifiers // last entry is pointer to next master sector allocation table page for (int i = 0; i < nIntPerSector - 1; i++) { thisSID = MSAT[i]; if (thisSID < 0) break out; gotoSector(thisSID); for (int j = nIntPerSector; --j >= 0;) SAT[nSID++] = readInt(); } thisSID = MSAT[nIntPerSector - 1]; } } catch (Exception e) { System.out.println(e.toString()); } } private void getShortSectorAllocationTable() { int nSSID = 0; int thisSID = header.SID_SSAT_start; int nMax = header.nSSATsectors * nIntPerSector; SSAT = new int[nMax]; try { while (thisSID > 0 && nSSID < nMax) { gotoSector(thisSID); for (int j = 0; j < nIntPerSector; j++) { SSAT[nSSID++] = readInt(); //System.out.println("short: " + thisSID+"."+j+" SSID=" +(nSSID-1)+" "+SSAT[nSSID-1]); } thisSID = SAT[thisSID]; } } catch (Exception e) { System.out.println(e.toString()); } } private void getDirectoryTable() { int thisSID = header.SID_DIR_start; CompoundDocDirEntry thisEntry; rootEntry = null; try { while (thisSID > 0) { gotoSector(thisSID); for (int j = nDirEntriesperSector; --j >= 0;) { thisEntry = new CompoundDocDirEntry(this); thisEntry.readData(); if (thisEntry.lenStream > 0) { directory.addLast(thisEntry); //System.out.println(thisEntry.entryName); } if (thisEntry.entryType == 5) rootEntry = thisEntry; } thisSID = SAT[thisSID]; } } catch (Exception e) { System.out.println(e.toString()); } // System.out.println("CompoundDocument directory entry: \n" // + getDirectoryListing("\n")); } private SB getEntryAsString(CompoundDocDirEntry thisEntry, boolean asBinaryString) { if(thisEntry.isEmpty) return new SB(); //System.out.println(thisEntry.entryName + " " + thisEntry.entryType + " " + thisEntry.lenStream + " " + thisEntry.isStandard + " " + thisEntry.SIDfirstSector); return (thisEntry.isStandard ? getStandardStringData( thisEntry.SIDfirstSector, thisEntry.lenStream, asBinaryString) : getShortStringData(thisEntry.SIDfirstSector, thisEntry.lenStream, asBinaryString)); } private SB getStandardStringData(int thisSID, int nBytes, boolean asBinaryString) { SB data = new SB(); byte[] byteBuf = new byte[sectorSize]; ZipData gzipData = new ZipData(nBytes); try { while (thisSID > 0 && nBytes > 0) { gotoSector(thisSID); nBytes = getSectorData(data, byteBuf, sectorSize, nBytes, asBinaryString, gzipData); thisSID = SAT[thisSID]; } if (nBytes == -9999) return new SB(); } catch (Exception e) { System.out.println(e.toString()); } if (gzipData.isEnabled) gzipData.addTo(jzt, data); return data; } private int getSectorData(SB data, byte[] byteBuf, int nSectorBytes, int nBytes, boolean asBinaryString, ZipData gzipData) throws Exception { readByteArray(byteBuf, 0, byteBuf.length); int n = gzipData.addBytes(byteBuf, nSectorBytes, nBytes); if (n >= 0) return n; if (asBinaryString) { for (int i = 0; i < nSectorBytes; i++) { data.append(Integer.toHexString(byteBuf[i] & 0xFF)).appendC(' '); if (--nBytes < 1) break; } } else { for (int i = 0; i < nSectorBytes; i++) { if (byteBuf[i] == 0) return -9999; // don't allow binary data data.appendC((char) byteBuf[i]); if (--nBytes < 1) break; } } return nBytes; } private SB getShortStringData(int shortSID, int nBytes, boolean asBinaryString) { SB data = new SB(); if (rootEntry == null) return data; int thisSID = rootEntry.SIDfirstSector; int ptShort = 0; byte[] byteBuf = new byte[shortSectorSize]; ZipData gzipData = new ZipData(nBytes); try { //System.out.println("CD shortSID=" + shortSID); // point to correct short data sector, 512/64 = 4 per page while (thisSID >= 0 && shortSID >= 0 && nBytes > 0) { while (shortSID - ptShort >= nShortSectorsPerStandardSector) { ptShort += nShortSectorsPerStandardSector; thisSID = SAT[thisSID]; } seek(getOffset(thisSID) + (shortSID - ptShort) * shortSectorSize); nBytes = getSectorData(data, byteBuf, shortSectorSize, nBytes, asBinaryString, gzipData); shortSID = SSAT[shortSID]; //System.out.println("CD shortSID=" + shortSID); } } catch (Exception e) { System.out.println(data.toString()); System.out.println("reader error in CompoundDocument " + e.toString()); } if (gzipData.isEnabled) gzipData.addTo(jzt, data); return data; } }