| /* ==================================================================== |
| 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.hdgf.chunks; |
| |
| import java.io.BufferedReader; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.StringTokenizer; |
| |
| import org.apache.poi.util.LocaleUtil; |
| import org.apache.poi.util.POILogFactory; |
| import org.apache.poi.util.POILogger; |
| |
| /** |
| * Factor class to create the appropriate chunks, which |
| * needs the version of the file to process the chunk header |
| * and trailer areas. |
| * Makes use of chunks_parse_cmds.tbl from vsdump to be able |
| * to process the chunk value area |
| */ |
| public final class ChunkFactory { |
| /** The version of the currently open document */ |
| private int version; |
| /** |
| * Key is a Chunk's type, value is an array of its CommandDefinitions |
| */ |
| private final Map<Integer, CommandDefinition[]> chunkCommandDefinitions = |
| new HashMap<Integer, CommandDefinition[]>(); |
| /** |
| * What the name is of the chunk table definitions file? |
| * This file comes from the scratchpad resources directory. |
| */ |
| private static final String chunkTableName = |
| "/org/apache/poi/hdgf/chunks_parse_cmds.tbl"; |
| |
| /** For logging problems we spot with the file */ |
| private static final POILogger logger = POILogFactory.getLogger(ChunkFactory.class); |
| |
| public ChunkFactory(int version) throws IOException { |
| this.version = version; |
| |
| processChunkParseCommands(); |
| } |
| |
| /** |
| * Open chunks_parse_cmds.tbl and process it, to get the definitions |
| * of all the different possible chunk commands. |
| */ |
| private void processChunkParseCommands() throws IOException { |
| String line; |
| InputStream cpd = null; |
| BufferedReader inp = null; |
| try { |
| cpd = ChunkFactory.class.getResourceAsStream(chunkTableName); |
| if(cpd == null) { |
| throw new IllegalStateException("Unable to find HDGF chunk definition on the classpath - " + chunkTableName); |
| } |
| |
| inp = new BufferedReader(new InputStreamReader(cpd, LocaleUtil.CHARSET_1252)); |
| |
| while( (line = inp.readLine()) != null ) { |
| if (line.isEmpty() || "# \t".contains(line.substring(0,1))) { |
| continue; |
| } |
| |
| // Start xxx |
| if(!line.matches("^start [0-9]+$")) { |
| throw new IllegalStateException("Expecting start xxx, found " + line); |
| } |
| int chunkType = Integer.parseInt(line.substring(6)); |
| ArrayList<CommandDefinition> defsL = new ArrayList<CommandDefinition>(); |
| |
| // Data entries |
| while( (line = inp.readLine()) != null ) { |
| if (line.startsWith("end")) { |
| break; |
| } |
| StringTokenizer st = new StringTokenizer(line, " "); |
| int defType = Integer.parseInt(st.nextToken()); |
| int offset = Integer.parseInt(st.nextToken()); |
| String name = st.nextToken("\uffff").substring(1); |
| |
| CommandDefinition def = new CommandDefinition(defType,offset,name); |
| defsL.add(def); |
| } |
| |
| CommandDefinition[] defs = defsL.toArray(new CommandDefinition[defsL.size()]); |
| |
| // Add to the map |
| chunkCommandDefinitions.put(Integer.valueOf(chunkType), defs); |
| } |
| } finally { |
| if (inp != null) { |
| inp.close(); |
| } |
| if (cpd != null) { |
| cpd.close(); |
| } |
| } |
| } |
| |
| public int getVersion() { return version; } |
| |
| /** |
| * Creates the appropriate chunk at the given location. |
| * @param data |
| * @param offset |
| */ |
| public Chunk createChunk(byte[] data, int offset) { |
| // Create the header |
| ChunkHeader header = |
| ChunkHeader.createChunkHeader(version, data, offset); |
| // Sanity check |
| if(header.getLength() < 0) { |
| throw new IllegalArgumentException("Found a chunk with a negative length, which isn't allowed"); |
| } |
| |
| // How far up to look |
| int endOfDataPos = offset + header.getLength() + header.getSizeInBytes(); |
| |
| // Check we have enough data, and tweak the header size |
| // as required |
| if(endOfDataPos > data.length) { |
| logger.log(POILogger.WARN, |
| "Header called for " + header.getLength() +" bytes, but that would take us past the end of the data!"); |
| |
| endOfDataPos = data.length; |
| header.setLength(data.length - offset - header.getSizeInBytes()); |
| |
| if(header.hasTrailer()) { |
| header.setLength(header.getLength() - 8); |
| endOfDataPos -= 8; |
| } |
| if(header.hasSeparator()) { |
| header.setLength(header.getLength() - 4); |
| endOfDataPos -= 4; |
| } |
| } |
| |
| |
| // Create the trailer and separator, if required |
| ChunkTrailer trailer = null; |
| ChunkSeparator separator = null; |
| if(header.hasTrailer()) { |
| if(endOfDataPos <= data.length-8) { |
| trailer = new ChunkTrailer( |
| data, endOfDataPos); |
| endOfDataPos += 8; |
| } else { |
| logger.log(POILogger.ERROR, "Header claims a length to " + endOfDataPos + " there's then no space for the trailer in the data (" + data.length + ")"); |
| } |
| } |
| if(header.hasSeparator()) { |
| if(endOfDataPos <= data.length-4) { |
| separator = new ChunkSeparator( |
| data, endOfDataPos); |
| } else { |
| logger.log(POILogger.ERROR, "Header claims a length to " + endOfDataPos + " there's then no space for the separator in the data (" + data.length + ")"); |
| } |
| } |
| |
| // Now, create the chunk |
| byte[] contents = new byte[header.getLength()]; |
| System.arraycopy(data, offset+header.getSizeInBytes(), contents, 0, contents.length); |
| Chunk chunk = new Chunk(header, trailer, separator, contents); |
| |
| // Feed in the stuff from chunks_parse_cmds.tbl |
| CommandDefinition[] defs = chunkCommandDefinitions.get(Integer.valueOf(header.getType())); |
| if (defs == null) { |
| defs = new CommandDefinition[0]; |
| } |
| chunk.commandDefinitions = defs; |
| |
| // Now get the chunk to process its commands |
| chunk.processCommands(); |
| |
| // All done |
| return chunk; |
| } |
| |
| /** |
| * The definition of a Command, which a chunk may hold. |
| * The Command holds the value, this describes it. |
| */ |
| public static class CommandDefinition { |
| private int type; |
| private int offset; |
| private String name; |
| public CommandDefinition(int type, int offset, String name) { |
| this.type = type; |
| this.offset = offset; |
| this.name = name; |
| } |
| |
| public String getName() { |
| return name; |
| } |
| public int getOffset() { |
| return offset; |
| } |
| public int getType() { |
| return type; |
| } |
| } |
| } |