You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pdfbox.apache.org by le...@apache.org on 2013/11/17 16:27:34 UTC

svn commit: r1542748 [5/5] - in /pdfbox/branches/1.8: ./ examples/src/main/java/org/apache/pdfbox/examples/fdf/ examples/src/main/java/org/apache/pdfbox/examples/signature/ fontbox/src/main/java/org/apache/fontbox/cff/ fontbox/src/main/java/org/apache/...

Modified: pdfbox/branches/1.8/pdfbox/src/main/java/org/apache/pdfbox/util/PDFMergerUtility.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/1.8/pdfbox/src/main/java/org/apache/pdfbox/util/PDFMergerUtility.java?rev=1542748&r1=1542747&r2=1542748&view=diff
==============================================================================
--- pdfbox/branches/1.8/pdfbox/src/main/java/org/apache/pdfbox/util/PDFMergerUtility.java (original)
+++ pdfbox/branches/1.8/pdfbox/src/main/java/org/apache/pdfbox/util/PDFMergerUtility.java Sun Nov 17 15:27:33 2013
@@ -22,15 +22,18 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 
 import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
 import org.apache.pdfbox.cos.COSDictionary;
 import org.apache.pdfbox.cos.COSInteger;
 import org.apache.pdfbox.cos.COSName;
 import org.apache.pdfbox.cos.COSNumber;
 import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.cos.COSString;
 import org.apache.pdfbox.exceptions.COSVisitorException;
 import org.apache.pdfbox.pdmodel.PDDocument;
 import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
@@ -38,7 +41,11 @@ import org.apache.pdfbox.pdmodel.PDDocum
 import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
 import org.apache.pdfbox.pdmodel.PDPage;
 import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.PDNumberTreeNode;
 import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDMarkInfo;
+import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureTreeRoot;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
 import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
 import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
 import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
@@ -46,14 +53,14 @@ import org.apache.pdfbox.pdmodel.interac
 import org.apache.pdfbox.pdmodel.interactive.form.PDFieldFactory;
 
 /**
- * This class will take a list of pdf documents and merge them, saving the result
- * in a new document.
- *
+ * This class will take a list of pdf documents and merge them, saving the result in a new document.
+ * 
  * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
- * @version $Revision: 1.3 $
+ * 
  */
 public class PDFMergerUtility
 {
+    private static final String STRUCTURETYPE_DOCUMENT = "Document";
 
     private List<InputStream> sources;
     private String destinationFileName;
@@ -70,6 +77,7 @@ public class PDFMergerUtility
 
     /**
      * Get the name of the destination file.
+     * 
      * @return Returns the destination.
      */
     public String getDestinationFileName()
@@ -79,8 +87,8 @@ public class PDFMergerUtility
 
     /**
      * Set the name of the destination file.
-     * @param destination
-     *            The destination to set.
+     * 
+     * @param destination The destination to set.
      */
     public void setDestinationFileName(String destination)
     {
@@ -89,6 +97,7 @@ public class PDFMergerUtility
 
     /**
      * Get the destination OutputStream.
+     * 
      * @return Returns the destination OutputStream.
      */
     public OutputStream getDestinationStream()
@@ -98,6 +107,7 @@ public class PDFMergerUtility
 
     /**
      * Set the destination OutputStream.
+     * 
      * @param destStream The destination to set.
      */
     public void setDestinationStream(OutputStream destStream)
@@ -107,7 +117,7 @@ public class PDFMergerUtility
 
     /**
      * Add a source file to the list of files to merge.
-     *
+     * 
      * @param source Full path and file name of source document.
      */
     public void addSource(String source)
@@ -116,7 +126,7 @@ public class PDFMergerUtility
         {
             sources.add(new FileInputStream(new File(source)));
         }
-        catch(Exception e)
+        catch (Exception e)
         {
             throw new RuntimeException(e);
         }
@@ -124,7 +134,7 @@ public class PDFMergerUtility
 
     /**
      * Add a source file to the list of files to merge.
-     *
+     * 
      * @param source File representing source document
      */
     public void addSource(File source)
@@ -133,7 +143,7 @@ public class PDFMergerUtility
         {
             sources.add(new FileInputStream(source));
         }
-        catch(Exception e)
+        catch (Exception e)
         {
             throw new RuntimeException(e);
         }
@@ -141,7 +151,7 @@ public class PDFMergerUtility
 
     /**
      * Add a source to the list of documents to merge.
-     *
+     * 
      * @param source InputStream representing source document
      */
     public void addSource(InputStream source)
@@ -151,17 +161,17 @@ public class PDFMergerUtility
 
     /**
      * Add a list of sources to the list of documents to merge.
-     *
+     * 
      * @param sourcesList List of InputStream objects representing source documents
      */
     public void addSources(List<InputStream> sourcesList)
     {
-        this.sources.addAll(sourcesList);
+        sources.addAll(sourcesList);
     }
 
     /**
      * Merge the list of source documents, saving the result in the destination file.
-     *
+     * 
      * @throws IOException If there is an error saving the document.
      * @throws COSVisitorException If an error occurs while saving the destination file.
      */
@@ -187,7 +197,7 @@ public class PDFMergerUtility
                     tobeclosed.add(source);
                     appendDocument(destination, source);
                 }
-                if(destinationStream == null)
+                if (destinationStream == null)
                 {
                     destination.save(destinationFileName);
                 }
@@ -210,52 +220,51 @@ public class PDFMergerUtility
         }
     }
 
