示例#1
0
/** A record containing the necessary data for the font information */
public class FontRecord extends WritableRecordData implements Font {
  /** The logger */
  private static Logger logger = Logger.getLogger(FontRecord.class);

  /** The point height of this font */
  private int pointHeight;
  /** The index into the colour palette */
  private int colourIndex;
  /** The bold weight for this font (normal or bold) */
  private int boldWeight;
  /** The style of the script (italic or normal) */
  private int scriptStyle;
  /** The underline style for this font (none, single, double etc) */
  private int underlineStyle;
  /** The font family */
  private byte fontFamily;
  /** The character set */
  private byte characterSet;

  /** Indicates whether or not this font is italic */
  private boolean italic;
  /** Indicates whether or not this font is struck out */
  private boolean struckout;
  /** The name of this font */
  private String name;
  /**
   * Flag to indicate whether the derived data (such as the font index) has been initialized or not
   */
  private boolean initialized;

  /** The index of this font in the font list */
  private int fontIndex;

  /** Dummy indicators for overloading the constructor */
  private static class Biff7 {};

  public static final Biff7 biff7 = new Biff7();

  /** The conversion factor between microsoft internal units and point size */
  private static final int EXCEL_UNITS_PER_POINT = 20;

  /**
   * Constructor, used when creating a new font for writing out.
   *
   * @param bold the bold indicator
   * @param ps the point size
   * @param us the underline style
   * @param fn the name
   * @param it italicised indicator
   * @param ss the script style
   * @param ci the colour index
   */
  protected FontRecord(String fn, int ps, int bold, boolean it, int us, int ci, int ss) {
    super(Type.FONT);
    boldWeight = bold;
    underlineStyle = us;
    name = fn;
    pointHeight = ps;
    italic = it;
    scriptStyle = ss;
    colourIndex = ci;
    initialized = false;
    struckout = false;
  }

  /**
   * Constructs this object from the raw data. Used when reading in a format record
   *
   * @param t the raw data
   * @param ws the workbook settings
   */
  public FontRecord(Record t, WorkbookSettings ws) {
    super(t);

    byte[] data = getRecord().getData();

    pointHeight = IntegerHelper.getInt(data[0], data[1]) / EXCEL_UNITS_PER_POINT;
    colourIndex = IntegerHelper.getInt(data[4], data[5]);
    boldWeight = IntegerHelper.getInt(data[6], data[7]);
    scriptStyle = IntegerHelper.getInt(data[8], data[9]);
    underlineStyle = data[10];
    fontFamily = data[11];
    characterSet = data[12];
    initialized = false;

    if ((data[2] & 0x02) != 0) {
      italic = true;
    }

    if ((data[2] & 0x08) != 0) {
      struckout = true;
    }

    int numChars = data[14];
    if (data[15] == 0) {
      name = StringHelper.getString(data, numChars, 16, ws);
    } else if (data[15] == 1) {
      name = StringHelper.getUnicodeString(data, numChars, 16);
    } else {
      // Some font names don't have the unicode indicator
      name = StringHelper.getString(data, numChars, 15, ws);
    }
  }

  /**
   * Constructs this object from the raw data. Used when reading in a format record
   *
   * @param t the raw data
   * @param ws the workbook settings
   * @param dummy dummy overload
   */
  public FontRecord(Record t, WorkbookSettings ws, Biff7 dummy) {
    super(t);

    byte[] data = getRecord().getData();

    pointHeight = IntegerHelper.getInt(data[0], data[1]) / EXCEL_UNITS_PER_POINT;
    colourIndex = IntegerHelper.getInt(data[4], data[5]);
    boldWeight = IntegerHelper.getInt(data[6], data[7]);
    scriptStyle = IntegerHelper.getInt(data[8], data[9]);
    underlineStyle = data[10];
    fontFamily = data[11];
    initialized = false;

    if ((data[2] & 0x02) != 0) {
      italic = true;
    }

    if ((data[2] & 0x08) != 0) {
      struckout = true;
    }

    int numChars = data[14];
    name = StringHelper.getString(data, numChars, 15, ws);
  }

