| /* ==================================================================== |
| 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 |
| |
| 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. |
| ==================================================================== */ |
| |
| package org.apache.poi.hssf.record; |
| |
| import java.util.Map; |
| import java.util.function.Supplier; |
| |
| import org.apache.logging.log4j.LogManager; |
| import org.apache.logging.log4j.Logger; |
| import org.apache.poi.util.GenericRecordUtil; |
| import org.apache.poi.util.LittleEndianOutput; |
| import org.apache.poi.util.StringUtil; |
| |
| /** |
| * A External Workbook Description (Supplemental Book). |
| * Its only a dummy record for making new ExternSheet Record |
| */ |
| public final class SupBookRecord extends StandardRecord { |
| |
| private static final Logger LOG = LogManager.getLogger(SupBookRecord.class); |
| |
| public static final short sid = 0x01AE; |
| |
| private static final short SMALL_RECORD_SIZE = 4; |
| private static final short TAG_INTERNAL_REFERENCES = 0x0401; |
| private static final short TAG_ADD_IN_FUNCTIONS = 0x3A01; |
| |
| static final char CH_VOLUME = 1; |
| static final char CH_SAME_VOLUME = 2; |
| static final char CH_DOWN_DIR = 3; |
| static final char CH_UP_DIR = 4; |
| static final char CH_LONG_VOLUME = 5; |
| static final char CH_STARTUP_DIR = 6; |
| static final char CH_ALT_STARTUP_DIR = 7; |
| static final char CH_LIB_DIR = 8; |
| static final String PATH_SEPERATOR = System.getProperty("file.separator"); |
| |
| private short field_1_number_of_sheets; |
| private String field_2_encoded_url; |
| private final String[] field_3_sheet_names; |
| private final boolean _isAddInFunctions; |
| |
| public SupBookRecord(SupBookRecord other) { |
| super(other); |
| field_1_number_of_sheets = other.field_1_number_of_sheets; |
| field_2_encoded_url = other.field_2_encoded_url; |
| field_3_sheet_names = other.field_3_sheet_names; |
| _isAddInFunctions = other._isAddInFunctions; |
| } |
| |
| private SupBookRecord(boolean isAddInFuncs, short numberOfSheets) { |
| // else not 'External References' |
| field_1_number_of_sheets = numberOfSheets; |
| field_2_encoded_url = null; |
| field_3_sheet_names = null; |
| _isAddInFunctions = isAddInFuncs; |
| } |
| |
| public SupBookRecord(String url, String[] sheetNames) { |
| field_1_number_of_sheets = (short) sheetNames.length; |
| field_2_encoded_url = url; |
| field_3_sheet_names = sheetNames; |
| _isAddInFunctions = false; |
| } |
| |
| public static SupBookRecord createInternalReferences(short numberOfSheets) { |
| return new SupBookRecord(false, numberOfSheets); |
| } |
| public static SupBookRecord createAddInFunctions() { |
| return new SupBookRecord(true, (short)1 /* this field MUST be 0x0001 for add-in referencing */); |
| } |
| public static SupBookRecord createExternalReferences(String url, String[] sheetNames) { |
| return new SupBookRecord(url, sheetNames); |
| } |
| |
| public boolean isExternalReferences() { |
| return field_3_sheet_names != null; |
| } |
| public boolean isInternalReferences() { |
| return field_3_sheet_names == null && !_isAddInFunctions; |
| } |
| public boolean isAddInFunctions() { |
| return field_3_sheet_names == null && _isAddInFunctions; |
| } |
| /** |
| * called by the constructor, should set class level fields. Should throw |
| * runtime exception for bad/incomplete data. |
| * |
| * @param in the stream to read from |
| */ |
| public SupBookRecord(RecordInputStream in) { |
| int recLen = in.remaining(); |
| |
| field_1_number_of_sheets = in.readShort(); |
| |
| if(recLen > SMALL_RECORD_SIZE) { |
| // 5.38.1 External References |
| _isAddInFunctions = false; |
| |
| field_2_encoded_url = in.readString(); |
| String[] sheetNames = new String[field_1_number_of_sheets]; |
| for (int i = 0; i < sheetNames.length; i++) { |
| sheetNames[i] = in.readString(); |
| } |
| field_3_sheet_names = sheetNames; |
| return; |
| } |
| // else not 'External References' |
| field_2_encoded_url = null; |
| field_3_sheet_names = null; |
| |
| short nextShort = in.readShort(); |
| if(nextShort == TAG_INTERNAL_REFERENCES) { |
| // 5.38.2 'Internal References' |
| _isAddInFunctions = false; |
| } else if(nextShort == TAG_ADD_IN_FUNCTIONS) { |
| // 5.38.3 'Add-In Functions' |
| _isAddInFunctions = true; |
| if(field_1_number_of_sheets != 1) { |
| throw new RuntimeException("Expected 0x0001 for number of sheets field in 'Add-In Functions' but got (" |
| + field_1_number_of_sheets + ")"); |
| } |
| } else { |
| throw new RuntimeException("invalid EXTERNALBOOK code (" |
| + Integer.toHexString(nextShort) + ")"); |
| } |
| } |
| |
| protected int getDataSize() { |
| if(!isExternalReferences()) { |
| return SMALL_RECORD_SIZE; |
| } |
| int sum = 2; // u16 number of sheets |
| |
| sum += StringUtil.getEncodedSize(field_2_encoded_url); |
| |
| for (String field_3_sheet_name : field_3_sheet_names) { |
| sum += StringUtil.getEncodedSize(field_3_sheet_name); |
| } |
| return sum; |
| } |
| |
| public void serialize(LittleEndianOutput out) { |
| out.writeShort(field_1_number_of_sheets); |
| |
| if(isExternalReferences()) { |
| StringUtil.writeUnicodeString(out, field_2_encoded_url); |
| |
| for (String field_3_sheet_name : field_3_sheet_names) { |
| StringUtil.writeUnicodeString(out, field_3_sheet_name); |
| } |
| } else { |
| int field2val = _isAddInFunctions ? TAG_ADD_IN_FUNCTIONS : TAG_INTERNAL_REFERENCES; |
| |
| out.writeShort(field2val); |
| } |
| } |
| |
| public void setNumberOfSheets(short number){ |
| field_1_number_of_sheets = number; |
| } |
| |
| public short getNumberOfSheets(){ |
| return field_1_number_of_sheets; |
| } |
| |
| public short getSid() |
| { |
| return sid; |
| } |
| public String getURL() { |
| String encodedUrl = field_2_encoded_url; |
| if (encodedUrl != null && encodedUrl.length() >= 2) { |
| switch(encodedUrl.charAt(0)) { |
| case 0: // Reference to an empty workbook name |
| case 2: // Self-referential external reference |
| // will this just be empty string? |
| return encodedUrl.substring(1); |
| case 1: // encoded file name |
| return decodeFileName(encodedUrl); |
| |
| } |
| } |
| return encodedUrl; |
| } |
| private static String decodeFileName(String encodedUrl) { |
| /* see "MICROSOFT OFFICE EXCEL 97-2007 BINARY FILE FORMAT SPECIFICATION" */ |
| StringBuilder sb = new StringBuilder(); |
| for(int i=1; i<encodedUrl.length(); i++) { |
| char c = encodedUrl.charAt(i); |
| switch (c) { |
| case CH_VOLUME: |
| char driveLetter = encodedUrl.charAt(++i); |
| if (driveLetter == '@') { |
| sb.append("\\\\"); |
| } else { |
| //Windows notation for drive letters |
| sb.append(driveLetter).append(":"); |
| } |
| break; |
| case CH_SAME_VOLUME: |
| case CH_DOWN_DIR: |
| sb.append(PATH_SEPERATOR); |
| break; |
| case CH_UP_DIR: |
| sb.append("..").append(PATH_SEPERATOR); |
| break; |
| case CH_LONG_VOLUME: |
| //Don't known to handle... |
| LOG.atWarn().log("Found unexpected key: ChLongVolume - IGNORING"); |
| break; |
| case CH_STARTUP_DIR: |
| case CH_ALT_STARTUP_DIR: |
| case CH_LIB_DIR: |
| LOG.atWarn().log("EXCEL.EXE path unknown - using this directory instead: ."); |
| sb.append(".").append(PATH_SEPERATOR); |
| break; |
| default: |
| sb.append(c); |
| } |
| } |
| return sb.toString(); |
| } |
| public String[] getSheetNames() { |
| return field_3_sheet_names == null ? null : field_3_sheet_names.clone(); |
| } |
| |
| public void setURL(String pUrl) { |
| //Keep the first marker character! |
| field_2_encoded_url = field_2_encoded_url.substring(0, 1) + pUrl; |
| } |
| |
| @Override |
| public SupBookRecord copy() { |
| return new SupBookRecord(this); |
| } |
| |
| @Override |
| public HSSFRecordTypes getGenericRecordType() { |
| return HSSFRecordTypes.SUP_BOOK; |
| } |
| |
| @Override |
| public Map<String, Supplier<?>> getGenericProperties() { |
| return GenericRecordUtil.getGenericProperties( |
| "externalReferences", this::isExternalReferences, |
| "internalReferences", this::isInternalReferences, |
| "url", this::getURL, |
| "numberOfSheets", this::getNumberOfSheets, |
| "sheetNames", this::getSheetNames, |
| "addInFunctions", this::isAddInFunctions |
| ); |
| } |
| } |