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
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 package org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure;
19 import java.util.ArrayList;
20 import java.util.Iterator;
21 import java.util.List;
23 import org.apache.pdfbox.cos.COSArray;
24 import org.apache.pdfbox.cos.COSBase;
25 import org.apache.pdfbox.cos.COSDictionary;
26 import org.apache.pdfbox.cos.COSInteger;
27 import org.apache.pdfbox.cos.COSName;
28 import org.apache.pdfbox.cos.COSObject;
29 import org.apache.pdfbox.pdmodel.common.COSArrayList;
30 import org.apache.pdfbox.pdmodel.common.COSObjectable;
31 import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObject;
32 import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
35 * A node in the structure tree.
38 * @version $Revision: $
40 public abstract class PDStructureNode implements COSObjectable
44 * Creates a node in the structure tree. Can be either a structure tree root,
45 * or a structure element.
47 * @param node the node dictionary
48 * @return the structure node
50 public static PDStructureNode create(COSDictionary node)
52 String type = node.getNameAsString(COSName.TYPE);
53 if ("StructTreeRoot".equals(type))
55 return new PDStructureTreeRoot(node);
57 if ((type == null) || "StructElem".equals(type))
59 return new PDStructureElement(node);
61 throw new IllegalArgumentException("Dictionary must not include a Type entry with a value that is neither StructTreeRoot nor StructElem.");
65 private COSDictionary dictionary;
67 protected COSDictionary getCOSDictionary()
75 * @param type the type
77 protected PDStructureNode(String type)
79 this.dictionary = new COSDictionary();
80 this.dictionary.setName(COSName.TYPE, type);
84 * Constructor for an existing structure node.
86 * @param dictionary The existing dictionary.
88 protected PDStructureNode(COSDictionary dictionary)
90 this.dictionary = dictionary;
96 public COSBase getCOSObject()
98 return this.dictionary;
106 public String getType()
108 return this.getCOSDictionary().getNameAsString(COSName.TYPE);
112 * Returns a list of objects for the kids (K).
114 * @return a list of objects for the kids
116 public List<Object> getKids()
118 List<Object> kidObjects = new ArrayList<Object>();
119 COSBase k = this.getCOSDictionary().getDictionaryObject(COSName.K);
120 if (k instanceof COSArray)
122 Iterator<COSBase> kids = ((COSArray) k).iterator();
123 while (kids.hasNext())
125 COSBase kid = kids.next();
126 Object kidObject = this.createObject(kid);
127 if (kidObject != null)
129 kidObjects.add(kidObject);
135 Object kidObject = this.createObject(k);
136 if (kidObject != null)
138 kidObjects.add(kidObject);
147 * @param kids the kids
149 public void setKids(List<Object> kids)
151 this.getCOSDictionary().setItem(COSName.K,
152 COSArrayList.converterToCOSArray(kids));
156 * Appends a structure element kid.
158 * @param structureElement the structure element
160 public void appendKid(PDStructureElement structureElement)
162 this.appendObjectableKid(structureElement);
163 structureElement.setParent(this);
167 * Appends an objectable kid.
169 * @param objectable the objectable
171 protected void appendObjectableKid(COSObjectable objectable)
173 if (objectable == null)
177 this.appendKid(objectable.getCOSObject());
181 * Appends a COS base kid.
183 * @param object the COS base
185 protected void appendKid(COSBase object)
191 COSBase k = this.getCOSDictionary().getDictionaryObject(COSName.K);
194 // currently no kid: set new kid as kids
195 this.getCOSDictionary().setItem(COSName.K, object);
197 else if (k instanceof COSArray)
199 // currently more than one kid: add new kid to existing array
200 COSArray array = (COSArray) k;
205 // currently one kid: put current and new kid into array and set array as kids
206 COSArray array = new COSArray();
209 this.getCOSDictionary().setItem(COSName.K, array);
214 * Inserts a structure element kid before a reference kid.
216 * @param newKid the structure element
217 * @param refKid the reference kid
219 public void insertBefore(PDStructureElement newKid, Object refKid)
221 this.insertObjectableBefore(newKid, refKid);
225 * Inserts an objectable kid before a reference kid.
227 * @param newKid the objectable
228 * @param refKid the reference kid
230 protected void insertObjectableBefore(COSObjectable newKid, Object refKid)
236 this.insertBefore(newKid.getCOSObject(), refKid);
240 * Inserts an COS base kid before a reference kid.
242 * @param newKid the COS base
243 * @param refKid the reference kid
245 protected void insertBefore(COSBase newKid, Object refKid)
247 if ((newKid == null) || (refKid == null))
251 COSBase k = this.getCOSDictionary().getDictionaryObject(COSName.K);
256 COSBase refKidBase = null;
257 if (refKid instanceof COSObjectable)
259 refKidBase = ((COSObjectable) refKid).getCOSObject();
261 else if (refKid instanceof COSInteger)
263 refKidBase = (COSInteger) refKid;
265 if (k instanceof COSArray)
267 COSArray array = (COSArray) k;
268 int refIndex = array.indexOfObject(refKidBase);
269 array.add(refIndex, newKid.getCOSObject());
273 boolean onlyKid = k.equals(refKidBase);
274 if (!onlyKid && (k instanceof COSObject))
276 COSBase kObj = ((COSObject) k).getObject();
277 onlyKid = kObj.equals(refKidBase);
281 COSArray array = new COSArray();
283 array.add(refKidBase);
284 this.getCOSDictionary().setItem(COSName.K, array);
290 * Removes a structure element kid.
292 * @param structureElement the structure element
293 * @return <code>true</code> if the kid was removed, <code>false</code> otherwise
295 public boolean removeKid(PDStructureElement structureElement)
297 boolean removed = this.removeObjectableKid(structureElement);
300 structureElement.setParent(null);
306 * Removes an objectable kid.
308 * @param objectable the objectable
309 * @return <code>true</code> if the kid was removed, <code>false</code> otherwise
311 protected boolean removeObjectableKid(COSObjectable objectable)
313 if (objectable == null)
317 return this.removeKid(objectable.getCOSObject());
321 * Removes a COS base kid.
323 * @param object the COS base
324 * @return <code>true</code> if the kid was removed, <code>false</code> otherwise
326 protected boolean removeKid(COSBase object)
332 COSBase k = this.getCOSDictionary().getDictionaryObject(COSName.K);
335 // no kids: objectable is not a kid
338 else if (k instanceof COSArray)
340 // currently more than one kid: remove kid from existing array
341 COSArray array = (COSArray) k;
342 boolean removed = array.removeObject(object);
343 // if now only one kid: set remaining kid as kids
344 if (array.size() == 1)
346 this.getCOSDictionary().setItem(COSName.K, array.getObject(0));
352 // currently one kid: if current kid equals given object, remove kids entry
353 boolean onlyKid = k.equals(object);
354 if (!onlyKid && (k instanceof COSObject))
356 COSBase kObj = ((COSObject) k).getObject();
357 onlyKid = kObj.equals(object);
361 this.getCOSDictionary().setItem(COSName.K, null);
369 * Creates an object for a kid of this structure node.
370 * The type of object depends on the type of the kid. It can be
372 * <li>a {@link PDStructureElement},</li>
373 * <li>a {@link PDAnnotation},</li>
374 * <li>a {@link PDXObject},</li>
375 * <li>a {@link PDMarkedContentReference}</li>
376 * <li>a {@link Integer}</li>
382 protected Object createObject(COSBase kid)
384 COSDictionary kidDic = null;
385 if (kid instanceof COSDictionary)
387 kidDic = (COSDictionary) kid;
389 else if (kid instanceof COSObject)
391 COSBase base = ((COSObject) kid).getObject();
392 if (base instanceof COSDictionary)
394 kidDic = (COSDictionary) base;
399 String type = kidDic.getNameAsString(COSName.TYPE);
400 if ((type == null) || PDStructureElement.TYPE.equals(type))
402 // A structure element dictionary denoting another structure
404 return new PDStructureElement(kidDic);
406 else if (PDObjectReference.TYPE.equals(type))
408 // An object reference dictionary denoting a PDF object
409 return new PDObjectReference(kidDic);
411 else if (PDMarkedContentReference.TYPE.equals(type))
413 // A marked-content reference dictionary denoting a
414 // marked-content sequence
415 return new PDMarkedContentReference(kidDic);
418 else if (kid instanceof COSInteger)
420 // An integer marked-content identifier denoting a
421 // marked-content sequence
422 COSInteger mcid = (COSInteger) kid;
423 return mcid.intValue();