package org.apache.poi.hssf.model;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.poi.hssf.record.CRNCountRecord;
import org.apache.poi.hssf.record.CRNRecord;
import org.apache.poi.hssf.record.CountryRecord;
import org.apache.poi.hssf.record.ExternSheetRecord;
import org.apache.poi.hssf.record.ExternalNameRecord;
import org.apache.poi.hssf.record.NameCommentRecord;
import org.apache.poi.hssf.record.NameRecord;
import org.apache.poi.hssf.record.Record;
import org.apache.poi.hssf.record.SupBookRecord;
import org.apache.poi.ss.formula.SheetNameFormatter;
import org.apache.poi.ss.formula.ptg.Area3DPtg;
import org.apache.poi.ss.formula.ptg.ErrPtg;
import org.apache.poi.ss.formula.ptg.NameXPtg;
import org.apache.poi.ss.formula.ptg.Ptg;
import org.apache.poi.ss.formula.ptg.Ref3DPtg;
import org.apache.poi.ss.usermodel.Workbook;
final class LinkTable {
private static final class CRNBlock {
private final CRNCountRecord _countRecord;
private final CRNRecord[] _crns;
public CRNBlock(RecordStream rs) {
_countRecord = (CRNCountRecord) rs.getNext();
int nCRNs = _countRecord.getNumberOfCRNs();
CRNRecord[] crns = new CRNRecord[nCRNs];
for (int i = 0; i < crns.length; i++) {
crns[i] = (CRNRecord) rs.getNext();
}
_crns = crns;
}
public CRNRecord[] getCrns() {
return _crns.clone();
}
}
private static final class ExternalBookBlock {
private final SupBookRecord _externalBookRecord;
private ExternalNameRecord[] _externalNameRecords;
private final CRNBlock[] _crnBlocks;
public ExternalBookBlock(RecordStream rs) {
_externalBookRecord = (SupBookRecord) rs.getNext();
List<Object> temp = new ArrayList<>();
while (rs.peekNextClass() == ExternalNameRecord.class) {
temp.add(rs.getNext());
}
_externalNameRecords = new ExternalNameRecord[temp.size()];
temp.toArray(_externalNameRecords);
temp.clear();
while (rs.peekNextClass() == CRNCountRecord.class) {
temp.add(new CRNBlock(rs));
}
_crnBlocks = new CRNBlock[temp.size()];
temp.toArray(_crnBlocks);
}
public ExternalBookBlock(String url, String[] sheetNames) {
_externalBookRecord = SupBookRecord.createExternalReferences(url, sheetNames);
_crnBlocks = new CRNBlock[0];
}
public ExternalBookBlock(int numberOfSheets) {
_externalBookRecord = SupBookRecord.createInternalReferences((short) numberOfSheets);
_externalNameRecords = new ExternalNameRecord[0];
_crnBlocks = new CRNBlock[0];
}
public ExternalBookBlock() {
_externalBookRecord = SupBookRecord.createAddInFunctions();
_externalNameRecords = new ExternalNameRecord[0];
_crnBlocks = new CRNBlock[0];
}
public SupBookRecord getExternalBookRecord() {
return _externalBookRecord;
}
public String getNameText(int definedNameIndex) {
return _externalNameRecords[definedNameIndex].getText();
}
public int getNameIx(int definedNameIndex) {
return _externalNameRecords[definedNameIndex].getIx();
}
public int getIndexOfName(String name) {
for (int i = 0; i < _externalNameRecords.length; i++) {
if (_externalNameRecords[i].getText().equalsIgnoreCase(name)) {
return i;
}
}
return -1;
}
public int getNumberOfNames() {
return _externalNameRecords.length;
}
public int addExternalName(ExternalNameRecord rec) {
ExternalNameRecord[] tmp = new ExternalNameRecord[_externalNameRecords.length + 1];
System.arraycopy(_externalNameRecords, 0, tmp, 0, _externalNameRecords.length);
tmp[tmp.length - 1] = rec;
_externalNameRecords = tmp;
return _externalNameRecords.length - 1;
}
}
private ExternalBookBlock[] _externalBookBlocks;
private final ExternSheetRecord _externSheetRecord;
private final List<NameRecord> _definedNames;
private final int _recordCount;
private final WorkbookRecordList _workbookRecordList;
public LinkTable(List<Record> inputList, int startIndex, WorkbookRecordList workbookRecordList, Map<String, NameCommentRecord> commentRecords) {
_workbookRecordList = workbookRecordList;
RecordStream rs = new RecordStream(inputList, startIndex);
List<ExternalBookBlock> temp = new ArrayList<>();
while (rs.peekNextClass() == SupBookRecord.class) {
temp.add(new ExternalBookBlock(rs));
}
_externalBookBlocks = new ExternalBookBlock[temp.size()];
temp.toArray(_externalBookBlocks);
temp.clear();
if (_externalBookBlocks.length > 0) {
if (rs.peekNextClass() != ExternSheetRecord.class) {
_externSheetRecord = null;
} else {
_externSheetRecord = readExtSheetRecord(rs);
}
} else {
_externSheetRecord = null;
}
_definedNames = new ArrayList<>();
while (true) {
Class<? extends Record> nextClass = rs.peekNextClass();
if (nextClass == NameRecord.class) {
NameRecord nr = (NameRecord) rs.getNext();
_definedNames.add(nr);
} else if (nextClass == NameCommentRecord.class) {
NameCommentRecord ncr = (NameCommentRecord) rs.getNext();
commentRecords.put(ncr.getNameText(), ncr);
} else {
break;
}
}
_recordCount = rs.getCountRead();
_workbookRecordList.getRecords().addAll(inputList.subList(startIndex, startIndex + _recordCount));
}
private static ExternSheetRecord readExtSheetRecord(RecordStream rs) {
List<ExternSheetRecord> temp = new ArrayList<>(2);
while (rs.peekNextClass() == ExternSheetRecord.class) {
temp.add((ExternSheetRecord) rs.getNext());
}
int nItems = temp.size();
if (nItems < 1) {
throw new RuntimeException("Expected an EXTERNSHEET record but got ("
+ rs.peekNextClass().getName() + ")");
}
if (nItems == 1) {
return temp.get(0);
}
ExternSheetRecord[] esrs = new ExternSheetRecord[nItems];
temp.toArray(esrs);
return ExternSheetRecord.combine(esrs);
}
public LinkTable(int numberOfSheets, WorkbookRecordList workbookRecordList) {
_workbookRecordList = workbookRecordList;
_definedNames = new ArrayList<>();
_externalBookBlocks = new ExternalBookBlock[]{
new ExternalBookBlock(numberOfSheets),
};
_externSheetRecord = new ExternSheetRecord();
_recordCount = 2;
SupBookRecord supbook = _externalBookBlocks[0].getExternalBookRecord();
int idx = findFirstRecordLocBySid(CountryRecord.sid);
if (idx < 0) {
throw new RuntimeException("CountryRecord not found");
}
_workbookRecordList.add(idx + 1, _externSheetRecord);
_workbookRecordList.add(idx + 1, supbook);
}
public int getRecordCount() {
return _recordCount;
}
public NameRecord getSpecificBuiltinRecord(byte builtInCode, int sheetNumber) {
Iterator<NameRecord> iterator = _definedNames.iterator();
while (iterator.hasNext()) {
NameRecord record = iterator.next();
if (record.getBuiltInName() == builtInCode && record.getSheetNumber() == sheetNumber) {
return record;
}
}
return null;
}
public void removeBuiltinRecord(byte name, int sheetIndex) {
NameRecord record = getSpecificBuiltinRecord(name, sheetIndex);
if (record != null) {
_definedNames.remove(record);
}
}
public int getNumNames() {
return _definedNames.size();
}
public NameRecord getNameRecord(int index) {
return _definedNames.get(index);
}
public void addName(NameRecord name) {
_definedNames.add(name);
int idx = findFirstRecordLocBySid(ExternSheetRecord.sid);
if (idx == -1) idx = findFirstRecordLocBySid(SupBookRecord.sid);
if (idx == -1) idx = findFirstRecordLocBySid(CountryRecord.sid);
int countNames = _definedNames.size();
_workbookRecordList.add(idx + countNames, name);
}
public void removeName(int namenum) {
_definedNames.remove(namenum);
}
public boolean nameAlreadyExists(NameRecord name) {
for (int i = getNumNames() - 1; i >= 0; i--) {
NameRecord rec = getNameRecord(i);
if (rec != name) {
if (isDuplicatedNames(name, rec))
return true;
}
}
return false;
}
private static boolean isDuplicatedNames(NameRecord firstName, NameRecord lastName) {
return lastName.getNameText().equalsIgnoreCase(firstName.getNameText())
&& isSameSheetNames(firstName, lastName);
}
private static boolean isSameSheetNames(NameRecord firstName, NameRecord lastName) {
return lastName.getSheetNumber() == firstName.getSheetNumber();
}
public String[] getExternalBookAndSheetName(int extRefIndex) {
int ebIx = _externSheetRecord.getExtbookIndexFromRefIndex(extRefIndex);
SupBookRecord ebr = _externalBookBlocks[ebIx].getExternalBookRecord();
if (!ebr.isExternalReferences()) {
return null;
}
int shIx1 = _externSheetRecord.getFirstSheetIndexFromRefIndex(extRefIndex);
int shIx2 = _externSheetRecord.getLastSheetIndexFromRefIndex(extRefIndex);
String firstSheetName = null;
String lastSheetName = null;
if (shIx1 >= 0) {
firstSheetName = ebr.getSheetNames()[shIx1];
}
if (shIx2 >= 0) {
lastSheetName = ebr.getSheetNames()[shIx2];
}
if (shIx1 == shIx2) {
return new String[]{
ebr.getURL(),
firstSheetName
};
} else {
return new String[]{
ebr.getURL(),
firstSheetName,
lastSheetName
};
}
}
private int getExternalWorkbookIndex(String workbookName) {
for (int i = 0; i < _externalBookBlocks.length; i++) {
SupBookRecord ebr = _externalBookBlocks[i].getExternalBookRecord();
if (!ebr.isExternalReferences()) {
continue;
}
if (workbookName.equals(ebr.getURL())) {
return i;
}
}
return -1;
}
public int linkExternalWorkbook(String name, Workbook externalWorkbook) {
int extBookIndex = getExternalWorkbookIndex(name);
if (extBookIndex != -1) {
return extBookIndex;
}
String[] sheetNames = new String[externalWorkbook.getNumberOfSheets()];
for (int sn = 0; sn < sheetNames.length; sn++) {
sheetNames[sn] = externalWorkbook.getSheetName(sn);
}
String url = "\000" + name;
ExternalBookBlock block = new ExternalBookBlock(url, sheetNames);
extBookIndex = extendExternalBookBlocks(block);
int idx = findFirstRecordLocBySid(ExternSheetRecord.sid);
if (idx == -1) {
idx = _workbookRecordList.size();
}
_workbookRecordList.add(idx, block.getExternalBookRecord());
for (int sn = 0; sn < sheetNames.length; sn++) {
_externSheetRecord.addRef(extBookIndex, sn, sn);
}
return extBookIndex;
}
public int getExternalSheetIndex(String workbookName, String firstSheetName, String lastSheetName) {
int externalBookIndex = getExternalWorkbookIndex(workbookName);
if (externalBookIndex == -1) {
throw new RuntimeException("No external workbook with name '" + workbookName + "'");
}
SupBookRecord ebrTarget = _externalBookBlocks[externalBookIndex].getExternalBookRecord();
int firstSheetIndex = getSheetIndex(ebrTarget.getSheetNames(), firstSheetName);
int lastSheetIndex = getSheetIndex(ebrTarget.getSheetNames(), lastSheetName);
int result = _externSheetRecord.getRefIxForSheet(externalBookIndex, firstSheetIndex, lastSheetIndex);
if (result < 0) {
result = _externSheetRecord.addRef(externalBookIndex, firstSheetIndex, lastSheetIndex);
}
return result;
}
private static int getSheetIndex(String[] sheetNames, String sheetName) {
for (int i = 0; i < sheetNames.length; i++) {
if (sheetNames[i].equals(sheetName)) {
return i;
}
}
throw new RuntimeException("External workbook does not contain sheet '" + sheetName + "'");
}
public int getFirstInternalSheetIndexForExtIndex(int extRefIndex) {
if (extRefIndex >= _externSheetRecord.getNumOfRefs() || extRefIndex < 0) {
return -1;
}
return _externSheetRecord.getFirstSheetIndexFromRefIndex(extRefIndex);
}
public int getLastInternalSheetIndexForExtIndex(int extRefIndex) {
if (extRefIndex >= _externSheetRecord.getNumOfRefs() || extRefIndex < 0) {
return -1;
}
return _externSheetRecord.getLastSheetIndexFromRefIndex(extRefIndex);
}
public void removeSheet(int sheetIdx) {
_externSheetRecord.removeSheet(sheetIdx);
}
public int checkExternSheet(int sheetIndex) {
return checkExternSheet(sheetIndex, sheetIndex);
}
public int checkExternSheet(int firstSheetIndex, int lastSheetIndex) {
int thisWbIndex = -1;
for (int i = 0; i < _externalBookBlocks.length; i++) {
SupBookRecord ebr = _externalBookBlocks[i].getExternalBookRecord();
if (ebr.isInternalReferences()) {
thisWbIndex = i;
break;
}
}
if (thisWbIndex < 0) {
throw new RuntimeException("Could not find 'internal references' EXTERNALBOOK");
}
int i = _externSheetRecord.getRefIxForSheet(thisWbIndex, firstSheetIndex, lastSheetIndex);
if (i >= 0) {
return i;
}
return _externSheetRecord.addRef(thisWbIndex, firstSheetIndex, lastSheetIndex);
}
private int findFirstRecordLocBySid(short sid) {
int index = 0;
for (Record record : _workbookRecordList.getRecords()) {
if (record.getSid() == sid) {
return index;
}
index++;
}
return -1;
}
public String resolveNameXText(int refIndex, int definedNameIndex, InternalWorkbook workbook) {
int extBookIndex = _externSheetRecord.getExtbookIndexFromRefIndex(refIndex);
int firstTabIndex = _externSheetRecord.getFirstSheetIndexFromRefIndex(refIndex);
if (firstTabIndex == -1) {
throw new RuntimeException("Referenced sheet could not be found");
}
ExternalBookBlock externalBook = _externalBookBlocks[extBookIndex];
if (externalBook._externalNameRecords.length > definedNameIndex) {
return _externalBookBlocks[extBookIndex].getNameText(definedNameIndex);
} else if (firstTabIndex == -2) {
NameRecord nr = getNameRecord(definedNameIndex);
int sheetNumber = nr.getSheetNumber();
StringBuilder text = new StringBuilder(64);
if (sheetNumber > 0) {
String sheetName = workbook.getSheetName(sheetNumber - 1);
SheetNameFormatter.appendFormat(text, sheetName);
text.append("!");
}
text.append(nr.getNameText());
return text.toString();
} else {
throw new ArrayIndexOutOfBoundsException(
"Ext Book Index relative but beyond the supported length, was " +
extBookIndex + " but maximum is " + _externalBookBlocks.length
);
}
}
public int resolveNameXIx(int refIndex, int definedNameIndex) {
int extBookIndex = _externSheetRecord.getExtbookIndexFromRefIndex(refIndex);
return _externalBookBlocks[extBookIndex].getNameIx(definedNameIndex);
}
public NameXPtg getNameXPtg(String name, int sheetRefIndex) {
for (int i = 0; i < _externalBookBlocks.length; i++) {
int definedNameIndex = _externalBookBlocks[i].getIndexOfName(name);
if (definedNameIndex < 0) {
continue;
}
int thisSheetRefIndex = findRefIndexFromExtBookIndex(i);
if (thisSheetRefIndex >= 0) {
if (sheetRefIndex == -1 || thisSheetRefIndex == sheetRefIndex) {
return new NameXPtg(thisSheetRefIndex, definedNameIndex);
}
}
}
return null;
}
public NameXPtg addNameXPtg(String name) {
int extBlockIndex = -1;
ExternalBookBlock extBlock = null;
for (int i = 0; i < _externalBookBlocks.length; i++) {
SupBookRecord ebr = _externalBookBlocks[i].getExternalBookRecord();
if (ebr.isAddInFunctions()) {
extBlock = _externalBookBlocks[i];
extBlockIndex = i;
break;
}
}
if (extBlock == null) {
extBlock = new ExternalBookBlock();
extBlockIndex = extendExternalBookBlocks(extBlock);
int idx = findFirstRecordLocBySid(ExternSheetRecord.sid);
_workbookRecordList.add(idx, extBlock.getExternalBookRecord());
_externSheetRecord.addRef(_externalBookBlocks.length - 1, -2, -2);
}
ExternalNameRecord extNameRecord = new ExternalNameRecord();
extNameRecord.setText(name);
extNameRecord.setParsedExpression(new Ptg[]{ErrPtg.REF_INVALID});
int nameIndex = extBlock.addExternalName(extNameRecord);
int supLinkIndex = 0;
for (Record record : _workbookRecordList.getRecords()) {
if (record instanceof SupBookRecord && ((SupBookRecord) record).isAddInFunctions()) {
break;
}
supLinkIndex++;
}
int numberOfNames = extBlock.getNumberOfNames();
_workbookRecordList.add(supLinkIndex + numberOfNames, extNameRecord);
int fakeSheetIdx = -2;
int ix = _externSheetRecord.getRefIxForSheet(extBlockIndex, fakeSheetIdx, fakeSheetIdx);
return new NameXPtg(ix, nameIndex);
}
private int extendExternalBookBlocks(ExternalBookBlock newBlock) {
ExternalBookBlock[] tmp = new ExternalBookBlock[_externalBookBlocks.length + 1];
System.arraycopy(_externalBookBlocks, 0, tmp, 0, _externalBookBlocks.length);
tmp[tmp.length - 1] = newBlock;
_externalBookBlocks = tmp;
return (_externalBookBlocks.length - 1);
}
private int findRefIndexFromExtBookIndex(int extBookIndex) {
return _externSheetRecord.findRefIndexFromExtBookIndex(extBookIndex);
}
public boolean changeExternalReference(String oldUrl, String newUrl) {
for (ExternalBookBlock ex : _externalBookBlocks) {
SupBookRecord externalRecord = ex.getExternalBookRecord();
if (externalRecord.isExternalReferences()
&& externalRecord.getURL().equals(oldUrl)) {
externalRecord.setURL(newUrl);
return true;
}
}
return false;
}
}