This is an automated email from the ASF dual-hosted git repository. tallison pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/tika.git
commit 3239f24863d5980395a6951e02e42fc9defcb37e Author: TALLISON <talli...@apache.org> AuthorDate: Mon Dec 3 14:43:24 2018 -0500 TIKA-2792 -- upgrade dependency coordinates for MP4Parser --- CHANGES.txt | 3 + tika-parsers/pom.xml | 8 +- .../tika/parser/mp4/DirectFileReadDataSource.java | 128 -------- .../java/org/apache/tika/parser/mp4/MP4Parser.java | 361 ++++++++++----------- 4 files changed, 189 insertions(+), 311 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index e33f0f5..1b1958b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -7,6 +7,9 @@ Release 2.0.0 - ??? Release 1.20 - ??? + * Upgrade MP4Parser to newer dependency coordinates: + org.mp4parser:isoparser (TIKA-2792). + * Upgrade to POI 4.0.1 (TIKA-2751). * Integrate/parameterize new angles handling in diff --git a/tika-parsers/pom.xml b/tika-parsers/pom.xml index b6bed3d..dace1b9 100644 --- a/tika-parsers/pom.xml +++ b/tika-parsers/pom.xml @@ -287,10 +287,14 @@ <version>7.0</version> </dependency> <dependency> - <groupId>com.googlecode.mp4parser</groupId> + <!-- 1.9.37 was built with jdk9 and + causes: Method rewind()Ljava/nio/ByteBuffer; + does not exist in class java.nio.ByteBuffer --> + <groupId>org.mp4parser</groupId> <artifactId>isoparser</artifactId> - <version>1.1.22</version> + <version>1.9.36</version> </dependency> + <dependency> <groupId>com.drewnoakes</groupId> <artifactId>metadata-extractor</artifactId> diff --git a/tika-parsers/src/main/java/org/apache/tika/parser/mp4/DirectFileReadDataSource.java b/tika-parsers/src/main/java/org/apache/tika/parser/mp4/DirectFileReadDataSource.java deleted file mode 100644 index cde2548..0000000 --- a/tika-parsers/src/main/java/org/apache/tika/parser/mp4/DirectFileReadDataSource.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.tika.parser.mp4; - -import static com.googlecode.mp4parser.util.CastUtils.l2i; - -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.math.BigInteger; -import java.nio.ByteBuffer; -import java.nio.channels.WritableByteChannel; - -import com.googlecode.mp4parser.DataSource; - -/** - * A {@link DataSource} implementation that relies on direct reads from a {@link RandomAccessFile}. - * It should be slower than {@link com.googlecode.mp4parser.FileDataSourceImpl} but does not incur the implicit file locks of - * memory mapped I/O on some JVMs. This implementation allows for a more controlled deletion of files - * and might be preferred when working with temporary files. - * @see <a href="http://bugs.java.com/view_bug.do?bug_id=4724038">JDK-4724038 : (fs) Add unmap method to MappedByteBuffer</a> - * @see <a href="http://bugs.java.com/view_bug.do?bug_id=6359560">JDK-6359560 : (fs) File.deleteOnExit() doesn't work when MappedByteBuffer exists (win)</a> - */ -public class DirectFileReadDataSource implements DataSource { - - private static final int TRANSFER_SIZE = 8192; - - private RandomAccessFile raf; - - public DirectFileReadDataSource(File f) throws IOException { - this.raf = new RandomAccessFile(f, "r"); - } - - public int read(ByteBuffer byteBuffer) throws IOException { - int len = byteBuffer.remaining(); - int totalRead = 0; - int bytesRead = 0; - byte[] buf = new byte[TRANSFER_SIZE]; - while (totalRead < len) { - int bytesToRead = Math.min((len - totalRead), TRANSFER_SIZE); - bytesRead = raf.read(buf, 0, bytesToRead); - if (bytesRead < 0) { - break; - } else { - totalRead += bytesRead; - } - byteBuffer.put(buf, 0, bytesRead); - } - if (bytesRead < 0 && position() == size() && byteBuffer.hasRemaining()) { - throw new IOException("End of stream reached earlier than expected"); - } - return ((bytesRead < 0) && (totalRead == 0)) ? -1 : totalRead; - } - - public int readAllInOnce(ByteBuffer byteBuffer) throws IOException { - if (byteBuffer.remaining() > raf.length()) { - throw new IOException("trying to readAllInOnce past end of stream"); - } - byte[] buf = new byte[byteBuffer.remaining()]; - int read = raf.read(buf); - byteBuffer.put(buf, 0, read); - return read; - } - - public long size() throws IOException { - return raf.length(); - } - - public long position() throws IOException { - return raf.getFilePointer(); - } - - public void position(long nuPos) throws IOException { - if (nuPos > raf.length()) { - throw new IOException("requesting seek past end of stream"); - } - raf.seek(nuPos); - } - - public long transferTo(long position, long count, WritableByteChannel target) throws IOException { - return target.write(map(position, count)); - } - - public ByteBuffer map(long startPosition, long size) throws IOException { - if (startPosition < 0 || size < 0) { - throw new IOException("startPosition and size must both be >= 0"); - } - //make sure that start+size aren't greater than avail size - //in raf. - BigInteger end = BigInteger.valueOf(startPosition); - end = end.add(BigInteger.valueOf(size)); - if (end.compareTo(BigInteger.valueOf(raf.length())) > 0) { - throw new IOException("requesting read past end of stream"); - } - - raf.seek(startPosition); - int payLoadSize = l2i(size); - //hack to check for potential overflow - if (Long.MAX_VALUE-payLoadSize < startPosition || - Long.MAX_VALUE-payLoadSize > raf.length()) { - throw new IOException("requesting read past end of stream"); - } - byte[] payload = new byte[payLoadSize]; - raf.readFully(payload); - return ByteBuffer.wrap(payload); - } - - @Override - public void close() throws IOException { - raf.close(); - } - - -} diff --git a/tika-parsers/src/main/java/org/apache/tika/parser/mp4/MP4Parser.java b/tika-parsers/src/main/java/org/apache/tika/parser/mp4/MP4Parser.java index 7baae65..6fa3203 100644 --- a/tika-parsers/src/main/java/org/apache/tika/parser/mp4/MP4Parser.java +++ b/tika-parsers/src/main/java/org/apache/tika/parser/mp4/MP4Parser.java @@ -16,46 +16,7 @@ */ package org.apache.tika.parser.mp4; -import java.io.IOException; -import java.io.InputStream; -import java.text.DecimalFormat; -import java.text.NumberFormat; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import com.coremedia.iso.IsoFile; -import com.coremedia.iso.boxes.Box; -import com.coremedia.iso.boxes.Container; -import com.coremedia.iso.boxes.FileTypeBox; -import com.coremedia.iso.boxes.MetaBox; -import com.coremedia.iso.boxes.MovieBox; -import com.coremedia.iso.boxes.MovieHeaderBox; -import com.coremedia.iso.boxes.SampleDescriptionBox; -import com.coremedia.iso.boxes.SampleTableBox; -import com.coremedia.iso.boxes.TrackBox; -import com.coremedia.iso.boxes.TrackHeaderBox; -import com.coremedia.iso.boxes.UserDataBox; -import com.coremedia.iso.boxes.apple.AppleItemListBox; -import com.coremedia.iso.boxes.sampleentry.AudioSampleEntry; -import com.googlecode.mp4parser.DataSource; -import com.googlecode.mp4parser.boxes.apple.AppleAlbumBox; -import com.googlecode.mp4parser.boxes.apple.AppleArtist2Box; -import com.googlecode.mp4parser.boxes.apple.AppleArtistBox; -import com.googlecode.mp4parser.boxes.apple.AppleCommentBox; -import com.googlecode.mp4parser.boxes.apple.AppleCompilationBox; -import com.googlecode.mp4parser.boxes.apple.AppleDiskNumberBox; -import com.googlecode.mp4parser.boxes.apple.AppleEncoderBox; -import com.googlecode.mp4parser.boxes.apple.AppleGenreBox; -import com.googlecode.mp4parser.boxes.apple.AppleNameBox; -import com.googlecode.mp4parser.boxes.apple.AppleRecordingYear2Box; -import com.googlecode.mp4parser.boxes.apple.AppleTrackAuthorBox; -import com.googlecode.mp4parser.boxes.apple.AppleTrackNumberBox; -import com.googlecode.mp4parser.boxes.apple.Utf8AppleDataBox; import org.apache.tika.exception.TikaException; import org.apache.tika.io.TemporaryResources; import org.apache.tika.io.TikaInputStream; @@ -68,9 +29,48 @@ import org.apache.tika.mime.MediaType; import org.apache.tika.parser.AbstractParser; import org.apache.tika.parser.ParseContext; import org.apache.tika.sax.XHTMLContentHandler; +import org.mp4parser.Box; +import org.mp4parser.Container; +import org.mp4parser.IsoFile; +import org.mp4parser.boxes.apple.AppleAlbumBox; +import org.mp4parser.boxes.apple.AppleArtist2Box; +import org.mp4parser.boxes.apple.AppleArtistBox; +import org.mp4parser.boxes.apple.AppleCommentBox; +import org.mp4parser.boxes.apple.AppleCompilationBox; +import org.mp4parser.boxes.apple.AppleDiskNumberBox; +import org.mp4parser.boxes.apple.AppleEncoderBox; +import org.mp4parser.boxes.apple.AppleGenreBox; +import org.mp4parser.boxes.apple.AppleItemListBox; +import org.mp4parser.boxes.apple.AppleNameBox; +import org.mp4parser.boxes.apple.AppleRecordingYear2Box; +import org.mp4parser.boxes.apple.AppleTrackAuthorBox; +import org.mp4parser.boxes.apple.AppleTrackNumberBox; +import org.mp4parser.boxes.apple.Utf8AppleDataBox; +import org.mp4parser.boxes.iso14496.part12.FileTypeBox; +import org.mp4parser.boxes.iso14496.part12.MetaBox; +import org.mp4parser.boxes.iso14496.part12.MovieBox; +import org.mp4parser.boxes.iso14496.part12.MovieHeaderBox; +import org.mp4parser.boxes.iso14496.part12.SampleDescriptionBox; +import org.mp4parser.boxes.iso14496.part12.SampleTableBox; +import org.mp4parser.boxes.iso14496.part12.TrackBox; +import org.mp4parser.boxes.iso14496.part12.TrackHeaderBox; +import org.mp4parser.boxes.iso14496.part12.UserDataBox; +import org.mp4parser.boxes.sampleentry.AudioSampleEntry; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; +import java.io.IOException; +import java.io.InputStream; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + /** * Parser for the MP4 media container format, as well as the older * QuickTime format that MP4 is based on. @@ -126,176 +126,175 @@ public class MP4Parser extends AbstractParser { TemporaryResources tmp = new TemporaryResources(); TikaInputStream tstream = TikaInputStream.get(stream, tmp); - try (DataSource dataSource = new DirectFileReadDataSource(tstream.getFile())) { - try (IsoFile isoFile = new IsoFile(dataSource)) { - tmp.addResource(isoFile); - - // Grab the file type box - FileTypeBox fileType = getOrNull(isoFile, FileTypeBox.class); - if (fileType != null) { - // Identify the type - MediaType type = MediaType.application("mp4"); - for (Map.Entry<MediaType, List<String>> e : typesMap.entrySet()) { - if (e.getValue().contains(fileType.getMajorBrand())) { - type = e.getKey(); - break; - } + try (IsoFile isoFile = new IsoFile(tstream.getFile())) { + tmp.addResource(isoFile); + + // Grab the file type box + FileTypeBox fileType = getOrNull(isoFile, FileTypeBox.class); + if (fileType != null) { + // Identify the type + MediaType type = MediaType.application("mp4"); + for (Map.Entry<MediaType, List<String>> e : typesMap.entrySet()) { + if (e.getValue().contains(fileType.getMajorBrand())) { + type = e.getKey(); + break; } - metadata.set(Metadata.CONTENT_TYPE, type.toString()); + } + metadata.set(Metadata.CONTENT_TYPE, type.toString()); - if (type.getType().equals("audio")) { - metadata.set(XMPDM.AUDIO_COMPRESSOR, fileType.getMajorBrand().trim()); - } - } else { - // Some older QuickTime files lack the FileType - metadata.set(Metadata.CONTENT_TYPE, "video/quicktime"); + if (type.getType().equals("audio")) { + metadata.set(XMPDM.AUDIO_COMPRESSOR, fileType.getMajorBrand().trim()); } + } else { + // Some older QuickTime files lack the FileType + metadata.set(Metadata.CONTENT_TYPE, "video/quicktime"); + } - // Get the main MOOV box - MovieBox moov = getOrNull(isoFile, MovieBox.class); - if (moov == null) { - // Bail out - return; - } + // Get the main MOOV box + MovieBox moov = getOrNull(isoFile, MovieBox.class); + if (moov == null) { + // Bail out + return; + } - XHTMLContentHandler xhtml = new XHTMLContentHandler(handler, metadata); - xhtml.startDocument(); + XHTMLContentHandler xhtml = new XHTMLContentHandler(handler, metadata); + xhtml.startDocument(); - // Pull out some information from the header box - MovieHeaderBox mHeader = getOrNull(moov, MovieHeaderBox.class); - if (mHeader != null) { - // Get the creation and modification dates - metadata.set(TikaCoreProperties.CREATED, mHeader.getCreationTime()); - metadata.set(TikaCoreProperties.MODIFIED, mHeader.getModificationTime()); + // Pull out some information from the header box + MovieHeaderBox mHeader = getOrNull(moov, MovieHeaderBox.class); + if (mHeader != null) { + // Get the creation and modification dates + metadata.set(Metadata.CREATION_DATE, mHeader.getCreationTime()); + metadata.set(TikaCoreProperties.MODIFIED, mHeader.getModificationTime()); - // Get the duration - double durationSeconds = ((double) mHeader.getDuration()) / mHeader.getTimescale(); - metadata.set(XMPDM.DURATION, DURATION_FORMAT.format(durationSeconds)); + // Get the duration + double durationSeconds = ((double) mHeader.getDuration()) / mHeader.getTimescale(); + metadata.set(XMPDM.DURATION, DURATION_FORMAT.format(durationSeconds)); - // The timescale is normally the sampling rate - metadata.set(XMPDM.AUDIO_SAMPLE_RATE, (int) mHeader.getTimescale()); - } + // The timescale is normally the sampling rate + metadata.set(XMPDM.AUDIO_SAMPLE_RATE, (int) mHeader.getTimescale()); + } - // Get some more information from the track header - // TODO Decide how to handle multiple tracks - List<TrackBox> tb = moov.getBoxes(TrackBox.class); - if (tb.size() > 0) { - TrackBox track = tb.get(0); - - TrackHeaderBox header = track.getTrackHeaderBox(); - // Get the creation and modification dates - metadata.set(TikaCoreProperties.CREATED, header.getCreationTime()); - metadata.set(TikaCoreProperties.MODIFIED, header.getModificationTime()); - - // Get the video with and height - metadata.set(Metadata.IMAGE_WIDTH, (int) header.getWidth()); - metadata.set(Metadata.IMAGE_LENGTH, (int) header.getHeight()); - - // Get the sample information - SampleTableBox samples = track.getSampleTableBox(); - SampleDescriptionBox sampleDesc = samples.getSampleDescriptionBox(); - if (sampleDesc != null) { - // Look for the first Audio Sample, if present - AudioSampleEntry sample = getOrNull(sampleDesc, AudioSampleEntry.class); - if (sample != null) { - XMPDM.ChannelTypePropertyConverter.convertAndSet(metadata, sample.getChannelCount()); - //metadata.set(XMPDM.AUDIO_SAMPLE_TYPE, sample.getSampleSize()); // TODO Num -> Type mapping - metadata.set(XMPDM.AUDIO_SAMPLE_RATE, (int) sample.getSampleRate()); - //metadata.set(XMPDM.AUDIO_, sample.getSamplesPerPacket()); - //metadata.set(XMPDM.AUDIO_, sample.getBytesPerSample()); - } + // Get some more information from the track header + // TODO Decide how to handle multiple tracks + List<TrackBox> tb = moov.getBoxes(TrackBox.class); + if (tb.size() > 0) { + TrackBox track = tb.get(0); + + TrackHeaderBox header = track.getTrackHeaderBox(); + // Get the creation and modification dates + metadata.set(TikaCoreProperties.CREATED, header.getCreationTime()); + metadata.set(TikaCoreProperties.MODIFIED, header.getModificationTime()); + + // Get the video with and height + metadata.set(Metadata.IMAGE_WIDTH, (int) header.getWidth()); + metadata.set(Metadata.IMAGE_LENGTH, (int) header.getHeight()); + + // Get the sample information + SampleTableBox samples = track.getSampleTableBox(); + SampleDescriptionBox sampleDesc = samples.getSampleDescriptionBox(); + if (sampleDesc != null) { + // Look for the first Audio Sample, if present + AudioSampleEntry sample = getOrNull(sampleDesc, AudioSampleEntry.class); + if (sample != null) { + XMPDM.ChannelTypePropertyConverter.convertAndSet(metadata, sample.getChannelCount()); + //metadata.set(XMPDM.AUDIO_SAMPLE_TYPE, sample.getSampleSize()); // TODO Num -> Type mapping + metadata.set(XMPDM.AUDIO_SAMPLE_RATE, (int) sample.getSampleRate()); + //metadata.set(XMPDM.AUDIO_, sample.getSamplesPerPacket()); + //metadata.set(XMPDM.AUDIO_, sample.getBytesPerSample()); } } + } - // Get metadata from the User Data Box - UserDataBox userData = getOrNull(moov, UserDataBox.class); - if (userData != null) { - MetaBox meta = getOrNull(userData, MetaBox.class); - - // Check for iTunes Metadata - // See http://atomicparsley.sourceforge.net/mpeg-4files.html and - // http://code.google.com/p/mp4v2/wiki/iTunesMetadata for more on these - AppleItemListBox apple = getOrNull(meta, AppleItemListBox.class); - if (apple != null) { - // Title - AppleNameBox title = getOrNull(apple, AppleNameBox.class); - addMetadata(TikaCoreProperties.TITLE, metadata, title); - - // Artist - AppleArtistBox artist = getOrNull(apple, AppleArtistBox.class); - addMetadata(TikaCoreProperties.CREATOR, metadata, artist); - addMetadata(XMPDM.ARTIST, metadata, artist); - - // Album Artist - AppleArtist2Box artist2 = getOrNull(apple, AppleArtist2Box.class); - addMetadata(XMPDM.ALBUM_ARTIST, metadata, artist2); - - // Album - AppleAlbumBox album = getOrNull(apple, AppleAlbumBox.class); - addMetadata(XMPDM.ALBUM, metadata, album); - - // Composer - AppleTrackAuthorBox composer = getOrNull(apple, AppleTrackAuthorBox.class); - addMetadata(XMPDM.COMPOSER, metadata, composer); - - // Genre - AppleGenreBox genre = getOrNull(apple, AppleGenreBox.class); - addMetadata(XMPDM.GENRE, metadata, genre); - - // Year - AppleRecordingYear2Box year = getOrNull(apple, AppleRecordingYear2Box.class); - if (year != null) { - metadata.set(XMPDM.RELEASE_DATE, year.getValue()); - } + // Get metadata from the User Data Box + UserDataBox userData = getOrNull(moov, UserDataBox.class); + if (userData != null) { + MetaBox meta = getOrNull(userData, MetaBox.class); + + // Check for iTunes Metadata + // See http://atomicparsley.sourceforge.net/mpeg-4files.html and + // http://code.google.com/p/mp4v2/wiki/iTunesMetadata for more on these + AppleItemListBox apple = getOrNull(meta, AppleItemListBox.class); + if (apple != null) { + // Title + AppleNameBox title = getOrNull(apple, AppleNameBox.class); + addMetadata(TikaCoreProperties.TITLE, metadata, title); + + // Artist + AppleArtistBox artist = getOrNull(apple, AppleArtistBox.class); + addMetadata(TikaCoreProperties.CREATOR, metadata, artist); + addMetadata(XMPDM.ARTIST, metadata, artist); + + // Album Artist + AppleArtist2Box artist2 = getOrNull(apple, AppleArtist2Box.class); + addMetadata(XMPDM.ALBUM_ARTIST, metadata, artist2); + + // Album + AppleAlbumBox album = getOrNull(apple, AppleAlbumBox.class); + addMetadata(XMPDM.ALBUM, metadata, album); + + // Composer + AppleTrackAuthorBox composer = getOrNull(apple, AppleTrackAuthorBox.class); + addMetadata(XMPDM.COMPOSER, metadata, composer); + + // Genre + AppleGenreBox genre = getOrNull(apple, AppleGenreBox.class); + addMetadata(XMPDM.GENRE, metadata, genre); + + // Year + AppleRecordingYear2Box year = getOrNull(apple, AppleRecordingYear2Box.class); + if (year != null) { + metadata.set(XMPDM.RELEASE_DATE, year.getValue()); + } - // Track number - AppleTrackNumberBox trackNum = getOrNull(apple, AppleTrackNumberBox.class); - if (trackNum != null) { - metadata.set(XMPDM.TRACK_NUMBER, trackNum.getA()); - //metadata.set(XMPDM.NUMBER_OF_TRACKS, trackNum.getB()); // TODO - } + // Track number + AppleTrackNumberBox trackNum = getOrNull(apple, AppleTrackNumberBox.class); + if (trackNum != null) { + metadata.set(XMPDM.TRACK_NUMBER, trackNum.getA()); + //metadata.set(XMPDM.NUMBER_OF_TRACKS, trackNum.getB()); // TODO + } - // Disc number - AppleDiskNumberBox discNum = getOrNull(apple, AppleDiskNumberBox.class); - if (discNum != null) { - metadata.set(XMPDM.DISC_NUMBER, discNum.getA()); - } + // Disc number + AppleDiskNumberBox discNum = getOrNull(apple, AppleDiskNumberBox.class); + if (discNum != null) { + metadata.set(XMPDM.DISC_NUMBER, discNum.getA()); + } - // Compilation - AppleCompilationBox compilation = getOrNull(apple, AppleCompilationBox.class); - if (compilation != null) { - metadata.set(XMPDM.COMPILATION, (int) compilation.getValue()); - } + // Compilation + AppleCompilationBox compilation = getOrNull(apple, AppleCompilationBox.class); + if (compilation != null) { + metadata.set(XMPDM.COMPILATION, (int) compilation.getValue()); + } - // Comment - AppleCommentBox comment = getOrNull(apple, AppleCommentBox.class); - addMetadata(XMPDM.LOG_COMMENT, metadata, comment); + // Comment + AppleCommentBox comment = getOrNull(apple, AppleCommentBox.class); + addMetadata(XMPDM.LOG_COMMENT, metadata, comment); - // Encoder - AppleEncoderBox encoder = getOrNull(apple, AppleEncoderBox.class); - if (encoder != null) { - metadata.set(XMP.CREATOR_TOOL, encoder.getValue()); - } + // Encoder + AppleEncoderBox encoder = getOrNull(apple, AppleEncoderBox.class); + if (encoder != null) { + metadata.set(XMP.CREATOR_TOOL, encoder.getValue()); + } - // As text - for (Box box : apple.getBoxes()) { - if (box instanceof Utf8AppleDataBox) { - xhtml.element("p", ((Utf8AppleDataBox) box).getValue()); - } + // As text + for (Box box : apple.getBoxes()) { + if (box instanceof Utf8AppleDataBox) { + xhtml.element("p", ((Utf8AppleDataBox) box).getValue()); } } - - // TODO Check for other kinds too } - // All done - xhtml.endDocument(); + // TODO Check for other kinds too } + + // All done + xhtml.endDocument(); + } finally { tmp.dispose(); }