| /* ==================================================================== |
| 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.hpbf.model.qcbits; |
| |
| import org.apache.poi.util.LittleEndian; |
| import org.apache.poi.util.StringUtil; |
| |
| |
| /** |
| * A "PLC " (PLC) based bit of Quill Contents. The exact |
| * format is determined by the type of the PLCs. |
| */ |
| public abstract class QCPLCBit extends QCBit { |
| private int numberOfPLCs; |
| private int typeOfPLCS; |
| /** |
| * The data which goes before the main PLC entries. |
| * This is apparently always made up of 2 byte |
| * un-signed ints.. |
| */ |
| private int[] preData; |
| /** The first value of each PLC, normally 4 bytes */ |
| private long[] plcValA; |
| /** The second value of each PLC, normally 4 bytes */ |
| private long[] plcValB; |
| |
| |
| private QCPLCBit(String thingType, String bitType, byte[] data) { |
| super(thingType, bitType, data); |
| |
| // First four bytes are the number |
| numberOfPLCs = (int)LittleEndian.getUInt(data, 0); |
| |
| // Next four bytes are the type |
| typeOfPLCS = (int)LittleEndian.getUInt(data, 4); |
| |
| // Init the arrays that we can |
| plcValA = new long[numberOfPLCs]; |
| plcValB = new long[numberOfPLCs]; |
| } |
| |
| |
| |
| public int getNumberOfPLCs() { |
| return numberOfPLCs; |
| } |
| public int getTypeOfPLCS() { |
| return typeOfPLCS; |
| } |
| |
| public int[] getPreData() { |
| return preData; |
| } |
| |
| public long[] getPlcValA() { |
| return plcValA; |
| } |
| public long[] getPlcValB() { |
| return plcValB; |
| } |
| |
| final void setPreData(int preData[]) { |
| this.preData = preData.clone(); |
| } |
| |
| final void setPlcValA(long[] plcValA) { |
| this.plcValA = plcValA.clone(); |
| } |
| |
| final void setPlcValB(long[] plcValB) { |
| this.plcValB = plcValB.clone(); |
| } |
| |
| |
| |
| public static QCPLCBit createQCPLCBit(String thingType, String bitType, byte[] data) { |
| // Grab the type |
| int type = (int)LittleEndian.getUInt(data, 4); |
| switch(type) { |
| case 0: |
| return new Type0(thingType, bitType, data); |
| case 4: |
| return new Type4(thingType, bitType, data); |
| case 8: |
| return new Type8(thingType, bitType, data); |
| case 12: // 0xc |
| return new Type12(thingType, bitType, data); |
| default: |
| throw new IllegalArgumentException("Sorry, I don't know how to deal with PLCs of type " + type); |
| } |
| } |
| |
| |
| /** |
| * Type 0 seem to be somewhat rare. They have 8 bytes of pre-data, |
| * then 2x 2 byte values. |
| */ |
| public static class Type0 extends QCPLCBit { |
| private Type0(String thingType, String bitType, byte[] data) { |
| super(thingType, bitType, data); |
| |
| // Grab our 4x pre-data |
| int preData[] = { |
| LittleEndian.getUShort(data, 8+0), |
| LittleEndian.getUShort(data, 8+2), |
| LittleEndian.getUShort(data, 8+4), |
| LittleEndian.getUShort(data, 8+6) |
| }; |
| setPreData(preData); |
| |
| // And grab the 2 byte values |
| int cntPlcs = getNumberOfPLCs(); |
| long plcValA[] = new long[cntPlcs], plcValB[] = new long[cntPlcs]; |
| for(int i=0; i<cntPlcs; i++) { |
| plcValA[i] = LittleEndian.getUShort(data, 16+(4*i)); |
| plcValB[i] = LittleEndian.getUShort(data, 16+(4*i)+2); |
| } |
| setPlcValA(plcValA); |
| setPlcValB(plcValB); |
| } |
| } |
| |
| /** |
| * Type 4 is quite common. They have 8 bytes of pre-data, |
| * then 2x 4 byte values. |
| */ |
| public static class Type4 extends QCPLCBit { |
| private Type4(String thingType, String bitType, byte[] data) { |
| super(thingType, bitType, data); |
| |
| // Grab our 4x pre-data |
| int preData[] = { |
| LittleEndian.getUShort(data, 8+0), |
| LittleEndian.getUShort(data, 8+2), |
| LittleEndian.getUShort(data, 8+4), |
| LittleEndian.getUShort(data, 8+6) |
| }; |
| setPreData(preData); |
| |
| // And grab the 4 byte values |
| int cntPlcs = getNumberOfPLCs(); |
| long plcValA[] = new long[cntPlcs], plcValB[] = new long[cntPlcs]; |
| for(int i=0; i<cntPlcs; i++) { |
| plcValA[i] = LittleEndian.getUInt(data, 16+(8*i)); |
| plcValB[i] = LittleEndian.getUInt(data, 16+(8*i)+4); |
| } |
| setPlcValA(plcValA); |
| setPlcValB(plcValB); |
| } |
| } |
| |
| /** |
| * Type 8 is quite common. They have 14 bytes of pre-data, |
| * then 2x 4 byte values. |
| */ |
| public static class Type8 extends QCPLCBit { |
| private Type8(String thingType, String bitType, byte[] data) { |
| super(thingType, bitType, data); |
| |
| // Grab our 7x pre-data |
| int preData[] = { |
| LittleEndian.getUShort(data, 8+0), |
| LittleEndian.getUShort(data, 8+2), |
| LittleEndian.getUShort(data, 8+4), |
| LittleEndian.getUShort(data, 8+6), |
| LittleEndian.getUShort(data, 8+8), |
| LittleEndian.getUShort(data, 8+10), |
| LittleEndian.getUShort(data, 8+12) |
| }; |
| setPreData(preData); |
| |
| // And grab the 4 byte values |
| int cntPlcs = getNumberOfPLCs(); |
| long plcValA[] = new long[cntPlcs], plcValB[] = new long[cntPlcs]; |
| for(int i=0; i<cntPlcs; i++) { |
| plcValA[i] = LittleEndian.getUInt(data, 22+(8*i)); |
| plcValB[i] = LittleEndian.getUInt(data, 22+(8*i)+4); |
| } |
| setPlcValA(plcValA); |
| setPlcValB(plcValB); |
| } |
| } |
| |
| /** |
| * Type 12 holds hyperlinks, and is very complex. |
| * There is normally one of these for each text |
| * area that contains at least one hyperlinks. |
| * The character offsets are relative to the start |
| * of the text area that this applies to. |
| */ |
| public static class Type12 extends QCPLCBit { |
| private String[] hyperlinks; |
| |
| private static final int oneStartsAt = 0x4c; |
| private static final int twoStartsAt = 0x68; |
| private static final int threePlusIncrement = 22; |
| |
| private Type12(String thingType, String bitType, byte[] data) { |
| super(thingType, bitType, data); |
| |
| int cntPlcs = getNumberOfPLCs(); |
| |
| // How many hyperlinks do we really have? |
| // (zero hyperlinks gets numberOfPLCs=1) |
| hyperlinks = new String[data.length == 0x34 ? 0 : cntPlcs]; |
| |
| // We have 4 bytes, then the start point of each |
| // hyperlink, then the end point of the text. |
| int preData[] = new int[1+cntPlcs+1]; |
| for(int i=0; i<preData.length; i++) { |
| preData[i] = (int)LittleEndian.getUInt(data, 8+(i*4)); |
| } |
| setPreData(preData); |
| |
| // Then we have a whole bunch of stuff, which grows |
| // with the number of hyperlinks |
| // For now, we think these are shorts |
| int at = 8+4+(cntPlcs*4)+4; |
| int until = 0x34; |
| if(cntPlcs == 1 && hyperlinks.length == 1) { |
| until = oneStartsAt; |
| } else if(cntPlcs >= 2) { |
| until = twoStartsAt + (cntPlcs-2)*threePlusIncrement; |
| } |
| |
| long plcValA[] = new long[(until-at)/2]; |
| long plcValB[] = new long[0]; |
| for(int i=0; i<plcValA.length; i++) { |
| plcValA[i] = LittleEndian.getUShort(data, at+(i*2)); |
| } |
| setPlcValA(plcValA); |
| setPlcValB(plcValB); |
| |
| // Finally, we have a series of lengths + hyperlinks |
| at = until; |
| for(int i=0; i<hyperlinks.length; i++) { |
| int len = LittleEndian.getUShort(data, at); |
| int first = LittleEndian.getUShort(data, at+2); |
| if(first == 0) { |
| // Crazy special case |
| // Length is in bytes, from the start |
| // Hyperlink appears to be empty |
| hyperlinks[i] = ""; |
| at += len; |
| } else { |
| // Normal case. Length is in characters |
| hyperlinks[i] = StringUtil.getFromUnicodeLE(data, at+2, len); |
| at += 2 + (2*len); |
| } |
| } |
| } |
| |
| /** |
| * Returns the number of hyperlinks, which should |
| * either be zero, or the number of PLC bits |
| */ |
| public int getNumberOfHyperlinks() { |
| return hyperlinks.length; |
| } |
| |
| /** |
| * Returns the URL of the hyperlink at the |
| * given index. |
| * @param number The hyperlink number, zero based |
| */ |
| public String getHyperlink(int number) { |
| return hyperlinks[number]; |
| } |
| /** |
| * Returns where in the text (in characters) the |
| * hyperlink at the given index starts |
| * applying to. |
| * This position is relative to the text area that this |
| * PLCBit applies to. |
| * @param number The hyperlink number, zero based |
| */ |
| public int getTextStartAt(int number) { |
| return getPreData()[1+number]; |
| } |
| /** |
| * Returns where in the text that this block |
| * of hyperlinks stops applying to. Normally, |
| * but not always the end of the text. |
| * This position is relative to the text area that this |
| * PLCBit applies to. |
| */ |
| public int getAllTextEndAt() { |
| return getPreData()[getNumberOfPLCs()+1]; |
| } |
| } |
| } |