409 lines
12 KiB
Java
409 lines
12 KiB
Java
/* ====================================================================
|
|
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.hwpf.model;
|
|
|
|
import java.io.IOException;
|
|
|
|
import org.apache.poi.hwpf.model.io.HWPFOutputStream;
|
|
import org.apache.poi.hwpf.sprm.CharacterSprmUncompressor;
|
|
import org.apache.poi.hwpf.sprm.ParagraphSprmUncompressor;
|
|
import org.apache.poi.hwpf.usermodel.CharacterProperties;
|
|
import org.apache.poi.hwpf.usermodel.ParagraphProperties;
|
|
import org.apache.poi.util.Internal;
|
|
import org.apache.poi.util.LittleEndian;
|
|
|
|
/**
|
|
* Represents a document's stylesheet. A word documents formatting is stored as
|
|
* compressed styles that are based on styles contained in the stylesheet. This
|
|
* class also contains static utility functions to uncompress different
|
|
* formatting properties.
|
|
* <p>
|
|
* Fields documentation is quotes from Microsoft Office Word 97-2007 Binary File
|
|
* Format (.doc) Specification, page 36 of 210
|
|
*
|
|
* @author Ryan Ackley
|
|
*/
|
|
@Internal
|
|
public final class StyleSheet implements HDFType {
|
|
|
|
public static final int NIL_STYLE = 4095;
|
|
// private static final int PAP_TYPE = 1;
|
|
// private static final int CHP_TYPE = 2;
|
|
// private static final int SEP_TYPE = 4;
|
|
// private static final int TAP_TYPE = 5;
|
|
|
|
@Deprecated
|
|
private final static ParagraphProperties NIL_PAP = new ParagraphProperties();
|
|
@Deprecated
|
|
private final static CharacterProperties NIL_CHP = new CharacterProperties();
|
|
|
|
private final static byte[] NIL_CHPX = new byte[] {};
|
|
private final static byte[] NIL_PAPX = new byte[] {0, 0};
|
|
|
|
/**
|
|
* Size of the STSHI structure
|
|
*/
|
|
private int _cbStshi;
|
|
|
|
/**
|
|
* General information about a stylesheet
|
|
*/
|
|
private Stshif _stshif;
|
|
|
|
StyleDescription[] _styleDescriptions;
|
|
|
|
/**
|
|
* StyleSheet constructor. Loads a document's stylesheet information,
|
|
*
|
|
* @param tableStream A byte array containing a document's raw stylesheet
|
|
* info. Found by using FileInformationBlock.getFcStshf() and
|
|
* FileInformationBLock.getLcbStshf()
|
|
*/
|
|
public StyleSheet(byte[] tableStream, int offset)
|
|
{
|
|
int startOffset = offset;
|
|
_cbStshi = LittleEndian.getShort( tableStream, offset );
|
|
offset += LittleEndian.SHORT_SIZE;
|
|
|
|
/*
|
|
* Count of styles in stylesheet
|
|
*
|
|
* The number of styles in this style sheet. There will be stshi.cstd
|
|
* (cbSTD, STD) pairs in the file following the STSHI. Note: styles can
|
|
* be empty, i.e. cbSTD==0.
|
|
*/
|
|
|
|
_stshif = new Stshif( tableStream, offset );
|
|
offset += Stshif.getSize();
|
|
|
|
// shall we discard cbLSD and mpstilsd?
|
|
|
|
offset = startOffset + LittleEndian.SHORT_SIZE + _cbStshi;
|
|
_styleDescriptions = new StyleDescription[_stshif.getCstd()];
|
|
for(int x = 0; x < _stshif.getCstd(); x++)
|
|
{
|
|
int stdSize = LittleEndian.getShort(tableStream, offset);
|
|
//get past the size
|
|
offset += 2;
|
|
if(stdSize > 0)
|
|
{
|
|
//byte[] std = new byte[stdSize];
|
|
|
|
StyleDescription aStyle = new StyleDescription(tableStream,
|
|
_stshif.getCbSTDBaseInFile(), offset, true);
|
|
|
|
_styleDescriptions[x] = aStyle;
|
|
}
|
|
|
|
offset += stdSize;
|
|
|
|
}
|
|
for(int x = 0; x < _styleDescriptions.length; x++)
|
|
{
|
|
if(_styleDescriptions[x] != null)
|
|
{
|
|
createPap(x);
|
|
createChp(x);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void writeTo(HWPFOutputStream out)
|
|
throws IOException
|
|
{
|
|
|
|
int offset = 0;
|
|
|
|
/*
|
|
* we don't support 2003 Word extensions in STSHI (but may be we should
|
|
* at least not delete them, shouldn't we?), so our structure is always
|
|
* 18 bytes in length -- sergey
|
|
*/
|
|
this._cbStshi = 18;
|
|
|
|
// add two bytes so we can prepend the stylesheet w/ its size
|
|
byte[] buf = new byte[_cbStshi + 2];
|
|
|
|
LittleEndian.putUShort(buf, offset, (short)_cbStshi);
|
|
offset += LittleEndian.SHORT_SIZE;
|
|
|
|
_stshif.setCstd( _styleDescriptions.length );
|
|
_stshif.serialize( buf, offset );
|
|
// offset += Stshif.getSize();
|
|
|
|
out.write(buf);
|
|
|
|
byte[] sizeHolder = new byte[2];
|
|
for (int x = 0; x < _styleDescriptions.length; x++)
|
|
{
|
|
if(_styleDescriptions[x] != null)
|
|
{
|
|
byte[] std = _styleDescriptions[x].toByteArray();
|
|
|
|
// adjust the size so it is always on a word boundary
|
|
LittleEndian.putShort(sizeHolder, 0, (short)((std.length) + (std.length % 2)));
|
|
out.write(sizeHolder);
|
|
out.write(std);
|
|
|
|
// Must always start on a word boundary.
|
|
if (std.length % 2 == 1)
|
|
{
|
|
out.write('\0');
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sizeHolder[0] = 0;
|
|
sizeHolder[1] = 0;
|
|
out.write(sizeHolder);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object o)
|
|
{
|
|
if (!(o instanceof StyleSheet)) return false;
|
|
StyleSheet ss = (StyleSheet)o;
|
|
|
|
if (!ss._stshif.equals( this._stshif )
|
|
|| ss._cbStshi != this._cbStshi
|
|
|| ss._styleDescriptions.length != this._styleDescriptions.length
|
|
) return false;
|
|
|
|
for (int i=0; i<_styleDescriptions.length; i++) {
|
|
StyleDescription tsd = this._styleDescriptions[i];
|
|
StyleDescription osd = ss._styleDescriptions[i];
|
|
if (tsd == null && osd == null) continue;
|
|
if (tsd == null || osd == null || !osd.equals(tsd)) return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
assert false : "hashCode not designed";
|
|
return 42; // any arbitrary constant will do
|
|
}
|
|
|
|
/**
|
|
* Creates a PartagraphProperties object from a papx stored in the
|
|
* StyleDescription at the index istd in the StyleDescription array. The PAP
|
|
* is placed in the StyleDescription at istd after its been created. Not
|
|
* every StyleDescription will contain a papx. In these cases this function
|
|
* does nothing
|
|
*
|
|
* @param istd The index of the StyleDescription to create the
|
|
* ParagraphProperties from (and also place the finished PAP in)
|
|
*/
|
|
@Deprecated
|
|
private void createPap(int istd)
|
|
{
|
|
StyleDescription sd = _styleDescriptions[istd];
|
|
ParagraphProperties pap = sd.getPAP();
|
|
byte[] papx = sd.getPAPX();
|
|
int baseIndex = sd.getBaseStyle();
|
|
if(pap == null && papx != null)
|
|
{
|
|
ParagraphProperties parentPAP = new ParagraphProperties();
|
|
if(baseIndex != NIL_STYLE)
|
|
{
|
|
|
|
parentPAP = _styleDescriptions[baseIndex].getPAP();
|
|
if(parentPAP == null) {
|
|
if(baseIndex == istd) {
|
|
// Oh dear, style claims that it is its own parent
|
|
throw new IllegalStateException("Pap style " + istd + " claimed to have itself as its parent, which isn't allowed");
|
|
}
|
|
// Create the parent style
|
|
createPap(baseIndex);
|
|
parentPAP = _styleDescriptions[baseIndex].getPAP();
|
|
}
|
|
|
|
}
|
|
|
|
if (parentPAP == null) {
|
|
parentPAP = new ParagraphProperties();
|
|
}
|
|
|
|
pap = ParagraphSprmUncompressor.uncompressPAP(parentPAP, papx, 2);
|
|
sd.setPAP(pap);
|
|
}
|
|
}
|
|
/**
|
|
* Creates a CharacterProperties object from a chpx stored in the
|
|
* StyleDescription at the index istd in the StyleDescription array. The
|
|
* CharacterProperties object is placed in the StyleDescription at istd after
|
|
* its been created. Not every StyleDescription will contain a chpx. In these
|
|
* cases this function does nothing.
|
|
*
|
|
* @param istd The index of the StyleDescription to create the
|
|
* CharacterProperties object from.
|
|
*/
|
|
@Deprecated
|
|
private void createChp(int istd)
|
|
{
|
|
StyleDescription sd = _styleDescriptions[istd];
|
|
CharacterProperties chp = sd.getCHP();
|
|
byte[] chpx = sd.getCHPX();
|
|
int baseIndex = sd.getBaseStyle();
|
|
|
|
if(baseIndex == istd) {
|
|
// Oh dear, this isn't allowed...
|
|
// The word file seems to be corrupted
|
|
// Switch to using the nil style so that
|
|
// there's a chance we can read it
|
|
baseIndex = NIL_STYLE;
|
|
}
|
|
|
|
// Build and decompress the Chp if required
|
|
if(chp == null && chpx != null)
|
|
{
|
|
CharacterProperties parentCHP = new CharacterProperties();
|
|
if(baseIndex != NIL_STYLE)
|
|
{
|
|
parentCHP = _styleDescriptions[baseIndex].getCHP();
|
|
if(parentCHP == null)
|
|
{
|
|
createChp(baseIndex);
|
|
parentCHP = _styleDescriptions[baseIndex].getCHP();
|
|
}
|
|
if(parentCHP == null) {
|
|
parentCHP = new CharacterProperties();
|
|
}
|
|
}
|
|
|
|
chp = CharacterSprmUncompressor.uncompressCHP(parentCHP, chpx, 0);
|
|
sd.setCHP(chp);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the number of styles in the style sheet.
|
|
* @return The number of styles in the style sheet.
|
|
*/
|
|
public int numStyles() {
|
|
return _styleDescriptions.length;
|
|
}
|
|
|
|
/**
|
|
* Gets the StyleDescription at index x.
|
|
*
|
|
* @param styleIndex
|
|
* the index of the desired StyleDescription.
|
|
*/
|
|
public StyleDescription getStyleDescription( int styleIndex )
|
|
{
|
|
return _styleDescriptions[styleIndex];
|
|
}
|
|
|
|
@Deprecated
|
|
public CharacterProperties getCharacterStyle( int styleIndex )
|
|
{
|
|
if ( styleIndex == NIL_STYLE )
|
|
{
|
|
return NIL_CHP;
|
|
}
|
|
|
|
if ( styleIndex >= _styleDescriptions.length )
|
|
{
|
|
return NIL_CHP;
|
|
}
|
|
|
|
return ( _styleDescriptions[styleIndex] != null ? _styleDescriptions[styleIndex]
|
|
.getCHP() : NIL_CHP );
|
|
}
|
|
|
|
@Deprecated
|
|
public ParagraphProperties getParagraphStyle( int styleIndex )
|
|
{
|
|
if ( styleIndex == NIL_STYLE )
|
|
{
|
|
return NIL_PAP;
|
|
}
|
|
|
|
if ( styleIndex >= _styleDescriptions.length )
|
|
{
|
|
return NIL_PAP;
|
|
}
|
|
|
|
if ( _styleDescriptions[styleIndex] == null )
|
|
{
|
|
return NIL_PAP;
|
|
}
|
|
|
|
if ( _styleDescriptions[styleIndex].getPAP() == null )
|
|
{
|
|
return NIL_PAP;
|
|
}
|
|
|
|
return _styleDescriptions[styleIndex].getPAP();
|
|
}
|
|
|
|
public byte[] getCHPX( int styleIndex )
|
|
{
|
|
if ( styleIndex == NIL_STYLE )
|
|
{
|
|
return NIL_CHPX;
|
|
}
|
|
|
|
if ( styleIndex >= _styleDescriptions.length )
|
|
{
|
|
return NIL_CHPX;
|
|
}
|
|
|
|
if ( _styleDescriptions[styleIndex] == null )
|
|
{
|
|
return NIL_CHPX;
|
|
}
|
|
|
|
if ( _styleDescriptions[styleIndex].getCHPX() == null )
|
|
{
|
|
return NIL_CHPX;
|
|
}
|
|
|
|
return _styleDescriptions[styleIndex].getCHPX();
|
|
}
|
|
|
|
public byte[] getPAPX( int styleIndex )
|
|
{
|
|
if ( styleIndex == NIL_STYLE )
|
|
{
|
|
return NIL_PAPX;
|
|
}
|
|
|
|
if ( styleIndex >= _styleDescriptions.length )
|
|
{
|
|
return NIL_PAPX;
|
|
}
|
|
|
|
if ( _styleDescriptions[styleIndex] == null )
|
|
{
|
|
return NIL_PAPX;
|
|
}
|
|
|
|
if ( _styleDescriptions[styleIndex].getPAPX() == null )
|
|
{
|
|
return NIL_PAPX;
|
|
}
|
|
|
|
return _styleDescriptions[styleIndex].getPAPX();
|
|
}
|
|
}
|