  /**
   * Publicly available copy constructor
   *
   * @param f the font to copy
   */
  protected FontRecord(Font f) {
    super(Type.FONT);

    Assert.verify(f != null);

    pointHeight = f.getPointSize();
    colourIndex = f.getColour().getValue();
    boldWeight = f.getBoldWeight();
    scriptStyle = f.getScriptStyle().getValue();
    underlineStyle = f.getUnderlineStyle().getValue();
    italic = f.isItalic();
    name = f.getName();
    struckout = false;
    initialized = false;
  }

  /**
   * Gets the byte data for writing out
   *
   * @return the raw data
   */
  public byte[] getData() {
    byte[] data = new byte[16 + name.length() * 2];

    // Excel expects font heights in 1/20ths of a point
    IntegerHelper.getTwoBytes(pointHeight * EXCEL_UNITS_PER_POINT, data, 0);

    // Set the font attributes to be zero for now
    if (italic) {
      data[2] |= 0x2;
    }

    if (struckout) {
      data[2] |= 0x08;
    }

    // Set the index to the colour palette
    IntegerHelper.getTwoBytes(colourIndex, data, 4);

    // Bold style
    IntegerHelper.getTwoBytes(boldWeight, data, 6);

    // Script style
    IntegerHelper.getTwoBytes(scriptStyle, data, 8);

    // Underline style
    data[10] = (byte) underlineStyle;

    // Set the font family to be 0
    data[11] = fontFamily;

    // Set the character set to be zero
    data[12] = characterSet;

    // Set the reserved bit to be zero
    data[13] = 0;

    // Set the length of the font name
    data[14] = (byte) name.length();

    data[15] = (byte) 1;

    // Copy in the string
    StringHelper.getUnicodeBytes(name, data, 16);

    return data;
  }

  /**
   * Accessor to see whether this object is initialized or not.
   *
   * @return TRUE if this font record has been initialized, FALSE otherwise
   */
  public final boolean isInitialized() {
    return initialized;
  }

  /**
   * Sets the font index of this record. Called from the FormattingRecords object
   *
   * @param pos the position of this font in the workbooks font list
   */
  public final void initialize(int pos) {
    fontIndex = pos;
    initialized = true;
  }

  /**
   * Resets the initialize flag. This is called by the constructor of WritableWorkbookImpl to reset
   * the statically declared fonts
   */
  public final void uninitialize() {
    initialized = false;
  }

  /**
   * Accessor for the font index
   *
   * @return the font index
   */
  public final int getFontIndex() {
    return fontIndex;
  }

  /**
   * Sets the point size for this font, if the font hasn't been initialized
   *
   * @param ps the point size
   */
  protected void setFontPointSize(int ps) {
    Assert.verify(!initialized);

    pointHeight = ps;
  }

  /**
   * Gets the point size for this font, if the font hasn't been initialized
   *
   * @return the point size
   */
  public int getPointSize() {
    return pointHeight;
  }

  /**
   * Sets the bold style for this font, if the font hasn't been initialized
   *
   * @param bs the bold style
   */
  protected void setFontBoldStyle(int bs) {
    Assert.verify(!initialized);

    boldWeight = bs;
  }

  /**
   * Gets the bold weight for this font
   *
   * @return the bold weight for this font
   */
  public int getBoldWeight() {
    return boldWeight;
  }

  /**
   * Sets the italic indicator for this font, if the font hasn't been initialized
   *
   * @param i the italic flag
   */
  protected void setFontItalic(boolean i) {
    Assert.verify(!initialized);

    italic = i;
  }

  /**
   * Returns the italic flag
   *
   * @return TRUE if this font is italic, FALSE otherwise
   */
  public boolean isItalic() {
    return italic;
  }

  /**
   * Sets the underline style for this font, if the font hasn't been initialized
   *
   * @param us the underline style
   */
  protected void setFontUnderlineStyle(int us) {
    Assert.verify(!initialized);

    underlineStyle = us;
  }

  /**
   * Gets the underline style for this font
   *
   * @return the underline style
   */
  public UnderlineStyle getUnderlineStyle() {
    return UnderlineStyle.getStyle(underlineStyle);
  }