-
     /**
      * append all pages from source to destination.
-     *
+     * 
      * @param destination the document to receive the pages
      * @param source the document originating the new pages
-     *
+     * 
      * @throws IOException If there is an error accessing data from either document.
      */
     public void appendDocument(PDDocument destination, PDDocument source) throws IOException
     {
-        if( destination.isEncrypted() )
+        if (destination.isEncrypted())
         {
-            throw new IOException( "Error: destination PDF is encrypted, can't append encrypted PDF documents." );
+            throw new IOException("Error: destination PDF is encrypted, can't append encrypted PDF documents.");
         }
-        if( source.isEncrypted() )
+        if (source.isEncrypted())
         {
-            throw new IOException( "Error: source PDF is encrypted, can't append encrypted PDF documents." );
+            throw new IOException("Error: source PDF is encrypted, can't append encrypted PDF documents.");
         }
         PDDocumentInformation destInfo = destination.getDocumentInformation();
         PDDocumentInformation srcInfo = source.getDocumentInformation();
-        destInfo.getDictionary().mergeInto( srcInfo.getDictionary() );
+        destInfo.getDictionary().mergeInto(srcInfo.getDictionary());
 
         PDDocumentCatalog destCatalog = destination.getDocumentCatalog();
         PDDocumentCatalog srcCatalog = source.getDocumentCatalog();
 
         // use the highest version number for the resulting pdf
-        float destVersion = destination.getDocument().getVersion(); 
-        float srcVersion = source.getDocument().getVersion(); 
+        float destVersion = destination.getDocument().getVersion();
+        float srcVersion = source.getDocument().getVersion();
 
         if (destVersion < srcVersion)
         {
             destination.getDocument().setVersion(srcVersion);
         }
-            
-        if( destCatalog.getOpenAction() == null )
+
+        if (destCatalog.getOpenAction() == null)
         {
-            destCatalog.setOpenAction( srcCatalog.getOpenAction() );
+            destCatalog.setOpenAction(srcCatalog.getOpenAction());
         }
 
-        // maybe there are some shared resources for all pages 
-        COSDictionary srcPages = (COSDictionary)srcCatalog.getCOSDictionary().getDictionaryObject( COSName.PAGES );
-        COSDictionary srcResources = (COSDictionary)srcPages.getDictionaryObject( COSName.RESOURCES );
-        COSDictionary destPages = (COSDictionary)destCatalog.getCOSDictionary().getDictionaryObject( COSName.PAGES );
-        COSDictionary destResources = (COSDictionary)destPages.getDictionaryObject( COSName.RESOURCES );
-        if (srcResources != null) 
+        // maybe there are some shared resources for all pages
+        COSDictionary srcPages = (COSDictionary) srcCatalog.getCOSDictionary().getDictionaryObject(COSName.PAGES);
+        COSDictionary srcResources = (COSDictionary) srcPages.getDictionaryObject(COSName.RESOURCES);
+        COSDictionary destPages = (COSDictionary) destCatalog.getCOSDictionary().getDictionaryObject(COSName.PAGES);
+        COSDictionary destResources = (COSDictionary) destPages.getDictionaryObject(COSName.RESOURCES);
+        if (srcResources != null)
         {
             if (destResources != null)
             {
@@ -266,56 +275,54 @@ public class PDFMergerUtility
                 destPages.setItem(COSName.RESOURCES, srcResources);
             }
         }
-        
+
         PDFCloneUtility cloner = new PDFCloneUtility(destination);
 
         try
         {
             PDAcroForm destAcroForm = destCatalog.getAcroForm();
             PDAcroForm srcAcroForm = srcCatalog.getAcroForm();
-            if( destAcroForm == null )
+            if (destAcroForm == null)
             {
-                cloner.cloneForNewDocument( srcAcroForm );
-                destCatalog.setAcroForm( srcAcroForm );
+                cloner.cloneForNewDocument(srcAcroForm);
+                destCatalog.setAcroForm(srcAcroForm);
             }
             else
             {
-                if( srcAcroForm != null )
+                if (srcAcroForm != null)
                 {
                     mergeAcroForm(cloner, destAcroForm, srcAcroForm);
                 }
             }
         }
-        catch(Exception e)
+        catch (Exception e)
         {
             // if we are not ignoring exceptions, we'll re-throw this
-            if(!ignoreAcroFormErrors)
+            if (!ignoreAcroFormErrors)
             {
-                throw (IOException)e;
+                throw (IOException) e;
             }
         }
 
-        COSArray destThreads = (COSArray)destCatalog.getCOSDictionary().getDictionaryObject(
-                COSName.THREADS);
-        COSArray srcThreads = (COSArray)cloner.cloneForNewDocument(
-                destCatalog.getCOSDictionary().getDictionaryObject( COSName.THREADS ));
-        if( destThreads == null )
+        COSArray destThreads = (COSArray) destCatalog.getCOSDictionary().getDictionaryObject(COSName.THREADS);
+        COSArray srcThreads = (COSArray) cloner.cloneForNewDocument(destCatalog.getCOSDictionary().getDictionaryObject(
+                COSName.THREADS));
+        if (destThreads == null)
         {
-            destCatalog.getCOSDictionary().setItem( COSName.THREADS, srcThreads );
+            destCatalog.getCOSDictionary().setItem(COSName.THREADS, srcThreads);
         }
         else
         {
-            destThreads.addAll( srcThreads );
+            destThreads.addAll(srcThreads);
         }
 
         PDDocumentNameDictionary destNames = destCatalog.getNames();
         PDDocumentNameDictionary srcNames = srcCatalog.getNames();
-        if( srcNames != null )
+        if (srcNames != null)
         {
-            if( destNames == null )
+            if (destNames == null)
             {
-                destCatalog.getCOSDictionary().setItem( COSName.NAMES,
-                        cloner.cloneForNewDocument( srcNames ) );
+                destCatalog.getCOSDictionary().setItem(COSName.NAMES, cloner.cloneForNewDocument(srcNames));
             }
             else
             {
@@ -326,142 +333,336 @@ public class PDFMergerUtility
 
         PDDocumentOutline destOutline = destCatalog.getDocumentOutline();
         PDDocumentOutline srcOutline = srcCatalog.getDocumentOutline();
-        if( srcOutline != null )
+        if (srcOutline != null)
         {
-            if( destOutline == null )
+            if (destOutline == null)
             {
-                PDDocumentOutline cloned =
-                    new PDDocumentOutline( (COSDictionary)cloner.cloneForNewDocument( srcOutline ) );
-                destCatalog.setDocumentOutline( cloned );
+                PDDocumentOutline cloned = new PDDocumentOutline((COSDictionary) cloner.cloneForNewDocument(srcOutline));
+                destCatalog.setDocumentOutline(cloned);
             }
             else
             {
                 PDOutlineItem first = srcOutline.getFirstChild();
-                if(first != null)
+                if (first != null)
                 {
-                    PDOutlineItem clonedFirst = new PDOutlineItem(
-                            (COSDictionary)cloner.cloneForNewDocument( first ));
-                    destOutline.appendChild( clonedFirst );
+                    PDOutlineItem clonedFirst = new PDOutlineItem((COSDictionary) cloner.cloneForNewDocument(first));
+                    destOutline.appendChild(clonedFirst);
                 }
             }
         }
 
         String destPageMode = destCatalog.getPageMode();
         String srcPageMode = srcCatalog.getPageMode();
-        if( destPageMode == null )
+        if (destPageMode == null)
         {
-            destCatalog.setPageMode( srcPageMode );
+            destCatalog.setPageMode(srcPageMode);
         }
 
-        COSDictionary destLabels = (COSDictionary)destCatalog.getCOSDictionary().getDictionaryObject(
+        COSDictionary destLabels = (COSDictionary) destCatalog.getCOSDictionary().getDictionaryObject(
                 COSName.PAGE_LABELS);
-        COSDictionary srcLabels = (COSDictionary)srcCatalog.getCOSDictionary().getDictionaryObject(
-                COSName.PAGE_LABELS);
-        if( srcLabels != null )
+        COSDictionary srcLabels = (COSDictionary) srcCatalog.getCOSDictionary()
+                .getDictionaryObject(COSName.PAGE_LABELS);
+        if (srcLabels != null)
         {
             int destPageCount = destination.getNumberOfPages();
             COSArray destNums = null;
-            if( destLabels == null )
+            if (destLabels == null)
             {
                 destLabels = new COSDictionary();
                 destNums = new COSArray();
-                destLabels.setItem( COSName.NUMS, destNums );
-                destCatalog.getCOSDictionary().setItem( COSName.PAGE_LABELS, destLabels );
+                destLabels.setItem(COSName.NUMS, destNums);
+                destCatalog.getCOSDictionary().setItem(COSName.PAGE_LABELS, destLabels);
             }
             else
             {
-                destNums = (COSArray)destLabels.getDictionaryObject( COSName.NUMS );
+                destNums = (COSArray) destLabels.getDictionaryObject(COSName.NUMS);
             }
-            COSArray srcNums = (COSArray)srcLabels.getDictionaryObject( COSName.NUMS );
+            COSArray srcNums = (COSArray) srcLabels.getDictionaryObject(COSName.NUMS);
             if (srcNums != null)
             {
-                for( int i=0; i<srcNums.size(); i+=2 )
+                for (int i = 0; i < srcNums.size(); i += 2)
                 {
-                    COSNumber labelIndex = (COSNumber)srcNums.getObject( i );
+                    COSNumber labelIndex = (COSNumber) srcNums.getObject(i);
                     long labelIndexValue = labelIndex.intValue();
-                    destNums.add( COSInteger.get( labelIndexValue + destPageCount ) );
-                    destNums.add( cloner.cloneForNewDocument( srcNums.getObject( i+1 ) ) );
+                    destNums.add(COSInteger.get(labelIndexValue + destPageCount));
+                    destNums.add(cloner.cloneForNewDocument(srcNums.getObject(i + 1)));
                 }
             }
         }
 
-        COSStream destMetadata = (COSStream)destCatalog.getCOSDictionary().getDictionaryObject( COSName.METADATA );
-        COSStream srcMetadata = (COSStream)srcCatalog.getCOSDictionary().getDictionaryObject( COSName.METADATA );
-        if( destMetadata == null && srcMetadata != null )
+        COSStream destMetadata = (COSStream) destCatalog.getCOSDictionary().getDictionaryObject(COSName.METADATA);
+        COSStream srcMetadata = (COSStream) srcCatalog.getCOSDictionary().getDictionaryObject(COSName.METADATA);
+        if (destMetadata == null && srcMetadata != null)
         {
-            PDStream newStream = new PDStream( destination, srcMetadata.getUnfilteredStream(), false );
-            newStream.getStream().mergeInto( srcMetadata );
+            PDStream newStream = new PDStream(destination, srcMetadata.getUnfilteredStream(), false);
+            newStream.getStream().mergeInto(srcMetadata);
             newStream.addCompression();
-            destCatalog.getCOSDictionary().setItem( COSName.METADATA, newStream );
+            destCatalog.getCOSDictionary().setItem(COSName.METADATA, newStream);
+        }
+
+        // merge logical structure hierarchy if logical structure information is available in both source pdf and
+        // destination pdf
+        boolean mergeStructTree = false;
+        int destParentTreeNextKey = -1;
+        COSDictionary destParentTreeDict = null;
+        COSDictionary srcParentTreeDict = null;
+        COSArray destNumbersArray = null;
+        COSArray srcNumbersArray = null;
+        PDMarkInfo destMark = destCatalog.getMarkInfo();
+        PDStructureTreeRoot destStructTree = destCatalog.getStructureTreeRoot();
+        PDMarkInfo srcMark = srcCatalog.getMarkInfo();
+        PDStructureTreeRoot srcStructTree = srcCatalog.getStructureTreeRoot();
+        if (destStructTree != null)
+        {
+            PDNumberTreeNode destParentTree = destStructTree.getParentTree();
+            destParentTreeNextKey = destStructTree.getParentTreeNextKey();
+            if (destParentTree != null)
+            {
+                destParentTreeDict = destParentTree.getCOSDictionary();
+                destNumbersArray = (COSArray) destParentTreeDict.getDictionaryObject(COSName.NUMS);
+                if (destNumbersArray != null)
+                {
+                    if (destParentTreeNextKey < 0)
+                    {
+                        destParentTreeNextKey = destNumbersArray.size() / 2;
+                    }
+                    if (destParentTreeNextKey > 0)
+                    {
+                        if (srcStructTree != null)
+                        {
+                            PDNumberTreeNode srcParentTree = srcStructTree.getParentTree();
+                            if (srcParentTree != null)
+                            {
+                                srcParentTreeDict = srcParentTree.getCOSDictionary();
+                                srcNumbersArray = (COSArray) srcParentTreeDict.getDictionaryObject(COSName.NUMS);
+                                if (srcNumbersArray != null)
+                                {
+                                    mergeStructTree = true;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            if (destMark != null && destMark.isMarked() && !mergeStructTree)
+            {
+                destMark.setMarked(false);
+            }
+            if (!mergeStructTree)
+            {
+                destCatalog.setStructureTreeRoot(null);
+            }
         }
 
-        //finally append the pages
+        // finally append the pages
         List<PDPage> pages = srcCatalog.getAllPages();
         Iterator<PDPage> pageIter = pages.iterator();
-        while( pageIter.hasNext() )
+        HashMap<COSDictionary, COSDictionary> objMapping = new HashMap<COSDictionary, COSDictionary>();
+        while (pageIter.hasNext())
         {
             PDPage page = pageIter.next();
-            PDPage newPage =
-                new PDPage( (COSDictionary)cloner.cloneForNewDocument( page.getCOSDictionary() ) );
-            newPage.setCropBox( page.findCropBox() );
-            newPage.setMediaBox( page.findMediaBox() );
-            newPage.setRotation( page.findRotation() );
-            destination.addPage( newPage );
+            PDPage newPage = new PDPage((COSDictionary) cloner.cloneForNewDocument(page.getCOSDictionary()));
+            newPage.setCropBox(page.findCropBox());
+            newPage.setMediaBox(page.findMediaBox());
+            newPage.setRotation(page.findRotation());
+            if (mergeStructTree)
+            {
+                updateStructParentEntries(newPage, destParentTreeNextKey);
+                objMapping.put(page.getCOSDictionary(), newPage.getCOSDictionary());
+                List<PDAnnotation> oldAnnots = page.getAnnotations();
+                List<PDAnnotation> newAnnots = newPage.getAnnotations();
+                for (int i = 0; i < oldAnnots.size(); i++)
+                {
+                    objMapping.put(oldAnnots.get(i).getDictionary(), newAnnots.get(i).getDictionary());
+                }
+                // TODO update mapping for XObjects
+            }
+            destination.addPage(newPage);
         }
-    }
+        if (mergeStructTree)
+        {
+            updatePageReferences(srcNumbersArray, objMapping);
+            for (int i = 0; i < srcNumbersArray.size() / 2; i++)
+            {
+                destNumbersArray.add(COSInteger.get(destParentTreeNextKey + i));
+                destNumbersArray.add(srcNumbersArray.getObject(i * 2 + 1));
+            }
+            destParentTreeNextKey += srcNumbersArray.size() / 2;
+            destParentTreeDict.setItem(COSName.NUMS, destNumbersArray);
+            PDNumberTreeNode newParentTreeNode = new PDNumberTreeNode(destParentTreeDict, COSBase.class);
+            destStructTree.setParentTree(newParentTreeNode);
+            destStructTree.setParentTreeNextKey(destParentTreeNextKey);
 
+            COSDictionary kDictLevel0 = new COSDictionary();
+            COSArray newKArray = new COSArray();
+            COSArray destKArray = destStructTree.getKArray();
+            COSArray srcKArray = srcStructTree.getKArray();
+            if (destKArray != null && srcKArray != null)
+            {
+                updateParentEntry(destKArray, kDictLevel0);
+                newKArray.addAll(destKArray);
+                if (mergeStructTree)
+                {
+                    updateParentEntry(srcKArray, kDictLevel0);
+                }
+                newKArray.addAll(srcKArray);
+            }
+            kDictLevel0.setItem(COSName.K, newKArray);
+            kDictLevel0.setItem(COSName.P, destStructTree);
+            kDictLevel0.setItem(COSName.S, new COSString(STRUCTURETYPE_DOCUMENT));
+            destStructTree.setK(kDictLevel0);
+        }
+    }
 
     private int nextFieldNum = 1;
 
     /**
-     * Merge the contents of the source form into the destination form
-     * for the destination file.
-     *
+     * Merge the contents of the source form into the destination form for the destination file.
+     * 
      * @param cloner the object cloner for the destination document
      * @param destAcroForm the destination form
      * @param srcAcroForm the source form
      * @throws IOException If an error occurs while adding the field.
      */
     private void mergeAcroForm(PDFCloneUtility cloner, PDAcroForm destAcroForm, PDAcroForm srcAcroForm)
-        throws IOException
+            throws IOException
     {
         List destFields = destAcroForm.getFields();
         List srcFields = srcAcroForm.getFields();
-        if( srcFields != null )
+        if (srcFields != null)
         {
-            if( destFields == null )
+            if (destFields == null)
             {
                 destFields = new COSArrayList();
-                destAcroForm.setFields( destFields );
+                destAcroForm.setFields(destFields);
             }
             Iterator srcFieldsIterator = srcFields.iterator();
             while (srcFieldsIterator.hasNext())
             {
-                PDField srcField = (PDField)srcFieldsIterator.next();
-                PDField destField =
-                    PDFieldFactory.createField(
-                        destAcroForm,
-                        (COSDictionary)cloner.cloneForNewDocument(srcField.getDictionary() ));
+                PDField srcField = (PDField) srcFieldsIterator.next();
+                PDField destField = PDFieldFactory.createField(destAcroForm,
+                        (COSDictionary) cloner.cloneForNewDocument(srcField.getDictionary()));
                 // if the form already has a field with this name then we need to rename this field
                 // to prevent merge conflicts.
-                if ( destAcroForm.getField(destField.getFullyQualifiedName()) != null )
+                if (destAcroForm.getField(destField.getFullyQualifiedName()) != null)
                 {
-                    destField.setPartialName("dummyFieldName"+(nextFieldNum++));
+                    destField.setPartialName("dummyFieldName" + (nextFieldNum++));
                 }
                 destFields.add(destField);
             }
         }
     }
 
+    /**
+     * Indicates if acroform errors are ignored or not.
+     * 
+     * @return true if acroform errors are ignored
+     */
     public boolean isIgnoreAcroFormErrors()
     {
         return ignoreAcroFormErrors;
     }
 
-    public void setIgnoreAcroFormErrors(boolean ignoreAcroFormErrors)
+    /**
+     * Set to true to ignore acroform errors.
+     * 
+     * @param ignoreAcroFormErrorsValue true if acroform errors should be ignored
+     */
+    public void setIgnoreAcroFormErrors(boolean ignoreAcroFormErrorsValue)
     {
-        this.ignoreAcroFormErrors = ignoreAcroFormErrors;
+        ignoreAcroFormErrors = ignoreAcroFormErrorsValue;
     }
 
+    /**
+     * Update the Pg and Obj references to the new (merged) page.
+     * 
+     * @param parentTreeEntry
+     * @param objMapping mapping between old and new references
+     */
+    private void updatePageReferences(COSDictionary parentTreeEntry, HashMap<COSDictionary, COSDictionary> objMapping)
+    {
+        COSBase page = parentTreeEntry.getDictionaryObject(COSName.PG);
+        if (page instanceof COSDictionary)
+        {
+            if (objMapping.containsKey(page))
+            {
+                parentTreeEntry.setItem(COSName.PG, objMapping.get(page));
+            }
+        }
+        COSBase obj = parentTreeEntry.getDictionaryObject(COSName.OBJ);
+        if (obj instanceof COSDictionary)
+        {
+            if (objMapping.containsKey(obj))
+            {
+                parentTreeEntry.setItem(COSName.OBJ, objMapping.get(obj));
+            }
+        }
+        COSBase kSubEntry = parentTreeEntry.getDictionaryObject(COSName.K);
+        if (kSubEntry instanceof COSArray)
+        {
+            updatePageReferences((COSArray) kSubEntry, objMapping);
+        }
+        else if (kSubEntry instanceof COSDictionary)
+        {
+            updatePageReferences((COSDictionary) kSubEntry, objMapping);
+        }
+    }
+
+    private void updatePageReferences(COSArray parentTreeEntry, HashMap<COSDictionary, COSDictionary> objMapping)
+    {
+        for (int i = 0; i < parentTreeEntry.size(); i++)
+        {
+            COSBase subEntry = parentTreeEntry.getObject(i);
+            if (subEntry instanceof COSArray)
+            {
+                updatePageReferences((COSArray) subEntry, objMapping);
+            }
+            else if (subEntry instanceof COSDictionary)
+            {
+                updatePageReferences((COSDictionary) subEntry, objMapping);
+            }
+        }
+    }
+
+    /**
+     * Update the P reference to the new parent dictionary.
+     * 
+     * @param kArray the kids array
+     * @param newParent the new parent
+     */
+    private void updateParentEntry(COSArray kArray, COSDictionary newParent)
+    {
+        for (int i = 0; i < kArray.size(); i++)
+        {
+            COSBase subEntry = kArray.getObject(i);
+            if (subEntry instanceof COSDictionary)
+            {
+                COSDictionary dictEntry = (COSDictionary) subEntry;
+                if (dictEntry.getDictionaryObject(COSName.P) != null)
+                {
+                    dictEntry.setItem(COSName.P, newParent);
+                }
+            }
+        }
+    }
+
+    /**
+     * Update the StructParents and StructParent values in a PDPage.
+     * 
+     * @param page the new page
+     * @param structParentOffset the offset which should be applied
+     */
+    private void updateStructParentEntries(PDPage page, int structParentOffset) throws IOException
+    {
+        page.setStructParents(page.getStructParents() + structParentOffset);
+        List<PDAnnotation> annots = page.getAnnotations();
+        List<PDAnnotation> newannots = new ArrayList<PDAnnotation>();
+        for (PDAnnotation annot : annots)
+        {
+            annot.setStructParent(annot.getStructParent() + structParentOffset);
+            newannots.add(annot);
+        }
+        page.setAnnotations(newannots);
+    }
 
 }

Modified: pdfbox/branches/1.8/pdfbox/src/test/java/org/apache/pdfbox/util/TestDateUtil.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/1.8/pdfbox/src/test/java/org/apache/pdfbox/util/TestDateUtil.java?rev=1542748&r1=1542747&r2=1542748&view=diff
==============================================================================
--- pdfbox/branches/1.8/pdfbox/src/test/java/org/apache/pdfbox/util/TestDateUtil.java (original)
+++ pdfbox/branches/1.8/pdfbox/src/test/java/org/apache/pdfbox/util/TestDateUtil.java Sun Nov 17 15:27:33 2013
@@ -17,22 +17,32 @@
 package org.apache.pdfbox.util;
 
 import java.io.IOException;
+import java.text.ParsePosition;
 import java.util.Calendar;
 import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.SimpleTimeZone;
 import java.util.TimeZone;
 
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
 
+import org.apache.pdfbox.cos.COSString;
+
 /**
  * Test the date conversion utility.
  *
  * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
- * @version $Revision: 1.2 $
+ * @author <a href="mailto:zweibieren@ahoo.com">Fred Hansen</a>
+ * 
  */
 public class TestDateUtil extends TestCase
 {
+    private static final int MINS = 60*1000, HRS = 60*MINS;
+    // expect parse fail
+    private static final int BAD = -666;  
+    
     /**
      * Test class constructor.
      *
@@ -45,29 +55,68 @@ public class TestDateUtil extends TestCa
         super( name );
     }
 
+    
+    ////////////////////////////////////////////////////
+    // Test body follows
+    
+    
     /**
      * Test common date formats.
      *
      * @throws Exception when there is an exception
      */
-    public void testExtract()
-        throws Exception
+    public void testExtract() throws Exception
     {
         TimeZone timezone = TimeZone.getDefault();
         TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
-        try {
-            assertEquals( DateConverter.toCalendar( "D:05/12/2005" ), new GregorianCalendar( 2005, 4, 12 ) );
-            assertEquals( DateConverter.toCalendar( "5/12/2005 15:57:16" ), new GregorianCalendar( 2005, 4,12,15,57,16 ) );
-        } finally {
+        try 
+        {
+            assertCalendarEquals( new GregorianCalendar( 2005, 4, 12 ), 
+                    DateConverter.toCalendar( "D:05/12/2005" ) );
+            assertCalendarEquals( new GregorianCalendar( 2005, 4,12,15,57,16 ), 
+                    DateConverter.toCalendar( "5/12/2005 15:57:16" ) );
+        }
+        catch (IOException ex) 
+        {
+            ex.printStackTrace();
+        }
+        finally 
+        {
             TimeZone.setDefault(timezone);
         }
+        // check that new toCalendar gives NullPointer for a null arg
+        try 
+        { 
+            DateConverter.toCalendar(null, null);
+            assertNotNull(null);    // failed to have expected exception
+        } 
+        catch (NullPointerException ex) 
+        {
+            // expected outcome
+        }   
     }
-
+    
+    /**
+     * Calendar.equals test case.
+     * 
+     * @param expect the expected calendar value
+     * @param was the calendar value to be checked
+     */
+    public void assertCalendarEquals(Calendar expect, Calendar was) 
+    {
+        assertEquals( expect.getTimeInMillis(), was.getTimeInMillis() );
+        assertEquals( expect.getTimeZone().getRawOffset(), 
+                was.getTimeZone().getRawOffset() );
+    }
+    
     /**
      * Test case for
-     * <a href="https://issues.apache.org/jira/browse/PDFBOX-598">PDFBOX-598</a>
+     * <a href="https://issues.apache.org/jira/browse/PDFBOX-598">PDFBOX-598</a>.
+     * 
+     * @throws IOException if something went wrong.
      */
-    public void testDateConversion() throws Exception { 
+    public void testDateConversion() throws IOException 
+    { 
         Calendar c = DateConverter.toCalendar("D:20050526205258+01'00'"); 
         assertEquals(2005, c.get(Calendar.YEAR)); 
         assertEquals(05-1, c.get(Calendar.MONTH)); 
@@ -78,42 +127,323 @@ public class TestDateUtil extends TestCa
         assertEquals(0, c.get(Calendar.MILLISECOND)); 
     }
 
-    public void testDateConverter() throws Exception {
-        TimeZone timezone = TimeZone.getDefault();
-        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
-        try {
-            assertDate("2010-01-01T00:00:00+00:00", "D:2010");
-            assertDate("2010-01-01T00:00:00+00:00", "2010");
-            assertDate("2010-04-23T00:00:00+00:00", "D:20100423");
-            assertDate("2010-04-23T00:00:00+00:00", "20100423");
-
-            // assertDate("2007-04-30T19:36:47+????", "20070430193647+713'00'");
-            // assertDate("2007-08-21T10:35:22+00:00", "Tue Aug 21 10:35:22 2007");
-            assertDate("2008-11-04T00:00:00+00:00", "Tuesday, November 04, 2008");
-            // assertDate("2007-12-17T02:02:03+00:00", "200712172:2:3");
-            // assertDate("????", "Unknown");
-            // assertDate("2009-03-19T20:01:22+00:00", "20090319 200122");
-            //  assertDate("2008-05-12T09:47:00+00:00", "9:47 5/12/2008");
-
-            // assertDate("2009-04-01T00:00:00+02:00", "20090401+0200");
-            assertDate("2008-01-11T00:00:00+00:00", "Friday, January 11, 2008");
-            // assertDate("2009-04-01T00:00:00+04:00", "20090401+04'00'");
-            // assertDate("2009-04-01T00:00:00+09:00", "20090401+09'00'");
-            // assertDate("2009-04-01T00:00:00-02:00", "20090401-02'00'");
-            // assertDate("2009-04-01T06:01:01+00:00", "20090401 01:01:01 -0500");
-            // assertDate("2000-05-26T11:25:10+00:00", "26 May 2000 11:25:10");
-            // assertDate("2000-05-26T11:25:00+00:00", "26 May 2000 11:25");
-        } finally {
-            TimeZone.setDefault(timezone);
+    /**
+     * Check toCalendar.
+     * @param yr expected year value
+     *  If an IOException is the expected result, yr should be null
+     * @param mon expected month value
+     * @param day expected dayofmonth value
+     * @param hr expected hour value
+     * @param min expected minute value
+     * @param sec expected second value
+     * @param tz represents expected timezone offset 
+     * @param orig  A date to be parsed.
+     * @throws Exception If an unexpected error occurs.
+     */
+    private static void checkParse(int yr, int mon, int day, 
+                int hr, int min, int sec, int offset,  
+                String orig) throws Exception 
+    {
+        String pdfDate = String.format("D:%04d%02d%02d%02d%02d%02d%+03d'00'", 
+                yr,mon,day,hr,min,sec,offset);
+        String iso8601Date = String.format("%04d-%02d-%02d"
+                + "T%02d:%02d:%02d%+03d:00", 
+                yr,mon,day,hr,min,sec,offset);
+        Calendar cal = null;
+        try 
+        {
+            cal = DateConverter.toCalendar(orig);
+        }
+        catch (IOException ex) 
+        {
+            assertEquals(yr, BAD);
         }
+        if (cal != null) 
+        {
+            assertEquals(iso8601Date, DateConverter.toISO8601(cal));
+            assertEquals(pdfDate, DateConverter.toString(cal));
+        }
+        // new toCalendar()
+        cal = DateConverter.toCalendar(orig, null);
+        if (yr == BAD) 
+        {
+            assertEquals(cal.get(Calendar.YEAR), DateConverter.INVALID_YEAR);
+        }
+        else
+        {
+            assertEquals(pdfDate, DateConverter.toString(cal));
+        }
+    }
+
+    /**
+     * Test dates in various formats.
+     * Years differ to make it easier to find failures.
+     * @throws Exception none expected
+     */
+    public void testDateConverter() throws Exception 
+    {
+            int year = Calendar.getInstance().get(Calendar.YEAR);
+            checkParse(2010, 4,23, 0, 0, 0, 0, "D:20100423");
+            checkParse(2011, 4,23, 0, 0, 0, 0, "20110423");
+            checkParse(2012, 1, 1, 0, 0, 0, 0, "D:2012");
+            checkParse(2013, 1, 1, 0, 0, 0, 0, "2013");
+
+            // PDFBOX-1219
+            checkParse(2001, 1,31,10,33, 0, +1,  "2001-01-31T10:33+01:00  ");   
+            // PDFBOX-465
+            checkParse(2002, 5,12, 9,47, 0, 0, "9:47 5/12/2002");  
+            // PDFBOX-465
+            checkParse(2003,12,17, 2, 2, 3, 0, "200312172:2:3"); 
+            // PDFBOX-465
+            checkParse(2009, 3,19,20, 1,22, 0, "  20090319 200122");  
+
+            checkParse(2014, 4, 1, 0, 0, 0, +2, "20140401+0200");
+            // "EEEE, MMM dd, yy",
+            checkParse(2115, 1,11, 0, 0, 0, 0, "Friday, January 11, 2115");  
+            // "EEEE, MMM dd, yy",
+            checkParse(1915, 1,11, 0, 0, 0, 0, "Monday, Jan 11, 1915");  
+            // "EEEE, MMM dd, yy",
+            checkParse(2215, 1,11, 0, 0, 0, 0, "Wed, January 11, 2215");  
+            // "EEEE, MMM dd, yy",
+            checkParse(2015, 1,11, 0, 0, 0, 0, " Sun, January 11, 2015 ");  
+            checkParse(2016, 4, 1, 0, 0, 0, +4, "20160401+04'00'");
+            checkParse(2017, 4, 1, 0, 0, 0, +9, "20170401+09'00'");
+            checkParse(2018, 4, 1, 0, 0, 0, -2, "20180401-02'00'");
+            checkParse(2019, 4, 1, 6, 1, 1, -11, "20190401 6:1:1 -1100");
+            checkParse(2020, 5,26,11,25,10, 0, "26 May 2020 11:25:10");
+            checkParse(2021, 5,26,11,23, 0, 0, "26 May 2021 11:23");
+
+            // try dates invalid due to out of limit values
+            checkParse(BAD, 0, 0, 0, 0, 0,  0,  "Tuesday, May 32 2000 11:27 UCT");
+            checkParse(BAD, 0, 0, 0, 0, 0,  0,  "32 May 2000 11:25");
+            checkParse(BAD, 0, 0, 0, 0, 0,  0,  "Tuesday, May 32 2000 11:25");
+            checkParse(BAD, 0, 0, 0, 0, 0,  0,  "19921301 11:25");
+            checkParse(BAD, 0, 0, 0, 0, 0,  0,  "19921232 11:25");
+            checkParse(BAD, 0, 0, 0, 0, 0,  0,  "19921001 11:60");
+            checkParse(BAD, 0, 0, 0, 0, 0,  0,  "19920401 24:25");
+            
+            checkParse(BAD, 0, 0, 0, 0, 0,  0, 
+            "20070430193647+713'00' illegal tz hr");  // PDFBOX-465
+            checkParse(BAD, 0, 0, 0, 0, 0,  0, "nodigits");
+            checkParse(BAD, 0, 0, 0, 0, 0,  0, "Unknown"); // PDFBOX-465
+            checkParse(BAD, 0, 0, 0, 0, 0,  0, "333three digit year");
+            
+            checkParse(2000, 2,29, 0, 0, 0, 0, "2000 Feb 29"); // valid date
+            checkParse(2000, 2,29, 0, 0, 0,+11, " 2000 Feb 29 GMT + 11:00"); // valid date
+            checkParse(BAD, 0, 0, 0, 0, 0,  0, "2100 Feb 29 GMT+11"); // invalid date
+            checkParse(2012, 2,29, 0, 0, 0,+11, "2012 Feb 29 GMT+11"); // valid date
+            checkParse(BAD, 0, 0, 0, 0, 0,  0, "2012 Feb 30 GMT+11"); // invalid date
+
+            checkParse(1970,12,23, 0, 8, 0,  0, "1970 12 23:08");  // test ambiguous date 
+            
+            // cannot have P for PM
+            // cannot have Sat. instead of Sat
+            // EST works, but EDT does not; EST is a special kludge in Java
+            
+            // test cases for all entries on old formats list
+            //  "E, dd MMM yyyy hh:mm:ss a"  
+            checkParse(1971, 7, 6, 17, 22, 1, 0, "Tuesday, 6 Jul 1971 5:22:1 PM"); 
+            //  "EE, MMM dd, yyyy hh:mm:ss a"
+            checkParse(1972, 7, 6, 17, 22, 1, 0, "Thu, July 6, 1972 5:22:1 pm");   
+            //  "MM/dd/yyyy hh:mm:ss"
+            checkParse(1973, 7, 6, 17, 22, 1, 0, "7/6/1973 17:22:1");   
+            //  "MM/dd/yyyy"
+            checkParse(1974, 7, 6, 0, 0, 0, 0, "7/6/1974");   
+            //  "yyyy-MM-dd'T'HH:mm:ss'Z'"
+            checkParse(1975, 7, 6, 17, 22, 1, -10, "1975-7-6T17:22:1-1000");   
+            //  "yyyy-MM-dd'T'HH:mm:ssz"
+            checkParse(1976, 7, 6, 17, 22, 1, -4, "1976-7-6T17:22:1GMT-4");   
+            //  "yyyy-MM-dd'T'HH:mm:ssz"
+            checkParse(BAD, 7, 6, 17, 22, 1, -4, "2076-7-6T17:22:1EDT");   // "EDT" is not a known tz ID
+            //  "yyyy-MM-dd'T'HH:mm:ssz"
+            // ATTENTION ****************************************
+            // changed from "EST" to "GMT-5", as this doesn't work on java5
+            // ATTENTION ****************************************
+            checkParse(1960, 7, 6, 17, 22, 1, -5, "1960-7-6T17:22:1GMT-5");   // "EST" does not have a DST rule
+           //  "EEEE, MMM dd, yyyy"
+            checkParse(1977, 7, 6, 0, 0, 0, 0, "Wednesday, Jul 6, 1977");   
+            //  "EEEE MMM dd, yyyy HH:mm:ss"
+            checkParse(1978, 7, 6, 17, 22, 1, 0, "Thu Jul 6, 1978 17:22:1");   
+            //  "EEEE MMM dd HH:mm:ss z yyyy"
+            checkParse(1979, 7, 6, 17, 22, 1, +8, "Friday July 6 17:22:1 GMT+08:00 1979");   
+            //  "EEEE, MMM dd, yyyy 'at' hh:mma"
+            checkParse(1980, 7, 6, 16, 23, 0, 0, "Sun, Jul 6, 1980 at 4:23pm");   
+            //  "EEEEEEEEEE, MMMMMMMMMMMM dd, yyyy"
+            checkParse(1981, 7, 6, 0, 0, 0, 0, "Monday, July 6, 1981");   
+            //  "dd MMM yyyy hh:mm:ss"
+            checkParse(1982, 7, 6, 17, 22, 1, 0, "6 Jul 1982 17:22:1");   
+            //  "M/dd/yyyy hh:mm:ss"
+            checkParse(1983, 7, 6, 17, 22, 1, 0, "7/6/1983 17:22:1");   
+            //  "MM/d/yyyy hh:mm:ss"
+            checkParse(1984, 7, 6, 17, 22, 1, 0, "7/6/1984 17:22:01");   
+            //  "M/dd/yyyy"
+            checkParse(1985, 7, 6, 0, 0, 0, 0, "7/6/1985");   
+            //  "MM/d/yyyy"
+            checkParse(1986, 7, 6, 0, 0, 0, 0, "07/06/1986");   
+            //  "M/d/yyyy hh:mm:ss"
+            checkParse(1987, 7, 6, 17, 22, 1, 0, "7/6/1987 17:22:1");   
+            //  "M/d/yyyy"
+            checkParse(1988, 7, 6, 0, 0, 0, 0, "7/6/1988");   
+
+            // test ends of range of two digit years
+            checkParse(year-79, 1, 1, 0, 0, 0, 0, "1/1/" + ((year-79)%100)
+                    + " 00:00:00");   //  "M/d/yy hh:mm:ss"
+            //  "M/d/yy"
+            checkParse(year+19, 1, 1, 0, 0, 0, 0, "1/1/" + ((year+19)%100));   
+            
+            //  "yyyyMMdd hh:mm:ss Z"  
+            checkParse(1991, 7, 6, 17, 7, 1, +6, "19910706 17:7:1 Z+0600");   
+            //  "yyyyMMdd hh:mm:ss"
+            checkParse(1992, 7, 6, 17, 7, 1, 0, "19920706 17:07:01");   
+            //  "yyyyMMdd'+00''00'''"
+            checkParse(1993, 7, 6, 0, 0, 0, 0, "19930706+00'00'");   
+            //  "yyyyMMdd'+01''00'''"
+            checkParse(1994, 7, 6, 0, 0, 0, 1, "19940706+01'00'");   
+            //  "yyyyMMdd'+02''00'''"
+            checkParse(1995, 7, 6, 0, 0, 0, 2, "19950706+02'00'");   
+            //  "yyyyMMdd'+03''00'''"
+            checkParse(1996, 7, 6, 0, 0, 0, 3, "19960706+03'00'");   
+             //   . . .
+            // "yyyyMMdd'-10''00'''"
+            checkParse(1997, 7, 6, 0, 0, 0, -10, "19970706-10'00'");   
+            // "yyyyMMdd'-11''00'''"
+            checkParse(1998, 7, 6, 0, 0, 0, -11, "19980706-11'00'");   
+            //  "yyyyMMdd"
+            checkParse(1999, 7, 6, 0, 0, 0, 0, "19990706");   
+            // ambiguous big-endian date
+            checkParse(2073,12,25, 0, 8, 0, 0, "2073 12 25:08"); 
+            
     }
 
-    private void assertDate(String expected, String date) throws Exception {
-        Calendar calendar = DateConverter.toCalendar(date);
-        assertEquals(expected, DateConverter.toISO8601(calendar));
+    private static void checkToString(int yr, int mon, int day, 
+                int hr, int min, int sec, TimeZone tz, int off) throws Exception 
+    {
+        // construct a GregoreanCalendar from args
+        GregorianCalendar cal = new GregorianCalendar(tz, Locale.ENGLISH);
+        cal.set(yr, mon-1, day, hr, min, sec);
+        // create expected strings
+        String pdfDate = String.format("D:%04d%02d%02d%02d%02d%02d%+03d'00'", 
+                yr,mon,day,hr,min,sec,off);
+        String iso8601Date = String.format("%04d-%02d-%02d"
+                + "T%02d:%02d:%02d%+03d:00", 
+                yr,mon,day,hr,min,sec,off);
+        // compare outputs from toString and toISO8601 with expected values
+        assertEquals(pdfDate, DateConverter.toString(cal));
+        assertEquals(iso8601Date, DateConverter.toISO8601(cal));
+    }
+    
+    /** 
+     * Test toString() and toISO8601() for various dates.
+     * 
+     * @throws Exception if something went wrong.
+     */
+    public void testToString() throws Exception 
+    {                                                              // std DST
+        TimeZone tzPgh = TimeZone.getTimeZone("America/New_York");   // -5 -4
+        TimeZone tzBerlin = TimeZone.getTimeZone("Europe/Berlin");   // +1 +2
+        TimeZone tzMaputo = TimeZone.getTimeZone("Africa/Maputo");   // +2 +2
+        TimeZone tzAruba = TimeZone.getTimeZone("America/Aruba");    // -4 -4
+        TimeZone tzJamaica = TimeZone.getTimeZone("America/Jamaica");// -5 -5
+        TimeZone tzMcMurdo = TimeZone.getTimeZone("Antartica/McMurdo");// +12 +13
+        
+        assertNull(DateConverter.toCalendar((COSString) null));
+        assertNull(DateConverter.toCalendar((String) null));
+        
+        checkToString(2013, 8, 28, 3, 14, 15, tzPgh, -4);
+        checkToString(2014, 2, 28, 3, 14, 15, tzPgh, -5);
+        checkToString(2015, 8, 28, 3, 14, 15, tzBerlin, +2);
+        checkToString(2016, 2, 28, 3, 14, 15, tzBerlin, +1);
+        checkToString(2017, 8, 28, 3, 14, 15, tzAruba, -4);
+        checkToString(2018, 1, 1, 1, 14, 15, tzJamaica, -5);
+        checkToString(2019, 12, 31, 12, 59, 59, tzJamaica, -5);
+        checkToString(2020, 2, 29, 0, 0, 0, tzMaputo, +2);
+        // McMurdo has a daylightsavings rule, but it seems never to apply
+        checkToString(1981, 1, 1, 1, 14, 15, tzMcMurdo, +0);
+        checkToString(1982, 2, 1, 1, 14, 15, tzMcMurdo, +0);
+        checkToString(1983, 3, 1, 1, 14, 15, tzMcMurdo, +0);
+        checkToString(1984, 4, 1, 1, 14, 15, tzMcMurdo, +0);
+        checkToString(1985, 5, 1, 1, 14, 15, tzMcMurdo, +0);
+        checkToString(1986, 6, 1, 1, 14, 15, tzMcMurdo, +0);
+        checkToString(1987, 7, 1, 1, 14, 15, tzMcMurdo, +0);
+        checkToString(1988, 8, 1, 1, 14, 15, tzMcMurdo, +0);
+        checkToString(1989, 9, 1, 1, 14, 15, tzMcMurdo, +0);
+        checkToString(1990, 10, 1, 1, 14, 15, tzMcMurdo, +0);
+        checkToString(1991, 11, 1, 1, 14, 15, tzMcMurdo, +0);
+        checkToString(1992, 12, 1, 1, 14, 15, tzMcMurdo, +0);
+    }
+   
+    private static void checkParseTZ(int expect, String src) 
+    {
+        GregorianCalendar dest = DateConverter.newGreg();
+        DateConverter.parseTZoffset(src, dest, new ParsePosition(0));
+        assertEquals(expect, dest.get(Calendar.ZONE_OFFSET));
     }
 
     /**
+     * Timezone testcase.
+     */
+    public void testParseTZ() 
+    {
+        checkParseTZ(0*HRS+0*MINS, "+00:00");
+        checkParseTZ(0*HRS+0*MINS, "-0000");
+        checkParseTZ(1*HRS+0*MINS, "+1:00");
+        checkParseTZ(-(1*HRS+0*MINS), "-1:00");
+        checkParseTZ(-(1*HRS+30*MINS), "-0130");
+        checkParseTZ(11*HRS+59*MINS, "1159");
+        checkParseTZ(-(11*HRS+30*MINS), "1230");
+        checkParseTZ(11*HRS+30*MINS, "-12:30");
+        checkParseTZ(0*HRS+0*MINS, "Z");
+        checkParseTZ(-(8*HRS+0*MINS), "PST");
+        checkParseTZ(0*HRS+0*MINS, "EDT");  // EDT does not parse
+        checkParseTZ(-(3*HRS+0*MINS), "GMT-0300");
+        checkParseTZ(+(11*HRS+0*MINS), "GMT+11:00");
+        checkParseTZ(-(6*HRS+0*MINS), "America/Chicago");
+        // ATTENTION ****************************************
+        // changed from "Europe/Moscow" to "Europe/Berlin", as this doesn't work on java5
+        // ATTENTION ****************************************
+        checkParseTZ(+(1*HRS+0*MINS), "Europe/Berlin");
+        checkParseTZ((5*HRS+0*MINS), "0500");
+        checkParseTZ((5*HRS+0*MINS), "+0500");
+        checkParseTZ((11*HRS+0*MINS), "+11'00'");
+        checkParseTZ(0, "Z");
+    }
+    
+    private static void checkFormatOffset(double off, String expect) 
+    {
+        TimeZone tz = new SimpleTimeZone((int)(off*60*60*1000), "junkID");
+        String got = DateConverter.formatTZoffset(tz.getRawOffset(), ":");
+        assertEquals(expect, got);
+    }
+    
+    /**
+     * Timezone offset testcase.
+     * 
+     * @throws Exception
+     */
+    public void testFormatTZoffset()
+    {
+        checkFormatOffset(-12.1, "+11:54");
+        checkFormatOffset(12.1, "-11:54");
+        checkFormatOffset(0, "+00:00");
+        checkFormatOffset(-1, "-01:00");
+        checkFormatOffset(.5, "+00:30");
+        checkFormatOffset(-0.5, "-00:30");
+        checkFormatOffset(.1, "+00:06");
+        checkFormatOffset(-0.1, "-00:06");
+        checkFormatOffset(-12, "+00:00");
+        checkFormatOffset(12, "+00:00");
+        checkFormatOffset(-11.5, "-11:30");
+        checkFormatOffset(11.5, "+11:30");
+        checkFormatOffset(11.9, "+11:54");
+        checkFormatOffset(11.1, "+11:06");
+        checkFormatOffset(-11.9, "-11:54");
+        checkFormatOffset(-11.1, "-11:06");
+    }
+    
+    // testbody precedes
+    ////////////////////////////////////////////////////
+    
+    /**
      * Set the tests in the suite for this test class.
      *
      * @return the Suite.
@@ -130,7 +460,10 @@ public class TestDateUtil extends TestCa
      */
     public static void main( String[] args )
     {
-        String[] arg = {TestDateUtil.class.getName() };
+        String[] arg = 
+        {
+            TestDateUtil.class.getName() 
+        };
         junit.textui.TestRunner.main( arg );
     }
 }

Modified: pdfbox/branches/1.8/preflight/src/main/java/org/apache/pdfbox/preflight/process/FileSpecificationValidationProcess.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/1.8/preflight/src/main/java/org/apache/pdfbox/preflight/process/FileSpecificationValidationProcess.java?rev=1542748&r1=1542747&r2=1542748&view=diff
==============================================================================
--- pdfbox/branches/1.8/preflight/src/main/java/org/apache/pdfbox/preflight/process/FileSpecificationValidationProcess.java (original)
+++ pdfbox/branches/1.8/preflight/src/main/java/org/apache/pdfbox/preflight/process/FileSpecificationValidationProcess.java Sun Nov 17 15:27:33 2013
@@ -58,7 +58,7 @@ public class FileSpecificationValidation
             {
                 COSDictionary dic = (COSDictionary) cBase;
                 String type = dic.getNameAsString(COSName.TYPE);
-                if (FILE_SPECIFICATION_VALUE_TYPE.equals(type))
+                if (FILE_SPECIFICATION_VALUE_TYPE.equals(type) || COSName.F.getName().equals(type))
                 {
                     // ---- It is a file specification
                     validateFileSpecification(ctx, dic);