/* ==================================================================== 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. ==================================================================== */ namespace HH.WMS.Utils.NPOI.DDF { using System; using System.Text; using System.Collections; using HH.WMS.Utils.NPOI.Util; /// /// Escher array properties are the most wierd construction ever invented /// with all sorts of special cases. I'm hopeful I've got them all. /// @author Glen Stampoultzis (glens at superlinksoftware.com) /// public class EscherArrayProperty : EscherComplexProperty { /** * The size of the header that goes at the * start of the array, before the data */ private const int FIXED_SIZE = 3 * 2; /** * Normally, the size recorded in the simple data (for the complex * data) includes the size of the header. * There are a few cases when it doesn't though... */ private bool sizeIncludesHeaderSize = true; /** * When Reading a property from data stream remeber if the complex part is empty and Set this flag. */ private bool emptyComplexPart = false; public EscherArrayProperty(short id, byte[] complexData) : base(id, CheckComplexData(complexData)) { emptyComplexPart = complexData.Length == 0; } public EscherArrayProperty(short propertyNumber, bool isBlipId, byte[] complexData) : base(propertyNumber, isBlipId, CheckComplexData(complexData)) { } private static byte[] CheckComplexData(byte[] complexData) { if (complexData == null || complexData.Length == 0) complexData = new byte[6]; return complexData; } public int NumberOfElementsInArray { get { return LittleEndian.GetUShort(complexData, 0); } set { int expectedArraySize = value * GetActualSizeOfElements(SizeOfElements) + FIXED_SIZE; if (expectedArraySize != complexData.Length) { byte[] newArray = new byte[expectedArraySize]; Array.Copy(complexData, 0, newArray, 0, complexData.Length); complexData = newArray; } LittleEndian.PutShort(complexData, 0, (short)value); } } public int NumberOfElementsInMemory { get { return LittleEndian.GetUShort(complexData, 2); } set { int expectedArraySize = value * GetActualSizeOfElements(this.SizeOfElements) + FIXED_SIZE; if (expectedArraySize != complexData.Length) { byte[] newArray = new byte[expectedArraySize]; Array.Copy(complexData, 0, newArray, 0, expectedArraySize); complexData = newArray; } LittleEndian.PutShort(complexData, 2, (short)value); } } public short SizeOfElements { get { return LittleEndian.GetShort(complexData, 4); } set { LittleEndian.PutShort(complexData, 4, (short)value); int expectedArraySize = NumberOfElementsInArray * GetActualSizeOfElements(SizeOfElements) + FIXED_SIZE; if (expectedArraySize != complexData.Length) { // Keep just the first 6 bytes. The rest is no good to us anyway. byte[] newArray = new byte[expectedArraySize]; Array.Copy(complexData, 0, newArray, 0, 6); complexData = newArray; } } } /// /// Gets the element. /// /// The index. /// public byte[] GetElement(int index) { int actualSize = GetActualSizeOfElements(SizeOfElements); byte[] result = new byte[actualSize]; Array.Copy(complexData, FIXED_SIZE + index * actualSize, result, 0, result.Length); return result; } /// /// Sets the element. /// /// The index. /// The element. public void SetElement(int index, byte[] element) { int actualSize = GetActualSizeOfElements(SizeOfElements); Array.Copy(element, 0, complexData, FIXED_SIZE + index * actualSize, actualSize); } /// /// Retrieves the string representation for this property. /// /// public override String ToString() { String nl = Environment.NewLine; StringBuilder results = new StringBuilder(); results.Append(" {EscherArrayProperty:" + nl); results.Append(" Num Elements: " + NumberOfElementsInArray + nl); results.Append(" Num Elements In Memory: " + NumberOfElementsInMemory + nl); results.Append(" Size of elements: " + SizeOfElements + nl); for (int i = 0; i < NumberOfElementsInArray; i++) { results.Append(" Element " + i + ": " + HexDump.ToHex(GetElement(i)) + nl); } results.Append("}" + nl); return "propNum: " + PropertyNumber + ", propName: " + EscherProperties.GetPropertyName(PropertyNumber) + ", complex: " + IsComplex + ", blipId: " + IsBlipId + ", data: " + nl + results.ToString(); } /// /// We have this method because the way in which arrays in escher works /// is screwed for seemly arbitary reasons. While most properties are /// fairly consistent and have a predictable array size, escher arrays /// have special cases. /// /// The data array containing the escher array information /// The offset into the array to start Reading from. /// the number of bytes used by this complex property. public int SetArrayData(byte[] data, int offset) { if (emptyComplexPart) { complexData = new byte[0]; } else { short numElements = LittleEndian.GetShort(data, offset); short numReserved = LittleEndian.GetShort(data, offset + 2); short sizeOfElements = LittleEndian.GetShort(data, offset + 4); int arraySize = GetActualSizeOfElements(sizeOfElements) * numElements; if (arraySize == complexData.Length) { // The stored data size in the simple block excludes the header size complexData = new byte[arraySize + 6]; sizeIncludesHeaderSize = false; } Array.Copy(data, offset, complexData, 0, complexData.Length); } return complexData.Length; } /// /// Serializes the simple part of this property. ie the first 6 bytes. /// Needs special code to handle the case when the size doesn't /// include the size of the header block /// /// /// /// public override int SerializeSimplePart(byte[] data, int pos) { LittleEndian.PutShort(data, pos, Id); int recordSize = complexData.Length; if (!sizeIncludesHeaderSize) { recordSize -= 6; } LittleEndian.PutInt(data, pos + 2, recordSize); return 6; } /// /// Sometimes the element size is stored as a negative number. We /// negate it and shift it to Get the real value. /// /// The size of elements. /// public static int GetActualSizeOfElements(short sizeOfElements) { if (sizeOfElements < 0) return (short)((-sizeOfElements) >> 2); else return sizeOfElements; } } }