  /**
   * Sets the colour for this font, if the font hasn't been initialized
   *
   * @param c the colour
   */
  protected void setFontColour(int c) {
    Assert.verify(!initialized);

    colourIndex = c;
  }

  /**
   * Gets the colour for this font
   *
   * @return the colour
   */
  public Colour getColour() {
    return Colour.getInternalColour(colourIndex);
  }

  /**
   * Sets the script style (eg. superscript, subscript) for this font, if the font hasn't been
   * initialized
   *
   * @param ss the colour
   */
  protected void setFontScriptStyle(int ss) {
    Assert.verify(!initialized);

    scriptStyle = ss;
  }

  /**
   * Gets the script style
   *
   * @return the script style
   */
  public ScriptStyle getScriptStyle() {
    return ScriptStyle.getStyle(scriptStyle);
  }

  /**
   * Gets the name of this font
   *
   * @return the name of this font
   */
  public String getName() {
    return name;
  }

  /**
   * Standard hash code method
   *
   * @return the hash code for this object
   */
  public int hashCode() {
    return name.hashCode();
  }

  /**
   * Standard equals method
   *
   * @param o the object to compare
   * @return TRUE if the objects are equal, FALSE otherwise
   */
  public boolean equals(Object o) {
    if (o == this) {
      return true;
    }

    if (!(o instanceof FontRecord)) {
      return false;
    }

    FontRecord font = (FontRecord) o;

    if (pointHeight == font.pointHeight
        && colourIndex == font.colourIndex
        && boldWeight == font.boldWeight
        && scriptStyle == font.scriptStyle
        && underlineStyle == font.underlineStyle
        && italic == font.italic
        && struckout == font.struckout
        && fontFamily == font.fontFamily
        && characterSet == font.characterSet
        && name.equals(font.name)) {
      return true;
    }

    return false;
  }

  /**
   * Accessor for the strike out flag
   *
   * @return TRUE if this font is struck out, FALSE otherwise
   */
  public boolean isStruckout() {
    return struckout;
  }

  /**
   * Sets the struck out flag
   *
   * @param os TRUE if the font is struck out, false otherwise
   */
  protected void setFontStruckout(boolean os) {
    struckout = os;
  }
}
示例#2
0
/** Parses the biff file passed in, and builds up an internal representation of the spreadsheet */
public class WorkbookParser extends Workbook implements ExternalSheet, WorkbookMethods {
  /** The logger */
  private static Logger logger = Logger.getLogger(WorkbookParser.class);

  /** The excel file */
  private File excelFile;
  /** The number of open bofs */
  private int bofs;
  /** Indicates whether or not the dates are based around the 1904 date system */
  private boolean nineteenFour;
  /** The shared string table */
  private SSTRecord sharedStrings;
  /** The names of all the worksheets */
  private ArrayList boundsheets;
  /** The xf records */
  private FormattingRecords formattingRecords;
  /** The fonts used by this workbook */
  private Fonts fonts;

  /** The sheets contained in this workbook */
  private ArrayList sheets;

  /** The last sheet accessed */
  private SheetImpl lastSheet;

  /** The index of the last sheet retrieved */
  private int lastSheetIndex;

  /** The named records found in this workbook */
  private HashMap namedRecords;

  /** The list of named records */
  private ArrayList nameTable;

  /** The external sheet record. Used by formulas, and names */
  private ExternalSheetRecord externSheet;

  /** The list of supporting workbooks - used by formulas */
  private ArrayList supbooks;

  /** The bof record for this workbook */
  private BOFRecord workbookBof;

  /** The Mso Drawing Group record for this workbook */
  private MsoDrawingGroupRecord msoDrawingGroup;

  /** The property set record associated with this workbook */
  private ButtonPropertySetRecord buttonPropertySet;

  /** Workbook protected flag */
  private boolean wbProtected;

  /** Contains macros flag */
  private boolean containsMacros;

  /** The workbook settings */
  private WorkbookSettings settings;

  /** The drawings contained in this workbook */
  private DrawingGroup drawingGroup;

