| /* ==================================================================== |
| 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 java.io.OutputStream; |
| |
| 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; |
| import org.apache.poi.util.LittleEndianConsts; |
| |
| /** |
| * 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 { |
| |
| 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 static final ParagraphProperties NIL_PAP = new ParagraphProperties(); |
| @Deprecated |
| private static final CharacterProperties NIL_CHP = new CharacterProperties(); |
| |
| private static final byte[] NIL_CHPX = new byte[]{}; |
| private static final 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 += LittleEndianConsts.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); |
| |
| // shall we discard cbLSD and mpstilsd? |
| |
| offset = startOffset + LittleEndianConsts.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(OutputStream 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 += LittleEndianConsts.SHORT_SIZE; |
| |
| _stshif.setCstd(_styleDescriptions.length); |
| _stshif.serialize(buf, offset); |
| // offset += Stshif.getSize(); |
| |
| out.write(buf); |
| |
| byte[] sizeHolder = new byte[2]; |
| for (StyleDescription styleDescription : _styleDescriptions) { |
| if (styleDescription != null) { |
| byte[] std = styleDescription.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 (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; |
| } |
| |
| if (styleIndex == -1) { |
| 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 (styleIndex == -1) { |
| 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 (styleIndex == -1) { |
| 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 (styleIndex == -1) { |
| return NIL_PAPX; |
| } |
| |
| if (_styleDescriptions[styleIndex] == null) { |
| return NIL_PAPX; |
| } |
| |
| if (_styleDescriptions[styleIndex].getPAPX() == null) { |
| return NIL_PAPX; |
| } |
| |
| return _styleDescriptions[styleIndex].getPAPX(); |
| } |
| } |