用于生成 Office 文件的 .NET 组件。
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

696 lines
24 KiB

/* ====================================================================
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.
==================================================================== */
/* ================================================================
* About NPOI
* Author: Tony Qu
* Author's email: tonyqus (at) gmail.com
* Author's Blog: tonyqus.wordpress.com.cn (wp.tonyqus.cn)
* HomePage: http://www.codeplex.com/npoi
* Contributors:
*
* ==============================================================*/
namespace Npoi.Core.HPSF
{
using System;
using System.IO;
using System.Text;
using System.Collections;
using Npoi.Core.Util;
using Npoi.Core.HPSF.Wellknown;
using System.Globalization;
/// <summary>
/// Adds writing capability To the {@link Section} class.
/// Please be aware that this class' functionality will be merged into the
/// {@link Section} class at a later time, so the API will Change.
/// @since 2002-02-20
/// </summary>
public class MutableSection : Section
{
/**
* If the "dirty" flag is true, the section's size must be
* (re-)calculated before the section is written.
*/
private bool dirty = true;
/**
* List To assemble the properties. Unfortunately a wrong
* decision has been taken when specifying the "properties" field
* as an Property[]. It should have been a {@link java.util.List}.
*/
private ArrayList preprops;
/**
* Contains the bytes making out the section. This byte array is
* established when the section's size is calculated and can be reused
* later. It is valid only if the "dirty" flag is false.
*/
private byte[] sectionBytes;
/// <summary>
/// Initializes a new instance of the <see cref="MutableSection"/> class.
/// </summary>
public MutableSection()
{
dirty = true;
formatID = null;
offset = -1;
preprops = new ArrayList();
}
/// <summary>
/// Constructs a <c>MutableSection</c> by doing a deep copy of an
/// existing <c>Section</c>. All nested <c>Property</c>
/// instances, will be their mutable counterparts in the new
/// <c>MutableSection</c>.
/// </summary>
/// <param name="s">The section Set To copy</param>
public MutableSection(Section s)
{
SetFormatID(s.FormatID);
Property[] pa = s.Properties;
MutableProperty[] mpa = new MutableProperty[pa.Length];
for (int i = 0; i < pa.Length; i++)
mpa[i] = new MutableProperty(pa[i]);
SetProperties(mpa);
this.Dictionary=(s.Dictionary);
}
/// <summary>
/// Sets the section's format ID.
/// </summary>
/// <param name="formatID">The section's format ID</param>
public void SetFormatID(ClassID formatID)
{
this.formatID = formatID;
}
/// <summary>
/// Sets the section's format ID.
/// </summary>
/// <param name="formatID">The section's format ID as a byte array. It components
/// are in big-endian format.</param>
public void SetFormatID(byte[] formatID)
{
ClassID fid = this.FormatID;
if (fid == null)
{
fid = new ClassID();
SetFormatID(fid);
}
fid.Bytes=formatID;
}
/// <summary>
/// Sets this section's properties. Any former values are overwritten.
/// </summary>
/// <param name="properties">This section's new properties.</param>
public void SetProperties(Property[] properties)
{
this.properties = properties;
preprops = new ArrayList();
for (int i = 0; i < properties.Length; i++)
preprops.Add(properties[i]);
dirty = true;
}
/// <summary>
/// Sets the string value of the property with the specified ID.
/// </summary>
/// <param name="id">The property's ID</param>
/// <param name="value">The property's value. It will be written as a Unicode
/// string.</param>
public void SetProperty(int id, String value)
{
SetProperty(id, Variant.VT_LPWSTR, value);
dirty = true;
}
/// <summary>
/// Sets the int value of the property with the specified ID.
/// </summary>
/// <param name="id">The property's ID</param>
/// <param name="value">The property's value.</param>
public void SetProperty(int id, int value)
{
SetProperty(id, Variant.VT_I4, value);
dirty = true;
}
/// <summary>
/// Sets the long value of the property with the specified ID.
/// </summary>
/// <param name="id">The property's ID</param>
/// <param name="value">The property's value.</param>
public void SetProperty(int id, long value)
{
SetProperty(id, Variant.VT_I8, value);
dirty = true;
}
/// <summary>
/// Sets the bool value of the property with the specified ID.
/// </summary>
/// <param name="id">The property's ID</param>
/// <param name="value">The property's value.</param>
public void SetProperty(int id, bool value)
{
SetProperty(id, Variant.VT_BOOL, value);
dirty = true;
}
/// <summary>
/// Sets the value and the variant type of the property with the
/// specified ID. If a property with this ID is not yet present in
/// the section, it will be Added. An alReady present property with
/// the specified ID will be overwritten. A default mapping will be
/// used To choose the property's type.
/// </summary>
/// <param name="id">The property's ID.</param>
/// <param name="variantType">The property's variant type.</param>
/// <param name="value">The property's value.</param>
public void SetProperty(int id, long variantType,
Object value)
{
MutableProperty p = new MutableProperty();
p.ID=id;
p.Type=variantType;
p.Value=value;
SetProperty(p);
dirty = true;
}
/// <summary>
/// Sets the property.
/// </summary>
/// <param name="p">The property To be Set.</param>
public void SetProperty(Property p)
{
long id = p.ID;
RemoveProperty(id);
preprops.Add(p);
dirty = true;
}
/// <summary>
/// Removes the property.
/// </summary>
/// <param name="id">The ID of the property To be Removed</param>
public void RemoveProperty(long id)
{
for (IEnumerator i = preprops.GetEnumerator(); i.MoveNext(); )
if (((Property)i.Current).ID == id)
{
preprops.Remove(i.Current);
break;
}
dirty = true;
}
/// <summary>
/// Sets the value of the bool property with the specified
/// ID.
/// </summary>
/// <param name="id">The property's ID</param>
/// <param name="value">The property's value</param>
protected void SetPropertyBooleanValue(int id, bool value)
{
SetProperty(id, Variant.VT_BOOL, value);
}
/// <summary>
/// Returns the section's size in bytes.
/// </summary>
/// <value>The section's size in bytes.</value>
public override int Size
{
get
{
if (dirty)
{
try
{
size = CalcSize();
dirty = false;
}
catch (Exception)
{
throw;
}
}
return size;
}
}
/// <summary>
/// Calculates the section's size. It is the sum of the Lengths of the
/// section's header (8), the properties list (16 times the number of
/// properties) and the properties themselves.
/// </summary>
/// <returns>the section's Length in bytes.</returns>
private int CalcSize()
{
using (MemoryStream out1 = new MemoryStream())
{
Write(out1);
/* Pad To multiple of 4 bytes so that even the Windows shell (explorer)
* shows custom properties. */
sectionBytes = Util.Pad4(out1.ToArray());
return sectionBytes.Length;
}
}
private class PropertyComparer : IComparer
{
#region IComparer Members
int IComparer.Compare(object o1, object o2)
{
Property p1 = (Property)o1;
Property p2 = (Property)o2;
if (p1.ID < p2.ID)
return -1;
else if (p1.ID == p2.ID)
return 0;
else
return 1;
}
#endregion
}
/// <summary>
/// Writes this section into an output stream.
/// Internally this is done by writing into three byte array output
/// streams: one for the properties, one for the property list and one for
/// the section as such. The two former are Appended To the latter when they
/// have received all their data.
/// </summary>
/// <param name="out1">The stream To Write into.</param>
/// <returns>The number of bytes written, i.e. the section's size.</returns>
public int Write(Stream out1)
{
/* Check whether we have alReady generated the bytes making out the
* section. */
if (!dirty && sectionBytes != null)
{
out1.Write(sectionBytes,0,sectionBytes.Length);
return sectionBytes.Length;
}
/* The properties are written To this stream. */
using (MemoryStream propertyStream =
new MemoryStream())
{
/* The property list is established here. After each property that has
* been written To "propertyStream", a property list entry is written To
* "propertyListStream". */
using (MemoryStream propertyListStream =
new MemoryStream())
{
/* Maintain the current position in the list. */
int position = 0;
/* Increase the position variable by the size of the property list so
* that it points behind the property list and To the beginning of the
* properties themselves. */
position += 2 * LittleEndianConsts.INT_SIZE +
PropertyCount * 2 * LittleEndianConsts.INT_SIZE;
/* Writing the section's dictionary it tricky. If there is a dictionary
* (property 0) the codepage property (property 1) must be Set, Too. */
int codepage = -1;
if (GetProperty(PropertyIDMap.PID_DICTIONARY) != null)
{
Object p1 = GetProperty(PropertyIDMap.PID_CODEPAGE);
if (p1 != null)
{
if (!(p1 is int))
throw new IllegalPropertySetDataException
("The codepage property (ID = 1) must be an " +
"Integer object.");
}
else
/* Warning: The codepage property is not Set although a
* dictionary is present. In order To cope with this problem we
* Add the codepage property and Set it To Unicode. */
SetProperty(PropertyIDMap.PID_CODEPAGE, Variant.VT_I2,
CodePageUtil.CP_UNICODE);
codepage = Codepage;
}
/* Sort the property list by their property IDs: */
preprops.Sort(new PropertyComparer());
/* Write the properties and the property list into their respective
* streams: */
for (int i = 0; i < preprops.Count; i++)
{
MutableProperty p = (MutableProperty)preprops[i];
long id = p.ID;
/* Write the property list entry. */
TypeWriter.WriteUIntToStream(propertyListStream, (uint)p.ID);
TypeWriter.WriteUIntToStream(propertyListStream, (uint)position);
/* If the property ID is not equal 0 we Write the property and all
* is fine. However, if it Equals 0 we have To Write the section's
* dictionary which has an implicit type only and an explicit
* value. */
if (id != 0)
{
/* Write the property and update the position To the next
* property. */
position += p.Write(propertyStream, Codepage);
}
else
{
if (codepage == -1)
throw new IllegalPropertySetDataException
("Codepage (property 1) is undefined.");
position += WriteDictionary(propertyStream, dictionary,
codepage);
}
}
propertyStream.Flush();
propertyListStream.Flush();
/* Write the section: */
byte[] pb1 = propertyListStream.ToArray();
byte[] pb2 = propertyStream.ToArray();
/* Write the section's Length: */
TypeWriter.WriteToStream(out1, LittleEndianConsts.INT_SIZE * 2 +
pb1.Length + pb2.Length);
/* Write the section's number of properties: */
TypeWriter.WriteToStream(out1, PropertyCount);
/* Write the property list: */
out1.Write(pb1, 0, pb1.Length);
/* Write the properties: */
out1.Write(pb2, 0, pb2.Length);
int streamLength = LittleEndianConsts.INT_SIZE * 2 + pb1.Length + pb2.Length;
return streamLength;
}
}
}
/// <summary>
/// Writes the section's dictionary
/// </summary>
/// <param name="out1">The output stream To Write To.</param>
/// <param name="dictionary">The dictionary.</param>
/// <param name="codepage">The codepage to be used to Write the dictionary items.</param>
/// <returns>The number of bytes written</returns>
/// <remarks>
/// see MSDN KB: http://msdn.microsoft.com/en-us/library/aa380065(VS.85).aspx
/// </remarks>
private static int WriteDictionary(Stream out1,
IDictionary dictionary, int codepage)
{
int length = TypeWriter.WriteUIntToStream(out1, (uint)dictionary.Count);
for (IEnumerator i = dictionary.Keys.GetEnumerator(); i.MoveNext(); )
{
long key = Convert.ToInt64(i.Current, CultureInfo.InvariantCulture);
String value = (String)dictionary[key];
//tony qu added: some key is int32 instead of int64
if(value==null)
value = (String)dictionary[(int)key];
if (codepage == CodePageUtil.CP_UNICODE)
{
/* Write the dictionary item in Unicode. */
int sLength = value.Length + 1;
if (sLength % 2 == 1)
sLength++;
length += TypeWriter.WriteUIntToStream(out1, (uint)key);
length += TypeWriter.WriteUIntToStream(out1, (uint)sLength);
byte[] ca =
Encoding.GetEncoding(codepage).GetBytes(value);
for (int j =0; j < ca.Length; j++)
{
out1.WriteByte(ca[j]);
length ++;
}
sLength -= value.Length;
while (sLength > 0)
{
out1.WriteByte(0x00);
out1.WriteByte(0x00);
length += 2;
sLength--;
}
}
else
{
/* Write the dictionary item in another codepage than
* Unicode. */
length += TypeWriter.WriteUIntToStream(out1, (uint)key);
length += TypeWriter.WriteUIntToStream(out1, (uint)value.Length + 1);
try
{
byte[] ba =
Encoding.GetEncoding(codepage).GetBytes(value);
for (int j = 0; j < ba.Length; j++)
{
out1.WriteByte(ba[j]);
length++;
}
}
catch (Exception ex)
{
throw new IllegalPropertySetDataException(ex);
}
out1.WriteByte(0x00);
length++;
}
}
return length;
}
/// <summary>
/// OverWrites the base class' method To cope with a redundancy:
/// the property count is maintained in a separate member variable, but
/// shouldn't.
/// </summary>
/// <value>The number of properties in this section.</value>
public override int PropertyCount
{
get { return preprops.Count; }
}
/// <summary>
/// Returns this section's properties.
/// </summary>
/// <value>This section's properties.</value>
public override Property[] Properties
{
get
{
EnsureProperties();
return properties;
}
}
/// <summary>
/// Ensures the properties.
/// </summary>
public void EnsureProperties()
{
properties = (Property[])preprops.ToArray(typeof(Property));
}
/// <summary>
/// Gets a property.
/// </summary>
/// <param name="id">The ID of the property To Get</param>
/// <returns>The property or null if there is no such property</returns>
public override Object GetProperty(long id)
{
/* Calling Properties ensures that properties and preprops are in
* sync. */
EnsureProperties();
return base.GetProperty(id);
}
/// <summary>
/// Sets the section's dictionary. All keys in the dictionary must be
/// {@link java.lang.long} instances, all values must be
/// {@link java.lang.String}s. This method overWrites the properties with IDs
/// 0 and 1 since they are reserved for the dictionary and the dictionary's
/// codepage. Setting these properties explicitly might have surprising
/// effects. An application should never do this but always use this
/// method.
/// </summary>
/// <value>
/// the dictionary
/// </value>
public override IDictionary Dictionary
{
get {
return this.dictionary;
}
set
{
if (value != null)
{
for (IEnumerator i = value.Keys.GetEnumerator();
i.MoveNext(); )
if (!(i.Current is Int64 || i.Current is Int32))
throw new IllegalPropertySetDataException
("Dictionary keys must be of type long. but it's " + i.Current + ","+i.Current.GetType().Name+" now");
this.dictionary = value;
/* Set the dictionary property (ID 0). Please note that the second
* parameter in the method call below is unused because dictionaries
* don't have a type. */
SetProperty(PropertyIDMap.PID_DICTIONARY, -1, value);
/* If the codepage property (ID 1) for the strings (keys and
* values) used in the dictionary is not yet defined, Set it To
* Unicode. */
if (GetProperty(PropertyIDMap.PID_CODEPAGE) == null)
{
SetProperty(PropertyIDMap.PID_CODEPAGE, Variant.VT_I2,
CodePageUtil.CP_UNICODE);
}
}
else
{
/* Setting the dictionary To null means To Remove property 0.
* However, it does not mean To Remove property 1 (codepage). */
RemoveProperty(PropertyIDMap.PID_DICTIONARY);
}
}
}
/// <summary>
/// Sets the property.
/// </summary>
/// <param name="id">The property ID.</param>
/// <param name="value">The property's value. The value's class must be one of those
/// supported by HPSF.</param>
public void SetProperty(int id, Object value)
{
if (value is String)
SetProperty(id, (String)value);
else if (value is long)
SetProperty(id, ((long)value));
else if (value is int)
SetProperty(id, value);
else if (value is short)
SetProperty(id, (short)value);
else if (value is bool)
SetProperty(id, (bool)value);
else if (value is DateTime)
SetProperty(id, Variant.VT_FILETIME, value);
else
throw new HPSFRuntimeException(
"HPSF does not support properties of type " +
value.GetType().Name + ".");
}
/// <summary>
/// Removes all properties from the section including 0 (dictionary) and
/// 1 (codepage).
/// </summary>
public void Clear()
{
Property[] properties = Properties;
for (int i = 0; i < properties.Length; i++)
{
Property p = properties[i];
RemoveProperty(p.ID);
}
}
/// <summary>
/// Gets the section's codepage, if any.
/// </summary>
/// <value>The section's codepage if one is defined, else -1.</value>
public new int Codepage
{
get { return base.Codepage; }
set
{
SetProperty(PropertyIDMap.PID_CODEPAGE, Variant.VT_I2,
value);
}
}
}
}