  /**
   * Constructs this object from the raw excel data
   *
   * @param f the excel 97 biff file
   * @param s the workbook settings
   */
  public WorkbookParser(File f, WorkbookSettings s) {
    super();
    excelFile = f;
    boundsheets = new ArrayList(10);
    fonts = new Fonts();
    formattingRecords = new FormattingRecords(fonts);
    sheets = new ArrayList(10);
    supbooks = new ArrayList(10);
    namedRecords = new HashMap();
    lastSheetIndex = -1;
    wbProtected = false;
    containsMacros = false;
    settings = s;
  }

  /**
   * Gets the sheets within this workbook. NOTE: Use of this method for very large worksheets can
   * cause performance and out of memory problems. Use the alternative method getSheet() to retrieve
   * each sheet individually
   *
   * @return an array of the individual sheets
   */
  public Sheet[] getSheets() {
    Sheet[] sheetArray = new Sheet[getNumberOfSheets()];

    for (int i = 0; i < getNumberOfSheets(); i++) {
      sheetArray[i] = (Sheet) sheets.get(i);
    }
    return sheetArray;
  }

  /**
   * Interface method from WorkbookMethods - gets the specified sheet within this workbook
   *
   * @param index the zero based index of the required sheet
   * @return The sheet specified by the index
   */
  public Sheet getReadSheet(int index) {
    return getSheet(index);
  }

  /**
   * Gets the specified sheet within this workbook
   *
   * @param index the zero based index of the required sheet
   * @return The sheet specified by the index
   */
  public Sheet getSheet(int index) {
    // First see if the last sheet index is the same as this sheet index.
    // If so, then the same sheet is being re-requested, so simply
    // return it instead of rereading it
    if ((lastSheet != null) && lastSheetIndex == index) {
      return lastSheet;
    }

    // Flush out all of the cached data in the last sheet
    if (lastSheet != null) {
      lastSheet.clear();

      if (!settings.getGCDisabled()) {
        System.gc();
      }
    }

    lastSheet = (SheetImpl) sheets.get(index);
    lastSheetIndex = index;
    lastSheet.readSheet();

    return lastSheet;
  }

  /**
   * Gets the sheet with the specified name from within this workbook
   *
   * @param name the sheet name
   * @return The sheet with the specified name, or null if it is not found
   */
  public Sheet getSheet(String name) {
    // Iterate through the boundsheet records
    int pos = 0;
    boolean found = false;
    Iterator i = boundsheets.iterator();
    BoundsheetRecord br = null;

    while (i.hasNext() && !found) {
      br = (BoundsheetRecord) i.next();

      if (br.getName().equals(name)) {
        found = true;
      } else {
        pos++;
      }
    }

    return found ? getSheet(pos) : null;
  }

  /**
   * Gets the sheet names
   *
   * @return an array of strings containing the sheet names
   */
  public String[] getSheetNames() {
    String[] names = new String[boundsheets.size()];

    BoundsheetRecord br = null;
    for (int i = 0; i < names.length; i++) {
      br = (BoundsheetRecord) boundsheets.get(i);
      names[i] = br.getName();
    }

    return names;
  }

  /**
   * Package protected function which gets the real internal sheet index based upon the external
   * sheet reference. This is used for extern sheet references which are specified in formulas
   *
   * @param index the external sheet reference
   * @return the actual sheet index
   */
  public int getExternalSheetIndex(int index) {
    // For biff7, the whole external reference thing works differently
    // Hopefully for our purposes sheet references will all be local
    if (workbookBof.isBiff7()) {
      return index;
    }

    Assert.verify(externSheet != null);

    int firstTab = externSheet.getFirstTabIndex(index);

    return firstTab;
  }

  /**
   * Package protected function which gets the real internal sheet index based upon the external
   * sheet reference. This is used for extern sheet references which are specified in formulas
   *
   * @param index the external sheet reference
   * @return the actual sheet index
   */
  public int getLastExternalSheetIndex(int index) {
    // For biff7, the whole external reference thing works differently
    // Hopefully for our purposes sheet references will all be local
    if (workbookBof.isBiff7()) {
      return index;
    }

    Assert.verify(externSheet != null);

    int lastTab = externSheet.getLastTabIndex(index);

    return lastTab;
  }

