diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Slide.java b/src/scratchpad/src/org/apache/poi/hslf/model/Slide.java index 96ca4a42e..25521fbde 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/Slide.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Slide.java @@ -119,6 +119,14 @@ public class Slide extends Sheet } } + /** + * Changes the Slide's (external facing) page number. + * @see SlideShow.reorderSlide() + */ + public void setSlideNumber(int newSlideNumber) { + _slideNo = newSlideNumber; + } + /** * Create a TextBox object that represents the slide's title. * diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/RecordContainer.java b/src/scratchpad/src/org/apache/poi/hslf/record/RecordContainer.java index 996f92909..293cd2b4d 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/RecordContainer.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/RecordContainer.java @@ -18,6 +18,7 @@ package org.apache.poi.hslf.record; +import org.apache.poi.util.ArrayUtil; import org.apache.poi.util.LittleEndian; import org.apache.poi.hslf.util.MutableByteArrayOutputStream; @@ -35,7 +36,7 @@ import java.io.ByteArrayOutputStream; public abstract class RecordContainer extends Record { protected Record[] _children; - private Boolean addingChildRecordLock = new Boolean(true); + private Boolean changingChildRecordsLock = new Boolean(true); /** * Return any children @@ -47,22 +48,19 @@ public abstract class RecordContainer extends Record */ public boolean isAnAtom() { return false; } - /** - * Add a new child record onto a record's list of children. - */ - public void appendChildRecord(Record newChild) { - synchronized(addingChildRecordLock) { - addChildAt(newChild, _children.length); - } - } + /* =============================================================== + * Internal Move Helpers + * =============================================================== + */ + /** * Finds the location of the given child record */ private int findChildLocation(Record child) { // Synchronized as we don't want things changing // as we're doing our search - synchronized(addingChildRecordLock) { + synchronized(changingChildRecordsLock) { for(int i=0; i<_children.length; i++) { if(_children[i].equals(child)) { return i; @@ -72,6 +70,21 @@ public abstract class RecordContainer extends Record return -1; } + /** + * Adds a child record, at the very end. + * @param newChild The child record to add + */ + private void appendChild(Record newChild) { + synchronized(changingChildRecordsLock) { + // Copy over, and pop the child in at the end + Record[] nc = new Record[(_children.length + 1)]; + System.arraycopy(_children, 0, nc, 0, _children.length); + // Switch the arrays + nc[_children.length] = newChild; + _children = nc; + } + } + /** * Adds the given new Child Record at the given location, * shuffling everything from there on down by one @@ -79,30 +92,47 @@ public abstract class RecordContainer extends Record * @param position */ private void addChildAt(Record newChild, int position) { - synchronized(addingChildRecordLock) { - Record[] newChildren = new Record[_children.length+1]; - // Move over to the new array, shuffling on by one after - // the addition point - for(int i=0; i<_children.length; i++) { - if(i == position) { - newChildren[i] = newChild; - } + synchronized(changingChildRecordsLock) { + // Firstly, have the child added in at the end + appendChild(newChild); + + // Now, have them moved to the right place + moveChildRecords( (_children.length-1), position, 1 ); + } + } + + /** + * Moves number child records from oldLoc + * to newLoc. Caller must have the changingChildRecordsLock + * @param oldLoc the current location of the records to move + * @param newLoc the new location for the records + * @param number the number of records to move + */ + private void moveChildRecords(int oldLoc, int newLoc, int number) { + if(oldLoc == newLoc) { return; } + if(number == 0) { return; } + + // Check that we're not asked to move too many + if(oldLoc+number > _children.length) { + throw new IllegalArgumentException("Asked to move more records than there are!"); + } + + // Do the move + ArrayUtil.arrayMoveWithin(_children, oldLoc, newLoc, number); + } + + + /* =============================================================== + * External Move Methods + * =============================================================== + */ - if(i >= position) { - newChildren[i+1] = _children[i]; - } - if(i < position) { - newChildren[i] = _children[i]; - } - } - - // Special case - new record goes at the end - if(position == _children.length) { - newChildren[position] = newChild; - } - - // All done, replace our child list - _children = newChildren; + /** + * Add a new child record onto a record's list of children. + */ + public void appendChildRecord(Record newChild) { + synchronized(changingChildRecordsLock) { + appendChild(newChild); } } @@ -112,7 +142,7 @@ public abstract class RecordContainer extends Record * @param after */ public void addChildAfter(Record newChild, Record after) { - synchronized(addingChildRecordLock) { + synchronized(changingChildRecordsLock) { // Decide where we're going to put it int loc = findChildLocation(after); if(loc == -1) { @@ -130,7 +160,7 @@ public abstract class RecordContainer extends Record * @param after */ public void addChildBefore(Record newChild, Record before) { - synchronized(addingChildRecordLock) { + synchronized(changingChildRecordsLock) { // Decide where we're going to put it int loc = findChildLocation(before); if(loc == -1) { @@ -142,6 +172,69 @@ public abstract class RecordContainer extends Record } } + /** + * Moves the given Child Record to before the supplied record + */ + public void moveChildBefore(Record child, Record before) { + moveChildrenBefore(child, 1, before); + } + + /** + * Moves the given Child Records to before the supplied record + */ + public void moveChildrenBefore(Record firstChild, int number, Record before) { + if(number < 1) { return; } + + synchronized(changingChildRecordsLock) { + // Decide where we're going to put them + int newLoc = findChildLocation(before); + if(newLoc == -1) { + throw new IllegalArgumentException("Asked to move children before another record, but that record wasn't one of our children!"); + } + + // Figure out where they are now + int oldLoc = findChildLocation(firstChild); + if(oldLoc == -1) { + throw new IllegalArgumentException("Asked to move a record that wasn't a child!"); + } + + // Actually move + moveChildRecords(oldLoc, newLoc, number); + } + } + + /** + * Moves the given Child Records to after the supplied record + */ + public void moveChildrenAfter(Record firstChild, int number, Record after) { + if(number < 1) { return; } + + synchronized(changingChildRecordsLock) { + // Decide where we're going to put them + int newLoc = findChildLocation(after); + if(newLoc == -1) { + throw new IllegalArgumentException("Asked to move children before another record, but that record wasn't one of our children!"); + } + // We actually want after this though + newLoc++; + + // Figure out where they are now + int oldLoc = findChildLocation(firstChild); + if(oldLoc == -1) { + throw new IllegalArgumentException("Asked to move a record that wasn't a child!"); + } + + // Actually move + moveChildRecords(oldLoc, newLoc, number); + } + } + + + /* =============================================================== + * External Serialisation Methods + * =============================================================== + */ + /** * Write out our header, and our children. * @param headerA the first byte of the header diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/SlideListWithText.java b/src/scratchpad/src/org/apache/poi/hslf/record/SlideListWithText.java index df3e607b9..1c33857c6 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/SlideListWithText.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/SlideListWithText.java @@ -18,6 +18,7 @@ package org.apache.poi.hslf.record; +import org.apache.poi.util.ArrayUtil; import org.apache.poi.util.LittleEndian; import java.io.IOException; @@ -149,6 +150,34 @@ public class SlideListWithText extends RecordContainer writeOut(_header[0],_header[1],_type,_children,out); } + /** + * Shifts a SlideAtomsSet to a new position. + * Works by shifting the child records about, then updating + * the SlideAtomSets array + * @param toMove The SlideAtomsSet to move + * @param newPosition The new (0 based) position for the SlideAtomsSet + */ + public void repositionSlideAtomsSet(SlideAtomsSet toMove, int newPosition) { + // Ensure it's one of ours + int curPos = -1; + for(int i=0; i= slideAtomsSets.length) { + throw new IllegalArgumentException("The new position must be between 0, and the number of SlideAtomsSets"); + } + + // Build the new records list + moveChildrenBefore(toMove.getSlidePersistAtom(), toMove.slideRecords.length, slideAtomsSets[newPosition].getSlidePersistAtom()); + + // Build the new SlideAtomsSets list + ArrayUtil.arrayMoveWithin(slideAtomsSets, curPos, newPosition, 1); + } /** * Inner class to wrap up a matching set of records that hold the diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java index 13b5aa0a3..178c63095 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java @@ -45,6 +45,7 @@ import org.apache.poi.hslf.record.SlideListWithText.*; import org.apache.poi.hslf.record.PersistPtrHolder; import org.apache.poi.hslf.record.PositionDependentRecord; import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException; +import org.apache.poi.util.ArrayUtil; /** * This class is a friendly wrapper on top of the more scary HSLFSlideShow. @@ -243,7 +244,8 @@ public class SlideShow _fonts = _documentRecord.getEnvironment().getFontCollection(); } } else { - System.err.println("No core record found with ID " + (i+1) + " based on PersistPtr lookup"); + // No record at this number + // Odd, but not normally a problem } } } @@ -461,6 +463,42 @@ public class SlideShow public Document getDocumentRecord() { return _documentRecord; } + /* =============================================================== + * Re-ordering Code + * =============================================================== + */ + + + /** + * Re-orders a slide, to a new position. + * @param oldSlideNumer The old slide number (1 based) + * @param newSlideNumber The new slide number (1 based) + */ + public void reorderSlide(int oldSlideNumer, int newSlideNumber) { + // Ensure these numbers are valid + if(oldSlideNumer < 1 || newSlideNumber < 1) { + throw new IllegalArgumentException("Old and new slide numbers must be greater than 0"); + } + if(oldSlideNumer > _slides.length || newSlideNumber > _slides.length) { + throw new IllegalArgumentException("Old and new slide numbers must not exceed the number of slides (" + _slides.length + ")"); + } + + // Shift the SlideAtomsSet + SlideListWithText slwt = _documentRecord.getSlideSlideListWithText(); + slwt.repositionSlideAtomsSet( + slwt.getSlideAtomsSets()[(oldSlideNumer-1)], + (newSlideNumber-1) + ); + + // Re-order the slides + ArrayUtil.arrayMoveWithin(_slides, (oldSlideNumer-1), (newSlideNumber-1), 1); + + // Tell the appropriate slides their new numbers + for(int i=0; i<_slides.length; i++) { + _slides[i].setSlideNumber( (i+1) ); + } + } + /* =============================================================== * Addition Code * =============================================================== diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestReOrderingSlides.java b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestReOrderingSlides.java new file mode 100644 index 000000000..a1ebd7cab --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestReOrderingSlides.java @@ -0,0 +1,295 @@ + +/* ==================================================================== + Copyright 2002-2004 Apache Software Foundation + + Licensed 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.hslf.usermodel; + + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; + +import junit.framework.TestCase; +import org.apache.poi.hslf.*; +import org.apache.poi.hslf.model.*; + +/** + * Tests that SlideShow can re-order slides properly + * + * @author Nick Burch (nick at torchbox dot com) + */ +public class TestReOrderingSlides extends TestCase { + // A SlideShow with one slide + private HSLFSlideShow hss_one; + private SlideShow ss_one; + + // A SlideShow with two slides + private HSLFSlideShow hss_two; + private SlideShow ss_two; + + // A SlideShow with three slides + private HSLFSlideShow hss_three; + private SlideShow ss_three; + + /** + * Create/open the slideshows + */ + public void setUp() throws Exception { + String dirname = System.getProperty("HSLF.testdata.path"); + + String filename = dirname + "/Single_Coloured_Page.ppt"; + hss_one = new HSLFSlideShow(filename); + ss_one = new SlideShow(hss_one); + + filename = dirname + "/basic_test_ppt_file.ppt"; + hss_two = new HSLFSlideShow(filename); + ss_two = new SlideShow(hss_two); + + filename = dirname + "/incorrect_slide_order.ppt"; + hss_three = new HSLFSlideShow(filename); + ss_three = new SlideShow(hss_three); + } + + /** + * Test that we can "re-order" a slideshow with only 1 slide on it + */ + public void testReOrder1() throws Exception { + // Has one slide + assertEquals(1, ss_one.getSlides().length); + Slide s1 = ss_one.getSlides()[0]; + + // Check slide 1 is as expected + assertEquals(256, s1._getSheetNumber()); + assertEquals(3, s1._getSheetRefId()); + assertEquals(1, s1.getSlideNumber()); + + // Now move it to one + ss_one.reorderSlide(1, 1); + + // Write out, and read back in + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + hss_one.write(baos); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + + HSLFSlideShow hss_read = new HSLFSlideShow(bais); + SlideShow ss_read = new SlideShow(hss_read); + + // Check it still has 1 slide + assertEquals(1, ss_read.getSlides().length); + + // And check it's as expected + s1 = ss_read.getSlides()[0]; + assertEquals(256, s1._getSheetNumber()); + assertEquals(3, s1._getSheetRefId()); + assertEquals(1, s1.getSlideNumber()); + } + + /** + * Test doing a dummy re-order on a slideshow with + * two slides in it + */ + public void testReOrder2() throws Exception { + // Has two slides + assertEquals(2, ss_two.getSlides().length); + Slide s1 = ss_two.getSlides()[0]; + Slide s2 = ss_two.getSlides()[1]; + + // Check slide 1 is as expected + assertEquals(256, s1._getSheetNumber()); + assertEquals(4, s1._getSheetRefId()); // master has notes + assertEquals(1, s1.getSlideNumber()); + // Check slide 2 is as expected + assertEquals(257, s2._getSheetNumber()); + assertEquals(6, s2._getSheetRefId()); // master and 1 have notes + assertEquals(2, s2.getSlideNumber()); + + // Don't swap them around + ss_two.reorderSlide(2, 2); + + // Write out, and read back in + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + hss_two.write(baos); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + + HSLFSlideShow hss_read = new HSLFSlideShow(bais); + SlideShow ss_read = new SlideShow(hss_read); + + // Check it still has 2 slides + assertEquals(2, ss_read.getSlides().length); + + // And check it's as expected + s1 = ss_read.getSlides()[0]; + s2 = ss_read.getSlides()[1]; + assertEquals(256, s1._getSheetNumber()); + assertEquals(4, s1._getSheetRefId()); + assertEquals(1, s1.getSlideNumber()); + assertEquals(257, s2._getSheetNumber()); + assertEquals(6, s2._getSheetRefId()); + assertEquals(2, s2.getSlideNumber()); + } + + /** + * Test re-ordering slides in a slideshow with 2 slides on it + */ + public void testReOrder2swap() throws Exception { + // Has two slides + assertEquals(2, ss_two.getSlides().length); + Slide s1 = ss_two.getSlides()[0]; + Slide s2 = ss_two.getSlides()[1]; + + // Check slide 1 is as expected + assertEquals(256, s1._getSheetNumber()); + assertEquals(4, s1._getSheetRefId()); // master has notes + assertEquals(1, s1.getSlideNumber()); + // Check slide 2 is as expected + assertEquals(257, s2._getSheetNumber()); + assertEquals(6, s2._getSheetRefId()); // master and 1 have notes + assertEquals(2, s2.getSlideNumber()); + + // Swap them around + ss_two.reorderSlide(2, 1); + + // Write out, and read back in + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + hss_two.write(baos); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + + HSLFSlideShow hss_read = new HSLFSlideShow(bais); + SlideShow ss_read = new SlideShow(hss_read); + + // Check it still has 2 slides + assertEquals(2, ss_read.getSlides().length); + + // And check it's as expected + s1 = ss_read.getSlides()[0]; + s2 = ss_read.getSlides()[1]; + assertEquals(257, s1._getSheetNumber()); + assertEquals(6, s1._getSheetRefId()); + assertEquals(1, s1.getSlideNumber()); + assertEquals(256, s2._getSheetNumber()); + assertEquals(4, s2._getSheetRefId()); + assertEquals(2, s2.getSlideNumber()); + } + + /** + * Test doing a dummy re-order on a slideshow with + * three slides in it + */ + public void testReOrder3() throws Exception { + // Has three slides + assertEquals(3, ss_three.getSlides().length); + Slide s1 = ss_three.getSlides()[0]; + Slide s2 = ss_three.getSlides()[1]; + Slide s3 = ss_three.getSlides()[2]; + + // Check slide 1 is as expected + assertEquals(256, s1._getSheetNumber()); + assertEquals(3, s1._getSheetRefId()); // no notes on master + assertEquals(1, s1.getSlideNumber()); + // Check slide 2 is as expected (was re-ordered from 3) + assertEquals(258, s2._getSheetNumber()); + assertEquals(5, s2._getSheetRefId()); // no notes on slide + assertEquals(2, s2.getSlideNumber()); + // Check slide 3 is as expected (was re-ordered from 2) + assertEquals(257, s3._getSheetNumber()); + assertEquals(4, s3._getSheetRefId()); // no notes on slide + assertEquals(3, s3.getSlideNumber()); + + // Don't swap them around + ss_three.reorderSlide(2, 2); + + // Write out, and read back in + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + hss_three.write(baos); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + + HSLFSlideShow hss_read = new HSLFSlideShow(bais); + SlideShow ss_read = new SlideShow(hss_read); + + // Check it still has 3 slides + assertEquals(3, ss_read.getSlides().length); + + // And check it's as expected + s1 = ss_read.getSlides()[0]; + s2 = ss_read.getSlides()[1]; + s3 = ss_read.getSlides()[2]; + + assertEquals(256, s1._getSheetNumber()); + assertEquals(3, s1._getSheetRefId()); + assertEquals(1, s1.getSlideNumber()); + assertEquals(258, s2._getSheetNumber()); + assertEquals(5, s2._getSheetRefId()); + assertEquals(2, s2.getSlideNumber()); + assertEquals(257, s3._getSheetNumber()); + assertEquals(4, s3._getSheetRefId()); + assertEquals(3, s3.getSlideNumber()); + } + + /** + * Test re-ordering slides in a slideshow with 3 slides on it + */ + public void testReOrder3swap() throws Exception { + // Has three slides + assertEquals(3, ss_three.getSlides().length); + Slide s1 = ss_three.getSlides()[0]; + Slide s2 = ss_three.getSlides()[1]; + Slide s3 = ss_three.getSlides()[2]; + + // Check slide 1 is as expected + assertEquals(256, s1._getSheetNumber()); + assertEquals(3, s1._getSheetRefId()); // no notes on master + assertEquals(1, s1.getSlideNumber()); + // Check slide 2 is as expected (was re-ordered from 3) + assertEquals(258, s2._getSheetNumber()); + assertEquals(5, s2._getSheetRefId()); // no notes on slide + assertEquals(2, s2.getSlideNumber()); + // Check slide 3 is as expected (was re-ordered from 2) + assertEquals(257, s3._getSheetNumber()); + assertEquals(4, s3._getSheetRefId()); // no notes on slide + assertEquals(3, s3.getSlideNumber()); + + // Put 3 in place of 1 + // (1 -> 2, 2 -> 3) + ss_three.reorderSlide(3, 1); + + // Write out, and read back in + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + hss_three.write(baos); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + + HSLFSlideShow hss_read = new HSLFSlideShow(bais); + SlideShow ss_read = new SlideShow(hss_read); + + // Check it still has 3 slides + assertEquals(3, ss_read.getSlides().length); + + // And check it's as expected + s1 = ss_read.getSlides()[0]; + s2 = ss_read.getSlides()[1]; + s3 = ss_read.getSlides()[2]; + + assertEquals(257, s1._getSheetNumber()); + assertEquals(4, s1._getSheetRefId()); + assertEquals(1, s1.getSlideNumber()); + assertEquals(256, s2._getSheetNumber()); + assertEquals(3, s2._getSheetRefId()); + assertEquals(2, s2.getSlideNumber()); + assertEquals(258, s3._getSheetNumber()); + assertEquals(5, s3._getSheetRefId()); + assertEquals(3, s3.getSlideNumber()); + } +}