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.

EscherContainerRecord.java 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.ddf;
  16. import java.io.PrintWriter;
  17. import java.util.ArrayList;
  18. import java.util.Collections;
  19. import java.util.Iterator;
  20. import java.util.List;
  21. import java.util.Map;
  22. import java.util.Spliterator;
  23. import java.util.function.Supplier;
  24. import org.apache.logging.log4j.LogManager;
  25. import org.apache.logging.log4j.Logger;
  26. import org.apache.poi.util.GenericRecordUtil;
  27. import org.apache.poi.util.HexDump;
  28. import org.apache.poi.util.LittleEndian;
  29. import static org.apache.logging.log4j.util.Unbox.box;
  30. /**
  31. * Escher container records store other escher records as children.
  32. * The container records themselves never store any information beyond
  33. * the standard header used by all escher records. This one record is
  34. * used to represent many different types of records.
  35. */
  36. public final class EscherContainerRecord extends EscherRecord implements Iterable<EscherRecord> {
  37. public static final short DGG_CONTAINER = EscherRecordTypes.DGG_CONTAINER.typeID;
  38. public static final short BSTORE_CONTAINER = EscherRecordTypes.BSTORE_CONTAINER.typeID;
  39. public static final short DG_CONTAINER = EscherRecordTypes.DG_CONTAINER.typeID;
  40. public static final short SPGR_CONTAINER = EscherRecordTypes.SPGR_CONTAINER.typeID;
  41. public static final short SP_CONTAINER = EscherRecordTypes.SP_CONTAINER.typeID;
  42. public static final short SOLVER_CONTAINER = EscherRecordTypes.SOLVER_CONTAINER.typeID;
  43. private static final Logger LOGGER = LogManager.getLogger(EscherContainerRecord.class);
  44. /**
  45. * in case if document contains any charts we have such document structure:
  46. * BOF
  47. * ...
  48. * DrawingRecord
  49. * ...
  50. * ObjRecord|TxtObjRecord
  51. * ...
  52. * EOF
  53. * ...
  54. * BOF(Chart begin)
  55. * ...
  56. * DrawingRecord
  57. * ...
  58. * ObjRecord|TxtObjRecord
  59. * ...
  60. * EOF
  61. * So, when we call EscherAggregate.createAggregate() we have not all needed data.
  62. * When we got warning "WARNING: " + bytesRemaining + " bytes remaining but no space left"
  63. * we should save value of bytesRemaining
  64. * and add it to container size when we serialize it
  65. */
  66. private int _remainingLength;
  67. private final List<EscherRecord> _childRecords = new ArrayList<>();
  68. public EscherContainerRecord() {}
  69. public EscherContainerRecord(EscherContainerRecord other) {
  70. super(other);
  71. _remainingLength = other._remainingLength;
  72. other._childRecords.stream().map(EscherRecord::copy).forEach(_childRecords::add);
  73. }
  74. @Override
  75. public int fillFields(byte[] data, int pOffset, EscherRecordFactory recordFactory) {
  76. int bytesRemaining = readHeader(data, pOffset);
  77. int bytesWritten = 8;
  78. int offset = pOffset + 8;
  79. while (bytesRemaining > 0 && offset < data.length) {
  80. EscherRecord child = recordFactory.createRecord(data, offset);
  81. int childBytesWritten = child.fillFields(data, offset, recordFactory);
  82. bytesWritten += childBytesWritten;
  83. offset += childBytesWritten;
  84. bytesRemaining -= childBytesWritten;
  85. addChildRecord(child);
  86. if (offset >= data.length && bytesRemaining > 0) {
  87. _remainingLength = bytesRemaining;
  88. LOGGER.atWarn().log("Not enough Escher data: {} bytes remaining but no space left", box(bytesRemaining));
  89. }
  90. }
  91. return bytesWritten;
  92. }
  93. @Override
  94. public int serialize( int offset, byte[] data, EscherSerializationListener listener )
  95. {
  96. listener.beforeRecordSerialize( offset, getRecordId(), this );
  97. LittleEndian.putShort(data, offset, getOptions());
  98. LittleEndian.putShort(data, offset+2, getRecordId());
  99. int remainingBytes = 0;
  100. for (EscherRecord r : this) {
  101. remainingBytes += r.getRecordSize();
  102. }
  103. remainingBytes += _remainingLength;
  104. LittleEndian.putInt(data, offset+4, remainingBytes);
  105. int pos = offset+8;
  106. for (EscherRecord r : this) {
  107. pos += r.serialize(pos, data, listener );
  108. }
  109. listener.afterRecordSerialize( pos, getRecordId(), pos - offset, this );
  110. return pos - offset;
  111. }
  112. @Override
  113. public int getRecordSize() {
  114. int childRecordsSize = 0;
  115. for (EscherRecord r : this) {
  116. childRecordsSize += r.getRecordSize();
  117. }
  118. return 8 + childRecordsSize;
  119. }
  120. /**
  121. * Do any of our (top level) children have the given recordId?
  122. *
  123. * @param recordId the recordId of the child
  124. *
  125. * @return true, if any child has the given recordId
  126. */
  127. public boolean hasChildOfType(short recordId) {
  128. return _childRecords.stream().anyMatch(r -> r.getRecordId() == recordId);
  129. }
  130. @Override
  131. public EscherRecord getChild( int index ) {
  132. return _childRecords.get(index);
  133. }
  134. /**
  135. * @return a copy of the list of all the child records of the container.
  136. */
  137. @Override
  138. public List<EscherRecord> getChildRecords() {
  139. return new ArrayList<>(_childRecords);
  140. }
  141. public int getChildCount() {
  142. return _childRecords.size();
  143. }
  144. /**
  145. * @return an iterator over the child records
  146. */
  147. @Override
  148. public Iterator<EscherRecord> iterator() {
  149. return Collections.unmodifiableList(_childRecords).iterator();
  150. }
  151. /**
  152. * @return a spliterator over the child records
  153. *
  154. * @since POI 5.2.0
  155. */
  156. @Override
  157. public Spliterator<EscherRecord> spliterator() {
  158. return _childRecords.spliterator();
  159. }
  160. /**
  161. * replaces the internal child list with the contents of the supplied {@code childRecords}
  162. */
  163. @Override
  164. public void setChildRecords(List<EscherRecord> childRecords) {
  165. if (childRecords == _childRecords) {
  166. throw new IllegalStateException("Child records private data member has escaped");
  167. }
  168. _childRecords.clear();
  169. _childRecords.addAll(childRecords);
  170. }
  171. /**
  172. * Removes the given escher record from the child list
  173. *
  174. * @param toBeRemoved the escher record to be removed
  175. * @return true, if the record was found and removed
  176. */
  177. public boolean removeChildRecord(EscherRecord toBeRemoved) {
  178. return _childRecords.remove(toBeRemoved);
  179. }
  180. /**
  181. * Returns all of our children which are also
  182. * EscherContainers (may be 0, 1, or vary rarely 2 or 3)
  183. *
  184. * @return EscherContainer children
  185. */
  186. public List<EscherContainerRecord> getChildContainers() {
  187. List<EscherContainerRecord> containers = new ArrayList<>();
  188. for (EscherRecord r : this) {
  189. if(r instanceof EscherContainerRecord) {
  190. containers.add((EscherContainerRecord) r);
  191. }
  192. }
  193. return containers;
  194. }
  195. @Override
  196. public String getRecordName() {
  197. final short id = getRecordId();
  198. EscherRecordTypes t = EscherRecordTypes.forTypeID(id);
  199. return (t != EscherRecordTypes.UNKNOWN) ? t.recordName : "Container 0x" + HexDump.toHex(id);
  200. }
  201. @Override
  202. public void display(PrintWriter w, int indent) {
  203. super.display(w, indent);
  204. for (EscherRecord escherRecord : this) {
  205. escherRecord.display(w, indent + 1);
  206. }
  207. }
  208. /**
  209. * Append a child record
  210. *
  211. * @param record the record to be added
  212. */
  213. public void addChildRecord(EscherRecord record) {
  214. _childRecords.add(record);
  215. }
  216. /**
  217. * Add a child record before the record with given recordId
  218. *
  219. * @param record the record to be added
  220. * @param insertBeforeRecordId the recordId of the next sibling
  221. */
  222. public void addChildBefore(EscherRecord record, int insertBeforeRecordId) {
  223. int idx = 0;
  224. for (EscherRecord rec : this) {
  225. if(rec.getRecordId() == (short)insertBeforeRecordId) {
  226. break;
  227. }
  228. // TODO - keep looping? Do we expect multiple matches?
  229. idx++;
  230. }
  231. _childRecords.add(idx, record);
  232. }
  233. public <T extends EscherRecord> T getChildById( short recordId ) {
  234. for ( EscherRecord childRecord : this ) {
  235. if ( childRecord.getRecordId() == recordId ) {
  236. @SuppressWarnings( "unchecked" )
  237. final T result = (T) childRecord;
  238. return result;
  239. }
  240. }
  241. return null;
  242. }
  243. /**
  244. * Recursively find records with the specified record ID
  245. *
  246. * @param recordId the recordId to be searched for
  247. * @param out - list to store found records
  248. */
  249. public void getRecordsById(short recordId, List<EscherRecord> out){
  250. for (EscherRecord r : this) {
  251. if(r instanceof EscherContainerRecord) {
  252. EscherContainerRecord c = (EscherContainerRecord)r;
  253. c.getRecordsById(recordId, out );
  254. } else if (r.getRecordId() == recordId){
  255. out.add(r);
  256. }
  257. }
  258. }
  259. @Override
  260. public Map<String, Supplier<?>> getGenericProperties() {
  261. return GenericRecordUtil.getGenericProperties(
  262. "base", super::getGenericProperties,
  263. "isContainer", this::isContainerRecord
  264. );
  265. }
  266. @Override
  267. public Enum getGenericRecordType() {
  268. return EscherRecordTypes.forTypeID(getRecordId());
  269. }
  270. @Override
  271. public EscherContainerRecord copy() {
  272. return new EscherContainerRecord(this);
  273. }
  274. }