  /**
   * Gets the name of the external sheet specified by the index
   *
   * @param index the external sheet index
   * @return the name of the external sheet
   */
  public String getExternalSheetName(int index) {
    // For biff7, the whole external reference thing works differently
    // Hopefully for our purposes sheet references will all be local
    if (workbookBof.isBiff7()) {
      BoundsheetRecord br = (BoundsheetRecord) boundsheets.get(index);

      return br.getName();
    }

    int supbookIndex = externSheet.getSupbookIndex(index);
    SupbookRecord sr = (SupbookRecord) supbooks.get(supbookIndex);

    int firstTab = externSheet.getFirstTabIndex(index);

    if (sr.getType() == SupbookRecord.INTERNAL) {
      // It's an internal reference - get the name from the boundsheets list
      BoundsheetRecord br = (BoundsheetRecord) boundsheets.get(firstTab);

      return br.getName();
    } else if (sr.getType() == SupbookRecord.EXTERNAL) {
      // External reference - get the sheet name from the supbook record
      StringBuffer sb = new StringBuffer();
      sb.append('[');
      sb.append(sr.getFileName());
      sb.append(']');
      sb.append(sr.getSheetName(firstTab));
      return sb.toString();
    }

    // An unknown supbook - return unkown
    return "[UNKNOWN]";
  }

  /**
   * Gets the name of the external sheet specified by the index
   *
   * @param index the external sheet index
   * @return the name of the external sheet
   */
  public String getLastExternalSheetName(int index) {
    // For biff7, the whole external reference thing works differently
    // Hopefully for our purposes sheet references will all be local
    if (workbookBof.isBiff7()) {
      BoundsheetRecord br = (BoundsheetRecord) boundsheets.get(index);

      return br.getName();
    }

    int supbookIndex = externSheet.getSupbookIndex(index);
    SupbookRecord sr = (SupbookRecord) supbooks.get(supbookIndex);

    int lastTab = externSheet.getLastTabIndex(index);

    if (sr.getType() == SupbookRecord.INTERNAL) {
      // It's an internal reference - get the name from the boundsheets list
      BoundsheetRecord br = (BoundsheetRecord) boundsheets.get(lastTab);

      return br.getName();
    } else if (sr.getType() == SupbookRecord.EXTERNAL) {
      // External reference - get the sheet name from the supbook record
      StringBuffer sb = new StringBuffer();
      sb.append('[');
      sb.append(sr.getFileName());
      sb.append(']');
      sb.append(sr.getSheetName(lastTab));
      return sb.toString();
    }

    // An unknown supbook - return unkown
    return "[UNKNOWN]";
  }

  /**
   * Returns the number of sheets in this workbook
   *
   * @return the number of sheets in this workbook
   */
  public int getNumberOfSheets() {
    return sheets.size();
  }

  /** Closes this workbook, and frees makes any memory allocated available for garbage collection */
  public void close() {
    if (lastSheet != null) {
      lastSheet.clear();
    }
    excelFile.clear();

    if (!settings.getGCDisabled()) {
      System.gc();
    }
  }

  /**
   * Adds the sheet to the end of the array
   *
   * @param s the sheet to add
   */
  final void addSheet(Sheet s) {
    sheets.add(s);
  }

