/* ==================================================================== 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]; } } }