+/* $RCSfile$
+ * $Author: egonw $
+ * $Date: 2006-03-18 15:59:33 -0600 (Sat, 18 Mar 2006) $
+ * $Revision: 4652 $
+ *
+ * Some portions of this file have been modified by Robert Hanson hansonr.at.stolaf.edu 2012-2017
+ * for use in SwingJS via transpilation into JavaScript using Java2Script.
+ *
+ * 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.
+ *
+ * DIRECTORY STRUCTURE IS NOT REGENERATED
+ *
+ * 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.
+ *
+ */
+
+public class CompoundDocument extends BinaryDocument{
+
+// RandomAccessFile file;
+ CompoundDocHeader header = new CompoundDocHeader(this);
+ Lst<CompoundDocDirEntry> directory = new Lst<CompoundDocDirEntry>();
+ CompoundDocDirEntry rootEntry;
+
+ protected GenericZipTools jzt;
+
+ int[] SAT;
+ int[] SSAT;
+ int sectorSize;
+ int shortSectorSize;
+ int nShortSectorsPerStandardSector;
+ int nIntPerSector;
+ int nDirEntriesperSector;
+
+ // called by reflection
+
+ public CompoundDocument(){
+ super();
+ this.isBigEndian = true;
+ }
+
+ public void setDocStream(GenericZipTools jzt, BufferedInputStream bis) {
+ this.jzt = jzt;
+ if (!isRandom) {
+ stream = new DataInputStream(bis);
+ }
+ stream.mark(Integer.MAX_VALUE);
+ if (!readHeader())
+ return;
+ getSectorAllocationTable();
+ getShortSectorAllocationTable();
+ getDirectoryTable();
+ }
+
+ public Lst<CompoundDocDirEntry> getDirectory() {
+ return directory;
+ }
+
+ public String getDirectoryListing(String separator) {
+ SB sb = new SB();
+ for (int i = 0; i < directory.size(); i++) {
+ CompoundDocDirEntry thisEntry = directory.get(i);
+ if (!thisEntry.isEmpty)
+ sb.append(separator).append(thisEntry.entryName)
+ .append("\tlen=").appendI(thisEntry.lenStream)
+ .append("\tSID=").appendI(thisEntry.SIDfirstSector)
+ .append(thisEntry.isStandard ? "\tfileOffset="
+ + getOffset(thisEntry.SIDfirstSector) : "");
+ }
+ return sb.toString();
+ }
+
+ 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<String, String> 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";
+ fileData.put(prefix + "/" + name, appendData(new SB(), name, thisEntry, isBinary).toString());
+ }
+ }
+ close();
+ }
+
+ @Override
+ public SB getAllDataFiles(String binaryFileList, String firstFile) {
+// firstFile is now ignored
+// 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;
+// }
+// }
+// }
+ SB data = new SB();
+ data.append("Compound Document File Directory: ");
+ data.append(getDirectoryListing("|"));
+ data.append("\n");
+ CompoundDocDirEntry thisEntry;
+ binaryFileList = "|" + binaryFileList + "|";
+ for (int i = 0, n = directory.size(); i < n; i++) {
+ thisEntry = directory.get(i);
+ //System.out.println("CompoundDocument reading " + thisEntry.entryName);
+ String name = thisEntry.entryName;
+ switch (thisEntry.entryType) {
+ case 5: // root
+ break;
+ case 1: // user storage (dir)
+ data.append("NEW Directory ").append(name).append("\n");
+ break;
+ case 2: // user stream (file)
+ if (name.endsWith(".gz"))
+ name = name.substring(0, name.length() - 3);
+ appendData(data, name, thisEntry, binaryFileList.indexOf("|" + thisEntry.entryName + "|") >= 0);
+ break;
+ }
+ }
+ close();
+ return data;
+ }
+
+ private SB appendData(SB data, String name, CompoundDocDirEntry thisEntry,
+ boolean isBinary) {
+ data.append("BEGIN Directory Entry ").append(name).append("\n");
+ data.appendSB(getEntryAsString(thisEntry, isBinary));
+ data.append("\nEND Directory Entry ").append(name).append("\n");
+ 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();
+ directory.addLast(thisEntry);
+ 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;
+ }
+}