  /**
   * Does the hard work of building up the object graph from the excel bytes
   *
   * @exception BiffException
   * @exception PasswordException if the workbook is password protected
   */
  protected void parse() throws BiffException, PasswordException {
    Record r = null;

    BOFRecord bof = new BOFRecord(excelFile.next());
    workbookBof = bof;
    bofs++;

    if (!bof.isBiff8() && !bof.isBiff7()) {
      throw new BiffException(BiffException.unrecognizedBiffVersion);
    }

    if (!bof.isWorkbookGlobals()) {
      throw new BiffException(BiffException.expectedGlobals);
    }
    ArrayList continueRecords = new ArrayList();
    nameTable = new ArrayList();

    // Skip to the first worksheet
    while (bofs == 1) {
      r = excelFile.next();

      if (r.getType() == Type.SST) {
        continueRecords.clear();
        Record nextrec = excelFile.peek();
        while (nextrec.getType() == Type.CONTINUE) {
          continueRecords.add(excelFile.next());
          nextrec = excelFile.peek();
        }

        // cast the array
        Object[] rec = continueRecords.toArray();
        Record[] records = new Record[rec.length];
        System.arraycopy(rec, 0, records, 0, rec.length);

        sharedStrings = new SSTRecord(r, records, settings);
      } else if (r.getType() == Type.FILEPASS) {
        throw new PasswordException();
      } else if (r.getType() == Type.NAME) {
        NameRecord nr = null;

        if (bof.isBiff8()) {
          nr = new NameRecord(r, settings, namedRecords.size());
        } else {
          nr = new NameRecord(r, settings, namedRecords.size(), NameRecord.biff7);
        }

        namedRecords.put(nr.getName(), nr);
        nameTable.add(nr);
      } else if (r.getType() == Type.FONT) {
        FontRecord fr = null;

        if (bof.isBiff8()) {
          fr = new FontRecord(r, settings);
        } else {
          fr = new FontRecord(r, settings, FontRecord.biff7);
        }
        fonts.addFont(fr);
      } else if (r.getType() == Type.PALETTE) {
        PaletteRecord palette = new PaletteRecord(r);
        formattingRecords.setPalette(palette);
      } else if (r.getType() == Type.NINETEENFOUR) {
        NineteenFourRecord nr = new NineteenFourRecord(r);
        nineteenFour = nr.is1904();
      } else if (r.getType() == Type.FORMAT) {
        FormatRecord fr = null;
        if (bof.isBiff8()) {
          fr = new FormatRecord(r, settings, FormatRecord.biff8);
        } else {
          fr = new FormatRecord(r, settings, FormatRecord.biff7);
        }
        try {
          formattingRecords.addFormat(fr);
        } catch (NumFormatRecordsException e) {
          // This should not happen.  Bomb out
          Assert.verify(false, e.getMessage());
        }
      } else if (r.getType() == Type.XF) {
        XFRecord xfr = null;
        if (bof.isBiff8()) {
          xfr = new XFRecord(r, XFRecord.biff8);
        } else {
          xfr = new XFRecord(r, XFRecord.biff7);
        }

        try {
          formattingRecords.addStyle(xfr);
        } catch (NumFormatRecordsException e) {
          // This should not happen.  Bomb out
          Assert.verify(false, e.getMessage());
        }
      } else if (r.getType() == Type.BOUNDSHEET) {
        BoundsheetRecord br = null;

        if (bof.isBiff8()) {
          br = new BoundsheetRecord(r);
        } else {
          br = new BoundsheetRecord(r, BoundsheetRecord.biff7);
        }

        if (br.isSheet() || br.isChart()) {
          boundsheets.add(br);
        }
      } else if (r.getType() == Type.EXTERNSHEET) {
        if (bof.isBiff8()) {
          externSheet = new ExternalSheetRecord(r, settings);
        } else {
          externSheet = new ExternalSheetRecord(r, settings, ExternalSheetRecord.biff7);
        }
      } else if (r.getType() == Type.CODEPAGE) {
        CodepageRecord cr = new CodepageRecord(r);
        settings.setCharacterSet(cr.getCharacterSet());
      } else if (r.getType() == Type.SUPBOOK) {
        SupbookRecord sr = new SupbookRecord(r, settings);
        supbooks.add(sr);
      } else if (r.getType() == Type.PROTECT) {
        ProtectRecord pr = new ProtectRecord(r);
        wbProtected = pr.isProtected();
      } else if (r.getType() == Type.OBJPROJ) {
        containsMacros = true;
      } else if (r.getType() == Type.MSODRAWINGGROUP) {
        msoDrawingGroup = new MsoDrawingGroupRecord(r);

        if (drawingGroup == null) {
          drawingGroup = new DrawingGroup(DrawingGroup.READ);
        }

        drawingGroup.add(msoDrawingGroup);

        Record nextrec = excelFile.peek();
        while (nextrec.getType() == Type.CONTINUE) {
          drawingGroup.add(excelFile.next());
          nextrec = excelFile.peek();
        }
      } else if (r.getType() == Type.BUTTONPROPERTYSET) {
        buttonPropertySet = new ButtonPropertySetRecord(r);
      } else if (r.getType() == Type.EOF) {
        bofs--;
      }
    }

    bof = null;
    if (excelFile.hasNext()) {
      r = excelFile.next();

      if (r.getType() == Type.BOF) {
        bof = new BOFRecord(r);
      }
    }

    // Only get sheets for which there is a corresponding Boundsheet record
    while (bof != null && getNumberOfSheets() < boundsheets.size()) {
      if (!bof.isBiff8() && !bof.isBiff7()) {
        throw new BiffException(BiffException.unrecognizedBiffVersion);
      }

      if (bof.isWorksheet()) {
        // Read the sheet in
        SheetImpl s =
            new SheetImpl(
                excelFile, sharedStrings, formattingRecords, bof, workbookBof, nineteenFour, this);

        BoundsheetRecord br = (BoundsheetRecord) boundsheets.get(getNumberOfSheets());
        s.setName(br.getName());
        s.setHidden(br.isHidden());
        addSheet(s);
      } else if (bof.isChart()) {
        // Read the sheet in
        SheetImpl s =
            new SheetImpl(
                excelFile, sharedStrings, formattingRecords, bof, workbookBof, nineteenFour, this);

        BoundsheetRecord br = (BoundsheetRecord) boundsheets.get(getNumberOfSheets());
        s.setName(br.getName());
        s.setHidden(br.isHidden());
        addSheet(s);
      } else {
        logger.warn("BOF is unrecognized");

        while (excelFile.hasNext() && r.getType() != Type.EOF) {
          r = excelFile.next();
        }
      }

      // The next record will normally be a BOF or empty padding until
      // the end of the block is reached.  In exceptionally unlucky cases,
      // the last EOF  will coincide with a block division, so we have to
      // check there is more data to retrieve.
      // Thanks to liamg for spotting this
      bof = null;
      if (excelFile.hasNext()) {
        r = excelFile.next();

        if (r.getType() == Type.BOF) {
          bof = new BOFRecord(r);
        }
      }
    }
  }

