2002-05-04 11:45:05 -04:00
|
|
|
/* ====================================================================
|
2006-12-22 14:18:16 -05:00
|
|
|
Licensed to the Apache Software Foundation (ASF) under one or more
|
|
|
|
contributor license agreements. See the NOTICE file distributed with
|
|
|
|
this work for additional information regarding copyright ownership.
|
|
|
|
The ASF licenses this file to You under the Apache License, Version 2.0
|
|
|
|
(the "License"); you may not use this file except in compliance with
|
|
|
|
the License. You may obtain a copy of the License at
|
2004-04-09 09:05:39 -04:00
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
==================================================================== */
|
2004-08-23 04:52:54 -04:00
|
|
|
|
2002-05-04 11:45:05 -04:00
|
|
|
|
|
|
|
package org.apache.poi.hssf.util;
|
|
|
|
|
2008-01-08 05:36:36 -05:00
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.StringTokenizer;
|
|
|
|
|
2008-02-15 07:04:42 -05:00
|
|
|
public final class AreaReference {
|
|
|
|
|
|
|
|
/** The character (!) that separates sheet names from cell references */
|
|
|
|
private static final char SHEET_NAME_DELIMITER = '!';
|
|
|
|
/** The character (:) that separates the two cell references in a multi-cell area reference */
|
|
|
|
private static final char CELL_DELIMITER = ':';
|
|
|
|
/** The character (') used to quote sheet names when they contain special characters */
|
|
|
|
private static final char SPECIAL_NAME_DELIMITER = '\'';
|
|
|
|
|
|
|
|
private final CellReference _firstCell;
|
|
|
|
private final CellReference _lastCell;
|
|
|
|
private final boolean _isSingleCell;
|
2002-05-04 11:45:05 -04:00
|
|
|
|
2008-01-08 05:36:36 -05:00
|
|
|
/**
|
2008-02-15 07:04:42 -05:00
|
|
|
* Create an area ref from a string representation. Sheet names containing special characters should be
|
|
|
|
* delimited and escaped as per normal syntax rules for formulas.<br/>
|
|
|
|
* The area reference must be contiguous (i.e. represent a single rectangle, not a union of rectangles)
|
2002-05-04 11:45:05 -04:00
|
|
|
*/
|
|
|
|
public AreaReference(String reference) {
|
2008-01-08 05:36:36 -05:00
|
|
|
if(! isContiguous(reference)) {
|
2008-02-15 07:04:42 -05:00
|
|
|
throw new IllegalArgumentException(
|
|
|
|
"References passed to the AreaReference must be contiguous, " +
|
|
|
|
"use generateContiguous(ref) if you have non-contiguous references");
|
2008-01-08 05:36:36 -05:00
|
|
|
}
|
|
|
|
|
2008-02-15 07:04:42 -05:00
|
|
|
String[] parts = separateAreaRefs(reference);
|
|
|
|
_firstCell = new CellReference(parts[0]);
|
|
|
|
|
|
|
|
if(parts.length == 2) {
|
|
|
|
_lastCell = new CellReference(parts[1]);
|
|
|
|
_isSingleCell = false;
|
|
|
|
} else {
|
|
|
|
_lastCell = _firstCell;
|
|
|
|
_isSingleCell = true;
|
2002-05-04 11:45:05 -04:00
|
|
|
}
|
|
|
|
}
|
2008-01-08 05:36:36 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Is the reference for a contiguous (i.e.
|
|
|
|
* unbroken) area, or is it made up of
|
|
|
|
* several different parts?
|
|
|
|
* (If it is, you will need to call
|
|
|
|
* ....
|
|
|
|
*/
|
|
|
|
public static boolean isContiguous(String reference) {
|
|
|
|
if(reference.indexOf(',') == -1) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Takes a non-contiguous area reference, and
|
|
|
|
* returns an array of contiguous area references.
|
|
|
|
*/
|
|
|
|
public static AreaReference[] generateContiguous(String reference) {
|
|
|
|
ArrayList refs = new ArrayList();
|
|
|
|
StringTokenizer st = new StringTokenizer(reference, ",");
|
|
|
|
while(st.hasMoreTokens()) {
|
|
|
|
refs.add(
|
|
|
|
new AreaReference(st.nextToken())
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return (AreaReference[])refs.toArray(new AreaReference[refs.size()]);
|
|
|
|
}
|
|
|
|
|
2008-02-15 07:04:42 -05:00
|
|
|
/**
|
|
|
|
* @return <code>false</code> if this area reference involves more than one cell
|
|
|
|
*/
|
|
|
|
public boolean isSingleCell() {
|
|
|
|
return _isSingleCell;
|
2002-05-04 11:45:05 -04:00
|
|
|
}
|
2008-02-15 07:04:42 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @return the first cell reference which defines this area. Usually this cell is in the upper
|
|
|
|
* left corner of the area (but this is not a requirement).
|
2008-01-08 11:20:48 -05:00
|
|
|
*/
|
2008-02-15 07:04:42 -05:00
|
|
|
public CellReference getFirstCell() {
|
|
|
|
return _firstCell;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Note - if this area reference refers to a single cell, the return value of this method will
|
|
|
|
* be identical to that of <tt>getFirstCell()</tt>
|
|
|
|
* @return the second cell reference which defines this area. For multi-cell areas, this is
|
|
|
|
* cell diagonally opposite the 'first cell'. Usually this cell is in the lower right corner
|
|
|
|
* of the area (but this is not a requirement).
|
|
|
|
*/
|
|
|
|
public CellReference getLastCell() {
|
|
|
|
return _lastCell;
|
2002-05-05 12:55:41 -04:00
|
|
|
}
|
2008-01-08 11:20:48 -05:00
|
|
|
/**
|
|
|
|
* Returns a reference to every cell covered by this area
|
|
|
|
*/
|
|
|
|
public CellReference[] getAllReferencedCells() {
|
|
|
|
// Special case for single cell reference
|
2008-02-15 07:04:42 -05:00
|
|
|
if(_isSingleCell) {
|
|
|
|
return new CellReference[] { _firstCell, };
|
2008-01-08 11:20:48 -05:00
|
|
|
}
|
2008-02-15 07:04:42 -05:00
|
|
|
|
2008-01-08 11:20:48 -05:00
|
|
|
// Interpolate between the two
|
2008-02-15 07:04:42 -05:00
|
|
|
int minRow = Math.min(_firstCell.getRow(), _lastCell.getRow());
|
|
|
|
int maxRow = Math.max(_firstCell.getRow(), _lastCell.getRow());
|
|
|
|
int minCol = Math.min(_firstCell.getCol(), _lastCell.getCol());
|
|
|
|
int maxCol = Math.max(_firstCell.getCol(), _lastCell.getCol());
|
|
|
|
String sheetName = _firstCell.getSheetName();
|
2008-01-08 11:20:48 -05:00
|
|
|
|
|
|
|
ArrayList refs = new ArrayList();
|
|
|
|
for(int row=minRow; row<=maxRow; row++) {
|
|
|
|
for(int col=minCol; col<=maxCol; col++) {
|
2008-02-15 07:04:42 -05:00
|
|
|
CellReference ref = new CellReference(sheetName, row, col, _firstCell.isRowAbsolute(), _firstCell.isColAbsolute());
|
2008-01-08 11:20:48 -05:00
|
|
|
refs.add(ref);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return (CellReference[])refs.toArray(new CellReference[refs.size()]);
|
|
|
|
}
|
2003-05-17 13:53:38 -04:00
|
|
|
|
2008-02-15 07:04:42 -05:00
|
|
|
/**
|
|
|
|
* Example return values:
|
|
|
|
* <table border="0" cellpadding="1" cellspacing="0" summary="Example return values">
|
|
|
|
* <tr><th align='left'>Result</th><th align='left'>Comment</th></tr>
|
|
|
|
* <tr><td>A1:A1</td><td>Single cell area reference without sheet</td></tr>
|
|
|
|
* <tr><td>A1:$C$1</td><td>Multi-cell area reference without sheet</td></tr>
|
|
|
|
* <tr><td>Sheet1!A$1:B4</td><td>Standard sheet name</td></tr>
|
|
|
|
* <tr><td>'O''Brien''s Sales'!B5:C6' </td><td>Sheet name with special characters</td></tr>
|
|
|
|
* </table>
|
|
|
|
* @return the text representation of this area reference as it would appear in a formula.
|
|
|
|
*/
|
|
|
|
public String formatAsString() {
|
|
|
|
StringBuffer sb = new StringBuffer(32);
|
|
|
|
sb.append(_firstCell.formatAsString());
|
|
|
|
if(!_isSingleCell) {
|
|
|
|
sb.append(CELL_DELIMITER);
|
|
|
|
if(_lastCell.getSheetName() == null) {
|
|
|
|
sb.append(_lastCell.formatAsString());
|
|
|
|
} else {
|
|
|
|
// don't want to include the sheet name twice
|
|
|
|
_lastCell.appendCellReference(sb);
|
|
|
|
}
|
2002-05-04 11:45:05 -04:00
|
|
|
}
|
2008-02-15 07:04:42 -05:00
|
|
|
return sb.toString();
|
|
|
|
}
|
|
|
|
public String toString() {
|
|
|
|
StringBuffer sb = new StringBuffer(64);
|
|
|
|
sb.append(getClass().getName()).append(" [");
|
|
|
|
sb.append(formatAsString());
|
|
|
|
sb.append("]");
|
|
|
|
return sb.toString();
|
2002-05-04 11:45:05 -04:00
|
|
|
}
|
2003-05-17 13:53:38 -04:00
|
|
|
|
2002-05-04 11:45:05 -04:00
|
|
|
/**
|
2008-02-15 07:04:42 -05:00
|
|
|
* Separates Area refs in two parts and returns them as separate elements in a String array,
|
|
|
|
* each qualified with the sheet name (if present)
|
|
|
|
*
|
|
|
|
* @return array with one or two elements. never <code>null</code>
|
2002-05-04 11:45:05 -04:00
|
|
|
*/
|
2008-02-15 07:04:42 -05:00
|
|
|
private static String[] separateAreaRefs(String reference) {
|
|
|
|
// TODO - refactor cell reference parsing logic to one place.
|
|
|
|
// Current known incarnations:
|
|
|
|
// FormulaParser.GetName()
|
|
|
|
// CellReference.separateRefParts()
|
|
|
|
// AreaReference.separateAreaRefs() (here)
|
|
|
|
// SheetNameFormatter.format() (inverse)
|
|
|
|
|
|
|
|
|
|
|
|
int len = reference.length();
|
|
|
|
int delimiterPos = -1;
|
|
|
|
boolean insideDelimitedName = false;
|
|
|
|
for(int i=0; i<len; i++) {
|
|
|
|
switch(reference.charAt(i)) {
|
|
|
|
case CELL_DELIMITER:
|
|
|
|
if(!insideDelimitedName) {
|
|
|
|
if(delimiterPos >=0) {
|
|
|
|
throw new IllegalArgumentException("More than one cell delimiter '"
|
|
|
|
+ CELL_DELIMITER + "' appears in area reference '" + reference + "'");
|
|
|
|
}
|
|
|
|
delimiterPos = i;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
continue;
|
|
|
|
case SPECIAL_NAME_DELIMITER:
|
|
|
|
// fall through
|
|
|
|
}
|
|
|
|
if(!insideDelimitedName) {
|
|
|
|
insideDelimitedName = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(i >= len-1) {
|
|
|
|
// reference ends with the delimited name.
|
|
|
|
// Assume names like: "Sheet1!'A1'" are never legal.
|
|
|
|
throw new IllegalArgumentException("Area reference '" + reference
|
|
|
|
+ "' ends with special name delimiter '" + SPECIAL_NAME_DELIMITER + "'");
|
|
|
|
}
|
|
|
|
if(reference.charAt(i+1) == SPECIAL_NAME_DELIMITER) {
|
|
|
|
// two consecutive quotes is the escape sequence for a single one
|
|
|
|
i++; // skip this and keep parsing the special name
|
|
|
|
} else {
|
|
|
|
// this is the end of the delimited name
|
|
|
|
insideDelimitedName = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(delimiterPos < 0) {
|
|
|
|
return new String[] { reference, };
|
2003-05-17 13:53:38 -04:00
|
|
|
}
|
|
|
|
|
2008-02-15 07:04:42 -05:00
|
|
|
String partA = reference.substring(0, delimiterPos);
|
|
|
|
String partB = reference.substring(delimiterPos+1);
|
|
|
|
if(partB.indexOf(SHEET_NAME_DELIMITER) >=0) {
|
|
|
|
// TODO - are references like "Sheet1!A1:Sheet1:B2" ever valid?
|
|
|
|
// FormulaParser has code to handle that.
|
|
|
|
|
|
|
|
throw new RuntimeException("Unexpected " + SHEET_NAME_DELIMITER
|
|
|
|
+ " in second cell reference of '" + reference + "'");
|
|
|
|
}
|
|
|
|
|
|
|
|
int plingPos = partA.lastIndexOf(SHEET_NAME_DELIMITER);
|
|
|
|
if(plingPos < 0) {
|
|
|
|
return new String [] { partA, partB, };
|
2003-05-17 13:53:38 -04:00
|
|
|
}
|
2008-02-15 07:04:42 -05:00
|
|
|
|
|
|
|
String sheetName = partA.substring(0, plingPos + 1); // +1 to include delimiter
|
|
|
|
|
|
|
|
return new String [] { partA, sheetName + partB, };
|
2002-05-04 11:45:05 -04:00
|
|
|
}
|
2004-08-23 04:52:54 -04:00
|
|
|
}
|