/********************************************************************* * * Copyright (C) 2002 Andrew Khan * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ***************************************************************************/ package jxl.write.biff; import jxl.common.Logger; import jxl.biff.BuiltInName; import jxl.biff.IntegerHelper; import jxl.biff.StringHelper; import jxl.biff.Type; import jxl.biff.WritableRecordData; /** * A name record. Simply takes the binary data from the name * record read in */ class NameRecord extends WritableRecordData { // The logger private static Logger logger = Logger.getLogger(NameRecord.class); /** * The binary data for output to file */ private byte[] data; /** * The name */ private String name; /** * The built in name */ private BuiltInName builtInName; /** * The index into the name table */ private int index; /** * The 0-based index sheet reference for a record name * 0 is for a global reference */ private int sheetRef = 0; /** * Modified flag */ private boolean modified; /** * A nested class to hold range information */ static class NameRange { private int columnFirst; private int rowFirst; private int columnLast; private int rowLast; private int externalSheet; NameRange(jxl.read.biff.NameRecord.NameRange nr) { columnFirst = nr.getFirstColumn(); rowFirst = nr.getFirstRow(); columnLast = nr.getLastColumn(); rowLast = nr.getLastRow(); externalSheet = nr.getExternalSheet(); } /** * Create a new range for the name record. */ NameRange(int extSheet, int theStartRow, int theEndRow, int theStartCol, int theEndCol) { columnFirst = theStartCol; rowFirst = theStartRow; columnLast = theEndCol; rowLast = theEndRow; externalSheet = extSheet; } int getFirstColumn() {return columnFirst;} int getFirstRow() {return rowFirst;} int getLastColumn() {return columnLast;} int getLastRow() {return rowLast;} int getExternalSheet() { return externalSheet;} void incrementFirstRow() { rowFirst++ ; } void incrementLastRow() { rowLast++ ; } void decrementFirstRow() { rowFirst-- ; } void decrementLastRow() { rowLast-- ; } void incrementFirstColumn() { columnFirst++ ; } void incrementLastColumn() { columnLast++ ; } void decrementFirstColumn() { columnFirst-- ; } void decrementLastColumn() { columnLast-- ; } byte[] getData() { byte[] d = new byte[10]; // Sheet index IntegerHelper.getTwoBytes(externalSheet, d, 0); // Starting row IntegerHelper.getTwoBytes(rowFirst, d, 2); // End row IntegerHelper.getTwoBytes(rowLast, d, 4); // Start column IntegerHelper.getTwoBytes(columnFirst & 0xff, d, 6); // End columns IntegerHelper.getTwoBytes(columnLast & 0xff, d, 8); return d; } } /** * The ranges covered by this name */ private NameRange[] ranges; // Constants which refer to the parse tokens after the string private static final int cellReference = 0x3a; private static final int areaReference = 0x3b; private static final int subExpression = 0x29; private static final int union = 0x10; // An empty range private static final NameRange EMPTY_RANGE = new NameRange(0,0,0,0,0); /** * Constructor - used when copying sheets * * @param index the index into the name table */ public NameRecord(jxl.read.biff.NameRecord sr, int ind) { super(Type.NAME); data = sr.getData(); name = sr.getName(); sheetRef = sr.getSheetRef(); index = ind; modified = false; // Copy the ranges jxl.read.biff.NameRecord.NameRange[] r = sr.getRanges(); ranges = new NameRange[r.length]; for (int i = 0 ; i < ranges.length ; i++) { ranges[i] = new NameRange(r[i]); } } /** * Create a new name record with the given information. * * @param theName Name to be created. * @param theIndex Index of this name. * @param extSheet External sheet index this name refers to. * @param theStartRow First row this name refers to. * @param theEndRow Last row this name refers to. * @param theStartCol First column this name refers to. * @param theEndCol Last column this name refers to. * @param global TRUE if this is a global name */ NameRecord(String theName, int theIndex, int extSheet, int theStartRow, int theEndRow, int theStartCol, int theEndCol, boolean global) { super(Type.NAME); name = theName; index = theIndex; sheetRef = global ? 0 : index+1; // 0 indicates a global name, otherwise // the 1-based index of the sheet ranges = new NameRange[1]; ranges[0] = new NameRange(extSheet, theStartRow, theEndRow, theStartCol, theEndCol); modified = true; } /** * Create a new name record with the given information. * * @param theName Name to be created. * @param theIndex Index of this name. * @param extSheet External sheet index this name refers to. * @param theStartRow First row this name refers to. * @param theEndRow Last row this name refers to. * @param theStartCol First column this name refers to. * @param theEndCol Last column this name refers to. * @param global TRUE if this is a global name */ NameRecord(BuiltInName theName, int theIndex, int extSheet, int theStartRow, int theEndRow, int theStartCol, int theEndCol, boolean global) { super(Type.NAME); builtInName = theName; index = theIndex; sheetRef = global ? 0 : index + 1; // 0 indicates a global name, otherwise // the 1-based index of the sheet ranges = new NameRange[1]; ranges[0] = new NameRange(extSheet, theStartRow, theEndRow, theStartCol, theEndCol); } /** * Create a new name record with the given information for 2-range entities. * * @param theName Name to be created. * @param theIndex Index of this name. * @param extSheet External sheet index this name refers to. * @param theStartRow First row this name refers to. * @param theEndRow Last row this name refers to. * @param theStartCol First column this name refers to. * @param theEndCol Last column this name refers to. * @param theStartRow2 First row this name refers to (2nd instance). * @param theEndRow2 Last row this name refers to (2nd instance). * @param theStartCol2 First column this name refers to (2nd instance). * @param theEndCol2 Last column this name refers to (2nd instance). * @param global TRUE if this is a global name */ NameRecord(BuiltInName theName, int theIndex, int extSheet, int theStartRow, int theEndRow, int theStartCol, int theEndCol, int theStartRow2, int theEndRow2, int theStartCol2, int theEndCol2, boolean global) { super(Type.NAME); builtInName = theName; index = theIndex; sheetRef = global ? 0 : index + 1; // 0 indicates a global name, otherwise // the 1-based index of the sheet ranges = new NameRange[2]; ranges[0] = new NameRange(extSheet, theStartRow, theEndRow, theStartCol, theEndCol); ranges[1] = new NameRange(extSheet, theStartRow2, theEndRow2, theStartCol2, theEndCol2); } /** * Gets the binary data for output to file * * @return the binary data */ public byte[] getData() { if (data != null && !modified) { // this is a copy return data; } final int NAME_HEADER_LENGTH = 15; final byte AREA_RANGE_LENGTH = 11; final byte AREA_REFERENCE = 0x3b; int detailLength; if (ranges.length > 1) { detailLength = (ranges.length * AREA_RANGE_LENGTH) + 4; } else { detailLength = AREA_RANGE_LENGTH; } int length = NAME_HEADER_LENGTH + detailLength; length += builtInName != null ? 1 : name.length(); data = new byte[length]; // Options int options = 0; if (builtInName != null) { options |= 0x20; } IntegerHelper.getTwoBytes(options, data, 0); // Keyboard shortcut data[2] = 0; // Length of the name in chars if (builtInName != null) { data[3] = (byte) 0x1; } else { data[3] = (byte) name.length(); } // Size of the definitions IntegerHelper.getTwoBytes(detailLength, data, 4); // Sheet index IntegerHelper.getTwoBytes(sheetRef, data, 6); IntegerHelper.getTwoBytes(sheetRef, data, 8); // Byte 10-13 are optional lengths [0,0,0,0] // Byte 14 is length of name which is not used. // The name if (builtInName != null) { data[15] = (byte) builtInName.getValue(); } else { StringHelper.getBytes(name, data, 15); } // The actual range definition. int pos = builtInName != null ? 16 : name.length() + 15; // If there are multiple ranges for the name, we must specify a // subExpression type rather than areaReference and then put out // multiple areaReference entries with an end byte. if (ranges.length > 1) { data[pos++] = subExpression; // Length of remaining bytes excluding itself IntegerHelper.getTwoBytes(detailLength - 3, data, pos); pos += 2; byte[] rd; for (int i = 0 ; i < ranges.length ; i++) { data[pos++] = areaReference; rd = ranges[i].getData(); System.arraycopy(rd, 0, data, pos, rd.length); pos += rd.length; } data[pos] = 0x10; } else { // Range format - area data[pos] = areaReference; // The range data byte[] rd = ranges[0].getData(); System.arraycopy(rd, 0, data, pos+1, rd.length); } return data; } /** * Accessor for the name * * @return the name */ public String getName() { return name; } /** * Accessor for the index of this name in the name table * * @return the index of this name in the name table */ public int getIndex() { return index; } /** * The 0-based index sheet reference for a record name * 0 is for a global reference * * @return the sheet reference for name formula */ public int getSheetRef() { return sheetRef; } /** * Set the index sheet reference for a record name * 0 is for a global reference * */ public void setSheetRef(int i) { sheetRef = i; IntegerHelper.getTwoBytes(sheetRef, data, 8); } /** * Gets the array of ranges for this name * @return the ranges */ public NameRange[] getRanges() { return ranges; } /** * Called when a row is inserted on the * * @param sheetIndex the sheet index on which the column was inserted * @param row the column number which was inserted */ void rowInserted(int sheetIndex, int row) { for (int i = 0 ; i < ranges.length ; i++) { if (sheetIndex != ranges[i].getExternalSheet()) { continue; // shame on me - this is no better than a goto } if (row <= ranges[i].getFirstRow()) { ranges[i].incrementFirstRow(); modified = true; } if (row <= ranges[i].getLastRow()) { ranges[i].incrementLastRow(); modified = true; } } } /** * Called when a row is removed on the worksheet * * @param sheetIndex the sheet index on which the column was inserted * @param row the column number which was inserted * @reeturn TRUE if the name is to be removed entirely, FALSE otherwise */ boolean rowRemoved(int sheetIndex, int row) { for (int i = 0 ; i < ranges.length ; i++) { if (sheetIndex != ranges[i].getExternalSheet()) { continue; // shame on me - this is no better than a goto } if (row == ranges[i].getFirstRow() && row == ranges[i].getLastRow()) { // remove the range ranges[i] = EMPTY_RANGE; } if (row < ranges[i].getFirstRow() && row > 0) { ranges[i].decrementFirstRow(); modified = true; } if (row <= ranges[i].getLastRow()) { ranges[i].decrementLastRow(); modified = true; } } // If all ranges are empty, then remove the name int emptyRanges = 0; for (int i = 0 ; i < ranges.length; i++) { if (ranges[i] == EMPTY_RANGE) { emptyRanges++; } } if (emptyRanges == ranges.length) { return true; } // otherwise just remove the empty ones NameRange[] newRanges = new NameRange[ranges.length - emptyRanges]; for (int i = 0 ; i < ranges.length ; i++) { if (ranges[i] != EMPTY_RANGE) { newRanges[i] = ranges[i]; } } ranges = newRanges; return false; } /** * Called when a row is removed on the worksheet * * @param sheetIndex the sheet index on which the column was inserted * @param row the column number which was inserted * @reeturn TRUE if the name is to be removed entirely, FALSE otherwise */ boolean columnRemoved(int sheetIndex, int col) { for (int i = 0 ; i < ranges.length ; i++) { if (sheetIndex != ranges[i].getExternalSheet()) { continue; // shame on me - this is no better than a goto } if (col == ranges[i].getFirstColumn() && col == ranges[i].getLastColumn()) { // remove the range ranges[i] = EMPTY_RANGE; } if (col < ranges[i].getFirstColumn() && col > 0) { ranges[i].decrementFirstColumn(); modified = true; } if (col <= ranges[i].getLastColumn()) { ranges[i].decrementLastColumn(); modified = true; } } // If all ranges are empty, then remove the name int emptyRanges = 0; for (int i = 0 ; i < ranges.length; i++) { if (ranges[i] == EMPTY_RANGE) { emptyRanges++; } } if (emptyRanges == ranges.length) { return true; } // otherwise just remove the empty ones NameRange[] newRanges = new NameRange[ranges.length - emptyRanges]; for (int i = 0 ; i < ranges.length ; i++) { if (ranges[i] != EMPTY_RANGE) { newRanges[i] = ranges[i]; } } ranges = newRanges; return false; } /** * Called when a row is inserted on the * * @param sheetIndex the sheet index on which the column was inserted * @param col the column number which was inserted */ void columnInserted(int sheetIndex, int col) { for (int i = 0 ; i < ranges.length ; i++) { if (sheetIndex != ranges[i].getExternalSheet()) { continue; // shame on me - this is no better than a goto } if (col <= ranges[i].getFirstColumn()) { ranges[i].incrementFirstColumn(); modified = true; } if (col <= ranges[i].getLastColumn()) { ranges[i].incrementLastColumn(); modified = true; } } } }