  /**
   * Accessor for the formattingRecords, used by the WritableWorkbook when creating a copy of this
   *
   * @return the formatting records
   */
  public FormattingRecords getFormattingRecords() {
    return formattingRecords;
  }

  /**
   * Accessor for the externSheet, used by the WritableWorkbook when creating a copy of this
   *
   * @return the external sheet record
   */
  public ExternalSheetRecord getExternalSheetRecord() {
    return externSheet;
  }

  /**
   * Accessor for the MsoDrawingGroup, used by the WritableWorkbook when creating a copy of this
   *
   * @return the Mso Drawing Group record
   */
  public MsoDrawingGroupRecord getMsoDrawingGroupRecord() {
    return msoDrawingGroup;
  }

  /**
   * Accessor for the supbook records, used by the WritableWorkbook when creating a copy of this
   *
   * @return the supbook records
   */
  public SupbookRecord[] getSupbookRecords() {
    SupbookRecord[] sr = new SupbookRecord[supbooks.size()];

    for (int i = 0; i < sr.length; i++) {
      sr[i] = (SupbookRecord) supbooks.get(i);
    }

    return sr;
  }

  /**
   * Accessor for the name records. Used by the WritableWorkbook when creating a copy of this
   *
   * @return the array of names
   */
  public NameRecord[] getNameRecords() {
    NameRecord[] na = new NameRecord[nameTable.size()];

    for (int i = 0; i < nameTable.size(); i++) {
      na[i] = (NameRecord) nameTable.get(i);
    }

    return na;
  }

  /**
   * Accessor for the fonts, used by the WritableWorkbook when creating a copy of this
   *
   * @return the fonts used in this workbook
   */
  public Fonts getFonts() {
    return fonts;
  }

  /**
   * Gets the named cell from this workbook. If the name refers to a range of cells, then the cell
   * on the top left is returned. If the name cannot be found, null is returned
   *
   * @param name the name of the cell/range to search for
   * @return the cell in the top left of the range if found, NULL otherwise
   */
  public Cell findCellByName(String name) {
    NameRecord nr = (NameRecord) namedRecords.get(name);

    if (nr == null) {
      return null;
    }

    NameRecord.NameRange[] ranges = nr.getRanges();

    // Go and retrieve the first cell in the first range
    Sheet s = getSheet(ranges[0].getExternalSheet());
    Cell cell = s.getCell(ranges[0].getFirstColumn(), ranges[0].getFirstRow());

    return cell;
  }

  /**
   * Gets the named range from this workbook. The Range object returns contains all the cells from
   * the top left to the bottom right of the range. If the named range comprises an adjacent range,
   * the Range[] will contain one object; for non-adjacent ranges, it is necessary to return an
   * array of length greater than one. If the named range contains a single cell, the top left and
   * bottom right cell will be the same cell
   *
   * @param name the name to find
   * @return the range of cells
   */
  public Range[] findByName(String name) {
    NameRecord nr = (NameRecord) namedRecords.get(name);

    if (nr == null) {
      return null;
    }

    NameRecord.NameRange[] ranges = nr.getRanges();

    Range[] cellRanges = new Range[ranges.length];

    for (int i = 0; i < ranges.length; i++) {
      cellRanges[i] =
          new RangeImpl(
              this,
              getExternalSheetIndex(ranges[i].getExternalSheet()),
              ranges[i].getFirstColumn(),
              ranges[i].getFirstRow(),
              getLastExternalSheetIndex(ranges[i].getExternalSheet()),
              ranges[i].getLastColumn(),
              ranges[i].getLastRow());
    }

    return cellRanges;
  }

  /**
   * Gets the named ranges
   *
   * @return the list of named cells within the workbook
   */
  public String[] getRangeNames() {
    Object[] keys = namedRecords.keySet().toArray();
    String[] names = new String[keys.length];
    System.arraycopy(keys, 0, names, 0, keys.length);

    return names;
  }

  /**
   * Method used when parsing formulas to make sure we are trying to parse a supported biff version
   *
   * @return the BOF record
   */
  public BOFRecord getWorkbookBof() {
    return workbookBof;
  }

  /**
   * Determines whether the sheet is protected
   *
   * @return whether or not the sheet is protected
   */
  public boolean isProtected() {
    return wbProtected;
  }

  /**
   * Accessor for the settings
   *
   * @return the workbook settings
   */
  public WorkbookSettings getSettings() {
    return settings;
  }

  /**
   * Accessor/implementation method for the external sheet reference
   *
   * @param sheetName the sheet name to look for
   * @return the external sheet index
   */
  public int getExternalSheetIndex(String sheetName) {
    return 0;
  }

  /**
   * Accessor/implementation method for the external sheet reference
   *
   * @param sheetName the sheet name to look for
   * @return the external sheet index
   */
  public int getLastExternalSheetIndex(String sheetName) {
    return 0;
  }

  /**
   * Gets the name at the specified index
   *
   * @param index the index into the name table
   * @return the name of the cell
   */
  public String getName(int index) {
    Assert.verify(index >= 0 && index < nameTable.size());
    return ((NameRecord) nameTable.get(index)).getName();
  }

  /**
   * Gets the index of the name record for the name
   *
   * @param name the name to search for
   * @return the index in the name table
   */
  public int getNameIndex(String name) {
    NameRecord nr = (NameRecord) namedRecords.get(name);

    return nr != null ? nr.getIndex() : 0;
  }

  /** Accessor for the drawing group */
  public DrawingGroup getDrawingGroup() {
    return drawingGroup;
  }

  /**
   * Accessor for the CompoundFile. For this feature to return non-null value, the propertySets
   * feature in WorkbookSettings must be enabled and the workbook must contain additional property
   * sets. This method is used during the workbook copy
   *
   * @return the base compound file if it contains additional data items and property sets are
   *     enabled
   */
  public CompoundFile getCompoundFile() {
    return excelFile.getCompoundFile();
  }

  /**
   * Accessor for the containsMacros
   *
   * @return TRUE if this workbook contains macros, FALSE otherwise
   */
  public boolean containsMacros() {
    return containsMacros;
  }

  /** Accessor for the button property set, used during copying */
  public ButtonPropertySetRecord getButtonPropertySet() {
    return buttonPropertySet;
  }
}