From d4195b4362464c7824a6649ee6220cc730b1b99e Mon Sep 17 00:00:00 2001 From: fritz-hh Date: Fri, 26 Apr 2013 14:46:47 +0200 Subject: [PATCH] jhove package added --- OCRmyPDF.sh | 3 +- jhove/COPYING | 502 + jhove/LICENSE | 17 + jhove/README | 227 + jhove/RELEASENOTES | 1676 +++ jhove/build.xml | 78 + jhove/classes/ADump.java | 235 + jhove/classes/GDump.java | 264 + jhove/classes/J2Dump.java | 216 + jhove/classes/JDump.java | 404 + jhove/classes/Jhove.java | 313 + jhove/classes/JhoveView.java | 229 + jhove/classes/PDump.java | 115 + jhove/classes/TDump.java | 361 + jhove/classes/UserHome.java | 33 + jhove/classes/WDump.java | 196 + jhove/classes/build.xml | 128 + .../hul/ois/jhove/AESAudioMetadata.java | 940 ++ .../edu/harvard/hul/ois/jhove/Agent.java | 167 + .../edu/harvard/hul/ois/jhove/AgentType.java | 60 + .../hul/ois/jhove/AnalogDigitalFlagType.java | 40 + .../edu/harvard/hul/ois/jhove/App.java | 128 + .../hul/ois/jhove/ByteArrayXMPSource.java | 66 + .../edu/harvard/hul/ois/jhove/Callback.java | 25 + .../edu/harvard/hul/ois/jhove/Checksum.java | 93 + .../hul/ois/jhove/ChecksumInputStream.java | 145 + .../harvard/hul/ois/jhove/ChecksumType.java | 37 + .../harvard/hul/ois/jhove/Checksummer.java | 193 + .../harvard/hul/ois/jhove/ConfigHandler.java | 395 + .../harvard/hul/ois/jhove/ConfigWriter.java | 212 + .../hul/ois/jhove/CountedInputStream.java | 114 + .../jhove/DefaultConfigurationBuilder.java | 128 + .../edu/harvard/hul/ois/jhove/Document.java | 194 + .../harvard/hul/ois/jhove/DocumentType.java | 61 + .../edu/harvard/hul/ois/jhove/Dump.java | 134 + .../hul/ois/jhove/EnumerationType.java | 70 + .../harvard/hul/ois/jhove/ErrorMessage.java | 58 + .../hul/ois/jhove/ExternalSignature.java | 60 + .../harvard/hul/ois/jhove/HandlerBase.java | 812 ++ .../edu/harvard/hul/ois/jhove/Identifier.java | 64 + .../harvard/hul/ois/jhove/IdentifierType.java | 115 + .../harvard/hul/ois/jhove/InfoMessage.java | 62 + .../hul/ois/jhove/InternalSignature.java | 140 + .../edu/harvard/hul/ois/jhove/JhoveBase.java | 1356 ++ .../harvard/hul/ois/jhove/JhoveException.java | 19 + .../edu/harvard/hul/ois/jhove/MacStuff.java | 96 + .../edu/harvard/hul/ois/jhove/Message.java | 131 + .../edu/harvard/hul/ois/jhove/Module.java | 264 + .../edu/harvard/hul/ois/jhove/ModuleBase.java | 1410 ++ .../edu/harvard/hul/ois/jhove/ModuleInfo.java | 17 + .../hul/ois/jhove/NisoImageMetadata.java | 2117 +++ .../hul/ois/jhove/ObjectIdentifier.java | 118 + .../harvard/hul/ois/jhove/OutputHandler.java | 196 + .../edu/harvard/hul/ois/jhove/Property.java | 214 + .../harvard/hul/ois/jhove/PropertyArity.java | 60 + .../harvard/hul/ois/jhove/PropertyPath.java | 278 + .../harvard/hul/ois/jhove/PropertyType.java | 110 + .../harvard/hul/ois/jhove/RAFInputStream.java | 261 + .../harvard/hul/ois/jhove/RFC1766Lang.java | 92 + .../edu/harvard/hul/ois/jhove/Rational.java | 100 + .../edu/harvard/hul/ois/jhove/RepInfo.java | 599 + .../edu/harvard/hul/ois/jhove/Signature.java | 157 + .../harvard/hul/ois/jhove/SignatureType.java | 54 + .../hul/ois/jhove/SignatureUseType.java | 55 + .../harvard/hul/ois/jhove/TextMDMetadata.java | 438 + .../hul/ois/jhove/Utf8BlockMarker.java | 62 + .../hul/ois/jhove/XMLWrapperStream.java | 161 + .../edu/harvard/hul/ois/jhove/XMPHandler.java | 175 + .../edu/harvard/hul/ois/jhove/XMPSource.java | 118 + .../edu/harvard/hul/ois/jhove/build.xml | 79 + .../hul/ois/jhove/handler/AuditHandler.java | 438 + .../ois/jhove/handler/META-INF/MANIFEST.MF | 12 + .../hul/ois/jhove/handler/TextHandler.java | 2035 +++ .../hul/ois/jhove/handler/XmlHandler.java | 4383 ++++++ .../ois/jhove/handler/audit/AuditCount.java | 109 + .../ois/jhove/handler/audit/AuditState.java | 126 + .../hul/ois/jhove/handler/audit/build.xml | 32 + .../hul/ois/jhove/handler/audit/package.html | 24 + .../harvard/hul/ois/jhove/handler/build.xml | 65 + .../hul/ois/jhove/handler/package.html | 25 + .../edu/harvard/hul/ois/jhove/jhoveConfig.xsd | 118 + .../hul/ois/jhove/module/AiffModule.java | 763 + .../hul/ois/jhove/module/AsciiModule.java | 432 + .../ois/jhove/module/BytestreamModule.java | 167 + .../hul/ois/jhove/module/GifModule.java | 993 ++ .../hul/ois/jhove/module/HtmlModule.java | 765 + .../hul/ois/jhove/module/Jpeg2000Module.java | 878 ++ .../hul/ois/jhove/module/JpegModule.java | 1830 +++ .../hul/ois/jhove/module/PdfModule.java | 4115 ++++++ .../hul/ois/jhove/module/TiffModule.java | 1282 ++ .../hul/ois/jhove/module/Utf8Block.java | 234 + .../hul/ois/jhove/module/Utf8Module.java | 613 + .../hul/ois/jhove/module/WaveModule.java | 896 ++ .../hul/ois/jhove/module/XmlModule.java | 1167 ++ .../ois/jhove/module/aiff/AiffStrings.java | 29 + .../jhove/module/aiff/AnnotationChunk.java | 53 + .../jhove/module/aiff/ApplicationChunk.java | 84 + .../ois/jhove/module/aiff/AudioRecChunk.java | 68 + .../ois/jhove/module/aiff/AuthorChunk.java | 37 + .../ois/jhove/module/aiff/CommentsChunk.java | 90 + .../ois/jhove/module/aiff/CommonChunk.java | 185 + .../ois/jhove/module/aiff/CopyrightChunk.java | 37 + .../hul/ois/jhove/module/aiff/ExtDouble.java | 82 + .../jhove/module/aiff/FormatVersionChunk.java | 59 + .../jhove/module/aiff/InstrumentChunk.java | 128 + .../ois/jhove/module/aiff/MarkerChunk.java | 77 + .../hul/ois/jhove/module/aiff/MidiChunk.java | 58 + .../hul/ois/jhove/module/aiff/NameChunk.java | 36 + .../hul/ois/jhove/module/aiff/SaxelChunk.java | 86 + .../ois/jhove/module/aiff/SoundDataChunk.java | 67 + .../hul/ois/jhove/module/aiff/TextChunk.java | 77 + .../hul/ois/jhove/module/aiff/build.xml | 32 + .../hul/ois/jhove/module/aiff/package.html | 24 + .../harvard/hul/ois/jhove/module/build.xml | 108 + .../hul/ois/jhove/module/gif/GifStrings.java | 59 + .../hul/ois/jhove/module/gif/build.xml | 32 + .../hul/ois/jhove/module/gif/package.html | 24 + .../hul/ois/jhove/module/html/CharStream.java | 110 + .../hul/ois/jhove/module/html/DTDMapper.java | 112 + .../ois/jhove/module/html/Html3_2DocDesc.java | 698 + .../ois/jhove/module/html/Html4DocDesc.java | 528 + .../jhove/module/html/Html4StrictDocDesc.java | 452 + .../ois/jhove/module/html/Html4TFDocDesc.java | 672 + .../module/html/Html4_01FrameDocDesc.java | 39 + .../module/html/Html4_01StrictDocDesc.java | 227 + .../jhove/module/html/Html4_01TFDocDesc.java | 210 + .../module/html/Html4_01TransDocDesc.java | 42 + .../module/html/Html4_0FrameDocDesc.java | 42 + .../module/html/Html4_0StrictDocDesc.java | 234 + .../jhove/module/html/Html4_0TFDocDesc.java | 202 + .../module/html/Html4_0TransDocDesc.java | 45 + .../jhove/module/html/HtmlAttributeDesc.java | 112 + .../ois/jhove/module/html/HtmlCharStream.java | 467 + .../ois/jhove/module/html/HtmlDocDesc.java | 518 + .../ois/jhove/module/html/HtmlMetadata.java | 407 + .../jhove/module/html/HtmlSpecialToken.java | 32 + .../hul/ois/jhove/module/html/HtmlStack.java | 136 + .../ois/jhove/module/html/HtmlTagDesc.java | 388 + .../jhove/module/html/HtmlTempTagDesc.java | 49 + .../ois/jhove/module/html/JHAttribute.java | 88 + .../hul/ois/jhove/module/html/JHCloseTag.java | 39 + .../hul/ois/jhove/module/html/JHComment.java | 24 + .../hul/ois/jhove/module/html/JHDoctype.java | 33 + .../hul/ois/jhove/module/html/JHElement.java | 73 + .../ois/jhove/module/html/JHErrorElement.java | 71 + .../hul/ois/jhove/module/html/JHOpenTag.java | 440 + .../hul/ois/jhove/module/html/JHPCData.java | 97 + .../hul/ois/jhove/module/html/JHXmlDecl.java | 31 + .../ois/jhove/module/html/ParseException.java | 192 + .../hul/ois/jhove/module/html/ParseHtml.java | 668 + .../hul/ois/jhove/module/html/ParseHtml.jj | 316 + .../jhove/module/html/ParseHtmlConstants.java | 64 + .../module/html/ParseHtmlTokenManager.java | 1207 ++ .../jhove/module/html/SimpleCharStream.java | 401 + .../hul/ois/jhove/module/html/Token.java | 81 + .../ois/jhove/module/html/TokenMgrError.java | 133 + .../hul/ois/jhove/module/html/build.xml | 32 + .../hul/ois/jhove/module/html/package.html | 62 + .../hul/ois/jhove/module/html/xhtml-lat1.ent | 196 + .../ois/jhove/module/html/xhtml-special.ent | 80 + .../ois/jhove/module/html/xhtml-symbol.ent | 237 + .../ois/jhove/module/html/xhtml1-frameset.dtd | 1235 ++ .../ois/jhove/module/html/xhtml1-strict.dtd | 978 ++ .../jhove/module/html/xhtml1-transitional.dtd | 1201 ++ .../ois/jhove/module/html/xhtml11-flat.dtd | 4513 ++++++ .../hul/ois/jhove/module/iff/Chunk.java | 74 + .../hul/ois/jhove/module/iff/ChunkHeader.java | 84 + .../hul/ois/jhove/module/iff/Superchunk.java | 61 + .../hul/ois/jhove/module/iff/build.xml | 32 + .../hul/ois/jhove/module/iff/package.html | 27 + .../jhove/module/jpeg/ArithConditioning.java | 62 + .../hul/ois/jhove/module/jpeg/JpegExif.java | 205 + .../ois/jhove/module/jpeg/JpegStrings.java | 136 + .../jhove/module/jpeg/QuantizationTable.java | 61 + .../hul/ois/jhove/module/jpeg/SRS.java | 62 + .../hul/ois/jhove/module/jpeg/Spiff.java | 102 + .../hul/ois/jhove/module/jpeg/SpiffDir.java | 120 + .../hul/ois/jhove/module/jpeg/Tiling.java | 151 + .../hul/ois/jhove/module/jpeg/build.xml | 32 + .../hul/ois/jhove/module/jpeg/package.html | 24 + .../jhove/module/jpeg2000/AssociationBox.java | 113 + .../ois/jhove/module/jpeg2000/BPCCBox.java | 63 + .../module/jpeg2000/BinaryFilterBox.java | 141 + .../ois/jhove/module/jpeg2000/BoxHeader.java | 99 + .../ois/jhove/module/jpeg2000/BoxHolder.java | 196 + .../module/jpeg2000/COCMarkerSegment.java | 123 + .../module/jpeg2000/CODMarkerSegment.java | 117 + .../module/jpeg2000/CRGMarkerSegment.java | 73 + .../module/jpeg2000/CaptureResolutionBox.java | 86 + .../jhove/module/jpeg2000/ChannelDefBox.java | 98 + .../ois/jhove/module/jpeg2000/Codestream.java | 259 + .../module/jpeg2000/CodestreamHeaderBox.java | 124 + .../module/jpeg2000/CodestreamRegBox.java | 93 + .../jhove/module/jpeg2000/ColorGroupBox.java | 83 + .../jhove/module/jpeg2000/ColorSpecBox.java | 174 + .../module/jpeg2000/CommentMarkerSegment.java | 71 + .../jhove/module/jpeg2000/CompOptionsBox.java | 57 + .../module/jpeg2000/ComponentMapBox.java | 98 + .../module/jpeg2000/ComposLayerHdrBox.java | 163 + .../jhove/module/jpeg2000/CompositionBox.java | 164 + .../jhove/module/jpeg2000/ContCodestream.java | 254 + .../module/jpeg2000/ContCodestreamBox.java | 70 + .../jhove/module/jpeg2000/CrossRefBox.java | 100 + .../module/jpeg2000/DDResolutionBox.java | 100 + .../module/jpeg2000/DataEntryURLBox.java | 86 + .../ois/jhove/module/jpeg2000/DefaultBox.java | 44 + .../module/jpeg2000/DefaultMarkerSegment.java | 47 + .../module/jpeg2000/DesiredReproBox.java | 80 + .../module/jpeg2000/DigSignatureBox.java | 179 + .../jhove/module/jpeg2000/FileTypeBox.java | 142 + .../module/jpeg2000/FragmentInputStream.java | 109 + .../module/jpeg2000/FragmentListBox.java | 102 + .../module/jpeg2000/FragmentTableBox.java | 102 + .../ois/jhove/module/jpeg2000/GTSOBox.java | 62 + .../hul/ois/jhove/module/jpeg2000/IPRBox.java | 64 + .../jhove/module/jpeg2000/ImageHeaderBox.java | 109 + .../module/jpeg2000/InstructionSetBox.java | 145 + .../hul/ois/jhove/module/jpeg2000/JP2Box.java | 440 + .../jhove/module/jpeg2000/JP2HeaderBox.java | 159 + .../ois/jhove/module/jpeg2000/JP2Strings.java | 146 + .../ois/jhove/module/jpeg2000/LabelBox.java | 82 + .../ois/jhove/module/jpeg2000/MainOrTile.java | 124 + .../hul/ois/jhove/module/jpeg2000/Marker.java | 40 + .../jhove/module/jpeg2000/MarkerSegment.java | 231 + .../jhove/module/jpeg2000/NumberListBox.java | 96 + .../ois/jhove/module/jpeg2000/OpacityBox.java | 105 + .../module/jpeg2000/PLMMarkerSegment.java | 85 + .../module/jpeg2000/PLTMarkerSegment.java | 73 + .../module/jpeg2000/POCMarkerSegment.java | 109 + .../module/jpeg2000/PPMMarkerSegment.java | 63 + .../module/jpeg2000/PPTMarkerSegment.java | 70 + .../ois/jhove/module/jpeg2000/PaletteBox.java | 130 + .../module/jpeg2000/QCCMarkerSegment.java | 113 + .../module/jpeg2000/QCDMarkerSegment.java | 89 + .../module/jpeg2000/RGNMarkerSegment.java | 75 + .../hul/ois/jhove/module/jpeg2000/ROIBox.java | 138 + .../jpeg2000/ReaderRequirementsBox.java | 106 + .../jhove/module/jpeg2000/ResolutionBox.java | 85 + .../module/jpeg2000/SIZMarkerSegment.java | 136 + .../module/jpeg2000/SOTMarkerSegment.java | 63 + .../module/jpeg2000/TLMMarkerSegment.java | 100 + .../hul/ois/jhove/module/jpeg2000/Tile.java | 116 + .../ois/jhove/module/jpeg2000/TilePart.java | 62 + .../module/jpeg2000/TopLevelBoxHolder.java | 52 + .../ois/jhove/module/jpeg2000/UUIDBox.java | 91 + .../jhove/module/jpeg2000/UUIDInfoBox.java | 135 + .../jhove/module/jpeg2000/UUIDListBox.java | 66 + .../hul/ois/jhove/module/jpeg2000/XMLBox.java | 74 + .../hul/ois/jhove/module/jpeg2000/build.xml | 32 + .../ois/jhove/module/jpeg2000/package.html | 24 + .../harvard/hul/ois/jhove/module/package.html | 26 + .../hul/ois/jhove/module/pdf/AProfile.java | 1179 ++ .../ois/jhove/module/pdf/AProfileLevelA.java | 59 + .../hul/ois/jhove/module/pdf/ArrayEnd.java | 19 + .../hul/ois/jhove/module/pdf/ArrayStart.java | 19 + .../jhove/module/pdf/Ascii85FilterStream.java | 29 + .../module/pdf/AsciiHexFilterStream.java | 29 + .../hul/ois/jhove/module/pdf/Comment.java | 19 + .../ois/jhove/module/pdf/CrossRefStream.java | 305 + .../hul/ois/jhove/module/pdf/Destination.java | 134 + .../ois/jhove/module/pdf/DictionaryEnd.java | 19 + .../ois/jhove/module/pdf/DictionaryStart.java | 19 + .../hul/ois/jhove/module/pdf/DocNode.java | 192 + .../jhove/module/pdf/FileSpecification.java | 86 + .../ois/jhove/module/pdf/FileTokenizer.java | 131 + .../hul/ois/jhove/module/pdf/Filter.java | 66 + .../hul/ois/jhove/module/pdf/Hexadecimal.java | 24 + .../hul/ois/jhove/module/pdf/Keyword.java | 19 + .../jhove/module/pdf/LinearizedProfile.java | 302 + .../hul/ois/jhove/module/pdf/Literal.java | 741 + .../hul/ois/jhove/module/pdf/Name.java | 24 + .../ois/jhove/module/pdf/NameTreeNode.java | 215 + .../hul/ois/jhove/module/pdf/Numeric.java | 107 + .../ois/jhove/module/pdf/ObjectStream.java | 138 + .../ois/jhove/module/pdf/PageLabelNode.java | 368 + .../hul/ois/jhove/module/pdf/PageObject.java | 222 + .../ois/jhove/module/pdf/PageTreeNode.java | 261 + .../hul/ois/jhove/module/pdf/Parser.java | 459 + .../hul/ois/jhove/module/pdf/PdfArray.java | 115 + .../ois/jhove/module/pdf/PdfDictionary.java | 75 + .../ois/jhove/module/pdf/PdfException.java | 83 + .../jhove/module/pdf/PdfFlateInputStream.java | 345 + .../ois/jhove/module/pdf/PdfIndirectObj.java | 50 + .../jhove/module/pdf/PdfInvalidException.java | 51 + .../module/pdf/PdfMalformedException.java | 54 + .../hul/ois/jhove/module/pdf/PdfObject.java | 77 + .../hul/ois/jhove/module/pdf/PdfProfile.java | 196 + .../ois/jhove/module/pdf/PdfSimpleObject.java | 130 + .../hul/ois/jhove/module/pdf/PdfStream.java | 240 + .../hul/ois/jhove/module/pdf/PdfStrings.java | 100 + .../ois/jhove/module/pdf/PdfXMPSource.java | 95 + .../module/pdf/RunLengthFilterStream.java | 29 + .../hul/ois/jhove/module/pdf/State.java | 94 + .../ois/jhove/module/pdf/StdStructTypes.java | 71 + .../hul/ois/jhove/module/pdf/Stream.java | 227 + .../jhove/module/pdf/StreamInputStream.java | 55 + .../ois/jhove/module/pdf/StreamTokenizer.java | 97 + .../jhove/module/pdf/StringValuedToken.java | 49 + .../jhove/module/pdf/StructureElement.java | 377 + .../ois/jhove/module/pdf/StructureTree.java | 230 + .../ois/jhove/module/pdf/TaggedProfile.java | 72 + .../hul/ois/jhove/module/pdf/Token.java | 41 + .../hul/ois/jhove/module/pdf/Tokenizer.java | 903 ++ .../hul/ois/jhove/module/pdf/X1Profile.java | 450 + .../hul/ois/jhove/module/pdf/X1aProfile.java | 73 + .../hul/ois/jhove/module/pdf/X2Profile.java | 226 + .../hul/ois/jhove/module/pdf/X3Profile.java | 230 + .../ois/jhove/module/pdf/XProfileBase.java | 477 + .../hul/ois/jhove/module/pdf/build.xml | 32 + .../hul/ois/jhove/module/pdf/package.html | 24 + .../hul/ois/jhove/module/tiff/ExifIFD.java | 1040 ++ .../hul/ois/jhove/module/tiff/GPSInfoIFD.java | 665 + .../ois/jhove/module/tiff/GeoTiffStrings.java | 2077 +++ .../module/tiff/GlobalParametersIFD.java | 157 + .../hul/ois/jhove/module/tiff/IFD.java | 1014 ++ .../module/tiff/InteroperabilityIFD.java | 87 + .../ois/jhove/module/tiff/TiffException.java | 40 + .../hul/ois/jhove/module/tiff/TiffFXBase.java | 93 + .../hul/ois/jhove/module/tiff/TiffIFD.java | 3967 ++++++ .../ois/jhove/module/tiff/TiffProfile.java | 546 + .../jhove/module/tiff/TiffProfileClassB.java | 68 + .../jhove/module/tiff/TiffProfileClassF.java | 156 + .../jhove/module/tiff/TiffProfileClassG.java | 70 + .../jhove/module/tiff/TiffProfileClassIT.java | 49 + .../module/tiff/TiffProfileClassITBL.java | 85 + .../module/tiff/TiffProfileClassITBLP1.java | 111 + .../module/tiff/TiffProfileClassITBP.java | 86 + .../module/tiff/TiffProfileClassITBPP1.java | 103 + .../module/tiff/TiffProfileClassITBPP2.java | 98 + .../module/tiff/TiffProfileClassITCT.java | 76 + .../module/tiff/TiffProfileClassITCTP1.java | 105 + .../module/tiff/TiffProfileClassITCTP2.java | 103 + .../module/tiff/TiffProfileClassITFP.java | 98 + .../module/tiff/TiffProfileClassITFPP1.java | 113 + .../module/tiff/TiffProfileClassITFPP2.java | 114 + .../module/tiff/TiffProfileClassITHC.java | 90 + .../module/tiff/TiffProfileClassITHCP1.java | 110 + .../module/tiff/TiffProfileClassITHCP2.java | 99 + .../module/tiff/TiffProfileClassITLW.java | 87 + .../module/tiff/TiffProfileClassITLWP1.java | 97 + .../module/tiff/TiffProfileClassITLWP2.java | 88 + .../module/tiff/TiffProfileClassITMP.java | 79 + .../module/tiff/TiffProfileClassITMPP1.java | 112 + .../module/tiff/TiffProfileClassITMPP2.java | 109 + .../module/tiff/TiffProfileClassITSD.java | 84 + .../module/tiff/TiffProfileClassITSDP2.java | 102 + .../jhove/module/tiff/TiffProfileClassP.java | 71 + .../jhove/module/tiff/TiffProfileClassR.java | 72 + .../jhove/module/tiff/TiffProfileClassY.java | 73 + .../ois/jhove/module/tiff/TiffProfileDLF.java | 80 + .../jhove/module/tiff/TiffProfileDLFBW.java | 51 + .../module/tiff/TiffProfileDLFColor.java | 60 + .../jhove/module/tiff/TiffProfileDLFGray.java | 60 + .../ois/jhove/module/tiff/TiffProfileDNG.java | 123 + .../module/tiff/TiffProfileDNGThumb.java | 68 + .../ois/jhove/module/tiff/TiffProfileEP.java | 148 + .../jhove/module/tiff/TiffProfileExif.java | 125 + .../jhove/module/tiff/TiffProfileExifIFD.java | 67 + .../module/tiff/TiffProfileExifThumb.java | 71 + .../ois/jhove/module/tiff/TiffProfileFXC.java | 140 + .../ois/jhove/module/tiff/TiffProfileFXF.java | 105 + .../ois/jhove/module/tiff/TiffProfileFXJ.java | 107 + .../ois/jhove/module/tiff/TiffProfileFXL.java | 114 + .../ois/jhove/module/tiff/TiffProfileFXM.java | 115 + .../ois/jhove/module/tiff/TiffProfileFXS.java | 98 + .../jhove/module/tiff/TiffProfileGeoTIFF.java | 48 + .../module/tiff/TiffProfilePagemaker6.java | 128 + .../module/tiff/TiffProfilePhotoshop.java | 46 + .../jhove/module/tiff/TiffProfileRFC1314.java | 74 + .../hul/ois/jhove/module/tiff/TiffTags.java | 237 + .../hul/ois/jhove/module/tiff/build.xml | 33 + .../hul/ois/jhove/module/tiff/package.html | 24 + .../hul/ois/jhove/module/wave/AXMLChunk.java | 59 + .../jhove/module/wave/AssocDataListChunk.java | 106 + .../jhove/module/wave/BroadcastExtChunk.java | 123 + .../hul/ois/jhove/module/wave/CartChunk.java | 196 + .../hul/ois/jhove/module/wave/CueChunk.java | 102 + .../hul/ois/jhove/module/wave/DataChunk.java | 75 + .../hul/ois/jhove/module/wave/ExifInfo.java | 123 + .../jhove/module/wave/ExifMakerNoteChunk.java | 53 + .../jhove/module/wave/ExifStringChunk.java | 69 + .../module/wave/ExifUserCommentChunk.java | 100 + .../jhove/module/wave/ExifVersionChunk.java | 59 + .../hul/ois/jhove/module/wave/FactChunk.java | 61 + .../ois/jhove/module/wave/FormatChunk.java | 228 + .../jhove/module/wave/InstrumentChunk.java | 79 + .../hul/ois/jhove/module/wave/LabelChunk.java | 48 + .../jhove/module/wave/LabeledTextChunk.java | 86 + .../hul/ois/jhove/module/wave/LinkChunk.java | 109 + .../jhove/module/wave/LinkChunkHandler.java | 110 + .../ois/jhove/module/wave/ListInfoChunk.java | 232 + .../jhove/module/wave/ListInfoTextChunk.java | 151 + .../hul/ois/jhove/module/wave/MpegChunk.java | 76 + .../hul/ois/jhove/module/wave/NoteChunk.java | 48 + .../jhove/module/wave/PeakEnvelopeChunk.java | 186 + .../ois/jhove/module/wave/SampleChunk.java | 148 + .../jhove/module/wave/SimpleTextChunk.java | 62 + .../ois/jhove/module/wave/WaveStrings.java | 494 + .../hul/ois/jhove/module/wave/build.xml | 32 + .../hul/ois/jhove/module/wave/package.html | 24 + .../hul/ois/jhove/module/xml/EntityInfo.java | 12 + .../module/xml/ProcessingInstructionInfo.java | 12 + .../hul/ois/jhove/module/xml/SchemaInfo.java | 12 + .../ois/jhove/module/xml/XhtmlProcessing.java | 336 + .../ois/jhove/module/xml/XmlDeclHandler.java | 105 + .../ois/jhove/module/xml/XmlDeclStream.java | 351 + .../jhove/module/xml/XmlLexicalHandler.java | 123 + .../jhove/module/xml/XmlModuleHandler.java | 549 + .../hul/ois/jhove/module/xml/build.xml | 32 + .../hul/ois/jhove/module/xml/package.html | 24 + .../edu/harvard/hul/ois/jhove/package.html | 32 + .../hul/ois/jhove/viewer/AppInfoWindow.java | 127 + .../hul/ois/jhove/viewer/ConfigWindow.java | 649 + .../hul/ois/jhove/viewer/InfoWindow.java | 264 + .../hul/ois/jhove/viewer/JhoveWindow.java | 927 ++ .../hul/ois/jhove/viewer/MainScreen.java | 75 + .../ois/jhove/viewer/ModuleInfoWindow.java | 318 + .../hul/ois/jhove/viewer/NoConfAlert.java | 59 + .../hul/ois/jhove/viewer/NumericField.java | 31 + .../hul/ois/jhove/viewer/PrefsWindow.java | 117 + .../hul/ois/jhove/viewer/ProgressWindow.java | 174 + .../hul/ois/jhove/viewer/RepTreeRoot.java | 1530 ++ .../hul/ois/jhove/viewer/ViewHandler.java | 153 + .../hul/ois/jhove/viewer/ViewWindow.java | 172 + .../hul/ois/jhove/viewer/jhove-logo.gif | Bin 0 -> 5438 bytes .../harvard/hul/ois/jhove/viewer/package.html | 24 + jhove/classes/overview.html | 6 + jhove/conf/README | 63 + jhove/conf/jhove-byteoffset=true.conf | 47 + jhove/conf/jhove-withTextMD.conf | 51 + jhove/conf/jhove.conf | 45 + jhove/conf/jhove.conf.old | 45 + jhove/configure.pl | 88 + jhove/doc/allclasses-frame.html | 777 + jhove/doc/allclasses-noframe.html | 777 + jhove/doc/constant-values.html | 2170 +++ jhove/doc/deprecated-list.html | 199 + .../hul/ois/jhove/AESAudioMetadata.Face.html | 367 + .../jhove/AESAudioMetadata.FaceRegion.html | 322 + .../jhove/AESAudioMetadata.FormatRegion.html | 427 + .../ois/jhove/AESAudioMetadata.TimeDesc.html | 325 + .../hul/ois/jhove/AESAudioMetadata.html | 1419 ++ .../doc/edu/harvard/hul/ois/jhove/Agent.html | 555 + .../edu/harvard/hul/ois/jhove/AgentType.html | 346 + .../hul/ois/jhove/AnalogDigitalFlagType.html | 288 + jhove/doc/edu/harvard/hul/ois/jhove/App.html | 387 + .../hul/ois/jhove/ByteArrayXMPSource.html | 336 + .../edu/harvard/hul/ois/jhove/Callback.html | 219 + .../edu/harvard/hul/ois/jhove/Checksum.html | 350 + .../hul/ois/jhove/ChecksumInputStream.html | 482 + .../harvard/hul/ois/jhove/ChecksumType.html | 287 + .../harvard/hul/ois/jhove/Checksummer.html | 518 + .../harvard/hul/ois/jhove/ConfigHandler.html | 797 ++ .../harvard/hul/ois/jhove/ConfigWriter.html | 301 + .../hul/ois/jhove/CountedInputStream.html | 409 + .../jhove/DefaultConfigurationBuilder.html | 324 + .../edu/harvard/hul/ois/jhove/Document.html | 633 + .../harvard/hul/ois/jhove/DocumentType.html | 366 + jhove/doc/edu/harvard/hul/ois/jhove/Dump.html | 414 + .../hul/ois/jhove/EnumerationType.html | 297 + .../harvard/hul/ois/jhove/ErrorMessage.html | 323 + .../hul/ois/jhove/ExternalSignature.html | 316 + .../HandlerBase.SynchronizedDateFormat.html | 336 + .../harvard/hul/ois/jhove/HandlerBase.html | 1962 +++ .../edu/harvard/hul/ois/jhove/Identifier.html | 327 + .../harvard/hul/ois/jhove/IdentifierType.html | 630 + .../harvard/hul/ois/jhove/InfoMessage.html | 325 + .../hul/ois/jhove/InternalSignature.html | 480 + .../edu/harvard/hul/ois/jhove/JhoveBase.html | 1919 +++ .../harvard/hul/ois/jhove/JhoveException.html | 252 + .../edu/harvard/hul/ois/jhove/MacStuff.html | 284 + .../edu/harvard/hul/ois/jhove/Message.html | 486 + .../doc/edu/harvard/hul/ois/jhove/Module.html | 995 ++ .../edu/harvard/hul/ois/jhove/ModuleBase.html | 3213 +++++ .../edu/harvard/hul/ois/jhove/ModuleInfo.html | 314 + .../hul/ois/jhove/NisoImageMetadata.html | 5427 +++++++ .../hul/ois/jhove/ObjectIdentifier.html | 276 + .../harvard/hul/ois/jhove/OutputHandler.html | 857 ++ .../edu/harvard/hul/ois/jhove/Property.html | 395 + .../harvard/hul/ois/jhove/PropertyArity.html | 336 + .../harvard/hul/ois/jhove/PropertyPath.html | 396 + .../harvard/hul/ois/jhove/PropertyType.html | 565 + .../harvard/hul/ois/jhove/RAFInputStream.html | 486 + .../harvard/hul/ois/jhove/RFC1766Lang.html | 286 + .../edu/harvard/hul/ois/jhove/Rational.html | 380 + .../edu/harvard/hul/ois/jhove/RepInfo.html | 1425 ++ .../edu/harvard/hul/ois/jhove/Signature.html | 491 + .../harvard/hul/ois/jhove/SignatureType.html | 289 + .../hul/ois/jhove/SignatureUseType.html | 288 + .../harvard/hul/ois/jhove/TextMDMetadata.html | 1073 ++ .../hul/ois/jhove/Utf8BlockMarker.html | 299 + .../hul/ois/jhove/XMLWrapperStream.html | 335 + .../edu/harvard/hul/ois/jhove/XMPHandler.html | 406 + .../edu/harvard/hul/ois/jhove/XMPSource.html | 347 + .../hul/ois/jhove/handler/AuditHandler.html | 729 + .../hul/ois/jhove/handler/TextHandler.html | 491 + .../hul/ois/jhove/handler/XmlHandler.html | 1132 ++ .../ois/jhove/handler/audit/AuditCount.html | 467 + .../ois/jhove/handler/audit/AuditState.html | 464 + .../jhove/handler/audit/package-frame.html | 34 + .../jhove/handler/audit/package-summary.html | 180 + .../ois/jhove/handler/audit/package-tree.html | 154 + .../hul/ois/jhove/handler/package-frame.html | 36 + .../ois/jhove/handler/package-summary.html | 186 + .../hul/ois/jhove/handler/package-tree.html | 156 + .../hul/ois/jhove/module/AiffModule.html | 1236 ++ .../hul/ois/jhove/module/AsciiModule.html | 556 + .../ois/jhove/module/BytestreamModule.html | 408 + .../hul/ois/jhove/module/GifModule.html | 852 ++ .../hul/ois/jhove/module/HtmlModule.html | 765 + .../hul/ois/jhove/module/Jpeg2000Module.html | 1562 ++ .../hul/ois/jhove/module/JpegModule.html | 1769 +++ .../hul/ois/jhove/module/PdfModule.html | 3146 ++++ .../hul/ois/jhove/module/TiffException.html | 226 + .../hul/ois/jhove/module/TiffModule.html | 783 + .../hul/ois/jhove/module/Utf8Block.html | 352 + .../hul/ois/jhove/module/Utf8Module.html | 610 + .../hul/ois/jhove/module/WaveModule.html | 1615 +++ .../hul/ois/jhove/module/XmlModule.html | 781 + .../ois/jhove/module/aiff/AiffStrings.html | 236 + .../jhove/module/aiff/AnnotationChunk.html | 328 + .../jhove/module/aiff/ApplicationChunk.html | 306 + .../ois/jhove/module/aiff/AudioRecChunk.html | 312 + .../ois/jhove/module/aiff/AuthorChunk.html | 285 + .../ois/jhove/module/aiff/CommentsChunk.html | 306 + .../ois/jhove/module/aiff/CommonChunk.html | 306 + .../ois/jhove/module/aiff/CopyrightChunk.html | 285 + .../hul/ois/jhove/module/aiff/ExtDouble.html | 277 + .../jhove/module/aiff/FormatVersionChunk.html | 307 + .../jhove/module/aiff/InstrumentChunk.html | 306 + .../ois/jhove/module/aiff/MarkerChunk.html | 306 + .../hul/ois/jhove/module/aiff/MidiChunk.html | 306 + .../hul/ois/jhove/module/aiff/NameChunk.html | 285 + .../hul/ois/jhove/module/aiff/SaxelChunk.html | 312 + .../ois/jhove/module/aiff/SoundDataChunk.html | 306 + .../hul/ois/jhove/module/aiff/TextChunk.html | 372 + .../ois/jhove/module/aiff/package-frame.html | 64 + .../jhove/module/aiff/package-summary.html | 245 + .../ois/jhove/module/aiff/package-tree.html | 155 + .../hul/ois/jhove/module/gif/GifStrings.html | 358 + .../ois/jhove/module/gif/package-frame.html | 32 + .../ois/jhove/module/gif/package-summary.html | 178 + .../ois/jhove/module/gif/package-tree.html | 151 + .../hul/ois/jhove/module/html/CharStream.html | 497 + .../hul/ois/jhove/module/html/DTDMapper.html | 311 + .../ois/jhove/module/html/Html3_2DocDesc.html | 260 + .../ois/jhove/module/html/Html4DocDesc.html | 1314 ++ .../jhove/module/html/Html4StrictDocDesc.html | 765 + .../ois/jhove/module/html/Html4TFDocDesc.html | 968 ++ .../module/html/Html4_01FrameDocDesc.html | 299 + .../module/html/Html4_01StrictDocDesc.html | 289 + .../jhove/module/html/Html4_01TFDocDesc.html | 325 + .../module/html/Html4_01TransDocDesc.html | 299 + .../module/html/Html4_0FrameDocDesc.html | 299 + .../module/html/Html4_0StrictDocDesc.html | 292 + .../jhove/module/html/Html4_0TFDocDesc.html | 322 + .../module/html/Html4_0TransDocDesc.html | 302 + .../jhove/module/html/HtmlAttributeDesc.html | 475 + .../ois/jhove/module/html/HtmlCharStream.html | 1252 ++ .../ois/jhove/module/html/HtmlDocDesc.html | 625 + .../ois/jhove/module/html/HtmlMetadata.html | 719 + .../jhove/module/html/HtmlSpecialToken.html | 238 + .../hul/ois/jhove/module/html/HtmlStack.html | 567 + .../ois/jhove/module/html/HtmlTagDesc.html | 795 ++ .../jhove/module/html/HtmlTempTagDesc.html | 330 + .../ois/jhove/module/html/JHAttribute.html | 369 + .../hul/ois/jhove/module/html/JHCloseTag.html | 326 + .../hul/ois/jhove/module/html/JHComment.html | 263 + .../hul/ois/jhove/module/html/JHDoctype.html | 321 + .../hul/ois/jhove/module/html/JHElement.html | 371 + .../ois/jhove/module/html/JHErrorElement.html | 329 + .../hul/ois/jhove/module/html/JHOpenTag.html | 648 + .../hul/ois/jhove/module/html/JHPCData.html | 331 + .../hul/ois/jhove/module/html/JHXmlDecl.html | 267 + .../ois/jhove/module/html/ParseException.html | 496 + .../hul/ois/jhove/module/html/ParseHtml.html | 823 ++ .../jhove/module/html/ParseHtmlConstants.html | 673 + .../module/html/ParseHtmlTokenManager.html | 519 + .../jhove/module/html/SimpleCharStream.html | 1015 ++ .../hul/ois/jhove/module/html/Token.html | 501 + .../ois/jhove/module/html/TokenMgrError.html | 383 + .../ois/jhove/module/html/package-frame.html | 133 + .../jhove/module/html/package-summary.html | 405 + .../ois/jhove/module/html/package-tree.html | 193 + .../hul/ois/jhove/module/iff/Chunk.html | 379 + .../hul/ois/jhove/module/iff/ChunkHeader.html | 333 + .../hul/ois/jhove/module/iff/Superchunk.html | 311 + .../ois/jhove/module/iff/package-frame.html | 36 + .../ois/jhove/module/iff/package-summary.html | 189 + .../ois/jhove/module/iff/package-tree.html | 153 + .../jhove/module/jpeg/ArithConditioning.html | 264 + .../hul/ois/jhove/module/jpeg/JpegExif.html | 315 + .../ois/jhove/module/jpeg/JpegStrings.html | 347 + .../jhove/module/jpeg/QuantizationTable.html | 264 + .../hul/ois/jhove/module/jpeg/SRS.html | 268 + .../hul/ois/jhove/module/jpeg/Spiff.html | 540 + .../hul/ois/jhove/module/jpeg/SpiffDir.html | 307 + .../hul/ois/jhove/module/jpeg/Tiling.html | 479 + .../ois/jhove/module/jpeg/package-frame.html | 46 + .../jhove/module/jpeg/package-summary.html | 207 + .../ois/jhove/module/jpeg/package-tree.html | 151 + .../jhove/module/jpeg2000/AssociationBox.html | 390 + .../ois/jhove/module/jpeg2000/BPCCBox.html | 354 + .../module/jpeg2000/BinaryFilterBox.html | 414 + .../ois/jhove/module/jpeg2000/BoxHeader.html | 365 + .../ois/jhove/module/jpeg2000/BoxHolder.html | 609 + .../module/jpeg2000/COCMarkerSegment.html | 307 + .../module/jpeg2000/CODMarkerSegment.html | 304 + .../module/jpeg2000/CRGMarkerSegment.html | 304 + .../module/jpeg2000/CaptureResolutionBox.html | 354 + .../jhove/module/jpeg2000/ChannelDefBox.html | 353 + .../ois/jhove/module/jpeg2000/Codestream.html | 552 + .../module/jpeg2000/CodestreamHeaderBox.html | 378 + .../module/jpeg2000/CodestreamRegBox.html | 355 + .../jhove/module/jpeg2000/ColorGroupBox.html | 378 + .../jhove/module/jpeg2000/ColorSpecBox.html | 356 + .../module/jpeg2000/CommentMarkerSegment.html | 305 + .../jhove/module/jpeg2000/CompOptionsBox.html | 331 + .../module/jpeg2000/ComponentMapBox.html | 354 + .../module/jpeg2000/ComposLayerHdrBox.html | 438 + .../jhove/module/jpeg2000/CompositionBox.html | 419 + .../jhove/module/jpeg2000/ContCodestream.html | 439 + .../module/jpeg2000/ContCodestreamBox.html | 357 + .../jhove/module/jpeg2000/CrossRefBox.html | 388 + .../module/jpeg2000/DDResolutionBox.html | 354 + .../module/jpeg2000/DataEntryURLBox.html | 353 + .../ois/jhove/module/jpeg2000/DefaultBox.html | 348 + .../module/jpeg2000/DefaultMarkerSegment.html | 307 + .../module/jpeg2000/DesiredReproBox.html | 355 + .../module/jpeg2000/DigSignatureBox.html | 357 + .../jhove/module/jpeg2000/FileTypeBox.html | 372 + .../module/jpeg2000/FragmentInputStream.html | 312 + .../module/jpeg2000/FragmentListBox.html | 380 + .../module/jpeg2000/FragmentTableBox.html | 355 + .../ois/jhove/module/jpeg2000/GTSOBox.html | 332 + .../hul/ois/jhove/module/jpeg2000/IPRBox.html | 357 + .../jhove/module/jpeg2000/ImageHeaderBox.html | 354 + .../module/jpeg2000/InstructionSetBox.html | 330 + .../hul/ois/jhove/module/jpeg2000/JP2Box.html | 892 ++ .../jhove/module/jpeg2000/JP2HeaderBox.html | 353 + .../ois/jhove/module/jpeg2000/JP2Strings.html | 487 + .../ois/jhove/module/jpeg2000/LabelBox.html | 404 + .../ois/jhove/module/jpeg2000/MainOrTile.html | 591 + .../hul/ois/jhove/module/jpeg2000/Marker.html | 328 + .../jhove/module/jpeg2000/MarkerSegment.html | 984 ++ .../jhove/module/jpeg2000/NumberListBox.html | 384 + .../ois/jhove/module/jpeg2000/OpacityBox.html | 354 + .../module/jpeg2000/PLMMarkerSegment.html | 304 + .../module/jpeg2000/PLTMarkerSegment.html | 304 + .../module/jpeg2000/POCMarkerSegment.html | 302 + .../module/jpeg2000/PPMMarkerSegment.html | 307 + .../module/jpeg2000/PPTMarkerSegment.html | 305 + .../ois/jhove/module/jpeg2000/PaletteBox.html | 354 + .../module/jpeg2000/QCCMarkerSegment.html | 305 + .../module/jpeg2000/QCDMarkerSegment.html | 305 + .../module/jpeg2000/RGNMarkerSegment.html | 303 + .../hul/ois/jhove/module/jpeg2000/ROIBox.html | 380 + .../jpeg2000/ReaderRequirementsBox.html | 354 + .../jhove/module/jpeg2000/ResolutionBox.html | 358 + .../module/jpeg2000/SIZMarkerSegment.html | 306 + .../module/jpeg2000/SOTMarkerSegment.html | 303 + .../module/jpeg2000/TLMMarkerSegment.html | 302 + .../hul/ois/jhove/module/jpeg2000/Tile.html | 354 + .../ois/jhove/module/jpeg2000/TilePart.html | 289 + .../module/jpeg2000/TopLevelBoxHolder.html | 333 + .../ois/jhove/module/jpeg2000/UUIDBox.html | 355 + .../jhove/module/jpeg2000/UUIDInfoBox.html | 399 + .../jhove/module/jpeg2000/UUIDListBox.html | 354 + .../hul/ois/jhove/module/jpeg2000/XMLBox.html | 380 + .../jhove/module/jpeg2000/package-frame.html | 166 + .../module/jpeg2000/package-summary.html | 453 + .../jhove/module/jpeg2000/package-tree.html | 163 + .../hul/ois/jhove/module/package-frame.html | 56 + .../hul/ois/jhove/module/package-summary.html | 226 + .../hul/ois/jhove/module/package-tree.html | 154 + .../hul/ois/jhove/module/pdf/AProfile.html | 421 + .../ois/jhove/module/pdf/AProfileLevelA.html | 328 + .../hul/ois/jhove/module/pdf/ArrayEnd.html | 238 + .../hul/ois/jhove/module/pdf/ArrayStart.html | 238 + .../jhove/module/pdf/Ascii85FilterStream.html | 267 + .../module/pdf/AsciiHexFilterStream.html | 267 + .../hul/ois/jhove/module/pdf/Comment.html | 266 + .../ois/jhove/module/pdf/CrossRefStream.html | 530 + .../hul/ois/jhove/module/pdf/Destination.html | 368 + .../ois/jhove/module/pdf/DictionaryEnd.html | 238 + .../ois/jhove/module/pdf/DictionaryStart.html | 238 + .../hul/ois/jhove/module/pdf/DocNode.html | 615 + .../jhove/module/pdf/FileSpecification.html | 303 + .../ois/jhove/module/pdf/FileTokenizer.html | 430 + .../hul/ois/jhove/module/pdf/Filter.html | 333 + .../hul/ois/jhove/module/pdf/Hexadecimal.html | 290 + .../hul/ois/jhove/module/pdf/Keyword.html | 266 + .../jhove/module/pdf/LinearizedProfile.html | 289 + .../hul/ois/jhove/module/pdf/Literal.html | 511 + .../hul/ois/jhove/module/pdf/Name.html | 299 + .../ois/jhove/module/pdf/NameTreeNode.html | 373 + .../hul/ois/jhove/module/pdf/Numeric.html | 405 + .../ois/jhove/module/pdf/ObjectStream.html | 324 + .../ois/jhove/module/pdf/PageLabelNode.html | 586 + .../hul/ois/jhove/module/pdf/PageObject.html | 500 + .../ois/jhove/module/pdf/PageTreeNode.html | 422 + .../hul/ois/jhove/module/pdf/Parser.html | 784 + .../hul/ois/jhove/module/pdf/PdfArray.html | 396 + .../ois/jhove/module/pdf/PdfDictionary.html | 375 + .../ois/jhove/module/pdf/PdfException.html | 364 + .../jhove/module/pdf/PdfFlateInputStream.html | 417 + .../ois/jhove/module/pdf/PdfIndirectObj.html | 294 + .../jhove/module/pdf/PdfInvalidException.html | 330 + .../module/pdf/PdfMalformedException.html | 331 + .../hul/ois/jhove/module/pdf/PdfObject.html | 419 + .../hul/ois/jhove/module/pdf/PdfProfile.html | 519 + .../ois/jhove/module/pdf/PdfSimpleObject.html | 447 + .../hul/ois/jhove/module/pdf/PdfStream.html | 483 + .../hul/ois/jhove/module/pdf/PdfStrings.html | 313 + .../ois/jhove/module/pdf/PdfXMPSource.html | 371 + .../module/pdf/RunLengthFilterStream.html | 267 + .../hul/ois/jhove/module/pdf/State.html | 864 ++ .../ois/jhove/module/pdf/StdStructTypes.html | 306 + .../hul/ois/jhove/module/pdf/Stream.html | 518 + .../jhove/module/pdf/StreamInputStream.html | 282 + .../ois/jhove/module/pdf/StreamTokenizer.html | 411 + .../jhove/module/pdf/StringValuedToken.html | 366 + .../jhove/module/pdf/StructureElement.html | 304 + .../ois/jhove/module/pdf/StructureTree.html | 350 + .../ois/jhove/module/pdf/TaggedProfile.html | 295 + .../hul/ois/jhove/module/pdf/Token.html | 290 + .../hul/ois/jhove/module/pdf/Tokenizer.html | 729 + .../hul/ois/jhove/module/pdf/X1Profile.html | 394 + .../hul/ois/jhove/module/pdf/X1aProfile.html | 341 + .../hul/ois/jhove/module/pdf/X2Profile.html | 339 + .../hul/ois/jhove/module/pdf/X3Profile.html | 339 + .../ois/jhove/module/pdf/XProfileBase.html | 666 + .../ois/jhove/module/pdf/package-frame.html | 153 + .../ois/jhove/module/pdf/package-summary.html | 422 + .../ois/jhove/module/pdf/package-tree.html | 183 + .../jhove/module/tiff/DirectoryProperty.html | 723 + .../hul/ois/jhove/module/tiff/ExifIFD.html | 824 ++ .../hul/ois/jhove/module/tiff/GPSInfoIFD.html | 960 ++ .../ois/jhove/module/tiff/GeoTiffStrings.html | 681 + .../ois/jhove/module/tiff/GeoTiffTags.html | 483 + .../module/tiff/GlobalParametersIFD.html | 447 + .../hul/ois/jhove/module/tiff/IFD.html | 1900 +++ .../module/tiff/InteroperabilityIFD.html | 351 + .../module/tiff/StandardIFDProperty.html | 283 + .../hul/ois/jhove/module/tiff/TagFamily.html | 789 + .../ois/jhove/module/tiff/TiffException.html | 292 + .../hul/ois/jhove/module/tiff/TiffFXBase.html | 373 + .../hul/ois/jhove/module/tiff/TiffIFD.html | 4793 +++++++ .../ois/jhove/module/tiff/TiffProfile.html | 1146 ++ .../jhove/module/tiff/TiffProfileClassB.html | 290 + .../jhove/module/tiff/TiffProfileClassF.html | 296 + .../jhove/module/tiff/TiffProfileClassG.html | 294 + .../jhove/module/tiff/TiffProfileClassIT.html | 298 + .../module/tiff/TiffProfileClassITBL.html | 299 + .../module/tiff/TiffProfileClassITBLP1.html | 301 + .../module/tiff/TiffProfileClassITBP.html | 299 + .../module/tiff/TiffProfileClassITBPP1.html | 301 + .../module/tiff/TiffProfileClassITBPP2.html | 298 + .../module/tiff/TiffProfileClassITCT.html | 295 + .../module/tiff/TiffProfileClassITCTP1.html | 295 + .../module/tiff/TiffProfileClassITCTP2.html | 295 + .../module/tiff/TiffProfileClassITFP.html | 301 + .../module/tiff/TiffProfileClassITFPP1.html | 297 + .../module/tiff/TiffProfileClassITFPP2.html | 298 + .../module/tiff/TiffProfileClassITHC.html | 300 + .../module/tiff/TiffProfileClassITHCP1.html | 300 + .../module/tiff/TiffProfileClassITHCP2.html | 298 + .../module/tiff/TiffProfileClassITLW.html | 299 + .../module/tiff/TiffProfileClassITLWP1.html | 300 + .../module/tiff/TiffProfileClassITLWP2.html | 301 + .../module/tiff/TiffProfileClassITMP.html | 300 + .../module/tiff/TiffProfileClassITMPP1.html | 301 + .../module/tiff/TiffProfileClassITMPP2.html | 298 + .../module/tiff/TiffProfileClassITSD.html | 300 + .../module/tiff/TiffProfileClassITSDP2.html | 302 + .../jhove/module/tiff/TiffProfileClassP.html | 294 + .../jhove/module/tiff/TiffProfileClassR.html | 294 + .../jhove/module/tiff/TiffProfileClassY.html | 294 + .../ois/jhove/module/tiff/TiffProfileDLF.html | 327 + .../jhove/module/tiff/TiffProfileDLFBW.html | 301 + .../module/tiff/TiffProfileDLFColor.html | 301 + .../jhove/module/tiff/TiffProfileDLFGray.html | 301 + .../ois/jhove/module/tiff/TiffProfileDNG.html | 341 + .../module/tiff/TiffProfileDNGThumb.html | 297 + .../ois/jhove/module/tiff/TiffProfileEP.html | 296 + .../jhove/module/tiff/TiffProfileExif.html | 294 + .../jhove/module/tiff/TiffProfileExifIFD.html | 295 + .../module/tiff/TiffProfileExifThumb.html | 325 + .../ois/jhove/module/tiff/TiffProfileFXC.html | 318 + .../ois/jhove/module/tiff/TiffProfileFXF.html | 321 + .../ois/jhove/module/tiff/TiffProfileFXJ.html | 318 + .../ois/jhove/module/tiff/TiffProfileFXL.html | 318 + .../ois/jhove/module/tiff/TiffProfileFXM.html | 324 + .../ois/jhove/module/tiff/TiffProfileFXS.html | 318 + .../jhove/module/tiff/TiffProfileGeoTIFF.html | 289 + .../module/tiff/TiffProfilePagemaker6.html | 289 + .../module/tiff/TiffProfilePhotoshop.html | 289 + .../jhove/module/tiff/TiffProfileRFC1314.html | 294 + .../hul/ois/jhove/module/tiff/TiffTags.html | 225 + .../ois/jhove/module/tiff/package-frame.html | 159 + .../jhove/module/tiff/package-summary.html | 431 + .../ois/jhove/module/tiff/package-tree.html | 166 + .../hul/ois/jhove/module/wave/AXMLChunk.html | 311 + .../jhove/module/wave/AssocDataListChunk.html | 321 + .../jhove/module/wave/BroadcastExtChunk.html | 306 + .../hul/ois/jhove/module/wave/CartChunk.html | 306 + .../hul/ois/jhove/module/wave/CueChunk.html | 307 + .../hul/ois/jhove/module/wave/DataChunk.html | 310 + .../hul/ois/jhove/module/wave/ExifInfo.html | 411 + .../jhove/module/wave/ExifMakerNoteChunk.html | 308 + .../jhove/module/wave/ExifStringChunk.html | 307 + .../module/wave/ExifUserCommentChunk.html | 306 + .../jhove/module/wave/ExifVersionChunk.html | 308 + .../hul/ois/jhove/module/wave/FactChunk.html | 308 + .../ois/jhove/module/wave/FormatChunk.html | 377 + .../jhove/module/wave/InstrumentChunk.html | 309 + .../hul/ois/jhove/module/wave/LabelChunk.html | 314 + .../jhove/module/wave/LabeledTextChunk.html | 307 + .../hul/ois/jhove/module/wave/LinkChunk.html | 310 + .../jhove/module/wave/LinkChunkHandler.html | 395 + .../ois/jhove/module/wave/ListInfoChunk.html | 351 + .../jhove/module/wave/ListInfoTextChunk.html | 313 + .../hul/ois/jhove/module/wave/MpegChunk.html | 306 + .../hul/ois/jhove/module/wave/NoteChunk.html | 314 + .../jhove/module/wave/PeakEnvelopeChunk.html | 306 + .../ois/jhove/module/wave/SampleChunk.html | 313 + .../jhove/module/wave/SimpleTextChunk.html | 306 + .../ois/jhove/module/wave/WaveStrings.html | 384 + .../ois/jhove/module/wave/package-frame.html | 82 + .../jhove/module/wave/package-summary.html | 294 + .../ois/jhove/module/wave/package-tree.html | 160 + .../hul/ois/jhove/module/xml/EntityInfo.html | 280 + .../module/xml/ProcessingInstructionInfo.html | 280 + .../hul/ois/jhove/module/xml/SchemaInfo.html | 280 + .../ois/jhove/module/xml/XhtmlProcessing.html | 268 + .../ois/jhove/module/xml/XmlDeclHandler.html | 429 + .../ois/jhove/module/xml/XmlDeclStream.html | 576 + .../jhove/module/xml/XmlLexicalHandler.html | 499 + .../jhove/module/xml/XmlModuleHandler.html | 899 ++ .../ois/jhove/module/xml/package-frame.html | 46 + .../ois/jhove/module/xml/package-summary.html | 209 + .../ois/jhove/module/xml/package-tree.html | 161 + .../harvard/hul/ois/jhove/package-frame.html | 162 + .../hul/ois/jhove/package-summary.html | 455 + .../harvard/hul/ois/jhove/package-tree.html | 190 + .../hul/ois/jhove/viewer/AppInfoWindow.html | 425 + .../hul/ois/jhove/viewer/ConfigWindow.html | 409 + .../hul/ois/jhove/viewer/ConfigWriter.html | 320 + .../hul/ois/jhove/viewer/InfoWindow.html | 604 + .../JhoveWindow.InvisibleFilenameFilter.html | 270 + .../hul/ois/jhove/viewer/JhoveWindow.html | 876 ++ .../hul/ois/jhove/viewer/LeafHolder.html | 261 + .../hul/ois/jhove/viewer/MainScreen.html | 269 + .../ois/jhove/viewer/ModuleInfoWindow.html | 464 + .../hul/ois/jhove/viewer/NoConfAlert.html | 395 + .../hul/ois/jhove/viewer/NumericField.html | 434 + .../hul/ois/jhove/viewer/PrefsWindow.html | 433 + .../hul/ois/jhove/viewer/ProgressWindow.html | 589 + .../hul/ois/jhove/viewer/RepTreeRoot.html | 303 + .../ois/jhove/viewer/ViewCellRenderer.html | 258 + .../hul/ois/jhove/viewer/ViewHandler.html | 484 + .../hul/ois/jhove/viewer/ViewWindow.html | 525 + .../hul/ois/jhove/viewer/package-frame.html | 56 + .../hul/ois/jhove/viewer/package-summary.html | 231 + .../hul/ois/jhove/viewer/package-tree.html | 191 + jhove/doc/help-doc.html | 217 + jhove/doc/index-all.html | 11866 ++++++++++++++++ jhove/doc/index.html | 39 + jhove/doc/overview-frame.html | 70 + jhove/doc/overview-summary.html | 218 + jhove/doc/overview-tree.html | 314 + jhove/doc/package-list | 15 + jhove/doc/packages.html | 32 + jhove/doc/resources/inherit.gif | Bin 0 -> 57 bytes jhove/doc/serialized-form.html | 1301 ++ jhove/doc/stylesheet.css | 29 + .../it/eng/jhove/module/odf/JingDriver.java | 76 + .../eng/jhove/module/odf/ManifestHandler.java | 82 + .../it/eng/jhove/module/odf/MetaHandler.java | 87 + .../it/eng/jhove/module/odf/OdfModule.java | 775 + .../it/eng/jhove/module/png/PngModule.java | 1341 ++ jhove/gdump | 37 + jhove/gdump.bat | 46 + jhove/j2dump | 37 + jhove/j2dump.bat | 46 + jhove/jdump | 37 + jhove/jdump.bat | 46 + jhove/jhove | 58 + jhove/jhove.bat | 58 + jhove/jhove.tmpl | 54 + jhove/jhove_bat.tmpl | 63 + jhove/lib/OdfModule.jar | Bin 0 -> 67976 bytes jhove/lib/PngModule.jar | Bin 0 -> 17191 bytes jhove/md5.pl | 14 + jhove/packagejhove.sh | 44 + jhove/pdump | 37 + jhove/pdump.bat | 46 + .../DefaultConfigurationBuilderTest.java | 20 + jhove/userhome | 24 + jhove/userhome.bat | 26 + 900 files changed, 311821 insertions(+), 1 deletion(-) create mode 100644 jhove/COPYING create mode 100644 jhove/LICENSE create mode 100644 jhove/README create mode 100644 jhove/RELEASENOTES create mode 100644 jhove/build.xml create mode 100644 jhove/classes/ADump.java create mode 100644 jhove/classes/GDump.java create mode 100644 jhove/classes/J2Dump.java create mode 100644 jhove/classes/JDump.java create mode 100644 jhove/classes/Jhove.java create mode 100644 jhove/classes/JhoveView.java create mode 100644 jhove/classes/PDump.java create mode 100644 jhove/classes/TDump.java create mode 100644 jhove/classes/UserHome.java create mode 100644 jhove/classes/WDump.java create mode 100644 jhove/classes/build.xml create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/AESAudioMetadata.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/Agent.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/AgentType.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/AnalogDigitalFlagType.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/App.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/ByteArrayXMPSource.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/Callback.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/Checksum.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/ChecksumInputStream.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/ChecksumType.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/Checksummer.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/ConfigHandler.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/ConfigWriter.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/CountedInputStream.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/DefaultConfigurationBuilder.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/Document.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/DocumentType.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/Dump.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/EnumerationType.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/ErrorMessage.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/ExternalSignature.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/HandlerBase.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/Identifier.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/IdentifierType.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/InfoMessage.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/InternalSignature.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/JhoveBase.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/JhoveException.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/MacStuff.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/Message.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/Module.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/ModuleBase.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/ModuleInfo.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/NisoImageMetadata.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/ObjectIdentifier.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/OutputHandler.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/Property.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/PropertyArity.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/PropertyPath.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/PropertyType.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/RAFInputStream.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/RFC1766Lang.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/Rational.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/RepInfo.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/Signature.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/SignatureType.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/SignatureUseType.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/TextMDMetadata.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/Utf8BlockMarker.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/XMLWrapperStream.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/XMPHandler.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/XMPSource.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/build.xml create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/handler/AuditHandler.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/handler/META-INF/MANIFEST.MF create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/handler/TextHandler.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/handler/XmlHandler.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/handler/audit/AuditCount.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/handler/audit/AuditState.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/handler/audit/build.xml create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/handler/audit/package.html create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/handler/build.xml create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/handler/package.html create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/jhoveConfig.xsd create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/AiffModule.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/AsciiModule.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/BytestreamModule.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/GifModule.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/HtmlModule.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/Jpeg2000Module.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/JpegModule.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/PdfModule.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/TiffModule.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/Utf8Block.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/Utf8Module.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/WaveModule.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/XmlModule.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/AiffStrings.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/AnnotationChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/ApplicationChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/AudioRecChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/AuthorChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/CommentsChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/CommonChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/CopyrightChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/ExtDouble.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/FormatVersionChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/InstrumentChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/MarkerChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/MidiChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/NameChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/SaxelChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/SoundDataChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/TextChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/build.xml create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/package.html create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/build.xml create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/gif/GifStrings.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/gif/build.xml create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/gif/package.html create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/CharStream.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/DTDMapper.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html3_2DocDesc.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4DocDesc.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4StrictDocDesc.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4TFDocDesc.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4_01FrameDocDesc.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4_01StrictDocDesc.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4_01TFDocDesc.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4_01TransDocDesc.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4_0FrameDocDesc.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4_0StrictDocDesc.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4_0TFDocDesc.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4_0TransDocDesc.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/HtmlAttributeDesc.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/HtmlCharStream.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/HtmlDocDesc.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/HtmlMetadata.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/HtmlSpecialToken.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/HtmlStack.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/HtmlTagDesc.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/HtmlTempTagDesc.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHAttribute.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHCloseTag.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHComment.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHDoctype.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHElement.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHErrorElement.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHOpenTag.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHPCData.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHXmlDecl.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/ParseException.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/ParseHtml.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/ParseHtml.jj create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/ParseHtmlConstants.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/ParseHtmlTokenManager.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/SimpleCharStream.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/Token.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/TokenMgrError.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/build.xml create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/package.html create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/xhtml-lat1.ent create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/xhtml-special.ent create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/xhtml-symbol.ent create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/xhtml1-frameset.dtd create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/xhtml1-strict.dtd create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/xhtml1-transitional.dtd create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/html/xhtml11-flat.dtd create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/iff/Chunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/iff/ChunkHeader.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/iff/Superchunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/iff/build.xml create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/iff/package.html create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/ArithConditioning.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/JpegExif.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/JpegStrings.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/QuantizationTable.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/SRS.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/Spiff.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/SpiffDir.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/Tiling.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/build.xml create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/package.html create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/AssociationBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/BPCCBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/BinaryFilterBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/BoxHeader.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/BoxHolder.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/COCMarkerSegment.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CODMarkerSegment.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CRGMarkerSegment.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CaptureResolutionBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ChannelDefBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/Codestream.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CodestreamHeaderBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CodestreamRegBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ColorGroupBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ColorSpecBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CommentMarkerSegment.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CompOptionsBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ComponentMapBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ComposLayerHdrBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CompositionBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ContCodestream.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ContCodestreamBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CrossRefBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/DDResolutionBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/DataEntryURLBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/DefaultBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/DefaultMarkerSegment.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/DesiredReproBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/DigSignatureBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/FileTypeBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/FragmentInputStream.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/FragmentListBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/FragmentTableBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/GTSOBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/IPRBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ImageHeaderBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/InstructionSetBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/JP2Box.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/JP2HeaderBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/JP2Strings.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/LabelBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/MainOrTile.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/Marker.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/MarkerSegment.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/NumberListBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/OpacityBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/PLMMarkerSegment.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/PLTMarkerSegment.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/POCMarkerSegment.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/PPMMarkerSegment.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/PPTMarkerSegment.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/PaletteBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/QCCMarkerSegment.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/QCDMarkerSegment.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/RGNMarkerSegment.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ROIBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ReaderRequirementsBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ResolutionBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/SIZMarkerSegment.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/SOTMarkerSegment.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/TLMMarkerSegment.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/Tile.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/TilePart.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/TopLevelBoxHolder.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/UUIDBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/UUIDInfoBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/UUIDListBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/XMLBox.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/build.xml create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/package.html create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/package.html create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/AProfile.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/AProfileLevelA.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/ArrayEnd.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/ArrayStart.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Ascii85FilterStream.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/AsciiHexFilterStream.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Comment.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/CrossRefStream.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Destination.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/DictionaryEnd.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/DictionaryStart.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/DocNode.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/FileSpecification.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/FileTokenizer.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Filter.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Hexadecimal.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Keyword.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/LinearizedProfile.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Literal.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Name.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/NameTreeNode.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Numeric.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/ObjectStream.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PageLabelNode.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PageObject.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PageTreeNode.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Parser.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfArray.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfDictionary.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfException.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfFlateInputStream.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfIndirectObj.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfInvalidException.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfMalformedException.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfObject.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfProfile.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfSimpleObject.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfStream.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfStrings.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfXMPSource.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/RunLengthFilterStream.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/State.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/StdStructTypes.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Stream.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/StreamInputStream.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/StreamTokenizer.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/StringValuedToken.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/StructureElement.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/StructureTree.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/TaggedProfile.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Token.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Tokenizer.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/X1Profile.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/X1aProfile.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/X2Profile.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/X3Profile.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/XProfileBase.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/build.xml create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/package.html create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/ExifIFD.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/GPSInfoIFD.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/GeoTiffStrings.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/GlobalParametersIFD.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/IFD.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/InteroperabilityIFD.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffException.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffFXBase.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffIFD.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfile.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassB.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassF.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassG.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassIT.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITBL.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITBLP1.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITBP.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITBPP1.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITBPP2.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITCT.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITCTP1.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITCTP2.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITFP.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITFPP1.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITFPP2.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITHC.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITHCP1.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITHCP2.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITLW.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITLWP1.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITLWP2.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITMP.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITMPP1.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITMPP2.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITSD.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITSDP2.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassP.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassR.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassY.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileDLF.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileDLFBW.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileDLFColor.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileDLFGray.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileDNG.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileDNGThumb.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileEP.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileExif.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileExifIFD.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileExifThumb.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileFXC.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileFXF.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileFXJ.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileFXL.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileFXM.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileFXS.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileGeoTIFF.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfilePagemaker6.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfilePhotoshop.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileRFC1314.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffTags.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/build.xml create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/package.html create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/wave/AXMLChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/wave/AssocDataListChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/wave/BroadcastExtChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/wave/CartChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/wave/CueChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/wave/DataChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/wave/ExifInfo.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/wave/ExifMakerNoteChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/wave/ExifStringChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/wave/ExifUserCommentChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/wave/ExifVersionChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/wave/FactChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/wave/FormatChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/wave/InstrumentChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/wave/LabelChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/wave/LabeledTextChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/wave/LinkChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/wave/LinkChunkHandler.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/wave/ListInfoChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/wave/ListInfoTextChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/wave/MpegChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/wave/NoteChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/wave/PeakEnvelopeChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/wave/SampleChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/wave/SimpleTextChunk.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/wave/WaveStrings.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/wave/build.xml create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/wave/package.html create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/xml/EntityInfo.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/xml/ProcessingInstructionInfo.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/xml/SchemaInfo.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/xml/XhtmlProcessing.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/xml/XmlDeclHandler.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/xml/XmlDeclStream.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/xml/XmlLexicalHandler.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/xml/XmlModuleHandler.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/xml/build.xml create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/module/xml/package.html create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/package.html create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/viewer/AppInfoWindow.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/viewer/ConfigWindow.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/viewer/InfoWindow.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/viewer/JhoveWindow.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/viewer/MainScreen.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/viewer/ModuleInfoWindow.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/viewer/NoConfAlert.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/viewer/NumericField.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/viewer/PrefsWindow.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/viewer/ProgressWindow.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/viewer/RepTreeRoot.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/viewer/ViewHandler.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/viewer/ViewWindow.java create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/viewer/jhove-logo.gif create mode 100644 jhove/classes/edu/harvard/hul/ois/jhove/viewer/package.html create mode 100644 jhove/classes/overview.html create mode 100644 jhove/conf/README create mode 100644 jhove/conf/jhove-byteoffset=true.conf create mode 100644 jhove/conf/jhove-withTextMD.conf create mode 100644 jhove/conf/jhove.conf create mode 100644 jhove/conf/jhove.conf.old create mode 100644 jhove/configure.pl create mode 100644 jhove/doc/allclasses-frame.html create mode 100644 jhove/doc/allclasses-noframe.html create mode 100644 jhove/doc/constant-values.html create mode 100644 jhove/doc/deprecated-list.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/AESAudioMetadata.Face.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/AESAudioMetadata.FaceRegion.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/AESAudioMetadata.FormatRegion.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/AESAudioMetadata.TimeDesc.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/AESAudioMetadata.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/Agent.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/AgentType.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/AnalogDigitalFlagType.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/App.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/ByteArrayXMPSource.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/Callback.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/Checksum.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/ChecksumInputStream.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/ChecksumType.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/Checksummer.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/ConfigHandler.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/ConfigWriter.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/CountedInputStream.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/DefaultConfigurationBuilder.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/Document.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/DocumentType.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/Dump.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/EnumerationType.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/ErrorMessage.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/ExternalSignature.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/HandlerBase.SynchronizedDateFormat.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/HandlerBase.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/Identifier.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/IdentifierType.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/InfoMessage.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/InternalSignature.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/JhoveBase.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/JhoveException.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/MacStuff.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/Message.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/Module.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/ModuleBase.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/ModuleInfo.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/NisoImageMetadata.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/ObjectIdentifier.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/OutputHandler.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/Property.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/PropertyArity.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/PropertyPath.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/PropertyType.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/RAFInputStream.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/RFC1766Lang.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/Rational.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/RepInfo.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/Signature.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/SignatureType.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/SignatureUseType.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/TextMDMetadata.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/Utf8BlockMarker.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/XMLWrapperStream.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/XMPHandler.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/XMPSource.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/handler/AuditHandler.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/handler/TextHandler.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/handler/XmlHandler.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/handler/audit/AuditCount.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/handler/audit/AuditState.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/handler/audit/package-frame.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/handler/audit/package-summary.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/handler/audit/package-tree.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/handler/package-frame.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/handler/package-summary.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/handler/package-tree.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/AiffModule.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/AsciiModule.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/BytestreamModule.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/GifModule.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/HtmlModule.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/Jpeg2000Module.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/JpegModule.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/PdfModule.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/TiffException.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/TiffModule.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/Utf8Block.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/Utf8Module.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/WaveModule.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/XmlModule.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/aiff/AiffStrings.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/aiff/AnnotationChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/aiff/ApplicationChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/aiff/AudioRecChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/aiff/AuthorChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/aiff/CommentsChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/aiff/CommonChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/aiff/CopyrightChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/aiff/ExtDouble.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/aiff/FormatVersionChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/aiff/InstrumentChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/aiff/MarkerChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/aiff/MidiChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/aiff/NameChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/aiff/SaxelChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/aiff/SoundDataChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/aiff/TextChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/aiff/package-frame.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/aiff/package-summary.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/aiff/package-tree.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/gif/GifStrings.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/gif/package-frame.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/gif/package-summary.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/gif/package-tree.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/CharStream.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/DTDMapper.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/Html3_2DocDesc.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/Html4DocDesc.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/Html4StrictDocDesc.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/Html4TFDocDesc.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/Html4_01FrameDocDesc.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/Html4_01StrictDocDesc.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/Html4_01TFDocDesc.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/Html4_01TransDocDesc.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/Html4_0FrameDocDesc.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/Html4_0StrictDocDesc.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/Html4_0TFDocDesc.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/Html4_0TransDocDesc.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/HtmlAttributeDesc.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/HtmlCharStream.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/HtmlDocDesc.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/HtmlMetadata.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/HtmlSpecialToken.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/HtmlStack.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/HtmlTagDesc.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/HtmlTempTagDesc.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/JHAttribute.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/JHCloseTag.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/JHComment.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/JHDoctype.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/JHElement.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/JHErrorElement.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/JHOpenTag.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/JHPCData.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/JHXmlDecl.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/ParseException.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/ParseHtml.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/ParseHtmlConstants.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/ParseHtmlTokenManager.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/SimpleCharStream.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/Token.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/TokenMgrError.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/package-frame.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/package-summary.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/html/package-tree.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/iff/Chunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/iff/ChunkHeader.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/iff/Superchunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/iff/package-frame.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/iff/package-summary.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/iff/package-tree.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg/ArithConditioning.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg/JpegExif.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg/JpegStrings.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg/QuantizationTable.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg/SRS.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg/Spiff.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg/SpiffDir.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg/Tiling.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg/package-frame.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg/package-summary.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg/package-tree.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/AssociationBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/BPCCBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/BinaryFilterBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/BoxHeader.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/BoxHolder.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/COCMarkerSegment.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/CODMarkerSegment.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/CRGMarkerSegment.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/CaptureResolutionBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/ChannelDefBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/Codestream.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/CodestreamHeaderBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/CodestreamRegBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/ColorGroupBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/ColorSpecBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/CommentMarkerSegment.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/CompOptionsBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/ComponentMapBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/ComposLayerHdrBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/CompositionBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/ContCodestream.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/ContCodestreamBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/CrossRefBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/DDResolutionBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/DataEntryURLBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/DefaultBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/DefaultMarkerSegment.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/DesiredReproBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/DigSignatureBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/FileTypeBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/FragmentInputStream.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/FragmentListBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/FragmentTableBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/GTSOBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/IPRBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/ImageHeaderBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/InstructionSetBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/JP2Box.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/JP2HeaderBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/JP2Strings.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/LabelBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/MainOrTile.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/Marker.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/MarkerSegment.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/NumberListBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/OpacityBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/PLMMarkerSegment.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/PLTMarkerSegment.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/POCMarkerSegment.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/PPMMarkerSegment.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/PPTMarkerSegment.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/PaletteBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/QCCMarkerSegment.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/QCDMarkerSegment.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/RGNMarkerSegment.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/ROIBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/ReaderRequirementsBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/ResolutionBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/SIZMarkerSegment.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/SOTMarkerSegment.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/TLMMarkerSegment.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/Tile.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/TilePart.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/TopLevelBoxHolder.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/UUIDBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/UUIDInfoBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/UUIDListBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/XMLBox.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/package-frame.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/package-summary.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/jpeg2000/package-tree.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/package-frame.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/package-summary.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/package-tree.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/AProfile.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/AProfileLevelA.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/ArrayEnd.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/ArrayStart.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/Ascii85FilterStream.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/AsciiHexFilterStream.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/Comment.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/CrossRefStream.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/Destination.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/DictionaryEnd.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/DictionaryStart.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/DocNode.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/FileSpecification.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/FileTokenizer.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/Filter.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/Hexadecimal.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/Keyword.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/LinearizedProfile.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/Literal.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/Name.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/NameTreeNode.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/Numeric.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/ObjectStream.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/PageLabelNode.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/PageObject.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/PageTreeNode.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/Parser.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/PdfArray.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/PdfDictionary.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/PdfException.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/PdfFlateInputStream.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/PdfIndirectObj.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/PdfInvalidException.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/PdfMalformedException.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/PdfObject.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/PdfProfile.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/PdfSimpleObject.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/PdfStream.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/PdfStrings.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/PdfXMPSource.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/RunLengthFilterStream.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/State.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/StdStructTypes.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/Stream.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/StreamInputStream.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/StreamTokenizer.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/StringValuedToken.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/StructureElement.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/StructureTree.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/TaggedProfile.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/Token.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/Tokenizer.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/X1Profile.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/X1aProfile.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/X2Profile.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/X3Profile.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/XProfileBase.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/package-frame.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/package-summary.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/pdf/package-tree.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/DirectoryProperty.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/ExifIFD.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/GPSInfoIFD.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/GeoTiffStrings.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/GeoTiffTags.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/GlobalParametersIFD.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/IFD.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/InteroperabilityIFD.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/StandardIFDProperty.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TagFamily.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffException.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffFXBase.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffIFD.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfile.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassB.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassF.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassG.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassIT.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITBL.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITBLP1.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITBP.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITBPP1.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITBPP2.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITCT.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITCTP1.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITCTP2.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITFP.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITFPP1.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITFPP2.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITHC.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITHCP1.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITHCP2.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITLW.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITLWP1.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITLWP2.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITMP.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITMPP1.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITMPP2.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITSD.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITSDP2.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassP.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassR.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassY.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileDLF.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileDLFBW.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileDLFColor.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileDLFGray.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileDNG.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileDNGThumb.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileEP.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileExif.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileExifIFD.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileExifThumb.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileFXC.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileFXF.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileFXJ.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileFXL.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileFXM.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileFXS.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileGeoTIFF.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfilePagemaker6.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfilePhotoshop.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileRFC1314.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/TiffTags.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/package-frame.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/package-summary.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/tiff/package-tree.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/wave/AXMLChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/wave/AssocDataListChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/wave/BroadcastExtChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/wave/CartChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/wave/CueChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/wave/DataChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/wave/ExifInfo.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/wave/ExifMakerNoteChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/wave/ExifStringChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/wave/ExifUserCommentChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/wave/ExifVersionChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/wave/FactChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/wave/FormatChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/wave/InstrumentChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/wave/LabelChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/wave/LabeledTextChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/wave/LinkChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/wave/LinkChunkHandler.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/wave/ListInfoChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/wave/ListInfoTextChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/wave/MpegChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/wave/NoteChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/wave/PeakEnvelopeChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/wave/SampleChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/wave/SimpleTextChunk.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/wave/WaveStrings.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/wave/package-frame.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/wave/package-summary.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/wave/package-tree.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/xml/EntityInfo.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/xml/ProcessingInstructionInfo.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/xml/SchemaInfo.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/xml/XhtmlProcessing.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/xml/XmlDeclHandler.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/xml/XmlDeclStream.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/xml/XmlLexicalHandler.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/xml/XmlModuleHandler.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/xml/package-frame.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/xml/package-summary.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/module/xml/package-tree.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/package-frame.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/package-summary.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/package-tree.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/viewer/AppInfoWindow.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/viewer/ConfigWindow.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/viewer/ConfigWriter.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/viewer/InfoWindow.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/viewer/JhoveWindow.InvisibleFilenameFilter.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/viewer/JhoveWindow.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/viewer/LeafHolder.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/viewer/MainScreen.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/viewer/ModuleInfoWindow.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/viewer/NoConfAlert.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/viewer/NumericField.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/viewer/PrefsWindow.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/viewer/ProgressWindow.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/viewer/RepTreeRoot.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/viewer/ViewCellRenderer.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/viewer/ViewHandler.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/viewer/ViewWindow.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/viewer/package-frame.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/viewer/package-summary.html create mode 100644 jhove/doc/edu/harvard/hul/ois/jhove/viewer/package-tree.html create mode 100644 jhove/doc/help-doc.html create mode 100644 jhove/doc/index-all.html create mode 100644 jhove/doc/index.html create mode 100644 jhove/doc/overview-frame.html create mode 100644 jhove/doc/overview-summary.html create mode 100644 jhove/doc/overview-tree.html create mode 100644 jhove/doc/package-list create mode 100644 jhove/doc/packages.html create mode 100644 jhove/doc/resources/inherit.gif create mode 100644 jhove/doc/serialized-form.html create mode 100644 jhove/doc/stylesheet.css create mode 100644 jhove/extramodules/it/eng/jhove/module/odf/JingDriver.java create mode 100644 jhove/extramodules/it/eng/jhove/module/odf/ManifestHandler.java create mode 100644 jhove/extramodules/it/eng/jhove/module/odf/MetaHandler.java create mode 100644 jhove/extramodules/it/eng/jhove/module/odf/OdfModule.java create mode 100644 jhove/extramodules/it/eng/jhove/module/png/PngModule.java create mode 100644 jhove/gdump create mode 100644 jhove/gdump.bat create mode 100644 jhove/j2dump create mode 100644 jhove/j2dump.bat create mode 100644 jhove/jdump create mode 100644 jhove/jdump.bat create mode 100644 jhove/jhove create mode 100644 jhove/jhove.bat create mode 100644 jhove/jhove.tmpl create mode 100644 jhove/jhove_bat.tmpl create mode 100644 jhove/lib/OdfModule.jar create mode 100644 jhove/lib/PngModule.jar create mode 100644 jhove/md5.pl create mode 100644 jhove/packagejhove.sh create mode 100644 jhove/pdump create mode 100644 jhove/pdump.bat create mode 100644 jhove/test/edu/harvard/hul/ois/jhove/DefaultConfigurationBuilderTest.java create mode 100644 jhove/userhome create mode 100644 jhove/userhome.bat diff --git a/OCRmyPDF.sh b/OCRmyPDF.sh index b7e8d420..a6f29956 100644 --- a/OCRmyPDF.sh +++ b/OCRmyPDF.sh @@ -69,6 +69,7 @@ EXIT_OTHER_ERROR="5" LOG_ERR="0" # 0=only error messages LOG_INFO="1" # 1=error messages and some infos LOG_DEBUG="2" # 2=debug level logging +JHOVE="./jhove/bin/JhoveApp.jar" # java SW for validating the final PDF/A # Initialization the configuration parameters with default values VERBOSITY="$LOG_ERR" # default verbosity level @@ -308,7 +309,7 @@ done < "$FILE_SIZE_PAGES" # validate generated pdf file (compliance to PDF/A) [ $VERBOSITY -ge $LOG_DEBUG ] && echo "Output file: Checking compliance to PDF/A standard" -java -jar /root/jhove-1_9/jhove/bin/JhoveApp.jar -m PDF-hul "$FILE_OUTPUT_PDFA" > "$FILE_VALIDATION_LOG" +java -jar "$JHOVE" -m PDF-hul "$FILE_OUTPUT_PDFA" > "$FILE_VALIDATION_LOG" grep -i "Status|Message" "$FILE_VALIDATION_LOG" # summary of the validation [ $VERBOSITY -ge $LOG_DEBUG ] && cat "$FILE_VALIDATION_LOG" # check the validation results diff --git a/jhove/COPYING b/jhove/COPYING new file mode 100644 index 00000000..fa79db0a --- /dev/null +++ b/jhove/COPYING @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/jhove/LICENSE b/jhove/LICENSE new file mode 100644 index 00000000..89b8f920 --- /dev/null +++ b/jhove/LICENSE @@ -0,0 +1,17 @@ +JHOVE - JSTOR/Harvard Object Validation Environment +Copyright 2003-2008 by JSTOR and the President and Fellows of Harvard College + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lessor General Public License as +published by the Free Software Foundation; either version 2.1 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +USA diff --git a/jhove/README b/jhove/README new file mode 100644 index 00000000..0ccdee06 --- /dev/null +++ b/jhove/README @@ -0,0 +1,227 @@ +JHOVE - JSTOR/Harvard Object Validation Environment +Copyright 2003-2012 by JSTOR and the President and Fellows of Harvard College +JHOVE is made available under the GNU Lesser General Public License (LGPL; +see the file LICENSE for details) + +Rev. 1.9, 2012-12-17 + +JHOVE (the JSTOR/Harvard Object Validation Environment, pronounced "jhove") +is an extensible software framework for performing format identification, +validation, and characterization of digital objects. + +o Format identification is the process of determining the format to which a + digital object conforms: "I have a digital object; what format is it?" +o Format validation is the process of determining the level of compliance of a + digital object to the specification for its purported format: "I have an + object purportedly of format F; is it?" +o Format characterization is the process of determing the format-specific + significant properties of an object of a given format: "I have an object of + format F; what are its salient properties?" + +These actions are frequently necessary during routine operation of digital +repositories and for digital preservation activities. + +The output from JHOVE is controlled by output handlers. JHOVE uses an +extensible plug-in architecture; it can be configured at the time of its +invocation to include whatever specific format modules and output handlers +that are desired. The initial release of JHOVE includes modules for +arbitrary byte streams, ASCII and UTF-8 encoded text, AIFF and WAVE audio, +GIF, JPEG, JPEG 2000, TIFF, and PDF; and text and XML output handlers. + +The JHOVE project is a collaboration of JSTOR and the Harvard University +Library. Development of JHOVE was funded in part by the Andrew W. Mellon +Foundation. JHOVE is made available under the GNU Lesser General Public +License (LGPL; see the file LICENSE for details). + +JHOVE is currently being maintained by indpendent developers. + +REQUIREMENTS + +1. Java J2SE 1.5 +(JHOVE was originally implemented using the Sun J2SE SDK 1.4.1 and has +been tested to work with 1.5) + +2. If you would like to compile the JHOVE source code, then +Apache Ant, a Java-based build tool is necessary. +Note that the JAVA_HOME environment variable must be set appropriately for +Ant to work properly. +(JHOVE was implemented and tested using Ant 1.5.1.) + +DISTRIBUTION + +The JHOVE distribution package includes: + + jhove/ # JHOVE home directory + COPYING # GNU Lesser General Public License + LICENSE # JHOVE license information + README + RELEASENOTES # JHOVE release notes + bin/ + jhove.jar # JHOVE API package + jhove-handler.jar # Standard output handler package + jhove-module.jar # Standard module package + JhoveApp.jar # JHOVE command line application + JhoveView.jar # JHOVE with Swing GUI front-end + build.xml # Ant configuration file + classes/ + build.xml # Ant configuration file + edu/ ... # JHOVE API packages + ADump.* # AIFF dump utility class + GDump.* # GIF dump utility class + Jhove.* # JHOVE main class + JDump.* # JPEG dump utility class + J2Dump.* # JPEG 2000 dump utility class + PDump.* # PDF dump utility class + TDump.* # TIFF dump utility class + UserHome.* # user.home property utility class + WDump.* # WAVE dump utility class + conf/ + jhove.conf # JHOVE configuration file + jhove.xsd # JHOVE output schema + jhoveConfig.xsd # JHOVE configuration file schema + doc/ + *.html # API documentation + ... + examples/ # Sample files + ascii/ ... + gif/ ... + jpeg/ ... + jpeg2000/ ... + pdf/ ... + tiff/ ... + utf-8/ ... + adump* # AIFF dump Bourne shell driver + adump.bat* # AIFF dump DOS shell driver script + gdump* # GIF dump Bourne shell driver + gdump.bat* # GIF dump DOS shell driver script + jdump* # JPEG dump Bourne shell driver + jdump.bat* # JPEG dump DOS shell driver script + j2dump* # JPEG 2000 dump Bourne shell driver + j2dump.bat* # JPEG 2000 dump DOS shell driver + jhove.tmpl* # Template for JHOVE Bourne shell driver script + jhove_bat.tmpl* # Template for JHOVE DOS shell driver script + pdump* # PDF dump Bourne shell driver + pdump.bat* # PDF dump DOS shell driver script + tdump* # TIFF dump Bourne shell driver + tdump.bat* # TIFF dump DOS shell driver script + userhome* # user.home Bourne shell driver + userhome.bat* # user.home DOS shell driver script + wdump* # WAVE dump Bourne shell driver + wdump.bat* # WAVE dump DOS shell driver script + +INSTALLATION + +Edit the configuration file, jhove/conf/jhove.conf, and set the absolute +pathname of the JHOVE home directory and the temporary directory (in which +temporary files are created): + + jhove-home-directory + temporary-directory + +The JHOVE home directory is the top-most directory in the distribution TAR +or ZIP file. On Unix systems, "/var/tmp" is an appropriate temporary +directory; on Windows, "C:\Temp". For example, if the distribution TAR +file is disaggregated on a Unix system in the directory "/users/stephen/ +projects", then the configuration file should read: + + /users/stephen/projects/jhove + /var/tmp + +In the JHOVE home directory, copy the JHOVE Bourne shell driver script +template, "jhove.tmpl", to "jhove" (or the equivalent Windows shell +script, "jhove_bat.tmpl" to "jhove.bat"), and set the +JHOVE home directory, Java home directory, and Java interpreter: + + JHOVE_HOME=jhove-home-directory + JAVA_HOME=java-home-directory + JAVA=java-interpreter + +The JAVA_HOME property should provide the absolute pathname of the Java +runtime or SDK installation; JAVA should provide the absolute pathname of the +Java interpreter. For example: + + JHOVE_HOME=/users/stephen/projects/jhove + JAVA_HOME=/usr/local/j2re1.4.1_02 + JAVA=$JAVA_HOME/bin/java + +In the DOS shell driver script, jhove.bat, the equivalent three +variables are: + + SET JHOVE_HOME=jhove-home-directory + SET JAVA_HOME=java-home-directory + SET JAVA=%JAVA_HOME%\bin\java + +For example: + + SET JHOVE_HOME="C:\Program Files\jhove" + SET JAVA_HOME="C:\Program Files\java\j2re1.4.1_02" + SET JAVA=%JAVA_HOME%\bin\java + +The quotation marks are necessary because of the embedded space characters. +On Windows platforms it may also be necessary to add the Java bin subdirectory +to the System PATH environment variable: + + PATH=C:\Program Files\java\j2re1.4.1_02\bin;... + +(For information on setting a Windows environment variable, consult your local +documentation or system administrator.) + +USAGE + + java Jhove [-c config] [-m module] [-h handler] [-e encoding] [-H handler] + [-o output] [-x saxclass] [-t tempdir] [-b bufsize] + [-l loglevel] [[-krs] dir-file-or-uri [...]] + +where -c config Configuration file pathname + -m module Module name + -h handler Output handler name (defaults to TEXT) + -e encoding Character encoding used by output handler (defaults to UTF-8) + -H handler About handler name + -o output Output file pathname (defaults to standard output) + -x saxclass SAX parser class (defaults to J2SE default) + -t tempdir Temporary directory in which to create temporary files + -b bufsize Buffer size for buffered I/O (defaults to J2SE 1.4 default) + -l loglevel Logging level + -k Calculate CRC32, MD5, and SHA-1 checksums + -r Display raw data flags, not textual equivalents + -s Format identification based on internal signatures only + dir-file-or-uri Directory or file pathname or URI of formated content + stream + +All named modules and output handlers must be found on the Java CLASSPATH at +the time of invocation. The JHOVE driver script, jhove/jhove, automatically +sets the CLASSPATH and invokes the Jhove main class: + + jhove [-c config] [-m module] [-h handler] [-e encoding] [-H handler] + [-o output] [-x saxclass] [-t tempdir] [-b bufsize] [-l loglevel] + [[-krs] dir-file-or-uri [...]] + +The following additional programs are available, primarily for testing +and debugging purposes. They display a minimally processed, human-readable +version of the contents of AIFF, GIF, JPEG, JPEG 2000, PDF, TIFF, and WAVE +files: + + java ADump aiff-file + java GDump gif-file + java JDump jpeg-file + java J2Dump jpeg2000-file + java PDump pdf-file + java TDump tiff-file + java WDump wave-file + +For convenience, the following driver scripts are also available: + + adump aiff-file + gdump gif-file + jdump jpeg-file + j2dump jpeg2000-file + pdump pdf-file + tdump tiff-file + wdump wave-file + +The JHOVE Swing-based GUI interface can be invoked from a command shell from +the jhove/bin sub-directory: + + java -jar JhoveView.jar -c + +where is the pathname of the JHOVE configuration file. diff --git a/jhove/RELEASENOTES b/jhove/RELEASENOTES new file mode 100644 index 00000000..bda2cec5 --- /dev/null +++ b/jhove/RELEASENOTES @@ -0,0 +1,1676 @@ +RELEASENOTES +JHOVE - JSTOR/Harvard Object Validation Environment +Copyright 2003-2009 by JSTOR and the President and Fellows of Harvard College +JHOVE is made available under the GNU Lesser General Public License (LGPL; +see the file LICENSE for details) + +Versions 1.7 and beyond of JHOVE are no longer under the control of Harvard. + +RELEASE NOTES FOR JHOVE 1.9 + +GENERAL + +1. Jhove.java and JhoveView.java now get their version information from + JhoveBase.java. Before it was redundantly kept in three places, and + sometimes they didn't all get updated for a new release. Like in 1.8. + +2. ConfigWriter was in the package edu.harvard.hul.ois.jhove.viewer, which + caused a NoClassDefFoundError if non-GUI configurations didn't include + JhoveViewer.jar in the classpath. It's been moved to + edu.harvard.hul.ois.jhove. + +3. Added script packagejhove.sh and made md5.pl part of the CVS repository + to make packaging for delivery easier. + +4. jhove.bat now simply uses the Java command rather than requiring + the user to set up the Java path. + +5. JhoveView.jar and jhove (the top level shell script) are now forced + by ant to be executable so there are no mistakes. + +6. Warning message given on invalid buffer size string, and minimum + buffer size is 1024. + +7. Configuration file code for adding handlers and giving init strings + to modules was an awful mess that never could have worked. + Major repairs done. + +AIFF MODULE + +1. If an AIFF file was found to be little-endian, the module instance + would stay in little-endian mode for all subsequent files. This + has been fixed. + +TIFF MODULE + +1. TIFF files that had strip or tile offsets but no corresponding byte + counts were throwing an exception all the way to the top level. Now + they're correctly being reported as invalid. + +XML MODULE + +1. Cleaned up reporting of schemas, Added some small classes to replace + the use of string arrays for information structures. Made URI comparison + for local schema parameter case-independent. Resolved conflict between + "s" and "schema" parameters. + +WAVE MODULE + +1. Some uncaught exceptions caused the module to throw all the way + back to JhoveBase and not report any result for certain defective + files. These now report the file as not well-formed. + + +RELEASE NOTES FOR JHOVE 1.8 + +GENERAL + +1. If JHOVE doesn't find a configuration file, it creates a default one. + +2. Generics widely added to clean up the code. + +3. build.xml files fixed to force compilation to Java 1.5. + +4. Shell script "jhove" no longer makes you figure out where JAVA_HOME is. + +PDF MODULE + +1. Several errors in checking for PDF-A compliance were corrected. Aside from + fixing some outright bugs, the Contents key for non-text Annotations is + no longer checked, as its presence is only recommended and not required. + +2. Improved code by HÃ¥kan Svenson is now used for finding the trailer. + +TIFF MODULE + +1. TIFF tag 700 (XMP) now accepts field type 7 (UNDEFINED) as well as 1 + (BYTE), on the basis of Adobe's XMP spec, part 3. + +2. If compression scheme 6 is used in a file, an InfoMessage will report + that the file uses deprecated compression. + +WAVE MODULE + +1. The Originator Reference property, found in the Broadcast Wave Extension + (BEXT) chunk, is now reported. + +RELEASE NOTES FOR JHOVE 1.7 +2012-08-12 + +GENERAL + +1. JHOVE 1.7, as well as future releases unless noted otherwise, is + released independently of Harvard under the GNU General Public License. + +2. JHOVE now will tell you where it was looking for the config file if it + can't open it. This should help debug configuration problems. + +XML HANDLER + +1. Changes to XmlHandler.java and NisoImageMetadata.java to correct invalid MIX + 2.0 XML output in the value of grayResponseUnit. It was previously writing + integers (as in 1.0) rather than the expected enumerated strings. + +PDF MODULE + +1. A situation that caused an infinite loop and eventual memory exhaustion + processing in some PDF files with malformed literals has been fixed. + +RELEASE NOTES FOR JHOVE 1.6 +2011-01-03 + +XML HANDLER AND TEXT HANDLER + +1. The default version of MIX is now 2.0. In earlier versions it was 0.2. + However, MIX 2.0 still isn't supported in the text handler, so it will + produce 1.0 output by default. The XML handler will produce MIX 2.0 + output. + +TIFF MODULE + +1. JHOVE returned a \"String index out of range: 4\" exceptions during + TIFF validation for a tiff contains an empty (not NULL) date/time + field. This has been corrected so that a date/time field with + the wrong length won't be parsed but will report an error instead. + +2. If text tags contain characters which aren't printable ASCII, these + are now output as escape sequences so that invalid XML isn't + output. + +UTF-8 MODULE + +1. Updated to Unicode 6.0.0. + +RELEASE NOTES FOR JHOVE 1.5 +2009-12-17 + +PDF MODULE + +1. An ArrayIndexOutOfBoundsException was thrown on a PDF with an invalid + object number in the cross-reference stream. In JHOVE 1.5, this is + correctly reported as a violation of well-formedness. + +UTF-8 MODULE + +1. With some very simple UTF-8 files, JHOVE handlers would throw an exception + processing them, and the GUI would fail silently. This happened with files + using no UTF-8 blocks. This has been fixed. + +TEXTMD (multiple modules) + +1. TextMD metadata can now optionally be reported. To get this, it's + necessary to edit jhove.conf. TextMD can be enabled on a per-module + basis for HtmlModule, AsciiModule, Utf8Module, and XmlModule. + The element for each chosen module must contain the element + withtextmd=true (no spaces). + +2. The TextMD feature was added by Thomas Ledoux. + + + +RELEASE NOTES FOR JHOVE 1.4 +2009-07-30 + +PDF MODULE + + 1. The PDF/A profile has been updated to the final version of + 19005-1:2005(E) and made more thorough. Among the changes: + + a. The set-state and no-op actions disqualify a PDF/A candidate. + + b. The ASCIIHexDecode and ASCII85Decode filters no longer + disqualify a candidate. + + c. Checking of outlines has been added. + + d. Additional checking of Type 1 fonts and symbolic fonts. + + e. Bug fix in checking type 2 subfonts. + + f. An LZW filter in an image object disqualifies a candidate. + + g. The xpacket processing instruction is checked for attributes + which disqualify from PDF/A. + + h. Conformity to implementation limits is checked as a condition + of PDF/A conformity. + +JPEG2000 MODULE + + 1. The pathological case of an image with no components is checked so + it won't cause a crash. + +XML HANDLER + + 1. A reset() function has been added so that if the handler is reused, + it will return to a valid initial state. + +RELEASE NOTES FOR JHOVE 1.3 +2009-06-04 + +GENERAL + + 1. The build.xml files now force compilation to Java 1.4, preventing + accidental distributions that aren't 1.4-compatible. + 2. Spaces are allowed in file paths on Windows, if the path is + enclosed in quotes. This fix had been in version 1.1i, and had been + lost since then. + +PDF MODULE + + 1. According to the PDF 1.6 specification, table 3.4, parameters for a + stream filter can be either a dictionary or the null object. The null + object was treated as an error; it is now allowed. + 2. Object stream handling was seriously buggy, causing rejection of + well-formed and valid files; it's better now. + 3. In PDF 1.4, an outline dictionary unconditionally must have a "First" + and a "Last" entry. JHOVE follows this requirement, declaring a file + invalid if it isn't met. However, PDF 1.6 relaxes the requirement, + applying it only "if there are any open or closed outline entries." + Thus, an empty outline dictionary with no "First" or "Last" entry + is valid. It is now accepted (for all PDF versions). + 4. If a page number tree in a PDF file is missing an expected "Nums" + entry, this was being reported as an invalid date. A more appropriate + error message is now given. + +TIFF MODULE + + 1. TIFF tag 33723 (IPTC-NAA) was considered valid only if the data + type is ASCII or LONG. But according to Aware Systems, the valid + types are UNDEFINED and BYTE. All four types are now accepted. + +XML HANDLER + + 1. Omissions in MIX 1.0 and 2.0 output have been fixed. + +RELEASE NOTES FOR JHOVE 1.2 +2009-02-10 + +GENERAL + + 1. A bug has been fixed in CountedInputStream, which could potentially + have caused infinite recursion in some modules. + +HTML MODULE + + 1. An incompatibility with Java 1.6 has been fixed. + +PDF MODULE + + 1. A null pointer exception would be thrown for PDF documents without a + document root tree. This has been fixed. + 2. A source of possible false positives in PDF profiles has been fixed. + 3. Certain checks weren't being done to Type 2 fonts, and some PDF/A + profile violations might have been missed as a result. This has + been fixed. + +WAVE MODULE + + 1. Sub-chunks of the 'adtl' chunk are now constrained to even byte + boundaries. + +XML HANDLER + + 1. MIX 2.0 is now supported. + 2. The URL for the MIX 0.2 schema has changed to reflect the change + on the LOC MIX site. + 3. The handler was sometimes incorrectly reporting whether the + AESAudioMetadata property had an empty value or not. This has + been fixed. + + +RELEASE NOTES FOR JHOVE 1.1 +Rev. 2008-02-22 + +COMMAND-LINE INTERFACE + + 1. Allow filenames with internal spaces if they are quoted on the + command line. + 2. Corrected error setting the Classpath in the Windows Shell script + (jhove.bat) + 3. Corrected error opening the configuration file using the default + GCJ parser in the GNU Java Runtime Environment. + +GUI (SWING) INTERFACE (JHOVE VIEW) + + 1. AES metadata properties displayed in the RepInfo window rearranged + slightly to make their ordering consistent with the Text and XML + handlers. + 2. The JhoveView.main() method will now accept a "-c configFile" option + on the command line. The GUI interface can now be invoked by: + + java -jar bin/JhoveView.jar -c configFile + + 3. Corrected error opening the configuration file using the default + GCJ parser in the GNU Java Runtime Environment. + 4. Correct recurrent problems with reading the configuration file on + Windows installations. + +AIFF MODULE + + 1. Correct value for first sample offset by included non-zero offset + defined in the SSND chunk. + 2. Do not report bitrate reduction data for PCM data. + 3. All non-final instance fields and methods are protected, rather than + private. + +ASCII MODULE + + 1. A minimal file containing no line-end characters now does not + produce an empty ASCIIMetadata property, which is invalid against + the JHOVE schema. + 2. Zero-length files are considered not well-formed. + 3. Issue informative message if file contains no printable characters. + 4. All non-final instance fields and methods are protected, rather than + private. + +BYTESTREAM MODULE + + 1. All non-final instance fields and methods are protected, rather than + private. + +GIF MODULE + + 1. All non-final instance fields and methods are protected, rather than + private. + +HTML MODULE + + 1. The HTMLMetadata block in the module output is only produced if + there is at least one actual metadata property to report. + 2. All non-final instance fields and methods are protected, rather than + private. + +JPEG MODULE + + 1. The JPEG module reports the X and Y sampling frequency for files + meeting the JFIF profile. + 2. The JPEG module reports the pixel aspect ratio for JFIF profile + files for which it is defined. + 3. File handles were not being properly closed when processing embedded + EXIF metadata. In cases where JHOVE was invoked against large + numbers of objects this was causing a premature crash due to the + resource leak. + 4. All non-final instance fields and methods are protected, rather than + private. + 5. Correct parsing of the EXIF "subsecTimeOriginal" (37251) and + "subsecTimeDigitized" (37522) properties. + 6. Validation errors in embedded EXIF metdata were not being fully + reported. + +JPEG 2000 MODULE + + 1. All non-final instance fields and methods are protected, rather than + private. + 2. Files generated by the LuraWave codec are no longer incorrecly identified + as having unrecognized QCC marker segments. + +PDF MODULE + + 1. Date strings are now parsed with strict conformance to the ASN.1 + syntax. + 2. Destinations defined by indirect references to non-existent objects + are assumed to have the value "null". Files containing such + destinations are reported as "well-formed, but not valid". + 3. No attempt is made to display encrypted outline item title strings are + not displayed. + 4. Catch error if the Info key of the trailer dictionary is not an + indirect reference. + 5. Read entire page tree structure, regardless of its internal + organization. This error may have caused the under reporting of + page resources, such as fonts and images. + 6. The NISO Compression Scheme for all images using the CCITTFaxDecode + compression filter is now reported properly; previously, the scheme + was always reported as CCITT 1D even if the actual compression + algorithm was CCITT Group 3 or 4. + 7. Properly parse UTF-16 escape characters encoded in double-byte form. + 8. The module properly stops looking for the header comment after 1024 + bytes. + 9. All non-final instance fields and methods are protected, rather than + private. + 10. The number of incremental updates is now reported correctly, rather than + the total number of file trailers, which is one greater than the number + of updates. + 11. Only up to 1000 fonts will be reported. After that, an informative + message will be generated. The limit can be set using the parameter + "nxxxx" in the module-specific section of the configuration file: + + + edu.harvard.hul.ois.jhove.module.PdfModule + n2000 + + + 12. Subfonts of Type 0 are now being properly reported. + 13. PDF/A-1b profile is now being properly reported. + 14. Permit trailer info key to be optional. + 15. Additional correction for outline recursion. + 16. Fix treatment of indirect object of Actions. + 17. Correctly handle trailer dictionary without Info entry. + 18. Ignore comments within dictionaries. + +TIFF MODULE + + 1. Corrected error parsing pyramidal TIFF using the SubIFDs tag with a + type of IFD (13) rather than LONG (4). + 7. Correct parsing of the EXIF "subsecTimeOriginal" (37251) and + "subsecTimeDigitized" (37522) properties. + 2. All sub-IFDs of a pyramidal TIFF are now properly parsed. + 3. The EXIF GainControl tag (41991) is now correctly identified as + a SHORT, not a RATIONAL, value. + 4. Corrected error in which valid files were reported as being only + well-formed due to an incorrect parsing of the DateTime (306) tag. + 5. Byte-aligned offsets can be considered well-formed if the module + parameter "byteoffset=true" is set in the configuration file: + + + edu.harvard.hul.ois.jhove.module.TiffModule + byteoffset=true + + + 6. All non-final instance fields and methods are protected, rather than + private. + 7. Correct parsing of the EXIF "subsecTimeOriginal" (37251) and + "subsecTimeDigitized" (37522) properties. + 8. Using the "-s" option, the TIFF module was incorrectlly reporting + signature matches for text files starting with "II". + 9. Validation errors in embedded EXIF metdata were not being fully + reported. + +UTF8 MODULE + + 1. Corrected error under which malformed UTF-8 files containing encoding + sequences starting with a byte value in the range 0xF8 through 0xFF + were reported as well-formed and valid. + 2. Zero-length files are considered not well-formed. + 3. Issue informative message if file contains no printable characters. + 4. All non-final instance fields and methods are protected, rather than + private. + +WAVE MODULE + + 1. BWF files now set the correct start time in the AES metadata. + 2. All non-final instance fields and methods are protected, rather than + private. + 3. "cue " and "adtl" chunks are now properly read. + +XML MODULE + + 1. The DTD is assumed to be the first DOCTYPE system ID in the file with an + ".dtd" extension. + 2. All non-final instance fields and methods are protected, rather than + private. + 3. The module correctly handles schemaLocation attributes that do not + provide two whitespace-separated URIs. + +TEXT HANDLER + + 1. AES audio metadata properties rearranged slightly to make their + ordering consistent with the XML schema. + +XML HANDLER + + 1. Correct sample rate formatting in AES Time Code Format (TCF) + temporal references. + 2. Correct face IDREF in AES metadata. + 3. Disallowed control characters are removed from content. + 4. Null property values no longer generate empty elements. + 5. Image technical metadata can be reported in terms of the MIX 1.0 schema, + as opposed to the default reporting against MIX 0.2. To specify the + 1.0 schema include the directive: + + 1.0 + + if the configuration file. + +JHOVE API + + 1. The process() and processFile() methods of the JhoveBase class are now + public, to permit direct access to the API by applications. + 2. Checksum calculations now use buffered I/O uniformly for improved + performance. + 3. All non-final fields and methods in the JhoveBase class are + protected, rather than private. + 4. When invoked with the "-s" option JHOVE now reports the signature + matched format and MIME type. + 5. The processing of files in a directory is now performed in an + alphabetically sorted order. + +ADUMP UTILITY + + 1. Display the field values of known chunks. + +TDUMP UTILITY + + 1. New format that sorts all tag definitions by their byte offset and + also displays the byte ranges for image data. + 2. Command line flags permit the suppression of BYTE data display (-b) and + and subIFD parsing (-s). + +USERHOME UTILITY + + 1. A new utility program, UserHome, is available to determine the value + of the Java user.home property needed to know where to place the + configuration file. This utility can be invoked by the driver scripts + "userhome" (Bourne shell) or "userhome.bat" (Windows). + +************************************************************************ + +RELEASE NOTES FOR JHOVE 1.0 +Rev. 2005-05-26 + +GENERAL + + 1. Zero length files are now handled properly in all modules. + + 2. Missing start time in audio files is now handled property in all + audio modules. + + 3. Miscellaneous bug fixes, enhancements, and documentation updates. + +AIFF MODULE + + 1. Corrected error causing BitrateReduction to be incorrectly reported + for uncompressed PCM audio. + + +JPEG2000 MODULE + + 1. The module now validates the enumerated ICC profile types in the + Color Specification Box. In the JP2 profile, an unrecognized ICC + profile type marks the file as not well formed; in the JPX, the file + is merely not valid. + + 2. In the beta 3 release certain invalid JPEG 2000 files were + reported as well formed in the JP2 profile. This has been corrected. + +PDF MODULE + + 1. Following the practice of Acrobar, the PDF module will accept + the "%PDF-1.n" header comment anywhere in the first 1024 bytes of a + file (with appropriate notification via an information message), + rather than requiring that it start at byte offset 0. + + 2. The requirements for the PDF/A profile have been brought into + conformance with the most recent version of the PDF/A specification, + ISO/DIS 19005-1 of 2004-12-22. + + 3. Corrected bug that prevented valid PDF/X-1 files from being + recognized as such. + +WAVE MODULE + + 1. Corrected error causing BitrateReduction to be incorrectly reported + for uncompressed PCM audio. + +XML HANDLER + + 1. Dates reported for the NISO Z39.87 + element are now canonicalized to be in proper ISO 8601 form. + + 2. The NISO Z39.87 element is now + reported, if known. + +AUDIT HANDLER + + 1. The current working directory is reported as the "home" + attribute of the element and individual files are reported + as relative pathnames + +************************************************************************ + +RELEASE NOTES FOR JHOVE 1.0 (beta 3) +Rev. 2005-02-04 + +1 GENERAL + + 1. The architecture has been modified to simplify the use of JHOVE + with new "front ends." The new JhoveBase class is used in + conjunction with the App class to incorporate nearly all the + work of setting up a JHOVE instance. The main Jhove class and the App + class are now smaller than before. + + 2. Checksums were often being reported with incorrect values due to + an output formatting error that dropped zeroes. This has been fixed. + + 3. New utilities GDUMP and JDUMP created for GIF and JPEG documents. + + 4. Error messages are more consistently factored into submessages. + This allows messages indicating the same type of error to + be more readily grouped. + + 5. Some modules were reporting a MIME type for a document that is + not well-formed. This no longer occurs. + + 6. Duplicate reporting of AES BitDepth has been suppressed. + + 7. New module for HTML format. Be sure to update the configuration + file, jhove/conf/jhove.conf, to include the module: + + ... + + edu.harvard.hul.ois.jhove.module.HtmlModule + + ... + + 8. The AES audio metadata representation has been updated to + conform with schema version 1.02b (pre-release). + + 9. New property, sigMatches, has been added to RepInfo. This + records which module(s) regarded the signature of the document as a + match, even if the document was not well-formed. This is useful in + identifying broken documents that are reported as ASCII or Bytestream. + + 10. The logging API is supported, permitting the generation of + debugging messages. + + 11. All modules are now non-final, so that they can be subclassed by + adventurous users. + + 12. The -p and -P arguments of the command line are no longer + supported. Instead, the equivalent parameters can be + provided to all variants of JHOVE (including those which + don't take a command line) by specifying a element + within the element of the configuration file. + Example: + + + edu.harvard.hul.ois.jhove.module.PdfModule + a + f + p + + +2 JHOVE COMMAND-LINE INTERFACE + + 1. The JHOVE command-line interface can now accept directory names, + as well as file pathnames and URIs: + + java Jhove [-c config] [-m module] [-h handler] [-e encoding] + [-H handler] [-o output] [-x saxclass] [-t tempdir] + [-b bufsize] [-l loglevel] [[-krs] dir-file-or-uri [...]] + + All of the files in the directories are processed in a + depth-first recursive descent. + +3 JHOVEVIEWER (SWING GUI) INTERFACE + + 1. The JhoveViewer class now allows dragging of a directory or of + multiple files, and the output for all files is presented in a single + window. This significantly reduces the window clutter. + + 2. The JhoveViewer presents the module menu in alphabetical order + rather than configuration file order. + + 3. The JhoveViewer was failing to report some submessages. This is fixed. + + 4. The JhoveViewer was failing silently on certain URL errors; it + now puts up an error alert. + + 5. If an empty module class name is added in the Configuration + dialog, it is ignored. + +4 AIFF MODULE + + 1. Descriptive properties added. + + 2. Checksum was sometimes missing; fixed. + + 3. Specification URL added to descriptive information. + + 4. Reported MIME type changed to 'audio/x-aiff' from 'application/aiff'. + +5 GIF MODULE + + 1. BitsPerSample is now reported. + +6 JPEG MODULE + + 1. Errors occurring when parsing an optional EXIF segment were not + being reported. This problem manifested itself by incorrectly + reporting that the JPEG file is not well-formed. + + 2. Array size bug in BitsPerSample fixed. + +7 JPEG2000 MODULE + + 1. Specification information added for ITU. + + 2. Errors in parsing of an EXIF segment are now reported. + +8 PDF MODULE + + 1. In certain instances the module was inappropriately reporting + well-formed PDF files as being non-well-formed, indicating + (incorrectly) that the file does not contain a trailer. + + 2. Fixed a NullPointerException being thrown with a defective page + root tree. + + 3. Certain broken cross-reference tables would throw the module + into a loop. This is fixed. + + 4. Problems in XMP data that triggered a SAX error were being + reported to standard output as a "fatal error." They are now properly + reported. + + 5. Error in offset reporting fixed. + + 6. Now reports FontFile2 and FontFile3. + + 7. File trailers are now found more reliably. + + 8. PDF/A profile updated to latest draft proposal, ISO/CD 19005-1 + (2004-09-20). + + 9. Parameters that would have been specified by the -p argument + of the command line are now specified by the element + in the configuration file. The sense of these parameters + has been reversed; by default, the PDF module presents + the maximum amount of information unless suppressed by + including the characters a, p, f, or o in the parameter value(s). + +9 TIFF MODULE + + 1. Adobe DNG tags are recognized, and a DNG profile has been added. + + 2. Bug in DATETIME checking fixed. + + 3. Changes in validity tests for PhotometricInterpretation, + SamplesPerPixel and BitsPerSample. + + 4. Corrected spurious null values for some properties. + + 5. Tag data type checking was badly broken, now fixed. + +10 WAVE MODULE + + 1. Type 'exif' recognized in LIST chunk. + + 2. Format and signature information updated. + + 3. Checksum was sometimes missing; fixed. + + 4. Reported MIME type changed to 'audio/x-wave' from 'audio/x-wav'. + +11 XML MODULE + + 1. Now reports 1.0 and 1.1 as versions rather than profiles. + + 2. Reported MIME type changed to 'text/xml' from 'application/xml'. + + 3. A base URL for DTD's may now be specified using the + element. The URL must be preceded by the letter b + to distinguish it from potential future parameters, e.g., + + + edu.harvard.hul.ois.jhove.module.XmlModule + bhttp://www.example.com/ + + +12 XML HANDLER + + 1. The "xsi" namespace is now defined in the NISO Image Metadata + and AES Audio Metadata elements. This + allows these segments to validate when extracted from the JHOVE output + document. + + 2. The element is properly named; it + had been improperly displayed as . + + 3. X and YSamplingFrequency are reported as positive integers + ("600"), not ratios ("600/1"), for consistency with the MIX schema. + + 4. An empty Properties element in the XML handler is now suppressed. + +13 GDUMP UTILITY + + 1. New utility to dump GIF files in human-readable form. + +14 JDUMP UTILITY + + 1. New utility to dump JPEG files in human-readable form. + +15 TDUMP UTILITY + + 1. The output format has changed slightly, e.g. + + 00000000: "II" (little endian) 42 + 00000008: IFD 1 with 15 entries + 00000034: 254 (NewSubFileType) LONG 1 = 0 + 00000046: 256 (ImageWidth) LONG 1 = 2948 + 00000058: 257 (ImageLength) LONG 1 = 4620 + ... + +************************************************************************ + +RELEASE NOTES FOR JHOVE 1.0 (beta 2) +Rev. 2004-07-19 + +1. GENERAL + + 1.1 Multiple files can now be specified in command line. + + jhove ... [[-krs] file-or-uri ...] + + A single output document (XML or text) will be generated for a + set of files specified in a command line. + + 1.2 API version information is now available through methods in the + App class. + + 1.3 AESAudioMetadata property has been added for sound formats. The + new PropertyPath class facilitates the extraction of Properties + by applications that use the JHOVE API. + + 1.4 The ErrorMessage and InfoMessage classes now support a submessage + string for more flexible message factoring. + + 1.5 The SAX parser class may now be specified in the jhove.properties + file in the property "edu.harvard.hul.ois.jhove.saxClass". + +2. GRAPHIC USER INTERFACE (JhoveView) + + 2.1 Supports drag and drop of directories; subdirectories are + processed recursively. + + 2.2 The menu option "File > Close document windows" closes all document + windows. + +3. MODULES (GENERAL) + + 3.1 Performance has been improved in all modules. + + 3.2 New modules for JPEG 2000, AIFF, and WAVE formats. Be sure to + update the configuration file, jhove/conf/jhove.conf, to include + these modules: + + ... + + edu.harvard.hul.ois.jhove.module.AiffModule + + + edu.harvard.hul.ois.jhove.module.WaveModule + + + edu.harvard.hul.ois.jhove.module.Jpeg2000Module + + ... + + 3.3 Bug reading unsigned integers has been fixed. + +4. PDF MODULE + + 4.1 More information provided about encryption keys. + + 4.2 UserAccess property now shows "No permissions" if no bits are + set. + +5. GIF MODULE + + 5.1 Unexpected EOF is now handled cleanly. + +6. JPEG MODULE + + 6.1 Exif data exception properly thrown. + +7. TIFF MODULE + + 7.1 Identification of Exif profile has been improved. + + 7.2 Photoshop tags 34377 and 50255 are now recognized. + + 7.3 Bug in handling ExtraSamples tag fixed. + + 7.4 Bug in determining valid date/time formats; the range for hours was + incorrectly constrained to 1-24, rather than 0-24. + +8. XML MODULE + + 8.1 If no encoding is specified, encoding is now reported as UTF-8. + + 8.2 Catches and reports UTFDataFormatException. + + 8.3 A greater range of parsers (including Xerces) now will do + schema validation. + +9. XML HANDLER + + 9.1 Omitted values in NisoImageMetadata were being reported in XML + in some cases as default values (e.g., -1). These have been + suppressed. + + 9.2 element was inappropriately nested underneath + the element. + + 9.3 The "subMessage" attribute is now properly defined in the jhove.xsd + schema. +======= +JHOVE - JSTOR/Harvard Object Validation Environment +Copyright 2003-2009 by JSTOR and the President and Fellows of Harvard College +JHOVE is made available under the GNU Lesser General Public License (LGPL; +see the file LICENSE for details) + +RELEASE NOTES FOR JHOVE 1.5 +2009-12-17 + +PDF MODULE + +1. An ArrayIndexOutOfBoundsException was thrown on a PDF with an invalid + object number in the cross-reference stream. In JHOVE 1.5, this is + correctly reported as a violation of well-formedness. + +UTF-8 MODULE + +1. With some very simple UTF-8 files, JHOVE handlers would throw an exception + processing them, and the GUI would fail silently. This happened with files + using no UTF-8 blocks. This has been fixed. + +TEXTMD (multiple modules) + +1. TextMD metadata can now optionally be reported. To get this, it's + necessary to edit jhove.conf. TextMD can be enabled on a per-module + basis for HtmlModule, AsciiModule, Utf8Module, and XmlModule. + The element for each chosen module must contain the element + withtextmd=true (no spaces). + +2. The TextMD feature was added by Thomas Ledoux. + + + +RELEASE NOTES FOR JHOVE 1.4 +2009-07-30 + +PDF MODULE + + 1. The PDF/A profile has been updated to the final version of + 19005-1:2005(E) and made more thorough. Among the changes: + + a. The set-state and no-op actions disqualify a PDF/A candidate. + + b. The ASCIIHexDecode and ASCII85Decode filters no longer + disqualify a candidate. + + c. Checking of outlines has been added. + + d. Additional checking of Type 1 fonts and symbolic fonts. + + e. Bug fix in checking type 2 subfonts. + + f. An LZW filter in an image object disqualifies a candidate. + + g. The xpacket processing instruction is checked for attributes + which disqualify from PDF/A. + + h. Conformity to implementation limits is checked as a condition + of PDF/A conformity. + +JPEG2000 MODULE + + 1. The pathological case of an image with no components is checked so + it won't cause a crash. + +XML HANDLER + + 1. A reset() function has been added so that if the handler is reused, + it will return to a valid initial state. + +RELEASE NOTES FOR JHOVE 1.3 +2009-06-04 + +GENERAL + + 1. The build.xml files now force compilation to Java 1.4, preventing + accidental distributions that aren't 1.4-compatible. + 2. Spaces are allowed in file paths on Windows, if the path is + enclosed in quotes. This fix had been in version 1.1i, and had been + lost since then. + +PDF MODULE + + 1. According to the PDF 1.6 specification, table 3.4, parameters for a + stream filter can be either a dictionary or the null object. The null + object was treated as an error; it is now allowed. + 2. Object stream handling was seriously buggy, causing rejection of + well-formed and valid files; it's better now. + 3. In PDF 1.4, an outline dictionary unconditionally must have a "First" + and a "Last" entry. JHOVE follows this requirement, declaring a file + invalid if it isn't met. However, PDF 1.6 relaxes the requirement, + applying it only "if there are any open or closed outline entries." + Thus, an empty outline dictionary with no "First" or "Last" entry + is valid. It is now accepted (for all PDF versions). + 4. If a page number tree in a PDF file is missing an expected "Nums" + entry, this was being reported as an invalid date. A more appropriate + error message is now given. + +TIFF MODULE + + 1. TIFF tag 33723 (IPTC-NAA) was considered valid only if the data + type is ASCII or LONG. But according to Aware Systems, the valid + types are UNDEFINED and BYTE. All four types are now accepted. + +XML HANDLER + + 1. Omissions in MIX 1.0 and 2.0 output have been fixed. + +RELEASE NOTES FOR JHOVE 1.2 +2009-02-10 + +GENERAL + + 1. A bug has been fixed in CountedInputStream, which could potentially + have caused infinite recursion in some modules. + +HTML MODULE + + 1. An incompatibility with Java 1.6 has been fixed. + +PDF MODULE + + 1. A null pointer exception would be thrown for PDF documents without a + document root tree. This has been fixed. + 2. A source of possible false positives in PDF profiles has been fixed. + 3. Certain checks weren't being done to Type 2 fonts, and some PDF/A + profile violations might have been missed as a result. This has + been fixed. + +WAVE MODULE + + 1. Sub-chunks of the 'adtl' chunk are now constrained to even byte + boundaries. + +XML HANDLER + + 1. MIX 2.0 is now supported. + 2. The URL for the MIX 0.2 schema has changed to reflect the change + on the LOC MIX site. + 3. The handler was sometimes incorrectly reporting whether the + AESAudioMetadata property had an empty value or not. This has + been fixed. + + +RELEASE NOTES FOR JHOVE 1.1 +Rev. 2008-02-22 + +COMMAND-LINE INTERFACE + + 1. Allow filenames with internal spaces if they are quoted on the + command line. + 2. Corrected error setting the Classpath in the Windows Shell script + (jhove.bat) + 3. Corrected error opening the configuration file using the default + GCJ parser in the GNU Java Runtime Environment. + +GUI (SWING) INTERFACE (JHOVE VIEW) + + 1. AES metadata properties displayed in the RepInfo window rearranged + slightly to make their ordering consistent with the Text and XML + handlers. + 2. The JhoveView.main() method will now accept a "-c configFile" option + on the command line. The GUI interface can now be invoked by: + + java -jar bin/JhoveView.jar -c configFile + + 3. Corrected error opening the configuration file using the default + GCJ parser in the GNU Java Runtime Environment. + 4. Correct recurrent problems with reading the configuration file on + Windows installations. + +AIFF MODULE + + 1. Correct value for first sample offset by included non-zero offset + defined in the SSND chunk. + 2. Do not report bitrate reduction data for PCM data. + 3. All non-final instance fields and methods are protected, rather than + private. + +ASCII MODULE + + 1. A minimal file containing no line-end characters now does not + produce an empty ASCIIMetadata property, which is invalid against + the JHOVE schema. + 2. Zero-length files are considered not well-formed. + 3. Issue informative message if file contains no printable characters. + 4. All non-final instance fields and methods are protected, rather than + private. + +BYTESTREAM MODULE + + 1. All non-final instance fields and methods are protected, rather than + private. + +GIF MODULE + + 1. All non-final instance fields and methods are protected, rather than + private. + +HTML MODULE + + 1. The HTMLMetadata block in the module output is only produced if + there is at least one actual metadata property to report. + 2. All non-final instance fields and methods are protected, rather than + private. + +JPEG MODULE + + 1. The JPEG module reports the X and Y sampling frequency for files + meeting the JFIF profile. + 2. The JPEG module reports the pixel aspect ratio for JFIF profile + files for which it is defined. + 3. File handles were not being properly closed when processing embedded + EXIF metadata. In cases where JHOVE was invoked against large + numbers of objects this was causing a premature crash due to the + resource leak. + 4. All non-final instance fields and methods are protected, rather than + private. + 5. Correct parsing of the EXIF "subsecTimeOriginal" (37251) and + "subsecTimeDigitized" (37522) properties. + 6. Validation errors in embedded EXIF metdata were not being fully + reported. + +JPEG 2000 MODULE + + 1. All non-final instance fields and methods are protected, rather than + private. + 2. Files generated by the LuraWave codec are no longer incorrecly identified + as having unrecognized QCC marker segments. + +PDF MODULE + + 1. Date strings are now parsed with strict conformance to the ASN.1 + syntax. + 2. Destinations defined by indirect references to non-existent objects + are assumed to have the value "null". Files containing such + destinations are reported as "well-formed, but not valid". + 3. No attempt is made to display encrypted outline item title strings are + not displayed. + 4. Catch error if the Info key of the trailer dictionary is not an + indirect reference. + 5. Read entire page tree structure, regardless of its internal + organization. This error may have caused the under reporting of + page resources, such as fonts and images. + 6. The NISO Compression Scheme for all images using the CCITTFaxDecode + compression filter is now reported properly; previously, the scheme + was always reported as CCITT 1D even if the actual compression + algorithm was CCITT Group 3 or 4. + 7. Properly parse UTF-16 escape characters encoded in double-byte form. + 8. The module properly stops looking for the header comment after 1024 + bytes. + 9. All non-final instance fields and methods are protected, rather than + private. + 10. The number of incremental updates is now reported correctly, rather than + the total number of file trailers, which is one greater than the number + of updates. + 11. Only up to 1000 fonts will be reported. After that, an informative + message will be generated. The limit can be set using the parameter + "nxxxx" in the module-specific section of the configuration file: + + + edu.harvard.hul.ois.jhove.module.PdfModule + n2000 + + + 12. Subfonts of Type 0 are now being properly reported. + 13. PDF/A-1b profile is now being properly reported. + 14. Permit trailer info key to be optional. + 15. Additional correction for outline recursion. + 16. Fix treatment of indirect object of Actions. + 17. Correctly handle trailer dictionary without Info entry. + 18. Ignore comments within dictionaries. + +TIFF MODULE + + 1. Corrected error parsing pyramidal TIFF using the SubIFDs tag with a + type of IFD (13) rather than LONG (4). + 7. Correct parsing of the EXIF "subsecTimeOriginal" (37251) and + "subsecTimeDigitized" (37522) properties. + 2. All sub-IFDs of a pyramidal TIFF are now properly parsed. + 3. The EXIF GainControl tag (41991) is now correctly identified as + a SHORT, not a RATIONAL, value. + 4. Corrected error in which valid files were reported as being only + well-formed due to an incorrect parsing of the DateTime (306) tag. + 5. Byte-aligned offsets can be considered well-formed if the module + parameter "byteoffset=true" is set in the configuration file: + + + edu.harvard.hul.ois.jhove.module.TiffModule + byteoffset=true + + + 6. All non-final instance fields and methods are protected, rather than + private. + 7. Correct parsing of the EXIF "subsecTimeOriginal" (37251) and + "subsecTimeDigitized" (37522) properties. + 8. Using the "-s" option, the TIFF module was incorrectlly reporting + signature matches for text files starting with "II". + 9. Validation errors in embedded EXIF metdata were not being fully + reported. + +UTF8 MODULE + + 1. Corrected error under which malformed UTF-8 files containing encoding + sequences starting with a byte value in the range 0xF8 through 0xFF + were reported as well-formed and valid. + 2. Zero-length files are considered not well-formed. + 3. Issue informative message if file contains no printable characters. + 4. All non-final instance fields and methods are protected, rather than + private. + +WAVE MODULE + + 1. BWF files now set the correct start time in the AES metadata. + 2. All non-final instance fields and methods are protected, rather than + private. + 3. "cue " and "adtl" chunks are now properly read. + +XML MODULE + + 1. The DTD is assumed to be the first DOCTYPE system ID in the file with an + ".dtd" extension. + 2. All non-final instance fields and methods are protected, rather than + private. + 3. The module correctly handles schemaLocation attributes that do not + provide two whitespace-separated URIs. + +TEXT HANDLER + + 1. AES audio metadata properties rearranged slightly to make their + ordering consistent with the XML schema. + +XML HANDLER + + 1. Correct sample rate formatting in AES Time Code Format (TCF) + temporal references. + 2. Correct face IDREF in AES metadata. + 3. Disallowed control characters are removed from content. + 4. Null property values no longer generate empty elements. + 5. Image technical metadata can be reported in terms of the MIX 1.0 schema, + as opposed to the default reporting against MIX 0.2. To specify the + 1.0 schema include the directive: + + 1.0 + + if the configuration file. + +JHOVE API + + 1. The process() and processFile() methods of the JhoveBase class are now + public, to permit direct access to the API by applications. + 2. Checksum calculations now use buffered I/O uniformly for improved + performance. + 3. All non-final fields and methods in the JhoveBase class are + protected, rather than private. + 4. When invoked with the "-s" option JHOVE now reports the signature + matched format and MIME type. + 5. The processing of files in a directory is now performed in an + alphabetically sorted order. + +ADUMP UTILITY + + 1. Display the field values of known chunks. + +TDUMP UTILITY + + 1. New format that sorts all tag definitions by their byte offset and + also displays the byte ranges for image data. + 2. Command line flags permit the suppression of BYTE data display (-b) and + and subIFD parsing (-s). + +USERHOME UTILITY + + 1. A new utility program, UserHome, is available to determine the value + of the Java user.home property needed to know where to place the + configuration file. This utility can be invoked by the driver scripts + "userhome" (Bourne shell) or "userhome.bat" (Windows). + +************************************************************************ + +RELEASE NOTES FOR JHOVE 1.0 +Rev. 2005-05-26 + +GENERAL + + 1. Zero length files are now handled properly in all modules. + + 2. Missing start time in audio files is now handled property in all + audio modules. + + 3. Miscellaneous bug fixes, enhancements, and documentation updates. + +AIFF MODULE + + 1. Corrected error causing BitrateReduction to be incorrectly reported + for uncompressed PCM audio. + + +JPEG2000 MODULE + + 1. The module now validates the enumerated ICC profile types in the + Color Specification Box. In the JP2 profile, an unrecognized ICC + profile type marks the file as not well formed; in the JPX, the file + is merely not valid. + + 2. In the beta 3 release certain invalid JPEG 2000 files were + reported as well formed in the JP2 profile. This has been corrected. + +PDF MODULE + + 1. Following the practice of Acrobar, the PDF module will accept + the "%PDF-1.n" header comment anywhere in the first 1024 bytes of a + file (with appropriate notification via an information message), + rather than requiring that it start at byte offset 0. + + 2. The requirements for the PDF/A profile have been brought into + conformance with the most recent version of the PDF/A specification, + ISO/DIS 19005-1 of 2004-12-22. + + 3. Corrected bug that prevented valid PDF/X-1 files from being + recognized as such. + +WAVE MODULE + + 1. Corrected error causing BitrateReduction to be incorrectly reported + for uncompressed PCM audio. + +XML HANDLER + + 1. Dates reported for the NISO Z39.87 + element are now canonicalized to be in proper ISO 8601 form. + + 2. The NISO Z39.87 element is now + reported, if known. + +AUDIT HANDLER + + 1. The current working directory is reported as the "home" + attribute of the element and individual files are reported + as relative pathnames + +************************************************************************ + +RELEASE NOTES FOR JHOVE 1.0 (beta 3) +Rev. 2005-02-04 + +1 GENERAL + + 1. The architecture has been modified to simplify the use of JHOVE + with new "front ends." The new JhoveBase class is used in + conjunction with the App class to incorporate nearly all the + work of setting up a JHOVE instance. The main Jhove class and the App + class are now smaller than before. + + 2. Checksums were often being reported with incorrect values due to + an output formatting error that dropped zeroes. This has been fixed. + + 3. New utilities GDUMP and JDUMP created for GIF and JPEG documents. + + 4. Error messages are more consistently factored into submessages. + This allows messages indicating the same type of error to + be more readily grouped. + + 5. Some modules were reporting a MIME type for a document that is + not well-formed. This no longer occurs. + + 6. Duplicate reporting of AES BitDepth has been suppressed. + + 7. New module for HTML format. Be sure to update the configuration + file, jhove/conf/jhove.conf, to include the module: + + ... + + edu.harvard.hul.ois.jhove.module.HtmlModule + + ... + + 8. The AES audio metadata representation has been updated to + conform with schema version 1.02b (pre-release). + + 9. New property, sigMatches, has been added to RepInfo. This + records which module(s) regarded the signature of the document as a + match, even if the document was not well-formed. This is useful in + identifying broken documents that are reported as ASCII or Bytestream. + + 10. The logging API is supported, permitting the generation of + debugging messages. + + 11. All modules are now non-final, so that they can be subclassed by + adventurous users. + + 12. The -p and -P arguments of the command line are no longer + supported. Instead, the equivalent parameters can be + provided to all variants of JHOVE (including those which + don't take a command line) by specifying a element + within the element of the configuration file. + Example: + + + edu.harvard.hul.ois.jhove.module.PdfModule + a + f + p + + +2 JHOVE COMMAND-LINE INTERFACE + + 1. The JHOVE command-line interface can now accept directory names, + as well as file pathnames and URIs: + + java Jhove [-c config] [-m module] [-h handler] [-e encoding] + [-H handler] [-o output] [-x saxclass] [-t tempdir] + [-b bufsize] [-l loglevel] [[-krs] dir-file-or-uri [...]] + + All of the files in the directories are processed in a + depth-first recursive descent. + +3 JHOVEVIEWER (SWING GUI) INTERFACE + + 1. The JhoveViewer class now allows dragging of a directory or of + multiple files, and the output for all files is presented in a single + window. This significantly reduces the window clutter. + + 2. The JhoveViewer presents the module menu in alphabetical order + rather than configuration file order. + + 3. The JhoveViewer was failing to report some submessages. This is fixed. + + 4. The JhoveViewer was failing silently on certain URL errors; it + now puts up an error alert. + + 5. If an empty module class name is added in the Configuration + dialog, it is ignored. + +4 AIFF MODULE + + 1. Descriptive properties added. + + 2. Checksum was sometimes missing; fixed. + + 3. Specification URL added to descriptive information. + + 4. Reported MIME type changed to 'audio/x-aiff' from 'application/aiff'. + +5 GIF MODULE + + 1. BitsPerSample is now reported. + +6 JPEG MODULE + + 1. Errors occurring when parsing an optional EXIF segment were not + being reported. This problem manifested itself by incorrectly + reporting that the JPEG file is not well-formed. + + 2. Array size bug in BitsPerSample fixed. + +7 JPEG2000 MODULE + + 1. Specification information added for ITU. + + 2. Errors in parsing of an EXIF segment are now reported. + +8 PDF MODULE + + 1. In certain instances the module was inappropriately reporting + well-formed PDF files as being non-well-formed, indicating + (incorrectly) that the file does not contain a trailer. + + 2. Fixed a NullPointerException being thrown with a defective page + root tree. + + 3. Certain broken cross-reference tables would throw the module + into a loop. This is fixed. + + 4. Problems in XMP data that triggered a SAX error were being + reported to standard output as a "fatal error." They are now properly + reported. + + 5. Error in offset reporting fixed. + + 6. Now reports FontFile2 and FontFile3. + + 7. File trailers are now found more reliably. + + 8. PDF/A profile updated to latest draft proposal, ISO/CD 19005-1 + (2004-09-20). + + 9. Parameters that would have been specified by the -p argument + of the command line are now specified by the element + in the configuration file. The sense of these parameters + has been reversed; by default, the PDF module presents + the maximum amount of information unless suppressed by + including the characters a, p, f, or o in the parameter value(s). + +9 TIFF MODULE + + 1. Adobe DNG tags are recognized, and a DNG profile has been added. + + 2. Bug in DATETIME checking fixed. + + 3. Changes in validity tests for PhotometricInterpretation, + SamplesPerPixel and BitsPerSample. + + 4. Corrected spurious null values for some properties. + + 5. Tag data type checking was badly broken, now fixed. + +10 WAVE MODULE + + 1. Type 'exif' recognized in LIST chunk. + + 2. Format and signature information updated. + + 3. Checksum was sometimes missing; fixed. + + 4. Reported MIME type changed to 'audio/x-wave' from 'audio/x-wav'. + +11 XML MODULE + + 1. Now reports 1.0 and 1.1 as versions rather than profiles. + + 2. Reported MIME type changed to 'text/xml' from 'application/xml'. + + 3. A base URL for DTD's may now be specified using the + element. The URL must be preceded by the letter b + to distinguish it from potential future parameters, e.g., + + + edu.harvard.hul.ois.jhove.module.XmlModule + bhttp://www.example.com/ + + +12 XML HANDLER + + 1. The "xsi" namespace is now defined in the NISO Image Metadata + and AES Audio Metadata elements. This + allows these segments to validate when extracted from the JHOVE output + document. + + 2. The element is properly named; it + had been improperly displayed as . + + 3. X and YSamplingFrequency are reported as positive integers + ("600"), not ratios ("600/1"), for consistency with the MIX schema. + + 4. An empty Properties element in the XML handler is now suppressed. + +13 GDUMP UTILITY + + 1. New utility to dump GIF files in human-readable form. + +14 JDUMP UTILITY + + 1. New utility to dump JPEG files in human-readable form. + +15 TDUMP UTILITY + + 1. The output format has changed slightly, e.g. + + 00000000: "II" (little endian) 42 + 00000008: IFD 1 with 15 entries + 00000034: 254 (NewSubFileType) LONG 1 = 0 + 00000046: 256 (ImageWidth) LONG 1 = 2948 + 00000058: 257 (ImageLength) LONG 1 = 4620 + ... + +************************************************************************ + +RELEASE NOTES FOR JHOVE 1.0 (beta 2) +Rev. 2004-07-19 + +1. GENERAL + + 1.1 Multiple files can now be specified in command line. + + jhove ... [[-krs] file-or-uri ...] + + A single output document (XML or text) will be generated for a + set of files specified in a command line. + + 1.2 API version information is now available through methods in the + App class. + + 1.3 AESAudioMetadata property has been added for sound formats. The + new PropertyPath class facilitates the extraction of Properties + by applications that use the JHOVE API. + + 1.4 The ErrorMessage and InfoMessage classes now support a submessage + string for more flexible message factoring. + + 1.5 The SAX parser class may now be specified in the jhove.properties + file in the property "edu.harvard.hul.ois.jhove.saxClass". + +2. GRAPHIC USER INTERFACE (JhoveView) + + 2.1 Supports drag and drop of directories; subdirectories are + processed recursively. + + 2.2 The menu option "File > Close document windows" closes all document + windows. + +3. MODULES (GENERAL) + + 3.1 Performance has been improved in all modules. + + 3.2 New modules for JPEG 2000, AIFF, and WAVE formats. Be sure to + update the configuration file, jhove/conf/jhove.conf, to include + these modules: + + ... + + edu.harvard.hul.ois.jhove.module.AiffModule + + + edu.harvard.hul.ois.jhove.module.WaveModule + + + edu.harvard.hul.ois.jhove.module.Jpeg2000Module + + ... + + 3.3 Bug reading unsigned integers has been fixed. + +4. PDF MODULE + + 4.1 More information provided about encryption keys. + + 4.2 UserAccess property now shows "No permissions" if no bits are + set. + +5. GIF MODULE + + 5.1 Unexpected EOF is now handled cleanly. + +6. JPEG MODULE + + 6.1 Exif data exception properly thrown. + +7. TIFF MODULE + + 7.1 Identification of Exif profile has been improved. + + 7.2 Photoshop tags 34377 and 50255 are now recognized. + + 7.3 Bug in handling ExtraSamples tag fixed. + + 7.4 Bug in determining valid date/time formats; the range for hours was + incorrectly constrained to 1-24, rather than 0-24. + +8. XML MODULE + + 8.1 If no encoding is specified, encoding is now reported as UTF-8. + + 8.2 Catches and reports UTFDataFormatException. + + 8.3 A greater range of parsers (including Xerces) now will do + schema validation. + +9. XML HANDLER + + 9.1 Omitted values in NisoImageMetadata were being reported in XML + in some cases as default values (e.g., -1). These have been + suppressed. + + 9.2 element was inappropriately nested underneath + the element. + + 9.3 The "subMessage" attribute is now properly defined in the jhove.xsd + schema. + diff --git a/jhove/build.xml b/jhove/build.xml new file mode 100644 index 00000000..6e595955 --- /dev/null +++ b/jhove/build.xml @@ -0,0 +1,78 @@ + + Project build file + Jhove - JSTOR/Harvard Object Validation Environment + Version 1.0 2004-09-10 + Copyright 2004 by JSTOR and the President and Fellows of Harvard College + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jhove/classes/ADump.java b/jhove/classes/ADump.java new file mode 100644 index 00000000..aea25df1 --- /dev/null +++ b/jhove/classes/ADump.java @@ -0,0 +1,235 @@ +/********************************************************************** + * JDump - JSTOR/Harvard Object Validation Environment + * Copyright 2004-2005 by the President and Fellows of Harvard College + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + **********************************************************************/ + +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.aiff.*; +import java.io.*; + +/** + * Dump contents of AIFF file in human-readable format. + * @author Gary McGath + */ +public class ADump extends Dump { + + /* Fixed value for first 4 bytes */ + private static final int[] sigByte = + { 0X46, 0X4F, 0X52, 0X4D }; + private static final boolean ENDIAN = true; /* bigEndian */ + + /****************************************************************** + * MAIN ENTRY POINT. + ******************************************************************/ + + /** + * Main entry point. + * @param args Command line arguments + */ + public static void main (String [] args) + { + if (args.length < 1) { + System.err.println ("usage: java ADump file"); + System.exit (-1); + } + try { + FileInputStream file = new FileInputStream (args[0]); + BufferedInputStream buffer = new BufferedInputStream (file); + DataInputStream stream = new DataInputStream (buffer); + ADump dump = new ADump (); // Just to access contained classes + long os = 0; + for (int i=0; i<4; i++) { + int ch; + ch = stream.readUnsignedByte(); + if (ch != sigByte[i]) { + System.out.println ("No AIFF FORM header"); + System.exit (-2); + } + } + os += 4; + long ckSize = ModuleBase.readUnsignedInt (stream, ENDIAN); + + // Read the file type + StringBuffer formType = new StringBuffer (4); + for (int i=0; i<4; i++) { + int ch = ModuleBase.readUnsignedByte(stream); + formType.append((char) ch); + } + System.out.println ("00000000: FORM " + ckSize + ": " + formType); + + boolean aiff_c = false; + if (formType.toString ().equals ("AIFC")) { + aiff_c = true; + } + + StringBuffer sbuf = new StringBuffer (); + boolean endOfFile = false; + while (!endOfFile) { + // Read chunks + try { + sbuf.setLength(0); + // Read chunk name. + for (int i=0; i<4; i++) { + int ch = ModuleBase.readUnsignedByte(stream); + sbuf.append((char) ch); + } + String ckID = sbuf.toString (); + // Read size (excluding chunk name and size fields) + ckSize = ModuleBase.readUnsignedInt (stream, ENDIAN); + System.out.print (leading (os, 8) + os + ": " + ckID + + " " + ckSize); + long alreadyRead = 0; + if (ckID.equals ("AESD")) { + int [] aes = new int[24]; + for (int i=0; i<24; i++) { + aes[i] = ModuleBase.readUnsignedByte (stream); + + } + System.out.print (": " + aes[0]); + for (int i=1; i<24; i++) { + System.out.print ("," + aes[i]); + } + alreadyRead = 24; + } + else if (ckID.equals ("ANNO") || + ckID.equals ("AUTH") || + ckID.equals ("(c) ") || + ckID.equals ("NAME")) { + sbuf.setLength (0); + for (int i=0; i 0) { + break; + } + else { + boxStack.pop (); + } + } + + // Read the header of a JP2 box + Box box = dump.new Box (stream); + try { + box.read (); + } + catch (EOFException e) { + endOfFile = true; + break; + } + os += box.length - box.bytesLeft; + + // If it's contained in a superbox, subtract + // this box from its remaining length + if (boxtop != null) { + boxtop.bytesLeft -= box.length; + } + System.out.println (leading (os, 8) + os + ": " + + stackPrefix (boxStack) + box.type + " " + box.length); + if (box.isSuperbox ()) { + boxStack.push (box); + } + else { + os += box.bytesLeft; + stream.skipBytes((int) box.bytesLeft); + } + + // A "length" of 0 means the box occupies the rest of the file. + if (box.length == 0) { + endOfFile = true; + } + } + } + catch (Exception e) { + e.printStackTrace (System.err); + System.exit (-2); + } + } + + + /* Constructs a qualifying prefix to indicate nested boxes. */ + private static String stackPrefix (Stack boxStack) + { + StringBuffer retval = new StringBuffer (); + // In defiance of gravity, we rummage through the stack + // of boxes starting at the bottom. + for (int i = 0; i < boxStack.size(); i++) { + Box box = (Box) boxStack.elementAt (i); + // Remove trailing spaces from types for better readability + retval.append (box.type.trim() + "/"); + } + return retval.toString (); + } + + + + /** Local class for defining JPEG2000 boxes. */ + class Box { + public String type; + public long length; + public long bytesLeft; + public boolean hasBoxes; + DataInputStream dstream; + + public Box (DataInputStream stream) + { + this.dstream = stream; + } + + + /** Reads a box header and sets up for reading contents. */ + public void read () throws IOException + { + length = ModuleBase.readUnsignedInt (dstream, ENDIAN, null); + long headerLength = 8; + type = read4Chars (); + // If the length field is 1, there is an 8-byte extended + // length field. + if (length == 1) { + length = ModuleBase.readSignedLong(dstream, true, null); + headerLength = 16; + } + bytesLeft = length - headerLength; + } + + + /* Reads a 4-character name */ + private String read4Chars() throws IOException { + StringBuffer sbuf = new StringBuffer(4); + for (int i = 0; i < 4; i++) { + int ch = ModuleBase.readUnsignedByte(dstream, null); + sbuf.append((char) ch); + } + return sbuf.toString(); + } + + /** Returns true if this box contains other boxes. + * At present, we don't deal with the insides of boxes + * that contain both data and boxes (e.g., cref). */ + public boolean isSuperbox () + { + // If it's a known superbox type, we return true. + // If we've left any out, that will merely make us + // lose the subboxes of that type. + String [] supertypes = { "asoc", "cgrp", "comp", "drep", + "ftbl", "jp2h", "jpch", + "jplh", "res ", "uuid" }; + for (int i = 0; i < supertypes.length; i++) { + if (supertypes[i].equals (type)) { + return true; + } + } + return false; + } + } + + +} diff --git a/jhove/classes/JDump.java b/jhove/classes/JDump.java new file mode 100644 index 00000000..07eca9ab --- /dev/null +++ b/jhove/classes/JDump.java @@ -0,0 +1,404 @@ +/********************************************************************** + * JDump - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by the President and Fellows of Harvard College + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + **********************************************************************/ + +import edu.harvard.hul.ois.jhove.*; +import java.io.*; +//import java.util.*; + +/** + * Dump contents of JPEG file in human-readable format. + */ +public class JDump + extends Dump +{ + /****************************************************************** + * MAIN ENTRY POINT. + ******************************************************************/ + + /** + * Main entry point. + * @param args Command line arguments + */ + public static void main (String [] args) + { + if (args.length < 1) { + System.err.println ("usage: java JDump file"); + System.exit (-1); + } + + try { + FileInputStream file = new FileInputStream (args[0]); + BufferedInputStream buffer = new BufferedInputStream (file); + DataInputStream stream = new DataInputStream (buffer); + boolean bigEndian = true; + + long os = 0; + + boolean endOfImage = false; + boolean readingECS = false; + boolean haveCode = false;; + int nECS = 0; + int code = 0; + while (!endOfImage) { + if (!readingECS) { + if (!haveCode) { + code = stream.readUnsignedByte (); + for (int i=0; + (code = stream.readUnsignedByte ()) == 0xff; + i++) { + System.out.println (leading (os, 8) + os + + ": fill 0xff"); + os++; + } + } + } + else { + boolean ff = false; + int length = 0; + while (true) { + code = stream.readUnsignedByte (); + length++; + if (code == 0xff) { + ff = true; + } + else if (ff) { + if (code != 0x00) { + length -= 2; + break; + } + else { + ff = false; + } + } + } + System.out.println (leading (os, 8) + os + ": ECS" + + nECS + " " + length + " ..."); + os += length; + nECS++; + readingECS = false; + haveCode = true; + continue; + } + + if (code == 0x01) { + System.out.println (leading (os, 8) + os + ": TEM"); + } + else if ((code >= 0xc0 && code <= 0xc3) || + (code >= 0xc5 && code <= 0xc7) || + (code >= 0xc9 && code <= 0xcb) || + (code >= 0xcd && code <= 0xcf)) { + int n = code - 0xc0; +/****************** int length = markerSegment (stream, bigEndian); */ + int length = ModuleBase.readUnsignedShort (stream, + bigEndian, + null); + int P = stream.readUnsignedByte (); + int Y = ModuleBase.readUnsignedShort (stream, bigEndian, + null); + int X = ModuleBase.readUnsignedShort (stream, bigEndian, + null); + int Nf = stream.readUnsignedByte (); + int [] Ci = new int [Nf]; + int [] Hi = new int [Nf]; + int [] Vi = new int [Nf]; + int [] Tqi = new int [Nf]; + for (int i=0; i= 0xd0 && code <= 0xd7) { + int m = code - 0xd0; + System.out.println (leading (os, 8) + os + ": RST" + m); + + readingECS = true; + } + else if (code == 0xd8) { + System.out.println (leading (os, 8) + os + ": SOI"); + } + else if (code == 0xd9) { + System.out.println (leading (os, 8) + os + ": EOI"); + endOfImage = true; + break; + } + else if (code == 0xda) { + int length = markerSegment (stream, bigEndian); + System.out.println (leading (os, 8) + os + ": SOS" + " " + + length + " ..."); + os += length; + + readingECS = true; + } + else if (code == 0xdb) { + int length = markerSegment (stream, bigEndian); + System.out.println (leading (os, 8) + os + ": DQT" + " " + + length + " ..."); + os += length; + } + else if (code == 0xdc) { + int length = markerSegment (stream, bigEndian); + System.out.println (leading (os, 8) + os + ": DNL" + " " + + length + " ..."); + os += length; + } + else if (code == 0xdd) { + int length = markerSegment (stream, bigEndian); + System.out.println (leading (os, 8) + os + ": DRI" + " " + + length + " ..."); + os += length; + } + else if (code == 0xde) { + int length = markerSegment (stream, bigEndian); + System.out.println (leading (os, 8) + os + ": DHP" + " " + + length + " ..."); + os += length; + } + else if (code == 0xdf) { + int length = markerSegment (stream, bigEndian); + System.out.println (leading (os, 8) + os + ": EXP" + " " + + length + " ..."); + os += length; + } + else if (code == 0xe0) { + int length = ModuleBase.readUnsignedShort (stream, + bigEndian, + null); + String id = readChars (stream, 5); + if (id.equals ("JFIF\0")) { + int major = stream.readUnsignedByte (); + int minor = stream.readUnsignedByte (); + int units = stream.readUnsignedByte (); + int xDensity = ModuleBase.readUnsignedShort (stream, + bigEndian, + null); + int yDensity = ModuleBase.readUnsignedShort (stream, + bigEndian, + null); + int xThumbnail = stream.readUnsignedByte (); + int yThumbnail = stream.readUnsignedByte (); + System.out.print (leading (os, 8) + os + ": APP0 " + + "\"" + id + "\" " + major + "." + + minor + " " + units + " " + + xDensity + "x" + yDensity + " " + + xThumbnail + "x" + yThumbnail); + int n = length - 16; + if (n > 0) { + for (int i=0; i 0) { + for (int i=0; i= 0xe1 && code <= 0xef) { + int n = code - 0xe0; + /* + int length = markerSegment (stream, bigEndian); + */ + int length = ModuleBase.readUnsignedShort (stream, + bigEndian, + null); + if ((n == 1 || n == 2) && length >= 8) { + String id = readChars (stream, 4); + int NULL = stream.readUnsignedByte (); + int padding = stream.readUnsignedByte (); + if (id.equals ("Exif") || id.equals ("FPXR")) { + System.out.println (leading (os, 8) + os + + ": APP" + n + " \"" + id + + "\" " + NULL + " " + padding + + " " + (length-8) + ": ..."); + } + else { + System.out.println (leading (os, 8) + os + + ": APP" + n + " " + length + + " ..."); + } + for (int i=8; i 0) { + int [] cap = new int [n]; + for (int i=0; i= 0xf7 && code <= 0xfd) { + int n = code - 0xf0; + int length = markerSegment (stream, bigEndian); + System.out.println (leading (os, 8) + os + ": JPG" + n + + " " + length + " ..."); + os += length; + } + else if (code == 0xfe) { + int length = ModuleBase.readUnsignedShort (stream, + bigEndian, + null); + String comment = readChars (stream, length-2); + System.out.println (leading (os, 8) + os + ": COM \"" + + comment + "\""); + os += length; + } + else { + int length = markerSegment (stream, bigEndian); + String hex = Integer.toHexString (code); + System.out.println (leading (os, 8) + os + ": RES (0x" + + leading (hex, 2) + hex + ") " + + length + " ..."); + } + os += 2; + } + + stream.close (); + } + catch (Exception e) { + e.printStackTrace (System.err); + System.exit (-2); + } + } + + /** + * Read marker segment data + * @param stream Data input stream + * @param bigEndian True if big-endian + * @return Length of marker segment + */ + private static int markerSegment (DataInputStream stream, + boolean bigEndian) + throws IOException + { + int length = ModuleBase.readUnsignedShort (stream, + bigEndian, + null); + for (int i=2; i list = new ArrayList (); + + + /********************************************************** + * Parse command line arguments: + * -m module Module name + * -h handler Output handler + * -e encoding Output encoding + * -H handler About handler + * -o output Output file pathname + * -t tempdir Directory for temp files + * -b bufsize Buffer size for buffered I/O + * -k Calculate checksums + * -r Display raw numeric flags + * -s Check internal signatures only + * dirFileOrUri Directories, file pathnames, or URIs + * + * The following arguments were defined in previous + * versions, but are now obsolete + * -p param OBSOLETE + * -P param OBSOLETE + **********************************************************/ + + quoted = false; + StringBuffer filename = null; + + for (int i=0; i 0) { + dirFileOrUri = new String [len]; + for (int i=0; i 80) { + msg = msg.substring (0, 79) + "..."; + } + JOptionPane.showMessageDialog (hiddenFrame, + msg, "Jhove Error", JOptionPane.ERROR_MESSAGE); + } +} diff --git a/jhove/classes/PDump.java b/jhove/classes/PDump.java new file mode 100644 index 00000000..2a6437c3 --- /dev/null +++ b/jhove/classes/PDump.java @@ -0,0 +1,115 @@ +/********************************************************************** + * PDump - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + **********************************************************************/ + +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.pdf.*; +import java.io.*; + +/** + * Dump contents of PDF file in human-readable format. + */ +public class PDump + extends Dump +{ + /****************************************************************** + * MAIN ENTRY POINT. + ******************************************************************/ + + /** + * Main entry point. + * @param args Command line arguments + */ + public static void main (String [] args) + { + if (args.length < 1) { + System.err.println ("usage: java PDump file"); + System.exit (-1); + } + + try { + RandomAccessFile file = new RandomAccessFile (args[0], "r"); + Tokenizer tokenizer = new FileTokenizer (file); + Token token = null; + long offset = 0; + while ((token = tokenizer.getNext ()) != null) { + System.out.print (leading (offset, 8) + offset + ": "); + if (token instanceof ArrayEnd) { + System.out.println ("ArrayEnd"); + } + else if (token instanceof ArrayStart) { + System.out.println ("ArrayStart"); + } + else if (token instanceof Comment) { + System.out.println ("Comment \"" + + ((Comment) token).getValue () + "\""); + } + else if (token instanceof DictionaryEnd) { + System.out.println ("DictionaryEnd"); + } + else if (token instanceof DictionaryStart) { + System.out.println ("DictionaryStart"); + } +// else if (token instanceof Hexadecimal) { +// System.out.println ("Hexadecimal[" + +// (((Hexadecimal) token).isPDFDocEncoding () ? +// "PDF" : "UTF-16") + "] \"" + +// ((Hexadecimal) token).getValue () + +// "\""); +// } + else if (token instanceof Keyword) { + System.out.println ("Keyword \"" + + ((Keyword) token).getValue () + "\""); + } + else if (token instanceof Literal) { + System.out.println ("Literal[" + + (((Literal)token).isPDFDocEncoding () ? + "PDF" : "UTF-16") + "] \"" + + ((Literal) token).getValue () + "\""); + } + else if (token instanceof Name) { + System.out.println ("Name \"" + + ((Name) token).getValue () + "\""); + } + else if (token instanceof Numeric) { + Numeric numeric = (Numeric) token; + if (numeric.isReal ()) { + System.out.println ("Numeric " + numeric.getValue ()); + } + else { + System.out.println ("Numeric " + + numeric.getIntegerValue ()); + } + } + else if (token instanceof Stream) { + System.out.println ("Stream " + + ((Stream) token).getLength ()); + } + else { + System.out.println (token); + } + offset = tokenizer.getOffset (); + } + } + catch (Exception e) { + e.printStackTrace (System.err); + System.exit (-2); + } + } +} diff --git a/jhove/classes/TDump.java b/jhove/classes/TDump.java new file mode 100644 index 00000000..7063fda3 --- /dev/null +++ b/jhove/classes/TDump.java @@ -0,0 +1,361 @@ +/********************************************************************** + * TDump - JSTOR/Harvard Object Validation Environment + * Copyright 2003-2004 by JSTOR and the President and Fellows of Harvard College + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + **********************************************************************/ + +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.tiff.*; +import java.io.*; +import java.util.*; + +/** + * Dump contents of TIFF file in human-readable format. + */ +public class TDump + extends Dump +{ + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + + /** Count of IFDs. */ + private static int _nIFDs; + /** Sorted associative map of tags. */ + private static Map _tags; + + /****************************************************************** + * MAIN ENTRY POINT. + ******************************************************************/ + + /** + * Main entry point. + * @param args Command line arguments + */ + public static void main (String [] args) + { + if (args.length < 1) { + System.err.println ("usage: java TDump [-bs] file"); + System.exit (-1); + } + + String tiff = null; + boolean nobyte = false; + boolean nosub = false; + for (int i=0; i 0) { + } + file.close (); + } + catch (Exception e) { + e.printStackTrace (System.out); + err = -2; + } + finally { + + /* Display all tags in offset-sorted order. */ + + Iterator iter = _tags.keySet ().iterator (); + while (iter.hasNext ()) { + String os = (String) iter.next (); + System.out.println (os + ": " + (String) _tags.get (os)); + } + if (err != 0) { + System.exit (err); + } + } + } + + /** + * Read IFDs. + * @param file Open TIFF file + * @param bigEndian True if big-endian + * @param offset Byte offset of IFD + * @param nobyte If true, only display the first 8 bytes of data of + * type BYTE + * @param nosub If true, do not parse subIFDs + */ + private static long readIFD (RandomAccessFile file, boolean bigEndian, + long offset, boolean nobyte, boolean nosub) + throws Exception + { + int nIFD = ++_nIFDs; + List subIFDs = new ArrayList (); + List stripByteCounts = new ArrayList (); + List stripOffsets = new ArrayList (); + + file.seek (offset); + int nEntries = ModuleBase.readUnsignedShort (file, bigEndian); + _tags.put (leading (offset, 8) + offset, "IFD " + nIFD + " with " + + nEntries + " entries"); + + String name = null; + for (int i=0; i 4) { + long vo = ModuleBase.readUnsignedInt (file, bigEndian); + file.seek (vo); + + buffer.append (" @" + vo); + } + StringBuffer ascii = new StringBuffer (); + for (int j=0; j 4) { + long vo = ModuleBase.readUnsignedInt (file, bigEndian); + file.seek (vo); + + buffer.append (" @" + vo); + } + buffer.append (" ="); + long ct = count; + if (nobyte && count > 8) { + ct = 8; + } + for (int j=0; j 8) { + buffer.append (" ..."); + } + } + else if (type == IFD.DOUBLE) { + long vo = ModuleBase.readUnsignedInt (file, bigEndian); + file.seek (vo); + + buffer.append (" @" + vo + " ="); + for (int j=0; j 1) { + long vo = ModuleBase.readUnsignedInt (file, bigEndian); + file.seek (vo); + + buffer.append (" @" + vo); + } + buffer.append (" ="); + for (int j=0; j 1) { + long vo = ModuleBase.readUnsignedInt (file, bigEndian); + file.seek (vo); + + buffer.append (" @" + vo + " = "); + } + else if (tag == 330 || /* Sub IFD */ + tag == 34665 || /* EXIF IFD */ + tag == 34853 || /* EXIF GPS IFD */ + tag == 40965) { /* EXIF Interoperability IFD */ + buffer.append (" @"); + } + else { + buffer.append (" = "); + } + for (int j=0; j 0) { + buffer.append (" "); + } + buffer.append (in); + + if (tag == 330 || /* Sub IFD */ + tag == 34665 || /* EXIF IFD */ + tag == 34853 || /* EXIF GPS IFD */ + tag == 40965) { /* EXIF Interoperability IFD */ + subIFDs.add (new Long (in)); + } + else if (tag == 273) { + stripOffsets.add (new Long (in)); + } + else if (tag == 279) { + stripByteCounts.add (new Long (in)); + } + } + } + else if (type == IFD.RATIONAL) { + long vo = ModuleBase.readUnsignedInt (file, bigEndian); + file.seek (vo); + + buffer.append (" @" + vo + " ="); + for (int j=0; j 4) { + long vo = ModuleBase.readUnsignedInt (file, bigEndian); + file.seek (vo); + + buffer.append (" @" + vo); + } + buffer.append (" ="); + for (int j=0; j 2) { + long vo = ModuleBase.readUnsignedInt (file, bigEndian); + file.seek (vo); + + buffer.append (" @" + vo); + } + buffer.append (" ="); + for (int j=0; j 1) { + long vo = ModuleBase.readUnsignedInt (file, bigEndian); + file.seek (vo); + + buffer.append (" @" + vo); + } + buffer.append (" ="); + for (int j=0; j 2) { + long vo = ModuleBase.readUnsignedInt (file, bigEndian); + file.seek (vo); + + buffer.append (" @" + vo); + } + buffer.append (" ="); + for (int j=0; j 0) { + } + } + } + + long os = offset + 2 + nEntries*12; + file.seek (os); + long next = ModuleBase.readUnsignedInt (file, bigEndian); + _tags.put (leading (os, 8) + os, "NextIFDOffset LONG @" + next); + + int len = stripOffsets.size (); + if (len > 0) { + for (int j=0; j 14) { + int bitsPerSample = + ModuleBase.readUnsignedShort (stream, ENDIAN); + System.out.print (" " + bitsPerSample); + alreadyRead = 16; + if (ckSize > 16) { + int size = + ModuleBase.readUnsignedShort (stream, + ENDIAN); + System.out.print (" " + size); + alreadyRead = 18; + if (size == 22) { + int validBitsPerSample = + ModuleBase.readUnsignedShort (stream, + ENDIAN); + long channelMask = + ModuleBase.readUnsignedInt (stream, + ENDIAN); + hex = Long.toHexString (channelMask); + System.out.print (" " + + validBitsPerSample + + " 0x" + + leading (hex, 8) + hex + + " 0x"); + for (int i=0; i<4; i++) { + long guid = + ModuleBase.readUnsignedInt ( + stream, + ENDIAN); + hex = Long.toHexString (guid); + System.out.print (leading (hex, 8) + + hex); + } + alreadyRead = 40; + } + } + } + } + System.out.println (); + + if (ckID.equals ("list") || ckID.equals ("LIST")) { + readNestedChunks (ckID, stream, ckSize, os + 8); + } + else { + stream.skipBytes ((int) (ckSize - alreadyRead)); + } + os += ckSize + 8; + } + catch (EOFException e) { + endOfFile = true; + } + } + } + catch (Exception e) { + } + } + + /* The "list" and "LIST" chunks (which are two distinct chunk types) + hold nested chunks. */ + private static void readNestedChunks + (String ckID, DataInputStream stream, long ckSize, long os) + throws IOException + { + String listType = read4Chars (stream); + System.out.println ("List type = " + listType); + while (ckSize > 0) { + String subCkID = read4Chars (stream); + long subCkSize = ModuleBase.readUnsignedInt (stream, ENDIAN, null); + System.out.println (leading (os, 8) + os + ": " + + ckID + "/" + subCkID + " " + subCkSize); + stream.skipBytes ((int) subCkSize); + os += subCkSize + 8; + ckSize -= subCkSize + 8; + } + } + + private static String read4Chars (DataInputStream stream) + throws IOException + { + StringBuffer sbuf = new StringBuffer(4); + for (int i = 0; i < 4; i++) { + int ch = ModuleBase.readUnsignedByte(stream, null); + sbuf.append((char) ch); + } + return sbuf.toString (); + } +} diff --git a/jhove/classes/build.xml b/jhove/classes/build.xml new file mode 100644 index 00000000..d4f81dac --- /dev/null +++ b/jhove/classes/build.xml @@ -0,0 +1,128 @@ + + Main build file + Jhove - JSTOR/Harvard Object Validation Environment + Version 1.1 2006-02-13 + Copyright 2004-2006 by JSTOR and the President and Fellows of Harvard College + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+ + + + + + + +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + +
diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/AESAudioMetadata.java b/jhove/classes/edu/harvard/hul/ois/jhove/AESAudioMetadata.java new file mode 100644 index 00000000..5f97869b --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/AESAudioMetadata.java @@ -0,0 +1,940 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004-2005 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +import java.util.*; + +/** + * Encapsulation of the AES Metadata for Audio documents + * + * @author Gary McGath + * + */ +public class AESAudioMetadata +{ + /****************************************************************** + * PUBLIC CLASS FIELDS. + ******************************************************************/ + + /** Big-endian constant. */ + public static final int BIG_ENDIAN = 0; + + /** Little-endian constant. */ + public static final int LITTLE_ENDIAN = 1; + + /** Analog / digital labels. */ + public static final String [] A_D = { + "ANALOG", "PHYS_DIGITAL", "FILE_DIGITAL" + }; + + /** Values for primary identifier type */ + public static final String + FILE_NAME = "FILE_NAME", + OTHER = "OTHER"; + + /** Constant for an undefined integer value. */ + public static final int NULL = -1; + /** Constant for an undefined floating-point value. */ + public static final double NILL = -1.0; + + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + * + ******************************************************************/ + + /** Constant value for the SchemaVersion field */ + public static final String SCHEMA_VERSION = "1.02b"; + + /** Constant value for the disposition field */ + private static final String DEFAULT_DISPOSITION = "validation"; + + private String _analogDigitalFlag; + private String _appSpecificData; + private String _audioDataEncoding; + private int _byteOrder; + private String _disposition; + private List _faceList; + private long _firstSampleOffset; + private String _format; + private List _formatList; + private int _numChannels; + private String _primaryIdentifier; + private String _primaryIdentifierType; + private String _primaryIdentifierOtherType; + private String _schemaVersion; + private String _specificationVersion; + private String[] _use; + + /* Most recently added FormatRegion */ + private FormatRegion _curFormatRegion; + /* Most recently added Face */ + private Face _curFace; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** Instantiate a NisoImageMetadata object. + */ + public AESAudioMetadata () + { + _schemaVersion = SCHEMA_VERSION; + _disposition = DEFAULT_DISPOSITION; + _analogDigitalFlag = null; + _format = null; + _specificationVersion = null; + _audioDataEncoding = null; + _primaryIdentifier = null; + _primaryIdentifierType = null; + _use = null; + + // We add one format region to get started. In practice, + // that one is all we're likely to need. but more can be + // added if necessary. + _formatList = new LinkedList (); + _faceList = new LinkedList (); + addFormatRegion (); + addFace (); + _numChannels = NULL; + _byteOrder = NULL; + _firstSampleOffset = NULL; + } + + /****************************************************************** + * PUBLIC STATIC INTERFACES. + * + ******************************************************************/ + /** + * Public interface to the nested FormatRegion object. Instances + * of this should be created only by addFormatRegion, but can be + * accessed through the public methods of this interface. + */ + public static interface FormatRegion { + /** Returns the bit depth. */ + public int getBitDepth (); + /** Returns the bitrate reduction (compression information). + * This will be an array of seven strings (which may be + * empty, but should never be null) interpreted as follows: + *
    + *
  • 0: codecName + *
  • 1: codecNameVersion + *
  • 2: codecCreatorApplication + *
  • 3: codecCreatorApplicationVersion + *
  • 4: codecQuality + *
  • 5: dataRate + *
  • 6: dataRateMode + *
+ */ + public String[] getBitrateReduction (); + /** Returns the sample rate. */ + public double getSampleRate (); + /** Returns the word size. */ + int getWordSize (); + /** Returns true if the region is empty. */ + public boolean isEmpty (); + /** Sets the bit depth value. */ + public void setBitDepth (int bitDepth); + /** Sets the bitrate reduction information to null (no compression). */ + public void clearBitrateReduction (); + /** Sets the bitrate reduction (aka compression type). */ + public void setBitrateReduction (String codecName, + String codecNameVersion, + String codecCreatorApplication, + String codecCreatorApplicationVersion, + String codecQuality, + String dataRate, + String dataRateMode); + /** Sets the sample rate. */ + public void setSampleRate (double sampleRate); + /** Sets the word size. */ + public void setWordSize (int wordSize); + } + + /** + * Public interface to the nested TimeDesc object. Instances + * of this should be created only by appropriate methods, but can be + * accessed through the public methods of this interface. + */ + public static interface TimeDesc { + /** Returns the hours component. */ + public int getHours (); + /** Returns the minutes component. */ + public int getMinutes (); + /** Returns the seconds component. */ + public int getSeconds (); + /** Returns the frames component of the fraction of a second. + * We always consider frames to be thirtieths of a second. */ + public int getFrames (); + /** Returns the samples remaining after the frames part of + * the fractional second. */ + public int getSamples (); + /** Returns the sample rate on which the samples remainder + * is based. */ + public double getSampleRate (); + } + + /** Public interface to the nested Face object. Instances + * of this should be created only by appropriate methods, but can be + * accessed through the public methods of this interface. */ + public static interface Face { + /** Returns an indexed FaceRegion. */ + public FaceRegion getFaceRegion (int i); + + /** Adds a FaceRegion. This may be called repeatedly to + * add multiple FaceRegions. */ + public void addFaceRegion (); + + /** Returns the starting time. */ + public TimeDesc getStartTime (); + + /** Returns the duration. */ + public TimeDesc getDuration (); + + /** Returns the direction. */ + public String getDirection (); + + /** Sets the starting time. This will be converted + * into a TimeDesc. */ + public void setStartTime (long samples); + + /** Sets the duration. This will be converted + * into a TimeDesc. */ + public void setDuration (long samples); + + /** Sets the direction. This must be one of the + * directionTypes. FORWARD is recommended for most + * or all cases. + */ + public void setDirection (String direction); + + /* End of interface Face */ + } + + /** Public interface to the nested FaceRegion object. Instances + * of this should be created only by appropriate methods, but can be + * accessed through the public methods of this interface. */ + public static interface FaceRegion { + /** Returns the starting time. */ + public TimeDesc getStartTime (); + + /** Returns the duration. */ + public TimeDesc getDuration (); + + /** Returns the channel map locations. The array length must + * equal the number of channels. */ + public String[] getMapLocations (); + + /** Sets the starting time. */ + public void setStartTime (long samples); + + /** Sets the duration. */ + public void setDuration (long samples); + + /** Sets the channel map locations. The array length must + * equal the number of channels. */ + public void setMapLocations (String[] locations); + + /* End of interface FaceRegion */ + } + + /****************************************************************** + * STATIC MEMBER CLASSES. + * + ******************************************************************/ + /** The implementation of the FormatRegion interface. The combination + * of a public interface and a private implementation is suggested + * in _Java in a Nutshell_. + */ + class FormatRegionImpl implements FormatRegion { + + private int _bitDepth; + private double _sampleRate; + private int _wordSize; + private String[] _bitrateReduction; + + public FormatRegionImpl () { + _bitDepth = NULL; + _sampleRate = NILL; + _wordSize = NULL; + _bitrateReduction = null; + } + + /** Returns bit depth. */ + public int getBitDepth () + { + return _bitDepth; + } + + /** Returns the bitrate reduction (compression information). + * This will be an array of seven strings (which may be + * empty but not null) interpreted respectively as follows: + *
    + *
  • 0: codecName + *
  • 1: codecNameVersion + *
  • 2: codecCreatorApplication + *
  • 3: codecCreatorApplicationVersion + *
  • 4: codecQuality + *
  • 5: dataRate + *
  • 6: dataRateMode + *
+ */ + public String[] getBitrateReduction () + { + return _bitrateReduction; + } + + /** Returns sample rate. */ + public double getSampleRate () + { + return _sampleRate; + } + + /** Returns word size. */ + public int getWordSize () + { + return _wordSize; + } + + /** Returns true if the FormatRegion contains only + * default values. */ + public boolean isEmpty () + { + return _bitDepth == NULL && + _sampleRate == NILL && + _wordSize == NULL; + } + + /** Sets bit depth. */ + public void setBitDepth (int bitDepth) + { + _bitDepth = bitDepth; + } + + /** Sets the bitrate reduction information to null (no compression). */ + public void clearBitrateReduction () + { + _bitrateReduction = null; + } + + /** Sets the bitrate reduction (compression type). */ + public void setBitrateReduction (String codecName, + String codecNameVersion, + String codecCreatorApplication, + String codecCreatorApplicationVersion, + String codecQuality, + String dataRate, + String dataRateMode) + { + _bitrateReduction = new String[7]; + _bitrateReduction[0] = codecName; + _bitrateReduction[1] = codecNameVersion; + _bitrateReduction[2] = codecCreatorApplication; + _bitrateReduction[3] = codecCreatorApplicationVersion; + _bitrateReduction[4] = codecQuality; + _bitrateReduction[5] = dataRate; + _bitrateReduction[6] = dataRateMode; + } + + /** Sets sample rate. */ + public void setSampleRate (double sampleRate) + { + _sampleRate = sampleRate; + } + + + /** Sets word size. */ + public void setWordSize (int wordSize) + { + _wordSize = wordSize; + } + + /* End of FormatRegionImpl */ + } + + /** The implementation of the TimeDesc interface. The combination + * of a public interface and a private implementation is suggested + * in _Java in a Nutshell_. + */ + class TimeDescImpl implements TimeDesc + { + private int _hours; + private int _minutes; + private int _seconds; + private int _frames; + private int _samples; + private double _sampleRate; + private int _frameCount; + + /* Constructor rewritten to avoid rounding errors when converting to + * TCF. Now uses integer remainder math instead of floating point. + * Changed the base unit from a double representing seconds to a long + * representing samples. Changed all existing calls (that I could find) + * to this method to accomodate this change. + * + * @author David Ackerman + */ + public TimeDescImpl (long samples) + { + long _sample_count = samples; + _frameCount = 30; + _sampleRate = _curFormatRegion.getSampleRate (); + + /* It seems that this method is initially called before a valid + * sample rate has been established, causing a divide by zero + * error. + */ + if (_sampleRate < 0) { + _sampleRate = 44100.0; //reasonable default value + } + + long sample_in_1_frame = (long)(_sampleRate/_frameCount); + long sample_in_1_second = sample_in_1_frame * _frameCount; + long sample_in_1_minute = sample_in_1_frame * _frameCount * 60; + long sample_in_1_hour = sample_in_1_frame * _frameCount * 60 * 60; + long sample_in_1_day = sample_in_1_frame * _frameCount * 60 * 60 * 24; + + // BWF allows for a negative timestamp but tcf does not, so adjust + // time accordingly + // this might be a good place to report a warning during validation + if (_sample_count < 0) { + _sample_count += sample_in_1_day; + _sample_count = (_sample_count % sample_in_1_day); + } + + _hours = (int)(_sample_count / sample_in_1_hour); + _sample_count -= (_hours * sample_in_1_hour); + _minutes = (int)(_sample_count / sample_in_1_minute); + _sample_count -= (_minutes * sample_in_1_minute); + _seconds = (int)(_sample_count / sample_in_1_second); + _sample_count -= (_seconds * sample_in_1_second); + _frames = (int)(_sample_count / sample_in_1_frame); + _sample_count -= (_frames * sample_in_1_frame); + _samples = (int)_sample_count; + + /* At present TCF does not have the ability to handle time stamps + * > midnight. Industry practice is to roll the clock forward to + * zero or back to 23:59:59:29... when crossing this boundary + * condition. + */ + _hours = _hours % 24; + } + + /** Returns the hours component. */ + public int getHours () { + return _hours; + } + + /** Returns the minutes component. */ + public int getMinutes () { + return _minutes; + } + + /** Returns the seconds component. */ + public int getSeconds () { + return _seconds; + } + + /** Returns the frames component of the fraction of a second. + * We always consider frames to be thirtieths of a second. */ + public int getFrames () { + return _frames; + } + + /** Returns the samples remaining after the frames part of + * the fractional second. */ + public int getSamples () { + return _samples; + } + + /** Returns the sample rate on which the samples remainder + * is based. */ + public double getSampleRate () { + return _sampleRate; + } + } /* End of TimeDescImpl */ + + /** The implementation of the Face interface. The combination + * of a public interface and a private implementation is suggested + * in _Java in a Nutshell_. + */ + class FaceImpl implements Face { + List _regionList; + TimeDesc _startTime; + TimeDesc _duration; + String _direction; + + + /** Constructor. Initially the duration is set + * to null, indicating unknown value. */ + public FaceImpl () + { + _regionList = new ArrayList (); + _startTime = new TimeDescImpl (0); + _duration = null; + } + + + /** Returns an indexed FaceRegion. */ + public FaceRegion getFaceRegion (int i) { + return (FaceRegion) _regionList.get (i); + } + + /** Adds a FaceRegion. This may be called repeatedly to + * add multiple FaceRegions. */ + public void addFaceRegion () { + _regionList.add (new FaceRegionImpl ()); + } + + /** Returns the starting time. Will be zero if not + * explicitly specified. */ + public TimeDesc getStartTime () { + return _startTime; + } + + /** Returns the duration. May be null if the duration + * is unspecified. */ + public TimeDesc getDuration () { + return _duration; + } + + /** Returns the direction. */ + public String getDirection () + { + return _direction; + } + + /** Sets the starting time. This will be converted + * into a TimeDesc. */ + public void setStartTime (long samples) + { + _startTime = new TimeDescImpl (samples); + } + + /** Sets the duration. This will be converted + * into a TimeDesc. */ + public void setDuration (long samples) + { + _duration = new TimeDescImpl (samples); + } + + /** Sets the direction. This must be one of the + * directionTypes. FORWARD is recommended for most + * or all cases. + */ + public void setDirection (String direction) + { + _direction = direction; + } + + /* End of FaceImpl */ + } + + + /** The implementation of the Face interface. The combination + * of a public interface and a private implementation is suggested + * in _Java in a Nutshell_. + */ + class FaceRegionImpl implements FaceRegion { + private TimeDesc _startTime; + private TimeDesc _duration; + private String[] _mapLocations; + + public FaceRegionImpl () + { + _startTime = new TimeDescImpl (0); + _duration = null; + } + + /** Returns the starting time. */ + public TimeDesc getStartTime () { + return _startTime; + } + + /** Returns the duration. */ + public TimeDesc getDuration () { + return _duration; + } + + /** Returns the channel map locations. The array length + * will equal the number of channels. */ + public String[] getMapLocations () + { + return _mapLocations; + } + + /** Sets the duration. This will be converted + * into a TimeDesc. */ + public void setStartTime (long samples) { + _startTime = new TimeDescImpl (samples); + } + + /** Sets the duration. This will be converted + * into a TimeDesc. */ + public void setDuration (long samples) + { + _duration = new TimeDescImpl (samples); + } + + /** Sets the channel map locations. The array length must + * equal the number of channels. */ + public void setMapLocations (String[] locations) + { + _mapLocations = locations; + } + /* End of FaceRegionImpl */ + } + + /* End of inner classes */ + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + * + * Accessor methods. + ******************************************************************/ + + /** Returns analog/digital flag. Value will always be + * "FILE_DIGITAL" in practice. */ + public String getAnalogDigitalFlag () + { + return _analogDigitalFlag; + } + + /** Returns application-specific data. We assume this is + * representable in String format. + */ + public String getAppSpecificData () + { + return _appSpecificData; + } + + /** Returns audio data encoding. */ + public String getAudioDataEncoding () + { + return _audioDataEncoding; + } + + /** Returns the bitrate reduction (compression information). + * This will be an array of seven strings (which may be + * empty, but should never be null) interpreted as follows: + *
    + *
  • 0: codecName + *
  • 1: codecNameVersion + *
  • 2: codecCreatorApplication + *
  • 3: codecCreatorApplicationVersion + *
  • 4: codecQuality + *
  • 5: dataRate + *
  • 6: dataRateMode + *
+ */ + public String[] getBitrateReduction () + { + return _curFormatRegion.getBitrateReduction(); + } + + /* Returns the sample rate. */ + public double getSampleRate () + { + return _curFormatRegion.getSampleRate (); + } + + /** Return the byte order: 0 = big-endian; 1 = little-endian. */ + public int getByteOrder () + { + return _byteOrder; + } + + /** Returns disposition. */ + public String getDisposition () + { + return _disposition; + } + + /** Gets the list of Faces. Normally there will be only one face + * in a digital file. */ + public List getFaceList () + { + return _faceList; + } + + /** Return the offset of the first byte of sample data. */ + public long getFirstSampleOffset () + { + return _firstSampleOffset; + } + + /** Returns format name. */ + public String getFormat () + { + return _format; + } + + /** Gets the list of Format Regions. Since one is created + * automatically on initialization, it's possible that the + * list will contain a Format Region with only default values. + * This should be checked with isEmpty (). + */ + public List getFormatList () + { + return _formatList; + } + + /** Returns the names of the map locations. + * The returned + * value is an array whose length equals the number of + * channels and whose elements correspond to channels 0, 1, + * etc. + */ + public String[] getMapLocations() { + return _curFace.getFaceRegion(0).getMapLocations(); + } + + /** Returns number of channels. */ + public int getNumChannels () + { + return _numChannels; + } + + /** Returns primary identifier. */ + public String getPrimaryIdentifier () + { + return _primaryIdentifier; + } + + /** Returns primary identifier type. */ + public String getPrimaryIdentifierType () + { + return _primaryIdentifierType; + } + + /** Returns schema version. */ + public String getSchemaVersion () + { + return _schemaVersion; + } + + /** Returns specification version of the document format. */ + public String getSpecificationVersion () + { + return _specificationVersion; + } + + /** Returns the use (role of the document). + * The value returned is an array of two strings, + * the useType and the otherType. */ + public String[] getUse () + { + return _use; + } + + + + + + /****************************************************************** + * Mutator methods. + ******************************************************************/ + + /** Sets the analog/digital flag. The value set should always + * be "FILE_DIGITAL". */ + public void setAnalogDigitalFlag (String flagType) + { + _analogDigitalFlag = flagType; + } + + /** Sets the bitrate reduction (compression type). */ + public void setBitrateReduction (String codecName, + String codecNameVersion, + String codecCreatorApplication, + String codecCreatorApplicationVersion, + String codecQuality, + String dataRate, + String dataRateMode) + { + _curFormatRegion.setBitrateReduction (codecName, + codecNameVersion, codecCreatorApplication, + codecCreatorApplicationVersion, + codecQuality, dataRate, dataRateMode); + } + + /** Set the bitrate reduction information to null (no compression). */ + public void clearBitrateReduction () + { + _curFormatRegion.clearBitrateReduction (); + } + + /** Sets the byte order. + * @param order Byte order: 0 = big-endian, 1 = little-endian + */ + public void setByteOrder (int order) + { + _byteOrder = order; + } + + /** Sets the byte order. + */ + public void setByteOrder (String order) + { + if (order.substring (0, 3).toLowerCase ().equals ("big")) { + _byteOrder = BIG_ENDIAN; + } + else if (order.substring (0, 6).toLowerCase ().equals ("little")) { + _byteOrder = LITTLE_ENDIAN; + } + } + + /** Sets the audio data encoding. */ + public void setAudioDataEncoding (String audioDataEncoding) + { + _audioDataEncoding = audioDataEncoding; + } + + /** Set the application-specific data. For present purposes, + * we assume this is representable as a text string. */ + public void setAppSpecificData (String data) + { + _appSpecificData = data; + } + + /** Sets the bit depth. */ + public void setBitDepth (int bitDepth) + { + _curFormatRegion.setBitDepth (bitDepth); + } + + /** Sets the disposition. */ + public void setDisposition (String disposition) + { + _disposition = disposition; + } + + /** Sets the direction. + * This must be one of the values + * FORWARD, REVERSE, A_WIND, B_WIND, C_WIND, D_WIND, + * FRONT, BACK. FORWARD may be the only one that + * makes sense for digital formats. + */ + public void setDirection (String direction) + { + _curFace.setDirection (direction); + } + + /** Sets the duration in samples. + * This affects the current face and its first FaceRegion. + */ + public void setDuration (long duration) + { + _curFace.setDuration (duration); + _curFace.getFaceRegion(0).setDuration (duration); + } + + /** Sets the offset of the first byte of sample data. */ + public void setFirstSampleOffset (long offset) + { + _firstSampleOffset = offset; + } + + /** Sets the format name. */ + public void setFormat (String format) + { + _format = format; + } + + /** Sets the array of channel map locations. The length + * of the array must equal the number of channels. */ + public void setMapLocations (String[] locations) { + _curFace.getFaceRegion(0).setMapLocations (locations); + } + + /** Sets the number of channels. */ + public void setNumChannels (int numChannels) + { + _numChannels = numChannels; + } + + /** Sets the primary identifier. */ + public void setPrimaryIdentifier (String primaryIdentifier) + { + _primaryIdentifier = primaryIdentifier; + } + + /** Sets the primary identifier type. If the primary identifier + * type is OTHER, use setOtherPrimaryIdentifierType instead. + */ + public void setPrimaryIdentifierType (String primaryIdentifierType) + { + _primaryIdentifierType = primaryIdentifierType; + } + + /** Sets the primary identifier type as "OTHER", and + * set the otherType. + */ + public void setOtherPrimaryIdentifierType (String otherType) + { + _primaryIdentifierType = "OTHER"; + _primaryIdentifierOtherType = otherType; + } + + /** Sets the sample rate. */ + public void setSampleRate (double sampleRate) + { + _curFormatRegion.setSampleRate (sampleRate); + } + + /** Sets the specification version of the document format.*/ + public void setSpecificationVersion (String specificationVersion) + { + _specificationVersion = specificationVersion; + } + + /** Sets the start time in samples. + * This affects the current face and its first FaceRegion. + */ + public void setStartTime (long samples) + { + _curFace.setStartTime (samples); + _curFace.getFaceRegion(0).setStartTime (samples); + } + + /** Sets the role of the document. Permitted values are + * ORIGINAL_MASTER, PRESERVATION_MASTER, PRODUCTION_MASTER, + * SERVICE, PREVIEW, or OTHER. + * If useType is "OTHER", then otherType + * is significant. Since OTHER is the only meaningful + * value for a digital document, the code assumes this will always + * be the case and uses otherType. */ + public void setUse (String useType, String otherType) + { + _use = new String[] {useType, otherType}; + } + + /** Sets the word size. */ + public void setWordSize (int wordSize) + { + _curFormatRegion.setWordSize (wordSize); + } + + /** Adds a FormatRegion object to a FormatSize list. + * The most recently added FormatRegion object will + * be filled in by setBitDepth, setSampleRate, and + * setWordSize. + */ + public void addFormatRegion () + { + _curFormatRegion = new FormatRegionImpl (); + _formatList.add (_curFormatRegion); + } + + /** Adds a Face. + */ + public void addFace () + { + _curFace = new FaceImpl (); + _faceList.add (_curFace); + _curFace.addFaceRegion(); + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/Agent.java b/jhove/classes/edu/harvard/hul/ois/jhove/Agent.java new file mode 100644 index 00000000..42a6a4dc --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/Agent.java @@ -0,0 +1,167 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +/** + * Encapsulates information about agents, either individual + * persons or corporate bodies. + */ +public class Agent +{ + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + private String _name; + private AgentType _type; + private String _address; + private String _telephone; + private String _fax; + private String _email; + private String _web; + private String _note; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * Creates an Agent given a name and an AgentType. + */ + public Agent (String name, AgentType type) + { + _name = name; + _type = type; + } + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + ******************************************************************/ + + /** + * Returns the value of the address property. + */ + public String getAddress () + { + return _address; + } + + /** + * Returns the value of the email property. + */ + public String getEmail () + { + return _email; + } + + /** + * Returns the value of the fax property. + */ + public String getFax () + { + return _fax; + } + + /** + * Returns the value of the name property. + */ + public String getName () + { + return _name; + } + + /** + * Returns the value of the note property. + */ + public String getNote () + { + return _note; + } + + /** + * Returns the value of the telephone property. + */ + public String getTelephone () + { + return _telephone; + } + + /** + * Returns the value of the type property. + */ + public AgentType getType () + { + return _type; + } + + /** + * Returns the value of the web property. + */ + public String getWeb () + { + return _web; + } + + /****************************************************************** + * Mutator methods. + ******************************************************************/ + + /** + * Sets the value of the address property. + */ + public void setAddress (String address) + { + _address = address; + } + + /** + * Sets the value of the email property. + */ + public void setEmail (String email) + { + _email = email; + } + + /** + * Sets the value of the fax property. + */ + public void setFax (String fax) + { + _fax = fax; + } + + /** + * Sets the value of the name property. + */ + public void setName (String name) + { + _name = name; + } + + /** + * Sets the value of the note property. + */ + public void setNote (String note) + { + _note = note; + } + + /** + * Sets the value of the telephone property. + */ + public void setTelephone (String telephone) + { + _telephone = telephone; + } + + /** + * Sets the value of the web property. + */ + public void setWeb (String web) + { + _web = web; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/AgentType.java b/jhove/classes/edu/harvard/hul/ois/jhove/AgentType.java new file mode 100644 index 00000000..43cda3ee --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/AgentType.java @@ -0,0 +1,60 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + + +/** + * This class defines enumerated types for an Agent. + * Applications will not create or modify AgentTypes, but will + * use one of the predefined AgentType instances COMMERCIAL, GOVERNMENT, + * EDUCATIONAL, NONPROFIT, STANDARD, or OTHER. + * + * @see Agent + */ +public final class AgentType + extends EnumerationType +{ + /****************************************************************** + * PUBLIC STATIC INSTANCES. + ******************************************************************/ + + /** + * Agent type for a commercial entity. + */ + public static final AgentType COMMERCIAL = new AgentType ("Commercial"); + /** + * Agent type for a governmental body. + */ + public static final AgentType GOVERNMENT = new AgentType ("Government"); + /** + * Agent type for an educational institution. + */ + public static final AgentType EDUCATIONAL = new AgentType ("Educational"); + /** + * Agent type for a non-profit organization. + */ + public static final AgentType NONPROFIT = new AgentType ("Non-profit"); + /** + * Agent type for a standards body. + */ + public static final AgentType STANDARD = new AgentType ("Standards body"); + /** + * Agent type that doesn't fit the other categories. + */ + public static final AgentType OTHER = new AgentType ("Other"); + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * Applications will never create AgentTypes directly. + **/ + private AgentType (String value) + { + super (value); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/AnalogDigitalFlagType.java b/jhove/classes/edu/harvard/hul/ois/jhove/AnalogDigitalFlagType.java new file mode 100644 index 00000000..515b5c33 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/AnalogDigitalFlagType.java @@ -0,0 +1,40 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +/** + * This class defines enumerated types for the analog/digital + * flag of AESAudioMetadata. + * Applications will not create or modify instances of this class, but will + * use one of the predefined AnalogDigitalFlagType instances. + * + * @author Gary McGath + * + */ +public class AnalogDigitalFlagType extends EnumerationType { + + /** Enumeration instance for analog data */ + public static final AnalogDigitalFlagType ANALOG = + new AnalogDigitalFlagType ("ANALOG"); + + /** Enumeration instance for physical digital data */ + public static final AnalogDigitalFlagType PHYS_DIGITAL = + new AnalogDigitalFlagType ("PHYS_DIGITAL"); + + /** Enumeration instance for FILE digital data */ + public static final AnalogDigitalFlagType FILE_DIGITAL = + new AnalogDigitalFlagType ("FILE_DIGITAL"); + + /** + * Applications will never create PropertyTypes directly. + **/ + private AnalogDigitalFlagType (String value) + { + super (value); + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/App.java b/jhove/classes/edu/harvard/hul/ois/jhove/App.java new file mode 100644 index 00000000..08990b42 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/App.java @@ -0,0 +1,128 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +//import java.io.*; +import java.util.*; + +/** + * The application class for JHOVE. One instance of this class is + * generated by the application. Various utility and informational + * methods are contained here. + */ +public class App +{ + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + /** Application build date. */ + private Date _date; + + /** Application name. */ + private String _name; + + /** Application release identifier. */ + private String _release; + + /** Application rights statement. */ + private String _rights; + + /** Application invocation syntax. */ + private String _usage; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * Creates an App object. + * @param name Name of the application. + * @param release Release identifier. + * @param date Last modification date of the application code, + * in the form of an array of three numbers. + * date[0] is the year, + * date[1] the month, and + * date[2] the day. + * @param usage String summarizing command line usage, to + * output when app name is typed with no + * arguments or flags. + * @param rights Copyright or other rights statement. + */ + public App (String name, String release, int [] date, String usage, + String rights) + { + Calendar calendar = new GregorianCalendar (); + calendar.set (date[0], date[1]-1, date[2]); + + _name = name; + _release = release; + _date = calendar.getTime (); + _usage = usage; + _rights = rights; + } + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + * + * Accessor methods. + ******************************************************************/ + + /** + * Returns application code creation date + */ + public Date getDate () + { + return _date; + } + + /** + * Returns the name of this application + */ + public String getName () + { + return _name; + } + + /** + * Returns the release identifier for this application. + */ + public String getRelease () + { + return _release; + } + + /** + * Returns the rights string. + */ + public String getRights () + { + return _rights; + } + + /** + * Returns the usage string, which should be output when the user + * enters a command line with only the application name. + */ + public String getUsage () + { + return _usage; + } + + /****************************************************************** + * Serialization methods. + ******************************************************************/ + + /** + * Outputs detailed information about the application, + * including configuration, available modules and handlers, + * etc. + */ + public void show (OutputHandler handler) + { + handler.show (this); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/ByteArrayXMPSource.java b/jhove/classes/edu/harvard/hul/ois/jhove/ByteArrayXMPSource.java new file mode 100644 index 00000000..92b96981 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/ByteArrayXMPSource.java @@ -0,0 +1,66 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +import java.io.*; + +/** + * Class for providing an InputSource to XMPHandler, + * with a ByteArrayInputStream as the basis of the + * InputSource. This is suitable for a number of modules. + * + * @author Gary McGath + * + */ +public class ByteArrayXMPSource extends XMPSource { + + /* The underlying ByteArrayInputStream. */ + ByteArrayInputStream _instrm; + + /** + * Constructor based on ByteArrayInputStream. + * + * @param instrm ByteArrayInputStream containing the XMP + */ + public ByteArrayXMPSource (ByteArrayInputStream instrm) + throws IOException + { + super (new InputStreamReader + (new XMLWrapperStream (instrm))); + _instrm = instrm; + // Prepare for resetting. + instrm.mark (instrm.available ()); + } + + + /** + * Constructor based on ByteArrayInputStream with encoding. + * + * @param instrm ByteArrayInputStream containing the XMP + */ + public ByteArrayXMPSource (ByteArrayInputStream instrm, + String encoding) + throws IOException + { + super (new InputStreamReader + (new XMLWrapperStream (instrm), encoding)); + _instrm = instrm; + // Prepare for resetting. + instrm.mark (instrm.available ()); + } + + + + /* (non-Javadoc) + * @see edu.harvard.hul.ois.jhove.XMPSource#resetReader() + */ + protected void resetReader() { + _instrm.reset (); + _reader = new InputStreamReader (_instrm); + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/Callback.java b/jhove/classes/edu/harvard/hul/ois/jhove/Callback.java new file mode 100644 index 00000000..708026c6 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/Callback.java @@ -0,0 +1,25 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +/** + * An interface for supporting a general, simple callback function. + */ +public interface Callback +{ + /** + * A generic callback function. Any class which needs to support + * callback can implement callback and pass a reference to itself + * to the function that does the callback. + * + * @param selector An indicator of the function to be performed. + * Interpretation is determined by the implementing class. + * @param parm Whatever data may be appropriate to the callback. + * + * @return As specified by the implementing class. + */ + public int callback (int selector, Object parm); +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/Checksum.java b/jhove/classes/edu/harvard/hul/ois/jhove/Checksum.java new file mode 100644 index 00000000..a7be0ffa --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/Checksum.java @@ -0,0 +1,93 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + + +/** + * This class encapsulates the result of calculations which provide a greater + * or lesser degree of confirmation of the integrity of a digital + * object's content, including checksums, CRC's, message digests, + * etc. + * + * @see ChecksumType + * @see Checksummer + */ +public class Checksum +{ + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + private ChecksumType _type; + private String _value; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * Creates a Checksum with a given value and type + */ + public Checksum (String value, ChecksumType type) + { + _value = value; + _type = type; + } + + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + * + * Accessor methods. + ******************************************************************/ + + /** + * Returns this Checksum's type + */ + public ChecksumType getType () + { + return _type; + } + + /** + * Returns this Checksum's value + */ + public String getValue () + { + return _value; + } + + /****************************************************************** + * Mutator methods. + ******************************************************************/ + + /** + * Sets the type of this Checksum + */ + public void setType (ChecksumType type) + { + _type = type; + } + + /** + * Sets the value of this Checksum + */ + public void setValue (String value) + { + _value = value; + } + + /****************************************************************** + * Put here as a convenience for checksum calculators + *****************************************************************/ + /** + * Maps unsigned byte value (0 to 256) to signed byte value (-128 to 127). + */ + public static byte unsignedByteToByte (int value) + { + return (byte) ((value < 127) ? value : value - 256); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/ChecksumInputStream.java b/jhove/classes/edu/harvard/hul/ois/jhove/ChecksumInputStream.java new file mode 100644 index 00000000..99255969 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/ChecksumInputStream.java @@ -0,0 +1,145 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003-2006 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +import java.io.IOException; +import java.io.*; + +/** + * A ChecksumInputStream is a FilterInputStream with the added + * functionality of calculating checksums as it goes. + * + * The idea of replacing this with java.util.zip.CheckedInputStream + * looks very tempting, but we need the byte count, which + * CheckedInputStream doesn't provide. + * + * @author Gary McGath + * + */ +public class ChecksumInputStream extends FilterInputStream { + + private InputStream subsumedStream; + private Checksummer _cksummer; + private long _nBytes; + + /** + * Constructor. + * + * @param stream Stream to be filtered + * @param cksummer Object to calculate checksum on the bytes + * as they are read + */ + public ChecksumInputStream(InputStream stream, Checksummer cksummer) { + super (stream); + subsumedStream = stream; + _cksummer = cksummer; + _nBytes = 0; + } + + /** + * Reads a byte from the subsumed stream, updating + * the byte count and the checksums. + */ + public int read() throws IOException { + int ch = subsumedStream.read (); + if (ch >= 0) { + _nBytes++; + if (_cksummer != null) { + _cksummer.update (ch); + } + } + return ch; + } + + /** + * Reads some number of bytes from the input stream and + * stores them into the buffer array b. The number of + * bytes actually read is returned as an integer. + * + * All bytes read are fed through the checksummer. + */ + public int read(byte[] b) throws IOException + { + int len = subsumedStream.read (b); + // Careful here -- don't want to add -1 bytes at EOF + if (len > 0) { + if (_cksummer != null) { + _cksummer.update (b); + } + _nBytes += len; + } + return len; + } + + /** + * Reads up to len bytes of data from the input stream + * into an array of bytes. An attempt is made to read as + * many as len bytes, but a smaller number may be read, + * possibly zero. The number of bytes actually read is + * returned as an integer. + * + * All bytes read are fed through the checksummer. + */ + public int read(byte[] b, int off, int len) throws IOException + { + len = subsumedStream.read (b, off, len); + // Careful here -- don't want to add -1 bytes at EOF + if (len > 0) { + if (_cksummer != null) { + _cksummer.update (b, off, len); + } + _nBytes += len; + } + return len; + } + + /** + * Skips n bytes. + * Reads them and feeds them through the checksummer. + */ + public long skip (long n) throws IOException { + long nret = 0; + while (n > 0) { + // grab the data in reasonable buffer-sized chunks. + int bufsize = (int) (n > 8192 ? 8192 : n); + byte[] buf = new byte[bufsize]; + int nread = read (buf); + if (nread <= 0) { + break; + } + nret += nread; + n -= nread; + } + return nret; + } + + /** + * Closes the subsumed stream. + */ + public void close () throws IOException + { + subsumedStream.close(); + } + + /** + * Returns the byte count to date on the stream. + * This returns the number of bytes read. + * Because of buffering, this is not a reliable + * indicator of how many bytes have actually been processed. + */ + public long getNBytes () + { + return _nBytes; + } + + /** + * Returns the Checksummer object. + */ + public Checksummer getChecksummer () + { + return _cksummer; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/ChecksumType.java b/jhove/classes/edu/harvard/hul/ois/jhove/ChecksumType.java new file mode 100644 index 00000000..d3d454ae --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/ChecksumType.java @@ -0,0 +1,37 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + + +/** + * This class defines enumerated types for a Checksum on a content + * stream or file. + * Applications will not create or modify ChecksumTypes, but will + * use one of the predefined ChecksumType instances + * CRC32, MD5, or SHA1. + * + * @see Checksum + */ +public final class ChecksumType + extends EnumerationType +{ + /** 32-bit Cyclical Redundancy Checksum. */ + public static final ChecksumType CRC32 = new ChecksumType ("CRC32"); + + /** 128-bit Message Digest 5. */ + public static final ChecksumType MD5 = new ChecksumType ("MD5"); + + /** 160-bit Secure Hash Algorithm. */ + public static final ChecksumType SHA1 = new ChecksumType ("SHA-1"); + + /** + * Applications will never create ChecksumTypes directly. + **/ + private ChecksumType (String value) + { + super (value); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/Checksummer.java b/jhove/classes/edu/harvard/hul/ois/jhove/Checksummer.java new file mode 100644 index 00000000..58dacb46 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/Checksummer.java @@ -0,0 +1,193 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003-2006 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; +import java.security.*; +import java.util.zip.*; + +/** + * The Checksummer class encapsulates the calculation of the + * CRC32, MD5, and SHA-1 checksums. + */ +public class Checksummer implements java.util.zip.Checksum +{ + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + /** Byte count. */ + protected long _nByte; + /** CRC32 object. */ + private CRC32 _crc32; + /** MD5 message digest. */ + private MessageDigest _md5; + /** SHA-1 message digest. */ + private MessageDigest _sha1; + + /** + * Creates a Checksummer, with instances of each of + * CRC32, MD5 MessageDigest, and SHA-1 MessageDigest. + * If one or both of the MessageDigests aren't supported + * on the current platform, they are left as null. + * + * @see CRC32 + * @see MessageDigest + */ + public Checksummer () + { + reset (); + } + + /** Resets all checksums and the byte count to their + * initial values. + */ + public void reset () + { + _nByte = 0; + _crc32 = new CRC32 (); + try { + _md5 = MessageDigest.getInstance ("MD5"); + _sha1 = MessageDigest.getInstance ("SHA-1"); + } + catch (NoSuchAlgorithmException e) { + } + } + + /** getValue is required by the Checksum interface, but + * we can return only one of the three values. We + * return the CRC32 value, since that's the one which + * is guaranteed to be available. + */ + public long getValue () + { + return _crc32.getValue (); + } + + /** + * Updates the checksum with the argument. + * Called when a signed byte is available. + */ + public void update (byte b) + { + _crc32.update (b); + if (_md5 != null) { + _md5.update (b); + } + if (_sha1 != null) { + _sha1.update (b); + } + } + + /** + * Updates the checksum with the argument. + * Called when an unsigned byte is available. + */ + public void update (int b) + { + byte sb; + if (b > 127) { + sb = (byte) (b - 256); + } + else { + sb = (byte) b; + } + update (sb); + } + + /** + * Updates the checksum with the argument. + * Called when a byte array is available. + */ + public void update (byte[] b) + { + _crc32.update (b); + if (_md5 != null) { + _md5.update (b); + } + if (_sha1 != null) { + _sha1.update (b); + } + } + + /** + * Updates the checksum with the argument. + * Called when a byte array is available. + */ + public void update (byte[] b, int off, int len) + { + _crc32.update (b, off, len); + if (_md5 != null) { + _md5.update (b, off, len); + } + if (_sha1 != null) { + _sha1.update (b, off, len); + } + } + + /** + * Returns the value of the CRC32 as a hex string. + */ + public String getCRC32 () + { + return padLeadingZeroes + (Long.toHexString (_crc32.getValue ()), 8); + } + + /** + * Returns the value of the MD5 digest as a hex string. + * Returns null if the digest is not available. + */ + public String getMD5 () + { + String value = null; + + if (_md5 != null) { + StringBuffer buffer = new StringBuffer (); + byte [] digest = _md5.digest (); + for (int i=0; i= 0) ? digest[i] : 256+digest[i]; + buffer.append (padLeadingZeroes + (Integer.toHexString (un), 2)); + } + value = buffer.toString (); + } + + return value; + } + + /** + * Returns the value of the SHA-1 digest as a hex string. + * Returns null if the digest is not available. + */ + public String getSHA1 () + { + String value = null; + + if (_sha1 != null) { + StringBuffer buffer = new StringBuffer (); + byte [] digest = _sha1.digest (); + for (int i=0; i= 0) ? digest[i] : 256+digest[i]; + buffer.append (padLeadingZeroes + (Integer.toHexString (un), 2)); + } + value = buffer.toString (); + } + + return value; + } + + /** Pad a hexadecimal (or other numeric) string out to + * the specified length with leading zeroes. */ + private String padLeadingZeroes (String str, int len) + { + // This is optimized for adding just one leading zero + // or none, which will be the usual case. + while (str.length () < len) { + str = "0" + str; + } + return str; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/ConfigHandler.java b/jhove/classes/edu/harvard/hul/ois/jhove/ConfigHandler.java new file mode 100644 index 00000000..c9d9a702 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/ConfigHandler.java @@ -0,0 +1,395 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.*; +import org.xml.sax.*; + +/** + * SAX Parser for the configuration file. + */ +public class ConfigHandler + extends org.xml.sax.helpers.DefaultHandler +{ + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + private String _class; + protected StringBuffer _content; + private Map _extension; + + /** The schema name */ + private final static String configSchemaName = + "jhoveConfig.xsd"; + + /** The list of handlers. Each element in the List is an + * array of two Strings representing the class and the initialization + * string. */ + private List _handler; + + /** The list of handler parameters. Each element in the List is + * a List of Strings (which may be empty but not null) representing + * parameters to be passed to the module. List elements are in + * one-to-one correspondence with _handler. + */ + protected List> _handlerParams; + private String _init; + private List _param; + private String _tempDir; + private String _mixVsn; + private String _encoding; + private String _logLevel; + private int _bufferSize; + private String _jhoveHome; + private int _sigBytes; + + protected boolean _isHandler; + + /* _isModule is protected rather than private so that subclasses + * can add elements to the module element. */ + protected boolean _isModule; + + private boolean _isTempDir; + private boolean _isMixVsn; + private boolean _isEncoding; + private boolean _isBufferSize; + private boolean _isJhoveHome; + private boolean _isLogLevel; + private boolean _isSigBytes; + + /** The list of modules. Each element in the List is an + * array of two Strings representing the class and the initialization + * string. */ + protected List _module; + + /** The list of module parameters. Each element in the List is + * a List of Strings (which may be empty but not null) representing + * parameters to be passed to the module. List elements are in + * one-to-one correspondence with _module. + */ + protected List> _modParams; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * Creates a ConfigHandler. + */ + public ConfigHandler () + { + _module = new ArrayList (); + _handler = new ArrayList (); + _modParams = new ArrayList> (); + _handlerParams = new ArrayList> (); + + _isModule = false; + _isHandler = false; + _isTempDir = false; + _isEncoding = false; + _isBufferSize = false; + _isJhoveHome = false; + _isLogLevel = false; + + _bufferSize = -1; + _encoding = null; + _extension = new Hashtable (); + _tempDir = null; + _mixVsn = null; + _sigBytes = 1024; + _logLevel = null; + } + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + * + * Accessor methods. + ******************************************************************/ + + /** + * Returns the List of Modules specified by the config file. + * Each element of the List is a String[2] whose elements are + * the module class name and initialization value. + * + * @see Module + */ + public List getModule () + { + return _module; + } + + /** + * Returns the List of module parameters specified by the config file. + * Each element of the List is a List (possibly empty) of Strings + * whose elements are parameters to pass to the module. The + * values returned by getModuleParams() are in + * one-to-one correspondence with those return by getModule(). + */ + public List> getModuleParams () + { + return _modParams; + } + + /** + * Returns the List of handler parameters specified by the config file. + * Each element of the List is a List (possibly empty) of Strings + * whose elements are parameters to pass to the output handler. The + * values returned by getHandlerParams() are in + * one-to-one correspondence with those return by getHandler(). + */ + public List> getHandlerParams () + { + return _handlerParams; + } + + /** + * Returns the List of OutputHandlers specified by the config file. + * + * @see OutputHandler + */ + public List getHandler () + { + return _handler; + } + + /** + * Returns the temporary directory path specified by the config file, + * with final path separator. + */ + public String getTempDir () + { + return _tempDir; + } + + /** Returns the MIX schema version specified by the config file. + * Acceptable values are "0.2" and "1.0" and "2.0". + */ + public String getMixVsn () + { + return _mixVsn; + } + + /** Returns the number of bytes to examine when looking for an + * indefinitely positioned signature, or checking the first + * sigBytes bytes of a file in lieu of a signature. + */ + public int getSigBytes () + { + return _sigBytes; + } + + /** + * Returns the character encoding specified by the config file. + */ + public String getEncoding () + { + return _encoding; + } + + /** + * Returns the buffer size specified in the config file. + * + * @return the buffer size, or -1 if none specified + */ + public int getBufferSize () + { + return _bufferSize; + } + + /** + * Return an associative map of configuration extensions. + */ + public Map getExtensions () + { + return _extension; + } + + /** + * Returns the path to the application's home directory, + * with final path separator. + */ + public String getJhoveHome () + { + return _jhoveHome; + } + + /** + * Returns the name of the desired log level. This should be the + * name of one of the predefined values of java.util.logging.Level, + * e.g., "WARNING", "INFO", "ALL". The default level is SEVERE. + */ + public String getLogLevel () + { + return _logLevel; + } + + + /****************************************************************** + * SAX parser methods. + ******************************************************************/ + + /** + * SAX parser callback method. + */ + public void startElement (String namespaceURI, String localName, + String rawName, Attributes atts) + throws SAXException + { + _content = new StringBuffer (); + + if (rawName.equals ("module")) { + _isModule = true; + _init = null; + _param = new ArrayList (1); + _class = null; + } + else if (rawName.equals ("outputHandler")) { + _isHandler = true; + _init = null; + _param = new ArrayList (1); + _class = null; + } + else if (rawName.equals ("tempDirectory")) { + _isTempDir = true; + } + else if (rawName.equals ("mixVersion")) { + _isMixVsn = true; + } + else if (rawName.equals ("defaultEncoding")) { + _isEncoding = true; + } + else if (rawName.equals ("bufferSize")) { + _isBufferSize = true; + } + else if (rawName.equals ("jhoveHome")) { + _isJhoveHome = true; + } + else if (rawName.equals ("logLevel")) { + _isLogLevel = true; + } + else if (rawName.equals ("sigBytes")) { + _isSigBytes = true; + } + } + + /** + * SAX parser callback method. + */ + public void characters (char [] ch, int start, int length) + throws SAXException + { + _content.append (ch, start, length); + } + + /** + * SAX parser callback method. + */ + public void endElement (String namespaceURI, String localName, + String rawName) + throws SAXException + { + if (_isModule) { + if (rawName.equals ("class")) { + _class = _content.toString (); + } + else if (rawName.equals ("init")) { + _init = _content.toString (); + } + else if (rawName.equals ("param")) { + _param.add (_content.toString ()); + } + else if (rawName.equals ("module")) { + ModuleInfo modInfo = new ModuleInfo( _class, _init); + _module.add (modInfo); + _modParams.add (_param); + _isModule = false; + } + } + else if (_isHandler) { + if (rawName.equals ("class")) { + _class = _content.toString (); + } + else if (rawName.equals ("init")) { + _init = _content.toString (); + } + else if (rawName.equals ("param")) { + _param.add (_content.toString ()); + } + else if (rawName.equals ("outputHandler")) { + String [] tuple = { _class, _init }; + _handler.add (tuple); + _handlerParams.add (_param); + _isHandler = false; + } + } + else if (_isTempDir) { + _tempDir = _content.toString ().trim (); + _isTempDir = false; + } + else if (_isMixVsn) { + _mixVsn = _content.toString ().trim (); + _isMixVsn = false; + } + else if (_isSigBytes) { + try { + _sigBytes = Integer.parseInt (_content.toString ().trim ()); + } + catch (NumberFormatException e) {} + _isSigBytes = false; + } + else if (_isEncoding) { + _encoding = _content.toString ().trim (); + _isEncoding = false; + } + else if (_isJhoveHome) { + _jhoveHome = _content.toString ().trim (); + _isJhoveHome = false; + } + else if (_isLogLevel) { + _logLevel = _content.toString ().trim (); + _isLogLevel = false; + } + else if (_isBufferSize) { + try { + _bufferSize = Integer.parseInt (_content.toString ().trim ()); + } + catch (NumberFormatException e) { + /* Just ignore a malformed number */ + } + _isBufferSize = false; + } + else if (!rawName.equals ("jhoveConfig")) { + _extension.put (rawName, _content.toString ().trim ()); + } + } + + /** EntityResolver designed to locate the config schema. It tries to find it + * as a local resource. + * + * It appears that not all SAX implementations will actually call this + * function for schema resolution, so this isn't a guarantee that the + * schema in the config file won't be called directly. But hopefully + * it will cut down on the burden on the server with the official + * schema copy. + */ + public InputSource resolveEntity (String publicId, String systemId) + throws SAXException, IOException { + if (systemId.endsWith (configSchemaName)) { + try { + URL resURL = this.getClass().getResource("jhoveConfig.xsd"); + InputStream strm = resURL.openStream (); + return new InputSource (strm); + } + catch (Exception e) {} + } + // If we couldn't get the local resource, use default location methods + return super.resolveEntity (publicId, systemId); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/ConfigWriter.java b/jhove/classes/edu/harvard/hul/ois/jhove/ConfigWriter.java new file mode 100644 index 00000000..97718feb --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/ConfigWriter.java @@ -0,0 +1,212 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +import java.io.*; +import java.util.*; +import javax.swing.*; + +import edu.harvard.hul.ois.jhove.viewer.ConfigWindow; + +/** + * + * Class to write out configuration information to the configuration file. + * To minimize the chance of getting into a bad state, it writes to a + * temporary file, then replaces the old config file with that file, + * rather than directly overwriting the existing file. + * + * @author Gary McGath + * + */ +public class ConfigWriter { + + + private PrintWriter _confOut; + private File _tempFile; + private File _confFile; + ConfigWindow _parent; + + /** + * Constructor. + * Creates a temporary file for writing and creates an OutputStreamWriter + * to write to it. If there is already a file located by + * file, it will not be replaced or overwritten + * until writeFile has successfully written out + * the temporary file. + * + * @param file Location of the configuration file + * + * @param parent The ConfigWindow which invoked this instance. + * May be null if invoked to write a default config file. + */ + public ConfigWriter (File file, ConfigWindow parent) throws IOException + { + _confFile = file; + _parent = parent; + // Set up a temporary file to write to. + String path = file.getParent(); + file.getParentFile().mkdirs(); // Make sure the directory exists + _tempFile = File.createTempFile ("jho", ".conf", new File (path)); + //_tempFile.createNewFile(); + FileOutputStream ostrm = new FileOutputStream (_tempFile); + OutputStreamWriter osw = new OutputStreamWriter (ostrm, "UTF-8"); + _confOut = new PrintWriter (osw); + } + + + /** + * Writes out the content of the file to the temporary file, + * then deletes the existing configuration file (as specified + * by the constructor parameter) and renames the temporary file + * to the configuration file. + * + * If the temporary file can't be written, or the configuration + * file can't be replaced, a warning dialog is put up and the + * configuration file remains unchanged. + */ + public void writeFile (List modules, + List handlers, + File homeDir, + File tempDir, + String encoding, + int bufferSize) throws IOException + { + writeHead (); + + // Write the home and temp directories. Home must always be valid. + _confOut.println (" " + + encodeContent (homeDir.getPath ()) + + ""); + + // Write out the encoding + if (encoding != null && encoding.length() > 0) { + _confOut.println (" " + + encodeContent (encoding) + ""); + } + + + if (tempDir != null) { + _confOut.println (" " + + encodeContent (tempDir.getPath ()) + + ""); + } + + // Write the buffer size if not default + if (bufferSize > 0) { + _confOut.println (" " + bufferSize + + ""); + } + + // Write out the modules + ListIterator iter = modules.listIterator (); + while (iter.hasNext ()) { + ModuleInfo minfo = iter.next (); + // The class must be non-null, but init may be null. + // If the class is empty, it's a user error (probably + // clicked "Add" and then lost track of it). Don't + // write it out. + if (!"".equals (minfo.clas)) { + _confOut.println (" "); + _confOut.println (" " + encodeContent (minfo.clas) + + ""); + if (minfo.init != null && minfo.init.length () > 0) { + _confOut.println (" " + encodeContent (minfo.init) + + ""); + } + /** tuple[2] and beyond are parameters */ + if (minfo.params != null) { + for (int i = 0; i < minfo.params.length; i++) { + _confOut.println (" " + encodeContent(minfo.params[i]) + + ""); + } + } + _confOut.println (" "); + } + } + + // Write out the handlers + ListIterator hiter = handlers.listIterator (); + while (hiter.hasNext ()) { + String handler = hiter.next ()[0]; + if (handler.length() > 0) { // Don't write out blank handler names + _confOut.println (" "); + _confOut.println (" " + encodeContent (handler) + + ""); + _confOut.println (" "); + } + } + + writeTail (); + _confOut.close (); + + // Replace the old file with the new. + if (_confFile.exists () && !_confFile.delete ()) { + if (_parent != null) { + JOptionPane.showMessageDialog(_parent, + "Can't replace old config file", + "Error", + JOptionPane.ERROR_MESSAGE); + } + _tempFile.delete (); + } + else { + _tempFile.renameTo (_confFile); + } + } + + /* Write the fixed lines which begin the config file */ + private void writeHead () throws IOException + { + _confOut.println(""); + _confOut.println(""); + } + + /* Write out the fixed end of the config file */ + private void writeTail () throws IOException + { + _confOut.println(""); + } + + + /** + * Encodes a content String in XML-clean form, converting characters + * to entities as necessary. The null string will be + * converted to an empty string. + */ + private static String encodeContent (String content) + { + if (content == null) { + content = ""; + } + StringBuffer buffer = new StringBuffer (content); + + int n = 0; + while ((n = buffer.indexOf ("&", n)) > -1) { + buffer.insert (n+1, "amp;"); + n +=5; + } + n = 0; + while ((n = buffer.indexOf ("<", n)) > -1) { + buffer.replace (n, n+1, "<"); + n += 4; + } + n = 0; + while ((n = buffer.indexOf (">", n)) > -1) { + buffer.replace (n, n+1, ">"); + n += 4; + } + + return buffer.toString (); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/CountedInputStream.java b/jhove/classes/edu/harvard/hul/ois/jhove/CountedInputStream.java new file mode 100644 index 00000000..6bc1c035 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/CountedInputStream.java @@ -0,0 +1,114 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +import java.io.*; + +/** + * A FilterInputStream which passes only a specified + * number of bytes, then returns an EOF condition. + * + * @author Gary McGath + * + */ +public class CountedInputStream extends FilterInputStream { + + private int bytesLeft; + + /** + * @param instrm The InputStream being counted + * @param count The number of bytes to be allowed + */ + public CountedInputStream(InputStream instrm, int count) { + super(instrm); + bytesLeft = count; + } + + + /** Reads a single byte from the stream and decrements + * the count of remaining bytes. If the count + * is exhausted, returns -1 to signify end of file. */ + public int read() throws IOException { + if (bytesLeft <= 0) { + return -1; + } + else { + int ch = super.read (); + if (ch != -1) { + --bytesLeft; + } + return ch; + } + } + + + /** + * Reads some number of bytes from the input stream and + * stores them into the buffer array b. The number of + * bytes actually read is returned as an integer. + * + * The number of bytes read will not exceed the number + * of bytes remaining in the count. The count is + * decremented by the number of bytes actually read. + */ + public int read(byte[] b) throws IOException + { + int len = b.length; + int bytesRead; + if (len <= bytesLeft) { + // Limit doesn't affect us, do normal read + bytesRead = super.read (b); + } + else { + // Limit the read to bytesLeft + bytesRead = super.read (b, 0, bytesLeft); + } + bytesLeft -= bytesRead; + return bytesRead; + } + + + /** + * Reads up to len bytes of data from the input stream + * into an array of bytes. An attempt is made to read as + * many as len bytes, but a smaller number may be read, + * possibly zero. The number of bytes actually read is + * returned as an integer. + * + * The number of bytes read will not exceed the number + * of bytes remaining in the count. The count is + * decremented by the number of bytes actually read. + */ + public int read(byte[] b, int off, int len) throws IOException + { + int bytesRead; + if (len <= bytesLeft) { + // Limit doesn't affect us, do normal read + bytesRead = super.read (b, off, len); + } + else { + bytesRead = super.read (b, off, bytesLeft); + } + bytesLeft -= bytesRead; + return bytesRead; + } + + /** + * Skips n bytes. + * Decrements the count by the number of bytes + * actually skipped. + */ + public long skip (long n) throws IOException { + long bytesRead = super.skip (n); + if (bytesLeft < bytesRead) { + bytesLeft = 0; + } + else { + bytesLeft -= bytesRead; + } + return bytesRead; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/DefaultConfigurationBuilder.java b/jhove/classes/edu/harvard/hul/ois/jhove/DefaultConfigurationBuilder.java new file mode 100644 index 00000000..0123cae8 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/DefaultConfigurationBuilder.java @@ -0,0 +1,128 @@ +package edu.harvard.hul.ois.jhove; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import edu.harvard.hul.ois.jhove.module.*; + +/** This class creates a default configuration if no valid configuration file + * is found. */ +public class DefaultConfigurationBuilder { + + private final static String FILE_SEP = System.getProperty ("file.separator"); + private final static String HOME_DIR = System.getProperty ("user.home"); + private final static String JHOVE_DIR = HOME_DIR + FILE_SEP + "jhove"; + private final static String TEMP_DIR = System.getProperty("java.io.tmpdir"); + private final static String DEFAULT_ENCODING = "utf-8"; + private final static int DEFAULT_BUFFER_SIZE = 131072; + + private File configFile; + + + /** Constructor. A location for the file may be specified or + * left null, */ + public DefaultConfigurationBuilder (File location) { + if (location != null) { + configFile = location; + } + else { + configFile = new File (JHOVE_DIR + + FILE_SEP + "conf" + + FILE_SEP + "jhove.conf"); + } + } + + + public void writeDefaultConfigFile () throws IOException { +// if (TEMP_DEBUG) { +// String configFileName = "null"; +// if (configFile != null) +// configFileName = configFile.getAbsolutePath(); +// System.out.println ("writeDefaultConfigFile: path is " + configFileName); +// } + ConfigWriter cw = new ConfigWriter (configFile, null); + List modules = getModules(); + // TextHandler, XmlHandler, and AuditHandler are loaded by + // default, so there are no handlers to put in the config file. + List handlers = new ArrayList (); + File homeDir = new File (JHOVE_DIR); + File tempDir = new File (TEMP_DIR); + try { + cw.writeFile(modules, handlers, homeDir, tempDir, + DEFAULT_ENCODING, DEFAULT_BUFFER_SIZE); + } + catch (IOException e) { +// if (TEMP_DEBUG) +// e.printStackTrace(); + throw e; + } + } + +// public void writeDefaultConfigFile () throws IOException { +// ConfigWriter cw = new ConfigWriter (configFile, null); +// List modules = getModules(); +// // TextHandler, XmlHandler, and AuditHandler are loaded by +// // default, so there are no handlers to put in the config file. +// List handlers = new ArrayList (); +// File homeDir = new File (JHOVE_DIR); +// File tempDir = new File (TEMP_DIR); +// cw.writeFile(modules, handlers, homeDir, tempDir, +// DEFAULT_ENCODING, DEFAULT_BUFFER_SIZE); +// } + + public File getConfigFile () { + return configFile; + } + + + protected List getModules () { + int nModules = builtInModules.length; + ArrayList mods = new ArrayList (nModules); + try { + for (int i = 0; i < nModules; i++) { + Class cls = builtInModules[i]; + ModuleInfo minfo = new ModuleInfo(cls.getName()); + minfo.init = null; // Never used at present + minfo.params = getDefaultConfigParameters (cls); + mods.add(minfo); + } + } + catch (Exception e) {} + return mods; + } + + + /** We can't have a static method in an Interface and override it, so we + * have to get a bit ugly to fake static inheritance. The advantage of + * this is that only the Modules with non-null default config file + * parameters have to implement the defaultConfigParams static field. */ + protected String[] getDefaultConfigParameters (Class c) { + try { + Field dcpField = c.getField("defaultConfigParams"); + return (String[]) dcpField.get(null); + } + catch (Exception e) { + return new String [] {}; + } + } + + /** The array of build-in modules. If a module is added to or removed from + * the build, this array must be changed. The Bytestream module is + * loaded by default and so is not listed here. */ + private Class[] builtInModules = { + AiffModule.class, + AsciiModule.class, + GifModule.class, + HtmlModule.class, + Jpeg2000Module.class, + JpegModule.class, + PdfModule.class, + TiffModule.class, + Utf8Module.class, + WaveModule.class, + XmlModule.class + }; +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/Document.java b/jhove/classes/edu/harvard/hul/ois/jhove/Document.java new file mode 100644 index 00000000..2a8b9921 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/Document.java @@ -0,0 +1,194 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +import java.util.*; + +/** + * This class encapsulates information about format specification documents. + * + * @see DocumentType + */ +public class Document +{ + private List _author; + private String _date; + private String _edition; + private String _enum; + private List _identifier; + private String _note; + private String _pages; + private List _publisher; + private String _title; + private DocumentType _type; + + /** + * Creates a Document with a given title and one of the predefined + * DocumentTypes. + */ + public Document (String title, DocumentType type) + { + _title = title; + _type = type; + + _author = new ArrayList (); + _identifier = new ArrayList (); + _publisher = new ArrayList (); + } + + /** + * Returns a List of Agents, each representing an author of this + * Document. + * Returns an empty list if no authors have been listed. + * + * @see Agent + */ + public List getAuthor () + { + return _author; + } + + /** + * Returns the date of this Document + */ + public String getDate () + { + return _date; + } + + /** + * Returns informaton on the edition of this Document + */ + public String getEdition () + { + return _edition; + } + + /** + * Returns the enumeration (e.g., serial volume and number) + * of this Document + */ + public String getEnumeration () + { + return _enum; + } + + /** + * Returns the list of formal Identifiers for this Document. + * If no Identifiers are given, returns an empty list. + */ + public List getIdentifier () + { + return _identifier; + } + + /** + * Returns the note associated with this Document + */ + public String getNote () + { + return _note; + } + + /** + * Returns pagination information for this Document + */ + public String getPages () + { + return _pages; + } + + /** + * Returns a List of Agents, each representing a publisher of this + * Document. If no publishers are listed, returns an empty list. + */ + public List getPublisher () + { + return _publisher; + } + + /** + * Returns the title of this Document + */ + public String getTitle () + { + return _title; + } + + /** + * Returns one of the predefined DocumentTypes as the type of + * this Document + */ + public DocumentType getType () + { + return _type; + } + + /** + * Adds an author to the list of authors + */ + public void setAuthor (Agent author) + { + _author.add (author); + } + + /** + * Sets the date of this Document + */ + public void setDate (String date) + { + _date = date; + } + + /** + * Sets edition information for this Document + */ + public void setEdition (String edition) + { + _edition = edition; + } + + /** + * Sets enumeration information (e.g., serial volume and number) + * for this Document + */ + public void setEnumeration (String enm) + { + _enum = enm; + } + + /** + * Adds an Identifier to the list of identifiers + */ + public void setIdentifier (Identifier identifier) + { + _identifier.add (identifier); + } + + /** + * Sets a note giving additional information about this Document + */ + public void setNote (String note) + { + _note = note; + } + + /** + * Sets pagination information for this Document + */ + public void setPages (String pages) + { + _pages = pages; + } + + /** + * Adds a publisher to the list of publishers + */ + public void setPublisher (Agent publisher) + { + _publisher.add (publisher); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/DocumentType.java b/jhove/classes/edu/harvard/hul/ois/jhove/DocumentType.java new file mode 100644 index 00000000..1676fe1f --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/DocumentType.java @@ -0,0 +1,61 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + + +/** + * This class defines enumerated types for a Document. + * Applications will not create or modify DocumentTypes, but will + * use one of the predefined DocumentType instances + * ARTICLE, BOOK, REPORT, RFC, STANDARD, WEB, or OTHER. + * + * @see Document + * + */ +public final class DocumentType + extends EnumerationType +{ + /** + * Document type for a printed article. + */ + public static final DocumentType ARTICLE = new DocumentType ("Article"); + /** + * Document type for an book. + */ + public static final DocumentType BOOK = new DocumentType ("Book"); + /** + * Document type for a report. + */ + public static final DocumentType REPORT = new DocumentType ("Report"); + /** + * Document type for an IETF Request for Comment. + */ + public static final DocumentType RFC = new DocumentType ("RFC"); + /** + * Document type for a standards body publication. + */ + public static final DocumentType STANDARD = new DocumentType ("Standard"); + /** + * Document type for a Web page. + */ + public static final DocumentType WEB = new DocumentType ("Web"); + /** + * Document type that doesn't fit the other categories. + */ + public static final DocumentType OTHER = new DocumentType ("Other"); + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * Applications will never create DocumentTypes directly. + **/ + private DocumentType (String value) + { + super (value); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/Dump.java b/jhove/classes/edu/harvard/hul/ois/jhove/Dump.java new file mode 100644 index 00000000..990886ea --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/Dump.java @@ -0,0 +1,134 @@ +/********************************************************************** + * JHOVE - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by the President and Fellows of Harvard College + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +import java.io.*; + +/** + * Common methods for dump utilities. + */ +public class Dump +{ + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + public Dump () + { + } + + /****************************************************************** + * PUBLIC CLASS METHODS. + ******************************************************************/ + + /** + * Return leading characters to pad out the byte offset to field width. + * @param os Byte offset + * @param width Field width + * @return String of leading zeros + */ + protected static String leading (int os, int width) + { + return leading (os, width, '0'); + } + + /** + * Return leading characters to pad out the byte offset to field width. + * @param os Byte offset + * @param width Field width + * @return String of leading zeros + */ + protected static String leading (long os, int width) + { + return leading (os, width, '0'); + } + + /** + * Return leading characters to pad out the byte offset to field width. + * @param os Byte offset + * @param width Field width + * @param pad Padding character + * @return String of leading characters + */ + protected static String leading (int os, int width, char pad) + { + return leading ((long) os, width, pad); + } + + /** + * Return leading characters to pad out the byte offset to field width. + * @param os Byte offset + * @param width Field width + * @param pad Padding character + * @return String of leading characters + */ + protected static String leading (long os, int width, char pad) + { + String ss = Long.toString (os); + StringBuffer buffer = new StringBuffer (); + for (int j=0; jpublic final static instances of the subclass, + * within the subclass definition, as there are enumeration cases. + * No other instances of an EnumerationType should ever be created, + * and the only operations which should ever be performed on an + * EnumerationType are assignment and equality testing. + * + */ +public abstract class EnumerationType +{ + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + /** Enumeration value. */ + private String _value; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * Instantiate an EnumerationType object. + * @param value Enumeration value + */ + protected EnumerationType (String value) + { + _value = value; + } + + /** + * Private no-argument constructor to close off the default + * constructor. + */ + private EnumerationType () + { + } + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + ******************************************************************/ + + /** + * Type equality test. + * @param enm Enumerated type + * @return True, if equal + */ + public boolean equals (EnumerationType enm) + { + return this == enm; + } + + /** + * Return enumeration value. + * @return Value + */ + public String toString () + { + return _value; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/ErrorMessage.java b/jhove/classes/edu/harvard/hul/ois/jhove/ErrorMessage.java new file mode 100644 index 00000000..51104ab6 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/ErrorMessage.java @@ -0,0 +1,58 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +/** + * This class encapsulates an error message from a Module, representing + * a problem in the content being analyzed. + */ +public class ErrorMessage + extends Message +{ + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * Create an ErrorMessage. + * @param message Human-readable string describing the problem. + */ + public ErrorMessage (String message) + { + super (message); + } + + /** + * Create an ErrorMessage. + * @param message Human-readable string describing the problem. + * @param offset The offset in the file at which the problem + * was detected. + */ + public ErrorMessage (String message, long offset) + { + super (message, offset); + } + /** + * Create an ErrorMessage. + * @param message Human-readable string describing the problem. + */ + public ErrorMessage (String message, String subMessage) + { + super (message, subMessage); + } + + /** + * Create an ErrorMessage. + * @param message Human-readable string describing the problem. + * @param subMessage Human-readable additional information. + * @param offset The offset in the file at which the problem + * was detected. + */ + public ErrorMessage (String message, String subMessage, long offset) + { + super (message, subMessage, offset); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/ExternalSignature.java b/jhove/classes/edu/harvard/hul/ois/jhove/ExternalSignature.java new file mode 100644 index 00000000..206966b7 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/ExternalSignature.java @@ -0,0 +1,60 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003-4 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +/** + * This class encapsulates information about external format signatures. + * The value of a Signature may be either a String or a byte array + * (stored as an int array to avoid signed byte problems). + */ +public class ExternalSignature + extends Signature +{ + /** + * Creates an ExternalSignature given a string value, a type, + * and a use requirement. + */ + public ExternalSignature (String value, SignatureType type, + SignatureUseType use) + { + super (value, type, use); + } + + /** + * Creates an ExternalSignature given a byte array, a type, + * and a use requirement. + */ + public ExternalSignature (int[] value, SignatureType type, + SignatureUseType use) + { + super (value, type, use); + } + + /** + * Creates an ExternalSignature given a string value, a type, + * a use requirement, and a note. + */ + public ExternalSignature (String value, SignatureType type, + SignatureUseType use, + String note) + { + super (value, type, use, note); + } + + + /** + * Creates an ExternalSignature given a byte array, a type, + * a use requirement, and a note. + */ + public ExternalSignature (int[] value, SignatureType type, + SignatureUseType use, + String note) + { + super (value, type, use, note); + } + + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/HandlerBase.java b/jhove/classes/edu/harvard/hul/ois/jhove/HandlerBase.java new file mode 100644 index 00000000..39ca94bf --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/HandlerBase.java @@ -0,0 +1,812 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +import java.io.*; +import java.text.*; +import java.util.*; +import java.util.logging.*; + +/** + * Abstract base class for Jhove output handlers. + * Output handlers should normally subclass HandlerBase. + */ +public abstract class HandlerBase + implements OutputHandler +{ + /****************************************************************** + * PUBLIC CLASS FIELDS. + ******************************************************************/ + + /** + * A DateFormat for representing a Date in yyyy-MM-dd + * (e.g., 2003-07-31) format. + */ + public static SynchronizedDateFormat date = + new SynchronizedDateFormat ("yyyy-MM-dd"); + + /** + * A DateFormat for representing a Date in yyyy-MM-dd HH:mm:ss z + * (e.g., 2003-07-31 15:31:12 EDT) format. + */ + public static SynchronizedDateFormat dateTime = + new SynchronizedDateFormat ("yyyy-MM-dd HH:mm:ss z"); + + /** + * A DateFormat for representing a Date in ISO 8601 + * (e.g., 2003-07-31T15:31:12-0400) format. + * We subclass SimpleDateFormat to make it thread-safe. + */ + public static SynchronizedDateFormat iso8601 = + new SynchronizedDateFormat ("yyyy-MM-dd'T'HH:mm:ssZ"); + + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + /** The application object */ + protected App _app; + /** The Jhove engine */ + protected JhoveBase _base; + /** Handler last modification date */ + protected Date _date; + /** Character encoding for writer */ + protected String _encoding; + /** Initialization value. */ + protected String _init; + /** List of default parameters. */ + protected List _defaultParams; + /** JHOVE engine. */ + protected JhoveBase _je; + /** Indentation level */ + protected int _level; + /** Handler name */ + protected String _name; + /** Handler note */ + protected String _note; + /** Handler release description. */ + protected String _release; + /** Handler-specific parameter. */ + protected String _param; + /** Copyright notice */ + protected String _rights; + /** Handler specification document list */ + protected List _specification; + /** Handler vendor */ + protected Agent _vendor; + /** Writer for doing output */ + protected PrintWriter _writer; + /** Logger for a handler class. */ + protected Logger _logger; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * Constructors of all subclasses of HandlerBase should call + * this as a super constructor. + * + * @param name Name of the handler + * @param release Release identifier + * @param date Last modification date of the handler code, + * in the form of an array of three numbers. + * date[0] is the year, + * date[1] the month, and + * date[2] the day. + * @param note Additional information about the handler + * (may be null) + * @param rights Copyright notice for the handler + */ + protected HandlerBase (String name, String release, int [] date, + String note, String rights) + { + // Though we're actually in the jhove package, all the related + // action logically belongs in the handler package, so we name + // this logger accordingly. + _logger = Logger.getLogger ("edu.harvard.hul.ois.jhove.handler"); + _logger.info ("Initializing " + name); + _name = name; + _release = release; + _encoding = "UTF-8"; + + Calendar calendar = new GregorianCalendar (); + calendar.set (date[0], date[1]-1, date[2]); + _date = calendar.getTime (); + + _note = note; + _rights = rights; + _specification = new ArrayList (); + + _level = -1; + } + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + * + * Initialization methods. + ******************************************************************/ + + /** + * Reset the handler. This needs to be called before each invocation. + */ + public void reset () { + _level = -1; + } + + /** + * Set a a List of default parameters for the module. + * + * @param params A List whose elements are Strings. + * May be empty. + */ + public void setDefaultParams (List params) + { + _defaultParams = params; + } + + /** + * Applies the default parameters. + * Calling this clears any prior parameters. + */ + public void applyDefaultParams () + throws Exception + { + resetParams (); + Iterator iter = _defaultParams.iterator (); + while (iter.hasNext ()) { + String parm = iter.next (); + param (parm); + } + } + + /** Reset parameter settings. + * Returns to a default state without any parameters. + * The default method clears the saved parameter. + */ + public void resetParams () + throws Exception + { + _param = null; + } + + + /** + * Per-instantiation initialization. + * The default method does nothing. + */ + public void init (String init) + throws Exception + { + _init = init; + } + + /** + * Per-action initialization. + * The default method does nothing. + */ + public void param (String param) + throws Exception + { + _param = param; + } + + /****************************************************************** + * Accessor methods. + ******************************************************************/ + + /** + * Return the last modification date of this OutputHandler, as a + * Java Date object + */ + public final Date getDate () + { + return _date; + } + + /** + * Return the OutputHandler name + */ + public final String getName () + { + return _name; + } + + /** + * Return the OutputHandler note + */ + public final String getNote () + { + return _note; + } + + /** + * Return the release identifier + */ + public final String getRelease () + { + return _release; + } + + /** + * Return the copyright information string + */ + public final String getRights () + { + return _rights; + } + + /** + * Returns a list of Document objects (one for each + * specification document). The specification + * list is generated by the OutputHandler, and specifications cannot + * be added by callers. + * + * @see Document + */ + public final List getSpecification () + { + return _specification; + } + + /** + * Return the vendor information + */ + public final Agent getVendor () + { + return _vendor; + } + + /** + * Returns this handler's encoding. + */ + public String getEncoding () + { + return _encoding; + } + + /****************************************************************** + * Mutator methods. + ******************************************************************/ + + /** + * Pass the associated App object to this Module. + * The App makes various services available. + */ + public final void setApp (App app) + { + _app = app; + } + + /** + * Assigns the JHOVE engine object to provide services to this handler + */ + public final void setBase (JhoveBase je) + { + _je = je; + } + + /** + * Assigns the encoding to be used by this OutputHandler + */ + public void setEncoding (String encoding) + { + _encoding = encoding; + } + + /** + * Assigns a PrintWriter to do output for this OutputHandler + */ + public final void setWriter (PrintWriter writer) + { + _writer = writer; + } + + /****************************************************************** + * Serialization methods. + ******************************************************************/ + + /** + * Callback allowing post-parse, pre-show analysis of object + * representation information. + * @param info Object representation information + */ + public void analyze (RepInfo info) + { + /* Do nothing, which is sufficient for most handlers. */ + } + + /** + * Callback indicating a directory is finished being processed. + */ + public void endDirectory () + { + /* Do nothing, which is sufficient for most handlers. */ + } + + /** + * Callback to give the handler the opportunity to decide whether or + * not to process a file. Most handlers will always return true. + * @param filepath File pathname + */ + public boolean okToProcess (String filepath) + { + return true; + } + + /** + * Outputs information about a Module + */ + public abstract void show (Module module); + + /** + * Outputs the information contained in a RepInfo object + */ + public abstract void show (RepInfo info); + + /** + * Outputs information about the OutputHandler specified + * in the parameter + */ + public abstract void show (OutputHandler handler); + + /** + * Outputs minimal information about the application + */ + public abstract void show (); + + /** + * Outputs detailed information about the application, + * including configuration, available modules and handlers, + * etc. + */ + public abstract void show (App app); + + /** + * Do the initial output. This should be in a suitable format + * for including multiple files between the header and the footer. + */ + public abstract void showHeader (); + + /** + * Do the final output. This should be in a suitable format + * for including multiple files between the header and the footer. + */ + public abstract void showFooter (); + + /** + * Close the writer after all output has been done. + */ + public void close () + { + _writer.close (); + } + + /** + * Callback indicating a new directory is being processed. + * @param directory Directory path + */ + public void startDirectory (String directory) + { + /* Do nothing, which is sufficient for most handlers. */ + } + + /****************************************************************** + * PRIVATE CLASS METHODS. + * + * XML methods. + ******************************************************************/ + + /** + * Return the XML DOCTYPE instruction. + * @param root Root element of the DTD + * @param uri URI of the DTD + */ + protected static String doctype (String root, String uri) + { + return doctype (root, null, uri); + } + + /** + * Return the XML DOCTYPE instruction. + * @param root Root element of the DTD + * @param name Public name of the DTD + * @param uri URI of the DTD + */ + protected static String doctype (String root, String name, String uri) + { + StringBuffer s = new StringBuffer (""); + + return s.toString (); + } + + /** + * Returns, as a String, an empty XML. + * + * @param tag XML tag + */ + protected static String element (String tag) + { + return "<" + tag + "/>"; + } + + /** + * Returns, as a String, an XML element with a given tag and content + * + * @param tag An XML tag + * @param content Content string. Characters requiring + * conversion to entitites will be converted. + */ + protected static String element (String tag, String content) + { + return elementStart (tag) + encodeContent (content) + elementEnd (tag); + } + + /** + * Returns, as a String, + * an XML element with a given tag and attributes + * + * @param tag An XML tag + * @param attrs An array of String[2] elements, where for each + * element, attrs[i][0] is the attribute key and + * attrs[i][1] is the attribute value. + * Null values are skipped. + */ + protected static String element (String tag, String [][] attrs) + { + StringBuffer buffer = new StringBuffer ("<"); + buffer.append (tag); + for (int i=0; i"); + + return buffer.toString (); + } + + /** + * Returns, as a String, + * an XML element with a given tag, content and attributes + * + * @param tag An XML tag + * @param content Content string. Characters requiring + * conversion to entitites will be converted. + * @param attrs An array of String[2] elements, where for each + * element, attrs[i][0] is the attribute key and + * attrs[i][1] is the attribute value. + * Null values are skipped. + * + */ + protected static String element (String tag, String [][] attrs, + String content) + { + StringBuffer buffer = new StringBuffer ("<"); + buffer.append (tag); + for (int i=0; i"); + buffer.append (encodeContent (content)); + buffer.append (elementEnd (tag)); + + return buffer.toString (); + } + + /** + * Returns, as a String, the closing tag of an element. + * No checking is done that opening and closing tags match. + * + * @param tag An XML tag + */ + protected static String elementEnd (String tag) + { + return ""; + } + + /** + * Returns, as a String, the opening tag of an element. + * + * @param tag An XML tag + */ + protected static String elementStart (String tag) + { + return "<" + tag + ">"; + } + + /** + * Returns, as a String, the opening tag of an element with + * specified attributes. + * + * @param tag An XML tag + * @param attrs An array of String[2] elements, where for each + * element, attrs[i][0] is the attribute key and + * attrs[i][1] is the attribute value. + */ + protected static String elementStart (String tag, String [][] attrs) + { + StringBuffer buffer = new StringBuffer ("<"); + buffer.append (tag); + for (int i=0; i"); + + return buffer.toString (); + } + + /** + * Encodes a content String in XML-clean form, converting characters + * to entities as necessary and removing control characters disallowed + * by XML. The null string will be converted to an empty string. + */ + private static String encodeContent (String content) + { + if (content == null) { + content = ""; + } + StringBuffer buffer = new StringBuffer (content); + + /* Remove disallowed control characters from the content string. */ + int n = buffer.length (); + for (int i=0; i -1) { + buffer.insert (n+1, "amp;"); + n +=5; + } + n = 0; + while ((n = buffer.indexOf ("<", n)) > -1) { + buffer.replace (n, n+1, "<"); + n += 4; + } + n = 0; + while ((n = buffer.indexOf (">", n)) > -1) { + buffer.replace (n, n+1, ">"); + n += 4; + } + + return buffer.toString (); + } + + /** + * Encodes an attribute value String in XML-clean form, + * converting quote characters to entities and removing control + * characters disallowed by XML. + */ + private static String encodeValue (String value) + { + StringBuffer buffer = new StringBuffer (value); + + /* Remove disallowed control characters from the value string. */ + int n = buffer.length (); + for (int i=0; i characters which are disallowed in xml + n = 0; + while ((n = buffer.indexOf ("&", n)) > -1) { + buffer.insert (n+1, "amp;"); + n +=5; + } + n = 0; + while ((n = buffer.indexOf ("<", n)) > -1) { + buffer.replace (n, n+1, "<"); + n += 4; + } + n = 0; + while ((n = buffer.indexOf (">", n)) > -1) { + buffer.replace (n, n+1, ">"); + n += 4; + } + n = 0; + while ((n = buffer.indexOf ("\"", n)) > -1) { + // [LP] fix for invalid escaping, "" quotes were accidentally left in place. + buffer.replace (n, n+1, """); + n +=7; + } + + return buffer.toString (); + } + + /** + * Return a canonical XML declaration with default encoding. + */ + protected static String xmlDecl () + { + return ""; + } + + /** + * Return a canonical XML declaration with specified encoding. + */ + protected static String xmlDecl (String encoding) + { + return ""; + } + + /****************************************************************** + * Nesting level methods. + ******************************************************************/ + + /** + * Returns a String containing a number of spaces equal + * to the current indent level. + */ + protected static String getIndent (int level) + { + StringBuffer s = new StringBuffer (); + for (int i=0; i 0) { + buffer.append (separator); + } + buffer.append (Integer.toString (iarray[i])); + } + return buffer.toString (); + } + + /** + * Return String representation of an array of long with + * space separator. + */ + protected static String longArray (long [] larray) + { + StringBuffer buffer = new StringBuffer (); + for (int i=0; i 0) { + buffer.append (" "); + } + buffer.append (Long.toString (larray[i])); + } + return buffer.toString (); + } + + /** + * Return String representation of an array of Rational, each evaluated + * as a double, with space separator. + */ + protected static String rationalArray (Rational [] rarray) + { + StringBuffer buffer = new StringBuffer (); + for (int i=0; i 0) { + buffer.append (" "); + } + buffer.append (rarray[i].toDouble ()); + } + return buffer.toString (); + } + + + /** + * Return String representation of an array of Rational, each as + * two integers, with space separator. + */ + protected static String rationalArray10 (Rational [] rarray) + { + StringBuffer buffer = new StringBuffer (); + for (int i=0; i 0) { + buffer.append (" "); + } + buffer.append (rarray[i].getNumerator ()); + buffer.append (" "); + buffer.append (rarray[i].getNumerator ()); + } + return buffer.toString (); + } + + /** + * Return String representation of an array of double. + */ + protected static String doubleArray (double [] darray) + { + StringBuffer buffer = new StringBuffer (); + for (int i=0; i 0) { + buffer.append (" "); + } + buffer.append (Double.toString (darray[i])); + } + return buffer.toString (); + } + + + /* Text formatting methods. */ + /* Convert a date to the dateTime format used by the + * XML schema. This is ISO 8610 with a colon between + * the hour and minute fields of the time zone. Unfortunately, + * SimpleDateFormat generates the time zone without the + * colon; this also conforms to 8601, but doesn't conform + * to the schema, so we have to diddle it. + */ + protected String toDateTime (Date date) + { + String isoStr = iso8601.format (date); + // We can't directly use a SimpleDateFormat, because + // the 'z' field gives us the colonless time zone. + int len = isoStr.length (); + // Add the colon before the last two characters. + return isoStr.substring (0, len - 2) + + ":" + + isoStr.substring (len - 2); + } + + /** A DateFormat class to address an issue of thread safety. */ + public static class SynchronizedDateFormat extends SimpleDateFormat + { + public SynchronizedDateFormat(String pattern) { + super(pattern); + } + public synchronized StringBuffer format(Date date, + StringBuffer toAppendTo, FieldPosition pos) { + return super.format(date, toAppendTo, pos); + } + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/Identifier.java b/jhove/classes/edu/harvard/hul/ois/jhove/Identifier.java new file mode 100644 index 00000000..4cc41e03 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/Identifier.java @@ -0,0 +1,64 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +/** + * This class encapsulates information about an identifier + * for a specification document. + */ +public class Identifier +{ + private IdentifierType _type; + private String _value; + private String _note; + + /** + * Create an Identifier. + * @param value The text displayed for this Identifier. + * @param type The type of identification. + */ + public Identifier (String value, IdentifierType type) + { + _value = value; + _type = type; + } + + /** + * Create an Identifier. + * @param value The text displayed for this Identifier. + * @param type The type of identification. + * @param note A note giving supplementary information. + */ + public Identifier (String value, IdentifierType type, String note) + { + this (value, type); + _note = note; + } + + /** + * Return the identifier type. + */ + public IdentifierType getType () + { + return _type; + } + + /** + * Return the displayable string. + */ + public String getValue () + { + return _value; + } + + /** + * Return the note, which will be null if none was specified. + */ + public String getNote() + { + return _note; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/IdentifierType.java b/jhove/classes/edu/harvard/hul/ois/jhove/IdentifierType.java new file mode 100644 index 00000000..83913580 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/IdentifierType.java @@ -0,0 +1,115 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + + +/** + * This class defines enumerated types for an Identifier of a + * format specification document. + * Applications will not create or modify IdentifierTypes, but will + * use one of the predefined IdentifierType instances + * ANSI, DDC, DOI, ECMA, HANDLE, ISO, ISBN, LC, LCCN, + * NISO, PII, RFC, SICI, URI, URL, URN, or OTHER. + * + * @see Identifier + */ +public final class IdentifierType + extends EnumerationType +{ + /** + * Identifier type for American National Standards Institute. + */ + public static final IdentifierType ANSI = new IdentifierType ("ANSI"); + /** + * Identifier type for Dewey Decimal Classification. + */ + public static final IdentifierType DDC = new IdentifierType ("DDC"); + /** + * Identifier type for Digital Object Identifier. + */ + public static final IdentifierType DOI = new IdentifierType ("DOI"); + /** + * Identifier type for ECMA. + */ + public static final IdentifierType ECMA = new IdentifierType ("ECMA"); + /** + * Identifier type for CNRI Handle. + */ + public static final IdentifierType HANDLE = new IdentifierType ("Handle"); + /** + * Identifier type for International Standards Organization. + */ + public static final IdentifierType ISO = new IdentifierType ("ISO"); + /** + * Identifier type for International Standard Book Number. + */ + public static final IdentifierType ISBN = new IdentifierType ("ISBN"); + /** + * Identifier type for Library of Congress classification. + */ + public static final IdentifierType LC = new IdentifierType ("LC"); + /** + * Identifier type for Library of Congress catalogue number. + */ + public static final IdentifierType LCCN = new IdentifierType ("LCCN"); + /** + * Identifier type for NISO standard number. + */ + public static final IdentifierType NISO = new IdentifierType ("NISO"); + /** + * Identifier type for Publisher Item Identifier. + */ + public static final IdentifierType PII = new IdentifierType ("PII"); + /** + * Identifier type for IETF Request for Comment. + */ + public static final IdentifierType RFC = new IdentifierType ("RFC"); + /** + * Identifier type for Serial Item and Contribution Identifier. + */ + public static final IdentifierType SICI = new IdentifierType ("SICI"); + /** + * Identifier type for Uniform Resource Identifier. + */ + public static final IdentifierType URI = new IdentifierType ("URI"); + /** + * Identifier type for Uniform Resource Locator. + */ + public static final IdentifierType URL = new IdentifierType ("URL"); + /** + * Identifier type for Uniform Resource Name. + */ + public static final IdentifierType URN = new IdentifierType ("URN"); + /** + * Identifier type for CCITT. + */ + public static final IdentifierType CCITT = new IdentifierType ("CCITT"); + /** + * Identifier type for International Telecommunication Union. + */ + public static final IdentifierType ITU = new IdentifierType ("ITU"); + /** + * Identifier type for Japan Electronics and Information Technology + * Industries Association. + */ + public static final IdentifierType JEITA = new IdentifierType ("JEITA"); + /** + * Identifier type for whatever doesn't fit other categories. + */ + public static final IdentifierType OTHER = new IdentifierType ("Other"); + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * Applications will never create SignatureTypes directly. + **/ + private IdentifierType (String value) + { + super (value); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/InfoMessage.java b/jhove/classes/edu/harvard/hul/ois/jhove/InfoMessage.java new file mode 100644 index 00000000..7cde1973 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/InfoMessage.java @@ -0,0 +1,62 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +/** + * This class encapsulates an informational message from a Module, giving + * information (not necessarily a problem) + * about the content being analyzed or the way that Jhove + * deals with it. + */ +public class InfoMessage + extends Message +{ + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * Create an InfoMessage. + * @param message Human-readable string giving the information. + */ + public InfoMessage (String message) + { + super (message); + } + + /** + * Create an InfoMessage. + * @param message Human-readable string giving the information. + * @param offset The offset in the file relevant to the + * situation being described + */ + public InfoMessage (String message, long offset) + { + super (message, offset); + } + + /** + * Create an InfoMessage. + * @param message Human-readable string giving the information. + * @param subMessage Human-readable additional information. + */ + public InfoMessage (String message, String subMessage) + { + super (message, subMessage); + } + + /** + * Create an InfoMessage. + * @param message Human-readable string giving the information. + * @param subMessage Human-readable additional information. + * @param offset The offset in the file relevant to the + * situation being described + */ + public InfoMessage (String message, String subMessage, long offset) + { + super (message, subMessage, offset); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/InternalSignature.java b/jhove/classes/edu/harvard/hul/ois/jhove/InternalSignature.java new file mode 100644 index 00000000..0f6eeb98 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/InternalSignature.java @@ -0,0 +1,140 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +/** + * This class encapsulates information about internal format signatures. + * The value of a Signature may be either a String or a byte array + * (stored as an int array to avoid signed byte problems). + */ +public class InternalSignature + extends Signature +{ + private boolean _hasFixedOffset; + private int _offset; + + /** + * Creates an InternalSignature given a string value, a type, + * and a use requirement. An InternalSignature created with + * this constructor does not have a fixed byte offset. + */ + public InternalSignature (String value, SignatureType type, + SignatureUseType use) + { + super (value, type, use); + _hasFixedOffset = false; + } + + /** + * Creates an InternalSignature given a byte array, a type, + * and a use requirement. An InternalSignature created with + * this constructor does not have a fixed byte offset. + */ + public InternalSignature (int[] value, SignatureType type, + SignatureUseType use) + { + super (value, type, use); + _hasFixedOffset = false; + } + + /** + * Creates an InternalSignature given a string value, a type, + * a use requirement, and a byte offset. An InternalSignature + * created with this constructor has a fixed byte offset. + */ + public InternalSignature (String value, SignatureType type, + SignatureUseType use, int offset) + { + super (value, type, use); + _offset = offset; + _hasFixedOffset = true; + } + + /** + * Creates an InternalSignature given a byte array, a type, + * a use requirement, and a byte offset. An InternalSignature + * created with this constructor has a fixed byte offset. + */ + public InternalSignature (int[] value, SignatureType type, + SignatureUseType use, int offset) + { + super (value, type, use); + _offset = offset; + _hasFixedOffset = true; + } + + /** + * Creates an InternalSignature given a string value, a type, + * a use requirement, and a note. An InternalSignature created with + * this constructor does not have a fixed byte offset. + */ + public InternalSignature (String value, SignatureType type, + SignatureUseType use, String note) + { + super (value, type, use, note); + _hasFixedOffset = false; + } + + /** + * Creates an InternalSignature given a byte array, a type, + * a use requirement, and a note. An InternalSignature created with + * this constructor does not have a fixed byte offset. + */ + public InternalSignature (int[] value, SignatureType type, + SignatureUseType use, String note) + { + super (value, type, use, note); + _hasFixedOffset = false; + } + + /** + * Creates an InternalSignature given a string value, a type, + * a use requirement, a byte offset, and a note. + * An InternalSignature created with + * this constructor has a fixed byte offset. + */ + public InternalSignature (String value, SignatureType type, + SignatureUseType use, int offset, + String note) + { + super (value, type, use, note); + _offset = offset; + _hasFixedOffset = true; + } + + /** + * Creates an InternalSignature given a string value, a type, + * a use requirement, a byte offset, and a note. + * An InternalSignature created with + * this constructor has a fixed byte offset. + */ + public InternalSignature (int[] value, SignatureType type, + SignatureUseType use, int offset, + String note) + { + super (value, type, use, note); + _offset = offset; + _hasFixedOffset = true; + } + + /** + * Returns the byte offset. This value is meaningful only + * if this InternalSignature has a fixed byte offset. + */ + public int getOffset () + { + return _offset; + } + + /** + * Returns true if this InternalSignature + * has a fixed byte offset. + */ + public boolean hasFixedOffset () + { + return _hasFixedOffset; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/JhoveBase.java b/jhove/classes/edu/harvard/hul/ois/jhove/JhoveBase.java new file mode 100644 index 00000000..7484190d --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/JhoveBase.java @@ -0,0 +1,1356 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2005-2007 by the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +import edu.harvard.hul.ois.jhove.handler.*; +import edu.harvard.hul.ois.jhove.module.*; +import java.io.*; +import java.net.*; +import java.util.*; +import java.util.logging.*; +import javax.net.ssl.KeyManager; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import javax.xml.parsers.*; +import org.xml.sax.*; +import org.xml.sax.helpers.*; + +/** + * The JHOVE engine, providing all base services necessary to build an + * application. + * + * More than one JhoveBase may be instantiated and process files in + * concurrent threads. Any one instance must not be multithreaded. + */ +public class JhoveBase +{ + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + + private static Date _date; + public static final String _name = "JhoveBase"; + public static final String _release = "1.9"; + public static final int [] DATE = {2012, 12, 17}; + private static final String _rights = + "Derived from software Copyright 2004-2011 " + + "by the President and Fellows of Harvard College. " + + "Version 1.7 independently released. " + + "Released under the GNU Lesser General Public License."; + + /** JHOVE buffer size property. */ + private static final String BUFFER_PROPERTY = "edu.harvard.hul.ois." + + "jhove.bufferSize"; + /** JHOVE configuration directory */ + private static final String CONFIG_DIR = "conf"; + + /** JHVOE configuration file property. */ + private static final String CONFIG_PROPERTY = "edu.harvard.hul.ois." + + "jhove.config"; + /** JHOVE default buffer size. */ + private static final int DEFAULT_BUFFER = 131072; + + /** JHOVE default character encoding. */ + private static final String DEFAULT_ENCODING = "utf-8"; + + /** Default temporary directory. */ + private static final String DEFAULT_TEMP = "."; + + /** JHOVE encoding property. */ + private static final String ENCODING_PROPERTY = "edu.harvard.hul.ois." + + "jhove.encoding"; + /** JHOVE home directory */ + private static final String JHOVE_DIR = "jhove"; + + /** JHOVE SAX parser class property. */ + private static final String SAX_PROPERTY = "edu.harvard.hul.ois.jhove." + + "saxClass"; + /** JHOVE temporary directory property. */ + private static final String TEMPDIR_PROPERTY = "edu.harvard.hul.ois." + + "jhove.tempDirectory"; + + /** MIX schema version property. */ + private static final String MIXVSN_PROPERTY = "edu.harvard.hul.ois." + + "jhove.mixvsn"; + + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + /** Flag for aborting activity. */ + protected boolean _abort; + /** Buffer size for buffered I/O. */ + protected int _bufferSize; + protected boolean _checksum; + /** Configuration file pathname. */ + protected String _configFile; + /** Selected encoding. */ + protected String _encoding; + /** Associate map of configution extensions. */ + protected Map _extensions; + /** Ordered list of output handlers. */ + protected List _handlerList; + /** Map of output handlers (for fast access by name). */ + protected Map _handlerMap; + /** JHOVE home directory. */ + protected String _jhoveHome; + /** Ordered list of modules. */ + protected List _moduleList; + /** Map of modules (for fast access by name). */ + protected Map _moduleMap; + protected String _outputFile; + /** SAX parser class. */ + protected String _saxClass; + protected boolean _showRaw; + protected boolean _signature; + /** Temporary directory. */ + protected String _tempDir; + /** MIX version. */ + protected String _mixVsn; + /** Number of bytes for fake signature checking. */ + protected int _sigBytes; + /** Directory for saving files. */ + protected File _saveDir; + /** Byte count for digital object */ + protected long _nByte; + /** Callback function to check for termination. */ + Callback _callback; + /** Current URL connection. */ + protected URLConnection _conn; + /** Thread currently parsing a document. */ + protected Thread _currentThread; + + /** Logger for this class. */ + protected Logger _logger; + /** Logger resource bundle. */ + protected String _logLevel; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * Instantiate a JhoveBase object. + * @throws JhoveException If invoked with JVM lower than 1.5 + */ + public JhoveBase () + throws JhoveException + { + _logger = Logger.getLogger ("edu.harvard.hul.ois.jhove"); + _logger.setLevel (Level.SEVERE); + /* Make sure we have a satisfactory version of Java. */ + String version = System.getProperty ("java.vm.version"); + if (version.compareTo ("1.5.0") < 0) { + String bad = "Java 1.5 or higher is required"; + _logger.severe (bad); + throw new JhoveException (bad); + } + + /* Tell any https connections to be accepted automatically. */ + HttpsURLConnection.setDefaultHostnameVerifier (new NaiveHostnameVerifier()); + + /* Initialize the engine. */ + + Calendar calendar = new GregorianCalendar (); + calendar.set (DATE[0], DATE[1]-1, DATE[2]); + _date = calendar.getTime (); + + _moduleList = new ArrayList (20); + _moduleMap = new TreeMap (); + + _handlerList = new ArrayList (); + _handlerMap = new TreeMap (); + + _abort = false; + _bufferSize = -1; + _checksum = false; + _showRaw = false; + _signature = false; + _callback = null; + } + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + ******************************************************************/ + + /** + * Initialize the JHOVE engine. + * @param configFile Configuration file pathname + */ + public void init (String configFile, String saxClass) + throws JhoveException + { + _configFile = configFile; + _saxClass = saxClass; + + File config = null; + String err = null; + // If we get an error, attempt to get through the best we can, + // then throw a JhoveException with err as its message. + + if (_configFile != null) { + config = new File (_configFile); + if (!config.exists () || !config.isFile ()) { + DefaultConfigurationBuilder dcb = new DefaultConfigurationBuilder(config); + try { + dcb.writeDefaultConfigFile(); + } + catch (Exception e) { + err = "Configuration file " + + config.getAbsolutePath() + + " not found or " + + "not readable and could not create default file; use -c to specify"; + config = null; + } + } + + if (config != null) { + XMLReader parser = null; + try { + if (saxClass == null) { + /* Use Java 1.4 methods to create default parser. + */ + SAXParserFactory factory = + SAXParserFactory.newInstance(); + factory.setNamespaceAware (true); + parser = factory.newSAXParser ().getXMLReader (); + } + else { + parser = XMLReaderFactory.createXMLReader (saxClass); + } + } + catch (Exception e) { + // If we can't get a SAX parser, we're stuck. + throw new JhoveException ("SAX parser not found: " + + saxClass); + } + _logger.info ("Using SAX parser " + parser.getClass ().getName ()); + ConfigHandler configHandler = new ConfigHandler (); + parser.setContentHandler (configHandler); + parser.setEntityResolver(configHandler); + /* Attempt to set schema awareness to avoid validation + * errors. + */ + try { + parser.setFeature("http://xml.org/sax/features/validation", + true); + parser.setProperty ("http://java.sun.com/xml/jaxp/" + + "properties/schemaLanguage", + "http://www.w3.org/2001/XMLSchema"); + } + catch (SAXException e) + {} + + try { + String canonicalPath = config.getCanonicalPath (); + String fileURL = "file://"; + if (canonicalPath.charAt (0) != '/') { + fileURL += '/'; + } + fileURL += canonicalPath; + parser.parse (fileURL); + } + catch (IOException e) { + throw new + JhoveException ("Cannot read configuration file: " + + configFile); + } + catch (SAXException s) { + throw new + JhoveException ("Error parsing configuration file: " + + s.getMessage ()); + } + + /* Update the application state to reflect the configuration + * file, if necessary. + */ + _extensions = configHandler.getExtensions (); + _jhoveHome = configHandler.getJhoveHome (); + + _encoding = configHandler.getEncoding (); + if (_encoding == null) { + _encoding = getFromProperties (ENCODING_PROPERTY); + if (_encoding == null) { + _encoding = DEFAULT_ENCODING; + } + } + + _tempDir = configHandler.getTempDir (); + if (_tempDir == null) { + _tempDir = getFromProperties (TEMPDIR_PROPERTY); + if (_tempDir == null) { + _tempDir = DEFAULT_TEMP; + } + } + + // get the MIX version. if not specified, defaults to 2.0. + _mixVsn = configHandler.getMixVsn (); + if (_mixVsn == null) { + _mixVsn = "2.0"; // default + } + + // Get the maximum number of bytes to examine when doing + // pseudo-signature checking + _sigBytes = configHandler.getSigBytes (); + + // If a log level was specified in the config file, + // attempt to set it, unless it was already + // explicitly set. + if (_logLevel == null) { + _logLevel = configHandler.getLogLevel (); + if (_logLevel != null) { + try { + _logger.setLevel (Level.parse (_logLevel)); + } + catch (Exception e) {} + } + } + + _bufferSize = configHandler.getBufferSize (); + if (_bufferSize < 0) { + String size = getFromProperties (BUFFER_PROPERTY); + if (size != null) { + try { + _bufferSize = Integer.parseInt (size); + } + catch (Exception e) {} + } + if (_bufferSize < 0) { + _bufferSize = DEFAULT_BUFFER; + } + } + + /* Retrieve the ordered lists of modules and output handlers */ + List modList = configHandler.getModule (); + List hanList = null; + List> params = configHandler.getModuleParams (); + int n = modList.size (); + for (int i=0; i param = params.get (i); + try { + Class cl = Class.forName (modInfo.clas); + Module module = (Module) cl.newInstance (); + module.init (modInfo.init); + module.setDefaultParams (param); + + _moduleList.add (module); + _moduleMap.put (module.getName ().toLowerCase (), + module); + _logger.info ("Initialized " + module.getName ()); + } + catch (Exception e) { + if (err == null) { + err = "cannot instantiate module: " + + modInfo.clas; + } + } + } + + hanList = configHandler.getHandler (); + params = configHandler.getHandlerParams (); + n = hanList.size (); + for (int i=0; i param = params.get (i); + try { + Class cl = Class.forName (tuple[0]); + OutputHandler handler = + (OutputHandler) cl.newInstance (); + //handler.init (modInfo.init); + handler.setDefaultParams (param); + + _handlerList.add (handler); + _handlerMap.put (handler.getName ().toLowerCase (), + handler); + } + catch (Exception e) { + if (err == null) { + err = "cannot instantiate handler: " + tuple[0]; + } + } + } + } + // If we found any error, the caller needs to deal with it. + if (err != null) { + throw new JhoveException (err); + } + } + else { + throw new JhoveException ("Initialization exception; location not specified for configuration file."); + } + + /**************************************************************** + * The Bytestream module and the Text, XML, and Audit output + * handlers are always statically loaded. + ****************************************************************/ + + Module module = new BytestreamModule (); + module.setDefaultParams (new ArrayList ()); + _moduleList.add (module); + _moduleMap.put (module.getName ().toLowerCase (), module); + + OutputHandler handler = new TextHandler (); + handler.setDefaultParams (new ArrayList ()); + _handlerList.add (handler); + _handlerMap.put (handler.getName ().toLowerCase (), handler); + + handler = new XmlHandler (); + handler.setDefaultParams (new ArrayList ()); + _handlerList.add (handler); + _handlerMap.put (handler.getName ().toLowerCase (), handler); + + handler = new AuditHandler (); + handler.setDefaultParams (new ArrayList ()); + _handlerList.add (handler); + _handlerMap.put (handler.getName ().toLowerCase (), handler); + } + + + /** Sets a callback object for tracking progress. By default, + * the callback is null. */ + public void setCallback (Callback callback) + { + _callback = callback; + } + + + + /** Processes a file or directory, or outputs information. + * If dirFileOrUri is null, Does one of the following: + *
    + *
  • If module is non-null, provides information about the module. + *
  • Otherwise if aboutHandler is non-null, + * provides information about that handler. + *
  • If they're both null, provides information about the + * application. + *
+ * @param app The App object for the application + * @param module The module to be used + * @param aboutHandler If specified, the handler about which info is requested + * @param handler The handler for processing the output + * @param outputFile Name of the file to which output should go + * @param dirFileOrUri One or more file names or URI's to be analyzed + */ + public void dispatch (App app, Module module, /* String moduleParam, */ + OutputHandler aboutHandler, + OutputHandler handler, /*String handlerParam,*/ + String outputFile, + String [] dirFileOrUri) + throws Exception + { + _abort = false; + /* If no handler is specified, use the default TEXT handler. */ + if (handler == null) { + handler = (OutputHandler) _handlerMap.get ("text"); + } + handler.reset (); + _outputFile = outputFile; + + handler.setApp (app); + handler.setBase (this); + handler.setWriter (makeWriter (_outputFile, _encoding)); + //handler.param (handlerParam); + + handler.showHeader (); /* Show handler header info. */ + + if (dirFileOrUri == null) { + if (module != null) { /* Show info about module. */ + //module.param (moduleParam); + module.applyDefaultParams(); + module.show (handler); + } + else if (aboutHandler != null) { /* Show info about handler. */ + handler.show (aboutHandler); + } + else { /* Show info about application */ + app.show (handler); + } + } + else { + for (int i=0; i code || code >= 300) { + throw new JhoveException ("URL not found: " + + dirFileOrUri); + } + } + catch (Exception e) { + throw new JhoveException("URL not found: " + dirFileOrUri); + } + } + lastModified = conn.getLastModified (); + + /* Convert the URI to a temporary file and use + * for the input stream. + */ + + try { + file = connToTempFile (conn, info); + if (file == null) { + return false; // user aborted + } + isTemp = true; + } + catch (IOException e) { + _conn = null; + String msg = "cannot read URL: " + dirFileOrUri; + String msg1 = e.getMessage (); + if (msg1 != null) { + msg += " (" + msg1 + ")"; + } + throw new JhoveException (msg); + } + if (conn instanceof HttpsURLConnection) { + ((HttpsURLConnection) conn).disconnect (); + } + _conn = null; + } + else { + file = new File (dirFileOrUri); + } + + if (file.isDirectory ()) { + File [] files = file.listFiles (); + info = null; // free up unused RepInfo before recursing + + /* Sort the files in ascending order by filename. */ + Arrays.sort (files); + + handler.startDirectory (file.getCanonicalPath ()); + for (int i=0; i iter = _moduleList.iterator(); + while (iter.hasNext ()) { + Module mod = (Module) iter.next (); + RepInfo infc = (RepInfo) info.clone (); + + if (mod.hasFeature ("edu.harvard.hul.ois.jhove.canValidate")) { + try { + if (!processFile (app, mod, /*moduleParam,*/ false, file, + infc)) { + return false; + } + if (infc.getWellFormed () == RepInfo.TRUE) { + info.copy (infc); + break; + } + else { + // We want to know what modules matched the + // signature, so we force the sigMatch property + // to be persistent. + info.setSigMatch (infc.getSigMatch ()); + } + } + catch (Exception e) { + /* The assumption is that in trying to analyze + * the wrong type of file, the module may go + * off its track and throw an exception, so we + * just continue on to the next module. + */ + continue; + } + } + } + } + info.show (handler); + } + } + if (file != null && isTemp) { + file.delete (); + } + return true; + } + + /** + * Saves a URLConnection's data stream to a temporary file. + * This may be interrupted asynchronously by calling + * abort (), in which case it will delete + * the temporary file and return null. + */ + public File connToTempFile (URLConnection conn, RepInfo info) + throws IOException + { + File tempFile; + try { + tempFile = newTempFile (); + } + catch (IOException e) { + // Throw a more meaningful exception + throw new IOException ("cannot create temp file"); + } + OutputStream outstrm = null; + DataInputStream instrm = null; + if (_bufferSize > 0) { + outstrm = new BufferedOutputStream + (new FileOutputStream (tempFile), _bufferSize); + } + else { + outstrm = new BufferedOutputStream + (new FileOutputStream (tempFile)); + } + try { + if (_bufferSize > 0) { + instrm = new DataInputStream (new BufferedInputStream + (conn.getInputStream (), _bufferSize)); + } + else { + instrm = new DataInputStream (new BufferedInputStream + (conn.getInputStream ())); + } + } + catch (UnknownHostException e) { + tempFile.delete (); + throw new IOException (e.toString ()); + } + catch (IOException e) { + // IOExceptions other than UnknownHostException + tempFile.delete (); + throw e; + } + catch (Exception e) { + // Arbitrary URL's may throw unpredictable expressions; + // treat them as IOExceptions + tempFile.delete (); + throw new IOException (e.toString ()); + } + + Checksummer ckSummer = null; + if (_checksum) { + ckSummer = new Checksummer (); + } + _nByte = 0; + + int appModulo = 4000; + /* Copy the connection stream to the file. While we're + here, calculate the checksums. */ + try { + byte by; + for (;;) { + // Make sure other threads can get in occasionally to cancel + if ((_nByte % appModulo) == 0) { + Thread.yield (); + if (_callback != null) { + _callback.callback (1, new Long (_nByte)); + } + // In order to avoid doing too many callbacks, limit + // the checking to a number of bytes at least 1/10 of + // the bytes read so far. + if (appModulo * 10 < _nByte) { + appModulo = (int) (_nByte / 10); + } + } + if (_abort) { + // Asynchronous abort requested. Clean up. + instrm.close (); + outstrm.close (); + tempFile.delete (); + return null; + } + int ch = instrm.readUnsignedByte (); + if (ckSummer != null) { + ckSummer.update (ch); + } + by = Checksum.unsignedByteToByte (ch); + _nByte++; + outstrm.write (by); + } + } + catch (EOFException e) { + /* This is the normal way for detecting we're done */ + } + /* The caller is responsible for disconnecting conn. */ + instrm.close (); + outstrm.close (); + + /* Update RepInfo */ + info.setSize (_nByte); + if (ckSummer != null) { + info.setChecksum (new Checksum (ckSummer.getCRC32 (), + ChecksumType.CRC32)); + String value = ckSummer.getMD5 (); + if (value != null) { + info.setChecksum (new Checksum (value, ChecksumType.MD5)); + } + value = ckSummer.getSHA1 (); + if (value != null) { + info.setChecksum (new Checksum (value, ChecksumType.SHA1)); + } + } + return tempFile; + } + + /** + * Aborts an activity. This simply sets a flag; whether + * anything is aborted depends on what activity is + * happening. + */ + public void abort () + { + _abort = true; + HttpsURLConnection conn = null; + if (_conn instanceof HttpsURLConnection) { + conn = (HttpsURLConnection) _conn; + } + // If we're stuck in socket I/O, then there is no way + // to kill the thread cleanly. Wait a few seconds, + // and if we're still not terminated, pull the plug on + // the socket. + try { + Thread.sleep (4000); + } + catch (InterruptedException e) { + } + if (conn != null) { + // This is a non-deprecated way of bringing the connection + // to a screeching halt. disconnect will (we hope) close + // the underlying socket, killing any hanging I/O. + conn.disconnect (); + } + } + + + + /* Processes the file. Returns false if aborted, or if the + * module is incapable of validation. This shouldn't be called + * if the module doesn't have the validation feature. */ + public boolean processFile (App app, Module module, /*String moduleParam,*/ + boolean verbose, File file, RepInfo info) + throws Exception + { + if (!module.hasFeature("edu.harvard.hul.ois.jhove.canValidate")) { + return false; + } + if (_callback != null) { + _callback.callback (2, info.getUri()); + } + module.setApp (app); + module.setBase (this); + module.setVerbosity (verbose ? Module.MAXIMUM_VERBOSITY : + Module.MINIMUM_VERBOSITY); + module.applyDefaultParams (); + if (module.isRandomAccess ()) { + + /* Module needs random access input. */ + + RandomAccessFile raf = new RandomAccessFile (file, "r"); + if (_signature) { + module.checkSignatures (file, raf, info); + } + else { + module.parse (raf, info); + } + raf.close (); + } + else { + + /* Module accepts stream input. */ + + + InputStream stream = new FileInputStream (file); + try { + if (_signature) { + module.checkSignatures (file, stream, info); + } + else { + int parseIndex = module.parse (stream, info, 0); + /* If parse returns non-zero, reparse with a fresh stream. */ + while (parseIndex != 0) { + stream.close (); + stream = new FileInputStream (file); + parseIndex = module.parse (stream, info, parseIndex); + } + } + } + finally { + stream.close (); + } + } + return true; // Successful processing + } + + /** + * Creates a temporary file with a unique name. + * The file will be deleted when the application exits. + */ + public File tempFile () + throws IOException + { + File file = null; + + /* If no temporary directory has been specified, use the + * Java default temp directory. + */ + if (_tempDir == null) { + file = File.createTempFile ("JHOV", ""); + } + else { + File dir = new File (_tempDir); + file = File.createTempFile ("JHOV", "", dir); + } + file.deleteOnExit(); + + return file; + } + + /****************************************************************** + * Accessor methods. + ******************************************************************/ + + /** + * Returns the abort flag. + */ + public boolean getAbort () + { + return _abort; + } + + /** + * Returns buffer size. A value of -1 signifies that the invoknig + * code should assume the default buffer size. + */ + public int getBufferSize () + { + return _bufferSize; + } + + /** + * Returns the configuration file. + */ + public String getConfigFile () + { + return _configFile; + } + + /** + * Returns the engine date (the date at which + * this instance was created). + */ + public Date getDate () + { + return _date; + } + + /** + * Returns the output encoding. + */ + public String getEncoding () + { + return _encoding; + } + + /** + * Return the JHOVE configuration extensions. + */ + public Map getExtension () + { + return _extensions; + } + + /** + * Return the JHOVE configuration extension by name. + */ + public String getExtension (String name) + { + return (String) _extensions.get (name); + } + + /** + * Return a handler by name. + */ + public OutputHandler getHandler (String name) + { + OutputHandler handler = null; + if (name != null) { + handler = _handlerMap.get (name.toLowerCase ()); + } + return handler; + } + + + /** Returns map of handler names to handlers. */ + public Map getHandlerMap () + { + return _handlerMap; + } + + /** Returns the list of handlers. */ + public List getHandlerList () + { + return _handlerList; + } + + /** + * Returns the JHOVE home directory. + */ + public String getJhoveHome () + { + return _jhoveHome; + } + + /** + * Returns a module by name. + */ + public Module getModule (String name) + { + Module module = null; + if (name != null) { + module = (Module) _moduleMap.get (name.toLowerCase ()); + } + return module; + } + + /** Returns the Map of module names to modules. */ + public Map getModuleMap () + { + return _moduleMap; + } + + /** Returns the List of modules. */ + public List getModuleList () + { + return _moduleList; + } + + /** + * Returns the engine name. + */ + public String getName () + { + return _name; + } + + /** + * Returns the output file. + */ + public String getOuputFile () + { + return _outputFile; + } + + /** + * Returns the engine release. + */ + public String getRelease () + { + return _release; + } + + /** + * Return the engine rights statement + */ + public String getRights () + { + return _rights; + } + + /** + * Return the SAX class. + */ + public String getSaxClass () + { + return _saxClass; + } + + /** + * Return the temporary directory. + */ + public String getTempDirectory () + { + return _tempDir; + } + + /** Return the maximum number of bytes to check, for modules that look for + * an indefinitely positioned signature or check the first sigBytes bytes + * in lieu of a signature + */ + public int getSigBytes () + { + return _sigBytes; + } + + + /** + * Return the directory designated for saving files. + * This is simply the directory most recently set by + * setSaveDirectory. */ + public File getSaveDirectory () + { + return _saveDir; + } + + /** Returns true if checksumming is requested. */ + public boolean getChecksumFlag () + { + return _checksum; + } + + /** Returns true if raw output is requested. + * Raw output means numeric rather than symbolic output; + * its exact interpretation is up to the module, but generally + * applies to named flags. + */ + public boolean getShowRawFlag () + { + return _showRaw; + } + + /** Returns the "check signature only" flag. */ + public boolean getSignatureFlag () + { + return _signature; + } + + /** Returns the requested MIX schema version. */ + public String getMixVersion () + { + return _mixVsn; + } + + /****************************************************************** + * Mutator methods. + ******************************************************************/ + + /** + * Sets the buffer size. A value of -1 signifies that the invoking + * code will assume the default buffer size. + * + * Any non-negative value less than 1024 will result in a buffer size of 1024. + */ + public void setBufferSize (int bufferSize) + { + if (bufferSize >= 0 && bufferSize < 1024) { + _bufferSize = 1024; + } + else { + _bufferSize = bufferSize; + } + } + + + /** + * Sets the output encoding. + */ + public void setEncoding (String encoding) + { + _encoding = encoding; + } + + /** + * Sets the temporary directory path. + */ + public void setTempDirectory (String tempDir) + { + _tempDir = tempDir; + } + + /** + * Sets the log level. The value should be the name of + * a predefined instance of java.util.logging.Level, + * e.g., "WARNING", "INFO", "ALL". This will override + * the config file setting. + */ + public void setLogLevel (String level) + { + _logLevel = level; + if (level != null) { + try { + _logger.setLevel (Level.parse (_logLevel)); + } + catch (Exception e) {} + } + } + + + /** Sets the value to be returned by doChecksum(). */ + public void setChecksumFlag (boolean checksum) + { + _checksum = checksum; + } + + /** Sets the value to be returned by getShowRawFlag (), + * which determines if only raw numeric values should be output. + */ + public void setShowRawFlag (boolean raw) + { + _showRaw = raw; + } + + /** Sets the "check signature only" flag. */ + public void setSignatureFlag (boolean signature) + { + _signature = signature; + } + + /** + * Sets the default directory for subsequent save + * operations. + */ + public void setSaveDirectory (File dir) + { + _saveDir = dir; + } + + /** + * Sets the current thread for parsing. + */ + public void setCurrentThread (Thread t) + { + _currentThread = t; + } + + /** + * Resets the abort flag. This must be called at the beginning of + * any activity for which the abort flag may subsequently be set. + */ + public void resetAbort () + { + _abort = false; + } + + /****************************************************************** + * PRIVATE CLASS METHODS. + ******************************************************************/ + + /** Uses the user.home property to locate the configuration + * file. The file is expected to be in the subdirectory + * named by CONFIG_DIR under the home directory, and to + * be named jhove.conf. Returns null + * if no such file is found. + */ + public static String getConfigFileFromProperties () + { + String configFile = null; + configFile = getFromProperties (CONFIG_PROPERTY); + if (configFile == null) { + try { + String fs = System.getProperty ("file.separator"); + configFile = System.getProperty ("user.home") + fs + + JHOVE_DIR + fs + CONFIG_DIR + fs + "jhove.conf"; + } + catch (Exception e) { + } + } + + return configFile; + } + + /** Returns the value of the property + edu.harvard.hul.ois.jhove.saxClass, + which should be the name of the main SAX class. + Returns null if no such property + has been set up. */ + public static String getSaxClassFromProperties () + { + String saxClass = getFromProperties (SAX_PROPERTY); + + return saxClass; + } + + /** Returns a named value from the properties file. */ + public static String getFromProperties (String name) + { + String value = null; + + try { + String fs = System.getProperty ("file.separator"); + Properties props = new Properties (); + String propsFile = System.getProperty ("user.home") + fs + + JHOVE_DIR + fs + "jhove.properties"; + FileInputStream stream = new FileInputStream (propsFile); + props.load (stream); + stream.close (); + + value = props.getProperty (name); + } + catch (Exception e) { + } + + return value; + } + + /** + * Creates an output PrintWriter. + * @param outputFile Output filepath. If null, writer goes to System.out. + * @param encoding Character encoding. Must not be null. + */ + protected static PrintWriter makeWriter (String outputFile, String encoding) + throws JhoveException + { + PrintWriter output = null; + OutputStreamWriter osw = null; + if (outputFile != null) { + try { + FileOutputStream stream = new FileOutputStream (outputFile); + osw = new OutputStreamWriter (stream, encoding); + output = new PrintWriter (osw); + } + catch (UnsupportedEncodingException u) { + throw new JhoveException ("unsupported character encoding: " + + encoding); + } + catch (FileNotFoundException e) { + throw new JhoveException ("cannot open output file: " + + outputFile); + } + } + if (output == null) { + try { + osw = new OutputStreamWriter (System.out, encoding); + } + catch (UnsupportedEncodingException u) { + throw new JhoveException ("unsupported character encoding: " + + encoding); + } + output = new PrintWriter (osw); + } + return output; + } + + + /** + * Creates a temporary file with a unique name. + * The file will be deleted when the application exits. + */ + public File newTempFile () + throws IOException + { + return tempFile (); + } + + + /** A HostnameVerifier for https connections that will never ask for + * certificates. + */ + private class NaiveHostnameVerifier implements HostnameVerifier { + public boolean verify(String hostname, SSLSession session) { + return true; + } + } + + /** A TrustManager which should accept all certificates. + */ + private class RelaxedX509TrustManager implements X509TrustManager { + public boolean isClientTrusted(java.security.cert.X509Certificate[] chain){ + return true; + } + public boolean isServerTrusted(java.security.cert.X509Certificate[] chain) { + return true; + } + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return null; + } + public void checkClientTrusted(java.security.cert.X509Certificate[] chain) { + + } + public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String s) { + + } + public void checkServerTrusted(java.security.cert.X509Certificate[] chain) { + + } + public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String s) { + + } + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/JhoveException.java b/jhove/classes/edu/harvard/hul/ois/jhove/JhoveException.java new file mode 100644 index 00000000..2eebe2ba --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/JhoveException.java @@ -0,0 +1,19 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +public class JhoveException + extends Exception +{ + public JhoveException () + { + super (); + } + public JhoveException (String message) + { + super (message); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/MacStuff.java b/jhove/classes/edu/harvard/hul/ois/jhove/MacStuff.java new file mode 100644 index 00000000..106429bf --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/MacStuff.java @@ -0,0 +1,96 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +import java.io.*; +import java.lang.reflect.*; +//import com.apple.eio.FileManager; + + +/** + * Code specific to Macintosh Java. This class consists of static + * methods, and should not be instantiated. Its methods should be + * called only on the Macintosh OS X platform. It requires the + * package com.apple.eio.FileManager. + * + * @author Gary McGath + * + */ +public class MacStuff { + + /** Private constructor to prevent instantiation.*/ + private MacStuff () + { + } + + + /** + * Determines if we're running on a Macintosh, + * so appropriate UI adjustments can be made. In accordance + * with Apple's recommendations, this checks for the existence + * of the mrj.version property rather than checking the os.name + * property. + */ + public static boolean isMacintosh () + { + return (System.getProperty ("mrj.version") != null); + + } + + + + /** + * Returns true if a file has the given file type. + * This method uses FileManager in a dynamic way, so + * that it will merely throw a ClassNotFound exception + * if it fails. + * + * Currently this code isn't actually used, since the + * Jhove application is specified as checking only internal + * signatures. Should some future version or add-on + * code wish to use it, the code should + * look something like this: + * + *
+     *           try {
+     *               if (sig.getType() == SignatureType.FILETYPE &&
+     *                       MacStuff.isMacintosh ()) {
+     *                   if (!MacStuff.fileHasType(file, sig.getValueString())) {
+     *                       info.setConsistent (false);
+     *                   }    
+     *               }
+     *           }
+     *           catch (ClassNotFoundException e) {
+     *               // Mac classes missing -- can't check filetype.
+     *           }
+     * 
+ */ + public static boolean fileHasType(File file, String type) + throws ClassNotFoundException + { + // Need to make this completely dynamic. + //return type.equals (FileManager.getFileType (file)); + try { + if (type == null) { + return false; + } + Class fmclass = Class.forName ("com.apple.eio.FileManager"); + Class[] params = new Class[1]; + params[0] = Class.forName ("java.io.File"); + Object[] args = new Object[1]; + args[0] = file; + Method method = fmclass.getMethod ("getFileType", params); + String ftype = (String) method.invoke (null, args); + return (type.equals (ftype)); + } + catch (ClassNotFoundException e) { + throw e; + } + catch (Exception f) { + return false; + } + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/Message.java b/jhove/classes/edu/harvard/hul/ois/jhove/Message.java new file mode 100644 index 00000000..0c605b53 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/Message.java @@ -0,0 +1,131 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +/** + * This class encapsulates a String to be displayed. + */ +public abstract class Message +{ + /****************************************************************** + * PUBLIC CLASS FIELDS. + ******************************************************************/ + + /** Value indicating a null offset. */ + public static final long NULL = -1; + + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + /** Message text. */ + protected String _message; + + /** Additional information. */ + protected String _subMessage; + + /** Byte offset to which message applies. */ + protected long _offset; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * Create a Message. This constructor cannot be invoked directly, + * since Message is abstract. + * @param message Human-readable string. + */ + protected Message (String message) + { + init (message, null, NULL); + } + + /** + * Create a Message. This constructor cannot be invoked directly, + * since Message is abstract. The second argument + * adds secondary details to the primary message; + * the message will typically be displayed in the + * form "message:subMessage". + * @param message Human-readable string. + * @param subMessage Human-readable additional information. + */ + protected Message (String message, String subMessage) + { + init (message, subMessage, NULL); + } + + /** + * Create a Message. This constructor cannot be invoked directly, + * since Message is abstract. The second argument + * adds secondary details to the primary message; + * the message will typically be displayed in the + * form "message:subMessage". + * @param message Human-readable string. + * @param offset Byte offset associated with the message. + */ + protected Message (String message, long offset) + { + init (message, null, offset); + } + + /** + * Create a Message. This constructor cannot be invoked directly, + * since Message is abstract. The second argument + * adds secondary details to the primary message; + * the message will typically be displayed in the + * form "message:subMessage". + * @param message Human-readable string. + * @param subMessage Human-readable additional information. + * @param offset Byte offset associated with the message. + */ + protected Message (String message, String subMessage, long offset) + { + init (message, subMessage, offset); + } + + /** + * Initialize the Message object. + * @param message Human-readable string. + * @param subMessage Human-readable additional information. + */ + private void init (String message, String subMessage, long offset) + { + _message = message; + _subMessage = subMessage; + _offset = offset; + } + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + * + * Accessor methods. + ******************************************************************/ + + /** + * Get the message string. + */ + public String getMessage () + { + return _message; + } + + /** + * Get the submessage string. + */ + public String getSubMessage () + { + return _subMessage; + } + + /** + * Return the offset to which the information is related. + */ + public long getOffset () + { + return _offset; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/Module.java b/jhove/classes/edu/harvard/hul/ois/jhove/Module.java new file mode 100644 index 00000000..c7ef7558 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/Module.java @@ -0,0 +1,264 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003-4 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +import java.io.*; +import java.util.*; + +/** + * Public interface for Jhove format-specific plug-in modules. + * + * All format modules must implement the Module interface; + * usually the best way to do this will be to subclass ModuleBase. + * Modules must be declared in the configuration file and present + * in the Classpath to be recognized by Jhove. + * + * @see ModuleBase + */ +public interface Module +{ + public static final int + MAXIMUM_VERBOSITY = 1, + MINIMUM_VERBOSITY = 2; + + /** + * Per-instantiation initialization. + * + * @param init Initialization parameter. This is typically obtained + * from the configuration file. + */ + public void init (String init) + throws Exception; + + /** + * Sets list of default parameters. + * + * @param params A List whose elements are Strings. + * May be empty. + */ + public void setDefaultParams (List params); + + + /** + * Applies the default parameters. + */ + public void applyDefaultParams () + throws Exception; + + /** Reset parameter settings. + * Returns to a default state without any parameters. + */ + public void resetParams () + throws Exception; + + /** + * Per-action initialization. + * May be called multiple times. + * + * @param param Initialization parameter. + */ + public void param (String param) + throws Exception; + + /** + * Pass the associated App object to this Module. + * The App makes various services available. + */ + public void setApp (App app); + + /** + * Pass the JHOVE engine object to this Module. + */ + public void setBase (JhoveBase je); + + /** + * Set the degree of verbosity desired from the module. + * + * @param verbosity The requested verbosity value. + */ + public void setVerbosity (int verbosity); + + /** + * Return the name of this Module. + */ + public String getName (); + + /** + * Return the release identifier + */ + public String getRelease (); + + /** + * Return the last modification date of the Module, as a + * Java Date object + */ + public Date getDate (); + + /** + * Return the vendor information + */ + public Agent getVendor (); + + /** + * Return the copyright information string + */ + public String getRights (); + + /** + * Return the Module note + */ + public String getNote (); + + /** + * Return the array of format names supported by this Module + */ + public String [] getFormat (); + + /** + * Return the array of MIME type strings for formats supported + * by this Module + */ + public String [] getMimeType (); + + /** + * Return details as to the specific format versions or + * variants that are supported by this Module + */ + public String getCoverage (); + + /** + * Return the List of Signatures recognized by this Module + */ + public List getSignature (); + + /** + * Returns a list of Document objects (one for each + * specification document of the format). The specification + * list is generated by the Module, and specifications cannot + * be added by callers. + * + * @see Document + */ + public List getSpecification (); + + /** + * Return the string describing well-formedness criteria + */ + public String getWellFormedNote (); + + /** + * Returns the RepInfo note + */ + public String getRepInfoNote (); + + /** + * Returns the string describing validity criteria + */ + public String getValidityNote (); + + /** + * Returns the random access flag (true if the module operates + * on random access files, false if it operates on streams) + */ + public boolean isRandomAccess (); + + /** + * Returns true if the module supports a given + * named feature, and false if the feature is + * unsupported or unknown. + */ + public boolean hasFeature (String feature); + + /** + * Returns the full list of features. + */ + public List getFeatures (); + + /** + * Returns the list of default parameters. + */ + public List getDefaultParams (); + + + + /** + * Parse the content of a stream digital object and store the + * results in RepInfo. + * A given Module will normally implement only one of the two + * parse methods, leaving the other as a stub. + * + * @param stream An InputStream, positioned at its beginning, + * which is generated from the object to be parsed. + * If multiple calls to parse are made + * on the basis of a nonzero value being returned, + * a new InputStream must be provided each time. + * + * @param info A fresh (on the first call) RepInfo object + * which will be modified + * to reflect the results of the parsing + * If multiple calls to parse are made + * on the basis of a nonzero value being returned, + * the same RepInfo object should be passed with each + * call. + * + * @param parseIndex Must be 0 in first call to parse. If + * parse returns a nonzero value, it must be + * called again with parseIndex + * equal to that return value. + */ + public int parse (InputStream stream, RepInfo info, int parseIndex) + throws IOException; + + /** + * Parse the content of a random access digital object and store the + * results in RepInfo. + * A given Module will normally implement only one of the two + * parse methods, leaving the other as a stub. + * + * @param file A RandomAccessFile, positioned at its beginning, + * which is generated from the object to be parsed + * @param info A fresh RepInfo object which will be modified + * to reflect the results of the parsing + */ + public void parse (RandomAccessFile file, RepInfo info) + throws IOException; + + /** + * Check if the digital object conforms to this Module's + * internal signature information. + * + * @param file A File object for the object being parsed + * @param stream An InputStream, positioned at its beginning, + * which is generated from the object to be parsed + * @param info A fresh RepInfo object which will be modified + * to reflect the results of the test + */ + public void checkSignatures (File file, + InputStream stream, + RepInfo info) + throws IOException; + + /** + * Check if the digital object conforms to this Module's + * internal signature information. + * + * @param file A File object for the object being parsed + * @param raf A RandomAccessFile, positioned at its beginning, + * which is generated from the object to be parsed + * @param info A fresh RepInfo object which will be modified + * to reflect the results of the test + */ + public void checkSignatures (File file, + RandomAccessFile raf, + RepInfo info) + throws IOException; + + /** + * Generates information about this Module. + * The format of the output depends on the OutputHandler. + */ + public void show (OutputHandler handler); +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/ModuleBase.java b/jhove/classes/edu/harvard/hul/ois/jhove/ModuleBase.java new file mode 100644 index 00000000..c387a510 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/ModuleBase.java @@ -0,0 +1,1410 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003-4 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +import java.io.*; +import java.security.*; +import java.util.*; +import java.util.zip.*; +import java.util.logging.*; + +/** + * This class is an abstract implementation of the Module interface. + * It contains all the methods required for a Module, but doesn't + * do anything by itself. A subclass should provide a functional + * implmentation of parse (InputStream stream, RepInfo info, int parseIndex) + * if it is not random access, or + * parse (RandomAccessFile file, RepInfo info) + * if it is random access. + * + */ +public abstract class ModuleBase + implements Module +{ + /****************************************************************** + * PROTECTED INSTANCE FIELDS. + ******************************************************************/ + + /** The application object */ + protected App _app; + /** Coverage information */ + protected String _coverage; + /** Module last modification date */ + protected Date _date; + /** Formats recognized by this Module */ + protected String [] _format; + /** Initialization value. */ + protected String _init; + /** List of default parameters. */ + protected List _defaultParams; + /** JHOVE engine. */ + protected JhoveBase _je; + /** MIME types supported by this Module */ + protected String [] _mimeType; + /** Module name */ + protected String _name; + /** Module note */ + protected String _note; + /** Module-specific parameter. */ + protected String _param; + /** Module release description */ + protected String _release; + /** RepInfo note */ + protected String _repInfoNote; + /** Copyright notice */ + protected String _rights; + /** Module Signature list */ + protected List _signature; + /** Module specification document list */ + protected List _specification; + /** Module vendor */ + protected Agent _vendor; + /** Well-formedness criteria */ + protected String _wellFormedNote; + /** Validity criteria */ + protected String _validityNote; + /** Random access flag */ + protected boolean _isRandomAccess; + /** Byte count of content object */ + protected long _nByte; + /** CRC32 calculated on content object */ + protected CRC32 _crc32; + /** MD5 digest calculated on content object */ + protected MessageDigest _md5; + /** SHA-1 digest calculated on content object */ + protected MessageDigest _sha1; + /** Flag indicating valid checksum information set */ + protected boolean _checksumFinished; + /** Indicator of how much data to report */ + protected int _verbosity; + /** Flag to indicate read routines should count the stream */ + protected boolean _countStream; + /** The dominant "endianness" of the Module. */ + protected boolean _bigEndian; + /** The list of supported features. */ + protected List _features; + /** Logger for a module class. */ + protected Logger _logger; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * Constructors of all subclasses of ModuleBase should call + * this as a super constructor. + * + * @param name Name of the module + * @param release Release identifier + * @param date Last modification date of the module code, + * in the form of an array of three numbers. + * date[0] is the year, + * date[1] the month, and + * date[2] the day. + * @param format Array of format names supported by the module + * @param coverage Details as to the specific format versions or + * variants that are supported by the module + * @param mimeType Array of MIME type strings for formats + * supported by the module + * @param wellFormedNote Brief explanation of what constitutes + * well-formed content + * @param validityNote Brief explanation of what constitutes + * valid content + * @param repInfoNote Note pertaining to RepInfo (may be null) + * @param note Additional information about the module + * (may be null) + * @param rights Copyright notice for the module + * @param isRandomAccess true if the module treats content as + * random-access data, if it treats content + * as stream data + */ + protected ModuleBase (String name, String release, int [] date, + String [] format, String coverage, + String [] mimeType, String wellFormedNote, + String validityNote, String repInfoNote, String note, + String rights, boolean isRandomAccess) + { + // Though we're actually in the jhove package, all the related + // action logically belongs in the module package, so we name + // this logger accordingly. + _logger = Logger.getLogger ("edu.harvard.hul.ois.jhove.module"); + _logger.info ("Initializing " + name); + _name = name; + _release = release; + + Calendar calendar = new GregorianCalendar (); + calendar.set (date[0], date[1]-1, date[2]); + _date = calendar.getTime (); + + _format = format; + _coverage = coverage; + _mimeType = mimeType; + _signature = new ArrayList (); + _specification = new ArrayList (); + _wellFormedNote = wellFormedNote; + _repInfoNote = repInfoNote; + _validityNote = validityNote; + _note = note; + _rights = rights; + _isRandomAccess = isRandomAccess; + + _verbosity = MINIMUM_VERBOSITY; + initFeatures (); + } + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + * + * Initialization methods. + ******************************************************************/ + + /** Initializes the feature list. + * This method puts the following features in the list: + *
    + *
  • edu.harvard.hul.ois.canValidate + *
  • edu.harvard.hul.ois.canIdentify + *
+ */ + public void initFeatures () + { + _features = new ArrayList (2); + _features.add ("edu.harvard.hul.ois.jhove.canValidate"); + _features.add ("edu.harvard.hul.ois.jhove.canCharacterize"); + } + + + /** + * Per-instantiation initialization. + * The default method does nothing but save its parameter. + */ + public void init (String init) + throws Exception + { + _init = init; + } + + /** + * Set a a List of default parameters for the module. + * + * @param params A List whose elements are Strings. + * May be empty. + */ + public void setDefaultParams (List params) + { + _defaultParams = params; + } + + /** + * Applies the default parameters. + * Calling this clears any prior parameters. + */ + public void applyDefaultParams () + throws Exception + { + resetParams (); + Iterator iter = _defaultParams.iterator (); + while (iter.hasNext ()) { + String parm = iter.next (); + param (parm); + } + } + + /** Reset parameter settings. + * Returns to a default state without any parameters. + * The default method clears the saved parameter. + */ + public void resetParams () + throws Exception + { + _param = null; + } + + /** + * Per-action initialization. May be called multiple times. + * The default method does nothing but save its parameter. + */ + public void param (String param) + throws Exception + { + _param = param; + } + + /****************************************************************** + * Accessor methods. + ******************************************************************/ + + /** + * Returns the App object. + */ + public App getApp() { + return _app; + } + + /** + * Returns the JHOVE engine object. + */ + public JhoveBase getBase() { + return _je; + } + + /** + * Returns the value of _nByte. + * Meaningful only for modules that use a counted InputStream. + */ + public long getNByte () + { + return _nByte; + } + + /** + * Returns true if the dominant "endianness" of the + * module, or the current file being processed, + * is big-endian, otherwise false. This does not guarantee + * that all numbers in the module follow the dominant endianness, + * particularly as formats sometimes incorporate data stored in + * a previously defined format. For some formats, e.g., TIFF, the + * endianness depends on the file being processed. + * + * Every module must initialize the value of _bigEndian for this + * function, or else assign its value when parsing a file, + * to return a meaningful result. For some modules (e.g., + * ASCII, endianness has no meaning. + */ + public boolean isBigEndian () + { + return _bigEndian; + } + + /** + * Return details as to the specific format versions or + * variants that are supported by this module + */ + public final String getCoverage () + { + return _coverage; + } + + /** + * Return the last modification date of this Module, as a + * Java Date object + */ + public final Date getDate () + { + return _date; + } + + /** + * Return the array of format names supported by this Module + */ + public final String [] getFormat () + { + return _format; + } + + /** + * Return the array of MIME type strings for formats supported + * by this Module + */ + public final String [] getMimeType () + { + return _mimeType; + } + + /** + * Return the module name + */ + public final String getName () + { + return _name; + } + + /** + * Return the module note + */ + public final String getNote () + { + return _note; + } + + /** + * Return the release identifier + */ + public final String getRelease () + { + return _release; + } + + /** + * Return the RepInfo note + */ + public final String getRepInfoNote () + { + return _repInfoNote; + } + + /** + * Return the copyright information string + */ + public final String getRights () + { + return _rights; + } + + /** + * Return the List of Signatures recognized by this Module + */ + public final List getSignature () + { + return _signature; + } + + /** + * Returns a list of Document objects (one for each + * specification document of the format). The specification + * list is generated by the Module, and specifications cannot + * be added by callers. + * + * @see Document + */ + public final List getSpecification () + { + return _specification; + } + + /** + * Return the vendor information + */ + public final Agent getVendor () + { + return _vendor; + } + + /** + * Return the string describing well-formedness criteria + */ + public final String getWellFormedNote () + { + return _wellFormedNote; + } + + /** + * Return the string describing validity criteria + */ + public final String getValidityNote () + { + return _validityNote; + } + + /** + * Return the random access flag (true if the module operates + * on random access files, false if it operates on streams) + */ + public final boolean isRandomAccess () + { + return _isRandomAccess; + } + + /** + * Returns true if the module supports a given + * named feature, and false if the feature is + * unsupported or unknown. Feature names are case sensitive. + * + * It is recommended that features be named using package + * nomenclature. The following features are, by default, + * supported by the modules developed by OIS: + * + *
    + *
  • edu.harvard.hul.ois.canValidate + *
  • edu.harvard.hul.ois.canIdentify + *
+ */ + public boolean hasFeature (String feature) + { + if (_features == null) { + // dubious, but check it + return false; + } + Iterator iter = _features.iterator (); + while (iter.hasNext ()) { + String f = iter.next (); + if (f.equals (feature)) { + return true; + } + } + return false; + } + + /** + * Returns the full list of features. + */ + public List getFeatures () + { + return _features; + } + + /** + * Returns the list of default parameters. + */ + public List getDefaultParams () + { + return _defaultParams; + } + + + /****************************************************************** + * Mutator methods. + ******************************************************************/ + + /** + * Pass the associated App object to this Module. + * The App makes various services available. + */ + public final void setApp (App app) + { + _app = app; + } + + /** + * Pass the JHOVE engine object to this Module. + */ + public final void setBase (JhoveBase je) + { + _je = je; + } + + /** + * Set the value of the validityNote property, which + * briefly explains the validity criteria of this Module. + */ + public final void setValidityNote (String validityNote) + { + _validityNote = validityNote; + } + + + /** + * Set the value of the CRC32 calculated for the content object. + * The checksum-like functions can be set by the caller. + * Setting any of these creates the assumption that the + * calculation is already done, and sets the checksumFinished + * flag to inhibit recalculation. + */ + public final void setCRC32 (CRC32 crc32) + { + _crc32 = crc32; + _checksumFinished = true; + } + + /** + * Set the degree of verbosity desired from the module. The setting + * of param can override the verbosity setting. + * It does not affect whether raw data are reported or not, only + * which data are reported. + * + * + * @param verbosity The requested verbosity value. Recognized + * values are Module.MINIMUM_VERBOSITY and Module.MAXIMUM_VERBOSITY. + * The interpretation of the value depends on the module, and + * the module may choose not to use this setting. However, + * modules should treat MAXIMUM_VERBOSITY as a request for + * all the data available from the module. + */ + public void setVerbosity (int verbosity) + { + _verbosity = verbosity; + } + + /** + * Sets the byte count for the content object, and sets + * the checksumFinished flag. + */ + public final void setNByte (long nByte) + { + _nByte = nByte; + _checksumFinished = true; + } + + /** + * Sets the MD5 calculated digest for the content object, and sets + * the checksumFinished flag. + */ + public final void setMD5 (MessageDigest md5) + { + _md5 = md5; + _checksumFinished = true; + } + + /** + * Sets the SHA-1 calculated digest for the content object, and sets + * the checksumFinished flag. + */ + public final void setSHA1 (MessageDigest sha1) + { + _sha1 = sha1; + _checksumFinished = true; + } + + /****************************************************************** + * Parsing methods. + ******************************************************************/ + + /** + * Parse the content of a stream digital object and store the + * results in RepInfo. + * A given Module will normally override only one of the two + * parse methods; the default method does nothing. + * + * @param stream An InputStream, positioned at its beginning, + * which is generated from the object to be parsed. + * If multiple calls to parse are made + * on the basis of a nonzero value being returned, + * a new InputStream must be provided each time. + * + * @param info A fresh (on the first call) RepInfo object + * which will be modified + * to reflect the results of the parsing + * If multiple calls to parse are made + * on the basis of a nonzero value being returned, + * the same RepInfo object should be passed with each + * call. + * + * @param parseIndex Must be 0 in first call to parse. If + * parse returns a nonzero value, it must be + * called again with parseIndex + * equal to that return value. + */ + public int parse (InputStream stream, RepInfo info, int parseIndex) + throws IOException + { + return 0; + } + + /** + * Parse the content of a random access digital object and store the + * results in RepInfo. + * A given Module will normally override only one of the two + * parse methods; the default method does nothing. + * + * @param file A RandomAccessFile, positioned at its beginning, + * which is generated from the object to be parsed + * @param info A fresh RepInfo object which will be modified + * to reflect the results of the parsing + */ + public void parse (RandomAccessFile file, RepInfo info) + throws IOException + { + } + + /** + * Check if the digital object conforms to this Module's + * internal signature information. + * This function checks the file against the list of predefined + * signatures for the module. If there are no predefined + * signatures, it calls parse with the arguments passed to it. + * Override this for modules that check digital signatures in + * some other way. Any module for which the signature may be located + * other than at the beginning of the file must override. + * + * @param file A File object for the object being parsed + * @param stream An InputStream, positioned at its beginning, + * which is generated from the object to be parsed + * @param info A fresh RepInfo object which will be modified + * to reflect the results of the test + */ + public void checkSignatures (File file, + InputStream stream, + RepInfo info) + throws IOException + { + info.setFormat (_format[0]); + info.setMimeType (_mimeType[0]); + info.setModule (this); + int sigsChecked = 0; + if (_signature.size() > 0) { + /* Get each of the internal sigs defined for the module + * and test it. All sigs must be present. If there are + * no internal signatures, this test is meaningless. */ + byte[] sigBuf = new byte[1024]; + stream.read(sigBuf); + stream.close(); + ListIterator iter = _signature.listIterator(); + while (iter.hasNext ()) { + Signature sig = ((Signature) iter.next ()); + if (sig instanceof InternalSignature) { + InternalSignature isig = (InternalSignature) sig; + int[] sigValue = isig.getValue (); + int offset = isig.getOffset(); + boolean match = true; + for (int i = 0; i < sigValue.length; i++) { + if (sigBuf[offset + i] != sigValue[i]) { + match = false; + break; + } + } + if (!match && isig.getUse().equals (SignatureUseType.MANDATORY)) { + info.setWellFormed (false); + return; + } + if (match) { + // Only count optional signatures if they match. + ++sigsChecked; + } + } + } + } + if (sigsChecked == 0) { + // No internal sigs defined, parse the file. + int parseIndex = parse (stream, info, 0); + while (parseIndex != 0) { + stream.close (); + stream = new FileInputStream (file); + parseIndex = parse (stream, info, parseIndex); + } + } + else if (info.getWellFormed() == RepInfo.TRUE) { + info.setSigMatch(_name); + } + } + + /** + * Check if the digital object conforms to this Module's + * internal signature information. + * + * @param file A File object representing the object to be + * parsed + * + * @param raf A RandomAccessFile, positioned at its beginning, + * which is generated from the object to be parsed + * + * @param info A fresh RepInfo object which will be modified + * to reflect the results of the test + */ + public void checkSignatures (File file, + RandomAccessFile raf, + RepInfo info) + throws IOException + { + info.setFormat (_format[0]); + info.setMimeType (_mimeType[0]); + info.setModule (this); + int sigsChecked = 0; + /* Get each of the internal sigs defined for the module + * and test it. */ + ListIterator iter = _signature.listIterator(); + try { + while (iter.hasNext ()) { + Signature sig = ((Signature) iter.next ()); + if (sig instanceof InternalSignature) { + InternalSignature isig = (InternalSignature) sig; + /* What about non-fixed offset? */ + raf.seek (isig.getOffset ()); + int[] sigValue = isig.getValue (); + boolean match = true; + for (int i = 0; i < sigValue.length; i++) { + if (readUnsignedByte (raf) != sigValue[i]) { + match = false; + break; + } + } + if (!match && isig.getUse().equals (SignatureUseType.MANDATORY)) { + info.setWellFormed (false); + break; + } + if (match) { + // Only count optional signatures if they match. + ++sigsChecked; + } + } + } + } + catch (Exception e) { + // We may get here on a short file. + info.setWellFormed (false); + return; + } + // Must match at least one signature. + if (sigsChecked == 0) { + info.setWellFormed (false); + } + else if (info.getWellFormed() == RepInfo.TRUE) { + info.setSigMatch(_name); + } + } + + /** + * Initializes the state of the module for parsing. This should be + * called early in each module's parse() method. If a module + * overrides it to provide additional functionality, the module's + * initParse() should call super.initParse(). + */ + protected void initParse () + { + _logger.info (_name + " called initParse"); + _checksumFinished = false; + _nByte = 0; + _crc32 = new CRC32 (); + try { + _md5 = MessageDigest.getInstance ("MD5"); + _sha1 = MessageDigest.getInstance ("SHA-1"); + } + catch (NoSuchAlgorithmException e) { + } + } + + /** + * Calculates the checksums for a module that uses a + * random access file. + */ + protected void calcRAChecksum (Checksummer ckSummer, RandomAccessFile raf) + throws IOException + { + if (ckSummer == null) { + return; + } + + raf.seek (0); + byte [] buffer = new byte[_je.getBufferSize ()]; + int n = -1; + try { + while ((n = raf.read (buffer)) != -1) { + if (n > 0) { + ckSummer.update (buffer, 0, n); + } + } + } + catch (Exception e) {} + } + + /** + * Set the checksum values. + * @param ckSummer Checksummer object + * @param info RepInfo object + */ + protected void setChecksums (Checksummer ckSummer, RepInfo info) + { + if (ckSummer != null){ + info.setChecksum (new Checksum (ckSummer.getCRC32 (), + ChecksumType.CRC32)); + String value = ckSummer.getMD5 (); + if (value != null) { + info.setChecksum (new Checksum (value, ChecksumType.MD5)); + } + if ((value = ckSummer.getSHA1 ()) != null) { + info.setChecksum (new Checksum (value, ChecksumType.SHA1)); + } + } + } + + /** + * Generates information about this Module. + * The format of the output depends on the OutputHandler. + */ + public void show (OutputHandler handler) + { + handler.show (this); + } + + /****************************************************************** + * PRIVATE INSTANCE METHODS. + ******************************************************************/ + + /** + * Returns the hex string representation of the CRC32 result. + */ + protected String getCRC32 () + { + return Long.toHexString (_crc32.getValue ()); + } + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + ******************************************************************/ + + + /** + * Returns an Property representing an integer value. + * If raw output is specified for the module, returns + * an INTEGER property, and labels and + * index are unused. Otherwise, + * returns a STRING property, with the + * string being the element of labels + * whose index is the index of + * value in index. + */ + public Property addIntegerProperty( + String name, + int value, + String[] labels, + int[] index) + { + boolean rawOutput = _je.getShowRawFlag (); + Property prop = null; + if (!rawOutput) { + int n = -1; + for (int i = 0; i < index.length; i++) { + if (value == index[i]) { + n = i; + break; + } + } + if (n > -1) { + prop = new Property(name, PropertyType.STRING, labels[n]); + } + } + if (prop == null) { + prop = new Property(name, PropertyType.INTEGER, new Integer(value)); + } + + return prop; + } + + + /** + * Returns an Property representing an integer value. + * If raw output is specified for the module, returns + * an INTEGER property, and labels and + * index are unused. Otherwise, + * returns a STRING property, with the + * string being the element of labels + * whose index is value. + */ + public Property addIntegerProperty (String name, int value, + String [] labels) + { + if (!_je.getShowRawFlag ()) { + try { + return new Property (name, PropertyType.STRING, labels[value]); + } + catch (Exception e) { + // fall through + } + } + return new Property (name, PropertyType.INTEGER, + new Integer (value)); + } + + /** + * Reads an unsigned byte from a DataInputStream. + * @param stream Stream to read + */ + public static int readUnsignedByte (DataInputStream stream) + throws IOException + { + return readUnsignedByte (stream, null); + } + + /** + * Reads an unsigned byte from a DataInputStream. + * @param stream Stream to read + * @param counted If non-null, module for which value of _nByte + * shall be incremented appropriately + */ + public static int readUnsignedByte (DataInputStream stream, + ModuleBase counted) + throws IOException + { + int val = stream.readUnsignedByte(); + if (counted != null) { + counted._nByte++; + } + return val; + } + + /** + * Reads an unsigned byte from a RandomAccessFile. + */ + public static int readUnsignedByte (RandomAccessFile file) + throws IOException + { + return file.readUnsignedByte (); + } + + /** + * Reads into a byte buffer from a DataInputStream. + * + * @param stream Stream to read from + * @param buf Byte buffer to fill up + * @param counted If non-null, module for which value of _nByte + * shall be incremented appropriately + */ + public static int readByteBuf (DataInputStream stream, byte[] buf, + ModuleBase counted) + throws IOException + { + int bytesRead = stream.read (buf); + if (counted != null && bytesRead > 0) { + counted._nByte += bytesRead; + } + return bytesRead; + } + + /** + * Reads two bytes as an unsigned short value from a DataInputStream. + * @param stream The stream to read from. + * @param bigEndian If true, interpret the first byte as the high + * byte, otherwise interpret the first byte as + * the low byte. + */ + public static int readUnsignedShort (DataInputStream stream, + boolean bigEndian) + throws IOException + { + return readUnsignedShort (stream, bigEndian, null); + } + + /** + * Reads two bytes as an unsigned short value from a DataInputStream. + * @param stream The stream to read from. + * @param bigEndian If true, interpret the first byte as the high + * byte, otherwise interpret the first byte as + * the low byte. + */ + public static int readUnsignedShort (DataInputStream stream, + boolean bigEndian, + ModuleBase counted) + throws IOException + { + int n = 0; + if (bigEndian) { + n = stream.readUnsignedShort (); + } + else { + int b1 = stream.readUnsignedByte (); + int b0 = stream.readUnsignedByte (); + n = (b0 << 8) | b1; + } + if (counted != null) { + counted._nByte += 2; + } + return n; + } + + /** + * Reads two bytes as an unsigned short value from a + * RandomAccessFile. + * + * @param file The file to read from. + * @param bigEndian If true, interpret the first byte as the high + * byte, otherwise interpret the first byte as + * the low byte. + */ + public static int readUnsignedShort (RandomAccessFile file, + boolean bigEndian) + throws IOException + { + int n = 0; + if (bigEndian) { + n = file.readUnsignedShort (); + } + else { + int b1 = file.readUnsignedByte (); + int b0 = file.readUnsignedByte (); + n = (b0 << 8) | b1; + } + return n; + } + + /** + * Reads four bytes as an unsigned 32-bit value from a + * DataInputStream. + * + * @param stream The stream to read from. + * @param bigEndian If true, interpret the first byte as the high + * byte, otherwise interpret the first byte as + * the low byte. + */ + public static long readUnsignedInt (DataInputStream stream, + boolean bigEndian) + throws IOException + { + return readUnsignedInt (stream, bigEndian, null); + } + + /** + * Reads four bytes as an unsigned 32-bit value from a + * DataInputStream. + * + * @param stream The stream to read from. + * @param bigEndian If true, interpret the first byte as the high + * byte, otherwise interpret the first byte as + * the low byte. + */ + public static long readUnsignedInt (DataInputStream stream, + boolean bigEndian, + ModuleBase counted) + throws IOException + { + long n = 0; + if (bigEndian) { + n = stream.readInt(); /* This is a signed value. */ + if (n < 0) { + //n = 2147483648L + n; + n = (long) n & 0XFFFFFFFFL; + } + } + else { + long b3 = stream.readUnsignedByte (); + long b2 = stream.readUnsignedByte (); + long b1 = stream.readUnsignedByte (); + long b0 = stream.readUnsignedByte (); + n = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; + } + if (counted != null) { + counted._nByte += 4; + } + return n; + } + + /** + * Reads four bytes as an unsigned 32-bit value from a + * RandomAccessFile. + * + * @param file The file to read from. + * @param bigEndian If true, interpret the first byte as the high + * byte, otherwise interpret the first byte as + * the low byte. + */ + public static long readUnsignedInt (RandomAccessFile file, + boolean bigEndian) + throws IOException + { + long n = 0; + if (bigEndian) { + n = file.readInt(); /* This is a signed value. */ + if (n < 0) { + //n = 2147483648L + n; + n = (long) n & 0XFFFFFFFFL; + } + } + else { + // For efficiency, do one read rather than four + byte buf[] = new byte[4]; + file.read (buf); + long b3 = buf[0] & 0XFFL; + long b2 = buf[1] & 0XFFL; + long b1 = buf[2] & 0XFFL; + long b0 = buf[3] & 0XFFL; + n = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; + } + return n; + } + + + + /** + * Reads eight bytes as a signed 64-bit value from a + * DataInputStream. (There is no way in Java to have + * an unsigned long.) + * + * @param stream The stream to read from. + * @param bigEndian If true, interpret the first byte as the high + * byte, otherwise interpret the first byte as + * the low byte. + */ + public static long readSignedLong (DataInputStream stream, + boolean bigEndian, + ModuleBase counted) + throws IOException + { + long n = 0; + if (bigEndian) { + n = stream.readLong(); /* This is a signed value. */ + } + else { + long b7 = stream.readUnsignedByte (); + long b6 = stream.readUnsignedByte (); + long b5 = stream.readUnsignedByte (); + long b4 = stream.readUnsignedByte (); + long b3 = stream.readUnsignedByte (); + long b2 = stream.readUnsignedByte (); + long b1 = stream.readUnsignedByte (); + long b0 = stream.readUnsignedByte (); + n = (b0 << 56) | (b1 << 48) | (b2 << 40) | (b3 << 32) | + (b4 << 24) | (b5 << 16) | (b6 << 8) | b7; + } + if (counted != null) { + counted._nByte += 8; + } + return n; + } + + public static Rational readUnsignedRational (DataInputStream stream, + boolean endian) + throws IOException + { + return readUnsignedRational (stream, endian, null); + } + + public static Rational readUnsignedRational (DataInputStream stream, + boolean endian, + ModuleBase counted) + throws IOException + { + long n = readUnsignedInt (stream, endian, counted); + long d = readUnsignedInt (stream, endian, counted); + return new Rational (n, d); + } + + public static Rational readUnsignedRational (RandomAccessFile file, + boolean endian) + throws IOException + { + long n = readUnsignedInt (file, endian); + long d = readUnsignedInt (file, endian); + return new Rational (n, d); + } + + public static Rational readSignedRational (DataInputStream stream, + boolean endian, + ModuleBase counted) + throws IOException + { + long n = readSignedInt (stream, endian, counted); + long d = readSignedInt (stream, endian, counted); + return new Rational (n, d); + } + + public static Rational readSignedRational (RandomAccessFile file, + boolean endian) + throws IOException + { + long n = readSignedInt (file, endian); + long d = readSignedInt (file, endian); + return new Rational (n, d); + } + + public static int readSignedByte (RandomAccessFile file) + throws IOException + { + return file.readByte (); + } + + public static int readSignedShort (RandomAccessFile file, boolean endian) + throws IOException + { + int b = readUnsignedShort (file, endian); + if ((b & 0X8000) != 0) { + b |= ~0XFFFF; + } + return b; + } + + public static int readSignedInt (RandomAccessFile file, boolean endian) + throws IOException + { + long b = readUnsignedInt (file, endian); + if ((b & 0X80000000L) != 0) { + b |= ~0XFFFFFFFFL; + } + return (int) b; + } + + public static int readSignedByte (DataInputStream stream) + throws IOException + { + return readSignedByte (stream, null); + } + + public static int readSignedByte (DataInputStream stream, + ModuleBase counted) + throws IOException + { + int val = stream.readByte (); + if (counted != null) { + counted._nByte++; + } + return val; + } + + public static int readSignedShort (DataInputStream stream, boolean endian) + throws IOException + { + return readSignedShort (stream, endian, null); + } + + public static int readSignedShort (DataInputStream stream, boolean endian, + ModuleBase counted) + throws IOException + { + int b = readUnsignedShort (stream, endian, counted); + if ((b & 0X8000) != 0) { + b |= ~0XFFFF; + } + return b; + } + + public static int readSignedInt (DataInputStream stream, boolean endian) + throws IOException + { + return readSignedInt (stream, endian, null); + } + + public static int readSignedInt (DataInputStream stream, boolean endian, + ModuleBase counted) + throws IOException + { + long b = readUnsignedInt (stream, endian, counted); + if ((b & 0X80000000L) != 0) { + b |= ~0XFFFFFFFFL; + } + return (int) b; + } + + public static float readFloat (RandomAccessFile file, boolean endian) + throws IOException + { + float f = 0.0F; + if (endian) { + f = file.readFloat (); + } + else { + // For efficiency, do one read rather than four + byte buf[] = new byte[4]; + file.read (buf); + int b3 = buf[0] & 0XFF; + int b2 = buf[1] & 0XFF; + int b1 = buf[2] & 0XFF; + int b0 = buf[3] & 0XFF; + f = Float.intBitsToFloat (b0<<24 | b1<<16 | b2<<8 | b3); + } + return f; + } + + public static float readFloat (DataInputStream stream, boolean endian, + ModuleBase counted) + throws IOException + { + float f = 0.0F; + if (endian) { + f = stream.readFloat (); + } + else { + int b3 = stream.readUnsignedByte (); + int b2 = stream.readUnsignedByte (); + int b1 = stream.readUnsignedByte (); + int b0 = stream.readUnsignedByte (); + f = Float.intBitsToFloat (b0<<24 | b1<<16 | b2<<8 | b3); + } + if (counted != null) { + counted._nByte += 4; + } + return f; + } + + public static double readDouble (RandomAccessFile file, boolean endian) + throws IOException + { + double f = 0.0F; + if (endian) { + f = file.readDouble (); + } + else { + // For efficiency, do one read rather than eight + byte buf[] = new byte[8]; + file.read (buf); + long b7 = buf[0] & 0XFFL; + long b6 = buf[1] & 0XFFL; + long b5 = buf[2] & 0XFFL; + long b4 = buf[3] & 0XFFL; + long b3 = buf[4] & 0XFFL; + long b2 = buf[5] & 0XFFL; + long b1 = buf[6] & 0XFFL; + long b0 = buf[7] & 0XFFL; + + f = Double.longBitsToDouble (b0<<56 | b1<<48 | b2<<40 | + b3<<32 | b4<<24 | b5<<16 | + b6<< 8 | b7); + } + return f; + } + + public static double readDouble (DataInputStream stream, boolean endian) + throws IOException + { + return readDouble (stream, endian, null); + } + + public static double readDouble (DataInputStream stream, boolean endian, + ModuleBase counted) + throws IOException + { + double f = 0.0F; + if (endian) { + f = stream.readDouble (); + } + else { + long b7 = (long) stream.readUnsignedByte (); + long b6 = (long) stream.readUnsignedByte (); + long b5 = (long) stream.readUnsignedByte (); + long b4 = (long) stream.readUnsignedByte (); + long b3 = (long) stream.readUnsignedByte (); + long b2 = (long) stream.readUnsignedByte (); + long b1 = (long) stream.readUnsignedByte (); + long b0 = (long) stream.readUnsignedByte (); + f = Double.longBitsToDouble (b0<<56 | b1<<48 | b2<<40 | + b3<<32 | b4<<24 | b5<<16 | + b6<< 8 | b7); + } + if (counted != null) { + counted._nByte += 8; + } + return f; + } + + /* Skip over some bytes. */ + public int skipBytes (DataInputStream stream, int bytesToSkip) + throws IOException + { + return skipBytes (stream, bytesToSkip, null); + } + + /* Skip over some bytes. */ + public int skipBytes (DataInputStream stream, int bytesToSkip, + ModuleBase counted) + throws IOException + { + int n = stream.skipBytes (bytesToSkip); + if (counted != null) { + counted._nByte += n; + } + return n; + } + + /** + * A convenience method for getting a buffered DataInputStream + * from a module's InputStream. If the size specified is 0 or + * less, the default buffer size is used. + */ + public static DataInputStream getBufferedDataStream (InputStream stream, + int size) + { + BufferedInputStream bis; + if (size <= 0) { + bis = new BufferedInputStream (stream); + } + else { + bis = new BufferedInputStream (stream, size); + } + return new DataInputStream (bis); + } + + /** + * A utility for converting a Vector of Properties to an + * Array. It can be simpler to build a Vector and then + * call VectorToPropArray than to allocate an array and + * drop all the Properites into the correct indices. + * All the members of the Vector must be of type Property, + * or a ClassCastException will be thrown. + */ + protected Property[] vectorToPropArray (Vector vec) + { + Property[] prop = new Property[vec.size ()]; + for (int i = 0; i < vec.size (); i++) { + prop[i] = (Property) vec.elementAt (i); + } + return prop; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/ModuleInfo.java b/jhove/classes/edu/harvard/hul/ois/jhove/ModuleInfo.java new file mode 100644 index 00000000..f286b3d9 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/ModuleInfo.java @@ -0,0 +1,17 @@ +package edu.harvard.hul.ois.jhove; + +/** A small class to hold information about a module. */ +public class ModuleInfo { + public String clas; + public String init; + public String[] params; + + public ModuleInfo (String className) { + clas = className; + } + + public ModuleInfo (String className, String init) { + clas = className; + this.init = init; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/NisoImageMetadata.java b/jhove/classes/edu/harvard/hul/ois/jhove/NisoImageMetadata.java new file mode 100644 index 00000000..f4a5d14a --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/NisoImageMetadata.java @@ -0,0 +1,2117 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + + +/** + * Encapsulation of the NISO Z39.87-2002 / AIIM 20-2002 Data Dictionary -- + * Technical Metadata for Digital Still Images + */ +public class NisoImageMetadata +{ + /****************************************************************** + * PUBLIC CLASS FIELDS. + ******************************************************************/ + + /** 7.7.3.15 auto focus value labels. */ + public static final String [] AUTOFOCUS = { + "unidentified", "auto focus used", "auto focus interrupted", + "near focused", "soft focus", "manual" + }; + + /** 7.7.3.13 back light value labels. */ + public static final String [] BACKLIGHT = { + "front leight", "backlight 1", "backlight 2" + }; + + /** 6.1.2 byte order value labels. */ + public static final String [] BYTEORDER = { + "big-endian", "little-endian" + }; + + /** 6.2.3.1 Checksum method value labels. */ + public static final String [] CHECKSUM_METHOD = { + "CRC32", "MD5", "SHA-1" + }; + + /** 6.1.4.1 Color space value labels. */ + public static final String [] COLORSPACE = { + "white is zero", "black is zero", "RGB", "palette color", + "transparency mask", "CMYK", "YCbCr", "CIE L*a*b*", "ICC L*a*b*", + "ITU L*a*b*", + "CFA", + "CIE Log2(L)", + "CIE Log2(L)(u',v')", + "LinearRaw" + }; + /** Index for 6.1.4.1 color space value labels. */ + public static final int [] COLORSPACE_INDEX = { + 0, 1, 2, 3, + 4, 5, 6, 8, 9, + 10, + 32803, + 32844, + 32845, + 34892 + }; + + /** 6.1.3.1 Compression scheme value labels. */ + public static final String [] COMPRESSION_SCHEME = { + "uncompressed", "CCITT 1D", "CCITT Group 3", "CCITT Group 4", /* 1-4 */ + "LZW", "JPEG", "ISO JPEG", "Deflate", /* 5-8 */ + "JBIG", /* 32661 */ + "RLE with word alignment", /* 32771 */ + "PackBits", "NeXT 2-bit encoding", "ThunderScan 4-bit encoding", /* 32773- */ + "RasterPadding in CT or MP", /* 32895 */ + "RLE for LW", "RLE for HC", "RLE for BL", /* 32896-8 */ + "Pixar 10-bit LZW", /* 32908 */ + "Pixar companded 11-bit ZIP encoding", /* 32909 */ + "PKZIP-style Deflate encoding", /* 32946 */ + "Kodak DCS", /* 32947 */ + "SGI 32-bit Log Luminance encoding", /* 34676 */ + "SGI 24-bit Log Luminance encoding", /* 34677 */ + "JPEG 2000" /* 34712 */ + }; + /** Index for 6.1.3.1 compression scheme value labels. */ + public static final int [] COMPRESSION_SCHEME_INDEX = { + 1, 2, 3, 4, + 5, 6, 7, 8, + 32661, + 32771, + 32773, 32766, 32809, + 32895, + 32896, 32897, 32898, + 32908, + 32909, + 32946, + 32947, + 34676, + 34677, + 34712 + }; + + /** 6.2.5 display orientation value labels. */ + public static final String [] DISPLAY_ORIENTATION = { + "portrait", "landscape" + }; + + public static final String [] EXTRA_SAMPLES = { + "unspecified", "associated alpha", "unassociated alpha", + "range or depth" + }; + + /** 7.7.3.10 flash value labels. */ + public static final String [] FLASH = { + "yes", "no" + }; + + /** 7.7.3.12 flash return value labels. */ + public static final String [] FLASH_RETURN = { + "yes", "no" + }; + + /** 8.2.6 gray response unit value labels for version 0.2. */ + public static final String [] GRAY_RESPONSE_UNIT_02 = { + "", "tenths of a unit", "hundredths of a unit", + "thousandths of a unit", "ten-thousandths of a unit", + "hundred-thousandths of a unit" + }; + + /** Gray response unit value for version 2.0 of MIX, corresponding + * to NISO values of 1-5 */ + public static final String [] GRAY_RESPONSE_UNIT_20 = { + "Number represents tenths of a unit", + "Number represents hundredths of a unit", + "Number represents thousandths of a unit", + "Number represents ten-thousandths of a unit", + "Number represents hundred-thousandths of a unit" + }; + + /** extra sample value for version 2.0 of MIX, corresponding + * to NISO values of 0-3 **/ + public static final String [] EXTRA_SAMPLE_20 = { + "unspecified data", + "associated alpha data (with pre-multiplied color)", + "unassociated alpha data", + "range or depth data" + }; + + /** 7.7.3.6 metering mode value labels. */ + public static final String [] METERING_MODE = { + "unidentified", "average", "center-weighted average", "spot", + "multispot", "pattern", "partial" + }; + + /** 6.2.4 orientation value labels. */ + public static final String [] ORIENTATION = { + "", "normal", "reflected horiz", "rotated 180 deg", "reflected vert", + "left top", "rotated cw 90 deg", "Right bottom", "Rotated ccw 90 deg", + "Unknown" + }; + + /** 6.1.6 planar configuration value labels. */ + public static final String [] PLANAR_CONFIGURATION = { + "", "chunky", "planar" + }; + + /** 8.1.1 sampling frequency plane value labels. */ + public static final String [] SAMPLING_FREQUENCY_PLANE = { + "", "camera/scanner focal plane", "object plane", "source object plane" + }; + + /** 8.1.2 sampling frequency unit value labels. */ + public static final String [] SAMPLING_FREQUENCY_UNIT = { + "", "no absolute unit", "inch", "centimeter" + }; + + /** 7.7.3.7 scene illuminant value labels. */ + public static final String [] SCENE_ILLUMINANT = { + "unidentified", "daylight", "fluorescent", "tungsten lamp", + "flash", "standard illuminant A", "standard illuminat B", + "standard illuminant C", "D55 illuminant", "D65 illuminant", + "D75 illuminant" + }; + /** Index for 7.7.3.7 scene illuminant value labels. */ + public static final int [] SCENE_ILLUMINANT_INDEX = { + 0, 1, 2, 3, 10, 17, 18, 19, 20, 21, 22 + }; + + /** 6.1.5.1 segment type value labels. */ + public static final String [] SEGMENT_TYPE = { + "strips", "tiles" + }; + + /** 7.8 sensor value labels. */ + public static final String [] SENSOR = { + "Undefined", "MonochromeArea", "OneChipColorArea", "TwoChipColorArea", + "ThreeChipColorArea", "ColorSequentialArea", "MonochromeLinear", + "ColorTriLinear", "ColorSequentialLinear" + }; + + /** 8.1.7.1 (8.1.8.1) source dimension unit. */ + public static final String [] SOURCE_DIMENSION_UNIT = { + "inches", "mm" + }; + + /** 6.1.4.4 YCbCr positioning value labels. */ + public static final String [] YCBCR_POSITIONING = { + "", "centered", "cosited" + }; + + /** 8.3.1 TargetType. */ + public static final String [] TARGET_TYPE = { + "external", "internal" + }; + + /** Undefined value. */ + public static final int NULL = -1; + public static final double NILL = -1.0; + + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + * + * 6 Basic image parameters + ******************************************************************/ + + /** 6.1.1 MIME type */ + private String _mimeType; + + /** 6.1.2 Byte order */ + private String _byteOrder; + + /** 6.1.3.1 Compression scheme */ + private int _compressionScheme; + /** 6.1.3.2 Compression level */ + private int _compressionLevel; + + /** 6.1.4.1 Color space */ + private int _colorSpace; + + /** 6.1.4.2.1 ICC profile name */ + private String _profileName; + /** 6.1.4.2.2 ICC profile url */ + private String _profileURL; + + /** 6.1.4.3 YCbCr sub-sampling */ + private int [] _yCbCrSubSampling; + /** 6.1.4.4 YCbCr positioning */ + private int _yCbCrPositioning; + /** 6.1.4.5 YCbCr coefficients */ + private Rational [] _yCbCrCoefficients; + /** 6.1.4.6 Reference black and white */ + private Rational [] _referenceBlackWhite; + + /** 6.1.5.1 Segment type */ + private int _segmentType; + /** 6.1.5.2 Strip offsets */ + private long [] _stripOffsets; + /** 6.1.5.3 Rows per strip */ + private long _rowsPerStrip; + /** 6.1.5.4 Strip byte counts */ + private long [] _stripByteCounts; + /** 6.1.5.5 Tile width */ + private long _tileWidth; + /** 6.1.5.6 Tile length */ + private long _tileLength; + /** 6.1.5.7 Tile offsets */ + private long [] _tileOffsets; + /** 6.1.5.8 Tile byte counts */ + private long [] _tileByteCounts; + + /** 6.1.6 Planar configuration */ + private int _planarConfiguration; + + /** 6.2.1 Image identifier */ + private String _imageIdentifier; + /** 6.2.1.1 Image identifier location */ + private String _imageIdentifierLocation; + + /** 6.2.2 File size */ + private long _fileSize; + /** 6.2.3.1 Checksum method */ + private int _checksumMethod; + /** 6.2.3.2 Checksum value */ + private String _checksumValue; + + /** 6.2.4 orientation */ + private int _orientation; + + /** 6.2.5 Display orientation */ + private int _displayOrientation; + + /** 6.2.6.1 X targeted display aspect ratio */ + private long _xTargetedDisplayAR; + /** 6.2.6.2 Y targeted display aspect ratio */ + private long _yTargetedDisplayAR; + + /** 6.3 Preferred presentation */ + private String _preferredPresentation; + + /****************************************************************** + * 7 Image creation + ******************************************************************/ + + /** 7.1 Source type */ + private String _sourceType; + + /** 7.2 Source ID */ + private String _sourceID; + + /** 7.3 Image producer */ + private String _imageProducer; + + /** 7.4 Host computer */ + private String _hostComputer; + /** 7.4.1 Operating system */ + private String _os; + /** 7.4.2 OS version */ + private String _osVersion; + + /** 7.5 Device source */ + private String _deviceSource; + + /** 7.6.1.1 Scanner system manufacturer */ + private String _scannerManufacturer; + /** 7.6.1.2.1 Scanner model name */ + private String _scannerModelName; + /** 7.6.1.2.2 Scanner model number */ + private String _scannerModelNumber; + /** 7.6.1.2.3 Scanner model serial number */ + private String _scannerModelSerialNo; + /** 7.6.2.1 Scanning software */ + private String _scanningSoftware; + /** 7.6.2.2 Scanning software version number */ + private String _scanningSoftwareVersionNo; + + /** 7.6.3 Pixel size (in meters) */ + private double _pixelSize; + + /** 7.6.3.2.1 X physical scan resolution */ + private double _xPhysScanResolution; + /** 7.6.3.2.2 Y physical scan resolution */ + private double _yPhysScanResolution; + + /** 7.7.1 Digital camera manufacturer */ + private String _digitalCameraManufacturer; + /** 7.7.2 Digital camera model */ + private String _digitalCameraModel; + + /** 7.7.3.1 F number */ + private double _fNumber; + /** 7.7.3.2 Exposure time */ + private double _exposureTime; + /** 7.7.3.3 Brightness */ + private double _brightness; + /** 7.7.3.4 Exposure bias */ + private double _exposureBias; + /** 7.7.3.5 Subject distance */ + private double [] _subjectDistance; + /** 7.7.3.6 Metering mode */ + private int _meteringMode; + /** 7.7.3.7 Scene illuminant */ + private int _sceneIlluminant; + /** 7.7.3.8 Color temperature */ + private double _colorTemp; + /** 7.7.3.9 Focal length (in meters) */ + private double _focalLength; + /** 7.7.3.10 Flash */ + private int _flash; + /** 7.7.3.11 Flash energy */ + private double _flashEnergy; + /** 7.7.3.12 Flash return */ + private int _flashReturn; + /** 7.7.3.13 Back light */ + private int _backLight; + /** 7.7.3.14 Exposure index */ + private double _exposureIndex; + /** 7.7.3.15 Auto focus */ + private int _autoFocus; + /** 7.7.3.16.1 X print aspect ratio */ + private double _xPrintAspectRatio; + /** 7.7.3.16.2 Y print aspect ratio */ + private double _yPrintAspectRatio; + + /** 7.8 Sensor */ + private int _sensor; + + /** 7.9 Date/time created */ + private String _dateTimeCreated; + + /** 7.10 Methodology */ + private String _methodology; + + /****************************************************************** + * Imaging performance assessment + ******************************************************************/ + + /** 8.1.1 Sampling frequency plane */ + private int _samplingFrequencyPlane; + /** 8.1.2 Sampling frequency unit */ + private int _samplingFrequencyUnit; + /** 8.1.3 X sampling frequency */ + private Rational _xSamplingFrequency; + /** 8.1.4 Y sampling frequency */ + private Rational _ySamplingFrequency; + /** 8.1.5 Image width */ + private long _imageWidth; + /** 8.1.6 Image Length */ + private long _imageLength; + /** 8.1.7 Source X dimension */ + private double _sourceXDimension; + /** 8.1.8 Source X dimension unit */ + private int _sourceXDimensionUnit; + /** 8.1.9 Source Y dimension */ + private double _sourceYDimension; + /** 8.1.10 Source Y dimension unit */ + private int _sourceYDimensionUnit; + + /** 8.2.1 Bits per sample */ + private int [] _bitsPerSample; + /** 8.2.2 Samples per pixel */ + private int _samplesPerPixel; + /** 8.2.3 Extra samples */ + private int [] _extraSamples; + + /** 8.2.4.1 Colormap reference */ + private String _colormapReference; + /** 8.2.4.2 Colormap bit code value */ + private int [] _colormapBitCodeValue; + /** 8.2.4.3 Colormap red value */ + private int [] _colormapRedValue; + /** 8.2.4.4 Colormap green value */ + private int [] _colormapGreenValue; + /** 8.2.4.5 Colormap blue value */ + private int [] _colormapBlueValue; + + /** 8.2.5 Gray response curve */ + private int [] _grayResponseCurve; + /** 8.2.6 Gray response unit */ + private int _grayResponseUnit; + + /** 8.2.7.1 Whitepoint X value */ + private Rational _whitePointXValue; + /** 8.2.7.2 Whitepoint Y value */ + private Rational _whitePointYValue; + + /** 8.2.8.1 Primary chromaticities Red X */ + private Rational _primaryChromaticitiesRedX; + /** 8.2.8.2 Primary chromaticities Red Y */ + private Rational _primaryChromaticitiesRedY; + /** 8.2.8.3 Primary chromaticities Green X */ + private Rational _primaryChromaticitiesGreenX; + /** 8.2.8.4 Primary chromaticities Green Y */ + private Rational _primaryChromaticitiesGreenY; + /** 8.2.8.5 Primary chromaticities Blue X */ + private Rational _primaryChromaticitiesBlueX; + /** 8.2.8.6 Primary chromaticities Blue Y */ + private Rational _primaryChromaticitiesBlueY; + + /* 8.3 Target data */ + /** 8.3.1 Target Type */ + private int _targetType; + /** 8.3.2.1 TargetIDManufacturer */ + private String _targetIDManufacturer; + /** 8.3.2.2 TargetIDName */ + private String _targetIDName; + /** 8.3.2.3 TargetIDNo */ + private String _targetIDNo; + /** 8.3.2.4 TargetIDMedia */ + private String _targetIDMedia; + /** 8.3.3 ImageData */ + private String _imageData; + /** 8.3.4 PerformanceData */ + private String _performanceData; + /** 8.3.5 Profiles */ + private String _profiles; + + /* 9 Change history */ + /** 9.1.1 DateTimeProcessed */ + private String _dateTimeProcessed; + /** 9.1.2 SourceData */ + private String _sourceData; + /** 9.1.3 ProcessingAgency */ + private String _processingAgency; + /** 9.1.4.1 ProcessingSoftwareName */ + private String _processingSoftwareName; + /** 9.1.4.2 ProcessingSoftwareVersion */ + private String _processingSoftwareVersion; + /** 9.1.5 ProcessingActions */ + private String[] _processingActions; + + /* 9.2 PreviousImageMetadata -- not currently supported */ + + /* Data for Swing-based viewer. */ + private Property _viewerData; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** Instantiate a NisoImageMetadata object. + */ + public NisoImageMetadata () + { + _autoFocus = NULL; + _backLight = NULL; + _brightness = NILL; + _checksumMethod = NULL; + _colorSpace = NULL; + _colorTemp = NILL; + _compressionLevel = NULL; + _compressionScheme = NULL; + _dateTimeProcessed = null; + _displayOrientation = NULL; + _exposureBias = NILL; + _exposureIndex = NILL; + _exposureTime = NILL; + _fileSize = NULL; + _flash = NULL; + _flashEnergy = NILL; + _flashReturn = NULL; + _fNumber = NILL; + _focalLength = NILL; + _grayResponseUnit = NULL; + _imageData = null; + _imageLength = NULL; + _imageWidth = NULL; + _meteringMode = NULL; + _orientation = NULL; + _performanceData = null; + _pixelSize = NILL; + _planarConfiguration = NULL; + _processingActions = null; + _processingAgency = null; + _processingSoftwareName = null; + _processingSoftwareVersion = null; + _profiles = null; + _rowsPerStrip = NULL; + _samplesPerPixel = NULL; + _samplingFrequencyPlane = NULL; + _samplingFrequencyUnit = NULL; + _sceneIlluminant = NULL; + _segmentType = NULL; + _sensor = NULL; + _sourceData = null; + _sourceXDimension = NILL; + _sourceXDimensionUnit = NULL; + _sourceYDimension = NILL; + _sourceYDimensionUnit = NULL; + _tileLength = NULL; + _tileWidth = NULL; + _targetIDManufacturer = null; + _targetIDMedia = null; + _targetIDName = null; + _targetIDNo = null; + _targetType = NULL; + _xPhysScanResolution = NILL; + _xPrintAspectRatio = NILL; + _xSamplingFrequency = null; + _xTargetedDisplayAR = NULL; + _yCbCrPositioning = NULL; + _yPhysScanResolution = NILL; + _yPrintAspectRatio = NILL; + _ySamplingFrequency = null; + _yTargetedDisplayAR = NULL; + _viewerData = null; + } + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + * + * Accessor methods. + ******************************************************************/ + + /** Get 7.7.3.15 auto focus. */ + public int getAutoFocus () + { + return _autoFocus; + } + + /** Get 7.7.3.13 back light. */ + public int getBackLight () + { + return _backLight; + } + + /** Get 8.2.1 bits per sample. */ + public int [] getBitsPerSample () + { + return _bitsPerSample; + } + + /** Get 7.7.3.3 Brightness. */ + public double getBrightness () + { + return _brightness; + } + + /** Get 6.1.2 byte order. */ + public String getByteOrder () + { + return _byteOrder; + } + + /** Get 6.2.3.1 Checksum method. */ + public int getChecksumMethod () + { + return _checksumMethod; + } + + /** Get 6.2.3.2 Checksum value. */ + public String getChecksumValue () + { + return _checksumValue; + } + + /** Get 8.2.4.2 colormap bit code value. */ + public int [] getColormapBitCodeValue () + { + return _colormapBitCodeValue; + } + + /** Get 8.2.4.5 colormap blue value. */ + public int [] getColormapBlueValue () + { + return _colormapBlueValue; + } + + /** Get 8.2.4.4 colormap green value. */ + public int [] getColormapGreenValue () + { + return _colormapGreenValue; + } + + /** Get 8.2.4.3 colormap red value. */ + public int [] getColormapRedValue () + { + return _colormapRedValue; + } + + /** Get 8.2.4.1 colormap reference. */ + public String getColormapReference () + { + return _colormapReference; + } + + /** Get 6.1.4.1 color space. */ + public int getColorSpace () + { + return _colorSpace; + } + + /** Get 7.7.3.8 color temperature. */ + public double getColorTemp () + { + return _colorTemp; + } + + /** Get 6.1.3.2 compression level. */ + public int getCompressionLevel () + { + return _compressionLevel; + } + + /** Get 6.1.3.1 compression scheme. */ + public int getCompressionScheme () + { + return _compressionScheme; + } + + /** Get 7.9 date/time created. */ + public String getDateTimeCreated () + { + return _dateTimeCreated; + } + + /** Get 9.1.1 DateTimeProcessed */ + public String getDateTimeProcessed () + { + return _dateTimeProcessed; + } + + /** Get 7.5 device source. */ + public String getDeviceSource () + { + return _deviceSource; + } + + /** Get 7.7.1 digital camera manufacturer. */ + public String getDigitalCameraManufacturer () + { + return _digitalCameraManufacturer; + } + + /** Get 7.7.2 digital camera model. */ + public String getDigitalCameraModel () + { + return _digitalCameraModel; + } + + /** Get 6.2.5 Display orientation. */ + public int getDisplayOrientation () + { + return _displayOrientation; + } + + /** Get 7.7.3.4 exposure bias. */ + public double getExposureBias () + { + return _exposureBias; + } + + /** Get 7.7.3.14 exposure index. */ + public double getExposureIndex () + { + return _exposureIndex; + } + + /** Get 7.7.3.2 exposure time. */ + public double getExposureTime () + { + return _exposureTime; + } + + /** Get 8.2.3 extra samples. */ + public int [] getExtraSamples () + { + return _extraSamples; + } + + /** Get 6.2.2 file size. */ + public long getFileSize () + { + return _fileSize; + } + + /** Get 7.7.3.10 flash. */ + public int getFlash () + { + return _flash; + } + + /** Get 7.7.3.11 flash energy. */ + public double getFlashEnergy () + { + return _flashEnergy; + } + + /** Get 7.7.3.12 flash return. */ + public int getFlashReturn () + { + return _flashReturn; + } + + /** Get 7.7.3.1 F number. */ + public double getFNumber () + { + return _fNumber; + } + + /** Get 7.7.3.9 focal length. */ + public double getFocalLength () + { + return _focalLength; + } + + /** Get 8.2.5 gray response curve. */ + public int [] getGrayResponseCurve () + { + return _grayResponseCurve; + } + + /** Get 8.2.6 gray response unit. */ + public int getGrayResponseUnit () + { + return _grayResponseUnit; + } + + /** Get 7.4 host computer. */ + public String getHostComputer () + { + return _hostComputer; + } + + /** Get 8.3.3 ImageData */ + public String getImageData () + { + return _imageData; + } + + /** Get 6.2.1 Image identifier. */ + public String getImageIdentifier () + { + return _imageIdentifier; + } + + /** Get 6.2.1.1 Image identifier location. */ + public String getImageIdentifierLocation () + { + return _imageIdentifierLocation; + } + + /** Get 8.1.6 image length. */ + public long getImageLength () + { + return _imageLength; + } + + /** Get 7.3 Image producer. */ + public String getImageProducer () + { + return _imageProducer; + } + + /** Get 8.1.5 image width. */ + public long getImageWidth () + { + return _imageWidth; + } + + /** Get 7.7.3.6 metering mode. */ + public int getMeteringMode () + { + return _meteringMode; + } + + /** Get 7.10 methodology. */ + public String getMethodology () + { + return _methodology; + } + + /** Get 6.1.1 MIME type. */ + public String getMimeType () + { + return _mimeType; + } + + /** Get 6.2.4 Orientation. */ + public int getOrientation () + { + return _orientation; + } + + /** Get 7.4.1 OS (operating system). */ + public String getOS () + { + return _os; + } + + /** Get 7.4.2 OS version. */ + public String getOSVersion () + { + return _osVersion; + } + + /** Get 8.3.4 PerformanceData. */ + public String getPerformanceData () + { + return _performanceData; + } + + /** Get 7.6.3.1 pixel size. */ + public double getPixelSize () + { + return _pixelSize; + } + + /** Get 6.1.6 Planar configuration. */ + public int getPlanarConfiguration () + { + return _planarConfiguration; + } + + /** Get 6.3 preferred presentation. */ + public String getPreferredPresentation () + { + return _preferredPresentation; + } + + /** Get 8.2.8.5 primary chromaticities blue X. */ + public Rational getPrimaryChromaticitiesBlueX () + { + return _primaryChromaticitiesBlueX; + } + + /** Get 8.2.8.6 primary chromaticities blue Y. */ + public Rational getPrimaryChromaticitiesBlueY () + { + return _primaryChromaticitiesBlueY; + } + + /** Get 8.2.8.3 primary chromaticities green X. */ + public Rational getPrimaryChromaticitiesGreenX () + { + return _primaryChromaticitiesGreenX; + } + + /** Get 8.2.8.4 primary chromaticities green Y. */ + public Rational getPrimaryChromaticitiesGreenY () + { + return _primaryChromaticitiesGreenY; + } + + /** Get 8.2.8.1 primary chromaticities red X. */ + public Rational getPrimaryChromaticitiesRedX () + { + return _primaryChromaticitiesRedX; + } + + /** Get 8.2.8.2 primary chromaticities red Y. */ + public Rational getPrimaryChromaticitiesRedY () + { + return _primaryChromaticitiesRedY; + } + + /** Get 9.1.5 ProcessingActions. */ + public String[] getProcessingActions () + { + return _processingActions; + } + + /** Get 9.1.3 ProcessingAgency. */ + public String getProcessingAgency () + { + return _processingAgency; + } + + /** Get 9.1.4.1 ProcessingSoftwareName */ + public String getProcessingSoftwareName () + { + return _processingSoftwareName; + } + + /** Get 9.1.4.2 ProcessingSoftwareVersion */ + public String getProcessingSoftwareVersion () + { + return _processingSoftwareVersion; + } + + /** Get 6.1.4.2.1 ICC profile name. */ + public String getProfileName () + { + return _profileName; + } + + /** Get 8.3.5 Profiles */ + public String getProfiles () + { + return _profiles; + } + + /** Get 6.1.4.2.2 ICC profile URL. */ + public String getProfileURL () + { + return _profileURL; + } + + /** Get 6.1.4.6 Reference black and white. */ + public Rational [] getReferenceBlackWhite () + { + return _referenceBlackWhite; + } + + /** Get 6.1.5.3 Rows per strip. */ + public long getRowsPerStrip () + { + return _rowsPerStrip; + } + + /** Get 8.2.2 samples per pixel. */ + public int getSamplesPerPixel () + { + return _samplesPerPixel; + } + + /** Get 8.1.1 sampling frequency plane. */ + public int getSamplingFrequencyPlane () + { + return _samplingFrequencyPlane; + } + + /** Get 8.1.2 sampling frequency unit. */ + public int getSamplingFrequencyUnit () + { + return _samplingFrequencyUnit; + } + + /** Get 7.6.1.1 scanner manufacturer. */ + public String getScannerManufacturer () + { + return _scannerManufacturer; + } + + /** Get 7.6.1.2.1 scanner model name. */ + public String getScannerModelName () + { + return _scannerModelName; + } + + /** Get 7.6.1.2.2 scanner model number. */ + public String getScannerModelNumber () + { + return _scannerModelNumber; + } + + /** Get 7.6.1.2.3 scanner model serial number. */ + public String getScannerModelSerialNo () + { + return _scannerModelSerialNo; + } + + /** Get 7.6.2.1 scanning software. */ + public String getScanningSoftware () + { + return _scanningSoftware; + } + + /** Get 7.6.2.2 scanning software version number. */ + public String getScanningSoftwareVersionNo () + { + return _scanningSoftwareVersionNo; + } + + /** Get 7.7.3.7 scene illuminant. */ + public int getSceneIlluminant () + { + return _sceneIlluminant; + } + + /** Get 6.1.5.1 segment type. */ + public int getSegmentType () + { + return _segmentType; + } + + /** Get 7.8 sensor. */ + public int getSensor () + { + return _sensor; + } + + /** Get 9.1.2 SourceData. */ + public String getSourceData () + { + return _sourceData; + } + + /** Get 7.2 source ID. */ + public String getSourceID () + { + return _sourceID; + } + + /** Get 7.1 Source type. */ + public String getSourceType () + { + return _sourceType; + } + + public double getSourceXDimension () + { + return _sourceXDimension; + } + + public int getSourceXDimensionUnit () + { + return _sourceXDimensionUnit; + } + + public double getSourceYDimension () + { + return _sourceYDimension; + } + + public int getSourceYDimensionUnit () + { + return _sourceYDimensionUnit; + } + + /** Get 6.1.5.4 Strip byte counts. */ + public long [] getStripByteCounts () + { + return _stripByteCounts; + } + + /** Get 6.1.5.2 Strip offsets. */ + public long [] getStripOffsets () + { + return _stripOffsets; + } + + /** Get 7.7.3.5 Subject distance. */ + public double [] getSubjectDistance () + { + return _subjectDistance; + } + + /** Get 8.3.2.1 TargetIDManufacturer */ + public String getTargetIDManufacturer () + { + return _targetIDManufacturer; + } + + /** Get 8.3.2.3 TargetIDMedia */ + public String getTargetIDMedia () + { + return _targetIDMedia; + } + + /** Get 8.3.2.2 TargetIDName */ + public String getTargetIDName () + { + return _targetIDName; + } + + /** Get 8.3.2.3 TargetIDNo */ + public String getTargetIDNo () + { + return _targetIDNo; + } + + /** Get 8.3.1 Target Type */ + public int getTargetType () + { + return _targetType; + } + + /** Get 6.1.5.8 Tile byte counts. */ + public long [] getTileByteCounts () + { + return _tileByteCounts; + } + + /** Get 6.1.5.6 Tile length. */ + public long getTileLength () + { + return _tileLength; + } + + /** Get 6.1.5.7 Tile offsets. */ + public long [] getTileOffsets () + { + return _tileOffsets; + } + + /** Get 6.1.5.5 Tile width. */ + public long getTileWidth () + { + return _tileWidth; + } + + /** Get 8.2.7.1 white point X value. */ + public Rational getWhitePointXValue () + { + return _whitePointXValue; + } + + /** Get 8.2.7.2 white point Y value. */ + public Rational getWhitePointYValue () + { + return _whitePointYValue; + } + + /** Get 7.7.3.16.1 X print aspect ratio. */ + public double getXPrintAspectRatio () + { + return _xPrintAspectRatio; + } + + /** Get 7.6.3.2.1 X physcal scanning resolution. */ + public double getXPhysScanResolution () + { + return _xPhysScanResolution; + } + + /** Get 8.1.3 X sampling frequency. */ + public Rational getXSamplingFrequency () + { + return _xSamplingFrequency; + } + + /** Get 6.2.6 X targeted display aspect ratio. */ + public long getXTargetedDisplayAR () + { + return _xTargetedDisplayAR; + } + + /** Get 6.1.4.5 YCbCr coefficients. */ + public Rational [] getYCbCrCoefficients () + { + return _yCbCrCoefficients; + } + + /** Get 6.1.4.4 YCbCr positioning. */ + public int getYCbCrPositioning () + { + return _yCbCrPositioning; + } + + /** Get 6.1.4.3 YCbCr subsampling. */ + public int [] getYCbCrSubSampling () + { + return _yCbCrSubSampling; + } + + /** Get 7.6.3.2.2 Y physcal scanning resolution. */ + public double getYPhysScanResolution () + { + return _yPhysScanResolution; + } + + /** Get 7.7.3.16.2 Y print aspect ratio. */ + public double getYPrintAspectRatio () + { + return _yPrintAspectRatio; + } + + /** Get 8.1.4 Y sampling frequency. */ + public Rational getYSamplingFrequency () + { + return _ySamplingFrequency; + } + + /** Get 6.2.7 Y targeted display aspect ratio. */ + public long getYTargetedDisplayAR () + { + return _yTargetedDisplayAR; + } + + /** Get data for Swing GUI viewer. */ + public Property getViewerData () + { + return _viewerData; + } + + + /****************************************************************** + * Mutator methods. + ******************************************************************/ + + /** Set 7.7.3.15 auto focus. + * @param focus Auto focus + */ + public void setAutoFocus (int focus) + { + _autoFocus = focus; + } + + /** Set 7.7.3.13 back light. + * @param light Back light + */ + public void setBackLight (int light) + { + _backLight = light; + } + + /** Set 8.2.1 bits per sample. + * @param bits Bits per sample + */ + public void setBitsPerSample (int [] bits) + { + _bitsPerSample = bits; + } + + /** Set 7.7.3.3 brightness. + * @param brightness Brightness + */ + public void setBrightness (double brightness) + { + _brightness = brightness; + } + + /** Set 6.1.2 byte order. + * @param order Byte order + */ + public void setByteOrder (String order) + { + _byteOrder = order; + } + + /** Set 8.2.4.2 colormap bit code value. + * @param value Bit code value + */ + public void setColormapBitCodeValue (int [] value) + { + _colormapBitCodeValue = value; + } + + /** Set 8.2.4.4 colormap blue value. + * @param value Blue value + */ + public void setColormapBlueValue (int [] value) + { + _colormapBlueValue = value; + } + + /** Set 8.2.4.3 colormap green value. + * @param value Green value + */ + public void setColormapGreenValue (int [] value) + { + _colormapGreenValue = value; + } + + /** Set 8.2.4.2 colormap red value. + * @param value Red value + */ + public void setColormapRedValue (int [] value) + { + _colormapRedValue = value; + } + + /** Set 8.2.4.1 colormap reference. + * @param reference Colormap reference + */ + public void setColormapReference (String reference) + { + _colormapReference = reference; + } + + /** Set 6.1.4.1 color space + * @param space Color space + */ + public void setColorSpace (int space) + { + _colorSpace = space; + } + + /** Set 7.7.3.8 color temperature. + * @param temp Color temperature + */ + public void setColorTemp (double temp) + { + _colorTemp = temp; + } + + /** Set 6.1.3.2 compression level. + * @param level Compression level + */ + public void setCompressionLevel (int level) + { + _compressionLevel = level; + } + + /** Set 6.1.3.1 compression scheme. + * @param scheme Compression scheme + */ + public void setCompressionScheme (int scheme) + { + _compressionScheme = scheme; + } + + /** Set 7.9 date/time created. + * TIFF dates get converted to ISO 8601 format. + * @param date Date/time created + */ + public void setDateTimeCreated (String date) + { + _dateTimeCreated = make8601Valid (date); + } + + /** Set 9.1.1 DateTimeProcessed. + * TIFF dates get converted to ISO 8601 format. + * @param date Date/time processed + */ + public void setDateTimeProcessed (String date) + { + _dateTimeProcessed = make8601Valid (date); + } + + /** Set 7.5 Device source. + * @param source Device source + */ + public void setDeviceSource (String source) + { + _deviceSource = source; + } + + /** Set 7.7.1 digital camera manufacturer. + * @param manufacturer Camera manufacturer + */ + public void setDigitalCameraManufacturer (String manufacturer) + { + _digitalCameraManufacturer = manufacturer; + } + + /** Set 7.7.2 digital camera model. + * @param model Camera model + */ + public void setDigitalCameraModel (String model) + { + _digitalCameraModel = model; + } + + /** Set 6.2.5 display orientation. + * @param orientation Display orientation + */ + public void setDisplayOrientation (int orientation) + { + _displayOrientation = orientation; + } + + /** Set 7.2.3.4 exposure bias. + * @param bias Exposure bias + */ + public void setExposureBias (double bias) + { + _exposureBias = bias; + } + + /** Set 7.2.3.14 exposure index. + * @param index Exposure index + */ + public void setExposureIndex (double index) + { + _exposureIndex = index; + } + + /** Set 7.7.3.2 exposure time. + * @param time Exposure time + */ + public void setExposureTime (double time) + { + _exposureTime = time; + } + + /** Set 8.2.3 extra samples. + * @param extra Extra samples + */ + public void setExtraSamples (int [] extra) + { + _extraSamples = extra; + } + + /** Set 6.2.2 file size. + * @param size File size + */ + public void setFileSize (long size) + { + _fileSize = size; + } + + /** Set 7.7.3.1 F number. + * @param f F number + */ + public void setFNumber (double f) + { + _fNumber = f; + } + + /** Set 7.7.3.11 flash energy. + * @param energy Flash energy + */ + public void setFlashEnergy (double energy) + { + _flashEnergy = energy; + } + + /** Set 7.7.3.12 flash return. + * @param ret Flash return + */ + public void setFlashReturn (int ret) + { + _flashReturn = ret; + } + + /** Set 7.7.3.10 flash. + * @param flash Flash + */ + public void setFlash (int flash) + { + _flash = flash; + } + + /** Set 7.7.3.9 focal length (double meters). + * @param length Focal length + */ + public void setFocalLength (double length) + { + _focalLength = length; + } + + /** Set 8.2.5 gray response curve. + * @param curve Gray response curve + */ + public void setGrayResponseCurve (int [] curve) + { + _grayResponseCurve = curve; + } + + /** Set 8.2.6 gray response unit. + * @param unit Gray response unit + */ + public void setGrayResponseUnit (int unit) + { + _grayResponseUnit = unit; + } + + /** Set 7.4 host computer. + * @param computer Host computer + */ + public void setHostComputer (String computer) + { + _hostComputer = computer; + } + + /** Set 8.3.3 ImageData. + * @param imageData Image Data filename or URN + */ + public void setImageData (String imageData) + { + _imageData = imageData; + } + + /** Set 6.2.1 Image identifier. + * @param identifier Image identifier + */ + public void setImageIdentifier (String identifier) + { + _imageIdentifier = identifier; + } + + /** Set 6.2.1 Image identifier location. + * @param location identifier location + */ + public void setImageIdentifierLocation (String location) + { + _imageIdentifierLocation = location; + } + + /** Set 8.1.6 image length. + * @param length Image length + */ + public void setImageLength (long length) + { + _imageLength = length; + } + + /** Set 7.3 image producer. + * @param producer Image producer + */ + public void setImageProducer (String producer) + { + _imageProducer = producer; + } + + /** Set 8.1.5 image width. + * @param width Image width + */ + public void setImageWidth (long width) + { + _imageWidth = width; + } + + /** Set 7.7.3.6 metering mode. + * @param mode Metering mode + */ + public void setMeteringMode (int mode) + { + _meteringMode = mode; + } + + /** Set 7.10 methodology. + * @param methodology Methodology + */ + public void setMethodology (String methodology) + { + _methodology = methodology; + } + + /** Set 6.1.1 MIME type. + * @param type MIME type + */ + public void setMimeType (String type) + { + _mimeType = type; + } + + /** Set 6.2.4 orientation. + * @param orientation Orientation + */ + public void setOrientation (int orientation) + { + _orientation = orientation; + } + + /* Set 7.4.1 OS (operating system). + * @param os Operating system + */ + public void setOS (String os) + { + _os = os; + } + + /** Set 7.4.2 OS version. + * @param version OS version + */ + public void setOSVersion (String version) + { + _osVersion = version; + } + + /** Set 8.3.4 PerformanceData. + * @param performanceData Performance data filename or URN + */ + public void setPerformanceData (String performanceData) + { + _performanceData = performanceData; + } + + /** Set 7.6.3.1 pixel size. + * @param size Pixel size + */ + public void setPixelSize (double size) + { + _pixelSize = size; + } + + /** Set 6.1.6 Planar configuration. + * @param configuration Planar configuration + */ + public void setPlanarConfiguration (int configuration) + { + _planarConfiguration = configuration; + } + + /** Set 6.3 preferred presentation. + * @param presentation Preferred presentation + */ + public void setPreferredPresentation (String presentation) + { + _preferredPresentation = presentation; + } + + /** Set 8.2.8.5 primary chromaticities blue X. + * @param x Blue x + */ + public void setPrimaryChromaticitiesBlueX (Rational x) + { + _primaryChromaticitiesBlueX = x; + } + + /** Set 8.2.8.6 primary chromaticities blue Y. + * @param y Blue y + */ + public void setPrimaryChromaticitiesBlueY (Rational y) + { + _primaryChromaticitiesBlueY = y; + } + + /** Set 8.2.8.3 primary chromaticities green X. + * @param x Green x + */ + public void setPrimaryChromaticitiesGreenX (Rational x) + { + _primaryChromaticitiesGreenX = x; + } + + /** Set 8.2.8.4 primary chromaticities green Y. + * @param y Green y + */ + public void setPrimaryChromaticitiesGreenY (Rational y) + { + _primaryChromaticitiesGreenY = y; + } + + /** Set 8.2.8.1 primary chromaticities red X. + * @param x Red x + */ + public void setPrimaryChromaticitiesRedX (Rational x) + { + _primaryChromaticitiesRedX = x; + } + + /** Set 8.2.8.2 primary chromaticities red Y. + * @param y Red y + */ + public void setPrimaryChromaticitiesRedY (Rational y) + { + _primaryChromaticitiesRedY = y; + } + + /** Set 9.1.5 ProcessingActions. + * @param actions Array of strings giving image processing steps + */ + public void setProcessingActions (String[] actions) + { + _processingActions = actions; + } + + /** Set 9.1.3 ProcessingAgency. + * @param processingAgency Identifier of producing organization + */ + public void setProcessingAgency (String processingAgency) + { + _processingAgency = processingAgency; + } + + /** Set 9.1.4.1 ProcessingSoftwareName + * @param name Name of the image processing software + */ + public void setProcessingSoftwareName (String name) + { + _processingSoftwareName = name; + } + + /** Set 9.1.4.2 ProcessingSoftwareVersion + * @param version Version number of the processing software + */ + public void setProcessingSoftwareVersion (String version) + { + _processingSoftwareVersion = version; + } + + /** Set 6.1.4.1 ICC profile name. + * @param name Profile name + */ + public void setProfileName (String name) + { + _profileName = name; + } + + /** Set 8.3.5 Profiles. + * @param profiles Color profile filename or URN + */ + public void setProfiles (String profiles) + { + _profiles = profiles; + } + + /** Set 6.1.4.2 ICC profile URL. + * @param URL Profile URL + */ + public void setProfileURL (String URL) + { + _profileURL = URL; + } + + /** Set 6.1.4.6 reference black and white. + * @param reference Reference + */ + public void setReferenceBlackWhite (Rational [] reference) + { + _referenceBlackWhite = reference; + } + + /** Set 6.1.5.3 Rows per strip. + * @param rows Rows per strip + */ + public void setRowsPerStrip (long rows) + { + _rowsPerStrip = rows; + } + + /** Set 8.1.1 sampling frequency plane. + * @param plane Sampling frequency plane + */ + public void setSamplingFrequencyPlane (int plane) + { + _samplingFrequencyPlane = plane; + } + + /** Set 8.2.2 samples per pixel. + * @param samples Samples per pixel + */ + public void setSamplesPerPixel (int samples) + { + _samplesPerPixel = samples; + } + + /** Set 8.1.2 sampling frequency unit. + * @param unit Sampling frequency unit + */ + public void setSamplingFrequencyUnit (int unit) + { + _samplingFrequencyUnit = unit; + } + + /** Set 7.6.1.1 scanner manufacturer. + * @param manufacturer Scanner manufacturer + */ + public void setScannerManufacturer (String manufacturer) + { + _scannerManufacturer = manufacturer; + } + + /** Set 7.6.1.2.1 scanner model name. + * @param name Scanner model name + */ + public void setScannerModelName (String name) + { + _scannerModelName = name; + } + + /** Set 7.6.1.2.2 scanner model number. + * @param number Scanner model number + */ + public void setScannerModelNumber (String number) + { + _scannerModelNumber = number; + } + + /** Set 7.6.1.2.3 scanner model serial number. + * @param number Scanner model serial number + */ + public void setScannerModelSerialNo (String number) + { + _scannerModelSerialNo = number; + } + + /** Set 7.6.2.1 scanning software. + * @param software Scanning software + */ + public void setScanningSoftware (String software) + { + _scanningSoftware = software; + } + + /** Set 7.6.2.2 scanning software version number. + * @param number Scanning software version number + */ + public void setScanningSoftwareVersionNo (String number) + { + _scanningSoftwareVersionNo = number; + } + + /** Set 7.7.3.7 scene illuminant. + * @param illuminant Scene illuminant + */ + public void setSceneIlluminant (int illuminant) + { + _sceneIlluminant = illuminant; + } + + /** Set 7.8 sensor. + * @param sensor Sensor + */ + public void setSensor (int sensor) + { + _sensor = sensor; + } + + /** Set 9.1.2 SourceData. + * @param sourceData Source data identifier + */ + public void setSourceData (String sourceData) + { + _sourceData = sourceData; + } + + /** Set 7.2 source ID. + * @param id Source ID + */ + public void setSourceID (String id) + { + _sourceID = id; + } + + /** Set 7.1 source type. + * @param type Source type + */ + public void setSourceType (String type) + { + _sourceType = type; + } + + /** Set 8.1.7 source X dimension. + * @param x X dimension + */ + public void setSourceXDimension (double x) + { + _sourceXDimension = x; + } + + /** Set 8.1.7.1 source X dimension unit. + * @param unit X dimension unit + */ + public void setSourceXDimensionUnit (int unit) + { + _sourceXDimensionUnit = unit; + } + + /** Set 8.1.8 source Y dimension. + * @param y Y dimension + */ + public void setSourceYDimension (double y) + { + _sourceYDimension = y; + } + + /** Set 8.1.8.1 source Y dimension unit. + * @param unit Y dimension unit + */ + public void setSourceYDimensionUnit (int unit) + { + _sourceYDimensionUnit = unit; + } + + /** Set 6.1.5.4 Strip byte counts. + * @param counts Byte counts + */ + public void setStripByteCounts (long [] counts) + { + _stripByteCounts = counts; + } + + /** Set 6.1.5.2 Strip offsets. + * @param offsets Strip offsets + */ + public void setStripOffsets (long [] offsets) + { + _stripOffsets = offsets; + } + + /** Set 7.7.3.5 Subject distance + * @param distance Subject distance + */ + public void setSubjectDistance (double [] distance) + { + _subjectDistance = distance; + } + + /** Set 8.3.2.1 TargetIDManufacturer */ + public void setTargetIDManufacturer (String targetIDManufacturer) + { + _targetIDManufacturer = targetIDManufacturer; + } + + /** Set 8.3.2.4 TargetIDMedia */ + public void setTargetIDMedia (String targetIDMedia) + { + _targetIDMedia = targetIDMedia; + } + + /** Set 8.3.2.2 TargetIDName */ + public void setTargetIDName (String targetIDName) + { + _targetIDName = targetIDName; + } + + /** Set 8.3.2.3 TargetIDNo */ + public void setTargetIDNo (String targetIDNo) + { + _targetIDNo = targetIDNo; + } + + /** Set 8.3.1 TargetType */ + public void setTargetType (int targetType) + { + _targetType = targetType; + } + + /** Set 6.1.5.8 Tile byte counts. + * @param counts Byte counts + */ + public void setTileByteCounts (long [] counts) + { + _tileByteCounts = counts; + } + + /** Set 6.1.5.6 Tile length. + * @param length Tile length + */ + public void setTileLength (long length) + { + _tileLength = length; + } + + /** Set 6.1.5.7 Tile offsets. + * @param offsets tile offsets + */ + public void setTileOffsets (long [] offsets) + { + _tileOffsets = offsets; + } + + /** Set 6.1.5.5 Tile width. + * @param width Tile width + */ + public void setTileWidth (long width) + { + _tileWidth = width; + } + + /** Set 8.2.7.1 white point X value. + * @param x White point X + */ + public void setWhitePointXValue (Rational x) + { + _whitePointXValue = x; + } + + /** Set 8.2.7.2 white point Y value. + * @param y White point Y + */ + public void setWhitePointYValue (Rational y) + { + _whitePointYValue = y; + } + + /** Set 7.6.3.2.1 X physical scanning resolution. + * @param x X physical scanning resolution + */ + public void setXPhysScanResolution (double x) + { + _xPhysScanResolution = x; + } + + /** Set 7.7.3.16.1 X print aspect ratio. + * @param x X aspect ratio + */ + public void setXPrintAspectRatio (double x) + { + _xPrintAspectRatio = x; + } + + /** Set 8.1.3 X sampling frequency. + * @param x X sampling frequency + */ + public void setXSamplingFrequency (Rational x) + { + _xSamplingFrequency = x; + } + + /** Set 6.2.6.1 X targeted display aspect ratio. + * @param x X units + */ + public void setXTargetedDisplayAspectRatio (long x) + { + _xTargetedDisplayAR = x; + } + + /** Set 6.1.4.5 YCbCr coefficients. + * @param coefficients Coefficients + */ + public void setYCbCrCoefficients (Rational [] coefficients) + { + _yCbCrCoefficients = coefficients; + } + + /** Set 6.1.4.4 YCbCr positioning. + * @param positioning Positioning + */ + public void setYCbCrPositioning (int positioning) + { + _yCbCrPositioning = positioning; + } + + /** Set 6.1.4.3 YCbCr Sub-sampling. + * @param sampling Sub-sampling + */ + public void setYCbCrSubSampling (int [] sampling) + { + _yCbCrSubSampling = sampling; + } + + /** Set 7.6.3.2.2 Y physical scanning resolution. + * @param y Y physical scanning resolution + */ + public void setYPhysScanResolution (double y) + { + _yPhysScanResolution = y; + } + + /** Set 7.7.3.16.2 Y print aspect ratio. + * @param y Y aspect ratio + */ + public void setYPrintAspectRatio (double y) + { + _yPrintAspectRatio = y; + } + + /** Set 8.1.4 Y sampling frequency. + * @param y Y sampling frequency + */ + public void setYSamplingFrequency (Rational y) + { + _ySamplingFrequency = y; + } + + /** Set 6.2.6.2 Y targeted display aspect ratio. + * @param y Y units + */ + public void setYTargetedDisplayAspectRatio (long y) + { + _yTargetedDisplayAR = y; + } + + /** Set information for Swing GUI viewer. + * @param viewerData Private data for RepTreeModel + */ + public void setViewerData (Property viewerData) + { + _viewerData = viewerData; + } + + /* Canonicizes (canonizes? whatever) a date to ISO + * 8601 format. Returns null if it can't make sense of + * it. Returns the date unchanged if it's already + * canonical. Initially this converts TIFF dates to ISO. + */ + private String make8601Valid (String date) + { + try { + if (date.charAt (4) == ':') { + // It's a TIFF date, or a good imitation of one. + // TIFF dates have exact offsets, making things easy. + String yr = date.substring (0, 4); + String mo = date.substring (5, 7); + String da = date.substring (8, 10); + String hr = date.substring (11, 13); + String mi = date.substring (14, 16); + String se = date.substring (17, 19); + return yr + "-" + mo + "-" + da + "T" + + hr + ":" + mi + ":" + se; + } + return date; // default + } + catch (Exception e) { + // Malformed date + return null; + } + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/ObjectIdentifier.java b/jhove/classes/edu/harvard/hul/ois/jhove/ObjectIdentifier.java new file mode 100644 index 00000000..83dd0cf7 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/ObjectIdentifier.java @@ -0,0 +1,118 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003-4 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +import java.io.*; +import java.util.*; + +/** + * Module for identification of a document. "Identification" + * means determining, by querying modules successively, what the format + * of a document is. The Bytestream module is always queried last, + * so a document will by identified as a Bytestream if all else fails. + */ +public class ObjectIdentifier +{ + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + private List _moduleList; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + public ObjectIdentifier (List moduleList) + { + _moduleList = moduleList; + } + + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + * + * Processing methods. + * + ******************************************************************/ + + /** + * Perform identification on a file. The file is parsed by + * each of the modules in the module list until one + * declares that the file is well-formed. It is assumed + * that there is a module in the list (normally Bytestream + * at the end) which will always consider a file well-formed. + */ + public void identify (File file, RepInfo info, + String parm, boolean verbose, + boolean shortCheck) + throws IOException + { + /****************************************************** + * Go through all modules, in the order in the config + * file, calling the parse method till we find one + * which matches. + ******************************************************/ + + ListIterator modIter = _moduleList.listIterator(); + while (modIter.hasNext ()) { + /* We need clean RepInfo for each run */ + RepInfo info1; + info1 = (RepInfo) info.clone (); + + Module mod = (Module) modIter.next (); + try { + if (!mod.hasFeature("edu.harvard.hul.ois.jhove.canValidate")) { + continue; + } + if (mod.isRandomAccess ()) { + RandomAccessFile raf = + new RandomAccessFile (file, "r"); + mod.param (parm); + if (verbose) { + mod.setVerbosity (Module.MAXIMUM_VERBOSITY); + } + if (shortCheck) { + mod.checkSignatures (file, raf, info1); + } + else { + mod.parse (raf, info1); + } + raf.close (); + } + else { + InputStream stream = new FileInputStream (file); + mod.param (parm); + if (shortCheck) { + mod.checkSignatures (file, stream, info1); + } + else { + int parseIndex = mod.parse (stream, info1, 0); + while (parseIndex != 0) { + stream.close (); + stream = new FileInputStream (file); + parseIndex = mod.parse (stream, info1, parseIndex); + } + } + stream.close (); + } + } + catch (Exception e) { + /* The assumption is that in trying to analyze + the wrong type of file, the module may go + off its track and throw an exception, so we + just continue on to the next module. + */ + continue; + } + if (info1.getWellFormed () == RepInfo.TRUE) { + info.copy (info1); + break; + } + } + } + + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/OutputHandler.java b/jhove/classes/edu/harvard/hul/ois/jhove/OutputHandler.java new file mode 100644 index 00000000..9667f137 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/OutputHandler.java @@ -0,0 +1,196 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +import java.io.*; +import java.util.*; + +/** + * Public interface for Jhove output handlers. + * All output handlers must implement OutputHandler, and in + * normal cases should subclass HandlerBase. + */ +public interface OutputHandler +{ + + /** + * Reset the handler. This needs to be called before each invocation. + */ + public void reset (); + + /** + * Callback allowing post-parse, pre-show analysis of object + * representation information. + * @param info Object representation information + */ + public void analyze (RepInfo info); + + /** + * Callback indicating a directory is finished being processed. + * Most handlers will do nothing. + */ + public void endDirectory (); + + /** + * Returns the name of this handler + */ + public String getName (); + /** + * Returns release information for this handler + */ + public String getRelease (); + + /** + * Returns the last modification date of this handler + */ + public Date getDate (); + + /** + * Returns a List of Document objects giving the format + * specification documentation + * + * @see Document + */ + public List getSpecification (); + + /** + * Returns a List of Agent objects giving the vendor(s) + * of this handler. + */ + public Agent getVendor (); + + /** + * Returns this handler's note + */ + public String getNote (); + + /** + * Returns this handler's copyright information + */ + public String getRights (); + + /** + * Returns this handler's encoding. + */ + public String getEncoding (); + + /** + * Per-instantiation initialization. + * + * @param init Initialization parameter. This is typically obtained + * from the configuration file. + */ + public void init (String init) + throws Exception; + + /** + * Callback to give the handler the opportunity to decide whether or + * not to process a file. Most handlers will always return true. + * @param filepath File pathname + */ + public boolean okToProcess (String filepath); + + /** + * Sets list of default parameters. + * + * @param params A List whose elements are Strings. + * May be empty. + */ + public void setDefaultParams (List params); + + + /** + * Applies the default parameters. + */ + public void applyDefaultParams () + throws Exception; + + /** Reset parameter settings. + * Returns to a default state without any parameters. + */ + public void resetParams () + throws Exception; + + /** + * Per-action initialization. + * + * @param param Initialization parameter. + */ + public void param (String param) + throws Exception; + + /** + * Assigns an application object to provide services to this handler + */ + public void setApp (App app); + + /** + * Assigns the JHOVE engine object to provide services to this handler + */ + public void setBase (JhoveBase je); + + /** + * Assigns the encoding to be used by this OutputHandler + */ + public void setEncoding (String encoding); + + /** + * Assigns a PrintWriter to do output for this OutputHandler + */ + public void setWriter (PrintWriter output); + + /** + * Outputs information about a Module + */ + public void show (Module module); + + /** + * Outputs the information contained in a RepInfo object + */ + public void show (RepInfo info); + + /** + * Outputs information about the OutputHandler specified + * in the parameter + */ + public void show (OutputHandler handler); + + /** + * Outputs minimal information about the application + */ + public void show (); + + /** + * Outputs detailed information about the application, + * including configuration, available modules and handlers, + * etc. + */ + public void show (App app); + + /** + * Do the initial output. This should be in a suitable format + * for including multiple files between the header and the footer. + */ + public void showHeader (); + + /** + * Do the final output. This should be in a suitable format + * for including multiple files between the header and the footer. + */ + public void showFooter (); + + /** + * Do appropriate finalization after all output is complete. + */ + public void close (); + + /** + * Callback indicating a new directory is being processed. + * Most handlers will do nothing. + * @param directory Directory path + */ + public void startDirectory (String directory); +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/Property.java b/jhove/classes/edu/harvard/hul/ois/jhove/Property.java new file mode 100644 index 00000000..94cbe98f --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/Property.java @@ -0,0 +1,214 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +import java.util.*; + +/** + * This class encapsulates arbitrary format-specific properties. + * A Property's value can be a simple object or a structure. + * If it is a simple object, it has arity SCALAR. If it is + * a structure, its must be a Map, a List, a Set, or an array, + * with corresponding Arity. The simple object (in the case of + * arity SCALAR) or the components of the structure must have a + * type corresponding to one of the enumerations given by + * PropertyType. + * + * The components of a Property may themselves be Property + * objects, allowing nested structures. + * + * @see PropertyType + * @see PropertyArity + */ +public class Property +{ + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + private String _name; + private PropertyType _type; + private PropertyArity _arity; + private Object _value; + + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * Creates a Property with arity SCALAR. + * + * @param name The displayable name of the property + * @param type The type of property + * @param value The value of the property. The type of the + * parameter must agree with type. + */ + public Property (String name, PropertyType type, Object value) + { + init (name, type, PropertyArity.SCALAR, value); + } + + /** + * Creates a Property with a given name, type, arity and value. + * + * @param name The displayable name of the property. + * @param type The type of property. + * @param arity The arity of the property. + * @param value The value of the property. If the arity is + * SCALAR, the type of this parameter must agree + * with type. Otherwise, the arity + * must agree with arity, and its + * components must agree with type. + */ + public Property (String name, PropertyType type, PropertyArity arity, + Object value) + { + init (name, type, arity, value); + } + + private void init (String name, PropertyType type, PropertyArity arity, + Object value) + { + /* Some limited type checking. Checking for mismatched + types here may help avoid difficult chasing down + of the bugs such mismatches cause. */ + if (value == null) { + throw new NullPointerException ("Null value for Property not permitted"); + } + if (arity == PropertyArity.SCALAR) { + if (value instanceof List || + value instanceof Map || + value instanceof Set) { + throw new IncompatibleClassChangeError + ("Wrong class for Scalar Property"); + } + } + else if (arity == PropertyArity.MAP) { + if (!(value instanceof Map)) { + throw new IncompatibleClassChangeError + ("Wrong class for Map Property"); + } + + } + else if (arity == PropertyArity.SET) { + if (!(value instanceof Set)) { + throw new IncompatibleClassChangeError + ("Wrong class for Set Property"); + } + + } + else if (arity == PropertyArity.LIST) { + if (!(value instanceof List)) { + throw new IncompatibleClassChangeError + ("Wrong class for List Property"); + } + + } + + _name = name; + _type = type; + _arity = arity; + _value = value; + } + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + * + * Accessor methods. + ******************************************************************/ + + /** + * Returns the arity (type of structure) of this Property. + */ + public PropertyArity getArity () + { + return _arity; + } + + /** + * Return a property by its name, regardless of its position in the + * structural hierarchy of properties. + * @param name Property name + * @return Named property (or null) + */ + public Property getByName (String name) + { + if (_name.equals (name)) { + return this; + } + + if (!_arity.equals (PropertyArity.SCALAR) && + _type.equals (PropertyType.PROPERTY)) { + if (_arity.equals (PropertyArity.ARRAY)) { + Property [] array = (Property []) _value; + for (int i=0; iInteger, Boolean, Byte, + * Character, Double, + * Float, Long, and + * Short, a Property with arity ARRAY is an array of primitive + * Java types rather than Objects (e.g., int + * rather than Integer). + */ + public static final PropertyArity ARRAY = new PropertyArity ("Array"); + + /** + * An Arity corresponding to java.util.List or any of its derived classes. + */ + public static final PropertyArity LIST = new PropertyArity ("List"); + + /** + * An Arity corresponding to java.util.Map or any of its derived classes. + */ + public static final PropertyArity MAP = new PropertyArity ("Map"); + + /** + * An Arity corresponding to a simple object, which must be of a + * type corresponding to one of the instances of + * PropertyType. + */ + public static final PropertyArity SCALAR = new PropertyArity ("Scalar"); + + /** + * An Arity corresponding to java.util.Set or any of its derived classes. + */ + public static final PropertyArity SET = new PropertyArity ("Set"); + + /** + * Applications will never create PropertyArities directly. + **/ + private PropertyArity (String value) + { + super (value); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/PropertyPath.java b/jhove/classes/edu/harvard/hul/ois/jhove/PropertyPath.java new file mode 100644 index 00000000..317af2c4 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/PropertyPath.java @@ -0,0 +1,278 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + + +import java.util.*; + +/** + * A description of the location of a Property in a + * RepInfo object. Properties can be nested under other + * properties, in lists, maps, or subproperties. This class + * provides a general way to specify their location. + * + * For the moment, only paths by name are fully supported. + * + * @author Gary McGath + * + */ +public class PropertyPath { + + private List pathInfo; + + /** + * No-argument constructor. + * Creates an empty path. + */ + public PropertyPath () + { + pathInfo = new LinkedList (); + } + + /** + * Cloning constructor. + * This creates a new pathInfo list, whose elements + * are shared with the original PropertyPath's list. + */ + public PropertyPath (PropertyPath path) + { + pathInfo = new LinkedList (); + pathInfo.addAll (path.pathInfo); + } + + /** + * String array constructor. + * This allows creation of a path in a common and simple + * case: a hierarchy of named subproperties. It's the + * equivalent of creating an empty PropertyPath and then + * calling addElement successively with each of the strings + * in the array. + */ + public PropertyPath (String[] pathArray) + { + pathInfo = new LinkedList (); + for (int i = 0; i < pathArray.length; i++) { + pathInfo.add (pathArray[i]); + } + } + + /** + * Adds a property name to the path. + */ + public void addPropertyName (String str) + { + pathInfo.add (str); + // Now -- how do we add a key and distinguish it from a + // property name? We could define an internal type for + // keys. Do that for the moment and see what that leads + // to in retrieval. Alternatively, it isn't needed because + // we can see that a property has arity Map. + } + + /** + * Adds a key to the path, for a property map. + */ + public void addPropertyKey (Object obj) + { + pathInfo.add (new PropertyKey (obj)); + } + + /** + * Adds an index to the path, for an indexed property. + */ + public void addPropertyIndex (int idx) + { + pathInfo.add (new Integer (idx)); + } + + /** + * Walk down the path and return the specified Property. + * + * @param info The RepInfo object to search + * + * @return The specified Property if found, otherwise null. + */ + public Property locateProperty (RepInfo info) + { + return locateProperty (info, false); + } + + + /** + * Walk down the path and return the specified Property. + * + * @param info The RepInfo object to search + * @param trace If true, write debugging information + * to standard output. + * + * @return The specified Property if found, otherwise null. + */ + public Property locateProperty (RepInfo info, boolean trace) + { + if (pathInfo.isEmpty ()) { + // An empty path can't reach any property + if (trace) { + System.out.println ("Empty property path"); + } + return null; + } + Object obj = pathInfo.get(0); + if (!(obj instanceof String)) { + // The initial qualifier must be a property name + if (trace) { + System.out.println ("Not a property name"); + } + return null; + } + String top = (String) obj; + if (trace) { + System.out.println ("Getting proprerty " + top); + } + Property prop = info.getProperty (top); + if (prop == null) { + // No property of that name in RepInfo + if (trace) { + System.out.println ("Property is null"); + } + return null; + } + int pathLen = pathInfo.size (); + // Pass the CDR of the list to locateSubProperty. + return locateSubProperty (prop, pathInfo.subList (1, pathLen), trace); + } + + /* Recursive function for extracting a subproperty of a property. */ + private Property locateSubProperty (Property property, List path, boolean trace) + { + // If there's nothing left of the path, we're done. + if (path.isEmpty ()) { + return property; + } + List cdr = path.subList (1, path.size()); + PropertyArity arity = property.getArity (); + PropertyType type = property.getType (); + Object val = property.getValue (); + if (trace) { + System.out.println ("Property arity = " + arity + ", type = " + type); + } + // If the type isn't PROPERTY, then there are no subproperties. + if (type != PropertyType.PROPERTY) { + if (trace) { + System.out.println ("Not a property, type is " + type.toString ()); + } + return null; + } + Object obj = path.get (0); + + if (obj instanceof String) { + Iterator iter; + // Iterate through the property and see if any of the elements + // are Properties that match the name. + String name = (String) obj; + if (trace) { + System.out.println ("Looking for subproperty " + name + + " arity= " + arity.toString ()); + } + if (arity.equals (PropertyArity.SCALAR)) { + // There's just one shot at matching a scalar. + Property p = (Property) property.getValue (); + if (p.getName().equals (name)) { + return locateSubProperty (p, cdr, trace); + } + else { + return null; + } + } + else if (arity.equals (PropertyArity.ARRAY)) { + // We know it's an array of Properties, which saves much + // hair-tearing. + Property[] parray = (Property []) val; + for (int i = 0; i < parray.length; i++) { + Property p = parray[i]; + if (p.getName ().equals (name)) { + return locateSubProperty (p, cdr, trace); + } + } + return null; + } + else if (arity.equals (PropertyArity.LIST)) { + iter = ((List) val).listIterator (); + return getIteratedSubProperty (iter, name, cdr, trace); + } + else if (arity.equals (PropertyArity.SET)) { + iter = ((Set) val).iterator (); + return getIteratedSubProperty (iter, name, cdr, trace); + } + else if (arity.equals (PropertyArity.MAP)) { + iter = ((Map) val).values().iterator(); + return getIteratedSubProperty (iter, name, cdr, trace); + } + else { + // Should never happen, but keep compiler happy + //System.out.println ("Unknown arity"); + return null; + } + } + else if (obj instanceof Integer) { + int idx = ((Integer) obj).intValue(); + //System.out.println ("Property index = " + idx + ", arity= " + arity.toString ()); + if (arity.equals (PropertyArity.LIST)) { + List propList = (List) val; + return locateSubProperty + ((Property) propList.get (idx), cdr, trace); + } + else if (arity.equals (PropertyArity.ARRAY)) { + Property[] propArr = (Property []) val; + return locateSubProperty (propArr[idx], cdr, trace); + } + else { + // Other arities are not indexable + return null; + } + } + else if (obj instanceof PropertyKey) { + // This is applicable only to a Map. + if (arity != PropertyArity.MAP) { + return null; + } + return null; // I'm not sure this case is even meaningful + } + else { + // We should never get here + return null; + } + } + + /* Walk through an Iterator, whose elements are Properties, + * and return the subproperty by path of the first element + * whose name matches name. + */ + private Property getIteratedSubProperty (Iterator iter, + String name, + List path, + boolean trace) + { + while (iter.hasNext ()) { + Property p = (Property) iter.next (); + if (p.getName ().equals (name)) { + return locateSubProperty (p, path, trace); + } + } + return null; + } + + + + private class PropertyKey + { + public Object key; + + public PropertyKey (Object obj) + { + key = obj; + } + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/PropertyType.java b/jhove/classes/edu/harvard/hul/ois/jhove/PropertyType.java new file mode 100644 index 00000000..8e311691 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/PropertyType.java @@ -0,0 +1,110 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003-2009 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + + +/** + * This class defines enumerated types for an Property of + * some given content. + * Applications will not create or modify PropertyTypes, but will + * use one of the predefined PropertyType instances + * BOOLEAN, BYTE, CHARACTER, DATE, DOUBLE, FLOAT, INTEGER, + * LONG, OBJECT, PROPERTY, SHORT, STRING, RATIONAL, or + * NISOIMAGEMETADATA. + * + * @see Property + */ +public final class PropertyType + extends EnumerationType +{ + /** + * Property type for a Boolean object, or a + * boolean if the Arity is Array. + */ + public static final PropertyType BOOLEAN = new PropertyType ("Boolean"); + /** + * Property type for a Byte object, or a byte + * if the Arity is Array. + */ + public static final PropertyType BYTE = new PropertyType ("Byte"); + /** + * Property type for a Character object, or a + * char if the Arity is Array. + */ + public static final PropertyType CHARACTER = new PropertyType("Character"); + /** + * Property type for a Date object. + */ + public static final PropertyType DATE = new PropertyType ("Date"); + /** + * Property type for a Double object, or + *a double if the Arity is Array. + */ + public static final PropertyType DOUBLE = new PropertyType ("Double"); + /** + * Property type for a Float object, or a + * float if the Arity is Array. + */ + public static final PropertyType FLOAT = new PropertyType ("Float"); + /** + * Property type for an Integer object, or an + * integer if the Arity is Array. + */ + public static final PropertyType INTEGER = new PropertyType ("Integer"); + /** + * Property type for a Long object, or a + * long if the Arity is Array. + */ + public static final PropertyType LONG = new PropertyType ("Long"); + /** + * Property type for an Object. + */ + public static final PropertyType OBJECT = new PropertyType ("Object"); + /** + * Property type for an AESAudioMetadata. + */ + public static final PropertyType AESAUDIOMETADATA = + new PropertyType ("AESAudioMetadata"); + /** + * Property type for a NisoImageMetadata. + */ + public static final PropertyType NISOIMAGEMETADATA = + new PropertyType ("NISOImageMetadata"); + /** + * Property type for a TextMDMetadata. + */ + public static final PropertyType TEXTMDMETADATA = + new PropertyType ("TextMDMetadata"); + /** + * Property type for a Property object. + */ + public static final PropertyType PROPERTY = new PropertyType ("Property"); + /** + * Property type for a Short object, or a + * short if the Arity is Array. + */ + public static final PropertyType SHORT = new PropertyType ("Short"); + /** + * Property type for a String object. + */ + public static final PropertyType STRING = new PropertyType ("String"); + /** + * Property type for a Rational object. + */ + public static final PropertyType RATIONAL = new PropertyType ("Rational"); + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * Applications will never create PropertyTypes directly. + **/ + private PropertyType (String value) + { + super (value); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/RAFInputStream.java b/jhove/classes/edu/harvard/hul/ois/jhove/RAFInputStream.java new file mode 100644 index 00000000..89dc3c5d --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/RAFInputStream.java @@ -0,0 +1,261 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +import java.io.*; + +/** + * An InputStream layered on top of a RandomAccessFile. + * This is useful for a Module which has requirements that + * force it to use a RandomAccessFile, but is usually + * accessed sequentially. + * + * An RAFInputStream maintains its own position information + * in the file, so multiple RAFInputStreams in the same file + * will work without interference. However, this class is + * not thread-safe. + * + * @author Gary McGath + * + */ +public class RAFInputStream extends InputStream { + + /* The file on which the stream is based */ + private RandomAccessFile _raf; + + /* Size of fileBuf */ + private int fileBufSize; + + /* Buffer for reading from the file */ + private byte[] fileBuf; + + /* Offset for reading next byte from filebuf */ + private int fileBufPos; + + /* Number of valid bytes in fileBuf */ + private int fileBufBytes; + + /* Position in file for next read from RandomAccessFile */ + private long fileOffset; + + /* EOF flag */ + boolean eof; + + /** + * Constructor with default buffer size. + * The stream starts at the current position of the + * RandomAccessFile. + * + * @param raf The file on which the + * stream is to be based. + */ + public RAFInputStream(RandomAccessFile raf) { + super(); + _raf = raf; + fileBufSize = 65536; // pick a size, any size + init (); + } + + + /** + * Constructor with buffer size. + * The stream starts at the current position of the + * RandomAccessFile. + * + * @param raf The file on which the + * stream is to be based. + * + * @param bufferSize The buffer size to be used. + * If less than or equal to 0, the + * default buffer size is used. + */ + public RAFInputStream(RandomAccessFile raf, int bufferSize) { + super(); + _raf = raf; + fileBufSize = (bufferSize <= 0 ? 65536 : bufferSize); + init (); + } + + private void init () + { + fileBufBytes = 0; + fileBufPos = 0; + fileBuf = new byte[fileBufSize]; + try { + fileOffset = _raf.getFilePointer (); + } + catch (IOException e) {} + eof = false; + + } + /** + * Reads a single byte from the file. + */ + public int read() throws IOException { + if (eof) { + return -1; + } + if (fileBufPos >= fileBufBytes) { + // Need to read another bufferful + _raf.seek (fileOffset); + fileBufBytes = _raf.read (fileBuf); + fileBufPos = 0; + if (fileBufBytes <= 0) { + // No more in file + eof = true; + return -1; + } + else { + fileOffset += fileBufBytes; + } + } + return ((int) fileBuf[fileBufPos++] & 0XFF); + } + + /** + * Reads some number of bytes from the input stream and + * stores them into the buffer array b. The number of + * bytes actually read is returned as an integer. + */ + public int read (byte[] b) throws IOException + { + int bytesToRead = b.length; + int bytesRead = 0; + for (;;) { + // See how many bytes are available in fileBuf. + int fbAvail = fileBufBytes - fileBufPos; + if (fbAvail <= 0) { + // Need to read another bufferful + _raf.seek (fileOffset); + fileBufBytes = _raf.read (fileBuf); + fileBufPos = 0; + if (fileBufBytes <= 0) { + // No more in file -- return what we have + eof = true; + return bytesRead; + } + fbAvail = fileBufBytes; + fileOffset += fileBufBytes; + } + if (fbAvail > bytesToRead) { + // We have more than enough bytes. + fbAvail = bytesToRead; + } + for (int i = 0; i < fbAvail; i++) { + b[bytesRead++] = fileBuf[fileBufPos++]; + bytesToRead--; + } + if (bytesToRead == 0) { + return bytesRead; + } + } + } + + /** + * Reads up to len bytes of data from the input stream + * into an array of bytes. An attempt is made to read as + * many as len bytes, but a smaller number may be read, + * possibly zero. The number of bytes actually read is + * returned as an integer. + * + */ + public int read(byte[] b, int off, int len) throws IOException + { + int bytesToRead = len; + int bytesRead = 0; + for (;;) { + // See how many bytes are available in fileBuf. + int fbAvail = fileBufBytes - fileBufPos; + if (fbAvail <= 0) { + // Need to read another bufferful + _raf.seek (fileOffset); + fileBufBytes = _raf.read (fileBuf); + fileBufPos = 0; + if (fileBufBytes <= 0) { + // No more in file -- return what we have + eof = true; + return bytesRead; + } + fbAvail = fileBufBytes; + fileOffset += fileBufBytes; + } + if (fbAvail > bytesToRead) { + // We have more than enough bytes. + fbAvail = bytesToRead; + } + for (int i = 0; i < fbAvail; i++) { + b[off + bytesRead++] = fileBuf[fileBufPos++]; + bytesToRead--; + } + if (bytesToRead == 0) { + return bytesRead; + } + } + } + + + /** Skips some number of bytes. + * + * @return The number of bytes actually skipped. + */ + public long skip (long n) throws IOException + { + // If the range of the skip lies + // within the current buffer, we just adjust + // the buffer offset. + int bytesLeft = fileBufBytes - fileBufPos; + if (bytesLeft > n) { + fileBufPos += (int) n; + } + else { + // doesn't fit within the buffer. + // Set up to seek to the new position. + //long curPos = _raf.getFilePointer (); + if (fileOffset + n - bytesLeft > _raf.length ()) { + fileOffset = _raf.length (); + } + //seek (curPos + n - bytesLeft); + else { + fileOffset += n - bytesLeft; + } + fileBufBytes = 0; // Invalidate current buffer + } + return n; + } + + + + /** + * Returns the RandomAccessFile object. + */ + public RandomAccessFile getRAF () + { + return _raf; + } + + + /** Positions the stream to a different point in the file. + * This invalidates the buffer. + */ + public void seek (long offset) throws IOException + { + _raf.seek (offset); + fileBufBytes = 0; + fileBufPos = 0; + eof = false; + } + + /** Returns the current position in the file. + * What is reported is the position of the byte + * in the file which was last extracted from + * the buffer. + */ + public long getFilePos () throws IOException + { + return _raf.getFilePointer() - + (fileBufBytes - fileBufPos); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/RFC1766Lang.java b/jhove/classes/edu/harvard/hul/ois/jhove/RFC1766Lang.java new file mode 100644 index 00000000..ae06c232 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/RFC1766Lang.java @@ -0,0 +1,92 @@ + +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + + +package edu.harvard.hul.ois.jhove; + + +/** + * Class encapsulating RFC-1766 language codes. + */ +public final class RFC1766Lang +{ + + String _langCode; + + /** + * Constructor. + * + * @param str The ASCII string for the language code. + */ + public RFC1766Lang (String str) + { + _langCode = str; + } + + /** + * Returns the language code string. + */ + public String getLangCode () + { + return _langCode; + } + + /** + * Returns true if the language code + * string is syntactically compliant. + * The primary tag must be either a two-letter code, + * the letter i, or the letter x (case insensitive); + * no checking is done against registry lists. + */ + public boolean isSyntaxCorrect () + { + int i; + if (_langCode == null) { + return false; + } + char[] chrs = _langCode.toLowerCase().toCharArray(); + char firstChar = '\0'; + + int ntags = 0; + int taglength = 0; + for (i = 0; i < chrs.length; i++) { + char ch = chrs[i]; + if (i == 0) { + firstChar = ch; + } + if (!Character.isLetter (ch) && ch != '-') { + return false; + } + if (ch == '-') { + taglength = 0; + + // If this is the primary tag, do some checks + if (ntags++ == 0) { + if (taglength == 1) { + if (firstChar != 'i' && firstChar != 'x') { + return false; + } + else if (taglength != 2) { + return false; + } + } + } + } + else { + taglength++; + if (taglength > 8) { + return false; + } + } + } + // A dangling hyphen at the end isn't allowed. + if (taglength == 0) { + return false; + } + + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/Rational.java b/jhove/classes/edu/harvard/hul/ois/jhove/Rational.java new file mode 100644 index 00000000..609dc797 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/Rational.java @@ -0,0 +1,100 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +/** + * This class encapsulates a number which is defined as the ratio + * of two 32-bit unsigned integers, in accordance with the TIFF + * specification. + */ +public class Rational +{ + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + /** Numerator of ratio. */ + private long _numerator; + /** Denominator of ratio. */ + private long _denominator; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * The arguments to this constructor are long in order to + * represent all possible 32-bit unsigned integers. Parameters + * greater than 2 ^ 32 - 1 are not meaningful. + * @param numerator numerator of the Rational value + * @param denominator denominator of the Rational value + */ + public Rational (long numerator, long denominator) + { + _numerator = numerator; + _denominator = denominator; + } + + /** + * The arguments to the int constructor are treated as + * 32-bit unsigned integers. + * @param numerator numerator of the Rational value + * @param denominator denominator of the Rational value + */ + public Rational (int numerator, int denominator) + { + _numerator = (long) numerator & 0XFFFFFFFF; + _denominator = (long) denominator & 0XFFFFFFFF; + } + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + ******************************************************************/ + + /** + * Returns the Numerator property. + */ + public long getNumerator () + { + return _numerator; + } + + /** + * Returns the Denominator property. + */ + public long getDenominator() + { + return _denominator; + } + + /** + * Converts to a floating-point value (numerator/denominator). + * May throw an ArithmeticException. + **/ + public double toDouble () + { + return ((double) _numerator / (double) _denominator); + } + + /** + * Converts to a long value (numerator/denominator). + * May throw an ArithmeticException. + **/ + public long toLong () + { + return (long) ((double) _numerator / (double) _denominator); + } + + /** + * Represents the Rational as a String in the form of + * "numerator/denominator". + */ + public String toString () + { + return Long.toString (_numerator) + "/" + + Long.toString (_denominator); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/RepInfo.java b/jhove/classes/edu/harvard/hul/ois/jhove/RepInfo.java new file mode 100644 index 00000000..7baa8ce3 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/RepInfo.java @@ -0,0 +1,599 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003-2005 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +import java.util.*; + +/** + * This class encapsulates representation information, as defined + * by ISO/IEC 14721, about a content stream. + * + * @see ISO/IEC + * 14721 (PDF) + */ +public class RepInfo implements Cloneable +{ + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + /** List of checksums. */ + private List _checksum; + + /** Consistency flag. */ + private boolean _consistent; + + /** Validity flag. A ternary variable which can have a value + * of TRUE, FALSE, or UNDETERMINED. */ + private int _valid; + + /** Values for _valid */ + public final static int + TRUE = 1, + FALSE = 0, + UNDETERMINED = -1; + + /** Creation date. */ + private Date _created; + + /** External representation information. */ + private RepInfo _external; + + /** Format identifier. */ + private String _format; + + /** Modification date. */ + private Date _lastModified; + + /** List of diagnostic and informative messages. */ + private List _message; + + /** MIME media type. */ + private String _mimeType; + + /** The module used to populate this representation information. */ + private Module _module; + + /** List of conforming format profiles. */ + private List _profile; + + /** List of modules for which signature matches. */ + private List _sigMatch; + + /** Associative map of module-specific representation information. */ + private Map _property; + + /** Object size. */ + private long _size; + + /** Object file pathname or URI. */ + private String _uri; + + /** Flag indicating _uri is a URL if true. */ + private boolean _urlFlag; + + /** Well-formed flag. A ternary variable which can have a value + * of TRUE, FALSE, or UNDETERMINED. */ + private int _wellFormed; + + /** Version of format which applies. */ + private String _version; + + /** Note. */ + private String _note; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * Creates a RepInfo with a URI reference + * + * @param uri Object file pathname or URI + */ + public RepInfo (String uri) + { + init (uri); + } + + /** + * Creates a RepInfo with a URI reference and an external RepInfo. + * + * By default, urlFlag is false. + * + * @param uri Object file pathname or URI + * @param external External representation information + */ + public RepInfo (String uri, RepInfo external) + { + init (uri); + _external = external; + } + + private void init (String uri) + { + _uri = uri; + _size = -1; + _wellFormed = TRUE; + _consistent = true; + _urlFlag = false; + _valid = TRUE; + + _checksum = new ArrayList (); + _message = new ArrayList (); + _profile = new ArrayList (); + _property = new TreeMap (); + _sigMatch = new ArrayList (); + } + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + ******************************************************************/ + + /** + * Clones the RepInfo one level deep, making fresh copies + * of the checksum, message, profile, signature match, + * and property fields. + * The external RepInfo (if any) is not cloned, but + * is attached directly to the clone. + */ + public Object clone () + { + RepInfo newri; + try { + newri = (RepInfo) super.clone (); + } + catch (CloneNotSupportedException e) { + return null; // should never happen + } + + newri._checksum = new ArrayList (_checksum); + newri._message = new ArrayList(_message); + newri._profile = new ArrayList (_profile); + newri._sigMatch = new ArrayList (_sigMatch); + newri._property = new TreeMap (_property); + + return (Object) newri; + } + + /** + * Copies all the information out of the parameter object. + * This is a "shallow" copy; it is assumed that the parameter + * object is a temporary one that will not be further modified. + */ + public void copy (RepInfo info) + { + _checksum = info._checksum; + _consistent = info._consistent; + _created = info._created; + _external = info._external; + _format = info._format; + _lastModified = info._lastModified; + _message = info._message; + _mimeType = info._mimeType; + _profile = info._profile; + _property = info._property; + _size = info._size; + _uri = info._uri; + _urlFlag = info._urlFlag; + _wellFormed = info._wellFormed; + _valid = info._valid; + _version = info._version; + _note = info._note; + _module = info._module; + _sigMatch = info._sigMatch; + } + + /****************************************************************** + * + * Accessor methods. + ******************************************************************/ + + /** + * Returns this object's list of Checksums + */ + public List getChecksum () + { + return _checksum; + } + + /** + * Returns the creation date stored in this object. A creation + * date is not automatically generated, but must be explicitly + * stored. + **/ + public Date getCreated () + { + return _created; + } + + /** + * Return the format identifier + */ + public String getFormat () + { + return _format; + } + + /** + * Returns the last modified date stored in this object. A + * date is not automatically generated, but must be explicitly + * stored. + **/ + public Date getLastModified () + { + return _lastModified; + } + + /** + * Returns the message list stored in this object + */ + public List getMessage () + { + return _message; + } + + /** + * Returns the MIME type string stored in this object + */ + public String getMimeType () + { + return _mimeType; + } + + /** + * Return the module. + */ + public Module getModule () + { + return _module; + } + + /** + * Returns the list of profiles (Strings) stored in this object + */ + public List getProfile () + { + return _profile; + } + + /** + * Returns the Property map stored in this object. The + * Property map contains key-value pairs whose key is a + * String and whose value is a Property. + */ + public Map getProperty () + { + return _property; + } + + /** + * Returns a named Property from the Property map + * + * @param name The name of the Property. + */ + public Property getProperty (String name) + { + Property property = null; + if (_property.size () > 0) { + property = (Property) _property.get (name); + } + + return property; + } + + /** + * Returns the size property stored in this object. + */ + public long getSize () + { + return _size; + } + + /** + * Returns the URI property stored in this object. + */ + public String getUri () + { + return _uri; + } + + /** + * Returns a flag which, if true, indicates + * the object is a URL. + */ + public boolean getURLFlag () + { + return _urlFlag; + } + + /** + * Returns the value of the consistency flag. + */ + public boolean isConsistent () + { + return _consistent; + } + + /** + * Returns the value of the well-formed flag. + * Can return TRUE, FALSE, or UNDETERMINED. + */ + public int getWellFormed () + { + return _wellFormed; + } + + /** + * Returns the value of the validity flag. + * Can return TRUE, FALSE, or UNDETERMINED. + */ + public int getValid () + { + return _valid; + } + + /** + * Returns the version property stored in this object + */ + public String getVersion () + { + return _version; + } + + /** + * Returns the note property stored in this object + */ + public String getNote () + { + return _note; + } + + /** + * Returns the list of matching signatures. + * JhoveBase will make this value persistent across + * module invocations for a given document, so the list + * returned will reflect all modules that have looked + * at the document so far. + */ + public List getSigMatch () + { + return _sigMatch; + } + + /** + * Return property by name, regardless of its position in the + * property hierarchy. + * @param name Property name + * @return Named property (or null) + */ + public Property getByName (String name) + { + Property prop = null; + + Collection coll = _property.values (); + Iterator iter = coll.iterator (); + while (iter.hasNext ()) { + prop = (Property) iter.next (); + if ((prop = prop.getByName (name)) != null) { + break; + } + } + + return prop; + } + + /****************************************************************** + * Mutator methods. + ******************************************************************/ + + /** + * Append a Checksum object to the checksum list. + */ + public void setChecksum (Checksum checksum) + { + _checksum.add (checksum); + } + + /** + * Set the value of the consistency flag + */ + public void setConsistent (boolean consistent) + { + _consistent = consistent; + } + + /** + * Set the creation date + */ + public void setCreated (Date created) + { + _created = created; + } + + /** + * Set the format identifier + */ + public void setFormat (String format) + { + _format = format; + } + + /** + * Set the last modified date + */ + public void setLastModified (Date lastModified) + { + _lastModified = lastModified; + } + + /** + * Append a Message object to the message list + */ + public void setMessage (Message message) + { + _message.add (message); + } + + /** + * Set the MIME type string + */ + public void setMimeType (String mimeType) + { + _mimeType = mimeType; + } + + /** + * Add the module. + */ + public void setModule (Module module) + { + _module = module; + } + + /** + * Append a profile String to the profile list + */ + public void setProfile (String profile) + { + _profile.add (profile); + } + + /** + * Add a Property to the property map. The name of the Property + * becomes its key in the map. + */ + public void setProperty (Property property) + { + _property.put (property.getName (), property); + } + + /** + * Set the size property + */ + public void setSize (long size) + { + _size = size; + } + + /** + * Set the flag to indicate whether this is a URL (true) + * or a file (false) + */ + public void setURLFlag (boolean flag) + { + _urlFlag = flag; + } + + /** + * Set the well-formed flag + * + * @param wellFormed Boolean argument that maps to + * an integer value: + * true maps to TRUE, and false to FALSE. + */ + public void setWellFormed (boolean wellFormed) + { + _wellFormed = wellFormed ? TRUE : FALSE; + if (!wellFormed) { + _consistent = false; + _valid = FALSE; + } + } + + /** + * Set the wellFormed flag. + * Setting wellFormed to false forces the consistent and + * valid flags to be false as well. + */ + public void setWellFormed (int wellFormed) + { + _wellFormed = wellFormed; + if (wellFormed == FALSE) { + _consistent = false; + _valid = FALSE; + } + if (wellFormed == UNDETERMINED) { + _valid = UNDETERMINED; + } + } + + /** + * Set the validity flag + * + * @param valid Boolean argument that maps to + * an integer value: + * true maps to TRUE, and false to FALSE. + */ + public void setValid (boolean valid) + { + _valid = valid ? TRUE : FALSE; + } + + /** + * Set the validity flag + * + * @param valid Permitted values are TRUE, FALSE, AND + * UNDETERMINED. The effect of using + * other values is undefined. + */ + public void setValid (int valid) + { + _valid = valid; + } + + /** + * Set the version string + */ + public void setVersion (String version) + { + _version = version; + } + + /** + * Set the note string + */ + public void setNote (String note) + { + _note = note; + } + + /** Adds the name of a module, signifying that the document + * signature matched the module's requirements. + * JhoveBase will make this value persistent across + * module invocations for a given document. + */ + public void setSigMatch (String modname) + { + _sigMatch.add (modname); + } + + /** Adds a list of module names, signifying that the document + * signature matched the module's requirements. + * Any previous list is lost. + * JhoveBase will make this value persistent across + * module invocations for a given document. + */ + public void setSigMatch (List modnames) + { + _sigMatch = modnames; + } + + /****************************************************************** + * Serialization methods. + ******************************************************************/ + + /** + * Output the information in this object. The format and + * destination of the output are determined by the + * OutputHandler. + */ + public void show (OutputHandler handler) + { + handler.analyze (this); + handler.show (this); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/Signature.java b/jhove/classes/edu/harvard/hul/ois/jhove/Signature.java new file mode 100644 index 00000000..9a70ff99 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/Signature.java @@ -0,0 +1,157 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +/** + * This class encapsulates information about format signatures, + * both internal and external. + * The value of a Signature may be either a String or a byte array + * (stored as an int array to avoid signed byte problems). + */ +public abstract class Signature +{ + private int[] _value; + private String _stringValue; + private SignatureType _type; + private SignatureUseType _use; + private String _note; + + /** + * A Signature cannot be created directly; this constructor + * can be called as the superclass constructor from a subclass. + * This constructor uses a String value. + */ + protected Signature (String value, SignatureType type, + SignatureUseType use) + { + this (new int[value.length ()], type, use); + int len = value.length (); + _stringValue = value; + for (int i = 0; i < len; i++) { + _value[i] = value.charAt(i); + } + } + + /** + * A Signature cannot be created directly; this constructor + * can be called as the superclass constructor from a subclass. + * This constructor uses a byte array (stored as an int array) value. + */ + protected Signature (int[] value, SignatureType type, + SignatureUseType use) + { + _value = value; + _type = type; + _use = use; + _stringValue = null; + } + + /** + * A Signature cannot be created directly; this constructor + * can be called as the superclass constructor from a subclass. + * This constructor uses a String value and allows specification + * of a note. + */ + protected Signature (String value, SignatureType type, + SignatureUseType use, + String note) + { + this (new int[value.length ()], type, use, note); + int len = value.length (); + for (int i = 0; i < len; i++) { + _value[i] = value.charAt(i); + } + _stringValue = value; + } + + /** + * A Signature cannot be created directly; this constructor + * can be called as the superclass constructor from a subclass. + * This constructor uses a byte array (stored as an int array) value + * and allows specification of a note. + */ + protected Signature (int[] value, SignatureType type, + SignatureUseType use, + String note) + { + this (value, type, use); + _note = note; + } + + /** + * Returns the type of this Signature + */ + public SignatureType getType () + { + return _type; + } + + /** + * Returns the use requirement for this Signature + */ + public SignatureUseType getUse () + { + return _use; + } + + /** + * Returns the byte array value for this Signature. + * If this Signature was constructed from a String, it + * returns the characters of the String as the bytes of + * the array. + */ + public int[] getValue () + { + return _value; + } + + /** + * Returns the note specified for this Signature, or null + * if no note was specified. + */ + public String getNote () + { + return _note; + } + + /** + * Returns true if this Signature's value was provided as a + * String, false if as an array. + */ + public boolean isStringValue () + { + return (_stringValue != null); + } + + /** + * Returns the string value of this Signature. Returns null + * if this Signature was constructed with an array. + */ + public String getValueString () + { + return _stringValue; + } + + /** + * Returns the value of this Signature as a hexadecimal string. + * The length of the string is twice the length of the array + * or string from which this Signature was created, and all + * alphabetic characters are lower case. + */ + public String getValueHexString () + { + StringBuffer valBuf = new StringBuffer ("0x"); + for (int i = 0; i < _value.length; i++) { + /* Make each byte exactly two digits */ + int b = _value[i]; + if (b < 16) { + valBuf.append ('0'); + } + valBuf.append (Integer.toHexString (b)); + } + return valBuf.toString (); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/SignatureType.java b/jhove/classes/edu/harvard/hul/ois/jhove/SignatureType.java new file mode 100644 index 00000000..544dcae6 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/SignatureType.java @@ -0,0 +1,54 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + + +/** + * This class defines enumerated types for a Signature in a module. + * Applications will not create or modify SignatureTypes, but will + * use one of the predefined SignatureType instances + * EXTENSION, FILETYPE, or MAGIC. + * + * @see Signature + */ +public final class SignatureType + extends EnumerationType +{ + /****************************************************************** + * PUBLIC STATIC INSTANCES. + ******************************************************************/ + + /** + * Signature type for a file extension, i.e., a sequence of + * characters following a period character in a file name. + */ + public static final SignatureType EXTENSION = + new SignatureType ("File extension"); + + /** + * Signature type for a Macintosh OS file type. This applies + * only to Mac OS files, and is always a four-character code. + */ + public static final SignatureType FILETYPE = + new SignatureType ("Mac OS file type"); + /** + * Signature type for a "magic number" stored in the file. + */ + public static final SignatureType MAGIC = + new SignatureType ("Magic number"); + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * Applications will never create SignatureTypes directly. + **/ + private SignatureType (String value) + { + super (value); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/SignatureUseType.java b/jhove/classes/edu/harvard/hul/ois/jhove/SignatureUseType.java new file mode 100644 index 00000000..d62de3cb --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/SignatureUseType.java @@ -0,0 +1,55 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + + +/** + * This class defines enumerated use types for a Signature in a module. + * These give information on whether a signature is required in + * valid content. + * Applications will not create or modify SignatureUseTypes, but will + * use one of the predefined SignatureUseType instances + * MANDATORY, MANDATORY_IF_APPLICABLE, or OPTIONAL. + * + * @see Signature + */ +public final class SignatureUseType + extends EnumerationType +{ + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + + /** + * Use type for a required signature. + */ + public static final SignatureUseType MANDATORY = + new SignatureUseType ("Mandatory"); + + + /** + * Use type for a conditionally required signature. + */ + public static final SignatureUseType MANDATORY_IF_APPLICABLE = + new SignatureUseType ("Mandatory if applicable"); + /** + * Use type for an optional signature. + */ + public static final SignatureUseType OPTIONAL = + new SignatureUseType ("Optional"); + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * Applications will never create SignatureUseTypes directly. + **/ + private SignatureUseType (String value) + { + super (value); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/TextMDMetadata.java b/jhove/classes/edu/harvard/hul/ois/jhove/TextMDMetadata.java new file mode 100644 index 00000000..847c963f --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/TextMDMetadata.java @@ -0,0 +1,438 @@ +package edu.harvard.hul.ois.jhove; + +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +/** + * Encapsulation of the textMD metadata for text files. + * See http://www.loc.gov/standards/textMd for more information. + * + * @author Thomas Ledoux + * + */ +public class TextMDMetadata { + /** + * textMD namespace and version + */ + public static final String NAMESPACE = "info:lc/xmlns/textMD-v3"; + public static final String DEFAULT_LOCATION = + "http://www.loc.gov/standards/textMD/textMD-v3.01a.xsd"; + public static final String VERSION = "3.0"; + + /** + * Uses enumerated values of 'big', 'little', and 'middle' endian. + */ + public static final String[] BYTE_ORDER = { + "big", "little", "middle" + }; + public static final int BYTE_ORDER_BIG = 0; + public static final int BYTE_ORDER_LITTLE = 1; + public static final int BYTE_ORDER_MIDDLE= 2; + + /** + * Uses enumerated values of 'CR', 'LF' and 'CR/LF' for the idenntification of the linebreak. + */ + public static final String[] LINEBREAK = { + "CR", "LF", "CR/LF" + }; + public static final int LINEBREAK_CR = 0; + public static final int LINEBREAK_LF = 1; + public static final int LINEBREAK_CRLF = 2; + + /** + * Array of textMD charsets unknown by java.nio.charset.Charsets + */ + protected static final String[] UNKNOWN_JAVA_CHARSET = + new String[] { + "ISO-10646-UTF-1", "ISO_646.basic:1983", "INVARIANT", "BS_4730", "NATS-SEFI", + "NATS-SEFI-ADD", "NATS-DANO", "NATS-DANO-ADD", "SEN_850200_B", "SEN_850200_C", "ISO-2022-CN-EXT", + "JIS_C6220-1969-jp", "JIS_C6220-1969-ro", "IT", "PT", "ES", "greek7-old", "latin-greek", + "DIN_66003", "NF_Z_62-010_(1973)", "latin-greek-1", "ISO_5427", "JIS_C6226-1978", "BS_viewdata", + "INIS", "INIS-8", "INIS-cyrillic", "ISO_5427:1981", "ISO_5428:1980", "GB_1988-80", "GB_2312-80", + "NS_4551-1", "NS_4551-2", "NF_Z_62-010", "videotex-suppl", "PT2", "ES2", "MSZ_7795.3", "greek7", + "ASMO_449", "iso-ir-90", "JIS_C6229-1984-a", "JIS_C6229-1984-b", "JIS_C6229-1984-b-add", + "JIS_C6229-1984-hand", "JIS_C6229-1984-hand-add", "JIS_C6229-1984-kana", "ISO_2033-1983", + "ANSI_X3.110-1983", "T.61-7bit", "ECMA-cyrillic", "CSA_Z243.4-1985-1", "CSA_Z243.4-1985-2", + "CSA_Z243.4-1985-gr", "ISO_8859-6-E", "ISO_8859-6-I", "T.101-G2", "ISO_8859-8-E", "ISO_8859-8-I", + "CSN_369103", "JUS_I.B1.002", "ISO_6937-2-add", "IEC_P27-1", "JUS_I.B1.003-serb", + "JUS_I.B1.003-mac", "greek-ccitt", "NC_NC00-10:81", "ISO_6937-2-25", "GOST_19768-74", + "ISO_8859-supp", "ISO_10367-box", "ISO-8859-10", "latin-lap", "DS_2089", "us-dk", "dk-us", + "KSC5636", "ISO-10646-UCS-4", "DEC-MCS", "hp-roman8", "macintosh", "IBM038", "IBM274", "IBM275", + "IBM281", "IBM290", "IBM423", "IBM851", "IBM880", "IBM891", "IBM903", "IBM904", "IBM905", + "EBCDIC-AT-DE", "EBCDIC-AT-DE-A", "EBCDIC-CA-FR", "EBCDIC-DK-NO", "EBCDIC-DK-NO-A", "EBCDIC-FI-SE", + "EBCDIC-FI-SE-A", "EBCDIC-FR", "EBCDIC-IT", "EBCDIC-PT", "EBCDIC-ES", "EBCDIC-ES-A", "EBCDIC-ES-S", + "EBCDIC-UK", "EBCDIC-US", "UNKNOWN-8BIT", "MNEMONIC", "MNEM", "VISCII", "VIQR", "IBM00924", + "UNICODE-1-1", "SCSU", "UTF-7", "CESU-8", "UNICODE-1-1-UTF-7", "ISO-8859-14", "ISO-8859-16", + "Extended_UNIX_Code_Fixed_Width_for_Japanese", "ISO-10646-UCS-Basic", "ISO-10646-Unicode-Latin1", + "ISO-10646-J-1", "ISO-Unicode-IBM-1268", "ISO-Unicode-IBM-1276", "ISO-Unicode-IBM-1264", + "ISO-Unicode-IBM-1265", "ISO-8859-1-Windows-3.0-Latin-1", "ISO-8859-1-Windows-3.1-Latin-1", + "ISO-8859-2-Windows-Latin-2", "ISO-8859-9-Windows-Latin-5", "Adobe-Standard-Encoding", + "Ventura-US", "Ventura-International", "PC8-Danish-Norwegian", "PC8-Turkish", "IBM-Symbols", + "HP-Legal", "HP-Pi-font", "HP-Math8", "Adobe-Symbol-Encoding", "HP-DeskTop", "Ventura-Math", + "Microsoft-Publishing", "HZ-GB-2312", }; + /** + * Set of unknown charsets in Java + */ + protected static Set setOfUnknownJavaCharset; + + /** + * Map from ISO 639/2 T to ISO 639/2 B + */ + protected static Map fromISO_639_2_T2B; + + public static final String CHARSET_ASCII = "US-ASCII"; + public static final String CHARSET_UTF8 = "UTF-8"; + public static final String CHARSET_ISO8859_1 = "ISO-8859-1"; + + /** + * To represent the unknown + */ + public static final int NILL = -1; + + static { + setOfUnknownJavaCharset = new HashSet(Arrays.asList(UNKNOWN_JAVA_CHARSET)); + + // Map to transform from the terminology code to the bibliographic one + fromISO_639_2_T2B = new HashMap(); + fromISO_639_2_T2B.put("sqi", "alb"); + fromISO_639_2_T2B.put("hye", "arm"); + fromISO_639_2_T2B.put("eus", "baq"); + fromISO_639_2_T2B.put("mya", "bur"); + fromISO_639_2_T2B.put("zho", "chi"); + fromISO_639_2_T2B.put("ces", "cze"); + fromISO_639_2_T2B.put("nld", "dut"); + fromISO_639_2_T2B.put("fra", "fre"); + fromISO_639_2_T2B.put("kat", "geo"); + fromISO_639_2_T2B.put("deu", "ger"); + fromISO_639_2_T2B.put("ell", "gre"); + fromISO_639_2_T2B.put("isl", "ice"); + fromISO_639_2_T2B.put("mkd", "mac"); + fromISO_639_2_T2B.put("mri", "mao"); + fromISO_639_2_T2B.put("msa", "may"); + fromISO_639_2_T2B.put("fas", "per"); + fromISO_639_2_T2B.put("ron", "rum"); + fromISO_639_2_T2B.put("slk", "slo"); + fromISO_639_2_T2B.put("bod", "tib"); + fromISO_639_2_T2B.put("cym", "wel"); + } + + /** + charset + Usage: The character set employed by the text. Controlled vocab using IANA names for character sets. + Attributes: none. + Contains: none. + Contained by: character_info. + */ + private String charset; + + /** + byte_order + Usage: Byte order, primarily useful for cases where it's not clear just by specifying an IANA character set. + Uses enumerated values of big, little, and middle' endian. + Attributes: none. + Contains: none. + Contained by: character_info. + */ + private int byte_order = NILL; + + /** + byte_size + Usage: The size of an individual byte within the expressed as a number of bits (as integer). This does not necessarily equal the character size, as a character may have more than one, or a variable number of bytes per character. + Attributes: none. + Contains: none. + Contained by: character_info. + */ + private String byte_size; + + /** + character_size + Usage: The size of an individual character within the character set as a number of bytes of the size expressed in the byte_size. In the case of variable encodings, such as UTF-8 for Unicode, the character_size element should state "variable" and also identify the specific variable character set encoding in the encoding attribute. + Attributes: encoding. + Contains: none. + Contained by: character_info. + */ + private String character_size; + + /** + linebreak + Usage: How line breaks are represented in current file (which may differ from how they were originally encoded). Either carriage return, line feed, or carriage return/line feed. + Attributes: none. + Contains: none. + Contained by: character_info. + */ + private int linebreak = NILL; + + /** + language + Usage: Language(s) used in work. Use ISO 639-2 codes, which are enumerated in the schema as valid text values. + Attributes: none. + Contains: none. + Contained by: textMD. + */ + private String language; + + /** + markup_basis + Usage: The metalanguage used to create the markup language, such as SGML, XML, GML, etc. + Attributes: version. + Contains: none. + Contained by: textMD. + */ + private String markup_basis; + /** + version + Usage: Used to record the version number (as a string) for a given piece of software, a markup language, or a schema version. + */ + private String markup_basis_version; + + /** + markup_language + Usage: Markup language employed on the text (i.e., the specific schema or dtd). May be a URI for schema or dtd, but not mandatory. + Attributes: version. + Contains: none. + Contained by: textMD. + */ + private String markup_language; + + /** + version + Usage: Used to record the version number (as a string) for a given piece of software, a markup language, or a schema version. + */ + private String markup_language_version; + + /** + * @return the charset + */ + public String getCharset() { + return charset; + } + + /** + * @param charset the charset to set + */ + public void setCharset(String charset) { + this.charset = toTextMDCharset(charset); + } + + /** + * @return the byte_order + */ + public int getByte_order() { + return byte_order; + } + public String getByte_orderString() { + if (byte_order == NILL) { + return BYTE_ORDER[BYTE_ORDER_BIG]; // default !!! + } + return BYTE_ORDER[byte_order]; + } + + /** + * @param byte_order the byte_order to set + */ + public void setByte_order(int byte_order) { + this.byte_order = byte_order; + } + + /** + * @return the byte_size + */ + public String getByte_size() { + return byte_size; + } + + /** + * @param byte_size the byte_size to set + */ + public void setByte_size(String byte_size) { + this.byte_size = byte_size; + } + + /** + * @return the character_size + */ + public String getCharacter_size() { + return character_size; + } + + /** + * @param character_size the character_size to set + */ + public void setCharacter_size(String character_size) { + this.character_size = character_size; + } + + /** + * @return the linebreak + */ + public int getLinebreak() { + return linebreak; + } + + /** + * @return the linebreak in String form + */ + public String getLinebreakString() { + if (linebreak == NILL) { + return LINEBREAK[LINEBREAK_CRLF]; // default !!! + } + return LINEBREAK[linebreak]; + } + + /** + * @param linebreak the linebreak to set + */ + public void setLinebreak(int linebreak) { + this.linebreak = linebreak; + } + + /** + * @return the language + */ + public String getLanguage() { + return language; + } + + /** + * @param language the language to set + */ + public void setLanguage(String language) { + this.language = toISO_639_2(language); + } + + /** + * @return the markup_basis + */ + public String getMarkup_basis() { + return markup_basis; + } + + /** + * @param markup_basis the markup_basis to set + */ + public void setMarkup_basis(String markup_basis) { + this.markup_basis = markup_basis; + } + + /** + * @return the markup_basis_version + */ + public String getMarkup_basis_version() { + return markup_basis_version; + } + + /** + * @param markup_basis_version the markup_basis_version to set + */ + public void setMarkup_basis_version(String markup_basis_version) { + this.markup_basis_version = markup_basis_version; + } + + /** + * @return the markup_language + */ + public String getMarkup_language() { + return markup_language; + } + + /** + * @param markup_language the markup_language to set + */ + public void setMarkup_language(String markup_language) { + this.markup_language = markup_language; + } + + /** + * @return the markup_language_version + */ + public String getMarkup_language_version() { + return markup_language_version; + } + + /** + * @param markup_language_version the markup_language_version to set + */ + public void setMarkup_language_version(String markup_language_version) { + this.markup_language_version = markup_language_version; + } + + /** + * Transform a given charset in the "authorized" list given in the textMD schema enumeration. + * From the schema documentation on charset (http://www.loc.gov/standards/textMD/elementSet/index.html#element_charset). + * The character set employed by the text. Controlled vocab using IANA names for character sets: + * http://www.iana.org/assignments/character-sets. + * The problem arises because the java Charset uses the (preferred MIME name) where textMD uses the Name ... + * @param srcCharset charset from the file + * @return normalized charset + */ + public static String toTextMDCharset(String srcCharset) { + if (srcCharset == null) return null; + + Charset cs = null; + String textMDCharset = null; + try { + cs = Charset.forName(srcCharset); + textMDCharset = cs.name(); + } catch (Exception e) { + // Try a unknown one + if (setOfUnknownJavaCharset.contains(srcCharset)) { + textMDCharset = srcCharset; + } else { + // Downgrade to default + textMDCharset = CHARSET_ISO8859_1; + } + } + if (textMDCharset != null) { + return textMDCharset; + } else { + // Downgrade to default + return CHARSET_ISO8859_1; + } + } + + /** + * Transform a language to the ISO_639-2 language (only enumeration allowed in textMD schema). + * @param srcLang language in the file + * @return normalized language in 3 letters (except qaa-qtz) + */ + public static String toISO_639_2(String srcLang) { + if (srcLang == null) return null; + if ("qaa-qtz".equals(srcLang)) return srcLang; + + String textMDLang = null; + if (srcLang.length() == 3) { + textMDLang = srcLang; + } + else if (srcLang.length() == 2) { + try { + Locale loc = new Locale(srcLang); + textMDLang = loc.getISO3Language(); + } catch (Exception e) { + // Unknown language + } + } + else if (srcLang.length() > 3) { + // Just try with the first 2 characters + try { + Locale loc = new Locale(srcLang.substring(0, 2)); + srcLang = loc.getISO3Language(); + } catch (Exception e) { + // Unknown language + } + } + if (textMDLang != null && textMDLang.length() == 3) { + // From ISO 639-2/T to ISO 639-2/B + if (fromISO_639_2_T2B.containsKey(textMDLang)) { + textMDLang = (String)fromISO_639_2_T2B.get(textMDLang); + } + return textMDLang; + } + return null; + + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/Utf8BlockMarker.java b/jhove/classes/edu/harvard/hul/ois/jhove/Utf8BlockMarker.java new file mode 100644 index 00000000..a2c54fdd --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/Utf8BlockMarker.java @@ -0,0 +1,62 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +import edu.harvard.hul.ois.jhove.module.Utf8Block; +import java.util.*; + +/** + * + * @author Gary McGath + * + */ +public class Utf8BlockMarker { + private boolean[] blocksUsed; + + public Utf8BlockMarker () { + blocksUsed = new boolean [Utf8Block.unicodeBlock.length]; + } + + public void markBlock (int code) { + for (int i=0; i= code) { + blocksUsed[i] = true; + break; + } + } + } + + /** Returns a Property listing the blocks that have been + * marked as used. If no blocks have been marked, + * returns null. */ + public Property getBlocksUsedProperty (String name) + { + List block = new ArrayList (blocksUsed.length); + for (int i=0; iwrappedStream already contains a root element. + * @param version The XML version. Should be null or "1.0" unless + * there's a really good reason. + * @param encoding The name of the character encoding. May be null. + * @param standalone The value of the standalone attribute. May be null. + */ + public XMLWrapperStream (InputStream wrappedStream, + String rootName, + String version, + String encoding, + String standalone) + { + _wrappedStream = wrappedStream; + xmlDecl = ""; + state = DECL; + strIndex = 0; + + if (rootName != null) { + rootStart = "<" + rootName + ">"; + rootEnd = ""; + } + else { + rootStart = ""; + rootEnd = ""; + } + } + + /** + * Constructor. Equivalent to + * XMLWrapperStream (wrappedStream, null, null, null, null) + * + * @param wrappedStream The stream which this stream will subsume. + */ + public XMLWrapperStream (InputStream wrappedStream) + { + this (wrappedStream, null, null, null, null); + } + + /** + * Constructor. Equivalent to + * XMLWrapperStream (wrappedStream, rootName, null, null, null) + * + * @param wrappedStream The stream which this stream will subsume. + * @param rootName The name of the root element. May be null. + */ + public XMLWrapperStream (InputStream wrappedStream, String rootName) + { + this (wrappedStream, rootName, null, null, null); + } + + + /** + * Get a byte. Successive calls will return the + * XML declaration, then the wrapped stream. + * + * @see java.io.InputStream#read() + */ + public int read() throws IOException + { + int retval; + if (state == DECL) { + if (strIndex >= xmlDecl.length()) { + // We have finished the declaration string now. + state = ROOT_START; + strIndex = 0; + } + else { + // We haven't finished returning the declaration string. + return (int) xmlDecl.charAt(strIndex++); + } + } + + // Each state can fall through to the next + + if (state == ROOT_START) { + if (strIndex >= rootStart.length()) { + // We have finished the root element start now. + state = CONTENT; + } + else { + return (int) rootStart.charAt(strIndex++); + } + } + + if (state == CONTENT) { + // Metadata alleged stream doesn't look remotely like metadata -- + // probably looking at wrong part of file! + retval = _wrappedStream.read (); + if (retval == -1) { + state = ROOT_END; + strIndex = 0; + } + else { + return retval; + } + } + + // Must be ROOT_END if it gets here + if (strIndex >= rootEnd.length()) { + // We have finished the root element end and the document now. + return -1; + } + else { + return (int) rootEnd.charAt(strIndex++); + } + + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/XMPHandler.java b/jhove/classes/edu/harvard/hul/ois/jhove/XMPHandler.java new file mode 100644 index 00000000..984cc09a --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/XMPHandler.java @@ -0,0 +1,175 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + + +import org.xml.sax.*; + +/** + * This class encapsulates XMP metadata within a file. It makes use + * of an InputStream as a data source. + * + * This differs from normal XML handling in that it's necessary to + * process the xpacket processing instruction in order to determine + * the encoding of the XML. the processingInstruction function looks + * for xpacket, and throws a special SAXException if it's necessary + * to change encoding. + * + * We don't actually extract any information from the XMP, but + * simply check it for well-formedness. By convention, XMPHandler + * should be invoked on an XMPSource (TBW), which provides the + * ability to recapture the InputStream from which the XMP was + * obtained and put it into a property once it's verified here. + * + */ +public class XMPHandler extends org.xml.sax.helpers.DefaultHandler { + + /* URI strings */ + private final static String xmpBasicSchema = + "http://ns.adobe.com/xap/1.0/"; +// private final static String xmpRightsSchema = +// "http://ns.adobe.com/xap/1.0/rights/"; +// private final static String dublinCoreSchema = +// "http://purl.org/dc/elements/1.1/"; +// private final static String adobePDFSchema = +// "http://ns.adobe.com/pdf/1.3/"; +// private final static String photoshopSchema = +// "http://ns.adobe.com/photoshop/1.0/"; + + private int curStructType; + /* Values which may be assigned to curStructType */ + private final static int + UNASSIGNED = 0, + BAG = 1, + ALT = 2, + SEQ = 3; + + private boolean pdfaCompliant; + + public XMPHandler () + { + super (); + pdfaCompliant = true; // compliance is presumed till disproven + } + + + /** Returns true if no violations of PDF/A compliance have been found, + * false if a problem was detected. */ + public boolean isPdfaCompliant () { + return pdfaCompliant; + } + + + public void processingInstruction (String target, String data) + throws SAXException + { + if ("xpacket".equals (target)) { + // We assume that the data will be non-endian (i.e., simply + // a stream of bytes) unless we find a valid endian code. + boolean bigEndian = false; + boolean noEndian = true; + // a Processing Instruction can't really have attributes, + // so we have to parse the data string ourselves. The order + // of the attributes is guaranteed, fortunately. + int idx = data.indexOf ("begin="); + idx = data.indexOf ('"', idx + 1); + if (data.length () >= idx + 2) { + int char1 = (int) data.charAt (idx + 1); + int char2 = (int) data.charAt (idx + 2); + if (char1 == 0XFF && char2 == 0XFE) { + noEndian = false; + bigEndian = false; + } + else if (char1 == 0XFE && char2 == 0XFF) { + noEndian = false; + bigEndian = true; + } + // EF BB B8 signifies UTF-8, but that's the default anyway. + } + // Check the bytes attribute. We don't do anything with it except + // note that it isn't allowed with PDF/A. + idx = data.indexOf("bytes="); + if (idx > 0) { + pdfaCompliant = false; + } + // Next find encoding, which is optional. + idx = data.indexOf ("encoding="); + if (idx > 0) { + pdfaCompliant = false; // not allowed in PDF/A + idx = data.indexOf ('"', idx + 1); + int encEnd = data.indexOf ('"', idx + 1 ); + String encoding = data.substring (idx + 1, encEnd); + // Throw a SAXException which consists of + // "ENC=,", where + // endian is either 'B' (big), 'L' (little) or space (none), and + // enc is the encoding attribute. + // This is an expected exception, not an error. + String exText = "ENC="; + if (noEndian) { + exText += " ,"; + } + else if (bigEndian) { + exText += "B,"; + } + else { + exText += "L,"; + } + exText += encoding; + throw new SAXException (exText); + } + } + } + + + /** + * Catches the start of an element and, if it's one we care + * about, sets state information. + */ + public void startElement (String namespaceURI, String localName, + String rawName, Attributes atts) + throws SAXException + { + //System.out.println (namespaceURI); // Just for debugging and placeholding + if (xmpBasicSchema.equals (namespaceURI)) { + if ("Bag".equals (rawName)) { + curStructType = BAG; + } + else if ("Seq".equals (rawName)) { + curStructType = SEQ; + } + else if ("Alt".equals (rawName)) { + curStructType = ALT; + } + } + } + + + /** + * Catches the end of an element. + */ + public void endElement (String namespaceURI, String localName, + String rawName) + throws SAXException + { + if (xmpBasicSchema.equals (namespaceURI)) { + // Check for the end of an XMP structure + if ("Bag".equals (rawName)|| + "Seq".equals (rawName) || + "Alt".equals (rawName)) { + curStructType = UNASSIGNED; + } + } + } + + /** Catch a fatal error. This is put here because the default + * behavior is to report a "fatal error" to standard output, + * which is harmless but scary. + */ + public void fatalError(SAXParseException exception) + throws SAXException + { + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/XMPSource.java b/jhove/classes/edu/harvard/hul/ois/jhove/XMPSource.java new file mode 100644 index 00000000..f770478c --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/XMPSource.java @@ -0,0 +1,118 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove; + +//import java.io.InputStream; +import java.io.Reader; + +import org.xml.sax.InputSource; +import java.io.IOException; + +/** + * Class for providing an InputSource to XMPHandler. + * Only an InputSource based on a Reader is supported. + * + * Each module can provide its own subclass of XMPSource. + * The subclass must provide a way to + * reset to the beginning and reread the data when makeProperty + * is called. + * + * @author Gary McGath + * + */ +public abstract class XMPSource extends InputSource { + + /** The Reader on which the InputSource is based. */ + protected Reader _reader; + + + /** + * Constructor with Reader. + */ + public XMPSource(Reader rdr) { + super(rdr); + _reader = rdr; + } + + + /** + * Generates a property from the underlying data. + * The beginning and ending processing instructions are + * stripped out. + */ + public Property makeProperty () throws IOException + { + boolean maybePI = false; // set to true for ?xpacket partial match + boolean seenStart = false; // set to true after initial xpacket + resetReader (); // go back to the beginning + StringBuffer textBuf = new StringBuffer (); + StringBuffer xpacBuf = new StringBuffer (); + for (;;) { + int ch = _reader.read(); + if (ch < 0) { + break; + } + if (maybePI) { + xpacBuf.append((char) ch); + if (" + seenStart = true; + xpacBuf.setLength (0); + maybePI = false; + int prevCh = 0; + for (;;) { + ch = _reader.read (); + if (ch < 0 || + (prevCh == (int) '?' && ch == (int) '>')) { + break; + } + prevCh = ch; + } + } + else { + // This is the ending xpacket. Discard it. + // We're done. + break; + } + } + if (!". + // Start buffering. + maybePI = true; + xpacBuf.append ((char) ch); + } + else { + // Just plain text. Append it. + textBuf.append ((char) ch); + } + } + } + // Some XMP's end with lots of white space, so give + // it a trim before returning. + return new Property ("XMP", + PropertyType.STRING, + textBuf.toString ().trim ()); + } + + + /** + * Causes reading to begin from the start again. + * Typically this means creating a new value for + * _reader that will start over. + */ + protected abstract void resetReader (); + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/build.xml b/jhove/classes/edu/harvard/hul/ois/jhove/build.xml new file mode 100644 index 00000000..0d8ce0fb --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/build.xml @@ -0,0 +1,79 @@ + + API build file + Jhove - JSTOR/Harvard Object Validation Environment + Version 1.0 2004-02-03 + Copyright 2004 by JSTOR and the President and Fellows of Harvard College + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/handler/AuditHandler.java b/jhove/classes/edu/harvard/hul/ois/jhove/handler/AuditHandler.java new file mode 100644 index 00000000..0a1f469f --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/handler/AuditHandler.java @@ -0,0 +1,438 @@ +/********************************************************************** + * Audit output handler + * Copyright 2004 by the President and Fellows of Harvard College + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.handler; + +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.handler.audit.*; +import java.util.*; + +/** + * JHOVE audit output handler, derived from the standard JHOVE XML + * handler. It is expected that this class will be used as the parent for + * other, more interesting output handlers. Subclasses should override the + * implementations of the Impl methods, e.g., endDirectoryImpl (). + * @see JHOVE + * XML output handler + */ +public class AuditHandler + extends XmlHandler +{ + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + + /** Audit output handler name. */ + private static final String NAME = "Audit"; + + /** Audit output handler release ID. */ + private static final String RELEASE = "1.1"; + + /** Audit output handler release date. */ + private static final int [] DATE = {2005, 04, 22}; + + /** Audit output handler informative note. */ + private static final String NOTE = + "This output handler is derived from the standard JHOVE XML output " + + "handler. It is intended to be used as the parent class for other, " + + "more interesting handlers."; + + /** Audit output handler rights statement. */ + private static final String RIGHTS = + "Copyright 2004-2005 by the President and Fellows of Harvard College. " + + "Released under the GNU LGPL license"; + + /***************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + /** Home directory of the audit. */ + protected String _home; + + /** Number of files processed by MIME type. */ + protected Map _mimeType; + + /** State map. */ + protected Map _stateMap; + + /** State stack. */ + protected Stack _stateStack; + + /** Initial time. */ + protected long _t0; + + /** Number of files audited. */ + protected int _nAudit; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * Instantiate a AudiHandler object. + */ + public AuditHandler () + { + super (NAME, RELEASE, DATE, NOTE, RIGHTS); + + /* Define the standard output handler properties. */ + + _name = NAME; + _release = RELEASE; + Calendar calendar = new GregorianCalendar (); + calendar.set (DATE[0], DATE[1]-1, DATE[2]); + _date = calendar.getTime (); + _note = NOTE; + _rights = RIGHTS; + + /* Initialize the handler. */ + + _mimeType = new TreeMap (); + _stateMap = new TreeMap (); + _stateStack = new Stack (); + _nAudit = 0; + } + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + ******************************************************************/ + + /** + * Callback indicating a directory is finished being processed. + * Prop the state stack and place the current directory file count + * into the directory hash. + */ + public final void endDirectory () + { + AuditState state = (AuditState) _stateStack.pop (); + _stateMap.put (state.getDirectory (), state); + + endDirectoryImpl (state); + } + + /** + * Local extension to the standard callback indicating a directory is + * finished being processed. + * @param state Audit handler state + */ + public void endDirectoryImpl (AuditState state) + { + } + + /** + * Determine whether or not to process the file. + * @param filepath File pathname + */ + public final boolean okToProcess (String filepath) + { + AuditState state = (AuditState) _stateStack.peek (); + + boolean ok = okToProcessImpl (filepath, state); + if (!ok) { + state.setNotProcessed (state.getNotProcessed () + 1); + } + + return ok; + } + + /** + * Local extension to standard callback that determines whether or not + * to process the file. + * @param filepath File pathname + * @param state Audit handler state + */ + public boolean okToProcessImpl (String filepath, AuditState state) + { + return true; + } + + /** + * Outputs the information contained in a RepInfo object + * @param info Object representation information + */ + public void show (RepInfo info) + { + AuditState state = (AuditState) _stateStack.peek (); + + /* If the file is not found, then no module is assigned in the + * RepInfo object. + */ + if (info.getModule () == null) { + state.setNotFound (state.getNotFound () + 1); + + _writer.println (""); + } + else { + String mime = info.getMimeType (); + AuditCount count = (AuditCount) _mimeType.get (mime); + if (count == null) { + count = new AuditCount (); + } + + int valid = info.getValid (); + if (valid == RepInfo.TRUE) { + state.setValid (state.getValid () + 1); + count.setValid (count.getValid () + 1); + } + else { + state.setWellFormed (state.getWellFormed () + 1); + count.setWellFormed (count.getWellFormed () + 1); + } + _mimeType.put (mime, count); + } + + showImpl (info, state); + } + + /** + * Local extension to the standard callback that outputs the + * information contained in a RepInfo object + * @param info Object representation information + * @param state Audit handler state + */ + public void showImpl (RepInfo info, AuditState state) + { + String status = null; + String mime = info.getMimeType (); + if (mime != null) { + if (info.getWellFormed () == RepInfo.TRUE) { + if (info.getValid () == RepInfo.TRUE) { + status = "valid"; + } + else { + status = "well-formed"; + } + } + else { + status = "not well-formed"; + } + } + else { + status = "not found"; + } + + /* Retrieve the MD5 checksum, if available. */ + + String md5 = null; + List list = info.getChecksum (); + int len = list.size (); + for (int i=0; i 0 || state.getNotFound () > 0) { + _stateMap.put (state.getDirectory (), state); + } + + showFooterImpl (state); + // super.showFooter (); + + _writer.println (""); + + _writer.println (""); + + /* Update the elapsed time. */ + long dt = (System.currentTimeMillis () - _t0 + 999) / 1000; + + long ss = dt % 60; + long dm = dt / 60; + long mm = dm % 60; + long hh = dm / 60; + + _writer.println (""); + _writer.flush (); + } + + /** + * Local extension to the standard callback that does the final output. + * This should be in a suitable format for + * including multiple files between the header and the footer, and + * the XML of the header and footer must balance out. + * @param state Audit handler state + */ + public void showFooterImpl (AuditState state) + { + if (_nAudit > 0) { + String margin = getIndent (_level--); + _writer.println (margin + elementEnd ("audit")); + } + super.showFooter (); + } + + /** + * Do the initial output. This should be in a suitable format for + * including multiple files between the header and the footer, and + * the XML of the header and footer must balance out. + */ + public void showHeader () + { + /* Initialize the handler. */ + + _mimeType = new TreeMap (); + _stateMap = new TreeMap (); + _stateStack = new Stack (); + _nAudit = 0; + + _t0 = System.currentTimeMillis (); + + /* Instantiate a state object and initialize with the values + * of the global configuration file. + */ + + AuditState state = showHeaderImpl ("."); + _stateStack.push (state); + _home = state.getDirectory (); + } + + /** + * Local extension to the standard callback that does the initial output. + * This should be in a suitable format for including multiple files + * between the header and the footer, and the XML of the header and footer + * must balance out. + * @param directory Current directory filepath + */ + public AuditState showHeaderImpl (String directory) + { + super.showHeader (); + + return new AuditState (directory); + } + + /** + * Callback indicating a new directory is being processed. + * + * Additional state information can be added to the AuditState object + * in the showHeaderImpl() method before it is pushed onto the stack. + */ + public void startDirectory (String directory) + { + try { + AuditState state = (AuditState) + ((AuditState) _stateStack.peek ()).clone (directory); + + startDirectoryImpl (state); + _stateStack.push (state); + } + catch (CloneNotSupportedException e) { + e.printStackTrace (System.err); + System.exit (-1); + } + } + + /** + * Local extension to the standard callback indicating a new directory + * is being processed. + * @param state Audit handler state + */ + public void startDirectoryImpl (AuditState state) + { + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/handler/META-INF/MANIFEST.MF b/jhove/classes/edu/harvard/hul/ois/jhove/handler/META-INF/MANIFEST.MF new file mode 100644 index 00000000..5af99954 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/handler/META-INF/MANIFEST.MF @@ -0,0 +1,12 @@ +Manifest-Version: 1.0 +Ant-Version: Apache Ant 1.8.3 +Created-By: 1.6.0_37-b06-434-10M3909 (Apple Inc.) +Built-By: gmcgath +Built-On: 2012-12-09T08:40:55 + +Name: JHOVE +Version: 1.0 +Package: Handlers +Rights: Copyright 2004 by JSTOR and the President and Fellows of Harva + rd College + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/handler/TextHandler.java b/jhove/classes/edu/harvard/hul/ois/jhove/handler/TextHandler.java new file mode 100644 index 00000000..fa9fb8de --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/handler/TextHandler.java @@ -0,0 +1,2035 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003-2004 by JSTOR and the President and Fellows of Harvard College + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.handler; + +import edu.harvard.hul.ois.jhove.*; +import java.text.*; +import java.util.*; + +/** + * OutputHandler for plain text output. + */ +public class TextHandler + extends HandlerBase +{ + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + + private static final String NAME = "TEXT"; + private static final String RELEASE = "1.5"; + private static final int [] DATE = {2009, 10, 14}; + private static final String NOTE = "This is the default JHOVE output " + + "handler"; + private static final String RIGHTS = "Copyright 2003-2009 by JSTOR and " + + "the President and Fellows of Harvard College. " + + "Released under the terms of the GNU Lesser General Public License."; + + private NumberFormat _format; + + + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + /* Sample rate. */ + private double _sampleRate; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * Creates a TextHandler. + */ + public TextHandler () + { + super (NAME, RELEASE, DATE, NOTE, RIGHTS); + Agent agent = new Agent ("Harvard University Library", + AgentType.EDUCATIONAL); + agent.setAddress ("Office for Information Systems, " + + "90 Mt. Auburn St., " + + "Cambridge, MA 02138"); + agent.setTelephone ("+1 (617) 495-3724"); + agent.setEmail("jhove-support@hulmail.harvard.edu"); + _vendor = agent; + + _format = NumberFormat.getInstance (); + _format.setGroupingUsed (false); + _format.setMinimumFractionDigits (0); + } + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + ******************************************************************/ + + /** + * Outputs minimal information about the application + */ + public void show () + { + String margin = getIndent (++_level); + + _level--; + } + + /** + * Outputs detailed information about the application, + * including configuration, available modules and handlers, + * etc. + */ + public void show (App app) + { + String margin = getIndent (++_level); + + _writer.println (margin + "App:"); + _writer.println (margin + " API: " + _je.getRelease () + ", " + + HandlerBase.date.format (_je.getDate ())); + String configFile = _je.getConfigFile (); + if (configFile != null) { + _writer.println (margin + " Configuration: " + configFile ); + } + String s = _je.getSaxClass (); + if (s != null) { + _writer.println (margin + " SAXparser: " + s ); + } + s = _je.getJhoveHome (); + if (s != null) { + _writer.println (margin + " JhoveHome: " + s); + } + s = _je.getEncoding (); + if (s != null) { + _writer.println (margin + " Encoding: " + s); + } + s = _je.getTempDirectory (); + if (s != null) { + _writer.println (margin + " TempDirectory: " + s); + } + _writer.println (margin + " BufferSize: " + _je.getBufferSize ()); + Iterator iter = _je.getModuleMap ().keySet ().iterator (); + while (iter.hasNext ()) { + Module module = _je.getModule ((String) iter.next ()); + _writer.println (margin + " Module: " + module.getName () + " " + + module.getRelease ()); + } + iter = _je.getHandlerMap ().keySet ().iterator (); + while (iter.hasNext ()) { + OutputHandler handler = _je.getHandler ((String) iter.next ()); + _writer.println (margin + " OutputHandler: " + + handler.getName () + " " + + handler.getRelease ()); + } + + _writer.println (margin + " Usage: " + app.getUsage ()); + _writer.println (margin + " Rights: " + app.getRights ()); + + _level--; + } + + /** + * Outputs information about the OutputHandler specified + * in the parameter + */ + public void show (OutputHandler handler) + { + String margin = getIndent (++_level); + + _writer.println(margin + "Handler: " + handler.getName ()); + _writer.println (margin + " Release: " + handler.getRelease ()); + _writer.println (margin + " Date: " + + HandlerBase.date.format (handler.getDate ())); + List list = handler.getSpecification (); + int n = list.size (); + for (int i=0; i 0) { + _writer.print (margin + " Format: " + ss[0]); + for (int i=1; i 0) { + _writer.print (margin + " MIMEtype: " + ss[0]); + for (int i=1; i ftr = module.getFeatures (); + if (ftr != null) { + Iterator iter = ftr.iterator(); + while (iter.hasNext ()) { + s = iter.next (); + _writer.println (margin + " Feature: " + s); + } + } + + _writer.println (margin + " Methodology:"); + if ((s = module.getWellFormedNote ()) != null) { + _writer.println (margin + " Well-formed: " + s); + } + if ((s = module.getValidityNote ()) != null) { + _writer.println (margin + " Validity: " + s); + } + if ((s = module.getRepInfoNote ()) != null) { + _writer.println (margin + " RepresentationInformation: " + s); + } + Agent vendor = module.getVendor (); + if (vendor != null) { + showAgent (vendor, "Vendor"); + } + if ((s = module.getNote ()) != null) { + _writer.println (margin + " Note: " + s); + } + if ((s = module.getRights ()) != null) { + _writer.println (margin + " Rights: " + s); + } + + _level--; + } + + /** + * Outputs the information contained in a RepInfo object + */ + public void show (RepInfo info) + { + String margin = getIndent (++_level); + + Module module = info.getModule (); + _writer.println (margin + "RepresentationInformation: " + + info.getUri ()); + if (module != null) { + _writer.println (margin + " ReportingModule: " + module.getName() + + ", Rel. " + module.getRelease () + " (" + + date.format (module.getDate ()) + ")"); + } + + Date date = info.getCreated (); + if (date != null) { + _writer.println (margin + " Created: " + dateTime.format (date)); + } + date = info.getLastModified (); + if (date != null) { + _writer.println (margin + " LastModified: " + + dateTime.format (date)); + } + long size = info.getSize (); + if (size > -1) { + _writer.println (margin + " Size: " + size); + } + String s = info.getFormat (); + if (s != null) { + _writer.println (margin + " Format: " + s); + } + s = info.getVersion (); + if (s != null) { + _writer.println (margin + " Version: " + s); + } + if (!_je.getSignatureFlag ()) { + _writer.print (margin + " Status: "); + switch (info.getWellFormed ()) { + case RepInfo.TRUE: + s = "Well-Formed"; + break; + + case RepInfo.FALSE: + s = "Not well-formed"; + break; + + default: + s = "Unknown"; + break; + } + if (info.getWellFormed () == RepInfo.TRUE) { + switch (info.getValid ()) { + + case RepInfo.TRUE: + s += " and valid"; + break; + + case RepInfo.FALSE: + s += ", but not valid"; + break; + + // case UNDETERMINED: add nothing + } + } + _writer.println (s); + } + else { + // If we aren't checking signatures, we still need to say something. + _writer.print (margin + " Status: "); + switch (info.getWellFormed ()) { + case RepInfo.TRUE: + s = "Well-Formed"; + break; + + default: + s = "Not well-formed"; + break; + } + _writer.println (s); + } + List list = info.getSigMatch(); + int n = list.size (); + if (n > 0) { + _writer.println (margin + " SignatureMatches:"); + for (int i = 0; i < n; i++) { + _writer.println (margin + " " + + (String) list.get (i)); + } + } + + list = info.getMessage (); + n = list.size (); + for (int i=0; i 0) { + _writer.print (margin + " Profile: " + (String) list.get (0)); + for (int i=1; i -1) { + _writer.println (margin + " Offset: " + offset); + } + _level--; + } + + private void showSignature (Signature signature) + { + String margin = getIndent (++_level); + + String sigValue; + if (signature.isStringValue ()) { + sigValue = signature.getValueString (); + } + else { + sigValue = signature.getValueHexString (); + } + _writer.println (margin + signature.getType ().toString () + ": " + + sigValue); + if (signature.getType ().equals (SignatureType.MAGIC)) { + if (((InternalSignature) signature).hasFixedOffset ()) { + _writer.println (margin + " Offset: " + + ((InternalSignature) signature).getOffset ()); + } + } + String note = signature.getNote (); + if (note != null) { + _writer.println (margin + " Note: " + note); + } + String use = signature.getUse ().toString (); + if (use != null) { + _writer.println (margin + " Use: " + use); + } + _level--; + } + + /* showProperty may be called recursively. */ + private void showProperty (Property property, String key, String margin) + { + PropertyArity arity = property.getArity (); + + if (key == null) { + _writer.print (margin + " "); + } + else { + _writer.print (margin + " " + key + ": "); + } + if (arity.equals (PropertyArity.SCALAR)) { + showScalarProperty (property, margin); + } + else if (arity.equals (PropertyArity.LIST)) { + showListProperty (property, margin); + } + else if (arity.equals (PropertyArity.MAP)) { + showMapProperty (property, margin); + } + else if (arity.equals (PropertyArity.SET)) { + showSetProperty (property, margin); + } + else if (arity.equals (PropertyArity.ARRAY)) { + showArrayProperty (property, margin); + } + else { + _writer.println (); + } + } + + + private void showScalarProperty (Property property, String margin) + { + PropertyType type = property.getType (); + if (PropertyType.PROPERTY.equals (type)) { + _writer.println (); + Property prop = (Property) property.getValue (); + showProperty (prop, prop.getName (), margin + " "); + //_writer.println (); // Does this improve things? + } + else if (PropertyType.NISOIMAGEMETADATA.equals (type)) { + showNisoImageMetadata ((NisoImageMetadata) property.getValue (), + margin + " ", _je.getShowRawFlag ()); + } + else if (PropertyType.AESAUDIOMETADATA.equals (type)) { + showAESAudioMetadata ((AESAudioMetadata) property.getValue (), + margin + " ", _je.getShowRawFlag ()); + } + else if (PropertyType.TEXTMDMETADATA.equals(type)) { + showTextMDMetadata((TextMDMetadata) property.getValue(), + margin + " ", _je.getShowRawFlag ()); + } + else { + _writer.println (property.getValue ().toString ()); + } + } + + private void showListProperty (Property property, String margin) + { + PropertyType type = property.getType (); + boolean valueIsProperty = PropertyType.PROPERTY.equals (type); + boolean valueIsNiso = PropertyType.NISOIMAGEMETADATA.equals (type); + boolean valueIsTextMD = PropertyType.TEXTMDMETADATA.equals(type); + + List list = (List) property.getValue (); + + int n = list.size (); + int i; + if (n > 0) { + // Put a blank line after the name of the property list. + if (valueIsProperty) { + _writer.println (); + } + for (i = 0; i < n; i++) { + if (valueIsProperty) { + Property pval = (Property) list.get (i); + showProperty (pval, pval.getName (), margin + " "); + } + else if (valueIsNiso) { + showNisoImageMetadata ((NisoImageMetadata) list.get (i), + margin + " ", _je.getShowRawFlag ()); + } + else if (valueIsTextMD) { + showTextMDMetadata( (TextMDMetadata) list.get (i), + margin + " ", _je.getShowRawFlag ()); + } + else { + Object val = list.get (i); + if (i == 0) { + _writer.print (val); + } + else { + _writer.print (", " + val); + } + } + } + } + if (!valueIsProperty || n == 0) { + _writer.println (); + } + } + + private void showMapProperty (Property property, String margin) + { + /* Map output looks like + key : mapkey1 / mapval1, mapkey2 / mapval2, ... */ + PropertyType type = property.getType (); + boolean valueIsProperty = PropertyType.PROPERTY.equals (type); + boolean valueIsNiso = PropertyType.NISOIMAGEMETADATA.equals (type); + boolean valueIsTextMD = PropertyType.TEXTMDMETADATA.equals(type); + + Map propmap = (Map) property.getValue (); + Set keys = propmap.keySet(); + Iterator propiter = keys.iterator(); + while (propiter.hasNext ()) { + Object propkey = propiter.next(); + Object val = propmap.get(propkey); + if (valueIsProperty) { + Property pval = (Property) val; + showProperty (pval, pval.getName (), margin + " "); + String propkeyStr = propkey.toString (); + if (!(pval.getName ().equals(propkeyStr ))) { + _writer.println (" Key: " + propkeyStr); + } + } + else if (valueIsNiso) { + showNisoImageMetadata ((NisoImageMetadata) val, + margin + " ", _je.getShowRawFlag ()); + } + else if (valueIsTextMD) { + showTextMDMetadata ((TextMDMetadata) val, + margin + " ", _je.getShowRawFlag ()); + } + else { + _writer.println (" " + val.toString ()); + _writer.println (" Key: " + propkey.toString ()); + } + } + } + + private void showSetProperty (Property property, + String margin) { + PropertyType type = property.getType (); + boolean valueIsProperty = PropertyType.PROPERTY.equals (type); + boolean valueIsNiso = PropertyType.NISOIMAGEMETADATA.equals (type); + boolean valueIsTextMD = PropertyType.TEXTMDMETADATA.equals(type); + + Set propset = (Set) property.getValue (); + Iterator propiter = propset.iterator (); + boolean first = true; + while (propiter.hasNext ()) { + Object val = propiter.next (); + if (valueIsProperty) { + Property pval = (Property) val; + showProperty (pval, pval.getName (), margin + " "); + } + else if (valueIsNiso) { + showNisoImageMetadata ((NisoImageMetadata) val, + margin + " ", _je.getShowRawFlag ()); + } + else if (valueIsTextMD) { + showTextMDMetadata ((TextMDMetadata) val, + margin + " ", _je.getShowRawFlag ()); + } + else { + if (first) { + _writer.print (val.toString ()); + first = false; + } + else { + _writer.print (", " + val.toString ()); + } + } + } + _writer.println (); + } + + private void showArrayProperty (Property property, String margin) { + boolean[] boolArray = null; + byte[] byteArray = null; + char[] charArray = null; + java.util.Date[] dateArray = null; + double[] doubleArray = null; + float[] floatArray = null; + int[] intArray = null; + long[] longArray = null; + Object[] objArray = null; + Property[] propArray = null; + short[] shortArray = null; + String[] stringArray = null; + Rational[] rationalArray = null; + NisoImageMetadata[] nisoArray = null; + TextMDMetadata[] textMDArray = null; + int n = 0; + + PropertyType propType = property.getType(); + if (PropertyType.BOOLEAN.equals (propType)) { + boolArray = (boolean []) property.getValue (); + n = boolArray.length; + } + else if (PropertyType.BYTE.equals (propType)) { + byteArray = (byte []) property.getValue (); + n = byteArray.length; + } + else if (PropertyType.CHARACTER.equals (propType)) { + charArray = (char []) property.getValue (); + n = charArray.length; + } + else if (PropertyType.DATE.equals (propType)) { + dateArray = (java.util.Date []) property.getValue (); + n = dateArray.length; + } + else if (PropertyType.DOUBLE.equals (propType)) { + doubleArray = (double []) property.getValue (); + n = doubleArray.length; + } + else if (PropertyType.FLOAT.equals (propType)) { + floatArray = (float []) property.getValue (); + n = floatArray.length; + } + else if (PropertyType.INTEGER.equals (propType)) { + intArray = (int []) property.getValue (); + n = intArray.length; + } + else if (PropertyType.LONG.equals (propType)) { + longArray = (long []) property.getValue (); + n = longArray.length; + } + else if (PropertyType.OBJECT.equals (propType)) { + objArray = (Object []) property.getValue (); + n = objArray.length; + } + else if (PropertyType.SHORT.equals (propType)) { + shortArray = (short []) property.getValue (); + n = shortArray.length; + } + else if (PropertyType.STRING.equals (propType)) { + stringArray = (String []) property.getValue (); + n = stringArray.length; + } + else if (PropertyType.RATIONAL.equals (propType)) { + rationalArray = (Rational []) property.getValue (); + n = rationalArray.length; + } + else if (PropertyType.PROPERTY.equals (propType)) { + propArray = (Property []) property.getValue (); + n = propArray.length; + } + else if (PropertyType.NISOIMAGEMETADATA.equals (propType)) { + nisoArray = (NisoImageMetadata []) property.getValue (); + n = nisoArray.length; + } + else if (PropertyType.TEXTMDMETADATA.equals(propType)) { + textMDArray = (TextMDMetadata []) property.getValue (); + n = textMDArray.length; + } + + for (int i = 0; i < n; i++) { + String elem; + if (PropertyType.BOOLEAN.equals (propType)) { + elem = String.valueOf (boolArray[i]); + } + else if (PropertyType.BYTE.equals (propType)) { + elem = String.valueOf (byteArray[i]); + } + else if (PropertyType.CHARACTER.equals (propType)) { + elem = String.valueOf (charArray[i]); + } + else if (PropertyType.DATE.equals (propType)) { + elem = dateArray[i].toString(); + } + else if (PropertyType.DOUBLE.equals (propType)) { + elem = String.valueOf (doubleArray[i]); + } + else if (PropertyType.FLOAT.equals (propType)) { + elem = String.valueOf (floatArray[i]); + } + else if (PropertyType.INTEGER.equals (propType)) { + elem = String.valueOf (intArray[i]); + } + else if (PropertyType.LONG.equals (propType)) { + elem = String.valueOf (longArray[i]); + } + else if (PropertyType.OBJECT.equals (propType)) { + elem = objArray[i].toString(); + } + else if (PropertyType.SHORT.equals (propType)) { + elem = String.valueOf (shortArray[i]); + } + else if (PropertyType.STRING.equals (propType)) { + elem = stringArray[i]; + } + else if (PropertyType.RATIONAL.equals (propType)) { + elem = rationalArray[i].toString (); + } + else if (PropertyType.NISOIMAGEMETADATA.equals (propType)) { + if (i == 0) { + _writer.println (); + } + NisoImageMetadata niso = nisoArray[i]; + showNisoImageMetadata (niso, + margin + " ", _je.getShowRawFlag ()); + continue; + } + else if (PropertyType.TEXTMDMETADATA.equals (propType)) { + if (i == 0) { + _writer.println (); + } + showTextMDMetadata (textMDArray[i], + margin + " ", _je.getShowRawFlag ()); + continue; + } + else if (PropertyType.PROPERTY.equals (propType)) { + if (i == 0) { + _writer.println (); + } + Property pval = propArray[i]; + showProperty (pval, pval.getName (), margin + " "); + continue; + } + else elem = ""; + if (i == 0) { + _writer.print (elem); + } + else { + _writer.print (", " + elem); + } + } + if (propType != PropertyType.PROPERTY && + propType != PropertyType.NISOIMAGEMETADATA) { + _writer.println (); + } + } + + /* Output the textMD metadata, which is its own special + * kind of property. */ + private void showTextMDMetadata (TextMDMetadata textMD, String margin, + boolean rawOutput) + { + String margn2 = margin + " "; + String margn3 = margn2 + " "; + + _writer.println (); + _writer.println (margn2 + "Character_info:"); + String s = textMD.getCharset (); + if (s != null) { + _writer.println (margn3 + "Charset: " + s); + } + if ((s = textMD.getByte_orderString ()) != null) { + _writer.println (margn3 + "Byte_order: " + s); + } + if ((s = textMD.getByte_size ()) != null) { + _writer.println (margn3 + "Byte_size: " + s); + } + if ((s = textMD.getCharacter_size ()) != null) { + _writer.println (margn3 + "Character_size: " + s); + } + if ((s = textMD.getLinebreakString ()) != null) { + _writer.println (margn3 + "Linebreak: " + s); + } + + if ((s = textMD.getLanguage ()) != null) { + _writer.println (margn2 + "Language: " + s); + } + if ((s = textMD.getMarkup_basis ()) != null) { + _writer.println (margn2 + "Markup_basis: " + s); + } + if ((s = textMD.getMarkup_basis_version ()) != null) { + _writer.println (margn2 + "Markup_basis_version: " + s); + } + if ((s = textMD.getMarkup_language ()) != null) { + _writer.println (margn2 + "Markup_language: " + s); + } + if ((s = textMD.getMarkup_language_version ()) != null) { + _writer.println (margn2 + "Markup_language_version: " + s); + } + } + + /* Output the AES audio metadata, which is its own special + * kind of property. */ + private void showAESAudioMetadata (AESAudioMetadata aes, String margin, + boolean rawOutput) + { + String margn2 = margin + " "; + String margn3 = margn2 + " "; + String margn4 = margn3 + " "; + String margn5 = margn4 + " "; + + _sampleRate = aes.getSampleRate (); + + _writer.println (); + String s = aes.getAnalogDigitalFlag(); + if (s != null) { + _writer.println (margn2 + "AnalogDigitalFlag: " + s); + } + s = aes.getSchemaVersion (); + if (s != null) { + _writer.println (margn2 + "SchemaVersion: " + s); + } + s = aes.getFormat (); + if (s != null) { + _writer.println (margn2 + "Format: " + s); + } + s = aes.getSpecificationVersion (); + if (s != null) { + _writer.println (margn2 + "SpecificationVersion: " + s); + } + s = aes.getAppSpecificData(); + if (s != null) { + _writer.println (margn2 + "AppSpecificData: " + s); + } + s = aes.getAudioDataEncoding (); + if (s != null) { + _writer.println (margn2 + "AudioDataEncoding: " + s); + } + int in = aes.getByteOrder (); + if (in != AESAudioMetadata.NULL) { + _writer.println (margn2 + "ByteOrder: " + + (in == AESAudioMetadata.BIG_ENDIAN ? + "BIG_ENDIAN" : "LITTLE_ENDIAN")); + } + long lin = aes.getFirstSampleOffset (); + if (lin != AESAudioMetadata.NULL) { + _writer.println (margn2 + "FirstSampleOffset: " + + Long.toString (lin)); + } + String[] use = aes.getUse (); + if (use != null) { + _writer.println (margn2 + "Use:"); + _writer.println (margn3 + "UseType: " + use[0]); + _writer.println (margn3 + "OtherType: " + use[1]); + } + s = aes.getPrimaryIdentifier(); + if (s != null) { + String t= aes.getPrimaryIdentifierType (); + _writer.println (margn2 + "PrimaryIdentifier: " + s); + if (t != null) { + _writer.println (margn3 + "IdentifierType: " + t); + } + } + List facelist = aes.getFaceList (); + if (!facelist.isEmpty ()) { + // Add the face information, which is mostly filler. + AESAudioMetadata.Face f = + (AESAudioMetadata.Face) facelist.get(0); + _writer.println (margn2 + "Face: "); + _writer.println (margn3 + "TimeLine: "); + AESAudioMetadata.TimeDesc startTime = f.getStartTime(); + if (startTime != null) { + writeAESTimeRange (margn3, startTime, f.getDuration()); + } + int nchan = aes.getNumChannels (); + if (nchan != AESAudioMetadata.NULL) { + _writer.println (margn4 + "NumChannels: " + + Integer.toString (nchan)); + } + String[] locs = aes.getMapLocations (); + for (int ch = 0; ch < nchan; ch++) { + // write a stream description for each channel + _writer.println (margn4 + "Stream:"); + _writer.println (margn5 + "ChannelNum: " + Integer.toString (ch)); + _writer.println (margn5 + "ChannelAssignment: " + locs[ch]); + } + } + + // In the general case, a FormatList can contain multiple + // FormatRegions. This doesn't happen with any of the current + // modules; if it's needed in the future, simply set up an + // iteration loop on formatList. + List flist = aes.getFormatList (); + if (!flist.isEmpty ()) { + AESAudioMetadata.FormatRegion rgn = + (AESAudioMetadata.FormatRegion) flist.get(0); + int bitDepth = rgn.getBitDepth (); + double sampleRate = rgn.getSampleRate (); + int wordSize = rgn.getWordSize (); + String[] bitRed = rgn.getBitrateReduction (); + // Build a FormatRegion subtree if at least one piece of data + // that goes into it is present. + if (bitDepth != AESAudioMetadata.NULL || + sampleRate != AESAudioMetadata.NILL || + wordSize != AESAudioMetadata.NULL) { + _writer.println (margn2 + "FormatList:"); + _writer.println (margn3 + "FormatRegion:"); + if (bitDepth != AESAudioMetadata.NULL) { + _writer.println (margn4 + "BitDepth: " + Integer.toString (bitDepth)); + } + if (sampleRate != AESAudioMetadata.NILL) { + _writer.println (margn4 + "SampleRate: " + Double.toString (sampleRate)); + } + if (wordSize != AESAudioMetadata.NULL) { + _writer.println (margn4 + "WordSize: " + Integer.toString (wordSize)); + } + if (bitRed != null) { + _writer.println (margn4 + "BitrateReduction"); + _writer.println (margn5 + + "CodecName: " + bitRed[0]); + _writer.println (margn5 + + "codecNameVersion: " + bitRed[1]); + _writer.println (margn5 + + "codecCreatorApplication: " + bitRed[2]); + _writer.println (margn5 + + "codecCreatorApplicationVersion: " + bitRed[3]); + _writer.println (margn5 + + "codecQuality: " + bitRed[4]); + _writer.println (margn5 + + "dataRate: " + bitRed[5]); + _writer.println (margn5 + + "dataRateMode: " + bitRed[6]); + } + } + } + } + + /* start must be non-null, but duration may be null */ + private void writeAESTimeRange (String baseIndent, + AESAudioMetadata.TimeDesc start, + AESAudioMetadata.TimeDesc duration) + { + final String margn1 = baseIndent + " "; + final String margn2 = margn1 + " "; + final String margn3 = margn2 + " "; + _writer.println (margn1 + "StartTime:"); + _writer.println (margn2 + "FrameCount: 30"); + _writer.println (margn2 + "TimeBase: 1000"); + _writer.println (margn2 + "VideoField: FIELD_1"); + _writer.println (margn2 + "CountingMode: NTSC_NON_DROP_FRAME"); + _writer.println (margn2 + "Hours: " + Integer.toString (start.getHours ())); + _writer.println (margn2 + "Minutes: " + Integer.toString (start.getMinutes ())); + _writer.println (margn2 + "Seconds: " + Integer.toString (start.getSeconds ())); + _writer.println (margn2 + "Frames: " + Integer.toString (start.getFrames ())); + _writer.println (margn2 + "Samples: "); + double sr = start.getSampleRate (); + if (sr == 1.0) { + sr = _sampleRate; + } + _writer.println (margn3 + "SampleRate: S" + + Integer.toString ((int) sr)); + _writer.println (margn3 + "NumberOfSamples: " + + Integer.toString (start.getSamples ())); + _writer.println (margn2 + "FilmFraming: NOT_APPLICABLE"); + _writer.println (margn3 + "Type: ntscFilmFramingType"); + + if (duration != null) { + _writer.println (margn1 + "Duration:"); + _writer.println (margn2 + "FrameCount: 30"); + _writer.println (margn2 + "TimeBase: 1000"); + _writer.println (margn2 + "VideoField: FIELD_1"); + _writer.println (margn2 + "CountingMode: NTSC_NON_DROP_FRAME"); + _writer.println (margn2 + "Hours: " + + Integer.toString (duration.getHours ())); + _writer.println (margn2 + "Minutes: " + + Integer.toString (duration.getMinutes ())); + _writer.println (margn2 + "Seconds: " + + Integer.toString (duration.getSeconds ())); + _writer.println (margn2 + "Frames: " + + Integer.toString (duration.getFrames ())); + _writer.println (margn2 + "Samples: "); + sr = duration.getSampleRate (); + if (sr == 1.0) { + sr = _sampleRate; + } + _writer.println (margn3 + "SampleRate: S" + + Integer.toString ((int) sr)); + _writer.println (margn3 + "NumberOfSamples: " + + Integer.toString (duration.getSamples ())); + _writer.println (margn2 + "FilmFraming: NOT_APPLICABLE"); + _writer.println (margn3 + "Type: ntscFilmFramingType"); + } + } + + + /** + * Display the NISO image metadata formatted according to + * the MIX schema. The schema which is used may be 0.2 or 1.0, + * depending on the module parameters. + * @param niso NISO image metadata + */ + protected void showNisoImageMetadata (NisoImageMetadata niso, String margin, + boolean rawOutput) + { + if ("0.2".equals (_je.getMixVersion())) { + showNisoImageMetadata02 (niso, margin, rawOutput); + } + else { + showNisoImageMetadata10 (niso, margin, rawOutput); + } + } + + /* Output the Niso image metadata, which is its own special + * kind of property. This provides a text approximation to MIX 0.2. */ + private void showNisoImageMetadata02 (NisoImageMetadata niso, String margin, + boolean rawOutput) + { + String margn2 = margin + " "; + + _writer.println (); + String s = niso.getMimeType (); + if (s != null) { + _writer.println (margn2 + "MIMEType: " + s); + } + if ((s = niso.getByteOrder ()) != null) { + _writer.println (margn2 + "ByteOrder: " + s); + } + int n = niso.getCompressionScheme (); + if (n != NisoImageMetadata.NULL) { + _writer.println (margn2 + "CompressionScheme: " + + addIntegerValue (n, NisoImageMetadata.COMPRESSION_SCHEME, + NisoImageMetadata.COMPRESSION_SCHEME_INDEX, + rawOutput)); + } + if ((n = niso.getCompressionLevel ()) != NisoImageMetadata.NULL) { + _writer.println (margn2 + "CompressionLevel: " + n); + } + if ((n = niso.getColorSpace ()) != NisoImageMetadata.NULL) { + _writer.println (margn2 + "ColorSpace: " + + addIntegerValue (n, NisoImageMetadata.COLORSPACE, + NisoImageMetadata.COLORSPACE_INDEX, + rawOutput)); + } + if ((s = niso.getProfileName ()) != null) { + _writer.println (margn2 + "ProfileName: " + s); + } + if ((s = niso.getProfileURL ()) != null) { + _writer.println (margn2 + "ProfileURL: " + s); + } + int [] iarray = niso.getYCbCrSubSampling (); + if (iarray != null) { + _writer.print (margn2 + "YCbCrSubSampling: " + iarray[0]); + for (int i=1; i yres ? xres : yres); + _writer.println (margn2 + + "MaximumOpticalResolution: " + Double.toString (res)); + } + if ((s = niso.getScanningSoftware ()) != null) { + _writer.println (margn2 + "ScanningSoftware: " + s); + } + if ((s = niso.getScanningSoftwareVersionNo ()) != null) { + _writer.println (margn2 + "ScanningSoftwareVersionNo: " + s); + } + if ((s = niso.getDigitalCameraManufacturer ()) != null) { + _writer.println (margn2 + "DigitalCameraManufacturer: " + s); + } + if ((s = niso.getDigitalCameraModel ()) != null) { + _writer.println (margn2 + "DigitalCameraModel: " + s); + } + if ((d = niso.getFNumber ()) != NisoImageMetadata.NILL) { + _writer.println (margn2 + "FNumber: " + d); + } + if ((d = niso.getExposureTime ()) != NisoImageMetadata.NILL) { + _writer.println (margn2 + "ExposureTime: " + d); + } + if ((d = niso.getBrightness ()) != NisoImageMetadata.NILL) { + _writer.println (margn2 + "BrightnessValue: " + d); + } + if ((d = niso.getExposureBias ()) != NisoImageMetadata.NILL) { + _writer.println (margn2 + "ExposureBiasValue: " + d); + } + double [] darray = niso.getSubjectDistance (); + if (darray != null) { + _writer.print (margn2 + "SubjectDistance: " + darray[0]); + for (int i=1; i -1) { + s = labels[n]; + } + else { + outOfRange = true; + } + } + if (rawOutput || outOfRange) { + s = Integer.toString (value); + } + + return s; + } + + private String addRationalValue (Rational r, boolean rawOutput) + { + String s = null; + if (!rawOutput) { + s = _format.format (r.toDouble ()); + } + else { + s = r.toString (); + } + + return s; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/handler/XmlHandler.java b/jhove/classes/edu/harvard/hul/ois/jhove/handler/XmlHandler.java new file mode 100644 index 00000000..a308f914 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/handler/XmlHandler.java @@ -0,0 +1,4383 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003-2009 by JSTOR and the President and Fellows of Harvard College + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.handler; + +import edu.harvard.hul.ois.jhove.*; +import java.util.*; + +/** + * OutputHandler for XML output. + * + * @see Schema + * for JHOVE XML output + */ +public class XmlHandler + extends edu.harvard.hul.ois.jhove.HandlerBase + +{ + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + + /** Handler name. */ + private static final String NAME = "XML"; + + /** Handler release identifier. */ + private static final String RELEASE = "1.7"; + + /** Handler release date. */ + private static final int [] DATE = {2012, 8, 12}; + + /** Handler informative note. */ + private static final String NOTE = + "This output handler is defined by the XML Schema " + + "http://hul.harvard.edu/ois/xml/xsd/jhove/jhove.xsd"; + + /** Handler rights statement. */ + private static final String RIGHTS = + "Derived from software Copyright 2004-2011 " + + "by the President and Fellows of Harvard College. " + + "Version 1.7 independently released. " + + "Released under the GNU Lesser General Public License."; + + /** Localized line separator character. */ + private final static String EOL = System.getProperty ("line.separator"); + + /** Schema version. */ + private static final String SCHEMA_VERSION = "1.6"; + + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + /* Sample rate. */ + private double _sampleRate; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * Creates an XmlHandler. + */ + public XmlHandler () + { + super (NAME, RELEASE, DATE, NOTE, RIGHTS); + Agent agent = new Agent ("Harvard University Library", + AgentType.EDUCATIONAL); + agent.setAddress ("Office for Information Systems, " + + "90 Mt. Auburn St., " + + "Cambridge, MA 02138"); + agent.setTelephone ("+1 (617) 495-3724"); + agent.setEmail("jhove-support@hulmail.harvard.edu"); + _vendor = agent; + } + + + /** Constructor for use by subclasses. */ + public XmlHandler (String name, String release, int [] date, + String note, String rights) + { + super (name, release, date, note, rights); + Agent agent = new Agent ("Harvard University Library", + AgentType.EDUCATIONAL); + agent.setAddress ("Office for Information Systems, " + + "90 Mt. Auburn St., " + + "Cambridge, MA 02138"); + agent.setTelephone ("+1 (617) 495-3724"); + agent.setEmail("jhove-support@hulmail.harvard.edu"); + _vendor = agent; + } + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + ******************************************************************/ + + /** + * Outputs minimal information about the application + */ + public void show () + { + String margin = getIndent (++_level); + _level--; + } + + /** + * Outputs detailed information about the application, + * including configuration, available modules and handlers, + * etc. + */ + public void show (App app) + { + String margin = getIndent (++_level); + String margn2 = margin + " "; + String margn3 = margn2 + " "; + + _writer.println (margin + elementStart ("app")); + String [][] attrs = { {"date", date.format (_je.getDate ())} }; + _writer.println (margn2 + element ("api", attrs, + _je.getRelease ())); + String configFile = _je.getConfigFile (); + if (configFile != null) { + _writer.println (margn2 + element ("configuration", configFile)); + } + String s = _je.getSaxClass (); + if (s != null ) { + _writer.println (margn2 + element ("saxParser", s)); + } + s = _je.getJhoveHome (); + if (s != null ) { + _writer.println (margn2 + element ("jhoveHome", s)); + } + s = _je.getEncoding (); + if (s != null ) { + _writer.println (margn2 + element ("encoding", s)); + } + s = _je.getTempDirectory (); + if (s != null ) { + _writer.println (margn2 + element ("tempDirectory", s)); + } + _writer.println (margn2 + element ("bufferSize", + Integer.toString (_je.getBufferSize ()))); + _writer.println (margn2 + elementStart ("modules")); + Iterator iter = _je.getModuleMap ().keySet ().iterator (); + while (iter.hasNext ()) { + Module module = _je.getModule ((String) iter.next ()); + String [][] attr2 = { {"release", module.getRelease ()} }; + _writer.println (margn3 + element ("module", attr2, + module.getName ())); + } + _writer.println (margn2 + elementEnd ("modules")); + _writer.println (margn2 + elementStart ("outputHandlers")); + iter = _je.getHandlerMap ().keySet ().iterator (); + while (iter.hasNext ()) { + OutputHandler handler = _je.getHandler ((String) iter.next ()); + String [][] attr2 = { {"release", handler.getRelease ()} }; + _writer.println (margn3 + element ("outputHandler", attr2, + handler.getName ())); + } + _writer.println (margn2 + elementEnd ("outputHandlers")); + _writer.println (margn2 + element ("usage", app.getUsage ())); + _writer.println (margn2 + element ("rights", app.getRights ())); + _writer.println (margin + elementEnd ("app")); + _level--; + } + + /** + * Outputs information about the OutputHandler specified + * in the parameter + */ + public void show (OutputHandler handler) + { + String margin = getIndent (++_level); + String margn2 = margin + " "; + _writer.println (margin + elementStart ("handler")); + _writer.println (margn2 + element ("name", handler.getName ())); + _writer.println (margn2 + element ("release", handler.getRelease ())); + _writer.println (margn2 + element ("date", + date.format (handler.getDate ()))); + List list = handler.getSpecification (); + int n = list.size (); + if (n > 0) { + _writer.println (margn2 + elementStart ("specifications")); + ++_level; + for (int i=0; i 0) { + _writer.println (margn2 + elementStart ("formats")); + for (int i=0; i 0) { + _writer.println (margn2 + elementStart ("mimeTypes")); + for (int i=0; i 0) { + _writer.println (margn2 + elementStart ("signatures")); + ++_level; + for (int i=0; i 0) { + _writer.println (margn2 + elementStart ("specifications")); + ++_level; + for (int i=0; i -1) { + _writer.println (margn2 + element ("size", Long.toString (size))); + } + String s = info.getFormat (); + if (s != null) { + _writer.println (margn2 + element ("format", s)); + } + s = info.getVersion (); + if (s != null) { + _writer.println (margn2 + element ("version", s)); + } + String wfStr; + if (!_je.getSignatureFlag ()) { + switch (info.getWellFormed ()) { + case RepInfo.TRUE: + wfStr = "Well-Formed"; + break; + + case RepInfo.FALSE: + wfStr = "Not well-formed"; + break; + + default: + wfStr = "Unknown"; + break; + } + // If it's well-formed, append validity information + if (info.getWellFormed () == RepInfo.TRUE) { + switch (info.getValid ()) { + case RepInfo.TRUE: + wfStr += " and valid"; + break; + + case RepInfo.FALSE: + wfStr += ", but not valid"; + break; + + // case UNDETERMINED: add nothing + } + } + _writer.println (margn2 + element ("status", wfStr)); + } + else { +// If we aren't checking signatures, we still need to say something. + switch (info.getWellFormed ()) { + case RepInfo.TRUE: + wfStr = "Well-Formed"; + break; + + default: + wfStr = "Not well-formed"; + break; + } + _writer.println (margn2 + element ("status", wfStr)); + } + + List list = info.getSigMatch(); + int n = list.size (); + if (n > 0) { + _writer.println (margn2 + elementStart ("sigMatch")); + _level++; + for (int i = 0; i < n; i++) { + _writer.println (margn2 + element ("module", + (String) list.get (i))); + } + _level--; + _writer.println (margn2 + elementEnd ("sigMatch")); + } + + list = info.getMessage (); + n = list.size (); + if (n > 0) { + _writer.println (margn2 + elementStart ("messages")); + _level++; + for (int i=0; i 0) { + _writer.println (margn2 + elementStart ("profiles")); + for (int i=0; i 0) { + _writer.println (margn2 + elementStart ("properties")); + Iterator iter = map.keySet ().iterator (); + while (iter.hasNext ()) { + String key = (String) iter.next (); + Property property = info.getProperty (key); + showProperty (property); + } + _writer.println (margn2 + elementEnd ("properties")); + } + } + + list = info.getChecksum (); + n = list.size (); + if (n > 0) { + _writer.println (margn2 + elementStart ("checksums")); + _level++; + for (int i=0; i 0) { + _writer.println (margn2 + elementStart ("authors")); + ++_level; + for (int i=0; i 0) { + ++_level; + _writer.println (margn2 + elementStart ("publishers")); + for (int i=0; i 0) { + _writer.println (margn2 + elementStart ("identifiers")); + ++_level; + for (int i=0; i -1) { + attrs[1] [1] = Long.toString (offset); + hasAttr = true; + } + if (message instanceof ErrorMessage) { + attrs[2] [1] = "error"; + hasAttr = true; + } + else if (message instanceof InfoMessage) { + attrs[2] [1] = "info"; + hasAttr = true; + } + if (hasAttr) { + _writer.println (margin + element ("message", attrs, + message.getMessage ())); + } + else { + _writer.println (margin + element ("message", + message.getMessage ())); + } + _level--; + } + + protected void showSignature (Signature signature) + { + String margin = getIndent (++_level); + String margin1 = margin + " "; + String sigValue; + + _writer.println (margin + elementStart ("signature")); + if (signature.isStringValue ()) { + sigValue = signature.getValueString (); + } + else { + sigValue = signature.getValueHexString (); + } + _writer.println (margin1 + element ("type", + signature.getType ().toString ())); + _writer.println (margin1 + element ("value", + sigValue )); + if (signature.getType ().equals (SignatureType.MAGIC)) { + if (((InternalSignature) signature).hasFixedOffset ()) { + _writer.println (margin1 + + element ("offset", + "0x" + Integer.toHexString + (((InternalSignature) signature).getOffset ()))); + } + } + String note = signature.getNote (); + if (note != null) { + _writer.println (margin1 + element ("note", note)); + + } + String use = signature.getUse ().toString (); + if (use != null) { + _writer.println (margin1 + element ("use", use)); + } + _writer.println (margin + elementEnd ("signature")); + _level--; + } + + /* Do special conversions on values as needed. */ + protected String valueToString(Object obj) { + if (obj instanceof Date) { + return toDateTime ((Date) obj); + } + else return obj.toString (); + } + + protected void showProperty (Property property) + { + String margin = getIndent (++_level); + String margn2 = margin + " "; + String margn3 = margn2 + " "; + String margn4 = margn3 + " "; + + PropertyArity arity = property.getArity (); + PropertyType type = property.getType (); + + // If the property would generate an empty element, don't output it, + // as this could result in a schema violation. + if (isPropertyEmpty (property, arity)) + return; + + boolean valueIsProperty = PropertyType.PROPERTY.equals(type); + boolean valueIsNiso = PropertyType.NISOIMAGEMETADATA.equals(type); + boolean valueIsAes = PropertyType.AESAUDIOMETADATA.equals(type); + boolean valueIsTextMD = PropertyType.TEXTMDMETADATA.equals(type); + + String[][] propAttrs = new String[2][]; + propAttrs[0] = new String[] { "arity", arity.toString () }; + propAttrs[1] = new String[] { "type", type.toString () }; + _writer.println (margn2 + elementStart ("property")); + _writer.println (margn3 + element ("name", + property.getName ())); + _writer.println (margn3 + elementStart ("values", propAttrs)); + if (arity.equals (PropertyArity.SCALAR)) { + /* Just a single value */ + if (valueIsProperty) { + showProperty ((Property) property.getValue ()); + } + else if (valueIsNiso) { + _writer.println (margn4 + elementStart ("value")); + showNisoImageMetadata ((NisoImageMetadata)property.getValue()); + _writer.println (margn4 + elementEnd ("value")); + } + else if (valueIsAes) { + _writer.println (margn4 + elementStart ("value")); + showAESAudioMetadata ((AESAudioMetadata)property.getValue()); + _writer.println (margn4 + elementEnd ("value")); + } + else if (valueIsTextMD) { + _writer.println (margn4 + elementStart ("value")); + showTextMDMetadata ((TextMDMetadata)property.getValue()); + _writer.println (margn4 + elementEnd ("value")); + } + else { + _writer.println (margn4 + element ("value", + property.getValue ().toString ())); + } + } + else if (arity.equals (PropertyArity.LIST)) { + List propList = (List) property.getValue (); + ListIterator iter = propList.listIterator(); + while (iter.hasNext ()) { + Object val = iter.next(); + if (valueIsProperty) { + showProperty ((Property) val); + } + else if (valueIsNiso) { + _writer.println (margn4 + elementStart ("value")); + showNisoImageMetadata ((NisoImageMetadata)property.getValue()); + _writer.println (margn4 + elementEnd ("value")); + } + else if (valueIsAes) { + _writer.println (margn4 + elementStart ("value")); + showAESAudioMetadata ((AESAudioMetadata)property.getValue()); + _writer.println (margn4 + elementEnd ("value")); + } + else if (valueIsTextMD) { + _writer.println (margn4 + elementStart ("value")); + showTextMDMetadata ((TextMDMetadata)property.getValue()); + _writer.println (margn4 + elementEnd ("value")); + } + else { + _writer.println (margn4 + + element ("value", valueToString (val))); + } + } + } + else if (arity.equals (PropertyArity.MAP)) { + /* For a map, the key is the "key" attribute of its + corresponding value */ + Map propMap = (Map) property.getValue (); + Iterator keyIter = propMap.keySet ().iterator (); + while (keyIter.hasNext ()) { + Object key = keyIter.next(); + String keystr = key.toString (); + Object val = propMap.get (key); + String[][] attrs = new String[1][]; + String[] keyAttr = new String[2]; + keyAttr[0] = "key"; + keyAttr[1] = keystr; + attrs[0] = keyAttr; + if (valueIsProperty) { + Property pval = (Property) val; + // If the key equals the property name, suppress the key + if (pval.getName().equals (keystr)) { + _writer.print (margn4 + elementStart ("value")); + } + else { + _writer.print (margn4 + elementStart ("value", attrs)); + } + showProperty (pval); + _writer.println (margn4 + elementEnd ("value")); + } + else if (valueIsNiso) { + _writer.println (margn4 + elementStart ("value")); + showNisoImageMetadata ((NisoImageMetadata) val); + _writer.println (margn4 + elementEnd ("value")); + } + else if (valueIsAes) { + _writer.println (margn4 + elementStart ("value")); + showAESAudioMetadata ((AESAudioMetadata) val); + _writer.println (margn4 + elementEnd ("value")); + } + else if (valueIsTextMD) { + _writer.println (margn4 + elementStart ("value")); + showTextMDMetadata ((TextMDMetadata) val); + _writer.println (margn4 + elementEnd ("value")); + } + else { + _writer.println (margn4 + element ("value", attrs, + valueToString (val))); + } + } + } + else if (arity.equals (PropertyArity.SET)) { + Set propSet = (Set) property.getValue (); + Iterator iter = propSet.iterator(); + while (iter.hasNext ()) { + Object val = iter.next(); + if (valueIsProperty) { + showProperty ((Property) val); + } + else { + _writer.println (margn4 + element ("value", + valueToString (val))); + } + } + } + else if (arity.equals (PropertyArity.ARRAY)) { + showArrayProperty (property, margn4); + } + _writer.println (margn3 + elementEnd ("values")); + _writer.println (margn2 + elementEnd ("property")); + --_level; + } + + + /** Checks if a property would produce an empty XML element, and + * returns true if it would. + */ + private boolean isPropertyEmpty (Property property, PropertyArity arity) { + try { + if (arity.equals (PropertyArity.SET)) { + Set propSet = (Set) property.getValue (); + return (propSet.isEmpty()); + } + else if (arity.equals (PropertyArity.LIST)) { + List propList = (List) property.getValue (); + return (propList.isEmpty()); + } + else if (arity.equals (PropertyArity.MAP)) { + Map propMap = (Map) property.getValue (); + return (propMap.isEmpty()); + } + else if (arity.equals (PropertyArity.ARRAY)) { + // Ack! Is there any easy way to do this? + boolean[] boolArray = null; + byte[] byteArray = null; + char[] charArray = null; + java.util.Date[] dateArray = null; + double[] doubleArray = null; + float[] floatArray = null; + int[] intArray = null; + long[] longArray = null; + Object[] objArray = null; + Property[] propArray = null; + short[] shortArray = null; + String[] stringArray = null; + Rational[] rationalArray = null; + NisoImageMetadata[] nisoArray = null; + AESAudioMetadata[] aesArray = null; + TextMDMetadata[] textMDArray = null; + int n = 0; + + PropertyType propType = property.getType(); + if (PropertyType.BOOLEAN.equals (propType)) { + boolArray = (boolean []) property.getValue (); + n = boolArray.length; + } + else if (PropertyType.BYTE.equals (propType)) { + byteArray = (byte []) property.getValue (); + n = byteArray.length; + } + else if (PropertyType.CHARACTER.equals (propType)) { + charArray = (char []) property.getValue (); + n = charArray.length; + } + else if (PropertyType.DATE.equals (propType)) { + dateArray = (java.util.Date []) property.getValue (); + n = dateArray.length; + } + else if (PropertyType.DOUBLE.equals (propType)) { + doubleArray = (double []) property.getValue (); + n = doubleArray.length; + } + else if (PropertyType.FLOAT.equals (propType)) { + floatArray = (float []) property.getValue (); + n = floatArray.length; + } + else if (PropertyType.INTEGER.equals (propType)) { + intArray = (int []) property.getValue (); + n = intArray.length; + } + else if (PropertyType.LONG.equals (propType)) { + longArray = (long []) property.getValue (); + n = longArray.length; + } + else if (PropertyType.OBJECT.equals (propType)) { + objArray = (Object []) property.getValue (); + n = objArray.length; + } + else if (PropertyType.SHORT.equals (propType)) { + shortArray = (short []) property.getValue (); + n = shortArray.length; + } + else if (PropertyType.STRING.equals (propType)) { + stringArray = (String []) property.getValue (); + n = stringArray.length; + } + else if (PropertyType.RATIONAL.equals (propType)) { + rationalArray = (Rational []) property.getValue (); + n = rationalArray.length; + } + else if (PropertyType.PROPERTY.equals (propType)) { + propArray = (Property []) property.getValue (); + n = propArray.length; + } + else if (PropertyType.NISOIMAGEMETADATA.equals (propType)) { + nisoArray = (NisoImageMetadata []) property.getValue (); + n = nisoArray.length; + } + else if (PropertyType.AESAUDIOMETADATA.equals (propType)) { + aesArray = (AESAudioMetadata []) property.getValue (); + n = aesArray.length; + } + else if (PropertyType.TEXTMDMETADATA.equals (propType)) { + textMDArray = (TextMDMetadata []) property.getValue (); + n = textMDArray.length; + } + return (n == 0); + } + else { + return property.getValue ().toString ().length() == 0; + } + } + catch (Exception e) { + // If something goes seriously wrong, return true to punt the property + return true; + } + } + + /* The array property has so many special cases of its own + that we break it out of showProperty */ + protected void showArrayProperty (Property property, String margin) + { + boolean[] boolArray = null; + byte[] byteArray = null; + char[] charArray = null; + java.util.Date[] dateArray = null; + double[] doubleArray = null; + float[] floatArray = null; + int[] intArray = null; + long[] longArray = null; + Object[] objArray = null; + Property[] propArray = null; + short[] shortArray = null; + String[] stringArray = null; + Rational[] rationalArray = null; + NisoImageMetadata[] nisoArray = null; + AESAudioMetadata[] aesArray = null; + TextMDMetadata[] textMDArray = null; + int n = 0; + + PropertyType propType = property.getType(); + if (PropertyType.BOOLEAN.equals (propType)) { + boolArray = (boolean []) property.getValue (); + n = boolArray.length; + } + else if (PropertyType.BYTE.equals (propType)) { + byteArray = (byte []) property.getValue (); + n = byteArray.length; + } + else if (PropertyType.CHARACTER.equals (propType)) { + charArray = (char []) property.getValue (); + n = charArray.length; + } + else if (PropertyType.DATE.equals (propType)) { + dateArray = (java.util.Date []) property.getValue (); + n = dateArray.length; + } + else if (PropertyType.DOUBLE.equals (propType)) { + doubleArray = (double []) property.getValue (); + n = doubleArray.length; + } + else if (PropertyType.FLOAT.equals (propType)) { + floatArray = (float []) property.getValue (); + n = floatArray.length; + } + else if (PropertyType.INTEGER.equals (propType)) { + intArray = (int []) property.getValue (); + n = intArray.length; + } + else if (PropertyType.LONG.equals (propType)) { + longArray = (long []) property.getValue (); + n = longArray.length; + } + else if (PropertyType.OBJECT.equals (propType)) { + objArray = (Object []) property.getValue (); + n = objArray.length; + } + else if (PropertyType.SHORT.equals (propType)) { + shortArray = (short []) property.getValue (); + n = shortArray.length; + } + else if (PropertyType.STRING.equals (propType)) { + stringArray = (String []) property.getValue (); + n = stringArray.length; + } + else if (PropertyType.RATIONAL.equals (propType)) { + rationalArray = (Rational []) property.getValue (); + n = rationalArray.length; + } + else if (PropertyType.PROPERTY.equals (propType)) { + propArray = (Property []) property.getValue (); + n = propArray.length; + } + else if (PropertyType.NISOIMAGEMETADATA.equals (propType)) { + nisoArray = (NisoImageMetadata []) property.getValue (); + n = nisoArray.length; + } + else if (PropertyType.AESAUDIOMETADATA.equals (propType)) { + aesArray = (AESAudioMetadata []) property.getValue (); + n = aesArray.length; + } + else if (PropertyType.TEXTMDMETADATA.equals (propType)) { + textMDArray = (TextMDMetadata []) property.getValue (); + n = textMDArray.length; + } + + for (int i = 0; i < n; i++) { + String elem; + if (PropertyType.BOOLEAN.equals (propType)) { + elem = String.valueOf (boolArray[i]); + } + else if (PropertyType.BYTE.equals (propType)) { + elem = String.valueOf (byteArray[i]); + } + else if (PropertyType.CHARACTER.equals (propType)) { + elem = String.valueOf (charArray[i]); + } + else if (PropertyType.DATE.equals (propType)) { + elem = dateArray[i].toString(); + } + else if (PropertyType.DOUBLE.equals (propType)) { + elem = String.valueOf (doubleArray[i]); + } + else if (PropertyType.FLOAT.equals (propType)) { + elem = String.valueOf (floatArray[i]); + } + else if (PropertyType.INTEGER.equals (propType)) { + elem = String.valueOf (intArray[i]); + } + else if (PropertyType.LONG.equals (propType)) { + elem = String.valueOf (longArray[i]); + } + else if (PropertyType.OBJECT.equals (propType)) { + elem = valueToString (objArray[i]); + } + else if (PropertyType.SHORT.equals (propType)) { + elem = String.valueOf (shortArray[i]); + } + else if (PropertyType.STRING.equals (propType)) { + elem = stringArray[i]; + } + else if (PropertyType.RATIONAL.equals (propType)) { + elem = rationalArray[i].toString (); + } + else if (PropertyType.PROPERTY.equals (propType)) { + showProperty (propArray[i]); + continue; + } + else if (PropertyType.NISOIMAGEMETADATA.equals (propType)) { + showNisoImageMetadata (nisoArray[i]); + continue; + } + else if (PropertyType.AESAUDIOMETADATA.equals (propType)) { + showAESAudioMetadata (aesArray[i]); + continue; + } + else if (PropertyType.TEXTMDMETADATA.equals (propType)) { + showTextMDMetadata( textMDArray[i]); + continue; + } + else elem = ""; + _writer.println (margin + element ("value", elem)); + } + } + + /** + * Display the text metadata formatted according to + * the textMD schema (see http://www.loc.gov/standards/textMD). + * @param textMD textMD text metadata + */ + protected void showTextMDMetadata (TextMDMetadata textMD) { + String margin = getIndent (++_level); + String margn2 = margin + " "; + String margn3 = margn2 + " "; + + String [][] attrs = { + {"xmlns:textmd", TextMDMetadata.NAMESPACE}, + {"xmlns:xsi", + "http://www.w3.org/2001/XMLSchema-instance"}, + {"xsi:schemaLocation", + TextMDMetadata.NAMESPACE + " " + TextMDMetadata.DEFAULT_LOCATION}, + }; + _writer.println (margin + elementStart ("textmd:textMD", attrs)); + _writer.println (margn2 + elementStart("textmd:character_info")); + _writer.println (margn3 + element("textmd:charset", textMD.getCharset())); + _writer.println (margn3 + element("textmd:byte_order", textMD.getByte_orderString())); + _writer.println (margn3 + element("textmd:byte_size", textMD.getByte_size())); + if ("variable".equals(textMD.getCharacter_size())) { + String [][] attrs1 = {{"encoding", textMD.getCharset() }}; + _writer.println (margn3 + element("textmd:character_size", attrs1, "variable")); + } else { + _writer.println (margn3 + element("textmd:character_size", textMD.getCharacter_size())); + } + _writer.println (margn3 + element("textmd:linebreak", textMD.getLinebreakString())); + _writer.println (margn2 + elementEnd("textmd:character_info")); + if (textMD.getLanguage() != null && textMD.getLanguage().length() != 0) { + _writer.println (margn2 + element("textmd:language", textMD.getLanguage())); + } + if (textMD.getMarkup_basis() != null && textMD.getMarkup_basis().length() != 0) { + if (textMD.getMarkup_basis_version() != null) { + String [][] attrs1 = {{"version", textMD.getMarkup_basis_version() }}; + _writer.println (margn2 + element("textmd:markup_basis", attrs1, textMD.getMarkup_basis())); + } else { + _writer.println (margn2 + element("textmd:markup_basis", textMD.getMarkup_basis())); + } + } + if (textMD.getMarkup_language() != null && textMD.getMarkup_language().length() != 0) { + if (textMD.getMarkup_language_version() != null) { + String [][] attrs1 = {{"version", textMD.getMarkup_language_version() }}; + _writer.println (margn2 + element("textmd:markup_language", attrs1, textMD.getMarkup_language())); + } else { + _writer.println (margn2 + element("textmd:markup_language", textMD.getMarkup_language())); + } + } + _writer.println (margin + elementEnd ("textmd:textMD")); + _level--; + + } + /** + * Display the NISO image metadata formatted according to + * the MIX schema. The schema which is used may be 0.2 or 1.0 or 2.0, + * depending on the module parameters. + * @param niso NISO image metadata + */ + protected void showNisoImageMetadata (NisoImageMetadata niso) + { + if ("0.2".equals (_je.getMixVersion())) { + showNisoImageMetadata02 (niso); + } + else if ("1.0".equals (_je.getMixVersion())) { + showNisoImageMetadata10 (niso); + } + else { + showNisoImageMetadata20 (niso); + } + } + + /** + * Display the NISO image metadata formatted according to + * the MIX 0.2 schema. + */ + protected void showNisoImageMetadata02 (NisoImageMetadata niso) + { + String margin = getIndent (++_level); + + String [][] attrs = {{"xmlns:mix", "http://www.loc.gov/mix/"}, + {"xmlns:xsi", + "http://www.w3.org/2001/XMLSchema-instance"}, + {"xsi:schemaLocation", + "http://www.loc.gov/mix/ http://www.loc.gov/mix/mix02.xsd"}}; + _writer.println (margin + elementStart ("mix:mix", attrs)); + + showNisoBasicImageParameters02 (niso, margin); + showNisoImageCreation02 (niso, margin); + showNisoImagingPerformanceAssessment02 (niso, margin); + showNisoChangeHistory02 (niso, margin); + + _writer.println (margin + elementEnd ("mix:mix")); + + _level--; + } + + /* The NISO Metadata output is split into multiple functions so that + they're merely outrageously big rather than disgustingly big */ + /* Top level element 1 of 4: BasicImageParameters */ + protected void showNisoBasicImageParameters02 (NisoImageMetadata niso, String margin) + { + String margn2 = margin + " "; + String margn3 = margn2 + " "; + String margn4 = margn3 + " "; + String margn5 = margn4 + " "; + String margn6 = margn5 + " "; + + _writer.println (margn2 + elementStart ("mix:BasicImageParameters")); + + // Here things get rather deeply nested, and we want to be able + // to back out if it turns out that we really have no content to + // deliver. So rather than writing directly to the writer, we + // write to a StringBuffer that may or may not get used. + StringBuffer fBuf = new StringBuffer + (margn3 + elementStart ("mix:Format") + EOL); + boolean useFBuf = false; // set to true if some data show up + String s = niso.getMimeType (); + if (s != null) { + fBuf.append (margn4 + element ("mix:MIMEType", s) + EOL); + useFBuf = true; + } + if ((s = niso.getByteOrder ()) != null) { + fBuf.append (margn4 + element ("mix:ByteOrder", s) + EOL); + useFBuf = true; + } + int comp = niso.getCompressionScheme (); + int level = niso.getCompressionLevel (); + if (comp != NisoImageMetadata.NULL || level != NisoImageMetadata.NULL) { + fBuf.append (margn4 + elementStart ("mix:Compression") + EOL); + if (comp != NisoImageMetadata.NULL) { + fBuf.append (margn5 + element ("mix:CompressionScheme", + Integer.toString (comp)) + EOL); + } + if (level != NisoImageMetadata.NULL) { + fBuf.append (margn5 + element ("mix:CompressionLevel", + Integer.toString (level)) + EOL); + } + fBuf.append (margn4 + elementEnd ("mix:Compression") + EOL); + useFBuf = true; + } + // Nest photometric interpretation tentative buffer in fBuf + StringBuffer piBuf = new StringBuffer + (margn4 + elementStart ("mix:PhotometricInterpretation") + EOL); + boolean usePIBuf = false; + int n = niso.getColorSpace (); + if (n != NisoImageMetadata.NULL) { + piBuf.append (margn5 + element ("mix:ColorSpace", + Integer.toString(n)) + EOL); + usePIBuf = true; + } + String s2= niso.getProfileURL (); + if ((s = niso.getProfileName ()) != null || s2 != null) { + piBuf.append (margn5 + elementStart ("mix:ICCProfile") + EOL); + if (s != null) { + piBuf.append (margn6 + element ("mix:ProfileName", s) + EOL); + } + if (s2!= null) { + piBuf.append (margn6 + element ("mix:ProfileURL", s2) + EOL); + } + piBuf.append (margn5 + elementEnd ("mix:ICCProfile")); + usePIBuf = true; + } + int [] iarray = niso.getYCbCrSubSampling (); + if (iarray != null) { + piBuf.append (margn5 + element ("mix:YCbCrSubSampling", + integerArray (iarray)) + EOL); + usePIBuf = true; + } + if ((n = niso.getYCbCrPositioning ()) != NisoImageMetadata.NULL) { + piBuf.append (margn5 + element ("mix:YCbCrPositioning", + Integer.toString (n)) + EOL); + usePIBuf = true; + } + Rational [] rarray = niso.getYCbCrCoefficients (); + if (rarray != null) { + piBuf.append (margn5 + element ("mix:YCbCrCoefficients", + rationalArray (rarray)) + EOL); + usePIBuf = true; + } + if ((rarray = niso.getReferenceBlackWhite ()) != null) { + piBuf.append (margn5 + element ("mix:ReferenceBlackWhite", + rationalArray (rarray)) + EOL); + usePIBuf = true; + } + piBuf.append (margn4 + elementEnd ("mix:PhotometricInterpretation") + EOL); + if (usePIBuf) { + fBuf.append (piBuf); + useFBuf = true; + } + + // Now a tentative buffer for the Segments element + StringBuffer segBuf = new StringBuffer + (margn4 + elementStart ("mix:Segments") + EOL); + boolean useSegBuf = false; + if ((n = niso.getSegmentType ()) != NisoImageMetadata.NULL) { + segBuf.append (margn5 + element ("mix:SegmentType", + Integer.toString (n)) + EOL); + useSegBuf = true; + } + long [] larray = niso.getStripOffsets (); + if (larray != null) { + segBuf.append (margn5 + element ("mix:StripOffsets", + longArray (larray)) + EOL); + useSegBuf = true; + } + long ln = niso.getRowsPerStrip (); + if (ln != NisoImageMetadata.NULL) { + segBuf.append (margn5 + element ("mix:RowsPerStrip", + Long.toString (ln)) + EOL); + useSegBuf = true; + } + if ((larray = niso.getStripByteCounts ()) != null) { + segBuf.append (margn5 + element ("mix:StripByteCounts", + longArray (larray)) + EOL); + useSegBuf = true; + } + if ((ln = niso.getTileWidth ()) != NisoImageMetadata.NULL) { + segBuf.append (margn5 + element ("mix:TileWidth", + Long.toString (ln)) + EOL); + useSegBuf = true; + } + if ((ln = niso.getTileLength ()) != NisoImageMetadata.NULL) { + segBuf.append (margn5 + element ("mix:TileLength", + Long.toString (ln)) + EOL); + useSegBuf = true; + } + if ((larray = niso.getTileOffsets ()) != null) { + segBuf.append (margn5 + element ("mix:TileOffsets", + longArray (larray)) + EOL); + useSegBuf = true; + } + if ((larray = niso.getTileByteCounts ()) != null) { + segBuf.append (margn5 + element ("mix:TileByteCounts", + longArray (larray)) + EOL); + useSegBuf = true; + } + segBuf.append (margn4 + elementEnd ("mix:Segments") + EOL); + if (useSegBuf) { + fBuf.append (segBuf); + useFBuf = true; + } + if ((n = niso.getPlanarConfiguration ()) != NisoImageMetadata.NULL) { + fBuf.append (margn4 + element ("mix:PlanarConfiguration", + Integer.toString (n)) + EOL); + } + fBuf.append (margn3 + elementEnd ("mix:Format") + EOL); + if (useFBuf) { + _writer.print (fBuf.toString ()); + } + + // Now a tentative buffer for the File element. It's actually + // pretty safe that this will have some content, but I'd rather + // be consistent. Besides, it's a good test of the pattern. + StringBuffer fileBuf = new StringBuffer + (margn3 + elementStart ("mix:File") + EOL); + boolean useFileBuf = false; + s = niso.getImageIdentifier (); + if (s != null) { + fileBuf.append (margn4 + element ("mix:ImageIdentifier", s) + EOL); + useFileBuf = true; + } + ln = niso.getFileSize (); + if (ln != NisoImageMetadata.NULL) { + fileBuf.append (margn4 + element ("mix:FileSize", + Long.toString (ln)) + EOL); + useFileBuf = true; + } + n = niso.getChecksumMethod (); + s = niso.getChecksumValue (); + if (n != NisoImageMetadata.NULL || s != null) { + fileBuf.append (margn4 + elementStart ("mix:Checksum") + EOL); + if (n != NisoImageMetadata.NULL) { + fileBuf.append (margn5 + element ("mix:ChecksumMethod", + Integer.toString (n)) + EOL); + } + if (s != null) { + fileBuf.append (margn5 + element ("mix:ChecksumValue", s) + EOL); + } + fileBuf.append (margn4 + elementEnd ("mix:Checksum") + EOL); + useFileBuf = true; + } + n = niso.getOrientation (); + if (n != NisoImageMetadata.NULL) { + fileBuf.append (margn4 + element ("mix:Orientation", + Integer.toString (n)) + EOL); + useFileBuf = true; + } + n = niso.getDisplayOrientation (); + if (n != NisoImageMetadata.NULL) { + fileBuf.append (margn4 + element ("mix:DisplayOrientation", + Integer.toString (n)) + EOL); + useFileBuf = true; + } + ln = niso.getXTargetedDisplayAR (); + long ln2 = niso.getYTargetedDisplayAR (); + if (ln != NisoImageMetadata.NULL || + ln2 != NisoImageMetadata.NULL) { + fileBuf.append (margn4 + elementStart + ("mix:TargetedDisplayAR") + EOL); + if (ln != NisoImageMetadata.NULL) { + fileBuf.append (margn5 + element + ("mix:XTargetedDisplayAR", Long.toString (ln)) + EOL); + } + if (ln2 != NisoImageMetadata.NULL) { + fileBuf.append (margn5 + element + ("mix:YTargetedDisplayAR", Long.toString (ln2)) + EOL); + } + fileBuf.append (margn4 + elementEnd ("mix:TargetedDisplayAR") + EOL); + useFileBuf = true; + } + + fileBuf.append (margn3 + elementEnd ("mix:File") + EOL); + if (useFileBuf) { + _writer.print (fileBuf.toString ()); + } + _writer.println (margn2 + elementEnd ("mix:BasicImageParameters")); + + } + + /* Top level element 2 of 4: ImageCreation */ + protected void showNisoImageCreation02 (NisoImageMetadata niso, String margin) + { + String margn2 = margin + " "; + String margn3 = margn2 + " "; + String margn4 = margn3 + " "; + String margn5 = margn4 + " "; + String margn6 = margn5 + " "; + + _writer.println (margn2 + elementStart ("mix:ImageCreation")); + String s = niso.getSourceType (); + if (s != null) { + _writer.println (margn3 + element ("mix:SourceType", s)); + } + s = niso.getSourceID (); + if (s != null) { + _writer.println (margn3 + element ("mix:SourceID", s)); + } + s = niso.getImageProducer (); + if (s != null) { + _writer.println (margn3 + element ("mix:ImageProducer", s)); + } + + StringBuffer hostBuf = new StringBuffer + (margn3 + elementStart ("mix:Host") + EOL); + boolean useHostBuf = false; + s = niso.getHostComputer (); + if (s != null) { + hostBuf.append (margn4 + element ("mix:HostComputer", s) + EOL); + useHostBuf = true; + } + s = niso.getOS (); + if (s != null) { + hostBuf.append (margn4 + element ("mix:OperatingSystem", s) + EOL); + useHostBuf = true; + } + s = niso.getOSVersion (); + if (s != null) { + hostBuf.append (margn4 + element ("mix:OSVersion", s) + EOL); + useHostBuf = true; + } + hostBuf.append (margn3 + elementEnd ("mix:Host") + EOL); + if (useHostBuf) { + _writer.print (hostBuf); + } + + s = niso.getDeviceSource (); + if (s != null) { + _writer.println (margn3 + element ("mix:DeviceSource", s)); + } + + // Here things get rather deeply nested, and we want to be able + // to back out if it turns out that we really have no content to + // deliver. So rather than writing directly to the writer, we + // write to a StringBuffer that may or may not get used. + StringBuffer sscBuf = new StringBuffer + (margn3 + elementStart ("mix:ScanningSystemCapture") + EOL); + boolean useSSCBuf = false; // set to true if we find interesting data. + StringBuffer sshBuf = new StringBuffer + (margn4 + elementStart ("mix:ScanningSystemHardware") + EOL); + boolean useSSHBuf = false; + s = niso.getScannerManufacturer(); + if (s != null) { + sshBuf.append (margn5 + element ("mix:ScannerManufacturer", s) + EOL); + useSSHBuf = true; + } + s = niso.getScannerModelName (); + String s1 = niso.getScannerModelNumber (); + String s2 = niso.getScannerModelSerialNo (); + if (s != null || s1 != null || s2 != null) { + sshBuf.append (margn5 + elementStart ("mix:ScannerModel") + EOL); + if (s != null) { + sshBuf.append (margn6 + element ("mix:ScannerModelName", s) + EOL); + useSSHBuf = true; + } + if (s1 != null) { + sshBuf.append (margn6 + element ("mix:ScannerModelNumber", s1) + EOL); + useSSHBuf = true; + } + if (s2 != null) { + sshBuf.append (margn6 + element ("mix:ScannerModelSerialNo ", s2) + EOL); + useSSHBuf = true; + } + sshBuf.append (margn5 + elementEnd ("mix:ScannerModel") + EOL); + } + sshBuf.append (margn4 + elementEnd ("mix:ScanningSystemHardware") + EOL); + if (useSSHBuf) { + // There's some ScanningSystemHardware content, and therefore some + // ScanningSystemCapture content; keep the element. + sscBuf.append (sshBuf); + useSSCBuf = true; + } + + // Build a tentative buffer for scanning system software + StringBuffer sssBuf = new StringBuffer + (margn4 + elementStart ("mix:ScanningSystemSoftware") + EOL); + boolean useSSSBuf = false; + s = niso.getScanningSoftware (); + if (s != null) { + sssBuf.append (margn5 + element ("mix:ScanningSoftware", s) + EOL); + useSSSBuf = true; + } + s = niso.getScanningSoftwareVersionNo (); + if (s != null) { + sssBuf.append (margn5 + element ("mix:ScanningSoftwareVersionNo", s) + EOL); + useSSSBuf = true; + } + sssBuf.append (margn4 + elementEnd ("mix:ScanningSystemSoftware") + EOL); + if (useSSSBuf) { + // There's some ScanningSystemSoftware content, and therefore some + // ScanningSystemCapture content; keep the element. + sscBuf.append (sssBuf); + useSSCBuf = true; + } + + // Build a tentative buffer for scanner capture settings + StringBuffer scsBuf = new StringBuffer + (margn4 + elementStart ("mix:ScannerCaptureSettings") + EOL); + boolean useSCSBuf = false; + double d = niso.getPixelSize (); + if (d != NisoImageMetadata.NILL) { + scsBuf.append (margn5 + element ("mix:PixelSize", + Double.toString (d)) + EOL); + useSCSBuf = true; + } + d = niso.getXPhysScanResolution (); + double d1 = niso.getYPhysScanResolution (); + if (d != NisoImageMetadata.NILL || d1 != NisoImageMetadata.NILL) { + scsBuf.append (margn5 + elementStart ("mix:PhysScanResolution") + EOL); + if (d != NisoImageMetadata.NILL) { + scsBuf.append (margn6 + element ("mix:XphysScanResolution", + Double.toString (d)) + EOL); + } + if (d1 != NisoImageMetadata.NILL) { + scsBuf.append (margn6 + element ("mix:YphysScanResolution", + Double.toString (d1)) + EOL); + } + scsBuf.append (margn5 + elementEnd ("mix:PhysScanResolution") + EOL); + useSCSBuf = true; + } + scsBuf.append (margn4 + elementEnd ("mix:ScannerCaptureSettings") + EOL); + if (useSCSBuf) { + sscBuf.append (scsBuf); + useSSCBuf = true; + } + sscBuf.append (margn3 + elementEnd ("mix:ScanningSystemCapture") + EOL); + + // Finally! Do we use any of this stuff we just went through? + if (useSSCBuf) { + _writer.print (sscBuf.toString ()); + } + + + // Same deal for digital camera capture; put the element in a tentative + // StringBuffer and then decide if it's non-trivial. + StringBuffer dccBuf = new StringBuffer + (margn3 + elementStart ("mix:DigitalCameraCapture") + EOL); + boolean useDCCBuf = false; + s = niso.getDigitalCameraManufacturer (); + if (s != null) { + dccBuf.append (margn4 + element ("mix:DigitalCameraManufacturer", s) + EOL); + useDCCBuf = true; + } + s = niso.getDigitalCameraModel (); + if (s != null) { + dccBuf.append (margn4 + element ("mix:DigitalCameraModel", s) + EOL); + useDCCBuf = true; + } + + dccBuf.append (margn3 + elementEnd ("mix:DigitalCameraCapture") + EOL); + if (useDCCBuf) { + _writer.print (dccBuf.toString ()); + } + + + // Same tentative buffer deal for camera capture settings. + StringBuffer ccsBuf = new StringBuffer + (margn3 + elementStart ("mix:CameraCaptureSettings") + EOL); + boolean useCCSBuf = false; + d = niso.getFNumber (); + if (d != NisoImageMetadata.NILL) { + ccsBuf.append (margn4 + element ("mix:FNumber", + Double.toString (d)) + EOL); + useCCSBuf = true; + } + d = niso.getExposureTime (); + if (d != NisoImageMetadata.NILL) { + ccsBuf.append (margn4 + element ("mix:ExposureTime", + Double.toString (d)) + EOL); + useCCSBuf = true; + } + d = niso.getBrightness (); + if (d != NisoImageMetadata.NILL) { + ccsBuf.append (margn4 + element ("mix:Brightness", + Double.toString (d)) + EOL); + useCCSBuf = true; + } + d = niso.getExposureBias (); + if (d != NisoImageMetadata.NILL) { + ccsBuf.append (margn4 + element ("mix:ExposureBias", + Double.toString (d)) + EOL); + useCCSBuf = true; + } + double [] darray = niso.getSubjectDistance (); + if (darray != null) { + ccsBuf.append (margn4 + element ("mix:SubjectDistance", + doubleArray (darray)) + EOL); + useCCSBuf = true; + } + int n = niso.getMeteringMode (); + if (n != NisoImageMetadata.NULL) { + ccsBuf.append (margn4 + element ("mix:MeteringMode", + Integer.toString (n)) + EOL); + useCCSBuf = true; + } + n = niso.getSceneIlluminant (); + if (n != NisoImageMetadata.NULL) { + ccsBuf.append (margn4 + element ("mix:SceneIlluminant", + Integer.toString (n)) + EOL); + useCCSBuf = true; + } + d = niso.getColorTemp (); + if (d != NisoImageMetadata.NILL) { + ccsBuf.append (margn4 + element ("mix:ColorTemp", + Double.toString (d)) + EOL); + useCCSBuf = true; + } + d = niso.getFocalLength (); + if (d != NisoImageMetadata.NILL) { + ccsBuf.append (margn4 + element ("mix:FocalLength", + Double.toString (d)) + EOL); + useCCSBuf = true; + } + n = niso.getFlash (); + if (n != NisoImageMetadata.NULL) { + ccsBuf.append (margn4 + element ("mix:Flash", Integer.toString (n)) + EOL); + useCCSBuf = true; + } + d = niso.getFlashEnergy (); + if (d != NisoImageMetadata.NILL) { + ccsBuf.append (margn4 + element ("mix:FlashEnergy", + Double.toString (d)) + EOL); + useCCSBuf = true; + } + n = niso.getFlashReturn (); + if (n != NisoImageMetadata.NULL) { + ccsBuf.append (margn4 + element ("mix:FlashReturn", + Integer.toString (n)) + EOL); + useCCSBuf = true; + } + n = niso.getBackLight (); + if (n != NisoImageMetadata.NULL) { + ccsBuf.append (margn4 + element ("mix:BackLight", + Integer.toString (n)) + EOL); + useCCSBuf = true; + } + d = niso.getExposureIndex (); + if (d != NisoImageMetadata.NILL) { + ccsBuf.append (margn4 + element ("mix:ExposureIndex", + Double.toString (d)) + EOL); + useCCSBuf = true; + } + n = niso.getAutoFocus (); + if (n != NisoImageMetadata.NULL) { + ccsBuf.append (margn4 + element ("mix:AutoFocus", + Integer.toString (n)) + EOL); + useCCSBuf = true; + } + d = niso.getXPrintAspectRatio (); + d1 = niso.getYPrintAspectRatio (); + if (d != NisoImageMetadata.NILL || d1 != NisoImageMetadata.NILL) { + ccsBuf.append (margn4 + elementStart ("mix:PrintAspectRatio") + EOL); + if (d != NisoImageMetadata.NILL) { + ccsBuf.append (margn5 + element ("mix:XPrintAspectRatio", + Double.toString (d)) + EOL); + } + if (d1 != NisoImageMetadata.NILL) { + ccsBuf.append (margn5 + element ("mix:YPrintAspectRatio", + Double.toString (d1)) + EOL); + ccsBuf.append (margn4 + elementEnd ("mix:PrintAspectRatio") + EOL); + useCCSBuf = true; + } + } + ccsBuf.append (margn3 + elementEnd ("mix:CameraCaptureSettings") + EOL); + if (useCCSBuf) { + _writer.print (ccsBuf.toString ()); + } + + // Finally we get a relative breather without having to check nested + // elements. + n = niso.getSensor (); + if (n != NisoImageMetadata.NULL) { + _writer.println (margn3 + element ("mix:Sensor", + Integer.toString (n))); + } + s = niso.getDateTimeCreated (); + if (s != null) { + _writer.println (margn3 + element ("mix:DateTimeCreated", s)); + } + s = niso.getMethodology (); + if (s != null) { + _writer.println (margn3 + element ("mix:Methodology", s)); + } + + _writer.println (margn2 + elementEnd ("mix:ImageCreation")); + } + + /* Top level element 3 of 4: ImagingPerformanceAssessment */ + protected void showNisoImagingPerformanceAssessment02 (NisoImageMetadata niso, + String margin) + { + String margn2 = margin + " "; + String margn3 = margn2 + " "; + String margn4 = margn3 + " "; + String margn5 = margn4 + " "; + String margn6 = margn5 + " "; + String margn7 = margn6 + " "; + + StringBuffer ipaBuf = new StringBuffer + (margn3 + elementStart ("mix:ImagingPerformanceAssessment") + EOL); + boolean useIPABuf = false; // set to true if we find interesting data. + StringBuffer smBuf = new StringBuffer + (margn4 + elementStart ("mix:SpatialMetrics") + EOL); + boolean useSMBuf = false; + + int n = niso.getSamplingFrequencyPlane (); + if (n != NisoImageMetadata.NULL) { + smBuf.append (margn5 + element ("mix:SamplingFrequencyPlane", + Integer.toString (n)) + EOL); + useSMBuf = true; + } + n = niso.getSamplingFrequencyUnit (); + if (n != NisoImageMetadata.NULL) { + smBuf.append (margn5 + element ("mix:SamplingFrequencyUnit", + Integer.toString (n)) + EOL); + useSMBuf = true; + } + Rational r = niso.getXSamplingFrequency (); + if (r != null) { + smBuf.append (margn5 + element ("mix:XSamplingFrequency", + Long.toString (r.toLong())) + EOL); + useSMBuf = true; + } + r = niso.getYSamplingFrequency (); + if (r != null) { + smBuf.append (margn5 + element ("mix:YSamplingFrequency", + Long.toString (r.toLong())) + EOL); + useSMBuf = true; + } + long ln = niso.getImageWidth (); + if (ln != NisoImageMetadata.NULL) { + smBuf.append (margn5 + element ("mix:ImageWidth", + Long.toString (ln)) + EOL); + useSMBuf = true; + } + ln = niso.getImageLength (); + if (ln != NisoImageMetadata.NULL) { + smBuf.append (margn5 + element ("mix:ImageLength", + Long.toString (ln)) + EOL); + useSMBuf = true; + } + + double d = niso.getSourceXDimension (); + n = niso.getSourceXDimensionUnit (); + if (d != NisoImageMetadata.NILL || n != NisoImageMetadata.NULL) { + smBuf.append (margn5 + elementStart ("mix:Source_X") + EOL); + if (d != NisoImageMetadata.NILL) { + smBuf.append (margn6 + element ("mix:Source_Xdimension", + Double.toString (d)) + EOL); + } + if (n != NisoImageMetadata.NULL) { + smBuf.append (margn6 + element ("mix:Source_XdimensionUnit", + Integer.toString (n)) + EOL); + } + smBuf.append (margn5 + elementEnd ("mix:Source_X") + EOL); + useSMBuf = true; + } + + d = niso.getSourceYDimension (); + n = niso.getSourceYDimensionUnit (); + if (d != NisoImageMetadata.NILL || n != NisoImageMetadata.NULL) { + smBuf.append (margn4 + elementStart ("mix:Source_Y") + EOL); + if (d != NisoImageMetadata.NILL) { + smBuf.append (margn5 + element ("mix:Source_Ydimension", + Double.toString (d)) + EOL); + } + if (n != NisoImageMetadata.NULL) { + smBuf.append (margn5 + element ("mix:Source_YdimensionUnit", + Integer.toString (n)) + EOL); + } + smBuf.append (margn4 + elementEnd ("mix:Source_Y") + EOL); + useSMBuf = true; + } + smBuf.append (margn3 + elementEnd ("mix:SpatialMetrics") + EOL); + if (useSMBuf) { + ipaBuf.append (smBuf); + useIPABuf = true; + } + + // Now a tentative buffer for the Energetics element + StringBuffer eBuf = new StringBuffer + (margn3 + elementStart ("mix:Energetics") + EOL); + boolean useEBuf = false; + int [] iarray = niso.getBitsPerSample (); + if (iarray != null) { + eBuf.append (margn4 + element ("mix:BitsPerSample", + integerArray (iarray, ',')) + EOL); + useEBuf = true; + } + n = niso.getSamplesPerPixel (); + if (n != NisoImageMetadata.NULL) { + eBuf.append (margn4 + element ("mix:SamplesPerPixel", + Integer.toString (n)) + EOL); + useEBuf = true; + } + iarray = niso.getExtraSamples (); + if (iarray != null) { + // extraSamples can only be an integer, so the best we can do is + // snag the first value from the array. It also must be limited to + // 0, 1, 2, or 3. + n = iarray[0]; + if (n >= 0 && n <= 3) { + eBuf.append (margn4 + + element ("mix:ExtraSamples", Integer.toString (n)) + EOL); + useEBuf = true; + } + // This is what we'd really like to do, but it violates the schema. + // Keep this code around in the event the schema is fixed in the future. +// eBuf.append (margn4 + element ("mix:ExtraSamples", +// integerArray (iarray)) + EOL); +// useEBuf = true; + } + // Tentative buffer for colormap element within Energetics + StringBuffer cmBuf = new StringBuffer + (margn4 + elementStart ("mix:Colormap") + EOL); + boolean useCMBuf = false; + String s = niso.getColormapReference (); + if (s != null) { + cmBuf.append (margn5 + element ("mix:Reference", s) + EOL); + useCMBuf = true; + } + iarray = niso.getColormapRedValue (); + if (iarray != null) { + cmBuf.append (margn5 + elementStart ("mix:Wrap")); + // If the red array is there, assume the others are too, and are + // equal in length. + int[] bcarray = niso.getColormapBitCodeValue (); + int[] garray = niso.getColormapGreenValue (); + int[] barray = niso.getColormapBlueValue (); + try { + for (int i = 0; i < iarray.length; i++) { + cmBuf.append (margn6 + elementStart ("mix:Color") + EOL); + n = bcarray[i]; + if (n != NisoImageMetadata.NULL) { + cmBuf.append (margn7 + element ("mix:BitCodeValue", + Integer.toString (n)) + EOL); + } + n = iarray[i]; + if (n != NisoImageMetadata.NULL) { + cmBuf.append (margn7 + element ("mix:RedValue", + Integer.toString (n)) + EOL); + } + n = garray[i]; + if (n != NisoImageMetadata.NULL) { + cmBuf.append (margn7 + element ("mix:GreenValue", + Integer.toString (n)) + EOL); + } + n = barray[i]; + if (n != NisoImageMetadata.NULL) { + cmBuf.append (margn7 + element ("mix:BlueValue", + Integer.toString (n)) + EOL); + } + + cmBuf.append (margn6 + elementEnd ("mix:Color") + EOL); + } + } + catch (Exception e) { + // If the assumption mentioned above is wrong, + // we'll get broken XML, but at least won't die here. + } + cmBuf.append (margn5 + elementEnd ("mix:Wrap") + EOL); + useCMBuf = true; + } + cmBuf.append (margn4 + elementEnd ("mix:Colormap")); + if (useCMBuf) { + eBuf.append (cmBuf); + useEBuf = true; + } + + iarray = niso.getGrayResponseCurve (); + n = niso.getGrayResponseUnit (); + if (iarray != null || n != NisoImageMetadata.NULL) { + eBuf.append (margn4 + elementStart("mix:GrayResponse") + EOL); + if (iarray != null) { + eBuf.append (margn5 + element ("mix:GrayResponseCurve", + integerArray (iarray)) + EOL); + } + if (n != NisoImageMetadata.NULL) { + eBuf.append (margn5 + element ("mix:GrayResponseUnit", + Integer.toString (n)) + EOL); + } + eBuf.append (margn4 + elementEnd("mix:GrayResponse") + EOL); + useEBuf = true; + } + r = niso.getWhitePointXValue (); + Rational r1 = niso.getWhitePointYValue (); + if (r != null || r1 != null) { + // These are specified in the scheme as CIExyType. All the + // schema tells us about this type is that it's a string. + eBuf.append (margn4 + elementStart("mix:WhitePoint") + EOL); + if (r != null) { + eBuf.append (margn5 + element ("mix:WhitePoint_Xvalue", + r.toString ()) + EOL); + } + if (r1 != null) { + eBuf.append (margn5 + element ("mix:WhitePoint_Yvalue", + r1.toString ()) + EOL); + } + eBuf.append (margn4 + elementEnd("mix:WhitePoint") + EOL); + useEBuf = true; + } + + r = niso.getPrimaryChromaticitiesRedX (); + // For simplicity, we check only the red x in deciding whether + // to incorporate this element. A partial set of chromaticities + // would be meaningless anyway. + if (r != null) { + eBuf.append (margn4 + elementStart("mix:PrimaryChromaticities") + EOL); + eBuf.append (margn5 + element ("mix:PrimaryChromaticities_RedX", + r.toString ()) + EOL); + r = niso.getPrimaryChromaticitiesRedY (); + if (r != null) { + eBuf.append (margn5 + element ("mix:PrimaryChromaticities_RedY", + r.toString ()) + EOL); + } + r = niso.getPrimaryChromaticitiesGreenX (); + if (r != null) { + eBuf.append (margn5 + element ("mix:PrimaryChromaticities_GreenX", + r.toString ()) + EOL); + } + r = niso.getPrimaryChromaticitiesGreenY (); + if (r != null) { + eBuf.append (margn5 + element ("mix:PrimaryChromaticities_GreenY", + r.toString ()) + EOL); + } + r = niso.getPrimaryChromaticitiesBlueX (); + if (r != null) { + eBuf.append (margn5 + element ("mix:PrimaryChromaticities_BlueX", + r.toString ()) + EOL); + } + r = niso.getPrimaryChromaticitiesBlueY (); + if (r != null) { + eBuf.append (margn5 + element ("mix:PrimaryChromaticities_BlueY", + r.toString ()) + EOL); + } + eBuf.append (margn4 + elementEnd("mix:PrimaryChromaticities") + EOL); + useEBuf = true; + } + + eBuf.append (margn3 + elementEnd ("mix:Energetics") + EOL); + if (useEBuf) { + ipaBuf.append (eBuf); + useIPABuf = true; + } + + + // Another tentative buffer for TargetData + StringBuffer tdBuf = new StringBuffer + (margn3 + elementStart ("mix:TargetData") + EOL); + boolean useTDBuf = false; + n = niso.getTargetType (); + if (n != NisoImageMetadata.NULL) { + tdBuf.append (margn4 + element ("mix:TargetType", + Integer.toString (n)) + EOL); + useTDBuf = true; + } + + // Nest a TargetID tentative buffer in the TargetData buffer + StringBuffer tiBuf = new StringBuffer + (margn4 + elementStart ("mix:TargetID") + EOL); + boolean useTIBuf = false; + s = niso.getTargetIDManufacturer (); + if (s != null) { + tiBuf.append (margn5 + element ("mix:TargetIDManufacturer", s) + EOL); + useTIBuf = true; + } + s = niso.getTargetIDName (); + if (s != null) { + tiBuf.append (margn5 + element ("mix:TargetIDName", s) + EOL); + useTIBuf = true; + } + s = niso.getTargetIDNo (); + if (s != null) { + tiBuf.append (margn5 + element ("mix:TargetIDNo", s) + EOL); + useTIBuf = true; + } + s = niso.getTargetIDMedia (); + if (s != null) { + tiBuf.append (margn5 + element ("mix:TargetIDMedia", s) + EOL); + useTIBuf = true; + } + tiBuf.append (margn4 + elementEnd ("mix:TargetID") + EOL); + if (useTIBuf) { + tdBuf.append (tiBuf); + useTDBuf = true; + } + + s = niso.getImageData (); + if (s != null) { + tdBuf.append (margn5 + element ("mix:ImageData", s) + EOL); + useTDBuf = true; + } + s = niso.getPerformanceData (); + if (s != null) { + tdBuf.append (margn5 + element ("mix:PerformanceData", s) + EOL); + useTDBuf = true; + } + s = niso.getProfiles (); + if (s != null) { + tdBuf.append (margn5 + element ("mix:Profiles", s) + EOL); + useTDBuf = true; + } + tdBuf.append (margn3 + elementEnd ("mix:TargetData") + EOL); + if (useTDBuf) { + ipaBuf.append (tdBuf); + useIPABuf = true; + } + + ipaBuf.append (margn2 + elementEnd ("mix:ImagingPerformanceAssessment") + EOL); + if (useIPABuf) { + _writer.print (ipaBuf.toString ()); + } + } + + /* Top level element 4 of 4: ChangeHistory */ + protected void showNisoChangeHistory02 (NisoImageMetadata niso, + String margin) + { + String margn2 = margin + " "; + String margn3 = margn2 + " "; + String margn4 = margn3 + " "; + String margn5 = margn4 + " "; + String margn6 = margn5 + " "; + String margn7 = margn6 + " "; + + // Yet again, build elements in tentative buffers and throw them + // away if they prove trivial. + StringBuffer chBuf = new StringBuffer + (margn2 + elementStart ("mix:ChangeHistory") + EOL); + boolean useCHBuf = false; + StringBuffer ipBuf = new StringBuffer + (margn3 + elementStart ("mix:ImageProcessing") + EOL); + boolean useIPBuf = false; + + String s = niso.getDateTimeProcessed (); + if (s != null) { + ipBuf.append (margn4 + element ("DateTimeProcessed", s) + EOL); + useIPBuf = true; + } + s = niso.getSourceData (); + if (s != null) { + ipBuf.append (margn4 + element ("SourceData", s) + EOL); + useIPBuf = true; + } + s = niso.getProcessingAgency (); + if (s != null) { + ipBuf.append (margn4 + element ("ProcessingAgency", s) + EOL); + useIPBuf = true; + } + + // Third-level nesting of tentative buffer! + StringBuffer psBuf = new StringBuffer + (margn4 + elementStart ("ProcessingSoftware") + EOL); + boolean usePSBuf = false; + s = niso.getProcessingSoftwareName (); + if (s != null) { + psBuf.append (margn5 + element + ("ProcessingSoftwareName", s) + EOL); + usePSBuf = true; + } + s = niso.getProcessingSoftwareVersion (); + if (s != null) { + psBuf.append (margn5 + element + ("ProcessingSoftwareVersion", s) + EOL); + usePSBuf = true; + } + psBuf.append (margn4 + elementEnd ("ProcessingSoftware") + EOL); + if (usePSBuf) { + ipBuf.append (psBuf); + useIPBuf = true; + } + + // Hard to say, but I think the intent is that there be + // one ProcessingActions element per processing action. + String [] sarray = niso.getProcessingActions (); + if (sarray != null) { + for (int i = 0; i < sarray.length; i++) { + ipBuf.append (margn4 + element + ("ProcessingActions", sarray[i]) + EOL); + } + useIPBuf = true; + } + ipBuf.append (margn3 + elementEnd ("mix:ImageProcessing") + EOL); + if (useIPBuf) { + chBuf.append (ipBuf); + useCHBuf = true; + } + chBuf.append (margn2 + elementEnd ("mix:ChangeHistory") + EOL); + if (useCHBuf) { + _writer.print (chBuf.toString ()); + } + } + + /** + * Display the NISO image metadata formatted according to + * the MIX 1.0 schema. + */ + protected void showNisoImageMetadata10 (NisoImageMetadata niso) + { + String margin = getIndent (++_level); + + String [][] attrs = {{"xmlns:mix", "http://www.loc.gov/mix/v10"}, + {"xmlns:xsi", + "http://www.w3.org/2001/XMLSchema-instance"}, + {"xsi:schemaLocation", + "http://www.loc.gov/mix/v10 http://www.loc.gov/standards/mix/mix10/mix10.xsd"}}; + _writer.println (margin + elementStart ("mix:mix", attrs)); + + showNisoBasicDigitalObjectInformation10 (niso, margin); + showNisoBasicImageInformation10 (niso, margin); + showNisoImageCaptureMetadata10 (niso, margin); + showNisoImageAssessmentMetadata10 (niso, margin); + showChangeHistory10 (niso, margin); + + _writer.println (margin + elementEnd ("mix:mix")); + + _level--; + + } + + + /* The NISO Metadata output (1.0 now) is split into multiple functions so that + they're merely outrageously big rather than disgustingly big */ + /* Top level element 1 of 5: BasicDigitalObjectInformation */ + protected void showNisoBasicDigitalObjectInformation10 (NisoImageMetadata niso, String margin) + { + String margn2 = margin + " "; + String margn3 = margn2 + " "; + String margn4 = margn3 + " "; + String margn5 = margn4 + " "; + + _writer.println (margn2 + elementStart ("mix:BasicDigitalObjectInformation")); + + StringBuffer objIDBuf = new StringBuffer + (margn3 + elementStart ("mix:ObjectIdentifier") + EOL); + boolean useObjIDBuf = false; + objIDBuf.append (margn4 + element ("mix:objectIdentifierType", "JHOVE") + EOL); + String s = niso.getImageIdentifier (); + if (s != null) { + objIDBuf.append (margn4 + element ("mix:objectIdentifierValue", s) + EOL); + useObjIDBuf = true; + } + objIDBuf.append (margn3 + elementEnd ("mix:ObjectIdentifier") + EOL); + if (useObjIDBuf) { + _writer.print (objIDBuf.toString ()); + } + long ln = niso.getFileSize(); + if (ln != NisoImageMetadata.NULL) { + _writer.print (margn4 + element ("mix:fileSize", + Long.toString (ln)) + EOL); + } + + // TODO we really should output a FormatDesignation, but it isn't in the NisoImageMetadata class yet. + // TODO If we output a FormatDesignation, we should output a FormatRegistry. + + if ((s = niso.getByteOrder ()) != null) { + // Convert strings to MIX 1.0 form + if (s.startsWith ("big")) { + s = "big_endian"; + } + else if (s.startsWith ("little")) { + s = "little_endian"; + } + _writer.print (margn4 + element ("mix:byteOrder", s) + EOL); + } + int comp = niso.getCompressionScheme (); + int level = niso.getCompressionLevel (); + if (comp != NisoImageMetadata.NULL || level != NisoImageMetadata.NULL) { + _writer.print (margn4 + elementStart ("mix:Compression") + EOL); + if (comp != NisoImageMetadata.NULL) { + _writer.print (margn5 + element ("mix:compressionScheme", + Integer.toString (comp)) + EOL); + } + // TODO it isn't clear how to get from compression level to compression ratio + + _writer.print (margn4 + elementEnd ("mix:Compression") + EOL); + } + int n = niso.getChecksumMethod (); + s = niso.getChecksumValue (); + if (n != NisoImageMetadata.NULL || s != null) { + _writer.print (margn4 + elementStart ("mix:Fixity") + EOL); + if (n != NisoImageMetadata.NULL) { + _writer.print (margn5 + element ("mix:messageDigestAlgorithm", + Integer.toString (n)) + EOL); + } + if (s != null) { + _writer.print (margn5 + element ("mix:messageDigest", s) + EOL); + } + _writer.println (margn4 + elementEnd ("mix:Fixity")); + } + _writer.println (margn3 + elementEnd ("mix:BasicDigitalObjectInformation")); + } + + /* 1.0, Top level element 2 of 5: BasicImageInformation */ + protected void showNisoBasicImageInformation10 (NisoImageMetadata niso, String margin) + { + String margn2 = margin + " "; + String margn3 = margn2 + " "; + String margn4 = margn3 + " "; + String margn5 = margn4 + " "; + String margn6 = margn5 + " "; + String margn7 = margn6 + " "; + _writer.println (margn2 + elementStart ("mix:BasicImageInformation")); + StringBuffer basCharBuf = new StringBuffer + (margn3 + elementStart ("mix:BasicImageCharacteristics") + EOL); + boolean useBasCharBuf = false; + long ln = niso.getImageWidth (); + if (ln != NisoImageMetadata.NULL) { + basCharBuf.append (margn4 + element ("mix:imageWidth", + Long.toString (ln)) + EOL); + useBasCharBuf = true; + } + ln = niso.getImageLength (); + if (ln != NisoImageMetadata.NULL) { + basCharBuf.append (margn4 + element ("mix:imageHeight", + Long.toString (ln)) + EOL); + useBasCharBuf = true; + } + // Nest photometric interpretation tentative buffer in basCharBuf + StringBuffer piBuf = new StringBuffer + (margn4 + elementStart ("mix:PhotometricInterpretation") + EOL); + boolean usePIBuf = false; + int n = niso.getColorSpace (); + if (n != NisoImageMetadata.NULL) { + piBuf.append (margn5 + element ("mix:colorSpace", + Integer.toString(n)) + EOL); + usePIBuf = true; + } + String s = niso.getProfileName (); + String s2= niso.getProfileURL (); + if (s != null || s2 != null) { + piBuf.append (margn5 + elementStart ("mix:ColorProfile") + EOL); + piBuf.append (margn6 + elementStart ("mix:IccProfile") + EOL); + if (s != null) { + piBuf.append (margn7 + element ("mix:iccProfileName", s) +EOL); + } + if (s2!= null) { + piBuf.append (margn7 + element ("mix:iccProfileURL", s2) +EOL); + } + piBuf.append (margn6 + elementEnd ("mix:IccProfile") + EOL); + piBuf.append (margn5 + elementEnd ("mix:ColorProfile") + EOL); + usePIBuf = true; + } + int [] iarray = niso.getYCbCrSubSampling (); + n = niso.getYCbCrPositioning (); + Rational [] rarray = niso.getYCbCrCoefficients (); + if (iarray != null || n != NisoImageMetadata.NULL || rarray != null) { + piBuf.append (margn5 + elementStart ("mix:YCbCr") + EOL); + usePIBuf = true; + if (iarray != null && iarray.length >= 2) { + piBuf.append (margn6 + elementStart ("mix:YCbCrSubSampling") + + EOL); + piBuf.append (margn7 + element ("mix:yCbCrSubsampleHoriz", + Integer.toString (iarray[0])) + + EOL); + piBuf.append (margn7 + element ("mix:yCbCrSubsampleVert", + Integer.toString (iarray[1])) + + EOL); + piBuf.append (margn6 + elementEnd ("mix:YCbCrSubSampling") + + EOL); + } + if (n != NisoImageMetadata.NULL) { + piBuf.append (margn6 + element ("mix:yCbCrPositioning", + Integer.toString (n)) + EOL); + } + if (rarray != null) { + piBuf.append (margn6 + element ("mix:yCbCrCoefficients", + rationalArray10 (rarray)) + + EOL); + } + piBuf.append (margn5 + elementEnd ("mix:YCbCr") + EOL); + } + rarray = niso.getReferenceBlackWhite (); + if (rarray != null) { + piBuf.append (margn6 + element ("mix:referenceBlackWhite", + rationalArray10 (rarray)) + EOL); + usePIBuf = true; + } + piBuf.append (margn4 + elementEnd ("mix:PhotometricInterpretation") + + EOL); + if (usePIBuf) { + basCharBuf.append (piBuf); + useBasCharBuf = true; + } + basCharBuf.append (margn3 + + elementEnd ("mix:BasicImageCharacteristics")); + + if (useBasCharBuf) { + _writer.println (basCharBuf); + } + _writer.println (margn2 + elementEnd ("mix:BasicImageInformation")); + // TODO SpecialFormatCharacteristics would be nice to have here, + // but that's future expansion + } + + /* 1.0, Top level element 3 of 5: ImageCaptureMetadata */ + protected void showNisoImageCaptureMetadata10 (NisoImageMetadata niso, + String margin) + { + String margn2 = margin + " "; + String margn3 = margn2 + " "; + String margn4 = margn3 + " "; + String margn5 = margn4 + " "; + String margn6 = margn5 + " "; + String margn7 = margn6 + " "; + + StringBuffer captureBuffer = new StringBuffer (); + boolean useCaptureBuffer = false; + + String s = niso.getSourceType (); + if (s != null) { + captureBuffer.append (margn3 + element ("mix:sourceType", s)); + useCaptureBuffer = true; + } + s = niso.getSourceID (); + if (s != null) { + captureBuffer.append (margn3 + elementStart ("mix:SourceID")); + captureBuffer.append (margn3 + element ("mix:sourceIDValue", s)); + captureBuffer.append (margn3 + elementEnd ("mix:sourceID")); + useCaptureBuffer = true; + } + double d = niso.getSourceXDimension (); + int n = niso.getSourceXDimensionUnit (); + if (d != NisoImageMetadata.NILL || n != NisoImageMetadata.NULL) { + // Assume that both X and Y exist, or neither + captureBuffer.append (margn3 + elementStart ("mix:SourceSize")); + captureBuffer.append (margn4 + + elementStart ("mix:SourceXDimension") + EOL); + if (d != NisoImageMetadata.NILL) { + captureBuffer.append (margn5 + + element ("mix:sourceXDimensionValue", + Double.toString (d)) + EOL); + } + if (n != NisoImageMetadata.NULL) { + captureBuffer.append (margn5 + + element ("mix:sourceXDimensionUnit", + Integer.toString (n)) + EOL); + } + captureBuffer.append (margn4 + elementEnd ("mix:SourceXDimension")+ + EOL); + + d = niso.getSourceYDimension (); + n = niso.getSourceYDimensionUnit (); + if (d != NisoImageMetadata.NILL || n != NisoImageMetadata.NULL) { + captureBuffer.append (margn4 + + elementStart ("mix:SourceYDimension") + + EOL); + if (d != NisoImageMetadata.NILL) { + captureBuffer.append (margn5 + + element ("mix:sourceYDimensionValue", + Double.toString (d)) + EOL); + } + if (n != NisoImageMetadata.NULL) { + captureBuffer.append (margn5 + + element ("mix:sourceYDimensionUnit", + Integer.toString (n))+ EOL); + } + captureBuffer.append (margn4 + + elementEnd ("mix:SourceYDimension") + + EOL); + } + captureBuffer.append (margn3 + elementEnd ("mix:SourceSize") + + EOL); + useCaptureBuffer = true; + } + StringBuffer genCapBuf = + new StringBuffer (margn3 + + elementStart ("mix:GeneralCaptureInformation") + + EOL); + boolean useGenCapBuf = false; + + s = niso.getDateTimeCreated (); + if (s != null) { + genCapBuf.append (margn3 + element ("mix:dateTimeCreated", s) + + EOL); + useGenCapBuf = true; + } + s = niso.getImageProducer (); + if (s != null) { + genCapBuf.append (margn3 + element ("mix:imageProducer", s) + EOL); + useGenCapBuf = true; + } + + s = niso.getDeviceSource (); + if (s != null) { + genCapBuf.append (margn3 + element ("mix:captureDevice", s) + + EOL); + /* This has a restricted set of values. Does the setting code + * conform? + */ + } + + genCapBuf.append (margn3 + + elementEnd ("mix:GeneralCaptureInformation") + EOL); + if (useGenCapBuf) { + captureBuffer.append (genCapBuf); + useCaptureBuffer = true; + } + + // Here's a chunk of XML for scanners. + StringBuffer scanCapBuf = + new StringBuffer (margn3 + elementStart ("mix:ScannerCapture") + + EOL); + boolean useScanCapBuf = false; + String mfg = niso.getScannerManufacturer (); + if (mfg != null) { + scanCapBuf.append (margn4 + element ("mix:scannerManufacturer", + mfg) + EOL); + useScanCapBuf = true; + } + String model = niso.getScannerModelName (); + String modelNum = niso.getScannerModelNumber(); + String serNum = niso.getScannerModelSerialNo(); + if (model != null || modelNum != null || serNum != null) { + useScanCapBuf = true; + scanCapBuf.append (margn4 + elementStart ("mix:ScannerModel") + + EOL); + if (model != null) { + scanCapBuf.append (margn5 + element ("mix:scannerModelName", model) + + EOL); + } + if (modelNum != null) { + scanCapBuf.append (margn5 + element ("mix:scannerModelNumber", modelNum)+ + EOL); + } + if (serNum != null) { + scanCapBuf.append (margn5 + + element ("mix:scannerModelSerialNo", serNum) + EOL); + } + scanCapBuf.append (margn4 + elementEnd ("mix:ScannerModel") + EOL); + } + double xres = niso.getXPhysScanResolution(); + double yres = niso.getYPhysScanResolution(); + if (xres != NisoImageMetadata.NULL && yres != NisoImageMetadata.NULL) { + double res = (xres > yres ? xres : yres); + scanCapBuf.append (margn4 + element + ("mix:maximumOpticalResolution", Double.toString (res)) + + EOL); + } + s = niso.getScanningSoftware(); + if (s != null) { + useScanCapBuf = true; + scanCapBuf.append (margn4 + + elementStart ("mix:ScanningSystemSoftware") + + EOL); + scanCapBuf.append (margn5 + element ("mix:scanningSoftwareName", + s) + EOL); + s = niso.getScanningSoftwareVersionNo(); + if (s != null) { + scanCapBuf.append (margn5 + + element ("mix:scanningSoftwareVersionNo", + s) + EOL); + } + scanCapBuf.append (margn4 + + elementEnd ("mix:ScanningSystemSoftware") + + EOL); + } + scanCapBuf.append (margn3 + elementEnd ("mix:ScannerCapture") + EOL); + if (useScanCapBuf) { + captureBuffer.append (scanCapBuf); + useCaptureBuffer = true; + } + + // Now we'll hear from the digital cameras. + StringBuffer digCamBuf = + new StringBuffer (margn3 + + elementStart ("mix:DigitalCameraCapture") + EOL); + boolean useDigCamBuf = false; + + s = niso.getDigitalCameraManufacturer(); + if (s != null) { + digCamBuf.append (margn4 + + element ("mix:digitalCameraManufacturer") + EOL); + useDigCamBuf = true; + } + s = niso.getDigitalCameraModel(); + if (s != null) { + digCamBuf.append (margn4 + elementStart ("mix:DigitalCameraModel")+ + EOL); + digCamBuf.append (margn5 + element ("mix:digitalCameraModelName") + s + + EOL); + digCamBuf.append (margn4 + elementEnd ("mix:DigitalCameraModel") + + EOL); + useDigCamBuf = true; + } + + // Nest a buffer for CameraCaptureSettings + StringBuffer ccSetBuf = + new StringBuffer (margn4 + + elementStart ("mix:CameraCaptureSettings") + + EOL); + boolean useCcSetBuf = false; + // CameraCaptureSettings consists only of an ImageData element, so we + // don't need another use flag here. + ccSetBuf.append (margn5 + elementStart ("mix:ImageData") + EOL); + d = niso.getFNumber (); + if (d != NisoImageMetadata.NULL) { + ccSetBuf.append (margn6 + element ("mix:fNumber", + Double.toString (d)) + EOL); + useCcSetBuf = true; + } + d = niso.getExposureTime(); + if (d != NisoImageMetadata.NULL) { + ccSetBuf.append (margn6 + element ("mix:exposureTime", + Double.toString (d)) + EOL); + useCcSetBuf = true; + } + d = niso.getBrightness(); + if (d != NisoImageMetadata.NULL) { + ccSetBuf.append (margn6 + element ("mix:brightnessValue", + Double.toString (d)) + EOL); + useCcSetBuf = true; + } + d = niso.getExposureBias(); + if (d != NisoImageMetadata.NULL) { + ccSetBuf.append (margn6 + element ("mix:exposureBiasValue", + Double.toString (d)) + EOL); + useCcSetBuf = true; + } + double[] darray = niso.getSubjectDistance (); + if (darray != null) { + // For the old schema, we dumped out the whole array, but the 1.0 + // schema clearly says a non-negative number is expected. + // So just use darray[0]. + ccSetBuf.append (margn6 + element ("mix:subjectDistance", + Double.toString (darray[0])) + + EOL); + useCcSetBuf = true; + } + n = niso.getMeteringMode (); + if (n != NisoImageMetadata.NULL) { + ccSetBuf.append (margn6 + element ("mix:meteringMode", + Integer.toString (n)) + EOL); + useCcSetBuf = true; + } + n = niso.getFlash (); + if (n != NisoImageMetadata.NULL) { + ccSetBuf.append (margn6 + element ("mix:flash", + Integer.toString (n)) + EOL); + useCcSetBuf = true; + } + d = niso.getFocalLength (); + if (d != NisoImageMetadata.NULL) { + ccSetBuf.append (margn6 + element ("mix:focalLength", + Double.toString (d)) + EOL); + useCcSetBuf = true; + } + d = niso.getFlashEnergy (); + if (d != NisoImageMetadata.NULL) { + ccSetBuf.append (margn6 + element ("mix:flashEnergy", + Double.toString (d)) + EOL); + useCcSetBuf = true; + } + n = niso.getBackLight (); + if (n != NisoImageMetadata.NULL) { + ccSetBuf.append (margn6 + element ("mix:backLight", + Integer.toString (n)) + EOL); + useCcSetBuf = true; + } + d = niso.getExposureIndex (); + if (d != NisoImageMetadata.NULL) { + ccSetBuf.append (margn6 + element ("mix:exposureIndex", + Double.toString (d)) + EOL); + useCcSetBuf = true; + } + n = niso.getAutoFocus (); + if (n != NisoImageMetadata.NULL) { + ccSetBuf.append (margn6 + element ("mix:autoFocus", + Integer.toString (n)) + EOL); + useCcSetBuf = true; + } + d = niso.getXPrintAspectRatio(); + double d2 = niso.getYPrintAspectRatio(); + if (d != NisoImageMetadata.NULL || d2 != NisoImageMetadata.NULL) { + ccSetBuf.append (margn6 + elementStart ("mix:PrintAspectRatio") + + EOL); + if (d != NisoImageMetadata.NULL) { + ccSetBuf.append (margn7 + + element ("mix:xPrintAspectRatio", Double.toString(d)) + + EOL); + } + if (d2 != NisoImageMetadata.NULL) { + ccSetBuf.append (margn7 + + element ("mix:yPrintAspectRatio", Double.toString(d)) + + EOL); + } + + ccSetBuf.append (margn6 + elementEnd ("mix:PrintAspectRatio") + + EOL); + } + + ccSetBuf.append (margn5 + elementEnd ("mix:ImageData") + EOL); + ccSetBuf.append (margn4 + elementEnd ("mix:CameraCaptureSettings") + + EOL); + if (useCcSetBuf) { + digCamBuf.append (ccSetBuf); + useDigCamBuf = true; + } + digCamBuf.append (margn3 + elementEnd ("mix:DigitalCameraCapture") + + EOL); + if (useDigCamBuf) { + captureBuffer.append (digCamBuf); + useCaptureBuffer = true; + } + + n = niso.getOrientation(); + if (n != NisoImageMetadata.NULL) { + captureBuffer.append (margn3 + element ("mix:orientation", + Integer.toString (n)) + + EOL); + useCaptureBuffer = true; + } + s = niso.getMethodology(); + if (s != null) { + captureBuffer.append (margn3 + element ("mix:methodology", s) + + EOL); + } + if (useCaptureBuffer) { + _writer.println (margn2 + + elementStart ("mix:ImageCaptureMetadata")); + _writer.print (captureBuffer.toString ()); + _writer.println (margn2 + elementEnd ("mix:ImageCaptureMetadata")); + } + } + + /* 1.0, Top level element 4 of 5: ImageAssessmentMetadata */ + protected void showNisoImageAssessmentMetadata10 (NisoImageMetadata niso, String margin) + { + String margn2 = margin + " "; + String margn3 = margn2 + " "; + String margn4 = margn3 + " "; + String margn5 = margn4 + " "; + String margn6 = margn5 + " "; + + _writer.println (margn2 + elementStart ("mix:ImageAssessmentMetadata")); + StringBuffer metricsBuf = new StringBuffer (margn3 + + elementStart("mix:SpatialMetrics") + EOL); + boolean useMetricsBuf = false; + + int n = niso.getSamplingFrequencyPlane(); + if (n != NisoImageMetadata.NULL) { + metricsBuf.append (margn4 + + element("mix:samplingFrequencyPlane", + Integer.toString(n)) + EOL); + useMetricsBuf = true; + } + n = niso.getSamplingFrequencyUnit(); + if (n != NisoImageMetadata.NULL) { + metricsBuf.append (margn4 + + element("mix:samplingFrequencyUnit", + Integer.toString(n)) + EOL); + useMetricsBuf = true; + } + Rational r = niso.getXSamplingFrequency(); + if (r != null) { + rationalToString (metricsBuf, "mix:xSamplingFrequency", margn4, r); + } + r = niso.getYSamplingFrequency(); + if (r != null) { + rationalToString (metricsBuf, "mix:ySamplingFrequency", margn4, r); + } + metricsBuf.append(margn3 + elementEnd ("mix:SpatialMetrics")); + if (useMetricsBuf) { + _writer.println (metricsBuf); + } + + StringBuffer colorEncBuf = + new StringBuffer (margn3 + elementStart("mix:ImageColorEncoding") + + EOL); + boolean useColorEncBuf = false; + + int[] iarray = niso.getBitsPerSample(); + if (iarray != null) { + colorEncBuf.append (margn4 + elementStart ("mix:bitsPerSample") + + EOL); + colorEncBuf.append (margn5 + element ("mix:bitsPerSampleValue", + integerArray (iarray, ',')) + EOL); + colorEncBuf.append (margn5 + element ("mix:bitsPerSampleUnit", + "integer") + EOL); + // bitsPerSampleUnit can also be floating point. Don't ask me why. + colorEncBuf.append (margn4 + elementEnd ("mix:bitsPerSample") + + EOL); + useColorEncBuf = true; + } + n = niso.getSamplesPerPixel(); + if (n != NisoImageMetadata.NULL) { + colorEncBuf.append (margn4 + element ("mix:samplesPerPixel", + Integer.toString(n)) + EOL); + useColorEncBuf = true; + } + + iarray = niso.getExtraSamples(); + if (iarray != null) { + // extraSamples can only be an integer, so the best we can do is + // snag the first value from the array. It also must be limited to + // 0, 1, 2, or 3. + n = iarray[0]; + if (n >= 0 && n <= 3) { + colorEncBuf.append (margn4 + element ("mix:extraSamples", + Integer.toString (n)) + + EOL); + useColorEncBuf = true; + } + } + + String s = niso.getColormapReference(); + if (s != null) { + colorEncBuf.append (margn4 + + elementStart("mix:Colormap") + EOL); + colorEncBuf.append (margn5 + + element ("mix:colormapReference", s) + EOL); + colorEncBuf.append (margn4 + elementEnd ("mix:Colormap") + EOL); + useColorEncBuf = true; + } + + // This is complete nonsense, but it's what the spec says + iarray = niso.getGrayResponseCurve(); + if (iarray != null) { + colorEncBuf.append (margn4 + + element ("mix:grayResponseCurve", "N") + EOL); + useColorEncBuf = true; + } + + n = niso.getGrayResponseUnit(); + if (n != NisoImageMetadata.NULL) { + colorEncBuf.append (margn4 + + element ("mix:grayResponseUnit", + Integer.toString (n)) + EOL); + useColorEncBuf = true; + } + + r = niso.getWhitePointXValue(); + Rational r2 = niso.getWhitePointYValue(); + if (r != null || r2 != null) { + colorEncBuf.append (margn4 + + elementStart ("mix:WhitePoint") + EOL); + if (r != null) { + rationalToString (colorEncBuf, "mix:whitePointXValue", margn5, + r); + } + if (r2 != null) { + rationalToString (colorEncBuf, "mix:whitePointYValue", margn5, + r2); + } + colorEncBuf.append (margn4 + elementEnd ("mix:WhitePoint") + EOL); + useColorEncBuf = true; + } + + // A chromaticities buffer to go in the color encoding buffer. + StringBuffer chromaBuf = new StringBuffer (margn4 + + elementStart ("mix:PrimaryChromaticities") + EOL); + boolean useChromaBuf = false; + r = niso.getPrimaryChromaticitiesRedX(); + if (r != null) { + rationalToString (chromaBuf, "mix:primaryChromaticitiesRedX", + margn5, r); + useChromaBuf = true; + } + r = niso.getPrimaryChromaticitiesRedY(); + if (r != null) { + rationalToString (chromaBuf, "mix:primaryChromaticitiesRedY", + margn5, r); + useChromaBuf = true; + } + r = niso.getPrimaryChromaticitiesGreenX(); + if (r != null) { + rationalToString (chromaBuf, "mix:primaryChromaticitiesGreenX", + margn5, r); + useChromaBuf = true; + } + r = niso.getPrimaryChromaticitiesGreenY(); + if (r != null) { + rationalToString (chromaBuf, "mix:primaryChromaticitiesGreenY", + margn5, r); + useChromaBuf = true; + } + r = niso.getPrimaryChromaticitiesBlueX(); + if (r != null) { + rationalToString (chromaBuf, "mix:primaryChromaticitiesBlueX", + margn5, r); + useChromaBuf = true; + } + r = niso.getPrimaryChromaticitiesBlueY(); + if (r != null) { + rationalToString (chromaBuf, "mix:primaryChromaticitiesBlueY", + margn5, r); + useChromaBuf = true; + } + chromaBuf.append (margn4 + + elementEnd ("mix:PrimaryChromaticities") + EOL); + if(useChromaBuf) { + colorEncBuf.append (chromaBuf); + useColorEncBuf = true; + } + + colorEncBuf.append (margn3 + elementEnd ("mix:ImageColorEncoding") + EOL); + if (useColorEncBuf) { + _writer.print (colorEncBuf); + } + + StringBuffer targetBuf = new StringBuffer (margn3 + + elementStart ("mix:TargetData") + EOL); + boolean useTargetBuf = false; + n = niso.getTargetType(); + if (n != NisoImageMetadata.NULL) { + targetBuf.append (margn4 + + element ("mix:targetType", Integer.toString(n)) + EOL); + useTargetBuf = true; + } + + // Now a nested buffer for TargetID. + StringBuffer targetIDBuf = new StringBuffer (margn4 + + elementStart("mix:TargetID") + EOL); + boolean useTargetIDBuf = false; + + s = niso.getTargetIDManufacturer(); + if (s != null) { + targetIDBuf.append (margn5 + + element ("mix:targetManufacturer", s) + EOL); + useTargetIDBuf = true; + } + s = niso.getTargetIDName(); + if (s != null) { + targetIDBuf.append (margn5 + + element ("mix:targetName", s) + EOL); + useTargetIDBuf = true; + } + s = niso.getTargetIDNo(); + if (s != null) { + targetIDBuf.append (margn5 + + element ("mix:targetNo", s) + EOL); + useTargetIDBuf = true; + } + s = niso.getTargetIDMedia(); + if (s != null) { + targetIDBuf.append (margn5 + + element ("mix:targetMedia", s) + EOL); + useTargetIDBuf = true; + } + targetIDBuf.append (margn4 + + elementEnd ("mix:TargetID") + EOL); + + if (useTargetIDBuf) { + targetBuf.append (targetIDBuf); + useTargetBuf = true; + } + s = niso.getImageData(); + if (s != null) { + targetBuf.append (margn4 + + element ("mix:externalTarget", s) + EOL); + useTargetBuf = true; + } + s = niso.getPerformanceData(); + if (s != null) { + targetBuf.append (margn4 + + element ("mix:performanceData", s) + EOL); + useTargetBuf = true; + } + + targetBuf.append (margn3 + elementEnd ("mix:TargetData") + EOL); + + if (useTargetBuf) { + _writer.print (targetBuf); + } + _writer.println (margn2 + elementEnd ("mix:ImageAssessmentMetadata")); + } + + /* 1.0, Top level element 5 of 5: ChangeHistory (without time travel) */ + protected void showChangeHistory10 (NisoImageMetadata niso, String margin) + { + String margn2 = margin + " "; + String margn3 = margn2 + " "; + String margn4 = margn3 + " "; + String margn5 = margn4 + " "; + String margn6 = margn5 + " "; + + // There may be nothing at all to write. Put the whole thing in a buffer. + StringBuffer chBuf = + new StringBuffer (margn2 + elementStart ("mix:ChangeHistory") + EOL); + boolean useChBuf = false; + + chBuf.append (margn3 + elementStart ("mix:ImageProcessing") + EOL); + + String s = niso.getSourceData(); + if (s != null) { + chBuf.append (margn4 + element ("mix:sourceData") + EOL); + useChBuf = true; + } + s = niso.getProcessingAgency(); + if (s != null) { + chBuf.append (margn4 + element ("mix:processingAgency") + EOL); + useChBuf = true; + } + StringBuffer sftwBuf = new StringBuffer (margn4 + + elementStart ("mix:ProcessingSoftware") + EOL); + boolean useSftwBuf = false; + s = niso.getProcessingSoftwareName(); + if (s != null) { + sftwBuf.append (margn5 + + element ("mix:processingSoftwareName", s) + EOL); + useSftwBuf = true; + } + s = niso.getProcessingSoftwareVersion(); + if (s != null) { + sftwBuf.append (margn5 + + element ("mix:processingSoftwareVersion", s) + EOL); + useSftwBuf = true; + } + s = niso.getOS(); + if (s != null) { + sftwBuf.append (margn5 + + element ("mix:processingOperatingSystemName", s) + EOL); + useSftwBuf = true; + } + s = niso.getOSVersion(); + if (s != null) { + sftwBuf.append (margn5 + + element ("mix:processingOperatingSystemVersion", s) + EOL); + useSftwBuf = true; + } + sftwBuf.append (margn4 + + elementEnd ("mix:ProcessingSoftware") + EOL); + if (useSftwBuf) { + chBuf.append (sftwBuf); + useChBuf = true; + } + + String[] sarray = niso.getProcessingActions(); + if (sarray != null) { + for (int i = 0; i < sarray.length; i++) { + chBuf.append (margn4 + + element ("mix:processingActions", sarray[i]) + EOL); + } + useChBuf = true; + } + + chBuf.append (margn3 + elementEnd ("mix:ImageProcessing") + EOL); + chBuf.append (margn2 + elementEnd ("mix:ChangeHistory") + EOL); + if (useChBuf) { + _writer.println (chBuf); + } + + } + + + /** + * Display the NISO image metadata formatted according to + * the MIX 2.0 schema. + */ + protected void showNisoImageMetadata20 (NisoImageMetadata niso) + { + String margin = getIndent (++_level); + + String [][] attrs = {{"xmlns:mix", "http://www.loc.gov/mix/v20"}, + {"xmlns:xsi", + "http://www.w3.org/2001/XMLSchema-instance"}, + {"xsi:schemaLocation", + "http://www.loc.gov/mix/v20 http://www.loc.gov/standards/mix/mix20/mix20.xsd"}}; + _writer.println (margin + elementStart ("mix:mix", attrs)); + + showNisoBasicDigitalObjectInformation20 (niso, margin); + showNisoBasicImageInformation20 (niso, margin); + showNisoImageCaptureMetadata20 (niso, margin); + showNisoImageAssessmentMetadata20 (niso, margin); + showChangeHistory20 (niso, margin); + + _writer.println (margin + elementEnd ("mix:mix")); + + _level--; + + } + + /* The NISO Metadata output for version 2.0. + Top level element 1 of 6: BasicDigitalObjectInformation */ + protected void showNisoBasicDigitalObjectInformation20 (NisoImageMetadata niso, String margin) + { + String margn2 = margin + " "; + String margn3 = margn2 + " "; + String margn4 = margn3 + " "; + String margn5 = margn4 + " "; + + _writer.println (margn2 + elementStart ("mix:BasicDigitalObjectInformation")); + + StringBuffer objIDBuf = new StringBuffer + (margn3 + elementStart ("mix:ObjectIdentifier") + EOL); + objIDBuf.append (margn4 + element ("mix:objectIdentifierType", "JHOVE") + EOL); + String s = niso.getImageIdentifier (); + if (s != null) { + objIDBuf.append (margn4 + element ("mix:objectIdentifierValue", s) + EOL); + } + objIDBuf.append (margn3 + elementEnd ("mix:ObjectIdentifier") + EOL); + _writer.print (objIDBuf.toString ()); + long ln = niso.getFileSize(); + if (ln != NisoImageMetadata.NULL) { + _writer.print (margn3 + element ("mix:fileSize", + Long.toString (ln)) + EOL); + } + + if ((s = niso.getByteOrder ()) != null) { + // Convert strings to MIX 1.0 form + if (s.startsWith ("big")) { + s = "big endian"; + } + else if (s.startsWith ("little")) { + s = "little endian"; + } + _writer.print (margn3 + element ("mix:byteOrder", s) + EOL); + } + + int comp = niso.getCompressionScheme (); + int level = niso.getCompressionLevel (); + String compStr; + switch (comp) { + case 1: + compStr = "Uncompressed"; + break; + case 2: + compStr = "CCITT 1D"; + break; + case 3: + compStr = "Group 3 Fax"; + break; + case 4: + compStr = "Group 4 Fax"; + break; + case 5: + compStr = "LZW"; + break; + case 6: + compStr = "JPEG"; + break; + case 32773: + compStr = "PackBits"; + break; + default: + compStr = "Unknown"; + break; + } + if (comp != NisoImageMetadata.NULL || level != NisoImageMetadata.NULL) { + _writer.print (margn3 + elementStart ("mix:Compression") + EOL); + if (comp != NisoImageMetadata.NULL) { + _writer.print (margn4 + element ("mix:compressionScheme", + compStr) + EOL); + } + // TODO it isn't clear how to get from compression level to compression ratio + + _writer.print (margn3 + elementEnd ("mix:Compression") + EOL); + } + + // NOTE: Checksum method and value are never set currently. If they are, the + // values set will need to be converted to meaningful MIX values. This code is left + // here just as a reminder. + int n = niso.getChecksumMethod (); + s = niso.getChecksumValue (); + if (n != NisoImageMetadata.NULL || s != null) { + _writer.print (margn4 + elementStart ("mix:Fixity") + EOL); + if (n != NisoImageMetadata.NULL) { + _writer.print (margn5 + element ("mix:messageDigestAlgorithm", + Integer.toString (n)) + EOL); + } + if (s != null) { + _writer.print (margn5 + element ("mix:messageDigest", s) + EOL); + } + _writer.println (margn4 + elementEnd ("mix:Fixity")); + } + + + _writer.print(margn2 + elementEnd ("mix:BasicDigitalObjectInformation") + EOL); + } + + + /* MIX/NISO 2.0, Top level element 2 of 5: BasicImageInformation */ + protected void showNisoBasicImageInformation20 (NisoImageMetadata niso, String margin) + { + String margn2 = margin + " "; + String margn3 = margn2 + " "; + String margn4 = margn3 + " "; + String margn5 = margn4 + " "; + String margn6 = margn5 + " "; + String margn7 = margn6 + " "; + _writer.println (margn2 + elementStart ("mix:BasicImageInformation")); + StringBuffer basCharBuf = new StringBuffer + (margn3 + elementStart ("mix:BasicImageCharacteristics") + EOL); + boolean useBasCharBuf = false; + long ln = niso.getImageWidth (); + if (ln != NisoImageMetadata.NULL) { + basCharBuf.append (margn4 + element ("mix:imageWidth", + Long.toString (ln)) + EOL); + useBasCharBuf = true; + } + ln = niso.getImageLength (); + if (ln != NisoImageMetadata.NULL) { + basCharBuf.append (margn4 + element ("mix:imageHeight", + Long.toString (ln)) + EOL); + useBasCharBuf = true; + } + // Nest photometric interpretation tentative buffer in basCharBuf + StringBuffer piBuf = new StringBuffer + (margn4 + elementStart ("mix:PhotometricInterpretation") + EOL); + boolean usePIBuf = false; + int n = niso.getColorSpace (); + if (n != NisoImageMetadata.NULL) { + piBuf.append (margn5 + element ("mix:colorSpace", + photometricInterpretationToString (n)) + EOL); + usePIBuf = true; + } + String s = niso.getProfileName (); + String s2= niso.getProfileURL (); + if (s != null || s2 != null) { + piBuf.append (margn5 + elementStart ("mix:ColorProfile") + EOL); + piBuf.append (margn6 + elementStart ("mix:IccProfile") + EOL); + if (s != null) { + piBuf.append (margn7 + element ("mix:iccProfileName", s) +EOL); + } + if (s2!= null) { + piBuf.append (margn7 + element ("mix:iccProfileURI", s2) +EOL); + } + piBuf.append (margn6 + elementEnd ("mix:IccProfile") + EOL); + // MIX 2.0 also allows embedded and local profiles. We don't currently support that. + piBuf.append (margn5 + elementEnd ("mix:ColorProfile") + EOL); + usePIBuf = true; + } + int [] iarray = niso.getYCbCrSubSampling (); + n = niso.getYCbCrPositioning (); + Rational [] rarray = niso.getYCbCrCoefficients (); + if (iarray != null || n != NisoImageMetadata.NULL || rarray != null) { + piBuf.append (margn5 + elementStart ("mix:YCbCr") + EOL); + usePIBuf = true; + if (iarray != null && iarray.length >= 2) { + piBuf.append (margn6 + elementStart ("mix:YCbCrSubSampling") + + EOL); + piBuf.append (margn7 + element ("mix:yCbCrSubsampleHoriz", + Integer.toString (iarray[0])) + + EOL); + piBuf.append (margn7 + element ("mix:yCbCrSubsampleVert", + Integer.toString (iarray[1])) + + EOL); + piBuf.append (margn6 + elementEnd ("mix:YCbCrSubSampling") + + EOL); + } + if (n != NisoImageMetadata.NULL) { + piBuf.append (margn6 + element ("mix:yCbCrPositioning", + Integer.toString (n)) + EOL); + } + if (rarray != null && rarray.length >= 3) { + piBuf.append (margn6 + elementStart ("mix:YCbCrCoefficients") + + EOL); + rationalToString(piBuf, "mix:lumaRed", margn7, rarray[0]); + rationalToString(piBuf, "mix:lumaGreen", margn7, rarray[1]); + rationalToString(piBuf, "mix:lumaBlue", margn7, rarray[2]); + piBuf.append (margn6 + elementEnd ("mix:YCbCrCoefficients") + + EOL); + } + piBuf.append (margn5 + elementEnd ("mix:YCbCr") + EOL); + } + + rarray = niso.getReferenceBlackWhite (); + if (rarray != null) { + piBuf.append (margn5 + elementStart("mix:ReferenceBlackWhite") + EOL); + for (int i = 0; i < rarray.length - 1; i += 2) { + piBuf.append (margn6 + elementStart("mix:Component") + EOL); + piBuf.append (margn7 + elementStart("mix:componentPhotometricInterpretation")); + // Tricky here. The reference BW might be given as either RGB or yCbCr. + String pi; + if (niso.getColorSpace() == 6) { // yCbCr + switch (i) { + case 0: + pi = "Y"; + break; + case 2: + pi = "Cb"; + break; + case 4: + default: + pi = "Cr"; + break; + } + } + else { + switch (i) { // otherwise assume RGB + case 0: + pi = "R"; + break; + case 2: + pi = "G"; + break; + case 4: + default: + pi = "B"; + break; + } + + } + piBuf.append (pi + elementEnd ("mix:componentPhotometricInterpretation") + EOL); + rationalToString (piBuf, "mix:footroom", margn7, rarray[i]); + rationalToString (piBuf, "mix:headroom", margn7, rarray[i + 1]); + piBuf.append (margn7 + elementEnd("mix:Component") + EOL); + } + piBuf.append (margn6 + elementEnd("mix:ReferenceBlackWhite") + EOL); + usePIBuf = true; + } + piBuf.append (margn4 + elementEnd ("mix:PhotometricInterpretation") + + EOL); + if (usePIBuf) { + basCharBuf.append (piBuf); + useBasCharBuf = true; + } + basCharBuf.append (margn3 + + elementEnd ("mix:BasicImageCharacteristics")); + + if (useBasCharBuf) { + _writer.println (basCharBuf); + } + _writer.println (margn2 + elementEnd ("mix:BasicImageInformation")); + // TODO SpecialFormatCharacteristics would be nice to have here, + // but that's future expansion + } + + + /* 2.0, Top level element 3 of 5: ImageCaptureMetadata */ + protected void showNisoImageCaptureMetadata20 (NisoImageMetadata niso, + String margin) + { + String margn2 = margin + " "; + String margn3 = margn2 + " "; + String margn4 = margn3 + " "; + String margn5 = margn4 + " "; + String margn6 = margn5 + " "; + String margn7 = margn6 + " "; + String margn8 = margn7 + " "; + + // We don't start with an ImageCaptureMetadata element, because the + // whole element is conditional on having some content. + StringBuffer captureBuffer = new StringBuffer (); + boolean useCaptureBuffer = false; + int n; + + String s = niso.getSourceType (); + String si = niso.getSourceID (); + double d = niso.getSourceXDimension (); + if (s != null || si != null || d != NisoImageMetadata.NILL) { + captureBuffer.append (margn3 + element ("mix:SourceInformation", s)); + useCaptureBuffer = true; + if (s != null) { + captureBuffer.append (margn4 + element ("mix:sourceType", s)); + } + if (si != null) { + captureBuffer.append (margn4 + elementStart ("mix:SourceID")); + captureBuffer.append (margn4 + element ("mix:sourceIDValue", si)); + captureBuffer.append (margn4 + elementEnd ("mix:sourceID")); + } + n = niso.getSourceXDimensionUnit (); + if (d != NisoImageMetadata.NILL || n != NisoImageMetadata.NULL) { + // Assume that both X and Y exist, or neither + captureBuffer.append (margn4 + elementStart ("mix:SourceSize")); + captureBuffer.append (margn5 + + elementStart ("mix:SourceXDimension") + EOL); + if (d != NisoImageMetadata.NILL) { + captureBuffer.append (margn6 + + element ("mix:sourceXDimensionValue", + Double.toString (d)) + EOL); + } + if (n != NisoImageMetadata.NULL) { + captureBuffer.append (margn6 + + element ("mix:sourceXDimensionUnit", + Integer.toString (n)) + EOL); + } + captureBuffer.append (margn5 + elementEnd ("mix:SourceXDimension")+ + EOL); + + d = niso.getSourceYDimension (); + n = niso.getSourceYDimensionUnit (); + if (d != NisoImageMetadata.NILL || n != NisoImageMetadata.NULL) { + captureBuffer.append (margn5 + + elementStart ("mix:SourceYDimension") + + EOL); + if (d != NisoImageMetadata.NILL) { + captureBuffer.append (margn6 + + element ("mix:sourceYDimensionValue", + Double.toString (d)) + EOL); + } + if (n != NisoImageMetadata.NULL) { + captureBuffer.append (margn6 + + element ("mix:sourceYDimensionUnit", + Integer.toString (n))+ EOL); + } + captureBuffer.append (margn5 + + elementEnd ("mix:SourceYDimension") + EOL); + } + captureBuffer.append (margn4 + elementEnd ("mix:SourceSize") + + EOL); + } + captureBuffer.append (margn3 + elementEnd ("mix:SourceInformation") + EOL); + } + StringBuffer genCapBuf = + new StringBuffer (margn3 + + elementStart ("mix:GeneralCaptureInformation") + EOL); + boolean useGenCapBuf = false; + + s = niso.getDateTimeCreated (); + if (s != null) { + genCapBuf.append (margn3 + element ("mix:dateTimeCreated", s) + + EOL); + useGenCapBuf = true; + } + s = niso.getImageProducer (); + if (s != null) { + genCapBuf.append (margn3 + element ("mix:imageProducer", s) + EOL); + useGenCapBuf = true; + } + + s = niso.getDeviceSource (); + if (s != null) { + genCapBuf.append (margn3 + element ("mix:captureDevice", s) + + EOL); + /* This has a restricted set of values. Does the setting code + * conform? + */ + } + + genCapBuf.append (margn3 + + elementEnd ("mix:GeneralCaptureInformation") + EOL); + if (useGenCapBuf) { + captureBuffer.append (genCapBuf); + useCaptureBuffer = true; + } + + // Here's a chunk of XML for scanners. + StringBuffer scanCapBuf = + new StringBuffer (margn3 + elementStart ("mix:ScannerCapture") + + EOL); + boolean useScanCapBuf = false; + String mfg = niso.getScannerManufacturer (); + if (mfg != null) { + scanCapBuf.append (margn4 + element ("mix:scannerManufacturer", + mfg) + EOL); + useScanCapBuf = true; + } + String model = niso.getScannerModelName (); + String modelNum = niso.getScannerModelNumber(); + String serNum = niso.getScannerModelSerialNo(); + if (model != null || modelNum != null || serNum != null) { + useScanCapBuf = true; + scanCapBuf.append (margn4 + elementStart ("mix:ScannerModel") + + EOL); + if (model != null) { + scanCapBuf.append (margn5 + element ("mix:scannerModelName", model) + + EOL); + } + if (modelNum != null) { + scanCapBuf.append (margn5 + element ("mix:scannerModelNumber", modelNum)+ + EOL); + } + if (serNum != null) { + scanCapBuf.append (margn5 + + element ("mix:scannerModelSerialNo", serNum) + EOL); + } + scanCapBuf.append (margn4 + elementEnd ("mix:ScannerModel") + EOL); + } + double xres = niso.getXPhysScanResolution(); + double yres = niso.getYPhysScanResolution(); + if (xres != NisoImageMetadata.NULL && yres != NisoImageMetadata.NULL) { + scanCapBuf.append (margn4 + elementStart + ("mix:MaximumOpticalResolution") + EOL); + scanCapBuf.append (margn5 + element + ("mix:xOpticalResolution", Double.toString (xres)) + EOL); + scanCapBuf.append (margn5 + element + ("mix:yOpticalResolution", Double.toString (yres)) + EOL); + scanCapBuf.append (margn5 + element + ("mix:resolutionUnit", "in.") + EOL); // is this a safe assumption? + scanCapBuf.append (margn4 + elementEnd ("mix:MaximumOpticalResolution")); + } + s = niso.getScanningSoftware(); + if (s != null) { + useScanCapBuf = true; + scanCapBuf.append (margn4 + + elementStart ("mix:ScanningSystemSoftware") + + EOL); + scanCapBuf.append (margn5 + element ("mix:scanningSoftwareName", + s) + EOL); + s = niso.getScanningSoftwareVersionNo(); + if (s != null) { + scanCapBuf.append (margn5 + + element ("mix:scanningSoftwareVersionNo", s) + EOL); + } + scanCapBuf.append (margn4 + + elementEnd ("mix:ScanningSystemSoftware") + EOL); + } + scanCapBuf.append (margn3 + elementEnd ("mix:ScannerCapture") + EOL); + if (useScanCapBuf) { + captureBuffer.append (scanCapBuf); + useCaptureBuffer = true; + } + + // Now we'll hear from the digital cameras. + StringBuffer digCamBuf = + new StringBuffer (margn3 + + elementStart ("mix:DigitalCameraCapture") + EOL); + boolean useDigCamBuf = false; + + s = niso.getDigitalCameraManufacturer(); + if (s != null) { + digCamBuf.append (margn4 + + element ("mix:digitalCameraManufacturer") + EOL); + useDigCamBuf = true; + } + s = niso.getDigitalCameraModel(); + if (s != null) { + digCamBuf.append (margn4 + elementStart ("mix:DigitalCameraModel")+ + EOL); + digCamBuf.append (margn5 + element ("mix:digitalCameraModelName") + s + + EOL); + digCamBuf.append (margn4 + elementEnd ("mix:DigitalCameraModel") + + EOL); + useDigCamBuf = true; + } + + // Nest a buffer for CameraCaptureSettings + StringBuffer ccSetBuf = + new StringBuffer (margn4 + + elementStart ("mix:CameraCaptureSettings") + + EOL); + boolean useCcSetBuf = false; + // CameraCaptureSettings consists only of an ImageData element, so we + // don't need another use flag here. + ccSetBuf.append (margn5 + elementStart ("mix:ImageData") + EOL); + d = niso.getFNumber (); + if (d != NisoImageMetadata.NULL) { + ccSetBuf.append (margn6 + element ("mix:fNumber", + Double.toString (d)) + EOL); + useCcSetBuf = true; + } + d = niso.getExposureTime(); + if (d != NisoImageMetadata.NULL) { + ccSetBuf.append (margn6 + element ("mix:exposureTime", + Double.toString (d)) + EOL); + useCcSetBuf = true; + } + d = niso.getBrightness(); + if (d != NisoImageMetadata.NULL) { + ccSetBuf.append (margn6 + element ("mix:brightnessValue", + Double.toString (d)) + EOL); + useCcSetBuf = true; + } + d = niso.getExposureBias(); + if (d != NisoImageMetadata.NULL) { + ccSetBuf.append (margn6 + element ("mix:exposureBiasValue", + Double.toString (d)) + EOL); + useCcSetBuf = true; + } + double[] darray = niso.getSubjectDistance (); + if (darray != null) { + // darray has two values. If they're equal, set "distance". Otherwise, + // set the min and max. + ccSetBuf.append (margn6 + elementStart("mix:SubjectDistance") + EOL); + useCcSetBuf = true; + if (darray[0] == darray[1]) { + ccSetBuf.append (margn7 + element ("mix:distance", + Double.toString(darray[0])) + EOL); + } + else { + ccSetBuf.append (margn7 + elementStart("mix:MinMaxDistance") + EOL); + ccSetBuf.append (margn8 + element ("mix:minDistance", + Double.toString(darray[0])) + EOL); + ccSetBuf.append (margn8 + element ("mix:maxDistance", + Double.toString(darray[1])) + EOL); + ccSetBuf.append (margn7 + elementEnd("mix:MinMaxDistance") + EOL); + } + ccSetBuf.append (margn6 + elementEnd("mix:SubjectDistance") + EOL); + + } + n = niso.getMeteringMode (); + if (n != NisoImageMetadata.NULL) { + ccSetBuf.append (margn6 + element ("mix:meteringMode", + Integer.toString (n)) + EOL); + useCcSetBuf = true; + } + n = niso.getFlash (); + if (n != NisoImageMetadata.NULL) { + ccSetBuf.append (margn6 + element ("mix:flash", + Integer.toString (n)) + EOL); + useCcSetBuf = true; + } + d = niso.getFocalLength (); + if (d != NisoImageMetadata.NULL) { + ccSetBuf.append (margn6 + element ("mix:focalLength", + Double.toString (d)) + EOL); + useCcSetBuf = true; + } + d = niso.getFlashEnergy (); + if (d != NisoImageMetadata.NULL) { + ccSetBuf.append (margn6 + element ("mix:flashEnergy", + Double.toString (d)) + EOL); + useCcSetBuf = true; + } + n = niso.getBackLight (); + if (n != NisoImageMetadata.NULL) { + ccSetBuf.append (margn6 + element ("mix:backLight", + Integer.toString (n)) + EOL); + useCcSetBuf = true; + } + d = niso.getExposureIndex (); + if (d != NisoImageMetadata.NULL) { + ccSetBuf.append (margn6 + element ("mix:exposureIndex", + Double.toString (d)) + EOL); + useCcSetBuf = true; + } + n = niso.getAutoFocus (); + if (n != NisoImageMetadata.NULL) { + ccSetBuf.append (margn6 + element ("mix:autoFocus", + Integer.toString (n)) + EOL); + useCcSetBuf = true; + } + d = niso.getXPrintAspectRatio(); + double d2 = niso.getYPrintAspectRatio(); + if (d != NisoImageMetadata.NULL || d2 != NisoImageMetadata.NULL) { + ccSetBuf.append (margn6 + elementStart ("mix:PrintAspectRatio") + + EOL); + if (d != NisoImageMetadata.NULL) { + ccSetBuf.append (margn7 + + element ("mix:xPrintAspectRatio", Double.toString(d)) + + EOL); + } + if (d2 != NisoImageMetadata.NULL) { + ccSetBuf.append (margn7 + + element ("mix:yPrintAspectRatio", Double.toString(d)) + + EOL); + } + + ccSetBuf.append (margn6 + elementEnd ("mix:PrintAspectRatio") + + EOL); + } + + ccSetBuf.append (margn5 + elementEnd ("mix:ImageData") + EOL); + ccSetBuf.append (margn4 + elementEnd ("mix:CameraCaptureSettings") + + EOL); + if (useCcSetBuf) { + digCamBuf.append (ccSetBuf); + useDigCamBuf = true; + } + digCamBuf.append (margn3 + elementEnd ("mix:DigitalCameraCapture") + + EOL); + if (useDigCamBuf) { + captureBuffer.append (digCamBuf); + useCaptureBuffer = true; + } + + n = niso.getOrientation(); + if (n != NisoImageMetadata.NULL) { + final String[] orient = { "unknown", + "normal*", + "normal, image flipped", + "normal, rotated 180\u00B0", + "normal, image flipped, rotated 180\u00B0", + "normal, image flipped, rotated cw 90\u00B0", + "normal, rotated ccw 90\u00B0", + "normal, image flipped, rotated ccw 90\u00B0", + "normal, rotated cw 90\u00B0" }; + if (n > 8 || n < 0) { + n = 0; // force "unknown" for bad value + } + captureBuffer.append (margn3 + element ("mix:orientation", + orient[n]) + + EOL); + useCaptureBuffer = true; + } + s = niso.getMethodology(); + if (s != null) { + captureBuffer.append (margn3 + element ("mix:methodology", s) + + EOL); + } + if (useCaptureBuffer) { + _writer.println (margn2 + + elementStart ("mix:ImageCaptureMetadata")); + _writer.print (captureBuffer.toString ()); + _writer.println (margn2 + elementEnd ("mix:ImageCaptureMetadata")); + } + } + + + /* 2.0, Top level element 4 of 5: ImageAssessmentMetadata */ + protected void showNisoImageAssessmentMetadata20 (NisoImageMetadata niso, String margin) + { + String margn2 = margin + " "; + String margn3 = margn2 + " "; + String margn4 = margn3 + " "; + String margn5 = margn4 + " "; + String margn6 = margn5 + " "; + + _writer.println (margn2 + elementStart ("mix:ImageAssessmentMetadata")); + StringBuffer metricsBuf = new StringBuffer (margn3 + + elementStart("mix:SpatialMetrics") + EOL); + boolean useMetricsBuf = false; + + int n = niso.getSamplingFrequencyPlane(); + if (n != NisoImageMetadata.NULL) { + metricsBuf.append (margn4 + + element("mix:samplingFrequencyPlane", + Integer.toString(n)) + EOL); + useMetricsBuf = true; + } + n = niso.getSamplingFrequencyUnit(); + if (n != NisoImageMetadata.NULL) { + final String sfu[] = {null, + "no absolute unit of measurement", "in.", "cm"}; + if (n < 1 || n > 3) { + n = 1; + } + metricsBuf.append (margn4 + + element("mix:samplingFrequencyUnit", + sfu[n]) + EOL); + useMetricsBuf = true; + } + Rational r = niso.getXSamplingFrequency(); + if (r != null) { + rationalToString (metricsBuf, "mix:xSamplingFrequency", margn4, r); + } + r = niso.getYSamplingFrequency(); + if (r != null) { + rationalToString (metricsBuf, "mix:ySamplingFrequency", margn4, r); + } + metricsBuf.append(margn3 + elementEnd ("mix:SpatialMetrics")); + if (useMetricsBuf) { + _writer.println (metricsBuf); + } + + StringBuffer colorEncBuf = + new StringBuffer (margn3 + elementStart("mix:ImageColorEncoding") + + EOL); + boolean useColorEncBuf = false; + + int[] iarray = niso.getBitsPerSample(); + if (iarray != null) { + colorEncBuf.append (margn4 + elementStart ("mix:BitsPerSample") + + EOL); + for (int ii = 1; ii < iarray.length; ii++) { + colorEncBuf.append (margn5 + element ("mix:bitsPerSampleValue", + Integer.toString(iarray[ii]) ) + EOL); + } + colorEncBuf.append (margn5 + element ("mix:bitsPerSampleUnit", + "integer") + EOL); + // bitsPerSampleUnit can also be floating point. Don't ask me why. + colorEncBuf.append (margn4 + elementEnd ("mix:BitsPerSample") + + EOL); + useColorEncBuf = true; + } + n = niso.getSamplesPerPixel(); + if (n != NisoImageMetadata.NULL) { + colorEncBuf.append (margn4 + element ("mix:samplesPerPixel", + Integer.toString(n)) + EOL); + useColorEncBuf = true; + } + + + iarray = niso.getExtraSamples(); + if (iarray != null) { + for (int ii = 0; ii < iarray.length; ii++) { + n = iarray[ii]; + if (n >= 0 && n <= 3) { + colorEncBuf.append (margn4 + element ("mix:extraSamples", + NisoImageMetadata.EXTRA_SAMPLE_20[n]) + + EOL); + useColorEncBuf = true; + } + } + } + + String s = niso.getColormapReference(); + if (s != null) { + colorEncBuf.append (margn4 + + elementStart("mix:Colormap") + EOL); + colorEncBuf.append (margn5 + + element ("mix:colormapReference", s) + EOL); + colorEncBuf.append (margn4 + elementEnd ("mix:Colormap") + EOL); + useColorEncBuf = true; + } + + iarray = niso.getGrayResponseCurve(); + n = niso.getGrayResponseUnit(); + + if (iarray != null || n != NisoImageMetadata.NULL) { + StringBuffer grayRespBuf = + new StringBuffer (margn4 + elementStart ("mix:GrayResponse") + EOL); + if (iarray != null) { + for (int ii = 0; ii < iarray.length; ii++) { + grayRespBuf.append (margn5 + element ("mix:grayResponseCurve", + Integer.toString(iarray[ii])) + EOL); + } + } + + if (n != NisoImageMetadata.NULL && n > 0 && n <= 5) { + // Convert integer to text value; only values 1-5 are legal + grayRespBuf.append (margn5 + + element ("mix:grayResponseUnit", + NisoImageMetadata.GRAY_RESPONSE_UNIT_20[n - 1]) + EOL); + } + grayRespBuf.append (margn4 + elementEnd ("mix:GrayResponse") + EOL); + colorEncBuf.append (grayRespBuf); + useColorEncBuf = true; + } + + r = niso.getWhitePointXValue(); + Rational r2 = niso.getWhitePointYValue(); + if (r != null || r2 != null) { + colorEncBuf.append (margn4 + + elementStart ("mix:WhitePoint") + EOL); + if (r != null) { + rationalToString (colorEncBuf, "mix:whitePointXValue", margn5, + r); + } + if (r2 != null) { + rationalToString (colorEncBuf, "mix:whitePointYValue", margn5, + r2); + } + colorEncBuf.append (margn4 + elementEnd ("mix:WhitePoint") + EOL); + useColorEncBuf = true; + } + + // A chromaticities buffer to go in the color encoding buffer. + StringBuffer chromaBuf = new StringBuffer (margn4 + + elementStart ("mix:PrimaryChromaticities") + EOL); + boolean useChromaBuf = false; + r = niso.getPrimaryChromaticitiesRedX(); + if (r != null) { + rationalToString (chromaBuf, "mix:primaryChromaticitiesRedX", + margn5, r); + useChromaBuf = true; + } + r = niso.getPrimaryChromaticitiesRedY(); + if (r != null) { + rationalToString (chromaBuf, "mix:primaryChromaticitiesRedY", + margn5, r); + useChromaBuf = true; + } + r = niso.getPrimaryChromaticitiesGreenX(); + if (r != null) { + rationalToString (chromaBuf, "mix:primaryChromaticitiesGreenX", + margn5, r); + useChromaBuf = true; + } + r = niso.getPrimaryChromaticitiesGreenY(); + if (r != null) { + rationalToString (chromaBuf, "mix:primaryChromaticitiesGreenY", + margn5, r); + useChromaBuf = true; + } + r = niso.getPrimaryChromaticitiesBlueX(); + if (r != null) { + rationalToString (chromaBuf, "mix:primaryChromaticitiesBlueX", + margn5, r); + useChromaBuf = true; + } + r = niso.getPrimaryChromaticitiesBlueY(); + if (r != null) { + rationalToString (chromaBuf, "mix:primaryChromaticitiesBlueY", + margn5, r); + useChromaBuf = true; + } + chromaBuf.append (margn4 + + elementEnd ("mix:PrimaryChromaticities") + EOL); + if (useChromaBuf) { + colorEncBuf.append (chromaBuf); + useColorEncBuf = true; + } + + colorEncBuf.append (margn3 + elementEnd ("mix:ImageColorEncoding") + EOL); + if (useColorEncBuf) { + _writer.print (colorEncBuf); + } + + StringBuffer targetBuf = new StringBuffer (margn3 + + elementStart ("mix:TargetData") + EOL); + boolean useTargetBuf = false; + n = niso.getTargetType(); + if (n != NisoImageMetadata.NULL) { + targetBuf.append (margn4 + + element ("mix:targetType", Integer.toString(n)) + EOL); + useTargetBuf = true; + } + + // Now a nested buffer for TargetID. + StringBuffer targetIDBuf = new StringBuffer (margn4 + + elementStart("mix:TargetID") + EOL); + boolean useTargetIDBuf = false; + + s = niso.getTargetIDManufacturer(); + if (s != null) { + targetIDBuf.append (margn5 + + element ("mix:targetManufacturer", s) + EOL); + useTargetIDBuf = true; + } + s = niso.getTargetIDName(); + if (s != null) { + targetIDBuf.append (margn5 + + element ("mix:targetName", s) + EOL); + useTargetIDBuf = true; + } + s = niso.getTargetIDNo(); + if (s != null) { + targetIDBuf.append (margn5 + + element ("mix:targetNo", s) + EOL); + useTargetIDBuf = true; + } + s = niso.getTargetIDMedia(); + if (s != null) { + targetIDBuf.append (margn5 + + element ("mix:targetMedia", s) + EOL); + useTargetIDBuf = true; + } + targetIDBuf.append (margn4 + elementEnd ("mix:TargetID") + EOL); + + if (useTargetIDBuf) { + targetBuf.append (targetIDBuf); + useTargetBuf = true; + } + s = niso.getImageData(); + if (s != null) { + targetBuf.append (margn4 + + element ("mix:externalTarget", s) + EOL); + useTargetBuf = true; + } + s = niso.getPerformanceData(); + if (s != null) { + targetBuf.append (margn4 + + element ("mix:performanceData", s) + EOL); + useTargetBuf = true; + } + + targetBuf.append (margn3 + elementEnd ("mix:TargetData") + EOL); + + if (useTargetBuf) { + _writer.print (targetBuf); + } + _writer.println (margn2 + elementEnd ("mix:ImageAssessmentMetadata")); + } + + + /* 2.0, Top level element 5 of 5: ChangeHistory */ + protected void showChangeHistory20 (NisoImageMetadata niso, String margin) + { + String margn2 = margin + " "; + String margn3 = margn2 + " "; + String margn4 = margn3 + " "; + String margn5 = margn4 + " "; + //String margn6 = margn5 + " "; + + // There may be nothing at all to write. Put the whole thing in a buffer. + StringBuffer chBuf = + new StringBuffer (margn2 + elementStart ("mix:ChangeHistory") + EOL); + boolean useChBuf = false; + + chBuf.append (margn3 + elementStart ("mix:ImageProcessing") + EOL); + + String s = niso.getSourceData(); + if (s != null) { + chBuf.append (margn4 + element ("mix:sourceData") + EOL); + useChBuf = true; + } + s = niso.getProcessingAgency(); + if (s != null) { + chBuf.append (margn4 + element ("mix:processingAgency") + EOL); + useChBuf = true; + } + StringBuffer sftwBuf = new StringBuffer (margn4 + + elementStart ("mix:ProcessingSoftware") + EOL); + boolean useSftwBuf = false; + s = niso.getProcessingSoftwareName(); + if (s != null) { + sftwBuf.append (margn5 + + element ("mix:processingSoftwareName", s) + EOL); + useSftwBuf = true; + } + s = niso.getProcessingSoftwareVersion(); + if (s != null) { + sftwBuf.append (margn5 + + element ("mix:processingSoftwareVersion", s) + EOL); + useSftwBuf = true; + } + s = niso.getOS(); + if (s != null) { + sftwBuf.append (margn5 + + element ("mix:processingOperatingSystemName", s) + EOL); + useSftwBuf = true; + } + s = niso.getOSVersion(); + if (s != null) { + sftwBuf.append (margn5 + + element ("mix:processingOperatingSystemVersion", s) + EOL); + useSftwBuf = true; + } + sftwBuf.append (margn4 + + elementEnd ("mix:ProcessingSoftware") + EOL); + if (useSftwBuf) { + chBuf.append (sftwBuf); + useChBuf = true; + } + + String[] sarray = niso.getProcessingActions(); + if (sarray != null) { + for (int i = 0; i < sarray.length; i++) { + chBuf.append (margn4 + + element ("mix:processingActions", sarray[i]) + EOL); + } + useChBuf = true; + } + + chBuf.append (margn3 + elementEnd ("mix:ImageProcessing") + EOL); + chBuf.append (margn2 + elementEnd ("mix:ChangeHistory") + EOL); + if (useChBuf) { + _writer.println (chBuf); + } + + } + + /** Convert the color space value (which is based on the TIFF + * PhotometricInterpretation convention) to one of the suggested + * values for MIX 2.0 */ + private String photometricInterpretationToString (int n) { + String s = "Unknown"; + switch (n) { + case 0: s = "WhiteIsZero"; break; + case 1: s = "BlackIsZero"; break; + case 2: s = "RGB"; break; + case 3: s = "PaletteColor"; break; + case 4: s = "TransparencyMask"; break; + case 5: s = "CMYK"; break; + case 6: s = "YCbCr"; break; + case 8: s = "CIELab"; break; + case 9: s = "ICCLab"; break; + case 10: s = "ITULab"; break; + case 32803: s = "CFA"; break; // used by DNG + case 34892: s = "LinearRaw"; break; // used by DNG + } + return s; + } + + + /** + * Display the audio metadata formatted according to + * the AES schema. + * @param aes AES audio metadata + */ + protected void showAESAudioMetadata (AESAudioMetadata aes) + { + _level += 3; + final String margin = getIndent (_level); + final String margn2 = margin + " "; + final String margn3 = margn2 + " "; + final String margn4 = margn3 + " "; + final String margn5 = margn4 + " "; + //final String margn6 = margn5 + " "; + + // ID strings. These are arbitrary, but must be unique + // within the document. + final String formatRegionID = "J1"; + final String faceRegionID = "J2"; + final String faceID = "J3"; + final String audioObjectID = "J4"; + final String streamIDBase = "J9"; + + _sampleRate = aes.getSampleRate (); + + final String [][] attrs = {{"xmlns:aes", "http://www.aes.org/audioObject"}, + {"xmlns:tcf", "http://www.aes.org/tcf"}, + {"xmlns:xsi", + "http://www.w3.org/2001/XMLSchema-instance"}, + {"ID", audioObjectID }, + {"analogDigitalFlag", + aes.getAnalogDigitalFlag ()}, + {"disposition", + "Validated by JHOVE"}, + {"schemaVersion","1.02b"}}; + _writer.println (margin + elementStart ("aes:audioObject", attrs)); + String s = aes.getFormat (); + if (s != null) { + String v = aes.getSpecificationVersion (); + String[][] fmattrs = new String[1][2]; + fmattrs[0][0] = "specificationVersion"; + if (v != null) { + fmattrs[0][1] = v; + } + else { + // Shouldn't happen + fmattrs[0][1] = ""; + } + _writer.println (margn2 + element + ("aes:format", fmattrs, s)); + } + s = aes.getAppSpecificData(); + if (s != null) { + _writer.println (margn2 + element + ("aes:appSpecificData", s)); + } + s = aes.getAudioDataEncoding (); + if (s != null) { + _writer.println (margn2 + element + ("aes:audioDataEncoding", s)); + } + int in = aes.getByteOrder (); + if (in != AESAudioMetadata.NULL) { + _writer.println (margn2 + element ("aes:byteOrder", + in == AESAudioMetadata.BIG_ENDIAN ? + "BIG_ENDIAN" : "LITTLE_ENDIAN")); + } + long lin = aes.getFirstSampleOffset (); + if (lin != AESAudioMetadata.NULL) { + _writer.println (margn2 + element ("aes:firstSampleOffset", + Long.toString (lin))); + } + String[] use = aes.getUse (); + if (use != null) { + String[][] uattrs = new String [][] + { { "useType", use[0] }, + { "otherType", use[1] }}; + _writer.println (margn2 + element ("aes:use", uattrs)); + } + s = aes.getPrimaryIdentifier(); + if (s != null) { + String t= aes.getPrimaryIdentifierType (); + String[][] idattrs = new String[1][2]; + idattrs[0][0] = "identifierType"; + if (t != null) { + idattrs[0][1] = t; + } + else { + // Shouldn't happen + idattrs[0][1] = ""; + } + _writer.println (margn2 + element + ("aes:primaryIdentifier", idattrs, s)); + } + + // Add the face information, which is mostly filler. + // In the general case, it can contain multiple Faces; + // this isn't supported yet. + List facelist = aes.getFaceList (); + if (!facelist.isEmpty ()) { + final String [] [] faceRegionAttrs = { + { "ID", faceRegionID }, + { "formatRef", formatRegionID }, + { "faceRef", faceID }, + { "label", "BuiltByJHOVE" } + }; + final String [] [] faceAttrs = { + { "direction", null }, + { "ID", faceID }, + { "audioObjectRef", audioObjectID }, + { "label", "Face" } + }; + AESAudioMetadata.Face f = + (AESAudioMetadata.Face) facelist.get(0); + faceAttrs[0] [1] = f.getDirection(); + _writer.println (margn2 + elementStart ("aes:face", faceAttrs)); + // Fill in a minimal time range. + AESAudioMetadata.TimeDesc startTime = f.getStartTime(); + if (startTime != null) { + _writer.println (margn3 + elementStart ("aes:timeline")); + writeAESTimeRange (margn3, startTime, f.getDuration()); + _writer.println (margn3 + elementEnd ("aes:timeline")); + } + + // For the present, assume just one face region + AESAudioMetadata.FaceRegion facergn = f.getFaceRegion (0); + _writer.println (margn3 + elementStart ("aes:region", faceRegionAttrs)); + _writer.println (margn4 + elementStart ("aes:timeRange")); + writeAESTimeRange (margn3, + facergn.getStartTime (), facergn.getDuration ()); + _writer.println (margn4 + elementEnd ("aes:timeRange")); + int nchan = aes.getNumChannels (); + if (nchan != AESAudioMetadata.NULL) { + _writer.println (margn4 + element ("aes:numChannels", + Integer.toString (nchan))); + } + String[] locs = aes.getMapLocations (); + for (int ch = 0; ch < nchan; ch++) { + // write a stream element for each channel + String [] [] streamAttrs = { + { "ID", streamIDBase + Integer.toString (ch) }, + { "label", "JHOVE" }, + { "faceRegionRef", faceRegionID } + + }; + _writer.println (margn4 + elementStart ("aes:stream", streamAttrs)); + String [] [] chanAttrs = { + { "channelNum", Integer.toString(ch) }, + { "mapLocation", locs[ch] } + }; + _writer.println (margn5 + element ("aes:channelAssignment", chanAttrs)); + _writer.println (margn4 + elementEnd ("aes:stream")); + } + _writer.println (margn3 + elementEnd ("aes:region")); + _writer.println (margn2+ elementEnd ("aes:face")); + } + + // In the general case, a FormatList can contain multiple + // FormatRegions. This doesn't happen with any of the current + // modules; if it's needed in the future, simply set up an + // iteration loop on formatList. + List flist = aes.getFormatList (); + if (!flist.isEmpty ()) { + AESAudioMetadata.FormatRegion rgn = + (AESAudioMetadata.FormatRegion) flist.get(0); + int bitDepth = rgn.getBitDepth (); + double sampleRate = rgn.getSampleRate (); + int wordSize = rgn.getWordSize (); + String[] bitRed = rgn.getBitrateReduction (); + // Build a FormatRegion subtree if at least one piece of data + // that goes into it is present. + if (bitDepth != AESAudioMetadata.NULL || + sampleRate != AESAudioMetadata.NILL || + wordSize != AESAudioMetadata.NULL) { + _writer.println (margn2 + elementStart ("aes:formatList")); + String[] [] frAttr = { { "ID", formatRegionID } }; + _writer.println (margn3 + elementStart ("aes:formatRegion", frAttr)); + if (bitDepth != AESAudioMetadata.NULL) { + _writer.println (margn4 + element ("aes:bitDepth", + Integer.toString (bitDepth))); + } + if (sampleRate != AESAudioMetadata.NILL) { + _writer.println (margn4 + element ("aes:sampleRate", + Double.toString (sampleRate))); + } + if (wordSize != AESAudioMetadata.NULL) { + _writer.println (margn4 + element ("aes:wordSize", + Integer.toString (wordSize))); + } + if (bitRed != null) { + _writer.println (margn4 + elementStart ("aes:bitrateReduction")); + _writer.println (margn5 + element + ("aes:codecName", bitRed[0])); + _writer.println (margn5 + element + ("aes:codecNameVersion", bitRed[1])); + _writer.println (margn5 + element + ("aes:codecCreatorApplication", bitRed[2])); + _writer.println (margn5 + element + ("aes:codecCreatorApplicationVersion", bitRed[3])); + _writer.println (margn5 + element + ("aes:codecQuality", bitRed[4])); + _writer.println (margn5 + element + ("aes:dataRate", bitRed[5])); + _writer.println (margn5 + element + ("aes:dataRateMode", bitRed[6])); + _writer.println (margn4 + elementEnd ("aes:bitrateReduction")); + } + _writer.println (margn3 + elementEnd ("aes:formatRegion")); + _writer.println (margn2 + elementEnd ("aes:formatList")); + } + } + /* This should go somewhere, but where? */ +// int nchan = aes.getNumChannels (); +// if (nchan != AESAudioMetadata.NULL) { +// _writer.println (margn2 + element ("aes:numChannels", +// Integer.toString (nchan))); +// } + + _writer.println (margin + elementEnd ("aes:audioObject")); + + _level -= 3; + } + + /* Break out the writing of a timeRangeType element. + * This always gives a start time of 0. This is all + * FAKE DATA for the moment. */ + private void writeAESTimeRange (String baseIndent, + AESAudioMetadata.TimeDesc start, + AESAudioMetadata.TimeDesc duration) + { + final String margn1 = baseIndent + " "; + final String margn2 = margn1 + " "; + final String margn3 = margn2 + " "; + final String [] [] attrs = { + { "tcf:frameCount", "30" }, + { "tcf:timeBase", "1000" }, + { "tcf:videoField", "FIELD_1" }, + { "tcf:countingMode", "NTSC_NON_DROP_FRAME" } + }; + final String [] [] ffAttrs = { + { "tcf:framing", "NOT_APPLICABLE" }, + { "xsi:type", "tcf:ntscFilmFramingType" } + }; + _writer.println (margn1 + elementStart ("tcf:startTime", attrs)); + _writer.println (margn2 + element ("tcf:hours", + Integer.toString (start.getHours ()))); + _writer.println (margn2 + element ("tcf:minutes", + Integer.toString (start.getMinutes ()))); + _writer.println (margn2 + element ("tcf:seconds", + Integer.toString (start.getSeconds ()))); + _writer.println (margn2 + element ("tcf:frames", + Integer.toString (start.getFrames ()) )); + String[] [] sampleAttrs = { + {"tcf:sampleRate", ""} + }; + double sr = start.getSampleRate (); + if (sr == 1.0) { + sr = _sampleRate; + } + sampleAttrs[0][1] = "S" + Integer.toString ((int) sr); + _writer.println (margn2 + elementStart ("tcf:samples", sampleAttrs)); + _writer.println (margn3 + element ("tcf:numberOfSamples", + Integer.toString (start.getSamples ()))); + _writer.println (margn2 + elementEnd("tcf:samples")); + _writer.println (margn2 + element ("tcf:filmFraming", ffAttrs)); + _writer.println (margn1 + elementEnd ("tcf:startTime")); + + if (duration != null) { + _writer.println (margn1 + elementStart ("tcf:duration", attrs)); + _writer.println (margn2 + element ("tcf:hours", + Integer.toString (duration.getHours ()))); + _writer.println (margn2 + element ("tcf:minutes", + Integer.toString (duration.getMinutes ()))); + _writer.println (margn2 + element ("tcf:seconds", + Integer.toString (duration.getSeconds ()))); + _writer.println (margn2 + element + ("tcf:frames", Integer.toString (duration.getFrames ()))); + sr = duration.getSampleRate (); + if (sr == 1.0) { + sr = _sampleRate; + } + sampleAttrs[0][1] = "S" + Integer.toString ((int) sr); + _writer.println (margn2 + elementStart ("tcf:samples", + sampleAttrs)); + _writer.println (margn3 + element ("tcf:numberOfSamples", + Integer.toString (duration.getSamples ()) )); + _writer.println (margn2 + elementEnd("tcf:samples")); + _writer.println (margn2 + element ("tcf:filmFraming", ffAttrs)); + _writer.println (margn1 + elementEnd ("tcf:duration")); + } + } + + /* Clean up a URI string by escaping forbidden characters. + * We assume (perhaps dangerously) that a % is the start of + * an already escaped hexadecimal sequence. */ + private String cleanURIString (String uri) { + StringBuffer sb = new StringBuffer (uri.length() * 2); + boolean change = false; + for (int i = 0; i < uri.length (); i++) { + char c = uri.charAt (i); + if ((c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || + (c == '%') || // assume it's an escape + ("-_.!~*'();/?:@=+$,".indexOf (c) >= 0)) { + sb.append (c); + } + else { + int cval = (int) c; + + // More significant hex digit + int mshd = (cval >> 4); + if (mshd >= 10) { + mshd += (int) 'A' - 10; + } + else { + mshd += (int) '0'; + } + sb.append ('%'); + sb.append ((char) mshd); + + // Less significant hex digit + int lshd = (cval & 0X0F); + if (lshd >= 10) { + lshd += (int) 'A' - 10; + } + else { + lshd += (int) '0'; + } + sb.append ((char) lshd); + change = true; + } + } + // For efficiency, return the original string + // if nothing changed. + if (change) { + return sb.toString (); + } + else { + return uri; + } + } + + /** Appends a Rational value to a StringBuffer */ + public void rationalToString (StringBuffer buf, String tag, String margin, + Rational r) + { + String margn2 = margin + " "; + + long numer = r.getNumerator (); + long denom = r.getDenominator (); + buf.append (margin + elementStart (tag) + EOL); + buf.append (margn2 + element ("mix:numerator", Long.toString (numer)) + + EOL); + if (denom != 1L) { + buf.append (margn2 + element ("mix:denominator", + Long.toString (denom)) + EOL); + } + buf.append (margin + elementEnd (tag) + EOL); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/handler/audit/AuditCount.java b/jhove/classes/edu/harvard/hul/ois/jhove/handler/audit/AuditCount.java new file mode 100644 index 00000000..c8df5ddf --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/handler/audit/AuditCount.java @@ -0,0 +1,109 @@ +/********************************************************************** + * Audit output handler + * Copyright 2004 by the President and Fellows of Harvard College + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.handler.audit; + +//import java.io.*; + +/** + * Count object for the JHOVE Audit output handler. + */ +public class AuditCount +{ + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + /** Number of files not processed. */ + protected int _notProcessed; + + /** Number of valid files. */ + protected int _valid; + + /** Number of well-formed files. */ + protected int _wellFormed; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * Instantiate a AuditCount object. + */ + public AuditCount () + { + _notProcessed = 0; + _valid = 0; + _wellFormed = 0; + } + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + * + * Accessor methods. + ******************************************************************/ + + /** Returns the total number of files not processed. */ + public int getNotProcessed () + { + return _notProcessed; + } + + /** Returns the total number of valid or well-formed + files. */ + public int getTotal () + { + return _valid + _wellFormed; + } + + /** Returns the total number of valid files. */ + public int getValid () + { + return _valid; + } + + /** Returns the total number of well-formed files. */ + public int getWellFormed () + { + return _wellFormed; + } + + /****************************************************************** + * Mutator methods. + ******************************************************************/ + + /** Sets the count of files that are not processed. */ + public void setNotProcessed (int notProcessed) + { + _notProcessed = notProcessed; + } + + /** Sets the count of valid files. */ + public void setValid (int valid) + { + _valid = valid; + } + + /** Sets the count of well-formed files. */ + public void setWellFormed (int wellFormed) + { + _wellFormed = wellFormed; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/handler/audit/AuditState.java b/jhove/classes/edu/harvard/hul/ois/jhove/handler/audit/AuditState.java new file mode 100644 index 00000000..b014b126 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/handler/audit/AuditState.java @@ -0,0 +1,126 @@ +/********************************************************************** + * Audit output handler + * Copyright 2004 by the President and Fellows of Harvard College + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.handler.audit; + +import java.io.*; + +/** + * State object for the JHOVE Audit output handler. + */ +public class AuditState + extends AuditCount + implements Cloneable +{ + /****************************************************************** + * PROTECTED INSTANCE FIELDS. + ******************************************************************/ + + /** Directory pathname. */ + protected String _directory; + + /** Number of files not found. */ + protected int _notFound; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * Instantiate a AuditState object. + */ + public AuditState (String directory) + { + super (); + init (directory); + } + + /** + * Initializes to a specified directory and clears counters. + */ + protected void init (String directory) + { + try { + File file = new File (directory); + _directory = file.getCanonicalPath (); + } + catch (Exception e) { + _directory = directory; + } + + _notFound = 0; + _valid = 0; + _wellFormed = 0; + } + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + ******************************************************************/ + + /** + * Creates and returns a copy of this object. + */ + public Object clone (String directory) + throws CloneNotSupportedException + { + AuditState state = (AuditState) super.clone (); + state.init (directory); + + return state; + } + + /****************************************************************** + * Accessor methods. + ******************************************************************/ + + /** Returns the directory path. */ + public String getDirectory () + { + return _directory; + } + + /** Returns the number of files not found. */ + public int getNotFound () + { + return _notFound; + } + + /****************************************************************** + * Mutator methods. + ******************************************************************/ + + /** Sets the directory path. */ + public void setDirectory (String directory) + { + try { + File file = new File (directory); + _directory = file.getCanonicalPath (); + } + catch (Exception e) { + _directory = directory; + } + } + + /** Sets the count of files not found. */ + public void setNotFound (int notFound) + { + _notFound = notFound; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/handler/audit/build.xml b/jhove/classes/edu/harvard/hul/ois/jhove/handler/audit/build.xml new file mode 100644 index 00000000..1bf72894 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/handler/audit/build.xml @@ -0,0 +1,32 @@ + + Audit output handler build file + Jhove - JSTOR/Harvard Object Validation Environment + Version 1.0 2004-12-09 + Copyright 2004 by JSTOR and the President and Fellows of Harvard College + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/handler/audit/package.html b/jhove/classes/edu/harvard/hul/ois/jhove/handler/audit/package.html new file mode 100644 index 00000000..3a807793 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/handler/audit/package.html @@ -0,0 +1,24 @@ + + + + + + + +Contains the supporting classes for the JHOVE audit output handler. + +
+ + + + + + + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/handler/build.xml b/jhove/classes/edu/harvard/hul/ois/jhove/handler/build.xml new file mode 100644 index 00000000..c53a9441 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/handler/build.xml @@ -0,0 +1,65 @@ + + Handler build file + Jhove - JSTOR/Harvard Object Validation Environment + Version 1.0 2004-02-03 + Copyright 2004 by JSTOR and the President and Fellows of Harvard College + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + + +
diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/handler/package.html b/jhove/classes/edu/harvard/hul/ois/jhove/handler/package.html new file mode 100644 index 00000000..bbad25e7 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/handler/package.html @@ -0,0 +1,25 @@ + + + + + + + +Contains the main classes for JHOVE output handlers. +All module classes are subclasses of HandlerBase. + +
+ + + + + + + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/jhoveConfig.xsd b/jhove/classes/edu/harvard/hul/ois/jhove/jhoveConfig.xsd new file mode 100644 index 00000000..ce221b73 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/jhoveConfig.xsd @@ -0,0 +1,118 @@ + + + + + + + + The JHOVE configuration file consists of the JHOVE home directory, optional temporary directory and buffer size, an unbounded, ordered set of modules, followed by an optional unbounded set of output handlers. It has a single required attribute, version, which specifies the schema version to which a configuration file conforms. + + + + + + JHOVE home directory. + + + + + Default character encoding used by the output handlers. + + + + + Optional directory in which temporary files are created. Default value is the current working directory. + + + + + Optional buffer size used for buffered I/O. Defeault value is 131072. + + + + + The version of the MIX schema to be used in metadata output. Default value is "0.2". + + + + + The number of initial bytes to search when matching internal signatures. + + + + + A module consists of a fully package qualified class name, followed by an optional initialization parameter. + + + + + + A class consists of the fully package qualified class name for the module. Note that the class must be found on the Java CLASSPATH at the time that JHOVE is invoked. + + + + + A module initialization value is passed to the module once at the time it is first instantiated. + + + + + A module parameter value that is passed to the module immediately prior to each invocation of the parse() method. + + + + + + + + An output handler consists of a fully package qualified class name. + + + + + + A class consists of the fully package qualified class name for the output handler. Note that the class must be found on the Java CLASSPATH at the time that JHOVE is invoked. + + + + + A handler initialization value that is passed to the class at the time it is first instantiated. + + + + + A handler parameter value that is passed to the module immediately prior to each invocation of the showHeader() method. + + + + + + + + Logging level. + + + + + + + + + + + + + + + + + + + The version attribute specifies the schema version to which a configuration file conforms. + + + + + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/AiffModule.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/AiffModule.java new file mode 100644 index 00000000..1be69d04 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/AiffModule.java @@ -0,0 +1,763 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003-2007 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module; + +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.aiff.*; +import edu.harvard.hul.ois.jhove.module.iff.*; + +import java.io.*; +import java.util.*; + +/** + * Module for identification and validation of AIFF files. + * Supports AIFF and AIFF-C. + * + * @author Gary McGath + */ +public class AiffModule + extends ModuleBase +{ + /****************************************************************** + * PRIVATE Instance FIELDS. + ******************************************************************/ + + /* Checksummer object */ + protected Checksummer _ckSummer; + + /* Input stream wrapper which handles checksums */ + protected ChecksumInputStream _cstream; + + /* Data input stream wrapped around _cstream */ + protected DataInputStream _dstream; + + /* Top-level metadata property */ + protected Property _metadata; + + /* Top-level property list */ + protected List _propList; + + /* AES audio metadata to go into AIFF metadata */ + protected AESAudioMetadata _aesMetadata; + + /* List of Annotation Chunk properties */ + protected List _annotationList; + + /* List of MIDI Chunk properties */ + protected List _midiList; + + /* List of Saxel properties */ + protected List _saxelList; + + /* Bytes remaining to be read. */ + protected long bytesRemaining; + + /* Flag to check for multiple sound chunks */ + protected boolean soundChunkSeen; + + /* Flag to check for exactly one format version chunk */ + protected boolean formatVersionChunkSeen; + + /* Flag to check for exactly one instrument chunk */ + protected boolean instrumentChunkSeen; + + /* Flag to check for exactly one common chunk */ + protected boolean commonChunkSeen; + + /* Flag to check for exactly one comments chunk */ + protected boolean commentsChunkSeen; + + /* Flag to check for exactly one name chunk */ + protected boolean nameChunkSeen; + + /* Flag to check for exactly one author chunk */ + protected boolean authorChunkSeen; + + /* Flag to check for exactly one copyright chunk */ + protected boolean copyrightChunkSeen; + + /* Flag to check for exactly one marker chunk */ + protected boolean markerChunkSeen; + + /* Flag to check for exactly one audio recording chunk */ + protected boolean audioRecChunkSeen; + + /* Flag to note that first sample offset has been recorded */ + protected boolean firstSampleOffsetMarked; + + /* File type */ + protected int fileType; + + /* Endianness for current file (not necessarily same as _bigEndian) */ + protected boolean thisFileBigEndian; + + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + + /* Chunk data orientation is big-endian. */ + private static final boolean BIGENDIAN = true; + + /* Values for fileType */ + public final static int + AIFFTYPE = 1, + AIFCTYPE = 2; + + /* Fixed value for first 4 bytes */ + private static final int[] sigByte = + { 0X46, 0X4F, 0X52, 0X4D }; + + private static final String NAME = "AIFF-hul"; + private static final String RELEASE = "1.3"; + private static final int [] DATE = {2006, 9, 5}; + private static final String [] FORMAT = { + "AIFF", "Audio Interchange File Format" + }; + private static final String COVERAGE = "AIFF 1.3, AIFF-C"; + private static final String [] MIMETYPE = { + "audio/x-aiff", "application/aiff" + }; + private static final String WELLFORMED = + "Magic number: \"FORM\" at byte offset 0; \"AIFF\"" + + "(for AIFF) or \"AIFC\" (for AIFF-C) at offset 8; one Form chunk " + + "containing one Common chunk and at most one Sound Data chunk " + + "(if numSampleFrames > 0); at most one instance of each of the " + + "following optional chunks: Marker, Instrument, Audio Recording, " + + "Comments, Name, Author, Copyright; all chunks required by a given " + + "profile exist in the file; all chunk structures are well-formed: " + + "a four ASCII character ID, followed by a 32 signed integer size, " + + "followed by a size byte data block (if size is odd, then the data " + + "block includes a final padding byte of value 0x00); and no data " + + "exist before the first byte of the chunk or after the " + + "last byte of the last chunk"; + private static final String VALIDITY = "The file is well-formed"; + private static final String REPINFO = + "Properties capturing the technical attributes of the audio " + + "from all chunks"; + private static final String NOTE = null; + private static final String RIGHTS = "Copyright 2004-2007 by JSTOR and " + + "the President and Fellows of Harvard College. " + + "Released under the GNU Lesser General Public License."; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + /** + * Instantiates an AiffModule object. + */ + public AiffModule () + { + super (NAME, RELEASE, DATE, FORMAT, COVERAGE, MIMETYPE, WELLFORMED, + VALIDITY, REPINFO, NOTE, RIGHTS, false); + Agent agent = new Agent ("Harvard University Library", + AgentType.EDUCATIONAL); + agent.setAddress ("Office for Information Systems, " + + "90 Mt. Auburn St., " + + "Cambridge, MA 02138"); + agent.setTelephone ("+1 (617) 495-3724"); + agent.setEmail("jhove-support@hulmail.harvard.edu"); + _vendor = agent; + + + Document doc = new Document ("Audio Interchange File Format: " + + "\"AIFF\", A Standard for Sampled Sound " + + "Files, Version 1.3", DocumentType.REPORT); + agent = new Agent ("Apple Computer, Inc.", + AgentType.COMMERCIAL); + agent.setAddress ("1 Infinite Loop, Cupertino, CA 95014"); + agent.setTelephone("(408) 996-1010"); + agent.setWeb ("http://www.apple.com/"); + doc.setAuthor (agent); + doc.setDate ("1989-01-04"); + doc.setIdentifier (new Identifier ("http://developer.apple.com/documentation/QuickTime/INMAC/SOUND/imsoundmgr.30.htm#pgfId=3190", + IdentifierType.URL)); + _specification.add (doc); + + doc = new Document ("Audio Interchange File Format AIFF-C: " + + "A revision to include compressed audio data", + DocumentType.REPORT); + doc.setAuthor (agent); + doc.setDate ("1991-08-26"); + doc.setNote ("*** DRAFT ***"); // Asterisks as in the printed document + _specification.add (doc); + + Signature sig = new ExternalSignature ("AIFF", SignatureType.FILETYPE, + SignatureUseType.OPTIONAL); + _signature.add (sig); + sig = new ExternalSignature ("AIFC", SignatureType.FILETYPE, + SignatureUseType.OPTIONAL); + _signature.add (sig); + sig = new ExternalSignature (".aif", SignatureType.EXTENSION, + SignatureUseType.OPTIONAL); + _signature.add (sig); + sig = new ExternalSignature (".aifc", SignatureType.EXTENSION, + SignatureUseType.OPTIONAL, + "For AIFF-C profile"); + _signature.add (sig); + + sig = new InternalSignature ("FORM", SignatureType.MAGIC, + SignatureUseType.MANDATORY, 0); + _signature.add (sig); + + sig = new InternalSignature ("AIFF", SignatureType.MAGIC, + SignatureUseType.OPTIONAL, 8, + "For AIFF profile"); + _signature.add (sig); + + sig = new InternalSignature ("AIFC", SignatureType.MAGIC, + SignatureUseType.OPTIONAL, 0, + "For AIFF-C profile"); + _signature.add (sig); + + _bigEndian = true; + } + + /** + * Parses the content of a purported AIFF digital object and stores the + * results in RepInfo. + * + * + * @param stream An InputStream, positioned at its beginning, + * which is generated from the object to be parsed + * @param info A fresh RepInfo object which will be modified + * to reflect the results of the parsing + * @param parseIndex Must be 0 in first call to parse. If + * parse returns a nonzero value, it must be + * called again with parseIndex + * equal to that return value. + */ + public int parse (InputStream stream, RepInfo info, int parseIndex) + throws IOException + { + initParse (); + info.setFormat (_format[0]); + info.setMimeType (_mimeType[0]); + info.setModule (this); + _aesMetadata.setPrimaryIdentifier(info.getUri()); + if (info.getURLFlag ()) { + _aesMetadata.setOtherPrimaryIdentifierType("URI"); + } + else { + _aesMetadata.setPrimaryIdentifierType(AESAudioMetadata.FILE_NAME); + } + + /* We may have already done the checksums while converting a + temporary file. */ + _ckSummer = null; + if (_je != null && _je.getChecksumFlag () && + info.getChecksum ().size () == 0) { + _ckSummer = new Checksummer (); + _cstream = new ChecksumInputStream (stream, _ckSummer); + _dstream = getBufferedDataStream (_cstream, _je != null ? + _je.getBufferSize () : 0); + } + else { + _dstream = getBufferedDataStream (stream, _je != null ? + _je.getBufferSize () : 0); + } + + try { + // Check the start of the file for the right opening bytes + for (int i = 0; i < 4; i++) { + int ch = readUnsignedByte(_dstream, this); + if (ch != sigByte[i]) { + info.setMessage(new ErrorMessage ("Document does not start with AIFF FORM Chunk", 0)); + info.setWellFormed (RepInfo.FALSE); + return 0; + } + } + /* If we got this far, take note that the signature is OK. */ + info.setSigMatch(_name); + + // Get the length of the Form chunk. This includes all + // the subsequent chunks in the file, but excludes the + // header ("FORM" and the length itself). +// bytesRemaining = readUnsignedInt (_dstream, _bigEndian, this); + bytesRemaining = readUnsignedInt (_dstream, BIGENDIAN, this); + + // Read the file type. + if (!readFileType (info)) { + return 0; + } + + while (bytesRemaining > 0) { + if (!readChunk (info)) { + break; + } + } + } + catch (EOFException e) { + info.setWellFormed (RepInfo.FALSE); + info.setMessage (new ErrorMessage + ("Unexpected EOF", _nByte)); + return 0; + } + + if (!commonChunkSeen) { + info.setWellFormed (RepInfo.FALSE); + info.setMessage (new ErrorMessage + ("Document does not contain a Common Chunk")); + } + if (fileType == AIFCTYPE && !formatVersionChunkSeen) { + info.setWellFormed (RepInfo.FALSE); + info.setMessage (new ErrorMessage + ("AIFF-C document must contain a Format Version Chunk")); + } + if (info.getWellFormed () != RepInfo.TRUE) { + return 0; + } + + /* This file looks OK. */ + if (_ckSummer != null){ + /* We may not have actually hit the end of file. If we're calculating + * checksums on the fly, we have to read and discard whatever is + * left, so it will get checksummed. */ + for (;;) { + try { + int n = skipBytes (_dstream, 2048, this); + if (n == 0) { + break; + } + } + catch (Exception e) { + break; + } + } + info.setSize (_cstream.getNBytes ()); + info.setChecksum (new Checksum (_ckSummer.getCRC32 (), + ChecksumType.CRC32)); + String value = _ckSummer.getMD5 (); + if (value != null) { + info.setChecksum (new Checksum (value, ChecksumType.MD5)); + } + if ((value = _ckSummer.getSHA1 ()) != null) { + info.setChecksum (new Checksum (value, ChecksumType.SHA1)); + } + } + + if (fileType == AIFFTYPE) { + info.setProfile("AIFF"); + } + else if (fileType == AIFCTYPE) { + info.setProfile ("AIFF-C"); + } + + _aesMetadata.setByteOrder (thisFileBigEndian ? AESAudioMetadata.BIG_ENDIAN : + AESAudioMetadata.LITTLE_ENDIAN); + + // Most properties were added by the Chunks. The Annotations, Saxel + // and MIDIData properties could have come from multiple chunks, + // and these were added to lists which we now make into Properties + // if there's anything to report. + if (!_annotationList.isEmpty ()) { + _propList.add (new Property ("Annotations", + PropertyType.PROPERTY, + PropertyArity.LIST, + _annotationList)); + } + if (!_midiList.isEmpty ()) { + _propList.add (new Property ("MIDIData", + PropertyType.PROPERTY, + PropertyArity.LIST, + _midiList)); + } + if (!_saxelList.isEmpty ()) { + _propList.add (new Property ("Saxels", + PropertyType.PROPERTY, + PropertyArity.LIST, + _saxelList)); + } + info.setProperty (_metadata); + + return 0; + } + + /** Sets the endian-ness. true indicates + * big-endian, and false means little-endian. + * This is needed because chunk data can change the + * usual little-endian byte order to big-endian. + */ + public void setEndian (boolean bigEndian) + { + thisFileBigEndian = bigEndian; + } + + /** Adds a Property to the AIFF metadata. */ + public void addAiffProperty (Property prop) + { + _propList.add (prop); + } + + /** Adds an Annotation Property to the annotation list. + * This will get put into an Annotations Property. */ + public void addAnnotation (Property prop) + { + _annotationList.add (prop); + } + + /** Adds a Saxel Property to the saxel list. + * This will get put into a Saxels Property. */ + public void addSaxel (Property prop) + { + _saxelList.add (prop); + } + + /** Adds a MIDI Property to the MIDI list. + * This will get put into a MIDIData Property. */ + public void addMidi (Property prop) + { + _midiList.add (prop); + } + + /** + * Initializes the state of the module for parsing. + */ + protected void initParse() + { + super.initParse (); + + thisFileBigEndian = _bigEndian; + _propList = new LinkedList (); + _metadata = new Property ("AIFFMetadata", + PropertyType.PROPERTY, + PropertyArity.LIST, + _propList); + + firstSampleOffsetMarked = false; + _aesMetadata = new AESAudioMetadata (); + _aesMetadata.setAnalogDigitalFlag("FILE_DIGITAL"); + _aesMetadata.setFormat ("AIFF"); // Further data may modify this value + _aesMetadata.setSpecificationVersion("1.3 (1989-01-04)"); // Ditto + _aesMetadata.setAudioDataEncoding ("PCM"); +// _aesMetadata.setBitrateReduction ("PCM", "", "", "", +// "LOSSY", "UNKNOWN", "FIXED"); + // Per Bugzilla 772, default is to omit bitrateReduction info + _aesMetadata.clearBitrateReduction(); + _aesMetadata.setUse ("OTHER", "JHOVE_validation"); + _aesMetadata.setDirection ("NONE"); + + _propList.add (new Property ("AESAudioMetadata", + PropertyType.AESAUDIOMETADATA, + _aesMetadata)); + + // Create a List for accumulating properties from Annotation Chunks + _annotationList = new LinkedList (); + + // Create a List for accumulating properties from MIDI Chunks + _midiList = new LinkedList (); + + // Create a List for accumulating properties from SAXL chunks + _saxelList = new LinkedList (); + + // Most chunk types are allowed to occur only once, + // and a few must occur exactly once. + // Clear flags for whether they have been seen. + soundChunkSeen = false; + commonChunkSeen = false; + markerChunkSeen = false; + formatVersionChunkSeen = false; + instrumentChunkSeen = false; + commentsChunkSeen = false; + nameChunkSeen = false; + authorChunkSeen = false; + audioRecChunkSeen = false; + copyrightChunkSeen = false; + } + + /** One-argument version of readUnsignedInt. */ + public long readUnsignedInt (DataInputStream stream) + throws IOException + { + return readUnsignedInt (stream, BIGENDIAN, this); + } + + /** One-argument version of readUnsignedShort. + */ + public int readUnsignedShort (DataInputStream stream) + throws IOException + { + return readUnsignedShort (stream, BIGENDIAN, this); + } + + /** One-argument version of readSignedShort. + */ + public int readSignedShort (DataInputStream stream) + throws IOException + { + return readSignedShort (stream, true, this); + } + + /** This reads an 80-bit SANE number, aka IEEE 754 + * extended double. + */ + public double read80BitDouble (DataInputStream stream) + throws IOException + { + byte[] buf = new byte[10]; + readByteBuf(_dstream, buf, this); + ExtDouble xd = new ExtDouble (buf); + return xd.toDouble(); + } + + /** + * Reads 4 bytes and concatenates them into a String. + * This pattern is used for ID's of various kinds. + */ + public String read4Chars(DataInputStream stream) throws IOException + { + StringBuffer sbuf = new StringBuffer(4); + for (int i = 0; i < 4; i++) { + int ch = readUnsignedByte(stream, this); + sbuf.append((char) ch); + } + return sbuf.toString(); + } + + /** Reads a Pascal string. + * A Pascal string is one whose count is given in the first + * byte. The count is exclusive of the count byte itself. + * A Pascal string can have a maximum of 255 characters. + * If the count of a Pascal string is even (meaning the total + * number of bytes is odd), there will be a pad byte to skip, + * so that the next item can start on an even boundary. + * + * We assume the string will be in ASCII or Macintosh encoding. + */ + public String readPascalString (DataInputStream stream) throws IOException + { + int byteCnt = readUnsignedByte (stream, this); + byte[] byteBuf = new byte[byteCnt]; + readByteBuf (_dstream, byteBuf, this); + if ((byteCnt & 1) == 0) { + skipBytes (_dstream, 1, this); + } + return new String (byteBuf, "MacRoman"); + } + + /** Converts a Macintosh-style timestamp (seconds since + * January 1, 1904) into a Java date. The timestamp is + * treated as a time in the default localization. + * Depending on that localization, + * there may be some variation in the exact hour of the date + * returned, e.g., due to daylight savings time. + * + */ + public Date timestampToDate (long timestamp) + { + Calendar cal = Calendar.getInstance (); + cal.set (1904, 0, 1, 0, 0, 0); + + // If we add the seconds directly, we'll truncate the long + // value when converting to int. So convert to hours plus + // residual seconds. + int hours = (int) (timestamp / 3600); + int seconds = (int) (timestamp - (long) hours * 3600L); + cal.add (Calendar.HOUR_OF_DAY, hours); + cal.add (Calendar.SECOND, seconds); + return cal.getTime (); + } + + /** Returns the filetype, which is AIFFTYPE or AIFCTYPE. */ + public int getFileType () + { + return fileType; + } + + /** Marks the first sample offset as the current byte position, + * if it hasn't already been marked. + * The SSND chunk offset value must be added to the current + * byte offset for a correct value. + */ + public void markFirstSampleOffset (long offset) + { + if (!firstSampleOffsetMarked) { + firstSampleOffsetMarked = true; + _aesMetadata.setFirstSampleOffset (_nByte + offset); + } + } + + /** Reads the file type. + * Broken out from parse(). + * If it is not a valid file type, returns false. + */ + protected boolean readFileType (RepInfo info) throws IOException + { + String typ = read4Chars (_dstream); + bytesRemaining -= 4; + if ("AIFF".equals (typ)) { + fileType = AIFFTYPE; + return true; + } + else if ("AIFC".equals (typ)) { + fileType = AIFCTYPE; + _aesMetadata.setFormat ("AIFF-C"); + _aesMetadata.setSpecificationVersion ("Draft 1991-08-26"); + return true; + } + else { + info.setMessage (new ErrorMessage + ("File type in Form Chunk is not AIFF or AIFC", _nByte)); + info.setWellFormed (RepInfo.FALSE); + return false; + } + } + + /** Reads an AIFF Chunk. + * + */ + protected boolean readChunk (RepInfo info) throws IOException + { + Chunk chunk = null; + ChunkHeader chunkh = new ChunkHeader (this, info); + if (!chunkh.readHeader(_dstream)) { + return false; + } + int chunkSize = (int) chunkh.getSize (); + bytesRemaining -= chunkSize + 8; + + String id = chunkh.getID (); + if ("FVER".equals (id)) { + if (formatVersionChunkSeen) { + dupChunkError (info, "Format Version"); + } + chunk = new FormatVersionChunk (this, chunkh, _dstream); + formatVersionChunkSeen = true; + } + else if ("APPL".equals (id)) { + chunk = new ApplicationChunk (this, chunkh, _dstream); + // Any number of application chunks is ok + } + else if ("COMM".equals (id)) { + if (commonChunkSeen) { + dupChunkError (info, "Common"); + } + chunk = new CommonChunk (this, chunkh, _dstream); + commonChunkSeen = true; + } + else if ("SSND".equals (id)) { + // Watch for multiple sound chunks + if (soundChunkSeen) { + dupChunkError (info, "Sound"); + } + else { + chunk = new SoundDataChunk (this, chunkh, _dstream); + soundChunkSeen = true; + } + } + else if ("COMT".equals (id)) { + if (commentsChunkSeen) { + dupChunkError (info, "Comments"); + } + chunk = new CommentsChunk (this, chunkh, _dstream); + commentsChunkSeen = true; + } + else if ("INST".equals (id)) { + if (instrumentChunkSeen) { + dupChunkError (info, "Instrument"); + } + chunk = new InstrumentChunk (this, chunkh, _dstream); + instrumentChunkSeen = true; + } + else if ("MARK".equals (id)) { + if (markerChunkSeen) { + dupChunkError (info, "Marker"); + } + else { + chunk = new MarkerChunk (this, chunkh, _dstream); + markerChunkSeen = true; + } + } + else if ("MIDI".equals (id)) { + chunk = new MidiChunk (this, chunkh, _dstream); + // Any number of MIDI chunks are allowed + } + else if ("NAME".equals (id)) { + if (nameChunkSeen) { + dupChunkError (info, "Name"); + } + else { + chunk = new NameChunk (this, chunkh, _dstream); + nameChunkSeen = true; + } + } + else if ("AUTH".equals (id)) { + if (authorChunkSeen) { + dupChunkError (info, "Author"); + } + else { + chunk = new AuthorChunk (this, chunkh, _dstream); + authorChunkSeen = true; + } + } + else if ("(c) ".equals (id)) { + if (copyrightChunkSeen) { + dupChunkError (info, "Copyright"); + } + else { + chunk = new CopyrightChunk (this, chunkh, _dstream); + copyrightChunkSeen = true; + } + } + else if ("AESD".equals (id)) { + if (audioRecChunkSeen) { + dupChunkError (info, "Audio Recording"); + } + else { + chunk = new AudioRecChunk (this, chunkh, _dstream); + audioRecChunkSeen = true; + } + } + else if ("SAXL".equals (id)) { + chunk = new SaxelChunk (this, chunkh, _dstream); + // Multiple saxel chunks are ok + } + else if ("ANNO".equals (id)) { + chunk = new AnnotationChunk (this, chunkh, _dstream); + // Multiple annotations are OK + } + else { + info.setMessage (new InfoMessage + ("Chunk type '" + id + "' ignored", _nByte)); + } + if (chunk != null) { + try { + if (!chunk.readChunk (info)) { + return false; + } + } + catch (JhoveException e) { + info.setMessage(new ErrorMessage (e.getMessage())); + info.setWellFormed (false); + return false; + } + } + else { + // Other chunk types are legal, just skip over them + skipBytes (_dstream, chunkSize, this); + } + if ((chunkSize & 1) != 0) { + // Must come out to an even byte boundary + skipBytes (_dstream, 1, this); + --bytesRemaining; + } + return true; + } + + /** Returns the module's AES metadata. */ + public AESAudioMetadata getAESMetadata () + { + return _aesMetadata; + } + + /* Factor out the reporting of duplicate chunks. */ + protected void dupChunkError (RepInfo info, String chunkName) + { + info.setMessage (new ErrorMessage + ("Multiple " + chunkName + " Chunks not permitted", + _nByte)); + info.setValid (false); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/AsciiModule.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/AsciiModule.java new file mode 100644 index 00000000..7c725398 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/AsciiModule.java @@ -0,0 +1,432 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003-2007 by JSTOR and the President and Fellows of Harvard College + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module; + +import edu.harvard.hul.ois.jhove.*; + +import java.io.*; +import java.util.*; + +/** + * Module for analysis of content as an ASCII stream. + */ +public class AsciiModule + extends ModuleBase +{ + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + + private static final String NAME = "ASCII-hul"; + private static final String RELEASE = "1.3"; + private static final int [] DATE = {2006, 9, 5}; + private static final String [] FORMAT = { + "ASCII", "US-ASCII", "ANSI X3.4", "ISO 646" + }; + private static final String COVERAGE = null; + private static final String [] MIMETYPE = {"text/plain; charset=US-ASCII"}; + private static final String WELLFORMED = "An ASCII object is well-formed "+ + "if each byte is between 0x00 and 0x7F"; + private static final String VALIDITY = null; + private static final String REPINFO = "Additional representation information includes: line ending and control characters"; + private static final String NOTE = null; + private static final String RIGHTS = "Copyright 2003-2007 by JSTOR and " + + "the President and Fellows of Harvard College. " + + "Released under the GNU Lesser General Public License."; + + private static final int CR = 0x0d; // '\r' + private static final int LF = 0x0a; // '\n' + + /* Mnemonics for control characters (0-1F) */ + private static final String controlCharMnemonics[] = { + "NUL (0x00)", "SOH (0x01)", "STX (0x02)", "ETX (0x03)", + "EOT (0x04)", "ENQ (0x05)", "ACK (0x06)", "BEL (0x07)", + "BS (0x08)", "TAB (0x09)", "LF (0x0A)", "VT (0x0B)", + "FF (0x0C)", "CR (0x0D)", "SO (0x0E)", "SI (0x0F)", + "DLE (0x10)", "DC1 (0x11)", "DC2 (0x12)", "DC3 (0x13)", + "DC4 (0x14)", "NAK (0x15)", "SYN (0x16)", "ETB (0x17)", + "CAN (0x18)", "EM (0x19)", "SUB (0x1A)", "ESC (0x1B)", + "FS (0x1C)", "GS (0x1D)", "RS (0x1E)", "US (0x1F)" + }; + + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + /* Input stream wrapper which handles checksums */ + protected ChecksumInputStream _cstream; + + /* Data input stream wrapped around _cstream */ + protected DataInputStream _dstream; + + protected boolean _lineEndCR; + protected boolean _lineEndLF; + protected boolean _lineEndCRLF; + protected int _prevChar; + protected Map _controlCharMap; + + /* Flag to know if the property TextMDMetadata is to be added */ + protected boolean _withTextMD = false; + /* Hold the information needed to generate a textMD metadata fragment */ + protected TextMDMetadata _textMD; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * Creates an AsciiModule. + */ + public AsciiModule () + { + super (NAME, RELEASE, DATE, FORMAT, COVERAGE, MIMETYPE, WELLFORMED, + VALIDITY, REPINFO, NOTE, RIGHTS, false); + + Agent agent = new Agent ("Harvard University Library", + AgentType.EDUCATIONAL); + agent.setAddress ("Office for Information Systems, " + + "90 Mt. Auburn St., " + + "Cambridge, MA 02138"); + agent.setTelephone ("+1 (617) 495-3724"); + agent.setEmail("jhove-support@hulmail.harvard.edu"); + _vendor = agent; + + Document doc = new Document ("Information technology -- ISO 7-bit " + + "coded character set for information " + + "interchange", DocumentType.STANDARD); + agent = new Agent ("ISO", AgentType.STANDARD); + agent.setAddress ("1, rue de Varembe, Casa postale 56, " + + "CH-1211, Geneva 20, Switzerland"); + agent.setTelephone ("+41 22 749 01 11"); + agent.setFax ("+41 22 733 34 30"); + agent.setEmail ("iso@iso.ch"); + agent.setWeb ("http://www.iso.org/"); + doc.setPublisher (agent); + doc.setDate ("1991"); + doc.setIdentifier (new Identifier ("ISO/IEC 646:1991", + IdentifierType.ISO)); + _specification.add (doc); + + doc = new Document ("Information Systems -- Coded Character Sets " + + "7-Bit American National Standard Code for " + + "Information Interchange (7-Bit ASCII)", + DocumentType.STANDARD); + agent = new Agent ("ANSI", AgentType.STANDARD); + agent.setAddress ("1819 L Street, NW, Washington, DC 20036"); + agent.setTelephone ("+1 (202) 293-8020"); + agent.setFax ("+1 (202) 293-9287"); + agent.setEmail ("info@ansi.org"); + agent.setWeb ("http://www.ansi.org/"); + doc.setPublisher (agent); + doc.setDate ("1986-12-30"); + doc.setIdentifier (new Identifier ("ANSI X3.4-1986", + IdentifierType.ANSI)); + _specification.add (doc); + + doc = new Document ("7-Bit coded Character Set",DocumentType.STANDARD); + doc.setEdition ("6th"); + doc.setDate ("1991-12"); + agent = new Agent ("ECMA", AgentType.STANDARD); + agent.setAddress ("114 Rue du Rhone, CH-1204 Geneva, Switzerland"); + agent.setTelephone ("+41 22 849.60.00"); + agent.setFax ("+41 22 849.60.01"); + agent.setEmail ("helpdesk@ecma.ch"); + agent.setWeb ("http://www.ecma-international.org/"); + doc.setPublisher (agent); + doc.setIdentifier (new Identifier ("ECMA-6", IdentifierType.ECMA)); + doc.setIdentifier (new Identifier ("http://www.ecma-international." + + "org/publications/files/ecma-st/" + + "Ecma-006.pdf",IdentifierType.URL)); + _specification.add (doc); + + } + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + * + * Parsing methods. + ******************************************************************/ + + /** + * Parse the content of a stream digital object and store the + * results in RepInfo. + */ + public final int parse (InputStream stream, RepInfo info, int parseIndex) + throws IOException + { + // Test if textMD is to be generated + if (_defaultParams != null) { + Iterator iter = _defaultParams.iterator (); + while (iter.hasNext ()) { + String param = (String) iter.next (); + if (param.toLowerCase ().equals ("withtextmd=true")) { + _withTextMD = true; + } + } + } + + initParse (); + info.setModule (this); + + // No line end types have been discovered. + _lineEndCR = false; + _lineEndLF = false; + _lineEndCRLF = false; + _prevChar = 0; + _controlCharMap = new HashMap (); + _textMD = new TextMDMetadata(); + + boolean printableChars = false; + + info.setFormat (_format[0]); + info.setMimeType (_mimeType[0]); + + /* We may have already done the checksums while converting a + temporary file. */ + Checksummer ckSummer = null; + if (_je != null && _je.getChecksumFlag () && + info.getChecksum ().size () == 0) { + ckSummer = new Checksummer (); + _cstream = new ChecksumInputStream (stream, ckSummer); + _dstream = getBufferedDataStream (_cstream, _je != null ? + _je.getBufferSize () : 0); + } + else { + _dstream = getBufferedDataStream (stream, _je != null ? + _je.getBufferSize () : 0); + } + boolean eof = false; + _nByte = 0; + while (!eof) { + try { + int ch = readUnsignedByte (_dstream, this); + + /* Only byte values 0x00 through 0x7f are valid. */ + + if (ch > 0x7f) { + ErrorMessage error = + new ErrorMessage ("Invalid character", + "Character = " + ((char) ch) + + " (0x" + Integer.toHexString (ch) + + ")", _nByte - 1); + info.setMessage (error); + info.setWellFormed (RepInfo.FALSE); + return 0; + } + /* Track what control characters are used. */ + if (ch < 0X20 && ch != 0X0D && ch != 0X0A) { + _controlCharMap.put (new Integer (ch), + controlCharMnemonics [ch]); + } + else if (ch == 0X7F) { + _controlCharMap.put (new Integer (ch), "DEL (0x7F)"); + } + + /* Determine the line ending type(s). */ + checkLineEnd(ch); + + /* Only byte values 0x20 through 0x7e are printable. */ + if (0x20 <= ch && ch <= 0x7e) { + printableChars = true; + } + + _prevChar = ch; + } + catch (EOFException e) { + eof = true; + /* Catch line endings at very end. */ + checkLineEnd(0); + } + } + + /* The object is well-formed ASCII. */ + + if (ckSummer != null){ + info.setSize (_cstream.getNBytes ()); + info.setChecksum (new Checksum (ckSummer.getCRC32 (), + ChecksumType.CRC32)); + String value = ckSummer.getMD5 (); + if (value != null) { + info.setChecksum (new Checksum (value, ChecksumType.MD5)); + } + if ((value = ckSummer.getSHA1 ()) != null) { + info.setChecksum (new Checksum (value, ChecksumType.SHA1)); + } + } + + /* Only non-zero-length files are well-formed ASCII. + */ + if (_nByte == 0) { + info.setMessage (new ErrorMessage ("Zero-length file")); + info.setWellFormed (RepInfo.FALSE); + return 0; + } + + /* Add the textMD information */ + _textMD.setCharset(TextMDMetadata.CHARSET_ASCII); + _textMD.setByte_order( + _bigEndian?TextMDMetadata.BYTE_ORDER_BIG:TextMDMetadata.BYTE_ORDER_LITTLE); + _textMD.setByte_size("8"); + _textMD.setCharacter_size("1"); + + /* Create a metadata property for the module-specific + * info. (4-Feb-04) */ + List metadataList = new ArrayList (2); + + /* Set property reporting line ending type */ + if (_lineEndCR || _lineEndLF || _lineEndCRLF) { + ArrayList propArray = new ArrayList(3); + if (_lineEndCR) { + propArray.add("CR"); + _textMD.setLinebreak(TextMDMetadata.LINEBREAK_CR); + } + if (_lineEndLF) { + propArray.add("LF"); + _textMD.setLinebreak(TextMDMetadata.LINEBREAK_LF); + } + if (_lineEndCRLF) { + propArray.add("CRLF"); + _textMD.setLinebreak(TextMDMetadata.LINEBREAK_CRLF); + } + Property property = new Property("LineEndings", + PropertyType.STRING, PropertyArity.LIST, propArray); + metadataList.add(property); + } + /* Set property reporting control characters used */ + if (!_controlCharMap.isEmpty ()) { + LinkedList propList = new LinkedList (); + String mnem; + for (int i = 0; i < 0X20; i++) { + mnem = (String) _controlCharMap.get (new Integer (i)); + if (mnem != null) { + propList.add (mnem); + } + } + /* need to check separately for DEL */ + mnem = (String) _controlCharMap.get (new Integer (0X7F)); + if (mnem != null) { + propList.add (mnem); + } + Property property = new Property ("ControlCharacters", + PropertyType.STRING, + PropertyArity.LIST, + propList); + metadataList.add (property); + } + + if (_withTextMD) { + Property property = new Property ("TextMDMetadata", + PropertyType.TEXTMDMETADATA, PropertyArity.SCALAR, _textMD); + metadataList.add (property); + } + + /* Add the ASCII-specific metadata, if it exists. */ + if (metadataList.size () > 0) { + info.setProperty (new Property ("ASCIIMetadata", + PropertyType.PROPERTY, + PropertyArity.LIST, + metadataList)); + } + + if (!printableChars) { + info.setMessage (new InfoMessage ("No printable characters")); + } + + return 0; + } + + + /** + * Check if the digital object conforms to this Module's + * internal signature information. + * An ASCII file has no "signature," so in cases like this we just + * check the beginning of the file as a plausible guess. This really + * proves nothing, since a text file could have a single accented + * character dozens of kilobytes into it. But oh well. + * + * @param file A File object for the object being parsed + * @param stream An InputStream, positioned at its beginning, + * which is generated from the object to be parsed + * @param info A fresh RepInfo object which will be modified + * to reflect the results of the test + */ + public void checkSignatures (File file, + InputStream stream, + RepInfo info) + throws IOException + { + info.setFormat (_format[0]); + info.setMimeType (_mimeType[0]); + info.setModule (this); + JhoveBase jb = getBase(); + int sigBytes = jb.getSigBytes(); + int bytesRead = 0; + boolean eof = false; + DataInputStream dstream = new DataInputStream (stream); + while (!eof && bytesRead < sigBytes) { + try { + int ch = readUnsignedByte (dstream, this); + ++bytesRead; + + /* Only byte values 0x00 through 0x7f are valid. */ + + if (ch > 0x7f) { + info.setWellFormed (false); + return; + } + } + catch (EOFException e) { + eof = true; + } + } + // Reject an empty file. + if (bytesRead == 0) { + info.setWellFormed (false); + return; + } + // Do this only after being sure it's OK, as this property + // is sticky. + info.setSigMatch(_name); + + } + + + /****************************************************************** + * PRIVATE INSTANCE METHODS. + ******************************************************************/ + + /* Accumulate information about line endings. ch is the + current character, and _prevChar the one before it. */ + protected void checkLineEnd (int ch) + { + if (ch == LF) { + if (_prevChar == CR) { + _lineEndCRLF = true; + } + else { + _lineEndLF = true; + } + } + else if (_prevChar == CR) { + _lineEndCR = true; + } + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/BytestreamModule.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/BytestreamModule.java new file mode 100644 index 00000000..7e5b98a9 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/BytestreamModule.java @@ -0,0 +1,167 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003-2007 by JSTOR and the President and Fellows of Harvard College + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module; + +import edu.harvard.hul.ois.jhove.*; +import java.io.*; + +/** + * Module for analysis of content as a byte stream. + * This is the module of last resort, accepting any content as + * valid and well-formed. + */ +public final class BytestreamModule + extends ModuleBase +{ + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + /* Input stream wrapper which handles checksums */ + protected ChecksumInputStream _cstream; + + /* Data input stream wrapped around _cstream */ + protected DataInputStream _dstream; + + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + + private static final String NAME = "BYTESTREAM"; + private static final String RELEASE = "1.3"; + private static final int [] DATE = {2007, 4, 10}; + private static final String [] FORMAT = {"bytestream"}; + private static final String COVERAGE = null; + private static final String [] MIMETYPE = {"application/octet-stream"}; + private static final String WELLFORMED = "All bytestreams are well-formed"; + private static final String VALIDITY = null; + private static final String REPINFO = null; + private static final String NOTE = "This is the default format"; + private static final String RIGHTS = "Copyright 2003-2007 by JSTOR and " + + "the President and Fellows of Harvard College. " + + "Released under the GNU Lesser General Public License."; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * Creates a BytestreamModule. + */ + public BytestreamModule () + { + super (NAME, RELEASE, DATE, FORMAT, COVERAGE, MIMETYPE, WELLFORMED, + VALIDITY, REPINFO, NOTE, RIGHTS, false); + + Agent agent = new Agent ("Harvard University Library", + AgentType.EDUCATIONAL); + agent.setAddress ("Office for Information Systems, " + + "90 Mt. Auburn St., " + + "Cambridge, MA 02138"); + agent.setTelephone ("+1 (617) 495-3724"); + agent.setEmail("jhove-support@hulmail.harvard.edu"); + _vendor = agent; + } + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + * + * Parsing methods. + ******************************************************************/ + + /** + * Parse the content of a stream digital object and store the + * results in RepInfo. + * Any arbitrary bytestream is considered well-formed. + */ + public final int parse (InputStream stream, RepInfo info, int parseIndex) + throws IOException + { + initParse (); + info.setModule (this); + info.setFormat (_format[0]); + info.setMimeType (_mimeType[0]); + + /* We may have already done the checksums while converting a + temporary file. */ + Checksummer ckSummer = null; + if (_je != null && _je.getChecksumFlag () && + info.getChecksum ().size () == 0) { + ckSummer = new Checksummer (); + _cstream = new ChecksumInputStream (stream, ckSummer); + _dstream = getBufferedDataStream (_cstream, _je != null ? + _je.getBufferSize () : 0); + } + else { + _dstream = getBufferedDataStream (stream, _je != null ? + _je.getBufferSize () : 0); + } + + boolean eof = false; + _nByte = 0; + byte[] byteBuf = new byte[4096]; + while (!eof) { + try { +// int ch = readUnsignedByte (_dstream, this); + // All the calculations are done down in ChecksumInputStream + int n = readByteBuf (_dstream, byteBuf, this); + if (n <= 0) { + break; + } + } + catch (EOFException e) { + eof = true; + } + } + info.setSize (_nByte); + if (_nByte == 0) { + info.setMessage (new InfoMessage ("Zero-length file")); + } + if (ckSummer != null) { + info.setChecksum (new Checksum (ckSummer.getCRC32 (), + ChecksumType.CRC32)); + String value = ckSummer.getMD5 (); + if (value != null) { + info.setChecksum (new Checksum (value, ChecksumType.MD5)); + } + if ((value = ckSummer.getSHA1 ()) != null) { + info.setChecksum (new Checksum (value, ChecksumType.SHA1)); + } + } + + return 0; + } + + /** + * Check signature. Bytestreams have no signatures, but since any + * byte stream is considered a valid Bytestream, return immediately + * doing nothing. The RepInfo._consistent flag will remain true. + */ + public void checkSignatures (File file, InputStream stream, + RepInfo info) + throws IOException + { + info.setFormat (_format[0]); + info.setMimeType (_mimeType[0]); + info.setModule (this); + info.setSigMatch(_name); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/GifModule.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/GifModule.java new file mode 100644 index 00000000..b5d4006b --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/GifModule.java @@ -0,0 +1,993 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003-2007 by JSTOR and the President and Fellows of Harvard College + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module; + +import java.io.*; +import java.util.*; +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.gif.*; +import org.xml.sax.XMLReader; +import org.xml.sax.SAXException; +import javax.xml.parsers.SAXParserFactory; + +/** + * Module for identification and validation of GIF files. + * + * @author Gary McGath + * + */ +public class GifModule extends ModuleBase +{ + /****************************************************************** + * DEBUGGING FIELDS. + * All debugging fields should be set to false for release code. + ******************************************************************/ + + /* Set to true to allow application identifiers to be case-insensitive. */ + private static final boolean debug_appIdentCaseInsens = false; + + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + + private static final String NAME = "GIF-hul"; + private static final String RELEASE = "1.3"; + private static final int [] DATE = {2006, 9, 5}; + private static final String [] FORMAT = {"GIF", + "Graphics Interchange Format"}; + private static final String COVERAGE = "GIF87a, GIF89a"; + private static final String [] MIMETYPE = {"image/gif"}; + private static final String WELLFORMED = "A GIF file is well-formed if " + + "it has a header block; a sequence of properly formed control, " + + "graphic-rendering, and special purpose blocks; and a trailer block"; + private static final String VALIDITY = "A GIF file is valid if " + + "well-formed, has at most one global color map, and at most one " + + "graphic control extension preceding an image descriptor or a plain " + + "text extension"; + private static final String REPINFO = "Additional representation " + + "information includes: NISO Z39.87 Digital Still Image Technical " + + "Metadata, and block-specific metadata"; + private static final String NOTE = "'GIF' and 'Graphics Interchange " + + "Format' are trademarks of " + + "Compuserve Interactive Services Inc."; + private static final String RIGHTS = "Copyright 2003-2007 by JSTOR and " + + "the President and Fellows of Harvard College. " + + "Released under the GNU Lesser General Public License."; + + /* Block type values */ + private static final int + EXT_BLOCK = 0X21, + APPLICATION_EXT = 0XFF, + COMMENT_EXT = 0XFE, + GRAPHIC_CONTROL_EXT = 0XF9, + IMAGE_DESC = 0X2C, + PLAIN_TEXT_EXT = 0X01, + TRAILER = 0X3B; + + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + /* First 6 bytes of file */ + protected byte _sig[]; + + /* Checksummer object */ + protected Checksummer _ckSummer; + + /* XMP property */ + protected Property _xmpProp; + + /* Input stream wrapper which handles checksums */ + protected ChecksumInputStream _cstream; + + /* Data input stream wrapped around _cstream */ + protected DataInputStream _dstream; + + /* Flag for presence of global color table */ + protected boolean _globalColorTableFlag; + + /* Size of global color table */ + protected int _globalColorTableSize; + + /* Count of graphic control extensions preceding + * something to modify */ + protected int _gceCounter; + + /* Top-level metadata property */ + protected Property _metadata; + + /* Blocks list property */ + protected List _blocksList; + + /* Total count of graphic and plain text extension blocks */ + protected int _numGraphicBlocks; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + /** + * Instantiate a GifModule object. + */ + public GifModule () + { + super (NAME, RELEASE, DATE, FORMAT, COVERAGE, MIMETYPE, WELLFORMED, + VALIDITY, REPINFO, NOTE, RIGHTS, false); + + Agent agent = new Agent ("Harvard University Library", + AgentType.EDUCATIONAL); + agent.setAddress ("Office for Information Systems, " + + "90 Mt. Auburn St., " + + "Cambridge, MA 02138"); + agent.setTelephone ("+1 (617) 495-3724"); + agent.setEmail("jhove-support@hulmail.harvard.edu"); + _vendor = agent; + + Document doc = new Document ("GIF (Graphics Interchange Format): A " + + "standard defining a mechanism for the " + + "storage and transmission of raster-" + + "based graphics information", + DocumentType.REPORT); + agent = new Agent ("Compuserve Interactive Services Inc.", + AgentType.COMMERCIAL); + agent.setAddress ("5000 Arlington Centre Blvd., Columbus, OS 43220"); + agent.setTelephone ("(614) 457-8600"); + agent.setWeb ("http://www.compuserve.com/"); + doc.setAuthor (agent); + doc.setDate ("1987-06-15"); + doc.setIdentifier (new Identifier ("http://www.w3.org/Graphics/GIF/spec-gif87.txt", + IdentifierType.URL)); + _specification.add (doc); + + doc = new Document ("Graphics Interchange Format", + DocumentType.REPORT); + doc.setEdition ("Version 89a"); + doc.setAuthor (agent); + doc.setDate ("1987-06-15"); + doc.setIdentifier (new Identifier ("http://www.w3.org/Graphics/GIF/spec-gif89a.txt", + IdentifierType.URL)); + _specification.add (doc); + + Signature sig = new InternalSignature ("GIF", SignatureType.MAGIC, + SignatureUseType.MANDATORY, 0); + _signature.add (sig); + sig = new InternalSignature ("87a", SignatureType.MAGIC, + SignatureUseType.MANDATORY_IF_APPLICABLE, + 3, "For version 87a"); + _signature.add (sig); + sig = new InternalSignature ("89a", SignatureType.MAGIC, + SignatureUseType.MANDATORY_IF_APPLICABLE, + 3, "For version 89a"); + _signature.add (sig); + + sig = new ExternalSignature (".gif", SignatureType.EXTENSION, + SignatureUseType.OPTIONAL); + _signature.add (sig); + + _bigEndian = false; + } + + /****************************************************************** + * Parsing methods. + ******************************************************************/ + + /** + * Check if the digital object conforms to this Module's + * internal signature information. + * + * @param file A File object for the object being parsed + * @param stream An InputStream, positioned at its beginning, + * which is generated from the object to be parsed + * @param info A fresh RepInfo object which will be modified + * to reflect the results of the test + */ + public void checkSignatures (File file, + InputStream stream, + RepInfo info) + throws IOException + { + int sigBytes[] = { 'G', 'I', 'F', '8', '*', 'a' }; + int i; + int ch; + try { + _dstream = null; + _dstream = getBufferedDataStream (stream, _je != null ? + _je.getBufferSize () : 0); + for (i = 0; i < 4; i++) { + ch = readUnsignedByte(_dstream, this); + if (ch != sigBytes[i]) { + info.setWellFormed (false); + return; + } + } + /* Byte 4 can be either 7 or 9 */ + ch = readUnsignedByte (_dstream, this); + if (ch != (int) '7' && ch != (int) '9') { + info.setWellFormed (false); + return; + } + ch = readUnsignedByte (_dstream, this); + if (ch != sigBytes[5]) { + info.setWellFormed (false); + return; + } + info.setModule (this); + info.setFormat (_format[0]); + info.setMimeType (_mimeType[0]); + info.setSigMatch(_name); + } + catch (Exception e) { + // Reading a very short file may take us here. + info.setWellFormed (false); + return; + } + } + + /** + * Parse the content of a purported GIF stream digital object and store the + * results in RepInfo. + * + * @param stream An InputStream, positioned at its beginning, + * which is generated from the object to be parsed + * @param info A fresh RepInfo object which will be modified + * to reflect the results of the parsing + * @param parseIndex Must be 0 in first call to parse. If + * parse returns a nonzero value, it must be + * called again with parseIndex + * equal to that return value. + */ + public int parse (InputStream stream, RepInfo info, int parseIndex) + throws IOException + { + initParse (); + info.setModule (this); + info.setFormat (_format[0]); + info.setMimeType (_mimeType[0]); + + _blocksList = new LinkedList (); + + Property _blocks = new Property ("Blocks", + PropertyType.PROPERTY, + PropertyArity.LIST, + _blocksList); + + // We may have already done the checksums while converting a + // temporary file. + _ckSummer = null; + if (_app != null && _je.getChecksumFlag () && + info.getChecksum ().size () == 0) { + _ckSummer = new Checksummer (); + _cstream = new ChecksumInputStream (stream, _ckSummer); + _dstream = getBufferedDataStream (_cstream, _app != null ? + _je.getBufferSize () : 0); + } + else { + _dstream = getBufferedDataStream (stream, _app != null ? + _je.getBufferSize () : 0); + } + + if (!readSig (info)) { + return 0; + } + + /* If we got this far, take note that the signature is OK. */ + info.setSigMatch(_name); + + if (!readLSD (info)) { + return 0; + } + + boolean moreToCome = true; + while (moreToCome) { + moreToCome = readBlock (info); + if (info.getWellFormed () == RepInfo.FALSE) { + return 0; + } + } + + if (_ckSummer != null){ + /* We may not have actually hit the end of file. If we're calculating + * checksums on the fly, we have to read and discard whatever is + * left, so it will get checksummed. */ + for (;;) { + try { + int n = skipBytes (_dstream, 2048, this); + if (n == 0) { + break; + } + } + catch (Exception e) { + break; + } + } + info.setSize (_cstream.getNBytes ()); + info.setChecksum (new Checksum (_ckSummer.getCRC32 (), + ChecksumType.CRC32)); + String value = _ckSummer.getMD5 (); + if (value != null) { + info.setChecksum (new Checksum (value, ChecksumType.MD5)); + } + if ((value = _ckSummer.getSHA1 ()) != null) { + info.setChecksum (new Checksum (value, ChecksumType.SHA1)); + } + } + Property metaArray[]; + if (_xmpProp != null) { + // Making this an array rather than a list is a pain, but it's policy + metaArray = new Property[3]; + } + else { + metaArray = new Property[2]; + } + _metadata = new Property ("GIFMetadata", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + metaArray); + metaArray[1] = _blocks; // this comes after GraphicRenderingBlocks, + // which we can't calculate yet + metaArray[0] = new Property ("GraphicRenderingBlocks", + PropertyType.INTEGER, + new Integer (_numGraphicBlocks)); + if (_xmpProp != null) { + metaArray[2] = _xmpProp; + } + info.setProperty (_metadata); + return 0; + } + + /** + * Initializes the state of the module for parsing. + */ + protected void initParse () + { + super.initParse (); + _sig = new byte[6]; + _globalColorTableFlag = false; + _globalColorTableSize = 0; + _gceCounter = 0; + _numGraphicBlocks = 0; + } + + + /* Read the 6-byte signature. */ + protected boolean readSig (RepInfo info) throws IOException + { + boolean eof = false; + String badHeader = "Invalid GIF header"; + int nbyt = 0; + while (nbyt < 6) { + try { + int ch = readUnsignedByte (_dstream, this); + if (nbyt < 6) { + _sig[(int) nbyt] = (byte) ch; + } + nbyt++; + //if (_ckSummer != null) { + // _ckSummer.update (ch); + //} + } + catch (EOFException e) { + eof = true; + info.setMessage(new ErrorMessage (badHeader, 0)); + info.setWellFormed (RepInfo.FALSE); + return false; + } + } + String sigStr = new String (_sig); + if ("GIF89a".equals (sigStr)) { + info.setVersion ("89a"); + info.setProfile ("GIF 89a"); + } + else if ("GIF87a".equals (sigStr)) { + info.setVersion ("87a"); + info.setProfile ("GIF 87a"); + } + else { + info.setMessage(new ErrorMessage (badHeader, 0)); + info.setWellFormed (RepInfo.FALSE); + return false; + } + return true; + } + + /* Read the Logical Screen Descriptor. */ + protected boolean readLSD (RepInfo info) throws IOException + { + Vector propVec = new Vector (8); + /* GIF data is always little-endian. */ + int width = readUnsignedShort (_dstream); + propVec.add (new Property ("LogicalScreenWidth", + PropertyType.INTEGER, + new Integer (width))); + int height = readUnsignedShort (_dstream); + propVec.add (new Property ("LogicalScreenHeight", + PropertyType.INTEGER, + new Integer (height))); + int packedFields = readUnsignedByte (_dstream, this); + _globalColorTableFlag = (packedFields & 0X80) != 0; + int bitsPerColor = ((packedFields & 0X70) >> 4) + 1; + propVec.add (new Property ("ColorResolution", + PropertyType.INTEGER, + new Integer (bitsPerColor))); + boolean sortFlag = (packedFields & 0X8) != 0; + int rawGlobalColorTableSize = packedFields & 0X7; + if (_globalColorTableFlag) { + _globalColorTableSize = 3 * + (1 << (rawGlobalColorTableSize + 1)); + } + + int bgColorIndex = readUnsignedByte (_dstream, this); + propVec.add (new Property ("BackgroundColorIndex", + PropertyType.INTEGER, + new Integer (bgColorIndex))); + int pixAspectRatio = readUnsignedByte (_dstream, this); + // The pixel aspect ratio is turned into a real aspect + // ratio by a formula, but we just report the raw number. + propVec.add (new Property ("PixelAspectRatio", + PropertyType.SHORT, + new Short ((short) pixAspectRatio))); + propVec.add (addByteProperty ("GlobalColorTableFlag", + _globalColorTableFlag ? 1 : 0, + GifStrings.GLOBAL_COLOR_TABLE_FLAG)); + propVec.add (addByteProperty ("GlobalColorTableSortFlag", + sortFlag ? 1 : 0, + GifStrings.COLOR_TABLE_SORT_FLAG)); + propVec.add (new Property ("GlobalColorTableSize", + PropertyType.SHORT, + new Short ((short) rawGlobalColorTableSize))); + Property prop = new Property ("LogicalScreenDescriptor", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + vectorToPropArray (propVec)); + _blocksList.add (prop); + + // Make a property with the global color table, if present + if (_globalColorTableFlag) { + short[] gctArray = new short[_globalColorTableSize]; + for (int i = 0; i < _globalColorTableSize; i++) { + gctArray[i] = (short) _dstream.readUnsignedByte (); + } + _blocksList.add (new Property ("GlobalColorTable", + PropertyType.SHORT, + PropertyArity.ARRAY, + gctArray)); + } + return true; + } + + + + /* Read Graphic blocks, Special blocks, and the trailer. + * Return false if we get an error that prevents further + * progress, or if we encounter the Trailer. */ + protected boolean readBlock (RepInfo info) throws IOException + { + int type; + try { + type = readUnsignedByte (_dstream, this); + } + catch (EOFException e) { + // The spec isn't fully clear on whether a trailer is + // required, but seems to imply it is. + info.setWellFormed (RepInfo.FALSE); + info.setMessage (new ErrorMessage + ("End of file reached without encountering Trailer block", + _nByte)); + return false; + } + try { + switch (type) { + case EXT_BLOCK: + return readExtBlock (info); + case IMAGE_DESC: + return readImage (info); + case TRAILER: + return false; // end of file + default: + info.setWellFormed (RepInfo.FALSE); + info.setMessage (new ErrorMessage + ("Unknown data block type", + "Type = " + type, _nByte)); + return false; + } + } + catch (EOFException e) { + // An EOF in the middle of a block is definitely a problem + info.setWellFormed (RepInfo.FALSE); + info.setMessage (new ErrorMessage + ("Unexpected end of file", _nByte)); + return false; + } + } + + /* Read an extension block. + */ + protected boolean readExtBlock (RepInfo info) throws IOException + { + int subtype = readUnsignedByte (_dstream, this); + switch (subtype) { + case APPLICATION_EXT: + return readAppExtension (info); + case COMMENT_EXT: + return readCommentExtension (info); + case GRAPHIC_CONTROL_EXT: + return readGraphicsCtlBlock (info); + case PLAIN_TEXT_EXT: + return readPlainTextExtension (info); + default: + info.setWellFormed (RepInfo.FALSE); + info.setMessage (new ErrorMessage + ("Unknown extension block type", + "Type = " + subtype, + _nByte)); + return false; + } + } + + /* Read an image descriptor and the subsequent data. + * We are positioned just after the type byte of the + * image descriptor. + */ + protected boolean readImage (RepInfo info) throws IOException + { + ++_numGraphicBlocks; + Vector propVec = new Vector (7); + NisoImageMetadata niso = new NisoImageMetadata (); + Property nisoProp = new Property ("NisoImageMetadata", + PropertyType.NISOIMAGEMETADATA, niso); + + // GIF doesn't have a lot of options, so several + // NISO properties are constants. + niso.setMimeType ("image/gif"); + niso.setByteOrder ("little-endian"); + niso.setCompressionScheme(5); // LZW + niso.setColorSpace (3); // palette color + niso.setOrientation(1); // normal + niso.setBitsPerSample (new int[] {8}); + + _gceCounter = 0; + int leftPos = readUnsignedShort (_dstream); + propVec.add (new Property ("ImageLeftPosition", + PropertyType.INTEGER, + new Integer (leftPos))); + int topPos = readUnsignedShort (_dstream); + propVec.add (new Property ("ImageTopPosition", + PropertyType.INTEGER, + new Integer (topPos))); + int width = readUnsignedShort (_dstream); + niso.setImageWidth (width); + int height = readUnsignedShort (_dstream); + niso.setImageLength (height); + int packedFields = readUnsignedByte (_dstream, this); + int interlaceFlag = (packedFields & 0X40) >> 6; + propVec.add (addByteProperty ("InterlaceFlag", + interlaceFlag, + GifStrings.INTERLACE_FLAG)); + int localColorTableFlag = (packedFields & 0X80) >> 7; + propVec.add (addByteProperty ("LocalColorTableFlag", + localColorTableFlag, + GifStrings.LOCAL_COLOR_TABLE_FLAG)); + int sortFlag = (packedFields & 0X20) >> 5; + propVec.add (addByteProperty ("LocalColorTableSortFlag", + sortFlag, + GifStrings.COLOR_TABLE_SORT_FLAG)); + int localColorTableSize = 0; + int rawLocalColorTableSize = packedFields & 0X7; + propVec.add (new Property ("LocalColorTableSize", + PropertyType.SHORT, + new Short ((short) rawLocalColorTableSize))); + propVec.add (nisoProp); + if (localColorTableFlag != 0) { + localColorTableSize = + 3 * (1 << (rawLocalColorTableSize + 1)); + skipBytes (_dstream, localColorTableSize, this); + } + Property prop = new Property ("ImageDescriptor", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + vectorToPropArray (propVec)); + _blocksList.add (prop); + + // Skip over the LZW minimum code size + int minCodeSize = readUnsignedByte (_dstream, this); + // Now read sub-blocks till we get one of zero size. + for (;;) { + int blockSize = readUnsignedByte (_dstream, this); + if (blockSize == 0) { + break; + } + skipBytes (_dstream, blockSize, this); + } + return true; + } + + /* Read an application extension block and fill in the appropriate + * properties */ + protected boolean readAppExtension (RepInfo info) + throws IOException + { + int blockSize = readUnsignedByte (_dstream, this); + if (blockSize != 11) { + info.setMessage (new ErrorMessage + ("Wrong application extension block size", + _nByte)); + info.setWellFormed (RepInfo.FALSE); + return false; + } + Vector propVec = new Vector (3); + StringBuffer appIdent = new StringBuffer (); + int i; + for (i = 0; i < 8; i++) { + appIdent.append((char) readUnsignedByte (_dstream, this)); + } + propVec.add (new Property ("ApplicationIdentifier", + PropertyType.STRING, + appIdent.toString ())); + + short appAuth[] = new short[3]; + for (i = 0; i < 3; i++) { + appAuth[i] = (short) readUnsignedByte (_dstream, this); + } + propVec.add (new Property ("ApplicationAuthenticationCode", + PropertyType.SHORT, + PropertyArity.ARRAY, + appAuth)); + + int appDataSize = 0; + // We are interested in the application extension for XMP. + if ((appIdent.toString ().equals ("XMP Data") || + (debug_appIdentCaseInsens && + appIdent.toString().toLowerCase().equals ("xmp data"))) && + appAuth[0] == (short) 'X' && + appAuth[1] == (short) 'M' && + appAuth[2] == (short) 'P') { + appDataSize = readXMP (); + } + else { + // Zip through the application data blocks, totalling their size + for (;;) { + int subBlockSize = readUnsignedByte (_dstream, this); + appDataSize += subBlockSize + 1; + if (subBlockSize == 0) { + break; + } + skipBytes (_dstream, subBlockSize, this); + } + } + propVec.add (new Property ("ApplicationDataSize", + PropertyType.INTEGER, + new Integer (appDataSize))); + + Property prop = new Property ("ApplicationExtension", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + vectorToPropArray(propVec)); + _blocksList.add (prop); + return true; + } + + /* Read an application extension block and fill in the appropriate + * properties. A comment extension should, by recommendation, + * contain ASCII, but actually can contain anything. Nulls + * are skipped, but everything else is included as is. */ + protected boolean readCommentExtension (RepInfo info) + throws IOException + { + StringBuffer buf = new StringBuffer (); + for (;;) { + int subBlockSize = readUnsignedByte (_dstream, this); + if (subBlockSize == 0) { + break; + } + for (int i = 0; i < subBlockSize; i++) { + int ch = readUnsignedByte (_dstream, this); + if (ch != 0) { + buf.append ((char) ch); + } + } + } + Property prop = new Property ("CommentExtension", + PropertyType.STRING, + buf.toString ()); + return true; + } + + /* Read an application extension block and fill in the appropriate + * properties */ + protected boolean readPlainTextExtension (RepInfo info) + throws IOException + { + ++_numGraphicBlocks; + _gceCounter = 0; + int blockSize = readUnsignedByte (_dstream, this); + if (blockSize != 12) { + info.setMessage (new ErrorMessage + ("Wrong plain text extension block size", + _nByte)); + info.setWellFormed (RepInfo.FALSE); + return false; + } + + // A plain text extension requires a global color table + if (!_globalColorTableFlag) { + info.setMessage (new ErrorMessage + ("Plain text extension requires global color table", + _nByte)); + info.setValid (false); + } + Vector propVec = new Vector (9); + int textLeft = readUnsignedShort (_dstream); + propVec.add (new Property ("TextGridLeftPosition", + PropertyType.INTEGER, + new Integer (textLeft))); + + int textTop = readUnsignedShort (_dstream); + propVec.add (new Property ("TextGridTopPosition", + PropertyType.INTEGER, + new Integer (textTop))); + + int textGWidth = readUnsignedShort (_dstream); + propVec.add (new Property ("TextGridWidth", + PropertyType.INTEGER, + new Integer (textGWidth))); + + int textGHeight = readUnsignedShort (_dstream); + propVec.add (new Property ("TextGridHeight", + PropertyType.INTEGER, + new Integer (textGHeight))); + + int charCWidth = readUnsignedByte (_dstream, this); + propVec.add (new Property ("CharacterCellWidth", + PropertyType.SHORT, + new Short ((short) charCWidth))); + + int charCHeight = readUnsignedByte (_dstream, this); + propVec.add (new Property ("CharacterCellHeight", + PropertyType.SHORT, + new Short ((short) charCHeight))); + + int textFgIdx = readUnsignedByte (_dstream, this); + propVec.add (new Property ("TextForegroundColorIndex", + PropertyType.SHORT, + new Short ((short) textFgIdx))); + + int textBgIdx = readUnsignedByte (_dstream, this); + propVec.add (new Property ("TextBackgroundColorIndex", + PropertyType.SHORT, + new Short ((short) textBgIdx))); + + // Read the text data. The GIF recommendation states that + // characters less than 0X20 or greater than 0XF7 (why F7? + // It's on at least 2 independent copies of the spec, so + // apparently it's not a typo, or else is a well-entrenched one) + // should be represented as spaces. + StringBuffer buf = new StringBuffer (); + for (;;) { + int subBlockSize = readUnsignedByte (_dstream, this); + if (subBlockSize == 0) { + break; + } + for (int i = 0; i < subBlockSize; i++) { + int ch = readUnsignedByte (_dstream, this); + if (ch >= 0X20 || ch <= 0XF7) { + buf.append ((char) ch); + } + else { + buf.append (' '); + } + } + } + propVec.add (new Property ("PlainTextData", + PropertyType.STRING, + buf.toString ())); + + Property prop = new Property ("PlainTextExtension", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + vectorToPropArray(propVec)); + _blocksList.add (prop); + return true; + } + + /* Read a graphics control block and fill in the + * relevant properties */ + protected boolean readGraphicsCtlBlock (RepInfo info) + throws IOException + { + Vector propVec = new Vector (5); + if (++_gceCounter > 1) { + info.setMessage (new ErrorMessage + ("Multiple graphics control blocks for one image", + _nByte)); + info.setWellFormed (RepInfo.FALSE); + } + int blockSize = readUnsignedByte (_dstream, this); + if (blockSize != 4) { + info.setMessage (new ErrorMessage + ("Wrong graphics control block size", + _nByte)); + info.setWellFormed (RepInfo.FALSE); + return false; + } + int packedFields = readUnsignedByte (_dstream, this); + int dispMethod = (packedFields & 0X1C) >> 3; + propVec.add (addByteProperty ("DisposalMethod", + dispMethod, + GifStrings.GCE_DISPOSAL_METHOD)); + int userInputFlag = (packedFields & 2) >> 1; + propVec.add (addByteProperty ("UserInputFlag", + userInputFlag, + GifStrings.GCE_USER_INPUT_FLAG)); + int transparencyFlag = packedFields & 1; + propVec.add (addByteProperty ("TransparencyFlag", + transparencyFlag, + GifStrings.GCE_TRANSPARENCY_FLAG)); + int delayTime = readUnsignedShort(_dstream); + propVec.add (new Property ("DelayTime", + PropertyType.INTEGER, + new Integer (delayTime))); + int transIndex = readUnsignedByte (_dstream, this); + propVec.add (new Property ("TransparentColorIndex", + PropertyType.SHORT, + new Short ((short) transIndex))); + // Skip the block terminator. + readUnsignedByte (_dstream, this); + + Property prop = new Property ("GraphicControlExtension", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + vectorToPropArray(propVec)); + _blocksList.add (prop); + return true; + } + + /* Read and process an XMP block and return the number of + * bytes read. When we reach a 0 byte, we've hit the + * end of the "magic" trailer. + */ + protected int readXMP () throws IOException + { + // Read bytes till we get to the trailer. Annoyingly, + // we don't know how big the byte buffer has to be, + // so we build a List of fixed-size buffers. We don't add + // curBuf to bufList till it's full. + List bufList = new LinkedList (); + final int bufsiz = 4096; + byte[] curBuf = new byte[bufsiz]; + int curBufOff = 0; + + // Fill up buffers till we hit a null. + for (;;) { + int ch = readUnsignedByte (_dstream, this); + if (ch == 0) { + // Read past second null, which concludes trailer + readUnsignedByte (_dstream, this); + break; + } + if (curBufOff == bufsiz) { + bufList.add (curBuf); + curBuf = new byte[bufsiz]; + curBufOff = 0; + } + curBuf[curBufOff++] = (byte) ch; + } + + // Consolidate the buffers into one big buffer. + // The magic trailer is 258 bytes long, of which 256 + // bytes were actually read into the buffers, so we + // set our target to 256 bytes less than the total. + int appDataSize = bufList.size() * bufsiz + curBufOff + 2; + int totalSize = appDataSize - 258; + byte[] bigBuf = new byte[totalSize]; + int bigBufOff = 0; + ListIterator iter = bufList.listIterator(); + int i; + l1: + while (iter.hasNext ()) { + byte[] buf = (byte []) iter.next (); + for (i = 0; i < bufsiz; i++) { + bigBuf[bigBufOff++] = buf[i]; + if (bigBufOff >= totalSize) { + break l1; + } + } + } + // Finally curBuf gets added + for (i = 0; i < curBufOff; i++) { + if (bigBufOff >= totalSize) { + break; + } + bigBuf[bigBufOff++] = curBuf[i]; + } + + // OK. All that was just to get the XMP into one big byte + // buffer. Now process it. + final String badMetadata = "Invalid or ill-formed XMP metadata"; + try { + ByteArrayInputStream strm = + new ByteArrayInputStream (bigBuf); + ByteArrayXMPSource src = new ByteArrayXMPSource (strm, "UTF-8"); + + // Create an InputSource to feed the parser. + SAXParserFactory factory = + SAXParserFactory.newInstance(); + factory.setNamespaceAware (true); + XMLReader parser = factory.newSAXParser ().getXMLReader (); + XMPHandler handler = new XMPHandler (); + parser.setContentHandler (handler); + parser.setErrorHandler (handler); + // We have to parse twice. The first time, we may get + // an encoding change as part of an exception thrown. If this + // happens, we create a new InputSource with the encoding, and + // continue. + try { + parser.parse (src); + _xmpProp = src.makeProperty (); + return appDataSize; + } + catch (SAXException se) { + String msg = se.getMessage (); + if (msg != null && msg.startsWith ("ENC=")) { + String encoding = msg.substring (5); + try { + //The only permitted encoding is UTF-8, but + //that may come under various aliased names, + //so we assume the encoding is legitimate. + src = new ByteArrayXMPSource (strm, encoding); + parser.parse (src); + } + catch (UnsupportedEncodingException uee) { + return appDataSize; + } + } + _xmpProp = src.makeProperty (); + return appDataSize; + } + } + catch (Exception e) { + return appDataSize; + } + + } + + protected Property addByteProperty (String name, int value, + String [] labels) + { + if (!_je.getShowRawFlag ()) { + try { + return new Property (name, PropertyType.STRING, labels[value]); + } + catch (Exception e) { + // fall through + } + } + return new Property (name, PropertyType.BYTE, new Byte ((byte) value)); + } + + + /* GIF is always little-endian, so readUnsignedShort can + * unambiguously drop its endian argument */ + protected int readUnsignedShort (DataInputStream stream) + throws IOException + { + return readUnsignedShort (stream, false, this); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/HtmlModule.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/HtmlModule.java new file mode 100644 index 00000000..2108b0d9 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/HtmlModule.java @@ -0,0 +1,765 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004-2007 by JSTOR and the President and Fellows of Harvard College + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module; + +import java.io.*; +import java.util.*; +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.html.*; + +/** + * Module for identification and validation of HTML files. + * + * HTML is different from most of the other documents in that + * sloppy construction is practically assumed in the specification. + * This module attempt to report as many errors as possible and + * recover reasonably from errors. To do this, there is more + * heuristic behavior built into this module than into the more + * straightforward ones. + * + * XHTML is recognized by this module, but is handed off to the + * XML module for processing. If the XML module is missing (which + * it shouldn't be if you've installed the JHOVE application without + * modifications), this won't be able to deal with XHTML files. + * + * HTML should be placed ahead of XML in the module order. If the + * XML module sees an XHTML file first, it will recognize it as XHTML, + * but won't be able to report the complete properties. + * + * The HTML module uses code created with the JavaCC parser generator + * and lexical analyzer generator. There is apparently a bug in + * JavaCC which causes blank lines not to be counted in certain cases, + * causing lexical errors to be reported with incorrect line numbers. + * + * @author Gary McGath + * + */ +public class HtmlModule extends ModuleBase { + + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + + private static final String NAME = "HTML-hul"; + private static final String RELEASE = "1.3"; + private static final int [] DATE = {2006, 9, 5}; + private static final String [] FORMAT = { + "HTML" + }; + private static final String COVERAGE = "HTML 3.2, HTML 4.0 Strict," + + "HTML 4.0 Transitional, HTML 4.0 Frameset, " + + "HTML 4.01 Strict, HTML 4.01 Transitional, HTML 4.01 Frameset" + + "XHTML 1.0 Strict, XHTML 1.0 Transitional, XHTML 1.0 Frameset" + + "XHTML 1.1"; + + private static final String [] MIMETYPE = { + "text/html" + }; + private static final String WELLFORMED = "An HTML file is well-formed " + + "if it meets the criteria defined in the HTML 3.2 specification " + + "(W3C Recommendation, 14-Jan-1997), " + + "the HTML 4.0 specification (W3C Recommendation, 24-Apr-1998, " + + "the HTML 4.01 specification (W3C Recommendation, 24-Dec-1999, " + + "the XHTML 1.0 specification (W3C Recommendation, 26-Jan-2000, " + + "revised 1-Aug-2002, " + + "or the XHTML 1.1 specification (W3C Recommendation, 31-May-2001"; + private static final String VALIDITY = "An HTML file is valid if it is " + + "well-formed and has a valid DOCTYPE declaration."; + private static final String REPINFO = "Languages, title, META tags, " + + "frames, links, scripts, images, citations, defined terms, " + + "abbreviations, entities, Unicode entity blocks"; + private static final String NOTE = ""; + private static final String RIGHTS = "Copyright 2004-2007 by JSTOR and " + + "the President and Fellows of Harvard College. " + + "Released under the GNU Lesser General Public License."; + + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + /* Input stream wrapper which handles checksums */ + protected ChecksumInputStream _cstream; + + /* Data input stream wrapped around _cstream */ + protected DataInputStream _dstream; + + /* Doctype extracted from document */ + protected String _doctype; + + /* Constants for the recognized flavors of HTML */ + public static final int + HTML_3_2 = 1, + HTML_4_0_STRICT = 2, + HTML_4_0_FRAMESET = 3, + HTML_4_0_TRANSITIONAL = 4, + HTML_4_01_STRICT = 5, + HTML_4_01_FRAMESET = 6, + HTML_4_01_TRANSITIONAL = 7, + XHTML_1_0_STRICT = 8, + XHTML_1_0_TRANSITIONAL = 9, + XHTML_1_0_FRAMESET = 10, + XHTML_1_1 = 11; + + /* Profile names, matching the above indices */ + private static final String[] profileNames = + { + null, + null, // there are no profiles for HTML 3.2 + "Strict", + "Frameset", + "Transitional", + "Strict", + "Frameset", + "Transitional", + "Strict", + "Frameset", + "Transitional", + null // there are no profiles for XHTML 1.1 + }; + + /* Version names, matching the above indices */ + private static final String[] versionNames = + { + null, + "HTML 3.2", + "HTML 4.0", + "HTML 4.0", + "HTML 4.0", + "HTML 4.01", + "HTML 4.01", + "HTML 4.01", + "XHTML 1.0", + "XHTML 1.0", + "XHTML 1.0", + "XHTML 1.1" + }; + + /* Flag to know if the property TextMDMetadata is to be added */ + protected boolean _withTextMD = false; + /* Hold the information needed to generate a textMD metadata fragment */ + protected TextMDMetadata _textMD; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + /** + * Instantiate an HtmlModule object. + */ + public HtmlModule () + { + super (NAME, RELEASE, DATE, FORMAT, COVERAGE, MIMETYPE, WELLFORMED, + VALIDITY, REPINFO, NOTE, RIGHTS, false); + + Agent agent = new Agent ("Harvard University Library", + AgentType.EDUCATIONAL); + agent.setAddress ("Office for Information Systems, " + + "90 Mt. Auburn St., " + + "Cambridge, MA 02138"); + agent.setTelephone ("+1 (617) 495-3724"); + agent.setEmail("jhove-support@hulmail.harvard.edu"); + _vendor = agent; + + /* HTML 3.2 spec */ + Document doc = new Document ("HTML 3.2 Reference Specification", + DocumentType.REPORT); + + Agent w3cAgent = new Agent ("Word Wide Web Consortium", AgentType.NONPROFIT); + w3cAgent.setAddress ("Massachusetts Institute of Technology, " + + "Computer Science and Artificial Intelligence Laboratory, " + + "32 Vassar Street, Room 32-G515, " + + "Cambridge, MA 02139"); + w3cAgent.setTelephone ("(617) 253-2613"); + w3cAgent.setFax ("(617) 258-5999"); + w3cAgent.setWeb ("http://www.w3.org/"); + doc.setPublisher (w3cAgent); + + Agent dRaggett = new Agent ("Dave Raggett", AgentType.OTHER); + doc.setAuthor(dRaggett); + + doc.setDate ("1997-01-14"); + doc.setIdentifier(new Identifier + ("http://www.w3c.org/TR/REC-html32-19970114", IdentifierType.URL)); + _specification.add (doc); + + /* HTML 4.0 spec */ + doc = new Document ("HTML 4.0 Specification", DocumentType.REPORT); + doc.setPublisher (w3cAgent); + doc.setAuthor(dRaggett); + Agent leHors = new Agent ("Arnaud Le Hors", AgentType.OTHER); + doc.setAuthor(leHors); + Agent jacobs = new Agent ("Ian Jacobs", AgentType.OTHER); + doc.setAuthor(jacobs); + doc.setDate ("1998-04-24"); + doc.setIdentifier(new Identifier + ("http://www.w3.org/TR/1998/REC-html40-19980424/", IdentifierType.URL)); + _specification.add (doc); + + + /* HTML 4.01 spec */ + doc = new Document ("HTML 4.01 Specification", DocumentType.REPORT); + doc.setPublisher (w3cAgent); + doc.setAuthor(dRaggett); + doc.setAuthor(leHors); + doc.setAuthor(jacobs); + doc.setDate ("1999-12-24"); + doc.setIdentifier(new Identifier + ("http://www.w3.org/TR/1999/REC-html401-19991224/", IdentifierType.URL)); + _specification.add (doc); + + /* XHTML 1.0 spec */ + doc = new Document ("XHTML(TM) 1.0 The Extensible HyperText Markup Language " + + "(Second Edition)", DocumentType.REPORT); + doc.setPublisher (w3cAgent); + doc.setDate ("01-08-2002"); + doc.setIdentifier (new Identifier + ("http://www.w3.org/TR/xhtml1/", IdentifierType.URL)); + _specification.add (doc); + + /* XHTML 1.1 spec */ + doc = new Document (" XHTML(TM) 1.1 - Module-based XHTML", + DocumentType.REPORT); + doc.setPublisher (w3cAgent); + doc.setDate ("31-05-2001"); + doc.setIdentifier (new Identifier + ("http://www.w3.org/TR/2001/REC-xhtml11-20010531/", + IdentifierType.URL)); + _specification.add (doc); + + /* XHTML 2.0 spec -- NOT included yet; this is presented in "conditionalized-out" + * form just as a note for future expansion. */ + if (false) { + doc = new Document ("XHTML 2.0, W3C Working Draft", DocumentType.OTHER); + doc.setPublisher (w3cAgent); + doc.setDate ("22-07-2004"); + doc.setIdentifier (new Identifier + ("http://www.w3.org/TR/2004/WD-xhtml2-20040722/", IdentifierType.URL)); + _specification.add (doc); + } + + Signature sig = new ExternalSignature (".html", SignatureType.EXTENSION, + SignatureUseType.OPTIONAL); + _signature.add (sig); + sig = new ExternalSignature (".htm", SignatureType.EXTENSION, + SignatureUseType.OPTIONAL); + _signature.add (sig); + } + + /** + * Parse the content of a purported HTML stream digital object and store the + * results in RepInfo. + * + * + * @param stream An InputStream, positioned at its beginning, + * which is generated from the object to be parsed. + * If multiple calls to parse are made + * on the basis of a nonzero value being returned, + * a new InputStream must be provided each time. + * + * @param info A fresh (on the first call) RepInfo object + * which will be modified + * to reflect the results of the parsing + * If multiple calls to parse are made + * on the basis of a nonzero value being returned, + * the same RepInfo object should be passed with each + * call. + * + * @param parseIndex Must be 0 in first call to parse. If + * parse returns a nonzero value, it must be + * called again with parseIndex + * equal to that return value. + */ + public int parse (InputStream stream, RepInfo info, int parseIndex) + throws IOException + { + if (parseIndex != 0) { + // Coming in with parseIndex = 1 indicates that we've determined + // this is XHTML; so we invoke the XML module to parse it. + // If parseIndex is 100, this is the first invocation of the + // XML module, so we call it with 0; otherwise we call it with + // the value of parseIndex. + if (isXmlAvailable ()) { + edu.harvard.hul.ois.jhove.module.XmlModule xmlMod = + new edu.harvard.hul.ois.jhove.module.XmlModule (); + if (parseIndex == 100) { + parseIndex = 0; + } + xmlMod.setApp (_app); + xmlMod.setBase (_je); + xmlMod.setXhtmlDoctype(_doctype); + return xmlMod.parse (stream, info, parseIndex); + } + else { + // The XML module shouldn't be missing from any installation, + // but someone who really wanted to could remove it. In + // that case, you deserve what you get. + info.setMessage ( new ErrorMessage + ("XML-HUL module required to validate XHTML documents")); + info.setWellFormed (false); // Treat it as completely wrong + return 0; + } + } + else { + /* parseIndex = 0, first call only */ + _doctype = null; + } + // Test if textMD is to be generated + if (_defaultParams != null) { + Iterator iter = _defaultParams.iterator (); + while (iter.hasNext ()) { + String param = (String) iter.next (); + if (param.toLowerCase ().equals ("withtextmd=true")) { + _withTextMD = true; + } + } + } + + initParse (); + info.setFormat (_format[0]); + info.setMimeType (_mimeType[0]); + info.setModule (this); + + if (_textMD == null || parseIndex == 0) { + _textMD = new TextMDMetadata(); + } + /* We may have already done the checksums while converting a + temporary file. */ + Checksummer ckSummer = null; + if (_je != null && _je.getChecksumFlag () && + info.getChecksum ().size () == 0) { + ckSummer = new Checksummer (); + _cstream = new ChecksumInputStream (stream, ckSummer); + _dstream = getBufferedDataStream (_cstream, _je != null ? + _je.getBufferSize () : 0); + } + else { + _dstream = getBufferedDataStream (stream, _je != null ? + _je.getBufferSize () : 0); + } + + ParseHtml parser = null; + HtmlMetadata metadata = null; + HtmlCharStream cstream = null; + try { + cstream = new HtmlCharStream (_dstream, "ISO-8859-1"); + parser = new ParseHtml (cstream); + } + catch (UnsupportedEncodingException e) { + info.setMessage (new ErrorMessage + ("Internal error: " + e.getMessage ())); + info.setWellFormed (false); + return 0; // shouldn't happen! + } + int type = 0; + try { + List elements = parser.HtmlDoc (); + if (elements.isEmpty ()) { + // Consider an empty document bad + info.setWellFormed (false); + info.setMessage (new ErrorMessage + ("Document is empty")); + return 0; + } + type = checkDoctype (elements); + if (type < 0) { + info.setWellFormed (false); + info.setMessage (new ErrorMessage + ("DOCTYPE is not HTML")); + return 0; + } + /* Check if there is at least one html, head, body or title tag. + * A plain text document + * might be interpreted as a single PCDATA, which is in some + * ethereal sense well-formed HTML, but it's pointless to consider + * it such. It might also use angle brackets as a text delimiter, + * and that shouldn't count as HTML either. */ + boolean hasElements = false; + Iterator iter = elements.iterator (); + while (iter.hasNext ()) { + Object o = iter.next (); + if (o instanceof JHOpenTag) { + String name = ((JHOpenTag) o).getName (); + if ("html".equals (name) || + "head".equals (name) || + "body".equals (name) || + "title".equals (name)) { + hasElements = true; + } + break; + } + } + if (!hasElements) { + info.setMessage (new ErrorMessage ("Document contains no html, head, body or title tags")); + info.setWellFormed (false); + return 0; + } + + // CRLF from HtmlCharStream ... + String lineEnd = cstream.getKindOfLineEnd(); + if (lineEnd == null) { + info.setMessage(new InfoMessage("Not able to determine type of end of line")); + _textMD.setLinebreak(TextMDMetadata.NILL); + } else if (lineEnd.equalsIgnoreCase("CR")) { + _textMD.setLinebreak(TextMDMetadata.LINEBREAK_CR); + } else if (lineEnd.equalsIgnoreCase("LF")) { + _textMD.setLinebreak(TextMDMetadata.LINEBREAK_LF); + } else if (lineEnd.equalsIgnoreCase("CRLF")) { + _textMD.setLinebreak(TextMDMetadata.LINEBREAK_CRLF); + } + + if (type == 0) { + /* If we can't find a doctype, it still might be XHTML + * if the elements start with an XML declaration and + * the root element is "html" */ + switch (seemsToBeXHTML (elements)) { + case 0: // Not XML + break; // fall through + case 1: // XML but not HTML + info.setMessage (new ErrorMessage + ("Document has XML declaration but no DOCTYPE; " + + "probably XML rather than HTML")); + info.setWellFormed (false); + return 0; + case 2: // probably XHTML + return 100; + } + info.setMessage (new ErrorMessage + ("Unrecognized or missing DOCTYPE declaration; " + + "validation continuing as HTML 3.2")); + info.setValid (false); + // But keep going + } + + HtmlDocDesc docDesc = null; + switch (type) { + case HTML_3_2: + default: + docDesc = new Html3_2DocDesc (); + _textMD.setMarkup_basis("HTML"); + _textMD.setMarkup_basis_version("3.2"); + break; + + case HTML_4_0_FRAMESET: + docDesc = new Html4_0FrameDocDesc (); + _textMD.setMarkup_basis("HTML"); + _textMD.setMarkup_basis_version("4.0"); + break; + case HTML_4_0_TRANSITIONAL: + docDesc = new Html4_0TransDocDesc (); + _textMD.setMarkup_basis("HTML"); + _textMD.setMarkup_basis_version("4.0"); + break; + case HTML_4_0_STRICT: + docDesc = new Html4_0StrictDocDesc (); + _textMD.setMarkup_basis("HTML"); + _textMD.setMarkup_basis_version("4.0"); + break; + case HTML_4_01_FRAMESET: + docDesc = new Html4_01FrameDocDesc (); + _textMD.setMarkup_basis("HTML"); + _textMD.setMarkup_basis_version("4.01"); + break; + case HTML_4_01_TRANSITIONAL: + docDesc = new Html4_01TransDocDesc (); + _textMD.setMarkup_basis("HTML"); + _textMD.setMarkup_basis_version("4.01"); + break; + case HTML_4_01_STRICT: + docDesc = new Html4_01StrictDocDesc (); + _textMD.setMarkup_basis("HTML"); + _textMD.setMarkup_basis_version("4.01"); + break; + case XHTML_1_0_STRICT: + case XHTML_1_0_TRANSITIONAL: + case XHTML_1_0_FRAMESET: + case XHTML_1_1: + // Force a second call to parse as XML. 100 is a + // magic code for the first XML call. + return 100; + } + _textMD.setMarkup_language(_doctype); + if (docDesc == null) { + info.setMessage (new InfoMessage ("Code for appropriate HTML version not available yet:" + + "substituting HTML 3.2")); + docDesc = new Html3_2DocDesc (); + } + docDesc.validate (elements, info); + metadata = docDesc.getMetadata (); + + // Try to get the charset from the meta Content + if (metadata.getCharset() != null) { + _textMD.setCharset(metadata.getCharset()); + } else { + _textMD.setCharset(TextMDMetadata.CHARSET_ISO8859_1); + } + String textMDEncoding = _textMD.getCharset(); + if (textMDEncoding.indexOf("UTF") != -1) { + _textMD.setByte_order( + _bigEndian?TextMDMetadata.BYTE_ORDER_BIG:TextMDMetadata.BYTE_ORDER_LITTLE); + _textMD.setByte_size("8"); + _textMD.setCharacter_size("variable"); + } + else { + _textMD.setByte_order( + _bigEndian?TextMDMetadata.BYTE_ORDER_BIG:TextMDMetadata.BYTE_ORDER_LITTLE); + _textMD.setByte_size("8"); + _textMD.setCharacter_size("1"); + } + } + catch (ParseException e) { + Token t = e.currentToken; + info.setMessage (new ErrorMessage ("Parse error", + "Line = " + t.beginLine + ", column = " + t.beginColumn)); + info.setWellFormed (false); + } + catch (TokenMgrError f) { + info.setMessage (new ErrorMessage ("TokenMgrError: " + + f.getLocalizedMessage())); + info.setWellFormed (false); + } + + if (info.getWellFormed() == RepInfo.FALSE) { + return 0; + } + + if (type != 0) { + if (profileNames[type] != null) { + info.setProfile (profileNames[type]); + } + info.setVersion (versionNames[type]); + } + + if (metadata != null) { + Property property = metadata.toProperty (_withTextMD?_textMD:null); + if (property != null) { + info.setProperty (property); + } + } + + if (ckSummer != null){ + info.setSize (_cstream.getNBytes ()); + info.setChecksum (new Checksum (ckSummer.getCRC32 (), + ChecksumType.CRC32)); + String value = ckSummer.getMD5 (); + if (value != null) { + info.setChecksum (new Checksum (value, ChecksumType.MD5)); + } + if ((value = ckSummer.getSHA1 ()) != null) { + info.setChecksum (new Checksum (value, ChecksumType.SHA1)); + } + } + + return 0; + } + + + /** + * Check if the digital object conforms to this Module's + * internal signature information. + * + * HTML is one of the most ill-defined of any open formats, so + * checking a "signature" really means using some heuristics. The only + * required tag is TITLE, but that could occur well into the file. So we + * look for any of three strings -- taking into account case-independence + * and white space -- within the first sigBytes bytes, and call that + * a signature check. + * + * @param file A File object for the object being parsed + * @param stream An InputStream, positioned at its beginning, + * which is generated from the object to be parsed + * @param info A fresh RepInfo object which will be modified + * to reflect the results of the test + */ + public void checkSignatures (File file, + InputStream stream, + RepInfo info) + throws IOException + { + info.setFormat (_format[0]); + info.setMimeType (_mimeType[0]); + info.setModule (this); + char[][] sigtext = new char[3][]; + sigtext[0] = "= 2) { + firstElem = (JHElement) elements.get (1); + } + if (!(firstElem instanceof JHDoctype)) { + return 0; // no DOCTYPE found + } + List dt = ((JHDoctype) firstElem).getDoctypeElements (); + if (dt.size () < 3) { + return 0; + } + try { + // Is DOCTYPE case sensitive? Assume not. + String str = ((String) dt.get(0)).toUpperCase (); + if (!"HTML".equals (str)) { + // It's not HTML + return -1; + } + str = ((String) dt.get (1)).toUpperCase (); + if (!"PUBLIC".equals (str)) { + return 0; + } + str = stripQuotes (((String) dt.get (2)).toUpperCase ()); + _doctype = str; + if ("-//W3C//DTD HTML 3.2 FINAL//EN".equals (str) || + "-//W3C//DTD HTML 3.2//EN".equals (str)) { + return HTML_3_2; + } + else if ("-//W3C//DTD HTML 4.0//EN".equals (str)) { + return HTML_4_0_STRICT; + } + else if ("-//W3C//DTD HTML 4.0 TRANSITIONAL//EN".equals (str)) { + return HTML_4_0_TRANSITIONAL; + } + else if ("-//W3C//DTD HTML 4.0 FRAMESET//EN".equals (str)) { + return HTML_4_0_FRAMESET; + } + else if ("-//W3C//DTD HTML 4.01//EN".equals (str)) { + return HTML_4_01_STRICT; + } + else if ("-//W3C//DTD HTML 4.01 TRANSITIONAL//EN".equals (str)) { + return HTML_4_01_TRANSITIONAL; + } + else if ("-//W3C//DTD HTML 4.01 FRAMESET//EN".equals (str)) { + return HTML_4_01_FRAMESET; + } + } + catch (Exception e) { + // Really shouldn't happen, but if it does we've got + // a bad doctype + return 0; + } + return 0; + } + + /* See if this document, even if it lacks a doctype, is most likely + * XHTML. The test is that the document starts with an XML declaration + * and has "html" for its first tag. + * + * Returns: + * 0 if there's no XML declaration + * 1 if there's an XML declaration but no html tag; in this + * case it's probably some other kind of XML + * 2 if there's an XML declaration and an html tag + * + */ + protected int seemsToBeXHTML (List elements) + { + JHElement elem; + try { + elem = (JHElement) elements.get (0); + if (!(elem instanceof JHXmlDecl)) { + return 0; + } + Iterator iter = elements.iterator (); + while (iter.hasNext ()) { + elem = (JHElement) iter.next (); + if (elem instanceof JHOpenTag) { + JHOpenTag tag = (JHOpenTag) elem; + return ("html".equals (tag.getName()) ? 2 : 1); + } + } + } + catch (Exception e) { + return 0; // document must be really empty + } + return 1; + } + + /* Remove quotes from the beginning and end of a string. If it doesn't + * have quotes in both places, leave it alone. */ + protected String stripQuotes(String str) { + int len = str.length (); + if (str.charAt (0) == '"' && + str.charAt (len - 1) == '"') { + return str.substring(1, len - 1); + } + else { + return str; + } + } + + /* Checks if the XML module is available. + */ + protected static boolean isXmlAvailable () + { + try { + Class.forName ("edu.harvard.hul.ois.jhove.module.XmlModule"); + return true; + } + catch (Exception e) { + return false; + } + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/Jpeg2000Module.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/Jpeg2000Module.java new file mode 100644 index 00000000..dd4072d2 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/Jpeg2000Module.java @@ -0,0 +1,878 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004-2007 by JSTOR and the President and Fellows of Harvard College + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module; + +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.jpeg2000.*; +import java.io.*; +import java.util.*; + +/** + * Module for identification and validation of JPEG 2000 files. + * + * Code is included for JPX, but is almost entirely untested due + * to the lack of available sample files that use more than a tiny + * fraction of the features. The current version of the module + * is based on the typo-laden and inconsisent "JPEG 2000 Part II + * Final Committee Draft" (7 December 2000). + * The final standard (5 May 2004) has just reached our hands, + * and this code will be reviewed against it and revised accordingly + * in the near future. (All opinions expressed in this + * paragraph are those of the programmer.) + * + * JPEG 2000 format is not JPEG format, and isn't compatible + * with it. As with JPEG, JPEG 2000 is not in itself + * a file format. It can be encapsulated in JP2 or JPX format, + * which are recognized here. + * + * @author Gary McGath + */ +public class Jpeg2000Module extends ModuleBase { + +/* + * Some general notes on JPEG 2000 parsing: + * The format started out as a straightforward stream format, which + * could be parsed as a stream from beginning to end. But JPX throws + * us several curves, requiring random access. The Cross Reference + * Box is a direction to replace itself with another box somewhere + * else in the file (or even in one or more entirely different files!), which + * may be scattered through different parts of the file even if it + * doesn't spread to other files. The Fragment Table Box does the + * same for codestreams. In addition, the Binary Filter Box allows + * boxes to be hidden within its compressed or encrypted data. All this + * makes the concept of "parent box" trickier than it is in unextended + * JP2, since parenthood and containment are not necessarily the same thing. + * + * The way I've chosen deal with this rather chimerical design is to + * have a RandomAccessFile for a base, but have each Box based on a + * DataInputStream. Not every Box has its own separate stream -- that + * would be wasteful -- but there can be multiple box streams active at + * once. Each box (JP2Box) has a parent (BoxHolder), which can be + * another JP2Box or a TopLevelBoxHolder. The extension to multiple + * files isn't supported here, since the job of Jhove is to validate + * individual files, but it wouldn't be difficult to add. + * + * Every BoxHolder (hence every box) implements the Iterator interface, + * so that a box can get its subboxes in a way which is blind to the + * details of their encoding. + */ + + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + + private static final String NAME = "JPEG2000-hul"; + private static final String RELEASE = "1.3"; + private static final int[] DATE = { 2007, 1, 8 }; + private static final String[] FORMAT = { "JPEG 2000", "JP2", "JPX" }; + private static final String COVERAGE = "JP2 (ISO/IEC 15444-1:2000/" + + "ITU-T Rec. T.800 (200)), JPX (ISO/IEC 15444-2:2004)"; + private static final String[] MIMETYPE = { "image/jp2", "image/jpx" }; + private static final String WELLFORMED = + "The required Signature and File Type box structures are the first " + + "two boxes in the file; all boxes required by a given profile exist " + + "in the file; all box structures are well-formed (a four byte " + + "unsigned integer Box Length, followed by a four byte unsigned " + + "integer Box type, followed by a eight byte unsigned integer Box " + + "Length, followed by the Box Contents); no data exist before the " + + "first byte of the first box or after the last byte of the last box"; + private static final String VALIDITY = "The file is well-formed"; + private static final String REPINFO = + "Properties capturing the technical attributes of the JPEG 2000 " + + "image from all boxes"; + private static final String NOTE = null; + private static final String RIGHTS = + "Copyright 2004-2007 by JSTOR and the " + + "President and Fellows of Harvard College. " + + "Released under the GNU Lesser General Public License."; + + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + /* NISO image metadata for the current image */ + protected NisoImageMetadata _niso; + + /* NISO image metadata for default image values */ + protected NisoImageMetadata _defaultNiso; + + /* DataInputSream for reading the file */ + protected DataInputStream _dstream; + + /* RandomAccessFile for reading the file */ + protected RandomAccessFile _raf; + + /* RAFInputStream underlying the DataInputStream */ + protected RAFInputStream _rafStream; + + /* Properties which are global to the file */ + protected List _propList; + + /* List of codestreams. An entry can be created by + * either a codestream or a codestream header, depending + * on which is seen first. The elements of the List + * are Codestream objects. */ + protected List codestreams; + + /* List of Binary Filter properties. */ + protected List binaryFilterProps; + + /* List of Association properties. */ + protected List associationProps; + + /* List of Digital Signature properties. */ + protected List digitalSigProps; + + /* Number of Contiguous Codestreams seen. + * May be less than or equal to the size of codestreams. */ + protected int nCodestreams; + + /* Number of Codestream headers seen */ + protected int nCodestreamHeaders; + + /* List of Color Spec properties */ + protected List colorSpecs; + + /* List of UUIDBox properties */ + protected List uuids; + + /* List of Compositing Layer properties */ + protected List composLayers; + + /* List of UUID Info properties */ + protected List uuidInfos; + + /* List of data (String) extracted from XML boxes */ + protected List xmlList; + + /* Flag for JP2 headerbox detection */ + protected boolean jp2HdrSeen; + + /* Flag for Reader Requirements detection */ + protected boolean rreqSeen; + + /* Flag for Color Specification detection */ + protected boolean colorSpecSeen; + + /* Flag for Image Header Box detection */ + protected boolean imageHeaderSeen; + + /* Flag for JP2 compliance */ + protected boolean jp2Compliant; + + /* Flag for JPX compliance */ + protected boolean jpxCompliant; + + /* The Codestream currently being worked on. This + * is an element of codestreams */ + protected Codestream curCodestream; + + /* Flag which is true when we are reading a + * BinaryFilterBox, which needs special handling + * for byte counts. */ + protected boolean filterMode; + + /* Fixed value for first 12 bytes */ + private static final int[] sigByte = + { + 0X00, + 0X00, + 0X00, + 0X0C, + 0X6A, + 0X50, + 0X20, + 0X20, + 0X0D, + 0X0A, + 0X87, + 0X0A }; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + /** + * Instantiate a JpegModule object. + */ + public Jpeg2000Module() { + super( + NAME, + RELEASE, + DATE, + FORMAT, + COVERAGE, + MIMETYPE, + WELLFORMED, + VALIDITY, + REPINFO, + NOTE, + RIGHTS, + true); + + Agent agent = + new Agent("Harvard University Library", AgentType.EDUCATIONAL); + agent.setAddress( + "Office for Information Systems, " + + "90 Mt. Auburn St., " + + "Cambridge, MA 02138"); + agent.setTelephone("+1 (617) 495-3724"); + agent.setEmail("jhove-support@hulmail.harvard.edu"); + _vendor = agent; + + Document doc = new Document ("Information technology -- " + + "JPEG 2000 image coding system -- Part 1: Code coding system", + DocumentType.STANDARD); + Agent isoAgent = new Agent ("ISO", AgentType.STANDARD); + isoAgent.setAddress ("1, rue de Varembe, Casa postale 56, " + + "CH-1211, Geneva 20, Switzerland"); + isoAgent.setTelephone ("+41 22 749 01 11"); + isoAgent.setFax ("+41 22 733 34 30"); + isoAgent.setEmail ("iso@iso.ch"); + isoAgent.setWeb ("http://www.iso.org/"); + doc.setAuthor (isoAgent); + doc.setIdentifier (new Identifier ("ISO/IEC 15444-1:2000", + IdentifierType.ISO)); + doc.setDate ("2002-07-31"); + _specification.add (doc); + + doc = new Document ("Information technology -- " + + "JPEG 2000 image coding system -- " + + "Part 2: Extensions", + DocumentType.STANDARD); + doc.setAuthor (isoAgent); // ISO agent + doc.setIdentifier (new Identifier ("ISO/IEC 15444-2:2004", + IdentifierType.ISO)); + doc.setDate ("2004-05-15"); + _specification.add (doc); + + doc = new Document ("MIME Type Registrations for JPEG 2000 " + + "(ISO/IEC 15444) RFC 3745", + DocumentType.RFC); + Agent ietfAgent = new Agent ("IETF", AgentType.STANDARD); + ietfAgent.setWeb ("http://www.ietf.org"); + doc.setPublisher (ietfAgent); + agent = new Agent ("D. Singer", + AgentType.OTHER); + doc.setAuthor(agent); + agent = new Agent ("R. Clark", + AgentType.OTHER); + doc.setAuthor(agent); + agent = new Agent ("D. Lee", + AgentType.OTHER); + doc.setAuthor(agent); + doc.setDate ("2004-04"); + Identifier ident = new Identifier + ("http://www.ietf.org/rfc/rfc3745.txt", + IdentifierType.URL); + doc.setIdentifier (ident); + _specification.add (doc); + + doc = new Document ("ITU-T Rec. T.800 (2002), Information " + + "technology -- JPEG 2000 image coding system: " + + "Core coding system", + DocumentType.STANDARD); + Agent ituAgent = new Agent ("ITU", AgentType.STANDARD); + ituAgent.setAddress("ITU, Place des Nations, " + + "CH-1211 Geneva 20 Switzerland"); + ituAgent.setTelephone("+41 22 730 51 11"); + ituAgent.setFax("+41 22 730 6500"); + ituAgent.setEmail ("itumail@itu.int"); + ituAgent.setWeb ("http://www.itu.int/home/"); + doc.setAuthor (ituAgent); + doc.setDate ("2002-08"); + ident = new Identifier ("ITU-T Rec. T.800 (2002)", + IdentifierType.ITU); + doc.setIdentifier (ident); + _specification.add (doc); + + + Signature sig = + new InternalSignature (sigByte, SignatureType.MAGIC, + SignatureUseType.MANDATORY, 0, + ""); + _signature.add (sig); + + sig = + new ExternalSignature( + ".jp2", + SignatureType.EXTENSION, + SignatureUseType.OPTIONAL); + _signature.add(sig); + + sig = + new ExternalSignature( + ".jpx", + SignatureType.EXTENSION, + SignatureUseType.OPTIONAL); + _signature.add(sig); + + sig = + new ExternalSignature( + ".jpf", + SignatureType.EXTENSION, + SignatureUseType.OPTIONAL); + _signature.add(sig); + + // Macintosh signature for JP2 files + sig = + new ExternalSignature( + "jp2 ", + SignatureType.FILETYPE, + SignatureUseType.OPTIONAL); + _signature.add(sig); + + // Macintosh signature for JPX files + sig = + new ExternalSignature( + "jpx ", + SignatureType.FILETYPE, + SignatureUseType.OPTIONAL); + _signature.add(sig); + + _bigEndian = true; + } + + /** + * Parse the content of a stream digital object and store the + * results in RepInfo. + * + * This module is based on a RandomAccessFile because of the + * requirements of the (so far) rarely used fragmented codestream + * feature. Since just about everything else can be done with + * an InputStream, we use a RAFInputStream except on the occasions + * when random access is needed. We pass the module as the counted + * argment to all read calls, so that we can compute relative + * positions in the stream based on _nByte. + * + * @param raf A RandomAccessFile to be parsed. + * + * @param info A fresh (on the first call) RepInfo object + * which will be modified + * to reflect the results of the parsing + * If multiple calls to parse are made + * on the basis of a nonzero value being returned, + * the same RepInfo object should be passed with each + * call. + */ + public final void parse(RandomAccessFile raf, RepInfo info) + throws IOException { + initParse(); + _rafStream = new RAFInputStream (raf, + _je != null ? _je.getBufferSize() : 0); + _dstream = new DataInputStream + (_rafStream); + info.setFormat(_format[0]); + info.setMimeType(_mimeType[0]); + info.setModule(this); + + _propList = new ArrayList(12); + Property metadata = + new Property( + "JPEG2000Metadata", + PropertyType.PROPERTY, + PropertyArity.LIST, + _propList); + + _raf = raf; + + // A JPEG 2000 file consists of a series of "boxes." + // The signature box must match the signature bytes. + int i = 0; + boolean badhdr = false; + try { + for (i = 0; i < 12; i++) { + int ch; + ch = readUnsignedByte(_dstream, this); + if (ch != sigByte[i]) { + badhdr = true; + break; + } + } + } + catch (IOException e) { + badhdr = true; + } + if (badhdr) { + info.setMessage (new ErrorMessage ("No JPEG 2000 header", i)); + info.setWellFormed(false); + return; + } + + /* If we got this far, take note that the signature is OK. */ + info.setSigMatch(_name); + + // Next check the file type box. + if (!readFileTypeBox(info)) { + return; + } + + // Go through the rest of the boxes. + if (!readBoxes(info)) { + return; + } + + if (info.getWellFormed() == RepInfo.FALSE) { + return; + } + + // File has been read successfully; do final processing. + + info.setProperty(metadata); + + // Calculate checksums, if necessary. + if (_je != null && _je.getChecksumFlag ()) { + if (info.getChecksum ().size () == 0) { + Checksummer ckSummer = new Checksummer (); + calcRAChecksum (ckSummer, raf); + setChecksums (ckSummer, info); + } + } + + // Reader Requirements box is mandatory for JPX + if (!rreqSeen || info.getValid() != RepInfo.TRUE) { + jpxCompliant = false; + } + if (!imageHeaderSeen || !colorSpecSeen || info.getValid() != RepInfo.TRUE) { + jp2Compliant = false; + } + if (jp2Compliant) { + info.setProfile("JP2"); + } + if (jpxCompliant) { + info.setProfile ("JPX"); + String mime = _mimeType[1]; + info.setMimeType(mime); + curCodestream.getNiso().setMimeType (mime); + // This doesn't deal with a case where some + // codestreams are JP2 and others are JPX; + // can that happen? + _defaultNiso.setMimeType (mime); + } + + if (!colorSpecs.isEmpty()) { + _propList.add( + new Property( + "ColorSpecs", + PropertyType.PROPERTY, + PropertyArity.LIST, + colorSpecs)); + } + if (!binaryFilterProps.isEmpty ()) { + _propList.add( + new Property( + "BinaryFilters", + PropertyType.PROPERTY, + PropertyArity.LIST, + binaryFilterProps)); + } + if (!associationProps.isEmpty ()) { + _propList.add( + new Property( + "Associations", + PropertyType.PROPERTY, + PropertyArity.LIST, + associationProps)); + } + + if (!digitalSigProps.isEmpty ()) { + _propList.add( + new Property( + "DigitalSignatures", + PropertyType.PROPERTY, + PropertyArity.LIST, + digitalSigProps)); + } + if (!uuids.isEmpty()) { + _propList.add( + new Property( + "UUIDs", + PropertyType.PROPERTY, + PropertyArity.LIST, + uuids)); + } + if (!composLayers.isEmpty ()) { + _propList.add( + new Property( + "CompositingLayers", + PropertyType.PROPERTY, + PropertyArity.LIST, + composLayers)); + } + if (!uuidInfos.isEmpty()) { + _propList.add( + new Property( + "UUIDInfoBoxes", + PropertyType.PROPERTY, + PropertyArity.LIST, + uuidInfos)); + } + if (!codestreams.isEmpty()) { + List csProps = new ArrayList(codestreams.size()); + ListIterator csIter = codestreams.listIterator(); + while (csIter.hasNext()) { + Codestream cs = (Codestream) csIter.next(); + csProps.add(cs.makeProperty()); + } + _propList.add( + new Property( + "Codestreams", + PropertyType.PROPERTY, + PropertyArity.LIST, + csProps)); + + } + if (!xmlList.isEmpty()) { + _propList.add( + new Property( + "XML", + PropertyType.STRING, + PropertyArity.LIST, + xmlList)); + } + + return; + } + + /** + * Return the current position in the module. + * This is somewhat optimistically named; it should + * be trusted only for relative positions when no + * seek operations occur in between calls to getFilePos. + */ + public long getFilePos() { + // return _nByte; + try { + return _rafStream.getFilePos (); + } + catch (IOException e) { + // really shouldn't happen + return 0; + } + } + + /** Seek to a new file position. */ + public void seek (long pos) throws IOException { + _rafStream.seek (pos); + } + + /** Returns the default NisoImageMetadata object. */ + public NisoImageMetadata getDefaultNiso() { + return _defaultNiso; + } + + /** Returns the current NisoImageMetadata object. + * If curCodestream is null, returns the default + * NisoImageMetadata, otherwise returns the + * NisoImageMetadata of curCodestream. + */ + public NisoImageMetadata getCurrentNiso() { + if (curCodestream == null) { + return _defaultNiso; + } else { + return curCodestream.getNiso(); + } + } + + /** Returns the nth Codestream, creating it if necessary, + * and make it the current one. The value of nCodestreams + * or nCodestreamHeaders + * is not affected, even if a new Codestream is created. */ + public Codestream getCodestream(int n) { + Codestream cs; + if (n < codestreams.size()) { + cs = (Codestream) codestreams.get(n); + } else { + cs = new Codestream(); + cs.setDefaultNiso(_defaultNiso); + codestreams.add(cs); + } + curCodestream = cs; + return cs; + } + + /** Returns the codestream count. + * Because items may be added to the codestreams list + * when only the header is seen, this may be less than + * the size of the codestreams list. It will never be + * more. */ + public int getNCodestreams() { + return nCodestreams; + } + + /** Returns the codestream header count. */ + public int getNCodestreamHeaders() { + return nCodestreamHeaders; + } + + + public boolean isJP2HdrSeen() { + return jp2HdrSeen; + } + + /** Sets the codestream count. This affects only the + * variable, not the size of the codestreams list. + * The codestream count should never be set to a value + * larger than the codestreams list; it signifies the + * number of elements of the list for which codestream + * boxes have actually been seen. + */ + public void setNCodestreams(int n) { + nCodestreams = n; + } + + /** Sets the codestream header count. */ + public void setNCodestreamHeaders(int n) { + nCodestreamHeaders = n; + } + + /** Set the flag indicating that a JP2 header + * has been seen. */ + public void setJP2HdrSeen(boolean b) { + jp2HdrSeen = b; + } + + /** Set the flag indicating the reader requirements box + * has been seen. */ + public void setRReqSeen (boolean b) { + rreqSeen = b; + } + + + /** Set the flag indicating the color specification box + * has been seen. */ + public void setColorSpecSeen (boolean b) { + colorSpecSeen = b; + } + + /** Set the flag indicating the color specification box + * has been seen. */ + public void setImageHeaderSeen (boolean b) { + imageHeaderSeen = b; + } + + + /** Sets a flag indicating JP2 compliance. If the + * flag is set to true, and the JPX + * compliance flag is also true, set the MIME type + * to "image/jpx". */ + public void setJP2Compliant(boolean b) { + jp2Compliant = b; + if (jp2Compliant && jpxCompliant) { + _defaultNiso.setMimeType(MIMETYPE[1]); + } + } + + /** Sets a flag indicating JPX compliance. */ + public void setJPXCompliant(boolean b) { + jpxCompliant = b; + } + + /** Adds a property to the JPEG2000 metadata.*/ + public void addProperty(Property p) { + _propList.add(p); + } + + /** Adds a color spec property to the metadata. */ + public void addColorSpec(Property p) { + colorSpecs.add(p); + } + + /** Adds a binary filter property to the metadata. */ + public void addBinaryFilterProp (Property p) { + binaryFilterProps.add (p); + } + + /** Adds an association property to the metadata. */ + public void addAssociationProp (Property p) { + associationProps.add (p); + } + + /** Adds a digital signature property to the metadata. */ + public void addDigitalSignatureProp (Property p) + { + digitalSigProps.add (p); + } + + /** Adds a UUID property to the list of UUID + * properties. Called from the UUIDBox. + */ + public void addUUID(Property p) { + uuids.add(p); + } + + /** Adds a UUIDInfo property to the list of UUIDInfo + * properties. Called from UUIDInfoBox. + */ + public void addUUIDInfo(Property p) { + uuidInfos.add(p); + } + + /** Adds a Compositing Layer property to the list + * of Compositing Layer properties. Called + * from the ComposLayerHdrBox. */ + public void addComposLayer (Property p) { + composLayers.add (p); + } + + /** Adds an XML string to the list of XML properties. + * Called from XMLBox. */ + public void addXML(String s) { + xmlList.add(s); + } + + /** + * Reads 4 bytes and concatenates them into a String. + */ + public String read4Chars(DataInputStream stream) throws IOException { + StringBuffer sbuf = new StringBuffer(4); + for (int i = 0; i < 4; i++) { + int ch = readUnsignedByte(stream, this); + sbuf.append((char) ch); + } + return sbuf.toString(); + } + + /** One-argument version of readUnsignedShort. + * JPEG2000 is always big-endian, so readUnsignedShort can + * unambiguously drop its endian argument. */ + public int readUnsignedShort(DataInputStream stream) throws IOException { + return readUnsignedShort(stream, true, this); + } + + /** One-argument version of readUnsignedInt. + * JPEG2000 is always big-endian, so readUnsignedInt can + * unambiguously drop its endian argument. */ + public long readUnsignedInt(DataInputStream stream) throws IOException { + return readUnsignedInt(stream, true, this); + } + + /** One-argument version of readSignedLong. + * JPEG2000 is always big-endian, so readSignedLong can + * unambiguously drop its endian argument. */ + public long readSignedLong(DataInputStream stream) throws IOException { + return readSignedLong(stream, true, this); + } + + /** + * Initializes the state of the module for parsing. + */ + protected void initParse() { + super.initParse(); + colorSpecs = new LinkedList(); + binaryFilterProps = new LinkedList (); + associationProps = new LinkedList (); + digitalSigProps = new LinkedList (); + uuids = new LinkedList(); + uuidInfos = new LinkedList(); + composLayers = new LinkedList (); + xmlList = new LinkedList(); + //uuidList = new LinkedList (); + codestreams = new LinkedList(); + curCodestream = null; + nCodestreams = 0; + nCodestreamHeaders = 0; + jp2HdrSeen = false; + //paletteSeen = false; + //cmSeen = false; + rreqSeen = false; + filterMode = false; + _defaultNiso = new NisoImageMetadata(); + _defaultNiso.setByteOrder("big-endian"); + _defaultNiso.setMimeType(MIMETYPE[0]); + + // Compliance flags are innocent till proven guilty + jp2Compliant = true; + jpxCompliant = true; + } + + /* Dispatcher for reading boxes. Because of the existence + * of filter boxes, any box can come from either _dstream + * or from a separate input stream that is being undeflated. + * This dispatcher always works from _dstream. We must + * pass a stream to every box function, to allow these + * multiple sources. + */ + protected boolean readBoxes(RepInfo info) throws IOException { + // From here on, boxes may occur with some freedom + // of order. Apparently the only indication that + // we're done is an end-of-file condition. + + TopLevelBoxHolder bh = new TopLevelBoxHolder (this, _raf, info, + _dstream); + while (bh.hasNext ()) { + JP2Box box = (JP2Box) bh.next (); + // TopLevelBoxHolder.next() may not be reliable about detecting + // that no more boxes are left. + if (box == null) { + break; + } + if (!box.readBox ()) { + return false; + } + } + + return true; + } + + /* Read the file type box. This assumes that we are actually + * positioned at the file type box, and reads its header. + * If we get something other than a file type box, the file + * is not well-formed. + */ + protected boolean readFileTypeBox(RepInfo info) throws IOException { + BoxHeader hdr = new BoxHeader(this, _dstream); + hdr.readHeader(); + // 8 bytes have been read + if (!"ftyp".equals(hdr.getType())) { + info.setMessage( + new ErrorMessage( + "Expected File Type Box, got " + hdr.getType(), + _nByte)); + info.setWellFormed(false); + return false; + + } + FileTypeBox box = new FileTypeBox(_raf); + box.setBoxHeader(hdr); + box.setDataInputStream(_dstream); + box.setRandomAccessFile (_raf); + box.setModule(this); + box.setRepInfo(info); + if (!box.readBox()) { + return false; + } + return true; + } + + /* This is called for any box that isn't recognized, or + * by placeholder methods for boxes that haven't yet + * been coded. */ + protected boolean skipOverBox( + BoxHeader hdr, + RepInfo info, + DataInputStream dstrm) + throws IOException { + if (hdr.getLength() != 0) { + skipBytes(dstrm, (int) hdr.getDataLength(), this); + } + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/JpegModule.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/JpegModule.java new file mode 100644 index 00000000..6ced48f1 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/JpegModule.java @@ -0,0 +1,1830 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003-2007 by JSTOR and the President and Fellows of Harvard College + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module; + +import java.io.*; +import java.util.*; +import java.text.NumberFormat; +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.jpeg.*; +import org.xml.sax.XMLReader; +import org.xml.sax.SAXException; +import javax.xml.parsers.SAXParserFactory; + +/** + * Module for identification and validation of JPEG files. + * + * General notes: + * + * There is no such thing as a "JPEG file format." There are several + * commonly used file formats which encapsulate JPEG data and conform + * to the JPEG stream format. There are also many formats which can + * encapsulate JPEG data within some larger wrapper; this module + * does not attempt to recognize them. Only JPEG file formats which + * are JPEG streams are treated here. A JPEG stream which isn't one + * of the known file formats will be regarded as well-formed, but + * not valid. To be valid, a file must conform to one of the following: + * JFIF, SPIFF, and JPEG/Exif. Other formats may be added in the future. + * + * This module uses the JPEG-L method of detecting a marker following + * a data stream, checking for a 0 high bit rather than an entire 0 + * byte. So long at no JPEG markers are defined with a value from 0 + * through 7F, this is valid for all JPEG files. + * + * * @author Gary McGath + */ +public class JpegModule extends ModuleBase +{ + /****************************************************************** + * DEBUGGING FIELDS. + * All debugging fields should be set to false for release code. + ******************************************************************/ + + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + + private static final String NAME = "JPEG-hul"; + private static final String RELEASE = "1.2"; + private static final int [] DATE = {2007, 2, 13}; + private static final String [] FORMAT = { + "JPEG", "ISO/IEC 10918-1:1994", "Joint Photographic Experts Group", + "JFIF", "JPEG File Interchange Format", + "SPIFF", "ISO/IEC 10918-3:1997", + "Still Picture Interchange File Format", + "JTIP", "ISO/IEC 10918-3:1997", "JPEG Tiled Image Pyramid", + "JPEG-LS", "ISO/IEC 14495" + }; + private static final String COVERAGE = + "JPEG (ISO/IEC 10918-1:1994), JFIF 1.02, " + + "SPIFF (ISO/IEC 10918-3:1997), " + + "Exif 2.0, 2.1 (JEIDA-49-1998), and 2.2 (JEITA CP-3451), " + + "JTIP (ISO/IEC 10918-3:1997), JPEG-LS (ISO/IEC 14495)"; + private static final String [] MIMETYPE = {"image/jpeg"}; + private static final String WELLFORMED = "A JPEG file is well-formed if " + + "the first three bytes are 0xFFD8FF, it consists of one or more " + + "correctly formatted segments (using markers 0xC0 through 0xFE), " + + "and the data streams following RSTn and SOS markers are correctly " + + "terminated"; + private static final String VALIDITY = "A JPEG file is valid if " + + "well-formed; the first non-comment segment is APP0 (with " + + "identifier 0x4A46494600, indicating JFIF or JTIP), APP1 (with " + + "identifier (0x457869660000, indicating Exif), APP8 (with " + + "identifier 0x545049464600, indicating SPIFF), or JPG7 (or SOF55, " + + "indicating JPEG-LS); D8 marker occurs only at the beginning of " + + "the file; any DTT segments are preceded by DTI segments; and all " + + "DTI segment tiling type have a value of 0, 1, or 2"; + private static final String REPINFO = "Additional representation " + + "information includes: NISO Z39.87 Digital Still Image Technical " + + "Metadata and segment-specific metadata"; + private static final String NOTE = null; + private static final String RIGHTS = "Copyright 2003-2007 by JSTOR and " + + "the President and Fellows of Harvard College. " + + "Released under the GNU Lesser General Public License."; + + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + /* Profile names. These are just informal identifiers, and probably will + * be formalized later. + */ + protected String jfifProfileName = "JFIF"; + protected String spiffProfileName = "SPIFF"; + protected String exifProfileName = "Exif"; + protected String jpeglProfileName = "JPEG-L"; + + /* a NumberFormat for handling the minor part of version numbers */ + protected NumberFormat minorFmt; + + /* Checksummer object */ + protected Checksummer _ckSummer; + + /* Input stream wrapper which handles checksums */ + protected ChecksumInputStream _cstream; + + /* Data input stream wrapped around _cstream */ + protected DataInputStream _dstream; + + /* Top-level metadata property */ + protected Property _metadata; + + /* Property for current image. This should go into a + * list of image properties, which in turn becomes + * the "image" property of _metadata. */ + protected Property _imageProp; + + /* Exif property */ + protected Property _exifProp; + + /* XMP property */ + protected Property _xmpProp; + + /* NISO image metadata */ + protected NisoImageMetadata _niso; + + /* Top-level property list */ + protected List _propList; + + /* List of image properties. */ + protected List _imageList; + + /* Tiling information, if a DTI has been seen. */ + protected Tiling _tiling; + + /* List of quantization tables. */ + protected List _quantTables; + + /* List of arithmetic conditioning entries */ + protected List _arithCondTables; + + /* List of SRS entries. */ + protected List _srsList; + + /* Property list for the primary image. */ + protected List _primaryImageList; + + /* Number of segments read */ + protected int _numSegments; + + /* Number of scans in the current image */ + protected int _numScans; + + /* Restart interval */ + protected int _restartInterval; + + /* Flag indicating an APP0 JFIF segment has been read */ + protected boolean _seenJFIF; + + /* Flag indicating an APP8 SPIFF segment has been read */ + protected boolean _seenSPIFF; + + /* Flag indicating a JPEG-L SOF55 (aka JPG7) segment + * has been read */ + protected boolean _seenJPEGL; + + /* Flag to make sure we report signature matching only once. */ + protected boolean _reportedSigMatch; + + /* SPIFF directory information. */ + protected SpiffDir _spiffDir; + + /* Flag indicating an APP1 Exif segment has been read */ + protected boolean _seenExif; + + /* Flag indicating the Exif profile is satisfied */ + protected boolean _exifProfileOK; + + /* Flag indicating lack of a JFIF segment has been reported */ + protected boolean _reportedJFIF; + + /* Flag indicating the first SOF has been read */ + protected boolean _seenSOF; + + /* List of comment text */ + protected List _commentsList; + + /* List of extensions used */ + protected List _jpegExtsList; + + /* List of application segments used */ + protected List _appSegsList; + + /* List of expand reference components markers. + * Members are boolean[2] */ + protected List _expList; + + /* Set of compression types used. */ + protected Set _compressSet; + + /* Capability 0 byte, from VER segment. -1 if none. */ + protected int _capability0; + + /* Capability 1 byte, from VER segment. -1 if none. */ + protected int _capability1; + + /* Fixed value for first 3 bytes */ + protected static final int[] sigByte = + { 0XFF, 0XD8, 0XFF }; + + /* Resolution units. */ + protected int _units; + + /* X resolution (or pixel aspect ratio X). */ + protected int _xDensity; + + /* Y resolution (or pixel aspect ration Y). */ + protected int _yDensity; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + /** + * Instantiate a JpegModule object. + */ + public JpegModule () + { + super (NAME, RELEASE, DATE, FORMAT, COVERAGE, MIMETYPE, WELLFORMED, + VALIDITY, REPINFO, NOTE, RIGHTS, false); + + // Set up a simple NumberFormat for version reporting + minorFmt = NumberFormat.getInstance (); + minorFmt.setMinimumIntegerDigits(2); + + // Define HUL vendor agent + Agent agent = new Agent ("Harvard University Library", + AgentType.EDUCATIONAL); + agent.setAddress ("Office for Information Systems, " + + "90 Mt. Auburn St., " + + "Cambridge, MA 02138"); + agent.setTelephone ("+1 (617) 495-3724"); + agent.setEmail("jhove-support@hulmail.harvard.edu"); + _vendor = agent; + + // Define C-Cube JPEG 1.02 doc + Document doc = new Document + ("Eric Hamilton, JPEG File Interchange Format, " + + "Version 1.02, September 1, 1992", + DocumentType.WEB); + agent = new Agent ("C-Cube Microsystems", AgentType.COMMERCIAL); + agent.setAddress ("1778 McCarthy Boulevard, Milipitas, CA 95035"); + agent.setTelephone ("+1 (408) 944-6314"); + agent.setFax ("+1 (408) 944-6314"); + doc.setPublisher (agent); + doc.setDate ("1992-09-01"); + doc.setIdentifier (new Identifier + ("http://www.w3.org/Graphics/JPEG/jfif3.pdf", + IdentifierType.URL)); + _specification.add (doc); + + // Define ISO standard + doc = new Document + ("ISO/IEC 10918-1:1994(E), Information technology -- " + + "Digital compression and coding of continuous-tone " + + "still images: Requirements and guidelines", + DocumentType.STANDARD); + Agent isoAgent = new Agent ("ISO", AgentType.STANDARD); + isoAgent.setAddress ("1, rue de Varembe, Casa postale 56, " + + "CH-1211, Geneva 20, Switzerland"); + isoAgent.setTelephone ("+41 22 749 01 11"); + isoAgent.setFax ("+41 22 733 34 30"); + isoAgent.setEmail ("iso@iso.ch"); + isoAgent.setWeb ("http://www.iso.org"); + doc.setPublisher (isoAgent); + doc.setIdentifier (new Identifier ("CCITT REc. T.81 (1992 E)", + IdentifierType.CCITT)); + _specification.add (doc); + + // Define ISO extensions + doc = new Document + ("ISO/IEC 10918-3:1997(E), Digital compression" + + "and coding of continuous-tone still-images: " + + "Extensions", + DocumentType.STANDARD); + doc.setPublisher (isoAgent); + doc.setIdentifier (new Identifier ("ITU-T Rec. T.84 (1996 E)", + IdentifierType.CCITT)); + _specification.add (doc); + + // Define ISO lossless baseline + doc = new Document + ("ISO/IEC 14495-1:1999(E), Information technology -- " + + "Lossless and near-lossless compression of " + + "continuous-tone still images: Baseline", + DocumentType.STANDARD); + doc.setPublisher (isoAgent); + _specification.add (doc); + + // Define ISO lossless extensions + doc = new Document + ("ISO/IEC 14495-2:2003(E), Information technology -- " + + "Lossless and near-lossless compression of " + + "continuous-tone still images: Extensions", + DocumentType.STANDARD); + doc.setPublisher (isoAgent); + _specification.add (doc); + + // Define JEITA Exif 2.2 doc + doc = new Document ("Exchangeable image file format for digital " + + "still cameras: Exif Version 2.2", + DocumentType.STANDARD); + Agent jeitaAgent = new Agent ("Japan Electronics and Information Technology " + + "Industries Association", AgentType.STANDARD); + jeitaAgent.setWeb ("http://www.jeita.or.jp/"); + jeitaAgent.setAddress ("Mitsui Sumitomo Kaijo Building Annex, " + + "11, Kanda Surugadai 3-chome, Chiyoda-ku, " + + "Tokyo 101-0062, Japan"); + jeitaAgent.setTelephone("+81(03) 3518-6421"); + jeitaAgent.setFax ("+81(03) 3295-8721"); + doc.setPublisher (jeitaAgent); + doc.setDate("2002-04"); + Identifier ident = new Identifier ("JEITA CP-3451", IdentifierType.JEITA); + doc.setIdentifier (ident); + ident = new Identifier ("http://www.exif.org/Exif2-2.PDF", + IdentifierType.URL); + doc.setIdentifier (ident); + _specification.add (doc); + + // Define Exif 2.1 doc + doc = new Document ("Digital Still Camera Image File Format Standard " + + "(Exchangeable image file format for Digital Still Camera:Exif)", + DocumentType.STANDARD); + doc.setPublisher (jeitaAgent); + doc.setDate ("1998-12"); + ident = new Identifier ("JEITA JEIDA-49-1998", IdentifierType.JEITA); + doc.setIdentifier (ident); + ident = new Identifier ("http://www.exif.org/dcf-exif.PDF", + IdentifierType.URL); + doc.setIdentifier (ident); + _specification.add (doc); + + Signature sig = + new InternalSignature (sigByte, SignatureType.MAGIC, + SignatureUseType.MANDATORY, 0, + ""); + _signature.add (sig); + + sig = new ExternalSignature (".jpg", SignatureType.EXTENSION, + SignatureUseType.OPTIONAL); + _signature.add (sig); + + + sig = new ExternalSignature (".jls", SignatureType.EXTENSION, + SignatureUseType.OPTIONAL, + "Generally used for JPEG-LS (ISO/IEC 14495)"); + _signature.add (sig); + + sig = new ExternalSignature (".spf", SignatureType.EXTENSION, + SignatureUseType.OPTIONAL, + "Generally used for SPIFF (ISO/IEC 10918-3:1997)"); + _signature.add (sig); + + _bigEndian = true; + } + + /****************************************************************** + * Parsing methods. + ******************************************************************/ + + /** + * Check if the digital object conforms to this Module's + * internal signature information. + * + * @param file A RandomAccessFile, positioned at its beginning, + * which is generated from the object to be parsed + * @param stream An InputStream, positioned at its beginning, + * which is generated from the object to be parsed + * @param info A fresh RepInfo object which will be modified + * to reflect the results of the test + */ + public void checkSignatures (File file, InputStream stream, RepInfo info) + throws IOException + { + int i; + int ch; + _dstream = getBufferedDataStream (stream, _je != null ? + _je.getBufferSize () : 0); + for (i = 0; i < 3; i++) { + try { + ch = readUnsignedByte(_dstream, this); + } + catch (Exception e) { + ch = -1; + } + if (ch != sigByte[i]) { + info.setWellFormed (false); + return; + } + } + info.setModule (this); + info.setFormat (_format[0]); + info.setMimeType (_mimeType[0]); + info.setSigMatch(_name); + } + + + + /** + * Parse the content of a purported JPEG stream digital object and store the + * results in RepInfo. + * + * This function uses the JPEG-L method of detecting a marker following + * a data stream, checking for a 0 high bit rather than an entire 0 + * byte. So long at no JPEG markers are defined with a value from 0 + * through 7F, this is valid for all JPEG files. + * + * @param stream An InputStream, positioned at its beginning, + * which is generated from the object to be parsed + * @param info A fresh RepInfo object which will be modified + * to reflect the results of the parsing + * @param parseIndex Must be 0 in first call to parse. If + * parse returns a nonzero value, it must be + * called again with parseIndex + * equal to that return value. + */ + public int parse (InputStream stream, RepInfo info, int parseIndex) + throws IOException + { + initParse (); + info.setFormat (_format[0]); + info.setMimeType (_mimeType[0]); + info.setModule (this); + + /* We may have already done the checksums while converting a + temporary file. */ + _ckSummer = null; + if (_je != null && _je.getChecksumFlag () && + info.getChecksum ().size () == 0) { + _ckSummer = new Checksummer (); + _cstream = new ChecksumInputStream (stream, _ckSummer); + _dstream = getBufferedDataStream (_cstream, _je != null ? + _je.getBufferSize () : 0); + } + else { + _dstream = getBufferedDataStream (stream, _je != null ? + _je.getBufferSize () : 0); + } + _propList = new LinkedList (); + _metadata = new Property ("JPEGMetadata", + PropertyType.PROPERTY, + PropertyArity.LIST, + _propList); + if (!readHeader (info)) { + return 0; + } + _niso = new NisoImageMetadata(); + Property nisoProp = new Property ("NisoImageMetadata", + PropertyType.NISOIMAGEMETADATA, _niso); + _primaryImageList.add (nisoProp); + initNiso (); + + // Count the number of segments read, exclusive of the APP0 header. + boolean dataPlowing = false; + ErrorMessage msg; + try { + // When true, we have to go through data till we find a marker + loop1: + for (;;) { + int dbyt = 0; + boolean sawFF = false; + if (dataPlowing) { + for (;;) { + dbyt = readUnsignedByte (_dstream, this); + if (dbyt == 0XFF) { + sawFF = true; + // multiple FF's count same as one + } + else if (sawFF) { + // Note use of JPEG-L check. For a + // standard JPEG check, we would use + // (dbyt != 0) + if ((dbyt & 0X80) != 0 ) { + dataPlowing = false; + break; + } + else { + // FF followed by 0 is discarded + sawFF = false; + } + } + } + } + else { + dbyt = readUnsignedByte (_dstream, this); + if (dbyt != 0XFF) { + info.setMessage (new ErrorMessage + ("Expected marker byte 255, got " + dbyt, _nByte)); + info.setWellFormed (false); + return 0; + } + // There can be padding bytes equal to FF, + // so read till we get one that isn't. + while (dbyt == 0XFF) { + dbyt = readUnsignedByte (_dstream, this); + } + } + _numSegments++; + if (!_seenJFIF && !_seenSPIFF && !_seenExif && + !_seenJPEGL && + _numSegments >= 2 && !_reportedJFIF) { + info.setMessage (new ErrorMessage + ("File does not begin with SPIFF, Exif or JFIF segment", _nByte)); + info.setValid (false); + _reportedJFIF = true; + } + if (dbyt >= 0XD0 && dbyt <= 0XD7) { + // RST[m] -- Restart with modulo 8 count 0-7 + dataPlowing = true; + } + else if (dbyt >= 0XF7 && dbyt <= 0XFD) { + // JPGn extension + readJPEGExtension (dbyt, info); + } + else switch (dbyt) { + case 0: + // Byte stuffing -- ignore + break; + + case 0XC0: + case 0XC1: + case 0XC2: + case 0XC3: + case 0XC5: + case 0XC6: + case 0XC7: + case 0XC9: + case 0XCA: + case 0XCB: + case 0XCD: + case 0XCE: + case 0XCF: + // SOF(n) marker; value indicates encoding type + readSOF (dbyt, info); + break; + + case 0XC4: + // DHT -- define Huffman tables + skipSegment (info); + break; + + case 0XCC: + // DAC -- define arithmetic coding conditioning + readDAC (info); + break; + + case 0XD9: + // EOI + break loop1; + + case 0XDA: + // SOS -- start of scan. This is followed by data. + skipSegment (info); + ++_numScans; + dataPlowing = true; + break; + + case 0XDB: + // DQT -- define quantization tables + readDQT (info); + break; + + case 0XDC: + // DNL -- define number of lines + skipSegment (info); + break; + + case 0XDD: + // DRI -- define restart interval + readDRI (info); + break; + + case 0XDE: + // DHP -- define hierarchical progression + readDHP (info); + break; + + case 0XDF: + //EXP -- Expand reference component + readEXP (info); + break; + + case 0XE0: + // APP0 extension + readAPP0 (info); + break; + + case 0XE8: + // APP8 extension + readAPP8 (info); + break; + + case 0XE1: + readAPP1 (info); + break; + + case 0XE2: + case 0XE3: + case 0XE4: + case 0XE5: + case 0XE6: + case 0XE7: + case 0XE9: + case 0XEA: + case 0XEB: + case 0XEC: + case 0XED: + case 0XEE: + case 0XEF: + // Appn extensions which we don't handle specially, + // but do report the existence thereof + reportAppExt (dbyt, info); + skipSegment (info); + break; + + case 0XF0: + // VER segment + readVer (info); + break; + + case 0XF1: + // DTI (defined tiled image) segment + readDTI (info); + break; + + case 0XF2: + // DTI (defined tile) segment + readDTT (info); + break; + + case 0XF4: + // SRS (selectively refined scan) segment + readSRS (info); + break; + + case 0XFE: + // comment + --_numSegments; // don't let comment trigger error + readComment (info); + break; + + default: + // Other values don't belong at the top level. + msg = new ErrorMessage ("Marker not valid in context", + _nByte); + info.setMessage (msg); + info.setValid (false); + break loop1; + } + } + + } + catch (EOFException e) { + msg = new ErrorMessage ("Unexpected end of file", + _nByte); + info.setMessage (msg); + info.setWellFormed (false); + return 0; + } + + info.setProperty (_metadata); + + if (_units == 0) { + List list = new ArrayList (); + list.add (new Property ("PixelAspectRatioX", PropertyType.INTEGER, + new Integer (_xDensity))); + list.add (new Property ("PixelAspectRatioY", PropertyType.INTEGER, + new Integer (_yDensity))); + _primaryImageList.add (new Property ("PixelAspectRatio", + PropertyType.PROPERTY, + PropertyArity.LIST, list)); + } + + // If there's tiling information, create a property for the + // primary image list. + if (_tiling != null) { + Property tp = buildTilingProp (info); + if (tp != null) { + _primaryImageList.add (tp); + } + } + if (_restartInterval >= 0) { + _primaryImageList.add ( new Property ("RestartInterval", + PropertyType.INTEGER, + new Integer (_restartInterval))); + } + _primaryImageList.add (new Property + ("Scans", PropertyType.INTEGER, + new Integer (_numScans))); + if (!_quantTables.isEmpty()) { + List qpl = new LinkedList (); + ListIterator iter = _quantTables.listIterator (); + while (iter.hasNext ()) { + QuantizationTable qt = (QuantizationTable) iter.next (); + qpl.add (qt.makeProperty (_je.getShowRawFlag())); + } + _primaryImageList.add (new Property + ("QuantizationTables", + PropertyType.PROPERTY, + PropertyArity.LIST, + qpl)); + } + + if (!_arithCondTables.isEmpty()) { + List qpl = new LinkedList (); + ListIterator iter = _arithCondTables.listIterator (); + while (iter.hasNext ()) { + ArithConditioning qt = (ArithConditioning) iter.next (); + qpl.add (qt.makeProperty (_je.getShowRawFlag())); + } + _primaryImageList.add (new Property + ("ArithmeticConditioning", + PropertyType.PROPERTY, + PropertyArity.LIST, + qpl)); + } + if (!_srsList.isEmpty()) { + List srsl = new LinkedList (); + ListIterator iter = _srsList.listIterator (); + while (iter.hasNext ()) { + SRS s = (SRS) iter.next (); + srsl.add (s.makeProperty ()); + } + _primaryImageList.add (new Property + ("SelectivelyRefinedScans", + PropertyType.PROPERTY, + PropertyArity.LIST, + srsl)); + } + + if (_ckSummer != null){ + /* We may not have actually hit the end of file. If we're calculating + * checksums on the fly, we have to read and discard whatever is + * left, so it will get checksummed. */ + for (;;) { + try { + int n = skipBytes (_dstream, 2048, this); + if (n == 0) { + break; + } + } + catch (Exception e) { + break; + } + } + info.setSize (_cstream.getNBytes ()); + info.setChecksum (new Checksum (_ckSummer.getCRC32 (), + ChecksumType.CRC32)); + String value = _ckSummer.getMD5 (); + if (value != null) { + info.setChecksum (new Checksum (value, ChecksumType.MD5)); + } + if ((value = _ckSummer.getSHA1 ()) != null) { + info.setChecksum (new Checksum (value, ChecksumType.SHA1)); + } + } + + // Put the primary image in the image list. + _imageList.add( new Property ("Image", + PropertyType.PROPERTY, + PropertyArity.LIST, + _primaryImageList)); + + // Report profiles. + if (_seenJFIF) { + info.setProfile (jfifProfileName); + } + if (_seenExif && _exifProfileOK) { + info.setProfile (exifProfileName); + } + if (_seenSPIFF) { + info.setProfile (spiffProfileName); + if (_spiffDir != null) { + // Grab any image properties from the SPIFF directory + // and add them to the image list. + _spiffDir.appendThumbnailProps (_imageList); + } + } + if (_seenJPEGL) { + info.setProfile (jpeglProfileName); + } + /* Create a new property list containing the count of the images and + * the list of image properties. + */ + List list = new ArrayList (); + list.add (new Property ("Number", PropertyType.INTEGER, + PropertyArity.SCALAR, + new Integer (_imageList.size ()))); + Iterator iter = _imageList.iterator (); + while (iter.hasNext ()) { + Property prop = (Property) iter.next (); + list.add (prop); + } + _propList.add (new Property ("Images", + PropertyType.PROPERTY, + PropertyArity.LIST, + list)); + // _imageList)); + if (!_commentsList.isEmpty()) { + _propList.add (new Property + ("Comments", PropertyType.STRING, + PropertyArity.LIST, + _commentsList)); + } + if (!_jpegExtsList.isEmpty()) { + _propList.add (new Property + ("Extensions", PropertyType.STRING, + PropertyArity.LIST, + _jpegExtsList)); + } + if (!_appSegsList.isEmpty ()) { + _propList.add (new Property + ("ApplicationSegments", PropertyType.STRING, + PropertyArity.LIST, + _appSegsList)); + } + if (!_expList.isEmpty ()) { + _propList.add (buildExpandProp (info)); + } + if (_exifProp != null) { + _primaryImageList.add (_exifProp); + } + if (_xmpProp != null) { + _primaryImageList.add (_xmpProp); + } + return 0; + } + + /** One-argument version of readUnsignedShort. + * JPEG is always big-endian, so readUnsignedShort can + * unambiguously drop its endian argument. */ + public int readUnsignedShort (DataInputStream stream) + throws IOException + { + return readUnsignedShort (stream, true, this); + } + + /** One-argument version of readUnsignedInt. + * JPEG is always big-endian, so readUnsignedInt can + * unambiguously drop its endian argument. */ + public long readUnsignedInt (DataInputStream stream) + throws IOException + { + return readUnsignedInt (stream, true, this); + } + + /** + * Initializes the state of the module for parsing. + */ + protected void initParse () + { + super.initParse (); + _imageList = new LinkedList (); + _tiling = null; + _restartInterval = -1; + _seenSOF = false; + _seenJFIF = false; + _seenSPIFF = false; + _seenJPEGL = false; + _spiffDir = null; + _seenExif = false; + _reportedSigMatch = false; + _exifProfileOK = false; + _reportedJFIF = false; + _numSegments = 0; + _numScans = 0; + _commentsList = new LinkedList (); + _jpegExtsList = new LinkedList (); + _appSegsList = new LinkedList (); + _primaryImageList = new LinkedList (); + _quantTables = new LinkedList (); + _arithCondTables = new LinkedList (); + _srsList = new LinkedList (); + _compressSet = new HashSet (); + _expList = new LinkedList (); + _exifProp = null; + _xmpProp = null; + _capability0 = -1; + _capability1 = -1; + } + + /** + * Initializes the constant portions of the niso metadata. + */ + protected void initNiso () + { + _niso.setMimeType("image/jpeg"); + _niso.setByteOrder ("big-endian"); + _niso.setCompressionScheme (6); // JPEG compression + } + + /* This just reads the initial SOI */ + protected boolean readHeader (RepInfo info) + { + int i; + int ch; + boolean valid = true; + try { + for (i = 0; i < 2; i++) { + ch = readUnsignedByte(_dstream, this); + if (ch != sigByte[i]) { + valid = false; + break; + } + } + } + catch (IOException e) { + valid = false; + } + if (!valid) { + info.setMessage(new ErrorMessage ("Invalid JPEG header", 0)); + info.setWellFormed (false); + return false; + } + return true; + } + + /* Reads an APP0 marker segment. + * We have already read the APP0 marker itself. */ + @SuppressWarnings("fallthrough") + protected void readAPP0 (RepInfo info) throws IOException + { + // Bytes for JFIF extension APP0 + final int jfxxByte[] = + { 0X4A, 0X46, 0X58, 0X58, 0X00 }; + // Bytes for JFIF base APP0 + final int jfifByte[] = + { 0X4A, 0X46, 0X49, 0X46, 0X00 }; + + /* Seeing an APP0 segment counts as seeing a signature. */ + if (!_reportedSigMatch) { + info.setSigMatch(_name); + _reportedSigMatch = true; + } + reportAppExt (0XE0, info); + + int ident[] = new int[5]; + int length = readUnsignedShort (_dstream); + + // It appears that a meaningless JFIF marker can be included + // in a valid SPIFF file. Ignore it. + if (_seenSPIFF) { + skipBytes (_dstream, length - 2, this); + return; + } + + for (int i = 0; i < 5; i++) { + ident[i] = readUnsignedByte (_dstream, this); + } + if (equalArray (ident, jfifByte)) { + if (_numSegments > 1) { + // Apparently this is OK in a spiff file + info.setMessage (new ErrorMessage + ("JFIF APP0 marker not at beginning of file", + _nByte)); + info.setValid (false); + skipBytes (_dstream, length - 7, this); + } + // This is a JFIF APP0 marker. It may come only + // at the beginning of a file. + _seenJFIF = true; + int majorVersion = readUnsignedByte (_dstream, this); + int minorVersion = readUnsignedByte (_dstream, this); + // Format version as M.mm + String vsn = Integer.toString(majorVersion) + "." + + minorFmt.format(minorVersion); + info.setVersion (vsn); + _units = readUnsignedByte (_dstream, this); + if (_units >= 0 && _units <= 2) { + // inches, cm, and no specified unit map linearly + // to NISO values + _niso.setSamplingFrequencyUnit(_units + 1); + } + _xDensity = readUnsignedShort (_dstream); + _yDensity = readUnsignedShort (_dstream); + if (_units != 0) { + _niso.setXSamplingFrequency (new Rational (_xDensity, 1)); + _niso.setYSamplingFrequency (new Rational (_yDensity, 1)); + } + int xThumbPix = readUnsignedByte (_dstream, this); + int yThumbPix = readUnsignedByte (_dstream, this); + + // If there is a thumbnail, create a property for it + if (xThumbPix > 0 && yThumbPix > 0) { + NisoImageMetadata thumbNiso = new NisoImageMetadata (); + thumbNiso.setImageWidth (xThumbPix); + thumbNiso.setImageLength (yThumbPix); + thumbNiso.setColorSpace(2); // RGB + thumbNiso.setCompressionScheme (1); // uncompressed + thumbNiso.setPixelSize(8); + + List thumbPropList = new LinkedList (); + thumbPropList.add (new Property ("NisoImageMetadata", + PropertyType.NISOIMAGEMETADATA, thumbNiso)); + Property thumbProp = new Property ("ThumbImage", + PropertyType.PROPERTY, + PropertyArity.LIST, + thumbPropList); + _imageList.add (thumbProp); + } + _niso.setColorSpace (6); // JFIF header implies Yc[b]c[r] + skipBytes (_dstream, 3 * xThumbPix * yThumbPix, this); + } + else if (equalArray (ident, jfxxByte)) { + int extCode = readUnsignedByte (_dstream, this); + switch (extCode) { + // The extension codes 0X10, 0X11, and 0X13 indicate + // different thumbnail formats. + + // 0X10 indicates that the thumbnail is itself a JPEG + // stream! Yech! Have to call the module recursively? + // Skip for now. + case 0X11: + // thumbnail, palette color, 1 byte/pixel (fall through) + case 0X13: + // thumbnail, RGB, 3 bytes/pixel + // Both of these have the same relevant information, the + // width and height. We just grab those and skip the rest. + int xThumbPix = readUnsignedByte (_dstream, this); + int yThumbPix = readUnsignedByte (_dstream, this); + skipBytes (_dstream, length - 4, this); + NisoImageMetadata thumbNiso = new NisoImageMetadata (); + thumbNiso.setImageWidth (xThumbPix); + thumbNiso.setImageLength (yThumbPix); + thumbNiso.setColorSpace (extCode == 0X13 ? 2 : 3); + thumbNiso.setCompressionScheme (1); // uncompressed + thumbNiso.setPixelSize(8); + List thumbPropList = new LinkedList (); + thumbPropList.add (new Property ("NisoImageMetadata", + PropertyType.NISOIMAGEMETADATA, thumbNiso)); + Property thumbProp = new Property ("ThumbImage", + PropertyType.PROPERTY, + PropertyArity.LIST, + thumbPropList); + _imageList.add (thumbProp); + + default: + skipBytes (_dstream, length - 2, this); + break; + + // we may want to do stuff with the JFXX APP0 + } + } + else { + skipBytes (_dstream, length - 2, this); + } + } + + /* Reads an APP1 marker segment. This may contain Exif + * data, i.e., a whole TIFF file embedded in the segment. + */ + protected void readAPP1 (RepInfo info) throws IOException + { + final int exifByte[] = + { 0X45, 0X78, 0X69, 0X66, 0X00, 0X00 }; + // First 6 bytes of xmpStr + final int xmpByte[] = + { 0X68, 0X74, 0X74, 0X70, 0X3A, 0X2F }; + final String xmpStr = "http://ns.adobe.com/xap/1.0/"; + reportAppExt (0XE1, info); + + int ident[] = new int[6]; + int length = readUnsignedShort (_dstream); + if (length < 8) { + // Guard against pathological short packets. + skipBytes (_dstream, length - 2, this); + return; + } + for (int i=0; i<6; i++) { + ident[i] = readUnsignedByte (_dstream, this); + } + if (equalArray (ident, exifByte)) { + // Some camera images have only an APP1 segment with + // Exif information to mark them, so count that as + // a "signature." + if (!_reportedSigMatch) { + info.setSigMatch(_name); + _reportedSigMatch = true; + } + + // Theoretically, the TIFF module could be missing, + // in which case we can't do anything, so check + // it first. + _seenExif = true; + if (!JpegExif.isTiffAvailable()) { + info.setMessage ( new InfoMessage + ("TIFF-HUL module required to report Exif data", + _nByte)); + skipBytes (_dstream, length - 8, this); + return; + } + JpegExif je = new JpegExif (); + RepInfo exifInfo = je.readExifData(_dstream, _je, length); + if (exifInfo != null) { + /* Copy any EXIF messages into the JPEG info object. */ + List list = exifInfo.getMessage (); + int size = list.size (); + for (int i=0; i= 32) { + // Check if the rest of xmpStr matches + boolean match = true; + for (int i=6; i<28; i++) { + int ch = readUnsignedByte (_dstream, this); + --length; + if (ch != (int) xmpStr.charAt (i)) { + match = false; + break; + } + } + if (!match) { + skipBytes (_dstream, length - 8, this); + return; + } + // This is an XMP packet, and we are now at the XMP + readUnsignedByte (_dstream, this); // skip null + --length; + byte[] xmpBuf = new byte[length - 8]; + readByteBuf (_dstream, xmpBuf, this); + _xmpProp = readXMP (xmpBuf); + } + else { + skipBytes (_dstream, length - 8, this); + } + } + + /* Reads an APP8 marker segment. This indicates a SPIFF + * file, if it's found at the beginning of the file. + * If we're already in a SPIFF file, it's a directory + * entry. + * We have already read the APP8 marker itself. */ + protected void readAPP8 (RepInfo info) throws IOException + { + final int spiffByte[] = + { 0X53, 0X50, 0X49, 0X46, 0X46, 0X00 }; + + /* Seeing an APP8 segment counts as seeing a signature. */ + if (!_reportedSigMatch) { + info.setSigMatch(_name); + _reportedSigMatch = true; + } + reportAppExt (0XE8, info); + + int length = readUnsignedShort (_dstream); + int ident[] = new int[6]; + if (_spiffDir != null) { + // we've already started a SPIFF file, so this + // should be a directory entry. + _spiffDir.readDirEntry(_dstream, length); + return; + } + for (int i = 0; i < 6; i++) { + ident[i] = readUnsignedByte (_dstream, this); + } + if (equalArray (ident, spiffByte)) { + if (_numSegments > 1) { + info.setMessage (new ErrorMessage + ("SPIFF marker not at beginning of file", + _nByte)); + info.setValid (false); + } + // This is a SPIFF marker. It may come only + // at the beginning of a file. + _seenSPIFF = true; + _spiffDir = new SpiffDir (this); + int majorVersion = readUnsignedByte (_dstream, this); + int minorVersion = readUnsignedByte (_dstream, this); + // Format version as M.mm + String vsn = Integer.toString(majorVersion) + "." + + minorFmt.format(minorVersion); + info.setVersion (vsn); + int profileID = readUnsignedByte (_dstream, this); + int numComponents = readUnsignedByte (_dstream, this); + long height = readUnsignedInt (_dstream); + _niso.setImageLength (height); + long width = readUnsignedInt (_dstream); + _niso.setImageWidth (width); + + int colorSpace = readUnsignedByte (_dstream, this); + int nisoCS = Spiff.colorSpaceToNiso (colorSpace); + if (nisoCS >= 0) { + _niso.setColorSpace (nisoCS); + } + @SuppressWarnings("unused") + int bps = readUnsignedByte (_dstream, this); + int compType = readUnsignedByte (_dstream, this); + int nisoCT = Spiff.compressionTypeToNiso (compType); + if (nisoCT >= 0) { + _niso.setCompressionScheme(nisoCT); + } + int units = readUnsignedByte (_dstream, this); + if (units > 0 && units <= 2) { + // inches, cm, and no specified unit map linearly + // to NISO values + _niso.setSamplingFrequencyUnit(units + 1); + } + @SuppressWarnings("unused") + long vRes = readUnsignedInt (_dstream); + @SuppressWarnings("unused") + long hRes = readUnsignedInt (_dstream); + // These are fixed point numbers (does it say where the + // point is?) unless units == 0, in which case there's + // just an aspect ration of vres/hres. + } + else { + skipBytes (_dstream, length - 8, this); + } + } + + /* Read the VER marker, and set version information accordingly */ + protected void readVer (RepInfo info) throws IOException + { + int length = readUnsignedShort (_dstream); + int majVersion = readUnsignedByte (_dstream, this); + int minVersion = readUnsignedByte (_dstream, this); + String vsn = Integer.toString(majVersion) + "." + + minorFmt.format(minVersion); + info.setVersion (vsn); + + // The number of capability bytes is equal to + // majVersion + 1. The current code understands + // major versions through 1, and per the specs, + // will ignore these bytes if majVersion is greater + // than 1. + int skip = length - 4; + if (majVersion <= 1) { + _capability0 = readUnsignedByte (_dstream, this); + skip--; + if (majVersion == 1) { + _capability1 = readUnsignedByte (_dstream, this); + skip--; + } + } + skipBytes (_dstream, skip, this); + _seenJPEGL = false; // Not permitted under JPEG-L + } + + /* Read the DTI segment, and begin setting up the tiling property */ + protected void readDTI (RepInfo info) throws IOException + { + @SuppressWarnings("unused") + int length = readUnsignedShort (_dstream); + _tiling = new Tiling (); + _tiling.setTilingType (readUnsignedByte (_dstream, this)); + _tiling.setVertScale (readUnsignedShort (_dstream)); + _tiling.setHorScale (readUnsignedShort (_dstream)); + _tiling.setRefGridHeight (readUnsignedInt (_dstream)); + _tiling.setRefGridWidth (readUnsignedInt (_dstream)); + _seenJPEGL = false; // Not permitted under JPEG-L + } + + /* Read the DTT segment. There should already be a tiling + * property set up. */ + protected void readDTT (RepInfo info) throws IOException + { + @SuppressWarnings("unused") + int length = readUnsignedShort (_dstream); + if (_tiling == null) { + info.setMessage (new ErrorMessage + ("DTT segment without previous DTI", _nByte)); + info.setValid (false); + return; + } + long vertScale = readUnsignedInt (_dstream); + long horScale = readUnsignedInt (_dstream); + long vertOffset = readUnsignedInt (_dstream); + long horOffset = readUnsignedInt (_dstream); + _tiling.addTile (vertScale, horScale, vertOffset, horOffset); + _seenJPEGL = false; // Not permitted under JPEG-L + } + + /* Read an SRS segment. This provides information about progressive + * scans and so on. */ + protected void readSRS (RepInfo info) throws IOException + { + int length = readUnsignedShort (_dstream); + int vertOffset = readUnsignedShort (_dstream); + int horOffset = readUnsignedShort (_dstream); + int vertSize = readUnsignedShort (_dstream); + int horSize = readUnsignedShort (_dstream); + _srsList.add (new SRS (vertOffset, horOffset, vertSize, horSize)); + } + + /* Accumulate reports of APPn segments into a property. It's + * tempting to report information about the segment, since + * many APPn segments have ASCII identifiers, but there's no + * guarantee of any content beyond the length field, so we + * just report the existence of all APPn segments. No attempt + * is made to weed out duplicates, since multiple instances of + * the same segment number are legitimate and informative. + */ + protected void reportAppExt (int dbyt, RepInfo info) + { + String appStr = "APP"; + if (dbyt <= 0XE9) { + // 0-9 + appStr += (char) (dbyt - 0XE0 + 0X30); + } + else { + // 10-15 + appStr += "1" + (char) (dbyt - 0XEA + 0X30); + } + _appSegsList.add (appStr); + } + + /* Read a SOF segment. The first one is the most interesting, since + * it contains the dimensions for the image. + * No multi-image support right now; this has to be figured out, + * including the distinction between images and frames. */ + protected void readSOF (int dbyt, RepInfo info) throws IOException + { + int length = readUnsignedShort (_dstream); + int precision = readUnsignedByte (_dstream, this); + int nLines = readUnsignedShort (_dstream); + int samPerLine = readUnsignedShort (_dstream); + int numComps = readUnsignedByte (_dstream, this); + skipBytes (_dstream, length - 8, this); + if (!_seenSOF) { + _niso.setImageLength(nLines); + _niso.setImageWidth(samPerLine); + int[] bps = new int[numComps]; + for (int i = 0; i < numComps; i++) { + bps[i] = precision; + } + _niso.setBitsPerSample(bps); + _niso.setSamplesPerPixel(numComps); + _propList.add (new Property + ("CompressionType", PropertyType.STRING, + JpegStrings.COMPRESSION_TYPE [dbyt - 0XC0])); + _seenSOF = true; + } + } + + /* Read a DHP segment. This has the same format as SOF. */ + protected void readDHP (RepInfo info) throws IOException + { + int length = readUnsignedShort (_dstream); + int precision = readUnsignedByte (_dstream, this); + int nLines = readUnsignedShort (_dstream); + int samPerLine = readUnsignedShort (_dstream); + int numComps = readUnsignedByte (_dstream, this); + skipBytes (_dstream, length - 8, this); + if (!_seenSOF) { + _niso.setImageLength(nLines); + _niso.setImageWidth(samPerLine); + int[] bps = new int[numComps]; + for (int i = 0; i < numComps; i++) { + bps[i] = precision; + } + _niso.setBitsPerSample(bps); + _niso.setSamplesPerPixel(numComps); + _seenSOF = true; + } + } + + /* Read an EXP segment. */ + protected void readEXP (RepInfo info) throws IOException + { + int length = readUnsignedShort (_dstream); + int lhlv = readUnsignedByte (_dstream, this); + boolean arr[] = new boolean[2]; + arr[0] = ((lhlv & 0XF0) != 0); + arr[1] = ((lhlv & 0X0F) != 0); + _expList.add (arr); + } + + /* Read a DRI (Data Restart Interval) segment. */ + protected void readDRI (RepInfo info) throws IOException + { + int length = readUnsignedShort (_dstream); + _restartInterval = readUnsignedShort (_dstream); + } + + /* Read a DQT (Define Quantization Table) segment. + * (10918-1:1994(E), B.2.4.1) */ + protected void readDQT (RepInfo info) throws IOException + { + int length = readUnsignedShort (_dstream); + int pqtq = readUnsignedByte (_dstream, this); + int pq = pqtq >> 4; + int tq = pqtq & 0X0F; + _quantTables.add (new QuantizationTable (pq, tq)); + skipBytes (_dstream, length - 3, this); + _seenJPEGL = false; // Not permitted under JPEG-L + } + + /* Read a DAC (Define Arithmetic Conditioning) segment. + * (10918-1:1994(E), B.2.4.1) */ + protected void readDAC (RepInfo info) throws IOException + { + int length = readUnsignedShort (_dstream); + int pqtq = readUnsignedByte (_dstream, this); + int pq = pqtq >> 4; + int tq = pqtq & 0X0F; + _arithCondTables.add (new ArithConditioning (pq, tq)); + skipBytes (_dstream, length - 3, this); + _seenJPEGL = false; // Not permitted under JPEG-L + } + + /* Read a JPGn (JPEG Extension) segment. */ + protected void readJPEGExtension(int dbyt, RepInfo info) + throws IOException + { + String ext; + if (dbyt <= 0XF9) { + // 0-9 + ext = "JPG" + (char) (dbyt - 0XF0 + 0X30); + } + else { + // 10-15 + ext = "JPG1" + (char) (dbyt - 0XFA + 0X30); + } + _jpegExtsList.add (ext); + + // JPEG extensions other than F7 and F8 are not permitted + // under JPEG-L + if (dbyt != 0XF7 && dbyt != 0XF8) { + _seenJPEGL = false; + } + + if (dbyt == 0XF7) { + // This is probably a JPEG-L file. + if (!_seenSPIFF && !_seenJFIF && !_seenExif && !_seenJPEGL) { + if (!_reportedSigMatch) { + info.setSigMatch(_name); + _reportedSigMatch = true; + } + int length = readUnsignedShort (_dstream); + int precision = readUnsignedByte (_dstream, this); + int nLines = readUnsignedShort (_dstream); + int samPerLine = readUnsignedShort (_dstream); + int numComps = readUnsignedByte (_dstream, this); + skipBytes (_dstream, length - 8, this); + _seenJPEGL = true; + _niso.setImageLength(nLines); + _niso.setImageWidth(samPerLine); + int[] bps = new int[numComps]; + for (int i = 0; i < numComps; i++) { + bps[i] = precision; + } + _niso.setBitsPerSample(bps); + _niso.setSamplesPerPixel(numComps); + _seenSOF = true; + return; + } + } + + int length = readUnsignedShort (_dstream); + skipBytes (_dstream, length - 2, this); + } + + /* Read a JPEG comment, and add its text to the comments list. + * The JPEG spec says only that the interpretation of the + * comment is left to the application. For a first shot, + * everything up to but not including the first null, or + * the entire comment data (whichever comes first) will be + * read into a string. */ + protected void readComment (RepInfo info) throws IOException + { + int length = readUnsignedShort (_dstream); + StringBuffer buf = new StringBuffer (); + boolean getChars = true; + for (int i = 0; i < length - 2; i++) { + int ch = readUnsignedByte (_dstream, this); + if (ch == 0) { + getChars = false; + // but keep reading bytes so we come out right + } + if (getChars) { + buf.append((char) ch); + } + } + if (buf.length() > 0) { + _commentsList.add (buf.toString ()); + } + } + + /* Build a property based on the capability0 and capability1 + * bytes. If these are both -1 (absent), return null. + */ + protected Property buildCapProp (RepInfo info) + { + if (_capability0 < 0) { + return null; + } + + try { + // If we're doing raw output, the capability + // properties will be numbers. If we're doing + // verbose output, they will be strings. + Property cap0Prop; + List capList = new ArrayList (3); + if (_je.getShowRawFlag()) { + cap0Prop = new Property ("Version0", + PropertyType.INTEGER, + new Integer (_capability0)); + } + else { + cap0Prop = new Property ("Version0", + PropertyType.STRING, + JpegStrings.CAPABILITY_V0 [_capability0]); + } + capList.add (cap0Prop); + + if (_capability1 >= 0) { + if (_je.getShowRawFlag()) { + Property cap1Prop = new Property ("Version1", + PropertyType.INTEGER, + new Integer (_capability1)); + capList.add (cap1Prop); + } + else { + // Capability 1 entails 2 strings, one for the + // basic capability, and one for tiling. + String cap1Str[] = new String[2]; + cap1Str[0] = + JpegStrings.CAPABILITY_V1[_capability1 & 0X1F]; + cap1Str[1] = + JpegStrings.TILING_CAPABILITY_V1[_capability1 >> 5]; + Property cap1Prop = new Property ("Version1", + PropertyType.STRING, + PropertyArity.ARRAY, + cap1Str); + capList.add (cap1Prop); + } + } + Property prop = new Property ("CapabilityIndicator", + PropertyType.PROPERTY, + PropertyArity.LIST, + capList); + return prop; + } + catch (Exception e) { + // If we get caught on an out-of-bounds value, + // etc., simply don't return the property. + return null; + } + } + + /* Build a property from the tiling information. */ + protected Property buildTilingProp (RepInfo info) + { + if (_tiling == null) { + return null; + } + try { + Property[] propArr = new Property [6]; + int tilingType = _tiling.getTilingType(); + if (_je.getShowRawFlag()) { + propArr[0] = new Property ("TilingType", + PropertyType.INTEGER, + new Integer (tilingType)); + } + else { + propArr[0] = new Property ("TilingType", + PropertyType.STRING, + JpegStrings.TILING_TYPE[tilingType]); + } + propArr[1] = new Property ("VerticalScale", + PropertyType.INTEGER, + new Integer (_tiling.getVertScale ())); + propArr[2] = new Property ("HorizontalScale", + PropertyType.INTEGER, + new Integer (_tiling.getHorScale ())); + propArr[3] = new Property ("RefGridHeight", + PropertyType.LONG, + new Long (_tiling.getRefGridHeight ())); + propArr[4] = new Property ("RefGridWidth", + PropertyType.LONG, + new Long (_tiling.getRefGridWidth ())); + propArr[5] = _tiling.buildTileListProp (); + return new Property ("Tiling", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + propArr); + } + catch (Exception e) { + // Out of bounds value -- punt. + // Should add an error message here. + info.setMessage (new ErrorMessage ("Unrecognized tiling data")); + info.setValid (false); + return null; + } + } + + protected Property buildExpandProp (RepInfo info) + { + List plist = new LinkedList (); + Property prop = new Property ("ExpansionSegments", + PropertyType.PROPERTY, + PropertyArity.LIST, + plist); + ListIterator iter = _expList.listIterator (); + while (iter.hasNext ()) { + boolean[] lhlv = (boolean []) iter.next (); + Property[] lhlvProp = new Property[2]; + lhlvProp[0] = new Property ("Horizontal", + PropertyType.BOOLEAN, + new Boolean (lhlv[0])); + lhlvProp[1] = new Property ("Vertical", + PropertyType.BOOLEAN, + new Boolean (lhlv[1])); + plist.add ( new Property ("Expansion", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + lhlvProp)); + } + return prop; + } + + /* Read XMP data from the tag, and return as a string. */ + protected Property readXMP (byte[] buf) + { + Property xmpProp = null; + //final String badMetadata = "Invalid or ill-formed XMP metadata"; + try { + ByteArrayInputStream strm = + new ByteArrayInputStream (buf); + ByteArrayXMPSource src = new ByteArrayXMPSource (strm); + + // Create an InputSource to feed the parser. + SAXParserFactory factory = + SAXParserFactory.newInstance(); + factory.setNamespaceAware (true); + XMLReader parser = factory.newSAXParser ().getXMLReader (); + XMPHandler handler = new XMPHandler (); + parser.setContentHandler (handler); + parser.setErrorHandler (handler); + // We have to parse twice. The first time, we may get + // an encoding change as part of an exception thrown. If this + // happens, we create a new InputSource with the encoding, and + // continue. + try { + parser.parse (src); + xmpProp = src.makeProperty (); + return xmpProp; + } + catch (SAXException se) { + String msg = se.getMessage (); + if (msg != null && msg.startsWith ("ENC=")) { + String encoding = msg.substring (5); + try { + //Reader rdr = new InputStreamReader (stream, encoding); + src = new ByteArrayXMPSource (strm, encoding); + parser.parse (src); + } + catch (UnsupportedEncodingException uee) { + return null; + } + } + xmpProp = src.makeProperty (); + return xmpProp; + } + } + catch (Exception e) { + return null; + } + } + + /* Extract useful information from the Exif NisoImageMetadata, and + * put it into our NisoImageMetadata. Not all of the Niso information + * from the Exif is meaningful; only that which we think (hope) is + * is copied. For example, the MIME type isn't meaningful, but + * information describing the camera or scanner is. + */ + protected void extractExifNisoData (NisoImageMetadata exifData) + { + int NULL = NisoImageMetadata.NULL; // just a shorthand + if (exifData.getAutoFocus () != NULL) { + _niso.setAutoFocus (exifData.getAutoFocus ()); + } + if (exifData.getBackLight () != NULL) { + _niso.setBackLight (exifData.getBackLight ()); + } + if (exifData.getBrightness () != NULL) { + _niso.setBrightness (exifData.getBrightness ()); + } + if (exifData.getColorTemp () != NULL) { + _niso.setColorTemp (exifData.getColorTemp ()); + } + if (exifData.getDeviceSource () != null) { + _niso.setDeviceSource (exifData.getDeviceSource ()); + } + if (exifData.getDigitalCameraManufacturer () != null) { + _niso.setDigitalCameraManufacturer + (exifData.getDigitalCameraManufacturer ()); + } + if (exifData.getDigitalCameraModel () != null) { + _niso.setDigitalCameraModel + (exifData.getDigitalCameraModel ()); + } + if (exifData.getExposureBias () != NULL) { + _niso.setExposureBias (exifData.getExposureBias ()); + } + if (exifData.getExposureIndex () != NULL) { + _niso.setExposureIndex (exifData.getExposureIndex ()); + } + if (exifData.getExposureTime () != NULL) { + _niso.setExposureTime (exifData.getExposureTime ()); + } + if (exifData.getFlash () != NULL) { + _niso.setFlash (exifData.getFlash ()); + } + if (exifData.getFlashEnergy () != NULL) { + _niso.setFlashEnergy (exifData.getFlashEnergy ()); + } + if (exifData.getFlashReturn () != NULL) { + _niso.setFlashReturn (exifData.getFlashReturn ()); + } + if (exifData.getFNumber () != NULL) { + _niso.setFNumber (exifData.getFNumber ()); + } + if (exifData.getHostComputer () != null) { + _niso.setHostComputer (exifData.getHostComputer ()); + } + if (exifData.getImageIdentifier () != null) { + _niso.setImageIdentifier (exifData.getImageIdentifier ()); + } + if (exifData.getImageProducer () != null) { + _niso.setImageProducer (exifData.getImageProducer ()); + } + if (exifData.getMeteringMode () != NULL) { + _niso.setMeteringMode (exifData.getMeteringMode ()); + } + if (exifData.getOS () != null) { + _niso.setOS (exifData.getOS ()); + } + if (exifData.getOSVersion () != null) { + _niso.setOSVersion (exifData.getOSVersion ()); + } + if (exifData.getPerformanceData () != null) { + _niso.setPerformanceData (exifData.getPerformanceData ()); + } + if (exifData.getProcessingAgency () != null) { + _niso.setProcessingAgency (exifData.getProcessingAgency ()); + } + if (exifData.getProcessingSoftwareName () != null) { + _niso.setProcessingSoftwareName + (exifData.getProcessingSoftwareName ()); + } + if (exifData.getProcessingSoftwareVersion () != null) { + _niso.setProcessingSoftwareVersion + (exifData.getProcessingSoftwareVersion ()); + } + if (exifData.getScannerManufacturer () != null) { + _niso.setScannerManufacturer + (exifData.getScannerManufacturer ()); + } + if (exifData.getScannerModelName () != null) { + _niso.setScannerModelName + (exifData.getScannerModelName ()); + } + if (exifData.getScannerModelNumber () != null) { + _niso.setScannerModelNumber + (exifData.getScannerModelNumber ()); + } + if (exifData.getScannerModelSerialNo () != null) { + _niso.setScannerModelSerialNo + (exifData.getScannerModelSerialNo ()); + } + if (exifData.getSceneIlluminant () != NULL) { + _niso.setSceneIlluminant (exifData.getSceneIlluminant ()); + } + if (exifData.getSubjectDistance () != null) { + _niso.setSubjectDistance (exifData.getSubjectDistance ()); + } + } + + /* Skip over a segment without doing anything. When this is called, + * we have already read the marker and the stream is ready to + * read the length. */ + protected boolean skipSegment (RepInfo info) throws IOException + { + int length = readUnsignedShort (_dstream); + skipBytes (_dstream, length - 2, this); + return true; + } + + /* Compare two arrays of int for equality. They must be + * the same length. */ + protected static boolean equalArray (int[] a, int[] b) + { + if (a.length != b.length) { + return false; + } + for (int i = 0; i < a.length; i++) { + if (a[i] != b[i]) { + return false; + } + } + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/PdfModule.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/PdfModule.java new file mode 100644 index 00000000..b006c966 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/PdfModule.java @@ -0,0 +1,4115 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003-2007 by JSTOR and the President and Fellows of Harvard College + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module; + +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.pdf.*; +import java.io.*; +import java.util.*; +//Importing org.xml.sax.* would make Parser ambiguous +import org.xml.sax.XMLReader; +import org.xml.sax.SAXException; +//import org.xml.sax.InputSource; +import javax.xml.parsers.SAXParserFactory; + +/** + * Module for identification and validation of PDF files. + */ +public class PdfModule + extends ModuleBase +{ + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + + private static final String NAME = "PDF-hul"; + private static final String RELEASE = "1.7"; + private static final int [] DATE = {2012, 8, 12}; + private static final String [] FORMAT = { + "PDF", "Portable Document Format" + }; + private static final String COVERAGE = + "PDF 1.0-1.6; PDF/X-1 (ISO 15930-1:2001), X-1a (ISO 15930-4:2003), " + + "X-2 (ISO 15930-5:2003), and X-3 (ISO 15930-6:2003); Tagged PDF; " + + "Linearized PDF; PDF/A (ISO/CD 19005-1)"; + private static final String [] MIMETYPE = {"application/pdf"}; + private static final String WELLFORMED = "A PDF file is " + + "well-formed if it meets the criteria defined in Chapter " + + "3 of the PDF Reference 1.6 (5th edition, 2004)"; + private static final String VALIDITY = null; + private static final String REPINFO = null; + private static final String NOTE = "This module does *not* validate " + + "data within content streams (including operators) or encrypted data"; + private static final String RIGHTS = "Copyright 2003-2007 by JSTOR and " + + "the President and Fellows of Harvard College. " + + "Released under the GNU Lesser General Public License."; + private static final String ENCRYPTED = ""; + + /** Font type selectors. */ + public final static int F_TYPE0 = 1, + F_TYPE1 = 2, + F_TT = 3, + F_TYPE3 = 4, + F_MM1 = 5, + F_CID0 = 6, + F_CID2 = 7; + + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + /* The maximum number of fonts that will be reported before we just + * give up and report a stub to avoid running out of memory. */ + protected int DEFAULT_MAX_FONTS = 1000; + + /* Constants for trailer parsing */ + private static final int EOFSCANSIZE = 1024; + private static final int XREFSCANSIZE = 128; // generous... + + protected RandomAccessFile _raf; + protected Parser _parser; + protected String _version; + protected Property _metadata; + protected Property _xmpProp; + protected long _eof; + protected long _startxref; + protected long _prevxref; + protected int _numFreeObjects; + protected Property _idProperty; + protected int _objCount; // Count of objects in the cross-reference table + protected int _numObjects; // Value of the "Size" entry in the trailer dictionary + protected int _numTrailers; // Count of the number of trailers (updates) + protected Map _objects; // Map of the objects in the file + protected long[] _xref; // array of object offsets from xref table + protected int[] [] _xref2; // array of int[2], giving object stream and offset when _xref[i] < 0 + protected boolean _xrefIsStream; // true if xref streams rather than tables are used + protected boolean _encrypted; // equivalent to _encryptDictRef != null + protected List _docCatalogList; // Info extracted from doc cat dict + protected List _encryptList; // Info from encryption dict + protected List _docInfoList; // info from doc info dict + protected List _extStreamsList; // List of external streams + protected List _imagesList; // List of image streams + protected List _filtersList; // List of filters + protected List _pagesList; // List of PageObjects + + protected Map _type0FontsMap; // Map of type 0 font dictionaries + protected Map _type1FontsMap; // Map of type 1 font dictionaries + protected Map _mmFontsMap; // Map of multi master font dictionaries + protected Map _type3FontsMap; // Map of type 3 font dictionaries + protected Map _trueTypeFontsMap; // Map of TrueType font dictionaries + protected Map _cid0FontsMap; // Map of CIDFont/Type1 dictionaries + protected Map _cid2FontsMap; // Map of CIDFont/TrueType dictionaries + + protected Map _pageSeqMap; // Map associating page object dicts with sequence numbers + + protected PdfIndirectObj _docCatDictRef; + protected PdfIndirectObj _encryptDictRef; + protected PdfIndirectObj _docInfoDictRef; + protected PdfIndirectObj _pagesDictRef; + + protected PdfDictionary _docCatDict; + protected PdfDictionary _docInfoDict; + protected PageTreeNode _docTreeRoot; + protected PdfDictionary _pageLabelDict; + protected PageLabelNode _pageLabelRoot; + protected NameTreeNode _embeddedFiles; + protected NameTreeNode _destNames; + protected PdfDictionary _encryptDict; + protected PdfDictionary _trailerDict; + protected PdfDictionary _viewPrefDict; + protected PdfDictionary _outlineDict; + protected PdfDictionary _destsDict; + + protected boolean _showFonts; + protected boolean _showOutlines; + protected boolean _showAnnotations; + protected boolean _showPages; + + protected boolean _actionsExist; + protected boolean _pdfACompliant; // flag checking PDF/A compliance + + protected boolean _recursionWarned; // Check if warning has been issued on recursive outlines. + + /* These three variables track whether a message has been posted + notifying the user of omitted information. */ + protected boolean _skippedFontsReported; + protected boolean _skippedOutlinesReported; + protected boolean _skippedAnnotationsReported; + protected boolean _skippedPagesReported; + + /** List of profile checkers */ + protected List _profile; + + /** Cached object stream. */ + protected ObjectStream _cachedObjectStream; + + /** Object number of cached object stream. */ + protected int _cachedStreamIndex; + + /** Map of visited nodes when walking through an outline. */ + protected Set _visitedOutlineNodes; + + /** maximum number of fonts to report full information on. */ + protected int maxFonts; + + /** Number of fonts reported so far. */ + protected int _nFonts; + + /* These are the message texts to post in case of omitted + information. */ + private final static String fontsSkippedString = + "Fonts exist, but are not displayed; to display " + + "remove param value of f from the config file"; + private final static String outlinesSkippedString = + "Outlines exist, but are not displayed; to display " + + "remove param value of o from the config file"; + private final static String annotationsSkippedString = + "Annotations exist, but are not displayed; to display " + + "remove param value of a from the config file"; + private final static String pagesSkippedString = + "Page information is not displayed; to display " + + "remove param value of p from the config file"; + + /* Warning messages. */ + protected final static String outlinesRecursiveString = + "Outlines contain recursive references."; + + /* Name-to-value array pairs for NISO metadata */ + private final static String[] compressionStrings = + { "LZWDecode", /* "FlateDecode", */ "RunLengthDecode", "DCTDecode", "CCITTFaxDecode"}; + private final static int[] compressionValues = + { 5, /* 8, */ 32773, 6, 2}; + /* The value of 2 (CCITTFaxDecode) is a placeholder; additional + * checking of the K parameter is needed to determine the real + * value if that's returned. */ + + private final static String [] colorSpaceStrings = + { "Lab", "DeviceRGB", "DeviceCMYK", "DeviceGray", "Indexed" }; + private final static int[] colorSpaceValues = + { 8, 2, 5, 1, 3 }; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * Creates an instance of the module and initializes identifying + * information. + */ + public PdfModule () + { + super (NAME, RELEASE, DATE, FORMAT, COVERAGE, MIMETYPE, WELLFORMED, + VALIDITY, REPINFO, NOTE, RIGHTS, true); + + Agent agent = new Agent ("Harvard University Library", + AgentType.EDUCATIONAL); + agent.setAddress ("Office for Information Systems, " + + "90 Mt. Auburn St., " + + "Cambridge, MA 02138"); + agent.setTelephone ("+1 (617) 495-3724"); + agent.setEmail("jhove-support@hulmail.harvard.edu"); + _vendor = agent; + + Document doc = new Document ("PDF Reference: Adobe Portable " + + "Document Format, Version 1.4", + DocumentType.BOOK); + agent = new Agent ("Adobe Systems, Inc.", AgentType.COMMERCIAL); + agent.setAddress ("345 Park Avenue, San Jose, California 95110-2704"); + agent.setTelephone ("+1 (408) 536-6000"); + agent.setFax ("+1 (408) 537-6000"); + agent.setWeb ("http://www.adobe.com/"); + doc.setPublisher (agent); + doc.setDate ("2001-12"); + doc.setEdition ("3rd edition"); + doc.setIdentifier (new Identifier ("0-201-75839-3", + IdentifierType.ISBN)); + doc.setIdentifier (new Identifier ("http://partners.adobe.com/asn/" + + "acrobat/docs/File_Format_" + + "Specifications/PDFReference.pdf", + IdentifierType.URL)); + _specification.add (doc); + + doc = new Document ("PDF Reference: Adobe Portable " + + "Document Format, Version 1.5", + DocumentType.BOOK); + doc.setPublisher (agent); + doc.setDate ("2003"); + doc.setEdition ("4th edition"); + doc.setIdentifier (new Identifier ( + "http://partners.adobe.com/public/developer/en/pdf/PDFReference15_v6.pdf", + IdentifierType.URL)); + _specification.add (doc); + + doc = new Document ("PDF Reference: Adobe Portable " + + "Document Format, Version 1.6", + DocumentType.BOOK); + doc.setPublisher (agent); + doc.setDate ("2004-11"); + doc.setEdition ("5th edition"); + doc.setIdentifier (new Identifier ( + "http://partners.adobe.com/public/developer/en/pdf/PDFReference16.pdf", + IdentifierType.URL)); + _specification.add (doc); + + + doc = new Document ("Graphic technology -- Prepress " + + "digital data exchange -- Use of PDF -- " + + "Part 1: Complete exchange using CMYK data " + + "(PDF/X-1 and PDF/X-1a)", + DocumentType.STANDARD); + Agent isoAgent = new Agent ("ISO", AgentType.STANDARD); + isoAgent.setAddress ("1, rue de Varembe, Casa postale 56, " + + "CH-1211, Geneva 20, Switzerland"); + isoAgent.setTelephone ("+41 22 749 01 11"); + isoAgent.setFax ("+41 22 733 34 30"); + isoAgent.setEmail ("iso@iso.ch"); + isoAgent.setWeb ("http://www.iso.org"); + doc.setPublisher (isoAgent); + doc.setDate ("2001-12-06"); + doc.setIdentifier (new Identifier ("ISO 15930-1:2001", + IdentifierType.ISO)); + _specification.add (doc); + + + doc = new Document ("Graphic technology -- Prepress " + + "digital data exchange -- Use of PDF -- " + + "Part 4: Complete exchange using CMYK and " + + "spot colour printing data using " + + "PDF 1.4 (PDF/X-1a)", + DocumentType.STANDARD); + doc.setPublisher (isoAgent); + doc.setDate ("2003-08-04"); + doc.setIdentifier (new Identifier ("ISO 15930-4:2003", + IdentifierType.ISO)); + _specification.add (doc); + + + doc = new Document ("Graphic technology -- Prepress " + + "digital data exchange -- Use of PDF -- " + + "Part 5: Partial exchange of printing data " + + "using PDF 1.4 (PDF/X-2)", + DocumentType.STANDARD); + doc.setPublisher (isoAgent); + doc.setDate ("2003-08-05"); + doc.setIdentifier (new Identifier ("ISO 15930-5:2003", + IdentifierType.ISO)); + _specification.add (doc); + + + doc = new Document ("Graphic technology -- Prepress " + + "digital data exchange -- Use of PDF -- " + + "Part 6: Complete exchange suitable for " + + "colour-managed workflows using " + + "PDF 1.4 (PDF/X-3)", + DocumentType.STANDARD); + doc.setPublisher (isoAgent); + doc.setDate ("2003-08-06"); + doc.setIdentifier (new Identifier ("ISO 15930-6:2003", + IdentifierType.ISO)); + _specification.add (doc); + + _signature.add (new ExternalSignature (".pdf", + SignatureType.EXTENSION, + SignatureUseType.OPTIONAL)); + _signature.add (new InternalSignature ("%PDF-1.", + SignatureType.MAGIC, + SignatureUseType.MANDATORY, + 0)); + + + doc = new Document ("Document management -- Electronic " + + "document file format for long-term " + + "preservation -- Part 1: Use of PDF (PDF/A)", + DocumentType.RFC); + doc.setPublisher (isoAgent); + doc.setDate ("2003-11-30"); + doc.setIdentifier (new Identifier ("ISO/CD 19005-1", + IdentifierType.ISO)); + doc.setIdentifier (new Identifier + ("http://www.aiim.org/documents/standards/ISO_19005-1_(E).doc", + IdentifierType.URL)); + _specification.add (doc); + + + _profile = new ArrayList (6); + _profile.add (new LinearizedProfile (this)); + TaggedProfile tpr = new TaggedProfile (this); + _profile.add (tpr); + AProfile apr = new AProfile (this); + _profile.add (apr); + // Link AProfile to TaggedProfile to save checking + // the former twice. + apr.setTaggedProfile (tpr); + + AProfileLevelA apra = new AProfileLevelA (this); + _profile.add (apra); + // AProfileLevelA depends on AProfile + apra.setAProfile(apr); + + X1Profile x1 = new X1Profile (this); + _profile.add (x1); + X1aProfile x1a = new X1aProfile (this); + _profile.add (x1a); + // Linking the X1 profile to the X1a profile saves checking the former twice. + x1a.setX1Profile (x1); + _profile.add (new X2Profile (this)); + _profile.add (new X3Profile (this)); + _showAnnotations = false; + _showFonts = false; + _showOutlines = false; + _showPages = false; + maxFonts = DEFAULT_MAX_FONTS; + } + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + * + * Parsing methods. + ******************************************************************/ + + /** Reset parameter settings. + * Returns to a default state without any parameters. + */ + public void resetParams () + throws Exception + { + _showAnnotations = true; + _showFonts = true; + _showOutlines = true; + _showPages = true; + maxFonts = DEFAULT_MAX_FONTS; + } + + /** + * Per-action initialization. May be called multiple times. + * + * @param param The module parameter; under command-line Jhove, the -p parameter. + * If the parameter contains the indicated characters, then the + * specified information is omitted; otherwise, it is included. + * (This is the reverse of the behavior prior to beta 3.) + * These characters may be provided as separate parameters, + * or all in a single parameter. + *
    + *
  • a: annotations
  • + *
  • f: fonts
  • + *
  • o: outlines
  • + *
  • p: pages
  • + *

+ * The parameter is case-independent. A null parameter is + * equivalent to the empty string. + */ + public void param (String param) + { + if (param != null) { + param = param.toLowerCase (); + if (param.indexOf ('a') >= 0) { + _showAnnotations = false; + } + if (param.indexOf ('f') >= 0) { + _showFonts = false; + } + if (param.indexOf ('o') >= 0) { + _showOutlines = false; + } + if (param.indexOf ('p') >= 0) { + _showPages = false; + } + if (param.indexOf ('n') >= 0) { + // Parse out the number after the n, and use that to set + // the maximum number of fonts reported. Default is DEFAULT_MAX_FONTS. + int n = param.indexOf ('n'); + StringBuffer b = new StringBuffer (); + for (int i = n + 1; i < param.length(); i++) { + char ch = param.charAt(i); + if (Character.isDigit (ch)) { + b.append(ch); + } + else { + break; + } + } + try { + int mx = Integer.parseInt (b.toString ()); + if (mx > 0) { + maxFonts = mx; + } + } + catch (Exception e) {} + } + } + } + + /** + * Parse a file and stores descriptive information. A RandomAccessFile + * must be used to represent the object. + * + * @param raf A PDF file + * @param info A clean RepInfo object, which will be modified to hold + * the descriptive information + */ + public final void parse (RandomAccessFile raf, RepInfo info) + throws IOException + { + initParse (); + info.setFormat (_format[0]); + info.setMimeType (_mimeType[0]); + info.setModule (this); + _objects = new HashMap (); + _raf = raf; + + Tokenizer tok = new FileTokenizer (_raf); + _parser = new Parser (tok); + _parser.setObjectMap (_objects); + + List metadataList = new ArrayList (11); + /* We construct a big whopping property, + which contains up to 11 subproperties */ + _metadata = new Property ("PDFMetadata", + PropertyType.PROPERTY, + PropertyArity.LIST, + metadataList); + + if (_raf.length () > 10000000000L) { // that's 10^10 + _pdfACompliant = false; // doesn't meet size limit in Appendix C of PDF spec + } + if (!parseHeader (info)) { + return; + } + if (!findLastTrailer (info)) { + return; + } + + /* Walk through the linked trailer and cross reference + sections. */ + _prevxref = -1; + boolean lastTrailer = true; + while (_startxref > 0) { + // After the first (last) trailer, parse only for next "Prev" link + if (!parseTrailer (info, !lastTrailer)) { + return; + } + if (!readXRefInfo (info)) { + return; + } + ++_numTrailers; + if (_xrefIsStream) { + /* If we have an xref stream, readXRefInfo dealt with all + * the streams in a single call. */ + break; + } + // Beware infinite loop on badly broken file + if (_startxref == _prevxref) { + info.setMessage (new ErrorMessage + ("Cross reference tables are broken", + _parser.getOffset ())); + info.setWellFormed (false); + return; + } + _startxref = _prevxref; + lastTrailer = false; + } + if (!readDocCatalogDict (info)) { + return; + } + if (!readEncryptDict (info)) { + return; + } + if (!readDocInfoDict (info)) { + return; + } + if (!readDocumentTree (info)) { + return; + } + if (!readPageLabelTree (info)) { + return; + } + if (!readXMPData (info)) { + return; + } + findExternalStreams (info); + if (!findFilters (info)) { + return; + } + findImages (info); + findFonts (info); + + /* Object is well-formed PDF. */ + + /* We may have already done the checksums while converting a + temporary file. */ + Checksummer ckSummer = null; + if (_je != null && _je.getChecksumFlag () && + info.getChecksum ().size () == 0) { + ckSummer = new Checksummer (); + calcRAChecksum (ckSummer, raf); + setChecksums (ckSummer, info); + } + + info.setVersion (_version); + metadataList.add(new Property ("Objects", + PropertyType.INTEGER, + new Integer (_numObjects))); + metadataList.add (new Property ("FreeObjects", + PropertyType.INTEGER, + new Integer (_numFreeObjects))); + metadataList.add (new Property ("IncrementalUpdates", + PropertyType.INTEGER, + new Integer (_numTrailers))); + if (_docCatalogList != null) { + metadataList.add (new Property("DocumentCatalog", + PropertyType.PROPERTY, + PropertyArity.LIST, + _docCatalogList)); + } + if (_encryptList != null) { + metadataList.add (new Property ("Encryption", + PropertyType.PROPERTY, + PropertyArity.LIST, + _encryptList)); + } + if (_docInfoList != null) { + metadataList.add (new Property ("Info", + PropertyType.PROPERTY, + PropertyArity.LIST, + _docInfoList)); + } + if (_idProperty != null) { + metadataList.add (_idProperty); + } + if (_extStreamsList != null && !_extStreamsList.isEmpty ()) { + metadataList.add (new Property ("ExternalStreams", + PropertyType.PROPERTY, + PropertyArity.LIST, + _extStreamsList)); + } + if (_filtersList != null && !_filtersList.isEmpty ()) { + metadataList.add (new Property ("Filters", + PropertyType.PROPERTY, + PropertyArity.LIST, + _filtersList)); + } + if (_imagesList != null && !_imagesList.isEmpty ()) { + metadataList.add (new Property ("Images", + PropertyType.PROPERTY, + PropertyArity.LIST, + _imagesList)); + } + if (_showFonts || _verbosity == Module.MAXIMUM_VERBOSITY) { + try { addFontsProperty (metadataList); } + catch (NullPointerException e) { + info.setMessage(new ErrorMessage ("unexpected error in parsing font property", e.toString())); + } + } + if (_nFonts > maxFonts) { + info.setMessage(new InfoMessage ("Too many fonts to report; some fonts omitted.", + "Total fonts = " + _nFonts)); + } + if (_xmpProp != null) { + metadataList.add (_xmpProp); + } + addPagesProperty (metadataList, info); + + if (!doOutlineStuff (info)) { + return; + } + + info.setProperty (_metadata); + + /* Check for profile conformance. */ + + if (!_parser.getPDFACompliant ()) { + _pdfACompliant = false; + } + ListIterator pter = _profile.listIterator (); + if (info.getWellFormed() == RepInfo.TRUE) { + // Well-formedness is necessary to satisfy any profile. + while (pter.hasNext ()) { + PdfProfile prof = (PdfProfile) pter.next (); + if (prof.satisfiesProfile (_raf, _parser)) { + info.setProfile (prof.getText ()); + } + } + } + } + + /** + * Returns true if the module hasn't detected any violations + * of PDF/A compliance. This must return true, but is not + * sufficient by itself, to establish compliance. The + * AProfile profiler makes the final determination. + */ + public boolean mayBePDFACompliant () + { + return _pdfACompliant; + } + + /** + * Returns the document tree root. + */ + public PageTreeNode getDocumentTree () + { + return _docTreeRoot; + } + + /** + * Returns the document information dictionary. + */ + public PdfDictionary getDocInfo () + { + return _docInfoDict; + } + + /** + * Returns the encryption dictionary. + */ + public PdfDictionary getEncryptionDict () + { + return _encryptDict; + } + + /** + * Return true if Actions have been detected in the file. + */ + public boolean getActionsExist () + { + return _actionsExist; + } + + /** + * Initialize the module. This is called at the start + * of parse restore the module to its initial state. + */ + protected final void initParse () + { + super.initParse (); + _xref = null; + _xref2 = null; + _version = ""; + _objects = null; + _numFreeObjects = 0; + _objCount = 0; + _docInfoList = null; + _extStreamsList = null; + _docCatalogList = null; + _encryptList = null; + _imagesList = null; + _filtersList = null; + _pagesList = null; + _type0FontsMap = null; + _type1FontsMap = null; + _mmFontsMap = null; + _type3FontsMap = null; + _trueTypeFontsMap = null; + _cid0FontsMap = null; + _cid2FontsMap = null; + _docCatDictRef = null; + _encryptDictRef = null; + _docInfoDictRef = null; + _pagesDictRef = null; + _docCatDict = null; + _docInfoDict = null; + _docTreeRoot = null; + _pageLabelDict = null; + _encryptDict = null; + _trailerDict = null; + _viewPrefDict = null; + _outlineDict = null; + _destsDict = null; + _pageSeqMap = null; + _pageLabelRoot = null; + _embeddedFiles = null; + _destNames = null; + _skippedFontsReported = false; + _skippedOutlinesReported = false; + _skippedAnnotationsReported = false; + _skippedPagesReported = false; + _idProperty = null; + _actionsExist = false; + _numObjects = 0; + _numTrailers = -1; + _pdfACompliant = true; // assume compliance till disproven + _xmpProp = null; + _cachedStreamIndex = -1; + _nFonts = 0; + } + + protected boolean parseHeader (RepInfo info) throws IOException + { + Token token = null; + String value = null; + final String nohdr = "No PDF header"; + + /* Parse file header. */ + + boolean foundSig = false; + for (;;) { + if (_parser.getOffset() > 1024) { + break; + } + try { + token = null; + token = _parser.getNext (1024L); + } + catch (IOException ee) { + break; + } + catch (Exception e) {} // fall through + if (token == null) { + break; + } + if (token instanceof Comment) { + value = ((Comment) token).getValue (); + if (value.indexOf ("PDF-1.") == 0) { + foundSig = true; + _version = value.substring (4, 7); + /* If we got this far, take note that the signature is OK. */ + info.setSigMatch(_name); + break; + } + // The implementation notes (though not the spec) + // allow an alternative signature of %!PS-Adobe-N.n PDF-M.m + if (value.indexOf ("!PS-Adobe-") == 0) { + // But be careful: that much by itself is the standard + // PostScript signature. + int n = value.indexOf ("PDF-1."); + if (n >= 11) { + foundSig = true; + _version = value.substring (n + 4); + // However, this is not PDF-A compliant. + _pdfACompliant = false; + info.setSigMatch (_name); + break; + } + } + } + + // If we don't find it right at the beginning, we aren't + // PDF/A compliant. + _pdfACompliant = false; + } + if (!foundSig) { + info.setWellFormed (false); + info.setMessage (new ErrorMessage (nohdr, 0L)); + return false; + } + // Check for PDF/A conformance. The next item must be + // a comment with four characters, each greater than 127 + try { + token = _parser.getNext (); + String cmt = ((Comment) token).getValue (); + char[] cmtArray = cmt.toCharArray (); + int ctlcnt = 0; + for (int i = 0; i < 4; i++) { + if ((int) cmtArray[i] > 127) { + ctlcnt++; + } + } + if (ctlcnt < 4) { + _pdfACompliant = false; + } + } + catch (Exception e) { + // Most likely a ClassCastException on a non-comment + _pdfACompliant = false; + } + return true; + } + + + private long lastEOFOffset(RandomAccessFile raf) throws IOException { + + long offset = 0; + long flen = 0; + byte[] buf = null; + + // overkill to restore fileposition, but make this + // as side-effect free as possible + long savepos = raf.getFilePointer(); + flen = raf.length(); + buf = new byte[(int) Math.min(EOFSCANSIZE, flen)]; + offset = flen - buf.length; + raf.seek(offset); + raf.read(buf); + raf.seek(savepos); + + //OK: + // flen is the total length of the file + // offset is 1024 bytes from the end of file or 0 if file is shorter than 1024 + // buf contains all bytes from offset to end of file + + long eofpos = -1; + // Note the limits, selected so the index never is out of bounds + for (int i = buf.length-4; i >= 1; i--) { + if (buf[i] == '%') { + if ((buf[i-1] == '%') && + (buf[i+1] == 'E') && + (buf[i+2] == 'O') && + (buf[i+3] == 'F')) { + eofpos = offset+i-1; + break; + } + } + } + +// if (Tracing.T_MODULE) System.out.println(flen - eofpos); + return eofpos; + + } + + + private long lastStartXrefOffset(RandomAccessFile raf, long eofOffset) throws IOException { + + long offset = 0; + long flen = 0; + byte[] buf = null; + + // overkill to restore fileposition, but make this + // as side-effect free as possible + long savepos = raf.getFilePointer(); + flen = raf.length(); + if (eofOffset <= 0) { + eofOffset = flen; + } + if (eofOffset >= flen) { + eofOffset = flen; + } + buf = new byte[(int) Math.min(XREFSCANSIZE, eofOffset)]; + offset = eofOffset - buf.length; + raf.seek(offset); + raf.read(buf); + raf.seek(savepos); + + //OK: + // flen is the total length of the file + // offset is 128 bytes from the end of file or 0 if file is shorter than 128 + // buf contains all bytes from offset to end of file + + long xrefpos = -1; + // Note the limits, selected so the index never is out of bounds + for (int i = buf.length-9; i >= 0; i--) { + if (buf[i] == 's') { + if ((buf[i+1] == 't') && + (buf[i+2] == 'a') && + (buf[i+3] == 'r') && + (buf[i+4] == 't') && + (buf[i+5] == 'x') && + (buf[i+6] == 'r') && + (buf[i+7] == 'e') && + (buf[i+8] == 'f')) { + xrefpos = offset+i; + break; + } + } + } + +// if (Tracing.T_MODULE) System.out.println(flen - xrefpos); + return xrefpos; + + } + + + /** Locate the last trailer of the file */ + protected boolean findLastTrailer (RepInfo info) throws IOException + { + /* Parse file trailer. Technically, this should be the last thing in + * the file, but we follow the Acrobat convention of looking in the + * last 1024 bytes. Since incremental updates may add multiple + * EOF comments, make sure that we use the last one in the file. */ + + Token token = null; + String value = null; + + _eof = lastEOFOffset(_raf); + + if (_eof < 0L) { + info.setWellFormed (false); + info.setMessage (new ErrorMessage ("No PDF trailer", + _raf.length ())); + return false; + } + + // For PDF-A compliance, this must be at the very end. + /* Fix contributed by FCLA, 2007-05-30, to test for trailing data + * properly. + * + * if (_raf.length () - _eof > 6) { + */ + if (_raf.length () - _eof > 7) { + _pdfACompliant = false; + } + + /* Retrieve the "startxref" keyword. */ + + long startxrefoffset = lastStartXrefOffset(_raf, _eof); + _startxref = -1L; + + if (startxrefoffset >= 0) { + try { + _parser.seek (startxrefoffset); // points to the 'startxref' kw + //_parser.seek (_eof - 23); // should we allow more slop? + } + catch (PdfException e) {} + while (true) { + try { + token = _parser.getNext (); + } + catch (Exception e) { + // we're starting at an arbitrary point, so there + // can be parsing errors. Ignore them till we get + // back in sync. + continue; + } + if (token == null) { + break; + } + if (token instanceof Keyword) { + value = ((Keyword) token).getValue (); + if (value.equals ("startxref")) { + try { + token = _parser.getNext (); + } + catch (Exception e) { + break; // no excuses here + } + if (token != null && token instanceof Numeric) { + _startxref = ((Numeric) token).getLongValue (); + } + } + } + } + } + if (_startxref < 0L) { + info.setWellFormed (false); + info.setMessage (new ErrorMessage ("Missing startxref keyword " + + "or value", _parser.getOffset ())); + return false; + } + return true; + } + /* Parse a "trailer" (which is not necessarily the last + thing in the file, as trailers can be linked.) */ + protected boolean parseTrailer (RepInfo info, + boolean prevOnly) + throws IOException + { + Token token = null; + String value = null; + String invalidMsg = "Invalid cross-reference table"; + /* Parse the trailer dictionary. */ + + try { + _parser.seek (_startxref); + /* The next object may be either the keyword "xref", signifying + * a classic cross-reference table, or a stream object, + * signifying the new-style cross-reference stream. + */ + Token xref = _parser.getNext (); + if (xref instanceof Keyword) { + _xrefIsStream = false; + _parser.getNext (Numeric.class, invalidMsg); // first object number + _objCount = ((Numeric) _parser.getNext + (Numeric.class, invalidMsg)).getIntegerValue (); + _parser.seek (_parser.getOffset () + _objCount*20); + } + else if (xref instanceof Numeric) { + /* No cross-ref tables to backtrack. */ + _xrefIsStream = true; + _prevxref = -1; + /* But I do need to read the dictionary at this point, to get + * essential stuff out of it. */ + PdfStream str = (PdfStream) _parser.readObjectDef((Numeric) xref); + PdfDictionary dict = str.getDict(); + _docCatDictRef = (PdfIndirectObj) dict.get ("Root"); + if (_docCatDictRef == null) { + throw new PdfInvalidException + ("Root entry missing in cross-ref stream dictionary", + _parser.getOffset ()); + } + /* We don't need to see a trailer dictionary. + * Move along, move along. */ + return true; + } + + /* Now find the "trailer" keyword. */ + long trailer = -1L; + while ((token = _parser.getNext ()) != null) { + if (token instanceof Keyword) { + value = ((Keyword) token).getValue (); + if (value.equals ("trailer")) { + token = _parser.getNext (); + if (token instanceof DictionaryStart) { + trailer = _parser.getOffset () - 7L; + break; + } + } + } + } + if (trailer < 0L) { + info.setWellFormed (false); + info.setMessage (new ErrorMessage ("No file trailer", + _parser.getOffset ())); + return false; + } + + _trailerDict = _parser.readDictionary (); + PdfObject obj; + + // Extract contents of the trailer dictionary + + _prevxref = -1; + obj = _trailerDict.get ("Prev"); + if (obj != null) { + if (obj != null && obj instanceof PdfSimpleObject) { + token = ((PdfSimpleObject) obj ).getToken (); + if (token instanceof Numeric) + _prevxref = ((Numeric) token).getLongValue (); + } + if (_prevxref < 0) { + throw new PdfInvalidException + ("Invalid Prev offset in trailer dictionary", + _parser.getOffset ()); + } + } + // If this isn't the last (first read) trailer, then we + // ignore all the other dictionary entries. + if (prevOnly) { + return true; + } + + obj = _trailerDict.get ("Size"); + if (obj != null) { + _numObjects = -1; + if (obj != null && obj instanceof PdfSimpleObject) { + token = ((PdfSimpleObject) obj ).getToken (); + if (token instanceof Numeric) + _numObjects = ((Numeric) token).getIntegerValue (); + _xref = new long[_numObjects]; + } + if (_numObjects < 0) { + throw new PdfInvalidException + ("Invalid Size entry in trailer dictionary", + _parser.getOffset ()); + } + if (_numObjects > 8388607) { + // Appendix C implementation limit is enforced by PDF/A + _pdfACompliant = false; + } + } + else throw new PdfInvalidException + ("Size entry missing in trailer dictionary", + _parser.getOffset ()); + _docCatDictRef = (PdfIndirectObj) _trailerDict.get ("Root"); + if (_docCatDictRef == null) { + throw new PdfInvalidException + ("Root entry missing in trailer dictionary", + _parser.getOffset ()); + } + _encryptDictRef =(PdfIndirectObj) _trailerDict.get ("Encrypt"); // This is at least v. 1.1 + _encrypted = (_encryptDictRef != null); + _parser.setEncrypted (_encrypted); + + PdfObject infoObj = _trailerDict.get("Info"); + if (infoObj != null && !(infoObj instanceof PdfIndirectObj)) { + throw new PdfInvalidException ("Trailer dictionary Info key is " + + "not an indirect reference", + _parser.getOffset ()); + } + _docInfoDictRef = (PdfIndirectObj) infoObj; + + obj = _trailerDict.get ("ID"); // This is at least v. 1.1 + if (obj != null) { + String badID = "Invalid ID in trailer"; + if (obj instanceof PdfArray) { + String [] id = new String[2]; + try { + PdfArray idArray = (PdfArray) obj; + Vector idVec = idArray.getContent (); + if (idVec.size () != 2) { + throw new PdfInvalidException (badID); + } + PdfSimpleObject idobj = (PdfSimpleObject) idVec.get(0); + id[0] = toHex + (((StringValuedToken) idobj.getToken () ).getRawBytes ()); + idobj = (PdfSimpleObject) idVec.get(1); + id[1] = toHex + (((StringValuedToken) idobj.getToken () ).getRawBytes ()); + _idProperty = new Property ("ID", PropertyType.STRING, + PropertyArity.ARRAY, id); + } + catch (Exception e) { + throw new PdfInvalidException (badID); + } + } + else { + throw new PdfInvalidException (badID, _parser.getOffset ()); + } + } + obj = _trailerDict.get ("XRefStm"); + if (obj != null) { + /* We have a "hybrid" cross-reference scheme. This means we have + * to go through the cross-reference stream and have its entries + * supplement the cross-reference section. */ + _logger.warning("Hybrid cross-reference not yet implemented"); + } + } + catch (PdfException e) { + + e.disparage (info); + info.setMessage (new ErrorMessage + (e.getMessage (), _parser.getOffset ())); + // If it's merely invalid rather than ill-formed, keep going + return (e instanceof PdfInvalidException); + } + return true; + } + + /* Parses the cross-reference table or stream. */ + protected boolean readXRefInfo (RepInfo info) throws IOException + { + if (_xrefIsStream) { + return readXRefStreams (info); + } + else { + return readXRefTables (info); + } + } + + /* Parses the cross-reference streams. This is called from + * readXRefInfo if there is no cross-reference table. + * I still need to deal with hybrid cases. All linked cross-reference + * streams are handled here. + */ + protected boolean readXRefStreams (RepInfo info) throws IOException + { + _pdfACompliant = false; // current version of PDF/A doesn't recognize XREF streams + while (_startxref > 0) { + try { + _parser.seek (_startxref); + PdfStream pstream = + (PdfStream) _parser.readObjectDef (); + int sObjNum = pstream.getObjNumber(); + CrossRefStream xstream = new CrossRefStream (pstream); + if (!xstream.isValid ()) { + return false; + } + xstream.initRead (_raf); + int no = xstream.getNumObjects (); + if (_xref == null) { + _xref = new long [no]; + _xref2 = new int[no] []; + } + if (sObjNum < 0 || sObjNum >= no) { + throw new PdfMalformedException + ("Invalid object number in cross-reference stream", + _parser.getOffset ()); + } + _xref[sObjNum] = _startxref; // insert the index of the xref stream itself + _startxref = xstream.getPrevXref(); + try { + while (xstream.readNextObject()) { + int objNum = xstream.getObjNum(); + if (xstream.isObjCompressed ()) { + // Hold off on this branch + _xref[objNum] = -1; // defers to _xref2 + _xref2[objNum] = new int[] { + xstream.getContentStreamObjNum(), + xstream.getContentStreamIndex() + }; + } + else { + if (_xref[objNum] == 0) { + _xref[objNum] = xstream.getOffset (); + } + } + } + _numFreeObjects += xstream.getFreeCount (); + } + catch (IOException e) { + info.setWellFormed (false); + info.setMessage (new ErrorMessage + ("Malformed cross reference stream", + _parser.getOffset ())); + return false; + } + } + catch (PdfException e) { + + e.disparage (info); + info.setMessage (new ErrorMessage + (e.getMessage (), _parser.getOffset ())); + // If it's merely invalid rather than ill-formed, keep going + return (e instanceof PdfInvalidException); + } + } + return true; // incomplete, but let it through + } + + /* Parses the cross-reference table. This is called from + * readXRefInfo if there is a cross-reference table. */ + protected boolean readXRefTables (RepInfo info) throws IOException + { + Token token = null; + String badXref = "Malformed cross-reference table"; + + try { + _parser.seek (_startxref); + token = _parser.getNext (); // "xref" keyword or numeric + if (token instanceof Keyword) { + while ((token = _parser.getNext ()) != null) { + int firstObj = 0; + // Look for the start of a cross-ref subsection, which + // begins with a base object number and a count. + if (token instanceof Numeric) { + firstObj = ((Numeric) token).getIntegerValue (); + } + else { + // On anything else, assume we're done with this section. + // (Most likely we've hit the keyword "trailer". + break; + } + _objCount = ((Numeric) _parser.getNext ()).getIntegerValue (); + for (int i=0; i<_objCount; i++) { + // In reading the cross-reference table, also check + // the extra syntactic requirements of PDF/A. + long offset = ((Numeric) _parser.getNext + (Numeric.class, badXref)).getLongValue (); + _parser.getNext (); // Generation number + if (_parser.getWSString ().length () > 1) { + _pdfACompliant = false; + } + token = _parser.getNext (Keyword.class, badXref); + if (_parser.getWSString ().length () > 1) { + _pdfACompliant = false; + } + // A keyword of "n" signifies an object in use, + // "f" signifies a free object. If we already + // have an entry for this object, don't replace it. + String keyval = ((Keyword) token).getValue (); + if (keyval.equals ("n")) { + if (_xref[firstObj + i] == 0) { + _xref[firstObj + i] = offset; + } + } + else if (keyval.equals ("f")) { + _numFreeObjects++; + } + else { + throw new PdfMalformedException + ("Illegal operator in xref table", + _parser.getOffset ()); + } + } + } + } + } + catch (PdfException e) { + e.disparage (info); + info.setMessage (new ErrorMessage (e.getMessage (), + _parser.getOffset ())); + return false; + } + catch (Exception e) { + info.setValid (false); + info.setMessage (new ErrorMessage (e.getMessage (), + _parser.getOffset ())); + } + return true; + } + + protected boolean readDocCatalogDict (RepInfo info) + throws IOException + { + final String nocat = "No document catalog dictionary"; + Property p = null; + _docCatDict = null; + _docCatalogList = new ArrayList (2); + // Get the Root reference which we had before, and + // resolve it to the dictionary object. + if (_docCatDictRef == null) { + info.setWellFormed (false); + info.setMessage (new ErrorMessage + (nocat, 0)); + return false; + } + try { + _docCatDict = (PdfDictionary) resolveIndirectObject + (_docCatDictRef); + } + catch (Exception e) { + e.printStackTrace(); + } + if (_docCatDict == null) { + // If it fails here, it's ill-formed, not + // just invalid + info.setWellFormed (false); + info.setMessage (new ErrorMessage + (nocat, 0)); + return false; + } + try { + + PdfObject viewPref = _docCatDict.get("ViewerPreferences"); + viewPref = resolveIndirectObject (viewPref); + if (viewPref instanceof PdfDictionary) { + _viewPrefDict = (PdfDictionary) viewPref; + p = buildViewPrefProperty (_viewPrefDict); + _docCatalogList.add (p); + } + String pLayoutText = "SinglePage"; // default + PdfObject pLayout = resolveIndirectObject (_docCatDict.get ("PageLayout")); + if (pLayout instanceof PdfSimpleObject) { + pLayoutText = ((PdfSimpleObject) pLayout).getStringValue (); + } + p = new Property ("PageLayout", + PropertyType.STRING, + pLayoutText); + _docCatalogList.add (p); + + String pModeText = "UseNone"; // default + PdfObject pMode = resolveIndirectObject (_docCatDict.get ("PageMode")); + if (pMode instanceof PdfSimpleObject) { + pModeText = ((PdfSimpleObject) pMode).getStringValue (); + } + p = new Property ("PageMode", + PropertyType.STRING, + pModeText); + _docCatalogList.add (p); + + PdfObject outlines = resolveIndirectObject (_docCatDict.get ("Outlines")); + if (outlines instanceof PdfDictionary) { + _outlineDict = (PdfDictionary) outlines; + } + + PdfObject lang = resolveIndirectObject (_docCatDict.get ("Lang")); + if (lang != null && lang instanceof PdfSimpleObject) { + String langText = ((PdfSimpleObject) lang).getStringValue (); + p = new Property ("Language", PropertyType.STRING, + _encrypted ? ENCRYPTED : langText); + _docCatalogList.add (p); + } + + // The Pages dictionary doesn't go into the property, + // but this is a convenient time to grab it and the page label dictionary. + _pagesDictRef = (PdfIndirectObj) _docCatDict.get ("Pages"); + _pageLabelDict = (PdfDictionary) + resolveIndirectObject (_docCatDict.get ("PageLabels")); + + // Grab the Version entry, and use it to override the + // file header IF it's later. + PdfObject vers = resolveIndirectObject (_docCatDict.get ("Version")); + if (vers instanceof PdfSimpleObject) { + String versString = ((PdfSimpleObject) vers).getStringValue (); + String infoVersString = _version; + try { + double ver = Double.parseDouble (versString); + double infoVer = Double.parseDouble (infoVersString); + /* Set a message if this doesn't agree with RepInfo */ + if (ver != infoVer) { + info.setMessage (new InfoMessage + ("File header gives version as " + + versString + + ", but catalog dictionary gives version as " + + infoVersString)); + } + /* Replace the version in RepInfo if this is larger */ + if (ver > infoVer) { + _version = versString; + } + } + catch (NumberFormatException e) { + throw new PdfInvalidException + ("Invalid Version in document catalog"); + } + } + + // Get the Names dictionary in order to grab the + // EmbeddedFiles and Dests entries. + final String badname = "Invalid Names dictionary"; + try { + PdfDictionary namesDict = + (PdfDictionary) resolveIndirectObject (_docCatDict.get ("Names")); + if (namesDict != null) { + PdfDictionary embeddedDict = (PdfDictionary) resolveIndirectObject + (namesDict.get ("EmbeddedFiles")); + if (embeddedDict != null) { + _embeddedFiles = new NameTreeNode (this, null, embeddedDict); + } + + PdfDictionary dDict = (PdfDictionary) resolveIndirectObject + (namesDict.get ("Dests")); + if (dDict != null) { + _destNames = new NameTreeNode (this, null, dDict); + } + } + } + catch (ClassCastException ce) { + throw new PdfInvalidException (badname); + } + catch (Exception e) { + throw new PdfMalformedException (badname); + } + + // Get the optional Dests dictionary. Note that destinations + // may be specified in either of two completely different + // ways: a dictionary here, or a name tree from the Names + // dictionary. + + try { + _destsDict = (PdfDictionary) resolveIndirectObject + (_docCatDict.get ("Dests")); + } + catch (ClassCastException ce) { + throw new PdfInvalidException ("Invalid Dests dictionary"); + } + catch (Exception e) { + throw new PdfMalformedException ("Invalid Dests dictionary"); + } + } + + catch (PdfException e) { + e.disparage (info); // clears Valid or WellFormed as appropriate + info.setMessage (new ErrorMessage + (e.getMessage (), _parser.getOffset ())); + // Keep going if it's only invalid + return (e instanceof PdfInvalidException); + } + catch (Exception e) { + // Unexpected exception -- declare not well-formed + info.setWellFormed (false); + info.setMessage (new ErrorMessage + (e.toString (), _parser.getOffset ())); + return false; + } + return true; + } + + protected boolean readEncryptDict (RepInfo info) + throws IOException + { + String filterText = ""; + String effText = null; + // Get the reference which we had before, and + // resolve it to the dictionary object. + if (_encryptDictRef == null) { + return true; // encryption entry is optional + } + try { + _encryptList = new ArrayList (6); + PdfDictionary dict = (PdfDictionary) resolveIndirectObject + (_encryptDictRef); + _encryptDict = dict; + + PdfObject filter = dict.get ("Filter"); + if (filter instanceof PdfSimpleObject) { + Token tok = ((PdfSimpleObject)filter).getToken (); + if (tok instanceof Name) { + filterText = ((Name) tok).getValue (); + } + } + Property p = new Property ("SecurityHandler", + PropertyType.STRING, + filterText); + _encryptList.add (p); + //PdfObject eff = dict.get ("EFF"); + if (filter instanceof PdfSimpleObject) { + Token tok = ((PdfSimpleObject)filter).getToken (); + if (tok instanceof Name) { + effText = ((Name) tok).getValue (); + } + } + if (effText != null) { + p = new Property ("EFF", PropertyType.STRING, effText); + _encryptList.add (p); + } + + int algValue = 0; + PdfObject algorithm = dict.get ("V"); + if (algorithm instanceof PdfSimpleObject) { + Token tok = ((PdfSimpleObject) algorithm).getToken (); + if (tok instanceof Numeric) { + algValue = ((Numeric) tok).getIntegerValue (); + if (_je != null && _je.getShowRawFlag ()) { + p = new Property ("Algorithm", + PropertyType.INTEGER, + new Integer (algValue)); + } + else { + try { + p = new Property ("Algorithm", + PropertyType.STRING, + PdfStrings.ALGORITHM[algValue]); + } + catch (Exception e) { + throw new PdfInvalidException + ("Invalid algorithm value in encryption dictionary", + _parser.getOffset ()); + } + } + if (p != null) { + _encryptList.add (p); + } + } + } + + int keyLen = 40; + PdfObject length = dict.get ("Length"); + if (length instanceof PdfSimpleObject) { + Token tok = ((PdfSimpleObject) length).getToken (); + if (tok instanceof Numeric) { + keyLen = ((Numeric) tok).getIntegerValue (); + } + if (_je != null) { + p = new Property ("KeyLength", + PropertyType.INTEGER, + new Integer (keyLen)); + _encryptList.add (p); + } + } + + if ("Standard".equals (filterText)) { + List stdList = new ArrayList (4); + // Flags have a known meaning only if Standard + // security handler was specified + PdfObject flagObj = dict.get ("P"); + PdfObject revObj = dict.get ("R"); + int rev = 2; // assume old rev if not present + if (revObj instanceof PdfSimpleObject) { + rev = ((PdfSimpleObject) revObj).getIntValue (); + } + if (flagObj instanceof PdfSimpleObject) { + int flags = + ((PdfSimpleObject) flagObj).getIntValue (); + String[] flagStrs; + if (rev == 2) { + flagStrs = PdfStrings.USERPERMFLAGS2; + } + else { + flagStrs = PdfStrings.USERPERMFLAGS3; + } + p = buildUserPermProperty (flags, flagStrs); + stdList.add (p); + + stdList.add (new Property ("Revision", + PropertyType.INTEGER, + new Integer (rev))); + } + PdfObject oObj = dict.get ("O"); + if (oObj != null) { + if (oObj instanceof PdfSimpleObject) { + stdList.add (new Property ("OwnerString", + PropertyType.STRING, + toHex (((PdfSimpleObject) oObj).getRawBytes ()))); + } + } + PdfObject uObj = dict.get ("U"); + if (uObj != null) { + if (uObj instanceof PdfSimpleObject) { + stdList.add (new Property ("UserString", + PropertyType.STRING, + toHex (((PdfSimpleObject) uObj).getRawBytes ()))); + } + } + _encryptList.add (new Property ("StandardSecurityHandler", + PropertyType.PROPERTY, + PropertyArity.LIST, + stdList)); + } + + } + catch (PdfException e) { + e.disparage (info); + info.setMessage (new ErrorMessage + (e.getMessage (), _parser.getOffset ())); + return (e instanceof PdfInvalidException); + } + return true; + } + + protected boolean readDocInfoDict (RepInfo info) + throws IOException + { + // Get the Info reference which we had before, and + // resolve it to the dictionary object. + if (_docInfoDictRef == null) { + return true; // Info is optional + } + _docInfoList = new ArrayList (9); + try { + _docInfoDict = (PdfDictionary) resolveIndirectObject + (_docInfoDictRef); + + addStringProperty (_docInfoDict, _docInfoList, "Title", "Title"); + addStringProperty (_docInfoDict, _docInfoList, "Author", "Author"); + addStringProperty (_docInfoDict, _docInfoList, "Subject", "Subject"); + addStringProperty (_docInfoDict, _docInfoList, "Keywords", "Keywords"); + addStringProperty (_docInfoDict, _docInfoList, "Creator", "Creator"); + addStringProperty (_docInfoDict, _docInfoList, "Producer", "Producer"); + // CreationDate requires string-to-date conversion + // ModDate does too + addDateProperty (_docInfoDict, _docInfoList, "CreationDate", "CreationDate"); + addDateProperty (_docInfoDict, _docInfoList, "ModDate", "ModDate"); + addStringProperty (_docInfoDict, _docInfoList, "Trapped", "Trapped"); + } + catch (PdfException e) { + e.disparage (info); + info.setMessage (new ErrorMessage + (e.getMessage (), _parser.getOffset ())); + // Keep parsing if it's only invalid + return (e instanceof PdfInvalidException); + } + catch (Exception e) { + info.setWellFormed(false); + info.setMessage (new ErrorMessage ("Unexpected exception " + + e.getClass().getName())); + } + return true; + } + + protected boolean readDocumentTree (RepInfo info) + throws IOException + { + try { + if (_pagesDictRef == null) { + throw new PdfInvalidException ("Document page tree not found"); + } + + PdfObject pagesObj = resolveIndirectObject (_pagesDictRef); + if (!(pagesObj instanceof PdfDictionary)) + throw new PdfMalformedException ("Invalid page dictionary object"); + PdfDictionary pagesDict = (PdfDictionary) pagesObj; + + _docTreeRoot = new PageTreeNode (this, null, pagesDict); + _docTreeRoot.buildSubtree (true, 100); + } + catch (PdfException e) { + e.disparage (info); + info.setMessage (new ErrorMessage + (e.getMessage (), _parser.getOffset ())); + // Continue parsing if it's only invalid + return (e instanceof PdfInvalidException); + } + catch (Exception e) { + // Catch any odd exceptions + info.setMessage (new ErrorMessage (e.getClass().getName(), _parser.getOffset ())); + info.setWellFormed(false); + return false; + } + return true; + } + + protected boolean readPageLabelTree (RepInfo info) + { + // the page labels number tree is optional. + try { + if (_pageLabelDict != null) { + _pageLabelRoot = new PageLabelNode (this, null, _pageLabelDict); + _pageLabelRoot.buildSubtree (); + } + } + catch (PdfException e) { + e.disparage (info); + info.setMessage (new ErrorMessage + (e.getMessage (), _parser.getOffset ())); + // Continue parsing if it's only invalid + return (e instanceof PdfInvalidException); + } + catch (Exception e) { + info.setWellFormed(false); + info.setMessage (new ErrorMessage ("Unexpected exception " + + e.getClass().getName())); + return false; + } + return true; // always succeeds + } + + protected boolean readXMPData (RepInfo info) + { + final String badMetadata = "Invalid or ill-formed XMP metadata"; + try { + PdfStream metadata = (PdfStream) resolveIndirectObject (_docCatDict.get ("Metadata")); + if (metadata == null) { + return true; // Not required + } + //PdfDictionary metaDict = metadata.getDict (); + + // Create an InputSource to feed the parser. + SAXParserFactory factory = + SAXParserFactory.newInstance(); + factory.setNamespaceAware (true); + XMLReader parser = factory.newSAXParser ().getXMLReader (); + PdfXMPSource src = new PdfXMPSource (metadata, getFile ()); + XMPHandler handler = new XMPHandler (); + parser.setContentHandler (handler); + parser.setErrorHandler (handler); + + // We have to parse twice. The first time, we may get + // an encoding change as part of an exception thrown. If this + // happens, we create a new InputSource with the encoding, and + // continue. + try { + parser.parse (src); + _xmpProp = src.makeProperty (); + } + catch (SAXException se) { + String msg = se.getMessage (); + if (msg != null && msg.startsWith ("ENC=")) { + String encoding = msg.substring (5); + try { + src = new PdfXMPSource (metadata, getFile (), encoding); + parser.parse (src); + _xmpProp = src.makeProperty (); + } + catch (UnsupportedEncodingException uee) { + throw new PdfInvalidException (badMetadata); + } + } + } + + } + catch (PdfException e) { + e.disparage (info); + info.setMessage (new ErrorMessage + (e.getMessage (), _parser.getOffset ())); + // Continue parsing if it's only invalid + return (e instanceof PdfInvalidException); + } + catch (Exception e) { + info.setMessage (new ErrorMessage (badMetadata, + _parser.getOffset ())); + info.setValid (false); + return false; + } + return true; + } + + protected void findExternalStreams (RepInfo info) throws IOException + { + _extStreamsList = new LinkedList (); + // stop processing if there is no root for the document tree + if (_docTreeRoot == null) + return; + _docTreeRoot.startWalk (); + try { + for (;;) { + // Get all the page objects in the document sequentially + PageObject page = _docTreeRoot.nextPageObject (); + if (page == null) { + break; + } + // Get the streams for the page and walk through them + List streams = page.getContentStreams (); + if (streams != null) { + ListIterator streamIter = streams.listIterator (); + while (streamIter.hasNext ()) { + PdfStream stream = (PdfStream) streamIter.next (); + String specStr = stream.getFileSpecification (); + if (specStr != null) { + Property prop = new Property ("File", + PropertyType.STRING, + specStr); + _extStreamsList.add (prop); + } + } + } + } + } + catch (Exception e) { + info.setWellFormed(false); + info.setMessage (new ErrorMessage ("Unexpected exception " + + e.getClass().getName())); + } + } + + /** Locates the filters in the content stream dictionaries + * and generate a list of unique pipelines. + * + * @return false if the filter structure is + * defective. + */ + protected boolean findFilters (RepInfo info) + throws IOException + { + _filtersList = new LinkedList (); + // stop processing if there is no root for the document tree + if (_docTreeRoot == null) + return false; + _docTreeRoot.startWalk (); + try { + for (;;) { + // Get all the page objects in the document sequentially + PageObject page = _docTreeRoot.nextPageObject (); + if (page == null) { + break; + } + // Get the streams for the page and walk through them + List streams = page.getContentStreams (); + if (streams != null) { + ListIterator streamIter = streams.listIterator (); + while (streamIter.hasNext ()) { + PdfStream stream = (PdfStream) streamIter.next (); + Filter[] filters = stream.getFilters (); + extractFilters (filters, stream); + } + } + } + } + catch (PdfException e) { + e.disparage (info); + info.setMessage (new ErrorMessage + (e.getMessage (), _parser.getOffset ())); + // Continue parsing if it's only invalid + return (e instanceof PdfInvalidException); + } + return true; + } + + /** Finds the filters in a stream or array object which is the value + * of a stream's Filter key, and put them in _filtersList + * if a duplicate isn't there already. If the name is + * "Crypt", appends a colon and the name if available. + * Returns the filter string whether it's added or not, + * or null if there are no filters. + */ + protected String extractFilters (Filter[] filters, PdfStream stream) + { + /* Concatenate the names into a string of names separated + * by spaces. */ + int len = filters.length; + if (len == 0) { + return null; + } + StringBuffer buf = new StringBuffer (); + for (int i = 0; i < len; i++) { + Filter filt = filters[i]; + String fname = filt.getFilterName (); + buf.append(fname); + /* If it's a Crypt filter, add the crypt name. */ + if ("Crypt".equals (fname)) { + String cname = filt.getNameParam(); + if (cname != null) { + buf.append (":" + cname); + } + } + if (i < len - 1) { + buf.append (' '); + } + } + String filterStr = buf.toString (); + boolean unique = true; + // Check for uniqueness. + Iterator iter = _filtersList.iterator (); + while (iter.hasNext ()) { + Property p = (Property) iter.next (); + String s = (String) p.getValue (); + if (s.equals (filterStr)) { + unique = false; + break; + } + } + if (filterStr != null && unique) { + Property prop = new Property ("FilterPipeline", + PropertyType.STRING, + filterStr); + _filtersList.add (prop); + } + return filterStr; + } + + protected void findImages (RepInfo info) throws IOException + { + _imagesList = new LinkedList (); + _docTreeRoot.startWalk (); + try { + for (;;) { + // Get all the page objects in the document sequentially + PageObject page = _docTreeRoot.nextPageObject (); + if (page == null) { + break; + } + // Get the resources for the page and look for image XObjects + PdfDictionary rsrc = page.getResources (); + if (rsrc != null) { + PdfDictionary xo = (PdfDictionary) + resolveIndirectObject (rsrc.get ("XObject")); + if (xo != null) { + Iterator iter = xo.iterator (); + while (iter.hasNext ()) { + // Get an XObject and check if it's an image. + PdfDictionary xobdict = null; + PdfObject xob = resolveIndirectObject + ((PdfObject) iter.next ()); + if (xob instanceof PdfStream) { + xobdict = ((PdfStream) xob).getDict (); + } + if (xobdict != null) { + PdfSimpleObject subtype = (PdfSimpleObject) xobdict.get ("Subtype"); + if ("Image".equals (subtype.getStringValue ())) { + // It's an image XObject. Report stuff. + List imgList = new ArrayList (10); + Property prop = new Property ("Image", + PropertyType.PROPERTY, + PropertyArity.LIST, + imgList); + NisoImageMetadata niso = new NisoImageMetadata (); + imgList.add (new Property ("NisoImageMetadata", + PropertyType.NISOIMAGEMETADATA, niso)); + niso.setMimeType("application/pdf"); + PdfSimpleObject widObj = (PdfSimpleObject) + xobdict.get ("Width"); + niso.setImageWidth(widObj.getIntValue ()); + PdfSimpleObject htObj = (PdfSimpleObject) + xobdict.get ("Height"); + niso.setImageLength(htObj.getIntValue ()); + + // Check for filters to add to the filter list + Filter[] filters = ((PdfStream) xob).getFilters (); + String filt = extractFilters (filters, (PdfStream) xob); + if (filt != null) { + // If the filter is one which the NISO schema + // knows about, put it in the NISO metadata, + // otherwise put it in a Filter property. + int nisoFilt = nameToNiso (filt, + compressionStrings, compressionValues); + if (nisoFilt >= 0) { + /* If it's 2, it's a CCITTFaxDecode + * filter. There may be an optional + * K entry that can change the + * value. + */ + PdfObject parms = + xobdict.get ("DecodeParms"); + if (parms != null) { + PdfSimpleObject kobj = null; + if (parms instanceof + PdfDictionary) { + kobj = (PdfSimpleObject) + ((PdfDictionary) parms).get ("K"); + } + /* Note that the DecodeParms + * value may also be an array + * of dictionaries. We are not + * handling that contingency. + */ + if (kobj != null) { + int k = kobj.getIntValue(); + if (k < 0) { + nisoFilt = 4; + } + else if (k > 0) { + nisoFilt = 3; + } + } + } + niso.setCompressionScheme(nisoFilt); + } + else { + imgList.add (new Property("Filter", + PropertyType.STRING, + filt)); + } + } + else { + niso.setCompressionScheme(1); // no filter + } + + // Check for color space info + PdfObject colorSpc = xobdict.get ("ColorSpace"); + if (colorSpc != null) { + String colorName = null; + if (colorSpc instanceof PdfSimpleObject) { + colorName = ((PdfSimpleObject) colorSpc).getStringValue (); + } + else if (colorSpc instanceof PdfArray) { + Vector vec = ((PdfArray) colorSpc).getContent (); + // Use the first element, which is the color space family + PdfSimpleObject fam = (PdfSimpleObject) vec.elementAt (0); + colorName = fam.getStringValue (); + } + if (colorName != null) { + int nisoSpace = nameToNiso (colorName, + colorSpaceStrings, colorSpaceValues); + if (nisoSpace >= 0) { + niso.setColorSpace(nisoSpace); + } + else { + imgList.add (new Property ("ColorSpace", + PropertyType.STRING, + colorName)); + } + } + } + + PdfSimpleObject bpc = (PdfSimpleObject) + xobdict.get ("BitsPerComponent"); + if (bpc != null) { + //imgList.add (new Property ("BitsPerComponent", + // PropertyType.INTEGER, + // new Integer (bpc.getIntValue ()))); + niso.setBitsPerSample(new int[] { bpc.getIntValue() }); + } + + PdfSimpleObject intent = (PdfSimpleObject) + xobdict.get ("Intent"); + if (intent != null) { + imgList.add (new Property ("Intent", + PropertyType.STRING, + intent.getStringValue ())); + } + + PdfSimpleObject imgmsk = (PdfSimpleObject) + xobdict.get ("ImageMask"); + if (imgmsk != null) { + boolean b = imgmsk.isTrue (); + imgList.add (new Property ("ImageMask", + PropertyType.BOOLEAN, + new Boolean (b))); + } + + PdfArray dcd = (PdfArray) xobdict.get ("Decode"); + if (dcd != null) { + Vector dcdvec = dcd.getContent (); + List dcdlst = new ArrayList (dcdvec.size ()); + Iterator diter = dcdvec.iterator (); + while (diter.hasNext ()) { + PdfSimpleObject d = (PdfSimpleObject) diter.next (); + dcdlst.add (new Integer (d.getIntValue ())); + } + imgList.add (new Property ("Decode", + PropertyType.INTEGER, + PropertyArity.LIST, + dcdlst)); + } + + PdfSimpleObject intrp = (PdfSimpleObject) + xobdict.get ("Interpolate"); + if (intrp != null) { + boolean b = intrp.isTrue (); + imgList.add (new Property ("Interpolate", + PropertyType.BOOLEAN, + new Boolean (b))); + } + + PdfSimpleObject nam = (PdfSimpleObject) + xobdict.get ("Name"); + if (nam != null) { + imgList.add (new Property ("Name", + PropertyType.STRING, + nam.getStringValue ())); + } + + PdfSimpleObject id = (PdfSimpleObject) + resolveIndirectObject + (xobdict.get ("ID")); + if (id != null) { + String idstr = toHex (id.getStringValue ()); + imgList.add (new Property ("ID", + PropertyType.STRING, + idstr)); + } + + _imagesList.add (prop); + } + + } + } + } + } + } + } + catch (PdfException e) { + e.disparage (info); + info.setMessage (new ErrorMessage + (e.getMessage (), _parser.getOffset ())); + } + catch (Exception e) { + info.setWellFormed(false); + info.setMessage (new ErrorMessage ("Unexpected exception " + + e.getClass().getName())); + } + } + + /* Convert a Filter name to a NISO compression scheme value. + * If the name is unknown to NISO, return -1. */ + protected int nameToNiso (String name, + String[] nameArray, + int[] valArray) + { + for (int i = 0; i < nameArray.length; i++) { + if (nameArray[i].equals (name)) { + return valArray[i]; + } + } + return -1; // no match + } + + protected void findFonts (RepInfo info) throws IOException + { + _type0FontsMap = new HashMap (); + _type1FontsMap = new HashMap (); + _trueTypeFontsMap = new HashMap (); + _mmFontsMap = new HashMap (); + _type3FontsMap = new HashMap (); + _cid0FontsMap = new HashMap (); + _cid2FontsMap = new HashMap (); + try { + _docTreeRoot.startWalk (); + for (;;) { + // This time we need all the page objects and page tree + // nodes, because resources can be inherited from + // page tree nodes. + DocNode node = _docTreeRoot.nextDocNode (); + if (node == null) { + break; + } + // Get the fonts for the node + PdfDictionary fonts = null; + fonts = node.getFontResources (); + if (fonts != null) { + // In order to make sure we have a collection of + // unique fonts, we store them in a map keyed by + // object number. + Iterator fontIter = fonts.iterator (); + while (fontIter.hasNext ()) { + PdfObject fontRef = (PdfObject) fontIter.next (); + PdfDictionary font = (PdfDictionary) + resolveIndirectObject (fontRef); + addFontToMap (font); + // If we've been directed appropriately, + // we accumulate the information, but don't + // report it. In that case, we post a message + // just once to that effect. + if (!_skippedFontsReported && + !_showFonts && + _verbosity != Module.MAXIMUM_VERBOSITY) { + info.setMessage (new InfoMessage + (fontsSkippedString)); + _skippedFontsReported = true; + } + } + } + } + } + catch (PdfException e) { + e.disparage (info); + info.setMessage (new ErrorMessage + (e.getMessage (), _parser.getOffset ())); + return; + } + catch (Exception e) { + // Unexpected exception. + _logger.warning( "PdfModule.findFonts: " + e.toString ()); + info.setWellFormed (false); + info.setMessage (new ErrorMessage + ("Unexpected error in findFonts", e.toString (), + _parser.getOffset ())); + return; + } + } + + /** Add the font to the appropriate map, and return its subtype. + * If we've exceeded the maximum number of fonts, then ignore it. */ + protected String addFontToMap (PdfDictionary font) + { + if (++_nFonts > maxFonts) { + return null; + } + String subtypeStr = null; + try { + PdfSimpleObject subtype = + (PdfSimpleObject) font.get ("Subtype"); + subtypeStr = subtype.getStringValue (); + if ("Type0".equals (subtypeStr)) { + _type0FontsMap.put( + new Integer (font.getObjNumber ()), + font); + // If the font is Type 0, we must go + // through its descendant fonts + PdfObject desc0 = font.get ("DescendantFonts"); + PdfArray descendants = + (PdfArray) resolveIndirectObject (desc0); + Vector subfonts = descendants.getContent (); + Iterator subfontIter = subfonts.iterator (); + while (subfontIter.hasNext ()) { + PdfObject subfont = (PdfObject) subfontIter.next (); + subfont = resolveIndirectObject (subfont); + addFontToMap ((PdfDictionary) subfont); + } + } + else if ("Type1".equals (subtypeStr)) { + _type1FontsMap.put( + new Integer (font.getObjNumber ()), + font); + } + else if ("MMType1".equals (subtypeStr)) { + _mmFontsMap.put( + new Integer (font.getObjNumber ()), + font); + } + else if ("Type3".equals (subtypeStr)) { + _type3FontsMap.put( + new Integer (font.getObjNumber ()), + font); + } + else if ("TrueType".equals (subtypeStr)) { + _trueTypeFontsMap.put( + new Integer (font.getObjNumber ()), + font); + } + else if ("CIDFontType0".equals (subtypeStr)) { + _cid0FontsMap.put( + new Integer (font.getObjNumber ()), + font); + } + else if ("CIDFontType2".equals (subtypeStr)) { + _cid2FontsMap.put( + new Integer (font.getObjNumber ()), + font); + } + return subtypeStr; + } + catch (Exception e) { + return null; + } + } + + + /****************************************************************** + * PRIVATE CLASS METHODS. + ******************************************************************/ + + protected static String toHex (String s) + { + StringBuffer buffer = new StringBuffer ("0x"); + + int len = s.length (); + for (int i=0; i v) + { + StringBuffer buffer = new StringBuffer ("0x"); + + int len = v.size (); + for (int i=0; i getFontMap (int selector) + { + switch (selector) { + case F_TYPE0: + return _type0FontsMap; + case F_TYPE1: + return _type1FontsMap; + case F_TT: + return _mmFontsMap; + case F_TYPE3: + return _type3FontsMap; + case F_MM1: + return _mmFontsMap; + case F_CID0: + return _cid0FontsMap; + case F_CID2: + return _cid2FontsMap; + default: + return null; + } + } + + /** + * Return a List of all the font maps. Together, these contain + * all the fonts and subfonts in the document. Some of the maps + * may be null. + */ + public List> getFontMaps () + { + List> lst = new ArrayList> (7); + lst.add (_type0FontsMap); + lst.add (_type1FontsMap); + lst.add (_mmFontsMap); + lst.add (_type3FontsMap); + lst.add (_trueTypeFontsMap); + lst.add (_cid0FontsMap); + lst.add (_cid2FontsMap); + return lst; + } + + /** + * Returns a NameTreeNode for the EmbeddedFiles entry of the + * Names dictionary. Returns null if there isn't one. + */ + public NameTreeNode getEmbeddedFiles () + { + return _embeddedFiles; + } + + /** + * Add the various font lists as a fonts property. Note: only add + * the "Fonts" property if there are, in fact, fonts defined. + */ + protected void addFontsProperty (List metadataList) + { + List fontTypesList = new LinkedList (); + Property fontp = null; + if (_type0FontsMap != null && !_type0FontsMap.isEmpty ()) { + try { + fontp = buildFontProperty ("Type0", _type0FontsMap, F_TYPE0); + fontTypesList.add (fontp); + } + catch (ClassCastException e) { + // Report an error here? + } + } + if (_type1FontsMap != null && !_type1FontsMap.isEmpty ()) { + try { + fontp = buildFontProperty ("Type1", _type1FontsMap, F_TYPE1); + fontTypesList.add (fontp); + } + catch (ClassCastException e) { + // Report an error here? + } + } + if (_trueTypeFontsMap != null && !_trueTypeFontsMap.isEmpty ()) { + try { + fontp = buildFontProperty ("TrueType", _trueTypeFontsMap, + F_TT); + fontTypesList.add (fontp); + } + catch (ClassCastException e) { + // Report an error here? + } + } + if (_type3FontsMap != null && !_type3FontsMap.isEmpty ()) { + try { + fontp = buildFontProperty ("Type3", _type3FontsMap, F_TYPE3); + fontTypesList.add (fontp); + } + catch (ClassCastException e) { + } + } + if (_mmFontsMap != null && !_mmFontsMap.isEmpty ()) { + try { + fontp = buildFontProperty ("MMType1", _mmFontsMap, F_MM1); + fontTypesList.add (fontp); + } + catch (ClassCastException e) { + } + } + if (_cid0FontsMap != null && !_cid0FontsMap.isEmpty ()) { + try { + fontp = buildFontProperty ("CIDFontType0", _cid0FontsMap, + F_CID0); + fontTypesList.add (fontp); + } + catch (ClassCastException e) { + } + } + if (_cid2FontsMap != null && !_cid2FontsMap.isEmpty ()) { + try { + fontp = buildFontProperty ("CIDFontType2", _cid2FontsMap, + F_CID2); + fontTypesList.add (fontp); + } + catch (ClassCastException e) { + } + } + if (fontTypesList.size () > 0) { + metadataList.add (new Property ("Fonts", PropertyType.PROPERTY, + PropertyArity.LIST, + fontTypesList)); + } + } + + /* Build Pages property, with associated subproperties. */ + protected void addPagesProperty (List metadataList, RepInfo info) + { + _pagesList = new LinkedList (); + _pageSeqMap = new HashMap (500); + try { + _docTreeRoot.startWalk (); + int pageIndex = 0; + // Start the pipe with two entries. + // We always need to have the current and the next + // entry from the page label tree in order to determine + // the lower and upper bounds of the applicable range. + // If the first entry has a bound greater than zero, + // that appears to be an undefined situation, so we + // always treat the first entry as starting at zero. + if (_pageLabelRoot != null) { + if (!_pageLabelRoot.findNextKeyValue ()) { + throw new PdfMalformedException ("Bad page labels"); + } + + _pageLabelRoot.findNextKeyValue (); + } + for (;;) { + // Get all the page objects in the document sequentially + // Have to do this in two passes so that link + // destinations can be properly reported. + PageObject page = _docTreeRoot.nextPageObject (); + if (page == null) { + break; + } + _pageSeqMap.put ( + new Integer (page.getDict ().getObjNumber ()), + new Integer (pageIndex + 1)); + } + _docTreeRoot.startWalk (); + for (;;) { + PageObject page = _docTreeRoot.nextPageObject (); + if (page == null) { + break; + } + Property p = buildPageProperty (page, pageIndex++, info); + _pagesList.add (p); + } + if (_showPages || _verbosity == Module.MAXIMUM_VERBOSITY) { + Property prop = new Property ("Pages", + PropertyType.PROPERTY, + PropertyArity.LIST, + _pagesList); + metadataList.add (prop); + } + else { + if (!_skippedPagesReported) { + info.setMessage (new InfoMessage + (pagesSkippedString)); + } + _skippedPagesReported = true; + } + } + catch (PdfException e) { + + e.disparage (info); + info.setMessage (new ErrorMessage + (e.getMessage (), + _parser.getOffset ())); + return ; + } + } + + /* Build a subproperty for one PageObject. */ + protected Property buildPageProperty (PageObject page, + int idx, + RepInfo info) + throws PdfException + { + List pagePropList = new ArrayList (4); + try { + // Foo on Java's inability to return values through + // parameters. Passing an array is a crock to achieve + // that effect. + int nominalNum[] = new int[1]; + Property plProp = buildPageLabelProperty (page, idx, nominalNum); + if (plProp != null) { + pagePropList.add (plProp); + } + if (plProp == null || nominalNum[0] != idx + 1) { + // Page sequence is different from label, or + // there is no label. Make it 1-based. + pagePropList.add (new Property ("Sequence", + PropertyType.INTEGER, + new Integer (idx + 1))); + + } + } + catch (PdfException e) { + throw e; + } + catch (Exception f) { + throw new PdfMalformedException ("Invalid page label info"); + } + + try { + List annotsList = new LinkedList (); + PdfArray annots = page.getAnnotations (); + if (annots != null) { + Vector contents = annots.getContent (); + for (int i = 0; i < contents.size (); i++) { + PdfDictionary annot = + (PdfDictionary) resolveIndirectObject + ((PdfObject) contents.elementAt (i)); + annotsList.add (buildAnnotProperty (annot, info)); + } + if (!annotsList.isEmpty ()) { + if (_showAnnotations || + _verbosity == Module.MAXIMUM_VERBOSITY) { + Property annotProp = new Property ("Annotations", + PropertyType.PROPERTY, + PropertyArity.LIST, + annotsList); + pagePropList.add (annotProp); + } + else { + // We don't report annotations if we got here, + // but we do report that we don't report them. + if (!_skippedAnnotationsReported) { + info.setMessage (new InfoMessage + (annotationsSkippedString)); + _skippedAnnotationsReported = true; + } + } + } + } + } + catch (PdfException e) { + throw e; + } + catch (Exception f) { + throw new PdfMalformedException ("Invalid Annotation list"); + } + + try { + // Rotation property is inheritable + PdfSimpleObject rot = (PdfSimpleObject) page.get ("Rotate", true); + if (rot != null && rot.getIntValue () != 0) { + pagePropList.add (new Property ("Rotate", + PropertyType.INTEGER, + new Integer (rot.getIntValue ()))); + } + + // UserUnit property (1.6), not inheritable + PdfSimpleObject uu = (PdfSimpleObject) page.get ("UserUnit", false); + if (uu != null) { + pagePropList.add (new Property ("UserUnit", + PropertyType.DOUBLE, + new Double (rot.getDoubleValue()))); + } + // Viewport dictionaries (1.6), not inheritable + PdfArray vp = (PdfArray) page.get ("VP", false); + if (vp != null) { + Vector vpv = vp.getContent(); + Iterator iter = vpv.iterator(); + List vplist = new ArrayList (vpv.size()); + while (iter.hasNext ()) { + PdfDictionary vpd = (PdfDictionary) + resolveIndirectObject((PdfObject) iter.next ()); + PdfObject vpdbb = vpd.get ("BBox"); + List vpPropList = new ArrayList (); + vpPropList.add (makeRectProperty + ((PdfArray) resolveIndirectObject (vpdbb), + "BBox")); + PdfObject meas = vpd.get ("Measure"); + if (meas instanceof PdfDictionary) { + vpPropList.add (buildMeasureProperty ((PdfDictionary) meas)); + // No, that's wrong -- the Viewport property itself + // needs to be a list with a bounding box. + } + vplist.add (new Property ("Viewport", + PropertyType.PROPERTY, + PropertyArity.LIST, + vpPropList)); + } + pagePropList.add (new Property ("Viewports", + PropertyType.PROPERTY, + PropertyArity.LIST, + vplist)); + } + // Thumbnail -- we just report if it's there. It's a + // non-inheritable property + PdfObject thumb = page.get ("Thumb", false); + if (thumb != null) { + pagePropList.add (new Property ("Thumb", + PropertyType.BOOLEAN, + Boolean.TRUE)); + } + return new Property ("Page", + PropertyType.PROPERTY, + PropertyArity.LIST, + pagePropList); + } +// catch (PdfException e) { +// throw e; +// } + catch (Exception f) { + throw new PdfMalformedException ("Invalid page dictionary"); + } + } + + /* Build a subproperty of a subproperty for page labels. + * The nomNumRef argument is a crock for returning the + * nominal number; element 0 of the array is replaced + * by the nominal number of the page. */ + protected Property buildPageLabelProperty (PageObject page, + int pageIndex, + int[] nomNumRef) + throws PdfException + { + if (_pageLabelRoot == null) { + return null; // no page label info + } + + // Note that our "current" page is the page label tree's + // "previous" key. Sorry about that... + int curFirstPage = _pageLabelRoot.getPrevKey (); + int nextFirstPage = _pageLabelRoot.getCurrentKey (); + try { + // If we're onto the next page range, advance our pointers. + if (pageIndex >= nextFirstPage) { + _pageLabelRoot.findNextKeyValue (); + curFirstPage = nextFirstPage; + } + PdfDictionary pageLabelDict = + (PdfDictionary) resolveIndirectObject + (_pageLabelRoot.getPrevValue ()); + StringBuffer labelText = new StringBuffer (); + PdfSimpleObject prefixObj = + (PdfSimpleObject) pageLabelDict.get ("P"); + if (prefixObj != null) { + labelText.append (prefixObj.getStringValue ()); + } + PdfSimpleObject firstPageObj = + (PdfSimpleObject) pageLabelDict.get ("St"); + int nominalPage; + if (firstPageObj != null) { + nominalPage = pageIndex - curFirstPage + + firstPageObj.getIntValue (); + } + else { + nominalPage = pageIndex - curFirstPage + 1; + } + if (nominalPage <= 0) { + throw new PdfInvalidException ("Invalid page label sequence"); + } + nomNumRef[0] = nominalPage; + + // Get the numbering style. If there is no numbering + // style entry, the label consists only of the prefix. + PdfSimpleObject numStyleObj = + (PdfSimpleObject) pageLabelDict.get ("S"); + String numStyle; + if (numStyleObj == null) { + numStyle = null; + } + else { + numStyle = numStyleObj.getStringValue (); + } + if ("D".equals (numStyle)) { + // Nice, simple decimal numbers + labelText.append (nominalPage); + } + else if ("R".equals (numStyle)) { + // Upper case roman numerals + labelText.append + (PageLabelNode.intToRoman (nominalPage, true)); + } + else if ("r".equals (numStyle)) { + // Lower case roman numerals + labelText.append + (PageLabelNode.intToRoman (nominalPage, false)); + } + else if ("A".equals (numStyle)) { + // Uppercase letters (A-Z, AA-ZZ, ...) + labelText.append + (PageLabelNode.intToBase26 (nominalPage, true)); + } + else if ("a".equals (numStyle)) { + // Lowercase letters (a-z, aa-zz, ...) + labelText.append + (PageLabelNode.intToBase26 (nominalPage, false)); + } + // It screws up the PDF output if we have a blank Label property. + if (labelText.length() == 0) { + labelText.append("[empty]"); + } + return new Property ("Label", + PropertyType.STRING, + labelText.toString ()); + } + catch (Exception e) { + throw new PdfMalformedException ("Problem with page label structure"); + } + } + + + /* Build a subproperty for a measure dictionary. */ + protected Property buildMeasureProperty (PdfDictionary meas) + { + List plist = new ArrayList (); + PdfObject itemObj = meas.get ("Subtype"); + if (itemObj instanceof PdfSimpleObject) { + plist.add (new Property ("Subtype", + PropertyType.STRING, + ((PdfSimpleObject)itemObj).getStringValue ())); + } + itemObj = meas.get ("R"); + if (itemObj instanceof PdfSimpleObject) { + plist.add (new Property ("Ratio", + PropertyType.STRING, + ((PdfSimpleObject) itemObj).getStringValue ())); + } + // All kinds of stuff I could add -- limit it to the required + // X, Y, D and A arrays. + itemObj = meas.get ("X"); + if (itemObj instanceof PdfArray) { + Vector v = ((PdfArray) itemObj).getContent (); + double[] x = new double[v.size()]; + for (int i = 0; i < v.size (); i++) { + PdfSimpleObject xobj = (PdfSimpleObject) v.elementAt (i); + x[i] = xobj.getDoubleValue(); + } + plist.add (new Property ("X", PropertyType.DOUBLE, + PropertyArity.ARRAY, x)); + } + itemObj = meas.get ("Y"); + if (itemObj instanceof PdfArray) { + Vector v = ((PdfArray) itemObj).getContent (); + double[] x = new double[v.size()]; + for (int i = 0; i < v.size (); i++) { + PdfSimpleObject xobj = (PdfSimpleObject) v.elementAt (i); + x[i] = xobj.getDoubleValue(); + } + plist.add (new Property ("Y", PropertyType.DOUBLE, + PropertyArity.ARRAY, x)); + } + itemObj = meas.get ("D"); + if (itemObj instanceof PdfArray) { + Vector v = ((PdfArray) itemObj).getContent (); + double[] x = new double[v.size()]; + for (int i = 0; i < v.size (); i++) { + PdfSimpleObject xobj = (PdfSimpleObject) v.elementAt (i); + x[i] = xobj.getDoubleValue(); + } + plist.add (new Property ("Distance", PropertyType.DOUBLE, + PropertyArity.ARRAY, x)); + } + itemObj = meas.get ("A"); + if (itemObj instanceof PdfArray) { + Vector v = ((PdfArray) itemObj).getContent (); + double[] x = new double[v.size()]; + for (int i = 0; i < v.size (); i++) { + PdfSimpleObject xobj = (PdfSimpleObject) v.elementAt (i); + x[i] = xobj.getDoubleValue(); + } + plist.add (new Property ("Area", PropertyType.DOUBLE, + PropertyArity.ARRAY, x)); + } + return new Property ("Measure", + PropertyType.PROPERTY, + PropertyArity.LIST, + plist); + } + + /* Build a subproperty of a subproperty for an annotation. */ + protected Property buildAnnotProperty (PdfDictionary annot, RepInfo info) + throws PdfException + { + List propList = new ArrayList (7); + PdfObject itemObj; + try { + // Subtype is required + itemObj = (PdfSimpleObject) annot.get ("Subtype"); + propList.add (new Property ("Subtype", + PropertyType.STRING, + ((PdfSimpleObject)itemObj).getStringValue ())); + + // Contents is optional for some subtypes, required for + // others. We consider it optional here. + itemObj = (PdfSimpleObject) annot.get ("Contents"); + if (itemObj != null) { + propList.add (new Property ("Contents", PropertyType.STRING, + _encrypted ? ENCRYPTED : + ((PdfSimpleObject)itemObj).getStringValue ())); + } + + // Rectangle is required, and must be in the rectangle format + itemObj = annot.get ("Rect"); + propList.add (makeRectProperty + ((PdfArray) resolveIndirectObject (itemObj), + "Rect")); + + // Name comes from the NM entry and is optional + itemObj = annot.get ("NM"); + if (itemObj != null) { + propList.add (new Property ("Name", + PropertyType.STRING, + ((PdfSimpleObject)itemObj).getStringValue ())); + } + + // LastModified is optional. The documentation says that + // a PDF date is preferred but not guaranteed. We just + // put it out as a string. + itemObj = annot.get ("M"); + if (itemObj != null) { + Literal lastModLit = + (Literal) ((PdfSimpleObject) itemObj).getToken (); + Property dateProp; + dateProp = new Property ("LastModified", + PropertyType.STRING, + lastModLit.getValue ()); + + propList.add (dateProp); + } + + // Flags. + itemObj = annot.get ("F"); + if (itemObj != null) { + int flagValue = ((PdfSimpleObject) itemObj).getIntValue (); + Property flagProp = (buildBitmaskProperty (flagValue, + "Flags", + PdfStrings.ANNOTATIONFLAGS, + "No flags set")); + if (flagProp != null) { + propList.add (flagProp); + } + } + + // Appearance dictionary -- just check if it's there. + itemObj = annot.get ("AP"); + if (itemObj != null) { + propList.add (new Property ("AppearanceDictionary", + PropertyType.BOOLEAN, + Boolean.TRUE)); + } + + // Action dictionary -- if it's there, set actionsExist + itemObj = annot.get ("A"); + if (itemObj != null) { + _actionsExist = true; + itemObj = resolveIndirectObject (itemObj); + // Actions are as common as Destinations for + // connecting to destination pages. If the Action + // is of type GoTo, note its destination. + PdfSimpleObject annType = (PdfSimpleObject) + ((PdfDictionary) itemObj).get ("S"); + if (annType == null) { + throw new PdfMalformedException ("Annotation dictionary " + + "missing required type (S) entry"); + } + if ("GoTo".equals (annType.getStringValue ())) { + PdfObject destObj = + ((PdfDictionary) itemObj).get ("D"); + if (destObj != null) { + addDestination (destObj, "ActionDest", propList, + info); + } + } + } + + // Destination object. + itemObj = annot.get ("Dest"); + if (itemObj != null) { + addDestination (itemObj, "Destination", propList, info); + } + + // Reply Type (RT) (1.6) + itemObj = annot.get ("RT"); + if (itemObj instanceof PdfSimpleObject) { + String type = ((PdfSimpleObject) itemObj).getStringValue(); + propList.add (new Property ("ReplyType", + PropertyType.STRING, + type)); + } + + // Intent (IT) (1.6) + itemObj = annot.get ("IT"); + if (itemObj instanceof PdfSimpleObject) { + String type = ((PdfSimpleObject) itemObj).getStringValue(); + propList.add (new Property ("Intent", + PropertyType.STRING, + type)); + } + + // Callout Line (CL) (1.6) + itemObj = annot.get ("CL"); + if (itemObj instanceof PdfArray) { + Vector clData = ((PdfArray) itemObj).getContent(); + // This should be an array of numbers. + Iterator iter = clData.iterator (); + List clList = new ArrayList (6); + while (iter.hasNext ()) { + PdfSimpleObject clItem = (PdfSimpleObject) iter.next (); + clList.add (new Double (clItem.getDoubleValue())); + } + propList.add (new Property ("CalloutLine", + PropertyType.DOUBLE, + PropertyArity.LIST, + clList)); + } + + return new Property ("Annotation", PropertyType.PROPERTY, + PropertyArity.LIST, propList); + } + catch (PdfMalformedException ee) { + // Just rethrow these + throw ee; + } + catch (Exception e) { + throw new PdfMalformedException ("Invalid Annotation property"); + } + } + + /* Given a PdfObject that stands for a Destination, add + * a representative property to the property list. + */ + protected void addDestination (PdfObject itemObj, String propName, + List propList, RepInfo info) + throws PdfException + { + try { + Destination dest = new Destination (itemObj, this, false); + if (dest.isIndirect()) { + // Encryption messes up name trees + if (!_encrypted) { + int pageObjNum = resolveIndirectDest + (dest.getIndirectDest ()); + if (pageObjNum == -1) { + // The scope of the reference is outside this + // file, so we just report it as such. + propList.add (new Property (propName, + PropertyType.STRING, + "External")); + } + else { + propList.add (new Property (propName, + PropertyType.INTEGER, + new Integer (pageObjNum))); + } + } + } + else { + int pageObjNum = dest.getPageDestObjNumber (); + Integer destPg = (Integer) + _pageSeqMap.get (new Integer (pageObjNum)); + if (destPg != null) { + propList.add (new Property (propName, + PropertyType.INTEGER, + destPg)); + } + } + } + catch (Exception e) { + propList.add (new Property (propName, PropertyType.STRING, + "null")); + info.setMessage (new ErrorMessage (e.getMessage (), + _parser.getOffset ())); + info.setValid (false); + } + } + + /* Build up a property for one of the kinds of fonts + * in the file. + */ + protected Property buildFontProperty (String name, Map map, int fontType) + { + List fontList = new LinkedList (); // list of fonts + Iterator fontIter = map.values ().iterator (); + while (fontIter.hasNext ()) { + // For each font in the map, build a property for it, + // which consists of a list of scalar properties. Each kind + // of font is spec'ed to have a slightly different set of + // properties, grumble... + PdfDictionary dict = (PdfDictionary) fontIter.next (); + List fontPropList = oneFontPropList (dict, fontType); + Property fProp = new Property ("Font", + PropertyType.PROPERTY, + PropertyArity.LIST, + fontPropList); + fontList.add (fProp); + } + return new Property (name, + PropertyType.PROPERTY, + PropertyArity.LIST, + fontList); + } + + /* Build the Property list for a given font */ + protected List oneFontPropList (PdfDictionary dict, int fontType) + { + List fontPropList = new LinkedList (); + Property prop; + if (fontType == F_TYPE1 || fontType == F_TYPE3 || fontType == F_MM1 || + fontType == F_TT) { + PdfObject tempObj = dict.get ("Name"); + PdfSimpleObject nameObj = null; + if (tempObj instanceof PdfSimpleObject) { + nameObj = (PdfSimpleObject) tempObj; + } + else if (tempObj instanceof PdfIndirectObj) { + nameObj = (PdfSimpleObject) + ((PdfIndirectObj) tempObj).getObject (); + } + + if (nameObj != null) { + String nameStr = nameObj.getStringValue (); + prop = new Property ("Name", PropertyType.STRING, nameStr); + fontPropList.add (prop); + } + } + + String baseStr = null; + if (fontType != F_TYPE3) { + PdfObject tempObj = dict.get ("BaseFont"); + PdfSimpleObject baseFontObj = null; + if (tempObj instanceof PdfSimpleObject) { + baseFontObj = (PdfSimpleObject) tempObj; + } + else if (tempObj instanceof PdfIndirectObj) { + baseFontObj = (PdfSimpleObject) + ((PdfIndirectObj) tempObj).getObject (); + } + + if (baseFontObj != null) { + baseStr = baseFontObj.getStringValue (); + prop = new Property ("BaseFont", PropertyType.STRING, + baseStr); + fontPropList.add (prop); + } + } + + if (fontType == F_CID0 || fontType == F_CID2) { + PdfObject elCid = dict.get ("CIDSystemInfo"); + try { + elCid = resolveIndirectObject (elCid); + } + catch (Exception e) {} + if (elCid instanceof PdfDictionary) { + prop = buildCIDInfoProperty ((PdfDictionary) elCid); + fontPropList.add (prop); + } + } + + if (fontType == F_TYPE1 || fontType == F_TT || fontType == F_MM1) { + if (isFontSubset (baseStr)) { + prop = new Property ("FontSubset", PropertyType.BOOLEAN, + Boolean.TRUE); + fontPropList.add (prop); + } + } + + if (fontType == F_TYPE1 || fontType == F_TT || fontType == F_MM1 || + fontType == F_TYPE3) { + PdfObject firstCharObj = dict.get("FirstChar"); + if (firstCharObj instanceof PdfIndirectObj) { + firstCharObj = ((PdfIndirectObj) firstCharObj).getObject (); + } + try { + int firstChar = ((PdfSimpleObject) firstCharObj).getIntValue(); + prop = new Property ("FirstChar", PropertyType.INTEGER, + new Integer (firstChar)); + fontPropList.add (prop); + } + catch (Exception e) {} + + PdfObject lastCharObj = dict.get("LastChar"); + if (lastCharObj instanceof PdfIndirectObj) { + lastCharObj = ((PdfIndirectObj) lastCharObj).getObject (); + } + try { + int lastChar = ((PdfSimpleObject) lastCharObj).getIntValue (); + prop = new Property ("LastChar", PropertyType.INTEGER, + new Integer (lastChar)); + fontPropList.add (prop); + } + catch (Exception e) {} + } + + if (fontType == F_TYPE3) { + // Put FontBBox and CharProcs into properties + PdfObject bboxObj = dict.get("FontBBox"); + try { + if (bboxObj instanceof PdfArray) { + fontPropList.add (makeRectProperty ((PdfArray) bboxObj, + "FontBBox")); + } + } + catch (Exception e) {} + + // For CharProcs, we're just checking if it's there. + // (It's required for a Type 3 font.) +// PdfObject charProcs = dict.get ("CharProcs"); +// prop = new Property ("CharProcs", +// PropertyType.BOOLEAN, +// new Boolean (charProcs != null)); +// fontPropList.add (prop); + } + + if (fontType == F_TYPE1 || fontType == F_TT || fontType == F_MM1 || + fontType == F_CID0 || fontType == F_CID2) { + PdfObject descriptorObj = dict.get ("FontDescriptor"); + try { + descriptorObj = resolveIndirectObject (descriptorObj); + } + catch (Exception e) {} + if (descriptorObj instanceof PdfDictionary) { + prop = buildFontDescriptorProperty ((PdfDictionary) + descriptorObj); + fontPropList.add (prop); + } + } + + PdfObject encodingObj = dict.get ("Encoding"); + try { + encodingObj = resolveIndirectObject (encodingObj); + } + catch (Exception e) {} + + if (fontType == F_TYPE0 || fontType == F_TYPE1 || fontType == F_TT || + fontType == F_MM1 || fontType == F_TYPE3) { + // Encoding property -- but only if Encoding is a name + if (encodingObj instanceof PdfSimpleObject) { + prop = new Property ("Encoding", PropertyType.STRING, + ((PdfSimpleObject) encodingObj).getStringValue ()); + fontPropList.add (prop); + } + } + + if (fontType == F_TYPE1 || fontType == F_TT || fontType == F_MM1 || + fontType == F_TYPE3) { + if (encodingObj != null && encodingObj instanceof PdfDictionary) { + prop = buildEncodingDictProperty ((PdfDictionary) encodingObj); + fontPropList.add (prop); + } + } + + if (fontType == F_TYPE0) { + // Encoding is reported as a CMapDictionary property for type 0 + if (encodingObj != null && encodingObj instanceof PdfStream) { + prop = buildCMapDictProperty ((PdfStream) encodingObj); + fontPropList.add (prop); + } + } + + if (fontType == F_TYPE3) { + // All we're interested in for Resources is whether + // the dictionary exists + PdfObject rsrc = dict.get ("Resources"); + if (rsrc != null) { + prop = new Property ("Resources", PropertyType.BOOLEAN, + Boolean.TRUE); + fontPropList.add (prop); + } + } + + if (fontType == F_TYPE0 || fontType == F_TYPE1 || fontType == F_TT || + fontType == F_MM1 || fontType == F_TYPE3) { + PdfObject toUniObj = dict.get ("ToUnicode"); + if (toUniObj != null) { + prop = new Property ("ToUnicode", PropertyType.BOOLEAN, + Boolean.TRUE); + fontPropList.add (prop); + } + } + + return fontPropList; + } + + /* Code for CMapProperty for Type 0 fonts, based on the Encoding + * entry, broken out of buildFontProperty. + */ + protected Property buildCMapDictProperty (PdfStream encoding) + { + PdfDictionary dict = encoding.getDict (); + List propList = new ArrayList (4); + Property prop = new Property ("CMapDictionary", + PropertyType.PROPERTY, + PropertyArity.LIST, + propList); + Property subprop; + + //PdfObject mapName = dict.get ("CMapName"); + + PdfObject cidSysInfo = dict.get ("CIDSystemInfo"); + // We can use buildCIDInfoProperty here to build the subproperty + PdfDictionary cidDict; + List cidList = new LinkedList (); + try { + if (cidSysInfo instanceof PdfDictionary) { + // One CIDInfo dictionary + cidDict = (PdfDictionary) cidSysInfo; + subprop = buildCIDInfoProperty (cidDict); + cidList.add (subprop); + } + else if (cidSysInfo instanceof PdfArray) { + // Many CIDInfo dictionaries + Vector v = ((PdfArray) cidSysInfo).getContent (); + for (int i = 0; i < v.size (); i++) { + cidDict = (PdfDictionary) v.elementAt (i); + Property subsubprop = buildCIDInfoProperty (cidDict); + cidList.add (subsubprop); + } + } + } + catch (Exception e) {} + + if (!cidList.isEmpty ()) { + subprop = new Property ("CIDSystemInfos", + PropertyType.PROPERTY, + PropertyArity.LIST, + cidList); + propList.add (subprop); + } + + //PdfObject wMod = dict.get ("WMode"); + //PdfObject useCMap = dict.get ("UseCMap"); + + return prop; + } + + /* Code for CIDInfoProperty for CIDFontType0 and CIDFontType2 + * conts. + */ + protected Property buildCIDInfoProperty (PdfDictionary dict) + { + List propList = new ArrayList (3); + Property prop = new Property ("CIDSystemInfo", + PropertyType.PROPERTY, + PropertyArity.LIST, + propList); + Property subprop; + + // Add the registry identifier + PdfObject reg = dict.get ("Registry"); + if (reg instanceof PdfSimpleObject) { + try { + String regText = ((PdfSimpleObject) reg).getStringValue (); + subprop = new Property ("Registry", PropertyType.STRING, + _encrypted ? ENCRYPTED : regText); + propList.add (subprop); + } + catch (Exception e) {} + } + + // Add the name of the char collection within the registry + PdfObject order = dict.get ("Ordering"); + if (reg instanceof PdfSimpleObject) { + try { + String ordText = + ((PdfSimpleObject) order).getStringValue (); + subprop = new Property ("Registry", + PropertyType.STRING, + ordText); + propList.add (subprop); + } + catch (Exception e) {} + } + + PdfObject supp = dict.get ("Supplement"); + if (supp instanceof PdfSimpleObject) { + try { + int suppvalue = ((PdfSimpleObject) supp).getIntValue (); + subprop = new Property ("Supplement", + PropertyType.INTEGER, + new Integer (suppvalue)); + propList.add (subprop); + } + catch (Exception e) {} + } + return prop; + } + + /* Code for EncodingDictionary Property for type 1, 3, TrueType, and + * MM fonts. This is based on a dictionary entry with the same name + * as the one for buildCMapDictProperty, but different information. + * Included properties are BaseEncoding and Differences. + */ + protected Property buildEncodingDictProperty (PdfDictionary encodingDict) + { + List propList = new ArrayList (2); + Property prop = new Property ("EncodingDictionary", + PropertyType.PROPERTY, + PropertyArity.LIST, + propList); + PdfObject baseEnc = encodingDict.get ("BaseEncoding"); + if (baseEnc instanceof PdfSimpleObject) { + String baseEncString = ((PdfSimpleObject) baseEnc).getStringValue (); + if (baseEncString != null) { + Property baseEncProp = new Property ("BaseEncoding", + PropertyType.STRING, + baseEncString); + propList.add (baseEncProp); + } + } + + PdfObject diffs = encodingDict.get ("Differences"); + Property diffsProp = new Property ("Differences", + PropertyType.BOOLEAN, + new Boolean (diffs != null)); + propList.add (diffsProp); + + return prop; + } + + /* Separated-out code for FontDescriptor property. This + * is a list of six Properies: FontName, Flags, + * FontBBox, FontFile, FontFile2, and FontFile3. + */ + protected Property buildFontDescriptorProperty + (PdfDictionary encodingDict) + { + List propList = new ArrayList (6); + Property prop = new Property ("FontDescriptor", + PropertyType.PROPERTY, + PropertyArity.LIST, + propList); + Property subprop; + try { + PdfSimpleObject fName = + (PdfSimpleObject) encodingDict.get ("FontName"); + String fNameStr = fName.getStringValue (); + subprop = new Property ("FontName", + PropertyType.STRING, + fNameStr); + propList.add (subprop); + } + catch (Exception e) {} + + try { + PdfSimpleObject flags = + (PdfSimpleObject) encodingDict.get ("Flags"); + int flagValue = flags.getIntValue (); + subprop = buildBitmaskProperty (flagValue, + "Flags", + PdfStrings.FONTDESCFLAGS, + "No flags set"); + if (subprop != null) { + propList.add (subprop); + } + } + catch (Exception e) {} + + try { + PdfArray bboxObj = + (PdfArray) encodingDict.get ("FontBBox"); + double[] bbox = bboxObj.toRectangle (); + // toRectangle is written to return an array of double, + // which is what the bounding box is in the most general + // case; but the spec requires an array of integer, so + // we convert is. This may seem like an excess of work, + // but I'd rather have toRectangle do the right thing + // rather than losing generality. + if (bbox != null) { + int [] ibbox = new int[4]; + for (int i = 0; i < 4; i++) { + ibbox[i] = (int) bbox[i]; + } + subprop = new Property ("FontBBox", + PropertyType.INTEGER, + PropertyArity.ARRAY, + ibbox); + propList.add (subprop); + } + } + catch (Exception e) {} + + PdfObject fontFile = encodingDict.get ("FontFile"); + if (fontFile != null) { + // All we care about is whether it exists or not + subprop = new Property ("FontFile", PropertyType.BOOLEAN, + Boolean.TRUE); + propList.add (subprop); + } + fontFile = encodingDict.get ("FontFile2"); + if (fontFile != null) { + subprop = new Property ("FontFile2", PropertyType.BOOLEAN, + Boolean.TRUE); + propList.add (subprop); + } + fontFile = encodingDict.get ("FontFile3"); + if (fontFile != null) { + subprop = new Property ("FontFile3", PropertyType.BOOLEAN, + Boolean.TRUE); + propList.add (subprop); + } + return prop; + } + + protected Property buildViewPrefProperty (PdfDictionary prefDict) + { + Property p; + PdfObject ob; + boolean b; + String s; + List propList = new ArrayList (12); + Property prop = new Property ("ViewerPreferences", + PropertyType.PROPERTY, + PropertyArity.LIST, + propList); + + ob = prefDict.get("HideToolbar"); + if (ob instanceof PdfSimpleObject) { + b = ((PdfSimpleObject) ob).isTrue (); + } + else { + b = false; + } + p = new Property ("HideToolbar", PropertyType.BOOLEAN, new Boolean (b)); + propList.add (p); + + ob = prefDict.get("HideMenubar"); + if (ob instanceof PdfSimpleObject) { + b = ((PdfSimpleObject) ob).isTrue (); + } + else { + b = false; + } + p = new Property ("HideMenubar", PropertyType.BOOLEAN, new Boolean (b)); + propList.add (p); + + ob = prefDict.get("HideWindowUI"); + if (ob instanceof PdfSimpleObject) { + b = ((PdfSimpleObject) ob).isTrue (); + } + else { + b = false; + } + p = new Property ("HideWindowUI", PropertyType.BOOLEAN, new Boolean (b)); + propList.add (p); + + ob = prefDict.get("FitWindow"); + if (ob instanceof PdfSimpleObject) { + b = ((PdfSimpleObject) ob).isTrue (); + } + else { + b = false; + } + p = new Property ("FitWindow", PropertyType.BOOLEAN, new Boolean (b)); + propList.add (p); + + ob = prefDict.get("CenterWindow"); + if (ob instanceof PdfSimpleObject) { + b = ((PdfSimpleObject) ob).isTrue (); + } + else { + b = false; + } + p = new Property ("CenterWindow", PropertyType.BOOLEAN, new Boolean (b)); + propList.add (p); + + ob = prefDict.get("DisplayDocTitle"); + if (ob instanceof PdfSimpleObject) { + b = ((PdfSimpleObject) ob).isTrue (); + } + else { + b = false; + } + p = new Property ("DisplayDocTitle", PropertyType.BOOLEAN, new Boolean (b)); + propList.add (p); + + ob = prefDict.get("NonFullScreenPageMode"); + if (ob instanceof PdfSimpleObject) { + s = ((PdfSimpleObject) ob).getStringValue (); + } + else s = "UseNone"; + p = new Property ("NonFullScreenPageMode", PropertyType.STRING, s); + propList.add (p); + + ob = prefDict.get("Direction"); + if (ob instanceof PdfSimpleObject) { + s = ((PdfSimpleObject) ob).getStringValue (); + } + else s = "L2R"; + p = new Property ("Direction", PropertyType.STRING, s); + propList.add (p); + + ob = prefDict.get("ViewArea"); + if (ob instanceof PdfSimpleObject) { + s = ((PdfSimpleObject) ob).getStringValue (); + } + else s = "CropBox"; + p = new Property ("ViewArea", PropertyType.STRING, s); + propList.add (p); + + ob = prefDict.get("ViewClip"); + if (ob instanceof PdfSimpleObject) { + s = ((PdfSimpleObject) ob).getStringValue (); + } + else s = "CropBox"; + p = new Property ("ViewClip", PropertyType.STRING, s); + propList.add (p); + + ob = prefDict.get("PrintArea"); + if (ob instanceof PdfSimpleObject) { + s = ((PdfSimpleObject) ob).getStringValue (); + } + else s = "CropBox"; + p = new Property ("PrintArea", PropertyType.STRING, s); + propList.add (p); + + ob = prefDict.get("PageClip"); + if (ob instanceof PdfSimpleObject) { + s = ((PdfSimpleObject) ob).getStringValue (); + } + else s = "CropBox"; + p = new Property ("PageClip", PropertyType.STRING, s); + propList.add (p); + return prop; + } + + /* Return TRUE if the string is a font subset string, which begins + with six uppercase letters and then a plus sign */ + protected boolean isFontSubset (String baseStr) + { + if (baseStr == null || baseStr.length () < 7) { + return false; + } + for (int i = 0; i < 6; i++) { + char ch = baseStr.charAt (i); + if (!Character.isUpperCase (ch)) { + return false; + } + } + return (baseStr.charAt (6) == '+'); + } + + + /* Create the "Outlines" property from the Outlines item in the + catalog dictionary. As a side effect, we set the actionsExist + flag if any Actions are found. Because we check destinations, + this can't be called till the page tree is built. + + Outlines can be recursive, according to Adobe people, so we have + to track visited nodes. + */ + protected Property buildOutlinesProperty (PdfDictionary dict, RepInfo info) + throws PdfException + { + _recursionWarned = false; + _visitedOutlineNodes = new HashSet (); + String malformed = "Malformed outline dictionary"; + List itemList = new LinkedList (); + Property prop = new Property ("Outlines", + PropertyType.PROPERTY, + PropertyArity.LIST, + itemList); + try { + PdfObject item = resolveIndirectObject (dict.get ("First")); + // In PDF 1.4, "First" and "Last" are unconditionally required. However, + // in 1.6, they can be omitted if there are no open or closed outline items. + // Strictly speaking, we should do several additional checks, but letting the + // outline go as empty seems sufficient. +// if (item == null || !(item instanceof PdfDictionary)) { +// throw new PdfInvalidException ("Outline dictionary missing required entry"); +// } + int listCount = 0; // Guard against looping + while (item != null) { + Integer onum = new Integer (item.getObjNumber ()); + Property p = buildOutlineItemProperty ((PdfDictionary) item, info); + itemList.add (p); + item = resolveIndirectObject (((PdfDictionary) item).get ("Next")); + if (item == null) { + break; + } + // Check if this object is its own sibling. (It really does happen!) + if (item.getObjNumber() == onum.intValue ()) { + if (!_recursionWarned) { + info.setMessage (new InfoMessage + (outlinesRecursiveString)); + _recursionWarned = true; + } + break; + } + if (++listCount > 2000) { + break; + } + } + } + catch (PdfException e1) { + throw e1; + } + catch (Exception e) { + throw new PdfMalformedException (malformed); + } + if (itemList.isEmpty ()) { + return null; + } + return prop; + } + + + + + /* Create an item property within the outlines hierarchy. If an + Outline item property has children, then there is a list + property called "Children" with elements called "Item". + It calls itself recursively to walk down the outline. */ + protected Property buildOutlineItemProperty (PdfDictionary dict, RepInfo info) + throws PdfException + { + String invalid = "Invalid outline dictionary item"; + List itemList = new ArrayList (3); + try { + Property prop = new Property ("Item", + PropertyType.PROPERTY, + PropertyArity.LIST, + itemList); + PdfSimpleObject title = (PdfSimpleObject) + resolveIndirectObject (dict.get ("Title")); + if (title == null) { + throw new PdfInvalidException (invalid); + } + itemList.add (new Property ("Title", PropertyType.STRING, + _encrypted ? ENCRYPTED : + title.getStringValue ())); + + // Check other required stuff + if (dict.get ("Parent") == null) { + throw new PdfInvalidException (invalid); + } + PdfObject cnt = dict.get ("Count"); + if (cnt != null && + (!(cnt instanceof PdfSimpleObject) || + !(((PdfSimpleObject) cnt).getToken () instanceof Numeric))) { + throw new PdfInvalidException (invalid); + } + // The entries for Prev, Next, First, and Last must + // all be indirect references or absent. Just cast them to + // throw an exception if they're something else + PdfIndirectObj ob = (PdfIndirectObj) dict.get ("Prev"); + ob = (PdfIndirectObj) dict.get ("Next"); + ob = (PdfIndirectObj) dict.get ("First"); + ob = (PdfIndirectObj) dict.get ("First"); + + // Check if there are Actions in the outline. This saves going + // through the outlines all over again if a Profile checker + // needs to know this. We flag only the existence of one or more Actions + // in the document. + if (dict.get ("A") != null) { + _actionsExist = true; + } + + PdfObject destObj = dict.get ("Dest"); + if (destObj != null) { + destObj = resolveIndirectObject(destObj); + Destination dest = new Destination (destObj, this, false); + if (dest.isIndirect()) { + itemList.add (new Property ("Destination", + PropertyType.STRING, + dest.getIndirectDest ())); + } + else { + int pageObjNum = dest.getPageDestObjNumber (); + Integer destPg = (Integer) + _pageSeqMap.get (new Integer (pageObjNum)); + if (destPg != null) { + itemList.add (new Property ("Destination", + PropertyType.INTEGER, + destPg)); + } + } + } + + PdfDictionary child = + (PdfDictionary) resolveIndirectObject (dict.get ("First")); + if (child != null) { + List childList = new LinkedList (); + Property childProp = new Property ("Children", + PropertyType.PROPERTY, + PropertyArity.LIST, + childList); + // We aren't catching all possible combinations of looping. Put a maximum + // on the list just to be safe. + int listCount = 0; + while (child != null) { + Integer onum = new Integer (child.getObjNumber ()); + if (_visitedOutlineNodes.contains (onum)) { + /* We have recursion! */ + if (!_recursionWarned) { + // Warn of recursion + info.setMessage (new InfoMessage + (outlinesRecursiveString)); + _recursionWarned = true; + } + } + else { + _visitedOutlineNodes.add (onum); + Property p = buildOutlineItemProperty ((PdfDictionary) child, info); + childList.add (p); + } + child = (PdfDictionary) + resolveIndirectObject (child.get ("Next")); + if (child == null) { + break; + } + // Check if this object is its own sibling. (It really does happen!) + if (child.getObjNumber() == onum.intValue ()) { + if (!_recursionWarned) { + info.setMessage (new InfoMessage + (outlinesRecursiveString)); + _recursionWarned = true; + } + break; + } + if (++listCount > 2000) + break; // safety check + } + itemList.add (childProp); + } + return prop; + } + catch (PdfException pe) { + throw pe; + } + catch (ClassCastException ce) { + throw new PdfInvalidException (invalid); + } + catch (Exception e) { + throw new PdfInvalidException (invalid); + } + } + + /* This is separated out from readDocCatalogDict, where it + would otherwise make sense, because we can't build + the outlines property till we have a page tree to + locate destinations. */ + protected boolean doOutlineStuff (RepInfo info) + { + if (_outlineDict != null) { + try { + Property oprop = buildOutlinesProperty + ((PdfDictionary) _outlineDict, info); + if (_showOutlines || _verbosity == Module.MAXIMUM_VERBOSITY) { + if (oprop != null){ + _docCatalogList.add (oprop); + } + } + else if (!_skippedOutlinesReported) { + // We report that we aren't reporting skipped outlines + info.setMessage (new InfoMessage + (outlinesSkippedString)); + _skippedOutlinesReported = true; + } + } + catch (PdfException e) { + info.setMessage (new ErrorMessage (e.getMessage(), + _parser.getOffset ())); + e.disparage (info); + // If it's just invalid, we can keep going + return (e instanceof PdfInvalidException); + } + } + return true; + } + + /* Given a PdfSimpleObject representing a key, + look up the Destination which it references. + There are two completely different ways this can be done, + though any given PDF file is supposed to implement only one. + If _destsDict is non-null, we look the string up there, and + may find either a dictionary or an array. Otherwise + if _destNames is non-null, it's a NameTreeNode which contains + the mapping. In either case, the destination could be + external, in which case we just return a string saying so. + (The implementation of Destinations in PDF is a prime example + of design by stone soup.) + We return the page sequence number for the referenced page. + If we can't find a match for the reference, we return -1. + */ + protected int resolveIndirectDest (PdfSimpleObject key) + throws PdfException + { + if (_destNames != null) { + Destination dest = new Destination (_destNames.get (key.getRawBytes ()), + this, true); +// if (dest == null) { +// return -1; +// } + return dest.getPageDestObjNumber (); + } + else { + return -1; // This is probably an error, actually + } + } + + + /* Build the user permission property., */ + protected Property buildUserPermProperty (int flags, String[] flagStrs) + { + return buildBitmaskProperty (flags, "UserAccess", flagStrs, + "No permissions"); + } + + /** Add a string proprerty, based on a dictionary entry + with a string value, to a specified List. */ + protected void addStringProperty(PdfDictionary dict, + List propList, + String key, + String propName) + { + String propText = null; + PdfObject propObject = dict.get (key); + if (propObject instanceof PdfSimpleObject) { + Token tok = ((PdfSimpleObject)propObject).getToken (); + if (tok instanceof Literal) { + if (_encrypted) { + propText = ENCRYPTED; + } + else { + propText = ((Literal) tok).getValue (); + } + propList.add (new Property (propName, + PropertyType.STRING, + propText)); + } + } + } + + /** Add a date proprerty, based on a dictionary entry + with a string value, to a specified List. */ + protected void addDateProperty(PdfDictionary dict, + List propList, + String key, + String propName) + throws PdfException + { + if (_encrypted) { + return; // can't decipher an encrypted date + } + PdfObject propObject = dict.get (key); + if (propObject instanceof PdfSimpleObject) { + Token tok = ((PdfSimpleObject)propObject).getToken (); + if (tok instanceof Literal) { + Date propDate = ((Literal) tok).parseDate (); + if (propDate != null) { + propList.add (new Property (propName, + PropertyType.DATE, + propDate)); + } + else { + throw new PdfInvalidException ("Improperly formed date", + 0); + } + } + } + } + + /* General function for adding a property with a 32-bit + value, with an array of Strings to interpret + the value as a bitmask. */ + protected Property buildBitmaskProperty (int val, String name, + String [] valueNames, + String defaultStr) + { + if (_je != null && _je.getShowRawFlag ()) { + return new Property (name, + PropertyType.INTEGER, + new Integer (val)); + } + else { + List slist = new LinkedList (); + try { + for (int i = 0; i < valueNames.length; i++) { + if ((val & (1 << i)) != 0 && + valueNames[i].length () > 0) { + slist.add (valueNames[i]); + } + } + // Provision for a default string if the property + // would otherwise have an empty list + if (slist.isEmpty() && defaultStr != null) { + slist.add (defaultStr); + } + } + catch (Exception e) { + return null; + } + return new Property (name, PropertyType.STRING, + PropertyArity.LIST, slist); + } + } + + /* Take a PdfArray which is supposed to conform to the rectangle + description (i.e., it's an array of 4 numbers) and create + a Property which is an array of 4 integers. */ + protected Property makeRectProperty (PdfArray arrObj, String name) + throws PdfException + { + int [] iarr = new int[4]; + double[] arr = ((PdfArray) arrObj).toRectangle (); + // toRectangle is written to return an array of double, + // which is what the bounding box is in the most general + // case; but the spec requires an array of integer, so + // we convert is. This may seem like an excess of work, + // but I'd rather have toRectangle do the right thing + // rather than losing generality. + for (int i = 0; i < 4; i++) { + iarr[i] = (int) arr[i]; + } + return new Property (name, + PropertyType.INTEGER, + PropertyArity.ARRAY, + iarr); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/TiffModule.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/TiffModule.java new file mode 100644 index 00000000..9beeede7 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/TiffModule.java @@ -0,0 +1,1282 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003-2012 by JSTOR and the President and Fellows of Harvard College + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module; + +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.tiff.*; + +import java.io.*; +import java.util.*; + +/** + * Module for identification and validation of TIFF files. + */ +public class TiffModule + extends ModuleBase +{ + + /** + * Value to write as module params to the default config file. + */ + public final static String[] defaultConfigParams = + { "byteoffset=true" }; + + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + + private static final String NAME = "TIFF-hul"; + private static final String RELEASE = "1.7"; + private static final int [] DATE = {2012, 8, 12}; + private static final String [] FORMAT = { + "TIFF", "Tagged Image File Format" + }; + private static final String COVERAGE = "TIFF 4.0, 5.0, and 6.0; " + + "TIFF/IT (ISO/DIS 12639:2003), including file types CT, LW, HC, MP, " + + "BP, BL, and FP, and conformance levels P1 and P2; TIFF/EP " + + "(ISO 12234-2:2001); " + + "Exif 2.0, 2.1 (JEIDA-49-1998), and 2.2 (JEITA CP-3451); " + + "Baseline GeoTIFF " + + "1.0; Baseline 6.0 bilevel (known in TIFF 5.0 as Class B), " + + "grayscale (Class G), palette-color (Class P), and RGB (Class R); " + + "6.0 extension YCbCr (Class Y); DLF Benchmark for Faithful Digital " + + "Reproductions of Monographs and Serials; TIFF-FX (RFC 2301), " + + "Class F (RFC 2306); RFC 1314; " + + "and DNG (Digital Negative)"; + + /*** These profiles are not reported anymore (SLA, 2004-01-06) + "Adobe PageMaker 6.0; Adobe Photoshop 'Advanced TIFF'; " + + ***/ + + private static final String [] MIMETYPE = { + "image/tiff", "image/tiff-fx", "image/ief" + }; + private static final String WELLFORMED = "A TIFF file is well-formed if " + + "it has a big-endian or little-endian header; at least one IFD; all " + + "IFDs are 16-bit word aligned; all IFDs have at least one entry; " + + "all IFD entries are sorted in ascending order by tag number; all " + + "IFD entries specify the correct type and count; all value offsets " + + "are 16-bit word aligned; all value offsets reference locations " + + "within the file; and the final IFD is followed by an offset of 0"; + private static final String VALIDITY = "A TIFF file is valid if " + + "well-formed; ImageLength, ImageWidth, and " + + "PhotometricInterpretation tags are defined; strip or tile tags " + + "are defined; tag values are self-consistent (see JHOVE " + + "documentation); TileWidth and TileLength values are integral " + + "multiples of 16; and DateTime tag is properly formatted"; + private static final String REPINFO = "Additional representation " + + "information includes: NISO Z39.87 Digital Still Image Technical " + + "Metadata and all other tag values"; + private static final String NOTE = null; + private static final String RIGHTS = "Copyright 2003-2007 by JSTOR and " + + "the President and Fellows of Harvard College. " + + "Released under the GNU Lesser General Public License."; + + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + /** List of profile checkers. */ + protected List _profile; + + /* Exif profile checker for the main IFD. */ + TiffProfileExif _exifMainProfile; + + /* Exif profile checker for the thumbnail IFD. */ + TiffProfileExifThumb _exifThumbnailProfile; + + /* DNG profile checker for Raw IFD. */ + TiffProfileDNG _dngMainProfile; + + /* DNG profile checker for IFD 0. */ + TiffProfileDNGThumb _dngThumbnailProfile; + + /** Special flag for Exif profiles: Is main IFD profile satisfied */ + protected boolean _exifFirstFlag; + /** Special flag for Exif profiles: Is thumbnail IFD profile satisfied */ + protected boolean _exifThumbnailFlag; + + /** Special flag for DNG profiles; is "thumbnail" (IFD 0) profile satisfied */ + protected boolean _dngThumbnailFlag; + /** Special flag for DNG profiles; is raw IFD profile satisfied */ + protected boolean _dngRawFlag; + + /** Open TIFF file. */ + protected RandomAccessFile _raf; + /** TIFF version. */ + protected int _version; + + protected boolean _byteOffsetIsValid; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * Instantiate a object. + */ + public TiffModule () + { + super (NAME, RELEASE, DATE, FORMAT, COVERAGE, MIMETYPE, WELLFORMED, + VALIDITY, REPINFO, NOTE, RIGHTS, true); + + // Define vendor agent (HUL) + Agent agent = new Agent ("Harvard University Library", + AgentType.EDUCATIONAL); + agent.setAddress ("Office for Information Systems, " + + "90 Mt. Auburn St., " + + "Cambridge, MA 02138"); + agent.setTelephone ("+1 (617) 495-3724"); + agent.setEmail("jhove-support@hulmail.harvard.edu"); + _vendor = agent; + + // Define TIFF 6.0 document with Adobe agent + Document doc = new Document ("TIFF, Revision 6.0", + DocumentType.REPORT); + Agent adobeAgent = new Agent ("Adobe Systems, Inc.", + AgentType.COMMERCIAL); + agent = adobeAgent; // save making multiple copies of Adobe + agent.setAddress ("345 Park Avenue, San Jose, California 95110-2704"); + agent.setTelephone ("+1 (408) 536-6000"); + agent.setFax ("+1 (408) 537-6000"); + agent.setWeb ("http://www.adobe.com/"); + doc.setPublisher (agent); + doc.setDate ("1992-06-03"); + doc.setEdition("Final"); + doc.setIdentifier (new Identifier ("http://partners.adobe.com/asn/" + + "tech/tiff/specification.jsp", + IdentifierType.URL)); + _specification.add (doc); + + // Define TIFF 5.0 document reusing Adobe agent + doc = new Document ("TIFF, Revision 5.0", DocumentType.REPORT); + agent = new Agent ("Aldus Corporation", AgentType.COMMERCIAL); + doc.setPublisher (agent); + doc.setDate ("1988-08-08"); + doc.setNote ("Aldus was acquired by Adobe Systems, Inc., in 1993"); + _specification.add (doc); + + doc = new Document ("Tagged Image File Format, Rev. 4.0", + DocumentType.REPORT); + agent = new Agent ("Aldus Corporation", AgentType.COMMERCIAL); + doc.setPublisher (agent); + doc.setDate ("1987-04-30"); + doc.setNote ("Aldus was acquired by Adobe Systems, Inc., in 1993"); + _specification.add (doc); + + // Define TIFF/EP document with ISO agent + doc = new Document ("ISO 12234-2:2001, Electronic still-picture " + + "imaging -- Removable memory -- " + + "Part 2: TIFF/EP image data format", + DocumentType.STANDARD); + Agent isoAgent = new Agent ("ISO", AgentType.STANDARD); + isoAgent.setAddress ("1, rue de Varembe, Casa postale 56, " + + "CH-1211, Geneva 20, Switzerland"); + isoAgent.setTelephone ("+41 22 749 01 11"); + isoAgent.setFax ("+41 22 733 34 30"); + isoAgent.setEmail ("iso@iso.ch"); + isoAgent.setWeb ("http://www.iso.org"); + doc.setPublisher (isoAgent); + doc.setDate ("2001-10-15"); + Identifier ident = new Identifier ("ISO 12234-2:2001(E)", + IdentifierType.ISO); + doc.setIdentifier (ident); + _specification.add (doc); + + // Define TIFF/IT document, reusing ISO agent + doc = new Document ("ISO/DIS 12639:2003, Graphic technology -- " + + "Prepress digital data exchange -- " + + "Tag image file format for image technology " + + "(TIFF/IT)", DocumentType.STANDARD); + /* This uses the same agent (ISO) as the prior doc */ + doc.setPublisher (isoAgent); + doc.setDate ("2003-09-04"); + ident = new Identifier ("ISO/DIS 12639:2003(E)", IdentifierType.ISO); + doc.setIdentifier (ident); + _specification.add (doc); + + // Define Digital Library Federation doc + doc = new Document ("Benchmark for Faithful Digital Reproductions " + + "of Monographs and Serials", + DocumentType.REPORT); + agent = new Agent ("Digital Library Federation", AgentType.NONPROFIT); + agent.setAddress ("1755 Massachusetts Ave., NW, Suite 500, " + + "Washington, DC 20036"); + agent.setTelephone ("+1 (202) 939-4761"); + agent.setFax ("+1 (202) 939-4765"); + agent.setEmail ("dlf@clir.org"); + agent.setWeb ("http://www.diglib.org/"); + doc.setPublisher (agent); + doc.setEdition ("Version 1"); + doc.setDate ("2002-12"); + ident = new Identifier ("http://www.diglib.org/standards/bmarkfin.htm", + IdentifierType.URL); + doc.setIdentifier (ident); + _specification.add (doc); + + // Define PageMaker TIFF doc, reusing Adobe agent + doc = new Document ("Adobe PageMaker TIFF 6.0 Technical Notes", + DocumentType.REPORT); + doc.setPublisher (adobeAgent); + doc.setDate ("1995-09-14"); + ident = new Identifier + ("http://partners.adobe.com/asn/developer/pdfs/tn/TIFFPM6.pdf", + IdentifierType.URL); + doc.setIdentifier (ident); + _specification.add (doc); + + // Define Photoshop TIFF doc, reusing Adobe agent + doc = new Document ("Adobe Photoshop TIFF Technical Notes", + DocumentType.REPORT); + doc.setPublisher (adobeAgent); + doc.setDate ("2002-03-22"); + ident = new Identifier ("http://partners.adobe.com/asn/developer/" + + "pdfs/tn/TIFFphotoshop.pdf", + IdentifierType.URL); + doc.setIdentifier (ident); + _specification.add (doc); + + // Define Photoshop file formats doc, reusing Adobe agent + doc = new Document ("Adobe Photoshop 6.0 File Formats Specification", + DocumentType.REPORT); + doc.setPublisher (adobeAgent); + doc.setDate ("2000-11"); + doc.setEdition ("Version 6.0, Release 2"); + _specification.add (doc); + + + // Define TIFF Class F doc + doc = new Document ("TIFF-F Revised Specification: The " + + "Spirit of TIFF Class F", + DocumentType.REPORT); + agent = new Agent ("Cygnet Technologies", AgentType.COMMERCIAL); + doc.setPublisher (agent); + doc.setDate ("1990-04-28"); + doc.setNote ("Cygnet is no longer in business"); + ident = new Identifier ("http://hul.harvard.edu/jhove/" + + "references.html#classf", + IdentifierType.URL); + doc.setIdentifier (ident); + _specification.add (doc); + + // Define IETF Class F doc, with IETF agent Added 2/2/04 + doc = new Document ("Tag Image File Format (TIFF) -- F Profile " + + "for Facsimile", + DocumentType.RFC); + Agent ietfAgent = new Agent ("IETF", AgentType.STANDARD); + ietfAgent.setWeb ("http://www.ietf.org"); + doc.setPublisher (ietfAgent); + doc.setDate ("1998-03"); + ident = new Identifier ("RFC 2306", IdentifierType.RFC); + doc.setIdentifier (ident); + ident = new Identifier + ("http://hul.harvard.edu/jhove/references.html#rfc2306", + IdentifierType.URL); + doc.setIdentifier (ident); + _specification.add (doc); + + // Define RFC 1314 doc, reusing IETF agent + doc = new Document ("A File Format for the Exchange of " + + "Images in the Internet", + DocumentType.RFC); + doc.setPublisher (ietfAgent); + doc.setDate ("1992-04"); + ident = new Identifier ("RFC 1314", IdentifierType.RFC); + doc.setIdentifier (ident); + ident = new Identifier ("http://www.ietf.org/rfc/rfc1314.txt", + IdentifierType.URL); + doc.setIdentifier (ident); + _specification.add (doc); + + // Define JEITA Exif 2.2 doc + doc = new Document ("Exchangeable image file format for digital " + + "still cameras: Exif Version 2.2", + DocumentType.STANDARD); + Agent jeitaAgent = new Agent ("Japan Electronics and Information Technology " + + "Industries Association", AgentType.STANDARD); + jeitaAgent.setWeb ("http://www.jeita.or.jp/"); + jeitaAgent.setAddress ("Mitsui Sumitomo Kaijo Building Annex, " + + "11, Kanda Surugadai 3-chome, Chiyoda-ku, " + + "Tokyo 101-0062, Japan"); + jeitaAgent.setTelephone("+81(03) 3518-6421"); + jeitaAgent.setFax ("+81(03) 3295-8721"); + doc.setPublisher (jeitaAgent); + doc.setDate("2002-04"); + ident = new Identifier ("JEITA CP-3451", IdentifierType.JEITA); + doc.setIdentifier (ident); + ident = new Identifier ("http://www.exif.org/Exif2-2.PDF", + IdentifierType.URL); + doc.setIdentifier (ident); + _specification.add (doc); + + // Define Exif 2.1 doc + doc = new Document ("Digital Still Camera Image File Format Standard " + + "(Exchangeable image file format for Digital Still Camera:Exif)", + DocumentType.STANDARD); + doc.setPublisher (jeitaAgent); + doc.setDate ("1998-12"); + ident = new Identifier ("JEITA JEIDA-49-1998", IdentifierType.JEITA); + doc.setIdentifier (ident); + ident = new Identifier ("http://www.exif.org/dcf-exif.PDF", + IdentifierType.URL); + doc.setIdentifier (ident); + _specification.add (doc); + + // Define GeoTIFF doc + doc = new Document ("GeoTIFF Format Specification: " + + "GeoTIFF Revision 1.0", DocumentType.REPORT); + agent = new Agent ("Niles Ritter", AgentType.OTHER); + doc.setAuthor (agent); + agent = new Agent ("Mike Ruth", AgentType.OTHER); + doc.setAuthor (agent); + agent = new Agent ("GeoTIFF Working Group", AgentType.OTHER); + doc.setPublisher (agent); + doc.setEdition ("Version 1.8.1"); + doc.setDate ("1995-10-31"); + ident = new Identifier ("http://remotesensing.org/geotiff/spec/" + + "geotiffhome.html", IdentifierType.URL); + doc.setIdentifier (ident); + _specification.add (doc); + + // Define RFC 2301 doc (Internet Fax) with IETF agent + doc = new Document ("File Format for Internet Fax", + DocumentType.RFC); + doc.setPublisher (ietfAgent); + doc.setDate ("1998-03"); + ident = new Identifier ("RFC 2301", IdentifierType.RFC); + doc.setIdentifier (ident); + ident = new Identifier ("http://www.ietf.org/rfc/rfc2301.txt", + IdentifierType.URL); + doc.setIdentifier (ident); + _specification.add (doc); + + // Whew -- finally done with docs + + int[] sigbyteI = { 0x49, 0x49, 42, 0 }; + Signature sig = + new InternalSignature (sigbyteI, SignatureType.MAGIC, + SignatureUseType.MANDATORY_IF_APPLICABLE, 0, + "Little-endian (least significant byte " + + "first)"); + _signature.add (sig); + + int[] sigbyteM = { 0x4D, 0x4D, 0, 42 }; + sig = + new InternalSignature (sigbyteM, SignatureType.MAGIC, + SignatureUseType.MANDATORY_IF_APPLICABLE, 0, + "Big-endian (most significant byte first)"); + _signature.add (sig); + + sig = new ExternalSignature ("TIFF", SignatureType.FILETYPE, + SignatureUseType.OPTIONAL); + _signature.add (sig); + + sig = new ExternalSignature (".tif", SignatureType.EXTENSION, + SignatureUseType.OPTIONAL); + _signature.add (sig); + + sig = new ExternalSignature (".tfx", SignatureType.EXTENSION, + SignatureUseType.OPTIONAL, + "For TIFF-FX"); + _signature.add (sig); + + sig = new ExternalSignature ("TFX ", SignatureType.FILETYPE, + SignatureUseType.OPTIONAL, + "For TIFF-FX"); + _signature.add (sig); + + buildProfileList (); + + _byteOffsetIsValid = false; + } + + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + * + * Parsing methods. + ******************************************************************/ + + /** Parse the TIFF for well-formedness and validity, + * accumulating representation information. + * @param raf Open TIFF file + * @param info Representation informatino + */ + public final void parse (RandomAccessFile raf, RepInfo info) + throws IOException + { + if (_defaultParams != null) { + Iterator iter = _defaultParams.iterator (); + while (iter.hasNext ()) { + String param = iter.next (); + if (param.toLowerCase ().equals ("byteoffset=true")) { + _byteOffsetIsValid = true; + } + } + } + + _raf = raf; + initParse (); + info.setModule (this); + info.setMimeType (_mimeType[0]); + info.setFormat (_format[0]); + + Property [] tiffMetadata = new Property[2]; + List ifds = null; + boolean inHeader = true; // Useful for catching empty files + try { + /* TIFF header is "II" (little-endian) or "MM" (big-endian), + * followed by the 16-bit integer value 42. + */ + raf.seek (0); + byte ch0 = _raf.readByte(); + byte ch1 = _raf.readByte(); + if (ch0 != ch1 || (ch0 != 0X49 && ch0 != 0X4D)) { + throw new TiffException ("No TIFF header: " + (char) ch0 + + (char) ch1, 0); + } + inHeader = false; + + _bigEndian = (ch0 == 0X4D); + tiffMetadata[0] = new Property ("ByteOrder", PropertyType.STRING, + _bigEndian ? "big-endian" : + "little-endian"); + + int magic = readUnsignedShort (_raf, _bigEndian); + if (magic != 42) { + throw new TiffException("No TIFF magic number: " + magic, 2); + } + + /* If we got this far, take note that the signature is OK. */ + info.setSigMatch(_name); + + /* The offset of the first IFD is found at offset 4. + /* The lowest recognized TIFF version is 4. Increment this + * as features specific to higher versions are recognized. + */ + _version = 4; + ifds = parseIFDs (4, info); + + info.setVersion (Integer.toString (_version) + ".0"); + + /* Construct IFDs property. */ + List ifdsList = new LinkedList (); + Property ifdsProp = new Property ("IFDs", PropertyType.PROPERTY, + PropertyArity.LIST, ifdsList); + ifdsList.add (new Property ("Number", PropertyType.INTEGER, + new Integer (ifds.size ()))); + + /* Build the IFD property list, for each of the IFDs. */ + ListIterator iter = ifds.listIterator (); + while (iter.hasNext ()) { + IFD ifd = (IFD) iter.next (); + ifdsList.add (ifd.getProperty (_je != null ? + _je.getShowRawFlag () : false)); + + /* Check if any messages were generated in constructing + * the property. If so, the IFD is invalid. + */ + + List errors = ifd.getErrors (); + if (!errors.isEmpty ()) { + info.setValid (false); + ListIterator eter = errors.listIterator (); + while (eter.hasNext ()) { + info.setMessage (new ErrorMessage (eter.next ())); + } + } + + /* Check each IFD for profile conformance. */ + + ListIterator pter = _profile.listIterator (); + while (pter.hasNext ()) { + TiffProfile prof = pter.next (); + if (!prof.isAlreadyOK ()) { + if (prof.satisfiesProfile (ifd)) { + info.setProfile (prof.getText ()); + } + } + } + + /* Checking the Exif profile is more complicated, + * since several IFD's need to meet their respective + * requirements. Try something like this. + */ + if (ifd.isFirst ()) { + _exifFirstFlag = _exifMainProfile.satisfiesProfile (ifd); + // We need to compare compression between the main and + // thumbnail IFD's. + _exifThumbnailProfile.setMainCompression + (((TiffIFD) ifd).getNisoImageMetadata().getCompressionScheme()); + } + else if (ifd.isThumbnail ()) { + _exifThumbnailFlag = _exifThumbnailProfile.satisfiesProfile (ifd); + } + /* The DNG profile is similarly complex, requiring IFD 0 to satisfy + * the thumbnail part and some other IFD to satisfy the main + * profile. The spec doesn't actually say they can't be the same + * profile, so we allow for that possibility. + */ + if (ifd.isFirst()) { + _dngThumbnailFlag = _dngThumbnailProfile.satisfiesProfile (ifd); + } + if (!_dngRawFlag) { + _dngRawFlag = _dngMainProfile.satisfiesProfile (ifd); + } + } + tiffMetadata[1] = ifdsProp; + + /* The Exif profile requires coordinating several IFD checks, so + * we accumulate flags and then set the profile text if they're + * all set. */ + if (_exifFirstFlag && _exifThumbnailFlag) { + info.setProfile (_exifMainProfile.getText()); + } + + /* Similarly for the DNG profile */ + if (_dngThumbnailFlag && _dngRawFlag) { + info.setProfile (_dngMainProfile.getText ()); + } + + info.setProperty (new Property ("TIFFMetadata", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + tiffMetadata)); + } + catch (TiffException e) { + info.setMessage (new ErrorMessage (e.getMessage (), + e.getOffset ())); + info.setWellFormed (false); + return; + } + catch (IOException e) { + String msg; + if (inHeader) { + msg = "File is too short"; + } + else { + msg = e.getClass().getName(); + } + info.setMessage (new ErrorMessage (msg)); + info.setWellFormed (false); + return; + } + + /* Object is well-formed TIFF. */ + + /* Calculate checksums, if necessary. */ + if (_je != null && _je.getChecksumFlag ()) { + if (info.getChecksum ().size () == 0) { + Checksummer ckSummer = new Checksummer (); + calcRAChecksum (ckSummer, raf); + setChecksums (ckSummer, info); + } + } + + info.setMimeType (_mimeType[selectMimeTypeIndex ()]); + + /* The document is well-formed; check IFD's for validity. */ + checkValidity (ifds, info); + } + + + /** Allow odd offsets in values */ + public void setByteOffsetValid (boolean v) { + _byteOffsetIsValid = v; + } + + + /** Special-purpose, limited parser for embedded Exif files. + * + * @param raf Open TIFF file + * @param info Representation informatino + */ + public final List exifParse (RandomAccessFile raf, RepInfo info) + throws IOException + { + _raf = raf; + initParse (); + + List ifds = null; + boolean inHeader = true; // flag to aid reporting empty file error + try { + /* TIFF header is "II" (little-endian) or "MM" (big-endian), + * followed by the 16-bit integer value 42. + */ + raf.seek (0); + byte ch0 = _raf.readByte(); + byte ch1 = _raf.readByte(); + if (ch0 != ch1 || (ch0 != 0X49 && ch0 != 0X4D)) { + throw new TiffException ("No TIFF header: " + (char) ch0 + + (char) ch1, 0); + } + _bigEndian = (ch0 == 0X4D); + + int magic = readUnsignedShort (_raf, _bigEndian); + if (magic != 42) { + throw new TiffException("No TIFF magic number: " + magic, 2); + } + inHeader = false; // There's SOMETHING in the file + + /* The offset of the first IFD is found at offset 4. + /* The lowest recognized TIFF version is 4. Increment this + * as features specific to higher versions are recognized. + */ + _version = 4; + ifds = parseIFDs (4, info, true, IFD.EXIF); + + //info.setVersion (Integer.toString (_version) + ".0"); + + /* Construct IFDs property. */ + //List ifdsList = new LinkedList (); + + ListIterator iter = ifds.listIterator (); + while (iter.hasNext ()) { + IFD ifd = iter.next (); + + /* Check if any messages were generated in constructing + * the property. If so, the IFD is invalid. + */ + + List errors = ifd.getErrors (); + if (!errors.isEmpty ()) { + info.setValid (false); + ListIterator eter = errors.listIterator (); + while (eter.hasNext ()) { + info.setMessage (new ErrorMessage ( eter.next ())); + } + } + + } + + } + catch (TiffException e) { + // For parsing EXIF, we don't want to make the enclosing + // document invalid, so we don't declare the EXIF non-well-formed + // even though it is. + info.setMessage (new InfoMessage (e.getMessage (), + e.getOffset ())); + return ifds; + } + catch (IOException e) { + String msg; + if (inHeader) { + msg = "Embedded Exif block is too short"; + } + else { + msg = e.getClass().getName(); + } + info.setMessage (new ErrorMessage (msg)); + info.setWellFormed (false); + return null; + } + + // Return the IFD list. + return ifds; + } + + /****************************************************************** + * PRIVATE INSTANCE METHODS. + ******************************************************************/ + + + /** Build list of profiles to check. + * Profile checking is, for the most part, done per IFD rather + * than per file. Exif profile checking is an exception, since + * it requires the coordination of multiple IFD's. Hence, the + * Exif profiles aren't added to the list, but treated elsewhere. + */ + protected void buildProfileList () + { + _profile = new ArrayList (30); + _profile.add (new TiffProfileClassB ()); + _profile.add (new TiffProfileClassG ()); + _profile.add (new TiffProfileClassP ()); + _profile.add (new TiffProfileClassR ()); + _profile.add (new TiffProfileClassY ()); + + _profile.add (new TiffProfileClassITBL ()); + _profile.add (new TiffProfileClassITBLP1 ()); + _profile.add (new TiffProfileClassITBP ()); + _profile.add (new TiffProfileClassITBPP1 ()); + _profile.add (new TiffProfileClassITBPP2 ()); + _profile.add (new TiffProfileClassITCT ()); + _profile.add (new TiffProfileClassITCTP1 ()); + _profile.add (new TiffProfileClassITCTP2 ()); + _profile.add (new TiffProfileClassITFP ()); + _profile.add (new TiffProfileClassITFPP1 ()); + _profile.add (new TiffProfileClassITFPP2 ()); + _profile.add (new TiffProfileClassITHC ()); + _profile.add (new TiffProfileClassITHCP1 ()); + _profile.add (new TiffProfileClassITHCP2 ()); + _profile.add (new TiffProfileClassITLW ()); + _profile.add (new TiffProfileClassITLWP1 ()); + _profile.add (new TiffProfileClassITLWP2 ()); + _profile.add (new TiffProfileClassITMP ()); + _profile.add (new TiffProfileClassITMPP1 ()); + _profile.add (new TiffProfileClassITMPP2 ()); + _profile.add (new TiffProfileClassITSD ()); + _profile.add (new TiffProfileClassITSDP2 ()); + + _profile.add (new TiffProfileEP ()); + + _profile.add (new TiffProfileGeoTIFF ()); + + _profile.add (new TiffProfileDLFBW ()); + _profile.add (new TiffProfileDLFGray ()); + _profile.add (new TiffProfileDLFColor ()); + + _profile.add (new TiffProfileRFC1314 ()); + + // TIFF/FX profiles. + _profile.add (new TiffProfileFXS ()); + _profile.add (new TiffProfileFXF ()); + _profile.add (new TiffProfileFXJ ()); + _profile.add (new TiffProfileFXL ()); + _profile.add (new TiffProfileFXC ()); + _profile.add (new TiffProfileFXM ()); + + _exifMainProfile = new TiffProfileExif (); + _exifThumbnailProfile = new TiffProfileExifThumb (); + _dngMainProfile = new TiffProfileDNG (); + _dngThumbnailProfile = new TiffProfileDNGThumb (); + } + + + + /** Go through all the IFD's, calling checkIFDValidity on each one + * that is a standard IFD. (Private IFD's have different requirements, + * and for the moment aren't checked here.) If any of them are invalid, + * set info's valid field to false. Validity problems are non-fatal, + * and more information is better, so we keep going with all IFDs even + * if we find problems. + */ + protected void checkValidity (List ifds, RepInfo info) + { + ListIterator iter = ifds.listIterator (); + while (iter.hasNext ()) { + try { + IFD ifd = iter.next (); + if (ifd instanceof TiffIFD) { + checkValidity ((TiffIFD) ifd, info); + } + } + catch (TiffException e) { + info.setMessage (new ErrorMessage (e.getMessage (), + e.getOffset ())); + info.setValid (false); + } + } + } + + /** Check the validity of the IFD. + * @param ifd IFD + */ + protected void checkValidity (TiffIFD ifd, RepInfo info) + throws TiffException + { + /* Required fields. */ + + NisoImageMetadata niso = ifd.getNisoImageMetadata (); + int photometricInterpretation = niso.getColorSpace (); + if (photometricInterpretation == NisoImageMetadata.NULL) { + reportInvalid ("PhotometricInterpretation not defined", info); + } + long imageWidth = niso.getImageWidth (); + if (imageWidth == NisoImageMetadata.NULL) { + reportInvalid ("ImageWidth not defined", info); + } + long imageLength = niso.getImageLength (); + if (imageLength == NisoImageMetadata.NULL) { + reportInvalid ("ImageLength not defined", info); + } + + /* Strips and tiles. */ + + long [] stripOffsets = niso.getStripOffsets (); + long [] stripByteCounts = niso.getStripByteCounts (); + boolean stripsDefined = (stripOffsets != null || + stripByteCounts != null); + + long tileWidth = niso.getTileWidth (); + long tileLength = niso.getTileLength (); + long [] tileOffsets = niso.getTileOffsets (); + long [] tileByteCounts = niso.getTileByteCounts (); + boolean tilesDefined = (tileWidth != NisoImageMetadata.NULL || + tileLength != NisoImageMetadata.NULL || + tileOffsets != null || + tileByteCounts != null); + + if (stripsDefined && tilesDefined) { + reportInvalid ("Strips and tiles defined together", info); + throw new TiffException ("Strips and tiles defined together"); + } + if (!stripsDefined && !tilesDefined) { + reportInvalid ("Neither strips nor tiles defined", info); + throw new TiffException ("Neither strips nor tiles defined"); + } + + int planarConfiguration = niso.getPlanarConfiguration (); + int samplesPerPixel = niso.getSamplesPerPixel (); + + if (stripsDefined) { + if (stripOffsets == null) { + reportInvalid ("StripOffsets not defined", info); + throw new TiffException ("StripOffsets not defined"); + } + if (stripByteCounts == null) { + reportInvalid ("StripByteCounts not defined", info); + throw new TiffException ("StripByteCounts not defined"); + } + + int len = stripOffsets.length; + if (len != stripByteCounts.length) { + reportInvalid ("StripOffsets inconsistent with " + + "StripByteCounts: " + len + "!=" + + stripByteCounts.length, info); + } + /* Check that all the strips are located within the file */ + try { + long fileLength = _raf.length (); + for (int i=0; i fileLength) { + reportInvalid ("Invalid strip offset", info); + } + } + } + catch (IOException e) { + } + } + + if (tilesDefined) { + if (tileWidth == NisoImageMetadata.NULL) { + reportInvalid ("TileWidth not defined", info); + } + if (tileLength == NisoImageMetadata.NULL) { + reportInvalid ("TileLength not defined", info); + } + if (tileOffsets == null) { + reportInvalid ("TileOffsets not defined", info); + } + if (tileByteCounts == null) { + reportInvalid ("TileByteCounts not defined", info); + } + + if (tileWidth%16 > 0) { + reportInvalid ("TileWidth not a multiple of 16: " + + tileWidth, info); + } + if (tileLength%16 > 0) { + reportInvalid ("TileLength not a multiple of 16: " + + tileLength, info); + } + + long tilesPerImage = ((imageWidth + tileWidth - 1)/tileWidth) * + ((imageLength + tileLength - 1)/tileLength); + if (planarConfiguration == 2) { + long spp_tpi = samplesPerPixel*tilesPerImage; + if (tileOffsets != null && tileOffsets.length < spp_tpi) { + reportInvalid ("Insufficient values for " + + "TileOffsets: " + + tileOffsets.length + "<" + + spp_tpi, info); + } + if (tileByteCounts != null && + tileByteCounts.length < spp_tpi) { + reportInvalid ("Insufficient values for " + + "TileByteCountts: " + + tileByteCounts.length + "<" + + spp_tpi, info); + } + } + else { + if (tileOffsets != null && + tileOffsets.length < tilesPerImage) { + reportInvalid ("Insufficient values for " + + "TileOffsets: " + + tileOffsets.length + "<" + + tilesPerImage, info); + } + if (tileByteCounts != null && + tileByteCounts.length < tilesPerImage) { + reportInvalid ("Insufficient values for " + + "TileByteCounts: " + + tileByteCounts.length + "<" + + tilesPerImage, info); + } + } + } + + /* Transparency mask. */ + + int newSubfileType = (int) ifd.getNewSubfileType (); + if ((photometricInterpretation == 4 && (newSubfileType & 4) == 0) || + (photometricInterpretation != 4 && (newSubfileType & 4) != 0)) { + reportInvalid ("PhotometricInterpretation and " + + "NewSubfileType must agree on " + + "transparency mask", info); + } + int [] bitsPerSample = niso.getBitsPerSample (); + if (photometricInterpretation == 4) { + if (samplesPerPixel < 1 || bitsPerSample[0] != 1) { + reportInvalid ("For transparency mask BitsPerSample " + + "must be 1", info); + } + } + + /* Samples per pixel. */ + + if (photometricInterpretation == 0 || photometricInterpretation == 1 || + photometricInterpretation == 3 || photometricInterpretation == 4) { + if (samplesPerPixel < 1) { + reportInvalid ("For PhotometricInterpretation, " + + "SamplesPerPixel must be >= 1, equals" + + samplesPerPixel, info); + } + } + if (photometricInterpretation == 2 || photometricInterpretation == 6 || + photometricInterpretation == 8) { + if (samplesPerPixel < 3) { + reportInvalid ("For PhotometricInterpretation, " + + "SamplesPerPixel must be >= 3, equals" + + samplesPerPixel, info); + } + } + + /* Palette color. */ + + if (photometricInterpretation == 3) { + int [] colormapBitCodeValue = niso.getColormapBitCodeValue (); + int [] colormapRedValue = niso.getColormapRedValue (); + int [] colormapGreenValue = niso.getColormapGreenValue (); + int [] colormapBlueValue = niso.getColormapBlueValue (); + if (colormapBitCodeValue == null || colormapRedValue == null || + colormapGreenValue == null || colormapBlueValue == null) { + reportInvalid ("ColorMap not defined for " + + "palette-color", info); + } + if (samplesPerPixel != 1) { + reportInvalid ("For palette-color SamplesPerPixel " + + "must be 1: " + samplesPerPixel, info); + } + int len = (1<= sampleMax || + dotRange[1] >= sampleMax) { + reportInvalid ("DotRange out of range specified " + + "by BitsPerSample", info); + } + } + + /* JPEG. */ + + if (niso.getCompressionScheme () == 6 && + ifd.getJPEGProc () == IFD.NULL) { + reportInvalid ("JPEGProc not defined for JPEG " + + "compression", info); + } + + /* CIE L*a*b*. */ + + if (photometricInterpretation == 8 || photometricInterpretation == 9) { + int len = 0; + int[] xs = niso.getExtraSamples (); + if (xs != null) { + len = niso.getExtraSamples ().length; + } + int in = samplesPerPixel - len; + if (in != 1 && in != 3) { + reportInvalid ("SamplesPerPixel-ExtraSamples not " + + "1 or 3: " + samplesPerPixel + "-" + + len, info); + } + for (int i=0; i 9999 || mm < 1 || mm > 12 || + dd < 1 || dd > 31 || hh < 0 || hh > 24 || + mn < 0 || mn > 59 || ss < 0 || mn > 59) { + reportInvalid ("Invalid DateTime digit: " + + dateTime, info); + } + } + catch (Exception e) { + reportInvalid ("Invalid DateTime digit: " + + dateTime, info); + } + } + } + + /** Report an instance of invalidity. */ + protected void reportInvalid (String s, RepInfo info) + { + info.setMessage (new ErrorMessage (s)); + info.setValid (false); + + } + + /** Parse all IFDs in the file, accumulating representation information. + * @param offset Starting byte offset + * @param info Representation information + */ + protected List parseIFDs (long offset, RepInfo info) + throws TiffException + { + return parseIFDs (offset, info, false, IFD.TIFF); + } + + + /** Parse all IFDs in the file, accumulating representation information. + * @param offset Starting byte offset + * @param info Representation information + * @param suppressErrors If true, use IFD even if it has errors + */ + protected List parseIFDs (long offset, RepInfo info, boolean suppressErrors, int ifdType) + throws TiffException + { + long next = 0L; + try { + _raf.seek (offset); + next = readUnsignedInt (_raf, _bigEndian); + } + catch (IOException e) { + throw new TiffException ("Premature EOF" , offset); + } + + if (next == 0L) { + throw new TiffException ("No IFD in file", offset); + } + + List list = new LinkedList (); + while (next != 0L) { + if ((next & 1) != 0) { + throw new TiffException ("IFD offset not word-aligned: " + + next); + } + IFD ifd = parseIFDChain (next, info, ifdType, list, suppressErrors); + next = ifd.getNext (); + } + + return list; + } + + protected IFD parseIFDChain (long next, RepInfo info, int type, List list, + boolean suppressErrors) + throws TiffException + { + IFD ifd = null; + switch (type) { + case IFD.EXIF: + ifd = new ExifIFD (next, info, _raf, _bigEndian); + break; + case IFD.INTEROPERABILITY: + ifd = new InteroperabilityIFD (next, info, _raf, _bigEndian); + break; + case IFD.GPSINFO: + ifd = new GPSInfoIFD (next, info, _raf, _bigEndian); + break; + case IFD.GLOBALPARAMETERS: + ifd = new GlobalParametersIFD (next, info, _raf, _bigEndian); + break; + default: + ifd = new TiffIFD (next, info, _raf, _bigEndian); + } + ifd.parse (_byteOffsetIsValid, suppressErrors); + + /* Update the TIFF version number. */ + int version = ifd.getVersion (); + if (version > _version) { + _version = version; + } + + if (list.size () == 0 && type == IFD.TIFF) { + ifd.setFirst (true); + } + else if (list.size() == 1 && type == IFD.TIFF) { + // For some profiles, the second IFD is assumed to + // be the thumbnail. This may not be valid under + // all circumstances. + ifd.setThumbnail (true); + } + list.add (ifd); + + if (ifd instanceof TiffIFD) { + TiffIFD tifd = (TiffIFD) ifd; + + long [] subIFDs = tifd.getSubIFDs (); + if (subIFDs != null) { + for (int i=0; i pter = _profile.listIterator (); + while (pter.hasNext ()) { + TiffProfile prof = pter.next (); + prof.setAlreadyOK (false); + } + // Initialize flags for the Exif profile. A thumbnail is not + // required, so by default we set the thumbnail flag to true. + // If there is a thumbnail, it must meet the profile. + _exifFirstFlag = false; + _exifThumbnailFlag = true; + + // Initialize flags for the DNG profile. + _dngThumbnailFlag = false; + _dngRawFlag = false; + } + + /** + * Return the index into _mimeType which should be used + * for the MIME type property. This must be called after + * all the profiles have been checked. An index of 0 is + * dominant; if any profiles return 0 from their getMimeClass + * method, or if conflicting values are returned by different + * satisfied profiles, then we return 0. + */ + protected int selectMimeTypeIndex() + { + int trial = -1; + ListIterator pter = _profile.listIterator (); + while (pter.hasNext ()) { + TiffProfile prof = pter.next (); + if (prof.isAlreadyOK ()) { + // Profile was satisfied + int idx = prof.getMimeClass (); + if (idx == 0) { + // 0 beats all others + return 0; + } + else if (trial >= 0 && idx != trial) { + // any conflict implies 0 + return 0; + } + else { + // Treat idx as the tentative return value + trial = idx; + } + } + } + if (trial == -1) { + // No profiles at all were satisfied + return 0; + } + else { + // All satisfied profiles returned trial + return trial; + } + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/Utf8Block.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/Utf8Block.java new file mode 100644 index 00000000..ebff24bf --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/Utf8Block.java @@ -0,0 +1,234 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module; + + +/** + * This class encapsulates a Unicode code block. + * + * @see Utf8Module + */ +public class Utf8Block +{ + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + /** Unicode 6.0.0 blocks, derived from + * <http://www.unicode.org/Public/3.2-Update/Blocks-3.2.0.txt> + * and updated to Unicode 6.0.0 */ + public static final Utf8Block [] unicodeBlock = { + new Utf8Block (0x0000, 0x007F, "Basic Latin"), + new Utf8Block (0x0080, 0x00FF, "Latin-1 Supplement"), + new Utf8Block (0x0100, 0x017F, "Latin Extended-A"), + new Utf8Block (0x0180, 0x024F, "Latin Extended-B"), + new Utf8Block (0x0250, 0x02AF, "IPA Extensions"), + new Utf8Block (0x02B0, 0x02FF, "Spacing Modifier Letters"), + new Utf8Block (0x0300, 0x036F, "Combining Diacritical Marks"), + new Utf8Block (0x0370, 0x03FF, "Greek and Coptic"), + new Utf8Block (0x0400, 0x04FF, "Cyrillic"), + new Utf8Block (0x0500, 0x052F, "Cyrillic Supplementary"), + new Utf8Block (0x0530, 0x058F, "Armenian"), + new Utf8Block (0x0590, 0x05FF, "Hebrew"), + new Utf8Block (0x0600, 0x06FF, "Arabic"), + new Utf8Block (0x0700, 0x074F, "Syriac"), + new Utf8Block (0x0780, 0x07BF, "Thaana"), + new Utf8Block (0x07C0, 0x07FF, "NKo"), + new Utf8Block (0x0840, 0x085F, "Mandaic"), + new Utf8Block (0x0900, 0x097F, "Devanagari"), + new Utf8Block (0x0980, 0x09FF, "Bengali"), + new Utf8Block (0x0A00, 0x0A7F, "Gurmukhi"), + new Utf8Block (0x0A80, 0x0AFF, "Gujarati"), + new Utf8Block (0x0B00, 0x0B7F, "Oriya"), + new Utf8Block (0x0B80, 0x0BFF, "Tamil"), + new Utf8Block (0x0C00, 0x0C7F, "Telugu"), + new Utf8Block (0x0C80, 0x0CFF, "Kannada"), + new Utf8Block (0x0D00, 0x0D7F, "Malayalam"), + new Utf8Block (0x0D80, 0x0DFF, "Sinhala"), + new Utf8Block (0x0E00, 0x0E7F, "Thai"), + new Utf8Block (0x0E80, 0x0EFF, "Lao"), + new Utf8Block (0x0F00, 0x0FFF, "Tibetan"), + new Utf8Block (0x1000, 0x109F, "Myanmar"), + new Utf8Block (0x10A0, 0x10FF, "Georgian"), + new Utf8Block (0x1100, 0x11FF, "Hangul Jamo"), + new Utf8Block (0x1200, 0x137F, "Ethiopic"), + new Utf8Block (0x13A0, 0x13FF, "Cherokee"), + new Utf8Block (0x1400, 0x167F, "Unified Canadian Aboriginal Syllabics"), + new Utf8Block (0x1680, 0x169F, "Ogham"), + new Utf8Block (0x16A0, 0x16FF, "Runic"), + new Utf8Block (0x1700, 0x171F, "Tagalog"), + new Utf8Block (0x1720, 0x173F, "Hanunoo"), + new Utf8Block (0x1740, 0x175F, "Buhid"), + new Utf8Block (0x1760, 0x177F, "Tagbanwa"), + new Utf8Block (0x1780, 0x17FF, "Khmer"), + new Utf8Block (0x1800, 0x18AF, "Mongolian"), + + /* 1900-1D7F new for 4.0 */ + new Utf8Block (0x1900, 0x194F, "Limbu"), + new Utf8Block (0x1950, 0x197F, "Tai Le"), + new Utf8Block (0x19E0, 0x19FF, "Khmer Symbols"), + new Utf8Block (0x1B00, 0x1B7F, "Balinese"), + new Utf8Block (0x1BC0, 0x1BFF, "Batak"), + new Utf8Block (0x1D00, 0x1D7F, "Phonetic Extensions"), + + new Utf8Block (0x1E00, 0x1EFF, "Latin Extended Additional"), + new Utf8Block (0x1F00, 0x1FFF, "Greek Extended"), + new Utf8Block (0x2000, 0x206F, "General Punctuation"), + new Utf8Block (0x2070, 0x209F, "Superscripts and Subscripts"), + new Utf8Block (0x20A0, 0x20CF, "Currency Symbols"), + new Utf8Block (0x20D0, 0x20FF, "Combining Diacritical Marks for Symbols"), + new Utf8Block (0x2100, 0x214F, "Letterlike Symbols"), + new Utf8Block (0x2150, 0x218F, "Number Forms"), + new Utf8Block (0x2190, 0x21FF, "Arrows"), + new Utf8Block (0x2200, 0x22FF, "Mathematical Operators"), + new Utf8Block (0x2300, 0x23FF, "Miscellaneous Technical"), + new Utf8Block (0x2400, 0x243F, "Control Pictures"), + new Utf8Block (0x2440, 0x245F, "Optical Character Recognition"), + new Utf8Block (0x2460, 0x24FF, "Enclosed Alphanumerics"), + new Utf8Block (0x2500, 0x257F, "Box Drawing"), + new Utf8Block (0x2580, 0x259F, "Block Elements"), + new Utf8Block (0x25A0, 0x25FF, "Geometric Shapes"), + new Utf8Block (0x2600, 0x26FF, "Miscellaneous Symbols"), + new Utf8Block (0x2700, 0x27BF, "Dingbats"), + new Utf8Block (0x27C0, 0x27EF, "Miscellaneous Mathematical Symbols-A"), + new Utf8Block (0x27F0, 0x27FF, "Supplemental Arrows-A"), + new Utf8Block (0x2800, 0x28FF, "Braille Patterns"), + new Utf8Block (0x2900, 0x297F, "Supplemental Arrows-B"), + new Utf8Block (0x2980, 0x29FF, "Miscellaneous Mathematical Symbols-B"), + new Utf8Block (0x2A00, 0x2AFF, "Supplemental Mathematical Operators"), + new Utf8Block (0x2C60, 0x2C7F, "Latin Extended-C"), + new Utf8Block (0x2E80, 0x2EFF, "CJK Radicals Supplement"), + new Utf8Block (0x2F00, 0x2FDF, "Kangxi Radicals"), + new Utf8Block (0x2FF0, 0x2FFF, "Ideographic Description Characters"), + new Utf8Block (0x3000, 0x303F, "CJK Symbols and Punctuation"), + new Utf8Block (0x3040, 0x309F, "Hiragana"), + new Utf8Block (0x30A0, 0x30FF, "Katakana"), + new Utf8Block (0x3100, 0x312F, "Bopomofo"), + new Utf8Block (0x3130, 0x318F, "Hangul Compatibility Jamo"), + new Utf8Block (0x3190, 0x319F, "Kanbun"), + new Utf8Block (0x31A0, 0x31BF, "Bopomofo Extended"), + new Utf8Block (0x31F0, 0x31FF, "Katakana Phonetic Extensions"), + new Utf8Block (0x3200, 0x32FF, "Enclosed CJK Letters and Months"), + new Utf8Block (0x3300, 0x33FF, "CJK Compatibility"), + new Utf8Block (0x3400, 0x4DBF, "CJK Unified Ideographs Extension A"), + + /* 4DC0-4DFF new for 4.0 */ + new Utf8Block (0x4DC0, 0x4DFF, "Yijing Hexagram Symbols"), + + new Utf8Block (0x4E00, 0x9FFF, "CJK Unified Ideographs"), + new Utf8Block (0xA000, 0xA48F, "Yi Syllables"), + new Utf8Block (0xA490, 0xA4CF, "Yi Radicals"), + new Utf8Block (0xA720, 0xA7FF, "Latin Extended-D"), + new Utf8Block (0xA840, 0xA87F, "Phags-pa"), + new Utf8Block (0xAB00, 0xAB2F, "Ethiopic Extended-A"), + new Utf8Block (0xAC00, 0xD7AF, "Hangul Syllables"), + new Utf8Block (0xD800, 0xDB7F, "High Surrogates"), + new Utf8Block (0xDB80, 0xDBFF, "High Private Use Surrogates"), + new Utf8Block (0xDC00, 0xDFFF, "Low Surrogates"), + new Utf8Block (0xE000, 0xF8FF, "Private Use Area"), + new Utf8Block (0xF900, 0xFAFF, "CJK Compatibility Ideographs"), + new Utf8Block (0xFB00, 0xFB4F, "Alphabetic Presentation Forms"), + new Utf8Block (0xFB50, 0xFDFF, "Arabic Presentation Forms-A"), + new Utf8Block (0xFE00, 0xFE0F, "Variation Selectors"), + new Utf8Block (0xFE20, 0xFE2F, "Combining Half Marks"), + new Utf8Block (0xFE30, 0xFE4F, "CJK Compatibility Forms"), + new Utf8Block (0xFE50, 0xFE6F, "Small Form Variants"), + new Utf8Block (0xFE70, 0xFEFF, "Arabic Presentation Forms-B"), + new Utf8Block (0xFF00, 0xFFEF, "Halfwidth and Fullwidth Forms"), + new Utf8Block (0xFFF0, 0xFFFF, "Specials"), + + new Utf8Block (0x10000, 0x1007F, "Linear B Syllabary"), + new Utf8Block (0x10080, 0x100FF, "Linear B Ideograms"), + new Utf8Block (0x10100, 0x1013F, "Aegean Numbers"), + + new Utf8Block (0x10300, 0x1032F, "Old Italic"), + new Utf8Block (0x10330, 0x1034F, "Gothic"), + + new Utf8Block (0x10380, 0x1039F, "Ugaritic"), + + new Utf8Block (0x10400, 0x1044F, "Deseret"), + new Utf8Block (0x10900, 0x1091F, "Phoenician"), + + new Utf8Block (0x10450, 0x1047F, "Shavian"), + new Utf8Block (0x10480, 0x104AF, "Osmanya"), + new Utf8Block (0x10800, 0x1083F, "Cypriot Syllabary"), + new Utf8Block (0x11000, 0x1107F, "Brahmi"), + new Utf8Block (0x12000, 0x120FF, "Cuneiform"), + new Utf8Block (0x16800, 0x168BF, "Bamum Supplement"), + new Utf8Block (0x1B000, 0x1B0FF, "Kana Supplement"), + + new Utf8Block (0x1D000, 0x1D0FF, "Byzantine Musical Symbols"), + new Utf8Block (0x1D100, 0x1D1FF, "Musical Symbols"), + new Utf8Block (0x1D360, 0x1D37F, "Counting Rod Numerals"), + new Utf8Block (0x1D400, 0x1D7FF, "Mathematical Alphanumeric Symbols"), + new Utf8Block (0x1F0A0, 0x1F0FF, "Playing Cards"), + new Utf8Block (0x1F300, 0x1F3FF, "Miscellaneous Symbols and Pictographs"), + new Utf8Block (0x1F600, 0x1F64F, "Emoticons"), + new Utf8Block (0x1F680, 0x1F6FF, "Transport and Map Symbols"), + new Utf8Block (0x1F700, 0x1F77F, "Alchemical Symbols"), + new Utf8Block (0x20000, 0x2A6DF, "CJK Unified Ideographs Extension B"), + new Utf8Block (0x2B740, 0x2B78F, "CJK Unified Ideographs Extension D"), + new Utf8Block (0x2F800, 0x2FA1F, "CJK Compatibility Ideographs Supplement"), + new Utf8Block (0xE0000, 0xE007F, "Tags"), + + /* E0100-E01EF new for 4.0 */ + new Utf8Block (0xE0100, 0xE01EF, "Variation Selectors Supplement"), + + new Utf8Block (0xF0000, 0xFFFFF, "Supplementary Private Use Area-A"), + new Utf8Block (0x100000, 0x10FFFF, "Supplementary Private Use Area-B") + }; + + private int _end; + private String _name; + private int _start; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * Creates a Utf8Block, specifying the start and end codes + * and block name. + * + * @param start Start code as defined in the Unicode block specification + * @param end End code as defined in the Unicode block specification + * @param name Block name + */ + public Utf8Block (int start, int end, String name) + { + _start = start; + _end = end; + _name = name; + } + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + ******************************************************************/ + + /** + * Returns the end code. + */ + public int getEnd () + { + return _end; + } + + /** + * Returns the block name. + */ + public String getName () + { + return _name; + } + + /** + * Returns the start code. + */ + public int getStart () + { + return _start; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/Utf8Module.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/Utf8Module.java new file mode 100644 index 00000000..423705e0 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/Utf8Module.java @@ -0,0 +1,613 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003-2007 by JSTOR and the President and Fellows of Harvard College + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module; + +import edu.harvard.hul.ois.jhove.*; + +import java.io.*; +import java.util.*; + +/** + * Module for analysis of content as a UTF-8 stream. + */ +public class Utf8Module + extends ModuleBase +{ + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + + private static final String NAME = "UTF8-hul"; + private static final String RELEASE = "1.5"; + private static final int [] DATE = {2011, 2, 3}; + private static final String [] FORMAT = {"UTF-8"}; + private static final String COVERAGE = "Unicode 6.0.0"; + private static final String [] MIMETYPE = {"text/plain; charset=UTF-8"}; + private static final String WELLFORMED = "An UTF-8 object is well-formed "+ + "if each character is correctly encoded as a one-to-four byte " + + "sequence, as defined in the specifications"; + private static final String VALIDITY = null; + private static final String REPINFO = "Additional representation " + + "information includes: number of characters and Unicode 6.0.0 code " + + "blocks"; + private static final String NOTE = null; + private static final String RIGHTS = "Copyright 2003-2011 by JSTOR and " + + "the President and Fellows of Harvard College. " + + "Released under the GNU Lesser General Public License."; + + private static final String [] POSITION = {"second", "third", "fourth"}; + private static final int CR = 0x0d; + private static final int LF = 0x0a; + + /* Mnemonics for control characters (0-1F) */ + private static final String controlCharMnemonics[] = { + "NUL (0x00)", "SOH (0x01)", "STX (0x02)", "ETX (0x03)", + "EOT (0x04)", "ENQ (0x05)", "ACK (0x06)", "BEL (0x07)", + "BS (0x08)", "TAB (0x09)", "LF (0x0A)", "VT (0x0B)", + "FF (0x0C)", "CR (0x0D)", "SO (0x0E)", "SI (0x0F)", + "DLE (0x10)", "DC1 (0x11)", "DC2 (0x12)", "DC3 (0x13)", + "DC4 (0x14)", "NAK (0x15)", "SYN (0x16)", "ETB (0x17)", + "CAN (0x18)", "EM (0x19)", "SUB (0x1A)", "ESC (0x1B)", + "FS (0x1C)", "GS (0x1D)", "RS (0x1E)", "US (0x1F)" + }; + + /****************************************************************** + *PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + /* Input stream wrapper which handles checksums */ + protected ChecksumInputStream _cstream; + + /* Data input stream wrapped around _cstream */ + protected DataInputStream _dstream; + + protected boolean _lineEndCR; + protected boolean _lineEndLF; + protected boolean _lineEndCRLF; + protected int _prevChar; + protected Map _controlCharMap; + protected int initialBytes[]; + protected Utf8BlockMarker blockMarker; + + /* Flag to know if the property TextMDMetadata is to be added */ + protected boolean _withTextMD = false; + /* Hold the information needed to generate a textMD metadata fragment */ + protected TextMDMetadata _textMD; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * Creates a Utf8Module. + */ + public Utf8Module () + { + super (NAME, RELEASE, DATE, FORMAT, COVERAGE, MIMETYPE, WELLFORMED, + VALIDITY, REPINFO, NOTE, RIGHTS, false); + + Agent agent = new Agent ("Harvard University Library", + AgentType.EDUCATIONAL); + agent.setAddress ("Office for Information Systems, " + + "90 Mt. Auburn St., " + + "Cambridge, MA 02138"); + agent.setTelephone ("+1 (617) 495-3724"); + agent.setEmail("jhove-support@hulmail.harvard.edu"); + _vendor = agent; + + Document doc = new Document ("The Unicode Standard, Version 6.0", + DocumentType.BOOK); + agent = new Agent ("The Unicode Consortium", AgentType.NONPROFIT); + agent.setWeb ("http://www.unicode.org/versions/Unicode6.0.0/"); + agent.setAddress ("Mountain View, California"); + doc.setAuthor (agent); + agent = new Agent ("Addison-Wesley", AgentType.COMMERCIAL); + agent.setAddress ("Boston, Massachusetts"); + doc.setPublisher (agent); + doc.setDate ("2011"); + doc.setIdentifier (new Identifier ("978-1-936213-01-6", + IdentifierType.ISBN)); + _specification.add (doc); + + doc = new Document ("Information technology -- Universal " + + "Multiple-Octet Coded Character Set (UCS) -- " + + "Part 1: Architecture and Basic Multilingual " + + "Plane. Appendix R, Amendment 2", + DocumentType.STANDARD); + agent = new Agent ("ISO", AgentType.STANDARD); + agent.setAddress ("1, rue de Varembe, Casa postale 56, " + + "CH-1211, Geneva 20, Switzerland"); + agent.setTelephone ("+41 22 749 01 11"); + agent.setFax ("+41 22 733 34 30"); + agent.setEmail ("iso@iso.ch"); + agent.setWeb ("http://www.iso.org/"); + doc.setPublisher (agent); + doc.setDate ("1991"); + doc.setIdentifier (new Identifier ("ISO/IEC 10646-1 Amendment 2", + IdentifierType.ISO)); + _specification.add (doc); + + doc = new Document ("UTF-8, a transformation format of ISO 10646", + DocumentType.RFC); + agent = new Agent ("F. Yergeau", AgentType.OTHER); + doc.setAuthor (agent); + agent = new Agent ("IETF", AgentType.NONPROFIT); + agent.setWeb ("http://www.ietf.org/"); + doc.setPublisher (agent); + doc.setDate ("1998-01"); + doc.setIdentifier (new Identifier ("RFC 2279", IdentifierType.RFC)); + doc.setIdentifier (new Identifier ("http://www.ietf.org/rfc/rfc2279.txt", + IdentifierType.URL)); + _specification.add (doc); + + } + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + * + * Parsing methods. + ******************************************************************/ + + /** + * Parse the content of a stream digital object and store the + * results in RepInfo. + * + * @param stream An InputStream, positioned at its beginning, + * which is generated from the object to be parsed. + * If multiple calls to parse are made + * on the basis of a nonzero value being returned, + * a new InputStream must be provided each time. + * + * @param info A fresh (on the first call) RepInfo object + * which will be modified + * to reflect the results of the parsing + * If multiple calls to parse are made + * on the basis of a nonzero value being returned, + * the same RepInfo object should be passed with each + * call. + * + * @param parseIndex Must be 0 in first call to parse. If + * parse returns a nonzero value, it must be + * called again with parseIndex + * equal to that return value. + * + */ + public final int parse (InputStream stream, RepInfo info, int parseIndex) + throws IOException + { + // Test if textMD is to be generated + if (_defaultParams != null) { + Iterator iter = _defaultParams.iterator (); + while (iter.hasNext ()) { + String param = (String) iter.next (); + if (param.toLowerCase ().equals ("withtextmd=true")) { + _withTextMD = true; + } + } + } + + initParse (); + info.setFormat (_format[0]); + info.setMimeType (_mimeType[0]); + info.setModule (this); + initialBytes = new int[4]; + + // No line end types have been discovered. + _lineEndCR = false; + _lineEndLF = false; + _lineEndCRLF = false; + _prevChar = 0; + _controlCharMap = new HashMap (); + _textMD = new TextMDMetadata(); + + boolean printableChars = false; + + info.setNote ("Additional representation information includes " + + "the line endings: CR, LF, or CRLF"); + _nByte = 0; + long nChar = 0; + /* We may have already done the checksums while converting a + temporary file. */ + Checksummer ckSummer = null; + if (_je != null && _je.getChecksumFlag () && + info.getChecksum ().size () == 0) { + ckSummer = new Checksummer (); + _cstream = new ChecksumInputStream (stream, ckSummer); + _dstream = getBufferedDataStream (_cstream, _je != null ? + _je.getBufferSize () : 0); + } + else { + _dstream = getBufferedDataStream (stream, _je != null ? + _je.getBufferSize () : 0); + } + blockMarker = new Utf8BlockMarker(); + + boolean eof = false; + while (!eof) { + try { + boolean isMark = false; + int [] b = new int[4]; + int ch = -1; + + /* Byte values must be valid UTF-8 encodings: */ + /* Unicode value Byte 1 Byte 2 Byte 3 Byte 4 */ + /* 000000000xxxxxxx 0xxxxxxx */ + /* 00000yyyyyxxxxxx 110yyyyy 10xxxxxx */ + /* zzzzyyyyyyxxxxxx 1110zzzz 10yyyyyy 10yyyyyy */ + /* uuuuuzzzzyyyyyyxxxxxx 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx */ + + b[0] = readUnsignedByte (_dstream, this); + if (_nByte < 4) { + isMark = checkMark (b[0], info); + if (info.getWellFormed () == RepInfo.FALSE) { + return 0; + } + if (isMark) { + nChar = 0; + } + } + + int nBytes = 1; + if (0xc0 <= b[0] && b[0] <= 0xdf) { + nBytes = 2; + } + else if (0xe0 <= b[0] && b[0] <= 0xef) { + nBytes = 3; + } + else if (0xf0 <= b[0] && b[0] <= 0xf7) { + nBytes = 4; + } + else if ((0x80 <= b[0] && b[0] <= 0xbf) || + (0xf8 <= b[0] && b[0] <= 0xff)){ + ErrorMessage error = + new ErrorMessage ("Not valid first byte of UTF-8 " + + "encoding", + "Value = " + ((char) b[0]) + + " (0x" + Integer.toHexString (b[0]) + + ")", _nByte); + info.setMessage (error); + info.setWellFormed (false); + return 0; + } + + for (int i=1; i b[i] || b[i] > 0xbf) { + ErrorMessage error = + new ErrorMessage ("Not valid " + POSITION[i-1] + + " byte of UTF-8 endcoding", + "Value = " + ((char) b[i]) + " (0x" + + Integer.toHexString (b[i]) + ")", + _nByte); + info.setMessage (error); + info.setWellFormed (false); + return 0; + } + } + + if (nBytes == 1) { + ch = b[0]; + } + else if (nBytes == 2) { + ch = ((b[0]&0x1f)<<6) + (b[1]&0x3f); + } + else if (nBytes == 3) { + ch = ((b[0]&0x0f)<<12) + ((b[1]&0x3f)<<6) + (b[2]&0x3f); + } + else if (nBytes == 4) { + ch = ((b[0]&0x07)<<18) + ((b[1]&0x3f)<<12) + + ((b[2]&0x3f)<<6) + (b[3]&0x3f); + } + + if (!isMark) { + blockMarker.markBlock(ch); + } + + /* Track what control characters are used. */ + if (ch < 0X20 && ch != 0X0D && ch != 0X0A) { + _controlCharMap.put (new Integer (ch), + controlCharMnemonics [ch]); + } + else if (ch == 0X7F) { + _controlCharMap.put (new Integer (ch), "DEL (0x7F)"); + } + + /* Character values U+000..U+001f,U+007f aren't printable. */ + if (ch > 0x001f && ch != 0x7f) { + printableChars = true; + } + + /* Determine the line ending type(s). */ + checkLineEnd(ch); + _prevChar = ch; + + nChar++; + } + catch (EOFException e) { + eof = true; + /* Catch line endings at very end. */ + checkLineEnd(0); + } + } + + /* Object is well-formed UTF-8. */ + + if (ckSummer != null) { + info.setSize (_cstream.getNBytes ()); + info.setChecksum (new Checksum (ckSummer.getCRC32 (), + ChecksumType.CRC32)); + String value = ckSummer.getMD5 (); + if (value != null) { + info.setChecksum (new Checksum (value, ChecksumType.MD5)); + } + if ((value = ckSummer.getSHA1 ()) != null) { + info.setChecksum (new Checksum (value, ChecksumType.SHA1)); + } + } + + /* Only non-zero-length files are well-formed UTF-8. + */ + if (_nByte == 0) { + info.setMessage (new ErrorMessage ("Zero-length file")); + info.setWellFormed (RepInfo.FALSE); + return 0; + } + + /* Add the textMD information */ + _textMD.setCharset(TextMDMetadata.CHARSET_UTF8); + _textMD.setByte_order( + _bigEndian?TextMDMetadata.BYTE_ORDER_BIG:TextMDMetadata.BYTE_ORDER_LITTLE); + _textMD.setByte_size("8"); + _textMD.setCharacter_size("variable"); + + /* Create a metadata property for the module-specific + * info. (4-Feb-04) */ + List metadataList = new ArrayList (4); + info.setProperty (new Property ("UTF8Metadata", + PropertyType.PROPERTY, + PropertyArity.LIST, + metadataList)); + + Property property = new Property ("Characters", PropertyType.LONG, + new Long (nChar)); + metadataList.add (property); + + property = blockMarker.getBlocksUsedProperty("UnicodeBlocks"); + if (property != null) { + metadataList.add (property); + } + + /* Set property reporting line ending type */ + if (_lineEndCR || _lineEndLF || _lineEndCRLF) { + ArrayList propArray = new ArrayList(3); + if (_lineEndCR) { + propArray.add("CR"); + _textMD.setLinebreak(TextMDMetadata.LINEBREAK_CR); + } + if (_lineEndLF) { + propArray.add("LF"); + _textMD.setLinebreak(TextMDMetadata.LINEBREAK_LF); + } + if (_lineEndCRLF) { + propArray.add("CRLF"); + _textMD.setLinebreak(TextMDMetadata.LINEBREAK_CRLF); + } + property = new Property ("LineEndings", PropertyType.STRING, + PropertyArity.LIST, propArray); + metadataList.add (property); + } + /* Set property reporting control characters used */ + if (!_controlCharMap.isEmpty ()) { + LinkedList propList = new LinkedList (); + String mnem; + for (int i = 0; i < 0X20; i++) { + mnem = (String) _controlCharMap.get (new Integer (i)); + if (mnem != null) { + propList.add (mnem); + } + } + /* need to check separately for DEL */ + mnem = (String) _controlCharMap.get (new Integer (0X7F)); + if (mnem != null) { + propList.add (mnem); + } + property = new Property ("ControlCharacters", PropertyType.STRING, + PropertyArity.LIST, propList); + metadataList.add (property); + } + + if (_withTextMD) { + property = new Property ("TextMDMetadata", + PropertyType.TEXTMDMETADATA, PropertyArity.SCALAR, _textMD); + metadataList.add (property); + } + + if (!printableChars) { + info.setMessage (new InfoMessage ("No printable characters")); + } + + return 0; + } + + + /** + * Check if the digital object conforms to this Module's + * internal signature information. + * Try to read the BOM if it's present, and check the beginning of the file. + * + * @param file A File object for the object being parsed + * @param stream An InputStream, positioned at its beginning, + * which is generated from the object to be parsed + * @param info A fresh RepInfo object which will be modified + * to reflect the results of the test + */ + public void checkSignatures (File file, + InputStream stream, + RepInfo info) + throws IOException + { + info.setFormat (_format[0]); + info.setMimeType (_mimeType[0]); + info.setModule (this); + initialBytes = new int[4]; + JhoveBase jb = getBase(); + int sigBytes = jb.getSigBytes(); + int bytesRead = 0; + blockMarker = new Utf8BlockMarker(); + boolean eof = false; + _nByte = 0; + long nChar = 0; + DataInputStream dstream = new DataInputStream (stream); + while (!eof && bytesRead < sigBytes) { + boolean isMark = false; + int [] b = new int[4]; + //int ch = -1; + try { + b[0] = readUnsignedByte (dstream, this); + ++bytesRead; + if (_nByte < 4) { + isMark = checkMark (b[0], info); + if (info.getWellFormed () == RepInfo.FALSE) { + return; + } + if (isMark) { + nChar = 0; + } + } + int nBytes = 1; + if (0xc0 <= b[0] && b[0] <= 0xdf) { + nBytes = 2; + } + else if (0xe0 <= b[0] && b[0] <= 0xef) { + nBytes = 3; + } + else if (0xf0 <= b[0] && b[0] <= 0xf7) { + nBytes = 4; + } + else if ((0x80 <= b[0] && b[0] <= 0xbf) || + (0xf8 <= b[0] && b[0] <= 0xff)){ + info.setWellFormed (false); + return ; + } + for (int i=1; i b[i] || b[i] > 0xbf) { + // Not a valid UTF-8 character + info.setWellFormed (false); + return; + } + } + + } + catch (EOFException e) { + eof = true; + } + } + if (bytesRead > 0) { + info.setSigMatch(_name); + } + else { + // Don't match an empty file + info.setWellFormed (false); + } + } + + /****************************************************************** + * PRIVATE INSTANCE METHODS. + ******************************************************************/ + + /** Accumulate information about line endings. + * @param ch Current character + */ + protected void checkLineEnd (int ch) + { + if (ch == LF) { + if (_prevChar == CR) { + _lineEndCRLF = true; + } + else { + _lineEndLF = true; + } + } + else if (_prevChar == CR) { + _lineEndCR = true; + } + } + + protected boolean checkMark (int byt, RepInfo info) + { + ErrorMessage msg; + initialBytes[(int) _nByte - 1] = byt; + if (_nByte == 3) { + // Check for UTF-8 byte order mark in 1st 3 bytes + if (initialBytes[0] == 0XEF && + initialBytes[1] == 0XBB && + initialBytes[2] == 0XBF) { + InfoMessage im = new InfoMessage + ("UTF-8 Byte Order Mark signature is present", 0); + info.setMessage (im); + // If we've found a non-character header, clear + // all usage blocks + blockMarker.reset (); + return true; + } + + if (initialBytes[0] == 0XFF && + initialBytes[1] == 0XFE) { + if (initialBytes[2] == 0 && + initialBytes[3] == 0) { + msg = new ErrorMessage + ("UCS-4 little-endian encoding, not UTF-8"); + } + else { + msg = new ErrorMessage + ("UTF-16 little-endian encoding, not UTF-8"); + } + info.setMessage (msg); + info.setWellFormed (false); + return false; + } + else if (initialBytes[0] == 0XFE && + initialBytes[1] == 0XFF) { + msg = new ErrorMessage + ("UTF-16 big-endian encoding, not UTF-8"); + info.setMessage (msg); + info.setWellFormed (false); + return false; + } + } + return false; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/WaveModule.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/WaveModule.java new file mode 100644 index 00000000..9999a04e --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/WaveModule.java @@ -0,0 +1,896 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004-2007 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module; + +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.iff.*; +import edu.harvard.hul.ois.jhove.module.wave.*; +import java.io.*; +import java.util.*; + +/** + * Module for identification and validation of WAVE sound files. + * + * There is no published specification for WAVE files; this module + * is based on several Internet sources. + * + * WAVE format is a type of RIFF format. RIFF, in turn, is a variant + * on EA IFF 85. + * + * @author Gary McGath + */ +public class WaveModule + extends ModuleBase +{ + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + /* Checksummer object */ + protected Checksummer _ckSummer; + + /* Input stream wrapper which handles checksums */ + protected ChecksumInputStream _cstream; + + /* Data input stream wrapped around _cstream */ + protected DataInputStream _dstream; + + /* Top-level metadata property */ + protected Property _metadata; + + /* Top-level property list */ + protected List _propList; + + /* List of Note properties */ + protected List _notes; + + /* List of Label properties */ + protected List _labels; + + /* List of Labeled Text properties */ + protected List _labeledText; + + /* List of Sample properties */ + protected List _samples; + + /* AES audio metadata to go into AIFF metadata */ + protected AESAudioMetadata _aesMetadata; + + /* Bytes remaining to be read. */ + protected long bytesRemaining; + + /* Bytes needed to store a file. */ + protected int _blockAlign; + + /* Exif data from file. */ + protected ExifInfo _exifInfo; + + /* Compression format, used for profile verification. */ + protected int compressionCode; + + /* Number of samples in the file. Obtained from the + * DATA chunk for uncompressed files, and the FACT + * chunk for compressed ones. */ + protected long numSamples; + + /* Sample rate from file. */ + protected long sampleRate; + + /* Flag to check for exactly one format chunk */ + protected boolean formatChunkSeen; + + /* Flag to check for presence of fact chunk */ + protected boolean factChunkSeen; + + /* Flag to check for not more than one data chunk */ + protected boolean dataChunkSeen; + + /* Flag to check for not more than one instrument chunk */ + protected boolean instrumentChunkSeen; + + /* Flag to check for not more than one MPEG chunk */ + protected boolean mpegChunkSeen; + + /* Flag to check for not more than one Cart chunk */ + protected boolean cartChunkSeen; + + /* Flag to check for not more than one broadcast audio extension chunk */ + protected boolean broadcastExtChunkSeen; + + /* Flag to check for not more than one peak envelope chunk */ + protected boolean peakChunkSeen; + + /* Flag to check for not more than one link chunk */ + protected boolean linkChunkSeen; + + /* Flag to check for not more than one cue chunk */ + protected boolean cueChunkSeen; + + /* Profile flag for PCMWAVEFORMAT */ + protected boolean flagPCMWaveFormat; + + /* Profile flag for WAVEFORMATEX */ + protected boolean flagWaveFormatEx; + + /* Profile flag for WAVEFORMATEXTENSIBLE */ + protected boolean flagWaveFormatExtensible; + + /* Profile flag for Broadcast Wave format. This indicates + * only that the Format chunk is acceptable; it is also + * necessary to verify that certain chunks were found. */ + protected boolean flagBroadcastWave; + + /* Version of Broadcast Wave, as determined from the Broadcast + * Extension Chunk. */ + protected int broadcastVersion; + + /* Flag to note that first sample offset has been recorded */ + protected boolean firstSampleOffsetMarked; + + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + + /* Fixed value for first 4 bytes */ + private static final int[] sigByte = + { 0X52, 0X49, 0X46, 0X46 }; + + private static final String NAME = "WAVE-hul"; + private static final String RELEASE = "1.3"; + private static final int [] DATE = {2007, 12, 14}; + private static final String [] FORMAT = { + "WAVE", + "Audio for Windows", + "EBU Technical Specification 3285", + "Broadcast Wave Format", + "BWF" + }; + private static final String COVERAGE = + "WAVE (WAVEFORMAT, PCMWAVEFORMAT, WAVEFORMATEX, WAVEFORMATEXTENSIBLE), " + + "Broadcast Wave Format (BWF) version 0 and 1"; + private static final String [] MIMETYPE = {"audio/x-wave", "audio/wave"}; + private static final String WELLFORMED = null; + private static final String VALIDITY = null; + private static final String REPINFO = null; + private static final String NOTE = + "There is no published standard for WAVE files. This module regards " + + "a file as valid if it conforms to common usage practices."; + private static final String RIGHTS = "Copyright 2004-2007 by JSTOR and the " + + "President and Fellows of Harvard College. " + + "Released under the GNU Lesser General Public License."; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + /** + * Instantiates an WaveModule object. + */ + public WaveModule () + { + super (NAME, RELEASE, DATE, FORMAT, COVERAGE, MIMETYPE, WELLFORMED, + VALIDITY, REPINFO, NOTE, RIGHTS, false); + Agent agent = new Agent ("Harvard University Library", + AgentType.EDUCATIONAL); + agent.setAddress ("Office for Information Systems, " + + "90 Mt. Auburn St., " + + "Cambridge, MA 02138"); + agent.setTelephone ("+1 (617) 495-3724"); + agent.setEmail("jhove-support@hulmail.harvard.edu"); + _vendor = agent; + + Agent msagent = new Agent ("Microsoft Corporation", + AgentType.COMMERCIAL); + msagent.setAddress (" One Microsoft Way, " + + "Redmond, WA 98052-6399"); + msagent.setTelephone ("+1 (800) 426-9400"); + msagent.setWeb ("http://www.microsoft.com"); + Document doc = new Document ("PCMWAVEFORMAT", DocumentType.WEB); + doc.setIdentifier (new Identifier + ("http://msdn.microsoft.com/library/default.asp?url=/library/en-us/" + + "multimed/htm/_win32_pcmwaveformat_str.asp", + IdentifierType.URL)); + doc.setPublisher (msagent); + _specification.add (doc); + + doc = new Document ("WAVEFORMATEX", DocumentType.WEB); + doc.setIdentifier (new Identifier + ("http://msdn.microsoft.com/library/default.asp?url=/library/en-us/" + + "multimed/htm/_win32_waveformatex_str.asp", + IdentifierType.URL)); + doc.setPublisher (msagent); + _specification.add (doc); + + doc = new Document ("WAVEFORMATEXTENSIBLE", DocumentType.WEB); + doc.setIdentifier (new Identifier + ("http://msdn.microsoft.com/library/default.asp?url=/library/en-us/" + + "multimed/htm/_win32_waveformatextensible_str.asp", + IdentifierType.URL)); + doc.setPublisher (msagent); + _specification.add (doc); + + agent = new Agent ("European Broadcasting Union", + AgentType.COMMERCIAL); + agent.setAddress ("Casa postale 45, Ancienne Route 17A, " + + "CH-1218 Grand-Saconex, Geneva, Switzerland"); + agent.setTelephone ("+ 41 (0)22 717 2111"); + agent.setFax("+ 41 (0)22 747 4000"); + agent.setEmail("techreview@ebu.ch"); + agent.setWeb("http://www.ebu.ch"); + + doc = new Document ("Broadcast Wave Format (EBU N22-1987)", + DocumentType.REPORT); + doc.setIdentifier (new Identifier + ("http://www.ebu.ch/CMSimages/en/tec_doc_t3285_tcm6-10544.pdf", + IdentifierType.URL)); + doc.setPublisher(agent); + _specification.add (doc); + + Signature sig = new ExternalSignature (".wav", SignatureType.EXTENSION, + SignatureUseType.OPTIONAL); + _signature.add (sig); + + sig = new ExternalSignature (".bwf", SignatureType.EXTENSION, + SignatureUseType.OPTIONAL, + "For BWF profile"); + _signature.add(sig); + + sig = new InternalSignature ("RIFF", SignatureType.MAGIC, + SignatureUseType.MANDATORY, 0); + _signature.add (sig); + sig = new InternalSignature ("WAVE", SignatureType.MAGIC, + SignatureUseType.MANDATORY, 8); + _signature.add (sig); + + _bigEndian = false; + } + + /** + * Parses the content of a purported WAVE digital object and stores the + * results in RepInfo. + * + * + * @param stream An InputStream, positioned at its beginning, + * which is generated from the object to be parsed + * @param info A fresh RepInfo object which will be modified + * to reflect the results of the parsing + * @param parseIndex Must be 0 in first call to parse. If + * parse returns a nonzero value, it must be + * called again with parseIndex + * equal to that return value. + */ + public int parse (InputStream stream, RepInfo info, int parseIndex) + throws IOException + { + initParse (); + info.setFormat (_format[0]); + info.setMimeType (_mimeType[0]); + info.setModule (this); + + _aesMetadata.setPrimaryIdentifier(info.getUri()); + if (info.getURLFlag ()) { + _aesMetadata.setOtherPrimaryIdentifierType("URI"); + } + else { + _aesMetadata.setPrimaryIdentifierType(AESAudioMetadata.FILE_NAME); + } + + /* We may have already done the checksums while converting a + temporary file. */ + _ckSummer = null; + if (_je != null && _je.getChecksumFlag () && + info.getChecksum ().size () == 0) { + _ckSummer = new Checksummer (); + _cstream = new ChecksumInputStream (stream, _ckSummer); + _dstream = getBufferedDataStream (_cstream, _je != null ? + _je.getBufferSize () : 0); + } + else { + _dstream = getBufferedDataStream (stream, _je != null ? + _je.getBufferSize () : 0); + } + + try { + // Check the start of the file for the right opening bytes + for (int i = 0; i < 4; i++) { + int ch = readUnsignedByte(_dstream, this); + if (ch != sigByte[i]) { + info.setMessage(new ErrorMessage ("Document does not start with RIFF chunk", 0)); + info.setWellFormed (false); + return 0; + } + } + /* If we got this far, take note that the signature is OK. */ + info.setSigMatch(_name); + + // Get the length of the Form chunk. This includes all + // the subsequent chunks in the file, but excludes the + // header ("FORM" and the length itself). + bytesRemaining = readUnsignedInt (_dstream); + + // Read the file type. + String typ = read4Chars (_dstream); + bytesRemaining -= 4; + if (!"WAVE".equals (typ)) { + info.setMessage (new ErrorMessage + ("File type in RIFF header is not WAVE", _nByte)); + info.setWellFormed (false); + return 0; + } + + while (bytesRemaining > 0) { + if (!readChunk (info)) { + break; + } + } + } + catch (EOFException e) { + info.setWellFormed (false); + info.setMessage (new ErrorMessage + ("Unexpected end of file", _nByte)); + return 0; + } + catch (Exception e) { // TODO make this more specific + e.printStackTrace(); + info.setWellFormed (false); + info.setMessage (new ErrorMessage + ("Exception reading file: " + e.getClass().getName() + + ", " + e.getMessage(), _nByte)); + return 0; + } + + // Set duration from number of samples and rate. + if (numSamples > 0) { + //_aesMetadata.setDuration((double) numSamples / sampleRate); + _aesMetadata.setDuration (numSamples); + } + + // Add note and label properties, if there's anything + // to report. + if (!_labels.isEmpty ()) { + _propList.add (new Property ("Labels", + PropertyType.PROPERTY, + PropertyArity.LIST, + _labels)); + } + if (!_labeledText.isEmpty ()) { + _propList.add (new Property ("LabeledText", + PropertyType.PROPERTY, + PropertyArity.LIST, + _labeledText)); + } + if (!_notes.isEmpty ()) { + _propList.add (new Property ("Notes", + PropertyType.PROPERTY, + PropertyArity.LIST, + _notes)); + } + if (!_samples.isEmpty ()) { + _propList.add (new Property ("Samples", + PropertyType.PROPERTY, + PropertyArity.LIST, + _samples)); + } + if (_exifInfo != null) { + _propList.add (_exifInfo.buildProperty ()); + } + if (!formatChunkSeen) { + info.setMessage (new ErrorMessage + ("No Format Chunk")); + info.setWellFormed (false); + return 0; + } + + /* This file looks OK. */ + if (_ckSummer != null){ + /* We may not have actually hit the end of file. If we're calculating + * checksums on the fly, we have to read and discard whatever is + * left, so it will get checksummed. */ + for (;;) { + try { + int n = skipBytes (_dstream, 2048, this); + if (n == 0) { + break; + } + } + catch (Exception e) { + break; + } + } + info.setSize (_cstream.getNBytes ()); + info.setChecksum (new Checksum (_ckSummer.getCRC32 (), + ChecksumType.CRC32)); + String value = _ckSummer.getMD5 (); + if (value != null) { + info.setChecksum (new Checksum (value, ChecksumType.MD5)); + } + if ((value = _ckSummer.getSHA1 ()) != null) { + info.setChecksum (new Checksum (value, ChecksumType.SHA1)); + } + } + + info.setProperty (_metadata); + + // Indicate satisfied profiles. + if (flagPCMWaveFormat) { + info.setProfile ("PCMWAVEFORMAT"); + } + if (flagWaveFormatEx) { + info.setProfile ("WAVEFORMATEX"); + } + if (flagWaveFormatExtensible) { + info.setProfile ("WAVEFORMATEXTENSIBLE"); + } + if (flagBroadcastWave) { + // Need to do some additional checks. + if (!broadcastExtChunkSeen) { + flagBroadcastWave = false; + } + if (compressionCode == FormatChunk.WAVE_FORMAT_MPEG) { + if (!broadcastExtChunkSeen || !factChunkSeen) { + flagBroadcastWave = false; + } + } + if (flagBroadcastWave) { + String prof = null; + switch (broadcastVersion) { + case 0: + prof = "Broadcast Wave Version 0"; + break; + + case 1: + prof = "Broadcast Wave Version 1"; + break; + + // Other versions are unknown at this time + } + if (prof != null) { + info.setProfile (prof); + } + } + } + return 0; + } + + /** Marks the first sample offset as the current byte position, + * if it hasn't already been marked. */ + public void markFirstSampleOffset () + { + if (!firstSampleOffsetMarked) { + firstSampleOffsetMarked = true; + _aesMetadata.setFirstSampleOffset (_nByte); + } + } + + /** Sets an ExifInfo object for the module. */ + public void setExifInfo (ExifInfo exifInfo) + { + _exifInfo = exifInfo; + } + + /** Set the number of bytes that holds an aligned sample. */ + public void setBlockAlign (int align) + { + _blockAlign = align; + } + + /** Returns the ExifInfo object. If no ExifInfo object + * has been set, returns null. */ + public ExifInfo getExifInfo () + { + return _exifInfo; + } + + /** Returns the compression code. */ + public int getCompressionCode () + { + return compressionCode; + } + + /** Returns the number of bytes needed per aligned + * sample. */ + public int getBlockAlign () + { + return _blockAlign; + } + + /** Adds a Property to the WAVE metadata. */ + public void addWaveProperty (Property prop) + { + _propList.add (prop); + } + + /** Adds a Label property */ + public void addLabel (Property p) + { + _labels.add (p); + } + + /** Adds a LabeledText property */ + public void addLabeledText (Property p) + { + _labeledText.add (p); + } + + /** Adds a Sample property */ + public void addSample (Property p) + { + _samples.add (p); + } + + /** Adds a Note string */ + public void addNote (Property p) + { + _notes.add (p); + } + + /** Adds the ListInfo property, which is a List of String Properties. */ + public void addListInfo (List l) { + _propList.add (new Property ("ListInfo", + PropertyType.PROPERTY, + PropertyArity.LIST, + l)); + } + + /** One-argument version of readSignedLong. + * WAVE is always little-endian, so readSignedInt can + * unambiguously drop its endian argument. */ + public long readSignedLong (DataInputStream stream) + throws IOException + { + return readSignedLong (stream, false, this); + } + + /** One-argument version of readUnsignedInt. + * WAVE is always little-endian, so readUnsignedInt can + * unambiguously drop its endian argument. */ + public long readUnsignedInt (DataInputStream stream) + throws IOException + { + return readUnsignedInt (stream, false, this); + } + + /** One-argument version of readSignedInt. + * WAVE is always little-endian, so readSignedInt can + * unambiguously drop its endian argument. */ + public int readSignedInt (DataInputStream stream) + throws IOException + { + return readSignedInt (stream, false, this); + } + + /** One-argument version of readUnsignedShort. + * WAVE is always little-endian, so readUnsignedShort can + * unambiguously drop its endian argument. */ + public int readUnsignedShort (DataInputStream stream) + throws IOException + { + return readUnsignedShort (stream, false, this); + } + + /** One-argument version of readSignedShort. + * WAVE is always little-endian, so readSignedShort can + * unambiguously drop its endian argument. */ + public int readSignedShort (DataInputStream stream) + throws IOException + { + return readSignedShort (stream, false, this); + } + + /** + * Reads 4 bytes and concatenates them into a String. + * This pattern is used for ID's of various kinds. + */ + public String read4Chars(DataInputStream stream) throws IOException + { + StringBuffer sbuf = new StringBuffer(4); + for (int i = 0; i < 4; i++) { + int ch = readUnsignedByte(stream, this); + if (ch != 0) { + sbuf.append((char) ch); // omit nulls + } + } + return sbuf.toString(); + } + + /** Set the compression format. Called from the Format + * chunk. */ + public void setCompressionCode (int cf) + { + compressionCode = cf; + } + + /** Add to the number of data bytes. This may be called + * multiple times to give a cumulative total. + */ + public void addSamples (long samples) + { + numSamples += samples; + } + + /** Set the sample rate. */ + public void setSampleRate (long rate) + { + sampleRate = rate; + } + + /** Set the profile flag for PCMWAVEFORMAT. */ + public void setPCMWaveFormat(boolean b) + { + flagPCMWaveFormat = b; + } + + /** Set the profile flag for WAVEFORMATEX. */ + public void setWaveFormatEx(boolean b) + { + flagWaveFormatEx = b; + } + + /** Set the profile flag for WAVEFORMATEXTENSIBLE. */ + public void setWaveFormatExtensible(boolean b) + { + flagWaveFormatExtensible = b; + } + + /** Set the profile flag for Broadcast Wave. */ + public void setBroadcastWave (boolean b) + { + flagBroadcastWave = b; + } + + /** Set the version from the Broadcast Extension chunk. */ + public void setBroadcastVersion (int version) + { + broadcastVersion = version; + } + + /** + * Initializes the state of the module for parsing. + */ + protected void initParse() + { + super.initParse (); + _propList = new LinkedList (); + _notes = new LinkedList (); + _labels = new LinkedList (); + _labeledText = new LinkedList (); + _samples = new LinkedList (); + firstSampleOffsetMarked = false; + numSamples = 0; + + _metadata = new Property ("WAVEMetadata", + PropertyType.PROPERTY, + PropertyArity.LIST, + _propList); + _aesMetadata = new AESAudioMetadata (); + _aesMetadata.setByteOrder (AESAudioMetadata.LITTLE_ENDIAN); + _aesMetadata.setAnalogDigitalFlag("FILE_DIGITAL"); + _aesMetadata.setFormat ("WAVE"); + _aesMetadata.setUse ("OTHER", "JHOVE_validation"); + _aesMetadata.setDirection ("NONE"); + + _propList.add (new Property ("AESAudioMetadata", + PropertyType.AESAUDIOMETADATA, + _aesMetadata)); + + // Most chunk types are allowed to occur only once, + // and a few must occur exactly once. + // Clear flags for whether they have been seen. + formatChunkSeen = false; + dataChunkSeen = false; + instrumentChunkSeen = false; + cartChunkSeen = false; + mpegChunkSeen = false; + broadcastExtChunkSeen = false; + peakChunkSeen = false; + linkChunkSeen = false; + cueChunkSeen = false; + + // Initialize profile flags + flagPCMWaveFormat = false; + flagWaveFormatEx = false; + flagWaveFormatExtensible = false; + flagBroadcastWave = false; + } + + /** Reads a WAVE Chunk. + * + */ + protected boolean readChunk (RepInfo info) throws IOException + { + Chunk chunk = null; + ChunkHeader chunkh = new ChunkHeader (this, info); + if (!chunkh.readHeader(_dstream)) { + return false; + } + int chunkSize = (int) chunkh.getSize (); + bytesRemaining -= chunkSize + 8; + + if (bytesRemaining < 0) { + info.setMessage (new ErrorMessage + ("Invalid chunk size", _nByte)); + return false; + } + + String id = chunkh.getID (); + if ("fmt ".equals (id)) { + if (formatChunkSeen) { + dupChunkError (info, "Format"); + } + chunk = new FormatChunk (this, chunkh, _dstream); + formatChunkSeen = true; + } + else if ("data".equals (id)) { + if (dataChunkSeen) { + dupChunkError (info, "Data"); + } + chunk = new DataChunk (this, chunkh, _dstream); + dataChunkSeen = true; + } + else if ("fact".equals (id)) { + chunk = new FactChunk (this, chunkh, _dstream); + factChunkSeen = true; + // Are multiple 'fact' chunks allowed? + } + else if ("note".equals (id)) { + chunk = new NoteChunk (this, chunkh, _dstream); + // Multiple note chunks are allowed + } + else if ("labl".equals (id)) { + chunk = new LabelChunk (this, chunkh, _dstream); + // Multiple label chunks are allowed + } + else if ("list".equals (id)) { + chunk = new AssocDataListChunk (this, chunkh, _dstream, info); + // Are multiple chunks allowed? Who knows? + } + else if ("LIST".equals (id)) { + chunk = new ListInfoChunk (this, chunkh, _dstream, info); + // Multiple list chunks must be OK, since there can + // be different types, e.g., an INFO list and an exif list. + } + else if ("smpl".equals (id)) { + chunk = new SampleChunk (this, chunkh, _dstream); + // Multiple sample chunks are allowed -- I think + } + else if ("inst".equals (id)) { + if (instrumentChunkSeen) { + dupChunkError (info, "Instrument"); + } + chunk = new InstrumentChunk (this, chunkh, _dstream); + // Only one instrument chunk is allowed + instrumentChunkSeen = true; + } + else if ("mext".equals (id)) { + if (mpegChunkSeen) { + dupChunkError (info, "MPEG"); + } + chunk = new MpegChunk (this, chunkh, _dstream); + // I think only one MPEG chunk is allowed + mpegChunkSeen = true; + } + else if ("cart".equals (id)) { + if (cartChunkSeen) { + dupChunkError (info, "Cart"); + } + chunk = new CartChunk (this, chunkh, _dstream); + cartChunkSeen = true; + } + else if ("bext".equals (id)) { + if (broadcastExtChunkSeen) { + dupChunkError (info, "Broadcast Audio Extension"); + } + chunk = new BroadcastExtChunk (this, chunkh, _dstream); + broadcastExtChunkSeen = true; + } + else if ("levl".equals (id)) { + if (peakChunkSeen) { + dupChunkError (info, "Peak Envelope"); + } + chunk = new PeakEnvelopeChunk (this, chunkh, _dstream); + peakChunkSeen = true; + } + else if ("link".equals (id)) { + if (linkChunkSeen) { + dupChunkError (info, "Link"); + } + chunk = new LinkChunk (this, chunkh, _dstream); + linkChunkSeen = true; + } + else if ("cue ".equals (id)) { + if (cueChunkSeen) { + dupChunkError (info, "Cue"); + } + chunk = new CueChunk (this, chunkh, _dstream); + cueChunkSeen = true; + } + else { + info.setMessage (new InfoMessage + ("Chunk type '" + id + "' ignored", _nByte)); + } + + if (chunk != null) { + try { + if (!chunk.readChunk (info)) { + return false; + } + } + catch (JhoveException e) { + info.setMessage(new ErrorMessage (e.getMessage())); + info.setWellFormed (false); + return false; + } + } + else { + // Other chunk types are legal, just skip over them + skipBytes (_dstream, chunkSize, this); + } + + if ((chunkSize & 1) != 0) { + // Must come out to an even byte boundary + skipBytes (_dstream, 1, this); + --bytesRemaining; + } + return true; + } + + /** Returns the module's AES metadata. */ + public AESAudioMetadata getAESMetadata () + { + return _aesMetadata; + } + + /* Factor out the reporting of duplicate chunks. */ + protected void dupChunkError (RepInfo info, String chunkName) + { + info.setMessage (new ErrorMessage + ("Multiple " + chunkName + " Chunks not permitted", + _nByte)); + info.setValid (false); + } + + /** General function for adding a property with a 32-bit + * value, with two arrays of Strings to interpret + * 0 and 1 values as a bitmask. + * + * @param val The bitmask + * @param name The name for the Property + * @param oneValueNames Array of names to use for '1' values + * @param zeroValueNames Array of names to use for '0' values + */ + public Property buildBitmaskProperty (int val, String name, + String [] oneValueNames, + String [] zeroValueNames) + { + if (_je != null && _je.getShowRawFlag ()) { + return new Property (name, + PropertyType.INTEGER, + new Integer (val)); + } + else { + List slist = new LinkedList (); + try { + for (int i = 0; i < oneValueNames.length; i++) { + String s = null; + if ((val & (1 << i)) != 0) { + s = oneValueNames[i]; + } + else { + s = zeroValueNames[i]; + } + if (s != null && s.length() > 0) { + slist.add (s); + } + } + } + catch (Exception e) { + return null; + } + return new Property (name, PropertyType.STRING, + PropertyArity.LIST, slist); + } + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/XmlModule.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/XmlModule.java new file mode 100644 index 00000000..283f6757 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/XmlModule.java @@ -0,0 +1,1167 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004-2007 by JSTOR and the President and Fellows of Harvard College + + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module; + +import java.io.*; +import java.util.*; +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.xml.*; +import edu.harvard.hul.ois.jhove.module.html.HtmlMetadata; +import edu.harvard.hul.ois.jhove.module.html.DTDMapper; + +import org.xml.sax.XMLReader; +import javax.xml.parsers.SAXParserFactory; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.helpers.*; + +/** + * Module for identification and validation of XML files. + * @author Gary McGath + */ +public class XmlModule + extends ModuleBase +{ + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + + private static final String NAME = "XML-hul"; + private static final String RELEASE = "1.4"; + private static final int [] DATE = {2007, 1, 8}; + private static final String [] FORMAT = { + "XML", "XHTML" + }; + private static final String COVERAGE = "XML 1.0"; + /* According to RFC 3023, text/xml should be used for human-readable + * XML documents, and application/xml should be used for documents + * that aren't easily read by humans. Since that determination + * is beyond the scope of this project, we err on the side of + * pessimism and use application/xml as the primary MIME type. + * MIMETYPE[2] is only for XHTML. */ + private static final String [] MIMETYPE = { + "text/xml", "application/xml", "text/html" + }; + private static final String WELLFORMED = "An XML file is well-formed if " + + "it meets the criteria defined in Section 2.1 of the XML " + + "specification (W3C Recommendation, 3rd edition, 2004-02-04)"; + private static final String VALIDITY = "An XML file is valid if " + + "well-formed, and the file has an associated DTD or XML Schema and " + + "the file meets the constraints defined by that DTD or Schema"; + private static final String REPINFO = "Additional representation " + + "information includes: version, endcoding, standalone flag, DTD or " + + "schema, namespaces, notations, character references, entities, " + + "processing instructions, and comments"; + private static final String NOTE = "This module determines " + + "well-formedness and validity using the SAX2-conforming parser " + + "specified by the invoking application"; + private static final String RIGHTS = "Copyright 2004-2007 by JSTOR and " + + "the President and Fellows of Harvard College. " + + "Released under the GNU Lesser General Public License."; + + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + /* Checksummer object */ + protected Checksummer _ckSummer; + + /* Input stream wrapper which handles checksums */ + protected ChecksumInputStream _cstream; + + /* Data input stream wrapped around _cstream */ + protected DataInputStream _dstream; + + /* Top-level property list. */ + protected List _propList; + + /* Top-level property. */ + protected Property _metadata; + + /* Doctype for XHTML documents only, otherwise null. */ + protected String _xhtmlDoctype; + + /* Base URL for DTD's. If null, all DTD URL's are absolute. */ + protected String _baseURL; + + /* Flag to control signature checking behavior. If true, + * checkSignatures insists on an XML document declaration; if + * false, it will parse the file if there is no document + * declaration. + */ + protected boolean _sigWantsDecl; + + /* Flag to indicate we're invoking the parser from checkSignatures. + * When true, it's up to checkSignatures to mark a signature as present. + */ + protected boolean _parseFromSig; + + /* Flag to know if the property TextMDMetadata is to be added */ + protected boolean _withTextMD = false; + /* Hold the information needed to generate a textMD metadata fragment */ + protected TextMDMetadata _textMD; + + /* Map from URIs to locally stored schemas */ + protected Map _localSchemas; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + /** + * Instantiate an XmlModule object. + */ + public XmlModule () + { + super (NAME, RELEASE, DATE, FORMAT, COVERAGE, MIMETYPE, WELLFORMED, + VALIDITY, REPINFO, NOTE, RIGHTS, false); + + Agent agent = new Agent ("Harvard University Library", + AgentType.EDUCATIONAL); + agent.setAddress ("Office for Information Systems, " + + "90 Mt. Auburn St., " + + "Cambridge, MA 02138"); + agent.setTelephone ("+1 (617) 495-3724"); + agent.setEmail("jhove-support@hulmail.harvard.edu"); + _vendor = agent; + + Document doc = new Document ("Extensible Markup Language (XML) 1.0 " + + "(Third Edition)", DocumentType.REPORT); + agent = new Agent ("Word Wide Web Consortium", AgentType.NONPROFIT); + agent.setAddress ("Massachusetts Institute of Technology, " + + "Computer Science and Artificial Intelligence Laboratory, " + + "32 Vassar Street, Room 32-G515, " + + "Cambridge, MA 02139"); + agent.setTelephone ("(617) 253-2613"); + agent.setFax ("(617) 258-5999"); + agent.setWeb ("http://www.w3.org/"); + doc.setPublisher (agent); + doc.setDate ("2004-02-04"); + doc.setIdentifier (new Identifier ("http://www.w3.org/TR/REC-xml", + IdentifierType.URL)); + _specification.add (doc); + + doc = new Document ("SAX", DocumentType.WEB); + doc.setIdentifier (new Identifier ("http://sax.sourceforge.net/", + IdentifierType.URL)); + _specification.add (doc); + + Signature sig = new ExternalSignature (".xml", SignatureType.EXTENSION, + SignatureUseType.OPTIONAL); + _signature.add (sig); + _localSchemas = new HashMap (); + } + + /** Sets the value of the doctype string, assumed to have been forced + * to upper case. This is set only when the HTML module invokes the + * XML module for an XHTML document. */ + public void setXhtmlDoctype (String doctype) + { + _xhtmlDoctype = doctype; + if (_textMD != null) { + _textMD.setMarkup_language(_xhtmlDoctype); + } + } + + /** Reset parameter settings. + * Returns to a default state without any parameters. + */ + public void resetParams () + throws Exception + { + _baseURL = null; + _sigWantsDecl = false; + _parseFromSig = false; + } + + /** + * Per-action initialization. + * + * @param param The module parameter; under command-line Jhove, the -p parameter. + * If the parameter starts with "schema", then the part to the + * right of the equal sign identifies a URI with a local path + * (URI, then semicolon, then path). + * If the first character is 's' and the parameter isn't "schema", + * then signature checking requires + * a document declaration, and the rest of the URL is considered + * as follows. + * If the parameter begins with 'b' or 'B', then the remainder of + * the parameter is used as a base URL. Otherwise it is ignored, + * and there is no base URL. + */ + public void param (String param) + { + if (param != null) { + param = param.toLowerCase (); + if (param.toLowerCase ().startsWith("schema=")) { + addLocalSchema(param); + } + else if (param.indexOf ('s') == 0) { + _sigWantsDecl = true; + param = param.substring(1); + } + else if (param.indexOf ('b') == 0) { + _baseURL = param.substring (1); + } + } + } + + + /** + * Parse the content of a purported XML digital object and store the + * results in RepInfo. + * + * This is designed to be called in two passes. On the first pass, + * a nonvalidating parse is done. If this succeeds, and the presence + * of DTD's or schemas is detected, then parse returns 1 so that it + * will be called again to do a validating parse. If there is nothing + * to validate, we consider it "valid." + * + * @param stream An InputStream, positioned at its beginning, + * which is generated from the object to be parsed. + * If multiple calls to parse are made + * on the basis of a nonzero value being returned, + * a new InputStream must be provided each time. + * + * @param info A fresh (on the first call) RepInfo object + * which will be modified + * to reflect the results of the parsing + * If multiple calls to parse are made + * on the basis of a nonzero value being returned, + * the same RepInfo object should be passed with each + * call. + * + * @param parseIndex Must be 0 in first call to parse. If + * parse returns a nonzero value, it must be + * called again with parseIndex + * equal to that return value. + */ + public int parse (InputStream stream, RepInfo info, int parseIndex) + throws IOException + { + // Test if textMD is to be generated + + if (_defaultParams != null) { + _withTextMD = false; + Iterator iter = _defaultParams.iterator (); + while (iter.hasNext ()) { + String param = iter.next (); + if (param.toLowerCase ().equals ("withtextmd=true")) { + _withTextMD = true; + } + } + } + + //boolean foundDTD = false; + boolean canValidate = true; + initParse (); + info.setFormat (_format[0]); + info.setMimeType (_mimeType[0]); + info.setModule (this); + if (_textMD == null || parseIndex == 0) { + _textMD = new TextMDMetadata(); + _xhtmlDoctype = null; + } + + /* We may have already done the checksums while converting a + temporary file. */ + _ckSummer = null; + if (_je != null && _je.getChecksumFlag () && + info.getChecksum ().size () == 0) { + _ckSummer = new Checksummer (); + } + _cstream = new ChecksumInputStream (stream, _ckSummer); + + _propList = new LinkedList (); + _metadata = new Property ("XMLMetadata", + PropertyType.PROPERTY, + PropertyArity.LIST, + _propList); + + XMLReader parser = null; + InputSource src = null; + XmlModuleHandler handler = null; + XmlLexicalHandler lexHandler = new XmlLexicalHandler (); + XmlDeclHandler declHandler = new XmlDeclHandler (); + + // The XmlDeclStream filters the characters, looking for an + // XML declaration, since there's no way to get that info + // out of SAX. + XmlDeclStream xds = new XmlDeclStream (_cstream); + try { + // Create an InputSource to feed the parser. + // If a SAX class was specified, use it, otherwise use + // the default parser. + src = new InputSource (xds); + // setSystemId may be helpful in resolving relative URI's, + // though its use is unclear. Its actual content is merely + // informative, not a part of any actual link + //src.setSystemId ("http://hul.harvard.edu/hul"); + if (_baseURL != null) { + src.setSystemId(new File(_baseURL).toURI().toURL().toString()); + } + String saxClass = _je.getSaxClass(); + if (saxClass == null) { + SAXParserFactory factory = + SAXParserFactory.newInstance(); + factory.setNamespaceAware (true); + parser = factory.newSAXParser ().getXMLReader (); + } + else { + parser = XMLReaderFactory.createXMLReader (saxClass); + } + handler = new XmlModuleHandler (); + handler.setXhtmlFlag (_xhtmlDoctype != null); + handler.setLocalSchemas (_localSchemas); + parser.setContentHandler (handler); + parser.setErrorHandler (handler); + parser.setEntityResolver (handler); + parser.setDTDHandler (handler); + try { + parser.setProperty + ("http://xml.org/sax/properties/lexical-handler", + lexHandler); + } + catch (SAXException e) { + info.setMessage (new InfoMessage + ("The XML implementation in use does not " + + "support the LexicalHandler interface. " + + "This may result in some properties not being reported.")); + } + try { + parser.setProperty + ("http://xml.org/sax/properties/declaration-handler", + declHandler); + } + catch (SAXException e) { + info.setMessage (new InfoMessage + ("The XML implementation in use does not " + + "support the DeclHandler interface. " + + "This may result in some properties not being reported.")); + } + + } + catch (Exception f) { + info.setMessage(new ErrorMessage (f.getMessage())); + info.setWellFormed (false); // actually not the file's fault + return 0; + } + try { + // On the first pass, we parse without validation. + parser.setFeature ("http://xml.org/sax/features/validation", + parseIndex == 0 ? false : true); + } + catch (SAXException se) { + if (parseIndex != 0) { + info.setMessage (new InfoMessage + ("The SAX parser is not capable of validation.")); + } + canValidate = false; + } + try { + parser.setFeature ("http://xml.org/sax/features/namespaces", + true); + } + catch (SAXException se) { + info.setMessage (new InfoMessage + ("The SAX parser does not support namespaces.")); + } + // This property for supporting schemas is a JAXP 1.2 + // recommendation, not likely to be supported widely as + // of this (February 2004) writing, and not supported in + // standard Crimson. But it looks like the way to prepare + // for schema validation in the future, and at least the + // info message will tell users why they're getting bogus + // invalid status. + + // Try 2 different ways of setting schema validation; + // it appears that no one way works for all parsers. + if (parseIndex > 0) { + try { + parser.setFeature("http://apache.org/xml/features/validation/schema", + true); + } + catch (SAXException ee) { + try { + parser.setProperty + ("http://java.sun.com/xml/jaxp/properties/schemaLanguage", + "http://www.w3.org/2001/XMLSchema"); + } + catch (SAXException e) { + info.setMessage (new InfoMessage + ("The XML implementation in use does not " + + "support schema language identification. This " + + "may result in documents specified by schemas " + + "being reported as invalid.")); + } + } + } + try { + parser.parse (src); + } + catch (FileNotFoundException ef) { + // Make this particular exception a little more user-friendly + info.setMessage (new ErrorMessage + ("File not found", + ef.getMessage ().toString ())); + info.setWellFormed (false); + return 0; + } + catch (UTFDataFormatException u) { + if (handler.getSigFlag () && !_parseFromSig) { + info.setSigMatch(_name); + } + info.setMessage (new ErrorMessage ("Invalid character encoding")); + info.setWellFormed (false); + return 0; + } + catch (IOException e) { + // We may get an IOException from trying to resolve an + // external entity. + if (handler.getSigFlag () && !_parseFromSig) { + info.setSigMatch(_name); + } + info.setMessage (new ErrorMessage + (e.getClass().getName() + ": " + + e.getMessage ().toString ())); + info.setWellFormed (false); + return 0; + } + catch (SAXParseException e) { + // Document failed to parse. + if (handler.getSigFlag () && !_parseFromSig) { + info.setSigMatch(_name); + } + int line = e.getLineNumber(); + int col = e.getColumnNumber(); + info.setMessage (new ErrorMessage + (e.getMessage ().toString (), + "Line = " + line + ", Column = " + col)); + info.setWellFormed (false); + return 0; + } + catch (SAXException e) { + // Other SAX error. + if (handler.getSigFlag ()) { + info.setSigMatch(_name); + } + // Sometimes the message will be null and another message + // wrapped inside it. Try to report that. + String msg = e.getMessage (); + if (msg == null) { + Throwable ee = e.getCause(); + if (ee != null) { + msg = "SAXException, cause = " + + ee.getClass().getName(); + } + else { + msg = "Unspecified SAXException"; + } + } + info.setMessage (new ErrorMessage (msg)); + info.setWellFormed (false); + return 0; + } + + // Check if user has aborted + if (_je.getAbort ()) { + return 0; + } + + if (handler.getSigFlag () && parseIndex == 0) { + info.setSigMatch(_name); + } + // If it's the first pass, check if we found a DTD + // or schema. + // If so, reparse with validation enabled. + // (Validation with schemas may prove futile, as the + // Crimson parser understands only DTD and DOCTYPE + // declarations as contributing to validity.) + String dtdURI = handler.getDTDURI (); + List schemaList = handler.getSchemas (); + + // In order to find the "primary" markup language, we try 3 things : + // 1/ first, the first NamespaceURI + // 3/ then, the first SchemaLocation + // 1/ finally, the dtd URI + // It should be noted that latter on when we look at the namespace in relation with the Root element + // if a URI is defined with it, it will get the preference ... + if (!schemaList.isEmpty()) { + SchemaInfo schItems = schemaList.get(0); + // First NamespaceURI + if (isNotEmpty(schItems.namespaceURI)) { + _textMD.setMarkup_language(schItems.namespaceURI); + // Then SchemaLocation + } + else if (isNotEmpty(schItems.location)) { + _textMD.setMarkup_language(schItems.location); + } + } + else if (isNotEmpty(dtdURI)) { + _textMD.setMarkup_language(dtdURI); + } + + if (parseIndex == 0) { + if ((handler.getDTDURI () != null || + !schemaList.isEmpty ()) && + canValidate) { + return 1; + } + else { + info.setValid (RepInfo.UNDETERMINED); + // This may get downgraded to false, but won't + // be upgraded to true. + } + } + + // Take a deep breath. We parsed it. Now assemble the + // properties. + info.setProperty (_metadata); + + // If it's XHTML, add the HTML property. + HtmlMetadata hMetadata = handler.getHtmlMetadata (); + if (hMetadata != null) { + info.setProperty (hMetadata.toProperty (_withTextMD?_textMD:null)); + } + + // Report the parser in a property. + _propList.add (new Property ("Parser", + PropertyType.STRING, + parser.getClass().getName())); + + // Add the version property. Give precedence to XHTML doctype. + String vers = null; + if (_xhtmlDoctype != null) { + vers = DTDMapper.getXHTMLVersion(_xhtmlDoctype); + _textMD.setMarkup_language_version(vers); + } + if (vers != null) { + info.setVersion (vers); + } + else { + vers = xds.getVersion (); + if (vers != null) { + info.setVersion (vers); + } + } + _textMD.setMarkup_basis_version(vers); + + // Add the encoding property. + String encoding = xds.getEncoding (); + if (encoding == null) { + // If no explicit encoding, use default (Bugzilla 136) + encoding = "UTF-8"; + } + _propList.add (new Property ("Encoding", + PropertyType.STRING, + encoding)); + + _textMD.setCharset(encoding); + String textMDEncoding = _textMD.getCharset(); + if (textMDEncoding.indexOf("UTF") != -1) { + _textMD.setByte_order( + _bigEndian?TextMDMetadata.BYTE_ORDER_BIG:TextMDMetadata.BYTE_ORDER_LITTLE); + _textMD.setByte_size("8"); + _textMD.setCharacter_size("variable"); + } + else { + _textMD.setByte_order( + _bigEndian?TextMDMetadata.BYTE_ORDER_BIG:TextMDMetadata.BYTE_ORDER_LITTLE); + _textMD.setByte_size("8"); + _textMD.setCharacter_size("1"); + } + // CRLF from XmlDeclStream ... + String lineEnd = xds.getKindOfLineEnd(); + if (lineEnd == null) { + info.setMessage(new InfoMessage("Not able to determine type of end of line")); + _textMD.setLinebreak(TextMDMetadata.NILL); + } else if (lineEnd.equalsIgnoreCase("CR")) { + _textMD.setLinebreak(TextMDMetadata.LINEBREAK_CR); + } else if (lineEnd.equalsIgnoreCase("LF")) { + _textMD.setLinebreak(TextMDMetadata.LINEBREAK_LF); + } else if (lineEnd.equalsIgnoreCase("CRLF")) { + _textMD.setLinebreak(TextMDMetadata.LINEBREAK_CRLF); + } + + // Add the standalone property. + String sa = xds.getStandalone (); + if (sa != null) { + _propList.add (new Property ("Standalone", + PropertyType.STRING, + sa)); + } + + // Add the DTD property. + if (dtdURI != null) { + _propList.add (new Property ("DTD_URI", + PropertyType.STRING, + dtdURI)); + } + + if (!schemaList.isEmpty ()) { + // Build a List of Properties, which will be the value + // of the Schemas Property. + List schemaPropList = new ArrayList (schemaList.size()); + ListIterator iter = schemaList.listIterator(); + // Iterate through all the schemas. + while (iter.hasNext ()) { + SchemaInfo schItems = iter.next (); + // Build a Property (Schema) whose value is an array + // of two Properties (NamespaceURI and SchemaLocation). + Property [] schItemProps = new Property[2]; + schItemProps[0] = new Property ("NamespaceURI", + PropertyType.STRING, + schItems.namespaceURI); + schItemProps[1] = new Property ("SchemaLocation", + PropertyType.STRING, + schItems.location); + schemaPropList.add (new Property ("Schema", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + schItemProps)); + } + // Now put the list into a Property, which goes into + // the metadata. + Property prop = new Property ("Schemas", + PropertyType.PROPERTY, + PropertyArity.LIST, + schemaPropList); + _propList.add (prop); + } + + // Add the root element. + String root = handler.getRoot (); + String rootPrefix = null; + if (root != null) { + _propList.add (new Property ("Root", + PropertyType.STRING, + root)); + if ("html".equals (root)) { + // Specify format as XHTML + info.setFormat (_format[1]); + // Set the version according to the doctype... how? + + } + // Get the prefix of root + int indexOfColon = root.indexOf(':'); + if (indexOfColon != -1) { + rootPrefix = root.substring(0, indexOfColon); + } + } + if (rootPrefix == null) { + rootPrefix = ""; + } + + // Declare properties we're going to add. They have + // some odd interdependencies, so we create them all + // and them add them in the right (specified) order. + Property namespaceProp = null; + Property notationsProp = null; + Property charRefsProp = null; + Property entitiesProp = null; + Property procInstProp = null; + Property commentProp = null; + Property unicodeBlocksProp = null; + + Map ns = handler.getNamespaces (); + if (!ns.isEmpty ()) { + Set keys = ns.keySet (); + List nsList = new ArrayList (keys.size()); + Iterator iter = keys.iterator(); + while (iter.hasNext ()) { + String key = iter.next (); + String val = (String) ns.get (key); + Property [] supPropArr = new Property[2]; + supPropArr[0] = new Property ("Prefix", + PropertyType.STRING, + key); + supPropArr[1] = new Property ("URI", + PropertyType.STRING, + val); + Property onens = new Property ("Namespace", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + supPropArr); + nsList.add (onens); + + // Try to find the namespace URI of root + if (rootPrefix.equalsIgnoreCase(key) && isNotEmpty(val)) { + _textMD.setMarkup_language(val); + } + } + namespaceProp = new Property ("Namespaces", + PropertyType.PROPERTY, + PropertyArity.LIST, + nsList); + } + + // CharacterReferences property goes here. + // Report as a list of 4-digit hexadecimal strings, + // e.g., 003C, 04AA, etc. + // Also build the Unicode blocks here. + List refs = xds.getCharacterReferences (); + if (!refs.isEmpty ()) { + Utf8BlockMarker utf8BM = new Utf8BlockMarker (); + List refList = new ArrayList (refs.size ()); + ListIterator iter = refs.listIterator (); + while (iter.hasNext ()) { + Integer refi = iter.next (); + int refint = refi.intValue (); + refList.add (intTo4DigitHex (refint)); + utf8BM.markBlock(refint); + } + charRefsProp = new Property + ("CharacterReferences", + PropertyType.STRING, + PropertyArity.LIST, + refList); + unicodeBlocksProp = + utf8BM.getBlocksUsedProperty("UnicodeCharRefBlocks"); + } + + // Entities property + // External unparsed entities + Set entNames = lexHandler.getEntityNames (); + Set attributeVals = handler.getAttributeValues (); + List entProps = new LinkedList (); + List uent = handler.getUnparsedEntities (); + List unparsedNotationNames = new LinkedList (); + if (!uent.isEmpty ()) { + ListIterator iter = uent.listIterator (); + //int i = 0; + while (iter.hasNext ()) { + // We check external parsed entities against + // the list of attribute values which we've + // accumulated. If a parsed entity name matches an + // attribute value, we assume it's used. + String[] entarr = (String[]) iter.next (); + String name = entarr[0]; + if (nameInCollection (name, attributeVals)) { + // Add the notation name to the list + // unparsedNotationNames, so we can use it + // in determining which notations are used. + unparsedNotationNames.add (entarr[3]); + List subPropList = new ArrayList (6); + subPropList.add( new Property ("Name", + PropertyType.STRING, + name)); + subPropList.add (new Property ("Type", + PropertyType.STRING, + "External unparsed")); + subPropList.add( new Property ("PublicID", + PropertyType.STRING, + entarr[1])); + subPropList.add( new Property ("SystemID", + PropertyType.STRING, + entarr[2])); + subPropList.add( new Property ("NotationName", + PropertyType.STRING, + entarr[3])); + + entProps.add (new Property ("Entity", + PropertyType.PROPERTY, + PropertyArity.LIST, + subPropList)); + } + } + } + + // Internal entities + List declEnts = declHandler.getInternalEntityDeclarations (); + if (!declEnts.isEmpty ()) { + ListIterator iter = declEnts.listIterator (); + while (iter.hasNext ()) { + String[] entarr = iter.next (); + String name = entarr[0]; + // include only if the entity was actually used + if (nameInCollection (name, entNames)) { + List subPropList = new ArrayList (4); + subPropList.add (new Property ("Name", + PropertyType.STRING, + name)); + subPropList.add (new Property ("Type", + PropertyType.STRING, + "Internal")); + subPropList.add (new Property ("Value", + PropertyType.STRING, + entarr[1])); + entProps.add (new Property ("Entity", + PropertyType.PROPERTY, + PropertyArity.LIST, + subPropList)); + } + } + } + + // External parsed entities + declEnts = declHandler.getExternalEntityDeclarations (); + if (!declEnts.isEmpty ()) { + ListIterator iter = declEnts.listIterator (); + while (iter.hasNext ()) { + String[] entarr = iter.next (); + String name = entarr[0]; + // include only if the entity was actually used + if (nameInCollection (name, entNames)) { + List subPropList = new ArrayList (4); + subPropList.add (new Property ("Name", + PropertyType.STRING, + name)); + subPropList.add (new Property ("Type", + PropertyType.STRING, + "External parsed")); + if (entarr[1] != null) { + subPropList.add (new Property ("PublicID", + PropertyType.STRING, + entarr[1])); + } + if (entarr[2] != null) { + subPropList.add (new Property ("SystemID", + PropertyType.STRING, + entarr[2])); + } + + entProps.add (new Property ("Entity", + PropertyType.PROPERTY, + PropertyArity.LIST, + subPropList)); + } + } + } + + if (!entProps.isEmpty ()) { + entitiesProp = new Property ("Entities", + PropertyType.PROPERTY, + PropertyArity.LIST, + entProps); + } + + List pi = handler.getProcessingInstructions (); + List piTargets = new LinkedList (); + if (!pi.isEmpty()) { + // Build a property, which consists of a list + // of properties, each of which is an array of + // two String properties, named Target and + // Data respectively. + List piPropList = new ArrayList (pi.size()); + ListIterator pii = pi.listIterator (); + while (pii.hasNext ()) { + ProcessingInstructionInfo pistr = pii.next (); + Property[] subPropArr = new Property[2]; + // Accumulate targets in a list, so we can tell + // which Notations use them. + // Wait a minute -- what we're doing here can't work!! TODO what's supposed to be happening? + //piTargets.add (subPropArr[0]); + subPropArr[0] = new Property ("Target", + PropertyType.STRING, + pistr.target); + subPropArr[1] = new Property ("Data", + PropertyType.STRING, + pistr.data); + piPropList.add(new Property ("ProcessingInstruction", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + subPropArr)); + } + procInstProp = new Property ("ProcessingInstructions", + PropertyType.PROPERTY, + PropertyArity.LIST, + piPropList); + } + + // Notations property. We list notations only if they're + // "actually used," meaning that they designate either + // the target of a processing instruction or the ndata + // of an unparsed entry which is itself "actually used." + List notations = handler.getNotations (); + if (!notations.isEmpty ()) { + List notProps = new ArrayList (notations.size ()); + ListIterator iter = notations.listIterator (); + List subPropList = new ArrayList (3); + while (iter.hasNext ()) { + String[] notArray = iter.next(); + String notName = notArray[0]; + // Check for use of Notation before including + // TODO this is implemented wrong! Need to reinvestigate + if (nameInCollection (notName, piTargets) || + nameInCollection (notName, unparsedNotationNames)) { + // notArray has name, public ID, system ID + subPropList.add (new Property ("Name", + PropertyType.STRING, + notName)); + if (notArray[1] != null) { + subPropList.add (new Property ("PublicID", + PropertyType.STRING, + notArray[1])); + } + if (notArray[2] != null) { + subPropList.add (new Property ("SystemID", + PropertyType.STRING, + notArray[2])); + } + notProps.add (new Property ("Notation", + PropertyType.PROPERTY, + PropertyArity.LIST, + subPropList)); + } + } + // Recheck emptiness in case only unprocessed notations were found + if (!notProps.isEmpty()) { + notationsProp = new Property ("Notations", + PropertyType.PROPERTY, + PropertyArity.LIST, + notProps); + } + } + + // Now add all the properties we created. + if (namespaceProp != null) { + _propList.add (namespaceProp); + } + if (notationsProp != null) { + _propList.add (notationsProp); + } + if (charRefsProp != null) { + _propList.add (charRefsProp); + } + if (unicodeBlocksProp != null) { + _propList.add (unicodeBlocksProp); + } + if (entitiesProp != null) { + _propList.add (entitiesProp); + } + if (procInstProp != null) { + _propList.add (procInstProp); + } + + List comm = lexHandler.getComments (); + if (!comm.isEmpty ()) { + commentProp = new Property ("Comments", + PropertyType.STRING, + PropertyArity.LIST, + comm); + } + if (commentProp != null) { + _propList.add (commentProp); + } + + // Check if parse detected invalid XML + if (!handler.isValid ()) { + info.setValid (false); + } + + if (info.getWellFormed () == RepInfo.TRUE) { + if (_xhtmlDoctype != null) { + info.setMimeType (_mimeType[2]); + } + else { + info.setMimeType (_mimeType[0]); + } + } + + // Add any messages from the parse. + List msgs = handler.getMessages (); + ListIterator msgi = msgs.listIterator (); + while (msgi.hasNext ()) { + info.setMessage ((Message) msgi.next ()); + } + + if (_withTextMD) { + _textMD.setMarkup_basis(info.getFormat()); + _textMD.setMarkup_basis_version(info.getVersion()); + Property property = new Property ("TextMDMetadata", + PropertyType.TEXTMDMETADATA, PropertyArity.SCALAR, _textMD); + _propList.add(property); + } + + if (_ckSummer != null){ + info.setChecksum (new Checksum (_ckSummer.getCRC32 (), + ChecksumType.CRC32)); + String value = _ckSummer.getMD5 (); + if (value != null) { + info.setChecksum (new Checksum (value, ChecksumType.MD5)); + } + if ((value = _ckSummer.getSHA1 ()) != null) { + info.setChecksum (new Checksum (value, ChecksumType.SHA1)); + } + } + if (info.getVersion () == null) { + info.setVersion ("1.0"); + _textMD.setMarkup_basis_version("1.0"); + } + return 0; + } + + + /** + * Check if the digital object conforms to this Module's + * internal signature information. + * + * XML is a particularly messy case; in general, there's no + * even moderately good way to check "signatures" without parsing + * the whole file, since the document declaration is optional. + * We provide the user two choices, based on the "s" parameter. + * If 's' is the first character of the module parameter, then + * we look for an XML document declaration, and say there's no + * signature if it's missing. (This can reject well-formed + * XML files, though not valid ones.) Otherwise, if there's no + * document declaration, we parse the whole file. + * + * @param file A File object for the object being parsed + * @param stream An InputStream, positioned at its beginning, + * which is generated from the object to be parsed + * @param info A fresh RepInfo object which will be modified + * to reflect the results of the test + */ + public void checkSignatures (File file, + InputStream stream, + RepInfo info) + throws IOException + { + _parseFromSig = false; + info.setFormat (_format[0]); + info.setMimeType (_mimeType[0]); + info.setModule (this); + String sigStr = "= sigStr.length()) { + info.setSigMatch(_name); + return; // sig matches + } + } + else break; + } + } + catch (IOException e) { + info.setWellFormed (false); + return; + } + if (_sigWantsDecl) { + + // No XML declaration, and it's manadatory according to the param. + info.setWellFormed (false); + return; + } + + // No XML signature, but we're allowed to parse the file now. + // This means rewinding back to the start of the file. + int parseIndex = 1; + _parseFromSig = true; // we set the sig match ourselves + while (parseIndex != 0) { + stream.close (); + stream = new FileInputStream (file); + parseIndex = parse (stream, info, parseIndex); + } + if (info.getWellFormed() == RepInfo.TRUE) { + info.setSigMatch (_name); + } + } + + + + protected void initParse () + { + super.initParse (); +// if (_defaultParams != null) { +// Iterator iter = _defaultParams.iterator (); +// while (iter.hasNext ()) { +// String param = iter.next (); +// if (param.toLowerCase ().startsWith("localschema=")) { +// addLocalSchema(param); +// } +// } +// } + } + + /* Checks if a String is .equals to any member of a Set of strings. */ + protected static boolean nameInCollection (String name, Collection coll) + { + Iterator iter = coll.iterator (); + while (iter.hasNext ()) { + String s = (String) iter.next (); + if (name.equals (s)) { + return true; + } + } + return false; + } + + /* Converts an int to a 4-digit hex value, e.g., + * 003F or F10A. This is used for Character References. */ + protected static String intTo4DigitHex (int n) + { + StringBuffer buf = new StringBuffer(4); + for (int i = 3; i >= 0; i--) { + int d = (n >> (4 * i)) & 0XF; // extract a nybble + if (d < 10) { + buf.append ((char) ((int) '0' + d)); + } + else { + buf.append ((char) ((int) 'A' + (d - 10))); + } + } + return buf.toString (); + } + + /** + * Verification that the string contains something usefull. + * @param value string to test + * @return boolean + */ + protected static boolean isNotEmpty(String value) { + return ( + (value != null) && + (value.length() != 0) && + !("[None]".equals(value)) + ); + } + + /** + * Add a mapping from a schema URI to a local file. + * The parameter is of the form schema=[URI];[path] + */ + private void addLocalSchema (String param) { + int eq = param.indexOf('='); + int semi = param.indexOf(';'); + try { + String uri = param.substring(eq+1, semi).trim(); + String path = param.substring(semi + 1).trim(); + File f = new File (path); + if (f.exists()) { + _localSchemas.put (uri, f); + } + } + catch (Exception e) {} + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/AiffStrings.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/AiffStrings.java new file mode 100644 index 00000000..e3a482c4 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/AiffStrings.java @@ -0,0 +1,29 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.aiff; + +/** + * A class for holding arrays of informative strings that will go into + * properties of an AIFF object. + * + * @author Gary McGath + * + */ +public class AiffStrings { + + /** A private constructor just to make sure nobody + instantiates the class by mistake. */ + private AiffStrings () + { + } + + /** Strings for looping types in the Instrument Chunk */ + public final static String[] LOOP_TYPE = + { "No looping", + "Forward looping", + "Forward/backward looping" }; + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/AnnotationChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/AnnotationChunk.java new file mode 100644 index 00000000..724c62d3 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/AnnotationChunk.java @@ -0,0 +1,53 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.aiff; + +import java.io.*; +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.AiffModule; +import edu.harvard.hul.ois.jhove.module.iff.*; + +/** + * Implementation of the AIFF Annotation Chunk. + * + * @author Gary McGath + * + */ +public class AnnotationChunk extends TextChunk { + + /** + * Constructor. + * + * @param module The AIFFModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the AIFF data are being read + */ + public AnnotationChunk( + AiffModule module, + ChunkHeader hdr, + DataInputStream dstrm) { + super(module, hdr, dstrm); + } + + + /** Reads a chunk and adds an Annotation property to the + * module's list of annotations. + * + * There can be multiple Annotation Chunks, so we don't + * create a property here directly. + * + * @return false if the chunk is structurally + * invalid, otherwise true + */ + public boolean readChunk(RepInfo info) throws IOException { + String name = readText (); + ((AiffModule) _module).addAnnotation (new Property (propName, + PropertyType.STRING, + name)); + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/ApplicationChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/ApplicationChunk.java new file mode 100644 index 00000000..157b0df8 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/ApplicationChunk.java @@ -0,0 +1,84 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.aiff; + +import java.io.DataInputStream; +import java.io.IOException; + +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.AiffModule; +import edu.harvard.hul.ois.jhove.module.iff.Chunk; +import edu.harvard.hul.ois.jhove.module.iff.ChunkHeader; + +/** + * Implementation of the AIFF Application Chunk. + * + * @author Gary McGath + * + */ +public class ApplicationChunk extends Chunk { + + /** + * Constructor. + * + * @param module The AIFFModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the AIFF data are being read + */ + public ApplicationChunk( + AiffModule module, + ChunkHeader hdr, + DataInputStream dstrm) { + super(module, hdr, dstrm); + } + + /** Reads a chunk and puts an Application property into + * the RepInfo object. + * + * @return false if the chunk is structurally + * invalid, otherwise true + */ + public boolean readChunk(RepInfo info) throws IOException + { + AiffModule module = (AiffModule) _module; + String applicationSignature = module.read4Chars (_dstream); + byte[] data = new byte[(int) (bytesLeft - 4)]; + ModuleBase.readByteBuf (_dstream, data, _module); + Property[] propArr = new Property[2]; + propArr[0] = new Property ("ApplicationSignature", + PropertyType.STRING, + applicationSignature); + AESAudioMetadata aes = module.getAESMetadata (); + aes.setAppSpecificData(applicationSignature); + // If the application signature is 'pdos' or 'stoc', + // then the beginning of the data area is a Pascal + // string naming the application. Otherwise, we + // just report the raw data. ('pdos' is for Apple II + // applications, 'stoc' for the entire non-Apple world.) + if ("stoc".equals (applicationSignature) || + "pdos".equals (applicationSignature)) { + String appName = module.readPascalString(_dstream); + bytesLeft -= appName.length() + 1; + module.skipBytes (_dstream, (int) bytesLeft, module); + propArr[1] = new Property ("ApplicationName", + PropertyType.STRING, + appName); + } + else { + propArr[1] = new Property ("Data", + PropertyType.BYTE, + PropertyArity.ARRAY, + data); + } + module.addAiffProperty (new Property ("Application", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + propArr)); + + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/AudioRecChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/AudioRecChunk.java new file mode 100644 index 00000000..b5945e91 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/AudioRecChunk.java @@ -0,0 +1,68 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.aiff; + +import java.io.DataInputStream; +import java.io.IOException; +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.AiffModule; +import edu.harvard.hul.ois.jhove.module.iff.Chunk; +import edu.harvard.hul.ois.jhove.module.iff.ChunkHeader; + +/** + * Implementation of the AIFF Audio Recording Chunk. + * + * The data bytes are put into an uninterpreted byte array + * Property. These are specified in the AES Recommended + * Practice for Digital Audio Engineering - Serial Transmission + * Format for Linearly Represented Digital Audio Data, + * Section 7.1, Channel Status Data. + * + * @author Gary McGath + * + */ +public class AudioRecChunk extends Chunk { + + /** + * Constructor. + * + * @param module The AIFFModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the AIFF data are being read + */ + public AudioRecChunk( + AiffModule module, + ChunkHeader hdr, + DataInputStream dstrm) { + super(module, hdr, dstrm); + } + + /** Reads a chunk and puts an AudioRecording property into + * the RepInfo object. + * + * @return false if the chunk is structurally + * invalid, otherwise true + */ + public boolean readChunk(RepInfo info) throws IOException { + AiffModule module = (AiffModule) _module; + if (bytesLeft != 24) { + // This chunk must always have exactly 24 bytes data + info.setMessage (new ErrorMessage + ("Audio Recording Chunk is incorrect size", + module.getNByte ())); + info.setWellFormed (false); + return false; + } + byte[] buf = new byte[24]; + ModuleBase.readByteBuf (_dstream, buf, module); + module.addAiffProperty (new Property ("AudioRecording", + PropertyType.BYTE, + PropertyArity.ARRAY, + buf)); + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/AuthorChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/AuthorChunk.java new file mode 100644 index 00000000..87c9707b --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/AuthorChunk.java @@ -0,0 +1,37 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.aiff; + +import java.io.DataInputStream; +import edu.harvard.hul.ois.jhove.module.AiffModule; +import edu.harvard.hul.ois.jhove.module.iff.*; + +/** + * Implementation of the AIFF Author Chunk. + * + * @author Gary McGath + * + */ +public class AuthorChunk extends TextChunk { + + /** + * Constructor. + * + * @param module The AIFFModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the AIFF data are being read + */ + public AuthorChunk( + AiffModule module, + ChunkHeader hdr, + DataInputStream dstrm) + { + super(module, hdr, dstrm); + propName = "Author"; + } + + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/CommentsChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/CommentsChunk.java new file mode 100644 index 00000000..ae6edbd8 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/CommentsChunk.java @@ -0,0 +1,90 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.aiff; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.*; +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.AiffModule; +import edu.harvard.hul.ois.jhove.module.iff.*; + +/** + * Implementation of the AIFF Comments Chunk. + * + * @author Gary McGath + * + */ +public class CommentsChunk extends Chunk { + + /** + * Constructor. + * + * @param module The AIFFModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the AIFF data are being read + */ + public CommentsChunk (AiffModule module, ChunkHeader hdr, + DataInputStream dstrm) + { + super(module, hdr, dstrm); + } + + /** Reads a chunk and puts a Comments property into + * the RepInfo object. + * + * @return false if the chunk is structurally + * invalid, otherwise true + */ + public boolean readChunk(RepInfo info) + throws IOException + { + AiffModule module = (AiffModule) _module; + int numComments = module.readUnsignedShort (_dstream); + bytesLeft -= 2; + if (numComments == 0) { + return true; // trivial case + } + // Create a List of comment properties + List comments = new ArrayList (numComments); + for (int i = 0; i < numComments; i++) { + long timestamp = module.readUnsignedInt (_dstream); + Date jTimestamp = module.timestampToDate (timestamp); + int marker = module.readSignedShort (_dstream); + int count = module.readUnsignedShort (_dstream); + bytesLeft -= 8; + byte[] buf = new byte[count]; + ModuleBase.readByteBuf(_dstream, buf, module); + bytesLeft -= count; + /* Ensure that each byt is a printable ASCII character. */ + for (int j=0; j 127) { + buf[j] = 32; + } + } + String comment = new String (buf, "ASCII"); + + // Build the property for one comment + Property[] comAr = new Property[2]; + comAr[0] = new Property ("Timestamp", + PropertyType.DATE, + jTimestamp); + comAr[1] = new Property ("CommentText", + PropertyType.STRING, + comment); + comments.add (new Property ("Comment", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + comAr)); + } + module.addAiffProperty(new Property ("Comments", + PropertyType.PROPERTY, + PropertyArity.LIST, + comments)); + + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/CommonChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/CommonChunk.java new file mode 100644 index 00000000..d43e8c35 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/CommonChunk.java @@ -0,0 +1,185 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.aiff; + +import edu.harvard.hul.ois.jhove.*; +import java.io.DataInputStream; +import java.io.IOException; + +import edu.harvard.hul.ois.jhove.module.AiffModule; +import edu.harvard.hul.ois.jhove.module.iff.*; + +/** + * The AIFF Common chunk. + * + * @author Gary McGath + * + */ +public class CommonChunk extends Chunk { + + /** + * Constructor. + * + * @param module The AIFFModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the AIFF data are being read + */ + public CommonChunk( + AiffModule module, + ChunkHeader hdr, + DataInputStream dstrm) { + super(module, hdr, dstrm); + } + + /** Reads a chunk and puts various properties into + * the RepInfo object. + * + * @return false if the chunk is structurally + * invalid, otherwise true + */ + public boolean readChunk(RepInfo info) throws IOException { + AiffModule module = (AiffModule) _module; + int numChannels = module.readUnsignedShort (_dstream); + long numSampleFrames = module.readUnsignedInt (_dstream); + int sampleSize = module.readUnsignedShort (_dstream); + bytesLeft -= 8; + + String compressionType = null; + String compressionName = null; + + double sampleRate = module.read80BitDouble (_dstream); + bytesLeft -= 10; + + if (module.getFileType () == AiffModule.AIFCTYPE) { + if (bytesLeft == 0) { + // This is a rather special case, but testing did turn up + // a file that misbehaved in this way. + info.setMessage (new ErrorMessage + ("Common Chunk in AIFF-C does not have compression type", + module.getNByte())); + info.setWellFormed (false); + return false; + } + compressionType = module.read4Chars (_dstream); + // According to David Ackerman, the compression type can + // change the endianness of the document. + if (compressionType.equals ("sowt")) { + module.setEndian (false); // little-endian + } + bytesLeft -= 4; + compressionName = module.readPascalString (_dstream); + bytesLeft -= compressionName.length () + 1; + } + + AESAudioMetadata aes = module.getAESMetadata (); + aes.setBitDepth (sampleSize); + aes.setSampleRate (sampleRate); + aes.setNumChannels (numChannels); + setChannelLocations (aes, numChannels); + //aes.setDuration ((double) numSampleFrames / sampleRate); + aes.setDuration (numSampleFrames); + module.addAiffProperty (new Property ("SampleFrames", + PropertyType.LONG, + new Long (numSampleFrames))); + // Proper handling of compression type should depend + // on whether raw output is set + if (compressionType != null) { + module.addAiffProperty (new Property ("CompressionType", + PropertyType.STRING, + compressionType)); + if (compressionType.equals ("NONE")) { + } + else if (compressionType.equals ("raw ")) { + aes.setAudioDataEncoding ("PCM 8-bit offset-binary"); + } + else if (compressionType.equals ("twos")) { + aes.setAudioDataEncoding ("PCM 16-bit twos-complement big-endian"); + } + else if (compressionType.equals ("sowt")) { + aes.setAudioDataEncoding ("PCM 16-bit twos-complement little-endian"); + } + else if (compressionType.equals ("fl32")) { + aes.setAudioDataEncoding ("PCM 32-bit integer"); + } + else if (compressionType.equals ("fl64")) { + aes.setAudioDataEncoding ("PCM 64-bit floating point"); + } + else if (compressionType.equals ("in24")) { + aes.setAudioDataEncoding ("PCM 24-bit integer"); + } + else if (compressionType.equals ("in32")) { + aes.setAudioDataEncoding ("PCM 32-bit integer"); + } + else { + aes.setAudioDataEncoding (compressionName); + + // The size of the data after compression isn't available + // from the Common chunk, so we mark it as "unknown." + // With a bit more sophistication, we could combine the + // information from here and the Sound Data chunk to get + // the effective byte rate, but we're about to release. + String name = compressionName; + if (name == null || name.length () == 0) { + name = compressionType; + } + aes.setBitrateReduction (compressionName, "", "", "", + "LOSSY", "UNKNOWN", "FIXED"); + } + } + if (compressionName != null && compressionName.length () > 0) { + module.addAiffProperty (new Property ("CompressionName", + PropertyType.STRING, + compressionName)); + } + + return true; + } + + /* Assign channel locationss according to the number of + * channels and the standard AIFF assignment. */ + @SuppressWarnings("fallthrough") + private void setChannelLocations + (AESAudioMetadata aes, int numChannels) + { + String[] mapLoc = new String[numChannels]; + switch (numChannels) { + case 1: + mapLoc[0] = "UNKNOWN"; + break; + + // There are two 4-channel alternatives. Pick one. + case 4: + mapLoc[3] = "SURROUND"; + // fall through to case 3 + + case 3: + mapLoc[2] = "CENTER"; + // fall through to case 2 + + case 2: + mapLoc[0] = "LEFT"; + mapLoc[1] = "RIGHT"; + break; + + case 6: + mapLoc[0] = "LEFT"; + mapLoc[1] = "LEFT_CENTER"; + mapLoc[2] = "CENTER"; + mapLoc[3] = "RIGHT"; + mapLoc[4] = "RIGHT_CENTER"; + mapLoc[5] = "SURROUND"; + break; + + // If we get some other number of channels, punt. + default: + for (int i = 0; i < numChannels; i++) { + mapLoc[i] = "UNKNOWN"; + } + } + aes.setMapLocations(mapLoc); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/CopyrightChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/CopyrightChunk.java new file mode 100644 index 00000000..ff52b719 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/CopyrightChunk.java @@ -0,0 +1,37 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.aiff; + +import java.io.DataInputStream; +import edu.harvard.hul.ois.jhove.module.AiffModule; +import edu.harvard.hul.ois.jhove.module.iff.*; + +/** + * Implementation of the AIFF Copyright Chunk. + * + * @author Gary McGath + * + */ +public class CopyrightChunk extends TextChunk { + + /** + * Constructor. + * + * @param module The AIFFModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the AIFF data are being read + */ + public CopyrightChunk( + AiffModule module, + ChunkHeader hdr, + DataInputStream dstrm) + { + super(module, hdr, dstrm); + propName = "Copyright"; + } + + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/ExtDouble.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/ExtDouble.java new file mode 100644 index 00000000..ffdf9efa --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/ExtDouble.java @@ -0,0 +1,82 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.aiff; + +/** + * Code to deal with the 80-bit floating point (extended double) + * numbers which occur in AIFF files. Should also be applicable + * in general. + * + * Java has no built-in support for IEEE 754 extended double numbers. + * Thus, we have to unpack the number and convert it to a double by + * hand. There is, of course, loss of precision. + * + * This isn't designed for high-precision work; as the standard + * disclaimer says, don't use it for life support systems or nuclear + * power plants. + * + * @author Gary McGath + * + */ +public class ExtDouble { + + byte[] _rawData; + + /** + * Constructor. + * + * @param rawData A 10-byte array representing the number + * in the sequence in which it was stored. + */ + public ExtDouble(byte[] rawData) + { + _rawData = rawData; + } + + + /** Convert the value to a Java double. This results in + * loss of precision. If the number is out of range, + * results aren't guaranteed. + */ + public double toDouble () + { + int sign; + int exponent; + long mantissa = 0; + + // Extract the sign bit. + sign = _rawData[0] >> 7; + + // Extract the exponent. It's stored with a + // bias of 16383, so subtract that off. + // Also, the mantissa is between 1 and 2 (i.e., + // all but 1 digits are to the right of the binary point, so + // we take 62 (not 63: see below) off the exponent for that. + exponent = (_rawData[0] << 8) | _rawData[1]; + exponent &= 0X7FFF; // strip off sign bit + exponent -= (16383 + 62); // 1 is added to the "real" exponent + + // Extract the mantissa. It's 64 bits of unsigned + // data, but a long is a signed number, so we have to + // discard the LSB. We'll lose more than that converting + // to double anyway. This division by 2 is the reason for + // adding an extra 1 to the exponent above. + int shifter = 55; + for (int i = 2; i < 9; i++) { + mantissa |= ((long) _rawData[i] & 0XFFL) << shifter; + shifter -= 8; + } + mantissa |= _rawData[9] >>> 1; + + // Now put it together in a floating point number. + double val = Math.pow (2, exponent); + val *= mantissa; + if (sign != 0) { + val = -val; + } + return val; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/FormatVersionChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/FormatVersionChunk.java new file mode 100644 index 00000000..1f4c1b08 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/FormatVersionChunk.java @@ -0,0 +1,59 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.aiff; + +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.AiffModule; +import edu.harvard.hul.ois.jhove.module.iff.*; + +import java.io.*; +import java.util.*; + +/** + * Implementation of the AIFF Format Version Chunk. + * This chunk occurs only in the AIFF/C variant. + * + * @author Gary McGath + * + */ +public class FormatVersionChunk extends Chunk { + + + /** + * Constructor. + * + * @param module The AIFFModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the AIFF data are being read + */ + public FormatVersionChunk ( + AiffModule module, + ChunkHeader hdr, + DataInputStream dstrm) + { + super (module, hdr, dstrm); + } + + /** Reads a chunk and puts a FormatVersion property into + * the RepInfo object. + * + * @return false if the chunk is structurally + * invalid, otherwise true + */ + public boolean readChunk (RepInfo info) throws IOException + { + AiffModule module = (AiffModule) _module; + long timestamp = module.readUnsignedInt (_dstream); + // The timestamp is in seconds since January 1, 1904. + // We must convert to Java time. + Date jTimestamp = module.timestampToDate (timestamp); + module.addAiffProperty (new Property ("FormatVersion", + PropertyType.DATE, + jTimestamp)); + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/InstrumentChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/InstrumentChunk.java new file mode 100644 index 00000000..2c1db033 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/InstrumentChunk.java @@ -0,0 +1,128 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.aiff; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.*; +import edu.harvard.hul.ois.jhove.RepInfo; +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.AiffModule; +import edu.harvard.hul.ois.jhove.module.iff.*; + +/** + * Implementation of the AIFF Instrument Chunk. + * + * @author Gary McGath + * + */ +public class InstrumentChunk extends Chunk { + + /** + * Constructor. + * + * @param module The AIFFModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the AIFF data are being read + */ + public InstrumentChunk( + AiffModule module, + ChunkHeader hdr, + DataInputStream dstrm) { + super(module, hdr, dstrm); + } + + /** Reads a chunk and puts an Instrument property into + * the RepInfo object. + * + * @return false if the chunk is structurally + * invalid, otherwise true + */ + public boolean readChunk(RepInfo info) throws IOException + { + AiffModule module = (AiffModule) _module; + int baseNote = ModuleBase.readUnsignedByte (_dstream, module); + int detune = ModuleBase.readSignedByte (_dstream, module); + int lowNote = ModuleBase.readUnsignedByte (_dstream, module); + int highNote = ModuleBase.readUnsignedByte (_dstream, module); + int lowVelocity = ModuleBase.readUnsignedByte (_dstream, module); + int highVelocity = ModuleBase.readUnsignedByte (_dstream, module); + int gain = module.readSignedShort (_dstream); + Loop sustainLoop = readLoop (module); + Loop releaseLoop = readLoop (module); + + List propList = new ArrayList (9); + propList.add (new Property ("BaseNote", + PropertyType.INTEGER, + new Integer (baseNote))); + propList.add (new Property ("Detune", + PropertyType.INTEGER, + new Integer (detune))); + propList.add (new Property ("LowNote", + PropertyType.INTEGER, + new Integer (lowNote))); + propList.add (new Property ("HighNote", + PropertyType.INTEGER, + new Integer (highNote))); + propList.add (new Property ("LowVelocity", + PropertyType.INTEGER, + new Integer (lowVelocity))); + propList.add (new Property ("HighVelocity", + PropertyType.INTEGER, + new Integer (highVelocity))); + propList.add (new Property ("Gain", + PropertyType.INTEGER, + new Integer (gain))); + propList.add (sustainLoop.loopProp("SustainLoop")); + propList.add (releaseLoop.loopProp("ReleaseLoop")); + module.addAiffProperty(new Property ("Instrument", + PropertyType.PROPERTY, + PropertyArity.LIST, + propList)); + return true; + } + + + private Loop readLoop (AiffModule module) throws IOException + { + int playMode = module.readSignedShort(_dstream); + int beginLoop = module.readUnsignedShort (_dstream); + int endLoop = module.readUnsignedShort (_dstream); + return new Loop (playMode, beginLoop, endLoop); + } + + + /* Local class for encapsulating the Loop structure */ + private class Loop { + public int playMode; + public int beginLoop; + public int endLoop; + + public Loop (int playMode, int beginLoop, int endLoop) + { + this.playMode = playMode; + this.beginLoop = beginLoop; + this.endLoop = endLoop; + } + + public Property loopProp (String name) + { + Property[] propArr = new Property[3]; + propArr[0] = _module.addIntegerProperty("PlayMode", playMode, + AiffStrings.LOOP_TYPE); + propArr[1] = new Property ("BeginLoop", + PropertyType.INTEGER, + new Integer (beginLoop)); + propArr[2] = new Property ("EndLoop", + PropertyType.INTEGER, + new Integer (endLoop)); + return new Property (name, + PropertyType.PROPERTY, + PropertyArity.ARRAY, + propArr); + } + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/MarkerChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/MarkerChunk.java new file mode 100644 index 00000000..3aaeaed8 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/MarkerChunk.java @@ -0,0 +1,77 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.aiff; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.*; +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.AiffModule; +import edu.harvard.hul.ois.jhove.module.iff.*; + +/** + * The AIFF Marker chunk. + * + * @author Gary McGath + * + */ +public class MarkerChunk extends Chunk { + + /** + * Constructor. + * + * @param module The AIFFModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the AIFF data are being read + */ + public MarkerChunk( + AiffModule module, + ChunkHeader hdr, + DataInputStream dstrm) { + super(module, hdr, dstrm); + } + + /** Reads a chunk and puts a Markers property into + * the RepInfo object. + * + * @return false if the chunk is structurally + * invalid, otherwise true + */ + public boolean readChunk(RepInfo info) throws IOException { + AiffModule module = (AiffModule) _module; + int numMarkers = module.readUnsignedShort (_dstream); + if (numMarkers == 0) { + return true; // trivial but legal case + } + List markerList = new ArrayList (numMarkers); + for (int i = 0; i < numMarkers; i++) { + int id = module.readUnsignedShort (_dstream); + long position = module.readUnsignedInt (_dstream); + String markerName = module.readPascalString(_dstream); + + Property[] mArr = new Property[3]; + mArr[0] = new Property ("ID", + PropertyType.INTEGER, + new Integer (id)); + mArr[1] = new Property ("Position", + PropertyType.LONG, + new Long (position)); + mArr[2] = new Property ("Name", + PropertyType.STRING, + markerName); + markerList.add (new Property ("Marker", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + mArr)); + } + module.addAiffProperty (new Property ("Markers", + PropertyType.PROPERTY, + PropertyArity.LIST, + markerList)); + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/MidiChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/MidiChunk.java new file mode 100644 index 00000000..f58dbf66 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/MidiChunk.java @@ -0,0 +1,58 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.aiff; + +import java.io.DataInputStream; +import java.io.IOException; + +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.AiffModule; +import edu.harvard.hul.ois.jhove.module.iff.Chunk; +import edu.harvard.hul.ois.jhove.module.iff.ChunkHeader; + +/** + * Implementation of the AIFF MIDI Chunk. + * + * @author Gary McGath + * + */ +public class MidiChunk extends Chunk { + + /** + * Constructor. + * + * @param module The AIFFModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the AIFF data are being read + */ + public MidiChunk( + AiffModule module, + ChunkHeader hdr, + DataInputStream dstrm) { + super(module, hdr, dstrm); + } + + /** Reads a chunk and puts an MIDI property into + * the RepInfo object. + * + * @return false if the chunk is structurally + * invalid, otherwise true + */ + public boolean readChunk(RepInfo info) throws IOException { + AiffModule module = (AiffModule) _module; + if (bytesLeft == 0) { + return true; // dubious, but call it legal + } + byte[] buf = new byte[(int) bytesLeft]; + ModuleBase.readByteBuf (_dstream, buf, _module); + module.addMidi (new Property ("MIDI", + PropertyType.BYTE, + PropertyArity.ARRAY, + buf)); + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/NameChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/NameChunk.java new file mode 100644 index 00000000..1901df78 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/NameChunk.java @@ -0,0 +1,36 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.aiff; + +import java.io.DataInputStream; +import edu.harvard.hul.ois.jhove.module.AiffModule; +import edu.harvard.hul.ois.jhove.module.iff.*; + +/** + * Implementation of the AIFF Name Chunk. + * + * @author Gary McGath + * + */ +public class NameChunk extends TextChunk { + + /** + * Constructor. + * + * @param module The AIFFModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the AIFF data are being read + */ + public NameChunk( + AiffModule module, + ChunkHeader hdr, + DataInputStream dstrm) + { + super(module, hdr, dstrm); + propName = "Name"; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/SaxelChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/SaxelChunk.java new file mode 100644 index 00000000..3a8bf460 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/SaxelChunk.java @@ -0,0 +1,86 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.aiff; + +import java.io.DataInputStream; +import java.io.IOException; +//import java.util.*; +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.AiffModule; +import edu.harvard.hul.ois.jhove.module.iff.Chunk; +import edu.harvard.hul.ois.jhove.module.iff.ChunkHeader; + +/** + * Implementation of the AIFF Saxel (Sound Accelerator) Chunk. + * + * The Saxel chunk has only a tentative and incomplete status in the + * AIFF-C draft of 1991, and apparently nothing further was + * ever done with it. For purposes of extracting parameters, + * we treat the description of the SaxelChunk and Saxels as + * valid, while regarding the SaxelData as opaque. + * + * @author Gary McGath + * + */ +public class SaxelChunk extends Chunk { + + /** + * Constructor. + * + * @param module The AIFFModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the AIFF data are being read + */ + public SaxelChunk( + AiffModule module, + ChunkHeader hdr, + DataInputStream dstrm) { + super(module, hdr, dstrm); + } + + /** Reads a chunk and puts a "Saxels" property into + * the RepInfo object. + * + * @return false if the chunk is structurally + * invalid, otherwise true + * + */ + public boolean readChunk(RepInfo info) throws IOException { + AiffModule module = (AiffModule) _module; + int numSaxels = module.readUnsignedShort (_dstream); + bytesLeft -= 2; + if (numSaxels == 0) { + return true; // trivial case + } + // Create a List of properties + for (int i = 0; i < numSaxels; i++) { + // Multiple saxel chunks are allowed, of which + // each can have multiple saxels. We put them + // all together into a single saxel list in + // the module. + Property[] propArr = new Property[2]; + int id = module.readUnsignedShort (_dstream); + int size = module.readUnsignedShort (_dstream); + // Just skip the actual data. + module.skipBytes (_dstream, size, module); + + // Build the property to add to the saxel list. + propArr[0] = new Property ("ID", + PropertyType.INTEGER, + new Integer (id)); + propArr[1] = new Property ("Size", + PropertyType.INTEGER, + new Integer (size)); + module.addSaxel (new Property ("Saxel", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + propArr)); + + } + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/SoundDataChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/SoundDataChunk.java new file mode 100644 index 00000000..31db1556 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/SoundDataChunk.java @@ -0,0 +1,67 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.aiff; + +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.AiffModule; +import edu.harvard.hul.ois.jhove.module.iff.*; + +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * Implementation of the AIFF Sound Data Chunk. + * + * @author Gary McGath + * + */ +public class SoundDataChunk extends Chunk { + + /** + * Constructor. + * + * @param module The AIFFModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the AIFF data are being read + */ + public SoundDataChunk (AiffModule module, ChunkHeader hdr, + DataInputStream dstrm) + { + super(module, hdr, dstrm); + } + + /** Reads a chunk and puts a SoundData property into + * the RepInfo object. + * + * @return false if the chunk is structurally + * invalid, otherwise true + */ + public boolean readChunk(RepInfo info) + throws IOException + { + AiffModule module = (AiffModule) _module; + Property[] propArray = new Property[3]; + long offset = module.readUnsignedInt (_dstream); + long blockSize = module.readUnsignedInt (_dstream); + propArray[0] = new Property ("Offset", PropertyType.LONG, + new Long (offset)); + propArray[1] = new Property ("BlockSize", PropertyType.LONG, + new Long (blockSize)); + propArray[2] = new Property ("DataLength", PropertyType.LONG, + new Long (bytesLeft - 8)); + module.addAiffProperty(new Property ("SoundData", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + propArray)); + // This must be called precisely at this point in reading the + // data stream to produce an accurate result. + module.markFirstSampleOffset (offset); + module.skipBytes (_dstream, (int) (bytesLeft - 8), module); + + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/TextChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/TextChunk.java new file mode 100644 index 00000000..8fbc961b --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/TextChunk.java @@ -0,0 +1,77 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.aiff; + +import java.io.DataInputStream; +import java.io.IOException; +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.AiffModule; +import edu.harvard.hul.ois.jhove.module.iff.*; + +/** + * Abstract superclass for the name, author, copyright, + * and annotation chunks, all of which have the same + * format. + * + * @author Gary McGath + * + */ +public abstract class TextChunk extends Chunk { + + /** Name of the property. The subclass constructor + * must set this appropriately. */ + protected String propName; + + /** + * Constructor. + * + * @param module The AIFFModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the AIFF data are being read + */ + public TextChunk (AiffModule module, ChunkHeader hdr, + DataInputStream dstrm) + { + super(module, hdr, dstrm); + } + + /** Reads a chunk and puts appropriate information into + * the RepInfo object. + * + * This method works for TextChunk, CopyrightChunk and + * AuthorChunk. AnnotationChunk overrides it, since there + * can be multiple annotations. + */ + public boolean readChunk (RepInfo info) + throws IOException + { + AiffModule module = (AiffModule) _module; + String name = readText (); + module.addAiffProperty (new Property (propName, PropertyType.STRING, + name)); + return true; + } + + /** + * Reads the chunk's text data. + * All text chunk subclasses consist of a text string + * which takes up the full byte count of the chunk. + * By the specification, the text is required to be ASCII. + */ + protected String readText () + throws IOException + { + byte[] buf = new byte[(int) bytesLeft]; + ModuleBase.readByteBuf (_dstream, buf, _module); + /* Ensure that each byt is a printable ASCII character. */ + for (int i=0; i 127) { + buf[i] = 32; + } + } + return new String (buf, "ASCII"); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/build.xml b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/build.xml new file mode 100644 index 00000000..5a9f12f3 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/build.xml @@ -0,0 +1,32 @@ + + JPEG module build file + Jhove - JSTOR/Harvard Object Validation Environment + Version 1.0 2004-05-05 + Copyright 2004 by JSTOR and the President and Fellows of Harvard College + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/package.html b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/package.html new file mode 100644 index 00000000..fa9c0fe4 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/aiff/package.html @@ -0,0 +1,24 @@ + + + + + + + +Contains supporting classes for the AIFF-HUL module. +
+ + + + + +
+ + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/build.xml b/jhove/classes/edu/harvard/hul/ois/jhove/module/build.xml new file mode 100644 index 00000000..534b2e81 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/build.xml @@ -0,0 +1,108 @@ + + Module build file + Jhove - JSTOR/Harvard Object Validation Environment + Version 1.0 2004-05-05 + Copyright 2004 by JSTOR and the President and Fellows of Harvard College + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/gif/GifStrings.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/gif/GifStrings.java new file mode 100644 index 00000000..6f201932 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/gif/GifStrings.java @@ -0,0 +1,59 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.gif; + +/** + * A class for holding arrays of informative strings that will go into + * properties of a GIF object. + */ +public class GifStrings { + + /** A private constructor just to make sure nobody + instantiates the class by mistake. */ + private GifStrings () + { + } + + /** Strings for presence or absence of global color table */ + public final static String[] GLOBAL_COLOR_TABLE_FLAG = + { "No global color table; background color index meaningless", + "Global color table follows; background color index meaningful" }; + + /** Strings for ordering or non-ordering of color table */ + public final static String[] COLOR_TABLE_SORT_FLAG = + { "Not ordered", + "Ordered by decreasing importance" }; + + /** GIF Capabilities Enquiry string: way in which the graphic is to + * be treated after being displayed */ + public final static String[] GCE_DISPOSAL_METHOD = + { "No disposal specified", + "Do not dispose", + "Restore to background color", + "Restore to previous" }; + + /** GIF Capabilities Enquiry string: user input + * expected or not */ + public final static String[] GCE_USER_INPUT_FLAG = + { "User input not expected", + "User input expected" }; + + /** GIF Capabilities Enquiry string: transparency + * index given or not */ + public final static String[] GCE_TRANSPARENCY_FLAG = + { "Transparent index is not given", "Transparent index given" }; + + /** Local color table present in image or not */ + public final static String[] LOCAL_COLOR_TABLE_FLAG = + { "No local color table; use global table if available", + "Local color table follows" }; + + /** Image is interlaced or not */ + public final static String[] INTERLACE_FLAG = + { "Image is not interlaced", + "Image is interlaced" }; + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/gif/build.xml b/jhove/classes/edu/harvard/hul/ois/jhove/module/gif/build.xml new file mode 100644 index 00000000..24836508 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/gif/build.xml @@ -0,0 +1,32 @@ + + GIF module build file + Jhove - JSTOR/Harvard Object Validation Environment + Version 1.0 2004-02-03 + Copyright 2004 by JSTOR and the President and Fellows of Harvard College + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/gif/package.html b/jhove/classes/edu/harvard/hul/ois/jhove/module/gif/package.html new file mode 100644 index 00000000..de55edcc --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/gif/package.html @@ -0,0 +1,24 @@ + + + + + + + +Contains supporting classes for the GIF-HUL module. +
+ + + + + +
+ + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/CharStream.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/CharStream.java new file mode 100644 index 00000000..2ef6380a --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/CharStream.java @@ -0,0 +1,110 @@ +/* Generated By:JavaCC: Do not edit this line. CharStream.java Version 3.0 */ +package edu.harvard.hul.ois.jhove.module.html; + +/** + * This interface describes a character stream that maintains line and + * column number positions of the characters. It also has the capability + * to backup the stream to some extent. An implementation of this + * interface is used in the TokenManager implementation generated by + * JavaCCParser. + * + * All the methods except backup can be implemented in any fashion. backup + * needs to be implemented correctly for the correct operation of the lexer. + * Rest of the methods are all used to get information like line number, + * column number and the String that constitutes a token and are not used + * by the lexer. Hence their implementation won't affect the generated lexer's + * operation. + */ + +public interface CharStream { + + /** + * Returns the next character from the selected input. The method + * of selecting the input is the responsibility of the class + * implementing this interface. Can throw any java.io.IOException. + */ + char readChar() throws java.io.IOException; + + /** + * Returns the column position of the character last read. + * @deprecated + * @see #getEndColumn + */ + int getColumn(); + + /** + * Returns the line number of the character last read. + * @deprecated + * @see #getEndLine + */ + int getLine(); + + /** + * Returns the column number of the last character for current token (being + * matched after the last call to BeginTOken). + */ + int getEndColumn(); + + /** + * Returns the line number of the last character for current token (being + * matched after the last call to BeginTOken). + */ + int getEndLine(); + + /** + * Returns the column number of the first character for current token (being + * matched after the last call to BeginTOken). + */ + int getBeginColumn(); + + /** + * Returns the line number of the first character for current token (being + * matched after the last call to BeginTOken). + */ + int getBeginLine(); + + /** + * Backs up the input stream by amount steps. Lexer calls this method if it + * had already read some characters, but could not use them to match a + * (longer) token. So, they will be used again as the prefix of the next + * token and it is the implemetation's responsibility to do this right. + */ + void backup(int amount); + + /** + * Returns the next character that marks the beginning of the next token. + * All characters must remain in the buffer between two successive calls + * to this method to implement backup correctly. + */ + char BeginToken() throws java.io.IOException; + + /** + * Returns a string made up of characters from the marked token beginning + * to the current buffer position. Implementations have the choice of returning + * anything that they want to. For example, for efficiency, one might decide + * to just return null, which is a valid implementation. + */ + String GetImage(); + + /** + * Returns an array of characters that make up the suffix of length 'len' for + * the currently matched token. This is used to build up the matched string + * for use in actions in the case of MORE. A simple and inefficient + * implementation of this is as follows : + * + * { + * String t = GetImage(); + * return t.substring(t.length() - len, t.length()).toCharArray(); + * } + */ + char[] GetSuffix(int len); + + /** + * The lexer calls this function to indicate that it is done with the stream + * and hence implementations can free any resources held by this class. + * Again, the body of this function can be just empty and it will not + * affect the lexer's operation. + */ + void Done(); + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/DTDMapper.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/DTDMapper.java new file mode 100644 index 00000000..96ed44e8 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/DTDMapper.java @@ -0,0 +1,112 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.html; + +import java.io.InputStream; +import java.net.URL; +import org.xml.sax.InputSource; + + +/** + * Class to map public DTD ID's to files which are included with this + * HTML module. This class is used by the XML module, due to the + * intermodulary nature of XHTML. + * + * @author Gary McGath + * + */ +public class DTDMapper { + + private final static String xhtml1Frameset = "-//W3C//DTD XHTML 1.0 FRAMESET//EN"; + private final static String xhtml1Strict = "-//W3C//DTD XHTML 1.0 STRICT//EN"; + private final static String xhtml1Transitional = "-//W3C//DTD XHTML 1.0 TRANSITIONAL//EN"; + private final static String xhtml11 = "-//W3C//DTD XHTML 1.1//EN"; + private final static String latin1Ent = "-//W3C//ENTITIES LATIN 1 FOR XHTML//EN"; + private final static String specialEnt = "-//W3C//ENTITIES SPECIAL FOR XHTML//EN"; + private final static String symbolEnt = "-//W3C//ENTITIES SYMBOLS FOR XHTML//EN"; + + /** Attempts to convert a public ID to a matching DTD or Entity resource. + * Returns an InputStream for that resource if there is a match. + * Otherwise returns null. + * + * @param publicID The PUBLIC ID associated with a DTD or entity document + */ + public static InputSource publicIDToFile(String publicID) + { + String filename = null; + if (publicID == null) { + return null; + } + // Make comparisons case-insensitive -- just in case + publicID = publicID.toUpperCase (); + if (xhtml1Frameset.equals (publicID)) { + filename = "xhtml1-frameset.dtd"; + } + else if (xhtml1Strict.equals (publicID)) { + filename = "xhtml1-strict.dtd"; + } + else if (xhtml1Transitional.equals (publicID)) { + filename = "xhtml1-transitional.dtd"; + } + else if (xhtml11.equals (publicID)) { + filename = "xhtml11-flat.dtd"; + } + else if (latin1Ent.equals (publicID)) { + filename = "xhtml-lat1.ent"; + } + else if (specialEnt.equals (publicID)) { + filename = "xhtml-special.ent"; + } + else if (symbolEnt.equals (publicID)) { + filename = "xhtml-symbol.ent"; + } + if (filename != null) { + URL dtdURL = DTDMapper.class.getResource(filename); + if (dtdURL != null) { + try { + InputStream strm = dtdURL.openStream(); + return new InputSource (strm); + } + catch (Exception e) { + return null; + } + } + } + return null; + } + + + /** Returns TRUE if the parameter is the public ID of a + * known XHTML DTD. */ + public static boolean isXHTMLDTD (String publicID) + { + if (publicID == null) { + return false; + } + publicID = publicID.toUpperCase (); + return (xhtml1Frameset.equals (publicID) || + xhtml1Strict.equals (publicID) || + xhtml1Transitional.equals (publicID) || + xhtml11.equals (publicID)); + } + + /** Returns the XHTML version associated with the DTD's + * public ID. Returns null if it isn't + * a known XHTML public ID. */ + public static String getXHTMLVersion (String publicID) { + publicID = publicID.toUpperCase (); + if (!isXHTMLDTD (publicID)) { + return null; + } + else if (xhtml11.equals (publicID)) { + return "1.1"; + } + else { + return "1.0"; + } + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html3_2DocDesc.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html3_2DocDesc.java new file mode 100644 index 00000000..7b7d914a --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html3_2DocDesc.java @@ -0,0 +1,698 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.html; + +import java.util.*; + +/** + * This class describes the requirements of an HTML 3.2 document. + * + * @author Gary McGath + * + */ +public class Html3_2DocDesc extends HtmlDocDesc { + + /* Static, private map of supported tags. + * For efficiency, we create a static Map + * of supported tags just once, then assign that to stSupportedElements + * in the constructor. */ + private static Map stSupportedElements; + + /* Static initializer. A superclass is initialized before its + * subclass, so we can count on the static initializer of HtmlDocDesc + * to have run already. + * + * It's time to start thinking about how to factor this code. + * Each element can be created separately, with the necessary + * arguments passed for each one. It would be a nice pattern if + * all elements had the same calling sequence, but realistically + * some are going to need extras such as special lists of names. + * The element functions (which will all be static) should be here + * if unique, or in the parent class if they can be used for more + * than one version of HTML. There should be a naming convention + * for the functions in the parent class indicating which names + * they can be used with. + */ + static { + stSupportedElements = new HashMap (280); + String[] fontMarkup = new String[] + { "tt", "i", "b", "u", "strike", "big", "small", "sub", "sup" }; + String[] phraseMarkup = new String[] + { "em", "strong", "dfn", "code", "samp", "kbd", "var", "cite" }; + String[] specialMarkup = new String[] + { "a", "img", "applet", "font", "basefont", "br", "script", "map" }; + String[] formMarkup = new String[] + { "input", "select", "textarea" }; + String[] listMarkup = new String [] + { "ul", "ol", "dir", "menu" }; + + + /* textContent lists all the content types which are permitted in + * the markup elements. For a first cut, strings signify the + * name of the element which is permitted. */ + List textContent = new ArrayList(35); + addStringsToList (fontMarkup, textContent); + addStringsToList (phraseMarkup, textContent); + addStringsToList (specialMarkup, textContent); + addStringsToList (formMarkup, textContent); + textContent.add (HtmlSpecialToken.PCDATA); + + List blockContent = new ArrayList (20); + addStringsToList (listMarkup, blockContent); + String[] blockMisc = new String[] + {"p", "pre", "dl", "div", "center", + "blockquote", "form", "isindex", "hr", "table" }; + addStringsToList (blockMisc, blockContent); + + List flowContent = new ArrayList (30); + flowContent.addAll (blockContent); + flowContent.addAll (textContent); + + int i; + String name; + HtmlTagDesc td; + + /* Text elements */ + for (i = 0; i < fontMarkup.length; i++) { + name = fontMarkup[i]; + td = new HtmlTagDesc (name, true, true, textContent, null); + stSupportedElements.put (name, td); + } + + /* Phrase elements. */ + for (i = 0; i < phraseMarkup.length; i++) { + name = phraseMarkup[i]; + td = new HtmlTagDesc (name, true, true, textContent, null); + stSupportedElements.put (name, td); + } + + addFontElement (textContent); + addBasefontElement (); + addBrElement (); + + /* Content for the BODY element, also used for other elements */ + List bodyContent = new ArrayList (100); + addStringsToList (headings, bodyContent); + bodyContent.addAll (textContent); + bodyContent.addAll (blockContent); + bodyContent.add ("address"); + + addBodyElement (bodyContent); + addAddressElement (textContent); + + HtmlAttributeDesc halignAtt = + new HtmlAttributeDesc ("align", + new String[] { "left", "center", "right" }, + HtmlAttributeDesc.IMPLIED); + /* Caution -- some elements' align attributes have a different + * set of permitted values. Don't use halignAtt for these. */ + + addDivElement (bodyContent, halignAtt); + addCenterElement (bodyContent); + addAElement (textContent); + addMapElement (); + + addAreaElement (); + addLinkElement (); + HtmlAttributeDesc ialignAtt = new HtmlAttributeDesc ("align", + new String[] { "top", "middle", "bottom", "left", "right" }, + HtmlAttributeDesc.IMPLIED); + addImgElement (ialignAtt); + addAppletElement (ialignAtt, textContent); + addParamElement (); + addHrElement (halignAtt); + addPElement (halignAtt, textContent); + + /* The heading (H1-H6) elements */ + List atts = new ArrayList (1); + atts.add (halignAtt); + for (i = 0; i < headings.length; i++) { + name = headings[i]; + td = new HtmlTagDesc (name, true, true, textContent, atts); + stSupportedElements.put (name, td); + } + + addPreElement (textContent); + addBlockquoteElement (bodyContent); + addDlElement (); + addDtElement (textContent); + addDdElement (flowContent); + + List listContent = new ArrayList (1); + listContent.add ("li"); + addOlElement (listContent); + addUlElement (listContent); + addDirElement (listContent); + addMenuElement (listContent); + addLiElement (flowContent); + addFormElement (bodyContent); + addInputElement (ialignAtt); + addSelectElement (); + addOptionElement (); + addTableElement (); + HtmlAttributeDesc valignAtt = + new HtmlAttributeDesc ("valign", + new String[] { "top", "middle", "bottom" }, + HtmlAttributeDesc.IMPLIED); + addTrElement (halignAtt, valignAtt); + + List thtdAtts = new ArrayList (7); // common attribute list for TH and TD + addSelfAttribute (thtdAtts, "nowrap"); + addSimpleAttribute (thtdAtts, "rowspan"); + addSimpleAttribute (thtdAtts, "colspan"); + thtdAtts.add (halignAtt); + thtdAtts.add (valignAtt); + addSimpleAttribute (thtdAtts, "width"); + addSimpleAttribute (thtdAtts, "height"); + + addThElement (bodyContent, thtdAtts); + addTdElement (bodyContent, thtdAtts); + addCaptionElement (textContent, valignAtt); + + addHeadElement (); + addTitleElement (); + addIsindexElement (); + addBaseElement (); + addMetaElement (); + addScriptElement (); + addStyleElement (); + + /* The HTML element */ + name = "html"; + List htmlContent = new ArrayList (2); + htmlContent.add ("head"); + htmlContent.add ("body"); + td = new HtmlTagDesc (name, false, false, htmlContent, null); + td.setAttributes (new String[] {"version" }); + stSupportedElements.put (name, td); + } + + /** Constructor. */ + public Html3_2DocDesc () + { + // publish stSupportedElements to superclass + supportedElements = stSupportedElements; + init (); + } + + /** Static initializers for each element. If elements are common to more + * than one HTML version, they should be moved into the superclass. + * Different initializers may have different argument lists. */ + private static void addAddressElement (List textContent) + { + String name = "address"; + List addressContent = new ArrayList (36); + addressContent.addAll (textContent); + addressContent.add ("p"); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, addressContent, null); + stSupportedElements.put (name, td); + } + + /* Initializer for "A" (anchor) element. */ + private static void addAElement (List textContent) + { + /* The Anchor (A) element */ + String name = "a"; + HtmlTagDesc td = new HtmlTagDesc (name, true, true, textContent, null); + td.setAttributes (new String[] + {"name", "href", "rel", "rev", "title" }); + td.setExcludedContent(new String[] { "a" }); + stSupportedElements.put (name, td); + + } + + /* Initializer for APPLET element. */ + private static void addAppletElement + (HtmlAttributeDesc ialignAtt, List textContent) + { + String name = "applet"; + List atts = new ArrayList (9); + addSimpleAttribute (atts, "codebase"); + atts.add (new HtmlAttributeDesc + ("code", null, HtmlAttributeDesc.REQUIRED)); + addSimpleAttribute (atts, "alt"); + addSimpleAttribute (atts, "name"); + atts.add (new HtmlAttributeDesc + ("width", null, HtmlAttributeDesc.REQUIRED)); + atts.add (new HtmlAttributeDesc + ("height", null, HtmlAttributeDesc.REQUIRED)); + atts.add (ialignAtt); + addSimpleAttribute (atts, "hspace"); + addSimpleAttribute (atts, "vspace"); + List appletContent = new ArrayList (36); + appletContent.addAll (textContent); + appletContent.add ("param"); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, appletContent, atts); + stSupportedElements.put (name, td); + + } + + private static void addAreaElement () + { + String name = "area"; + List atts = new ArrayList (5); + atts.add (new HtmlAttributeDesc ("shape", + new String[] {"rect", "circle", "poly" }, + HtmlAttributeDesc.REQUIRED)); + addSimpleAttribute (atts, "coords"); + addSimpleAttribute (atts, "href"); + atts.add (new HtmlAttributeDesc ("nohref", + new String[] {"nohref"}, + HtmlAttributeDesc.IMPLIED)); + atts.add (new HtmlAttributeDesc ("alt", + null, + HtmlAttributeDesc.REQUIRED)); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, null, atts); + stSupportedElements.put (name, td); + } + + private static void addBaseElement () + { + String name = "base"; + List atts = new ArrayList (1); + addSimpleAttribute (atts, "href"); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, null, atts); + stSupportedElements.put (name, td); + } + private static void addBasefontElement () + { + String name = "basefont"; + HtmlTagDesc td = new HtmlTagDesc (name, true, false, null, null); + td.setAttributes (new String[] {"size"}); + stSupportedElements.put (name, td); + } + + private static void addBlockquoteElement (List bodyContent) + { + /* The BLOCKQUOTE element */ + String name = "blockquote"; + HtmlTagDesc td = new HtmlTagDesc (name, true, true, bodyContent, null); + stSupportedElements.put (name, td); + } + + private static void addBodyElement (List bodyContent) + { + String name = "body"; + HtmlTagDesc td = new HtmlTagDesc (name, true, true, bodyContent, null); + td.setAttributes (new String [] + {"bgcolor", "text", "link", "vlink", "alink", "background" }); + stSupportedElements.put (name, td); + + } + + private static void addBrElement () + { + String name = "br"; + HtmlTagDesc td = new HtmlTagDesc (name, true, false, null, null); + stSupportedElements.put (name, td); + } + + private static void addCaptionElement (List textContent, + HtmlAttributeDesc valignAtt) + { + String name = "caption"; + List atts = new ArrayList (1); + atts.add (valignAtt); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, textContent, atts); + stSupportedElements.put (name, td); + } + private static void addCenterElement (List bodyContent) + { + String name = "center"; + HtmlTagDesc td = new HtmlTagDesc (name, true, true, bodyContent, null); + stSupportedElements.put (name, td); + } + + private static void addDdElement (List flowContent) + { + String name = "dd"; + HtmlTagDesc td = new HtmlTagDesc (name, true, false, flowContent, null); + stSupportedElements.put (name, td); + } + + private static void addDirElement (List listContent) + { + String name = "dir"; + List atts = new ArrayList (1); + addSelfAttribute (atts, "compact"); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, listContent, atts); + stSupportedElements.put (name, td); + } + + private static void addDivElement (List bodyContent, HtmlAttributeDesc halignAtt) + { + String name = "div"; + List atts = new ArrayList (1); + atts.add (halignAtt); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, bodyContent, atts); + stSupportedElements.put (name, td); + } + + private static void addDlElement () + { + String name = "dl"; + List dlContent = new ArrayList (2); + addStringsToList(new String[] { "dt", "dd" }, dlContent); + List atts = new ArrayList (1); + addSelfAttribute(atts, "compact"); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, dlContent, atts); + stSupportedElements.put (name, td); + } + + private static void addDtElement (List textContent) + { + String name = "dt"; + HtmlTagDesc td = new HtmlTagDesc (name, true, false, textContent, null); + stSupportedElements.put (name, td); + } + + private static void addFontElement (List textContent) + { + String name = "font"; + HtmlTagDesc td = new HtmlTagDesc (name, true, true, textContent, null); + td.setAttributes (new String[] {"size", "color"}); + stSupportedElements.put (name, td); + + } + + private static void addFormElement (List bodyContent) + { + final String name = "form"; + List atts = new ArrayList (3); + addSimpleAttribute (atts, "action"); + atts.add (new HtmlAttributeDesc ("method", null, + HtmlAttributeDesc.OTHER)); + atts.add (new HtmlAttributeDesc ("enctype", null, + HtmlAttributeDesc.OTHER)); + List formContent = new ArrayList (bodyContent.size ()); + formContent.addAll (bodyContent); + removeStringsFromList (formContent, new String[] { "form" }); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, formContent, atts); + stSupportedElements.put (name, td); + } + + private static void addHeadElement () + { + String name = "head"; + String[] headMisc = new String[] + {"script", "style", "meta", "link" }; + List headContent = new ArrayList (7); + headContent.add ("title"); + headContent.add ("isindex"); + headContent.add ("base"); + HtmlTagDesc td = new HtmlTagDesc (name, false, false, headContent, null); + stSupportedElements.put (name, td); + /* Attributes TITLE (required), ISINDEX (optional), and BASE (optional) + * are supposed to come in that order, ahead of anything else. + * For the moment, just toss them in with the rest. */ + addStringsToList (headMisc, headContent); + } + + private static void addHrElement (HtmlAttributeDesc halignAtt) + { + String name = "hr"; + List atts = new ArrayList (4); + + atts.add (halignAtt); + addSelfAttribute (atts, "noshade"); + addSimpleAttribute (atts, "size"); + addSimpleAttribute (atts, "width"); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, null, atts); + stSupportedElements.put (name, td); + } + + private static void addImgElement (HtmlAttributeDesc ialignAtt) + { + String name = "img"; + List atts = new ArrayList (10); + addRequiredAttribute (atts, "src"); + addSimpleAttribute (atts, "alt"); + atts.add (ialignAtt); + addSimpleAttribute (atts, "height"); + addSimpleAttribute (atts, "width"); + addSimpleAttribute (atts, "border"); + addSimpleAttribute (atts, "hspace"); + addSimpleAttribute (atts, "vspace"); + addSimpleAttribute (atts, "usemap"); + addSelfAttribute (atts, "ismap"); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, null, atts); + stSupportedElements.put (name, td); + } + + private static void addInputElement (HtmlAttributeDesc ialignAtt) + { + final String name = "input"; + List atts = new ArrayList (8); + atts.add (new HtmlAttributeDesc ("type", + new String[] {"text", "password", "checkbox", "radio", "submit", + "reset", "file", "hidden", "image"}, + HtmlAttributeDesc.OTHER)); + addSimpleAttribute (atts, "name"); + addSimpleAttribute (atts, "value"); + addSelfAttribute (atts, "checked"); + addSimpleAttribute (atts, "size"); + addSimpleAttribute (atts, "maxlength"); + addSimpleAttribute (atts, "src"); + atts.add (ialignAtt); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, null, atts); + stSupportedElements.put (name, td); + } + + private static void addIsindexElement () + { + final String name = "isindex"; + List atts = new ArrayList (1); + addSimpleAttribute (atts, "prompt"); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, null, atts); + stSupportedElements.put (name, td); + } + + private static void addLiElement (List flowContent) + { + final String name = "li"; + List atts = new ArrayList (2); + addSimpleAttribute (atts, "type"); + addSimpleAttribute (atts, "value"); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, flowContent, atts); + stSupportedElements.put (name, td); + } + + private static void addLinkElement () + { + final String name = "link"; + HtmlTagDesc td = new HtmlTagDesc (name, true, false, null, null); + td.setAttributes (new String [] + { "href", "rel", "rev", "title" }); + stSupportedElements.put (name, td); + + } + + private static void addMapElement () + { + final String name = "map"; + List atts = new ArrayList (1); + addSimpleAttribute (atts, "name"); + List mapContent = new ArrayList (1); + mapContent.add ("area"); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, mapContent, atts); + stSupportedElements.put (name, td); + } + + private static void addMenuElement (List listContent) + { + final String name = "menu"; + List atts = new ArrayList (1); + addSelfAttribute (atts, "compact"); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, listContent, atts); + stSupportedElements.put (name, td); + } + + private static void addMetaElement () + { + final String name = "meta"; + List atts = new ArrayList (3); + addSimpleAttribute (atts, "http-equiv"); + addSimpleAttribute (atts, "name"); + addRequiredAttribute (atts, "content"); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, null, atts); + stSupportedElements.put (name, td); + } + + private static void addOlElement (List listContent) + { + final String name = "ol"; + List atts = new ArrayList (3); + addSimpleAttribute (atts, "type"); + addSimpleAttribute (atts, "start"); + addSelfAttribute (atts, "compact"); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, listContent, atts); + stSupportedElements.put (name, td); + } + + private static void addOptionElement () + { + final String name = "option"; + List atts = new ArrayList (2); + addSelfAttribute (atts, "selected"); + addSimpleAttribute (atts,"value"); + List content = new ArrayList (1); + content.add (HtmlSpecialToken.PCDATA); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, content, atts); + stSupportedElements.put (name, td); + } + + private static void addPElement (HtmlAttributeDesc halignAtt, List textContent) + { + final String name = "p"; + List atts = new ArrayList (1); + atts.add (halignAtt); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, textContent, atts); + stSupportedElements.put (name, td); + } + + private static void addParamElement () + { + final String name = "param"; + List atts = new ArrayList (2); + addRequiredAttribute (atts, "name"); + addSimpleAttribute (atts, "value"); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, null, atts); + stSupportedElements.put (name, td); + } + + private static void addPreElement (List textContent) + { + final String name = "pre"; + List atts = new ArrayList (1); + addSimpleAttribute(atts, "width"); + List preContent = new ArrayList (35); + preContent.addAll(textContent); + /* Take out excluded elements */ + removeStringsFromList (preContent, + new String [] + {"img", "big", "small", "sub", "sup", "font"}); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, preContent, atts); + stSupportedElements.put (name, td); + } + + private static void addScriptElement () + { + /* In HTML 3.2, this is just a placeholder */ + final String name = "script"; + List content = new ArrayList (1); + content.add (HtmlSpecialToken.PCDATA); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, content, null); + stSupportedElements.put (name, td); + } + + private static void addSelectElement () + { + final String name = "select"; + List atts = new ArrayList (3); + addSimpleAttribute (atts, "name"); + addSimpleAttribute (atts, "size"); + addSelfAttribute (atts, "multiple"); + List content = new ArrayList (1); + content.add ("option"); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, content, atts); + stSupportedElements.put (name, td); + } + + private static void addStyleElement () + { + /* In HTML 3.2, this is just a placeholder */ + final String name = "style"; + List content = new ArrayList (1); + content.add (HtmlSpecialToken.PCDATA); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, content, null); + stSupportedElements.put (name, td); + } + + private static void addTableElement () + { + final String name = "table"; + List atts = new ArrayList (5); + atts.add (new HtmlAttributeDesc ("align", + new String[] {"left", "center", "right"}, + HtmlAttributeDesc.IMPLIED)); + addSimpleAttribute (atts, "width"); + addSimpleAttribute (atts, "border"); + addSimpleAttribute (atts, "cellspacing"); + addSimpleAttribute (atts, "cellpadding"); + List content = new ArrayList (2); + content.add ("caption"); + content.add ("tr"); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, content, atts); + stSupportedElements.put (name, td); + } + + private static void addTextareaElement () + { + final String name = "textarea"; + List atts = new ArrayList (3); + addSimpleAttribute (atts, "name"); + addSimpleAttribute (atts, "rows"); + addSimpleAttribute (atts, "cols"); + + List content = new ArrayList (1); + content.add (HtmlSpecialToken.PCDATA); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, content, atts); + stSupportedElements.put (name, td); + } + + private static void addTdElement (List bodyContent, List thtdAtts) + { + final String name = "td"; + HtmlTagDesc td = new HtmlTagDesc (name, true, false, bodyContent, thtdAtts); + stSupportedElements.put (name, td); + } + + private static void addThElement (List bodyContent, List thtdAtts) + { + final String name = "th"; + HtmlTagDesc td = new HtmlTagDesc (name, true, false, bodyContent, thtdAtts); + stSupportedElements.put (name, td); + } + + private static void addTitleElement () + { + /* I'm confused by the DTD for this one. + * Content consists only of PCDATA, but certain elements are + * specifically excluded from its content. This seems + * redundant. */ + String name = "title"; + List pcdataContent = new ArrayList (1); + pcdataContent.add (HtmlSpecialToken.PCDATA); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, pcdataContent, null); + stSupportedElements.put (name, td); + } + + private static void addTrElement + (HtmlAttributeDesc halignAtt, HtmlAttributeDesc valignAtt) + { + final String name = "tr"; + List atts = new ArrayList (2); + atts.add (halignAtt); + atts.add (valignAtt); + List content = new ArrayList (2); + content.add ("th"); + content.add ("td"); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, content, atts); + stSupportedElements.put (name, td); + } + + private static void addUlElement (List listContent) + { + final String name = "ul"; + List atts = new ArrayList (2); + addSimpleAttribute (atts, "type"); + addSelfAttribute (atts, "compact"); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, listContent, atts); + stSupportedElements.put (name, td); + } + + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4DocDesc.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4DocDesc.java new file mode 100644 index 00000000..0da6ae8d --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4DocDesc.java @@ -0,0 +1,528 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.html; +import java.util.*; + +/** + * Abstract class for common features of HTML 4.0 and 4.01 + * documents. + * + * @author Gary McGath + * + */ +public abstract class Html4DocDesc extends HtmlDocDesc { + + /** Names of font-related elements. */ + protected static String[] fontMarkup; + /** Names of phrase elements. */ + protected static String[] phraseMarkup; + /** Names of special elements. */ + protected static String[] specialMarkup; + /** Names of form elements. */ + protected static String[] formMarkup; + /** Names of list elements. */ + protected static String[] listMarkup; + + /** List of block elements. */ + protected static List blockContent; + /** List of flow elements. */ + protected static List flowContent; + /** List of inline elements. */ + protected static List inlineContent; + /** List consisting of the LI element. */ + protected static List listContent; + /** List of elements for the BODY element and some other elements. */ + protected static List bodyContent; + + + /** Core attributes list. */ + protected static List coreAttrs; + /** Internationalization attributes list. */ + protected static List i18nAttrs; + /** Event attributes list. */ + protected static List eventAttrs; + /** Big attributes list. The dtd calls this "attrs", but here + * it's called lotsaAttrs to avoid confusion with common + * local variables. */ + protected static List bigAttrs; + /** Big attributes plus reserved attributes. */ + protected static List biggerAttrs; + /** Attributes described as "reserved for future use." */ + protected static List reservedAttrs; + + /** Horizontal alignment attribute for cells. */ + protected static HtmlAttributeDesc halignAtt; + /** Vertical alignment attribute for cells. */ + protected static HtmlAttributeDesc valignAtt; + + /** Attributes for TH and TD elements */ + protected static List thtdAtts; + + /** Initialization code. This is called from the static initializer + * of our subclasses. */ + protected static void classInit4 (Map stSupportedElements) + { + phraseMarkup = new String[] + { "em", "strong", "dfn", "code", "samp", "kbd", "var", "cite", + "abbr", "acronym" }; + formMarkup = new String[] + { "input", "select", "textarea", "label", "button" }; + + /* Core attrs list, used for various elements */ + coreAttrs = new ArrayList (4); + addSimpleAttribute (coreAttrs, "id"); + addSimpleAttribute (coreAttrs, "class"); + addSimpleAttribute (coreAttrs, "style"); + addSimpleAttribute (coreAttrs, "title"); + + /* Internationalization attrs list */ + i18nAttrs = new ArrayList (2); + addSimpleAttribute (i18nAttrs, "lang"); + i18nAttrs.add (new HtmlAttributeDesc ("id", + new String[] {"ltr", "rtl"}, + HtmlAttributeDesc.IMPLIED)); + + /* Event attrs list */ + eventAttrs = new ArrayList (10); + addSimpleAttribute (eventAttrs, "onclick"); + addSimpleAttribute (eventAttrs, "ondblclick"); + addSimpleAttribute (eventAttrs, "onmousedown"); + addSimpleAttribute (eventAttrs, "onmouseup"); + addSimpleAttribute (eventAttrs, "onmouseover"); + addSimpleAttribute (eventAttrs, "onmousemove"); + addSimpleAttribute (eventAttrs, "onmouseout"); + addSimpleAttribute (eventAttrs, "onkeypress"); + addSimpleAttribute (eventAttrs, "onkeydown"); + addSimpleAttribute (eventAttrs, "onkeyup"); + + bigAttrs = new ArrayList + (coreAttrs.size() + i18nAttrs.size() + eventAttrs.size()); + bigAttrs.addAll (coreAttrs); + bigAttrs.addAll (i18nAttrs); + bigAttrs.addAll (eventAttrs); + + /* Attributes described as "reserved for future use." */ + reservedAttrs = new ArrayList (3); + addSimpleAttribute (reservedAttrs, "datasrc"); + addSimpleAttribute (reservedAttrs, "datafld"); + addSimpleAttribute (reservedAttrs, "dataformatas"); // yes, spelled that way + + /* Big attributes plus reserved attributes. */ + biggerAttrs = new ArrayList (bigAttrs.size() + 3); + biggerAttrs.addAll (bigAttrs); + biggerAttrs.addAll (reservedAttrs); + + /* Reusable attributes for cell alignment. */ + halignAtt = new HtmlAttributeDesc + ("align", + new String [] {"left", "center", "right", "justify", "char"}, + HtmlAttributeDesc.IMPLIED); + valignAtt = + new HtmlAttributeDesc ("valign", + new String[] { "top", "middle", "bottom", "baseline" }, + HtmlAttributeDesc.IMPLIED); + } + + /** Static initializers for each element. If elements are common to more + * than one HTML version, they should be moved into the superclass. + * Different initializers may have different argument lists. */ + + /** Defines the ADDRESS element. */ + protected static void addAddressElement (Map stSupportedElements) + { + String name = "address"; + List addressContent = new ArrayList (36); + addressContent.addAll (inlineContent); + addressContent.add ("p"); + HtmlTagDesc td = + new HtmlTagDesc (name, true, true, addressContent, bigAttrs); + stSupportedElements.put (name, td); + } + + /** Defines the BDO element. */ + protected static void addBdoElement (Map stSupportedElements) + { + String name = "bdo"; + List atts = new ArrayList (coreAttrs.size () + 2); + atts.addAll (coreAttrs); + addSimpleAttribute (atts, "lang"); + atts.add (new HtmlAttributeDesc ("dir", + new String[] { "ltr", "rtl" }, HtmlAttributeDesc.REQUIRED)); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, inlineContent, atts); + stSupportedElements.put (name, td); + } + + /** Defines the BODY element. */ + protected static void addBodyElement (Map stSupportedElements) + { + /* bodyContent is different for transitional and strict, but + * the code in this function is common to both. */ + String name = "body"; + List atts = new ArrayList (bigAttrs.size () + 2); + atts.addAll (bigAttrs); + addSimpleAttribute (atts, "onload"); + addSimpleAttribute (atts, "onunload"); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, bodyContent, atts); + stSupportedElements.put (name, td); + } + + /** Defines the COL element. */ + protected static void addColElement + (Map stSupportedElements, List cellalignAttrs) + { + String name = "col"; + List atts = new ArrayList (bigAttrs.size () + 8); + atts.addAll (bigAttrs); + atts.addAll (cellalignAttrs); + addSimpleAttribute (atts, "span"); + addSimpleAttribute (atts, "width"); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, null, atts); + stSupportedElements.put (name, td); + } + + /** Defines the COLGROUP element. */ + protected static void addColgroupElement + (Map stSupportedElements, List cellalignAttrs) + { + String name = "colgroup"; + List content = new ArrayList (1); + content.add ("col"); + List atts = new ArrayList (bigAttrs.size () + 8); + atts.addAll (bigAttrs); + atts.addAll (cellalignAttrs); + addSimpleAttribute (atts, "span"); + addSimpleAttribute (atts, "width"); + + HtmlTagDesc td = new HtmlTagDesc (name, true, false, content, atts); + stSupportedElements.put (name, td); + } + + /** Defines the DD element. */ + protected static void addDdElement (Map stSupportedElements) + { + String name = "dd"; + HtmlTagDesc td = + new HtmlTagDesc (name, true, false, flowContent, bigAttrs); + stSupportedElements.put (name, td); + } + + /** Defines the DEL element. */ + protected static void addDelElement (Map stSupportedElements) + { + final String name = "del"; + List atts = new ArrayList (bigAttrs.size () + 2); + atts.addAll (bigAttrs); + addSimpleAttribute (atts, "cite"); + addSimpleAttribute (atts, "datetime"); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, flowContent, atts); + stSupportedElements.put (name, td); + } + + /** Defines the DT element. */ + protected static void addDtElement (Map stSupportedElements) + { + String name = "dt"; + HtmlTagDesc td = + new HtmlTagDesc (name, true, false, inlineContent, bigAttrs); + stSupportedElements.put (name, td); + } + + /** Defines the FIELDSET element. */ + protected static void addFieldsetElement (Map stSupportedElements) + { + String name = "fieldset"; + List content = new ArrayList (flowContent.size () + 3); + content.addAll (flowContent); + content.add (HtmlSpecialToken.PCDATA); + content.add ("legend"); + + HtmlTagDesc td = + new HtmlTagDesc (name, true, true, content, bigAttrs); + stSupportedElements.put (name, td); + } + + /** Defines the INS element. */ + protected static void addInsElement (Map stSupportedElements) + { + final String name = "ins"; + List atts = new ArrayList (bigAttrs.size () + 2); + atts.addAll (bigAttrs); + addSimpleAttribute (atts, "cite"); + addSimpleAttribute (atts, "datetime"); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, flowContent, atts); + stSupportedElements.put (name, td); + } + + /** Defines the LABEL element. */ + protected static void addLabelElement + (Map stSupportedElements) + { + final String name = "label"; + List atts = new ArrayList (bigAttrs.size () + 4); + atts.addAll (bigAttrs); + addSimpleAttribute (atts, "for"); + addSimpleAttribute (atts, "accesskey"); + addSimpleAttribute (atts, "onfocus"); + addSimpleAttribute (atts, "onblur"); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, inlineContent, atts); + stSupportedElements.put (name, td); + } + + /** Defines the MAP element. + * HTML 4.0 and 4.01 actually have different definitions here. 4.0 + * allows block content or AREA elements, but not a mix of the two; + * 4.01 allows a mix of the two. The current version of the code + * doesn't allow that distinction to be expressed. (There are no + * differences between Strict and Transitional.) */ + protected static void addMapElement (Map stSupportedElements) + { + final String name = "map"; + List atts = new ArrayList (bigAttrs.size () + 1); + atts.addAll (bigAttrs); + addSimpleAttribute (atts, "name"); + List mapContent = new ArrayList (1); + mapContent.addAll (blockContent); + mapContent.add ("area"); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, mapContent, atts); + stSupportedElements.put (name, td); + } + + /** Defines the META element. */ + protected static void addMetaElement (Map stSupportedElements) + { + final String name = "meta"; + List atts = new ArrayList (3); + addSimpleAttribute (atts, "http-equiv"); + addSimpleAttribute (atts, "name"); + addRequiredAttribute (atts, "content"); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, null, atts); + stSupportedElements.put (name, td); + } + + /** Defines the OPTGROUP (option group) element. */ + protected static void addOptgroupElement + (Map stSupportedElements) + { + final String name = "option"; + List atts = new ArrayList (bigAttrs.size () + 2); + atts.addAll (bigAttrs); + addSelfAttribute (atts, "selected"); + addSimpleAttribute (atts,"label"); + List content = new ArrayList (1); + content.add ("option"); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, content, atts); + stSupportedElements.put (name, td); + } + + /** Defines the OPTION element. */ + protected static void addOptionElement + (Map stSupportedElements) + { + final String name = "option"; + List atts = new ArrayList (bigAttrs.size () + 4); + atts.addAll (bigAttrs); + addSelfAttribute (atts, "selected"); + addSelfAttribute (atts, "disabled"); + addSimpleAttribute (atts, "label"); + addSimpleAttribute (atts, "value"); + List content = new ArrayList (1); + content.add (HtmlSpecialToken.PCDATA); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, content, atts); + stSupportedElements.put (name, td); + } + + /** Defines the PARAM element. */ + protected static void addParamElement (Map stSupportedElements) + { + final String name = "param"; + List atts = new ArrayList (2); + addRequiredAttribute (atts, "name"); + addSimpleAttribute (atts, "value"); + atts.add (new HtmlAttributeDesc ("valuetype", + new String [] { "data", "ref", "object" }, + HtmlAttributeDesc.OTHER )); + addSimpleAttribute (atts, "type"); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, null, atts); + stSupportedElements.put (name, td); + } + + /** Defines the Q (short quote) element. */ + protected static void addQElement (Map stSupportedElements) + { + final String name = "q"; + List atts = new ArrayList (bigAttrs.size () + 1); + atts.addAll (bigAttrs); + addSimpleAttribute (atts, "cite"); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, inlineContent, atts); + stSupportedElements.put (name, td); + } + + /** Defines the SELECT element. */ + protected static void addSelectElement (Map stSupportedElements) + { + final String name = "select"; + List atts = new ArrayList (biggerAttrs.size () + 10); + atts.addAll (biggerAttrs); + addSimpleAttribute (atts, "name"); + addSimpleAttribute (atts, "size"); + addSelfAttribute (atts, "multiple"); + addSelfAttribute (atts, "disabled"); + addSimpleAttribute (atts, "tabindex"); + addSimpleAttribute (atts, "onfocus"); + addSimpleAttribute (atts, "onblur"); + addSimpleAttribute (atts, "onchange"); + List content = new ArrayList (2); + content.add ("option"); + content.add ("optgroup"); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, content, atts); + stSupportedElements.put (name, td); + } + + + /** Defines the SPAN element. */ + protected static void addSpanElement (Map stSupportedElements) + { + final String name = "span"; + HtmlTagDesc td = + new HtmlTagDesc (name, true, true, inlineContent, biggerAttrs); + stSupportedElements.put (name, td); + } + + + /** Defines the STYLE element. */ + protected static void addStyleElement (Map stSupportedElements) + { + final String name = "style"; + List content = new ArrayList (1); + content.add (HtmlSpecialToken.PCDATA); + List atts = new ArrayList (6); + atts.addAll (i18nAttrs); + addSimpleAttribute (atts, "type"); + addSimpleAttribute (atts, "media"); + addSimpleAttribute (atts, "title"); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, content, atts); + stSupportedElements.put (name, td); + } + + /** Defines the SUB (subscript) element. */ + protected static void addSubElement (Map stSupportedElements) + { + final String name = "sub"; + HtmlTagDesc td = new HtmlTagDesc + (name, true, true, inlineContent, bigAttrs); + stSupportedElements.put (name, td); + } + + /** Defines the SUP (superscript) element. */ + protected static void addSupElement (Map stSupportedElements) + { + final String name = "sup"; + HtmlTagDesc td = new HtmlTagDesc + (name, true, true, inlineContent, bigAttrs); + stSupportedElements.put (name, td); + } + + + /** Defines the TEXTAREA element. */ + protected static void addTextareaElement (Map stSupportedElements) + { + final String name = "textarea"; + List atts = new ArrayList (biggerAttrs.size () + 12); + addSimpleAttribute (atts, "name"); + addSimpleAttribute (atts, "rows"); + addSimpleAttribute (atts, "cols"); + addSelfAttribute (atts, "disabled"); + addSelfAttribute (atts, "readonly"); + addSimpleAttribute (atts, "tabindex"); + addSimpleAttribute (atts, "accesskey"); + addSimpleAttribute (atts, "onfocus"); + addSimpleAttribute (atts, "onblur"); + addSimpleAttribute (atts, "onselect"); + addSimpleAttribute (atts, "onchange"); + + List content = new ArrayList (1); + content.add (HtmlSpecialToken.PCDATA); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, content, atts); + stSupportedElements.put (name, td); + } + + /** Defines the TBODY element. */ + protected static void addTbodyElement (Map stSupportedElements, List cellalignAttrs) + { + final String name = "tbody"; + List content = new ArrayList (1); + content.add ("tr"); + List atts = new ArrayList (bigAttrs.size () + 8); + atts.addAll (bigAttrs); + atts.addAll (cellalignAttrs); + HtmlTagDesc td = new HtmlTagDesc (name, false, false, content, atts); + stSupportedElements.put (name, td); + } + + /** Defines the TD element. + * The strict and transitional versions have + * different attribute sets, but this is taken care of by the + * initialization of thtdAtts. */ + protected static void addTdElement (Map stSupportedElements) + { + final String name = "td"; + HtmlTagDesc td = new HtmlTagDesc (name, true, false, flowContent, thtdAtts); + stSupportedElements.put (name, td); + } + + + /** Defines the TFOOT element. */ + protected static void addTfootElement (Map stSupportedElements, List cellalignAttrs) + { + final String name = "tfoot"; + List content = new ArrayList (1); + content.add ("tr"); + List atts = new ArrayList (bigAttrs.size () + 8); + atts.addAll (bigAttrs); + atts.addAll (cellalignAttrs); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, content, atts); + stSupportedElements.put (name, td); + } + + /** Defines the TH element. + * The strict and transitional versions have + * different attribute sets, but this is taken care of by the + * initialization of thtdAtts. */ + protected static void addThElement (Map stSupportedElements) + { + final String name = "th"; + HtmlTagDesc td = new HtmlTagDesc (name, true, false, flowContent, thtdAtts); + stSupportedElements.put (name, td); + } + + /** Defines the THEAD element. */ + protected static void addTheadElement (Map stSupportedElements, List cellalignAttrs) + { + final String name = "thead"; + List content = new ArrayList (1); + content.add ("tr"); + List atts = new ArrayList (bigAttrs.size () + 8); + atts.addAll (bigAttrs); + atts.addAll (cellalignAttrs); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, content, atts); + stSupportedElements.put (name, td); + } + + /** Defines the TITLE element. */ + protected static void addTitleElement (Map stSupportedElements) + { + /* I'm confused by the DTD for this one. + * Content consists only of PCDATA, but certain elements are + * specifically excluded from its content. This seems + * redundant. */ + String name = "title"; + List pcdataContent = new ArrayList (1); + pcdataContent.add (HtmlSpecialToken.PCDATA); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, pcdataContent, i18nAttrs); + stSupportedElements.put (name, td); + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4StrictDocDesc.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4StrictDocDesc.java new file mode 100644 index 00000000..3139edb4 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4StrictDocDesc.java @@ -0,0 +1,452 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.html; + +import java.util.*; + +/** + * Abstract class for common features of HTML 4.0 and 4.01 strict + * documents. The differences between 4.0 and 4.01 are minor, so + * most of the code is found here. + * + * @author Gary McGath + * + */ +public abstract class Html4StrictDocDesc extends Html4DocDesc { + + + /** Code which is called from the static initializer of the + * subclasses. Note that this is called before the class + * is instantiated, and may reference only static fields + * and subroutines. */ + protected static void classInit4 (Map stSupportedElements) + { + Html4DocDesc.classInit4(stSupportedElements); + + String name; + HtmlTagDesc td; + + fontMarkup = new String[] + { "tt", "i", "b", "big", "small" }; + listMarkup = new String [] + { "ul", "ol" }; + specialMarkup = new String[] + { "a", "img", "object", "br", "script", "map", + "q", "sub", "sup", "span", "bdo" }; + /* inlineContent lists all the content types which are permitted in + * the markup elements. For a first cut, strings signify the + * name of the element which is permitted. */ + inlineContent = new ArrayList(35); + addStringsToList (fontMarkup, inlineContent); + addStringsToList (phraseMarkup, inlineContent); + addStringsToList (specialMarkup, inlineContent); + addStringsToList (formMarkup, inlineContent); + inlineContent.add (HtmlSpecialToken.PCDATA); + + blockContent = new ArrayList (20); + addStringsToList (headings, blockContent); + addStringsToList (listMarkup, blockContent); + String[] blockMisc = new String[] + {"p", "pre", "dl", "div", "noscript", + "blockquote", "form", "hr", "table", "fieldset", "address" }; + addStringsToList (blockMisc, blockContent); + + flowContent = new ArrayList (30); + flowContent.addAll (blockContent); + flowContent.addAll (inlineContent); + + /* Content for the BODY element, also used for other elements */ + bodyContent = new ArrayList (blockContent.size () + 3); + bodyContent.addAll (blockContent); + bodyContent.add ("script"); + bodyContent.add ("ins"); + bodyContent.add ("del"); + + listContent = new ArrayList (1); + listContent.add ("li"); + + /* Text elements */ + int i; + for (i = 0; i < fontMarkup.length; i++) { + name = fontMarkup[i]; + td = new HtmlTagDesc (name, true, true, inlineContent, bigAttrs); + stSupportedElements.put (name, td); + } + + /* Phrase elements. */ + for (i = 0; i < phraseMarkup.length; i++) { + name = phraseMarkup[i]; + td = new HtmlTagDesc (name, true, true, inlineContent, bigAttrs); + stSupportedElements.put (name, td); + } + + thtdAtts = new ArrayList (bigAttrs.size() + 7); // common attribute list for TH and TD + thtdAtts.addAll (bigAttrs); + addSimpleAttribute (thtdAtts, "abbr"); + addSimpleAttribute (thtdAtts, "axis"); + addSimpleAttribute (thtdAtts, "headers"); + addSimpleAttribute (thtdAtts, "scope"); + thtdAtts.add (halignAtt); + thtdAtts.add (valignAtt); + addSimpleAttribute (thtdAtts, "rowspan"); + addSimpleAttribute (thtdAtts, "colspan"); + + } + + + /** Static initializers for each element. If elements are common to more + * than one HTML version, they should be moved into the superclass. + * Different initializers may have different argument lists. */ + + /** Defines the A element. */ + protected static void addAElement (Map stSupportedElements) + { + /* The Anchor (A) element */ + String name = "a"; + List atts = new ArrayList (bigAttrs.size () + 14); + atts.addAll (bigAttrs); + addSimpleAttribute (atts, "type"); + addSimpleAttribute (atts, "name"); + addSimpleAttribute (atts, "href"); + addSimpleAttribute (atts, "hreflang"); + addSimpleAttribute (atts, "rel"); + addSimpleAttribute (atts, "rev"); + addSimpleAttribute (atts, "accesskey"); + addSimpleAttribute (atts, "shape"); + addSimpleAttribute (atts, "rect"); + addSimpleAttribute (atts, "coords"); + addSimpleAttribute (atts, "tabindex"); + addSimpleAttribute (atts, "onfocus"); + addSimpleAttribute (atts, "onblur"); + List content = new ArrayList (inlineContent.size ()); + content.addAll (inlineContent); + content.remove ("a"); + + HtmlTagDesc td = new HtmlTagDesc (name, true, true, content, atts); + stSupportedElements.put (name, td); + } + + + /** Defines the ADDRESS element. */ + protected static void addAddressElement (Map stSupportedElements) + { + String name = "address"; + HtmlTagDesc td = + new HtmlTagDesc (name, true, true, inlineContent, bigAttrs); + stSupportedElements.put (name, td); + } + + protected static void addAreaElement + (Map stSupportedElements, HtmlAttributeDesc shapeAtt) + { + String name = "area"; + List atts = new ArrayList (5); + atts.add (shapeAtt); + addSimpleAttribute (atts, "coords"); + addSimpleAttribute (atts, "href"); + atts.add (new HtmlAttributeDesc ("nohref", + new String[] {"nohref"}, + HtmlAttributeDesc.IMPLIED)); + atts.add (new HtmlAttributeDesc ("alt", + null, + HtmlAttributeDesc.REQUIRED)); + addSimpleAttribute (atts, "tabindex"); + addSimpleAttribute (atts, "accesskey"); + addSimpleAttribute (atts, "onfocus"); + addSimpleAttribute (atts, "onblur"); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, null, atts); + stSupportedElements.put (name, td); + } + + protected static void addBaseElement (Map stSupportedElements) + { + String name = "base"; + List atts = new ArrayList (1); + addRequiredAttribute (atts, "href"); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, null, atts); + stSupportedElements.put (name, td); + } + + protected static void addBlockquoteElement (Map stSupportedElements) + { + String name = "blockquote"; + List content = new ArrayList (blockContent.size () + 1); + content.addAll (blockContent); + content.add ("script"); + List atts = new ArrayList (bigAttrs.size () + 1); + atts.addAll (bigAttrs); + addSimpleAttribute (atts, "cite"); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, content, atts); + stSupportedElements.put (name, td); + } + + protected static void addBrElement (Map stSupportedElements, List coreAttrs) + { + String name = "br"; + HtmlTagDesc td = new HtmlTagDesc (name, true, false, null, coreAttrs); + stSupportedElements.put (name, td); + } + + protected static void addButtonElement + (Map stSupportedElements) + { + String name = "button"; + List content = new ArrayList (formMarkup.length + 3); + addStringsToList(formMarkup, content); + content.add ("form"); + content.add ("fieldset"); + List atts = new ArrayList (biggerAttrs.size () + 8); + atts.addAll (biggerAttrs); + addSimpleAttribute (atts, "name"); + addSimpleAttribute (atts, "value"); + atts.add (new HtmlAttributeDesc ("type", + new String[] {"button", "submit" , "reset"}, + HtmlAttributeDesc.OTHER)); + addSelfAttribute (atts, "disabled"); + addSimpleAttribute (atts, "tabindex"); + addSimpleAttribute (atts, "accesskey"); + addSimpleAttribute (atts, "onfocus"); + addSimpleAttribute (atts, "onblur"); + } + + + protected static void addCaptionElement + (Map stSupportedElements, List inlineContent, + HtmlAttributeDesc valignAtt) + { + String name = "caption"; + HtmlTagDesc td = new HtmlTagDesc (name, true, true, inlineContent, bigAttrs); + stSupportedElements.put (name, td); + } + + protected static void addDivElement (Map stSupportedElements) + { + String name = "div"; + HtmlTagDesc td = + new HtmlTagDesc (name, true, true, flowContent, bigAttrs); + stSupportedElements.put (name, td); + } + + protected static void addDlElement + (Map stSupportedElements) + { + String name = "dl"; + List dlContent = new ArrayList (2); + addStringsToList(new String[] { "dt", "dd" }, dlContent); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, dlContent, bigAttrs); + stSupportedElements.put (name, td); + } + + protected static void addHeadElement (Map stSupportedElements) + { + String name = "head"; + String[] headMisc = new String[] + {"script", "style", "meta", "link" }; + List headContent = new ArrayList (7); + headContent.add ("title"); + headContent.add ("base"); + headContent.add ("script"); + headContent.add ("style"); + headContent.add ("meta"); + headContent.add ("link"); + headContent.add ("object"); + HtmlTagDesc td = new HtmlTagDesc (name, false, false, headContent, null); + stSupportedElements.put (name, td); + /* Attributes TITLE (required), ISINDEX (optional), and BASE (optional) + * are supposed to come in that order, ahead of anything else. + * For the moment, just toss them in with the rest. */ + addStringsToList (headMisc, headContent); + } + + protected static void addLegendElement (Map stSupportedElements) + { + final String name = "label"; + List atts = new ArrayList (bigAttrs.size () + 1); + atts.addAll (bigAttrs); + addSimpleAttribute (atts, "accesskey"); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, inlineContent, atts); + stSupportedElements.put (name, td); + } + + protected static void addLiElement + (Map stSupportedElements) + { + final String name = "li"; + HtmlTagDesc td = + new HtmlTagDesc (name, true, false, flowContent, bigAttrs); + stSupportedElements.put (name, td); + } + + + protected static void addLinkElement + (Map stSupportedElements) + { + final String name = "link"; + List atts = new ArrayList (bigAttrs.size () + 8); + atts.addAll (bigAttrs); + addSimpleAttribute (atts, "charset"); + addSimpleAttribute (atts, "href"); + addSimpleAttribute (atts, "hreflang"); + addSimpleAttribute (atts, "type"); + addSimpleAttribute (atts, "rel"); + addSimpleAttribute (atts, "rev"); + addSimpleAttribute (atts, "media"); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, null, atts); + stSupportedElements.put (name, td); + } + + protected static void addNoscriptElement (Map stSupportedElements) + { + final String name = "noscript"; + HtmlTagDesc td = + new HtmlTagDesc (name, true, false, blockContent, bigAttrs); + stSupportedElements.put (name, td); + } + + protected static void addObjectElement (Map stSupportedElements) + { + final String name = "object"; + List atts = new ArrayList (biggerAttrs.size () + 13); + atts.addAll (biggerAttrs); + addSelfAttribute (atts, "declare"); + addSimpleAttribute (atts, "classid"); + addSimpleAttribute (atts, "codebase"); + addSimpleAttribute (atts, "data"); + addSimpleAttribute (atts, "type"); + addSimpleAttribute (atts, "codetype"); + addSimpleAttribute (atts, "archive"); + addSimpleAttribute (atts, "standby"); + addSimpleAttribute (atts, "height"); + addSimpleAttribute (atts, "width"); + addSimpleAttribute (atts, "usemap"); + addSimpleAttribute (atts, "name"); + addSimpleAttribute (atts, "tabindex"); + } + + + protected static void addOlElement + (Map stSupportedElements) + { + final String name = "ol"; + HtmlTagDesc td = + new HtmlTagDesc (name, true, true, listContent, bigAttrs); + stSupportedElements.put (name, td); + } + + + protected static void addPElement (Map stSupportedElements) + { + final String name = "p"; + HtmlTagDesc td = + new HtmlTagDesc (name, true, false, inlineContent, bigAttrs); + stSupportedElements.put (name, td); + } + + protected static void addPreElement (Map stSupportedElements) + { + final String name = "pre"; + List preContent = new ArrayList (inlineContent.size ()); + preContent.addAll(inlineContent); + /* Take out excluded elements */ + removeStringsFromList (preContent, + new String [] + {"img", "object", "big", "small", "sub", "sup"}); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, preContent, bigAttrs); + stSupportedElements.put (name, td); + } + + protected static void addScriptElement (Map stSupportedElements) + { + final String name = "script"; + List content = new ArrayList (1); + content.add (HtmlSpecialToken.PCDATA); + List atts = new ArrayList (6); + addSimpleAttribute (atts, "charset"); + addSimpleAttribute (atts, "type"); + addSimpleAttribute (atts, "src"); + addSelfAttribute (atts, "defer"); + addSimpleAttribute (atts, "event"); + addSimpleAttribute (atts, "for"); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, content, atts); + stSupportedElements.put (name, td); + } + + protected static void addTableElement (Map stSupportedElements) + { + final String name = "table"; + List atts = new ArrayList (biggerAttrs.size () + 8); + atts.addAll (biggerAttrs); + addSimpleAttribute (atts, "summary"); + addSimpleAttribute (atts, "width"); + addSimpleAttribute (atts, "border"); + atts.add (new HtmlAttributeDesc ("frame", + new String[] { "void", "above", "below", "hsides", + "lhs", "rhs", "vsides", "box", "border" }, + HtmlAttributeDesc.IMPLIED)); + atts.add (new HtmlAttributeDesc ("rules", + new String[] { "none", "groups", "rows", "cols", "all" }, + HtmlAttributeDesc.IMPLIED)); + + addSimpleAttribute (atts, "rules"); + addSimpleAttribute (atts, "cellspacing"); + addSimpleAttribute (atts, "cellpadding"); + addSimpleAttribute (atts, "datapagesize"); + List[] contentArray = new List[5]; + int[] contentSequence = new int[] + { HtmlTagDesc.SEQ0_1, + HtmlTagDesc.SEQ0_MANY, + HtmlTagDesc.SEQ0_1, + HtmlTagDesc.SEQ0_1, + HtmlTagDesc.SEQ1_MANY }; + List content = new ArrayList (1); + content.add ("caption"); + contentArray[0] = content; + + content = new ArrayList(2); + content.add ("col"); + content.add ("colgroup"); + contentArray[1] = content; + + content = new ArrayList (1); + content.add ("thead"); + contentArray[2] = content; + + content = new ArrayList (1); + content.add ("tfoot"); + contentArray[3] = content; + + content = new ArrayList (1); + content.add ("tbody"); + contentArray[4] = content; + + HtmlTagDesc td = new HtmlTagDesc + (name, true, true, contentSequence, contentArray, atts); + stSupportedElements.put (name, td); + } + + protected static void addTrElement (Map stSupportedElements) + { + final String name = "tr"; + List atts = new ArrayList (bigAttrs.size() + 3); + atts.addAll (bigAttrs); + atts.add (halignAtt); + atts.add (valignAtt); + List content = new ArrayList (2); + content.add ("th"); + content.add ("td"); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, content, atts); + td.addImplicitContainer((HtmlTagDesc) stSupportedElements.get ("tbody")); + stSupportedElements.put (name, td); + } + + protected static void addUlElement (Map stSupportedElements) + { + final String name = "ul"; + HtmlTagDesc td = + new HtmlTagDesc (name, true, true, listContent, bigAttrs); + stSupportedElements.put (name, td); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4TFDocDesc.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4TFDocDesc.java new file mode 100644 index 00000000..af5774fb --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4TFDocDesc.java @@ -0,0 +1,672 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.html; + +import edu.harvard.hul.ois.jhove.module.HtmlModule; +import java.util.*; + +/** + * Abstract class for common features of HTML 4.0 and 4.01 transitional and frameset + * documents. The differences between 4.0 and 4.01 are minor, as + * are the differences between transitional and frameset so + * most of the code is found here. + * + * @author Gary McGath + * + */ +public abstract class Html4TFDocDesc extends Html4DocDesc { + + /** Code which is called from the static initializer of the + * subclasses. Note that this is called before the class + * is instantiated, and may reference only static fields + * and subroutines. */ + protected static void classInit4 (Map stSupportedElements) + { + Html4DocDesc.classInit4(stSupportedElements); + + String name; + HtmlTagDesc td; + + fontMarkup = new String[] + { "tt", "i", "b", "u", "s", "strike", "big", "small" }; + listMarkup = new String [] + { "ul", "ol", "dir", "menu" }; + specialMarkup = new String[] + { "a", "img", "applet", "object", "font", + "basefont", "br", "script", "map", + "q", "sub", "sup", "span", "bdo", "iframe" }; + /* inlineContent lists all the content types which are permitted in + * the markup elements. For a first cut, strings signify the + * name of the element which is permitted. */ + inlineContent = new ArrayList(35); + addStringsToList (fontMarkup, inlineContent); + addStringsToList (phraseMarkup, inlineContent); + addStringsToList (specialMarkup, inlineContent); + addStringsToList (formMarkup, inlineContent); + inlineContent.add (HtmlSpecialToken.PCDATA); + + blockContent = new ArrayList (20); + addStringsToList (headings, blockContent); + addStringsToList (listMarkup, blockContent); + String[] blockMisc = new String[] + {"p", "pre", "dl", "div", "center", "noscript", + "blockquote", "form", "hr", "table", "fieldset", "address" }; + addStringsToList (blockMisc, blockContent); + + flowContent = new ArrayList (30); + flowContent.addAll (blockContent); + flowContent.addAll (inlineContent); + + /* Content for the BODY element, also used for other elements */ + bodyContent = new ArrayList (flowContent.size () + 3); + bodyContent.addAll (flowContent); + bodyContent.add ("ins"); + bodyContent.add ("del"); + + listContent = new ArrayList (1); + listContent.add ("li"); + + thtdAtts = new ArrayList (bigAttrs.size() + 7); // common attribute list for TH and TD + thtdAtts.addAll (bigAttrs); + addSimpleAttribute (thtdAtts, "abbr"); + addSimpleAttribute (thtdAtts, "axis"); + addSimpleAttribute (thtdAtts, "headers"); + addSimpleAttribute (thtdAtts, "scope"); + thtdAtts.add (halignAtt); + thtdAtts.add (valignAtt); + addSelfAttribute (thtdAtts, "nowrap"); + addSimpleAttribute (thtdAtts, "rowspan"); + addSimpleAttribute (thtdAtts, "colspan"); + addSimpleAttribute (thtdAtts, "bgcolor"); + addSimpleAttribute (thtdAtts, "width"); + addSimpleAttribute (thtdAtts, "height"); + } + + /** Defines the A element. */ + protected static void addAElement (Map stSupportedElements) + { + /* The Anchor (A) element */ + String name = "a"; + List atts = new ArrayList (bigAttrs.size () + 14); + atts.addAll (bigAttrs); + addSimpleAttribute (atts, "type"); + addSimpleAttribute (atts, "name"); + addSimpleAttribute (atts, "href"); + addSimpleAttribute (atts, "hreflang"); + addSimpleAttribute (atts, "target"); // not in strict + addSimpleAttribute (atts, "rel"); + addSimpleAttribute (atts, "rev"); + addSimpleAttribute (atts, "accesskey"); + addSimpleAttribute (atts, "shape"); + addSimpleAttribute (atts, "rect"); + addSimpleAttribute (atts, "coords"); + addSimpleAttribute (atts, "tabindex"); + addSimpleAttribute (atts, "onfocus"); + addSimpleAttribute (atts, "onblur"); + List content = new ArrayList (inlineContent.size ()); + content.addAll (inlineContent); + content.remove ("a"); + + HtmlTagDesc td = new HtmlTagDesc (name, true, true, content, atts); + stSupportedElements.put (name, td); + } + + protected static void addAppletElement + (Map stSupportedElements, HtmlAttributeDesc ialignAtt) + { + String name = "applet"; + List content = new ArrayList (flowContent.size ()); + content.addAll (flowContent); + content.add ("param"); + List atts = new ArrayList (9); + addSimpleAttribute (atts, "codebase"); + addSimpleAttribute (atts, "archive"); + addSimpleAttribute (atts, "code"); + addSimpleAttribute (atts, "object"); + addSimpleAttribute (atts, "alt"); + addSimpleAttribute (atts, "alt"); + addSimpleAttribute (atts, "name"); + addRequiredAttribute (atts, "width"); + addRequiredAttribute (atts, "height"); + atts.add (ialignAtt); + addSimpleAttribute (atts, "hspace"); + addSimpleAttribute (atts, "vspace"); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, content, atts); + stSupportedElements.put (name, td); + + } + + protected static void addAreaElement + (Map stSupportedElements, HtmlAttributeDesc shapeAtt) + { + String name = "area"; + List atts = new ArrayList (10); + atts.add (shapeAtt); + addSimpleAttribute (atts, "coords"); + addSimpleAttribute (atts, "href"); + addSimpleAttribute (atts, "target"); + atts.add (new HtmlAttributeDesc ("nohref", + new String[] {"nohref"}, + HtmlAttributeDesc.IMPLIED)); + atts.add (new HtmlAttributeDesc ("alt", + null, + HtmlAttributeDesc.REQUIRED)); + addSimpleAttribute (atts, "tabindex"); + addSimpleAttribute (atts, "accesskey"); + addSimpleAttribute (atts, "onfocus"); + addSimpleAttribute (atts, "onblur"); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, null, atts); + stSupportedElements.put (name, td); + } + + protected static void addBaseElement (Map stSupportedElements) + { + String name = "base"; + List atts = new ArrayList (2); + addSimpleAttribute (atts, "href"); + addSimpleAttribute (atts, "target"); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, null, atts); + stSupportedElements.put (name, td); + } + + protected static void addBasefontElement (Map stSupportedElements) + { + String name = "basefont"; + List atts = new ArrayList (4); + addSimpleAttribute (atts, "id"); + addSimpleAttribute (atts, "size"); + addSimpleAttribute (atts, "color"); + addSimpleAttribute (atts, "face"); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, null, atts); + stSupportedElements.put (name, td); + } + + protected static void addBlockquoteElement (Map stSupportedElements) + { + String name = "blockquote"; + List content = new ArrayList (blockContent.size () + 1); + content.addAll (blockContent); + content.add ("script"); + List atts = new ArrayList (bigAttrs.size () + 1); + atts.addAll (bigAttrs); + addSimpleAttribute (atts, "cite"); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, flowContent, atts); + stSupportedElements.put (name, td); + } + + protected static void addBrElement (Map stSupportedElements, List coreAttrs) + { + String name = "br"; + List atts = new ArrayList (coreAttrs.size () + 1); + atts.addAll (coreAttrs); + atts.add (new HtmlAttributeDesc ("clear", + new String[] {"left", "all", "right", "none" }, + HtmlAttributeDesc.OTHER)); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, null, atts); + stSupportedElements.put (name, td); + } + + protected static void addButtonElement + (Map stSupportedElements) + { + String name = "button"; + List content = new ArrayList (formMarkup.length + 3); + addStringsToList(formMarkup, content); + content.add ("form"); + content.add ("isindex"); + content.add ("fieldset"); + content.add ("iframe"); + List atts = new ArrayList (biggerAttrs.size () + 8); + atts.addAll (biggerAttrs); + addSimpleAttribute (atts, "name"); + addSimpleAttribute (atts, "value"); + atts.add (new HtmlAttributeDesc ("type", + new String[] {"button", "submit" , "reset"}, + HtmlAttributeDesc.OTHER)); + addSelfAttribute (atts, "disabled"); + addSimpleAttribute (atts, "tabindex"); + addSimpleAttribute (atts, "accesskey"); + addSimpleAttribute (atts, "onfocus"); + addSimpleAttribute (atts, "onblur"); + } + + protected static void addCaptionElement + (Map stSupportedElements, List inlineContent, + HtmlAttributeDesc valignAtt) + { + String name = "caption"; + List atts = new ArrayList (bigAttrs.size () + 1); + atts.add (new HtmlAttributeDesc ("align", + new String[] {"top", "bottom", "left", "right" }, + HtmlAttributeDesc.IMPLIED)); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, inlineContent, atts); + stSupportedElements.put (name, td); + } + + protected static void addCenterElement (Map stSupportedElements) + { + String name = "center"; + HtmlTagDesc td = new HtmlTagDesc (name, true, true, flowContent, bigAttrs); + stSupportedElements.put (name, td); + } + + protected static void addDirElement (Map stSupportedElements) + { + String name = "dir"; + List atts = new ArrayList (1); + addSelfAttribute (atts, "compact"); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, listContent, atts); + stSupportedElements.put (name, td); + } + + protected static void addDivElement (Map stSupportedElements) + { + String name = "div"; + List atts = new ArrayList (bigAttrs.size () + 1); + atts.addAll (bigAttrs); + atts.add (new HtmlAttributeDesc + ("align", + new String [] {"left", "center", "right", "justify"}, + HtmlAttributeDesc.IMPLIED)); + HtmlTagDesc td = + new HtmlTagDesc (name, true, true, flowContent, atts); + stSupportedElements.put (name, td); + } + + protected static void addDlElement + (Map stSupportedElements) + { + String name = "dl"; + List dlContent = new ArrayList (2); + addStringsToList(new String[] { "dt", "dd" }, dlContent); + List atts = new ArrayList (bigAttrs.size () + 1); + addSelfAttribute(atts, "compact"); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, dlContent, atts); + stSupportedElements.put (name, td); + } + + /** Adds the Font element. */ + protected static void addFontElement (Map stSupportedElements) + { + String name = "font"; + List atts = new ArrayList (bigAttrs.size () + 10); + atts.addAll (bigAttrs); + atts.addAll (i18nAttrs); + addSimpleAttribute (atts, "size"); + addSimpleAttribute (atts, "color"); + addSimpleAttribute (atts, "face"); + + HtmlTagDesc td = new HtmlTagDesc (name, true, true, inlineContent, atts); + stSupportedElements.put (name, td); + + } + + /** Adds to Frame element. */ + protected static void addFrameElement (Map stSupportedElements) + { + String name = "frame"; + List atts = new ArrayList (bigAttrs.size () + 8); + atts.addAll (bigAttrs); + addSimpleAttribute (atts, "longdesc"); + addSimpleAttribute (atts, "name"); + addSimpleAttribute (atts, "src"); + addSimpleAttribute (atts, "marginwidth"); + addSimpleAttribute (atts, "marginheight"); + addSelfAttribute (atts, "noresize"); + atts.add (new HtmlAttributeDesc ("scrolling", + new String[] { "yes", "no", "auto" }, + HtmlAttributeDesc.OTHER)); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, null, atts); + stSupportedElements.put (name, td); + } + + /** Adds the Frameset element. This is called only for + * 4.0 and 4.01 Frameset docuemnts. */ + protected static void addFramesetElement + (Map stSupportedElements) + { + String name = "frameset"; + List content = new ArrayList (3); + addStringsToList (new String[] {"frameset", "frame", "noframes" }, + content); + List atts = new ArrayList (coreAttrs.size () + 4); + atts.addAll (coreAttrs); + addSimpleAttribute(atts, "rows"); + addSimpleAttribute(atts, "cols"); + addSimpleAttribute(atts, "onload"); + addSimpleAttribute(atts, "onunload"); + HtmlTagDesc td = new HtmlTagDesc (name, false, false, content, atts); + stSupportedElements.put (name, td); + } + + protected static void addHeadElement (Map stSupportedElements) + { + String name = "head"; + String[] headMisc = new String[] + {"script", "style", "meta", "link" }; + List headContent = new ArrayList (7); + headContent.add ("title"); + headContent.add ("isindex"); + headContent.add ("base"); + headContent.add ("script"); + headContent.add ("style"); + headContent.add ("meta"); + headContent.add ("link"); + headContent.add ("object"); + HtmlTagDesc td = new HtmlTagDesc (name, false, false, headContent, null); + stSupportedElements.put (name, td); + /* Attributes TITLE (required), ISINDEX (optional), and BASE (optional) + * are supposed to come in that order, ahead of anything else. + * For the moment, just toss them in with the rest. */ + addStringsToList (headMisc, headContent); + } + + protected static void addHrElement (Map stSupportedElements) + { + String name = "hr"; + List atts = new ArrayList (bigAttrs.size () + 4); + atts.add (new HtmlAttributeDesc ("align", + new String[] { "left", "center", "right" }, + HtmlAttributeDesc.IMPLIED)); + addSelfAttribute (atts, "noshade"); + addSimpleAttribute (atts, "size"); + addSimpleAttribute (atts, "width"); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, null, atts); + stSupportedElements.put (name, td); + } + + + protected static void addInputElement + (Map stSupportedElements) + { + final String name = "input"; + List atts = new ArrayList (biggerAttrs.size () + 20); + atts.addAll (biggerAttrs); + atts.add (new HtmlAttributeDesc ("type", + new String[] {"text", "password", "checkbox", "radio", "submit", + "reset", "file", "hidden", "image", "button"}, + HtmlAttributeDesc.OTHER)); + addSimpleAttribute (atts, "name"); + addSimpleAttribute (atts, "value"); + addSelfAttribute (atts, "checked"); + addSelfAttribute (atts, "disabled"); + addSelfAttribute (atts, "readonly"); + addSimpleAttribute (atts, "size"); + addSimpleAttribute (atts, "maxlength"); + addSimpleAttribute (atts, "src"); + addSimpleAttribute (atts, "alt"); + addSimpleAttribute (atts, "usemap"); + addSimpleAttribute (atts, "tabindex"); + addSimpleAttribute (atts, "accesskey"); + addSimpleAttribute (atts, "onfocus"); + addSimpleAttribute (atts, "onblur"); + addSimpleAttribute (atts, "onselect"); + addSimpleAttribute (atts, "onchange"); + addSimpleAttribute (atts, "accept"); + atts.add (new HtmlAttributeDesc ("align", + new String[] { "top", "middle", "bottom", "left", "right" }, + HtmlAttributeDesc.IMPLIED)); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, null, atts); + stSupportedElements.put (name, td); + } + + + protected static void addLegendElement (Map stSupportedElements) + { + final String name = "label"; + List atts = new ArrayList (bigAttrs.size () + 2); + atts.addAll (bigAttrs); + addSimpleAttribute (atts, "accesskey"); + atts.add (new HtmlAttributeDesc ("align", + new String[] {"top", "left", "bottom", "right" }, + HtmlAttributeDesc.IMPLIED)); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, inlineContent, atts); + stSupportedElements.put (name, td); + } + + protected static void addLiElement + (Map stSupportedElements) + { + final String name = "li"; + List atts = new ArrayList (bigAttrs.size () + 2); + atts.addAll (bigAttrs); + addSimpleAttribute (atts, "type"); + addSimpleAttribute (atts, "value"); + HtmlTagDesc td = + new HtmlTagDesc (name, true, false, flowContent, atts); + stSupportedElements.put (name, td); + } + + protected static void addLinkElement + (Map stSupportedElements) + { + final String name = "link"; + List atts = new ArrayList (bigAttrs.size () + 8); + atts.addAll (bigAttrs); + addSimpleAttribute (atts, "charset"); + addSimpleAttribute (atts, "href"); + addSimpleAttribute (atts, "hreflang"); + addSimpleAttribute (atts, "type"); + addSimpleAttribute (atts, "rel"); + addSimpleAttribute (atts, "rev"); + addSimpleAttribute (atts, "media"); + addSimpleAttribute (atts, "target"); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, null, atts); + stSupportedElements.put (name, td); + } + + protected static void addMenuElement (Map stSupportedElements) + { + final String name = "menu"; + List atts = new ArrayList (1); + addSelfAttribute (atts, "compact"); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, listContent, atts); + stSupportedElements.put (name, td); + } + + + /** Adds the NOFRAMES element. The content differs between Transitional + * and Frameset DTD's. */ + protected static void addNoframesElement (Map stSupportedElements, int version) + { + final String name = "noframes"; + List content; + if (version == HtmlModule.HTML_4_01_FRAMESET || + version == HtmlModule.HTML_4_0_FRAMESET) { + content = new ArrayList (1); + // There's something I obviously don't understand about DTD syntax. + // The content is given as (BODY) -(NOFRAMES) + // But if the only allowed element is BODY, it's superfluous to + // exclude NOFRAMES. What am I missing? + content.add ("body"); + } + else { + content = flowContent; + } + HtmlTagDesc td = new HtmlTagDesc (name, true, true, content, bigAttrs); + stSupportedElements.put (name, td); + } + + + protected static void addNoscriptElement (Map stSupportedElements) + { + final String name = "noscript"; + HtmlTagDesc td = + new HtmlTagDesc (name, true, false, flowContent, bigAttrs); + stSupportedElements.put (name, td); + } + + protected static void addObjectElement (Map stSupportedElements) + { + final String name = "object"; + List atts = new ArrayList (biggerAttrs.size () + 13); + atts.addAll (biggerAttrs); + addSelfAttribute (atts, "declare"); + addSimpleAttribute (atts, "classid"); + addSimpleAttribute (atts, "codebase"); + addSimpleAttribute (atts, "data"); + addSimpleAttribute (atts, "type"); + addSimpleAttribute (atts, "codetype"); + addSimpleAttribute (atts, "archive"); + addSimpleAttribute (atts, "standby"); + addSimpleAttribute (atts, "height"); + addSimpleAttribute (atts, "width"); + addSimpleAttribute (atts, "usemap"); + addSimpleAttribute (atts, "name"); + addSimpleAttribute (atts, "tabindex"); + addSimpleAttribute (atts, "align"); + addSimpleAttribute (atts, "border"); + addSimpleAttribute (atts, "hspace"); + addSimpleAttribute (atts, "vspace"); + } + + protected static void addOlElement + (Map stSupportedElements) + { + final String name = "ol"; + List atts = new ArrayList (bigAttrs.size () + 3); + atts.addAll (bigAttrs); + addSimpleAttribute (atts, "type"); + addSelfAttribute (atts, "compact"); + addSimpleAttribute (atts, "start"); + HtmlTagDesc td = + new HtmlTagDesc (name, true, true, listContent, atts); + stSupportedElements.put (name, td); + } + + protected static void addPElement (Map stSupportedElements) + { + final String name = "p"; + List atts = new ArrayList (bigAttrs.size () + 1); + atts.addAll (bigAttrs); + atts.add (new HtmlAttributeDesc + ("align", + new String [] {"left", "center", "right", "justify"}, + HtmlAttributeDesc.IMPLIED)); + HtmlTagDesc td = + new HtmlTagDesc (name, true, false, inlineContent, atts); + stSupportedElements.put (name, td); + } + + protected static void addPreElement (Map stSupportedElements) + { + final String name = "pre"; + List preContent = new ArrayList (inlineContent.size ()); + preContent.addAll(inlineContent); + /* Take out excluded elements */ + removeStringsFromList (preContent, + new String [] + {"img", "object", "big", "small", "sub", "sup"}); + List atts = new ArrayList (bigAttrs.size () + 1); + atts.addAll (bigAttrs); + addSimpleAttribute (atts, "width"); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, preContent, atts); + stSupportedElements.put (name, td); + } + + protected static void addScriptElement (Map stSupportedElements) + { + final String name = "script"; + List content = new ArrayList (1); + content.add (HtmlSpecialToken.PCDATA); + List atts = new ArrayList (6); + addSimpleAttribute (atts, "charset"); + addSimpleAttribute (atts, "type"); + addSimpleAttribute (atts, "language"); + addSimpleAttribute (atts, "src"); + addSelfAttribute (atts, "defer"); + addSimpleAttribute (atts, "event"); + addSimpleAttribute (atts, "for"); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, content, atts); + stSupportedElements.put (name, td); + } + + protected static void addTableElement (Map stSupportedElements) + { + final String name = "table"; + List atts = new ArrayList (biggerAttrs.size () + 10); + atts.addAll (biggerAttrs); + addSimpleAttribute (atts, "summary"); + addSimpleAttribute (atts, "width"); + addSimpleAttribute (atts, "border"); + atts.add (new HtmlAttributeDesc ("frame", + new String[] { "void", "above", "below", "hsides", + "lhs", "rhs", "vsides", "box", "border" }, + HtmlAttributeDesc.IMPLIED)); + atts.add (new HtmlAttributeDesc ("rules", + new String[] { "none", "groups", "rows", "cols", "all" }, + HtmlAttributeDesc.IMPLIED)); + + addSimpleAttribute (atts, "rules"); + addSimpleAttribute (atts, "cellspacing"); + addSimpleAttribute (atts, "cellpadding"); + atts.add (new HtmlAttributeDesc ("align", + new String[] { "left", "center", "right" }, + HtmlAttributeDesc.IMPLIED)); + addSimpleAttribute (atts, "bgcolor"); + addSimpleAttribute (atts, "datapagesize"); + List[] contentArray = new List[5]; + int[] contentSequence = new int[] + { HtmlTagDesc.SEQ0_1, + HtmlTagDesc.SEQ0_MANY, + HtmlTagDesc.SEQ0_1, + HtmlTagDesc.SEQ0_1, + HtmlTagDesc.SEQ1_MANY }; + List content = new ArrayList (1); + content.add ("caption"); + contentArray[0] = content; + + content = new ArrayList(2); + content.add ("col"); + content.add ("colgroup"); + contentArray[1] = content; + + content = new ArrayList (1); + content.add ("thead"); + contentArray[2] = content; + + content = new ArrayList (1); + content.add ("tfoot"); + contentArray[3] = content; + + content = new ArrayList (1); + content.add ("tbody"); + contentArray[4] = content; + + HtmlTagDesc td = new HtmlTagDesc + (name, true, true, contentSequence, contentArray, atts); + stSupportedElements.put (name, td); + } + + protected static void addTrElement (Map stSupportedElements) + { + final String name = "tr"; + List atts = new ArrayList (bigAttrs.size() + 3); + atts.addAll (bigAttrs); + atts.add (halignAtt); + atts.add (valignAtt); + addSimpleAttribute (atts, "bgcolor"); + List content = new ArrayList (2); + content.add ("th"); + content.add ("td"); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, content, atts); + td.addImplicitContainer((HtmlTagDesc) stSupportedElements.get ("tbody")); + stSupportedElements.put (name, td); + } + + protected static void addUlElement (Map stSupportedElements) + { + final String name = "ul"; + List atts = new ArrayList (bigAttrs.size () + 2); + atts.addAll (bigAttrs); + addSimpleAttribute (atts, "type"); + addSelfAttribute (atts, "compact"); + HtmlTagDesc td = + new HtmlTagDesc (name, true, true, listContent, atts); + stSupportedElements.put (name, td); + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4_01FrameDocDesc.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4_01FrameDocDesc.java new file mode 100644 index 00000000..323e3e13 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4_01FrameDocDesc.java @@ -0,0 +1,39 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.html; + +import edu.harvard.hul.ois.jhove.module.HtmlModule; +import java.util.*; + +/** + * This class describes the requirements of an HTML 4.01 Frameset document. + * + * @author Gary McGath + * + */ +public class Html4_01FrameDocDesc extends Html4_01TFDocDesc { + + /* Static, private map of supported tags. + * For efficiency, we create a static Map + * of supported tags just once, then assign that to stSupportedElements + * in the constructor. */ + private static Map stSupportedElements; + + { + stSupportedElements = new HashMap (280); + Html4_01TFDocDesc.classInit4 + (stSupportedElements, HtmlModule.HTML_4_01_FRAMESET); + } + + /** Constructor. */ + public Html4_01FrameDocDesc () + { + // publish stSupportedElements to superclass + supportedElements = stSupportedElements; + init (); + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4_01StrictDocDesc.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4_01StrictDocDesc.java new file mode 100644 index 00000000..0ec10230 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4_01StrictDocDesc.java @@ -0,0 +1,227 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.html; + +//import edu.harvard.hul.ois.jhove.*; +import java.util.*; +//import java.io.*; + +/** + * This class describes the requirements of an HTML 4.01 Strict document. + * + * @author Gary McGath + * + */ +public class Html4_01StrictDocDesc extends Html4StrictDocDesc { + + /* Static, private map of supported tags. + * For efficiency, we create a static Map + * of supported tags just once, then assign that to stSupportedElements + * in the constructor. */ + private static Map stSupportedElements; + + /* Static initializer. A superclass is initialized before its + * subclass, so we can count on the static initializer of HtmlDocDesc + * to have run already. + */ + static { + stSupportedElements = new HashMap (280); + classInit4 (stSupportedElements); + + int i; + String name; + HtmlTagDesc td; + + + addSupElement (stSupportedElements); + addSubElement (stSupportedElements); + addSpanElement (stSupportedElements); + addBdoElement (stSupportedElements); + addBrElement (stSupportedElements, coreAttrs); + + addBodyElement (stSupportedElements); + addAddressElement (stSupportedElements); + addDivElement (stSupportedElements); + addAElement (stSupportedElements); + addMapElement (stSupportedElements); + + HtmlAttributeDesc shapeAtt = new HtmlAttributeDesc ("shape", + new String[] {"rect", "circle", "poly", "default" }, + HtmlAttributeDesc.REQUIRED); + addAreaElement (stSupportedElements, shapeAtt); + addLinkElement (stSupportedElements); + addImgElement (stSupportedElements); + addObjectElement (stSupportedElements); + addParamElement (stSupportedElements); + addHrElement (stSupportedElements); + addPElement (stSupportedElements); + + /* The heading (H1-H6) elements */ + for (i = 0; i < headings.length; i++) { + name = headings[i]; + td = new HtmlTagDesc (name, true, true, inlineContent, bigAttrs); + stSupportedElements.put (name, td); + } + + addPreElement (stSupportedElements); + addQElement (stSupportedElements); + addBlockquoteElement (stSupportedElements); + addInsElement (stSupportedElements); + addDelElement (stSupportedElements); + + addDlElement (stSupportedElements); + addDtElement (stSupportedElements); + addDdElement (stSupportedElements); + + addOlElement (stSupportedElements); + addUlElement (stSupportedElements); + addLiElement (stSupportedElements); + addFormElement (stSupportedElements); + addLabelElement (stSupportedElements); + + addInputElement (stSupportedElements); + addSelectElement (stSupportedElements); + addOptgroupElement (stSupportedElements); + addOptionElement (stSupportedElements); + addTextareaElement (stSupportedElements); + addFieldsetElement (stSupportedElements); + addLegendElement (stSupportedElements); + addButtonElement (stSupportedElements); + addTableElement (stSupportedElements); + + HtmlAttributeDesc halignAtt = new HtmlAttributeDesc + ("align", + new String [] {"left|center|right|justify|char"}, + HtmlAttributeDesc.IMPLIED); + HtmlAttributeDesc valignAtt = + new HtmlAttributeDesc ("valign", + new String[] { "top", "middle", "bottom", "baseline" }, + HtmlAttributeDesc.IMPLIED); + List cellalignAttrs = new ArrayList (4); // combine cellhalign and cellvalign + cellalignAttrs.add (new HtmlAttributeDesc ("align", + new String[] {"left", "center", "right", "justify", "char" }, + HtmlAttributeDesc.IMPLIED)); + addSimpleAttribute (cellalignAttrs, "char"); + addSimpleAttribute (cellalignAttrs, "charoff"); + addTheadElement (stSupportedElements, cellalignAttrs); + addTfootElement (stSupportedElements, cellalignAttrs); + addTbodyElement (stSupportedElements, cellalignAttrs); + addTrElement (stSupportedElements); + + addThElement (stSupportedElements); + addTdElement (stSupportedElements); + addCaptionElement + (stSupportedElements, inlineContent, valignAtt); + + addColgroupElement (stSupportedElements, cellalignAttrs); + addColElement (stSupportedElements, cellalignAttrs); + + addHeadElement (stSupportedElements); + addTitleElement (stSupportedElements); + addBaseElement (stSupportedElements); + addMetaElement (stSupportedElements); + addScriptElement (stSupportedElements); + addNoscriptElement (stSupportedElements); + addStyleElement (stSupportedElements); + + /* The HTML element */ + name = "html"; + List htmlContent = new ArrayList (2); + htmlContent.add ("head"); + htmlContent.add ("body"); + td = new HtmlTagDesc (name, false, false, htmlContent, i18nAttrs); + stSupportedElements.put (name, td); + } + + /** + * Constructor. + */ + public Html4_01StrictDocDesc () + { + // publish stSupportedElements to superclass + supportedElements = stSupportedElements; + init (); + } + + + + private static void addFormElement (Map stSupportedElements) + { + final String name = "form"; + List atts = new ArrayList (bigAttrs.size () + 8); + atts.addAll (bigAttrs); + addSimpleAttribute (atts, "action"); + addSimpleAttribute (atts, "method"); + addSimpleAttribute (atts, "enctype"); + addSimpleAttribute (atts, "accept"); // 4.01 + addSimpleAttribute (atts, "name"); // 4.01 + addSimpleAttribute (atts, "onsubmit"); + addSimpleAttribute (atts, "onreset"); + addSimpleAttribute (atts, "accept-charset"); + List formContent = new ArrayList (blockContent.size ()); + formContent.addAll (blockContent); + formContent.add ("script"); + removeStringsFromList (formContent, new String[] { "form" }); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, formContent, atts); + stSupportedElements.put (name, td); + } + + private static void addHrElement (Map stSupportedElements) + { + String name = "hr"; + HtmlTagDesc td = new HtmlTagDesc (name, true, false, null, bigAttrs); + stSupportedElements.put (name, td); + } + + private static void addImgElement (Map stSupportedElements) + { + String name = "img"; + List atts = new ArrayList (bigAttrs.size () + 10); + atts.addAll (bigAttrs); + addRequiredAttribute (atts, "src"); + addRequiredAttribute (atts, "alt"); + addSimpleAttribute (atts, "longdesc"); + addSimpleAttribute (atts, "name"); // new to 4.01 + addSimpleAttribute (atts, "height"); + addSimpleAttribute (atts, "width"); + addSimpleAttribute (atts, "usemap"); + addSelfAttribute (atts, "ismap"); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, null, atts); + stSupportedElements.put (name, td); + } + + private static void addInputElement + (Map stSupportedElements) + { + final String name = "input"; + List atts = new ArrayList (biggerAttrs.size () + 20); + atts.addAll (biggerAttrs); + atts.add (new HtmlAttributeDesc ("type", + new String[] {"text", "password", "checkbox", "radio", "submit", + "reset", "file", "hidden", "image", "button"}, + HtmlAttributeDesc.OTHER)); + addSimpleAttribute (atts, "name"); + addSimpleAttribute (atts, "value"); + addSelfAttribute (atts, "checked"); + addSelfAttribute (atts, "disabled"); + addSelfAttribute (atts, "readonly"); + addSimpleAttribute (atts, "size"); + addSimpleAttribute (atts, "maxlength"); + addSimpleAttribute (atts, "src"); + addSimpleAttribute (atts, "alt"); + addSimpleAttribute (atts, "usemap"); + addSelfAttribute (atts, "ismap"); // 4.01 + addSimpleAttribute (atts, "tabindex"); + addSimpleAttribute (atts, "accesskey"); + addSimpleAttribute (atts, "onfocus"); + addSimpleAttribute (atts, "onblur"); + addSimpleAttribute (atts, "onselect"); + addSimpleAttribute (atts, "onchange"); + addSimpleAttribute (atts, "accept"); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, null, atts); + stSupportedElements.put (name, td); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4_01TFDocDesc.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4_01TFDocDesc.java new file mode 100644 index 00000000..b328070c --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4_01TFDocDesc.java @@ -0,0 +1,210 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.html; + +import edu.harvard.hul.ois.jhove.module.HtmlModule; +import java.util.*; +/** + * Abstract class for the HTML 4.01 Transitional and Frameset document + * types. These are identical except for one element apiece, so nearly + * all the code is here or in its superclasses. + * + * @author Gary McGath + * + */ +public abstract class Html4_01TFDocDesc extends Html4TFDocDesc { + + /** Initialization code. This is called from the static initializer + * of our subclasses. */ + protected static void classInit4 (Map stSupportedElements, int version) + { + Html4TFDocDesc.classInit4(stSupportedElements); + int i; + String name; + HtmlTagDesc td; + + for (i = 0; i < fontMarkup.length; i++) { + name = fontMarkup[i]; + td = new HtmlTagDesc (name, true, true, inlineContent, bigAttrs); + stSupportedElements.put (name, td); + } + + /* Phrase elements. */ + for (i = 0; i < phraseMarkup.length; i++) { + name = phraseMarkup[i]; + td = new HtmlTagDesc (name, true, true, inlineContent, bigAttrs); + stSupportedElements.put (name, td); + } + + addSupElement (stSupportedElements); + addSubElement (stSupportedElements); + addSpanElement (stSupportedElements); + addBdoElement (stSupportedElements); + addBasefontElement (stSupportedElements); + addFontElement (stSupportedElements); + addBrElement (stSupportedElements, coreAttrs); + + addAddressElement (stSupportedElements); + addDivElement (stSupportedElements); + addCenterElement (stSupportedElements); + addAElement (stSupportedElements); + addMapElement (stSupportedElements); + + HtmlAttributeDesc shapeAtt = new HtmlAttributeDesc ("shape", + new String[] {"rect", "circle", "poly", "default" }, + HtmlAttributeDesc.REQUIRED); + addAreaElement (stSupportedElements, shapeAtt); + addLinkElement (stSupportedElements); + addImgElement (stSupportedElements); + addObjectElement (stSupportedElements); + addParamElement (stSupportedElements); + HtmlAttributeDesc ialignAtt = new HtmlAttributeDesc ("align", + new String[] { "top", "middle", "bottom", "left", "right" }, + HtmlAttributeDesc.IMPLIED); + addAppletElement (stSupportedElements, ialignAtt); + addHrElement (stSupportedElements); + addPElement (stSupportedElements); + + /* The heading (H1-H6) elements */ + for (i = 0; i < headings.length; i++) { + name = headings[i]; + td = new HtmlTagDesc (name, true, true, inlineContent, bigAttrs); + stSupportedElements.put (name, td); + } + + addPreElement (stSupportedElements); + addQElement (stSupportedElements); + addBlockquoteElement (stSupportedElements); + addInsElement (stSupportedElements); + addDelElement (stSupportedElements); + + addDlElement (stSupportedElements); + addDtElement (stSupportedElements); + addDdElement (stSupportedElements); + + addOlElement (stSupportedElements); + addUlElement (stSupportedElements); + addDirElement (stSupportedElements); + addMenuElement (stSupportedElements); + addLiElement (stSupportedElements); + addFormElement (stSupportedElements); + addLabelElement (stSupportedElements); + + addInputElement (stSupportedElements); + addSelectElement (stSupportedElements); + addOptgroupElement (stSupportedElements); + addOptionElement (stSupportedElements); + addTextareaElement (stSupportedElements); + addFieldsetElement (stSupportedElements); + addLegendElement (stSupportedElements); + addButtonElement (stSupportedElements); + addTableElement (stSupportedElements); + + HtmlAttributeDesc halignAtt = new HtmlAttributeDesc + ("align", + new String [] {"left", "center", "right", "justify", "char"}, + HtmlAttributeDesc.IMPLIED); + HtmlAttributeDesc valignAtt = + new HtmlAttributeDesc ("valign", + new String[] { "top", "middle", "bottom", "baseline" }, + HtmlAttributeDesc.IMPLIED); + List cellalignAttrs = new ArrayList (4); // combine cellhalign and cellvalign + cellalignAttrs.add (new HtmlAttributeDesc ("align", + new String[] {"left", "center", "right", "justify", "char" }, + HtmlAttributeDesc.IMPLIED)); + addSimpleAttribute (cellalignAttrs, "char"); + addSimpleAttribute (cellalignAttrs, "charoff"); + cellalignAttrs.add (valignAtt); + addTheadElement (stSupportedElements, cellalignAttrs); + addTfootElement (stSupportedElements, cellalignAttrs); + addTbodyElement (stSupportedElements, cellalignAttrs); + addTrElement (stSupportedElements); + + + addThElement (stSupportedElements); + addTdElement (stSupportedElements); + addCaptionElement + (stSupportedElements, inlineContent, valignAtt); + + addColgroupElement (stSupportedElements, cellalignAttrs); + addColElement (stSupportedElements, cellalignAttrs); + + addHeadElement (stSupportedElements); + addTitleElement (stSupportedElements); + addBaseElement (stSupportedElements); + addMetaElement (stSupportedElements); + addScriptElement (stSupportedElements); + addNoscriptElement (stSupportedElements); + addStyleElement (stSupportedElements); + + /* The HTML element */ + name = "html"; + List htmlContent = new ArrayList (2); + htmlContent.add ("head"); + if (version == HtmlModule.HTML_4_01_FRAMESET) { + htmlContent.add ("frameset"); + } + else { + htmlContent.add ("body"); + } + td = new HtmlTagDesc (name, false, false, htmlContent, i18nAttrs); + stSupportedElements.put (name, td); + addNoframesElement (stSupportedElements, version); + if (version == HtmlModule.HTML_4_01_FRAMESET) { + addFramesetElement (stSupportedElements); + addFrameElement (stSupportedElements); + } + addBodyElement (stSupportedElements); + } + + private static void addFormElement (Map stSupportedElements) + { + final String name = "form"; + List atts = new ArrayList (bigAttrs.size () + 9); + atts.addAll (bigAttrs); + addSimpleAttribute (atts, "action"); + addSimpleAttribute (atts, "method"); + addSimpleAttribute (atts, "enctype"); + addSimpleAttribute (atts, "accept"); + addSimpleAttribute (atts, "name"); + addSimpleAttribute (atts, "onsubmit"); + addSimpleAttribute (atts, "onreset"); + addSimpleAttribute (atts, "target"); + addSimpleAttribute (atts, "accept-charset"); + List formContent = new ArrayList (flowContent.size ()); + formContent.addAll (flowContent); + //formContent.add ("script"); + removeStringsFromList (formContent, new String[] { "form" }); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, formContent, atts); + stSupportedElements.put (name, td); + } + + + private static void addImgElement (Map stSupportedElements) + { + String name = "img"; + List atts = new ArrayList (bigAttrs.size () + 10); + atts.addAll (bigAttrs); + addRequiredAttribute (atts, "src"); + addRequiredAttribute (atts, "alt"); + addSimpleAttribute (atts, "longdesc"); + addSimpleAttribute (atts, "name"); // new to 4.01 + addSimpleAttribute (atts, "height"); + addSimpleAttribute (atts, "width"); + addSimpleAttribute (atts, "usemap"); + addSelfAttribute (atts, "ismap"); + atts.add (new HtmlAttributeDesc ("align", + new String[] { "top", "middle", "bottom", "left", "right" }, + HtmlAttributeDesc.IMPLIED)); + addSimpleAttribute (atts, "border"); + addSimpleAttribute (atts, "hspace"); + addSimpleAttribute (atts, "vspace"); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, null, atts); + stSupportedElements.put (name, td); + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4_01TransDocDesc.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4_01TransDocDesc.java new file mode 100644 index 00000000..34335669 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4_01TransDocDesc.java @@ -0,0 +1,42 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.html; + +import edu.harvard.hul.ois.jhove.module.HtmlModule; +import java.util.*; + +/** + * This class describes the requirements of an HTML 4.01 Transitional document. + * + * @author Gary McGath + * + */ +public class Html4_01TransDocDesc extends Html4_01TFDocDesc { + + /* Static, private map of supported tags. + * For efficiency, we create a static Map + * of supported tags just once, then assign that to stSupportedElements + * in the constructor. */ + private static Map stSupportedElements; + + { + stSupportedElements = new HashMap (280); + Html4_01TFDocDesc.classInit4 + (stSupportedElements, HtmlModule.HTML_4_01_TRANSITIONAL); + + } + + /** + * Constructor. + */ + public Html4_01TransDocDesc () + { + // publish stSupportedElements to superclass + supportedElements = stSupportedElements; + init (); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4_0FrameDocDesc.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4_0FrameDocDesc.java new file mode 100644 index 00000000..044eca62 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4_0FrameDocDesc.java @@ -0,0 +1,42 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.html; + + +import edu.harvard.hul.ois.jhove.module.HtmlModule; +import java.util.*; + +/** + * This class describes the requirements of an HTML 4.01 Frameset document. + * + * @author Gary McGath + * + */ +public class Html4_0FrameDocDesc extends Html4_0TFDocDesc { + + /* Static, private map of supported tags. + * For efficiency, we create a static Map + * of supported tags just once, then assign that to stSupportedElements + * in the constructor. */ + private static Map stSupportedElements; + + { + stSupportedElements = new HashMap (280); + Html4_0TFDocDesc.classInit4 + (stSupportedElements, HtmlModule.HTML_4_0_FRAMESET); + } + + /** + * Constructor. + */ + public Html4_0FrameDocDesc () + { + // publish stSupportedElements to superclass + supportedElements = stSupportedElements; + init (); + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4_0StrictDocDesc.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4_0StrictDocDesc.java new file mode 100644 index 00000000..8ab1c555 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4_0StrictDocDesc.java @@ -0,0 +1,234 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.html; + +//import edu.harvard.hul.ois.jhove.*; +import java.util.*; + +/** + * This class describes the requirements of an HTML 4.0 Strict document. + * + * @author Gary McGath + * + */ +public class Html4_0StrictDocDesc extends Html4StrictDocDesc { + + /* Static, private map of supported tags. + * For efficiency, we create a static Map + * of supported tags just once, then assign that to stSupportedElements + * in the constructor. */ + private static Map stSupportedElements; + + /* Static initializer. A superclass is initialized before its + * subclass, so we can count on the static initializer of HtmlDocDesc + * to have run already. + * + * It's time to start thinking about how to factor this code. + * Each element can be created separately, with the necessary + * arguments passed for each one. It would be a nice pattern if + * all elements had the same calling sequence, but realistically + * some are going to need extras such as special lists of names. + * The element functions (which will all be static) should be here + * if unique, or in the parent class if they can be used for more + * than one version of HTML. There should be a naming convention + * for the functions in the parent class indicating which names + * they can be used with. + */ + static { + stSupportedElements = new HashMap (280); + classInit4 (stSupportedElements); + + + int i; + String name; + HtmlTagDesc td; + + addSupElement (stSupportedElements); + addSubElement (stSupportedElements); + addSpanElement (stSupportedElements); + addBdoElement (stSupportedElements); + addBrElement (stSupportedElements, coreAttrs); + + addBodyElement (stSupportedElements); + addAddressElement (stSupportedElements); + addDivElement (stSupportedElements); + addAElement (stSupportedElements); + addMapElement (stSupportedElements); + + HtmlAttributeDesc shapeAtt = new HtmlAttributeDesc ("shape", + new String[] {"rect", "circle", "poly", "default" }, + HtmlAttributeDesc.REQUIRED); + addAreaElement (stSupportedElements, shapeAtt); + addLinkElement (stSupportedElements); + addImgElement (stSupportedElements); + addObjectElement (stSupportedElements); + addParamElement (stSupportedElements); + addHrElement (stSupportedElements); + addPElement (stSupportedElements); + + /* The heading (H1-H6) elements */ + for (i = 0; i < headings.length; i++) { + name = headings[i]; + td = new HtmlTagDesc (name, true, true, inlineContent, bigAttrs); + stSupportedElements.put (name, td); + } + + addPreElement (stSupportedElements); + addQElement (stSupportedElements); + addBlockquoteElement (stSupportedElements); + addInsElement (stSupportedElements); + addDelElement (stSupportedElements); + + addDlElement (stSupportedElements); + addDtElement (stSupportedElements); + addDdElement (stSupportedElements); + + addOlElement (stSupportedElements); + addUlElement (stSupportedElements); + addLiElement (stSupportedElements); + addFormElement (stSupportedElements); + addLabelElement (stSupportedElements); + + addInputElement (stSupportedElements); + addSelectElement (stSupportedElements); + addOptgroupElement (stSupportedElements); + addOptionElement (stSupportedElements); + addTextareaElement (stSupportedElements); + addFieldsetElement (stSupportedElements); + addLegendElement (stSupportedElements); + addButtonElement (stSupportedElements); + addTableElement (stSupportedElements); + + List cellalignAttrs = new ArrayList (4); // combine cellhalign and cellvalign + cellalignAttrs.add (new HtmlAttributeDesc ("align", + new String[] {"left", "center", "right", "justify", "char" }, + HtmlAttributeDesc.IMPLIED)); + addSimpleAttribute (cellalignAttrs, "char"); + addSimpleAttribute (cellalignAttrs, "charoff"); + addTheadElement (stSupportedElements, cellalignAttrs); + addTfootElement (stSupportedElements, cellalignAttrs); + addTbodyElement (stSupportedElements, cellalignAttrs); + addTrElement (stSupportedElements); + + addThElement (stSupportedElements); + addTdElement (stSupportedElements); + addCaptionElement + (stSupportedElements, inlineContent, valignAtt); + + addColgroupElement (stSupportedElements, cellalignAttrs); + addColElement (stSupportedElements, cellalignAttrs); + + addHeadElement (stSupportedElements); + addTitleElement (stSupportedElements); + addBaseElement (stSupportedElements); + addMetaElement (stSupportedElements); + addScriptElement (stSupportedElements); + addNoscriptElement (stSupportedElements); + addStyleElement (stSupportedElements); + + /* The HTML element */ + name = "html"; + List htmlContent = new ArrayList (2); + htmlContent.add ("head"); + htmlContent.add ("body"); + td = new HtmlTagDesc (name, false, false, htmlContent, i18nAttrs); + stSupportedElements.put (name, td); + } + + + /** + * Constructor. + * Most of the initialization work is done in a static code + * block rather than in the constructor, so as to minimize + * overhead on multiple invocations. + */ + public Html4_0StrictDocDesc () + { + // publish stSupportedElements to superclass + supportedElements = stSupportedElements; + init (); + } + + + private static void addFormElement (Map stSupportedElements) + { + final String name = "form"; + List atts = new ArrayList (bigAttrs.size () + 8); + atts.addAll (bigAttrs); + addSimpleAttribute (atts, "action"); + addSimpleAttribute (atts, "method"); + addSimpleAttribute (atts, "enctype"); + addSimpleAttribute (atts, "onsubmit"); + addSimpleAttribute (atts, "onreset"); + addSimpleAttribute (atts, "accept-charset"); + List formContent = new ArrayList (blockContent.size ()); + formContent.addAll (blockContent); + formContent.add ("script"); + removeStringsFromList (formContent, new String[] { "form" }); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, formContent, atts); + stSupportedElements.put (name, td); + } + + private static void addHrElement + (Map stSupportedElements) + { + String name = "hr"; + List atts = new ArrayList (coreAttrs.size () + eventAttrs.size ()); + atts.addAll (coreAttrs); + atts.addAll (eventAttrs); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, null, atts); + stSupportedElements.put (name, td); + } + + private static void addImgElement (Map stSupportedElements) + { + String name = "img"; + List atts = new ArrayList (bigAttrs.size () + 10); + atts.addAll (bigAttrs); + addRequiredAttribute (atts, "src"); + addRequiredAttribute (atts, "alt"); + addSimpleAttribute (atts, "longdesc"); + addSimpleAttribute (atts, "height"); + addSimpleAttribute (atts, "width"); + addSimpleAttribute (atts, "usemap"); + addSelfAttribute (atts, "ismap"); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, null, atts); + stSupportedElements.put (name, td); + } + + private static void addInputElement + (Map stSupportedElements) + { + final String name = "input"; + List atts = new ArrayList (biggerAttrs.size () + 20); + atts.addAll (biggerAttrs); + atts.add (new HtmlAttributeDesc ("type", + new String[] {"text", "password", "checkbox", "radio", "submit", + "reset", "file", "hidden", "image", "button"}, + HtmlAttributeDesc.OTHER)); + addSimpleAttribute (atts, "name"); + addSimpleAttribute (atts, "value"); + addSelfAttribute (atts, "checked"); + addSelfAttribute (atts, "disabled"); + addSelfAttribute (atts, "readonly"); + addSimpleAttribute (atts, "size"); + addSimpleAttribute (atts, "maxlength"); + addSimpleAttribute (atts, "src"); + addSimpleAttribute (atts, "alt"); + addSimpleAttribute (atts, "usemap"); + addSimpleAttribute (atts, "tabindex"); + addSimpleAttribute (atts, "accesskey"); + addSimpleAttribute (atts, "onfocus"); + addSimpleAttribute (atts, "onblur"); + addSimpleAttribute (atts, "onselect"); + addSimpleAttribute (atts, "onchange"); + addSimpleAttribute (atts, "accept"); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, null, atts); + stSupportedElements.put (name, td); + } + + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4_0TFDocDesc.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4_0TFDocDesc.java new file mode 100644 index 00000000..df954fb0 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4_0TFDocDesc.java @@ -0,0 +1,202 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.html; + +import edu.harvard.hul.ois.jhove.module.HtmlModule; +import java.util.*; +/** + * Abstract class for the HTML 4.0 Transitional and Frameset document + * types. These are identical except for one element apiece, so nearly + * all the code is here or in its superclasses. + * + * @author Gary McGath + * + */ +public abstract class Html4_0TFDocDesc extends Html4TFDocDesc { + + + protected static void classInit4 (Map stSupportedElements, int version) + { + Html4TFDocDesc.classInit4(stSupportedElements); + int i; + String name; + HtmlTagDesc td; + + for (i = 0; i < fontMarkup.length; i++) { + name = fontMarkup[i]; + td = new HtmlTagDesc (name, true, true, inlineContent, bigAttrs); + stSupportedElements.put (name, td); + } + + /* Phrase elements. */ + for (i = 0; i < phraseMarkup.length; i++) { + name = phraseMarkup[i]; + td = new HtmlTagDesc (name, true, true, inlineContent, bigAttrs); + stSupportedElements.put (name, td); + } + + addSupElement (stSupportedElements); + addSubElement (stSupportedElements); + addSpanElement (stSupportedElements); + addBdoElement (stSupportedElements); + addBasefontElement (stSupportedElements); + addFontElement (stSupportedElements); + addBrElement (stSupportedElements, coreAttrs); + + addAddressElement (stSupportedElements); + addDivElement (stSupportedElements); + addCenterElement (stSupportedElements); + addAElement (stSupportedElements); + addMapElement (stSupportedElements); + + HtmlAttributeDesc shapeAtt = new HtmlAttributeDesc ("shape", + new String[] {"rect", "circle", "poly", "default" }, + HtmlAttributeDesc.REQUIRED); + addAreaElement (stSupportedElements, shapeAtt); + addLinkElement (stSupportedElements); + addImgElement (stSupportedElements); + addObjectElement (stSupportedElements); + addParamElement (stSupportedElements); + HtmlAttributeDesc ialignAtt = new HtmlAttributeDesc ("align", + new String[] { "top", "middle", "bottom", "left", "right" }, + HtmlAttributeDesc.IMPLIED); + addAppletElement (stSupportedElements, ialignAtt); + addHrElement (stSupportedElements); + addPElement (stSupportedElements); + + /* The heading (H1-H6) elements */ + for (i = 0; i < headings.length; i++) { + name = headings[i]; + td = new HtmlTagDesc (name, true, true, inlineContent, bigAttrs); + stSupportedElements.put (name, td); + } + + addPreElement (stSupportedElements); + addQElement (stSupportedElements); + addBlockquoteElement (stSupportedElements); + addInsElement (stSupportedElements); + addDelElement (stSupportedElements); + + addDlElement (stSupportedElements); + addDtElement (stSupportedElements); + addDdElement (stSupportedElements); + + addOlElement (stSupportedElements); + addUlElement (stSupportedElements); + addDirElement (stSupportedElements); + addLiElement (stSupportedElements); + addFormElement (stSupportedElements); + addLabelElement (stSupportedElements); + + addInputElement (stSupportedElements); + addSelectElement (stSupportedElements); + addOptgroupElement (stSupportedElements); + addOptionElement (stSupportedElements); + addTextareaElement (stSupportedElements); + addFieldsetElement (stSupportedElements); + addLegendElement (stSupportedElements); + addButtonElement (stSupportedElements); + addTableElement (stSupportedElements); + + HtmlAttributeDesc halignAtt = new HtmlAttributeDesc + ("align", + new String [] {"left", "center", "right", "justify", "char"}, + HtmlAttributeDesc.IMPLIED); + HtmlAttributeDesc valignAtt = + new HtmlAttributeDesc ("valign", + new String[] { "top", "middle", "bottom", "baseline" }, + HtmlAttributeDesc.IMPLIED); + List cellalignAttrs = new ArrayList (4); // combine cellhalign and cellvalign + cellalignAttrs.add (new HtmlAttributeDesc ("align", + new String[] {"left", "center", "right", "justify", "char" }, + HtmlAttributeDesc.IMPLIED)); + addSimpleAttribute (cellalignAttrs, "char"); + addSimpleAttribute (cellalignAttrs, "charoff"); + cellalignAttrs.add (valignAtt); + addTheadElement (stSupportedElements, cellalignAttrs); + addTfootElement (stSupportedElements, cellalignAttrs); + addTbodyElement (stSupportedElements, cellalignAttrs); + addTrElement (stSupportedElements); + + + addThElement (stSupportedElements); + addTdElement (stSupportedElements); + addCaptionElement + (stSupportedElements, inlineContent, valignAtt); + + addColgroupElement (stSupportedElements, cellalignAttrs); + addColElement (stSupportedElements, cellalignAttrs); + + addHeadElement (stSupportedElements); + addTitleElement (stSupportedElements); + addBaseElement (stSupportedElements); + addMetaElement (stSupportedElements); + addScriptElement (stSupportedElements); + addNoscriptElement (stSupportedElements); + addStyleElement (stSupportedElements); + + /* The HTML element */ + name = "html"; + List htmlContent = new ArrayList (2); + htmlContent.add ("head"); + if (version == HtmlModule.HTML_4_0_FRAMESET) { + htmlContent.add ("frameset"); + } + else { + htmlContent.add ("body"); + } + td = new HtmlTagDesc (name, false, false, htmlContent, i18nAttrs); + stSupportedElements.put (name, td); + addNoframesElement (stSupportedElements, version); + if (version == HtmlModule.HTML_4_0_FRAMESET) { + addFramesetElement (stSupportedElements); + addFrameElement (stSupportedElements); + } + addBodyElement (stSupportedElements); + } + + private static void addFormElement (Map stSupportedElements) + { + final String name = "form"; + List atts = new ArrayList (bigAttrs.size () + 9); + atts.addAll (bigAttrs); + addSimpleAttribute (atts, "action"); + addSimpleAttribute (atts, "method"); + addSimpleAttribute (atts, "enctype"); + addSimpleAttribute (atts, "onsubmit"); + addSimpleAttribute (atts, "onreset"); + addSimpleAttribute (atts, "target"); + addSimpleAttribute (atts, "accept-charset"); + List formContent = new ArrayList (blockContent.size ()); + formContent.addAll (blockContent); + formContent.add ("script"); + removeStringsFromList (formContent, new String[] { "form" }); + HtmlTagDesc td = new HtmlTagDesc (name, true, true, formContent, atts); + stSupportedElements.put (name, td); + } + + private static void addImgElement (Map stSupportedElements) + { + String name = "img"; + List atts = new ArrayList (bigAttrs.size () + 10); + atts.addAll (bigAttrs); + addRequiredAttribute (atts, "src"); + addRequiredAttribute (atts, "alt"); + addSimpleAttribute (atts, "longdesc"); + addSimpleAttribute (atts, "height"); + addSimpleAttribute (atts, "width"); + addSimpleAttribute (atts, "usemap"); + addSelfAttribute (atts, "ismap"); + atts.add (new HtmlAttributeDesc ("align", + new String[] { "top", "middle", "bottom", "left", "right" }, + HtmlAttributeDesc.IMPLIED)); + addSimpleAttribute (atts, "border"); + addSimpleAttribute (atts, "hspace"); + addSimpleAttribute (atts, "vspace"); + HtmlTagDesc td = new HtmlTagDesc (name, true, false, null, atts); + stSupportedElements.put (name, td); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4_0TransDocDesc.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4_0TransDocDesc.java new file mode 100644 index 00000000..abad3c3b --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/Html4_0TransDocDesc.java @@ -0,0 +1,45 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.html; + +import edu.harvard.hul.ois.jhove.module.HtmlModule; +import java.util.*; + +/** + * This class describes the requirements of an HTML 4.0 Transitional document. + * + * @author Gary McGath + * + */ +public class Html4_0TransDocDesc extends Html4_0TFDocDesc { + + /* Static, private map of supported tags. + * For efficiency, we create a static Map + * of supported tags just once, then assign that to stSupportedElements + * in the constructor. */ + private static Map stSupportedElements; + + { + stSupportedElements = new HashMap (280); + Html4_0TFDocDesc.classInit4 + (stSupportedElements, HtmlModule.HTML_4_0_TRANSITIONAL); + + } + + /** + * Constructor. + * Most of the initialization work is done in a static code + * block rather than in the constructor, so as to minimize + * overhead on multiple invocations. + */ + public Html4_0TransDocDesc () + { + // publish stSupportedElements to superclass + supportedElements = stSupportedElements; + init (); + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/HtmlAttributeDesc.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/HtmlAttributeDesc.java new file mode 100644 index 00000000..9a628f1b --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/HtmlAttributeDesc.java @@ -0,0 +1,112 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.html; + +//import java.util.*; + +/** + * Class representing an abstract attribute of an HTML element. + * + * @author Gary McGath + * + */ +public class HtmlAttributeDesc { + + /** Permitted values for _kind */ + public final static int + REQUIRED = 1, // #REQUIRED + CURRENT = 2, // #CURRENT + CONREF = 3, // #CONREF + IMPLIED = 4, // #IMPLIED + OTHER = 5; // Explicit default + + private String _name; + private int _kind; + private String[] _permittedValues; + + + /** + * Constructor. + * + * @param name The name of the attribute. Must be lower case. + * @param permittedValues Specific values allowed for the parameter. If + * null, then any CDATA value is allowed. + * @param kind The kind of parameter. Must be REQUIRED, CURRENT, + * CONREF, or IMPLIED. + */ + public HtmlAttributeDesc (String name, + String[] permittedValues, + int kind) + { + _name = name; + _permittedValues = permittedValues; + _kind = kind; + } + + /** + * Constructor for an attribute that can take any value, with + * kind defaulting to IMPLIED. */ + public HtmlAttributeDesc (String name) + { + _name = name; + _permittedValues = null; + _kind = IMPLIED; + } + + /** + * Returns the attribute's name. + */ + public String getName () + { + return _name; + } + + + /** Returns true if this tag's name + * matches the parameter. */ + public boolean nameMatches(String name) + { + return _name.equals (name); + } + + + /** Returns true if the parameter is a permissible + * value for the attribute. + */ + public boolean valueOK (String name, String value) + { + if (_permittedValues == null) { + return true; + } + else if (value == null) { + // An attribute without a value is permitted only when + // there is only one legal value, and that equals the + // attribute's name. + if (_permittedValues.length == 1 && + _permittedValues[0].equals (name)) { + return true; + } + else { + return false; + } + } + else { + value = value.toLowerCase (); + for (int i = 0; i < _permittedValues.length; i++) { + if (_permittedValues[i].equals (value)) { + return true; + } + } + return false; // No match + } + } + + /** Return true if the attribute is required. */ + public boolean isRequired () + { + return _kind == REQUIRED; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/HtmlCharStream.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/HtmlCharStream.java new file mode 100644 index 00000000..c815ca7d --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/HtmlCharStream.java @@ -0,0 +1,467 @@ +/* This is a very slightly changed version of HtmlCharStream.java Version 3.0, + * which is produced by JavaCC. The change is to allow a character + * set other than Unicode to be specified, and to make the default + * ISO 8859-1. One of the main reasons for this is that some ISO 8859 + * characters look like illegal characters in Unicode, making PCDATA + * appear invalid just because it has funny characters. */ + +/* 24-Oct-04 GDM: Modified readChar to circumvent a bug which causes + * infinite loops when reading binary files (which are never HTML) */ +package edu.harvard.hul.ois.jhove.module.html; + +import java.io.UnsupportedEncodingException; + +/** + * An implementation of interface CharStream, where the stream is assumed to + * contain only ASCII characters (without unicode processing). + */ + +public class HtmlCharStream implements CharStream +{ + public static final boolean staticFlag = false; + int bufsize; + int available; + int tokenBegin; + public int bufpos = -1; + protected int bufline[]; + protected int bufcolumn[]; + + protected int column = 0; + protected int line = 1; + + protected boolean prevCharIsCR = false; + protected boolean prevCharIsLF = false; + + // Detection of end of line + protected boolean _lineEndCR; + protected boolean _lineEndLF; + protected boolean _lineEndCRLF; + + protected java.io.Reader inputStream; + + protected char[] buffer; + protected int maxNextCharInd = 0; + protected int inBuf = 0; + + protected void ExpandBuff(boolean wrapAround) + { + char[] newbuffer = new char[bufsize + 2048]; + int newbufline[] = new int[bufsize + 2048]; + int newbufcolumn[] = new int[bufsize + 2048]; + + try + { + if (wrapAround) + { + System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); + System.arraycopy(buffer, 0, newbuffer, + bufsize - tokenBegin, bufpos); + buffer = newbuffer; + + System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); + System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos); + bufline = newbufline; + + System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); + System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos); + bufcolumn = newbufcolumn; + + maxNextCharInd = (bufpos += (bufsize - tokenBegin)); + } + else + { + System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); + buffer = newbuffer; + + System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); + bufline = newbufline; + + System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); + bufcolumn = newbufcolumn; + + maxNextCharInd = (bufpos -= tokenBegin); + } + } + catch (Throwable t) + { + throw new Error(t.getMessage()); + } + + + bufsize += 2048; + available = bufsize; + tokenBegin = 0; + } + + protected void FillBuff() throws java.io.IOException + { + if (maxNextCharInd == available) + { + if (available == bufsize) + { + if (tokenBegin > 2048) + { + bufpos = maxNextCharInd = 0; + available = tokenBegin; + } + else if (tokenBegin < 0) + bufpos = maxNextCharInd = 0; + else + ExpandBuff(false); + } + else if (available > tokenBegin) + available = bufsize; + else if ((tokenBegin - available) < 2048) + ExpandBuff(true); + else + available = tokenBegin; + } + + int i; + try { + if ((i = inputStream.read(buffer, maxNextCharInd, + available - maxNextCharInd)) == -1) + { + inputStream.close(); + throw new java.io.IOException(); + } + else + maxNextCharInd += i; + return; + } + catch(java.io.IOException e) { + --bufpos; + backup(0); + if (tokenBegin == -1) + tokenBegin = bufpos; + throw e; + } + } + + public char BeginToken() throws java.io.IOException + { + tokenBegin = -1; + char c = readChar(); + tokenBegin = bufpos; + + return c; + } + + protected void UpdateLineColumn(char c) + { + column++; + + if (prevCharIsLF) + { + _lineEndLF = true; + prevCharIsLF = false; + line += (column = 1); + } + else if (prevCharIsCR) + { + if ( c == '\n') { + _lineEndCRLF = true; + } else { + _lineEndCR = true; + } + + prevCharIsCR = false; + if (c == '\n') + { + prevCharIsLF = true; + } + else + line += (column = 1); + } + + switch (c) + { + case '\r' : + prevCharIsCR = true; + break; + case '\n' : + prevCharIsLF = true; + break; + // GDM 29-Oct-04: It makes more sense to me to consider a tab + // as just one character; we have no way to know what + // the tab spacing in somebody's editor is. +// case '\t' : +// column--; +// column += (8 - (column & 07)); +// break; + default : + break; + } + + bufline[bufpos] = line; + bufcolumn[bufpos] = column; + } + + public char readChar() throws java.io.IOException + { + if (inBuf > 0) + { + --inBuf; + + if (++bufpos == bufsize) + bufpos = 0; + + return buffer[bufpos]; + } + + if (++bufpos >= maxNextCharInd) + FillBuff(); + + char c = buffer[bufpos]; + + /** GDM: Check against binary characters which may + * otherwise throw this thing into a loop. */ + if (c >= '\000' && c <= '\010') { + throw new java.io.IOException ("Illegal character read"); + } + UpdateLineColumn(c); + return (c); + } + + /** + * @deprecated + * @see #getEndColumn + */ + + public int getColumn() { + return bufcolumn[bufpos]; + } + + /** + * @deprecated + * @see #getEndLine + */ + + public int getLine() { + return bufline[bufpos]; + } + + public int getEndColumn() { + return bufcolumn[bufpos]; + } + + public int getEndLine() { + return bufline[bufpos]; + } + + public int getBeginColumn() { + /* GDM 24-Oct-04: Catch exceptions which may be thrown + * when parsing binary files */ + try { + return bufcolumn[tokenBegin]; + } + catch (ArrayIndexOutOfBoundsException e) { + return -1; + } + } + + public int getBeginLine() { + /* GDM 24-Oct-04: Catch exceptions which may be thrown + * when parsing binary files */ + try { + return bufline[tokenBegin]; + } + catch (ArrayIndexOutOfBoundsException e) { + return -1; + } + } + + public void backup(int amount) { + + inBuf += amount; + if ((bufpos -= amount) < 0) + bufpos += bufsize; + } + + public HtmlCharStream(java.io.Reader dstream, int startline, + int startcolumn, int buffersize) + { + inputStream = dstream; + line = startline; + column = startcolumn - 1; + + available = bufsize = buffersize; + buffer = new char[buffersize]; + bufline = new int[buffersize]; + bufcolumn = new int[buffersize]; + } + + public HtmlCharStream(java.io.Reader dstream, int startline, + int startcolumn) + { + this(dstream, startline, startcolumn, 4096); + } + + public HtmlCharStream(java.io.Reader dstream) + { + this(dstream, 1, 1, 4096); + } + public void ReInit(java.io.Reader dstream, int startline, + int startcolumn, int buffersize) + { + inputStream = dstream; + line = startline; + column = startcolumn - 1; + + if (buffer == null || buffersize != buffer.length) + { + available = bufsize = buffersize; + buffer = new char[buffersize]; + bufline = new int[buffersize]; + bufcolumn = new int[buffersize]; + } + prevCharIsLF = prevCharIsCR = false; + tokenBegin = inBuf = maxNextCharInd = 0; + bufpos = -1; + } + + public void ReInit(java.io.Reader dstream, int startline, + int startcolumn) + { + ReInit(dstream, startline, startcolumn, 4096); + } + + public void ReInit(java.io.Reader dstream) + { + ReInit(dstream, 1, 1, 4096); + } + public HtmlCharStream(java.io.InputStream dstream, int startline, + int startcolumn, int buffersize, String charset) + throws UnsupportedEncodingException + { + this(new java.io.InputStreamReader(dstream, charset), startline, startcolumn, 4096); + } + + public HtmlCharStream(java.io.InputStream dstream, int startline, + int startcolumn, String charset) + throws UnsupportedEncodingException + { + this(dstream, startline, startcolumn, 4096, charset); + } + + public HtmlCharStream(java.io.InputStream dstream, String charset) + throws UnsupportedEncodingException + { + this(dstream, 1, 1, 4096, charset); + } + + public void ReInit(java.io.InputStream dstream, int startline, + int startcolumn, int buffersize) + { + ReInit(new java.io.InputStreamReader(dstream), startline, startcolumn, 4096); + } + + public void ReInit(java.io.InputStream dstream) + { + ReInit(dstream, 1, 1, 4096); + } + public void ReInit(java.io.InputStream dstream, int startline, + int startcolumn) + { + ReInit(dstream, startline, startcolumn, 4096); + } + public String GetImage() + { + if (bufpos >= tokenBegin) + return new String(buffer, tokenBegin, bufpos - tokenBegin + 1); + else + return new String(buffer, tokenBegin, bufsize - tokenBegin) + + new String(buffer, 0, bufpos + 1); + } + + public char[] GetSuffix(int len) + { + char[] ret = new char[len]; + + if ((bufpos + 1) >= len) + System.arraycopy(buffer, bufpos - len + 1, ret, 0, len); + else + { + System.arraycopy(buffer, bufsize - (len - bufpos - 1), ret, 0, + len - bufpos - 1); + System.arraycopy(buffer, 0, ret, len - bufpos - 1, bufpos + 1); + } + + return ret; + } + + public void Done() + { + buffer = null; + bufline = null; + bufcolumn = null; + } + + /** + * Method to adjust line and column numbers for the start of a token. + */ + public void adjustBeginLineColumn(int newLine, int newCol) + { + int start = tokenBegin; + int len; + + if (bufpos >= tokenBegin) + { + len = bufpos - tokenBegin + inBuf + 1; + } + else + { + len = bufsize - tokenBegin + bufpos + 1 + inBuf; + } + + int i = 0, j = 0, k = 0; + int nextColDiff = 0, columnDiff = 0; + + while (i < len && + bufline[j = start % bufsize] == bufline[k = ++start % bufsize]) + { + bufline[j] = newLine; + nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j]; + bufcolumn[j] = newCol + columnDiff; + columnDiff = nextColDiff; + i++; + } + + if (i < len) + { + bufline[j] = newLine++; + bufcolumn[j] = newCol + columnDiff; + + while (i++ < len) + { + if (bufline[j = start % bufsize] != bufline[++start % bufsize]) + bufline[j] = newLine++; + else + bufline[j] = newLine; + } + } + + line = bufline[j]; + column = bufcolumn[j]; + } + + /** + * Retrieve the kind of end of line. + * @return + */ + public String getKindOfLineEnd() { + if (_lineEndCR || _lineEndLF || _lineEndCRLF) { + if (_lineEndCRLF) { + return "CRLF"; + } + if (_lineEndCR) { + return "CR"; + } + if (_lineEndLF) { + return "LF"; + } + } + return null; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/HtmlDocDesc.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/HtmlDocDesc.java new file mode 100644 index 00000000..1c299830 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/HtmlDocDesc.java @@ -0,0 +1,518 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.html; + +import edu.harvard.hul.ois.jhove.*; +import java.util.*; +//import java.io.*; + + + +/** + * This is an abstract class for processing an HTML document that has + * been parsed into a List of HtmlElements. It defines common behavior + * for all supported versions of HTML except XHTML. Subclasses + * modify this base as needed. + * + * @author Gary McGath + * + */ +public abstract class HtmlDocDesc { + + /** Metadata for this document. */ + private HtmlMetadata metadata; + + /** Generic list of supported tags. For efficiency, this is + * generated only once. Subclasses will need to get a copy + * of this list and make additions or deletions as necessary. + * They must not modify any of the existing + * members of the list. */ + protected static HashMap commonTags; + + /** List of supported tags for this version of HTML. The subclass + * is responsible for generating this, typically using commonTags + * as a starting point. */ + protected Map supportedElements; + + /** A representation of the HTML element. */ + protected HtmlTagDesc htmlElement; + /** A representation of the HEAD element. */ + protected HtmlTagDesc headElement; + /** A representation of the BODY element. */ + protected HtmlTagDesc bodyElement; + /** A representation of the FRAMESET element. */ + protected HtmlTagDesc framesetElement; + + + + private HtmlStack elementStack; + + /** Header tags, which are invariant for all HTML versions. */ + protected static String[] headings = + { "h1", "h2", "h3", "h4", "h5", "h6" }; + + + /** Consructor. */ + public HtmlDocDesc () + { + } + + /** Validates the document and puts interesting properties into the + * RepInfo. + * + * @param elements The element list constructed by the parser + * @param info The RepInfo object which will be populated + * with properties + */ + public boolean validate (List elements, RepInfo info) { + // As we get to each open tag, we + // check it against the corresponding HtmlTagDesc. If there isn't one, we + // mark the document as invalid but continue anyway; we create a temporary + // HtmlTagDesc object for the tag that we find, with the closing tag indicated + // as optional. + // For each open tag, we push the HtmlTagDesc object onto the stack. We check + // if it's in the allowed content of the enclosing element. If not, we report it + // as an error but continue with it anyway. + // + // We special-case HTML, HEAD and BODY, which can be implied. + // If a tag is found which requires the content model for one of + // these, and it isn't on the stack, we just push it. + + metadata = new HtmlMetadata (); + elementStack = new HtmlStack (); + elementStack.setHeadElement (headElement); + elementStack.setBodyElement (bodyElement); + elementStack.setFramesetElement (framesetElement); + Iterator iter = elements.iterator(); + while (iter.hasNext ()) { + JHElement elem = (JHElement) iter.next (); + if (elem instanceof JHDoctype) { + // Doctype requires no further processing; grammar + // will have already caught it if it's not at the top + continue; + } + else if (elem instanceof JHOpenTag) { + doOpenTag ((JHOpenTag) elem, info); + } + else if (elem instanceof JHCloseTag) { + doCloseTag ((JHCloseTag) elem, info); + } + else if (elem instanceof JHErrorElement) { + doErrorElement ((JHErrorElement) elem, info); + } + else if (elem instanceof JHPCData) { + doPCData ((JHPCData) elem, info, metadata); + } + } + // It's a requirement that there be at least a TITLE, + // and thus an implicit or explicit HEAD element. + if (!elementStack.isHeadSeen ()) { + info.setMessage(new ErrorMessage + ("Document must have implicit or explicit HEAD element")); + info.setValid (false); + } + return true; + } + + + /** Returns the metadata for this document. */ + public HtmlMetadata getMetadata () + { + return metadata; + } + + /** Initialization called by subclass constructors after supportedElements + * has been assigned. */ + protected void init () + { + htmlElement = (HtmlTagDesc) supportedElements.get ("html"); + headElement = (HtmlTagDesc) supportedElements.get ("head"); + bodyElement = (HtmlTagDesc) supportedElements.get ("body"); + } + + + + /* Break out open tag code */ + private void doOpenTag (JHOpenTag tag, RepInfo info) + { + String name = tag.getName ().toLowerCase (); + boolean unknownTag = false; + + String msg = tag.getErrorMessage (); + if (msg != null) { + info.setMessage (new ErrorMessage + (msg, + "Name = " + name + ", Line = " + + tag.getLine () + ", Column = " + + tag.getColumn () )); + info.setWellFormed (false); + // But keep going anyway! + } + + /* If it's anything but an HTML tag, and the stack is empty, + * push an "HTML" element. */ + if (elementStack.isEmpty ()) { + if (!"html".equals (name)) { + JHOpenTag fakeTag = new JHOpenTag ("html"); + fakeTag.setElement (htmlElement); + elementStack.push (fakeTag); + } + } + HtmlTagDesc tagDesc = + (HtmlTagDesc) supportedElements.get (name); + if (tagDesc == null) { + unknownTag = true; + } + // Check the context only if it's a known tag; + // otherwise we'll issue a redundant error message. + if (!unknownTag && !checkElementContext (tag, info)) { + String toptag = null; + if (!elementStack.isEmpty ()) { + JHOpenTag top = (JHOpenTag) elementStack.top(); + toptag = top.getName(); + } + info.setMessage (new ErrorMessage + ("Tag illegal in context", + "Name = " + name + ", " + + (toptag != null ? "Container = " + toptag + ", " : "") + + "Line = " + tag.getLine () + ", Column = " + + tag.getColumn () )); + info.setValid (false); + } + if (unknownTag) { + info.setMessage (new ErrorMessage + ("Unknown tag", + "Name = " + name + ", Line = " + + tag.getLine () + ", Column = " + + tag.getColumn ())); + info.setValid (false); + // Make a temporary tag descriptor + tagDesc = new HtmlTempTagDesc (name); + } + if (!unknownTag && info.getWellFormed() == RepInfo.TRUE) { + /* Check if the attributes are valid */ + List atts = tag.getAttributes (); + Iterator iter = atts.iterator (); + // Create a list to accumulate all attribute names. + List attNames = new ArrayList (atts.size ()); + while (iter.hasNext ()) { + JHAttribute att = (JHAttribute) iter.next (); + String attName = att.getName(); + attNames.add (attName); + String attVal = att.getValue(); + HtmlAttributeDesc attDesc = + tagDesc.namedAttDesc (attName); + if (attDesc == null) { + info.setMessage ( new ErrorMessage + ("Undefined attribute for element", + "Name = " + name + ", Attribute = " + + attName + ", Line = " + att.getLine () + + ", Column = " + att.getColumn ())); + info.setValid (false); + } + else { + /* Check if value is legit */ + if (!attDesc.valueOK (attName, attVal)) { + info.setMessage (new ErrorMessage + ("Improper value for attribute", + "Element = " + name + ", Attribute = " + + attName + ", Value = " + attVal + + ", Line = " + att.getLine () + + ", Column = " + att.getColumn ())); + info.setValid (false); + } + } + // Extract entities from attribute value + if (attVal != null) { + Iterator entIter = tag.getEntities (attVal).iterator (); + Utf8BlockMarker utf8BM = metadata.getUtf8BlockMarker (); + while (entIter.hasNext ()) { + String ent = (String) entIter.next (); + metadata.addEntity (ent); + // If it's a numerical entity, note which UTF8 block it's in + try { + if (ent.charAt (1) == '#') { + int entval = Integer.parseInt + (ent.substring (2, ent.length() - 1)); + utf8BM.markBlock(entval); + } + } + catch (Exception e) { + // Any exception means it's the wrong kind of entity + } + } + } + } + // Check if all required attributes were found. + List missingAtts = tagDesc.missingRequiredAttributes(attNames); + if (!missingAtts.isEmpty ()) { + info.setValid (false); + Iterator miter = missingAtts.iterator (); + while (miter.hasNext ()) { + String matt = (String) miter.next (); + info.setMessage (new ErrorMessage + ("Missing required attribute", + "Tag = " + name + ", Attribute = " + matt + + ", Line = " + tag.getLine () + + ", Column = " + tag.getColumn ())); + } + } + } + tag.processElement (metadata); + // If the content is empty, then a closing tag isn't permitted + // (SGML handbook 7.3), so we don't push the open tag. + // But if it's a temporary tag descriptor, we don't know + // anything about it, so all guesses are wild. Push it anyway. + if (tagDesc.isTemp () || !tagDesc.isContentEmpty()) { + tag.setElement (tagDesc); + elementStack.push (tag); + } + } + + private void doCloseTag (JHCloseTag tag, RepInfo info) + { + String name = tag.getName (); + // Dig down into the stack till we find an element which + // matches this. If there's none, report the document + // as not well formed. Also allow for the special case + // of an empty body. (An empty head is illegal.) + int idx = elementStack.search (name); + if (idx == -1) { + info.setMessage (new ErrorMessage + ("Close tag without matching open tag", + "Name = " + name + ", Line = " + tag.getLine () + + ", Column = " + tag.getColumn ())); + info.setValid (false); + } + else { + // Pop the stack down to the level of the matching tag. + elementStack.popTo (idx); + } + + } + + private void doErrorElement (JHErrorElement elem, RepInfo info) + { + elem.reportError (info); + } + + private void doPCData (JHPCData elem, RepInfo info, HtmlMetadata metadata) + { + // Pop any elements that have optional close tags and do not + // allow PCDATA. + if (elementStack.isEmpty ()) { + // PCData before any content. This generates an implicit + // html and body if they haven't already been seen. + // It also means the document isn't valid, since the title + // should precede any PCData. + info.setMessage(new ErrorMessage + ("Document must have implicit or explicit HEAD element")); + info.setValid (false); + return; + } + HtmlTagDesc top = elementStack.top ().getElement (); + if (top.isTemp() || top.allowsPCData ()) { + // We assume that PCData is allowed with unknown tags. + elem.processPCData (elementStack, metadata); + return; + } + // If we can pop elements with optional closing tags till we find + // one that allows PCData, we should do that. But popping the + // stack empty, as could happen if we're in a HEAD element, is + // wrong. So we always allow two elements to remain on the stack. + while (!top.isCloseTagRequired ()) { + if (elementStack.size () <= 2) { + break; + } + elementStack.popp (); + top = elementStack.top ().getElement (); + if (top.allowsPCData ()) { + elem.processPCData (elementStack, metadata); + return; + } + } + info.setMessage (new ErrorMessage ("PCData illegal in context", + "Line = " + + elem.getLine () + ", Column = " + + elem.getColumn () )); + info.setValid (false); + } + + /* Returns true if the element is permissible at this point. + * This may pop elements off the stack and push implied tags. + */ + private boolean checkElementContext (JHOpenTag elem, RepInfo info) + { + /* We are guaranteed there's something on the stack + * unless the tag is "html", but Paranoia Is A Virtue */ + String name = elem.getName (); + if (elementStack.isEmpty ()) { + if ("html".equals (name)) { + return true; + } + else { + // This shouldn't happen + return false; + } + } + if (elementStack.excludesTag (name)) { + return false; + } + JHOpenTag top = elementStack.top (); + for (;;) { + if (top.canGetMore () && top.allowsTag (name, this)) { + top.countComponent (); + return true; + } + if (!top.canAdvance ()) { + /* Can't advance, can't stay put. */ + break; + } + top.advanceIndex (); + } + + /* Kludgy special-case code for optional tags */ + HtmlTagDesc topElem = top.getElement (); + if (topElem == htmlElement) { + if (!elementStack.isHeadSeen () && headElement.allowsTag (name, this)) { + JHOpenTag fakeTag = new JHOpenTag ("head"); + fakeTag.setElement(headElement); + elementStack.push (fakeTag); + return true; + } + if (!elementStack.isBodySeen () && + bodyElement != null && + bodyElement.allowsTag (name, this)) { + JHOpenTag fakeTag = new JHOpenTag ("body"); + fakeTag.setElement (bodyElement); + elementStack.push (fakeTag); + return true; + } + return false; + } + else if (topElem == headElement) { + if ("body".equals (name) || "frameset".equals (name)) { + // Pop implied head end tag. Is this too much + // special-casing? + elementStack.popp (); + elementStack.push (elem); + return true; + } + else if (!elementStack.isBodySeen () && + bodyElement != null && + bodyElement.allowsTag (name, this)) { + // Similar to above case except that the head is + // implicitly terminated. + elementStack.popp (); + JHOpenTag fakeTag = new JHOpenTag ("body"); + fakeTag.setElement (bodyElement); + elementStack.push (fakeTag); + return true; + } + else { + return false; + } + } + + // Pop elements till we find a valid context. If + // the enclosing element doesn't have an optional close + // tag, report an error but pop it anyway. But first + // check if there even is a context to which we can pop things. + boolean complained = false; + boolean searchStack = false; + if (elementStack.size () > 2) { + Iterator iter = elementStack.iterator (); + // Discard html element + iter.next (); + while (iter.hasNext ()) { + JHOpenTag otag = (JHOpenTag) iter.next (); + if (otag.allowsTag (name, this)) { + searchStack = true; + break; + } + } + } + if (searchStack) { + // We've established we can pop down to something. + while (elementStack.size () > 2) { + if (!complained) { + top = elementStack.top (); + topElem = top.getElement (); + if (topElem.isCloseTagRequired()) { + info.setValid (false); + info.setMessage (new ErrorMessage + ("Tag illegal in context", + "Name = " + name + ", " + + "Container = " + top.getName() + ", " + + "Line = " + elem.getLine() + ", Column = " + + elem.getColumn ())); + } + } + elementStack.popp (); + top = elementStack.top (); + //topElem = top.getElement (); + if (top.allowsTag (name, this)) { + return true; + } + if (elementStack.isEmpty ()) { + break; + } + } + } + return false; + } + + /** Adds all the Strings in an array to the end of a List. */ + protected static void addStringsToList (String[] names, List lst) + { + for (int i = 0; i < names.length; i++) { + lst.add (names[i]); + } + } + + + /** Adds an attribute to a List, with unrestricted values and + * type IMPLIED. */ + protected static void addSimpleAttribute (List atts, String name) + { + atts.add (new HtmlAttributeDesc (name)); + } + + /** Adds an attribute to a List, with unrestricted values and + * type REQUIRED. */ + protected static void addRequiredAttribute (List atts, String name) + { + atts.add (new HtmlAttributeDesc (name, null, HtmlAttributeDesc.REQUIRED)); + } + + /** Adds an attribute to a List, with the only permitted value being + * the name of the attribute. This kind of attribute is normally + * represented in HTML without an explicit value; in fact, some (most?) + * readers won't permit an explicit value. */ + protected static void addSelfAttribute (List atts, String name) + { + atts.add (new HtmlAttributeDesc (name, + new String[] { name }, + HtmlAttributeDesc.IMPLIED)); + } + + /** Removes excluded strings from a List. */ + protected static void removeStringsFromList (List lst, String [] strs) + { + for (int i = 0; i < strs.length; i++) { + lst.remove(strs[i]); + } + } + + + /** Pushes an element onto the element stack. */ + protected void pushElementStack (JHOpenTag tag) + { + elementStack.push (tag); + } + + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/HtmlMetadata.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/HtmlMetadata.java new file mode 100644 index 00000000..75b6dbca --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/HtmlMetadata.java @@ -0,0 +1,407 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004-2009 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.html; + +import edu.harvard.hul.ois.jhove.*; +import java.util.*; + +/** + * Repository for an HTML document's metadata. + * Also hold some state information, so that properties involving + * tags, attributes and pcdata can be constructed. + * + * @author Gary McGath + * + */ +public class HtmlMetadata { + + private String _title; + private String _lang; + private List _meta; + private String _charset; + private TreeSet _languages; + private List _links; + private List _images; + private List _citations; + private List _defs; + private List _frames; + private List _scripts; + private List _abbrs; + private TreeSet _entities; + private Property _propUnderConstruction; + + /** Object for tracking UTF8 blocks. */ + private Utf8BlockMarker utf8BM; + + /** Constructor. Initializes to the empty state. */ + public HtmlMetadata () + { + // Mostly sets variables to their defaults; it's good + // documentation practice. Lists are set to null until + // there's actually something to add to them; this guarantees + // that toProperty() doesn't have to deal with empty lists. + _title = null; + _lang = null; + _meta = null; + _charset = null; + _links = null; + _images = null; + _citations = null; + _defs = null; + _frames = null; + _scripts = null; + _entities = null; + _languages = null; + _propUnderConstruction = null; + utf8BM = new Utf8BlockMarker (); + } + + /** Stores the contents of the TITLE element. */ + public void setTitle (String title) + { + _title = title; + } + + /** Stores the language defined in the HTML element. */ + public void setLanguage (String lang) + { + _lang = lang; + } + + /** Add a language defined in an attribute of any element + * except the HTML element. */ + public void addLanguage (String lang) + { + if (!lang.equals(_lang)) { + if (_languages == null) { + _languages = new TreeSet (); + } + _languages.add (lang); + } + } + + /** Adds a CITE element's pcdata to the Citations property. */ + public void addCitation (String text) + { + if (_citations == null) { + _citations = new LinkedList (); + } + _citations.add (text); + } + + /** Adds a META tag's contents to the Meta property. */ + public void addMeta (Property prop) + { + // We don't set _meta until there's a property; + // thus, we guarantee it will never be an empty list. + if (_meta == null) { + _meta = new LinkedList (); + } + _meta.add (prop); + + // Is it a httpequiv=Content-Type ? + String valContentType = extractHttpEquivValue(prop, "Content-Type"); + if (valContentType != null) { + final String toSearch = "charset="; + int indexOfCharset = valContentType.indexOf(toSearch); + if (indexOfCharset != -1) { + setCharset(valContentType.substring(indexOfCharset + toSearch.length())); + } + } + // Is it a httpequiv=Content-Language ? + String valContentLanguage = extractHttpEquivValue(prop, "Content-Language"); + if (valContentLanguage != null) { + setLanguage(valContentLanguage); + } + } + + /** + * Extract the content value associated with a given httpEquiv. + * @param prop List containing the description of the meta tag + * @param httpEquivValue the httpEquiv to consider + * @return the content value + */ + public String extractHttpEquivValue(Property prop, String httpEquivValue) { + if (httpEquivValue == null) return null; + String value = null; + Property httpEquiv = prop.getByName("Httpequiv"); + if (httpEquiv != null && + PropertyArity.SCALAR.equals(httpEquiv.getArity()) && + PropertyType.STRING.equals(httpEquiv.getType()) + ) { + String val = (String)httpEquiv.getValue(); + if (httpEquivValue.equalsIgnoreCase(val)) { + // Look for charset in the Content property + Property content = prop.getByName("Content"); + if (content != null && + PropertyArity.SCALAR.equals(content.getArity()) && + PropertyType.STRING.equals(content.getType()) + ) { + value = (String)content.getValue(); + } + } + } + return value; + } + + /** Stores the charset defined in the HTML element. */ + public void setCharset (String charset) + { + _charset = charset; + } + + /** Adds a FRAME tag's contents to the Meta property. */ + public void addFrame (Property prop) + { + // We don't set _frames until there's a property; + // thus, we guarantee it will never be an empty list. + if (_frames == null) { + _frames = new LinkedList (); + } + _frames.add (prop); + } + + /** Adds an ABBR tag's contents to the Meta property. */ + public void addAbbr (Property prop) + { + if (_abbrs == null) { + _abbrs = new LinkedList (); + } + _abbrs.add (prop); + } + + /** Adds a link to the Links property. */ + public void addLink (String link) + { + if (_links == null) { + _links = new LinkedList (); + } + _links.add (link); + } + + /** Adds an item to the Images property. */ + public void addImage (Property prop) + { + if (_images == null) { + _images = new LinkedList (); + } + _images.add (prop); + } + + /** Adds a defined term to the Defined Terms property. */ + public void addDef (String text) + { + if (_defs == null) { + _defs = new LinkedList (); + } + _defs.add (text); + } + + /** Adds the language of a SCRIPT element to the Scripts property. */ + public void addScript (String stype) + { + if (_scripts == null) { + _scripts = new LinkedList (); + } + _scripts.add (stype); + } + + /** Adds a String to the Entities property. This property is a + * SortedSet, so duplicates are not added, and the resulting set + * can be iterated in alphabetical order. */ + public void addEntity (String entity) + { + if (_entities == null) { + _entities = new TreeSet (); + } + _entities.add (entity); + } + + /** Returns the UTF8BlockMarker for the metadata. */ + public Utf8BlockMarker getUtf8BlockMarker () + { + return utf8BM; + } + + /** Returns the contents of the TITLE element. */ + public String getTitle () + { + return _title; + } + + public String getCharset() { + return _charset; + } + + /** Converts the metadata to a Property. */ + public Property toProperty (TextMDMetadata _textMD) + { + List propList = new LinkedList (); + Property val = new Property ("HTMLMetadata", + PropertyType.PROPERTY, + PropertyArity.LIST, + propList); + if (_lang != null) { + propList.add (new Property ("PrimaryLanguage", + PropertyType.STRING, + _lang)); + if (_textMD != null) { + _textMD.setLanguage(_lang); + } + } + if (_languages != null) { + propList.add (new Property ("OtherLanguages", + PropertyType.STRING, + PropertyArity.SET, + _languages)); + } + if (_title != null) { + propList.add (new Property ("Title", + PropertyType.STRING, + _title)); + } + if (_meta != null) { + // We're guaranteed that if _meta isn't null, it's non-empty. + propList.add (new Property ("MetaTags", + PropertyType.PROPERTY, + PropertyArity.LIST, + _meta)); + } + if (_frames != null) { + propList.add (new Property ("Frames", + PropertyType.PROPERTY, + PropertyArity.LIST, + _frames)); + } + if (_links != null) { + propList.add (new Property ("Links", + PropertyType.STRING, + PropertyArity.LIST, + _links)); + } + if (_scripts != null) { + propList.add (new Property ("Scripts", + PropertyType.STRING, + PropertyArity.LIST, + _scripts)); + } + if (_images != null) { + propList.add (new Property("Images", + PropertyType.PROPERTY, + PropertyArity.LIST, + _images)); + } + if (_citations != null) { + propList.add (new Property("Citations", + PropertyType.STRING, + PropertyArity.LIST, + _citations)); + } + if (_defs != null) { + propList.add (new Property ("DefinedTerms", + PropertyType.STRING, + PropertyArity.LIST, + _defs)); + } + if (_abbrs != null) { + propList.add (new Property ("Abbreviations", + PropertyType.PROPERTY, + PropertyArity.LIST, + _abbrs)); + } + if (_entities != null) { + propList.add (new Property ("Entities", + PropertyType.STRING, + PropertyArity.SET, + _entities)); + } + if (utf8BM != null) { + Property p = utf8BM.getBlocksUsedProperty("UnicodeEntityBlocks"); + if (p != null) { + propList.add (p); + } + } + if (_textMD != null) { + propList.add (new Property ("TextMDMetadata", + PropertyType.TEXTMDMETADATA, + PropertyArity.SCALAR, + _textMD)); + } + + if (propList.isEmpty ()) { + return null; + } + + return val; + } + + /** Sets a "property under construction". This is generally + * called when an XML element is found, and the PCDATA must + * be incorporated into the property. + */ + public void setPropUnderConstruction (Property p) + { + _propUnderConstruction = p; + } + + /** Returns the "property under construction." */ + public Property getPropUnderConstruction () + { + return _propUnderConstruction; + } + + /** Adds PCDATA text to the property under construction. + * This may not all be provided in one lump, so it + * has to allow for multiple chunks. */ + public void addToPropUnderConstruction + (char[] ch, int start, int length) + { + if (_propUnderConstruction != null) { + String argStr = new String (ch, start, length); + String name = _propUnderConstruction.getName (); + Object val = _propUnderConstruction.getValue (); + if ("abbr".equals (name)) { + // Theoretically, this can come in more than one + // chunk, but a long abbreviation is moronic if + // not oxymoronic. + List propList = (List) _propUnderConstruction.getValue(); + Property abProp = new Property ("abbr", + PropertyType.STRING, + argStr); + propList.add(0, abProp); + } + else if ("title".equals (name) || + "dfn".equals (name)) { + // For these properties, we just need to maintain + // the String. But to keep the design consistent and + // simple, we maintain the Property and then just pull + // out the String at the end. + // A Property is immutable. Rather than risk obscure + // consequences from changing this assumption, we append + // the text to a new Property. + _propUnderConstruction = new Property (name, + PropertyType.STRING, + (String) val + argStr); + } + } + } + + /** Finishes any property under construction. This is called + * when an end element is encountered. */ + public void finishPropUnderConstruction () + { + if (_propUnderConstruction != null) { + String name = _propUnderConstruction.getName (); + if ("abbr".equals(name)) { + addAbbr (_propUnderConstruction); + } + else if ("title".equals (name)) { + _title = (String) _propUnderConstruction.getValue (); + } + _propUnderConstruction = null; + } + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/HtmlSpecialToken.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/HtmlSpecialToken.java new file mode 100644 index 00000000..55b92749 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/HtmlSpecialToken.java @@ -0,0 +1,32 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.html; + +/** + * Class for defining special items in HTML element and attribute + * definitions. This class is never instantiated except by the + * static elements it defines. + * + * @author Gary McGath + * + */ +public class HtmlSpecialToken { + + String _name; + + /** Private constructor. This class may not be instantiated. */ + private HtmlSpecialToken () {} + + private HtmlSpecialToken (String name) + { + _name = name; + } + + //public static HtmlSpecialToken EMPTY = new HtmlSpecialToken ("EMPTY"); + /** The PCDATA token. Signifies that PCDATA is permitted in the content + * of an element. */ + public static HtmlSpecialToken PCDATA = new HtmlSpecialToken ("PCDATA"); +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/HtmlStack.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/HtmlStack.java new file mode 100644 index 00000000..12d1ee1d --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/HtmlStack.java @@ -0,0 +1,136 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.html; + +import java.util.*; + +/** + * A LinkedList dressed up as a stack for processing HTML objects. + * It knows about certain elements and their history on the stack. + * + * @author Gary McGath + * + */ +public class HtmlStack extends LinkedList { + + /** Elements which get special treatment. */ + private HtmlTagDesc headElement; + private HtmlTagDesc bodyElement; + private HtmlTagDesc framesetElement; + + private boolean headSeen; + private boolean bodySeen; + + /** Sets the value of the HEAD element for easy comparison */ + protected void setHeadElement (HtmlTagDesc elem) + { + headElement = elem; + } + + /** Sets the value of the HEAD element for easy comparison */ + protected void setBodyElement (HtmlTagDesc elem) + { + bodyElement = elem; + } + + /** Sets the value of the HEAD element for easy comparison */ + protected void setFramesetElement (HtmlTagDesc elem) + { + bodyElement = elem; + } + + /** Pops top element from element stack. If we ever decide + * to go to a different stack implementation, it's necessary + * only to change these methods. Also, they add some + * type checking. + * + * Name changed from "pop" to "popp" to avoid a conflict in Java 1.6 + * with the List class. + * */ + protected void popp () + { + removeLast (); + } + + /** Pushes an element onto the stack. This should have + * its element field set to function properly. */ + protected void push (JHOpenTag tag) + { + add (tag); + HtmlTagDesc element = tag.getElement (); + if (element == headElement) { + headSeen = true; + } + else if (element == bodyElement) { + bodySeen = true; + } + } + + /** Gets the top of the element stack without popping it. */ + protected JHOpenTag top () + { + return (JHOpenTag) getLast(); + } + + /** Searches backwards through the element stack for a + * match to a given tag. Return -1 if no match. */ + protected int search (String tag) + { + /* Supposedly this ListIterator setup works + * for walking backwards. */ + ListIterator liter = listIterator + (size()); + int idx = size () - 1; + while (liter.hasPrevious ()) { + JHOpenTag stackTag = (JHOpenTag) liter.previous(); + HtmlTagDesc elem = stackTag.getElement (); + if (elem.matches (tag)) { + return idx; + } + idx--; + } + + /* No match, return -1 */ + return -1; + } + + /** Pops elements from the stack up to and including the + * one indexed by idx */ + protected void popTo (int idx) + { + int npop = size () - idx; + for (int i = 0; i < npop; i++) { + removeLast(); + } + } + + /** Returns true if a HEAD element has been + * pushed on the stack. */ + protected boolean isHeadSeen () + { + return headSeen; + } + + /** Returns true if a BODY element has been + * pushed on the stack. */ + protected boolean isBodySeen () + { + return bodySeen; + } + + /** Returns true if any element on the stack + * prohibits the specified tag. */ + protected boolean excludesTag (String tag) { + Iterator iter = iterator (); + while (iter.hasNext ()) { + JHOpenTag stackTag = (JHOpenTag) iter.next (); + if (stackTag.getElement ().excludesTag (tag)) { + return true; + } + } + return false; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/HtmlTagDesc.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/HtmlTagDesc.java new file mode 100644 index 00000000..cf2f7c3e --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/HtmlTagDesc.java @@ -0,0 +1,388 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.html; + +import java.util.*; + +/** + * This class defines the permitted behavior of a particular HTML tag. + * The full descriptive power of a DTD description isn't implemented here, + * but content types and sequences of content are implemented. + * + * @author Gary McGath + * + */ +public class HtmlTagDesc { + + /* Name of element. Constructor forces it to lower case, regardless + * of how it was specified. */ + private String _name; + private boolean _openTagRequired; + private boolean _closeTagRequired; + + + /* Element tags in which this element can be nested. Only tags for + * elements with optional opening tags need to be listed here; + * listing others will just reduce efficiency. If there are no + * applicable elements, this can be left null (which it is by + * default). */ + private List _implicitContainers; + + /* Array of permitted content lists. null indicates an empty element. + * Each element of the array is a list of tag names which are permissible + * at a given point. + */ + private List[] _contentArray; + + /* Array of excluded content strings. Each String in the array is a tag which + * may not be included in any descendant of the element. */ + private String[] _excludedContent; + + /** Array controlling the number of times each element of _contentArray + * may be used. */ + protected int[] _sequence; + + /** Value in _sequence indicating an element may be used 0 or 1 times. */ + public final static int SEQ0_1 = 0; + + /** Value in _sequence indicating an element must be used exactly once. */ + public final static int SEQ1 = 1; + + /** Value in _sequence indicating an element may be used 1 or more times. */ + public final static int SEQ1_MANY = 2; + + /** Value in _sequence indicating an element may be used 0 or more times. */ + public final static int SEQ0_MANY = 3; + + /* List of Attributes which are recognized for this tag. + * Will never be null, but may be empty. */ + private List _attributes; + + + /** Constructor for simple case. + * + * @param name Name of the element + * @param openTagRequired true if an opening tag is required + * @param closeTagRequired true if a closing tag is required + * @param content List of permitted tags. But what do I do when + * a particular order is required? Null if element + * is defined at EMPTY. + * @param attributes List of HtmlAttributeDesc elements enumerating + * the permitted attributes. May be null, in which + * case _attributes will be stored as an empty list. + */ + public HtmlTagDesc (String name, + boolean openTagRequired, + boolean closeTagRequired, + List content, + List attributes) + { + _name = name.toLowerCase (); + _openTagRequired = openTagRequired; + _closeTagRequired = closeTagRequired; + _implicitContainers = new LinkedList (); + if (content == null) { + // Empty element, so there's nothing for the content array + _contentArray = null; + } + else { + _contentArray = new List[1]; + _contentArray[0] = content; + _sequence = new int[1]; + _sequence[0] = SEQ0_MANY; // assume most general case + } + if (attributes == null) { + _attributes = new ArrayList (1); + } + else { + _attributes = attributes; + } + } + + /** Constructor for sequenced case. + * + * @param name Name of the element + * @param openTagRequired true if an opening tag is required + * @param closeTagRequired true if a closing tag is required + * @param sequence Array indicating the sequencing of elements in + * content. Must have the same length + * as sequence. + * @param attributes List of HtmlAttributeDesc elements enumerating + * the permitted attributes. May be null, in which + * case _attributes will be stored as an empty list. + */ + public HtmlTagDesc (String name, + boolean openTagRequired, + boolean closeTagRequired, + int[] sequence, + List[] contentArray, + List attributes) + { + _name = name.toLowerCase (); + _openTagRequired = openTagRequired; + _closeTagRequired = closeTagRequired; + _implicitContainers = new LinkedList (); + _sequence = sequence; + _contentArray = contentArray; + if (attributes == null) { + _attributes = new ArrayList (1); + } + else { + _attributes = attributes; + } + } + + /** Specifies tags which may not be included in this + * element or in any element nested at any depth + * within it. Corresponds to the -(content) feature + * of the DTD. */ + public void setExcludedContent (String[] content) + { + _excludedContent = content; + } + + /** Returns true if a given tag is excluded + * within this element. It is necessary to call this + * method for each element on the stack to determine if + * it is excluded. */ + public boolean excludesTag (String tag) + { + if (_excludedContent == null) { + return false; + } + for (int i = 0; i < _excludedContent.length; i++) { + if (_excludedContent[i].equals (tag)) { + return true; + } + } + return false; + } + + /** Alternative way of setting the attribute names. + * This can be used where all the attributes are + * unrestricted. This will replace any previously + * set attributes. */ + public void setAttributes (String[] attributeArray) + { + List atts = new ArrayList (attributeArray.length); + for (int i = 0; i < attributeArray.length; i++) { + HtmlAttributeDesc desc = new HtmlAttributeDesc (attributeArray[i]); + atts.add (desc); + } + _attributes = atts; + } + + + /** Provides the object with an array of element tags in which + * this element can be nested. Only tags for + * elements with optional opening tags may be listed here. + */ + public void addImplicitContainer (HtmlTagDesc container) + { + _implicitContainers.add (container); + } + + public boolean matches (String name) + { + return name.equals (_name); + } + + /** Reports whether this is a temporary tag descriptor. + * Returns false unless overridden. + */ + public boolean isTemp () + { + return false; + } + + + /** Reports whether this element allows a given tag name + * in its content, at the specified index. + */ + protected boolean allowsTag (String tag, int index, HtmlDocDesc doc) + { + if (_contentArray == null) { + // null means no content allowed + return false; + } + /* Check for index out of bounds. */ + if (index >= _contentArray.length) { + return false; + } + Iterator iter = _contentArray[index].iterator (); + while (iter.hasNext ()) { + String allowedTag; + try { + allowedTag = (String) iter.next (); + } + catch (Exception e) { + // Catch bad casts here -- any non-strings + // should be ignored. + continue; + } + if (allowedTag.equals (tag)) { + return true; + } + } + + /* We might still be OK if we can construct a set of + * elements with optional opening tags which will fill + * in the gap. */ + HtmlTagDesc tagDesc = (HtmlTagDesc) doc.supportedElements.get (tag); + if (tagDesc != null && + tagDesc._implicitContainers != null) { + Iterator citer = tagDesc._implicitContainers.iterator (); + while (citer.hasNext ()) { + HtmlTagDesc ctnr = (HtmlTagDesc) citer.next (); + // Call self recursively to try to insert the implicit + // tag. There may be more than one level of recursion, + // at least theoretically. + if (allowsTag (ctnr._name, index, doc)) { + JHOpenTag ctnrTag = new JHOpenTag (ctnr._name); + ctnrTag.setElement (ctnr); + doc.pushElementStack(ctnrTag); + return true; + } + } + } + return false; + } + + /** Reports whether this element can be implicitly nested + * in an element with a given tag. There may be more than + * one implicit container for a tag; if the DTD is unambiguous, + * there should be only one which is permissible in any + * given context. + */ + protected List implicitContainers (String tag) + { + return _implicitContainers; + } + + /** Reports whether additional elements can be matched + * at the specified content index. The index is assumed + * to be legal. */ + protected boolean canGetMoreAt (int index, int elemCount) + { + switch (_sequence[index]) { + case SEQ0_1: + case SEQ1: + return (elemCount == 0); + case SEQ1_MANY: + case SEQ0_MANY: + return true; + default: + return false; // Should never happen + } + } + + /** Reports whether it's legal to advance to the next content + * index. The index is assumed to be legal, but the one + * to which it's trying to advance may not be. */ + protected boolean canAdvanceFrom (int index, int elemCount) + { + if (index == _sequence.length - 1) { + return false; // No more content to match + } + switch (_sequence[index]) { + case SEQ0_1: + case SEQ0_MANY: + return true; + case SEQ1: + return (elemCount == 1); + case SEQ1_MANY: + return (elemCount >= 1); + default: + return false; // Should never happen + } + } + + /** Reports whether this element allows a given tag name + * in its content. This version should be used only with + * element descriptors that aren't associated with tags, + * for determining if a hypothetical implied element could + * contain the given tag. + */ + protected boolean allowsTag (String tag, HtmlDocDesc doc) + { + return allowsTag (tag, 0, doc); + } + + + protected boolean allowsPCData () + { + if (_contentArray == null) { + return false; + } + Iterator iter = _contentArray[0].iterator (); + while (iter.hasNext ()) { + Object contentItem = iter.next (); + if (contentItem == HtmlSpecialToken.PCDATA) { + return true; + } + } + return false; + } + + + /** Returns the attribute with a given name, or null if + * no such attribute is defined for the element */ + protected HtmlAttributeDesc namedAttDesc (String name) + { + Iterator iter = _attributes.iterator (); + while (iter.hasNext ()) { + HtmlAttributeDesc desc = (HtmlAttributeDesc) iter.next (); + if (desc.nameMatches (name)) { + return desc; + } + } + /* No match. */ + return null; + } + + /** Accepts a list of attribute names, and returns a List + * of required attribute names which are not present + * in the parameter list. Returns an empty List + * if all required attributes are present. */ + protected List missingRequiredAttributes (List names) + { + List val = new ArrayList (_attributes.size ()); + // Build a list of required attributes, which will + // be whittled away by comparison with the parameter list + List reqNames = new ArrayList (_attributes.size ()); + Iterator iter = _attributes.iterator (); + while (iter.hasNext ()) { + HtmlAttributeDesc desc = (HtmlAttributeDesc) iter.next (); + if (desc.isRequired ()) { + boolean found = false; + Iterator niter = names.iterator (); + while (niter.hasNext ()) { + String name = (String) niter.next (); + if (desc.nameMatches (name.toLowerCase ())) { + found = true; + break; + } + } + if (!found) { + val.add (desc.getName ()); + } + } + } + return val; + } + + /** Returns true if the closing tag is required */ + protected boolean isCloseTagRequired () + { + return _closeTagRequired; + } + + /** Returns true if this element has empty content */ + protected boolean isContentEmpty () + { + return _contentArray == null; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/HtmlTempTagDesc.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/HtmlTempTagDesc.java new file mode 100644 index 00000000..d77e247c --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/HtmlTempTagDesc.java @@ -0,0 +1,49 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.html; + +/** + * Subclass of HtmlTagDesc for temporary tags. This doesn't add + * any functionality to the superclass, but serves as a marker class. + * + * @author Gary McGath + * + */ +public class HtmlTempTagDesc extends HtmlTagDesc { + + /** + * Constructor. + * + * @param name Tag name + */ + public HtmlTempTagDesc (String name) + { + super (name, false, false, null, null); + // To minimize excessive error messages, assume unlimited + // tags can be nested. + _sequence = new int[1]; + _sequence[0] = SEQ0_MANY; + } + + /** Reports whether this is a temporary tag descriptor. + * Returns true. + */ + public boolean isTemp () + { + return true; + } + + /** Reports whether this element allows a given tag name + * in its content, at the specified index. Since we know nothing + * about this element, no meaningful answer is possible. Return + * true just to minimize the number of extra error + * messages. + */ + protected boolean allowsTag (String tag, int index, HtmlDocDesc doc) + { + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHAttribute.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHAttribute.java new file mode 100644 index 00000000..b404b0bd --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHAttribute.java @@ -0,0 +1,88 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.html; + +/** + * A description of an attribute within a JHOpenTag. + * This is not a subclass of JHElement, as it isn't part of + * the elements list. It is simply a way to store the information + * about an attribute in a JHOpenTag. + * + * @author Gary McGath + * + */ +public class JHAttribute { + + private String _name; + private String _namespace; + private String _value; + private int _line; + private int _column; + + /** + * Constructor. + * + * @param name Name of the attribute. Will be forced to + * lower case as it is stored. Must not be null. + * @param namespace Namespace for the attribute. May be null + * if no namespace is specified. + * @param value Value of the attribute. May be null. + * If it is in quotes, the quotes will be stripped. + * @param line Line number at which the attribute begins. + * @param column Column number at which the attribute begins. + */ + public JHAttribute (String name, String namespace, String value, + int line, int column) + { + _name = name.toLowerCase (); + _namespace = namespace; + _line = line; + _column = column; + // Clean up value if it's quoted + if (value != null && + value.length () >= 2 && + value.charAt (0) == '\"' && + value.charAt (value.length() - 1) == '\"') { + value = value.substring (1, value.length () - 1); + } + _value = value; + } + + /** Returns the attribute's name. This is guaranteed to be + * in lower case. */ + public String getName () + { + return _name; + } + + /** Returns the namespace of the attribute's name. May be null. */ + public String getNamespace () + { + return _namespace; + } + + /** Returns the attribute's value. May be null. If not null + * and was originally enclosed in double quotes, the return + * value has quotes stripped. */ + public String getValue () + { + return _value; + } + + /** Returns the line number of the beginning of the + * attribute definition. */ + public int getLine () + { + return _line; + } + + /** Returns the column number of the beginning of the + * attribute definition. */ + public int getColumn () + { + return _column; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHCloseTag.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHCloseTag.java new file mode 100644 index 00000000..5212d08e --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHCloseTag.java @@ -0,0 +1,39 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.html; + +import java.util.*; + +/** + * Representation of a parsed HTML close tag. + * + * @author Gary McGath + * + */ +public class JHCloseTag extends JHElement { + public String _name; + + /** Constructor. + * + * @param elements The list of parsed elements, to which + * this gets added. + * @param name The name of the tag + * @param line Line number, for information reporting + * @param column Line number, for information reporting + */ + public JHCloseTag (List elements, String name, int line, int column) { + super (elements); + _name = name.toLowerCase (); + _line = line; + _column = column; + } + + /** Returns the tag's name. */ + public String getName () + { + return _name; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHComment.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHComment.java new file mode 100644 index 00000000..4d86d91d --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHComment.java @@ -0,0 +1,24 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.html; + +import java.util.*; + +/** + * Representation of a parsed HTML comment. + * + * @author Gary McGath + * + */ +public class JHComment extends JHElement { + + /** Constructor. + * Just adds the comment to the element list. */ + public JHComment (List elements, String text) { + super (elements); + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHDoctype.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHDoctype.java new file mode 100644 index 00000000..0c110ca0 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHDoctype.java @@ -0,0 +1,33 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.html; + +import java.util.*; + +/** + * Representation of a parsed HTML DOCTYPE. + * + * @author Gary McGath + * + */ +public class JHDoctype extends JHElement { + + /** List of tokens in the DOCTYPE. */ + public List _doctypeElements; + + /** Constructor. */ + public JHDoctype (List elements, List dtElements) { + super (elements); + _doctypeElements = dtElements; + } + + /** Returns the doctype token list. */ + public List getDoctypeElements () + { + return _doctypeElements; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHElement.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHElement.java new file mode 100644 index 00000000..b40679b7 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHElement.java @@ -0,0 +1,73 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.html; + +import java.util.*; +/** + * Abstract superclass for the representation of portions of + * an HTML file. This is not the same usage of "element" as the + * SGML definition, but refers to a parsed unit, such as an opening + * tag, closing tag, or PCDATA. + * + * @author Gary McGath + * + */ +public abstract class JHElement { + + public int _column; + public int _line; + + /** Constructor. + * + * @param elements List of elements representing the document. + * May be null for a stub element which is implied + * rather than being generated by the parser. + */ + public JHElement (List elements) { + if (elements != null) { + elements.add (this); + } + } + + + /** Returns the line number for the start of the element. */ + public int getLine () + { + return _line; + } + + /** Returns the column number for the start of the element. */ + public int getColumn () + { + return _column; + } + + /** Extracts entities from a text string and returns them as + * a List of Strings. + * If there are no entities, returns an empty List. */ + protected List getEntities (String text) + { + List lst = new LinkedList (); + int startIdx = 0; + for (;;) { + // Find the ampersand which starts an entity. + int idx = text.indexOf ("&", startIdx); + if (idx < 0) { + break; // no more occurrences + } + // Find the semicolon which ends the entity. + int semiIdx = text.indexOf (";", idx); + if (semiIdx < 0) { + break; // broken entity, no terminator + } + String ent = text.substring(idx, semiIdx + 1); + lst.add (ent); + // Advance to end of this entity + startIdx = semiIdx; + } + return lst; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHErrorElement.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHErrorElement.java new file mode 100644 index 00000000..37ad688e --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHErrorElement.java @@ -0,0 +1,71 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004-2005 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.html; + +import edu.harvard.hul.ois.jhove.*; +import java.util.*; + +/** + * A JHElement which signifies a syntactic error. + * + * @author Gary McGath + * + */ +public class JHErrorElement extends JHElement { + + private String _message; + private String _image; + private boolean _illFormed; + + /** Constructor. + * @param elements List of elements representing the document. + * @param message Message to be reported + * @param image Textual representation of the offending portion. + * This will be used as the submessage of a generated + * ErrorMessage. + * @param illFormed true if the error makes the document + * not well-formed, false if it makes + * it only invalid. + */ + public JHErrorElement (List elements, + String message, + String image, + boolean illFormed) { + super (elements); + _message = message; + _image = image; + _illFormed = illFormed; + } + + + public String getImage () + { + return _image; + } + + /** Puts the item's error message into the RepInfo + * object, and affects the wellFormed and valid + * flags as required. Once it's determined that + * a document is not well-formed, error elements indicating + * only invalidity will be ignored. However, additional + * messages that indicate the current level of badness + * (not well-formed or invalid) will continue to be reported.*/ + public void reportError (RepInfo info) + { + // If we're already not well-formed and the error element + // is for invalidity, don't bother with it. + if (info.getWellFormed() == RepInfo.FALSE && !_illFormed) { + return; + } + info.setMessage (new ErrorMessage (_message, _image)); + if (_illFormed) { + info.setWellFormed (false); + } + else { + info.setValid(false); + } + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHOpenTag.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHOpenTag.java new file mode 100644 index 00000000..5f84ab53 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHOpenTag.java @@ -0,0 +1,440 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.html; + +import edu.harvard.hul.ois.jhove.*; +import java.util.*; + +/** Representation of a parsed HTML open tag, including its attributes. + * This arguable would better be called an element, but JHElement is + * the name of the abstract superclass. + * + * @author Gary McGath + * + */ +public class JHOpenTag extends JHElement { + + /** Element name. + * Fields are made public to avoid overcomplicating the .jj file */ + public String _name; + /** List of element attributes. Each + * attributes is an array of two strings, the + * name and the value. If no explicit value + * was given, attribute[1] is null. If the + * attribute was in quotes, the quotes are still there. + */ + public List _attributes; + /** Description of the abstract element. */ + private HtmlTagDesc _element; + /** Error message generated by parser, or null. */ + private String _errorMessage; + + /* Index into _element's content array to indicate the position + * currently being matched against. */ + private int _contentIdx; + + /* Number of elements matched at the current content index. */ + private int _elementCount; + + /** + * Constructor. + * + * @param elements The list of parsed elements, to which + * this gets added. May be null for a stub + * element not generated by the parser. + * @param name The name of the tag + * @param attrs A List of attributes, representing + * the parsed attributes of the tag. Each + * attributes is an array of two strings, the + * name and the value. If no explicit value + * was given, attribute[1] is null. If the + * attribute was in quotes, the quotes are still there. + * @param line Line number, for information reporting + * @param column Line number, for information reporting + */ + public JHOpenTag (List elements, String name, List attrs, int line, int column) + { + super (elements); + _name = name.toLowerCase (); + _attributes = attrs; + _line = line; + _column = column; + //cleanAttributeQuotes (); + _contentIdx = 0; + _elementCount = 0; + } + + /** + * Constructor with error message. + * This is used to allow constructs which are erroneous but common -- + * specifically, the closing of a tag with "/>". + * + * @param elements The list of parsed elements, to which + * this gets added. May be null for a stub + * element not generated by the parser. + * @param name The name of the tag + * @param attrs A List of attributes, representing + * the parsed attributes of the tag. Each + * attributes is an array of two strings, the + * name and the value. If no explicit value + * was given, attribute[1] is null. If the + * attribute was in quotes, the quotes are still there. + * @param message An error message indicating that this element + * isn't well-formed, but we'll take it anyway. + */ + public JHOpenTag (List elements, String name, List attrs, + int line, int column, String message) + { + this (elements, name, attrs, line, column); + _errorMessage = message; + } + + /** Constructor for a stub attribute. This shouldn't ever be used + * by the parser, but only by the module for generating implied + * elements. */ + public JHOpenTag (String name) + { + super (null); + _name = name; + _attributes = new ArrayList (1); + _contentIdx = 0; + } + + /** Associates an the tag with an element definition. This is done + * by the HTML module, not by the parser. */ + public void setElement (HtmlTagDesc element) + { + _element = element; + } + + /** Returns the element definition which has been associated with + * this tag. */ + public HtmlTagDesc getElement () + { + return _element; + } + + + /** Returns the tag's name. */ + public String getName () + { + return _name; + } + + /** Returns the tag's attributes. + * + * @return The attributes as a List. Each + * attributes is an array of two strings, the + * name and the value. If no explicit value + * was given, attribute[1] is null. If the + * attribute was in quotes, the quotes are still there. + */ + public List getAttributes () + { + return _attributes; + } + + /** Process the element to extract any available metadata. */ + protected void processElement (HtmlMetadata mdata) + { + if ("html".equals (_name)) { + processHtml (mdata); + } + else if ("meta".equals (_name)) { + processMeta (mdata); + } + else if ("a".equals (_name)) { + processA (mdata); + } + else if ("img".equals (_name)) { + processImg (mdata); + } + else if ("frame".equals (_name)) { + processFrame (mdata); + } + else if ("script".equals (_name)) { + processScript (mdata); + } + + /* Look for certain attributes in any tag. */ + Iterator iter = _attributes.iterator (); + while (iter.hasNext ()) { + JHAttribute attr = (JHAttribute) iter.next (); + if ("lang".equals (attr.getName ()) && attr.getValue () != null) { + mdata.addLanguage (attr.getValue ()); + } + } + } + + + /** Returns true if the tag given in the parameter is + * allowable in our context. */ + protected boolean allowsTag (String tag, HtmlDocDesc doc) + { + return _element.allowsTag (tag, _contentIdx, doc); + } + + /** Checks if we can accept another element at the current + * content index. */ + protected boolean canGetMore () + { + return _element.canGetMoreAt (_contentIdx, _elementCount); + } + + /** Counts off a component at the current index. */ + protected void countComponent () + { + _elementCount++; + } + + /** Increments the value of _contentIdx */ + protected void advanceIndex () + { + _contentIdx++; + _elementCount = 0; + } + + + /** Reports whether it's legal to advance to the next content + * index. The index is assumed to be legal, but the one + * to which it's trying to advance may not be. */ + protected boolean canAdvance () + { + return _element.canAdvanceFrom (_contentIdx, _elementCount); + } + + /** Returns the error message associated with this element. + * If it returns a non-null value, the tag is not well-formed, + * and the error should be reported. + */ + protected String getErrorMessage () + { + return _errorMessage; + } + + + /** Processes metadata from an HTML tag */ + private void processHtml (HtmlMetadata mdata) + { + String lang = null; + Iterator iter = _attributes.iterator (); + while (iter.hasNext ()) { + JHAttribute attr = (JHAttribute) iter.next (); + if ("lang".equals (attr.getName ())) { + lang = attr.getValue (); + } + } + if (lang != null) { + mdata.setLanguage(lang); + } + } + + + /** Processes metadata from a META tag */ + private void processMeta (HtmlMetadata mdata) + { + String name = null; + String httpeq = null; + String content = null; + Iterator iter = _attributes.iterator (); + while (iter.hasNext ()) { + JHAttribute attr = (JHAttribute) iter.next (); + String attname = attr.getName (); + String attval = attr.getValue (); + if ("name".equals (attname)) { + name = attval; + } + if ("http-equiv".equals (attname)) { + httpeq = attval; + } + if ("content".equals (attname)) { + content = attval; + } + } + if (name != null || httpeq != null || content != null) { + List plist = new ArrayList (3); + if (name != null) { + plist.add (new Property ("Name", + PropertyType.STRING, + name)); + } + if (httpeq != null) { + plist.add (new Property ("Httpequiv", + PropertyType.STRING, + httpeq)); + } + if (content != null) { + plist.add (new Property ("Content", + PropertyType.STRING, + content)); + } + mdata.addMeta (new Property ("Meta", + PropertyType.PROPERTY, + PropertyArity.LIST, + plist)); + } + } + + /** Processes metadata from an A element. Only elements with an + * HREF attribute are of interest here. We ignore links + * to anchors. */ + private void processA (HtmlMetadata mdata) + { + Iterator iter = _attributes.iterator (); + while (iter.hasNext ()) { + JHAttribute attr = (JHAttribute) iter.next (); + if ("href".equals (attr.getName ())) { + String link = attr.getValue (); + if (link.length() > 0 && link.charAt (0) != '#') { + mdata.addLink (link); + } + break; + } + } + } + + /** Processes metadata from the IMG element. */ + private void processImg (HtmlMetadata mdata) + { + String alt = null; + String longdesc = null; + String src = null; + int height = -1; + int width = -1; + Iterator iter = _attributes.iterator (); + while (iter.hasNext ()) { + JHAttribute attr = (JHAttribute) iter.next (); + String attname = attr.getName (); + String attval = attr.getValue (); + if ("alt".equals (attname)) { + alt = attval; + } + else if ("src".equals (attname)) { + src = attval; + } + else if ("longdesc".equals (attname)) { + longdesc = attval; + } + else if ("height".equals (attname)) { + try { + height = Integer.parseInt(attval); + } + catch (Exception e) {} + } + else if ("width".equals (attname)) { + try { + width = Integer.parseInt(attval); + } + catch (Exception e) {} + } + } + List plist = new ArrayList (5); + if (alt != null) { + plist.add (new Property ("Alt", + PropertyType.STRING, + alt)); + } + if (longdesc != null) { + plist.add (new Property ("Longdesc", + PropertyType.STRING, + longdesc)); + } + if (src != null) { + plist.add (new Property ("Src", + PropertyType.STRING, + src)); + } + if (height >= 0) { + plist.add (new Property ("Height", + PropertyType.INTEGER, + new Integer (height))); + } + if (width >= 0) { + plist.add (new Property ("Width", + PropertyType.INTEGER, + new Integer (width))); + } + if (!plist.isEmpty ()) { + mdata.addImage(new Property ("Image", + PropertyType.PROPERTY, + PropertyArity.LIST, + plist)); + } + } + + /** Processes metadata from the FRAME element. */ + private void processFrame (HtmlMetadata mdata) + { + String name = null; + String title = null; + String src = null; + String longdesc = null; + int height = -1; + int width = -1; + Iterator iter = _attributes.iterator (); + while (iter.hasNext ()) { + JHAttribute attr = (JHAttribute) iter.next (); + String attname = attr.getName (); + String attval = attr.getValue (); + if ("name".equals (attname)) { + name = attval; + } + else if ("title".equals (attname)) { + title = attval; + } + else if ("src".equals (attname)) { + src = attval; + } + else if ("longdesc".equals (attname)) { + longdesc = attval; + } + } + List plist = new ArrayList (4); + if (name != null) { + plist.add (new Property ("Name", + PropertyType.STRING, + name)); + } + if (title != null) { + plist.add (new Property ("Title", + PropertyType.STRING, + title)); + } + if (longdesc != null) { + plist.add (new Property ("Longdesc", + PropertyType.STRING, + longdesc)); + } + if (src != null) { + plist.add (new Property ("src", + PropertyType.STRING, + src)); + } + if (!plist.isEmpty ()) { + mdata.addFrame(new Property ("Frame", + PropertyType.PROPERTY, + PropertyArity.LIST, + plist)); + } + } + + /** Processes metadata from the SCRIPT element. */ + private void processScript (HtmlMetadata mdata) + { + Iterator iter = _attributes.iterator (); + while (iter.hasNext ()) { + JHAttribute attr = (JHAttribute) iter.next (); + String attname = attr.getName (); + String attval = attr.getValue (); + if ("type".equals (attname)) { + if (attval.length() > 0 ) { + mdata.addScript (attval); + } + } + } + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHPCData.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHPCData.java new file mode 100644 index 00000000..1ba4cd63 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHPCData.java @@ -0,0 +1,97 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.html; + +import edu.harvard.hul.ois.jhove.*; +import java.util.*; + +import edu.harvard.hul.ois.jhove.PropertyType; + +/** Representation of parsed HTML PCDATA. + * + * @author Gary McGath + * + */ +public class JHPCData extends JHElement { + + public String _text; + + /** + * Constructor. + * + * @param elements The list of parsed elements, to which + * this gets added. May be null for a stub + * element not generated by the parser. + * @param text The name of the tag + * @param line Line number, for information reporting + * @param column Line number, for information reporting + */ + public JHPCData (List elements, String text, int line, int column) { + super (elements); + _text = text; + _line = line; + _column = column; + } + + /** Extracts metadata and entities from the PCData object + * and its stack context. */ + protected void processPCData (HtmlStack elementStack, HtmlMetadata metadata) + { + JHOpenTag tag = elementStack.top (); + String name = tag.getName(); + if ("title".equals (name)) { + metadata.setTitle (_text); + } + else if ("cite".equals (name)) { + metadata.addCitation (_text); + } + else if ("dfn".equals (name)) { + metadata.addDef (_text); + } + else if ("abbr".equals (name)) { + List abbrList = new ArrayList (2); + abbrList.add( (new Property ("Text", + PropertyType.STRING, + _text))); + Iterator iter = tag.getAttributes().iterator (); + while (iter.hasNext ()) { + String[] attr = (String []) iter.next (); + String attname = attr[0]; + String attval = attr[1]; + if ("title".equals (attname)) { + abbrList.add (new Property ("Title", + PropertyType.STRING, + attval)); + break; + } + } + metadata.addAbbr (new Property ("Abbr", + PropertyType.PROPERTY, + PropertyArity.LIST, + abbrList)); + } + // Extract the entities and add them to the metadata + Iterator iter = getEntities (_text).iterator (); + Utf8BlockMarker utf8BM = metadata.getUtf8BlockMarker (); + while (iter.hasNext ()) { + String ent = (String) iter.next (); + metadata.addEntity (ent); + // If it's a numerical entity, note which UTF8 block it's in + try { + if (ent.charAt (1) == '#') { + int entval = Integer.parseInt + (ent.substring (2, ent.length() - 1)); + utf8BM.markBlock(entval); + } + } + catch (Exception e) { + // Any exception means it's the wrong kind of entity + } + } + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHXmlDecl.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHXmlDecl.java new file mode 100644 index 00000000..2723c404 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/JHXmlDecl.java @@ -0,0 +1,31 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.html; + +import java.util.List; + +/** + * Representation of an XML declaration. This class allows + * XHTML files to be examined without choking. The actual + * work is done by the XML module, but first we have to determine + * that it is XHTML. + * + * @author Gary McGath + * + */ +public class JHXmlDecl extends JHElement { + + /** Constructor. We don't really care about the content; this is + * just a placeholder. So it has a minimal constructor. + * + * @param elements The list of parsed elements, to which + * this gets added. + */ + public JHXmlDecl (List elements) + { + super (elements); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/ParseException.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/ParseException.java new file mode 100644 index 00000000..c2684b3b --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/ParseException.java @@ -0,0 +1,192 @@ +/* Generated By:JavaCC: Do not edit this line. ParseException.java Version 3.0 */ +package edu.harvard.hul.ois.jhove.module.html; + +/** + * This exception is thrown when parse errors are encountered. + * You can explicitly create objects of this exception type by + * calling the method generateParseException in the generated + * parser. + * + * You can modify this class to customize your error reporting + * mechanisms so long as you retain the public fields. + */ +public class ParseException extends Exception { + + /** + * This constructor is used by the method "generateParseException" + * in the generated parser. Calling this constructor generates + * a new object of this type with the fields "currentToken", + * "expectedTokenSequences", and "tokenImage" set. The boolean + * flag "specialConstructor" is also set to true to indicate that + * this constructor was used to create this object. + * This constructor calls its super class with the empty string + * to force the "toString" method of parent class "Throwable" to + * print the error message in the form: + * ParseException: + */ + public ParseException(Token currentTokenVal, + int[][] expectedTokenSequencesVal, + String[] tokenImageVal + ) + { + super(""); + specialConstructor = true; + currentToken = currentTokenVal; + expectedTokenSequences = expectedTokenSequencesVal; + tokenImage = tokenImageVal; + } + + /** + * The following constructors are for use by you for whatever + * purpose you can think of. Constructing the exception in this + * manner makes the exception behave in the normal way - i.e., as + * documented in the class "Throwable". The fields "errorToken", + * "expectedTokenSequences", and "tokenImage" do not contain + * relevant information. The JavaCC generated code does not use + * these constructors. + */ + + public ParseException() { + super(); + specialConstructor = false; + } + + public ParseException(String message) { + super(message); + specialConstructor = false; + } + + /** + * This variable determines which constructor was used to create + * this object and thereby affects the semantics of the + * "getMessage" method (see below). + */ + protected boolean specialConstructor; + + /** + * This is the last token that has been consumed successfully. If + * this object has been created due to a parse error, the token + * followng this token will (therefore) be the first error token. + */ + public Token currentToken; + + /** + * Each entry in this array is an array of integers. Each array + * of integers represents a sequence of tokens (by their ordinal + * values) that is expected at this point of the parse. + */ + public int[][] expectedTokenSequences; + + /** + * This is a reference to the "tokenImage" array of the generated + * parser within which the parse error occurred. This array is + * defined in the generated ...Constants interface. + */ + public String[] tokenImage; + + /** + * This method has the standard behavior when this object has been + * created using the standard constructors. Otherwise, it uses + * "currentToken" and "expectedTokenSequences" to generate a parse + * error message and returns it. If this object has been created + * due to a parse error, and you do not catch it (it gets thrown + * from the parser), then this method is called during the printing + * of the final stack trace, and hence the correct error message + * gets displayed. + */ + public String getMessage() { + if (!specialConstructor) { + return super.getMessage(); + } + String expected = ""; + int maxSize = 0; + for (int i = 0; i < expectedTokenSequences.length; i++) { + if (maxSize < expectedTokenSequences[i].length) { + maxSize = expectedTokenSequences[i].length; + } + for (int j = 0; j < expectedTokenSequences[i].length; j++) { + expected += tokenImage[expectedTokenSequences[i][j]] + " "; + } + if (expectedTokenSequences[i][expectedTokenSequences[i].length - 1] != 0) { + expected += "..."; + } + expected += eol + " "; + } + String retval = "Encountered \""; + Token tok = currentToken.next; + for (int i = 0; i < maxSize; i++) { + if (i != 0) retval += " "; + if (tok.kind == 0) { + retval += tokenImage[0]; + break; + } + retval += add_escapes(tok.image); + tok = tok.next; + } + retval += "\" at line " + currentToken.next.beginLine + ", column " + currentToken.next.beginColumn; + retval += "." + eol; + if (expectedTokenSequences.length == 1) { + retval += "Was expecting:" + eol + " "; + } else { + retval += "Was expecting one of:" + eol + " "; + } + retval += expected; + return retval; + } + + /** + * The end of line string for this machine. + */ + protected String eol = System.getProperty("line.separator", "\n"); + + /** + * Used to convert raw characters to their escaped version + * when these raw version cannot be used as part of an ASCII + * string literal. + */ + protected String add_escapes(String str) { + StringBuffer retval = new StringBuffer(); + char ch; + for (int i = 0; i < str.length(); i++) { + switch (str.charAt(i)) + { + case 0 : + continue; + case '\b': + retval.append("\\b"); + continue; + case '\t': + retval.append("\\t"); + continue; + case '\n': + retval.append("\\n"); + continue; + case '\f': + retval.append("\\f"); + continue; + case '\r': + retval.append("\\r"); + continue; + case '\"': + retval.append("\\\""); + continue; + case '\'': + retval.append("\\\'"); + continue; + case '\\': + retval.append("\\\\"); + continue; + default: + if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) { + String s = "0000" + Integer.toString(ch, 16); + retval.append("\\u" + s.substring(s.length() - 4, s.length())); + } else { + retval.append(ch); + } + continue; + } + } + return retval.toString(); + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/ParseHtml.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/ParseHtml.java new file mode 100644 index 00000000..430e9034 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/ParseHtml.java @@ -0,0 +1,668 @@ +/* Generated By:JavaCC: Do not edit this line. ParseHtml.java */ +/** Caution: Changes made by hand to fix bugs. Be sure to reenter + * these fixes if the file is regenerated. + */ +package edu.harvard.hul.ois.jhove.module.html; + +import java.util.*; + +public class ParseHtml implements ParseHtmlConstants { + private List elements; + + public List getElements () + { + return elements; + } + + final public List HtmlDoc() throws ParseException { + elements = new LinkedList (); + label_1: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case STARTDOCTYPE: + case LABRACKET: + case PCDATA: + ; + break; + default: + jj_la1[0] = jj_gen; + break label_1; + } + Element(elements); + } + jj_consume_token(0); + {if (true) return elements;} + throw new Error("Missing return statement in function"); + } + + final public JHElement Element(List elements) throws ParseException { + JHElement elem; + try { + if (jj_2_1(2)) { + elem = Doctype(); + {if (true) return elem;} + } else if (jj_2_2(2)) { + elem = OpenTag(); + {if (true) return elem;} + } else if (jj_2_3(2)) { + elem = CloseTag(); + {if (true) return elem;} + } else { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case PCDATA: + elem = PCData(); + {if (true) return elem;} + break; + default: + jj_la1[1] = jj_gen; + if (jj_2_4(2)) { + elem = XMLDecl(); + {if (true) return elem;} + } else { + jj_consume_token(-1); + throw new ParseException(); + } + } + } + } catch (ParseException e) { + String errText = ""; + for (;;) { + token_source.SwitchTo(DEFAULT); + Token tok = getNextToken (); + if (tok.kind == LABRACKET || tok.kind == PCDATA) { + break; + } + errText += "Text = \"" + tok.image + "\", Line = " + + tok.beginLine + ", Column = " + tok.beginColumn; + /****** Added GDM 14-Jun-05 to avoid infinite loop ********/ + if ("".equals (tok.image)) { + break; + } + /******* End Added GDM 14-Jun-05 to avoid infinite loop ********/ + } + {if (true) return new JHErrorElement(elements, "Parsing error", errText, true);} + } + {if (true) return elem;} + throw new Error("Missing return statement in function"); + } + + final public JHOpenTag OpenTag() throws ParseException { + List attrs = new LinkedList (); + Token name; + String slasher; + boolean complete; + jj_consume_token(LABRACKET); + name = Name(); + label_2: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case NAME: + ; + break; + default: + jj_la1[2] = jj_gen; + break label_2; + } + Attribute(attrs); + } + slasher = TagCloser(); + if ("/".equals (slasher)) { + /* This is a special hack so that a tag closed with "/>" will keep + the whole thing from falling apart, yet will generate an error */ + {if (true) return new JHOpenTag (elements, name.image, attrs, + name.beginLine, name.beginColumn, + "Construction with \"/>\" is incorrect except in XHTML");} + } + else { + {if (true) return new JHOpenTag (elements, name.image, attrs, + name.beginLine, name.beginColumn);} + } + throw new Error("Missing return statement in function"); + } + + final public JHXmlDecl XMLDecl() throws ParseException { + List attrs = new LinkedList (); + jj_consume_token(LABRACKET); + jj_consume_token(QMARK); + jj_consume_token(NAME); + label_3: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case NAME: + ; + break; + default: + jj_la1[3] = jj_gen; + break label_3; + } + Attribute(attrs); + } + jj_consume_token(QMARK); + jj_consume_token(RABRACKET); + {if (true) return new JHXmlDecl (elements);} + throw new Error("Missing return statement in function"); + } + + final public JHCloseTag CloseTag() throws ParseException { + Token name; + jj_consume_token(LABRACKET); + jj_consume_token(SLASH); + name = Name(); + jj_consume_token(RABRACKET); + {if (true) return new JHCloseTag (elements, name.image, + name.beginLine, name.beginColumn);} + throw new Error("Missing return statement in function"); + } + + final public JHPCData PCData() throws ParseException { + Token tok = getToken(1); + jj_consume_token(PCDATA); + {if (true) return new JHPCData (elements, tok.image, tok.beginLine, tok.beginColumn);} + throw new Error("Missing return statement in function"); + } + + final public JHDoctype Doctype() throws ParseException { + List doctypeElements = new LinkedList (); + jj_consume_token(STARTDOCTYPE); + jj_consume_token(DOCTYPEKEYWORD); + label_4: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case NAKEDDTITEM: + case QUOTEDDTITEM: + ; + break; + default: + jj_la1[4] = jj_gen; + break label_4; + } + DoctypeItem(doctypeElements); + } + jj_consume_token(RABRACKET); + {if (true) return new JHDoctype (elements, doctypeElements);} + throw new Error("Missing return statement in function"); + } + + JHErrorElement ConsumeError() throws ParseException { + Token tok = getNextToken(); + return new JHErrorElement (elements, "Parsing error", tok.image, true); + } + + final public void DoctypeItem(List dtElements) throws ParseException { + Token tok = getToken(1); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case NAKEDDTITEM: + jj_consume_token(NAKEDDTITEM); + dtElements.add (tok.image); + break; + case QUOTEDDTITEM: + jj_consume_token(QUOTEDDTITEM); + dtElements.add (tok.image); + break; + default: + jj_la1[5] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + } + + final public Token Name() throws ParseException { + Token tok = getToken(1); + jj_consume_token(NAME); + {if (true) return tok;} + throw new Error("Missing return statement in function"); + } + + final public String AttrVal() throws ParseException { + Token tok = getToken(1); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case NAKEDVALUE: + jj_consume_token(NAKEDVALUE); + {if (true) return tok.image;} + break; + case QUOTEDVALUE: + jj_consume_token(QUOTEDVALUE); + {if (true) return tok.image;} + jj_consume_token(SINGQUOTEDVALUE); + {if (true) return tok.image;} + break; + default: + jj_la1[6] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + throw new Error("Missing return statement in function"); + } + + final public void Attribute(List attrs) throws ParseException { + JHAttribute attval; + Token name; + Token namespace; + String val; + if (jj_2_5(2)) { + namespace = Name(); + jj_consume_token(COLON); + name = Name(); + jj_consume_token(EQUALS); + val = AttrVal(); + attval = new JHAttribute (name.image, namespace.image, val, + name.beginLine, name.beginColumn); + attrs.add(attval); + } else if (jj_2_6(2)) { + namespace = Name(); + jj_consume_token(COLON); + name = Name(); + attval = new JHAttribute (name.image, namespace.image, + null, + name.beginLine, name.beginColumn); + attrs.add(attval); + } else if (jj_2_7(2)) { + name = Name(); + jj_consume_token(EQUALS); + val = AttrVal(); + attval = new JHAttribute (name.image, null, val, + name.beginLine, name.beginColumn); + attrs.add(attval); + } else if (jj_2_8(2)) { + name = Name(); + attval = new JHAttribute (name.image, null, null, + name.beginLine, name.beginColumn); + attrs.add(attval); + } else { + jj_consume_token(-1); + throw new ParseException(); + } + } + + final public String TagCloser() throws ParseException { + Token tok = getToken (1); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case SLASH: + jj_consume_token(SLASH); + jj_consume_token(RABRACKET); + {if (true) return tok.image;} + break; + case RABRACKET: + jj_consume_token(RABRACKET); + {if (true) return tok.image;} + break; + default: + jj_la1[7] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + throw new Error("Missing return statement in function"); + } + + final private boolean jj_2_1(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_1(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(0, xla); } + } + + final private boolean jj_2_2(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_2(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(1, xla); } + } + + final private boolean jj_2_3(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_3(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(2, xla); } + } + + final private boolean jj_2_4(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_4(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(3, xla); } + } + + final private boolean jj_2_5(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_5(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(4, xla); } + } + + final private boolean jj_2_6(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_6(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(5, xla); } + } + + final private boolean jj_2_7(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_7(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(6, xla); } + } + + final private boolean jj_2_8(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_8(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(7, xla); } + } + + final private boolean jj_3_6() { + if (jj_3R_9()) return true; + if (jj_scan_token(COLON)) return true; + return false; + } + + final private boolean jj_3R_9() { + if (jj_scan_token(NAME)) return true; + return false; + } + + final private boolean jj_3R_7() { + if (jj_scan_token(LABRACKET)) return true; + if (jj_scan_token(SLASH)) return true; + return false; + } + + final private boolean jj_3_5() { + if (jj_3R_9()) return true; + if (jj_scan_token(COLON)) return true; + return false; + } + + final private boolean jj_3R_8() { + if (jj_scan_token(LABRACKET)) return true; + if (jj_scan_token(QMARK)) return true; + return false; + } + + final private boolean jj_3R_5() { + if (jj_scan_token(STARTDOCTYPE)) return true; + if (jj_scan_token(DOCTYPEKEYWORD)) return true; + return false; + } + + final private boolean jj_3R_6() { + if (jj_scan_token(LABRACKET)) return true; + if (jj_3R_9()) return true; + return false; + } + + final private boolean jj_3_8() { + if (jj_3R_9()) return true; + return false; + } + + final private boolean jj_3_4() { + if (jj_3R_8()) return true; + return false; + } + + final private boolean jj_3_3() { + if (jj_3R_7()) return true; + return false; + } + + final private boolean jj_3_2() { + if (jj_3R_6()) return true; + return false; + } + + final private boolean jj_3_7() { + if (jj_3R_9()) return true; + if (jj_scan_token(EQUALS)) return true; + return false; + } + + final private boolean jj_3_1() { + if (jj_3R_5()) return true; + return false; + } + + public ParseHtmlTokenManager token_source; + public Token token, jj_nt; + private int jj_ntk; + private Token jj_scanpos, jj_lastpos; + private int jj_la; + public boolean lookingAhead = false; + private boolean jj_semLA; + private int jj_gen; + final private int[] jj_la1 = new int[8]; + static private int[] jj_la1_0; + static { + jj_la1_0(); + } + private static void jj_la1_0() { + jj_la1_0 = new int[] {0x16,0x10,0x20,0x20,0x18000,0x18000,0x180,0x2800,}; + } + final private JJCalls[] jj_2_rtns = new JJCalls[8]; + private boolean jj_rescan = false; + private int jj_gc = 0; + + public ParseHtml(CharStream stream) { + token_source = new ParseHtmlTokenManager(stream); + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 8; i++) jj_la1[i] = -1; + for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls(); + } + + public void ReInit(CharStream stream) { + token_source.ReInit(stream); + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 8; i++) jj_la1[i] = -1; + for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls(); + } + + public ParseHtml(ParseHtmlTokenManager tm) { + token_source = tm; + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 8; i++) jj_la1[i] = -1; + for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls(); + } + + public void ReInit(ParseHtmlTokenManager tm) { + token_source = tm; + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 8; i++) jj_la1[i] = -1; + for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls(); + } + + final private Token jj_consume_token(int kind) throws ParseException { + Token oldToken; + if ((oldToken = token).next != null) token = token.next; + else token = token.next = token_source.getNextToken(); + jj_ntk = -1; + if (token.kind == kind) { + jj_gen++; + if (++jj_gc > 100) { + jj_gc = 0; + for (int i = 0; i < jj_2_rtns.length; i++) { + JJCalls c = jj_2_rtns[i]; + while (c != null) { + if (c.gen < jj_gen) c.first = null; + c = c.next; + } + } + } + return token; + } + token = oldToken; + jj_kind = kind; + throw generateParseException(); + } + + static private final class LookaheadSuccess extends java.lang.Error { } + final private LookaheadSuccess jj_ls = new LookaheadSuccess(); + final private boolean jj_scan_token(int kind) { + if (jj_scanpos == jj_lastpos) { + jj_la--; + if (jj_scanpos.next == null) { + jj_lastpos = jj_scanpos = jj_scanpos.next = token_source.getNextToken(); + } else { + jj_lastpos = jj_scanpos = jj_scanpos.next; + } + } else { + jj_scanpos = jj_scanpos.next; + } + if (jj_rescan) { + int i = 0; Token tok = token; + while (tok != null && tok != jj_scanpos) { i++; tok = tok.next; } + if (tok != null) jj_add_error_token(kind, i); + } + if (jj_scanpos.kind != kind) return true; + if (jj_la == 0 && jj_scanpos == jj_lastpos) throw jj_ls; + return false; + } + + final public Token getNextToken() { + if (token.next != null) token = token.next; + else token = token.next = token_source.getNextToken(); + jj_ntk = -1; + jj_gen++; + return token; + } + + final public Token getToken(int index) { + Token t = lookingAhead ? jj_scanpos : token; + for (int i = 0; i < index; i++) { + if (t.next != null) t = t.next; + else t = t.next = token_source.getNextToken(); + } + return t; + } + + final private int jj_ntk() { + if ((jj_nt=token.next) == null) + return (jj_ntk = (token.next=token_source.getNextToken()).kind); + else + return (jj_ntk = jj_nt.kind); + } + + private java.util.Vector jj_expentries = new java.util.Vector(); + private int[] jj_expentry; + private int jj_kind = -1; + private int[] jj_lasttokens = new int[100]; + private int jj_endpos; + + private void jj_add_error_token(int kind, int pos) { + if (pos >= 100) return; + if (pos == jj_endpos + 1) { + jj_lasttokens[jj_endpos++] = kind; + } else if (jj_endpos != 0) { + jj_expentry = new int[jj_endpos]; + for (int i = 0; i < jj_endpos; i++) { + jj_expentry[i] = jj_lasttokens[i]; + } + boolean exists = false; + for (java.util.Enumeration e = jj_expentries.elements(); e.hasMoreElements();) { + int[] oldentry = (int[])(e.nextElement()); + if (oldentry.length == jj_expentry.length) { + exists = true; + for (int i = 0; i < jj_expentry.length; i++) { + if (oldentry[i] != jj_expentry[i]) { + exists = false; + break; + } + } + if (exists) break; + } + } + if (!exists) jj_expentries.addElement(jj_expentry); + if (pos != 0) jj_lasttokens[(jj_endpos = pos) - 1] = kind; + } + } + + public ParseException generateParseException() { + jj_expentries.removeAllElements(); + boolean[] la1tokens = new boolean[27]; + for (int i = 0; i < 27; i++) { + la1tokens[i] = false; + } + if (jj_kind >= 0) { + la1tokens[jj_kind] = true; + jj_kind = -1; + } + for (int i = 0; i < 8; i++) { + if (jj_la1[i] == jj_gen) { + for (int j = 0; j < 32; j++) { + if ((jj_la1_0[i] & (1< jj_gen) { + jj_la = p.arg; jj_lastpos = jj_scanpos = p.first; + switch (i) { + case 0: jj_3_1(); break; + case 1: jj_3_2(); break; + case 2: jj_3_3(); break; + case 3: jj_3_4(); break; + case 4: jj_3_5(); break; + case 5: jj_3_6(); break; + case 6: jj_3_7(); break; + case 7: jj_3_8(); break; + } + } + p = p.next; + } while (p != null); + } + jj_rescan = false; + } + + final private void jj_save(int index, int xla) { + JJCalls p = jj_2_rtns[index]; + while (p.gen > jj_gen) { + if (p.next == null) { p = p.next = new JJCalls(); break; } + p = p.next; + } + p.gen = jj_gen + xla - jj_la; p.first = token; p.arg = xla; + } + + static final class JJCalls { + int gen; + Token first; + int arg; + JJCalls next; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/ParseHtml.jj b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/ParseHtml.jj new file mode 100644 index 00000000..c37feca7 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/ParseHtml.jj @@ -0,0 +1,316 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +/* javacc grammar for parsing HTML into a List that higher-level + code can handle. +*/ + +options { + IGNORE_CASE = true; + USER_CHAR_STREAM = true; + STATIC = false; +} + +PARSER_BEGIN(ParseHtml) + + +package edu.harvard.hul.ois.jhove.module.html; + +import java.util.*; + +public class ParseHtml +{ + private List elements; + + public List getElements () + { + return elements; + } + + +} + +PARSER_END(ParseHtml) + + +/* Lexical productions start here. */ +TOKEN: +{ + : IN_DOCTYPE | + : IN_TAG +} + + +MORE: +{ + : IN_PCDATA +} + + +TOKEN: +{ + : DEFAULT +} + + + +TOKEN: +{ + | + : IN_ATTVALUE +} + + +TOKEN: +{ + ", "=", ",", "\""])+ > : IN_TAG | + : IN_TAG | + : IN_TAG +} + + + +TOKEN: +{ + | + | + +} + + +TOKEN: +{ + "> : DEFAULT +} + + +TOKEN: +{ + : IN_DOCTYPE2 +} + + +TOKEN: +{ + "])+ > | + +} + + +SKIP : /* white space */ +{ + " " +| "\t" +| "\n" +| "\r" +| "\f" +} + +MORE : +{ + : IN_COMMENT +} + + +SPECIAL_TOKEN : +{ + <"--" > : ENDING_COMMENT +} + + +MORE : +{ + < ~[] > +} + + + +SPECIAL_TOKEN : +{ + <">" > : DEFAULT | + < ~[">"] > : IN_COMMENT +} + + + +List HtmlDoc () : +{ + elements = new LinkedList (); +} +{ +/* Production block -- looks a little like Java but isn't */ + (Element(elements))* + { return elements; } +} + +JHElement Element (List elements) : +{ + JHElement elem; +} +{ + try { + LOOKAHEAD(2) + elem = Doctype () { return elem; } | + LOOKAHEAD(2) + elem = OpenTag() { return elem; } | + LOOKAHEAD(2) + elem = CloseTag() { return elem; } | + elem = PCData() { return elem; } | + LOOKAHEAD(2) + elem = XMLDecl() { return elem; } /* | + elem = ProcessingInst() */ + } + catch (ParseException e) { + String errText = ""; + for (;;) { + token_source.SwitchTo(DEFAULT); + Token tok = getNextToken (); + if (tok.kind == LABRACKET || tok.kind == PCDATA) { + break; + } + errText += "Text = \"" + tok.image + "\", Line = " + + tok.beginLine + ", Column = " + tok.beginColumn; + } + return new JHErrorElement(elements, "Parsing error", errText, true); + } + { return elem; } +} + + +JHOpenTag OpenTag () : +{ + List attrs = new LinkedList (); + Token name; + String slasher; + boolean complete; +} +{ + name = Name () (Attribute(attrs))* slasher = TagCloser () + { if ("/".equals (slasher)) { + /* This is a special hack so that a tag closed with "/>" will keep + the whole thing from falling apart, yet will generate an error */ + return new JHOpenTag (elements, name.image, attrs, + name.beginLine, name.beginColumn, + "Construction with \"/>\" is incorrect except in XHTML"); + } + else { + return new JHOpenTag (elements, name.image, attrs, + name.beginLine, name.beginColumn); + } + } +} + +JHXmlDecl XMLDecl () : +{ + List attrs = new LinkedList (); +} +{ + (Attribute(attrs))* + { return new JHXmlDecl (elements); } +} + +JHCloseTag CloseTag () : +{ + Token name; +} +{ + name = Name () + { return new JHCloseTag (elements, name.image, + name.beginLine, name.beginColumn); } +} + +JHPCData PCData () : +{ + Token tok = getToken(1); +} +{ + { return new JHPCData (elements, tok.image, tok.beginLine, tok.beginColumn); } +} + +JHDoctype Doctype () : +{ + List doctypeElements = new LinkedList (); +} +{ + (DoctypeItem (doctypeElements))* + {return new JHDoctype (elements, doctypeElements); } +} + + +/* This is a last-resort production which consumes a token + and returns an element that will be flagged as an error. */ +JAVACODE JHErrorElement ConsumeError () +{ + Token tok = getNextToken(); + return new JHErrorElement (elements, "Parsing error", tok.image, true); +} + +void DoctypeItem (List dtElements) : +{ + Token tok = getToken(1); +} +{ + { dtElements.add (tok.image); } | + { dtElements.add (tok.image); } +} + + +Token Name () : +{ + Token tok = getToken(1); +} +{ + { return tok; } +} + +String AttrVal () : +{ + Token tok = getToken(1); +} +{ + { return tok.image; } | + { return tok.image; } + { return tok.image; } +} + +void Attribute (List attrs) : +{ + JHAttribute attval; + Token name; + Token namespace; + String val; +} +{ + LOOKAHEAD(2) + namespace = Name() name = Name() val = AttrVal() + { attval = new JHAttribute (name.image, namespace.image, val, + name.beginLine, name.beginColumn); + attrs.add(attval); } | + LOOKAHEAD(2) + namespace = Name() name = Name() + { attval = new JHAttribute (name.image, namespace.image, + null, + name.beginLine, name.beginColumn); + attrs.add(attval); } | + LOOKAHEAD(2) + name = Name() val = AttrVal() + { attval = new JHAttribute (name.image, null, val, + name.beginLine, name.beginColumn); + attrs.add(attval); } | + LOOKAHEAD(2) + name = Name() + { attval = new JHAttribute (name.image, null, null, + name.beginLine, name.beginColumn); + attrs.add(attval); } +} + +String TagCloser () : +{ + Token tok = getToken (1); +} +{ + ( ) { return tok.image; } | + { return tok.image; } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/ParseHtmlConstants.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/ParseHtmlConstants.java new file mode 100644 index 00000000..1263c5b9 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/ParseHtmlConstants.java @@ -0,0 +1,64 @@ +/* Generated By:JavaCC: Do not edit this line. ParseHtmlConstants.java */ +package edu.harvard.hul.ois.jhove.module.html; + +public interface ParseHtmlConstants { + + int EOF = 0; + int STARTDOCTYPE = 1; + int LABRACKET = 2; + int PCDATASTART = 3; + int PCDATA = 4; + int NAME = 5; + int EQUALS = 6; + int NAKEDVALUE = 7; + int QUOTEDVALUE = 8; + int SINGQUOTEDVALUE = 9; + int QMARK = 10; + int SLASH = 11; + int COLON = 12; + int RABRACKET = 13; + int DOCTYPEKEYWORD = 14; + int NAKEDDTITEM = 15; + int QUOTEDDTITEM = 16; + int OPENCOMMENT = 22; + + int DEFAULT = 0; + int IN_PCDATA = 1; + int IN_TAG = 2; + int IN_ATTVALUE = 3; + int IN_DOCTYPE = 4; + int IN_DOCTYPE2 = 5; + int IN_COMMENT = 6; + int ENDING_COMMENT = 7; + + String[] tokenImage = { + "", + "\"", + "", + "", + "", + "", + "", + "", + "\"?\"", + "\"/\"", + "\":\"", + "\">\"", + "\"DOCTYPE\"", + "", + "", + "\" \"", + "\"\\t\"", + "\"\\n\"", + "\"\\r\"", + "\"\\f\"", + "\" + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/package.html b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/package.html new file mode 100644 index 00000000..6ea4ce5e --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/package.html @@ -0,0 +1,62 @@ + + + + + + + +

Contains supporting classes for the HTML-HUL module. +
+

+This module uses code generated by +JavaCC. +The grammar file is ParseHtml.jj. It can be compiled +using BuildParser.bat. Compiling it +generates the following files: + +

    +
  • CharStream.java +
  • ParseException.java +
  • ParseHtml.java +
  • ParseHtmlConstants.java +
  • ParseHtmlTokenManager.java +
  • SimpleCharStream.java +
  • Token.java +
  • TokenMgrError.java +
+ +

+In addition, HtmlCharStream.java has been created +by manually modifying CharStream.java. If a future +version of JavaCC changes CharStream.java, +HtmlCharStream.java should be changed to match. +

+A number of DTD and Entity files have been stored with this +package to facilitate resolution of Doctypes without having +to get them over the Internet. These are the W3 Consortium's +files, and no rights over them are claimed by including them here.
+The list of files: +

    +
  • xhtml1-frameset.dtd +
  • xhtml1-strict.dtd +
  • xhtml1-transitional.dtd +
  • xhtml-lat1.ent +
  • xhtml-special.ent +
  • xhtml-symbol.ent +
  • +
+

+This module uses the XML-HUL module in validating XHTML files. + + + +
+ + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/xhtml-lat1.ent b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/xhtml-lat1.ent new file mode 100644 index 00000000..ffee223e --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/xhtml-lat1.ent @@ -0,0 +1,196 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/xhtml-special.ent b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/xhtml-special.ent new file mode 100644 index 00000000..ca358b2f --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/xhtml-special.ent @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/xhtml-symbol.ent b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/xhtml-symbol.ent new file mode 100644 index 00000000..63c2abfa --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/xhtml-symbol.ent @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/xhtml1-frameset.dtd b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/xhtml1-frameset.dtd new file mode 100644 index 00000000..cbac564c --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/xhtml1-frameset.dtd @@ -0,0 +1,1235 @@ + + + + + +%HTMLlat1; + + +%HTMLsymbol; + + +%HTMLspecial; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/xhtml1-strict.dtd b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/xhtml1-strict.dtd new file mode 100644 index 00000000..3b5d12a5 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/xhtml1-strict.dtd @@ -0,0 +1,978 @@ + + + + + +%HTMLlat1; + + +%HTMLsymbol; + + +%HTMLspecial; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/xhtml1-transitional.dtd b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/xhtml1-transitional.dtd new file mode 100644 index 00000000..a20dd854 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/xhtml1-transitional.dtd @@ -0,0 +1,1201 @@ + + + + + +%HTMLlat1; + + +%HTMLsymbol; + + +%HTMLspecial; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/html/xhtml11-flat.dtd b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/xhtml11-flat.dtd new file mode 100644 index 00000000..fdf31846 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/html/xhtml11-flat.dtd @@ -0,0 +1,4513 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + + + + + + + + + + + + + + + + + + +]]> + + + + + + + + + + + + + + + +%xhtml-arch.mod;]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + + + + + + + + + + + + + +]]> + + + + + + + +]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + + + + + + + + + + +]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + + +]]> + + + + +]]> + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + + + +]]> + + + + + + + +]]> + + + +]]> + + +]]> + + + + + + + + + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + +]]> + + + + + + + + + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + +]]> + + + + + + + + + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + + + + + + + +]]> + + + +]]> + + + + +]]> + + + +]]> + + + + +]]> + + + +]]> + + + + +]]> + + + +]]> + + + + +]]> + + + +]]> + + + + +]]> + + + +]]> + + +]]> + + +]]> + + + + + + + + + + + + + + + + +]]> + + + +]]> + + +]]> + + + + + + + + + + + + + + + + + + + + + + +]]> + + + +]]> + + + + + + +]]> + + + +]]> + + + + + + +]]> + + + +]]> + + + + + + +]]> + + + +]]> + + + + + + +]]> + + + +]]> + + + + + + +]]> + + + +]]> + + +]]> + + + + + + + + + + + + + + + + + + +]]> + + + +]]> + + + + + + + +]]> + + + +]]> + + +]]> + + + + + + + + + + + + + + +]]> + + + +]]> + + +]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + +]]> + + + +]]> + + + + + + + + + + + + + +]]> + + + + + + +]]> + + + + + + +]]> +]]> + + + + + + + +]]> + + + + + + + + +]]> + + + + +]]> +]]> + + + + + + +]]> +]]> + + + + + + + + + + +]]> + + + + + +]]> + + + + + +]]> +]]> + + + + + +]]> + + + + + +]]> + + + + + +]]> +]]> +]]> + + +]]> + + + + + + + + + + + + + + + + + + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + +]]> + + + + + + + + + + + + + +]]> + + + +]]> + + +]]> + + +]]> + + + + + + + + + + + + + + + + +]]> + + + +]]> + + +]]> + + + + + + + + + + + + + + + + +]]> + + + +]]> + + +]]> + + + + + + + + + + + + + + + + +]]> + + + +]]> + + + + +]]> + + + + + + + + + + + + + + + + +]]> + + + +]]> + + + + + + + +]]> + + + +]]> + + +]]> + + + + + + + + + + + + + + + + +]]> + + + +]]> + + +]]> + + + + + + + + + + + + + + + + +]]> + + + +]]> + + +]]> + + + + + + + + + + + + + + +]]> + + + + + + +]]> + + + + + + + + + + + + + + + + + + + +]]> + + + +]]> + + +]]> + + + + + + + + + + + + + + + +]]> + + + + + + + + + + + + + + + + +]]> + + + +]]> + + +]]> + + + + + + + + + + + + + + + + +]]> + + + +]]> + + +]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + + + +]]> + + + + + + +]]> + + + +]]> + + + + + + + + +]]> + + + +]]> + + + + + + + + +]]> + + + +]]> + + + + + + + + +]]> + + + +]]> + + + + + + + + +]]> + + + +]]> + + + + + + + + +]]> + + + +]]> + + + + + + +]]> + + + +]]> + + + + + + + + +]]> + + + +]]> + + + + + + +]]> + + + +]]> + + +]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + + + +]]> + + + + + + + + +]]> + + + +]]> + + + + + + +]]> + + + + + +]]> + + + + + + +]]> + + + +]]> + + + + + + +]]> + + + +]]> + + + + + + +]]> + + + +]]> + + + + + + +]]> + + + +]]> + + + + + + + + +]]> + + + +]]> + + + + + + +]]> + + + +]]> + + + + + + +]]> + + + +]]> + + +]]> + + + + +%xhtml-legacy.mod;]]> + + + + + + + + + + + + + + + + + + +]]> + + + +]]> + + + + + + + +]]> + + + + + + +]]> + + + + + + + +]]> + + + +]]> + + + + + + + +]]> + + + + + + + +]]> + + +]]> + + + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/iff/Chunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/iff/Chunk.java new file mode 100644 index 00000000..f551bbc2 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/iff/Chunk.java @@ -0,0 +1,74 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.iff; + +import edu.harvard.hul.ois.jhove.*; + +import java.io.*; + +/** + * Abstract superclass for IFF/AIFF chunks. + * + * @author Gary McGath + * + */ +public abstract class Chunk { + + protected ModuleBase _module; + protected long bytesLeft; + protected DataInputStream _dstream; + + /** + * Constructor. + * @param module The Module under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the data are being read + */ + public Chunk (ModuleBase module, + ChunkHeader hdr, + DataInputStream dstrm) + { + _module = module; + bytesLeft = hdr.getSize (); + _dstream = dstrm; + } + + + /** Reads a chunk and puts appropriate information into + * the RepInfo object. + * + * @param info RepInfo object to receive information + * + * @return false if the chunk is structurally + * invalid, otherwise true + * @throws JhoveException + * + */ + public abstract boolean readChunk (RepInfo info) throws IOException, JhoveException; + + /** Convert a byte buffer cleanly to an ASCII string. + * This is used for fixed-allocation strings in Broadcast + * WAVE chunks, and might have uses elsewhere. + * If a string is shorter than its fixed allocation, we're + * guaranteed only that there is a null terminating the string, + * and noise could follow it. So we can't use the byte buffer + * constructor for a string. + */ + protected String byteBufString (byte[] b) + { + StringBuffer sb = new StringBuffer (b.length); + for (int i = 0; i < b.length; i++) { + byte c = b[i]; + if (c == 0) { + // Terminate when we see a null + break; + } + sb.append((char) c); + } + return sb.toString (); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/iff/ChunkHeader.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/iff/ChunkHeader.java new file mode 100644 index 00000000..7ca6d0bc --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/iff/ChunkHeader.java @@ -0,0 +1,84 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.iff; + +import edu.harvard.hul.ois.jhove.*; +import java.io.*; + +/** + * This class encapsulates an IFF/AIFF chunk header. + * + * @author Gary McGath + * + */ +public class ChunkHeader { + + private ModuleBase _module; + private RepInfo _repInfo; + private long _size; // This does not include the 8 bytes of header + private String _chunkID; // 4-character ID of the chunk + + /** + * Constructor. + * + * @param module The module under which the chunk is being read + * @param info The RepInfo object being used by the module + */ + public ChunkHeader (ModuleBase module, RepInfo info) + { + _module = module; + _repInfo = info; + } + + + /** + * Reads the header of a chunk. If _chunkID is non-null, + * it's assumed to have already been read. + */ + public boolean readHeader (DataInputStream dstrm) throws IOException + { + StringBuffer id = new StringBuffer(4); + for (int i = 0; i < 4; i++) { + int ch = ModuleBase.readUnsignedByte (dstrm, _module); + if (ch < 32) { + String hx = Integer.toHexString (ch); + if (hx.length () < 2) { + hx = "0" + hx; + } + _repInfo.setMessage (new ErrorMessage + ("Invalid character in Chunk ID", + "Character = 0x" + hx, + _module.getNByte ())); + _repInfo.setWellFormed (false); + return false; + } + id.append((char) ch); + } + _chunkID = id.toString (); + _size = ModuleBase.readUnsignedInt (dstrm, _module.isBigEndian (), _module); + return true; + } + + + /** Sets the chunk type, which is a 4-character code, directly. */ + public void setID (String id) + { + _chunkID = id; + } + + /** Returns the chunk type, which is a 4-character code */ + public String getID () + { + return _chunkID; + } + + /** Returns the chunk size (excluding the first 8 bytes) */ + public long getSize () + { + return _size; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/iff/Superchunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/iff/Superchunk.java new file mode 100644 index 00000000..d340f3df --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/iff/Superchunk.java @@ -0,0 +1,61 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.iff; + +import edu.harvard.hul.ois.jhove.*; +import java.io.*; + +/** + * Abstract class for a chunk that contains other chunks. + * It is assumed that the nested chunks come last in the chunk, + * so that once you start reading chunks, reaching the end of + * the superchunk is the indicator that there are no more chunks + * to read. + * + * @author Gary McGath + * + */ +public abstract class Superchunk extends Chunk { + + private RepInfo _repInfo; + + /** + * Constructor. + * + * @param module The WaveModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the WAVE data are being read + * @param info RepInfo object for error reporting + */ + public Superchunk (ModuleBase module, + ChunkHeader hdr, + DataInputStream dstrm, + RepInfo info) + { + super (module, hdr, dstrm); + _repInfo = info; + } + + /** + * Reads and returns the next ChunkHeader within this Chunk, + * and takes care of byte counting. If this Chunk is exhausted, + * returns null. + */ + public ChunkHeader getNextChunkHeader () throws IOException + { + if (bytesLeft <= 0) { + return null; + } + Chunk chunk = null; + ChunkHeader chunkh = new ChunkHeader (_module, _repInfo); + if (!chunkh.readHeader(_dstream)) { + return null; + } + int chunkSize = (int) chunkh.getSize (); + bytesLeft -= chunkSize + 8; + return chunkh; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/iff/build.xml b/jhove/classes/edu/harvard/hul/ois/jhove/module/iff/build.xml new file mode 100644 index 00000000..aab2c728 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/iff/build.xml @@ -0,0 +1,32 @@ + + IFF common package build file + Jhove - JSTOR/Harvard Object Validation Environment + Version 1.0 2004-02-03 + Copyright 2004 by JSTOR and the President and Fellows of Harvard College + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/iff/package.html b/jhove/classes/edu/harvard/hul/ois/jhove/module/iff/package.html new file mode 100644 index 00000000..ef874ffa --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/iff/package.html @@ -0,0 +1,27 @@ + + + + + + + +Contains supporting classes for modules based on the IFF +specification (EA IFF 85). This may include AIFF, RIFF and +others. This package may also, at some point, include a generic +IFF module. +
+ + + + + +
+ + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/ArithConditioning.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/ArithConditioning.java new file mode 100644 index 00000000..60358352 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/ArithConditioning.java @@ -0,0 +1,62 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Encapsulation of an arithmetic conditioning entry for a JPEG image. + * + * @author Gary McGath + * + */ +public class ArithConditioning { + + private int _tableClass; + private int _destIdentifier; + + + /** + * Constructor. + */ + public ArithConditioning(int tableClass, int destIdentifier) { + _tableClass = tableClass; + _destIdentifier = destIdentifier; + } + + + /** + * Returns a Property defining the conditioning data + */ + public Property makeProperty (boolean raw) + { + Property[] parray = new Property[2]; + if (raw) { + parray[0] = new Property ("TableClass", + PropertyType.INTEGER, + new Integer (_tableClass)); + } + else { + String prec = "Undefined"; + try { + prec = JpegStrings.DAC_CLASS[_tableClass]; + } + catch (Exception e) {} + parray[0] = new Property ("Precision", + PropertyType.STRING, + prec); + } + parray[1] = new Property ("DestinationIdentifier", + PropertyType.INTEGER, + new Integer (_destIdentifier)); + return new Property ("ArithmeticConditioning", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + parray); + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/JpegExif.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/JpegExif.java new file mode 100644 index 00000000..721f374e --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/JpegExif.java @@ -0,0 +1,205 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg; + +import java.io.*; +import java.util.*; +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.tiff.ExifIFD; +import edu.harvard.hul.ois.jhove.module.tiff.TiffIFD; +import edu.harvard.hul.ois.jhove.module.tiff.TiffProfileExif; +import edu.harvard.hul.ois.jhove.module.tiff.TiffProfileExifIFD; +//import edu.harvard.hul.ois.jhove.module.*; + +/** + * Reader of Exif data embedded in a JPEG App1 block. This makes use + * of the TIFF module, since an Exif stream is really an embedded TIFF + * file; but it is designed to fail cleanly if the TIFF module is absent. + * + * @author Gary McGath + * + */ +public final class JpegExif { + + private boolean _exifProfileOK; + + public JpegExif () + { + _exifProfileOK = false; + } + + + + /** Checks if the TIFF module is available. + */ + public static boolean isTiffAvailable () + { + try { + Class tiffClass = Class.forName ("edu.harvard.hul.ois.jhove.module.TiffModule"); + return true; + } + catch (Exception e) { + return false; + } + } + + /** Reads the Exif data from the current point at the data stream, + * puts it into a temporary file, and makes a RepInfo object + * available. This should be called only if isTiffAvailable() + * has returned true. + */ + public RepInfo readExifData (DataInputStream dstream, JhoveBase je, + int length) + { + RandomAccessFile tiffRaf = null; + File tiffFile = null; + FileOutputStream fos = null; + RepInfo info = new RepInfo ("tempfile"); + /* We're now at the beginning of the TIFF data. + * Copy it into a temporary file, then parse that + * as a TIFF file. + */ + try { + tiffFile = je.tempFile (); + } + catch (IOException e) { + info.setMessage (new ErrorMessage + ("Error creating temporary file. Check your configuration: " + + e.getMessage ())); + return info; + } + try { + fos = new FileOutputStream (tiffFile); + int bufSize = je.getBufferSize (); + int tiffLen = length - 8; + /* Set a default buffer size if the app doesn't specify one. */ + if (bufSize <= 0) { + bufSize = 32768; + } + if (bufSize > tiffLen) { + // can buffer whole file in one buffer + bufSize = tiffLen; + } + BufferedOutputStream bos = new BufferedOutputStream (fos, bufSize); + byte[] buf = new byte[bufSize]; + while (tiffLen > 0) { + //int len; + int sz; + if (tiffLen < bufSize) { + sz = tiffLen; + } + else { + sz = bufSize; + } + sz = dstream.read (buf, 0, sz); + bos.write(buf, 0, sz); + tiffLen -= sz; + } + fos.flush (); + edu.harvard.hul.ois.jhove.module.TiffModule tiffMod = + new edu.harvard.hul.ois.jhove.module.TiffModule (); + tiffMod.setByteOffsetValid(true); + // Now parse the file, using a special parsing method. + // Close only after we're all done. + tiffRaf = new RandomAccessFile (tiffFile, "r"); + List ifds = tiffMod.exifParse (tiffRaf, info); + if (ifds == null) { + return info; + } + + // Locate the Exif IFD. (We probably also want the + // Interoperability IFD eventually.) + ListIterator iter = ifds.listIterator(); + boolean first = true; + boolean haveNisoMetadata = false; + while (iter.hasNext()) { + Object ifd = iter.next (); + if (ifd instanceof TiffIFD) { + // The TIFF IFD has useful information, which gets put + // into its NISO metadata. Make it available to the caller. + if (first) { + NisoImageMetadata niso = ((TiffIFD) ifd).getNisoImageMetadata (); + // The first one is presumed to be the interesting one. + info.setProperty (new Property ("NisoImageMetadata", + PropertyType.NISOIMAGEMETADATA, + niso)); + haveNisoMetadata = true; + TiffProfileExif exifProfile = new TiffProfileExif (); + _exifProfileOK = exifProfile.satisfiesProfile ((TiffIFD) ifd); + } + } + if (ifd instanceof ExifIFD) { + // Now for complicated stuff copying out the appropriate properties. + // Probably I just want to go through them and match interesting + // properties one by one, and copy them directly out. + // Or do I just want to copy the whole Exif property? + ExifIFD eifd = (ExifIFD) ifd; + Property ifdProp = eifd.getProperty( (je.getShowRawFlag ())); + List exifList = null; + if (ifdProp != null) { + exifList = eifd.exifProps (ifdProp); + } + if (_exifProfileOK) { + TiffProfileExifIFD exifIFDProfile = new TiffProfileExifIFD (); + _exifProfileOK = exifIFDProfile.satisfiesProfile(eifd); + } + if (exifList != null) { + info.setProperty(new Property ("Exif", + PropertyType.PROPERTY, + PropertyArity.LIST, + exifList)); + } + // See if we have any interesting NISO metadata. If so, and + // we haven't gotten real NISO metadata, use it. + if (!haveNisoMetadata) { + NisoImageMetadata niso = eifd.getNisoImageMetadata (); + info.setProperty (new Property ("NisoImageMetadata", + PropertyType.NISOIMAGEMETADATA, + niso)); + } + } + first = false; + } + } + catch (IOException e) { + info.setMessage (new ErrorMessage + ("I/O exception processing Exif metadata: " + + e.getMessage ())); + // Maybe should put this directly in the parent's + // RepInfo, otherwise I have to copy the message afterwards. + } + finally { + if (tiffRaf != null) { + try { + tiffRaf.close(); + } + catch (Exception e) {} + } + if (fos != null) { + try { + fos.close(); + } + catch (Exception e) {} + } + if (tiffFile != null) { + try { + tiffFile.delete(); + } + catch (Exception e) {} + } + } + return info; + } + + + /** Returns true if the Exif IFD is present and satisfies + * the profile requirements. + */ + public boolean isExifProfileOK () + { + return _exifProfileOK; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/JpegStrings.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/JpegStrings.java new file mode 100644 index 00000000..2b73279b --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/JpegStrings.java @@ -0,0 +1,136 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg; + +/** + * A class for holding arrays of informative strings that will go into + * properties of a JPEG object. + */ +public class JpegStrings { + + /** A private constructor just to make sure nobody + instantiates the class by mistake. */ + private JpegStrings () + { + } + + /** JPEG compression types, indexed on marker byte - 0XC0. + * This applies only to marker codes for the primary image; + * thumbnail compression schemes are completely incompatible. */ + public final static String[] COMPRESSION_TYPE = + { "Huffman coding, Baseline DCT", + "Huffman coding, Extemded sequential DCT", + "Huffman coding, Progressive DCT", + "Huffman coding, Lossless (sequential)", + "", + "Huffman coding, Differential sequential DCT", + "Huffman coding, Differential progressive DCT", + "Huffman coding, Differential lossless (sequential)", + "", + "Arithmetic coding, Extended sequential DCT", + "Arithmetic coding, Progressive DCT", + "Arithmetic coding, Lossless (sequential)", + "", + "Arithmetic coding, Differential sequential DCT", + "Arithmetic coding, Differential progressive DCT", + "Arithmetic coding, Differential lossless (sequential)" + }; + + /** Values for capability indicator byte for Version 0 */ + public final static String[] CAPABILITY_V0 = + { + "", // 0 + "Baseline sequential", // 1 + "Extended sequential, Huffman, 8-bits", // 2 + "Extended sequential, arithmetic, 8-bits", // 3 + "Extended sequential, Huffman, 12-bits", // 4 + "Extended sequential, arithmetic, 12-bits", // 5 + "Spectral selection, Huffman, 8-bits", // 6 + "Spectral selection, arithmetic, 8-bits", // 7 + "Full progression, Huffman, 8-bits", // 8 + "Full progression, arithmetic, 8-bits", // 9 + "Spectral selection, Huffman, 12-bits", // 10 + "Spectral selection, arithmetic, 12-bits", // 11 + "Full progression, Huffman, 12-bits", // 12 + "Full progression, arithmetic, 12-bits", // 13 + "Lossless, Huffman", // 14 + "Lossless, arithmetic", // 15 + "Hierarchical, sequential Huffman, 8-bits", // 16 + "Hierarchical, sequential arithmetic, 8-bits", // 17 + "Hierarchical, sequential Huffman, 12-bits", // 18 + "Hierarchical, sequential arithmetic, 12-bits", // 19 + "Hierarchical, Spectral Selection, " + + "Huffman, 8-bits", // 20 + "Hierarchical, Spectral Selection, " + + "arithmetic, 8-bits", // 21 + "Hierarchical, Full progression, " + + "Huffman, 8-bits", // 22 + "Hierarchical, Full progression, " + + "arithmetic, 8-bits", // 23 + "Hierarchical, Spectral Selection, " + + "Huffman, 12-bits", // 24 + "Hierarchical, Spectral Selection, " + + "arithmetic, 12-bits", // 25 + "Hierarchical, Full progression, " + + "Huffman, 12-bits", // 26 + "Hierarchical, Full progression, " + + "arithmetic, 12-bits", // 27 + "Hierarchical, Lossless, Huffman", // 28 + "Hierarchical, Lossless, arithmetic" // 29 + }; + + + /** Values for capability indicator byte for Version 1. + * These are by bit position from right to left. + */ + public final static String[] CAPABILITY_V1 = + { + "10 < blocks per MCU < 20", // 0xxx xxx1 + "Variable quantization", // 0xxx xx1x + "Hierarchical selective refinement", // 0xxx x1xx + "Progressive selective refinement", // 0xxx 1xxx + "Componenet selective refinement", // 0xx1 xxxx + }; + + + /* Values for capability indicator byte, tiling bits, + * for Version 1. These match the indicated masks. + */ + public final static String[] TILING_CAPABILITY_V1 = + { + "No tiling", // 000x xxxx + "Simple tiling", // 001x xxxx + "Pyramidal tiling", // 010x xxxx + "Composite tiling" // 011x xxxx + }; + + /* Values for tiling type, as defined in the DTI segment. + */ + public final static String[] TILING_TYPE = + { + "Simple", // 0 + "Pyramidal", // 1 + "Composite" // 2 + }; + + /* Values for precision in DQT segment. + */ + public final static String[] DQT_PRECISION = + { + "8-bit", // 0 + "16-bit", // 1 + }; + + + /* Values for precision in DAC segment. + */ + public final static String[] DAC_CLASS = + { + "DC table or iossiess table", // 0 + "AC table", // 1 + }; +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/QuantizationTable.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/QuantizationTable.java new file mode 100644 index 00000000..39abca52 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/QuantizationTable.java @@ -0,0 +1,61 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Encapsulation of a quantization table entry for a JPEG image. + * + * @author Gary McGath + * + */ +public class QuantizationTable { + + private int _precision; + private int _destIdentifier; + + + /** + * Constructor. + */ + public QuantizationTable(int precision, int destIdentifier) { + _precision = precision; + _destIdentifier = destIdentifier; + } + + + /** + * Returns a Property defining the quantization table + */ + public Property makeProperty (boolean raw) + { + Property[] parray = new Property[2]; + if (raw) { + parray[0] = new Property ("Precision", + PropertyType.INTEGER, + new Integer (_precision)); + } + else { + String prec = "Undefined"; + try { + prec = JpegStrings.DQT_PRECISION[_precision]; + } + catch (Exception e) {} + parray[0] = new Property ("Precision", + PropertyType.STRING, + prec); + } + parray[1] = new Property ("DestinationIdentifier", + PropertyType.INTEGER, + new Integer (_destIdentifier)); + return new Property ("QuantizationTable", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + parray); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/SRS.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/SRS.java new file mode 100644 index 00000000..14a627f9 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/SRS.java @@ -0,0 +1,62 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Encapsulation of an SRS (selectively refined scan) entry for a JPEG image. + * + * @author Gary McGath + * + */ +public class SRS { + + private int _vertOffset; + private int _horOffset; + private int _vertSize; + private int _horSize; + + + /** + * Constructor. + */ + public SRS(int vertOffset, int horOffset, int vertSize, int horSize) + { + _vertOffset = vertOffset; + _horOffset = horOffset; + _vertSize = vertSize; + _horSize = horSize; + } + + + + /** + * Returns a Property defining the SRS + */ + public Property makeProperty () + { + Property[] parray = new Property[4]; + parray[0] = new Property ("VerticalOffset", + PropertyType.INTEGER, + new Integer (_vertOffset)); + parray[1] = new Property ("HorizontalOffset", + PropertyType.INTEGER, + new Integer (_horOffset)); + parray[2] = new Property ("VerticalSize", + PropertyType.INTEGER, + new Integer (_vertSize)); + parray[3] = new Property ("HorizontalSize", + PropertyType.INTEGER, + new Integer (_horSize)); + return new Property ("SelectivelyRefinedScan", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + parray); + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/Spiff.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/Spiff.java new file mode 100644 index 00000000..bdd2c225 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/Spiff.java @@ -0,0 +1,102 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg; + +/** + * Static methods and data for SPIFF Jpeg files. + * + * @author Gary McGath + * + */ +public class Spiff { + + /* Definitions of SPIFF tags. */ + public final static int + EOD = 1, + XFER_CHARACTERISTICS = 2, + IMAGE_ORIENTATION = 3, + THUMBNAIL = 4, + IMAGE_TITLE = 5, + IMAGE_DESC = 6, + TIME_STAMP = 7, + VERSION_IDENT = 8, + CREATOR_ID = 9, + PROTECTION_INDICATOR = 0XA, + COPYRIGHT_INFO = 0X0C, + CONTACT_INFO = 0X0D, + TILE_INDEX = 0X0E, + SCAN_INDEX = 0X0F, + SETREF = 0X10; + + /* Color space to NISO mapping array. */ + private final static int[] nisoColor = { + 0, // 0 bilevel, white is 0 + 6, // 1 YCbCr (1) + -1, // 2 other + 6, // 3 YCbCr (2) + 6, // 4 YCbCr (3) + -1, // 5 reserved + -1, // 6 reserved + -1, // 7 reserved + 1, // 8 grayscale (black is 0) + -1, // 9 PhotoYCC + 2, // 10 RGB + -1, // 11 CMY + 5, // 12 CMYK + -1, // 13 YCCK + 8, // 14 CIELab + 1 // 15 bilevel, black is 0 + }; + + + /* Compression to NISO mapping array. When we don't have + * an exact match, call it JPEG (6). */ + private final static int[] nisoCompScheme = { + 1, // 0 uncompressed + 6, // 1 T.4, MH + 6, // 2 T.4, MR + 6, // 3 T.6, MMR + 32661, // 4 JBIG + 6 // 5 JPEG + }; + + /** + * Private constructor, to prevent instantiation + */ + private Spiff() { + } + + + /** Converts S value to NISO color space. Return -1 if there + * is no matching color space in NISO, or the S value is out + * of bounds. + */ + public static int colorSpaceToNiso (int s) + { + if (s < 0 || s > nisoColor.length) { + return -1; + } + else { + return nisoColor[s]; + } + } + + + /** Converts C value to NISO compression scheme. Return -1 if there + * is no matching color space in NISO, or the S value is out + * of bounds. + */ + public static int compressionTypeToNiso (int s) + { + if (s < 0 || s > nisoCompScheme.length) { + return -1; + } + else { + return nisoCompScheme[s]; + } + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/SpiffDir.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/SpiffDir.java new file mode 100644 index 00000000..f7bf14f3 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/SpiffDir.java @@ -0,0 +1,120 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg; + +import java.io.*; +import java.util.*; +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.JpegModule; + +/** + * This class represents a SPIFF directory and the tags defined under + * it. A SPIFF directory consists of one or more APP8 segments, and + * may define ancillary images. It is always contained within the + * primary image stream. + * + * @author Gary McGath + * + */ +public class SpiffDir { + + + private JpegModule _module; + + /* list of thumbnail properties */ + private List _thumbnails; + + /** + * + */ + public SpiffDir(JpegModule module) { + _module = module; + _thumbnails = new LinkedList (); + } + + /** + * Reads a directory entry, starting at the position after + * the APP8 marker and length. If the entry is for a thumbnail, create + * a Property for that thumbnail and add it to the thumbnail + * list. Other tags provide interesting information, some of + * which should go into properties, but for the moment we + * just handle the thumbnail and ignore other tags. + * + * An APP8 segment which is in a SPIFF file, and isn't the + * first APP8 segment (file header), is presumed to be a + * directory entry. These directory entries are a little + * inconvenient, because they can contain offsets to data in + * what we otherwise handle as a stream format. The offsets + * can be either to data within the block, or to faraway + * indirect data blocks. For the present version, we ignore + * offset data, which seems to be used only for the actual + * image bits (e.g., TNDATA). + */ + public void readDirEntry (DataInputStream dstream, int length) + throws IOException + { + int tag = (int) _module.readUnsignedInt (dstream); + switch (tag) { + case Spiff.THUMBNAIL: + readThumbnail (dstream, length); + break; + + default: + _module.skipBytes (dstream, length - 6, _module); + break; + } + } + + + /** + * Appends any thumbnail properties that have been collected to + * the provided list. + */ + public void appendThumbnailProps (List imageList) + { + imageList.addAll (_thumbnails); + } + + + /* Reads a thumbnail entry. A Property is created and added to + * the list of thumbnails. + */ + private void readThumbnail (DataInputStream dstream, int length) + throws IOException + { + NisoImageMetadata niso = new NisoImageMetadata(); + _module.skipBytes (dstream, 4, _module); // tndata + int height = _module.readUnsignedShort (dstream); + int width = _module.readUnsignedShort (dstream); + int tns = ModuleBase.readUnsignedByte (dstream, _module); + int tnbps = ModuleBase.readUnsignedByte (dstream, _module); + int tnc = ModuleBase.readUnsignedByte (dstream, _module); + _module.skipBytes (dstream, length - 13, _module); + + // Fill in NISO data + niso.setMimeType("image/jpeg"); + niso.setByteOrder ("big-endian"); + niso.setBitsPerSample (new int[] {tnbps} ); + int cs = Spiff.colorSpaceToNiso(tns); + if (cs >= 0) { + niso.setColorSpace (cs); + } + int comp = Spiff.compressionTypeToNiso (tnc); + if (comp >= 0) { + niso.setCompressionScheme(comp); + } + Property nisoProp = new Property ("NisoImageMetadata", + PropertyType.NISOIMAGEMETADATA, niso); + List propList = new LinkedList (); + propList.add (nisoProp); + Property imageProp = new Property ("ThumbImage", + PropertyType.PROPERTY, + PropertyArity.LIST, + propList); + _thumbnails.add (imageProp); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/Tiling.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/Tiling.java new file mode 100644 index 00000000..1ac80aa9 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/Tiling.java @@ -0,0 +1,151 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg; + +import java.util.*; +import edu.harvard.hul.ois.jhove.*; + +/** + * Encapsulation of the tiling information for a JPEG image. + * + * @author Gary McGath + * + */ +public class Tiling { + + /* List of individual tile descriptions */ + private List tileList; + + private int _tilingType; + private int _vertScale; + private int _horScale; + private long _refGridHeight; + private long _refGridWidth; + + + /** + * Constructor. + */ + public Tiling() { + tileList = new LinkedList (); + } + + /** + * Adds a tile to the list. + */ + public void addTile (long vertScale, + long horScale, + long vertOffset, + long horOffset) + { + // Represent the tile as an array of 4 longs + long[] tile = new long[4]; + tile[0] = vertScale; + tile[1] = horScale; + tile[2] = vertOffset; + tile[3] = horOffset; + + tileList.add (tile); + } + + /** + * Returns a property listing all the tiles. + */ + public Property buildTileListProp () + { + List tpList = new LinkedList (); + ListIterator iter = tileList.listIterator (); + while (iter.hasNext ()) { + long[] tile = iter.next (); + Property[] tProp = new Property[4]; + tProp[0] = new Property ("VerticalScale", + PropertyType.LONG, + new Long (tile[0])); + tProp[1] = new Property ("HorizontalScale", + PropertyType.LONG, + new Long (tile[1])); + tProp[2] = new Property ("VerticalOffsret", + PropertyType.LONG, + new Long (tile[2])); + tProp[3] = new Property ("HorizontalOffset", + PropertyType.LONG, + new Long (tile[3])); + tpList.add (new Property ("Tile", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + tProp)); + } + return new Property ("Tiles", + PropertyType.PROPERTY, + PropertyArity.LIST, + tpList); + } + + + + public void setTilingType (int tilingType) + { + _tilingType = tilingType; + } + + + public void setVertScale (int vertScale) + { + _vertScale = vertScale; + } + + + public void setHorScale (int horScale) + { + _horScale = horScale; + } + + + public void setRefGridHeight (long refGridHeight) + { + _refGridHeight = refGridHeight; + } + + + public void setRefGridWidth (long refGridWidth) + { + _refGridWidth = refGridWidth; + } + + + public int getTilingType () + { + return _tilingType; + } + + + public int getVertScale () + { + return _vertScale; + } + + + public int getHorScale () + { + return _horScale; + } + + + public long getRefGridHeight () + { + return _refGridHeight; + } + + + public long getRefGridWidth () + { + return _refGridWidth; + } + + + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/build.xml b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/build.xml new file mode 100644 index 00000000..166a5004 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/build.xml @@ -0,0 +1,32 @@ + + JPEG module build file + Jhove - JSTOR/Harvard Object Validation Environment + Version 1.0 2004-02-03 + Copyright 2004 by JSTOR and the President and Fellows of Harvard College + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/package.html b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/package.html new file mode 100644 index 00000000..0250db38 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg/package.html @@ -0,0 +1,24 @@ + + + + + + + +Contains supporting classes for the JPEG-HUL module. +
+ + + + + +
+ + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/AssociationBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/AssociationBox.java new file mode 100644 index 00000000..41ec4f03 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/AssociationBox.java @@ -0,0 +1,113 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import edu.harvard.hul.ois.jhove.*; +import java.io.*; +import java.util.*; + +/** + * Association Box. + * + * An AssociationBox can occur in pretty much any superbox + * or at the top level. It simply establishes an association + * between boxes. + * + * See ISO/IEC FCD15444-2: 2000, L.9.11 + * + * @author Gary McGath + * + */ +public class AssociationBox extends JP2Box { + + + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + * or TopLevelBoxHolder + */ + public AssociationBox(RandomAccessFile raf, BoxHolder parent) { + super(raf, parent); + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + initBytesRead (); + hasBoxes = true; + int sizeLeft = (int) _boxHeader.getDataLength (); + + // Label and Number List boxes are given as examples, but + // there is actually no restriction; an Association Box + // can associate any arbitrary collection of boxes. + // In order to avoid doubling (squaring?) the complexity + // of the Module, only Image, Number List, Association and XML + // boxes are reported in detail. + + JP2Box box = null; + List boxProps = new LinkedList (); + while (hasNext ()) { + box = (JP2Box) next (); + if (box == null) { + break; + } + if (!box.readBox ()) { + return false; + } + Property sdProp = box.selfDescProperty (); + if (sdProp != null) { + boxProps.add (sdProp); + } + } + // Hierarchically add any association properties to the + // property list. + if (!associations.isEmpty ()) { + boxProps.add (new Property ("Associations", + PropertyType.PROPERTY, + PropertyArity.LIST, + associations)); + } + Property assocProp = new Property + ("Association", + PropertyType.PROPERTY, + PropertyArity.LIST, + boxProps); + if (_parentBox != null) { + _parentBox.addAssociation (assocProp); + } + else { + _module.addAssociationProp (assocProp); + } + finalizeBytesRead (); + return true; + } + + /** Returns a Property which describes the Box, for use + * by Association boxes and perhaps others. + * An Association box can recursively contain other + * Association boxes. Since an Association box adds + * Association properties to its ancestors, This just + * returns null to avoid duplicate reporting. + */ + protected Property selfDescProperty () { + return null; + } + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "Association Box"; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/BPCCBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/BPCCBox.java new file mode 100644 index 00000000..b251d1e3 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/BPCCBox.java @@ -0,0 +1,63 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; +import edu.harvard.hul.ois.jhove.*; + +/** + * Bits Per Component box. + * See I.5.3.2 in ISO/IEC 15444-1:2000 + * + * @author Gary McGath + * + */ +public class BPCCBox extends JP2Box { + + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + */ + public BPCCBox(RandomAccessFile raf, BoxHolder parent) { + super(raf, parent); + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + if (!(_parentBox instanceof JP2HeaderBox || + _parentBox instanceof CodestreamHeaderBox)) { + wrongBoxContext(); + return false; + } + initBytesRead (); + int len = (int) _boxHeader.getDataLength (); + int[] bits = new int[len]; + for (int i = 0; i < len; i++) { + bits[i] = ModuleBase.readUnsignedByte (_dstrm, _module); + } + NisoImageMetadata niso; + niso = _module.getCurrentNiso (); + niso.setBitsPerSample (bits); + + finalizeBytesRead (); + return true; + } + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "Bits Per Component Box"; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/BinaryFilterBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/BinaryFilterBox.java new file mode 100644 index 00000000..7d66fc5e --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/BinaryFilterBox.java @@ -0,0 +1,141 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; +import java.util.zip.InflaterInputStream; +import edu.harvard.hul.ois.jhove.*; + +/** + * Binary Filter box (JPX). + * See ISO/IEC FCD15444-2: 2000, L.9.14 + * + * A Binary Filter Box can subsume any number of + * other boxes, which will look to the module as if they + * simply replace this box. BoxHolder makes a special case + * of BinaryFilterBoxes, calling the getBoxStream method to + * extract the subsumed boxes. + * + * Only Deflate coding, not DES, is supported. + * + * It is assumed that a BinaryFilterBox is never + * encoded inside another BinaryFilterBox. + * + * This is untested code, due to lack of sample files; + * please report any bugs found to HUL/OIS. + * + * @author Gary McGath + * + */ +public class BinaryFilterBox extends JP2Box { + + private final static int[] gzipuuid = + { 0XEC, 0X34, 0X0B, 0X04, 0X74, 0XC5, 0X11, 0XD4, + 0XA7, 0X29, 0X87, 0X9E, 0XA3, 0X54, 0X8F, 0X0E }; + + private DataInputStream boxStream; + private JP2Box _realParent; + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + * or TopLevelBoxHolder + */ + public BinaryFilterBox(RandomAccessFile raf, JP2Box parent) { + super(raf, parent); + _realParent = parent; + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + // Compare the filter type with the GZIP type. + // If it's anything else, just report a property + // and ignore the contents. + byte[] uuidbuf = new byte[16]; + ModuleBase.readByteBuf (_dstrm, uuidbuf, _module); + boolean isGzip = true; + for (int i = 0; i < 16; i++) { + if ((int) uuidbuf[i] != gzipuuid[i]) { + isGzip = false; + } + } + + // Accumulate all binary filter UUIDs into a property. + _module.addBinaryFilterProp (new Property ("BinaryFilter", + PropertyType.BYTE, + PropertyArity.ARRAY, + uuidbuf)); + if (isGzip) { + // report that we've left information unprocessed + _repInfo.setMessage(new InfoMessage + ("Binary Filter Box of type other than Gzip, contents not processed", + _module.getFilePos ())); + } + else { + // We use a CountedInputStream, which will report an + // EOF after streamLimit bytes. + // The caller is responsible for making sure that + // the underlying stream doesn't get mixed up with this + // stream for counting purposes. + // We have to put a DataInputStream on top of the + // InflaterInputStream, which means there are two + // DataInputStreams in the stream stack. Ugly, but + // should still work. + int streamLimit = (int) (_boxHeader.getLength () - 16); + boxStream = new DataInputStream + (new InflaterInputStream + (new CountedInputStream (_dstrm, streamLimit))); + } + + // We report _bytesRead as the total number of bytes in the + // box, including the stream which hasn't actually been read + // yet, because that makes things easier for the caller to + // keep things counted. + return true; + } + + public Object next () + { + BoxHeader hdr = new BoxHeader (_module, boxStream); + try { + hdr.readHeader (); + JP2Box box = JP2Box.boxMaker (hdr.getType (), _realParent); + box.setModule(_module); + box.setRepInfo(_repInfo); + box.setRandomAccessFile(_raf); + box.setDataInputStream(boxStream); + return box; + } + catch (IOException e) { + // Will come here when the BoxHeader reaches an EOF + return null; + } + } + + + /** returns the InputStream which will provide the decompressed + * boxes subsumed in this Box. + */ + public DataInputStream getBoxStream () + { + return boxStream; + } + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "Binary Filter Box"; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/BoxHeader.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/BoxHeader.java new file mode 100644 index 00000000..0db48fdc --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/BoxHeader.java @@ -0,0 +1,99 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.Jpeg2000Module; +import java.io.*; + +/** + * Encapsulation of a JPEG 2000 box header. + * + * @author Gary McGath + * + */ +public class BoxHeader { + + private long _length; + private String _type; + private Jpeg2000Module _module; + private DataInputStream _dstream; + private long _headerLength; + + /** + * Constructor. + * + * @param mod The Module which uses this object + * @param dstrm The DataInputStream reading data for the Module + */ + public BoxHeader (Jpeg2000Module mod, DataInputStream dstrm) + { + _module = mod; + _dstream = dstrm; + } + + /** + * Reads 8 bytes from the beginning of the box and parses + * out the box length and type. + */ + public void readHeader () throws IOException + { + _length = ModuleBase.readUnsignedInt(_dstream, true, _module); + _type = _module.read4Chars (_dstream); + + // If the length field is 1, there is an 8-byte extended + // length field. + if (_length == 1) { + _length = ModuleBase.readSignedLong(_dstream, true, _module); + _headerLength = 16; + } + else { + _headerLength = 8; + } + } + + + /** + * Returns the box length, which includes the length and + * type fields. If the value returned is 0, the length + * of the box is all the remaining data to the end of the file. + */ + public long getLength () + { + return _length; + } + + + /** + * Returns the length of the header. + * This number is equal to the number + * of bytes that have been read by readHeader(). + */ + public long getHeaderLength () + { + return _headerLength; + } + + /** + * Returns the number of bytes in the Box, not including + * the header. This is equivalent to + * getLength() - getHeaderLength(). + * If getLength() would return 0, this value is + * meaningless. + */ + public long getDataLength () + { + return _length - _headerLength; + } + + /** + * Returns the box type. + */ + public String getType () + { + return _type; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/BoxHolder.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/BoxHolder.java new file mode 100644 index 00000000..7d748193 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/BoxHolder.java @@ -0,0 +1,196 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import edu.harvard.hul.ois.jhove.module.Jpeg2000Module; +import edu.harvard.hul.ois.jhove.*; +import java.io.*; +import java.util.*; + +/** + * A BoxHolder is a container for JPEG 2000 boxes. + * + * @author Gary McGath + * + */ +public class BoxHolder implements Iterator { + + protected Jpeg2000Module _module; + protected JP2Box _parentBox; + protected RandomAccessFile _raf; + protected DataInputStream _dstrm; + protected BoxHeader _boxHeader; + protected RepInfo _repInfo; + protected long bytesLeft; + protected long filePos; + protected boolean hasBoxes; + private BinaryFilterBox binaryFilterBox; + + /** + * Constructor. + */ + public BoxHolder(RandomAccessFile raf) { + hasBoxes = false; // subclass which is a superbox should override + _raf = raf; + binaryFilterBox = null; + try { + filePos = raf.getFilePointer (); + } + catch (IOException e) {} + + //makeInputStream (); + } + + + + /** Returns the file position. + * In practice, this means returning the beginning of the Box. */ + protected long getFilePos () + { + // ghaaaaaa ... Maybe the best I can do is report + // the start of the box in the file. the module's + // file position is useless. Of course, for a Binary + // Filter box, even getting the start of the box in + // the file will be tricky. + return filePos; + } + + + /** + * Checks if any more subboxes are available. + * This class doesn't fully conform to the Iterator interface, + * as there are some cases where the lack of more boxes + * won't be detected till an EOF is encounterd. So callers + * should call hasNext to avoid reading overruns, and then + * test the value returned by next for nullity. + */ + + public boolean hasNext () + { + return (hasBoxes && bytesLeft >= 8); + } + + + /* This should return the next Box, if any. */ + public Object next () + { + if (!hasBoxes) { + return null; + } + + try { + BoxHeader subhdr; + JP2Box nextBox; + // If we've encountered a BinaryFilterBox, it feeds + // us boxes till it's exhausted. When it has no + // more boxes, we set it to null to indicate we + // resume reading ordinary boxes. + if (binaryFilterBox != null) { + if (binaryFilterBox.hasNext ()) { + nextBox = (JP2Box) binaryFilterBox.next (); + } + else { + binaryFilterBox = null; + // Fall through into normal reading + } + } + if (bytesLeft < 8) { + return null; + } + subhdr = new BoxHeader (_module, _dstrm); + subhdr.readHeader (); + bytesLeft -= subhdr.getLength (); + String hType = subhdr.getType (); + if ("bfil".equals (hType)) { + binaryFilterBox = new BinaryFilterBox + (_raf, (this instanceof JP2Box) ? (JP2Box) this : null); + // If I can make the following magic actually + // work correctly, then I'm starting to get somewhere. + + if (binaryFilterBox.hasNext ()) { + return binaryFilterBox.next (); + } + else { + // The "else" is a BinaryFilterBox with no content. + // This seems unlikely, but assume it's legal and + // fall through to the next box. + subhdr.readHeader (); + hType = subhdr.getType (); + } + } + if ("cref".equals (hType)) { + // A Cross Reference Box is replaced by another box, + // which is found in the DataInputStream it produces. + CrossRefBox xrefBox = new CrossRefBox (_raf, + (this instanceof JP2Box) ? (JP2Box) this : null); + if (!xrefBox.readBox ()) { + return null; + } + BoxHeader xrefhdr = + new BoxHeader (_module, xrefBox.getCrossRefStream()); + xrefhdr.readHeader (); + nextBox = JP2Box.boxMaker (xrefhdr.getType (), + (this instanceof JP2Box) ? (JP2Box) this : null); + return nextBox; + } + else { + nextBox = JP2Box.boxMaker(hType, this); + + nextBox.setModule(_module); + nextBox.setRepInfo(_repInfo); + nextBox.setRandomAccessFile(_raf); + nextBox.setDataInputStream(_dstrm); + nextBox.setBoxHeader (subhdr); + return nextBox; + } + } + catch (IOException e) { + // Probably I should be reporting an error here + return null; + } + } + + /** Always throws UnsupportedOperationException. */ + public void remove () throws UnsupportedOperationException + { + throw new UnsupportedOperationException(); + } + + + /** Utility error reporting function for a subbox overrunning + * its superbox. + * Sets the RepInfo's wellFormed flag to false. + */ + protected void superboxOverrun () + { + _repInfo.setMessage (new ErrorMessage + ("Size of contained Box overruns " + getSelfPropName (), + _module.getFilePos ())); + _repInfo.setWellFormed (false); + } + + + /** Utility error reporting function for a subbox underrunning + * its superbox. + * Sets the RepInfo's wellFormed flag to false. + */ + protected void superboxUnderrun () + { + _repInfo.setMessage (new ErrorMessage + ("Size of contained Boxes underruns " + getSelfPropName (), + _module.getFilePos ())); + _repInfo.setWellFormed (false); + } + + + + /** Returns the name of the BoxHolder. All subclasses should + * override this. */ + protected String getSelfPropName () + { + return ""; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/COCMarkerSegment.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/COCMarkerSegment.java new file mode 100644 index 00000000..029022e8 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/COCMarkerSegment.java @@ -0,0 +1,123 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; +import java.util.*; +import edu.harvard.hul.ois.jhove.*; +//import edu.harvard.hul.ois.jhove.module.Jpeg2000Module; + +/** + * Class for the COC (Coding style component) marker segment. + * May occur in the main or the tile part header. In the + * main header it overrides the COD for the specified + * component. In the tile part header it overrides the + * COD for the component in the tile part. + * + * @author Gary McGath + * + */ +public class COCMarkerSegment extends MarkerSegment { + + /** + * Constructor. + */ + public COCMarkerSegment() { + super(); + } + + /** Process the marker segment. The DataInputStream + * will be at the point of having read the marker code. The + * process method must consume exactly the number + * of bytes remaining in the marker segment. + * + * @param bytesToEat The number of bytes that must be consumed. + * If it is 0 for a MarkerSegment, the + * number of bytes to consume is unknown. + * + * @return true if segment is well-formed, + * false otherwise. + */ + protected boolean process(int bytesToEat) throws IOException { + int compIdxBytes = nCompBytes(); + if (compIdxBytes == 0) { + // COC found before SIZ + _repInfo.setMessage (new ErrorMessage + ("COC marker segment at wrong position in codestream")); + return false; + } + int compIdx; + // size of Ccoc field depends on number of components + if (compIdxBytes < 257) { + compIdx = ModuleBase.readUnsignedByte (_dstream, _module); + } + else{ + compIdx = _module.readUnsignedShort (_dstream); + } + int codeStyle = ModuleBase.readUnsignedByte (_dstream, _module); + + // The SPcoc parameters + int nDecomp = ModuleBase.readUnsignedByte (_dstream, _module); + int codeBlockWid = ModuleBase.readUnsignedByte (_dstream, _module); + int codeBlockHt = ModuleBase.readUnsignedByte (_dstream, _module); + int codeBlockStyle = ModuleBase.readUnsignedByte (_dstream, _module); + int xform = ModuleBase.readUnsignedByte (_dstream, _module); + int precSize[] = null; + if ((codeStyle & 1) != 0) { + // The first parameter (8 bits) corresponds to the + // N(L)LL subband. Each successive parameter corresponds + // to each successive resolution level in order. + // I think that means the number of bytes equals the + // number of resolution levels + 1 -- but where do I get + // the number of resolution levels? Based on the (highly + // confusing) information about the marker segment length, + // that must be the same as the number of decomposition + // levels. + precSize = new int[nDecomp + 1]; + for (int i = 0; i < nDecomp + 1; i++) { + precSize[i] = ModuleBase.readUnsignedByte (_dstream, _module); + } + } + + // Build a property and attach it to the appropriate component. This + // may be a component of the codestream or of a tile part. The + // number of components is apparently established only by the SIZ + // marker segment and never changes for tiles or tile parts. + MainOrTile cs = getMainOrTile (); + List propList = new ArrayList (10); + propList.add (new Property ("CodingStyle", + PropertyType.INTEGER, + new Integer (codeStyle))); + propList.add (new Property ("NumberDecompositionLevels", + PropertyType.INTEGER, + new Integer (nDecomp))); + propList.add (new Property ("CodeBlockWidth", + PropertyType.INTEGER, + new Integer (codeBlockWid))); + propList.add (new Property ("CodeBlockHeight", + PropertyType.INTEGER, + new Integer (codeBlockHt))); + propList.add (new Property ("CodeBlockStyle", + PropertyType.INTEGER, + new Integer (codeBlockStyle))); + propList.add (new Property ("Transformation", + PropertyType.INTEGER, + new Integer (xform))); + propList.add (new Property ("PrecinctSize", + PropertyType.INTEGER, + PropertyArity.ARRAY, + precSize)); + cs.setCompProperty (compIdx, + new Property ("COC", + PropertyType.PROPERTY, + PropertyArity.LIST, + propList)); + + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CODMarkerSegment.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CODMarkerSegment.java new file mode 100644 index 00000000..a8cd0138 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CODMarkerSegment.java @@ -0,0 +1,117 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; +import java.util.*; +import edu.harvard.hul.ois.jhove.*; +//import edu.harvard.hul.ois.jhove.module.Jpeg2000Module; + +/** + * Class for the COD (coding style default) marker segment. + * This comes either in the main header or + * after an SOT and describes the functions + * used to code the entire tile. + * + * @author Gary McGath + * + */ +public class CODMarkerSegment extends MarkerSegment { + + public CODMarkerSegment () + { + } + + + + + /** + * Processes the marker segment. The DataInputStream + * will be at the point of having read the marker code. The + * process method must consume exactly the number + * of bytes remaining in the marker segment. + * + * @param bytesToEat The number of bytes that must be consumed. + * If it is 0 for a MarkerSegment, the + * number of bytes to consume is unknown. + */ + protected boolean process (int bytesToEat) throws IOException + { + int codeStyle = ModuleBase.readUnsignedByte (_dstream, _module); + + // The SGcod parameter, 32 bits + int progOrder = ModuleBase.readUnsignedByte (_dstream, _module); + int nLayers = _module.readUnsignedShort (_dstream); + int mcTrans = ModuleBase.readUnsignedByte (_dstream, _module); + + // The SPcod parameters + int nDecomp = ModuleBase.readUnsignedByte (_dstream, _module); + int codeBlockWid = ModuleBase.readUnsignedByte (_dstream, _module); + int codeBlockHt = ModuleBase.readUnsignedByte (_dstream, _module); + int codeBlockStyle = ModuleBase.readUnsignedByte (_dstream, _module); + int xform = ModuleBase.readUnsignedByte (_dstream, _module); + int precSize[] = null; + if ((codeStyle & 1) != 0) { + // The first parameter (8 bits) corresponds to the + // N(L)LL subband. Each successive parameter corresponds + // to each successive resolution level in order. + // I think that means the number of bytes equals the + // number of resolution levels + 1 -- but where do I get + // the number of resolution levels? Based on the (highly + // confusing) information about the marker segment length, + // that must be the same as the number of decomposition + // levels. + precSize = new int[nDecomp + 1]; + for (int i = 0; i < nDecomp + 1; i++) { + precSize[i] = ModuleBase.readUnsignedByte (_dstream, _module); + } + } + MainOrTile cs = getMainOrTile (); + + // Set values for the tile or codestream + List propList = new ArrayList (12); + propList.add (new Property ("CodingStyle", + PropertyType.INTEGER, + new Integer (codeStyle))); + propList.add (new Property ("ProgressionOrder", + PropertyType.INTEGER, + new Integer (progOrder))); + propList.add (new Property ("NumberOfLayers", + PropertyType.INTEGER, + new Integer (nLayers))); + propList.add (new Property ("MultipleComponentTransformation", + PropertyType.INTEGER, + new Integer (mcTrans))); + propList.add (new Property ("NumberDecompositionLevels", + PropertyType.INTEGER, + new Integer (nDecomp))); + propList.add (new Property ("CodeBlockWidth", + PropertyType.INTEGER, + new Integer (codeBlockWid))); + propList.add (new Property ("CodeBlockHeight", + PropertyType.INTEGER, + new Integer (codeBlockHt))); + propList.add (new Property ("CodeBlockStyle", + PropertyType.INTEGER, + new Integer (codeBlockStyle))); + propList.add (new Property ("Transformation", + PropertyType.INTEGER, + new Integer (xform))); + if (precSize != null) { + propList.add (new Property ("PrecinctSize", + PropertyType.INTEGER, + PropertyArity.ARRAY, + precSize)); + } + cs.setCODProperty (new Property ("CodingStyleDefault", + PropertyType.PROPERTY, + PropertyArity.LIST, + propList)); + + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CRGMarkerSegment.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CRGMarkerSegment.java new file mode 100644 index 00000000..1bf7b58c --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CRGMarkerSegment.java @@ -0,0 +1,73 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import edu.harvard.hul.ois.jhove.*; +import java.io.*; + +/** + * + * Class for the CRG (component registration) + * marker segment. + * + * @author Gary McGath + * + */ +public class CRGMarkerSegment extends MarkerSegment { + + /** + * Constructor. + */ + public CRGMarkerSegment() { + super(); + } + + + /** + * Processes the marker segment. The DataInputStream + * will be at the point of having read the marker code. The + * process method must consume exactly the number + * of bytes remaining in the marker segment. + * + * @param bytesToEat The number of bytes that must be consumed. + * If it is 0 for a MarkerSegment, the + * number of bytes to consume is unknown. + */ + protected boolean process(int bytesToEat) throws IOException + { + if (_ccs.getCurTile () != null) { + _repInfo.setMessage (new ErrorMessage + ("CRG header allowed only in main header of codestream")); + return false; + } + int ncomps = _cs.getNumComponents (); + if (ncomps * 4 != bytesToEat) { + _repInfo.setMessage (new ErrorMessage + ("CRG marker segment has incorrect length")); + } + int[] horOffsets = new int[ncomps]; + int[] vertOffsets = new int[ncomps]; + for (int i = 0; i < ncomps; i++) { + horOffsets[i] = _module.readUnsignedShort (_dstream); + vertOffsets[i] = _module.readUnsignedShort (_dstream); + } + Property[] props = new Property[2]; + props[0] = new Property ("HorizontalOffsets", + PropertyType.INTEGER, + PropertyArity.ARRAY, + horOffsets); + props[1] = new Property ("VerticalOffsets", + PropertyType.INTEGER, + PropertyArity.ARRAY, + vertOffsets); + _cs.setCRGProperty (new Property ("ComponentRegistration", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + props)); + + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CaptureResolutionBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CaptureResolutionBox.java new file mode 100644 index 00000000..54fbad52 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CaptureResolutionBox.java @@ -0,0 +1,86 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; +import edu.harvard.hul.ois.jhove.*; + +/** + * Capture Resolution Box. + * See I.5.3.7.1 in ISO/IEC 15444-1:2000 + * + * @author Gary McGath + * + */ +public class CaptureResolutionBox extends JP2Box { + + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + */ + public CaptureResolutionBox (RandomAccessFile raf, BoxHolder parent) + { + super (raf, parent); + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + if (!(_parentBox instanceof ResolutionBox)) { + wrongBoxContext (); + return false; + } + initBytesRead (); + ResolutionBox resBox = (ResolutionBox) _parentBox; + + // Vertical Capture grid resolution num & denom + int vrcNum = _module.readUnsignedShort (_dstrm); + int vrcDenom = _module.readUnsignedShort (_dstrm); + + // Horizontal Capture grid resolution num & denom + int hrcNum = _module.readUnsignedShort (_dstrm); + int hrcDenom = _module.readUnsignedShort (_dstrm); + + // Vertical and Horizontal capture grid exponents + int vrcExp = ModuleBase.readUnsignedByte (_dstrm, _module); + int hrcExp = ModuleBase.readUnsignedByte (_dstrm, _module); + + // We need to set resolution in NisoImageMetadata + // as a Rational. It seems unlikely that negative + // exponents will be used (signifying resolutions + // less than 1 dpi), so we figure the exponent into + // the numerator. Also, this resolution is in + // dots per meter, which isn't a NISO standard unit, + // so we multiply the denominator by 100 to give + // units per centimeter. + Rational vrc = new Rational + ((int) (vrcNum * Math.pow (10, vrcExp)), + vrcDenom * 100); + Rational hrc = new Rational + ((int) (hrcNum * Math.pow (10, hrcExp)), + hrcDenom * 100); + NisoImageMetadata niso = _module.getCurrentNiso (); + niso.setYSamplingFrequency (vrc); + niso.setXSamplingFrequency (hrc); + niso.setSamplingFrequencyUnit (3); + finalizeBytesRead (); + return true; + } + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "Capture Resolution Box"; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ChannelDefBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ChannelDefBox.java new file mode 100644 index 00000000..2ed61c32 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ChannelDefBox.java @@ -0,0 +1,98 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; +import edu.harvard.hul.ois.jhove.*; + +/** + * Channel Definition Box. + * See I.5.3.6 in ISO/IEC 15444-1:2000 + * and ISO/IEC FCD15444-2: 2000, L.9.4.5 + * + * @author Gary McGath + * + */ +public class ChannelDefBox extends JP2Box { + + + /** + * Constructor with superbox. + */ + public ChannelDefBox (RandomAccessFile raf, BoxHolder parent) + { + super (raf, parent); + } + + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + if (!(_parentBox instanceof JP2HeaderBox || + _parentBox instanceof CodestreamHeaderBox)) { + wrongBoxContext(); + return false; + } + initBytesRead (); + int len = (int) _boxHeader.getDataLength (); + + int nchan = _module.readUnsignedShort (_dstrm); + len -= 2; + Property[] chans = new Property[nchan]; + for (int i = 0; i < nchan; i++) { + Property[] cprop = new Property[3]; + int cidx = _module.readUnsignedShort (_dstrm); + cprop[0] = new Property ("ChannelIndex", + PropertyType.INTEGER, + new Integer (cidx)); + int typ = _module.readUnsignedShort (_dstrm); + cprop[1] = _module.addIntegerProperty ("ChannelType", typ, + JP2Strings.ctypStr, + JP2Strings.ctypIdx); + int assoc = _module.readUnsignedShort (_dstrm); + len -= 6; + + // The interpretation of the assoc field depends + // on the color space, so we just report it as + // an integer. + cprop[2] = new Property ("ChannelAssociation", + PropertyType.INTEGER, + new Integer (assoc)); + chans[i] = new Property ("Channel", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + cprop); + } + + _module.skipBytes (_dstrm, (int) len, _module); + + Property prop = new Property ("ChannelDefinition", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + chans); + if (_parentBox instanceof JP2HeaderBox) { + _module.addProperty (prop); + } + else if (_parentBox instanceof ComposLayerHdrBox) { + ((ComposLayerHdrBox) _parentBox).addChannelDef (prop); + } + + finalizeBytesRead (); + return true; + } + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "Channel Definition Box"; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/Codestream.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/Codestream.java new file mode 100644 index 00000000..3f72fa6d --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/Codestream.java @@ -0,0 +1,259 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import edu.harvard.hul.ois.jhove.*; +import java.util.*; + +/** + * The information on a codestream, extracted from a Contiguous Codestream + * or Fragment Table, and the corresponding Codestream Header if any. + * @author Gary McGath + * + */ +public class Codestream extends MainOrTile { + + private NisoImageMetadata _niso; + + /* List of Tile objects associated with the codestream */ + private List _tiles; + + /* List of lengths (Long objects) found in PPM code segments */ + private List _ppmLengthList; + + /* Label property */ + private Property _labelProperty; + + /* Component mapping property */ + private Property _compMapProperty; + + /* Property generated by the SIZ marker segment */ + private Property _sizProperty; + + /* Property generated by the CRG marker segment */ + private Property _crgProperty; + + /* Property generated by the PaletteBox of a CodestreamHeader */ + private Property _paletteProperty; + + /* Property generated by the ROIBox of a CodestreamHeader */ + private Property _roiProperty; + + /* List of tile length properties */ + private List tileLengthList; + + public Codestream () + { + _precSize = null; + + _niso = new NisoImageMetadata (); + } + + + /** Builds a + * Property out of everything we've collected. */ + public Property makeProperty () + { + List propList = new ArrayList (10); + + if (_labelProperty != null) { + propList.add (_labelProperty); + } + if (_compMapProperty != null) { + propList.add (_compMapProperty); + } + if (_paletteProperty != null) { + propList.add (_paletteProperty); + } + if (_roiProperty != null) { + propList.add (_roiProperty); + } + if (_sizProperty != null) { + propList.add (_sizProperty); + } + if (_codProperty != null) { + propList.add (_codProperty); + } + if (_qcdProperty != null) { + propList.add (_qcdProperty); + } + if (_pocProperty != null) { + propList.add (_pocProperty); + } + if (tileLengthList != null && !tileLengthList.isEmpty ()) { + propList.add (new Property ("TileLengths", + PropertyType.PROPERTY, + PropertyArity.LIST, + tileLengthList)); + } + if (_packetLengthList != null && !_packetLengthList.isEmpty ()) { + propList.add (new Property ("PacketLengths", + PropertyType.LONG, + PropertyArity.LIST, + _packetLengthList)); + } + if (_ppmLengthList != null && !_ppmLengthList.isEmpty ()) { + propList.add (new Property ("PackedPacketHeaderLengths", + PropertyType.LONG, + PropertyArity.LIST, + _ppmLengthList)); + } + if (_crgProperty != null) { + propList.add (_crgProperty ); + } + + propList.add (new Property ("NisoImageMetadata", + PropertyType.NISOIMAGEMETADATA, _niso)); + if (_tiles != null && !_tiles.isEmpty ()) { + List tpList = new ArrayList (_tiles.size ()); + ListIterator iter = _tiles.listIterator (); + while (iter.hasNext ()) { + Tile t = (Tile) iter.next (); + tpList.add (t.makeProperty ()); + } + propList.add (new Property ("Tiles", + PropertyType.PROPERTY, + PropertyArity.LIST, + tpList)); + } + if (!_comments.isEmpty ()) { + propList.add (new Property ("Comments", + PropertyType.PROPERTY, + PropertyArity.LIST, + _comments)); + } + if (_components != null) { + // The component array may be only partially populated, or + // not at all, so we reduce it to a List. + List clist = new ArrayList (_components.length); + for (int i = 0; i < _components.length; i++ ) { + Property c = _components[i]; + if (c != null) { + clist.add (c); + } + } + if (!clist.isEmpty ()) { + propList.add (new Property ("Components", + PropertyType.PROPERTY, + PropertyArity.LIST, + clist)); + } + } + return new Property ("Codestream", + PropertyType.PROPERTY, + PropertyArity.LIST, + propList); + } + + + /** Set the initial Niso values from a default Niso object. + * This doesn't attempt to be complete, but sets + * the values which we know could have been set from the + * JP2 header. */ + public void setDefaultNiso (NisoImageMetadata dNiso) + { + _niso.setByteOrder(dNiso.getByteOrder ()); + _niso.setMimeType (dNiso.getMimeType ()); + _niso.setBitsPerSample (dNiso.getBitsPerSample ()); + _niso.setImageLength (dNiso.getImageLength ()); + _niso.setImageWidth (dNiso.getImageWidth ()); + _niso.setSamplesPerPixel (dNiso.getSamplesPerPixel ()); + _niso.setCompressionScheme (dNiso.getCompressionScheme ()); + _niso.setYSamplingFrequency (dNiso.getYSamplingFrequency ()); + _niso.setXSamplingFrequency (dNiso.getXSamplingFrequency ()); + _niso.setSamplingFrequencyUnit (dNiso.getSamplingFrequencyUnit ()); + } + + /** Returns the images NisoImageMetadata. */ + public NisoImageMetadata getNiso () + { + return _niso; + } + + /** Assign a List of Tile objects to the tiles field */ + public void setTiles (List tiles) + { + _tiles = tiles; + } + + + /** Add a tile length property to the list of tile lengths. */ + public void addTileLength (Property p) + { + if (tileLengthList == null) { + tileLengthList = new LinkedList (); + } + tileLengthList.add (p); + } + + /** Add a PPM tilepart header length to the list of lengths */ + public void addPPMLength (long len) + { + _ppmLengthList.add (new Long (len)); + } + + + /** Sets the label property. */ + protected void setLabelProperty (Property p) + { + _labelProperty = p; + } + + /** Sets the component mapping property. */ + protected void setCompMapProperty (Property p) + { + _compMapProperty = p; + } + + /** Sets the palette property. */ + protected void setPaletteProperty (Property p) + { + _paletteProperty = p; + } + + /** Sets the ROI property. */ + protected void setROIProperty (Property p) + { + _roiProperty = p; + } + + /** Sets the SIZ property. */ + protected void setSIZProperty (Property p) + { + _sizProperty = p; + } + + /* Sets the CRG property. */ + protected void setCRGProperty (Property p) + { + _crgProperty = p; + } + + + + /** Set a property indexed by component. + * If a property for that component doesn't already + * exist, it is created. prop is then + * added to the property list of that property. */ + public void setCompProperty (int idx, Property prop) + { + if (_components != null && _components.length > idx) { + if (_components[idx] == null) { + // Have to create the component property + _components[idx] = new Property ("Component", + PropertyType.PROPERTY, + PropertyArity.LIST, + new LinkedList ()); + } + List pList = (List) _components[idx].getValue (); + pList.add (prop); + } + } + + + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CodestreamHeaderBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CodestreamHeaderBox.java new file mode 100644 index 00000000..a9607e2c --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CodestreamHeaderBox.java @@ -0,0 +1,124 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; +import edu.harvard.hul.ois.jhove.*; + +/** + * Codestream Header box. + * This is similar to a JP2HeaderBox, and has many of the same subboxes, + * but applies to a single codestream. + * + * See ISO/IEC FCD15444-2: 2000, L.9.3 + * + * @author Gary McGath + * + */ +public class CodestreamHeaderBox extends JP2Box { + + private Codestream curCodestream; + + + /** + * Constructor with superbox. + * + * @param parent Must be null or the TopLevelBoxHolder + */ + public CodestreamHeaderBox(RandomAccessFile raf, BoxHolder parent) { + super(raf, parent); + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + if (_parentBox != null) { + wrongBoxContext(); + return false; + } + initBytesRead (); + hasBoxes = true; + + int nch = _module.getNCodestreamHeaders () + 1; + _module.setNCodestreams (nch); + curCodestream = _module.getCodestream (nch); + + int sizeLeft = (int) _boxHeader.getDataLength() ; + BoxHeader subhdr = new BoxHeader (_module, _dstrm); + int state = 0; // state variable for checking progress of boxes + JP2Box box = null; + while (hasNext ()) { + box = (JP2Box) next (); + if (state == 0 && box instanceof LabelBox) { + state = 1; + if (!box.readBox ()) { + return false; + } + curCodestream.setLabelProperty (new Property ("Label", + PropertyType.STRING, + ((LabelBox) box).getLabel ())); + + // Read the next box + box = (JP2Box) next (); + if (box == null) { + break; + } + } + // First box, except perhaps for the label box, + // is the image header. + else if (state <= 1) { + if (box instanceof ImageHeaderBox) { + state = 2; + if (!box.readBox ()) { + return false; + } + } + else { + _repInfo.setMessage (new ErrorMessage + ("First box of Codestream Header must be image header", + _module.getFilePos ())); + _repInfo.setWellFormed (false); + return false; + } + } + else { + if (box instanceof BPCCBox || + box instanceof PaletteBox || + box instanceof ComponentMapBox || + box instanceof ROIBox) { + if (!box.readBox ()) { + return false; + } + } + else { + // Other boxes are legal; skip over them + box.skipBox (); + } + } + } + finalizeBytesRead (); + return true; + } + + /** Returns the associated Codestream object. */ + protected Codestream getCodestream () + { + return curCodestream; + } + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "Codestream Header Box"; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CodestreamRegBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CodestreamRegBox.java new file mode 100644 index 00000000..d5bdf0de --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CodestreamRegBox.java @@ -0,0 +1,93 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; +import edu.harvard.hul.ois.jhove.*; + +/** + * Codestream Registration Box. + * See ISO/IEC FCD15444-2: 2000, L.9.4.7 + * + * @author Gary McGath + * + */ +public class CodestreamRegBox extends JP2Box { + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + * (must be a ComposLayerHdrBox) + */ + public CodestreamRegBox(RandomAccessFile raf, BoxHolder parent) { + super(raf, parent); + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + if (!(_parentBox instanceof ComposLayerHdrBox)) { + wrongBoxContext(); + return false; + } + Property[] propArray = new Property[3]; + propArray[0] = new Property ("HorizontalGridSize", + PropertyType.INTEGER, + new Integer (_module.readUnsignedShort (_dstrm))); + propArray[1] = new Property ("VerticalGridSize", + PropertyType.INTEGER, + new Integer (_module.readUnsignedShort (_dstrm))); + int bytesLeft = (int) _boxHeader.getDataLength() - 4; + + // Each codestream entry is 6 bytes long (a short and 4 bytes) + int nStreams = bytesLeft / 6; + Property[] streamsProp = new Property[nStreams]; + for (int i = 0; i < nStreams; i++) { + // Build a property for one codestream + Property[] csProp = new Property[5]; + csProp[0] = new Property ("CodestreamNumber", + PropertyType.INTEGER, + new Integer (_module.readUnsignedShort (_dstrm))); + csProp[1] = new Property ("HorizontalResolution", + PropertyType.INTEGER, + new Integer (ModuleBase.readUnsignedByte (_dstrm, _module))); + csProp[2] = new Property ("VerticalResolution", + PropertyType.INTEGER, + new Integer (ModuleBase.readUnsignedByte (_dstrm, _module))); + csProp[3] = new Property ("HorizontalOffset", + PropertyType.INTEGER, + new Integer (ModuleBase.readUnsignedByte (_dstrm, _module))); + csProp[4] = new Property ("VerticalOffset", + PropertyType.INTEGER, + new Integer (ModuleBase.readUnsignedByte (_dstrm, _module))); + + streamsProp[i] = new Property ("Codestreams", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + csProp); + } + ((ComposLayerHdrBox) _parentBox).addCodestreamReg (new Property + ("CodestreamRegistration", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + streamsProp)); + return true; + } + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "Codestream Registration Box"; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ColorGroupBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ColorGroupBox.java new file mode 100644 index 00000000..ed1f2c1b --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ColorGroupBox.java @@ -0,0 +1,83 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import edu.harvard.hul.ois.jhove.*; +import java.io.*; + +/** + * Color Group Box. + * See ISO/IEC FCD15444-2: 2000, L.9.4.1 + * + * + * @author Gary McGath + * + */ +public class ColorGroupBox extends JP2Box { + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + * (must be a ComposLayerHdrBox) + */ + public ColorGroupBox(RandomAccessFile raf, BoxHolder parent) { + super(raf, parent); + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + // A Color Group box consists of 0 or more color specification + // boxes. It is allowed only in a Compositing Layer Header box. + if (!(_parentBox instanceof ComposLayerHdrBox)) { + wrongBoxContext(); + return false; + } + initBytesRead (); + int sizeLeft = (int) _boxHeader.getDataLength() ; + BoxHeader subhdr = new BoxHeader (_module, _dstrm); + int state = 0; // state variable for checking progress of boxes + JP2Box box = null; + while (hasNext ()) { + box = (JP2Box) next (); + if (box == null) { + break; + } + if (box instanceof ColorSpecBox) { + if (!box.readBox ()) { + return false; + } + } + else { + box.skipBox (); + } + } + + finalizeBytesRead (); + return false; + } + + /** Adds a color spec property to the parent Compositing Layer + * Header Box. */ + protected void addColorSpec (Property p) + { + ((ComposLayerHdrBox) _parentBox).addColorSpec (p); + } + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "Color Group Box"; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ColorSpecBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ColorSpecBox.java new file mode 100644 index 00000000..080f7eb8 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ColorSpecBox.java @@ -0,0 +1,174 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import edu.harvard.hul.ois.jhove.*; +import java.io.*; +import java.util.*; + +/** + * Color specification box. + * See I.5.3.3 in ISO/IEC 15444-1:2000 + * and ISO/IEC FCD15444-2: 2000, L.9.4.2 + * + * @author Gary McGath + * + */ +public class ColorSpecBox extends JP2Box { + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + * (must be JP2HeaderBox or ColorGroupBox) + */ + public ColorSpecBox (RandomAccessFile raf, BoxHolder parent) + { + super (raf, parent); + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + if (!(_parentBox instanceof JP2HeaderBox || + _parentBox instanceof ColorGroupBox)) { + wrongBoxContext(); + return false; + } + initBytesRead (); + int len = (int) _boxHeader.getDataLength (); + + List subProps = new ArrayList (2); + Property prop = new Property ("ColorSpec", + PropertyType.PROPERTY, + PropertyArity.LIST, + subProps); + int meth = ModuleBase.readUnsignedByte (_dstrm, _module); + if (meth > 2) { + _module.setJP2Compliant (false); // only 1-2 recognized by JP2 + } + subProps.add (_module.addIntegerProperty("Method", meth, + JP2Strings.methodStr)); + + // Precedence. Used in JPX only. + int prec = ModuleBase.readUnsignedByte (_dstrm, _module); + subProps.add (new Property ("Precedence", + PropertyType.INTEGER, + new Integer (prec))); + + // The approx field provides a litmus test for distinguishing + // a JP2 file from a JPX. JP2 may have only 0 for this value; + // JPX must have a non-zero value. + int approx = ModuleBase.readUnsignedByte (_dstrm, _module); + if (approx == 0) { + _module.setJPXCompliant (false); + subProps.add (new Property ("Approx", + PropertyType.INTEGER, + new Integer (0))); + } + else { + _module.setJP2Compliant (false); + subProps.add (_module.addIntegerProperty("Approx", approx, + JP2Strings.approxStr, + JP2Strings.approxIdx)); + } + + if (meth == 1) { + // with meth = 1, we have an enumerated colorspace + long enumCS = _module.readUnsignedInt (_dstrm); + _module.skipBytes (_dstrm, len - 7, _module); + Property p; + p = _module.addIntegerProperty ("EnumCS", (int) enumCS, + JP2Strings.enumCSStr); + subProps.add (p); + } + else if (meth == 2 ) { + // Code by Justin Littman incorporated here. + // With meth = 2, the profile must be either a Monochrome + // Input or a Three-Component Matrix-Based Input profile, + // as defined in ICC.1:1998-09. + //Read the ICC profile + //Skip the header (128 bytes) + _module.skipBytes(_dstrm, 128, _module); + //Tag count + long tagCount = _module.readUnsignedInt(_dstrm); + HashSet tagSignatureSet = new HashSet(); + for (int i=0; i < tagCount; i++) { + //Read the tag + tagSignatureSet.add(_module.read4Chars(_dstrm)); + //Skip the rest of the tag table entry + _module.skipBytes(_dstrm, 8, _module); + } + + //Check if Monochrome Input Profile + if (tagSignatureSet.contains("desc") && + tagSignatureSet.contains("kTRC") && + tagSignatureSet.contains("wtpt") && + tagSignatureSet.contains("cprt")) { + subProps.add (new Property ("RestrictedICCProfile", + PropertyType.STRING, + "Monochrome Input Profile")); + } + //Check if Three-Component Matrix-Based Input Profile + else if (tagSignatureSet.contains("desc") && + tagSignatureSet.contains("rXYZ") && + tagSignatureSet.contains("gXYZ") && + tagSignatureSet.contains("bXYZ") && + tagSignatureSet.contains("rTRC") && + tagSignatureSet.contains("gTRC") && + tagSignatureSet.contains("bTRC") && + tagSignatureSet.contains("wtpt") && + tagSignatureSet.contains("cprt")) { + subProps.add (new Property ("RestrictedICCProfile", + PropertyType.STRING, + "Three-Component Matrix-Based Input Profile")); + } + //Not a valid method 2 box + else { + //_module.setJP2Compliant (false); + _repInfo.setMessage(new ErrorMessage + ("Color spec box with method 2 has unrecognized ICC profile", + filePos)); + _repInfo.setValid(false); + } + } + else { + // We have an ICC profile, or else a method which isn't + // defined in the specification. This excludes it + // from the JP2 profile. + _module.setJP2Compliant (false); + } + // If it's in a JP2 Header, add to the default color specs. + // If it's in a Color Group Box, add to Compositing Layer + // properties. + if (_parentBox instanceof JP2HeaderBox) { + _module.addColorSpec (prop); + } + else if (_parentBox instanceof ColorGroupBox) { + ((ColorGroupBox) _parentBox).addColorSpec (prop); + } + // Skip any bytes we haven't read + if (_boxHeader.getLength () != 0) { + _module.skipBytes (_dstrm, + (int) (len - (_module.getFilePos () - startBytesRead)), _module); + } + finalizeBytesRead (); + _module.setColorSpecSeen (true); + return true; + } + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "Color Specification Box"; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CommentMarkerSegment.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CommentMarkerSegment.java new file mode 100644 index 00000000..721031ce --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CommentMarkerSegment.java @@ -0,0 +1,71 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.IOException; +import edu.harvard.hul.ois.jhove.*; + +/** + * Class for the COM (Comment) marker segment. + * This comes either in the main header or + * after an SOT. + * + * @author Gary McGath + * + */ +public class CommentMarkerSegment extends MarkerSegment { + + /** + * Constructor. + */ + public CommentMarkerSegment() { + super(); + } + + /** Process the marker segment. The DataInputStream + * will be at the point of having read the marker code. The + * process method must consume exactly the number + * of bytes remaining in the marker segment. + * + * @param bytesToEat The number of bytes that must be consumed. + * If it is 0 for a MarkerSegment, the + * number of bytes to consume is unknown. + * + * @return true if segment is well-formed, + * false otherwise. + */ + protected boolean process(int bytesToEat) throws IOException { + MainOrTile cs = getMainOrTile (); + int rcom = _module.readUnsignedShort (_dstream); + Property prop; + byte[] byteBuf = new byte[bytesToEat - 2]; + ModuleBase.readByteBuf (_dstream, byteBuf, _module); + switch (rcom) { + case 0: + // Binary comment + prop = new Property ("Comment", + PropertyType.BYTE, + PropertyArity.ARRAY, + byteBuf); + break; + + case 1: + // ISO Latin comment + prop = new Property ("Comment", + PropertyType.STRING, + new String (byteBuf)); + break; + + default: + _repInfo.setMessage( (new ErrorMessage + ("Unrecognized comment type"))); + return false; // other values are reserved + } + cs.addComment (prop); + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CompOptionsBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CompOptionsBox.java new file mode 100644 index 00000000..9a0dc242 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CompOptionsBox.java @@ -0,0 +1,57 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; + +/** + * Composition options box (JPX). + * + * See ISO/IEC FCD15444-2: 2000, L.9.10.1 + * + * @author Gary McGath + * + */ +public class CompOptionsBox extends JP2Box { + + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + */ + public CompOptionsBox(RandomAccessFile raf, BoxHolder parent) { + super(raf, parent); + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + if (!(_parentBox instanceof CompositionBox)) { + wrongBoxContext (); + return false; + } + initBytesRead (); + if (_boxHeader.getDataLength () != 10) { + wrongBoxSize (); + return false; + } + CompositionBox parent = (CompositionBox) _parentBox; + parent.setHeight (_module.readUnsignedInt (_dstrm)); + parent.setWidth (_module.readUnsignedInt (_dstrm)); + parent.setLoop (_module.readUnsignedShort (_dstrm)); + + finalizeBytesRead (); + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ComponentMapBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ComponentMapBox.java new file mode 100644 index 00000000..6efedb6a --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ComponentMapBox.java @@ -0,0 +1,98 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; +import edu.harvard.hul.ois.jhove.*; + +/** + * Component Mapping Box. + * See I.5.3.5 in ISO/IEC 15444-1:2000 + * + * @author Gary McGath + * + */ +public class ComponentMapBox extends JP2Box { + + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + */ + public ComponentMapBox (RandomAccessFile raf, BoxHolder parent) + { + super (raf, parent); + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + if (!(_parentBox instanceof JP2HeaderBox || + _parentBox instanceof CodestreamHeaderBox)) { + wrongBoxContext(); + return false; + } + initBytesRead (); + + int len = (int) _boxHeader.getDataLength (); + int ncomp = len / 4; + Property[] parray = new Property[ncomp]; + App app = _module.getApp (); + + // Build the array of properties for each component. + // Components potentially have lots of stuff attached + // to them, so probably I should define a Component + // class. Need to determine how the Component Mapping + // relates to component information in a codestream. + // Or maybe this doesn't have anything to do with it. + for (int i = 0; i < ncomp; i++) { + Property[] cprop = new Property[3]; + int index = _module.readUnsignedShort (_dstrm); + cprop[0] = new Property ("ComponentIndex", + PropertyType.INTEGER, + new Integer (index)); + int mtyp = ModuleBase.readUnsignedByte (_dstrm, _module); + cprop[1] = _module.addIntegerProperty ("MTyp", mtyp, + JP2Strings.mtypStr); + int pcol = ModuleBase.readUnsignedByte (_dstrm, _module); + cprop[2] = new Property ("PaletteComponent", + PropertyType.INTEGER, + new Integer (pcol)); + parray[i] = new Property ("Component", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + cprop); + } + + // put constructed property into the Module + Property cmProp = new Property ("ComponentMapping", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + parray); + if (_parentBox instanceof CodestreamHeaderBox) { + Codestream cs = ((CodestreamHeaderBox) _parentBox).getCodestream (); + cs.setCompMapProperty (cmProp); + } + else { + _module.addProperty (cmProp); + } + finalizeBytesRead (); + return true; + } + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "Component Mapping Box"; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ComposLayerHdrBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ComposLayerHdrBox.java new file mode 100644 index 00000000..6e10193d --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ComposLayerHdrBox.java @@ -0,0 +1,163 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; +import java.util.*; +import edu.harvard.hul.ois.jhove.*; + +/** + * Compositing Layer Header Box (JPX superbox). + * See ISO/IEC FCD15444-2: 2000, L.9.4 + * + * @author Gary McGath + * + */ +public class ComposLayerHdrBox extends JP2Box { + + private Property label; + private Property opacityProp; + private Property channelDefProp; + private Property codestreamRegProp; + private List colorSpecs; + + + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + */ + public ComposLayerHdrBox(RandomAccessFile raf, BoxHolder parent) { + super(raf, parent); + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + if (_parentBox != null) { + // Box must be at top level. + wrongBoxContext(); + return false; + } + initBytesRead (); + hasBoxes = true; + colorSpecs = new LinkedList (); + + // Unlike some other boxes, compositing layer boxes + // are numbered by their order in the file, starting + // with 0. A definite case of design by committee. + int sizeLeft = (int) _boxHeader.getDataLength() ; + //BoxHeader subhdr = new BoxHeader (_module, _dstrm); + int state = 0; // state variable for checking progress of boxes + JP2Box box = null; + boolean hasOpacity = false; + boolean hasChannelDef = false; + while (hasNext ()) { + box = (JP2Box) next (); + if (box == null) { + break; + } + if (box instanceof ColorGroupBox || + box instanceof OpacityBox || + box instanceof ChannelDefBox || + box instanceof CodestreamRegBox || + box instanceof IPRBox || + box instanceof ResolutionBox || + box instanceof LabelBox) { + if (!box.readBox ()) { + return false; + } + if (box instanceof OpacityBox) { + hasOpacity = true; + } + else if (box instanceof ChannelDefBox) { + hasChannelDef = true; + } + if (box instanceof LabelBox) { + label = new Property ("Label", + PropertyType.STRING, + ((LabelBox) box).getLabel ()); + } + } + else { + box.skipBox (); + } + } + if (hasOpacity && hasChannelDef) { + _repInfo.setMessage (new ErrorMessage + ("Compositing Layer Header may not have both " + + "Opacity and Channel Definition Boxes", + _module.getFilePos ())); + _repInfo.setValid (false); + } + finalizeBytesRead (); + + List propList = new ArrayList (4); + if (label != null) { + propList.add (label); + } + if (!colorSpecs.isEmpty ()) { + propList.add (new Property ("ColorSpecs", + PropertyType.PROPERTY, + PropertyArity.LIST, + colorSpecs)); + } + if (opacityProp != null) { + propList.add (opacityProp); + } + if (channelDefProp != null) { + propList.add (channelDefProp); + } + if (codestreamRegProp != null) { + propList.add (codestreamRegProp); + } + _module.addComposLayer(new Property + ("CompositeLayerHeader", + PropertyType.PROPERTY, + PropertyArity.LIST, + propList)); + return true; + } + + + /** Add a color specification property. */ + protected void addColorSpec (Property p) + { + colorSpecs.add (p); + } + + /** Add an opacity property. */ + protected void addOpacity (Property p) + { + opacityProp = p; + } + + /** Add channel definition property. */ + protected void addChannelDef (Property p) + { + channelDefProp = p; + } + + /** Add codestream registration property. */ + protected void addCodestreamReg (Property p) + { + codestreamRegProp = p; + } + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "Compositing Layer Header Box"; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CompositionBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CompositionBox.java new file mode 100644 index 00000000..88823ee5 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CompositionBox.java @@ -0,0 +1,164 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import edu.harvard.hul.ois.jhove.*; +import java.io.*; +import java.util.*; + +/** + * Composition Box (JPX). + * + * See ISO/IEC FCD15444-2: 2000, L.9.10 + * + * @author Gary McGath + * + */ +public class CompositionBox extends JP2Box { + + private List instSets; + private long _height; + private long _width; + private int _loop; + + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + */ + public CompositionBox(RandomAccessFile raf, BoxHolder parent) { + super(raf, parent); + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + JP2Box box; + if (_parentBox != null) { + // May not occur in a superbox + wrongBoxContext(); + return false; + } + initBytesRead (); + hasBoxes = true; + instSets = new LinkedList (); + + // A Composition box is a superbox which contains one + // Composition Options Box followed by 0 (?) or more + // Instruction Set Boxes. + //BoxHeader subhdr = new BoxHeader (_module, _dstrm); + //subhdr.readHeader (); + if (!hasNext ()) { + emptyBox (); + return false; + } + + // Read the options box + box = (JP2Box) next (); + if (!(box instanceof CompOptionsBox)) { + _repInfo.setMessage (new ErrorMessage + ("First box in Composition Box must be " + "Composition Options Box", + _module.getFilePos())); + _repInfo.setWellFormed (false); + return false; + } + long sizeLeft = _boxHeader.getDataLength () - box.getLength (); +// box = new CompOptionsBox (this); +// box.setBoxHeader (subhdr); +// box.setDataInputStream (_dstrm); +// box.setRandomAccessFile (_raf); +// box.setModule (_module); +// box.setRepInfo (_repInfo); + if (!box.readBox ()) { + return false; + } + + // Read the instruction set boxes + while (hasNext ()) { + box = (JP2Box) next (); + if (box == null) { + break; + } + if (box instanceof InstructionSetBox) { + if (!box.readBox ()) { + return false; + } + } + else { + box.skipBox (); + } + } + // A box has to be at least 8 bytes long, and there must + // not be any bytes left over. + if (sizeLeft != 0) { + // Underran the superbox -- get out quick + superboxUnderrun (); + return false; + + } + finalizeBytesRead (); + + List propList = new ArrayList (4); + propList.add (new Property ("Width", + PropertyType.LONG, + new Long (_width))); + propList.add (new Property ("Height", + PropertyType.LONG, + new Long (_height))); + propList.add (new Property ("Loop", + PropertyType.INTEGER, + new Integer (_loop))); + if (!instSets.isEmpty ()) { + propList.add (new Property ("InstructionSets", + PropertyType.PROPERTY, + PropertyArity.LIST, + instSets)); + } + _module.addProperty (new Property ("Composition", + PropertyType.PROPERTY, + PropertyArity.LIST, + propList)); + return true; + } + + /** Add an instruction set property to the list. + * This is called from InstructionSetBox. + */ + protected void addInstSet (Property p) + { + instSets.add (p); + } + + /** Set the height value. This is called from + * CompositionBox. */ + protected void setHeight (long h) + { + _height = h; + } + + + /** Set the height value. This is called from + * CompositionBox. */ + protected void setWidth (long w) + { + _width = w; + } + + + /** Set the loop value. This is called from + * CompositionBox. */ + protected void setLoop (int l) + { + _loop = l; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ContCodestream.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ContCodestream.java new file mode 100644 index 00000000..31066c8c --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ContCodestream.java @@ -0,0 +1,254 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.Jpeg2000Module; +import java.io.*; +import java.util.*; + +/** + * Encapsulation of a JPEG 2000 codestream. + * + * This is based on the information in Appendix A of + * ISO/IEC 15444-1:2000(E). That standard "does not + * include a definition of compliance or conformance." + * + * @author Gary McGath + * + */ +public class ContCodestream { + + private Codestream _codestream; + private long _length; + private Jpeg2000Module _module; + private DataInputStream _dstream; +// private List _tileParts; + private List _tiles; + private long _tileLeft; + + /* Tile for which we have most recently seen an unclosed SOT */ + private Tile _curTile; + + /* Set to true when a PPM marker segment is found */ + private boolean ppmSeen; + + /* Constants defining codestream markers */ + private final static int + SOC = 0X4F, // start of codestream + COD = 0X52, // coding style default + COC = 0X53, // coding style component + TLM = 0X55, // tile-part lengths + PLM = 0X57, // packet length, main header + PLT = 0X58, // packet length, tile-part header + QCD = 0X5C, // quantization default + QCC = 0X5D, // quantization component + RGN = 0X5E, // region of interest + POC = 0X5F, // progression order change + PPM = 0X60, // Packed packet headers, main header + PPT = 0X61, // packed packet headers, tile-part header + CRG = 0X63, // component registration + COM = 0X64, // comment + SOT = 0X90, // start of tile part + SOP = 0X91, // start of packet + EPH = 0X92, // end of packet header + SOD = 0X93, // start of data + EOC = 0XD9, // end of codestream + SIZ = 0X51; // image and tile size + + /** + * Constructor. + * + * @param length Length of the codestream, exclusive of the + * box header. If the codestream box has a length + * field of 0, pass 0 for this parameter. + */ + public ContCodestream (Jpeg2000Module module, + DataInputStream dstream, + long length) + { + _module = module; + _dstream = dstream; + _length = length; + _tiles = new LinkedList (); + //_tileParts = new LinkedList (); // Do I want both lists? + ppmSeen = false; + } + + + + /** Reading a codestream generates various bits of information about + * the image. These are made available after reading through + * accessor functions. + * + * @param cs The image which this codestream defines. + * Must have a non-null codestream + * field. + * + * @param info The RepInfo object which accumulates information + * about the document. Used for reporting errors. + * + * @return True if no fatal errors detected, + * false if error prevents safe continuation + */ + public boolean readCodestream (Codestream cs, RepInfo info) + throws IOException + { + final String badStream = "Ill-formed codestream"; + _codestream = cs; + long lengthLeft = _length; + _tileLeft = 0; + boolean socSeen = false; // flag to note an SOC marker has been seen + + // length may be 0, signifying that we go till EOF + if (lengthLeft == 0) { + lengthLeft = Long.MAX_VALUE; + } + try { + while (lengthLeft > 0) { + // "Marker segments" are followed by a length parameter, + // but "markers" aren't. + int ff = ModuleBase.readUnsignedByte (_dstream, _module); + if (ff != 0XFF) { + info.setMessage (new ErrorMessage (badStream)); + info.setWellFormed (false); + return false; + } + int marker = ModuleBase.readUnsignedByte (_dstream, _module); + if (marker == 0X4F) { + // we got the SOC marker, as expected + socSeen = true; + } + MarkerSegment ms = MarkerSegment.markerSegmentMaker (marker); + ms.setCodestream (cs); + ms.setContCodestream (this); + ms.setDataInputStream (_dstream); + ms.setRepInfo (info); + ms.setModule (_module); + int markLen = ms.readMarkLen (); + if (!ms.process (markLen == 0 ? 0 : markLen - 2)) { + info.setMessage (new ErrorMessage + ("Invalid marker segment")); + info.setWellFormed (false); + return false; + } + // markLen includes the marker length bytes, + // but not the marker bytes + + if (!(ms instanceof Marker)) { + lengthLeft -= markLen + 2; + // Count down on the bytes in a tile if we're in one + if (_tileLeft > 0) { + _tileLeft -= markLen + 2; + } + } + else { + // It's a plain marker -- no length data. + lengthLeft -= 2; + if (_tileLeft > 0) { + _tileLeft -= 2; + } + if (marker == SOD) { + // 0X93 is SOD, which is followed by a bitstream. + // We skip the number of bytes not yet deducted from _tileLeft + _module.skipBytes (_dstream, (int) _tileLeft, _module); + lengthLeft -= _tileLeft; + _tileLeft = 0; + } + else if (marker == EOC) { + break; // end of codestream + } + } + } + } + catch (EOFException e) { + // we're done + } + if (!socSeen) { + info.setMessage (new ErrorMessage (badStream)); + info.setWellFormed (false); + return false; + } + _codestream.setTiles (_tiles); + return true; + } + + + /** Returns the list of tiles. The elements are Tile objects. */ + public List getTiles () + { + return _tiles; + } + + /** Set the number of bytes remaining in the current tile. + * For use by MarkerSegment subclasses. + */ + protected void setTileLeft (long tileLeft) + { + _tileLeft = tileLeft; + } + + + + + /** Gets the tile whose index is idx. */ + protected Tile getTile (int idx) + { + // If we haven't reached this index before, add a tile. + // Tiles are supposed to be added sequentially, but + // PIAV. + while (_tiles.size () <= idx) { + _tiles.add (new Tile ()); + } + return (Tile) _tiles.get (idx); + } + + /** Sets the value of curTile. */ + protected void setCurTile (Tile tile) + { + _curTile = tile; + } + + /** Sets the value of the ppmSeen flag, signifying that + * a PPM marker segment has been encountered. */ + protected void setPPMSeen (boolean b) + { + ppmSeen = b; + } + + + /** Gets the value of curTile. May be null. */ + protected Tile getCurTile () + { + return _curTile; + } + + /** Returns the value of the ppmSeen flag, signifying that + * a PPM marker segment has been encountered. */ + protected boolean isPPMSeen () + { + return ppmSeen; + } + + + /* Based on marker code, return true if this is a marker + * segment (i.e., it has parameters). The documentation + * isn't fully clear, but I think the only way to determine + * what is a marker is to enumerate all values that are + * markers. */ + private static boolean isSegment (int marker) + { + if ((marker >= 0X30 && marker <= 0X3F) || + marker == SOC || // start of codestream + marker == EPH || // end of packet header + marker == SOD || // start of data + marker == EOC) { // end of codestream + return false; + } + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ContCodestreamBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ContCodestreamBox.java new file mode 100644 index 00000000..116e83f3 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ContCodestreamBox.java @@ -0,0 +1,70 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; +import edu.harvard.hul.ois.jhove.*; + +/** + * Continuous codestream box. + * See I.5.4 in ISO/IEC 15444-1:2000 + * + * @author Gary McGath + * + */ +public class ContCodestreamBox extends JP2Box { + + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + */ + public ContCodestreamBox (RandomAccessFile raf, BoxHolder parent) + { + super (raf, parent); + } + + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + * + * The reading and interpretation of the actual codestream + * occurs within the execution of readBox. + */ + public boolean readBox() throws IOException { + initBytesRead (); + + // Must come after the JP2 header + if (!_module.isJP2HdrSeen()) { + _repInfo.setMessage (new ErrorMessage + (noJP2Hdr, _module.getFilePos ())); + return false; + } + int ncs = _module.getNCodestreams () + 1; + _module.setNCodestreams (ncs); + Codestream curCodestream = _module.getCodestream (ncs); + long len = + _boxHeader.getLength () == 0 ? + 0 : _boxHeader.getDataLength (); + ContCodestream ccs = + new ContCodestream (_module, _dstrm, len); + boolean retval = ccs.readCodestream (curCodestream, _repInfo); + finalizeBytesRead (); + return retval; + } + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "Contiguous Codestream Box"; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CrossRefBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CrossRefBox.java new file mode 100644 index 00000000..20c8d1fa --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/CrossRefBox.java @@ -0,0 +1,100 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import edu.harvard.hul.ois.jhove.*; +import java.io.*; +import java.util.*; + +/** + * Cross Reference Box (JPX). + * A Cross Reference Box may be found in a Codestream + * Header, Compositing Layer Header, or Association box. + * When it is encountered, the box to which it refers + * should be substituted for the Cross Reference Box. + * Interesting features of the box are that it + * isn't all in one place, but is + * scattered through multiple locations by a fragment list, + * and it doesn't follow standard superbox rules. + * + * + * See ISO/IEC FCD15444-2: 2000, L.9.7 + * + * @author Gary McGath + * + */ +public class CrossRefBox extends JP2Box { + + DataInputStream fragStream; + + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + * or TopLevelBoxHolder + */ + public CrossRefBox(RandomAccessFile raf, BoxHolder parent) { + super(raf, parent); + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + if (! (_parentBox instanceof CodestreamHeaderBox || + _parentBox instanceof ComposLayerHdrBox || + _parentBox instanceof AssociationBox)) { + wrongBoxContext (); + return false; + } + initBytesRead (); + hasBoxes = true; + // Skip the box type + _module.read4Chars (_dstrm); + bytesLeft -= 4; + JP2Box box = null; + if (hasNext ()) { + box = (JP2Box) next (); + } + if (!(box instanceof FragmentListBox)) { + _repInfo.setMessage (new ErrorMessage + ("Cross Reference Box does not contain Fragment List Box", + _module.getFilePos ())); + _repInfo.setWellFormed (false); + return false; + } + box.readBox (); + List fragList = ((FragmentListBox) box).getFragmentList(); + //App app = _module.getApp(); + JhoveBase base = _module.getBase (); + int bufSize = base.getBufferSize (); + fragStream = new DataInputStream + (new FragmentInputStream (fragList, _raf, bufSize)); + finalizeBytesRead (); + return false; + } + + /** Returns a DataInputStream based on a FragmentInputStream + * so that the fragments can be read as a single entity. + */ + public DataInputStream getCrossRefStream () + { + return fragStream; + } + + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "Cross Reference Box"; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/DDResolutionBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/DDResolutionBox.java new file mode 100644 index 00000000..267ea01a --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/DDResolutionBox.java @@ -0,0 +1,100 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; +import java.util.*; +import edu.harvard.hul.ois.jhove.*; + +/** + * Default Display Resolution Box. + * See I.5.3.7.2 in ISO/IEC 15444-1:2000 + * + * @author Gary McGath + * + */ +public class DDResolutionBox extends JP2Box { + + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + */ + public DDResolutionBox (RandomAccessFile raf, BoxHolder parent) + { + super (raf, parent); + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + initBytesRead (); + if (!(_parentBox instanceof ResolutionBox)) { + wrongBoxContext(); + return false; + } + // The information consists of two values, horizontal and + // vertical, with numerator, denominator, and exponent, + // in dots per meter. Not clear whether to present this + // as raw data, turn it into a dots/cm rational, or what. + // I'll put it up as raw data for now. + ResolutionBox rb = (ResolutionBox) _parentBox; + List vresList = new ArrayList(3); + List hresList = new ArrayList(3); + vresList.add (new Property ("Numerator", + PropertyType.INTEGER, + new Integer (_module.readUnsignedShort (_dstrm)))); + vresList.add (new Property ("Denominator", + PropertyType.INTEGER, + new Integer (_module.readUnsignedShort (_dstrm)))); + hresList.add (new Property ("Numerator", + PropertyType.INTEGER, + new Integer (_module.readUnsignedShort (_dstrm)))); + hresList.add (new Property ("Denominator", + PropertyType.INTEGER, + new Integer (_module.readUnsignedShort (_dstrm)))); + vresList.add (new Property ("Exponent", + PropertyType.INTEGER, + new Integer (ModuleBase.readSignedByte (_dstrm, _module)))); + hresList.add (new Property ("Exponent", + PropertyType.INTEGER, + new Integer (ModuleBase.readSignedByte (_dstrm, _module)))); + // The three properties for each direction are subsumed into + // a property. + Property hres = new Property ("HorizResolution", + PropertyType.PROPERTY, + PropertyArity.LIST, + hresList); + Property vres = new Property ("VertResolution", + PropertyType.PROPERTY, + PropertyArity.LIST, + vresList); + // And the two resolution properties are subsumed into + // one property for the Module. + Property[] topProps = new Property[2]; + topProps[0] = hres; + topProps[1] = vres; + _module.addProperty(new Property ("DefaultDisplayResolution", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + topProps)); + finalizeBytesRead (); + return true; + } + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "Default Display Resolution Box"; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/DataEntryURLBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/DataEntryURLBox.java new file mode 100644 index 00000000..999381e7 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/DataEntryURLBox.java @@ -0,0 +1,86 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; +import java.util.*; +import edu.harvard.hul.ois.jhove.*; + + +/** + * Data Entry URL Box. + * + * @author Gary McGath + * + */ +public class DataEntryURLBox extends JP2Box { + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + */ + public DataEntryURLBox(RandomAccessFile raf, BoxHolder parent) { + super(raf, parent); + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + initBytesRead (); + for (int i = 0; i < 4; i++) { + // version and flags must be 0. + // If they aren't, keep going, since we can. + int v = ModuleBase.readUnsignedByte (_dstrm, _module); + if (v != 0) { + _repInfo.setMessage (new ErrorMessage + ("Unrecognized version or flag value in Data Entry URL Box", + _module.getFilePos ())); + _repInfo.setValid (false); + break; + } + } + // The URL is encoded as a null-terminated string + // of UTF-8 characters. + List byteList = new ArrayList (512); + for (;;) { + int b = ModuleBase.readUnsignedByte(_dstrm, _module); + if (b == 0) { + break; + } + byteList.add (new Byte ((byte) b)); + } + // Turn the Byte List into a byte array. (Is there a better + // way to do this?) + ListIterator li = byteList.listIterator (); + byte byteArr[] = new byte[byteList.size ()]; + int j = 0; + while (li.hasNext ()) { + byteArr[j] = ((Byte)li.next ()).byteValue (); + } + String s = new String (byteArr, "UTF-8"); + if (_parentBox instanceof UUIDInfoBox) { + UUIDInfoBox uu = (UUIDInfoBox) _parentBox; + uu.setURL (s); + } + + finalizeBytesRead (); + return true; + } + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "Data Entry URL Box"; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/DefaultBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/DefaultBox.java new file mode 100644 index 00000000..2aa65228 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/DefaultBox.java @@ -0,0 +1,44 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; + +/** + * + * Default class for Boxes that have not yet been implemented. + * Also used for the "free" box, which by definition contains + * no information. + * + * @author Gary McGath + * + */ +public class DefaultBox extends JP2Box { + + /** + * Constructor. + */ + public DefaultBox(RandomAccessFile raf) { + super(raf); + } + + /** + * Constructor with superbox. + */ + public DefaultBox (RandomAccessFile raf, BoxHolder parent) + { + super (raf, parent); + } + + /* (non-Javadoc) + * @see edu.harvard.hul.ois.jhove.module.jpeg2000.JP2Box#readBox() + */ + public boolean readBox() throws IOException { + skipBox (); + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/DefaultMarkerSegment.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/DefaultMarkerSegment.java new file mode 100644 index 00000000..506748f6 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/DefaultMarkerSegment.java @@ -0,0 +1,47 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; + +/** + * This class is used to handle any unrecognized or unimplemented + * marker segment in a codestream. + * + * @author Gary McGath + * + * To change the template for this generated type comment go to + * Window>Preferences>Java>Code Generation>Code and Comments + */ +public class DefaultMarkerSegment extends MarkerSegment { + + /** + * + */ + public DefaultMarkerSegment() { + super(); + } + + + /** + * Processes the marker segment. The DataInputStream + * will be at the point of having read the marker code. The + * process method must consume exactly the number + * of bytes remaining in the marker segment; for a marker, + * this number will always be 0. + * + * @param bytesToEat The number of bytes that must be consumed. + * For a Marker, this number will always be 0. + * If it is 0 for a MarkerSegment, the + * number of bytes to consume is unknown. + */ + protected boolean process (int bytesToEat) throws IOException + { + _module.skipBytes (_dstream, bytesToEat, _module); + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/DesiredReproBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/DesiredReproBox.java new file mode 100644 index 00000000..ddc216a1 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/DesiredReproBox.java @@ -0,0 +1,80 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; + +/** + * + * Desired Reproductions Box (JPX). + * + * See ISO/IEC FCD15444-2: 2000, L.9.15 + * + * @author Gary McGath + * + */ +public class DesiredReproBox extends JP2Box { + + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + */ + public DesiredReproBox(RandomAccessFile raf, BoxHolder parent) { + super(raf, parent); + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + // Oddly enough, this box is NOT required to be + // at the top level of the file. However, there + // can be only one in the file. + // It can have multiple subboxes, but the only + // significant one is the Graphics Technology + // Standard Output box, which simply holds an + // ISO profile. + + initBytesRead (); + int sizeLeft = (int) _boxHeader.getDataLength() ; + BoxHeader subhdr = new BoxHeader (_module, _dstrm); + JP2Box box = null; + while (hasNext ()) { + box = (JP2Box) next (); + if (box == null) { + break; + } + if (box instanceof GTSOBox) { + if (!box.readBox ()) { + return false; + } + } + else { + box.skipBox (); + } + } + + // There is a NISO property for reporting the + // profile name. I should figure out how to + // extract the name of a profile. + + finalizeBytesRead (); + return false; + } + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "Desired Reproductions Box"; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/DigSignatureBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/DigSignatureBox.java new file mode 100644 index 00000000..9426bfac --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/DigSignatureBox.java @@ -0,0 +1,179 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import edu.harvard.hul.ois.jhove.*; +import java.io.*; +import java.security.*; +import java.util.*; + +/** + * Digital Signature Box (JPX). + * See ISO/IEC FCD15444-2: 2000, L.9.17 + * + * Only the MD5 and SHA-1 + * algorithms are supported. + * + * @author Gary McGath + * + */ +public class DigSignatureBox extends JP2Box { + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + */ + public DigSignatureBox(RandomAccessFile raf, BoxHolder parent) { + super(raf, parent); + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + initBytesRead (); + int sizeLeft = (int) _boxHeader.getDataLength (); + // This may occur "anywhere in the file." Does that + // mean that all superboxes should check it as a possible + // subbox? + + List propList = new ArrayList (10); + JhoveBase je = _module.getBase (); + boolean raw = je.getShowRawFlag (); + int styp = ModuleBase.readUnsignedByte (_dstrm, _module); + if (styp > 5) { + // Known signature types are 0-5 + _repInfo.setMessage (new ErrorMessage + ("Unknown digital signature type", + _module.getFilePos ())); + _repInfo.setValid (false); + } + propList.add (_module.addIntegerProperty ("Type", + styp, JP2Strings.digitalSigTypeStr)); + + int ptyp = ModuleBase.readUnsignedByte (_dstrm, _module); + if (ptyp > 1) { + _repInfo.setMessage (new ErrorMessage + ("Unknown digital signature pointer type", + _module.getFilePos ())); + _repInfo.setValid (false); + } + propList.add (_module.addIntegerProperty ("PointerType", + styp, JP2Strings.digitalSigPtrTypeStr)); + sizeLeft -= 2; + + long off = 0; + long len = 0; + if (ptyp == 1) { + off = _module.readSignedLong (_dstrm); + len = _module.readSignedLong (_dstrm); + propList.add (new Property ("Offset", + PropertyType.LONG, + new Long (off))); + propList.add (new Property ("Length", + PropertyType.LONG, + new Long (len))); + sizeLeft -= 8; + } + + byte[] data = new byte[sizeLeft]; + ModuleBase.readByteBuf(_dstrm, data, _module); + + if (styp == 0 || styp == 1) { + try { + // If the whole file is indicated, set the + // parameters accordingly + if (ptyp == 0) { + off = 0; + len = _raf.length (); + } + propList.add (new Property ("Valid", + PropertyType.BOOLEAN, + new Boolean (isSigValid + (styp, off, len, data)))); + } + catch (NoSuchAlgorithmException e) { + // In the unlikely event the algorithms aren't + // available, just don't report validity. + } + catch (IOException f) {} + } + + _module.addDigitalSignatureProp (new Property + ("DigitalSignature", + PropertyType.PROPERTY, + PropertyArity.LIST, + propList)); + finalizeBytesRead (); + return true; + } + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "Digital Signature Box"; + } + + + + /* Check if the signature is valid. Only applicable + * for styp of 0 (MD5) or 1 (SHA-1). */ + private boolean isSigValid (int styp, + long off, long len, + byte[] data) + throws NoSuchAlgorithmException, + IOException + { + MessageDigest digest; + if (styp == 0) { + digest = MessageDigest.getInstance ("MD5"); + } + else { + digest = MessageDigest.getInstance ("SHA-1"); + } + + // With the new robustness of RAFInputStream, it should + // no longer be necessary to save the file position. + //long savePos = _raf.getFilePointer (); + + // If the whole file is indicated, set the parameters + // accordingly + try { + _raf.seek (off); + int buflen = (len < 65536 ? (int) len : 65536); + byte[] buf = new byte[buflen]; + while (len > 0) { + int btr = (len < buflen ? (int) len : buflen); + int bytesRead = _raf.read (buf, 0, btr); + digest.update (buf, 0, bytesRead); + } + byte[] digestVal = digest.digest (); + + // Check if we suffer from indigestion + if (digestVal.length != data.length) { + return false; + } + for (int i = 0; i < data.length; i++) { + if (digestVal[i] != data[i]) { + return false; + } + } + // Our digestion is good + return true; + } + catch (IOException e) { + return false; // most likely invalid range + } + + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/FileTypeBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/FileTypeBox.java new file mode 100644 index 00000000..ca9f6e79 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/FileTypeBox.java @@ -0,0 +1,142 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; +import java.util.*; +import edu.harvard.hul.ois.jhove.*; + +/** + * File Type Box. + * See I.5.2 in ISO/IEC 15444-1:2000 + * + * A File Type box can occur only as the first thing after the + * Signature Box, so this will be invoked only directly from + * the Module. + * + * @author Gary McGath + * + */ +public class FileTypeBox extends JP2Box { + + /** + * Constructor. + */ + public FileTypeBox(RandomAccessFile raf) { + super(raf); + } + + /** + * The constructor with superbox is meaningless. + */ + public FileTypeBox(RandomAccessFile raf, JP2Box parent) { + super (raf); + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + String brand = _module.read4Chars(_dstrm); + _module.addProperty (new Property ("Brand", + PropertyType.STRING, + brand)); + // 12 bytes have been read + + // Brand indicates intended compliance + if (!"jp2 ".equals (brand)) { + _module.setJP2Compliant (false); + } + if (!"jpx ".equals (brand)) { + _module.setJPXCompliant (false); + } + + long minv = _module.readUnsignedInt(_dstrm); + _module.addProperty (new Property ("MinorVersion", + PropertyType.LONG, + new Long (minv))); + // 16 bytes have been read + + // Read the compatibility list. It takes up the rest + // on the box length. + int ncomp = (((int) _boxHeader.getLength ()) - 16) / 4; + if (ncomp < 1) { + _repInfo.setMessage (new ErrorMessage + ("Empty compatibility list in File Type Box", + _module.getFilePos ())); + _repInfo.setWellFormed (false); + return false; + } + List compList = new ArrayList (ncomp); + boolean eflag = false; + StringBuffer hexcitem = new StringBuffer (8); + for (int i = 0; i < ncomp; i++) { + String citem = _module.read4Chars (_dstrm); + + // Some files have a count of entries, which isn't supposed + // to be there. If we see any nulls, report an ill-formed condition. + // For each entry, we build a hex string in hexcitem just in case + // it's necessary to report the string in hex. + char[] cbytes = citem.toCharArray(); + boolean binflag = false; + for (int j = 0; j < cbytes.length; j++) { + int ch = (int) cbytes[j]; + hexcitem.append (Integer.toHexString(ch)); + if (ch == 0 || ch >= 0X7F) { + binflag = true; + if (!eflag) { + eflag = true; // Avoid multiple report of same error + _repInfo.setValid (false); + _repInfo.setMessage (new ErrorMessage + ("Non-ASCII characters in compatibility item of File Type Box", + _module.getFilePos ())); + } + + } + } + if (!binflag) { + compList.add (citem); + } + else { + compList.add (hexifyString (citem)); + } + } + _module.addProperty (new Property ("Compatibility", + PropertyType.STRING, + PropertyArity.LIST, + compList)); + // All the bytes have been read + return true; + } + + private String hexifyString (String s) + { + StringBuffer retval = new StringBuffer (2 * s.length () + 2); + retval.append ("0X"); + char[] chs = s.toCharArray(); + for (int i = 0; i < chs.length; i++) { + String hs = Integer.toHexString ((int) chs[i]); + // Pad to 2 characters + if (hs.length () == 1) { + retval.append ('0'); + } + retval.append (hs); + } + return retval.toString (); + } + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "File Type Box"; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/FragmentInputStream.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/FragmentInputStream.java new file mode 100644 index 00000000..2e6bf09d --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/FragmentInputStream.java @@ -0,0 +1,109 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; +import java.util.*; + +/** + * A FragmentInputStream provides an interface by which + * the scattered fragments of a Fragment List Box can + * be read as a single stream. Only fragments within + * the originating file are supported, not fragments + * in external files. + * + * @author Gary McGath + * + */ +public class FragmentInputStream extends InputStream { + + private List _fragments; + private RandomAccessFile _raf; + private ListIterator fragIterator; + private long curFragment[]; + byte[] fragBuffer; + + /* Offset within fragBuffer. -1 indicates fragBuffer + * does not contain valid data. */ + private int bufOffset; + + /* Offset in the file from the start of the current fragment. */ + private int fragOffset; + + /* Size allocated to the buffer. */ + private int _bufSize; + + /* Bytes of actual data in the buffer. */ + private int bufBytes; + + /** + * @param fragments List of fragment entries. + * Each fragment entry is an array of two longs, + * with fragment[0] being the length and + * fragment[1] the offset. + */ + public FragmentInputStream(List fragments, RandomAccessFile raf) + { + super(); + _fragments = fragments; + _raf = raf; + init (-1); + } + + public FragmentInputStream (List fragments, + RandomAccessFile raf, + int bufSize) + { + super (); + _fragments = fragments; + _raf = raf; + init (bufSize); + } + + private void init (int bufSize) + { + fragIterator = _fragments.listIterator (); + // If no buffer size was specified, assign a default + // size. + if (bufSize <= 0) { + bufSize = 8192; + } + _bufSize = bufSize; + fragBuffer = new byte[bufSize]; + bufOffset = 0; + bufBytes = 0; + } + + /** + * Returns the next byte from the stream, buffering each fragment + * in turn until the last fragment is exhausted. + * + * @return The next byte of the stream, or -1 to indicate no + * more bytes are available. + */ + public int read() throws IOException { + if (bufOffset >= bufBytes) { + // We need a fresh buffer read. + if (curFragment == null || fragOffset >= curFragment[1]) { + // We need a new fragment. + if (fragIterator.hasNext ()) { + curFragment = fragIterator.next (); + fragOffset = 0; + } + else { + // No more data available. + return -1; + } + } + _raf.seek(curFragment[0] + fragOffset); + bufBytes = _raf.read(fragBuffer); + fragOffset += bufBytes; + bufOffset = 0; + } + return fragBuffer[bufOffset++]; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/FragmentListBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/FragmentListBox.java new file mode 100644 index 00000000..603b7c50 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/FragmentListBox.java @@ -0,0 +1,102 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import edu.harvard.hul.ois.jhove.*; +import java.io.*; +import java.util.*; + +/** + * Fragment List Box (JPX). + * Subbox of Fragment Table box or Cross-Reference box. + * See L.9.6.1 in ISO/IEC FCD15444-2:2000. + * + * @author Gary McGath + * + */ +public class FragmentListBox extends JP2Box { + + private List _fragmentList; + + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box. + * The parent may be a FragmentTableBox + * or a CrossReferenceBox. + */ + public FragmentListBox(RandomAccessFile raf, BoxHolder parent) { + super (raf, parent); + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + if (!_module.isJP2HdrSeen()) { + _repInfo.setMessage (new ErrorMessage + (noJP2Hdr, _module.getFilePos ())); + return false; + } + initBytesRead (); + int len = (int) _boxHeader.getDataLength (); + + int nFrags = _module.readUnsignedShort (_dstrm); + if (_boxHeader.getLength () != 0 && len != 14 * nFrags + 2) { + _repInfo.setMessage + (new ErrorMessage ("Fragment Table has invalid length", _module.getFilePos ())); + _repInfo.setWellFormed (false); + return false; + } + _fragmentList = new ArrayList (nFrags); + for (int i = 0; i < nFrags; i++) { + long offset = _module.readSignedLong (_dstrm); + long fragLen = _module.readUnsignedInt (_dstrm); + int dataRef = _module.readUnsignedShort (_dstrm); + + // If dataRef is nonzero, the stream is outside the file, + // and all we can do is report the reference. In fact, + // if any of the fragments are outside the file, we + // have to punt. So we should collect all the + // fragments and then read the stream. + + if (dataRef != 0) { + _fragmentList = null; // no can do fragments + _repInfo.setMessage (new InfoMessage + ("Document references an external file", _module.getFilePos())); + } + else if (_fragmentList != null) { + long[] frag = new long[2]; + frag[0] = offset; + frag[1] = fragLen; + _fragmentList.add (frag); + } + } + finalizeBytesRead (); + return true; + } + + + /** Returns the fragment list. If there are external references + * to fragments, returns null; in this case, a warning message + * has been added to the RepInfo object. */ + protected List getFragmentList () + { + return _fragmentList; + } + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "Fragment List Box"; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/FragmentTableBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/FragmentTableBox.java new file mode 100644 index 00000000..cb697bd7 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/FragmentTableBox.java @@ -0,0 +1,102 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import edu.harvard.hul.ois.jhove.*; +import java.io.*; +import java.util.List; + +/** + * Fragment Table Box (JPX). + * See L.9.6 in ISO/IEC FCD15444-2:2000. + * @author Gary McGath + * + */ +public class FragmentTableBox extends JP2Box { + + + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + * or TopLevelBoxHolder + */ + public FragmentTableBox (RandomAccessFile raf, BoxHolder parent) + { + super (raf, parent); + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + boolean retval = true; + initBytesRead (); + hasBoxes = true; + //int state = 0; // state variable for checking progress of boxes + JP2Box box = (JP2Box) next (); + if (box == null) { + return false; // empty box can't be right + } + // OK, how do I deal with subboxes? Should be able to handle + // them the same way as in the module, except that a different + // set of boxes is acceptable. The dispatcher (in BoxHeader?) + // may need to be given a list of boxes that are acceptable for + // any given context. For the top-level module, the list + // should probably (maybe) be in BoxHeader, but for boxes, the + // list of acceptable subboxes must be provided explicitly. + // Maybe an additional argument in the BoxHeader constructor. + if (box instanceof FragmentListBox) { + FragmentListBox fbox = (FragmentListBox) box; + if (!fbox.readBox ()) { + return false; + } + List fragList = fbox.getFragmentList(); + // fragList will be null if external files are referenced. + if (fragList != null) { + //App app = _module.getApp(); + JhoveBase base = _module.getBase (); + int bufSize = base.getBufferSize (); + FragmentInputStream fragStream = + new FragmentInputStream (fragList, _raf, bufSize); + DataInputStream dfstrm = new DataInputStream (fragStream); + int ncs = _module.getNCodestreams () + 1; + _module.setNCodestreams (ncs); + Codestream curCodestream = _module.getCodestream (ncs); + long len = + _boxHeader.getLength () == 0 ? + 0 : _boxHeader.getDataLength (); + ContCodestream ccs = + new ContCodestream (_module, dfstrm, len); + // Oh, FOOBAR. This creates another situation in which + // we can't count the bytes being read. Buf if we're + // going to a random access file, that may all be + // rendered moot anyway. + retval = ccs.readCodestream (curCodestream, _repInfo); + } + } + else { + _repInfo.setMessage (new ErrorMessage + ("Invalid fragment table", _module.getFilePos ())); + _repInfo.setWellFormed (false); + return false; + } + finalizeBytesRead (); + return retval; + } + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "Fragment Table Box"; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/GTSOBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/GTSOBox.java new file mode 100644 index 00000000..e8982de5 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/GTSOBox.java @@ -0,0 +1,62 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import edu.harvard.hul.ois.jhove.*; +import java.io.*; + +/** + * Graphics Technology Standard Output Box. + * This box holds an ICC color profile. + * + * See ISO/IEC FCD15444-2: 2000, L.9.15.1 + * + * @author Gary McGath + * + */ +public class GTSOBox extends JP2Box { + + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + */ + public GTSOBox(RandomAccessFile raf, BoxHolder parent) { + super(raf, parent); + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + initBytesRead (); + // Short of pulling out the bytes and somehow + // analyzing them, about all we can do is report + // the presence and length of the profile. + + // There can be only one GTSO box within the file, + // which seems oddly limiting compared to the rest + // of JPEG 2000. But it makes this simple. + long propSize = _boxHeader.getDataLength (); + Property sizeProp = new Property ("ProfileLength", + PropertyType.LONG, + new Long (propSize)); + _module.addProperty (new Property + ("GraphicsTechnologyStandardOutput", + PropertyType.PROPERTY, + sizeProp)); + _module.skipBytes (_dstrm, (int) propSize, _module); + finalizeBytesRead (); + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/IPRBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/IPRBox.java new file mode 100644 index 00000000..a9cf6ac9 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/IPRBox.java @@ -0,0 +1,64 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; +import edu.harvard.hul.ois.jhove.*; + +/** + * Intellectual Property Rights box. + * See I.6 in ISO/IEC 15444-1:2000 + * + * The spec says nothing about the content of the IPR box, + * so the generated Property reports it as a sequence of bytes. + * + * @author Gary McGath + * + */ +public class IPRBox extends JP2Box { + + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + */ + public IPRBox (RandomAccessFile raf, BoxHolder parent) + { + super (raf); + } + + + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + initBytesRead (); + int len = (int) _boxHeader.getDataLength (); + byte[] bytes = new byte[len]; + for (int i = 0; i < len; i++) { + bytes[i] = (byte) ModuleBase.readUnsignedByte (_dstrm, _module); + } + _module.addProperty (new Property ("IntellectualPropertyRights", + PropertyType.BYTE, + PropertyArity.ARRAY, + bytes)); + finalizeBytesRead (); + return true; + } + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "Intellectual Property Rights Box"; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ImageHeaderBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ImageHeaderBox.java new file mode 100644 index 00000000..3aff3343 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ImageHeaderBox.java @@ -0,0 +1,109 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; +import edu.harvard.hul.ois.jhove.*; + +/** + * Image Header Box. + * See I.5.3.1 in ISO/IEC 15444-1:2000 + * + * @author Gary McGath + * + */ +public class ImageHeaderBox extends JP2Box { + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + */ + public ImageHeaderBox(RandomAccessFile raf, BoxHolder parent) { + super(raf, parent); + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + if (!(_parentBox instanceof JP2HeaderBox)) { + _repInfo.setMessage (new ErrorMessage + ("ImageHeader Box in illegal context", _module.getFilePos ())); + return false; + } + initBytesRead (); + if (_boxHeader.getLength() != 22) { + _repInfo.setMessage (new ErrorMessage + ("Image Header Box is incorrect size", _module.getFilePos ())); + _repInfo.setWellFormed (false); + return false; + } + + // If this is called from a JP2 Header, we set values + // in _defaultNiso, otherwise we set them in the image's + // Niso metadata. Question: where do we get the codestream? + NisoImageMetadata niso; + if (_parentBox instanceof CodestreamHeaderBox) { + Codestream cs = ((CodestreamHeaderBox) _parentBox).getCodestream (); + niso = cs.getNiso (); + } + else { + niso = _module.getDefaultNiso (); + } + + long height = _module.readUnsignedInt (_dstrm); + niso.setImageLength (height); + long width = _module.readUnsignedInt (_dstrm); + niso.setImageWidth (width); + int nc = _module.readUnsignedShort (_dstrm); + if (nc == 0) { + _repInfo.setMessage (new ErrorMessage + ("ImageHeader Box haz zero components", _module.getFilePos ())); + return false; + } + niso.setSamplesPerPixel(nc); + int bpc = ModuleBase.readUnsignedByte(_dstrm, _module); + if (bpc != 255) { + // If the value is 255, use the BPC box. + int[] bits = new int[nc]; + int bps = (bpc & 0X7F) + 1; + for (int i = 0; i < nc; i++) { + bits[i] = bps; + } + // The high-order bit of bpc is 1 if the samples have + // signed values (!). What do we do with it? + niso.setBitsPerSample(bits); + } + int compression = ModuleBase.readUnsignedByte (_dstrm, _module); + if (compression == 7) { + niso.setCompressionScheme (34712); // JPEG 2000 + } + + int unk = ModuleBase.readUnsignedByte (_dstrm, _module); + _module.addProperty (new Property ("ColorspaceUnknown", + PropertyType.BOOLEAN, + new Boolean (unk != 0))); + + int ipr = ModuleBase.readUnsignedByte (_dstrm, _module); + // This just says whether there is an IPR box. + // Do we need to do anything with it? + finalizeBytesRead (); + _module.setImageHeaderSeen (true); + return true; + } + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "Image Header Box"; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/InstructionSetBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/InstructionSetBox.java new file mode 100644 index 00000000..c36ab20c --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/InstructionSetBox.java @@ -0,0 +1,145 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import edu.harvard.hul.ois.jhove.*; +import java.io.*; +import java.util.*; + +/** + * Instruction Set Box (JPX). + * See ISO/IEC FCD15444-2: 2000, L.9.10.2 + * + * + * @author Gary McGath + * + */ +public class InstructionSetBox extends JP2Box { + + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + */ + public InstructionSetBox(RandomAccessFile raf, BoxHolder parent) { + super(raf, parent); + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + + // Flags indicating which parameters are in instructions + + // Can be found only in a Composition Box + if (!(_parentBox instanceof CompositionBox)) { + wrongBoxContext (); + return false; + } + + initBytesRead (); + + // ityp flags indicate which parameters are present + int ityp = _module.readUnsignedShort (_dstrm); + boolean hasXO_YO = ((ityp & 1) != 0); + boolean hasWid_Ht = ((ityp & 2) != 0); + boolean hasAnimation = ((ityp & 8) != 0); + boolean hasCrop = ((ityp & 0X20) != 0); + + // Get the repeat count + int rept = _module.readUnsignedShort (_dstrm); + + // Get the tick duration. Ignored (but still takes up + // space) if hasAnimation is false. + long tick = _module.readUnsignedInt (_dstrm); + + int sizeLeft = (int) _boxHeader.getDataLength () - 8; + // If all significant bits of ityp are 0, there are no instructions + if ((ityp & 0X2B) == 0) { + if (sizeLeft != 0) { + _repInfo.setMessage (new ErrorMessage + ("Data underrun in Instruction Set Box", + _module.getFilePos ())); + _repInfo.setWellFormed (false); + return false; + } + } + else { + List instProps = new ArrayList (11); + // Loop to read instructions + while (sizeLeft >= 0) { + if (hasXO_YO) { + long xo = _module.readUnsignedInt (_dstrm); + instProps.add (new Property ("HorizontalOffset", + PropertyType.LONG, new Long (xo))); + long yo = _module.readUnsignedInt (_dstrm); + instProps.add (new Property ("VerticalOffset", + PropertyType.LONG, new Long (yo))); + sizeLeft -= 8; + } + if (hasWid_Ht) { + long width = _module.readUnsignedInt (_dstrm); + instProps.add (new Property ("Width", + PropertyType.LONG, new Long (width))); + long height = _module.readUnsignedInt (_dstrm); + instProps.add (new Property ("Height", + PropertyType.LONG, new Long (height))); + sizeLeft -= 8; + } + if (hasAnimation) { + long life = _module.readUnsignedInt (_dstrm); + // The high bit of life is the persistence flag + boolean persist = ((life & 0X80000000) != 0); + instProps.add (new Property ("Persist", + PropertyType.BOOLEAN, new Boolean (persist))); + life &= 0X7FFFFFFF; + instProps.add (new Property ("Life", + PropertyType.LONG, new Long (life))); + + // Sloppy documentation: I'm assuming that N + // and NEXT-USE are the same thing. + long nextuse = _module.readUnsignedInt (_dstrm); + instProps.add (new Property ("NextUse", + PropertyType.LONG, new Long (nextuse))); + sizeLeft -= 8; + } + if (hasCrop) { + long xc = _module.readUnsignedInt (_dstrm); + instProps.add (new Property ("HorizontalCropOffset", + PropertyType.LONG, new Long (xc))); + long yc = _module.readUnsignedInt (_dstrm); + instProps.add (new Property ("VerticalCropOffset", + PropertyType.LONG, new Long (yc))); + long wc = _module.readUnsignedInt (_dstrm); + instProps.add (new Property ("CroppedWidth", + PropertyType.LONG, new Long (wc))); + long hc = _module.readUnsignedInt (_dstrm); + instProps.add (new Property ("CroppedHeight", + PropertyType.LONG, new Long (hc))); + sizeLeft -= 16; + } + if (sizeLeft < 0) { + _repInfo.setMessage (new ErrorMessage + ("Data overrun in Instruction Set Box", + _module.getFilePos ())); + _repInfo.setWellFormed (false); + return false; + } + } + } + + finalizeBytesRead (); + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/JP2Box.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/JP2Box.java new file mode 100644 index 00000000..332c133e --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/JP2Box.java @@ -0,0 +1,440 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import edu.harvard.hul.ois.jhove.module.Jpeg2000Module; +import edu.harvard.hul.ois.jhove.*; +import java.io.*; +import java.util.*; + +/** + * Superclass for JPEG 2000 boxes. + * + * @author Gary McGath + * + */ +public abstract class JP2Box extends BoxHolder { + + protected long startBytesRead; + protected long _bytesRead; + protected List associations; + + protected final static String noJP2Hdr = + "Other boxes may not occur before JP2 Header"; + + /* Name to be used for self-description property. */ + protected final static String DESCRIPTION_NAME = + "Description"; + + /** + * Constructor. Has no arguments, so that + * invoking lots of different subclasses is + * relatively simple. setModule, setBoxHeader, + * setRepInfo, and setDataInputStream should + * be called immediately after the constructor. + */ + public JP2Box (RandomAccessFile raf) + { + super (raf); + init (null); + } + + /** + * Constructor for a box which is found within a + * superbox. + * + * @param parent parent superbox of this box + */ + public JP2Box (RandomAccessFile raf, BoxHolder parent) + { + super (raf); + init (parent); + } + + protected void init (BoxHolder parent) + { + //_boxHeader = hdr; + if (parent instanceof JP2Box) { + _parentBox = (JP2Box) parent; + } + else { + _parentBox = null; + } + _bytesRead = 0; + associations = new LinkedList (); + } + + + /* This is the key to the reorganization of the code. + * A normal box generates an RAFInputStream based + * on the underlying RandomAccessFile. What do the + * weird subclasses do, and how do I handle box + * substitution? Rather than calling boxMaker statically, + * should I add a method BoxHeader.getBox? That doesn't + * quite cover the case where one header (for a + * Binary Filter Box) turns into multiple boxes. Could + * have an iterator in the BoxHeader class that is capable + * of returning no boxes (in the case of a Free box) or + * multiple boxes, but usually returns one box. + * + * But a subbox iterator, which returns all the top-level + * boxes (at the top level) or all the subboxes of a given + * box, is more natural. For this we need to add a BoxHolder + * class, of which JP2Box is a subclass. BoxHolder will + * have a method to generate an iterator; this method gets + * a box (which it may keep in its pocket), and knows about + * Binary Filter boxes and Cross Reference boxes. + */ + + /** Sets the module under which the Box is being read. */ + public void setModule (Jpeg2000Module module) + { + _module = module; + } + + /** Sets the BoxHeader from which this Box was obtained. */ + public void setBoxHeader (BoxHeader hdr) + { + _boxHeader = hdr; + bytesLeft = _boxHeader.getDataLength (); + } + + /** Assigns a RepInfo object, so that subclasses of + * JP2Box can add Properties and Messages. */ + public void setRepInfo (RepInfo info) + { + _repInfo = info; + } + + /** Assigns the DataInputStream from which the box is + * being read. */ + public void setDataInputStream (DataInputStream dstrm) + { + _dstrm = dstrm; + } + + + /** Assigns the RandomAccessFile from which the box is + * being read. */ + public void setRandomAccessFile (RandomAccessFile raf) + { + _raf = raf; + } + + + /** Static factory method for generating an object of the + * appropriate subclass of MarkerSegment, based on the + * box type. + * This is for use in top-level reading of boxes, not + * subboxes. Provision is made for calling this with a + * parent box, but the set of boxes dispatched on + * is the set used at top level. + * + * Certain box types have magical characteristics and have + * to be checked by the BoxHolder. These include + * BinaryFilterBox and CrossReferenceBox. + * + * @param hType 4-character string indicating the box type + * @param parent parent BoxHolder + */ + public static JP2Box boxMaker (String hType, BoxHolder parent) + { + JP2Box box = null; + RandomAccessFile raf = null; + if (parent != null) { + raf = parent._raf; + } + if ("jp2h".equals (hType)) { + // The JP2 header superbox + box = new JP2HeaderBox (raf, parent); + } + else if ("asoc".equals (hType)) { + // Association box (JPX) + box = new AssociationBox (raf, parent); + } + else if ("bpcc".equals (hType)) { + box = new BPCCBox (raf, parent); + } + else if ("chck".equals (hType)) { + box = new DigSignatureBox (raf, parent); + } + else if ("cdef".equals (hType)) { + box = new ChannelDefBox (raf, parent); + } + else if ("cgrp".equals (hType)) { + box = new ColorGroupBox(raf, parent); + } + else if ("cmap".equals (hType)) { + box = new ComponentMapBox (raf, parent); + } + else if ("colr".equals (hType)) { + box = new ColorSpecBox (raf, parent); + } + else if ("comp".equals (hType)) { + // Composition box (JPX) + box = new CompositionBox (raf, parent); + } + else if ("copt".equals (hType)) { + // Composition options box (JPX) + box = new CompOptionsBox (raf, parent); + } + else if ("creg".equals (hType)) { + // codestream registration box (JPX) + box = new CodestreamRegBox (raf, parent); + } + else if ("drep".equals (hType)) { + box = new DesiredReproBox (raf, parent); + } + else if ("flst".equals (hType)) { + box = new FragmentListBox (raf, parent); + } + else if ("ftbl".equals (hType)) { + // Fragment Table box (JPX) + box = new FragmentTableBox (raf, parent); + } + else if ("gtso".equals (hType)) { + // Graphics Technology Standard Output Box (JPX) + box = new GTSOBox (raf, parent); + } + else if ("inst".equals (hType)) { + // Instruction Set box (JPX) + box = new InstructionSetBox (raf, parent); + } + else if ("ihdr".equals (hType)) { + box = new ImageHeaderBox (raf, parent); + } + else if ("jp2c".equals (hType)) { + // The Continuous Codestream box. + box = new ContCodestreamBox (raf, parent); + } + else if ("jpch".equals (hType)) { + // The Compositing Header box + box = new CodestreamHeaderBox (raf, parent); + } + else if ("jplh".equals (hType)) { + // The Compositing Layer Header box + box = new ComposLayerHdrBox (raf, parent); + } + else if ("jp2i".equals (hType)) { + // The Intellectual Property Rights box + box = new IPRBox (raf, parent); + } + else if ("lbl ".equals (hType)) { + box = new LabelBox (raf, parent); + } + else if ("nlst".equals (hType)) { + // Number list box (JPX) + box = new NumberListBox (raf, parent); + } + else if ("opct".equals (hType)) { + box = new OpacityBox (raf, parent); + } + else if ("pclr".equals (hType)) { + box = new PaletteBox (raf, parent); + } + else if ("res ".equals (hType)) { + box = new ResolutionBox (raf, parent); + } + else if ("roid".equals (hType)) { + box = new ROIBox (raf, parent); + } + else if ("resc".equals (hType)) { + // Capture Resolution Box (JPX) + box = new CaptureResolutionBox (raf, parent); + } + else if ("resd".equals (hType)) { + // Default Display Resolution Box (JPX) + box = new DDResolutionBox (raf, parent); + } + else if ("rreq".equals (hType)) { + // Reader Requirements box (JPX) + box = new ReaderRequirementsBox (raf, parent); + } + else if ("uinf".equals (hType)) { + box = new UUIDInfoBox (raf, parent); + } + else if ("ulst".equals (hType)) { + box = new UUIDListBox (raf, parent); + } + else if ("url ".equals (hType)) { + box = new DataEntryURLBox (raf, parent); + } + else if ("uuid".equals (hType)) { + box = new UUIDBox (raf, parent); + } + else if ("xml ".equals (hType)) { + box = new XMLBox (raf, parent); + } + else { + // Not recognized; skip over it. + // The "free" box, which simply indicates + // unused space, goes through here. + // So does the Media Data ("mdat") box, + // whose content is defined only by references + // into it from a Fragment Table. + box = new DefaultBox (raf); + } + return box; + } + + /* Bracketing code for calculating bytes read. + * Every subclass's readBox() method should start + * by calling initBytesRead and finish by calling + * finalizeBytesRead. + */ + protected void initBytesRead () + { + startBytesRead = _module.getFilePos (); + } + + protected void finalizeBytesRead () + { + _bytesRead = _module.getFilePos () - startBytesRead; + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * Thus, the header of the box must already have been read. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + * The number of bytes read must be placed in _bytesRead. + */ + public abstract boolean readBox () throws IOException; + + public int getBytesRead () + { + return (int) _bytesRead; + } + + + /** Skips over the box. Can be called when the box is + * legal but meaningless in the current context. + */ + public void skipBox() throws IOException { + initBytesRead (); + if (_boxHeader.getLength () != 0) { + _module.skipBytes (_dstrm, (int) + _boxHeader.getDataLength (), + _module); + } + finalizeBytesRead (); + } + + /* Adds an Association property. Most superboxes can + * contain Association boxes; these report themselves + * as Association properties. + */ + protected void addAssociation (Property p) + { + associations.add (p); + } + + + /** Utility error reporting function for incorrect box length. + * Sets the RepInfo's wellFormed flag to false. + */ + protected void wrongBoxSize () + { + _repInfo.setMessage (new ErrorMessage + ("Incorrect Box size for " + getSelfPropName (), + _module.getFilePos ())); + _repInfo.setWellFormed (false); + } + + /** Utility error reporting function for box in a context + * (superbox or lack thereof) which is not permitted. + * Sets the RepInfo's wellFormed flag to false. + */ + protected void wrongBoxContext () + { + _repInfo.setMessage (new ErrorMessage + ("Invalid context for " + getSelfPropName (), + _module.getFilePos ())); + _repInfo.setWellFormed (false); + } + + /** Utility error reporting function for a box which is + * expected to have subboxes, but doesn't. + */ + protected void emptyBox () + { + _repInfo.setMessage (new ErrorMessage + ("Box is empty", "Box type = " + getSelfPropName (), + _module.getFilePos ())); + _repInfo.setWellFormed (false); + } + + + /** Make a Property from the association list. + * Returns null if the list is empty. */ + protected Property makeAssocProperty () + { + if (associations.isEmpty ()) { + return null; + } + else return new Property ("Associations", + PropertyType.PROPERTY, + PropertyArity.LIST, + associations); + } + + /** Returns a Property which describes the Box, for use + * by Association boxes and perhaps others. + * Most subclasses will only have to override + * getSelfPropName and + * getSelfPropDesc. A subclass + * that shouldn't be added to the Association box's + * property can override this to return null. + */ + protected Property selfDescProperty () { + List subprops = new ArrayList (2); + String name = getSelfPropName (); + if (name == null) { + return null; + } + subprops.add (new Property ("Name", + PropertyType.STRING, + name)); + Property p2 = getSelfPropDesc (); + if (p2 != null) { + subprops.add (p2); + } + return new Property ("Box", + PropertyType.PROPERTY, + PropertyArity.LIST, + subprops); + } + + + /** Returns the name of the Box. All Boxes should + * override this. */ + protected String getSelfPropName () + { + return null; + } + + /** Returns a Property which describes the box. This is + * used as a subproperty of the Property returned by + * selfDescProperty. Properties that we don't care to + * describe don't have to override this. This class + * should return either null or a property + * with DESCRIPTION_NAME for its name. + */ + protected Property getSelfPropDesc (){ + return null; + } + + /** Returns the length of the box, including header, based + * on the information in the header. + */ + protected long getLength () + { + return _boxHeader.getLength(); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/JP2HeaderBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/JP2HeaderBox.java new file mode 100644 index 00000000..cf1b9a4b --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/JP2HeaderBox.java @@ -0,0 +1,159 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; +import edu.harvard.hul.ois.jhove.*; + +/** + * JP2 Header Box. + * See I.5.3 in ISO/IEC 15444-1:2000 + * and ISO/IEC FCD15444-2: 2000, L.9.2 + * + * + * @author Gary McGath + * + */ +public class JP2HeaderBox extends JP2Box { + + + /** + * Constructor with superbox. + */ + public JP2HeaderBox (RandomAccessFile raf, BoxHolder parent) + { + super (raf, parent); + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + initBytesRead (); + hasBoxes = true; + //int sizeLeft = (int) _boxHeader.getDataLength (); + if (_module.isJP2HdrSeen ()) { + _repInfo.setMessage (new ErrorMessage + ("Multiple JP2 Header Boxes not allowed", + _module.getFilePos ())); + // Skip the redundant box and set invalid flag, + // but keep going. + _repInfo.setValid (false); + if (_boxHeader.getLength () != 0) { + _module.skipBytes (_dstrm, + (int) _boxHeader.getDataLength (), + _module); + } + } + _module.setJP2HdrSeen (true); + + // In JP2 format, this must come before the Contiguous + // Codestream + if (_module.getNCodestreams () > 0) { + _module.setJP2Compliant (false); + } + + // The JP2 header consists of a variety of boxes, + // so we keep reading boxes till we run out of bytes. + //BoxHeader subhdr = new BoxHeader (_module, _dstrm); + int state = 0; // state variable for checking progress of boxes + JP2Box box = null; + boolean hasCMap = false; + boolean hasPalette = false; + while (hasNext ()) { + box = (JP2Box) next (); + + // A JPX, but not a JP2, can have a Label Box + // before the Image header. + if (state == 0 && box instanceof LabelBox) { + state = 1; + _module.setJP2Compliant (false); + //box = new LabelBox (this); + if (!box.readBox ()) { + return false; + } + _module.addProperty (new Property ("JP2HeaderLabel", + PropertyType.STRING, + ((LabelBox) box).getLabel ())); + + // Read the next box + box = (JP2Box) next (); + } + + // First box, except perhaps for the label box, + // is the image header. + else if (state <= 1) { + if (box instanceof ImageHeaderBox) { + state = 2; + if (!box.readBox ()) { + return false; + } + } + else { + _repInfo.setMessage (new ErrorMessage + ("First box of JP2 header must be image header", + _module.getFilePos ())); + _repInfo.setWellFormed (false); + return false; + } + } + else { + // Only certain boxes are meaningful in a JP2 Header. + // However, others should be skipped over, not considered + // errors. + if (box instanceof AssociationBox || + box instanceof BPCCBox || + box instanceof ColorSpecBox || + box instanceof PaletteBox || + box instanceof ComponentMapBox || + box instanceof ChannelDefBox || + box instanceof ResolutionBox || + box instanceof ROIBox) { + if (!box.readBox ()) { + return false; + } + } + else { + box.skipBox (); + } + + } + } + + + // Consistency checks + if (hasCMap && !hasPalette) { + _repInfo.setMessage (new ErrorMessage + ("JP2 Header has Component Mapping box without Palette Box", + _module.getFilePos ())); + _repInfo.setValid (false); + } + if (!hasCMap && hasPalette) { + _repInfo.setMessage (new ErrorMessage + ("JP2 Header has Palette box without Component Mapping Box", + _module.getFilePos ())); + _repInfo.setValid (false); + } + // If there were any Associations, add a property for them. + Property a = makeAssocProperty (); + if (a != null) { + _module.addProperty(a); + } + finalizeBytesRead (); + return true; + } + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "JP2 Header Box"; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/JP2Strings.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/JP2Strings.java new file mode 100644 index 00000000..532d26a5 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/JP2Strings.java @@ -0,0 +1,146 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +/** + * String constants for JPEG 2000 module. + * This module contains no code, and all data are static. + * + * + * @author Gary McGath + * + */ +public final class JP2Strings { + + /** + * Private constructor, to make sure the class isn't inadvertently + * initiated. + */ + private JP2Strings () + { + } + + + /** Strings for method values in the color specification box. */ + public final static String methodStr[] = { + "", + "Enumerated Colorspace", + "Restricted ICC Profile", + "Any ICC Method", + "Vendor Color Method" + }; + + + /** Strings for values of enumCS in the color specification box. + * Only values 16-17 are recognized by JP2. + */ + public final static String enumCSStr[] = { + "Bilevel (1 = black)", + "YCbCr (1)", + "", + "YCbCr (2)", + "YCbCr (3)", + "", "", "", "", // 5-8 + "PhotoYCC", + "", + "CMY", // 11 + "CMYK", // 12 + "YCCK", + "CIELab", + "", + + "sRGB", // 16 (JP2) + "Greyscale", // 17 (JP2) + "Bilevel (1 = white)", + "CIEJab", + "e-sRGB", // 20 + "ROMM-RGB", + "sRGB based YCbCr", + "YPbPr (1125/60)", + "YPbPr (1250/50)" // 24 + }; + + /** Strings for the MTYP field of the Component Mapping box. */ + public final static String mtypStr[] = { + "Direct Use", + "Palette Mapping" + }; + + /** Strings for the opacity type in the Opacity Box. */ + public final static String opacityTypeStr[] = { + "Last channel is opacity channel", + "Last channel is premultiplied opacity channel", + "Chroma key transparency" + }; + + /** Strings for the number type value in the Number + * List box. Types must be normalized by shifting + * the high byte right 24 bits before indexing. + */ + public final static String numberListTypeStr[] = { + "Rendered result", + "Codestream number", + "Compositing layer", + "Numbered entity" + }; + + /** Strings for types in the Digital Signature Box. */ + public final static String digitalSigTypeStr[] = { + "MD5 checksum", + "SHA-1 checksum", + "DSA signature", + "RSA signature on MD5 digest", + "RSA signature on SHA-1 digest", + "Cryptographic Message Syntax" + }; + + + /** Strings for pointer types in the Digital Signature Box. */ + public final static String digitalSigPtrTypeStr[] = { + "Whole file", + "Byte range" + }; + + /** Strings for the "region of interest present in codestream" field + * of the ROI box. */ + public final static String inCodestreamStr[] = { + "Codestream does not contain static region of interest", + "Codestream contains static region of interest" + }; + + /** Strings for the region type field of the ROI box. */ + public final static String roiTypeStr[] = { + "Rectangular", + "Elliptical" + }; + + /** Strings for the channel type field of the channel definition box, + * indexed by ctypIdx. */ + public final static String ctypStr[] = { + "Color image data", + "Opacity", + "Premultiplied opacity", + "Not specified" // 2^16 - 1 + }; + + /** Indexes for ctypStr. */ + public final static int ctypIdx[] = {0, 1, 2, 65535}; + + /** Strings for the approx field of the color specification box, + * indexed by approxIdx. A zero value will be reported as + * an Integer property of 0. */ + public final static String approxStr[] = { + "Accurate representation", + "Approximation with exceptional quality", + "Approximation with reasonable quality", + "Approximation with poor quality" + }; + + + /** Indexes for approxStr. */ + public final static int approxIdx[] = {1, 2, 3, 4}; +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/LabelBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/LabelBox.java new file mode 100644 index 00000000..fbdb2b81 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/LabelBox.java @@ -0,0 +1,82 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import edu.harvard.hul.ois.jhove.*; +import java.io.*; + +/** + * Label box. + * A Label box does nothing in itself; it simply makes its + * label string available for its superbox. + * + * See ISO/IEC FCD15444-2: 2000, L.9.13 + * + * @author Gary McGath + * + */ +public class LabelBox extends JP2Box { + + /* The label text. */ + private String _label; + + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + */ + public LabelBox(RandomAccessFile raf, BoxHolder parent) { + super(raf, parent); + } + + /** Reads the box, saving the label text. + * setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + if (_parentBox == null) { + wrongBoxContext(); + return false; + } + byte[] byteBuf = new byte [(int) _boxHeader.getDataLength()]; + ModuleBase.readByteBuf (_dstrm, byteBuf, _module); + _label = new String (byteBuf, "UTF-8"); + return true; + } + + + /** Returns the label string. Valid only after + * readBox() has been called. + */ + protected String getLabel () + { + return _label; + } + + /** Returns a Property which describes the Box, for use + * by Association boxes and perhaps others. + */ + protected Property getSelfPropDesc () + { + return new Property (DESCRIPTION_NAME, + PropertyType.STRING, + _label); + } + + + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "Label Box"; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/MainOrTile.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/MainOrTile.java new file mode 100644 index 00000000..d54370fe --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/MainOrTile.java @@ -0,0 +1,124 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import edu.harvard.hul.ois.jhove.*; +import java.util.*; + + +/** + * + * Abstract superclass for Codestream and Tile classes. + * Many marker segments can apply either to the codestream + * as a whole or to specific tiles; this class merges those + * features in a single place. + * + * @author Gary McGath + * + */ +public abstract class MainOrTile { + + /* Default value */ + protected final static int NULL = -1; + + /* Precinct size array */ + protected int[] _precSize; + + /** Array of components. This is created when the SIZ + * marker segment reports the number of components. */ + protected Property[] _components; + + /** Coding style default property */ + protected Property _codProperty; + + /** Quantization default property */ + protected Property _qcdProperty; + + /** Progression order change property */ + protected Property _pocProperty; + + /** Comments -- list of properties */ + protected List _comments; + + /* List of packet lengths */ + protected List _packetLengthList; + + + + + public MainOrTile () + { + _components = null; + _qcdProperty = null; + _codProperty = null; + _comments = new LinkedList (); + } + + + + /** Sets the number of components. As a side effect, + * creates the compoments array. This should be called + * from the SIZMarkerSegment class, and in a valid + * file will precede the setting of any components. + */ + public void setNumComponents (int nComp) + { + _components = new Property[nComp]; + } + + /** Sets a property indexed by component. */ + public void setCompProperty (int idx, Property prop) + { + if (_components != null && _components.length > idx) { + _components[idx] = prop; + } + } + + /** Gets the number of components. */ + protected int getNumComponents () + { + if (_components == null) { + return 0; + } + else { + return _components.length; + } + } + + /** Sets the coding style default property. */ + public void setCODProperty (Property prop) + { + _codProperty = prop; + } + + /** Sets the quantization default property. */ + public void setQCDProperty (Property prop) + { + _qcdProperty = prop; + } + + /** Sets the progression order change property. */ + public void setPOCProperty (Property prop) + { + _pocProperty = prop; + } + + /** Adds a property to the comment list */ + public void addComment (Property comment) + { + _comments.add (comment); + } + + + /** Add a packet length to the list of packet lengths. */ + public void addPacketLength (long len) + { + if (_packetLengthList == null) { + _packetLengthList = new LinkedList (); + } + _packetLengthList.add (new Long (len)); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/Marker.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/Marker.java new file mode 100644 index 00000000..331d7cb4 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/Marker.java @@ -0,0 +1,40 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; + +/** + * This is a subclass of MarkerSegment for Markers. + * Markers are those elements of a codestream which have + * no parameters. It can be subclassed for specific Markers, + * or used directly to provide default behavior. + * + * @author Gary McGath + * + */ +public class Marker extends MarkerSegment { + + public Marker () + { + } + + /** Overrides the superclass to return 0 without consuming + * any bytes from the DataInputStream. + */ + protected int readMarkLen () throws IOException + { + return 0; + } + + + /** Default processing. Does nothing, and always returns true. */ + protected boolean process (int bytesToEat) + { + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/MarkerSegment.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/MarkerSegment.java new file mode 100644 index 00000000..cef68531 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/MarkerSegment.java @@ -0,0 +1,231 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.Jpeg2000Module; + + +/** + * Abstract superclass for marker segments. + * + * @author Gary McGath + * + */ +public abstract class MarkerSegment { + + protected final static int + SOC = 0X4F, // start of codestream + COD = 0X52, // coding style default + COC = 0X53, // coding style component + TLM = 0X55, // tile-part lengths + PLM = 0X57, // packet length, main header + PLT = 0X58, // packet length, tile-part header + QCD = 0X5C, // quantization default + QCC = 0X5D, // quantization component + RGN = 0X5E, // region of interest + POC = 0X5F, // progression order change + PPM = 0X60, // Packed packet headers, main header + PPT = 0X61, // packed packet headers, tile-part header + CRG = 0X63, // component registration + COM = 0X64, // comment + SOT = 0X90, // start of tile part + SOP = 0X91, // start of packet + EPH = 0X92, // end of packet header + SOD = 0X93, // start of data + EOC = 0XD9, // end of codestream + SIZ = 0X51; // image and tile size + + protected ContCodestream _ccs; + protected Codestream _cs; + protected Jpeg2000Module _module; + protected DataInputStream _dstream; + protected RepInfo _repInfo; + + /** + * Constructor. + * After an instance of a MarkerSegment is created, + * the setter methods setContCodestream, + * setCodestream, setModule, + * and setDataInputStream must all be called as + * part of the setup before process is called. + */ + public MarkerSegment () + { + } + + /** Sets the Continuous Codestream from which this marker was + * obtained. + */ + public void setContCodestream (ContCodestream ccs) + { + _ccs = ccs; + } + + /** Sets the Codestream object being built. */ + public void setCodestream (Codestream cs) + { + _cs = cs; + } + + /** Sets the Module under which all this is happening. */ + public void setModule (Jpeg2000Module module) + { + _module = module; + } + + /** Sets the DataInputStream over which this marker is being + * read. + */ + public void setDataInputStream (DataInputStream dstream) + { + _dstream = dstream; + } + + /** Sets the RepInfo into which messages may be placed. */ + public void setRepInfo (RepInfo repInfo) + { + _repInfo = repInfo; + } + + /** Returns true if this segment is a Marker. + * Will return false unless overridden. */ + public boolean isMarker () + { + return false; + } + + /** Static factory method for generating an object of the + * appropriate subclass of MarkerSegment, based on the + * marker code. + * + * @param markerCode The 8-bit marker code (ignoring the FF). */ + protected static MarkerSegment markerSegmentMaker (int markerCode) + { + switch (markerCode) { + case SOT: + return new SOTMarkerSegment (); + + case COC: + return new COCMarkerSegment (); + + case COD: + return new CODMarkerSegment (); + + case COM: + return new CommentMarkerSegment (); + + case CRG: + return new CRGMarkerSegment (); + + case PLM: + return new PLMMarkerSegment (); + + case PLT: + return new PLTMarkerSegment (); + + case POC: + return new POCMarkerSegment (); + + case PPM: + return new PPMMarkerSegment (); + + case PPT: + return new PPTMarkerSegment (); + + case QCC: + return new QCCMarkerSegment (); + + case QCD: + return new QCDMarkerSegment (); + + case RGN: + return new RGNMarkerSegment (); + + case SIZ: + return new SIZMarkerSegment (); + + case TLM: + return new TLMMarkerSegment (); + + case SOC: // start of codestream + case EPH: // end of packet header + case SOD: // start of data + case EOC: + return new Marker (); + + // SOP won't be implemented, at least for the time + // being, since it occurs within the bitstream data + // of a codestream, which we don't analyze. + case SOP: + default: + return new DefaultMarkerSegment (); + } + } + + /** Reads and returns the length field of the marker segment. + * The setter methods setModule + * and setDataInputStream must be called as + * part of the setup before readMarkLen is called. + */ + protected int readMarkLen () throws IOException + { + return _module.readUnsignedShort (_dstream); + } + + + /** Determines size of fields indexed by number of components. + * Some marker segments have fields which are 1 byte long if + * the number of components is 1-255, and 2 bytes long if + * the number of components is 256-65535. + * + * @return 0 if number of components not yet set, otherwise 1 or 2 + */ + protected int nCompBytes () + { + int nComp = _cs.getNumComponents (); + if (nComp == 0) { + return 0; // indicates an error condition + } + int compIdx; + // size of Ccoc field depends on number of components + return (nComp < 257 ? 1 : 2); + } + + + /** Returns the MainOrTile object which is currently + * applicable in the Contiguous Codestream. If the + * Contiguous Codestream has a current Tile, that is + * returned; otherwise the Codestream object established + * by setCodestream is returned. + */ + protected MainOrTile getMainOrTile () + { + Tile tile = _ccs.getCurTile (); + if (tile != null) { + return tile; + } + else { + return _cs; + } + } + + /** Process the marker or marker segment. The DataInputStream + * will be at the point of having read the marker code. The + * process method must consume exactly the number + * of bytes remaining in the marker segment; for a marker, + * this number will always be 0. + * + * @param bytesToEat The number of bytes that must be consumed. + * For a Marker, this number will always be 0. + * If it is 0 for a MarkerSegment, the + * number of bytes to consume is unknown. + * @return true if segment is well-formed, + * false otherwise. + */ + protected abstract boolean process (int bytesToEat) throws IOException; +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/NumberListBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/NumberListBox.java new file mode 100644 index 00000000..8bd1e2f8 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/NumberListBox.java @@ -0,0 +1,96 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import edu.harvard.hul.ois.jhove.*; +import java.io.*; + +/** + * Number list box. + * Provides a list of numbers with types. + * It's apparently used only within an Association + * box, so it simply makes a property available. + * + * See ISO/IEC FCD15444-2: 2000, L.9.12 + * + * @author Gary McGath + * + */ +public class NumberListBox extends JP2Box { + + private Property[] propArray; + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + */ + public NumberListBox(RandomAccessFile raf, BoxHolder parent) { + super(raf, parent); + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + initBytesRead (); + + int numEntries = (int) _boxHeader.getDataLength () / 4; + if (numEntries > 0) { + propArray = new Property[numEntries]; + for (int i = 0; i < numEntries; i++) { + long num = _module.readUnsignedInt (_dstrm); + // High byte is type, low three bytes are the + // number. + int typeByte = (int) ((num & 0XFF000000L) >> 24); + int numValue = (int) (num & 0XFFFFFF); + App app = _module.getApp (); + Property[] p = new Property[2]; + p[0] = _module.addIntegerProperty("Type", + typeByte, + JP2Strings.numberListTypeStr); + p[1] = new Property ("Value", + PropertyType.INTEGER, + new Integer (numValue)); + propArray[i] = new Property ("Number", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + p); + } + } + finalizeBytesRead (); + return true; + } + + /** Returns a Property which describes the Box, for use + * by Association boxes and perhaps others. + */ + protected Property getSelfPropDesc () + { + if (propArray != null) { + return new Property (DESCRIPTION_NAME, + PropertyType.PROPERTY, + PropertyArity.ARRAY, + propArray); + } + else { + // A number list with no numbers isn't explicitly illegal + return null; + } + } + + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "Number List Box"; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/OpacityBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/OpacityBox.java new file mode 100644 index 00000000..0265a537 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/OpacityBox.java @@ -0,0 +1,105 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import edu.harvard.hul.ois.jhove.*; +import java.io.*; +import java.util.*; + +/** + * Opacity Box (JPX). + * See ISO/IEC FCD15444-2: 2000, L.9.4.6 + * + * + * @author Gary McGath + * + */ +public class OpacityBox extends JP2Box { + + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + */ + public OpacityBox(RandomAccessFile raf, BoxHolder parent) { + super(raf, parent); + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + if (!(_parentBox instanceof ComposLayerHdrBox)) { + wrongBoxContext(); + return false; + } + initBytesRead (); + + List propList = new ArrayList (4); + + App app = _module.getApp (); + int otyp = ModuleBase.readUnsignedByte (_dstrm, _module); + propList.add (_module.addIntegerProperty ("Type", + otyp, + JP2Strings.opacityTypeStr)); + if (otyp > 2) { + _repInfo.setMessage (new ErrorMessage + ("Invalid OTyp field in Opacity Box", + _module.getFilePos ())); + _repInfo.setValid (false); + return false; + } + + // The documentation of the Opacity Box is self-contradictory + // with regard to what OTyp values are + // followed by NCH and CV[n] fields. (There is also + // a reference to an unspecified "PR" field.) + // The only safe course is to see if there are any more bytes. + int bytesLeft = (int) _boxHeader.getDataLength () - 1; + if (bytesLeft > 0) { + int nch = ModuleBase.readUnsignedByte (_dstrm, _module); + // The size in bytes of the channel-key values + // depends on the bit depth of the corresponding + // channel, but it's simpler to calculate it based + // on the bytes remaining. + int[] keys = new int[nch]; + int keysize = (bytesLeft - 1) / nch; + for (int i = 0; i < nch; i++) { + int chkey = 0; + for (int j = 0; j < keysize; j++) { + chkey = (chkey << 8) + + ModuleBase.readUnsignedByte (_dstrm, _module); + } + keys[i] = chkey; + } + propList.add (new Property ("ChromaKeyValues", + PropertyType.INTEGER, + PropertyArity.ARRAY, + keys)); + } + ((ComposLayerHdrBox) _parentBox).addOpacity + (new Property ("Opacity", + PropertyType.PROPERTY, + PropertyArity.LIST, + propList)); + + finalizeBytesRead (); + return true; + } + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "Opacity Box"; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/PLMMarkerSegment.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/PLMMarkerSegment.java new file mode 100644 index 00000000..82711ec2 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/PLMMarkerSegment.java @@ -0,0 +1,85 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; +//import java.util.*; +import edu.harvard.hul.ois.jhove.*; + +/** + * Class for the PLM Marker segment. This gives packet lengths at + * the header level. + * + * @author Gary McGath + * + */ +public class PLMMarkerSegment extends MarkerSegment { + + // A tile part may extend across several marker segments. + // we store the remainder statically here. + static int nplmLeft; + + /** + * Constructor. + */ + public PLMMarkerSegment() { + super(); + } + + /** + * Processes the marker segment. The DataInputStream + * will be at the point of having read the marker code. The + * process method must consume exactly the number + * of bytes remaining in the marker segment. + * + * @param bytesToEat The number of bytes that must be consumed. + * If it is 0 for a MarkerSegment, the + * number of bytes to consume is unknown. + */ + protected boolean process(int bytesToEat) throws IOException { + int zplm = ModuleBase.readUnsignedByte (_dstream, _module); + --bytesToEat; + if (zplm == 0) { + nplmLeft = 0; + } + // Whether there is an nplm, giving the number of bytes + // of iplm information for the tile part, depends on whether + // the previous nplm has been counted out. This is actually + // quite excessive, since nplm can't be any bigger than 255. + // Who DESIGNED this silly marker segment anyway? + if (nplmLeft == 0) { + nplmLeft = ModuleBase.readUnsignedByte (_dstream, _module); + --bytesToEat; + } + + // To add to the complications, each iplm can have a different + // length. This allows unlimited packet lengths -- + // or to be exact, the maximum length is 2 ^ (7 * 255) if there's + // only one packet. For this implementation, we limit the maximum + // packet length to 2 ^ 63. + while (bytesToEat > 0) { + long pktLen = 0; + for (;;) { + int pkByte = ModuleBase.readUnsignedByte (_dstream, _module); + if (--bytesToEat < 0) { + // bytes of a number can't cross marker segment boundaries + _repInfo.setMessage (new ErrorMessage + ("Packet length in PLM marker segment crosses segment boundaries")); + return false; + } + pktLen = (pktLen << 7) | (pkByte | 0X7F); + if ((pkByte & 0X80) == 0) { + break; + } + _cs.addPacketLength (pktLen); + } + } + + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/PLTMarkerSegment.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/PLTMarkerSegment.java new file mode 100644 index 00000000..6a68e4dd --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/PLTMarkerSegment.java @@ -0,0 +1,73 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; +//import java.util.*; +import edu.harvard.hul.ois.jhove.*; + +/** + * Class for the PLT Marker segment. This gives packet lengths at + * the tile level. + * + * @author Gary McGath + * + */ +public class PLTMarkerSegment extends MarkerSegment { + + /** + * Constructor. + */ + public PLTMarkerSegment() { + super(); + } + + /** + * Processes the marker segment. The DataInputStream + * will be at the point of having read the marker code. The + * process method must consume exactly the number + * of bytes remaining in the marker segment. + * + * @param bytesToEat The number of bytes that must be consumed. + * If it is 0 for a MarkerSegment, the + * number of bytes to consume is unknown. + */ + protected boolean process(int bytesToEat) throws IOException { + Tile tile = _ccs.getCurTile (); + if (tile == null) { + _repInfo.setMessage (new ErrorMessage + ("PLT marker segment not allowed in codestream header")); + return false; // a tile (SOT) is required + } + int zplt = ModuleBase.readUnsignedByte (_dstream, _module); + --bytesToEat; + // As with PLM, each iplt can have a different + // length. This allows unlimited packet lengths -- + // or to be exact, the maximum length is 2 ^ (7 * 255) if there's + // only one packet. For this implementation, we limit the maximum + // packet length to 2 ^ 63. + while (bytesToEat > 0) { + long pktLen = 0; + for (;;) { + int pkByte = ModuleBase.readUnsignedByte (_dstream, _module); + if (--bytesToEat < 0) { + // bytes of a number can't cross marker segment boundaries + _repInfo.setMessage (new ErrorMessage + ("Packet length in PLT marker segment crosses segment boundaries")); + return false; + } + pktLen = (pktLen << 7) | (pkByte | 0X7F); + if ((pkByte & 0X80) == 0) { + break; + } + tile.addPacketLength (pktLen); + } + } + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/POCMarkerSegment.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/POCMarkerSegment.java new file mode 100644 index 00000000..bbbb72da --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/POCMarkerSegment.java @@ -0,0 +1,109 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; +import edu.harvard.hul.ois.jhove.*; + +/** + * Class for the POC (Progression order change) marker segment. + * May occur in the main or the tile part header. + * + * @author Gary McGath + * + */ +public class POCMarkerSegment extends MarkerSegment { + + /** + * + */ + public POCMarkerSegment() { + super(); + } + + /** Process the marker segment. The DataInputStream + * will be at the point of having read the marker code. The + * process method must consume exactly the number + * of bytes remaining in the marker segment. + * + * @param bytesToEat The number of bytes that must be consumed. + * If it is 0 for a MarkerSegment, the + * number of bytes to consume is unknown. + * + * @return true if segment is well-formed, + * false otherwise. + */ + protected boolean process(int bytesToEat) throws IOException { + int compIdxBytes = nCompBytes(); + if (compIdxBytes == 0) { + _repInfo.setMessage (new ErrorMessage + ("POC marker segment at wrong position in codestream")); + // POC found before SIZ + return false; + } + // The number of bytes per change depends on whether component + // indices take one or two bytes. + int changeSize = compIdxBytes < 257 ? 7 : 9; // number of bytes per change + int nChanges = bytesToEat / changeSize; + // Make sure it's an even multiple + if (changeSize * nChanges != bytesToEat) { + _repInfo.setMessage (new ErrorMessage + ("Invalid size for POC marker segment")); + return false; + } + Property[] changes = new Property[nChanges]; + for (int i = 0; i < nChanges; i++) { + int rspoc = _module.readUnsignedShort (_dstream); // resolution level idx + int cspoc; + // size of Ccoc field depends on number of components + if (compIdxBytes < 257) { + cspoc = ModuleBase.readUnsignedByte (_dstream, _module); + } + else{ + cspoc = _module.readUnsignedShort (_dstream); + } + int lyepoc = _module.readUnsignedShort (_dstream); + int repoc = ModuleBase.readUnsignedByte (_dstream, _module); + int cepoc; + if (compIdxBytes < 257) { + cepoc = ModuleBase.readUnsignedByte (_dstream, _module); + } + else { + cepoc = _module.readUnsignedShort (_dstream); + } + int ppoc = ModuleBase.readUnsignedByte (_dstream, _module); + Property[] propArr = new Property[5]; + propArr[0] = new Property ("StartResolutionLevelIndex", + PropertyType.INTEGER, + new Integer (rspoc)); + propArr[1] = new Property ("ComponentIndex", + PropertyType.INTEGER, + new Integer (cspoc)); + propArr[2] = new Property ("LayerIndex", + PropertyType.INTEGER, + new Integer (lyepoc)); + propArr[3] = new Property ("EndResolutionLevelIndex", + PropertyType.INTEGER, + new Integer (cepoc)); + propArr[4] = new Property ("ProgressionOrder", + PropertyType.INTEGER, + new Integer (ppoc)); + changes[i] = new Property ("Change", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + propArr); + } + MainOrTile cs = getMainOrTile (); + cs.setPOCProperty (new Property ("ProgressionOrderChange", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + changes)); + + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/PPMMarkerSegment.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/PPMMarkerSegment.java new file mode 100644 index 00000000..3fab22e7 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/PPMMarkerSegment.java @@ -0,0 +1,63 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; +import edu.harvard.hul.ois.jhove.*; + +/** + * Class for the PPM (Packed packet headers, main header) + * marker segment. I'm assuming for the present that the + * full details of packet headers is getting deeper into + * the bits than we want, so it just checks some basic + * information. There can be multiple PPM marker segments. + * + * @author Gary McGath + * + */ +public class PPMMarkerSegment extends MarkerSegment { + + /** + * Constructor. + */ + public PPMMarkerSegment() { + super(); + _ccs.setPPMSeen (true); + } + + + /** + * Processes the marker segment. The DataInputStream + * will be at the point of having read the marker code. The + * process method must consume exactly the number + * of bytes remaining in the marker segment. + * + * @param bytesToEat The number of bytes that must be consumed. + * If it is 0 for a MarkerSegment, the + * number of bytes to consume is unknown. + */ + protected boolean process(int bytesToEat) throws IOException + { + // Get index of this segment + int zppm = ModuleBase.readUnsignedByte (_dstream, _module); + --bytesToEat; + + while (bytesToEat > 0) { + // Number of bytes of Ippm info in the ith tile part + long nppm = _module.readUnsignedInt (_dstream); + bytesToEat -= 4; + if (nppm > bytesToEat) { + _repInfo.setMessage(new ErrorMessage + ("Invalid length for tile-part header in PPM packet")); + return false; + } + _cs.addPPMLength (nppm); + _module.skipBytes (_dstream, (int) nppm, _module); + bytesToEat -= nppm; + } + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/PPTMarkerSegment.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/PPTMarkerSegment.java new file mode 100644 index 00000000..37c10a97 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/PPTMarkerSegment.java @@ -0,0 +1,70 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; +import edu.harvard.hul.ois.jhove.*; + +/** + * Class for the PPT (Packed packet headers, tile-part header) + * marker segment. Similar to the PPM marker segment, but + * applicable to tile parts rather than the main header. + * + * @author Gary McGath + * + */ +public class PPTMarkerSegment extends MarkerSegment { + + /** + * Constructor. + */ + public PPTMarkerSegment() { + super(); + } + + /** + * Processes the marker segment. The DataInputStream + * will be at the point of having read the marker code. The + * process method must consume exactly the number + * of bytes remaining in the marker segment. + * + * @param bytesToEat The number of bytes that must be consumed. + * If it is 0 for a MarkerSegment, the + * number of bytes to consume is unknown. + */ + protected boolean process(int bytesToEat) throws IOException + { + if (_ccs.isPPMSeen ()) { + _repInfo.setMessage (new ErrorMessage + ("PPT and PPM not allowed in same codestream")); + return false; + } + Tile tile = _ccs.getCurTile (); + if (tile == null ) { + _repInfo.setMessage (new ErrorMessage + ("PPT not allowed in codestream header")); + return false; + } + + int zppt = ModuleBase.readUnsignedByte (_dstream, _module); + --bytesToEat; + while (bytesToEat > 0) { + // Number of bytes of Ippm info in the ith tile part + long nppt = _module.readUnsignedInt (_dstream); + bytesToEat -= 4; + if (nppt > bytesToEat) { + _repInfo.setMessage(new ErrorMessage + ("Invalid length for tile-part header in PPM packet")); + return false; + } + tile.addPPTLength (nppt); + _module.skipBytes (_dstream, (int) nppt, _module); + bytesToEat -= nppt; + } + + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/PaletteBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/PaletteBox.java new file mode 100644 index 00000000..0ceb9730 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/PaletteBox.java @@ -0,0 +1,130 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import edu.harvard.hul.ois.jhove.*; +import java.io.*; + +/** + * Palette box. + * See I.5.3.4 in ISO/IEC 15444-1:2000 + * + * @author Gary McGath + * + */ +public class PaletteBox extends JP2Box { + + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + */ + public PaletteBox (RandomAccessFile raf, BoxHolder parent) + { + super (raf, parent); + } + + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + if (!(_parentBox instanceof JP2HeaderBox)) { + _repInfo.setMessage (new ErrorMessage + ("Image Header Box in illegal context", _module.getFilePos ())); + return false; + } + initBytesRead (); + //_module.setPaletteSeen (true); + int len = (int) _boxHeader.getDataLength (); + long startNByte = _module.getFilePos(); + // Track how many bytes to skip + + int ne = _module.readUnsignedShort (_dstrm); + // 2 bytes have been read + if (ne < 1 || ne > 1024) { + _repInfo.setMessage (new ErrorMessage + ("Palette must have 1 to 1024 entries", _module.getFilePos())); + _repInfo.setValid (false); // But keep going anyway + } + Property[] subProp = new Property[4]; + subProp[0] = new Property ("Entries", PropertyType.INTEGER, + new Integer (ne)); + + int nc = ModuleBase.readUnsignedByte (_dstrm, _module); + // 3 bytes have been read + int bytesRead = 3; + subProp[1] = new Property ("Components", PropertyType.INTEGER, + new Integer (nc)); + + // Each component can, in principle, have a different bit depth, + // and each can separately be signed or unsigned. + int[] bpc = new int [nc]; + boolean[] cmpsigned = new boolean [nc]; + for (int i = 0; i < nc; i++) { + int b = ModuleBase.readUnsignedByte (_dstrm, _module); + cmpsigned[i] = ((b & 0X80) != 0); + bpc[i] = (b & 0X7F) + 1; + } + bytesRead += nc; + + subProp[2] = new Property ("BitDepth", PropertyType.INTEGER, + PropertyArity.ARRAY, bpc); + + // Now the actual component value arrays. Skip this if + // ne is out of bounds. + if (ne > 1024 || ne < 1) { + subProp[3] = new Property ("Values", PropertyType.STRING, + "Invalid"); + } + else { + Property[] cprop = new Property[nc]; + for (int i = 0; i < nc; i++) { + int[] c = new int[ne]; + for (int j = 0; j < ne; j++) { + c[j] = ModuleBase.readUnsignedByte (_dstrm, _module); + } + cprop[i] = new Property ("Component", + PropertyType.INTEGER, + PropertyArity.ARRAY, + c); + } + subProp[3] = new Property ("Values", PropertyType.PROPERTY, + PropertyArity.ARRAY, + cprop); + bytesRead += nc * ne; + } + Property palProp = new Property ("Palette", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + subProp); + if (_parentBox instanceof CodestreamHeaderBox) { + Codestream cs = ((CodestreamHeaderBox) _parentBox).getCodestream (); + cs.setPaletteProperty (palProp); + } + else { + _module.addProperty (palProp); + } + // Skip any bytes we haven't read + _module.skipBytes (_dstrm, + (int) (len - (_module.getFilePos() - startNByte)), _module); + + finalizeBytesRead (); + return true; + } + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "Palette Box"; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/QCCMarkerSegment.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/QCCMarkerSegment.java new file mode 100644 index 00000000..8c4b5ce6 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/QCCMarkerSegment.java @@ -0,0 +1,113 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; +import java.util.*; +import edu.harvard.hul.ois.jhove.*; + +/** + * Class for the QCC (Quantization component) marker segment. + * May occur in the main or the tile part header. In the + * main header it overrides the QCD for the specified + * component. In the tile part header it overrides the + * QCD for the component in the tile part. + * + * @author Gary McGath + * + */ +public class QCCMarkerSegment extends MarkerSegment { + + /** + * + */ + public QCCMarkerSegment() { + super(); + } + + /** Process the marker segment. The DataInputStream + * will be at the point of having read the marker code. The + * process method must consume exactly the number + * of bytes remaining in the marker segment. + * + * @param bytesToEat The number of bytes that must be consumed. + * If it is 0 for a MarkerSegment, the + * number of bytes to consume is unknown. + * + * @return true if segment is well-formed, + * false otherwise. + */ + protected boolean process(int bytesToEat) + throws IOException + { + int compIdxBytes = nCompBytes(); + if (compIdxBytes == 0) { + // QCC found before SIZ + _repInfo.setMessage (new ErrorMessage + ("QCC marker segment at wrong position in codestream")); + return false; + } + int compIdx; + int bytesEaten; + // size of Ccoc field depends on number of components + if (compIdxBytes < 257) { + compIdx = ModuleBase.readUnsignedByte (_dstream, _module); + bytesEaten = 1; + } + else{ + compIdx = _module.readUnsignedShort (_dstream); + bytesEaten = 2; + } + int sqcc = ModuleBase.readUnsignedByte (_dstream, _module); + bytesEaten++; + + int sqccLow = sqcc & 0X1F; + int nspqcc; + int spqcc[]; + switch (sqccLow) { + case 0: + // no quantization -- byte entries in spqcd + nspqcc = bytesToEat - bytesEaten; + spqcc = new int[nspqcc]; + for (int i = 0; i < nspqcc; i++) { + spqcc[i] = ModuleBase.readUnsignedByte (_dstream, _module); + } + break; + + case 1: + // scalar derived (just 2 bytes of value) + case 2: + // scalar expounded + nspqcc = (bytesToEat - bytesEaten) / 2; + spqcc = new int[nspqcc]; + for (int i = 0; i < nspqcc; i++) { + spqcc[i] = _module.readUnsignedShort (_dstream); + } + break; + default: + _repInfo.setMessage (new ErrorMessage + ("Unrecognized quantization type in QCC marker segment")); + return false; // reserved value + } + + MainOrTile cs = getMainOrTile (); + List propList = new ArrayList (2); + propList.add (new Property ("QuantizationStyle", + PropertyType.INTEGER, + new Integer (sqcc))); + propList.add (new Property ("StepValue", + PropertyType.INTEGER, + PropertyArity.ARRAY, + spqcc)); + cs.setCompProperty (compIdx, + new Property ("QuantizationComponent", + PropertyType.PROPERTY, + PropertyArity.LIST, + propList)); + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/QCDMarkerSegment.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/QCDMarkerSegment.java new file mode 100644 index 00000000..d269d70c --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/QCDMarkerSegment.java @@ -0,0 +1,89 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import edu.harvard.hul.ois.jhove.*; +import java.io.*; +import java.util.*; + +/** + * Class for the QCD (Quantization default) marker segment. + * This comes either in the main header or + * after an SOT. + * + * @author Gary McGath + * + */ +public class QCDMarkerSegment extends MarkerSegment { + + /** + * Constructor. + */ + public QCDMarkerSegment() { + super(); + } + + /** Process the marker segment. The DataInputStream + * will be at the point of having read the marker code. The + * process method must consume exactly the number + * of bytes remaining in the marker segment. + * + * @param bytesToEat The number of bytes that must be consumed. + * If it is 0 for a MarkerSegment, the + * number of bytes to consume is unknown. + * + * @return true if segment is well-formed, + * false otherwise. + */ + protected boolean process(int bytesToEat) throws IOException { + int sqcd = ModuleBase.readUnsignedByte (_dstream, _module); + // What follows depends on the value of sqcd in a messy way + int sqcdLow = sqcd & 0X1F; + int nspqcd; + int spqcd[]; + switch (sqcdLow) { + case 0: + // no quantization -- byte entries in spqcd + nspqcd = bytesToEat - 1; + spqcd = new int[nspqcd]; + for (int i = 0; i < nspqcd; i++) { + spqcd[i] = ModuleBase.readUnsignedByte (_dstream, _module); + } + break; + + case 1: + // scalar derived (just 2 bytes of value) + case 2: + // scalar expounded + nspqcd = (bytesToEat - 1) / 2; + spqcd = new int[nspqcd]; + for (int i = 0; i < nspqcd; i++) { + spqcd[i] = _module.readUnsignedShort (_dstream); + } + break; + + default: + _repInfo.setMessage (new ErrorMessage + ("Unrecognized quantization type in QCD marker segment")); + return false; // reserved value + } + List propList = new ArrayList (2); + propList.add (new Property ("QuantizationStyle", + PropertyType.INTEGER, + new Integer (sqcd))); + propList.add (new Property ("StepValue", + PropertyType.INTEGER, + PropertyArity.ARRAY, + spqcd)); + MainOrTile cs = getMainOrTile (); + cs.setQCDProperty (new Property ("QuantizationDefault", + PropertyType.PROPERTY, + PropertyArity.LIST, + propList)); + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/RGNMarkerSegment.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/RGNMarkerSegment.java new file mode 100644 index 00000000..11064d04 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/RGNMarkerSegment.java @@ -0,0 +1,75 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import edu.harvard.hul.ois.jhove.*; +import java.io.*; +import java.util.*; + +/** + * Class for the RGN (region of interest) marker segment. + * This comes either in the main header or + * after an SOT. + * + * @author Gary McGath + * + */ +public class RGNMarkerSegment extends MarkerSegment { + + /** + * + */ + public RGNMarkerSegment() { + super(); + } + + /** + * Processes the marker segment. The DataInputStream + * will be at the point of having read the marker code. The + * process method must consume exactly the number + * of bytes remaining in the marker segment. + * + * @param bytesToEat The number of bytes that must be consumed. + * If it is 0 for a MarkerSegment, the + * number of bytes to consume is unknown. + */ + protected boolean process(int bytesToEat) throws IOException { + int compIdxBytes = nCompBytes(); + if (compIdxBytes == 0) { + // RGN found before SIZ + _repInfo.setMessage (new ErrorMessage + ("RGN marker segment at wrong position in codestream")); + return false; + } + int compIdx; + // size of Ccoc field depends on number of components + if (compIdxBytes < 257) { + compIdx = ModuleBase.readUnsignedByte (_dstream, _module); + } + else{ + compIdx = _module.readUnsignedShort (_dstream); + } + int srgn = ModuleBase.readUnsignedByte (_dstream, _module); + int sprgn = ModuleBase.readUnsignedByte (_dstream, _module); + MainOrTile cs = getMainOrTile (); + + List propList = new ArrayList (2); + propList.add (new Property ("ROIStyle", + PropertyType.INTEGER, + new Integer (srgn))); + propList.add (new Property ("ROIParameter", + PropertyType.INTEGER, + new Integer (sprgn))); + cs.setCompProperty (compIdx, + new Property ("RegionOfInterest", + PropertyType.PROPERTY, + PropertyArity.LIST, + propList)); + + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ROIBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ROIBox.java new file mode 100644 index 00000000..07a09985 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ROIBox.java @@ -0,0 +1,138 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import edu.harvard.hul.ois.jhove.*; +import java.io.*; +import java.util.*; + +/** + * ROI Description box (JPX). + * See ISO/IEC FCD15444-2: 2000, L.9.16 + * + * @author Gary McGath + * + */ +public class ROIBox extends JP2Box { + + private Property roiProp; + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + */ + public ROIBox (RandomAccessFile raf, BoxHolder parent) + { + super (raf, parent); + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + final String baddata = "Invalid data in ROI"; + initBytesRead (); + int nroi = ModuleBase.readUnsignedByte (_dstrm, _module); + + List propList = new ArrayList (nroi); + JhoveBase je = _module.getBase (); + boolean raw = je.getShowRawFlag (); + for (int i = 0; i < nroi; i++) { + List roiPropList = new ArrayList (7); + int incs = ModuleBase.readUnsignedByte (_dstrm, _module); + if (incs > 1) { + _repInfo.setMessage (new ErrorMessage + (baddata, _module.getFilePos ())); + _repInfo.setValid (false); + } + roiPropList.add (_module.addIntegerProperty("InCodestream", + incs, JP2Strings.inCodestreamStr)); + + int rtyp = ModuleBase.readUnsignedByte (_dstrm, _module); + if (rtyp > 1) { + _repInfo.setMessage (new ErrorMessage + ("Invalid region type in ROI Box", _module.getFilePos ())); + _repInfo.setValid (false); + } + roiPropList.add (_module.addIntegerProperty("RegionType", + rtyp, JP2Strings.roiTypeStr)); + + int rcp = ModuleBase.readUnsignedByte (_dstrm, _module); + roiPropList.add (new Property ("CodingPriority", + PropertyType.INTEGER, + new Integer (rcp))); + + long lcx = _module.readUnsignedInt (_dstrm); + roiPropList.add (new Property ("HorizontalLocation", + PropertyType.LONG, + new Long (lcx))); + long lcy = _module.readUnsignedInt (_dstrm); + roiPropList.add (new Property ("HorizontalLocation", + PropertyType.LONG, + new Long (lcy))); + long wdt = _module.readUnsignedInt (_dstrm); + roiPropList.add (new Property ("Width", + PropertyType.LONG, + new Long (wdt))); + long hth = _module.readUnsignedInt (_dstrm); + roiPropList.add (new Property ("Height", + PropertyType.LONG, + new Long (hth))); + + propList.add (new Property ("ROI", + PropertyType.PROPERTY, + PropertyArity.LIST, + roiPropList)); + } + roiProp = new Property ("ROIs", + PropertyType.PROPERTY, + PropertyArity.LIST, + propList); + // If the parent box is an Association box, the property + // will be put into the Association property, so there's no + // need to put it in two places. + if (!(_parentBox instanceof AssociationBox)) { + if (_parentBox instanceof CodestreamHeaderBox) { + Codestream cs = ((CodestreamHeaderBox) _parentBox).getCodestream (); + cs.setROIProperty (roiProp); + } + else { + _module.addProperty (roiProp); + } + } + finalizeBytesRead (); + return true; + } + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "ROI Description Box"; + } + + /** Returns a Property which describes the box. This is + * used as a subproperty of the Property returned by + * selfDescProperty. + */ + protected Property getSelfPropDesc (){ + Property descProp; + if (roiProp != null) { + return new Property (DESCRIPTION_NAME, + PropertyType.PROPERTY, + roiProp); + } + else { + return null; + } + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ReaderRequirementsBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ReaderRequirementsBox.java new file mode 100644 index 00000000..ae97d384 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ReaderRequirementsBox.java @@ -0,0 +1,106 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import edu.harvard.hul.ois.jhove.*; +import java.io.*; + +/** + * Reader requirements box (JPX). + * See L.9.1 in ISO/IEC FCD15444-2:2000. + * + * @author Gary McGath + * + */ +public class ReaderRequirementsBox extends JP2Box { + + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + */ + public ReaderRequirementsBox(RandomAccessFile raf, BoxHolder parent) { + super (raf, parent); + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + final String badRR = "Invalid data in Reader Requirements box"; + if (_parentBox != null) { + wrongBoxContext(); +System.out.println ("READBOX parentBox != null"); +System.out.flush (); + return false; + } + initBytesRead (); + int len = (int) _boxHeader.getDataLength (); + + int maskLength = ModuleBase.readUnsignedByte (_dstrm, _module); + // maskLength specifies the size of FUAM and DCM, and may be + // 1, 2, 4 or 8 + long fuam; // fully understand aspects mask + long dcm; // decode completely mask + switch (maskLength) { + case 1: + fuam = ModuleBase.readUnsignedByte (_dstrm, _module); + dcm = ModuleBase.readUnsignedByte (_dstrm, _module); + break; + + case 2: + fuam = _module.readUnsignedShort (_dstrm); + dcm = _module.readUnsignedShort (_dstrm); + break; + + case 4: + fuam = _module.readUnsignedInt (_dstrm); + dcm = _module.readUnsignedInt (_dstrm); + break; + + case 8: + fuam = _module.readSignedLong (_dstrm); + dcm = _module.readSignedLong (_dstrm); + break; + + default: + _repInfo.setMessage (new ErrorMessage (badRR, _module.getFilePos ())); + _repInfo.setWellFormed (false); +System.out.println ("READBOX default"); +System.out.flush (); + return false; + } + + // nsf (number of standard flags) + int nsf = _module.readUnsignedShort (_dstrm); + for (int i = 0; i < nsf; i++) { + int sf = _module.readUnsignedShort (_dstrm); + } + // Table L-13, which gives legal values of the + // SF field, has a completely blank "value" column! + // Presumably SF stands for "science fiction." + + _module.skipBytes (_dstrm, + (int) (len - (_module.getFilePos () - startBytesRead)), _module); + finalizeBytesRead (); + _module.setRReqSeen (true); +System.out.println ("READBOX seen=true"); +System.out.flush (); + return true; + } + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "Reader Requirements Box"; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ResolutionBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ResolutionBox.java new file mode 100644 index 00000000..34c84bae --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/ResolutionBox.java @@ -0,0 +1,85 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; +//import edu.harvard.hul.ois.jhove.*; +//import edu.harvard.hul.ois.jhove.module.Jpeg2000Module; + +/** + * Resolution box. + * See I.5.3.7 in ISO/IEC 15444-1:2000 + * + * @author Gary McGath + * + * @see CaptureResolutionBox + * @see DDResolutionBox + */ +public class ResolutionBox extends JP2Box { + + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + */ + public ResolutionBox (RandomAccessFile raf, BoxHolder parent) + { + super (raf, parent); + //_parentBox = parent; + } + + + /* (non-Javadoc) + * @see edu.harvard.hul.ois.jhove.module.jpeg2000.JP2Box#readBox() + */ + public boolean readBox() throws IOException { + boolean seenresc = false; // flag for Capture Resolution box + boolean seenresd = false; // flog for Default Display Res box + initBytesRead (); + hasBoxes = true; + int sizeLeft = (int) _boxHeader.getDataLength() ; + + BoxHeader subhdr = new BoxHeader (_module, _dstrm); + //NisoImageMetadata niso = _module.getDefaultNiso (); + // Later have to implement support for compositing layers, + // assigning an appropriate value to niso + JP2Box box; + while (hasNext ()) { + + box = (JP2Box) next(); + if (box == null) { + break; + } + if (box instanceof CaptureResolutionBox) { + // Capture resolution box + seenresc = true; + if (!box.readBox ()) { + return false; + } + } + else if (box instanceof DDResolutionBox) { + // Default Display Resolution box + seenresd = true; + if (!box.readBox ()) { + return false; + } + } + else { + // Skip over other boxes. + box.skipBox (); + } + } + finalizeBytesRead (); + return true; + } + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "Resolution Box"; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/SIZMarkerSegment.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/SIZMarkerSegment.java new file mode 100644 index 00000000..88358cb0 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/SIZMarkerSegment.java @@ -0,0 +1,136 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; +import java.util.*; +import edu.harvard.hul.ois.jhove.*; + +/** + * Class for the SIZ marker segment. This is a mandatory marker + * in the main header, and provides information about the + * uncompressed image such as the width and height of the + * reference grid, the width and height of the tiles, the number + * of components, component bit depth, and the separation of + * component samples with respect to the reference grid. + * + * @author Gary McGath + * + */ +public class SIZMarkerSegment extends MarkerSegment { + + /** + * + */ + public SIZMarkerSegment() { + super(); + } + + /** Process the marker segment. The DataInputStream + * will be at the point of having read the marker code. The + * process method must consume exactly the number + * of bytes remaining in the marker segment. + * + * @param bytesToEat The number of bytes that must be consumed. + * If it is 0 for a MarkerSegment, the + * number of bytes to consume is unknown. + */ + protected boolean process (int bytesToEat) throws IOException + { + int rsiz = _module.readUnsignedShort (_dstream); + // rsiz = capabilities needed to decode + int xsiz = (int) _module.readUnsignedInt (_dstream); + // width of reference grid + int ysiz = (int) _module.readUnsignedInt (_dstream); + // height of reference grid + int xosiz = (int) _module.readUnsignedInt (_dstream); + // horizontal offset to left side of image area + int yosiz = (int) _module.readUnsignedInt (_dstream); + // vertical offset to top of image area + int xtsiz = (int) _module.readUnsignedInt (_dstream); + // width of one reference tile + int ytsiz = (int) _module.readUnsignedInt (_dstream); + // height of one reference tile + int xtosiz = (int) _module.readUnsignedInt (_dstream); + // horizontal offset to left side of first tile + int ytosiz = (int) _module.readUnsignedInt (_dstream); + // vertical offset to top of first tile + int csiz = _module.readUnsignedShort (_dstream); + // number of components + _cs.setNumComponents (csiz); + int ssiz[] = new int [csiz]; + // precision and sign of samples + for (int i = 0; i < csiz; i++) { + ssiz[i] = ModuleBase.readUnsignedByte (_dstream, _module); + } + // number of bits per component + int xrsiz[] = new int [csiz]; + // precision and sign of samples + for (int i = 0; i < csiz; i++) { + xrsiz[i] = ModuleBase.readUnsignedByte (_dstream, _module); + } + // horizontal sample separation + + int yrsiz[] = new int [csiz]; + for (int i = 0; i < csiz; i++) { + yrsiz[i] = ModuleBase.readUnsignedByte (_dstream, _module); + } + // vertical sample separation + + + // For now, just assemble the info into a SIZ property and + // hand it to the Codestream. + List plist = new ArrayList (13); + plist.add (new Property ("Capabilities", + PropertyType.INTEGER, + new Integer (rsiz))); + plist.add (new Property ("XSize", + PropertyType.INTEGER, + new Integer (xsiz))); + plist.add (new Property ("YSize", + PropertyType.INTEGER, + new Integer (ysiz))); + plist.add (new Property ("XOSize", + PropertyType.INTEGER, + new Integer (xosiz))); + plist.add (new Property ("YOSize", + PropertyType.INTEGER, + new Integer (yosiz))); + plist.add (new Property ("XTSize", + PropertyType.INTEGER, + new Integer (xtsiz))); + plist.add (new Property ("YTSize", + PropertyType.INTEGER, + new Integer (ytsiz))); + plist.add (new Property ("XTOSize", + PropertyType.INTEGER, + new Integer (xtosiz))); + plist.add (new Property ("YTOSize", + PropertyType.INTEGER, + new Integer (ytosiz))); + plist.add (new Property ("CSize", + PropertyType.INTEGER, + new Integer (csiz))); + plist.add (new Property ("SSize", + PropertyType.INTEGER, + PropertyArity.ARRAY, + ssiz)); + plist.add (new Property ("XRSize", + PropertyType.INTEGER, + PropertyArity.ARRAY, + xrsiz)); + plist.add (new Property ("YRSize", + PropertyType.INTEGER, + PropertyArity.ARRAY, + yrsiz)); + _cs.setSIZProperty(new Property ("ImageAndTileSize", + PropertyType.PROPERTY, + PropertyArity.LIST, + plist)); + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/SOTMarkerSegment.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/SOTMarkerSegment.java new file mode 100644 index 00000000..0e719b82 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/SOTMarkerSegment.java @@ -0,0 +1,63 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; +import edu.harvard.hul.ois.jhove.*; +//import edu.harvard.hul.ois.jhove.module.Jpeg2000Module; + +/** + * Class for the SOT (start of tile-part) marker segment. + * + * @author Gary McGath + * + */ +public class SOTMarkerSegment extends MarkerSegment { + + /** + * Constructor + * + */ + public SOTMarkerSegment() + { + } + + + /** Process the marker segment. The DataInputStream + * will be at the point of having read the marker code. The + * process method must consume exactly the number + * of bytes remaining in the marker segment. + * + * @param bytesToEat The number of bytes that must be consumed. + * If it is 0 for a MarkerSegment, the + * number of bytes to consume is unknown. + */ + protected boolean process (int bytesToEat) throws IOException + { + int tileIndex = _module.readUnsignedShort (_dstream); + long tileLeft = _module.readUnsignedInt (_dstream); + _ccs.setTileLeft (tileLeft); + int tilePartIndex = + ModuleBase.readUnsignedByte (_dstream, _module); + int numTileParts = + ModuleBase.readUnsignedByte (_dstream, _module); + + Tile tile = _ccs.getTile (tileIndex); + _ccs.setCurTile (tile); + TilePart tp = new TilePart (tile, tilePartIndex); + tile.addTilePart (tp); + tp.setLength (tileLeft); + // Shouldn't be anything left, but... + if (bytesToEat > 8) { + _module.skipBytes (_dstream, bytesToEat - 8, _module); + } + return true; + } + + + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/TLMMarkerSegment.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/TLMMarkerSegment.java new file mode 100644 index 00000000..04b002bd --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/TLMMarkerSegment.java @@ -0,0 +1,100 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import java.io.*; +import java.util.*; +import edu.harvard.hul.ois.jhove.*; + +/** + * Class for the TLM (tile length) marker segment. + * This may occur only in the main header. + * + * @author Gary McGath + * + */ +public class TLMMarkerSegment extends MarkerSegment { + + /** + * + */ + public TLMMarkerSegment() { + super(); + } + + /** + * Processes the marker segment. The DataInputStream + * will be at the point of having read the marker code. The + * process method must consume exactly the number + * of bytes remaining in the marker segment. + * + * @param bytesToEat The number of bytes that must be consumed. + * If it is 0 for a MarkerSegment, the + * number of bytes to consume is unknown. + */ + protected boolean process(int bytesToEat) throws IOException { + int ztlm = ModuleBase.readUnsignedByte (_dstream, _module); + int stlm = ModuleBase.readUnsignedByte (_dstream, _module); + int st = (stlm & 0X30) >> 4; + int sp = (stlm & 0X40) >> 6; + + int partLength = (sp == 1) ? 4 : 2; + switch (st) { + // case 0: add nothing + case 1: + partLength += 1; + break; + case 2: + partLength += 2; + break; + case 3: + _repInfo.setMessage (new ErrorMessage ("Invalid ST value in TLM marker segment")); + return false; // invalid st value + } + + int nParts = (bytesToEat - 2) / partLength; + // Make sure it's an even multiple + if (nParts * partLength != bytesToEat - 2) { + return false; + } + if (_ccs.getCurTile () != null) { + return false; // not permitted in a tile + } + for (int i = 0; i < nParts; i++) { + List tpList = new ArrayList (2); + // The TileIndex property is given only if st != 0 + if (st != 0) { + int ttlm; + if (st == 1) { + ttlm = ModuleBase.readUnsignedByte (_dstream, _module); + } + else { + ttlm = _module.readUnsignedShort (_dstream); + } + tpList.add (new Property ("Index", + PropertyType.INTEGER, + new Integer (ttlm))); + } + int length; + if (sp == 1) { + length = (int) _module.readUnsignedInt (_dstream); + } + else { + length = (int) _module.readUnsignedShort (_dstream); + } + tpList.add (new Property ("Length", + PropertyType.INTEGER, + new Integer (length))); + _cs.addTileLength (new Property ("TilePartLength", + PropertyType.PROPERTY, + PropertyArity.LIST, + tpList)); + } + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/Tile.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/Tile.java new file mode 100644 index 00000000..4673847c --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/Tile.java @@ -0,0 +1,116 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import edu.harvard.hul.ois.jhove.*; +import java.util.*; + +/** + * Encapsulation of a JPEG 2000 tile in a codestream. + * + * @author Gary McGath + * + */ +public class Tile extends MainOrTile { + + + private List _tileParts; + + /* List of lengths (Long objects) found in PPT code segments */ + private List _pptLengthList; + + + /** + * Constructor. + */ + public Tile () + { + + _tileParts = new LinkedList (); + } + + + /** Adds a TilePart to the List of TileParts. */ + public void addTilePart (TilePart tp) + { + _tileParts.add (tp); + } + + /** returns the List of TileParts.*/ + public List getTileParts () + { + return _tileParts; + } + + /** Adds a PPM tilepart header length to the list of lengths */ + public void addPPTLength (long len) + { + _pptLengthList.add (new Long (len)); + } + + /** Returns a Property describing the tile. + * The name of the Property is "Tile". */ + public Property makeProperty () + { + List propList = new LinkedList (); + if (!_tileParts.isEmpty ()) { + ListIterator tpiter = _tileParts.listIterator (); + while (tpiter.hasNext ()) { + TilePart tp = (TilePart) tpiter.next (); + propList.add (tp.makeProperty ()); + } + } + if (_components != null) { + // It's possible only some components have overriding + // properties. Go through the array and set a stub + // component for any that don't. + for (int i = 0; i < _components.length; i++) { + if (_components[i] == null) { + _components[i] = new Property ("Component", + PropertyType.PROPERTY, + PropertyArity.LIST, + new LinkedList ()); + } + } + propList.add (new Property ("Components", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + _components)); + } + if (_codProperty != null) { + propList.add (_codProperty); + } + if (_qcdProperty != null) { + propList.add (_qcdProperty); + } + if (_pocProperty != null) { + propList.add (_pocProperty); + } + if (_packetLengthList != null && !_packetLengthList.isEmpty ()) { + propList.add (new Property ("PacketLengths", + PropertyType.LONG, + PropertyArity.LIST, + _packetLengthList)); + } + if (_pptLengthList != null && _pptLengthList.isEmpty ()) { + propList.add (new Property ("PackedPacketHeaderLengths", + PropertyType.LONG, + PropertyArity.LIST, + _pptLengthList)); + } + if (!_comments.isEmpty ()) { + propList.add (new Property ("Comments", + PropertyType.PROPERTY, + PropertyArity.LIST, + _comments)); + } + return new Property ("Tile", + PropertyType.PROPERTY, + PropertyArity.LIST, + propList); + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/TilePart.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/TilePart.java new file mode 100644 index 00000000..9b41c001 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/TilePart.java @@ -0,0 +1,62 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import edu.harvard.hul.ois.jhove.*; +import java.util.*; + +/** + * Encapsulation of a tile-part in a JPEG 2000 codestream. + * + * @author Gary McGath + * + */ +public class TilePart { + + private Tile _tile; + private int _index; + private long _length; + + /** + * Constructor. + * + * @param tile The Tile of which this is a part + * @param index The index of this tile part + */ + public TilePart (Tile tile, int index) + { + _tile = tile; + _index = index; + } + + + /** Sets the length field. This must be called before + * calling makeProperty. */ + public void setLength (long len) + { + _length = len; + } + + + /** Returns a Property based on the TilePart. + * The Property is named "TilePart". */ + public Property makeProperty () + { + Property indexProp = new Property ("Index", + PropertyType.INTEGER, + new Integer (_index)); + Property lengthProp = new Property ("Length", + PropertyType.LONG, + new Long (_length)); + List propList = new ArrayList (2); + propList.add (indexProp); + propList.add (lengthProp); + return new Property ("TilePart", + PropertyType.PROPERTY, + PropertyArity.LIST, + propList); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/TopLevelBoxHolder.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/TopLevelBoxHolder.java new file mode 100644 index 00000000..f3b3a5cd --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/TopLevelBoxHolder.java @@ -0,0 +1,52 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import edu.harvard.hul.ois.jhove.RepInfo; +import edu.harvard.hul.ois.jhove.module.Jpeg2000Module; +import java.io.*; + +/** + * A subclass of BoxHolder specifically for getting top-level + * boxes in a JPEG 2000 file. + * + * + * @author Gary McGath + * + */ +public class TopLevelBoxHolder extends BoxHolder { + + private boolean eof; + + /** + * @param raf + */ + public TopLevelBoxHolder(Jpeg2000Module module, + RandomAccessFile raf, + RepInfo info, + DataInputStream dstream) + { + super(raf); + _module = module; + _dstrm = dstream; + _repInfo = info; + eof = false; + hasBoxes = true; + bytesLeft = Long.MAX_VALUE; + } + + /** Returns a name for use in messages. */ + protected String getSelfPropName () + { + return "Top Level"; + } + + public boolean hasNext () + { + return (!eof); + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/UUIDBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/UUIDBox.java new file mode 100644 index 00000000..b4da98ee --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/UUIDBox.java @@ -0,0 +1,91 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import edu.harvard.hul.ois.jhove.*; +import java.io.*; + +/** + * UUID Box. + * See I.7.2 in ISO/IEC 15444-1:2000 + * + * @author Gary McGath + * + * @see UUIDInfoBox + * @see UUIDListBox + */ +public class UUIDBox extends JP2Box { + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + */ + public UUIDBox (RandomAccessFile raf, BoxHolder parent) + { + super (raf, parent); + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + // The UUID box consists of a 16-byte UUID field + // and a variable-size data field. Both are binary + // data, so we make them byte array properties. + Property parray[] = new Property[2]; + + initBytesRead (); + int len = (int) _boxHeader.getDataLength (); + if (_boxHeader.getLength() != 0 && len < 16) { + wrongBoxSize (); + return false; + } + byte[] uuid = new byte[16]; + ModuleBase.readByteBuf (_dstrm, uuid, _module); + parray[0] = new Property ("UUID", + PropertyType.BYTE, + PropertyArity.ARRAY, + uuid); + + // Whatever is left is the data field. + // This gets difficult if the length field is + // 0, implying that the rest of the file is used. + int dataLen = len - 16; + if (dataLen > 0) { + byte[] dataBytes = new byte[dataLen]; + ModuleBase.readByteBuf (_dstrm, dataBytes, _module); + parray[1] = new Property ("Data", + PropertyType.BYTE, + PropertyArity.ARRAY, + dataBytes); + } + else { + // No data -- put in a FALSE property just as placeholder + parray[1] = new Property ("Data", + PropertyType.BOOLEAN, + Boolean.FALSE); + } + _module.addUUID (new Property ("UUIDBox", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + parray)); + + finalizeBytesRead (); + return true; + } + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "UUID Box"; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/UUIDInfoBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/UUIDInfoBox.java new file mode 100644 index 00000000..2a1a7d5b --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/UUIDInfoBox.java @@ -0,0 +1,135 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import edu.harvard.hul.ois.jhove.*; +import java.io.*; +import java.util.*; + +/** + * UUID info box. + * See I.7.3 in ISO/IEC 15444-1:2000 + * + * @author Gary McGath + * + */ +public class UUIDInfoBox extends JP2Box { + + private Property _urlProp; + private Property _uuidListProp; + + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + */ + public UUIDInfoBox(RandomAccessFile raf, BoxHolder parent) { + super(raf, parent); + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + if (_parentBox != null) { + wrongBoxContext (); + return false; + } + initBytesRead (); + hasBoxes = true; + int sizeLeft = (int) _boxHeader.getDataLength() ; + BoxHeader subhdr = new BoxHeader (_module, _dstrm); + JP2Box box = null; + while (hasNext ()) { + box = (JP2Box) next (); + if (box == null) { + break; + } + if (box instanceof UUIDListBox || + box instanceof UUIDListBox || + box instanceof DataEntryURLBox) { + box.setBoxHeader(subhdr); + box.setDataInputStream(_dstrm); + box.setRandomAccessFile (_raf); + box.setRepInfo(_repInfo); + box.setModule(_module); + if (!box.readBox ()) { + return false; + } + } + else { + box.skipBox (); + } + } + // A box has to be at least 8 bytes long, and there must + // not be any bytes left over. + if (sizeLeft != 0) { + // Underran the superbox -- get out quick + _repInfo.setMessage (new ErrorMessage + ("Size of contained boxes underruns UUID Info Box", + _module.getFilePos ())); + _repInfo.setWellFormed (false); + return false; + + } + List propList = new ArrayList (2); + if (_urlProp != null) { + propList.add (_urlProp); + } + if (_uuidListProp != null) { + propList.add (_uuidListProp); + } + _module.addUUIDInfo (new Property ("UUIDInfo", + PropertyType.PROPERTY, + PropertyArity.LIST, + propList)); + finalizeBytesRead (); + return true; + } + + + /** Sets the URL string. This will be called from + * DataEntryURLBox. + */ + protected void setURL(String url) + { + _urlProp = new Property ("URL", PropertyType.STRING, url); + } + + + /** Sets the UUID list. The argument is an array + * of byte arrays of length 16, or schematically: + * byte[][16]. + */ + protected void setUUIDList (byte[][] uuids) + { + List propList = new ArrayList (uuids.length); + for (int i = 0; i < uuids.length; i++) { + propList.add (new Property + ("UUIDList", + PropertyType.BYTE, + PropertyArity.ARRAY, + uuids[i])); + } + _uuidListProp = new Property ("UUIDInfo", + PropertyType.PROPERTY, + PropertyArity.LIST, + propList); + } + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "UUID Info Box"; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/UUIDListBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/UUIDListBox.java new file mode 100644 index 00000000..f1d3ee19 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/UUIDListBox.java @@ -0,0 +1,66 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import edu.harvard.hul.ois.jhove.*; +import java.io.*; + +/** + * UUID Box. + * See I.7.3.1 in ISO/IEC 15444-1:2000 + * + * @author Gary McGath + * + */ +public class UUIDListBox extends JP2Box { + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + */ + public UUIDListBox(RandomAccessFile raf, BoxHolder parent) { + super(raf, parent); + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + if (!(_parentBox instanceof UUIDInfoBox)) { + wrongBoxContext(); + return false; + } + initBytesRead (); + + int nUUID = _module.readUnsignedShort (_dstrm); + if ((16 * nUUID + 2) != _boxHeader.getDataLength()) { + wrongBoxSize (); + return false; + } + byte[][] uuids = new byte[nUUID][]; + for (int i = 0; i < nUUID; i++) { + ModuleBase.readByteBuf (_dstrm, uuids[i], _module); + } + if (_parentBox instanceof UUIDInfoBox) { + ((UUIDInfoBox) _parentBox).setUUIDList (uuids); + } + finalizeBytesRead (); + return true; + } + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "UUID List Box"; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/XMLBox.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/XMLBox.java new file mode 100644 index 00000000..ad0b7e0a --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/XMLBox.java @@ -0,0 +1,74 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.jpeg2000; + +import edu.harvard.hul.ois.jhove.*; +import java.io.*; + +/** + * XML Box. + * See I.7.1 in ISO/IEC 15444-1:2000 + * + * @author Gary McGath + * + */ +public class XMLBox extends JP2Box { + + private String xmlData; + + + + /** + * Constructor with superbox. + * + * @param parent parent superbox of this box + */ + public XMLBox (RandomAccessFile raf, BoxHolder parent) + { + super (raf, parent); + } + + /** Reads the box, putting appropriate information in + * the RepInfo object. setModule, setBoxHeader, + * setRepInfo and setDataInputStream must be called + * before readBox is called. + * readBox must completely consume the + * box, so that the next byte to be read by the + * DataInputStream is the FF byte of the next Box. + */ + public boolean readBox() throws IOException { + initBytesRead (); + int len = (int) _boxHeader.getDataLength (); + + byte[] bbuf = new byte[len]; + + ModuleBase.readByteBuf (_dstrm, bbuf, _module); + xmlData = new String (bbuf); + if (_parentBox == null) { + _module.addXML (xmlData); + } + + finalizeBytesRead (); + return true; + } + + /** Returns a Property which describes the Box, for use + * by Association boxes and perhaps others. + */ + protected Property getSelfPropDesc () + { + return new Property (DESCRIPTION_NAME, + PropertyType.STRING, + xmlData); + } + + + /** Returns the name of the Box. */ + protected String getSelfPropName () + { + return "XML Box"; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/build.xml b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/build.xml new file mode 100644 index 00000000..59242555 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/build.xml @@ -0,0 +1,32 @@ + + JPEG module build file + Jhove - JSTOR/Harvard Object Validation Environment + Version 1.0 2004-05-05 + Copyright 2004 by JSTOR and the President and Fellows of Harvard College + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/package.html b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/package.html new file mode 100644 index 00000000..4ed720f0 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/jpeg2000/package.html @@ -0,0 +1,24 @@ + + + + + + + +Contains supporting classes for the JPEG2000-HUL module. +
+ + + + + +
+ + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/package.html b/jhove/classes/edu/harvard/hul/ois/jhove/module/package.html new file mode 100644 index 00000000..d170708d --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/package.html @@ -0,0 +1,26 @@ + + + + + + + +Contains the main classes for JHOVE modules. +All module classes are subclasses of ModuleBase. +
+Additional classes for some modules are found in subpackages of +edu.harvard.hul.ois.jhove.module + + + + + + + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/AProfile.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/AProfile.java new file mode 100644 index 00000000..82079f33 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/AProfile.java @@ -0,0 +1,1179 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + + +package edu.harvard.hul.ois.jhove.module.pdf; + +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.*; +import java.io.*; +import java.util.*; +import org.xml.sax.*; +import javax.xml.parsers.*; + +/** + * PDF profile checker for PDF/A-1 documents. + * See 19005-1:2005(E), "Document Imaging Applications + * Application Issues". + * + * Revised to reflect the final standard. With the new + * terminology, this profile is specific to PDF/A-1; there may be + * additional standards in the PDF/A family later on. "PDF/A" + * means "PDF/A-1" in the documentation of this code. + * + * There are two levels of conformance, called Level A and Level B. + * We report these as two different profiles. To accomplish this, + * we use AProfileLevelA, linked to an instance of this, which + * simply checks if this profile established Level A compliance. + */ +public final class AProfile extends PdfProfile +{ + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + + /* TaggedProfile to which this profile is linked. */ + private TaggedProfile _taggedProfile; + private boolean _levelA; + private boolean hasDevRGB; + private boolean hasDevCMYK; + private boolean hasUncalCS; // flag for DeviceGray, DeviceCMYK or DeviceRGB + /* Allowable annotation types. Movie, Sound and FileAttachment + are allowed in PDF, but not in PDF/A. */ + private String[] annotTypes = { + "Text", "Link", "FreeText", "Line", "Square", "Circle", + "Polygon", "Polyline", "Highlight", "Underline", + "Squiggly", "StrikeOut", "Stamp", "Caret", + "Ink", "Popup", "Widget", "Screen", + "PrinterMark", "TrapNet" + }; + + /* The following are the annotation types which are considered + non-text annotations. */ + private String[] nonTextAnnotTypes = { + "Link", "Line", "Square", "Circle", + "Polygon", "Polyline", "Stamp", "Caret", + "Ink", "Popup", "Widget", "Screen", + "PrinterMark", "TrapNet" + }; + + private String[] excludedActions = { + "Launch", "Sound", "Movie", "ResetForm", + "ImportData", "JavaScript", "set-state", "no-op" + }; + + /* The following filters are not allowed */ + private String[] excludedFilters = { + /*"ASCIIHexDecode", "ASCII85Decode",*/ "LZWDecode" + }; + + + + /** + * Constructor. + * Creates an AProfile object for subsequent testing. + * + * @param module The module under which we are checking the profile. + * + */ + public AProfile (PdfModule module) + { + super (module); + _profileText = "ISO PDF/A-1, Level B"; + } + + /** + * Calling setTaggedProfile links this AProfile to a TaggedProfile. + * + */ + public void setTaggedProfile (TaggedProfile tpr) + { + _taggedProfile = tpr; + } + + /** + * Returns true if the document satisfies the profile + * at Level B or better. Also sets the level A flag to the + * appropriate value, so that satisfiesLevelA can subsequently + * be called. + * + */ + public boolean satisfiesThisProfile () + { + // Assume level A compliance. + _levelA = true; + // The module has already done some syntactic checks. + // If those failed, the file isn't compliant. + if (!_module.mayBePDFACompliant ()) { + _levelA = false; + return false; + } + + // Conforming to the TaggedProfile requirements is necessary + // for Level A + if (_taggedProfile != null && + !_taggedProfile.isAlreadyOK ()) { + _levelA = false; + // But it may still be Level B + } + + hasDevCMYK = false; + hasDevRGB = false; + hasUncalCS = false; + + try { + // Encryption dictionary is not allowed. + if (_module.getEncryptionDict () != null || + !trailerDictOK () || + !catalogOK () || + !resourcesOK () || + !fontsOK () || + !outlinesOK()) { + _levelA = false; + return false; + } + } + catch (Exception e) { + _levelA = false; + return false; + } + + return true; // Passed all tests + } + + /** Returns true if the document was found to be Level A + * conformant. This returns a meaningful result only after + * satisfiesThisProfile has been called, and + * is intended for use by the Level A profiler. */ + protected boolean satisfiesLevelA () + { + return _levelA; + } + + /* The Encrypt and Info entries aren't allowed in the trailer + dictionary. The ID entry is required. */ + private boolean trailerDictOK () + { + PdfDictionary trailerDict = _module.getTrailerDict (); + if (trailerDict == null) { + return false; // really shouldn't happen + } + try { + if (trailerDict.get ("Encrypt") != null/* || + trailerDict.get ("Info") != null*/) { + return false; + } + if (trailerDict.get ("ID") == null) { + return false; + } + } + catch (Exception e) { + return false; + } + return true; + } + + private boolean catalogOK () + { + PdfDictionary cat = _module.getCatalogDict (); + if (cat == null) { + return false; + } + try { + // The document catalog dictionary "should" be present. + // If it does, the value "shall" contain + // a valid RFC1766 language string. + PdfSimpleObject lang = (PdfSimpleObject) cat.get ("Lang"); + if (lang != null) { + RFC1766Lang l = new RFC1766Lang (lang.getStringValue ()); + if (!l.isSyntaxCorrect ()) { + return false; + } + } + + // It must have an unfiltered Metadata stream + PdfStream metadata = (PdfStream) + _module.resolveIndirectObject (cat.get ("Metadata")); + if (!metadataOK (metadata)) { + return false; + } + + // If it has an interactive form, it must meet certain criteria + PdfDictionary form = (PdfDictionary) + _module.resolveIndirectObject (cat.get ("AcroForm")); + if (form != null) { + if (!formOK (form)) { + return false; + } + } + + // It may not contain an AA entry or an OCProperties entry + if (cat.get ("AA") != null || + cat.get ("OCProperties") != null) { + return false; + } + } + catch (Exception e) { + return false; + } + return true; + } + + + private boolean fontsOK () + { + if (!type0FontsOK ()) { + return false; + } + // For each type of font (just because that's the easiest way + // to get the fonts from the PdfModule), check that each font + // has a ToUnicode entry which is a CMap stream. + List> lst = _module.getFontMaps (); + Iterator> iter = lst.listIterator (); + try { + while (iter.hasNext ()) { + Map fmap = (Map) iter.next (); + Iterator iter1 = fmap.values ().iterator (); + while (iter1.hasNext ()) { + PdfDictionary font = (PdfDictionary) iter1.next (); + if (!fontOK (font)) { + return false; + } + } + } + } + catch (Exception e) { + return false; + } + return true; + } + + + /* Check a font for validity */ + private boolean fontOK (PdfDictionary font) + { + try { + // The ToUnicode entry is required only for Level A, + // and there are an assortment of exceptions. + PdfSimpleObject fType = (PdfSimpleObject) font.get("Subtype"); + String fTypeStr = fType.getStringValue (); + PdfDictionary desc = (PdfDictionary) + _module.resolveIndirectObject (font.get ("FontDescriptor")); + // MODIF THL 2010/10/11 test if desc is null + PdfSimpleObject flagsObj = null; + if (desc != null) { + flagsObj = (PdfSimpleObject) _module.resolveIndirectObject( desc.get ("Flags")); + } + int flags = 0; + if (flagsObj != null) { + flags = flagsObj.getIntValue(); + } + if ("Type1".equals (fTypeStr)) { + // A Type 1 font must have a CharSet string in the + // font descriptor dictionary. + if (desc == null) { + return false; // The requirement mentioned above implies a FontDescriptor is needed. + } + if (desc.get ("CharSet") == null) { + return false; + } + + return true; + } + if ("Type0".equals (fTypeStr)) { + // Type 0 fonts are OK if the descendant CIDFont uses + // four specified character collections. + PdfObject order = font.get ("Ordering"); + if (order instanceof PdfSimpleObject) { + try { + String ordText = + ((PdfSimpleObject) order).getStringValue (); + if ("Adobe-GB1".equals (ordText) || + "Adobe-CNS1".equals (ordText) || + "Adobe-Japan1".equals (ordText) || + "Adobe-Korea1".equals (ordText)) { + return true; + } + } + catch (Exception e) {} + } + } + PdfObject enc = font.get ("Encoding"); + if (enc instanceof PdfSimpleObject) { + if ((flags & 0X04) != 0) { // symbolic font? + return false; // symbolic font must not have encoding + } + String encName = ((PdfSimpleObject) enc).getStringValue (); + if ("WinAnsiEncoding".equals (encName) || + "MacRomanDecoding".equals (encName) || + "MacExpertDecoding".equals (encName)) { + return true; + } + } + /* + * Fixed contributed by FCLA, 2007-05-30, to permit + * indirect as well as direct stream object. + * + * PdfStream toUni = (PdfStream) font.get ("ToUnicode"); + */ + PdfObject toUni = (PdfObject) font.get ("ToUnicode"); + if (toUni == null) { + _levelA = false; + } + } + catch (Exception e) { + return false; + } + return true; + } + + + /* Check the type 0 font map for compatibility with + CIDFont and CMap dictionaries */ + private boolean type0FontsOK () + { + Map type0Map = _module.getFontMap (PdfModule.F_TYPE0); + if (type0Map == null) { + return true; + } + try { + PdfSimpleObject ob; + Iterator iter = type0Map.values().iterator (); + while (iter.hasNext ()) { + String registry = null; + String ordering = null; + PdfDictionary font = (PdfDictionary) iter.next (); + // The Encoding entry can be a predefined name + // or a dictionary. If it's a dictionary, it + // must be compatible with the CIDSystemInfo + // dictionaries. + PdfObject enc = font.get ("Encoding"); + if (enc instanceof PdfDictionary) { + // it's a CMap dictionary. + PdfDictionary info = + (PdfDictionary) _module.resolveIndirectObject + (((PdfDictionary) enc).get ("CIDSystemInfo")); + ob = (PdfSimpleObject) info.get ("Registry"); + registry = ob.getStringValue (); + ob = (PdfSimpleObject) info.get ("Ordering"); + ordering = ob.getStringValue (); + } + PdfArray descendants = + (PdfArray) _module.resolveIndirectObject + (font.get ("DescendantFonts")); + // PDF 1.4 and previous allow only a single + // descendant font, and this must be a CIDFont. + // While Adobe warns that this may change in a + // previous version, we require here that the + // first descendant be a CIDFont, and ignore any others. + Vector subfonts = descendants.getContent (); + + /* + * Fix contributed by FCLA, 2007-05-30, to permit the + * subfonts array to store PdfObject as well as + * PdfDictionary. + * + * PdfDictionary subfont = + * (PdfDictionary) subfonts.elementAt (0); + * subfont = (PdfDictionary) + * _module.resolveIndirectObject (subfont); + */ + PdfObject objFont = (PdfObject) subfonts.elementAt (0); + PdfDictionary subfont = (PdfDictionary) + _module.resolveIndirectObject (objFont); + PdfSimpleObject subtype = + (PdfSimpleObject) subfont.get ("Subtype"); + /* + * Fix conributed by FCLA, 2007-05-30, to permit the + * comparison of a general PdfSimpleObject to a string. + * + * if (!"CIDFontType0".equals (subtype) && + * !"CIDFontType2".equals (subtype)) { + */ + if (!subtype.getStringValue ().equals ("CIDFontType0") && + !subtype.getStringValue ().equals ("CIDFontType2")) { + return false; + } + // If there's no CMap dictionary and this is the + // first subfont, save the registration and + // ordering strings. Otherwise make sure they match. + PdfDictionary info = + (PdfDictionary) _module.resolveIndirectObject + (subfont.get ("CIDSystemInfo")); + ob = (PdfSimpleObject) info.get ("Registry"); + String obstr = ob.getStringValue (); + if (registry == null) { + registry = obstr; + } + else { + if (!registry.equals (obstr)) { + return false; + } + } + ob = (PdfSimpleObject) info.get ("Ordering"); + obstr = ob.getStringValue (); + /* Fix contributed by FCLA, 2007-05-30, to fix an apparent + * typo. + * + * if (registry == null) { + */ + if (ordering == null) { + ordering = obstr; + } + else { + if (!ordering.equals (obstr)) { + return false; + } + } + // A type 2 subfont must meet certain restrictions + if ("CIDFontType2".equals (subtype.getStringValue())) { + // MODIF THL 2010/10/11 Accept IndirectObject + PdfObject cgmap1 = subfont.get ("CIDToGIDMap"); + if (cgmap1 == null) { + return false; + } + PdfObject cgmap; + if (cgmap1 instanceof PdfIndirectObj) { + cgmap = + (PdfObject) _module.resolveIndirectObject (cgmap1); + } else { + cgmap = cgmap1; + } + if (cgmap instanceof PdfSimpleObject) { + if (!"Identity".equals (((PdfSimpleObject)cgmap).getStringValue ())) { + return false; + } + } + else if (!(cgmap instanceof PdfStream)) { + return false; + } + } + } + } + catch (Exception e) { + return false; + } + return true; + } + + /* Check if a font has an acceptable encoding. This applies + only to TrueType fonts. */ + private boolean ttFontEncodingOK (PdfDictionary font) + { + try { + PdfDictionary desc = (PdfDictionary) + _module.resolveIndirectObject (font.get ("FontDescriptor")); + // Not all fonts -- in particular, the standard 14 -- + // are required to have FontDescriptors. How do we + // handle encoding in those cases? + if (desc == null) { + return true; // for now, give benefit of doubt + } + PdfSimpleObject flagObj = (PdfSimpleObject) + desc.get ("Flags"); + int flags = flagObj.getIntValue (); + if ((flags & 4) == 0) { + // It's a nonsymbolic font, check the Encoding + PdfSimpleObject encoding = + (PdfSimpleObject) font.get ("Encoding"); + String encStr = encoding.getStringValue (); + if (!"MacRomanEncoding".equals (encStr) && + !"WinAnsiEncoding".equals (encStr)) { + return false; + } + } + } + catch (Exception e) { + return false; + } + return true; + } + + + /* Check if the interactive form is OK */ + private boolean formOK (PdfDictionary form) + { + // Guess what? It's another hierarchy of dictionaries! + // So let's walk through the fields... + try { + PdfArray fields = (PdfArray) form.get ("Fields"); + Vector fieldVec = fields.getContent (); + for (int i = 0; i < fieldVec.size (); i++) { + PdfDictionary field = (PdfDictionary) fieldVec.elementAt (i); + if (!fieldOK (field)) { + return false; + } + } + // The NeedAppearances flag either shall not be present + // or shall be false. + PdfSimpleObject needapp = (PdfSimpleObject) form.get ("NeedAppearances"); + if (needapp != null) { + if (!needapp.isFalse ()) { + return false; + } + } + } + catch (Exception e) { + return false; + } + return true; + } + + + /* Check a form field for validity. We don't allow form fields + to have AA (Additional Actions) dictionaries */ + private boolean fieldOK (PdfDictionary field) + { + try { + // A Widget annotation dictionary or Field dictionary + // shall not contain the A or AA keys. + if (field.get ("AA") != null) { + return false; + } + if (field.get ("A") != null) { + return false; + } + // Every form field shall have an appearance dictionary + // associated with the field's data. + if (field.get ("DR") == null) { + return false; + } + PdfArray kids = (PdfArray) field.get ("Kids"); + // Now, just to complicate things, the contents of + // the array might be subfield dictionaries or might + // be widget annotations. Oh, and neither one has + // a required Type entry. + // We only case about subfields. + if (kids != null) { + Vector kidVec = kids.getContent (); + for (int i = 0; i < kidVec.size (); i++) { + PdfDictionary kid = (PdfDictionary) kidVec.elementAt (i); + // The safest way to check if this is a field seems + // to be to look for the required Parent entry. + if (kid.get ("Parent") != null) { + if (!fieldOK (kid)) { + return false; + } + } + } + } + } + catch (Exception e) { + return false; + } + return true; + } + + /* Walk through the page tree and check all Resources dictionaries + that we find. Along the way, we check several things: + + Color spaces. The document may not have both CMYK and + RGB color spaces. + + Extended graphic states. + + XObjects. + */ + private boolean resourcesOK () + { + PageTreeNode docTreeRoot = _module.getDocumentTree (); + try { + docTreeRoot.startWalk (); + DocNode docNode; + for (;;) { + docNode = docTreeRoot.nextDocNode (); + if (docNode == null) { + break; + } + // Check for node-level resources + PdfDictionary rsrc = docNode.getResources (); + if (rsrc != null) { + + // Check color spaces. + PdfDictionary cs = (PdfDictionary) + _module.resolveIndirectObject + (rsrc.get ("ColorSpace")); + if (!colorSpaceOK (cs)) { + return false; + } + + // Check extended graphics state. + PdfDictionary gs = (PdfDictionary) + _module.resolveIndirectObject + (rsrc.get ("ExtGState")); + if (!extGStateOK (gs)) { + return false; + } + + // Check XObjects. + PdfDictionary xo = (PdfDictionary) + _module.resolveIndirectObject + (rsrc.get ("XObject")); + if (!xObjectsOK (xo)) { + return false; + } + } + + // Check content streams for resources + if (docNode instanceof PageObject) { + List streams = + ((PageObject) docNode).getContentStreams (); + if (streams != null) { + Iterator iter = streams.listIterator (); + while (iter.hasNext ()) { + PdfStream stream = iter.next (); + PdfDictionary dict = stream.getDict (); + PdfDictionary rs = + (PdfDictionary) + _module.resolveIndirectObject(dict.get ("Resources")); + if (rs != null) { + PdfDictionary cs = (PdfDictionary) + _module.resolveIndirectObject + (rs.get ("ColorSpace")); + if (!colorSpaceOK (cs)) { + return false; + } + + PdfDictionary gs = (PdfDictionary) + _module.resolveIndirectObject + (rs.get ("ExtGState")); + if (!extGStateOK (gs)) { + return false; + } + + PdfDictionary xo = (PdfDictionary) + _module.resolveIndirectObject + (rs.get ("XObject")); + if (!xObjectsOK (xo)) { + return false; + } + } + // Also check for filters + PdfObject filters = + dict.get ("Filter"); + if (hasFilters (filters, excludedFilters)) { + return false; + } + } + } + + // Also check page objects for annotations. + // Must be one of the prescribed types, but not + // Movie, Sound, or FileAttachment. + PdfArray annots = ((PageObject) docNode).getAnnotations (); + if (annots != null) { + Vector annVec = annots.getContent (); + for (int i = 0; i < annVec.size (); i++) { + PdfDictionary annDict = (PdfDictionary) + _module.resolveIndirectObject + (annVec.elementAt (i)); + PdfSimpleObject subtypeObj = (PdfSimpleObject) annDict.get ("Subtype"); + String subtypeVal = subtypeObj.getStringValue (); + boolean stOK = false; + int j; + for (j = 0; j < annotTypes.length; j++) { + if (annotTypes[j].equals (subtypeVal)) { + + stOK = true; + break; + } + } + if (!stOK) { + return false; + } + + // If it's a Widget, it can't have an AA entry + if ("Widget".equals (subtypeVal)) { + if (annDict.get ("AA") != null) { + return false; + } + } + // For non-text annotation types, the + // Contents key is RECOMMENDED, not required. + // Therefore comment this test out GDM 4-Sep-2012 +// for (j = 0; i < nonTextAnnotTypes.length; j++) { +// if (nonTextAnnotTypes[i].equals (subtypeVal)) { +// if (annDict.get ("Contents") == null) { +// return false; +// } +// else { +// // Contents found, this dict OK +// break; +// } +// } +// } + + // if the CA key is present, it must have a + // value of 1.0. + PdfSimpleObject ca = (PdfSimpleObject) + annDict.get ("CA"); + if (ca != null) { + double caVal = ca.getDoubleValue (); + if (caVal != 1.0) { + return false; + } + } + } + } + } + } + } + catch (Exception e) { + return false; + } + return true; // passed all tests + } + + + + /* Check if a color space dictionary is conformant */ + private boolean colorSpaceOK (PdfDictionary cs) + { + // If it's null, that's fine. + if (cs == null) { + return true; + } + // Walk through the color space dictionary, + // checking device ("uncalibrated") color spaces + Iterator iter = cs.iterator (); + while (iter.hasNext ()) { + PdfObject res = (PdfObject) iter.next (); + if (res instanceof PdfArray) { + Vector resv = ((PdfArray) res).getContent (); + PdfSimpleObject snameobj = (PdfSimpleObject) resv.elementAt (0); + String sname = snameobj.getStringValue (); + boolean oldHasUncalCS = hasUncalCS; + if ("DeviceCMYK".equals (sname)) { + hasDevCMYK = true; + hasUncalCS = true; + } + else if ("DeviceRGB".equals (sname)) { + hasDevRGB = true; + hasUncalCS = true; + } + else if ("DeviceGray".equals (sname)) { + hasUncalCS = true; + } + // If this is the first time we've hit an uncalibrated + // color space, check for an appropriate OutputIntent dict. + if (hasUncalCS && !oldHasUncalCS) { + if (!checkUncalIntent ()) { + return false; + } + } + if (hasDevRGB && hasDevCMYK) { + return false; // can't have both in same file + } + } + } + return true; // passed all tests + } + + /* If there is an uncalibrated color space, then there must be a + * "PDF/A-1 OutputIntent." */ + private boolean checkUncalIntent () + { + try { + // First off, there must be an OutputIntents array + // in the document catalog dictionary. + PdfDictionary catDict = _module.getCatalogDict (); + PdfArray intentsArray = (PdfArray) _module.resolveIndirectObject + (catDict.get ("OutputIntents")); + if (intentsArray == null) { + return false; + } + Vector intVec = intentsArray.getContent (); + PdfStream theOutProfile = null; + boolean pdfaProfileSeen = false; + for (int i = 0; i < intVec.size (); i++) { + // Multiple intents arrays are allowed, but all must use + // the same DestOutputProfile object or none, and there + // must be at least one that as one and has GTS_PDFA1 as + // the value of its S key. + PdfDictionary intent = (PdfDictionary) intVec.elementAt (0); + PdfSimpleObject outCond = + (PdfSimpleObject) intent.get ("OutputCondition"); + if (outCond != null) { + PdfStream outProfile = (PdfStream) _module.resolveIndirectObject + (intent.get ("DestOutputProfile")); + if (outProfile != null) { + if (theOutProfile != null) { + // all output profiles must be the same. + if (outProfile != theOutProfile) { + return false; + } + } + else { + // All subsequent output profiles must matcht his. + theOutProfile = outProfile; + } + PdfSimpleObject subtype = (PdfSimpleObject) intent.get ("S"); + if (subtype != null) { + if ("GTS_PDFA1".equals (subtype.getStringValue())) { + pdfaProfileSeen = true; + } + } + } + } + } + if (theOutProfile == null || !pdfaProfileSeen) { + return false; + } + } + catch (Exception e) { + return false; + } + return true; + } + + + + + /* Check if the outlines (if any) are OK. This is a check + on Actions, and the module has already checked if there + are Actions in the outlines, so if there aren't any, + we save the time to do this test. */ + private boolean outlinesOK () + { + if (!_module.getActionsExist ()) { + return true; + } + PdfDictionary outlineDict = _module.getOutlineDict (); + if (outlineDict == null) { + return true; + } + try { + PdfDictionary item = (PdfDictionary) _module.resolveIndirectObject + (outlineDict.get ("First")); + while (item != null) { + if (!checkOutlineItem (item)) { + return false; + } + item = (PdfDictionary) _module.resolveIndirectObject + (((PdfDictionary) item).get ("Next")); + } + } + catch (Exception e) { + return false; + } + return true; + } + + + /* Check an outline item, going down recursively */ + private boolean checkOutlineItem (PdfDictionary item) + { + // Check if there are actions for this item + try { + PdfDictionary action = (PdfDictionary) + _module.resolveIndirectObject (item.get ("A")); + if (action != null) { + if (!actionOK (action)) { + return false; + } + } + PdfDictionary child = (PdfDictionary) + _module.resolveIndirectObject (item.get ("First")); + while (child != null) { + if (!checkOutlineItem (child)) { + return false; + } + child = (PdfDictionary) + _module.resolveIndirectObject (child.get ("Next")); + } + } + catch (Exception e) { + return false; + } + return true; + } + + + /* Validate an Action dictionary. Actions exclude certain types. */ + private boolean actionOK (PdfDictionary action) + { + int i; + // For some reason, an action's type is an "S" entry, not + // a "Subtype" entry. + try { + PdfSimpleObject actType = (PdfSimpleObject) action.get ("S"); + String actStr = actType.getStringValue (); + // Note: I should also be checking for the set-state + // and no-op actions, which are prohibited; but since + // the documentation I can find doesn't say what the + // actual names for these actions are, it's tough to + // exclude them, and I'd rather not guess the names. + for (i = 0; i < excludedActions.length; i++) { + if (excludedActions[i].equals (actStr)) { + return false; + } + } + // An action can have a "Next" entry which is either + // another action or an array of actions. Need to follow + // the whole tree to make sure all actions are legit. + PdfObject next = action.get ("Next"); + if (next instanceof PdfDictionary) { + if (!actionOK ((PdfDictionary) next)) { + return false; + } + } + else if (next instanceof PdfArray) { + Vector nextVec = ((PdfArray) next).getContent (); + for (i = 0; i < nextVec.size (); i++) { + PdfDictionary nact = (PdfDictionary) + nextVec.elementAt (i); + if (!actionOK (nact)) { + return false; + } + } + } + else if (next != null) { + return false; + } + } + catch (Exception e) { + return false; + } + return true; + } + + + + /* The ExtGState resource may not have a TR key, or a + TR2 with a value other than "Default". */ + private boolean extGStateOK (PdfDictionary gs) + { + if (gs == null) { + // no object means no problem + return true; + } + try { + PdfObject tr = gs.get ("TR"); + PdfObject tr2 = gs.get ("TR2"); + + if (tr != null) { + return false; + } + if (tr2 != null) { + String tr2Val = ((PdfSimpleObject) tr2).getStringValue (); + if (!"Default".equals (tr2Val)) { + return false; + } + } + + // RI is restricted to the traditional 4 rendering intents + PdfSimpleObject ri = (PdfSimpleObject) gs.get ("RI"); + if (ri != null) { + String riVal = ri.getStringValue (); + if (!validIntentString (riVal)) { + return false; + } + } + + // SMask is allowed only with a value of "None". + PdfSimpleObject smask = (PdfSimpleObject) gs.get ("SMask"); + if (smask != null) { + String smVal = smask.getStringValue (); + if (!"None".equals (smVal)) { + return false; + } + } + + // BM, if present, must be "Normal" or "Compatible" + PdfSimpleObject blendMode = + (PdfSimpleObject) gs.get ("BM"); + if (blendMode != null) { + String bmVal = blendMode.getStringValue (); + if (!"Normal".equals (bmVal) && + !"Compatible".equals (bmVal)) { + return false; + } + } + + // CA and ca must be 1.0, if present + PdfSimpleObject ca = (PdfSimpleObject) gs.get ("CA"); + double caVal; + if (ca != null) { + caVal = ca.getDoubleValue (); + if (caVal != 1.0) { + return false; + } + } + ca = (PdfSimpleObject) gs.get ("ca"); + if (ca != null) { + caVal = ca.getDoubleValue (); + if (caVal != 1.0) { + return false; + } + } + } + catch (Exception e) { + return false; + } + return true; // passed all tests + } + + + /** + * Checks a single XObject for xObjectsOK. + */ + protected boolean xObjectOK (PdfDictionary xo) + { + if (xo == null) { + // no XObject means no problem + return true; + } + try { + // PostScript XObjects aren't allowed. + // Image XObjects must meet certain tests. + PdfSimpleObject subtype = (PdfSimpleObject) xo.get ("Subtype"); + if (subtype != null) { + String subtypeVal = subtype.getStringValue (); + if ("PS".equals (subtypeVal)) { + // PS XObjects aren't allowed. + return false; + } + if ("Image".equals (subtypeVal)) { + if (!imageObjectOK (xo)) { + return false; + } + } + if ("Form".equals (subtypeVal)) { + if (!formObjectOK (xo)) { + return false; + } + } + } + } + catch (Exception e) { + return false; + } + return true; + } + + /** Checks if a Form xobject is valid. This overrides the method in + XProfileBase. */ + protected boolean formObjectOK (PdfDictionary xo) + { + // PDF/A elements can't have an OPI or Ref key in Form xobjects. + if (xo.get ("OPI") != null || xo.get ("Ref") != null) { + return false; + } + return true; + } + + + /** Checks if a single image XObject fits the profile */ + protected boolean imageObjectOK (PdfDictionary xo) + { + try { + // OPI and Alternates keys are disallowed + if (xo.get ("OPI") != null || + xo.get ("Alternates") != null) { + return false; + } + + // Check against LZW filter + PdfObject filters = + xo.get ("Filter"); + if (hasFilters (filters, excludedFilters)) { + return false; + } + + + // Interpolate is allowed only if its value is false. + PdfSimpleObject interp = (PdfSimpleObject) xo.get ("Interpolate"); + if (interp != null) { + if (!interp.isFalse ()) { + return false; + } + } + + // Intent must be one of the four standard rendering intents, + // if present. + PdfSimpleObject intent = (PdfSimpleObject) xo.get ("Intent"); + if (intent != null) { + String intentStr = intent.getStringValue (); + if (! validIntentString (intentStr)) { + return false; + } + } + + } + catch (Exception e) { + return false; + } + return true; + } + + private boolean validIntentString (String str) + { + return ("RelativeColorimetric".equals (str) || + "AbsoluteColorimetric".equals (str) || + "Perceptual".equals (str) || + "Saturation".equals (str)); + } + + // See if the metadata stream from the catalog dictionary is OK + private boolean metadataOK (PdfStream metadata) + { + // Presence of metadata is required + if (metadata == null) { + return false; + } + try { + PdfDictionary metaDict = metadata.getDict (); + if (metaDict.get ("Filter") != null) { + // We just metadata we didn't like. Filters aren't allowed. + return false; + } + + // Create an InputSource to feed the parser. + SAXParserFactory factory = + SAXParserFactory.newInstance(); + factory.setNamespaceAware (true); + XMLReader parser = factory.newSAXParser ().getXMLReader (); + //InputStream stream = new StreamInputStream (metadata, _module.getFile ()); + PdfXMPSource src = new PdfXMPSource (metadata, _module.getFile ()); + XMPHandler handler = new XMPHandler (); + parser.setContentHandler (handler); + parser.setErrorHandler (handler); + // We have to parse twice. The first time, we may get + // an encoding change as part of an exception thrown. If this + // happens, we create a new InputSource with the encoding, and + // continue. + try { + parser.parse (src); + if (!handler.isPdfaCompliant ()) { + return false; + } + } + catch (SAXException se) { + String msg = se.getMessage (); + if (msg != null && msg.startsWith ("ENC=")) { + // encoding change is not allowed with PDF/A, so there's no + // need to re-parse + return false; +// String encoding = msg.substring (5); +// try { +// //Reader rdr = new InputStreamReader (stream, encoding); +// src = new PdfXMPSource (metadata, _module.getFile (), encoding); +// parser.parse (src); +// if (!handler.isPdfaCompliant ()) { +// return false; +// } +// } +// catch (UnsupportedEncodingException uee) { +// return false; +// } + } + } + } + catch (Exception e) { + return false; + } + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/AProfileLevelA.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/AProfileLevelA.java new file mode 100644 index 00000000..8490fba1 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/AProfileLevelA.java @@ -0,0 +1,59 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004-2005 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import edu.harvard.hul.ois.jhove.module.PdfModule; + +/** + * PDF profile checker for PDF/A-1 documents, Level A. + * See ISO 19005-1:2005(E), "Document Imaging Applications + * Application Issues". + * + * This profile checker is completely dependent on AProfile. + * It simply queries an instance of AProfile for Level A compliance. + * + * @author Gary McGath + * + */ +public class AProfileLevelA extends PdfProfile { + + /* AProfile to which this profile is linked. */ + private AProfile _aProfile; + + /** + * Constructor. + * Creates an AProfileLevelA object for subsequent testing. + * + * @param module The module under which we are checking the profile. + * + */ + public AProfileLevelA(PdfModule module) { + super (module); + _profileText = "ISO PDF/A-1, Level A"; + } + + /** + * Returns true if the document satisfies the profile + * at Level A. This returns a meaningful result only if + * satisfiesThisProfile() has previously + * been called on the profile assigned by setAProfile. + * + */ + public boolean satisfiesThisProfile() { + return _aProfile.satisfiesLevelA(); + } + + /** + * Calling setAProfile links this AProfile to a TaggedProfile. + * This class gets all its information from the linked AProfile, + * so calling this is mandatory. + */ + public void setAProfile (AProfile tpr) + { + _aProfile = tpr; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/ArrayEnd.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/ArrayEnd.java new file mode 100644 index 00000000..d7ef600e --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/ArrayEnd.java @@ -0,0 +1,19 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +/** + * Class for Tokens which represent the "]" that closes an array. + */ +public class ArrayEnd + extends Token +{ + /** Creates an instance of an ArrayEnd */ + public ArrayEnd () + { + super (); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/ArrayStart.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/ArrayStart.java new file mode 100644 index 00000000..bf110690 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/ArrayStart.java @@ -0,0 +1,19 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +/** + * Class for Tokens which represent the "[" that opens an array. + */ +public class ArrayStart + extends Token +{ + /** Creates an instance of an ArrayStart */ + public ArrayStart () + { + super (); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Ascii85FilterStream.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Ascii85FilterStream.java new file mode 100644 index 00000000..6f8a1fbe --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Ascii85FilterStream.java @@ -0,0 +1,29 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2005 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import java.io.FilterInputStream; +import java.io.InputStream; + +/** + * This is a stub which may be implemented in the future. + * It appears to be unnecessary for object streams and cross-reference + * streams created by any version of Acrobat through 7.0, and we + * don't look at other types of streams. + * + * @author Gary McGath + * + */ +public class Ascii85FilterStream extends FilterInputStream { + + /** + * @param in + */ + public Ascii85FilterStream(InputStream in) { + super(in); + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/AsciiHexFilterStream.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/AsciiHexFilterStream.java new file mode 100644 index 00000000..363259e5 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/AsciiHexFilterStream.java @@ -0,0 +1,29 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2005 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import java.io.FilterInputStream; +import java.io.InputStream; + +/** + * This is a stub which may be implemented in the future. + * It appears to be unnecessary for object streams and cross-reference + * streams created by any version of Acrobat through 7.0, and we + * don't look at other types of streams. + * + * @author Gary McGath + * + */ +public class AsciiHexFilterStream extends FilterInputStream { + + /** + * @param in + */ + public AsciiHexFilterStream(InputStream in) { + super(in); + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Comment.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Comment.java new file mode 100644 index 00000000..c5a2b875 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Comment.java @@ -0,0 +1,19 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +/** + * Class for Tokens which represent PDF comments. + */ +public class Comment + extends StringValuedToken +{ + /** Creates an instance of a Comment */ + public Comment () + { + super (); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/CrossRefStream.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/CrossRefStream.java new file mode 100644 index 00000000..80b92284 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/CrossRefStream.java @@ -0,0 +1,305 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2005 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import java.io.*; +import java.util.*; + +/** + * This class implements the Cross-Reference Stream, an alternative + * to the Cross-Reference Table starting in PDF 1.4. + * + * A cross-reference stream is identified by a startxref keyword, + * as opposed to the xref keyword which identifies the old-style + * cross-reference table. + * + * JHOVE supports only FlateDecode as a filter for cross-reference + * streams. This is consistent with the implementation limitation + * described in Appendix H of the PDF manual for Acrobat 6 and earlier. + * + * + * @author Gary McGath + * + */ +public class CrossRefStream { + + private PdfStream _xstrm; // The underlying Stream object. + private PdfDictionary _dict; + private int _size; + private int[] _index; + private int[] _fieldSizes; + private int _freeCount; + private Filter[] _filters; + private int _entriesRead; + private int _bytesPerEntry; + private long _prevXref; // byte offset to previous xref stream, if any + + /* Per-object variables. */ + private int _objType; + private int _objNum; + private int _objField1; + private int _objField2; + + /** + * Constructor. + * + * @param xstrm PdfStream object which contains a presumed + * cross-reference stream. + */ + public CrossRefStream(PdfStream xstrm) { + _xstrm = xstrm; + _dict = xstrm.getDict (); + _freeCount = 0; + } + + /** Returns true if the PdfStream object meets + * the requirements of a cross-reference stream. Also extracts + * information from the dictionary for subsequent processing. + */ + public boolean isValid () { + try { + PdfObject typeobj = _dict.get ("Type"); + String typeStr = null; + if (typeobj instanceof PdfSimpleObject) { + typeStr = ((PdfSimpleObject) typeobj).getStringValue (); + if (!("XRef".equals (typeStr))) { + return false; + } + } + if (typeStr == null) { + return false; + } + PdfObject sizeobj = _dict.get ("Size"); + if (sizeobj instanceof PdfSimpleObject) { + _size = ((PdfSimpleObject) sizeobj).getIntValue(); + } + else { + return false; + } + + // The Index entry is optional, but must have the right + // format if it's present. + PdfObject indexobj = _dict.get ("Index"); + if (indexobj instanceof PdfArray) { + Vector vec = ((PdfArray) indexobj).getContent(); + // This is supposed to have a size of 2. + _index = new int[2]; + PdfSimpleObject idx = (PdfSimpleObject) vec.get (0); + _index[0] = idx.getIntValue (); + idx = (PdfSimpleObject) vec.get (1); + _index[1] = idx.getIntValue (); + } + else { + // Set up default index. + _index = new int[] { 0, _size }; + } + + // Get the field sizes. + PdfObject wObj = _dict.get ("W"); + if (wObj instanceof PdfArray) { + Vector vec = ((PdfArray) wObj).getContent (); + int len = vec.size (); + _fieldSizes = new int[len]; + for (int i = 0; i < len; i++) { + PdfSimpleObject ob = (PdfSimpleObject) vec.get (i); + _fieldSizes[i] = ob.getIntValue (); + } + } + + // Get the offset to the previous xref stream, if any. + PdfObject prevObj = _dict.get ("Prev"); + if (prevObj instanceof PdfSimpleObject) { + _prevXref = ((PdfSimpleObject) prevObj).getIntValue(); + } + else { + _prevXref = -1; + } + + // Get the filter, for subsequent decompression. + // We're guaranteed by the spec that this won't be a decryption + // filter. + _filters = _xstrm.getFilters(); + // Why isn't this being used? + + // passed all tests + return true; + } + catch (Exception e) { + return false; + } + } + + /** Prepares for reading the Stream. + * If the filter List includes one which we don't support, throws a + * PdfException. */ + public void initRead (RandomAccessFile raf) + throws IOException, PdfException + { + Stream strm = _xstrm.getStream (); + strm.setFilters (_xstrm.getFilters ()); + strm.initRead (raf); + _entriesRead = 0; + + /* Calculate the total bytes per entry. This may have + * some utility. */ + _bytesPerEntry = 0; + for (int i = 0; i < _fieldSizes.length; i++) { + _bytesPerEntry += _fieldSizes[i]; + } + } + + + + /** Reads the next object in the stream. + * + * After calling readObject, it is possible to + * call accessors to get information about the object. + * For the moment, we + * punt on the question of how to deal with Object Streams. + * + * Free objects are skipped over while being counted. After + * readNextObject() returns false, the caller + * may call getFreeCount() to determine the + * number of free objects. + * + * @return true if there is an object, false + * if no more objects are available. + */ + public boolean readNextObject () throws IOException + { + /* Get the field type. */ + int wid; + Stream is = _xstrm.getStream (); + int i; + int b; + + for (;;) { + /* Loop till we find an actual object; we just count + * type 0's, which are free entries. */ + wid = _fieldSizes[0]; + if (_entriesRead++ >= _index[1]) { + return false; // Read full complement + } + if (wid != 0) { + /* "Fields requiring more than one byte are stored + * with the high-order byte first." */ + _objType = 0; + for (i = 0; i < wid; i++) { + b = is.read (); + if (b < 0) { + return false; + } + _objType = _objType * 256 + b; + } + } + else { + _objType = 1; // Default if field width is 0 + } + + wid = _fieldSizes[1]; + _objField1 = 0; + for (i = 0; i < wid; i++) { + b = is.read (); + if (b < 0) { + return false; + } + _objField1 = _objField1 * 256 + b; + } + + wid = _fieldSizes[2]; + _objField2 = 0; + for (i = 0; i < wid; i++) { + b = is.read (); + if (b < 0) { + return false; + } + _objField2 = _objField2 * 256 + b; + } + + if (_objType != 0) { + _objNum = _index[0] + _entriesRead - 1; + return true; + } + else { + ++_freeCount; + } + } + } + + /** Returns number of the last object read by + * readNextObject (). + * Do not call if readNextObject () + * returns false. + */ + public int getObjNum () + { + return _objNum; + } + + + /** Returns true if the last object read by + * readNextObject () is a compressed object. + * Do not call if readNextObject () + * returns false. + */ + public boolean isObjCompressed () + { + return (_objType == 2); + } + + + /** Returns the number of free objects detected. This may + * be called after readNextObject returns + * false, signifying that all the objects + * have been read and all the free objects counted. + */ + public int getFreeCount () + { + return _freeCount; + } + + /** Returns the total object count. */ + public int getNumObjects () + { + return _index[0] + _index[1]; + } + + /** Returns the offset of the last object object read. + * This is meaningful only if the last object read + * was type 1 (uncompressed). + */ + public int getOffset () + { + return _objField1; + } + + /** Returns the object number of the content stream in + * which this object is stored. + * This is nmeaningful only if the last object read + * was type 2 (compressed in content stream). + */ + public int getContentStreamObjNum () + { + return _objField1; + } + + /** Returns the offset of the previous cross-reference stream, + * or -1 if none is specified. */ + public long getPrevXref () + { + return _prevXref; + } + + + /** Returns the content stream index of the last object read. + * This is nmeaningful only if the last object read + * was type 2 (compressed in content stream). + */ + public int getContentStreamIndex () + { + return _objField2; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Destination.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Destination.java new file mode 100644 index 00000000..5708be52 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Destination.java @@ -0,0 +1,134 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import edu.harvard.hul.ois.jhove.module.PdfModule; +import java.util.*; + +/** + * Class encapsulating PDF destination objects, which refer + * to a page in the document. + * + * We need to make two different kinds of distinctions: between + * destinations that make an indirect and a direct reference to + * a page; and between destinations that have been reached by + * a direct and an indirect reference. The PDF spec allows + * only one level of indirection, so each of these forms has + * options not available to the other. + * + * We call a destination which has been reached directly an + * unnamed destination, and one which has been reached indirectly + * a named destination. We call a destination which has an + * indirect target an indirect destination, and one which has + * a page object as a target a direct destination. Applying + * the PDF documentation, we find that a destination can never + * be both named and indirect. In other words, there are really + * two cases, involving three kinds of destinations: + * + *
    + *
  • An unnamed, direct destination, which refers to the page + * object. + *
  • An unnamed, indirect destination, which refers to a + * named, direct destination, which refers to the page object. + *
+ */ +public final class Destination +{ + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + + /* Flag indicating destination is indirect. */ + private boolean _indirect; + + /* Name of indirect destination. */ + private PdfSimpleObject _indirectDest; + + /* Page object for explicit destination. */ + private PdfDictionary _pageDest; + + /** + * Constructor. If this is a named destination, the destObj + * may be a PdfArray or a PdfDictionary; if this is not a + * named destination, the destObj may be a PdfSimpleObject + * (encapsulating a Literal or Name) or a PdfDictionary. + * + * @param destObj The destination object + * @param module The invoking PdfModule + * @param named Flag indicating whether this object came + * from a named destination. + */ + public Destination (PdfObject destObj, PdfModule module, boolean named) + throws PdfException + { + try { + if (!named && destObj instanceof PdfSimpleObject) { + _indirect = true; + _indirectDest = (PdfSimpleObject) destObj; + } + else if (destObj instanceof PdfArray) { + // We extract only the page reference, not the view. + _indirect = false; + Vector v= ((PdfArray) destObj).getContent (); + _pageDest = (PdfDictionary) module.resolveIndirectObject + ((PdfObject) v.elementAt (0)); + } + else if (named && destObj instanceof PdfDictionary) { + PdfArray destObj1 = (PdfArray) + ((PdfDictionary) destObj).get ("D"); + // the D entry is just like the array above. + _indirect = false; + Vector v= ((PdfArray) destObj1).getContent (); + _pageDest = (PdfDictionary) module.resolveIndirectObject + ((PdfObject) v.elementAt (0)); + } + else { + throw new Exception (""); + } + } + catch (Exception e) { + throw new PdfInvalidException ("Invalid destination object"); + } + } + + /** + * Returns true if the destination is indirect. + */ + public boolean isIndirect () + { + return _indirect; + } + + + /** + * Returns the string naming the indirect destination. + * Returns null if the destination is not indirect. + */ + public PdfSimpleObject getIndirectDest () + { + return _indirectDest; + } + + /** + * Returns the page object dictionary if the destination + * is direct. Returns null if the destination is not + * direct. + */ + public PdfDictionary getPageDest () + { + return _pageDest; + } + + /** + * Returns the object number of the page object dictionary + * if the destination is direct. Throws a NullPointerException + * otherwise. + */ + public int getPageDestObjNumber () throws NullPointerException + { + return _pageDest.getObjNumber (); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/DictionaryEnd.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/DictionaryEnd.java new file mode 100644 index 00000000..ad8480b8 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/DictionaryEnd.java @@ -0,0 +1,19 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +/** + * Class for Tokens which represent the ">>" sequence that ends a Dictionary. + */ +public class DictionaryEnd + extends Token +{ + /** Creates an instance of a DictionaryEnd */ + public DictionaryEnd () + { + super (); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/DictionaryStart.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/DictionaryStart.java new file mode 100644 index 00000000..b997cf7e --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/DictionaryStart.java @@ -0,0 +1,19 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +/** + * Class for Tokens which represent the "<<" sequence that opens a Dictionary. + */ +public class DictionaryStart + extends Token +{ + /** Creates an instance of a DictionaryStart */ + public DictionaryStart () + { + super (); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/DocNode.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/DocNode.java new file mode 100644 index 00000000..b0733177 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/DocNode.java @@ -0,0 +1,192 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import edu.harvard.hul.ois.jhove.module.PdfModule; +import java.io.*; + +/** + * Abstract class for nodes of a PDF document tree. + */ +public abstract class DocNode +{ + /** The PdfModule this node is associated with. */ + protected PdfModule _module; + + /** The parent node of this node. */ + protected PageTreeNode _parent; + + /** The dictionary which defines this node. */ + protected PdfDictionary _dict; + + /** True if this node is a PageObject. */ + protected boolean _pageObjectFlag; + + /** Set to true when all subnodes of this node + * have been iterated through following a StartWalk. */ + protected boolean _walkFinished; + + /** + * Superclass constructor. + * @param module The PdfModule under which we're operating + * @param parent The parent node in the document tree; + * may be null only for the root node + * @param dict The dictionary object on which this node + * is based + */ + public DocNode (PdfModule module, + PageTreeNode parent, + PdfDictionary dict) + { + _module = module; + _parent = parent; + _dict = dict; + // Debug code + PdfIndirectObj parentRef = (PdfIndirectObj) _dict.get ("Parent"); + } + + /** + * Returns true if this node is a PageObject. + */ + public boolean isPageObject () + { + return _pageObjectFlag; + } + + /** + * Initialize an iterator through the descendants of this node. + */ + public abstract void startWalk (); + + /** + * Get the next PageObject which is under this node. + */ + public abstract PageObject nextPageObject () throws PdfMalformedException; + + /** + * Get the next DocNode which is under this node. + * All PageTreeNodes and PageObjects are eventually returned + * by walking through a structure with nextNode. + */ + public abstract DocNode nextDocNode () throws PdfMalformedException; + + /** + * Returns the parent of this node. + */ + public DocNode getParent () + { + return _parent; + } + + /** + * Returns the page object or page tree node dictionary from + * which this object was constructed. + */ + public PdfDictionary getDict () + { + return _dict; + } + + + /** + * Get the Resources dictionary. Either a PageTreeNode or + * a PageObject can have a Resources dictionary. Returns + * null if there is no Resources dictionary. The object + * may be referenced indirectly. + */ + public PdfDictionary getResources () throws PdfException + { + String invres = "Invalid Resources Entry in document"; + if (_dict == null) { + throw new PdfMalformedException ("Missing dictionary in document node"); + } + try { + PdfObject resdict = _dict.get ("Resources"); + resdict = _module.resolveIndirectObject (resdict); + return (PdfDictionary) resdict; + } + catch (ClassCastException e) { + throw new PdfInvalidException (invres); + } + catch (IOException f) { + throw new PdfInvalidException (invres); + } + } + + /** + * Returns the dictionary of fonts within the node's Resources + * dictionary, if both exist. Otherwise returns null. + * The dictionary will most often have indirect object + * references as values. What is returned is not a + * Font dictionary, but rather a dictionary of Font + * dictionaries. + */ + public PdfDictionary getFontResources () throws PdfException + { + PdfDictionary resdict = getResources (); + if (resdict != null) { + try { + PdfObject fontdict = (PdfObject) resdict.get("Font"); + fontdict = _module.resolveIndirectObject (fontdict); + return (PdfDictionary) fontdict; + } + catch (Exception e) { + throw new PdfMalformedException + ("Invalid Font entry in Resources"); + } + } + else { + return null; + } + } + + /** + * Get the MediaBox of this node. MediaBox is an inheritable + * property, so it walks up the chain of ancestors if it doesn't + * contain one. Returns null if none. Throws a + * PdfInvalidException if an invalid MediaBox is found. + */ + public PdfArray getMediaBox () throws PdfInvalidException + { + final String badbox = "Malformed MediaBox in page tree"; + try { + PdfArray mbox = (PdfArray) get ("MediaBox", true); + if (mbox.toRectangle () != null) { + return mbox; + } + else { + // There's a MediaBox, but it's not a rectangle + throw new PdfInvalidException (badbox); + } + } + catch (Exception e) { + throw new PdfInvalidException (badbox); + } + } + + /** + * Get an named property. If this object doesn't + * have the specified property and inheritable + * is true, walks up the chain of ancestors + * to try to find one. If no ancestor has the property or + * inheritable is false, returns null. + */ + public PdfObject get (String key, boolean inheritable) + { + PdfObject val = _dict.get (key); + if (val == null) { + if (_parent == null || !inheritable) { + return null; + } + else { + return _parent.get (key, inheritable); + } + } + else { + return val; + } + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/FileSpecification.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/FileSpecification.java new file mode 100644 index 00000000..e11a7511 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/FileSpecification.java @@ -0,0 +1,86 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +/** + * A class which encapsulates a file specification in PDF. A file + * specification may be given as either a string or a dictionary. + * The specification is converted to a string according to the following + * rules: If a PDF string object is the file specifier, that string + * is used, without attempting to convert file separators to the local + * file system. If a PDF dictionary is used, one of the following is + * used, in decreasing order of preference: + * + *
    + *
  • The system-neutral file specification string + *
  • The Unix file specification string + *
  • The DOS file specification string + *
  • The Macintosh file specification string + *
+ */ +public class FileSpecification +{ + String _specString; + PdfObject _sourceObject; + + /** + * Constructor. + * + * @param obj A PdfDictionary with the file specification under the + * key "F", "Unix", "DOS", or "Mac"; or + * a PdfSimpleObject whose string value is the + * file specification. If obj is + * a dictionary and more than one key is specified, + * then the first of the keys F, Unix, DOS, and Mac + * to be found is used. + */ + public FileSpecification (PdfObject obj) throws PdfException + { + try { + _sourceObject = obj; + if (obj instanceof PdfDictionary) { + PdfDictionary dictObj = (PdfDictionary) obj; + PdfSimpleObject pathObj; + pathObj = (PdfSimpleObject) dictObj.get ("F"); + if (pathObj == null) { + pathObj = (PdfSimpleObject) dictObj.get ("Unix"); + } + if (pathObj == null) { + pathObj = (PdfSimpleObject) dictObj.get ("DOS"); + } + if (pathObj == null) { + pathObj = (PdfSimpleObject) dictObj.get ("Mac"); + } + if (pathObj != null) { + _specString = pathObj.getStringValue (); + } + } + else if (obj instanceof PdfSimpleObject) { + _specString = ((PdfSimpleObject) obj).getStringValue (); + } + } + catch (ClassCastException e) { + throw new PdfInvalidException ("Invalid file specification"); + } + } + + /** + * Returns the file specification as a string. + */ + public String getSpecString () + { + return _specString; + } + + + /** + * Returns the PdfObject from which the file specification was created. + */ + public PdfObject getSourceObject () + { + return _sourceObject; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/FileTokenizer.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/FileTokenizer.java new file mode 100644 index 00000000..f0f8cd7d --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/FileTokenizer.java @@ -0,0 +1,131 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2005 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import java.io.*; + +/** + * + * Tokenizer subclass which gets data from a RandomAccessFile. + * @author Gary McGath + * + */ +public class FileTokenizer extends Tokenizer { + + + /** Current offset to start of bytes stored in _fileBuffer */ + private long _fileBufferPositionOffset; + + /** File buffer */ + private byte[] _fileBuffer; + + /** Size for file buffer */ + private static final int FILEBUFSIZE = 4096; + + /** Number of valid bytes in fileBuffer */ + private int _fileBufferBytes; + + /** Offset to next valid byte in fileBuffer */ + private int _fileBufferOffset; + + + public FileTokenizer (RandomAccessFile file) + { + super (); + _file = file; + _fileBufferPositionOffset = -1; + _fileBuffer = new byte[FILEBUFSIZE]; + initFileBuffer (); + } + + private void initFileBuffer () + { + _fileBufferBytes = 0; + _fileBufferOffset = 0; + } + + + /** Gets the current position in the file. This method is + * aware of buffering. */ + public long getFilePos () throws IOException + { + return _fileBufferPositionOffset + _fileBufferOffset; + } + + + /** Gets a character from the file, using a buffer. */ + public int readChar () throws IOException + { + if (_fileBufferOffset >= _fileBufferBytes) { + // If the byte size is 0, we can assume a seek was already + // done, but otherwise we must seek safety. + if (_fileBufferBytes > 0) { + long newOffset = _fileBufferPositionOffset + _fileBufferOffset; + _file.seek (newOffset); + _fileBufferPositionOffset = newOffset; + } + _fileBufferBytes = _file.read(_fileBuffer); + if (_fileBufferBytes <= 0) { + throw new EOFException (); + } + _fileBufferOffset = 0; + } + return (int) (_fileBuffer[_fileBufferOffset++] & 0XFF); + } + + /** + * Set the Tokenizer to a new position in the file. + * + * @param offset The offset in bytes from the start of the file. + */ + public void seek (long offset) + throws IOException + { + if (_fileBufferPositionOffset >= 0 && + offset >= _fileBufferPositionOffset && + offset < _fileBufferPositionOffset + _fileBufferBytes) { + // Reposition within the buffer + _fileBufferOffset = (int) (offset - _fileBufferPositionOffset); + } + else { + _file.seek (offset); + initFileBuffer (); + _fileBufferPositionOffset = offset; + } + seekReset (offset); + } + + + /** + * Back up a byte so it will be read again. + */ + public void backupChar () + { + _fileBufferOffset--; + } + + /** Streams can occur only in files, not in streams, + * so some of the initialization of a stream object + * goes here. + */ + protected void initStream (Stream token) throws IOException + { + token.setOffset (getFilePos ()); + } + + + /** Sets the offset of a Stream to the current file position. + * Only the file-based tokenizer can do this, which is why this + * overrides the Tokenizer method. + */ + protected void setStreamOffset (Stream token) throws IOException + { + if (token.getOffset() < 0) { + token.setOffset (getFilePos ()); + } + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Filter.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Filter.java new file mode 100644 index 00000000..ed434684 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Filter.java @@ -0,0 +1,66 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2005 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +/** + * Container for a PDF stream filter. + * + * @author Gary McGath + * + */ +public class Filter { + + private String _filterName; + private PdfDictionary _decodeParms; + + /** + * Constructor. + * + * @param name The name of the filter. + */ + public Filter (String name) + { + _filterName = name; + } + + /** Returns the name of the filter. */ + public String getFilterName () + { + return _filterName; + } + + /** Returns the DecodeParms dictionary, or null if there is none. */ + public PdfDictionary getDecodeParms () + { + return _decodeParms; + } + + /** Returns the "Name" parameter of the filter, or null + * if there is no such parameter. + * This is normally associated with a Crypt filter, and + * shouldn't be confused with the name of the filter. + */ + public String getNameParam () + { + try { + if (_decodeParms != null) { + PdfSimpleObject obj = + (PdfSimpleObject) _decodeParms.get ("Name"); + return obj.getStringValue(); + } + } + catch (Exception e) { + } + return null; + } + + /** Stores the DecodeParms or FDecodeParms dictionary + * which is associated with this filter. */ + public void setDecodeParms (PdfDictionary parms) + { + _decodeParms = parms; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Hexadecimal.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Hexadecimal.java new file mode 100644 index 00000000..caddf455 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Hexadecimal.java @@ -0,0 +1,24 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +/** + * Class for Tokens which represent hexadecimally encoded PDF strings. + * This class really has no justification as a separate entity. Except + * for the way they're written, hexadecimal strings aren't different in + * any way from other strings. + * + * @deprecated + */ + public class Hexadecimal + extends Literal +{ + /** Creates an instance of a hexadecimal string literal */ + public Hexadecimal () + { + super (); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Keyword.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Keyword.java new file mode 100644 index 00000000..6df8f506 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Keyword.java @@ -0,0 +1,19 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +/** + * Class for Tokens which represent PDF keywords. + */ +public class Keyword + extends StringValuedToken +{ + /** Creates an instance of a Keyword */ + public Keyword () + { + super (); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/LinearizedProfile.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/LinearizedProfile.java new file mode 100644 index 00000000..cf7caae2 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/LinearizedProfile.java @@ -0,0 +1,302 @@ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import edu.harvard.hul.ois.jhove.module.*; +import java.util.*; + +/** + * PDF profile checker for Linearized documents. + */ +public final class LinearizedProfile extends PdfProfile +{ + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + + /** + * Creates a PdfProfile object for subsequent testing. + * + */ + public LinearizedProfile (PdfModule module) + { + super (module); + _profileText = "Linearized PDF"; + } + + /** + * Returns true if the document satisfies the profile. + * + */ + public boolean satisfiesThisProfile () + { + long fileLength; + try { + fileLength = _raf.length (); + // First we must find the first object from the beginning + // of the file. The documentation contradicts the examples + // on whether this is an indirect object or not. Based + // on the actual files I've seen so far, I think it isn't. + PdfObject firstObj = findFirstObject (); + if (!(firstObj instanceof PdfDictionary)) { + return false; + } + // Initial checks: that the first object is a linearization + // dictionary, and that it has a length element which matches + // the length of the file. + // All entries in a linearization dictionary must be direct. + PdfDictionary lindict = (PdfDictionary) firstObj; + if (lindict.get ("Linearized")!= null) { + PdfObject lengthObj = lindict.get ("L"); + if (lengthObj instanceof PdfSimpleObject) { + // The value of L must be the file length + Token lengthTok = + ((PdfSimpleObject) lengthObj).getToken (); + if (lengthTok instanceof Numeric) { + long length = ((Numeric) lengthTok).getLongValue (); + if (length != fileLength) { + return false; + } + } + else { + return false; + } + } + else { + return false; + } + } + else { + return false; + } + + // Next -- check the integrity of the hint tables. + // These are described in F.2.5 in the PDF 1.4 manual. + // The linearization dictionary must have an 'H' entry + // whose values is an array of 2 or 4 integers. + PdfArray hintArray = (PdfArray) lindict.get ("H"); + if (hintArray != null) { + Vector hintVec = hintArray.getContent (); + int vecSize = hintVec.size (); + if (vecSize != 2 && vecSize != 4) { + return false; + } + // element 0 (and 2, if present) is the offset + // of a stream object. Make sure it really is. + // Also check elements 1 and 3, which are lengths, + // for not overflowing the file. + for (int i = 0; i < vecSize; i += 2) { + PdfSimpleObject hobj = + (PdfSimpleObject) _module.resolveIndirectObject + ((PdfObject) hintVec.elementAt (i)); + Numeric hnum = (Numeric) hobj.getToken (); + long hoffset = hnum.getLongValue (); + hobj = (PdfSimpleObject) hintVec.elementAt (i + 1); + hnum = (Numeric) hobj.getToken (); + long hlen = hnum.getLongValue (); + if (hoffset + hlen > fileLength) { + return false; // hint dict runs past EOF + } + _parser.seek (hoffset); + // The documentation appears to lie here. What + // we find isn't the stream, but an object + // definition for the stream (of the form + // m n obj). Allow for both possibilities. + PdfObject hintStream = _parser.readObject (); + if (hintStream instanceof PdfSimpleObject) { + _parser.readObject (); // discard version no. + _parser.readObject (); // discard obj keyword + hintStream = _parser.readObject (); // the real thing + } + // Parser will see a dictionary, not the stream + if (!(hintStream instanceof PdfDictionary)) { + return false; + } + if (!validateHintStream ((PdfDictionary) hintStream)) { + return false; + } + } + } + else { + return false; + } + + // Check for valid first object number + PdfSimpleObject firstObjNum = (PdfSimpleObject) lindict.get ("O"); + if (! (firstObjNum.getToken () instanceof Numeric)) { + return false; + } + + // Check for valid offset to end of first page + PdfSimpleObject endpageObj = + (PdfSimpleObject) lindict.get ("E"); + Numeric endpageTok = (Numeric) endpageObj.getToken (); + long endpage = endpageTok.getLongValue (); + if (endpage > fileLength) { + return false; + } + + // Check for valid number of pages entry + PdfSimpleObject numpagesObj = + (PdfSimpleObject) lindict.get ("N"); + if (!(numpagesObj.getToken () instanceof Numeric)) { + return false; + } + + // Check offset to main cross-reference table + PdfSimpleObject xrefObj = + (PdfSimpleObject) lindict.get ("T"); + Numeric xrefTok = (Numeric) xrefObj.getToken (); + long xrefOffset = xrefTok.getLongValue (); + if (!verifyXRef (xrefOffset)) { + return false; + } + } + catch (Exception e) { + // An exception thrown anywhere means some assumption + // has been violated, so it's not linearized. + return false; + } + return true; // passed all tests + } + + /* Find the first object from the beginning of the file. + This is similar to, and perhaps a bit easier than, finding + the last dictionary. For the moment we don't worry about + what the object is. */ + private PdfObject findFirstObject () + { + try { + _parser.seek (8); + // To get in sync, read until we see the keyword + // "obj". + for (;;) { + Token tok = _parser.getNext (); + if (tok instanceof Keyword) { + if ("obj".equals(((Keyword) tok).getValue ())) { + PdfObject val = _parser.readObject (); + // Object must be completely contained in + // the first 1024 bytes. + if (_parser.getOffset () <= 1024) { + return val; + } + else { + return null; + } + } + } + if (_parser.getOffset () > 1024) { + return null; + } + } + + } + catch (Exception e) { + return null; + } + } + + + /* Read a cross-reference table to make sure it looks OK. + What we're pointing at is the first _entry_ of the + table, not the start of the subsection. + This means we don't know the object count, which makes + it very tough to figure out whether we're hit the + end or we really have an invalid table. + Settle for reading one object to see if it looks good. */ + private boolean verifyXRef (long xrefOffset) + { + try { + _parser.seek (xrefOffset); + _parser.getNext (Numeric.class, ""); // Object number + _parser.getNext (Numeric.class, ""); // Generation number + _parser.getNext (Keyword.class, ""); // n or f keyword + // If that didn't throw an exception, assume we're ok + return true; + } + catch (Exception e) { + return false; + } + } + + /* Check that a hint stream dictionary has some semblance + of validility. */ + private boolean validateHintStream (PdfDictionary hDict) + { + try { + // An offset to the shared object hint table + // is the one thing that's required. + PdfSimpleObject obj = (PdfSimpleObject) + hDict.get ("S"); + if (obj == null) { + return false; + } + int offset = obj.getIntValue (); + if (offset < 0) { + return false; + } + + // Other objects aren't required, but must be + // non-negative integers if they're there. + obj = (PdfSimpleObject) hDict.get ("T"); + if (obj != null) { + offset = obj.getIntValue (); + if (offset < 0) { + return false; + } + } + obj = (PdfSimpleObject) hDict.get ("O"); + if (obj != null) { + offset = obj.getIntValue (); + if (offset < 0) { + return false; + } + } + obj = (PdfSimpleObject) hDict.get ("A"); + if (obj != null) { + offset = obj.getIntValue (); + if (offset < 0) { + return false; + } + } + obj = (PdfSimpleObject) hDict.get ("E"); + if (obj != null) { + offset = obj.getIntValue (); + if (offset < 0) { + return false; + } + } + obj = (PdfSimpleObject) hDict.get ("V"); + if (obj != null) { + offset = obj.getIntValue (); + if (offset < 0) { + return false; + } + } + obj = (PdfSimpleObject) hDict.get ("I"); + if (obj != null) { + offset = obj.getIntValue (); + if (offset < 0) { + return false; + } + } + obj = (PdfSimpleObject) hDict.get ("L"); + if (obj != null) { + offset = obj.getIntValue (); + if (offset < 0) { + return false; + } + } + obj = (PdfSimpleObject) hDict.get ("C"); + if (obj != null) { + offset = obj.getIntValue (); + if (offset < 0) { + return false; + } + } + return true; // passed all tests + } + catch (Exception e) { + return false; + } + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Literal.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Literal.java new file mode 100644 index 00000000..dfb7a5fb --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Literal.java @@ -0,0 +1,741 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import java.util.*; +import java.text.*; +import java.io.EOFException; +import java.io.IOException; + +/** + * Class for Tokens which represent PDF strings. The class maintains + * a field for determining whether the string is encoded as PDF encoding + * or UTF-16. This is determined in the course of analyzing the + * characters for the token. + */ +public class Literal + extends StringValuedToken +{ + /** True if literal is in PDFDocEncoding; false if UTF-16. */ + private boolean _pdfDocEncoding; + + /** Used for accumulating a hex string */ + private StringBuffer rawHex; + + /** Used for accomodating the literal */ + private StringBuffer buffer; + + /** Indicates if a character for the first half of a hex byte + has already been buffered */ + private boolean haveHi; + + /** The high half-byte character */ + private int hi; + + /** First byte of a UTF-16 character. */ + int b1; + + /** First digit of a hexadecimal string value. */ + //int h1; + + /** The state of the tokenization. Only the subset of States which + pertain to Literals are used here. */ + private State _state; + + /** True if no discrepancies with PDF/A requirements have been found, + false if there is a discrepancy in this literal. */ + private boolean _pdfACompliant; + + /** Depth of parenthesis nesting. */ + private int _parenLevel; + + /** Mapping between PDFDocEncoding and Unicode code points. */ + public static char [] PDFDOCENCODING = { + '\u0000','\u0001','\u0002','\u0003','\u0004','\u0005','\u0006','\u0007', + '\b' ,'\t' ,'\n' ,'\u000b','\f' ,'\r' ,'\u000e','\u000f', + '\u0010','\u0011','\u0012','\u0013','\u0014','\u0015','\u0016','\u0017', + '\u02d8','\u02c7','\u02c6','\u02d9','\u02dd','\u02db','\u02da','\u02dc', + '\u0020','\u0021','\"' ,'\u0023','\u0024','\u0025','\u0026','\'', + '\u0028','\u0029','\u002a','\u002b','\u002c','\u002d','\u002e','\u002f', + '\u0030','\u0031','\u0032','\u0033','\u0034','\u0035','\u0036','\u0037', + '\u0038','\u0039','\u003a','\u003b','\u003c','\u003d','\u003e','\u003f', + '\u0040','\u0041','\u0042','\u0043','\u0044','\u0045','\u0046','\u0047', + '\u0048','\u0049','\u004a','\u004b','\u004c','\u004d','\u004e','\u004f', + '\u0050','\u0051','\u0052','\u0053','\u0054','\u0055','\u0056','\u0057', + '\u0058','\u0059','\u005a','\u005b','\\' ,'\u005d','\u005e','\u005f', + '\u0060','\u0061','\u0062','\u0063','\u0064','\u0065','\u0066','\u0067', + '\u0068','\u0069','\u006a','\u006b','\u006c','\u006d','\u006e','\u006f', + '\u0070','\u0071','\u0072','\u0073','\u0074','\u0075','\u0076','\u0077', + '\u0078','\u0079','\u007a','\u007b','\u007c','\u007d','\u007e','\u007f', + '\u2022','\u2020','\u2021','\u2026','\u2003','\u2002','\u0192','\u2044', + '\u2039','\u203a','\u2212','\u2030','\u201e','\u201c','\u201d','\u2018', + '\u2019','\u201a','\u2122','\ufb01','\ufb02','\u0141','\u0152','\u0160', + '\u0178','\u017d','\u0131','\u0142','\u0153','\u0161','\u017e','\u009f', + '\u20ac','\u00a1','\u00a2','\u00a3','\u00a4','\u00a5','\u00a6','\u00a7', + '\u00a8','\u00a9','\u00aa','\u00ab','\u00ac','\u00ad','\u00ae','\u00af', + '\u00b0','\u00b1','\u00b2','\u00b3','\u00b4','\u00b5','\u00b6','\u00b7', + '\u00b8','\u00b9','\u00ba','\u00bb','\u00bc','\u00bd','\u00be','\u00bf', + '\u00c0','\u00c1','\u00c2','\u00c3','\u00c4','\u00c5','\u00c6','\u00c7', + '\u00c8','\u00c9','\u00ca','\u00cb','\u00cc','\u00cd','\u00ce','\u00cf', + '\u00d0','\u00d1','\u00d2','\u00d3','\u00d4','\u00d5','\u00d6','\u00d7', + '\u00d8','\u00d9','\u00da','\u00db','\u00dc','\u00dd','\u00de','\u00df', + '\u00e0','\u00e1','\u00e2','\u00e3','\u00e4','\u00e5','\u00e6','\u00e7', + '\u00e8','\u00e9','\u00ea','\u00eb','\u00ec','\u00ed','\u00ef','\u00ef', + '\u00f0','\u00f1','\u00f2','\u00f3','\u00f4','\u00f5','\u00f6','\u00f7', + '\u00f8','\u00f9','\u00fa','\u00fb','\u00fc','\u00fd','\u00fe','\u00ff' + }; + + + private static final int CR = 0x0D; + private static final int LF = 0x0A; + private static final int BS = 0x08; + private static final int HT = 0x09; + private static final int FORMFEED = 0x0C; + private static final int ESC = 0X1B; + private static final int OPEN_PARENTHESIS = 0x28; + private static final int CLOSE_PARENTHESIS = 0x29; + private static final int BACKSLASH = 0x5C; + private static final int FE = 0xFE; + private static final int FF = 0xFF; + + /** Creates an instance of a string literal */ + public Literal () + { + super (); + _pdfDocEncoding = true; + rawHex = new StringBuffer (); + buffer = new StringBuffer (); + haveHi = false; + } + + /** + * Append a hex character. This is used only for hex literals + * (those that start with '<'). + * + * @param ch The integer 8-bit code for a hex character + */ + public void appendHex (int ch) throws PdfException + { + if (_rawBytes == null) { + _rawBytes = new Vector (32); + } + if (haveHi) { + _rawBytes.add(new Integer (hexToInt (hi, ch))); + haveHi = false; + } + else { + hi = ch; + haveHi = true; + } + } + + /** + * Process the incoming characters into a string literal. + * This is used for literals delimited + * by parentheses, as opposed to hex strings. + * + * @param tok The tokenizer, passed to give access to its getChar + * function. + * @return true if the character was processed + * normally, false if a terminating + * parenthesis was reached. + */ + public long processLiteral (Tokenizer tok) throws IOException + { + /** Variable for UTF-16 chars. */ + int utfch = 0; + /** First byte of a UTF-16 character. */ + int b1 = 0x00; + /* Character read from tokenizer. */ + int ch; + _parenLevel = 0; + _rawBytes = new Vector (32); + _state = State.LITERAL; + + long offset = 0; + for (;;) { + ch = tok.readChar (); + // If we get -1, then we've hit an EOF without proper termination of + // the literal. Throw an exception. + if (ch < 0) { + throw new EOFException ("Unterminated literal in PDF file"); + } + offset++; + _rawBytes.add (new Integer (ch)); + + if (_state == State.LITERAL) { + // We are still in a state of flux, determining the encoding + if (ch == FE) { + _state = State.LITERAL_FE; + } + else if (ch == CLOSE_PARENTHESIS && --_parenLevel < 0) { + // We have an empty string + setPDFDocEncoding (true); + setValue(buffer.toString()); + return offset; + } + else if (ch == BACKSLASH) { + ch = readBackslashSequence (false, tok); + if (ch == 0) { + continue; // invalid character, ignore + } + else if (ch == FE) { + _state = State.LITERAL_FE; + } + else { + // any other char is treated nonspecially + setPDFDocEncoding (true); + buffer.append (PDFDOCENCODING[ch]); + } + } + else { + // We now know we're in 8-bit PDF encoding. + // Append the character to the buffer. + if (ch == OPEN_PARENTHESIS) { + // Count open parens to be matched by close parens. + // Backslash-quoted parens won't get here. + ++_parenLevel; + } + _state = State.LITERAL_PDF; + setPDFDocEncoding (true); + buffer.append (PDFDOCENCODING[ch]); + } + } + else if (_state == (State.LITERAL_FE)) { + if (ch == FF) { + _state = State.LITERAL_UTF16_1; + setPDFDocEncoding (false); + } + else if (ch == BACKSLASH) { + ch = readBackslashSequence (false, tok); + if (ch == 0) { + continue; // invalid character, ignore + } + if (ch == FF) { + _state = State.LITERAL_UTF16_1; + setPDFDocEncoding (false); + } + else { + // any other char is treated nonspecially + setPDFDocEncoding (true); + // The FE is just an FE, put it in the buffer + buffer.append (PDFDOCENCODING[FE]); + buffer.append (PDFDOCENCODING[ch]); + } + } + else { + _state = State.LITERAL_PDF; + setPDFDocEncoding (true); + // The FE is just an FE, put it in the buffer + buffer.append (PDFDOCENCODING[FE]); + buffer.append (PDFDOCENCODING[ch]); + } + } + else if (_state == (State.LITERAL_PDF)) { + if (ch == CLOSE_PARENTHESIS && --_parenLevel < 0) { + setValue(buffer.toString()); + return offset; + } + else if (ch == BACKSLASH) { + ch = readBackslashSequence (false, tok); + if (ch == 0) { + continue; // invalid character, ignore + } + else { + // any other char is treated nonspecially + buffer.append (PDFDOCENCODING[ch]); + } + } + else { + buffer.append (PDFDOCENCODING[ch]); + } + } + else if (_state == (State.LITERAL_UTF16_1)) { + // First byte of a UTF16 character. But a close + // paren or backslash is a single-byte character. + // Parens within the string are double-byte characters, + // so we don't have to worry about them. + if (ch == CLOSE_PARENTHESIS) { + setValue(buffer.toString()); + return offset; + } + else if (ch == BACKSLASH) { + utfch = readBackslashSequence (true, tok); + if (utfch == 0) { + continue; // invalid character, ignore + } + } + else { + _state = State.LITERAL_UTF16_2; + b1 = ch; + } + } + else if (_state == (State.LITERAL_UTF16_2)) { + // Second byte of a UTF16 character. + utfch = 256 * b1 + ch; + _state = State.LITERAL_UTF16_1; + // an ESC may appear at any point to signify + // a language code. Remove the language code + // from the stream and save it in a list of codes. + if (utfch == ESC) { + readUTFLanguageCode (tok); + } + else { + /* It turns out that a backslash may be double-byte, + * rather than the assumed single.byte. The following + * allows for this. Suggested by Justin Litman, Library + * of Congress, 2006-03-17. + */ + if (utfch == BACKSLASH) { + utfch = readBackslashSequence (false, tok); + if (utfch == 0) { + continue; /* Invalid character, ignore. */ + } + } + buffer.append ((char) utfch); + } + } + } + } + + + + /** + * Convert the raw hex data. Two buffers are saved: _rawBytes + * for the untranslated hex-encoded data, and _value for the + * PDF or UTF encoded string. + */ + public void convertHex () throws PdfException + { + boolean utf = false; + StringBuffer buffer = new StringBuffer (); + // If a high byte is left hanging, complete it with a '0' + if (haveHi) { + _rawBytes.add (new Integer (hexToInt (hi, '0'))); + } + if (_rawBytes.size () >= 2 && rawByte (0) == 0XFE && + rawByte(1) == 0XFF) { + utf = true; + } + if (utf) { + // Gather pairs of bytes into characters without conversion + for (int i = 2; i < _rawBytes.size(); i += 2) { + buffer.append + ((char) (rawByte (i) * 256 + rawByte (i + 1))); + } + } + else { + // Convert single bytes to PDF encoded characters. + for (int i = 0; i < _rawBytes.size (); i++) { + buffer.append (Tokenizer.PDFDOCENCODING[rawByte (i)]); + } + } + _value = buffer.toString (); + } + + private static int hexToInt (int c1, int c2) throws PdfException + { + return 16 * hexValue (c1) + hexValue (c2); + } + + private static int hexValue (int h) throws PdfException + { + int d = 0; + if (0x30 <= h && h <= 0x39) { + // digit 0-9 + d = h - 0x30; + } + else if (0x41 <= h && h <= 0x46) { + // letter A-F + d = h - 0x37; + } + else if (0x61 <= h && h <= 0x66) { + // letter a-f + d = h - 0x57; + } + else { + throw new PdfMalformedException ("Invalid character in hex string"); + } + return d; + } + + + /* Extract a byte from _rawBytes. In order to allow for byte-short + situations, any byte off the end is returned as 0. */ + private int rawByte (int idx) + { + if (idx >= _rawBytes.size ()) { + return 0; + } + return ((Integer) _rawBytes.elementAt (idx)).intValue(); + } + + + /** + * Returns true if this string is in PDFDocEncoding, + * false if UTF-16. + */ + public boolean isPDFDocEncoding () + { + return _pdfDocEncoding; + } + + /** + * Sets the value of pDFDocEncoding. + */ + public void setPDFDocEncoding (boolean pdfDocEncoding) + { + _pdfDocEncoding = pdfDocEncoding; + } + + /** + * Returns true if the string value is a parsable date. + * Conforms to the ASN.1 date format: D:YYYYMMDDHHmmSSOHH'mm' + * where everything before and after YYYY is optional. + * If we take this literally, the format is frighteningly ambiguous + * (imagine, for instance, leaving out hours but not minutes and + * seconds), so the checking is a bit loose. + */ + public boolean isDate () + { + return parseDate () != null; + } + + /** + * Parse the string value to a date. PDF dates conform to + * the ASN.1 date format. This consists of + * D:YYYYMMDDHHmmSSOHH'mm' + * where everything before and after YYYY is optional. + * Adobe doesn't actually say so, but I'm assuming that if a + * field is included, everything to its left must be included, + * e.g., you can't have seconds but leave out minutes. + */ + public Date parseDate () + { + int year = 0; + int month = 0; + int day = 0; + int hour = 0; + int minute = 0; + int second = 0; + char timezonechar = '?'; // +, -, or Z + int timezonehour = 0; + int timezoneminute = 0; + Calendar cal = null; + + String str = getValue (); + if (str == null) { + return null; + } + str = str.trim (); + if (str.length() < 4) { + return null; + } + int datestate = 0; + int charidx = 0; + try { + wloop: + while (charidx < str.length ()) { + // We parse the date using a simple state machine, + // with a state for each date component. + switch (datestate) { + case 0: // starting state, may start with "D:" + if ("D:".equals (str.substring (charidx, charidx + 2))) { + charidx += 2; + } + datestate = 1; // advance regardless + break; + + case 1: // expecting year + year = Integer.parseInt (str.substring (charidx, charidx + 4)); + charidx += 4; + datestate = 2; + break; + + case 2: // expecting month + month = Integer.parseInt (str.substring (charidx, charidx+2)); + charidx += 2; + datestate = 3; + break; + + case 3: // expecting day of month + day = Integer.parseInt (str.substring (charidx, charidx + 2)); + if (day < 1 || day > 31) { + return null; + } + charidx += 2; + datestate = 4; + break; + + case 4: // expecting hour (00-23) + hour = Integer.parseInt (str.substring (charidx, charidx + 2)); + charidx += 2; + datestate = 5; + break; + + case 5: // expecting minute (00-59) + minute = Integer.parseInt (str.substring (charidx, charidx+2)); + charidx += 2; + datestate = 6; + break; + + case 6: // expecting second (00-59) + second = Integer.parseInt (str.substring (charidx, charidx+2)); + charidx += 2; + datestate = 7; + break; + + case 7: // expecting time zone ('+', '-', or 'Z') + timezonechar = str.charAt (charidx); + if (timezonechar != 'Z' && timezonechar != '+' && + timezonechar != '-') { + return null; + } + charidx++; + datestate = 8; + break; + + case 8: // expecting time zone hour. + // ignore if timezonechar is 'Z' + if (timezonechar == '+' || timezonechar == '-') { + timezonehour = Integer.parseInt (str.substring (charidx, + charidx + 2)); + if (timezonechar == '-') { + timezonehour = -timezonehour; + } + // Time zone hour must have trailing quote + if (!str.substring (charidx+2, charidx+3).equals ("'")) { + return null; + } + charidx += 3; + } + datestate = 9; + break; + + case 9: // expecting time zone minute -- in single quotes + // ignore if timezonechar is 'Z' + if (timezonechar == '+' || timezonechar == '-') { + if (str.charAt (charidx) == '\'') { + timezoneminute = + Integer.parseInt (str.substring (charidx, + charidx + 2)); + } + if (timezonechar == '-') { + timezoneminute = -timezoneminute; + } + // Time zone minute must have trailing quote + if (!str.substring (charidx+2, charidx+3).equals ("'")) { + return null; + } + } + break wloop; + } + } + } + // Previously, we assumed that a parsing exception meant the + // end of the date. This is too permissive; an exception means + // that the date is not well-formed. + catch (Exception e) { + return null; + } + if (datestate < 2) { + return null; // not enough fields + } + else { + // First we must construct the time zone string, then use + // it to make a TimeZone object. + if (timezonechar != '?') { + String tzStr = "GMT"; + if (timezonechar == 'Z') { + tzStr += "+0000"; + } + else { + tzStr += timezonechar; + NumberFormat nfmt = NumberFormat.getInstance (); + nfmt.setMinimumIntegerDigits (2); + nfmt.setMaximumIntegerDigits (2); + tzStr += nfmt.format (timezonehour); + tzStr += nfmt.format (timezoneminute); + } + TimeZone tz = TimeZone.getTimeZone (tzStr); + + // Use that TimeZone to create a Calendar with our date. + // Note that Java months are 0-based. + cal = Calendar.getInstance (tz); + } + else { + // time zone is unspecified + cal = Calendar.getInstance (); + } + cal.set (year, month - 1, day, hour, minute, second); + return cal.getTime (); + } + } + + + /** + * Returns true if this token doesn't violate any + * PDF/A rules, false if it does. + */ + public boolean isPDFACompliant () + { + return _pdfACompliant; + } + + +/* private void beginBackslashState () + { + octalBufLen = 0; + backslashFlag = true; + } +*/ + + + + /** After a backslash, read characters into an escape + sequence. If we don't find a valid escape sequence, + return 0. + */ + private int readBackslashSequence (boolean utf16, Tokenizer tok) + throws IOException + { + int ch = tok.readChar1 (utf16); + if (ch >= 0X30 && ch <= 0X37) { + int num = ch - 0X30; + // Read octal sequence. We may get 1, 2, or 3 characters. + // If we get a non-numeric character, we're done and we + // put it back. + for (int i = 0; i < 2; i++) { + int ch1 = tok.readChar1 (utf16); + if (ch1 >= 0X30 && ch1 <= 0X37) { + num = num * 8 + (ch1 - 0X30); + } + else { + //_fileBufferOffset--; // put it back + tok.backupChar (); // add this function to Tokenizer**** + _pdfACompliant = false; // octal sequences must be 3 chars in PDF/A + return num; + } + } + return num; + } + switch (ch) { + case 0X6E: // n + return LF; + case 0X72: // r + return CR; + case 0X74: // t + return HT; + case 0X68: // h + return BS; + case 0X66: // f + return FORMFEED; + case OPEN_PARENTHESIS: + return OPEN_PARENTHESIS; + case CLOSE_PARENTHESIS: + return CLOSE_PARENTHESIS; + case BACKSLASH: + return BACKSLASH; + default: + return 0; + } + } + + + /** We have just read an ESC in a UTF string. + Save all character up to and exclusive of the next ESC + as a language code. + */ + private void readUTFLanguageCode (Tokenizer tok) throws IOException + { + StringBuffer sb = new StringBuffer (); + for (;;) { + int ch = tok.readChar1(true); + if (ch == ESC) { + break; + } + sb.append ((char) ch); + } + tok.addLanguageCode (sb.toString ()); // ****add this to Tokenizer + //_languageCodes.add (sb.toString ()); + } + + /** If we're in the backslash substate (backslashFlag = true), then call + this to process characters. It will accumulate octal digits into + octalBuf and process other escaped characters. If the accumulation + produces a character, it will return that character code, otherwise + it will return 0 to indicate no character is available yet. + + Althought the backslash itself is a byte, even in a 16-bit + string, the characters which follow it are 16-bit characters, + not bytes. So we call this only after applying UTF-16 encoding + where applicable. + */ + + /* DEPRECATED for the current millisecond */ +/* private int backslashProcess (int ch) + { + if (ch >= 0X30 && ch <= 0X37) { + int num = ch - 0X30; + // An octal sequence may have 1, 2, or 3 characters. + // If we get a non-numeric character, we're done and + // return the character, and put the character we + // just received into a holding buffer. + octalBuf[octalBufLen++] = num; + if (octalBufLen == 3) { + return octalBufValue (); + } + for (int i = 0; i < 2; i++) { + int ch1 = readChar1 (utf16); + if (ch1 >= 0X30 && ch1 <= 0X37) { + num = num * 8 + (ch1 - 0X30); + } + else { + holdChar = ch; + _pdfACompliant = false; // octal sequences must be 3 chars in PDF/A + return num; + } + } + return num; + } + + // If no octal characters have been seen yet, look for an + // escaped character. + if (octalBufLen == 0) { + switch (ch) { + case 0X6E: // n + return LF; + case 0X72: // r + return CR; + case 0X74: // t + return HT; + case 0X68: // h + return BS; + case 0X66: // f + return FORMFEED; + case OPEN_PARENTHESIS: + return OPEN_PARENTHESIS; + case CLOSE_PARENTHESIS: + return CLOSE_PARENTHESIS; + case BACKSLASH: + return BACKSLASH; + default: + // illegal escape -- dump the character + return 0; + } + else { + // We have one or two buffered octal characters, + // but this isn't one. Put the current character + // in a holding buffer, and return the octal value. + holdCh = ch; + return octalBufValue (); + } + } + } + */ +} + + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Name.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Name.java new file mode 100644 index 00000000..fe67ffe7 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Name.java @@ -0,0 +1,24 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +/** + * Class for Tokens which represent PDF names. + */ +public class Name + extends StringValuedToken +{ + /** Creates an instance of a Name */ + public Name () + { + super (); + } + + /** Returns true if it's within the PDF/A implementation limit */ + public boolean isPdfACompliant () { + return _value.getBytes().length <= 127; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/NameTreeNode.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/NameTreeNode.java new file mode 100644 index 00000000..c1611e85 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/NameTreeNode.java @@ -0,0 +1,215 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import edu.harvard.hul.ois.jhove.module.PdfModule; +import java.util.*; + +/** + * Class for nodes of a PDF name tree, e.g., ExternalFiles. Name trees + * are intended for large amounts of data that won't have to all be brought + * into memory at once; so this implementation is geared toward file-based + * lookup of a key rather than creating an in-memory structure. Descendant + * nodes become available for garbage collection if they are not on the + * search path for a match. + * + * Keys are collated according to raw bytes, not character encoding. + */ +public class NameTreeNode +{ + protected PdfModule _module; + protected NameTreeNode _parent; + protected PdfDictionary _dict; // dictionary which defines this node + //private int _prevKey; // Key previously obtained in traversing tree + //private PdfObject _prevValue; // Value previously obtained in traversing tree + //private NameTreeNode _currentDescendant; + private Vector _kids; + private Vector _names; + private Vector _lowerLimit; // Lower limit of keys for this node -- null for root + private Vector _upperLimit; // Upper limit of keys for this node -- null for root + + + /** + * Constructor. + * @param module The PdfModule under which we're operating + * @param parent The parent node in the document tree; + * may be null only for the root node + * @param dict The dictionary object on which this node + * is based + */ + public NameTreeNode (PdfModule module, + NameTreeNode parent, + PdfDictionary dict) throws PdfException + { + final String inval = "Invalid name tree"; + _module = module; + _parent = parent; + _dict = dict; + + try { + // Get the limits of the key range. If there are no limits, this + // must be the root node. + PdfArray limitsDict = (PdfArray) module.resolveIndirectObject + (dict.get ("Limits")); + if (limitsDict == null) { + _lowerLimit = null; + _upperLimit = null; + } + else { + Vector vec = limitsDict.getContent (); + PdfSimpleObject limobj = (PdfSimpleObject) vec.elementAt (0); + _lowerLimit = limobj.getRawBytes (); + //dumpKey (_lowerLimit, "Lower limit: "); + limobj = (PdfSimpleObject) vec.elementAt (1); + _upperLimit = limobj.getRawBytes (); + //dumpKey (_upperLimit, "Upper limit: "); + } + + // Get the Kids and Names arrays. Normally only one will + // be present. + PdfArray kidsVec = (PdfArray) module.resolveIndirectObject + (dict.get ("Kids")); + if (kidsVec != null) { + _kids = kidsVec.getContent (); + } + else { + _kids = null; + } + PdfArray namesVec = (PdfArray) module.resolveIndirectObject + (dict.get ("Names")); + if (namesVec != null) { + _names = namesVec.getContent (); + } + else { + _names = null; + } + } + catch (ClassCastException ce) { + throw new PdfInvalidException (inval); + } + catch (NullPointerException ce) { + throw new PdfInvalidException (inval); + } + catch (Exception e) { + throw new PdfMalformedException (inval); + } + } + + /** + * See if a key is within the bounds of this node. All keys + * are within the bounds of the root node. + */ + public boolean inBounds (Vector key) + { + if (_lowerLimit == null) { + return true; // root node + } + else { + if (compareKey (key, _lowerLimit) < 0 || + compareKey (key, _upperLimit) > 0) { + return false; + } + return true; + } + } + + + /** + * Get the PdfObject which matches the key, or null if there is no match. + */ + public PdfObject get (Vector key) throws PdfException + { + final String invtree = "Invalid name tree"; + try { + if (!inBounds (key)) { + return null; + } + // If this has a Names array, it's a leaf node or standalone root; + // search it for the key. + if (_names != null) { + for (int i = 0; i < _names.size (); i += 2) { + PdfSimpleObject k1 = (PdfSimpleObject) _names.elementAt (i); + int cmp = compareKey (key, k1.getRawBytes ()); + if (cmp == 0) { + /* Match! */ + return _module.resolveIndirectObject + ((PdfObject) _names.elementAt (i + 1)); + } + else if (cmp < 0) { + // Passed position where match should be + return null; + } + } + return null; // just not there + } + else if (_kids != null) { + // It's a non-standalone root or an intermediate note. + // Figure out which descendant we should search. + for (int i = 0; i < _kids.size (); i++) { + PdfDictionary kid = (PdfDictionary) + _module.resolveIndirectObject ( + (PdfObject) _kids.elementAt (i)); + NameTreeNode kidnode = new NameTreeNode (_module, this, kid); + if (kidnode.inBounds (key)) { + return kidnode.get (key); + } + } + return null; // Not in any subnode + } + else throw new PdfMalformedException (invtree); + } + catch (PdfException e1) { + throw e1; + } + catch (Exception e) { + throw new PdfMalformedException (invtree); + } + } + + /* Compare two keys (Vectors of Integer). Returns -1 if the + first argument is less than the second, 1 if the first argument + is greater, and 0 if they are equal. Key A is less than key B + if A is a prefix of B. */ + private int compareKey (Vector a, Vector b) { + int lena = a.size (); + int lenb = b.size (); + int len = (lena < lenb ? lena : lenb); + for (int i = 0; i < len; i++) { + int ai = ((Integer) a.elementAt (i)).intValue (); + int bi = ((Integer) b.elementAt (i)).intValue (); + if (ai < bi) { + return -1; + } + else if (ai > bi) { + return 1; + } + } + // Both are equal as far as the length of the shorter one goes. + // To be equal, they must have the same length; otherwise the + // shorter one is the lesser. + if (lena == lenb) { + return 0; + } + else if (lena < lenb) { + return -1; + } + else { + return 1; + } + } + + + /* Debugging code */ + private void dumpKey (Vector v, String label) + { + System.out.print (label); + for (int i = 0; i < v.size (); i++) { + System.out.print (v.elementAt (i).toString () + " "); + } + System.out.println (); + } +} + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Numeric.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Numeric.java new file mode 100644 index 00000000..629ccf47 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Numeric.java @@ -0,0 +1,107 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +/** + * Class for Tokens which represent PDF numbers. Numeric values + * are stored as double if they have a real value, or + * as int if they have an integer value. The implementation notes + * state that the maximum value of an integer on a 32-bit machine + * is 2 ^ 31 - 1. However, they also say that byte offsets have + * a limit of 10 decimal digits, which is a larger value. So we + * store integer values as long. + */ +public class Numeric + extends Token +{ + /** True if real value; false if integer. */ + private boolean _real; + private double _realValue; + private long _intValue; + + /** Creates an instance of a Numeric */ + public Numeric () + { + super (); + _real = false; + _intValue = 0; + } + + /** Returns the value, converted to an integer */ + public int getIntegerValue () + { + if (_real) { + return (int) _realValue; + } + else { + return (int) _intValue; + } + } + + /** Returns the value, converted to a long */ + public long getLongValue () + { + if (_real) { + return (long) _realValue; + } + else { + return _intValue; + } + } + + /** Returns the value of this Numeric as a double */ + public double getValue () + { + if (_real) { + return _realValue; + } + else { + return (double) _intValue; + } + } + + /** + * Returns true if the value is stored as a floating-point + * number. + */ + public boolean isReal () + { + return _real; + } + + /** + * Set this object's value to a double. + */ + public void setValue (double value) + { + _realValue = value; + _real = true; + } + + + /** + * Set this object's value to a long. + */ + public void setValue (long value) + { + _intValue = value; + _real = false; + } + + /** Returns true if this is within PDF/A implementation limits. */ + public boolean isPdfACompliant () { + if (_real) { + double absRealValue = (_realValue < 0 ? -_realValue : _realValue); + return (absRealValue <= 3.404E38); + } + else { + return (_intValue <= 2147483647 && _intValue >= -2147483648); + } + + } + +} + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/ObjectStream.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/ObjectStream.java new file mode 100644 index 00000000..b6de08b0 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/ObjectStream.java @@ -0,0 +1,138 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2005 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import java.io.*; +import java.util.*; + +/** + * This class implements the Object Stream, a new way of storing + * objects starting in PDF 1.4. + * + * An object stream can contain one or more objects, as described in + * Section 3.4 of the PostScript manual. + * + * JHOVE supports only FlateDecode as a filter for cross-reference + * streams. This is consistent with the implementation limitation + * described in Appendix H of the PDF manual for Acrobat 6 and earlier. + * + * + * @author Gary McGath + * + */ +public class ObjectStream { + + private PdfStream _ostrm; // The underlying Stream object. + private PdfDictionary _dict; + private int _numObjects; + private int _firstOffset; + private Parser _parser; + private RandomAccessFile _raf; + + /* Index of the object stream. Each element is an int[2], + * consisting of an object number and an offset. + */ + private Map _index; + + /** + * Constructor. + */ + public ObjectStream(PdfStream ostrm, RandomAccessFile raf) { + _ostrm = ostrm; + _raf = raf; + _dict = ostrm.getDict (); + _parser = new Parser (new StreamTokenizer (raf, _ostrm.getStream())); + } + + /** Checks the validity of the stream dictionary, and extracts + * information necessary for subsequent reading. + */ + public boolean isValid () + { + try { + /* Type must be ObjStm */ + PdfObject obj = _dict.get ("Type"); + String typeStr = null; + if (obj instanceof PdfSimpleObject) { + typeStr = ((PdfSimpleObject) obj).getStringValue (); + } + if (!("ObjStm".equals (typeStr))) { + return false; + } + /* Number of objects */ + obj = _dict.get ("N"); + if (obj instanceof PdfSimpleObject) { + _numObjects = ((PdfSimpleObject) obj).getIntValue(); + } + else { + return false; + } + /* Offset of first object */ + obj = _dict.get ("First"); + if (obj instanceof PdfSimpleObject) { + _firstOffset = ((PdfSimpleObject) obj).getIntValue(); + } + else { + return false; + } + /* Optional refernce to object stream which this extends. */ + obj = _dict.get ("Extends"); + if (obj != null) { + /* What do we do with this? */ + } + return true; + } + catch (Exception e) { + return false; + } + + } + + + /** Reads the index of the object stream. + */ + public void readIndex () + throws PdfException, IOException + { + Stream strm = _ostrm.getStream (); + strm.setFilters (_ostrm.getFilters ()); + strm.initRead (_raf); + _index = new HashMap (_numObjects); + for (int i = 0; i < _numObjects; i++) { + /* If I'm reading it correctly, the numbers are + * encoded as ASCII strings separated by white space. + * I don't know what the restrictions, if any, are on + * the white space. + */ + Integer onum = new Integer (strm.readAsciiInt ()); + Integer offset = new Integer (strm.readAsciiInt ()); + _index.put (onum, offset); + } + } + + /** Extracts an object from the stream. */ + public PdfObject getObject (int objnum) + throws PdfException + { + Integer onum = new Integer (objnum); + Integer off = (Integer) _index.get (onum); + try { + if (off != null) { + int offset = off.intValue (); + _parser.seek (offset + _firstOffset); + return _parser.readObject (); + } + else { + return null; + } + } + catch (IOException e) { + throw new PdfMalformedException + ("Offset out of bounds in object stream"); + } + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PageLabelNode.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PageLabelNode.java new file mode 100644 index 00000000..6199c19b --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PageLabelNode.java @@ -0,0 +1,368 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import edu.harvard.hul.ois.jhove.module.PdfModule; +import java.util.*; + +/** + * Class for nodes of a PDF number tree. + */ +public class PageLabelNode +{ + /** The PdfModule this node is associated with. */ + protected PdfModule _module; + + /** The parent node of this node. */ + protected PageLabelNode _parent; + + /** The dictionary which defines this node. */ + protected PdfDictionary _dict; + + /** Set to true when all subnodes of this node + * have been iterated through following a StartWalk. */ + protected boolean _walkFinished; + + private List _descendants; + private Iterator _descendantsIter; + private int _currentKey; // Key most recently obtained in traversing tree + private PdfObject _currentValue; // Value most recently obtained in traversing tree + private int _prevKey; // Key previously obtained in traversing tree + private PdfObject _prevValue; // Value previously obtained in traversing tree + private int _currentNumsIndex; // Current index into Nums entry + private int _currentNumsLength; // Length of current Nums entry + private Vector _currentNumsVec; // Vector from the Nums entry + private PageLabelNode _currentDescendant; + private PageLabelNode _currentLeaf; + + /** + * Superclass constructor. + * @param module The PdfModule under which we're operating + * @param parent The parent node in the document tree; + * may be null only for the root node + * @param dict The dictionary object on which this node + * is based + */ + public PageLabelNode (PdfModule module, + PageLabelNode parent, + PdfDictionary dict) + { + _module = module; + _parent = parent; + _dict = dict; + } + + + /** + * Build the subtree of descendants of this node, using + * the Kids entry in the dictionary. Leaf nodes are + * recognized by not having a Kids entry. + */ + public void buildSubtree () throws PdfException + { + PdfArray kids = null; + try { + kids = (PdfArray) _dict.get("Kids"); + if (kids != null) { + Vector kidsVec = kids.getContent (); + _descendants = new ArrayList (kidsVec.size ()); + for (int i = 0; i < kidsVec.size (); i++) { + PdfDictionary kid = (PdfDictionary) + _module.resolveIndirectObject + ((PdfObject) kidsVec.elementAt (i)); + PageLabelNode nodeObj = + new PageLabelNode (_module, this, kid); + nodeObj.buildSubtree (); + _descendants.add(nodeObj); + } + } + else _descendants = null; + } + catch (PdfException pe) { + throw pe; + } + catch (Exception e) { + throw new PdfInvalidException ("Invalid page label node"); + } + + } + + /** + * Initialize an iterator through the descendants of this node. + */ + public void startWalk () + { + if (_descendants != null) { + _descendantsIter = _descendants.listIterator (); + _walkFinished = false; + } + else { + _descendantsIter = null; // leaf node, or root in isolation + _walkFinished = true; + } + _currentDescendant = null; + _currentLeaf = null; + _currentKey = -1; + _currentValue = null; + _prevKey = -1; + _prevValue = null; + } + + /** + * Get the next leaf object which is under this node. This function + * is designed such that calling startWalk() and then repeatedly + * calling nextLeafObject() will return all the leaf objects in the tree + * under this node, and finally will return null when there are no more. + * A leaf object is one which has no Kids; it is required to have a + * Nums entry. + */ + public PageLabelNode nextLeafObject () + { + if (_walkFinished) { + return null; + } + // _currentDescendant == null and _walkFinished == false indicates + // we're at the start. + if (_currentDescendant == null) { + if (_descendantsIter == null) { + // No descendants. This is a root node which functions as its + // only leaf. + _walkFinished = true; + return this; + } + else { + // Get first descendant + _currentDescendant = (PageLabelNode) _descendantsIter.next (); + _currentDescendant.startWalk (); + } + } + + PageLabelNode retval = _currentDescendant.nextLeafObject (); + if (retval == null) { + if (_descendantsIter.hasNext ()) { + _currentDescendant = (PageLabelNode) _descendantsIter.next (); + _currentDescendant.startWalk (); + return _currentDescendant.nextLeafObject (); + } + else { + // We've gone through all our descendants. + _walkFinished = true; + return null; + } + } + else return retval; + } + + /** + * Obtain the next key-value pair from the tree. This returns true + * if a pair is available, false if not. After this is called, + * getCurrentKey and getCurrentValue may be called to retrieve the + * key and value thus found. Each time this is called, + * currentKey and currentValue get copied into prevKey and + * prevValue. + */ + public boolean findNextKeyValue () throws PdfException + { + try { + if (_currentLeaf == null || _currentNumsIndex >= _currentNumsLength) { + _currentLeaf = nextLeafObject (); + if (_currentLeaf == null) { + _prevKey = _currentKey; + _prevValue = _currentValue; + _currentKey = Integer.MAX_VALUE; + return false; // all done + } + _currentNumsIndex = 0; + PdfArray pairArray = (PdfArray) + _module.resolveIndirectObject (_currentLeaf._dict.get ("Nums")); + if (pairArray == null) { + throw new PdfInvalidException ("Missing expected element in page number dictionary"); + } + _currentNumsVec = pairArray.getContent (); + _currentNumsLength = _currentNumsVec.size (); + } + + // The key and the value are in two successive positions in the + // array, which is of the form [key value key value ... ] + PdfSimpleObject keyObj = (PdfSimpleObject) + _currentNumsVec.elementAt (_currentNumsIndex); + // Save the previous key-value pair + _prevKey = _currentKey; + _prevValue = _currentValue; + _currentKey = keyObj.getIntValue (); + + _currentValue = (PdfObject) + _currentNumsVec.elementAt (_currentNumsIndex + 1); + _currentNumsIndex += 2; + + return true; + } + catch (PdfInvalidException e) { + throw e; + } + catch (Exception e) { + e.printStackTrace(); + throw new PdfInvalidException ("Invalid date in page number tree"); + } + } + + /** + * Returns key at current position in traversing tree + */ + public int getCurrentKey () + { + return _currentKey; + } + + /** + * Returns value associated with current key + */ + public PdfObject _getCurrentValue () + { + return _currentValue; + } + + /** + * Returns key previously obtained in traversing tree + */ + public int getPrevKey () + { + return _prevKey; + } + + /** + * Returns value associated with key previously obtained + * in traversing tree + */ + public PdfObject getPrevValue () + { + return _prevValue; + } + + /** + * A convenience method to turn integers into Roman + * numerals, for the generation of page labels. + */ + public static String intToRoman (int n, boolean upperCase) + { + StringBuffer buf = new StringBuffer (); + // Numbers of a thousand or more start with an "M" for + // each full thousand. + while (n >= 1000) { + buf.append ("M"); + n -= 1000; + } + // treat "CM" as a special case. + if (n >= 900) { + buf.append ("CM"); + n -= 900; + } + // 500 through 899 uses D, DC, DCC, DCCC + if (n >= 500) { + buf.append ("D"); + while (n >= 600) { + buf.append ("C"); + n -= 100; + } + n -= 500; + } + // 400 through 499 is CD + if (n >= 400) { + buf.append ("CD"); + n -= 400; + } + // 100 through 399 is C, CC, CCC + while (n >= 100) { + buf.append ("C"); + n -= 100; + } + // 90 through 99 is XC + if (n >= 90) { + buf.append ("XC"); + n -= 90; + } + // 50 through 89 is L, LX, LXX, LXXX + if (n >= 50) { + buf.append ("L"); + while (n >= 60) { + buf.append ("X"); + n -= 10; + } + n -= 50; + } + // 40 through 49 is XL + if (n >= 40) { + buf.append ("XL"); + n -= 40; + } + // 10 through 39 is X, XX, XXX + while (n >= 10) { + buf.append ("X"); + n -= 10; + } + // From here on, nitpick it out with a switch statement. + switch (n) { + case 1: + buf.append ("I"); + break; + case 2: + buf.append ("II"); + break; + case 3: + buf.append ("III"); + break; + case 4: + buf.append ("IV"); + break; + case 5: + buf.append ("V"); + break; + case 6: + buf.append ("VI"); + break; + case 7: + buf.append ("VII"); + break; + case 8: + buf.append ("VIII"); + break; + case 9: + buf.append ("IX"); + break; + } + String val = buf.toString (); + if (upperCase) { + return val; + } + else { + return val.toLowerCase (); + } + } + /** + * A convenience method to turn integers into + * "letter" page numbers as defined for PDF. + * The first 26 pages are A-Z, the next 26 AA-ZZ, + * etc. + */ + public static String intToBase26 (int n, boolean upperCase) + { + int repeatCount = ((n - 1) / 26) + 1; + StringBuffer buf = new StringBuffer (); + int ch; + // Have ch be the appropriate character to repeat + if (upperCase) { + ch = 65 + ((n - 1) % 26); + } + else { + ch = 97 + ((n - 1) % 26); + } + while (--repeatCount >= 0) { + buf.append ((char) ch); + } + return buf.toString (); + } +} + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PageObject.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PageObject.java new file mode 100644 index 00000000..3571ab87 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PageObject.java @@ -0,0 +1,222 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import edu.harvard.hul.ois.jhove.module.*; +import java.io.*; +import java.util.*; + +/** + * Class encapsulating a PDF page object node. + */ +public class PageObject extends DocNode +{ + private List _contentStreams; // contents of the page; may be null + private static final String badPageStr = "Invalid dictionary data for page"; + + /** + * Superclass constructor. + * @param module The module under which we're operating + * @param parent The parent node in the document tree; + * may be null only for the root node + * @param dict The dictionary object on which this node + * is based + */ + public PageObject (PdfModule module, + PageTreeNode parent, + PdfDictionary dict) + { + super (module, parent, dict); + _contentStreams = null; + _pageObjectFlag = true; + } + + /** + * Find the content stream(s) for this page. This is + * called when the page tree content stream is built + * by PageTreeNode. getContentStreams may + * subsequently be called to get the content. + */ + public void loadContent (PdfModule module) throws PdfException + { + try { + PdfObject contents = _dict.get("Contents"); + // the Contents entry in the dictionary may be either + // a stream or an array of streams. It may also + // be null, indicating no content. + if (contents != null) { + contents = module.resolveIndirectObject (contents); + if (contents instanceof PdfStream) { + _contentStreams = new ArrayList(1); + _contentStreams.add(contents); + return; + } + else if (contents instanceof PdfArray) { + Vector contentVec = + ((PdfArray) contents).getContent (); + if (contentVec.size () == 0) { + return; + } + _contentStreams = new ArrayList + (contentVec.size ()); + for (int i = 0; i < contentVec.size (); i++) { + PdfObject streamElement = (PdfObject) + contentVec.elementAt (i); + streamElement = module.resolveIndirectObject + (streamElement); + _contentStreams.add ((PdfStream) streamElement); + } + } + else { + throw new PdfInvalidException (badPageStr, 0); + } + } + } + catch (NullPointerException e) { + throw new PdfInvalidException (badPageStr, 0); + } + catch (ClassCastException e) { + throw new PdfInvalidException (badPageStr, 0); + } + catch (IOException e) { + throw new PdfMalformedException (badPageStr, 0); + } + } + + /** + * Returns the List of content streams. The list elements are + * of type PdfStream. + */ + public List getContentStreams () + { + return _contentStreams; + } + + /** + * Return the page's Annots array of dictionaries, or null if none + */ + public PdfArray getAnnotations () throws PdfException + { + String badAnnot = "Invalid Annotations"; + try { + return (PdfArray) _module.resolveIndirectObject (_dict.get ("Annots")); + } + catch (ClassCastException e) { + throw new PdfInvalidException (badAnnot); + } + catch (IOException e) { + throw new PdfMalformedException (badAnnot); + } + } + + + /** + * Call this function when recursively walking through a document + * tree. This allows nextPageObject () to be return this object + * exactly once. + */ + public void startWalk () + { + _walkFinished = false; + } + + /** + * Returns this object the first time it is called after startWalk + * is called, then null when called again. This allows a recursive + * walk through a document tree to work properly. + */ + public PageObject nextPageObject () + { + if (_walkFinished) + return null; + _walkFinished = true; + return this; + } + + /** + * Called to walk through all page tree nodes and page objects. + * Functionally identical with nextPageObject. + */ + public DocNode nextDocNode () + { + return nextPageObject (); + } + + /** + * Returns the ArtBox for the page, or null if none. Throws a + * PDFException if there is an ArtBox but it is not a rectangle. + */ + public PdfArray getArtBox () throws PdfException + { + final String badbox = "Malformed ArtBox in page tree"; + try { + PdfArray mbox = (PdfArray) _dict.get ("ArtBox"); + if (mbox == null) { + return null; + } + else if (mbox.toRectangle () != null) { + return mbox; + } + else { + // There's an ArtBox, but it's not a rectangle + throw new PdfInvalidException (badbox); + } + } + catch (Exception e) { + throw new PdfMalformedException (badbox); + } + } + + /** + * Returns the TrimBox for the page, or null if none. Throws a + * PDFException if there is an TrimBox but it is not a rectangle. + */ + public PdfArray getTrimBox () throws PdfException + { + final String badbox = "Malformed TrimBox in page tree"; + try { + PdfArray mbox = (PdfArray) _dict.get ("TrimBox"); + if (mbox == null) { + return null; + } + else if (mbox.toRectangle () != null) { + return mbox; + } + else { + // There's an TrimBox, but it's not a rectangle + throw new PdfInvalidException (badbox); + } + } + catch (Exception e) { + throw new PdfMalformedException (badbox); + } + } + + /** + * Returns the BleedBox for the page, or null if none. Throws a + * PDFException if there is an BleedBox but it is not a rectangle. + */ + public PdfArray getBleedBox () throws PdfException + { + final String badbox = "Malformed BleedBox in page tree"; + try { + PdfArray mbox = (PdfArray) _dict.get ("BleedBox"); + if (mbox == null) { + return null; + } + else if (mbox.toRectangle () != null) { + return mbox; + } + else { + // There's an BleedBox, but it's not a rectangle + throw new PdfInvalidException (badbox); + } + } + catch (Exception e) { + throw new PdfMalformedException (badbox); + } + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PageTreeNode.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PageTreeNode.java new file mode 100644 index 00000000..accbe1b6 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PageTreeNode.java @@ -0,0 +1,261 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import edu.harvard.hul.ois.jhove.module.*; +import java.util.*; + +/** + * Class encapsulating a PDF page tree node. + * The page tree is built such that callers can walk through + * it by calling startWalk and then calling nextDocNode + * (for all nodes) or nextPageObject (for pages only) repeatedly. + */ +public class PageTreeNode extends DocNode +{ + /* The descendant DocNodes. */ + private List _descendants; + private ListIterator _descendantsIter; + private DocNode _currentDescendant; + private boolean _walkFirst; + private Set _visitedNodes; + + /** + * Superclass constructor. + * @param module The PDFModule under which we're operating + * @param parent The parent node in the document tree; + * may be null only for the root node + * @param dict The dictionary object on which this node + * is based + */ + public PageTreeNode (PdfModule module, + PageTreeNode parent, + PdfDictionary dict) + { + super (module, parent, dict); + _pageObjectFlag = false; + _descendants = new ArrayList (1); // Empty list in case it doesn't get built + } + + /** + * Builds the subtree of descendants of this node, using + * the Kids entry in the dictionary. + */ + public void buildSubtree (boolean toplevel, int recGuard) + throws PdfException + { + buildSubtree (toplevel, recGuard, -1, -1); + } + + /** + * Builds the subtree of descendants of this node, using + * the Kids entry in the dictionary. + */ + public void buildSubtree (boolean toplevel, int recGuard, int objNumber, int genNumber) throws PdfException + { + /* Guard against infinite recursion */ + if (recGuard <= 0) { + throw new PdfMalformedException ("Excessive depth or infinite recursion in page tree structure"); + } + PdfArray kids = null; + try { + /* Section 3.6.2 of the PDF 1.6 doc says: + * "Applications should be prepared + * to handle any form of tree structure built of such nodes + * [page tree nodes and page nodes]. The simplest structure + * would consist of a single page tree node that references + * all of the document's page objects directly." + * But actually, the simplest structure would be a single + * page node. And it appears that Acrobat 7 will indeed + * generate such. + */ + /* Note that the Kids dictionary can be an indirect object. */ + PdfObject obj = _dict.get("Kids"); + if (obj instanceof PdfIndirectObj) { + kids = (PdfArray) (((PdfIndirectObj) obj).getObject ()); + } + else { + kids = (PdfArray) obj; + } + if (toplevel && kids == null) { + // The single page node case, maybe. + PdfSimpleObject type = (PdfSimpleObject) _dict.get ("Type"); + if (type != null && + "Page".equals (type.getStringValue())) { + PageObject pageObj = new PageObject + (_module, this, _dict); + _descendants = new ArrayList (1); + _descendants.add (pageObj); + } + } + else { + Vector kidsVec = kids.getContent (); + _descendants = new ArrayList (kidsVec.size ()); + for (int i = 0; i < kidsVec.size (); i++) { + PdfIndirectObj kidRef = + (PdfIndirectObj) kidsVec.elementAt (i); + /************************************************** + * To avoid a simple case of infinite recursion, check + * that this kid is not the same page object as its + * parent. + **************************************************/ + /************************************************** + int kidObjNumber = kidRef.getObjNumber (); + int kidGenNumber = kidRef.getGenNumber (); + if (objNumber >= 0 && genNumber >= 0 && + objNumber == kidObjNumber && + genNumber == kidGenNumber) { + break; + } + **************************************************/ + PdfDictionary kid = (PdfDictionary) + _module.resolveIndirectObject (kidRef); + PdfSimpleObject kidtype = + (PdfSimpleObject) kid.get("Type"); + String kidtypeStr = kidtype.getStringValue (); + if (kidtypeStr.equals("Page")) { + PageObject pageObj = new PageObject + (_module, this, kid); + pageObj.loadContent (_module); + _descendants.add(pageObj); + } + else if (kidtypeStr.equals ("Pages")) { + PageTreeNode nodeObj = + new PageTreeNode (_module, this, kid); + nodeObj.buildSubtree (false, recGuard - 1); + _descendants.add(nodeObj); + } + } + } + } + catch (PdfException ee) { + throw ee; + } + catch (Exception e) { + throw new PdfInvalidException ("Invalid page tree node"); + } + + } + + /** + * Initialize an iterator through the descendants of this node. + */ + public void startWalk () + { + _descendantsIter = _descendants.listIterator (); + _currentDescendant = null; + _walkFirst = true; + _walkFinished = false; + _visitedNodes = new HashSet (); // Track self-recursion + } + + /** + * Get the next PageObject which is under this node. This function + * is designed such that calling startWalk() and then repeatedly + * calling nextPageObject() will return all the PageObjects in the tree + * under this node, and finally will return null when there are no more. + */ + public PageObject nextPageObject () throws PdfMalformedException + { + if (_walkFinished) { + return null; + } + // _currentDescendant == null and _walkFinished == false indicates + // we're at the start. + if (_currentDescendant == null) { + if (!_descendantsIter.hasNext ()) { + _walkFinished = true; + return null; + } + + // Get first descendant + _currentDescendant = (DocNode) _descendantsIter.next (); + _currentDescendant.startWalk (); + } + + PageObject retval = _currentDescendant.nextPageObject (); + if (retval == null) { + if (_descendantsIter.hasNext ()) { + // Every node is a page object or + // has at least one page object below it, right? + _currentDescendant = (DocNode) _descendantsIter.next (); + _currentDescendant.startWalk (); + retval = _currentDescendant.nextPageObject (); + } + else { + // We've gone through all our descendants. + _walkFinished = true; + retval = null; + } + } + if (retval != null) { + int objnum = retval.getDict().getObjNumber(); + if (_visitedNodes.contains((Integer) objnum)) { + throw new PdfMalformedException("Improperly constructed page tree"); + } + _visitedNodes.add(objnum); + } + return retval; + } + + /** + * Get the next DocNode which is under this node. This function + * is designed such that calling startWalk() and then repeatedly + * calling nextPageObject() will return first this node, + * then all the DocNodes in the tree + * under this node. It finally will return null when there + * are no more. + */ + public DocNode nextDocNode () throws PdfMalformedException + { + if (_walkFinished) { + return null; + } + // _walkFinished == false and _walkFirst == true indicates + // we need to return "this". + if (_walkFirst) { + _walkFirst = false; + return this; + } + // _currentDescendant == null and _walkFinished == false indicates + // we're at the start. This is almost identical to the + // logic for nextPageObject. + if (_currentDescendant == null) { + if (!_descendantsIter.hasNext ()) { + _walkFinished = true; + return null; + } + + // Get first descendant + _currentDescendant = (DocNode) _descendantsIter.next (); + _currentDescendant.startWalk (); + } + + DocNode retval = _currentDescendant.nextDocNode (); + if (retval == null) { + if (_descendantsIter.hasNext ()) { + // Every node is a page object or + // has at least one page object below it, right? + _currentDescendant = (DocNode) _descendantsIter.next (); + _currentDescendant.startWalk (); + retval = _currentDescendant.nextDocNode (); + } + else { + // We've gone through all our descendants. + _walkFinished = true; + retval = null; + } + } + if (retval != null) { + int objnum = retval.getDict().getObjNumber(); + if (_visitedNodes.contains((Integer) objnum)) { + throw new PdfMalformedException("Improperly constructed page tree"); + } + _visitedNodes.add(objnum); + } + return retval; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Parser.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Parser.java new file mode 100644 index 00000000..8ae233fa --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Parser.java @@ -0,0 +1,459 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import java.io.*; +import java.util.*; + +/** + * The Parser class implements some limited syntactic analysis + * for PDF. It isn't by any means intended to be a full + * parser. Its main job is to track nesting of syntactic + * elements such as dictionary and array beginnings and + * ends. + */ +public class Parser +{ + + private Tokenizer _tokenizer; + private int _dictDepth; // number of dictionary starts on stack + private int _arrayDepth; // number of array starts on stack + private Map _objectMap; // the object map for the file + private boolean _encrypted; // true if the document is encrypted + + /* PDF/A compliance flag. */ + private boolean _pdfACompliant; + + + /** + * Constructor. A Parser works with a Tokenizer that feeds + * it tokens. + * + * @param tokenizer The Tokenizer which the parser will use + */ + public Parser (Tokenizer tokenizer) + { + _tokenizer = tokenizer; + _pdfACompliant = true; + reset (); + } + + /** + * Set the object map on which the parser will work. + */ + public void setObjectMap (Map objectMap) + { + _objectMap = objectMap; + } + + /** + * Clear the state of the parser so that it can start + * reading at a different place in the file. Clears the + * stack and the dictionary and array depth counters. + */ + public void reset () { + _dictDepth = 0; + _arrayDepth = 0; + } + + + /** + * Clear the state of the parser so that it can start + * reading at a different place in the file and ignore + * any nesting errors. Sets the + * stack and the dictionary and array depth counters to + * a large number so that nesting exceptions won't be thrown. + */ + public void resetLoose () { + _dictDepth = 1000000; + _arrayDepth = 1000000; + } + + /** + * Gets a token. Uses Tokenizer.getNext, and keeps track + * of the depth of dictionary and array nesting. + */ + public Token getNext () + throws IOException, PdfException + { + return getNext (0L); + } + + /** + * Gets a token. Uses Tokenizer.getNext, and keeps track + * of the depth of dictionary and array nesting. + * @param max Maximum allowable size of the token + */ + public Token getNext (long max) + throws IOException, PdfException + { + Token tok = _tokenizer.getNext (max); + if (tok instanceof DictionaryStart) { + ++_dictDepth; + } + else if (tok instanceof DictionaryEnd) { + --_dictDepth; + if (_dictDepth < 0) { + throw new PdfMalformedException ("Improperly nested dictionary delimiters"); + } + } + if (tok instanceof ArrayStart) { + ++_arrayDepth; + } + else if (tok instanceof ArrayEnd) { + --_arrayDepth; + if (_arrayDepth < 0) { + throw new PdfMalformedException ("Improperly nested array delimiters"); + } + } + return tok; + } + + /** + * A class-sensitive version of getNext. The token + * which is obtained must be of the specified class + * (or a subclass thereof), or a PdfInvalidException with + * message errMsg will be thrown. + */ + public Token getNext (Class clas, String errMsg) + throws IOException, PdfException + { + Token tok = getNext (); + if (!clas.isInstance (tok)) { + throw new PdfInvalidException (errMsg); + } + if (!tok.isPdfACompliant()) + _pdfACompliant = false; + return tok; + } + + /** + * Returns the number of dictionary starts not yet matched by + * dictionary ends. + */ + public int getDictDepth () + { + return _dictDepth; + } + + /** + * Tells this Parser, and its Tokenizer, whether the file + * is encrypted. + */ + public void setEncrypted (boolean encrypted) + { + _encrypted = encrypted; + _tokenizer.setEncrypted (encrypted); + } + + /** + * Returns the number of array starts not yet matched by + * array ends. + */ + public int getArrayDepth () + { + return _arrayDepth; + } + + /** + * Returns the Tokenizer's current whitespace string. + */ + public String getWSString () { + return _tokenizer.getWSString (); + } + + /** + * Returns the language code set from the Tokenizer. + */ + public Set getLanguageCodes () + { + return _tokenizer.getLanguageCodes (); + } + /** + * Returns false if either the parser or the tokenizer has detected + * non-compliance with PDF/A restrictions. A value of true + * is no guarantee that the file is compliant. + */ + public boolean getPDFACompliant () + { + if (!_tokenizer.getPDFACompliant ()) { + _pdfACompliant = false; + } + return _pdfACompliant; + } + + /** + * Set the value of the pdfACompliant flag. This may be used to + * clear previous detection of noncompliance. If the parameter + * has a value of true, the tokenizer's pdfACompliant + * flag is also set to true. + */ + public void setPDFACompliant (boolean pdfACompliant) + { + _pdfACompliant = pdfACompliant; + if (pdfACompliant) { + _tokenizer.setPDFACompliant (true); + } + } + + /** + * Reads an object definition, from wherever we are in the stream to + * the completion of one full object after the obj keyword. + */ + public PdfObject readObjectDef () throws IOException, PdfException + { + Numeric objNumTok = (Numeric) getNext + (Numeric.class, "Invalid object definition"); + return readObjectDef (objNumTok); + } + + /** Reads an object definition, given the first numeric object, which + * has already been read and is passed as an argument. This is called + * by the no-argument readObjectDef; the only other case in which it + * will be called is for a cross-reference stream, which can be distinguished + * from a cross-reference table only once the first token is read. + */ + public PdfObject readObjectDef (Numeric objNumTok) + throws IOException, PdfException + { + String invDef = "Invalid object definition"; + reset (); + // The start of an object must be obj + //Numeric objNumTok = (Numeric) getNext (Numeric.class, invDef); + Numeric genNumTok = (Numeric) getNext (Numeric.class, invDef); + Keyword objKey = (Keyword) getNext (Keyword.class, invDef); + if (!"obj".equals (objKey.getValue ())) { + throw new PdfMalformedException (invDef); + } + if (_tokenizer.getWSString ().length () > 1) { + _pdfACompliant = false; + } + PdfObject obj = readObject (); + + // Now a special-case check to read a stream object, which + // consists of a dictionary followed by a stream token. + if (obj instanceof PdfDictionary) { + Stream strm = null; + try { + strm = (Stream) getNext (Stream.class, ""); + } + catch (Exception e) { + // if we get an exception, it just means it wasn't a stream + } + if (strm != null) { + // Assimilate the dictionary and the stream token into the + // object to be returned + PdfStream strmObj = new PdfStream ((PdfDictionary) obj, strm); + if (!strmObj.isPdfaCompliant()) { + _pdfACompliant = false; + } + obj = strmObj; + } + } + + obj.setObjNumber (objNumTok.getIntegerValue ()); + obj.setGenNumber (genNumTok.getIntegerValue ()); + return obj; + } + + /** + * Reads an object. By design, this reader has a number + * of limitations. + *
    + *
  • It doesn't retain the contents of streams
  • + *
  • It doesn't recognize a stream when it's pointing at + * the stream's dictionary; it will just read the + * dictionary
  • + *
+ * Functions which it uses may call it recursively to build up structures. + * If it encounters a token inappropriate for an object start, it + * throws a PdfException on which getToken() may be called to retrieve + * that token. + */ + public PdfObject readObject () throws IOException, PdfException + { + Token tok = getNext (); + if (tok instanceof ArrayStart) { + return readArray (); + } + else if (tok instanceof DictionaryStart) { + return readDictionary (); + } + else if (tok.isSimpleToken ()) { + return new PdfSimpleObject (tok); + } + else { + throw new PdfMalformedException + ("Cannot parse object", getOffset(), tok); + } + } + + /** + * Reads an array. When this is called, we have already read the + * ArrayStart token, and arrayDepth has been incremented to reflect this. + */ + public PdfArray readArray () throws IOException, PdfException + { + PdfArray arr = new PdfArray (); + for (;;) { + PdfObject obj = null; + try { + obj = readObject (); + arr.add (obj); + } + // We detect the end of an array by a PdfException being thrown + // when readObject encounters the close bracket. When we get + // the end of the array, collapse the vector before returning the object. + catch (PdfException e) { + Token tok = e.getToken (); + if (tok instanceof ArrayEnd) { + collapseObjectVector (arr.getContent ()); + if (!arr.isPdfACompliant()) { + _pdfACompliant = false; + } + return arr; + } + else { + throw e; // real error + } + } + } + } + + + /** Reads a dictionary. When this is called, we have already read the + * DictionaryStart token, and dictDepth has been incremented to reflect this. + * Only for use in this special case, where we're picking up + * a dictionary in midstream. + */ + public PdfDictionary readDictionary () throws IOException, PdfException + { + PdfDictionary dict = new PdfDictionary (); + // Create a vector as a temporary holding place for the objects + Vector vec = new Vector (); + + for (;;) { + PdfObject obj = null; + try { + obj = readObject (); + // Comments within a dictionary need to be ignored. + if (obj instanceof PdfSimpleObject && + ((PdfSimpleObject) obj).getToken() instanceof Comment) { + continue; + } + vec.add (obj); + } + // We detect the end of a dictionary by a PdfException being thrown + // when readObject encounters the close angle brackets. When we get + // the end of the array, collapse the vector before returning the object. + catch (PdfException e) { + Token tok = e.getToken (); + if (tok instanceof DictionaryEnd) { + collapseObjectVector (vec); + String invalDict = "Malformed dictionary"; + // The collapsed vector must contain an even number of objects + int vecSize = vec.size (); + if ((vecSize % 2) != 0) { + throw new PdfMalformedException (invalDict + ": Vector must contain an even number of objects, but has " + vecSize, getOffset ()); + } + for (int i = 0; i < vecSize; i += 2) { + try { + Name key = (Name) ((PdfSimpleObject) + vec.elementAt (i)).getToken (); + PdfObject value = (PdfObject) vec.elementAt (i + 1); + dict.add (key.getValue (), value); + } + catch (Exception f) { + throw new PdfMalformedException (invalDict, getOffset ()); + } + } + if (!dict.isPdfACompliant()) { + _pdfACompliant = false; // exceeds implementation limit for PDF/A + } + return dict; + } + else { + throw e; // real error + } + } + } + } + + + /** + * Returns the current offset into the file. + */ + public long getOffset () + { + return _tokenizer.getOffset (); + } + + /** + * Positions the file to the specified offset, and + * resets the state for a new token stream. + */ + public void seek (long offset) + throws IOException, PdfException + { + _tokenizer.seek (offset); + reset (); + } + + + /** + * PDF has a wacky grammar which must be a legacy of + * PostScript's postfix syntax. A keyword of R means that + * the two previous objects are really part of an indirect object + * reference. This means that when a vector of objects is complete, + * it has to be read backwards so that indirect object references can + * be collapsed out. In the case of a dictionary, this has to be done + * before the content can be interpreted as key-value pairs. + */ + private void collapseObjectVector (Vector v) throws PdfException + { + for (int i = v.size() - 1; i >= 2; i--) { + PdfObject obj = (PdfObject) v.elementAt (i); + if (obj instanceof PdfSimpleObject) { + Token tok = ((PdfSimpleObject) obj).getToken (); + if (tok instanceof Keyword) { + if ("R".equals (((Keyword)tok).getValue ())) { + // We're in the key of 'R'. The two previous tokens + // had better be Numerics. Three objects in the Vector + // are replaced by one. + try { + PdfSimpleObject nobj = + (PdfSimpleObject) v.elementAt (i - 2); + Numeric ntok = (Numeric) nobj.getToken (); + int objNum = ntok.getIntegerValue (); + nobj = (PdfSimpleObject) v.elementAt (i - 1); + ntok = (Numeric) nobj.getToken (); + int genNum = ntok.getIntegerValue (); + v.set (i - 2, new PdfIndirectObj + (objNum, genNum, _objectMap)); + v.removeElementAt (i); + v.removeElementAt (i - 1); + i -= 2; + } + catch (Exception e) { + throw new PdfMalformedException + ("Malformed indirect object reference"); + } + } + } + } + } + } + + /** + * If true, do not attempt to parse non-whitespace delimited tokens, e.g., + * literal and hexadecimal strings. + * @param flag Scan mode flag + */ + public void scanMode (boolean flag) + { + _tokenizer.scanMode (flag); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfArray.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfArray.java new file mode 100644 index 00000000..f149c3b4 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfArray.java @@ -0,0 +1,115 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import java.util.*; + +/** + * A representation of a PDF array object. + */ +public class PdfArray extends PdfObject +{ + + private Vector _content; + + /** + * Creates a PdfArray object. + * + * @param objNumber The PDF object number + * @param genNumber The PDF generation number + */ + public PdfArray (int objNumber, int genNumber) + { + super (objNumber, genNumber); + _content = new Vector (); + } + + /** + * Creates a PdfArray object with empty contents. + * + */ + public PdfArray () + { + super (); + _content = new Vector (); + } + + /** + * Adds an object to the array. + */ + public void add (PdfObject obj) + { + _content.add (obj); + } + + /** + * Return the contents of the array as a Vector. + */ + public Vector getContent () + { + return _content; + } + + /** Report if it's within implementation limits defined for PDF/A. */ + public boolean isPdfACompliant () + { + return _content.size() <= 8191; + } + + /** + * Concatenate the elements, if they are PdfSimpleObjects, + * into a string separated by spaces. Return an empty string + * if there are no PdfSimpleObjects. + */ + public String toPipeline () + { + StringBuffer sb = new StringBuffer (); + for (int i = 0; i < _content.size (); i++) { + PdfObject elem = (PdfObject) _content.elementAt (i); + if (elem instanceof PdfSimpleObject) { + String elemval = ((PdfSimpleObject) elem).getStringValue (); + // separate items with a space + if (sb.length () > 0) { + sb.append (' '); + } + sb.append (elemval); + } + } + return sb.toString (); + } + + /** + * Attempts to convert this Array to a PDF rectangle. + * If the Array is a valid rectangle (i.e., an array of exactly + * four numbers), returns a Java array of four doubles reflecting + * the rectangle. Otherwise returns null. + */ + public double[] toRectangle () + { + if (_content.size () != 4) { + return null; + } + double[] retval = new double[4]; + try { + for (int i = 0; i < 4; i++) { + PdfObject elem = (PdfObject) _content.elementAt (i); + if (elem instanceof PdfSimpleObject) { + double d = ((PdfSimpleObject) elem).getDoubleValue (); + retval[i] = d; + } + else { + return null; + } + } + return retval; + } + catch (Exception e) { + // Any failure (e.g., a ClassCastException) is assumed to mean + // it wasn't a proper Rectangle + return null; + } + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfDictionary.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfDictionary.java new file mode 100644 index 00000000..aef75c8a --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfDictionary.java @@ -0,0 +1,75 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import java.util.*; + +/** + * A representation of a PDF dictionary object. + */ +public class PdfDictionary extends PdfObject +{ + + private Map _entries; + + /** + * Creates a PdfDictionary object. + * + * @param objNumber The PDF object number + * @param genNumber The PDF generation number + */ + public PdfDictionary (int objNumber, int genNumber) + { + super (objNumber, genNumber); + _entries = new HashMap (); + } + + /** + * Creates a PdfDictionary object. + * + */ + public PdfDictionary () + { + super (); + _entries = new HashMap (); + } + + /** + * Accumulate an entry into the dictionary. + * + * @param key String value of the dictionary key + * @param value PdfObject encapsulation of the dictionary value + */ + public void add (String key, PdfObject value) + { + _entries.put (key, value); + } + + /** Get the PDFObject whose key has the specified string + * value. Returns null if there is no such key. + * + * @param key The string value of the key to look up. + */ + public PdfObject get (String key) + { + return (PdfObject) _entries.get (key); + } + + /** Return true if it's within the PDF/A implementation limit. */ + public boolean isPdfACompliant () + { + return _entries.size() <= 4095; + } + + /** + * Returns an iterator which will successively return + * all the values in the dictionary. + */ + public Iterator iterator () + { + return _entries.values ().iterator (); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfException.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfException.java new file mode 100644 index 00000000..209442ec --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfException.java @@ -0,0 +1,83 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003-2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Abstract exception subclass used internally by the PDF module. + * Throwing a PDFException indicates that the document is + * ill-formed or invalid; use the appropriate subclass to + * indicate which. + */ +public abstract class PdfException extends Exception +{ + + /* Note 25-Feb-2004: Previously PdfException indicated + * a not-well-formed condition, and PdfInvalidException + * was a subclass of PdfException that indicated an + * invalid condition. This is a bad class hierarchy, + * since the role of PdfException was ambiguous, + * so PdfMalformedException was added, and PdfException + * was made abstract. + */ + private long _offset; // File offset at which the exception occurred + private Token _token; // Token associated with the exception + + /** + * Create a PdfException. + */ + public PdfException (String m) + { + super(m); + _offset = -1; + _token = null; + } + + /** + * Create a PdfException with specified offset. + */ + public PdfException (String m, long offset) + { + super(m); + _offset = offset; + _token = null; + } + + /** + * Create a PdfException with specified offset and token. + */ + public PdfException (String m, long offset, Token token) + { + super(m); + _offset = offset; + _token = token; + } + + /** + * Returns the offset at which the exception occurred. + */ + public long getOffset () + { + return _offset; + } + + /** + * Return the token associated with the exception. + */ + public Token getToken () + { + return _token; + } + + /** + * Performs the appropriate disparagement act on a RepInfo + * object, such as setting the valid or well-formed + * flag to false. + */ + public abstract void disparage (RepInfo info); + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfFlateInputStream.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfFlateInputStream.java new file mode 100644 index 00000000..0ae321dc --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfFlateInputStream.java @@ -0,0 +1,345 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2005 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import java.io.*; +import java.util.zip.*; + +/** + * An enhancement of InflaterInputStream to support Predictor and Columns. + * How complicated does this get? Do I need to read the whole thing before I can + * "predict" anything? + * + * @author Gary McGath + * + */ +public class PdfFlateInputStream extends FilterInputStream { + + private InflaterInputStream iis; + private PdfDictionary decodeParms; + private int predictor; + private int columns; + /* bits per component */ + private int bpc; + /* colors -- the term is being used in some idiosyncratic sense */ + private int colors; + + /* Number of bytes to hold last column of previous row, if needed */ + private int colBytes; + /* Length of the total row, including space for last column. + * (Actual length of a predictive row is rowLen - colBytes) */ + private int rowLen; + /* Two row buffers for double buffering */ + private byte[] rowBuf; + private byte[] rowBuf2; + private int rowBufOff; // aka linei_ + private boolean eof; + + /* offset to next available byte */ + private int iisBufOff; + /* byte array read from IIS */ + private byte[] iisBuf; + /* Allocation size for iisBuf */ + private final static int IISBUF_SIZE = 4096; + /* iisBufLen -- number of bytes of valid data in iisBuf */ + private int iisBufLen; + /* End of file indicator for IIS stream */ + private boolean iisEof; + + /** + * Constructor with null DecodeParms dictionary + * + * @param is InputStream to be inflated + */ + public PdfFlateInputStream(InputStream is) { + this (is, null); + } + + /** + * Constructor with specified DecodeParms dictionary + * + * @param is InputStream to be inflated + * @param parms DecodeParms dictionary. + * May be null, in which case this is equivalent + * to the one-parameter constructor. + * + */ + public PdfFlateInputStream (InputStream is, PdfDictionary parms) + { + super(is); + iis = new InflaterInputStream (is); + /* Set default values */ + predictor = 1; // no prediction + columns = 1; + bpc = 8; + colors = 1; + + decodeParms = parms; + iisBuf = new byte [IISBUF_SIZE]; + iisBufLen = 0; + iisBufOff = 0; + iisEof = false; + eof = false; + if (parms != null) { + /* Extract relevant dictionary defs */ + try { + PdfSimpleObject pred = (PdfSimpleObject) parms.get ("Predictor"); + if (pred != null) { + predictor = pred.getIntValue(); + } + } + catch (Exception e) {} + try { + PdfSimpleObject col = (PdfSimpleObject) parms.get ("Columns"); + if (col != null) { + columns = col.getIntValue(); + } + } + catch (Exception e) {} + try { + PdfSimpleObject bitsper = (PdfSimpleObject) parms.get ("BitsPerComponent"); + if (bitsper != null) { + bpc = bitsper.getIntValue(); + } + } + catch (Exception e) {} + } + /* Calculate byte counts */ + if (predictor != 1) { + colBytes = (colors * bpc + 7) / 8; + rowLen = (columns * colors * bpc + 7) / 8 + colBytes; + rowBuf = new byte[rowLen]; + rowBuf2 = new byte[rowLen]; + rowBufOff = rowLen; + } + } + + /** Reads one byte from the stream. + * Returns -1 if end of file is reached. + */ + public int read() throws IOException + { + if (eof) { + return -1; + } + if (predictor == 1) { + return readIISByte (); + } + if (rowBufOff == rowLen) { + // Starting out, or previous row exhausted. + readRow (); + if (eof) { + return -1; + } + } + return rowBuf[rowBufOff++] & 0XFF; + } + + /** Reads the specified number of bytes into a buffer. + * Returns the number of bytes actually read, or -1 if + * end of file has been reached. */ + public int read (byte[] b) throws IOException + { + /* Need to read a byte at a time till we have something to expand */ + return read (b, 0, b.length); + } + + /** Reads the specified number of bytes into a buffer + * with offset and length specified. + * Returns -1 if end of file has been reached. + * No matter how much is requested, this will only return one + * row's worth of data at most. + */ + public int read (byte[] b, int off, int len) throws IOException + { + if (eof) { + return -1; + } + if (predictor == 1) { + // predictor of 1 means no predictor. + //return iis.read (b, off, len); + // That can't be right, can it? + return readIISBytes(b, off, len); + } + if (rowBufOff == rowLen) { + // Starting out, or previous row exhausted. + readRow (); + if (eof) { + return -1; + } + } + if (len > rowLen - rowBufOff) { + /* Return no more than a row's worth, regardless */ + len = rowLen - rowBufOff; + } + for (int i = 0; i < len; i++) { + b[off + i] = rowBuf[rowBufOff++]; + } + return len; + } + + public long skip (long n) throws IOException { + return skipIISBytes(n); + } + + /* Takes bytes from the input buffer and turn them into bytes in the output buffer. + * Returns the number of bytes available. + */ + private int processBytes () + { + int avail = 0; + + return avail; + } + + /* Reads a row's worth of data and stores in rowBuf. */ + private void readRow () throws IOException + { + /* Swap rowBuf and rowBuf2 */ + byte[] r = rowBuf; + rowBuf = rowBuf2; + rowBuf2 = r; + rowBufOff = colBytes; + + // Ignore weird predictor of 15 for now + if (predictor >= 10) { + // throw one byte away + readIISByte (); + } + int off = colBytes; + while (off < rowLen) { + int n = readIISBytes(rowBuf, off, rowLen - off); + if (n > 0) { + off += n; + } + else { + eof =true; + return; + } + } + switch (predictor) { + case 1: + case 10: + break; + case 2: + // TIFF predictor + case 11: + // Sub -- left + for (int i = colBytes; i < rowLen; i++) { + rowBuf[i] += rowBuf[i-colBytes]; + } + break; + case 12: + // Up -- above + for (int i = colBytes; i < rowLen; i++) { + rowBuf[i] += rowBuf2[i]; + } + break; + case 13: + // Average -- (left + above) / 2 + for (int i = colBytes; i < rowLen; i++) { + rowBuf[i] += ((rowBuf[i - colBytes] & 0xFF) + + (rowBuf2[i] & 0xFF)) / 2; + } + break; + case 14: // Paeth -- closest of left, above, upper-left + for (int i=0+colBytes; i < rowLen; i++) { + int a = rowBuf[i - colBytes] & 0XFF; + int b = rowBuf2[i] & 0XFF; + int c = rowBuf2[i - colBytes] & 0XFF; + int p = a + b - c; + int pa = Math.abs(p - a); + int pb = Math.abs(p - b); + int pc = Math.abs(p - c); + + int val; + if (pa<=pb && pa<=pc) { + val = a; + } + else if (pb<=pc) { + val = b; + } + else { + val = c; + } + + rowBuf[i] += (byte)val; + } + break; + case 15: // optimum -- per line determination + break; + } + } + + /** Get an "inflated" byte. We do buffering here + * for efficiency. */ + private int readIISByte () + throws IOException + { + // iisBufOff -- offset to next available byte + // iisBuf -- byte array read from IIS + // iisBufLen -- number of bytes of valid data in iisBuf + if (iisBufOff >= iisBufLen && !iisEof) { + readIIS (); + } + if (iisEof) { + return -1; + } + return (int) (iisBuf[iisBufOff++] & 0XFF); + } + + /** Get a bufferload of bytes. */ + private int readIISBytes (byte[] buf, int off, int len) + throws IOException + { + if (iisBufOff >= iisBufLen && !iisEof) { + readIIS (); + } + if (iisEof) { + return -1; + } + /* We don't attempt to optimize across buffer boundaries */ + if (iisBufLen - iisBufOff < len) { + len = iisBufLen - iisBufOff; + } + for (int i = off; i < off + len; i++) { + buf[i] = iisBuf[iisBufOff++]; + } + return len; + } + + /** Skip a specified number of bytes. */ + private long skipIISBytes (long n) throws IOException { + if (iisBufOff >= iisBufLen && !iisEof) { + readIIS (); + } + if (iisEof) { + return -1; + } + if (iisBufLen - iisBufOff < n) { + n = iisBufLen + iisBufOff; + } + iisBufOff += n; + return n; + } + + /** Fill up the IIS buffer. Should be called only by + * other IIS buffer-specific routines. */ + private int readIIS () throws IOException + { + if (iisEof) { + return -1; + } + int n = iis.read (iisBuf); + iisBufOff = 0; + iisBufLen = n; + if (n <= 0) { + iisEof = true; + } + return n; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfIndirectObj.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfIndirectObj.java new file mode 100644 index 00000000..493d23e2 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfIndirectObj.java @@ -0,0 +1,50 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import java.util.*; + +/** + * A representation of a PDF indirect object reference. + */ +public class PdfIndirectObj extends PdfObject +{ + + private Map _objectMap; + private PdfObject _cachedObject; + + /** + * Creates a PdfIndirectObj object. + * + * @param objNumber The PDF object number + * @param genNumber The PDF generation number + * @param objectMap The object map for the PDF file + */ + public PdfIndirectObj (int objNumber, int genNumber, Map objectMap) + { + super (objNumber, genNumber); + _objectMap = objectMap; + _cachedObject = null; + } + + /** + * Retrieves the object which is referenced. Uses the + * cached reference if there is one; caches the reference + * if there wasn't one before. + */ + public PdfObject getObject () + { + if (_cachedObject != null) { + return _cachedObject; + } + else { + long key = ((long) _objNumber << 32) + + ((long) _genNumber & 0XFFFFFFFFL); + _cachedObject = (PdfObject) _objectMap.get (new Long (key)); + return _cachedObject; + } + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfInvalidException.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfInvalidException.java new file mode 100644 index 00000000..1c804b1b --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfInvalidException.java @@ -0,0 +1,51 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Exception subclass used internally by the PDF module. + * A PdfInvalidException is thrown when a condition indicates + * that the document is invalid but not necessarily ill-formed. + */ +public final class PdfInvalidException extends PdfException +{ + + /** + * Creates a PdfInvalidException. + */ + public PdfInvalidException (String m) + { + super(m); + } + + /** + * Creates a PdfInvalidException with specified offset. + */ + public PdfInvalidException (String m, long offset) + { + super(m, offset); + } + + /** + * Creates a PdfInvalidException with specified offset and token. + */ + public PdfInvalidException (String m, long offset, Token token) + { + super(m, offset, token); + } + + /** + * Performs the appropriate disparagement act on a RepInfo + * object. For a PdfInvalidException, this is to call + * setValid (false). + */ + public void disparage (RepInfo info) + { + info.setValid (false); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfMalformedException.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfMalformedException.java new file mode 100644 index 00000000..4d25f8e0 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfMalformedException.java @@ -0,0 +1,54 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Exception subclass used internally by the PDF module. + * A PdfMalformedException is thrown when a condition indicates + * that the document is not well-formed. + * + * @see PdfInvalidException + */ +@SuppressWarnings("serial") +public class PdfMalformedException extends PdfException { + + /** + * Creates a PdfMalformedException. + */ + public PdfMalformedException (String m) + { + super(m); + } + + /** + * Creates a PdfMalformedException with specified offset. + */ + public PdfMalformedException (String m, long offset) + { + super(m, offset); + } + + /** + * Creates a PdfMalformedException with specified offset and token. + */ + public PdfMalformedException (String m, long offset, Token token) + { + super(m, offset, token); + } + + /** + * Performs the appropriate disparagement act on a RepInfo + * object. For a PdfInvalidException, this is to call + * setValid (false). + */ + public void disparage (RepInfo info) + { + info.setWellFormed (false); + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfObject.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfObject.java new file mode 100644 index 00000000..b21eca8f --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfObject.java @@ -0,0 +1,77 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +/** + * The abstract superclass for all representations of objects + * in PDF files. Objects may be created using the obj syntax, + * in which case they have an object and generation number, or + * they may be parts of other objects, in which case they don't. + * All subclasses should implement a constructor + * which has the object and generation + * numbers as its last two arguments, and one which has the + * same arguments except for omitting these two. + */ +public abstract class PdfObject +{ + /** PDF object number. */ + protected int _objNumber; + + /** PDF generation number. */ + protected int _genNumber; + + /** + * Superclass constructor which should be called for all + * PdfObject instances that include an object and generation + * number. + * + * @param objNumber The PDF object number + * @param genNumber The PDF generation number + */ + public PdfObject (int objNumber, int genNumber) + { + _objNumber = objNumber; + _genNumber = genNumber; + } + + /** + * Superclass constructor for which the object and generation + * number will be added separately or not at all. Initializes + * the object and generation numbers to -1 to signify their + * absence. + */ + public PdfObject () + { + _objNumber = -1; + _genNumber = -1; + } + + /** Returns the PDF object number. If the object wasn't + given an object number, returns -1. */ + public int getObjNumber () + { + return _objNumber; + } + + /** Returns the PDF generation number. If the object wasn't + given a generation number, returns -1. */ + public int getGenNumber () + { + return _genNumber; + } + + /** Sets the PDF object number. */ + public void setObjNumber (int num) + { + _objNumber = num; + } + + /** Sets the PDF generation number. */ + public void setGenNumber (int num) + { + _genNumber = num; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfProfile.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfProfile.java new file mode 100644 index 00000000..a1426d9a --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfProfile.java @@ -0,0 +1,196 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import edu.harvard.hul.ois.jhove.module.*; +import java.io.*; +import java.util.*; + +/** + * Abstract class for PDF profile checkers. + */ +public abstract class PdfProfile +{ + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + + /** The module invoking this profile. */ + protected PdfModule _module; + + /** A brief human-readable description of the profile. */ + protected String _profileText; + + /** The Parser being used on the file. */ + protected Parser _parser; + + /** The file being analyzed. */ + protected RandomAccessFile _raf; + + /** Set to true if this file has previously + * been validated by an invocation of this PdfProfile. */ + private boolean _alreadyOK; + + /** + * Creates a PdfProfile. + * Subclass constructors should call the super constructor, + * then assign a value to _profileText. + * + * @param module The PDFModule we're working under + * + */ + public PdfProfile (PdfModule module) + { + _module = module; + } + + /** + * Returns the value of the alreadyOK flag. + * This flag when one profile depends on another, to save redundant + * checking. + * The alreadyOK flag is set whenever satisfiesProfile + * returns true. + */ + public boolean isAlreadyOK () + { + return _alreadyOK; + } + + + /** + * Returns true if the document satisfies the profile. + * This calls satisfiesThisProfile(), which does the actual work. + * + * @param raf The RandomAccessFile being parsed + * @param parser The Parser being used on the file + */ + public final boolean satisfiesProfile + (RandomAccessFile raf, Parser parser) + { + _raf = raf; + _parser = parser; + _alreadyOK = false; + boolean sp = satisfiesThisProfile (); + if (sp) { + _alreadyOK = true; + } + return sp; + } + + /** + * Returns true if the document satisfies the + * profile. Subclasses should override satisfiesThisProfile(), + * not satisfiesProfile(), as + * satisfiesProfile() does some + * additional bookkeeping for all subclases. + */ + public abstract boolean satisfiesThisProfile (); + + + + /** + * Returns the text which describes this profile. + */ + public String getText () + { + return _profileText; + } + + /** Returns true if a Filter object contains a filter name which + * matches any of the Strings in the second argument. + * Will return falsefalse is returned. + * @param names An array of Strings naming the filters which should + * precipitate a true result + */ + protected boolean hasFilters (PdfObject filter, String[] names) + { + String filterName; + try { + if (filter == null) { + return false; + } + if (filter instanceof PdfSimpleObject) { + // Name of just one filter + filterName = ((PdfSimpleObject) filter).getStringValue (); + for (int j = 0; j < names.length; j++) { + if (names[j].equals (filterName)) { + return true; + } + } + } + else { + // If it's not a name, it must be an array + Vector filterVec = ((PdfArray) filter).getContent (); + for (int i = 0; i < filterVec.size (); i++) { + PdfSimpleObject filt = + (PdfSimpleObject) filterVec.elementAt (i); + filterName = filt.getStringValue (); + for (int j = 0; j < names.length; j++) { + if (names[j].equals (filterName)) { + return true; + } + } + } + } + } + catch (Exception e) { + return false; + } + return false; // none of the filters were found + + } + + /** + * This checks the "XObjects" dictionary, which is a dictionary whose + * entries have values that are XObjects. Override xObjectOK to + * implement profile-specific behavior. + */ + protected boolean xObjectsOK (PdfDictionary xos) + { + if (xos == null) { + return true; // nothing to fail + } + try { + Iterator iter = xos.iterator (); + while (iter.hasNext ()) { + PdfObject obj = _module.resolveIndirectObject + ((PdfObject) iter.next ()); + if (obj instanceof PdfStream) { + obj = ((PdfStream) obj).getDict (); + } + if (obj instanceof PdfDictionary) { + PdfDictionary xobj = (PdfDictionary) obj; + if (!xObjectOK (xobj)) { + return false; + } + } + } + } + catch (Exception e) { + return false; + } + return true; + } + + /** + * Checks a single XObject for xObjectsOK. Always returns true. + * Override to implement tests. + */ + protected boolean xObjectOK (PdfDictionary xo) + { + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfSimpleObject.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfSimpleObject.java new file mode 100644 index 00000000..8c7298d1 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfSimpleObject.java @@ -0,0 +1,130 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import java.util.*; + +/** + * A representation of a PDF object which can be represented + * by a Token. In some cases, this means that the full + * content of the object isn't stored, because we don't + * (think we) need it for our purposes. + */ +public class PdfSimpleObject extends PdfObject +{ + + private Token _token; + + /** + * Creates a PdfSimpleObject. + * + * @param objNumber The PDF object number + * @param genNumber The PDF generation number + * @param token The Token represented by this object + */ + public PdfSimpleObject (Token token, int objNumber, int genNumber) + { + super (objNumber, genNumber); + _token = token; + } + + + /** + * Creates a PdfSimpleObject. + * + * @param token The Token represented by this object + */ + public PdfSimpleObject (Token token) + { + super (); + _token = token; + } + + /** + * Returns the token represented by this object. + */ + public Token getToken () + { + return _token; + } + + /** + * Return the string value of the token. Returns + * null if the token is not a StringValuedToken. + */ + public String getStringValue () + { + if (!(_token instanceof StringValuedToken)) { + return null; + } + else { + return ((StringValuedToken) _token).getValue (); + } + } + + + /** + * Return the raw bytes of the token, as a Vector of Integer objects. + * Returns null if the token is not a StringValuedToken. + */ + public Vector getRawBytes () + { + if (!(_token instanceof StringValuedToken)) { + return null; + } + else { + return ((StringValuedToken) _token).getRawBytes (); + } + } + + + /** + * Return the integer value of the token. Throws a ClassCastException + * if the token is not a Numeric. + */ + public int getIntValue () + { + return ((Numeric) _token).getIntegerValue (); + } + + /** + * Return the double value of the token. Throws a + * ClassCastException if the token is not a Numeric. + */ + public double getDoubleValue () + { + return ((Numeric) _token).getValue (); + } + + /** + * Return true if the value of the token is the keyword + * "true", and false otherwise. + */ + public boolean isTrue () + { + if (!(_token instanceof Keyword)) { + return false; + } + else { + return "true".equals (((Keyword) _token).getValue ()); + } + } + + /** + * Return true if the value of the token is the keyword + * "false", and false otherwise. + */ + public boolean isFalse () + { + if (!(_token instanceof Keyword)) { + return false; + } + else { + return "false".equals (((Keyword) _token).getValue ()); + } + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfStream.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfStream.java new file mode 100644 index 00000000..c64a6760 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfStream.java @@ -0,0 +1,240 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import java.util.*; + +/** + * A representation of a PDF stream object. + * A PdfStream consists of a dictionary and a stream token. + * By default the content of the stream isn't loaded, but + * it can be loaded when necessary. + */ +public class PdfStream extends PdfObject +{ + + private Stream _stream; + private PdfDictionary _dict; + private Filter[] _filters; + private boolean pdfaCompliant; + + /** + * Creates a PdfStream + * + * @param dict A dictionary describing the stream + * @param stream A Stream token + * @param objNumber The PDF object number + * @param genNumber The PDF generation number + */ + public PdfStream (PdfDictionary dict, Stream stream, + int objNumber, int genNumber) + throws PdfException + { + super (objNumber, genNumber); + _stream = stream; + _dict = dict; + pdfaCompliant = true; // assume compliance to start with + extractFilters (); + } + + + /** + * Creates a PdfStream. + * + * @param dict A dictionary describing the stream + * @param stream A Stream token + */ + public PdfStream (PdfDictionary dict, Stream stream) + throws PdfException + { + super (); + _stream = stream; + _dict = dict; + pdfaCompliant = true; // assume compliance to start with + extractFilters (); + } + + + /** + * Returns the stream's dictionary + */ + public PdfDictionary getDict () + { + return _dict; + } + + + /** + * Returns the stream's Stream portion + */ + public Stream getStream () + { + return _stream; + } + + + /** + * If the stream is external, returns the file specification + * for it, otherwise returns null. + */ + public String getFileSpecification () + { + PdfObject spec = _dict.get ("F"); + if (spec == null) { + return null; + } + try { + pdfaCompliant = false; // not allowed with PDF/A + FileSpecification fs = new FileSpecification (spec); + return fs.getSpecString (); + } + catch (PdfException e) { + return null; + } + } + + /** Returns true if no PDF/A compliance problems have been found, false if + * problems have been found */ + public boolean isPdfaCompliant () { + return pdfaCompliant; + } + + + /** + * Returns an array (possibly empty but not null) of the filters for + * this Stream. The elements of the array are Filter + * objects. + */ + public Filter[] getFilters () throws PdfException + { + return _filters; + } + + + /** + * Return the name of the filter, if the DecodeParams dictionary + * is present and has a "Name" entry. + */ + public String getFilterName () + { + PdfObject decparms = _dict.get ("DecodeParams"); + if (decparms instanceof PdfDictionary) { + PdfObject name = ((PdfDictionary) decparms).get ("Name"); + if (name instanceof PdfSimpleObject) { + return ((PdfSimpleObject)name).getStringValue(); + } + } + return null; + } + + + + /* Constructs the _filters array. */ + private void extractFilters () throws PdfException + { + boolean ff = false; + _filters = new Filter[] {}; // default value + PdfObject filter = _dict.get ("Filter"); + if (filter == null) { + filter = _dict.get ("FFilter"); + if (filter == null) { + return; + } + ff = true; + pdfaCompliant = false; + } + PdfObject parms; + if (ff) { + parms = _dict.get ("FDecodeParms"); + if (parms != null) { + pdfaCompliant = false; + } + } + else { + parms = _dict.get ("DecodeParms"); + } + + /* There may be a single filter, which will be a string, + * in which case the parms will be a single dictionary. + * Or there may be an array, in which case the params will + * be an array of dictionaries. The parms are optional, so + * they may also be null. + */ + try { + if (filter instanceof PdfArray) { + Vector vec = ((PdfArray) filter).getContent(); + int size = vec.size (); + Filter[] val = new Filter[size]; + Vector parmVec = null; + if (parms != null) { + parmVec = ((PdfArray) parms).getContent (); + } + for (int i = 0; i < size; i++) { + PdfSimpleObject f = (PdfSimpleObject) vec.get(i); + val[i] = new Filter (f.getStringValue()); + if (parmVec != null) { + PdfObject parm = (PdfObject) parmVec.get(i); + // Parameter may be the null object. + if (parm instanceof PdfSimpleObject) { + PdfSimpleObject sParm = (PdfSimpleObject) parm; + if ("null".equals (sParm.getStringValue ())) { + continue; + } + } + val[i].setDecodeParms((PdfDictionary) parmVec.get(i)); + } + } + _filters = val; + } + else { + /* Only other allowed value is a string */ + Filter[] val = new Filter[1]; + val[0] = new Filter + (((PdfSimpleObject) filter).getStringValue()); + if (parms instanceof PdfDictionary) { + val[0].setDecodeParms((PdfDictionary) parms); + } + _filters = val; + } + } + catch (Exception e) { + throw new PdfMalformedException ("Malformed filter"); + } + } + + + protected List makeFilterList () throws PdfException + { + Filter[] filters = getFilters (); + if (filters.length == 0) { + return null; + } + List lst = new ArrayList (filters.length); + for (int i = 0; i < filters.length; i++) { + lst.add (filters[i].getFilterName()); + } + return lst; + } + + + /** + * Returns true if this is an image stream. + */ + public boolean isImage () + { + // An image dictionary may not have a type, but must have a subtype + // of Image. + PdfObject subtype = _dict.get ("Subtype"); + if (subtype instanceof PdfSimpleObject) { + String subtypeStr = ((PdfSimpleObject) subtype).getStringValue (); + return ("Image".equals (subtypeStr)); + } + else { + return false; + } + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfStrings.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfStrings.java new file mode 100644 index 00000000..92a7d60f --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfStrings.java @@ -0,0 +1,100 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +/** + * A class for holding arrays of informative strings that will go into + * properties of a PDF object. + */ +public class PdfStrings +{ + + /** A private constructor just to make sure nobody + instantiates the class by mistake. */ + private PdfStrings () + { + } + + + /** Encryption algorithm strings. */ + public final static String[] ALGORITHM = + { "Undocumented", + "40 bit key", + "Key greater than 40", + "Unpublished" }; + + /** Flags for FontDescriptor. In PDF notation, bit 1 + * (not 0) is the low-order bit. + */ + public final static String[] FONTDESCFLAGS = + { + "FixedPitch", // 1 + "Serif", // 2 + "Symbolic", // 3 + "Script", // 4 + "", // 5 + "Nonsymbolic", // 6 + "Italic", // 7 + "", // 8 + "", // 9 + "", // 10 + "", // 11 + "", // 12 + "", // 13 + "", // 14 + "", // 15 + "", // 16 + "AllCap", // 17 + "SmallCap", // 18 + "ForceBold"}; // 19 + + /** Flags for user access permissions when revision 3 is specified. */ + public final static String[] USERPERMFLAGS3 = + { + "", // 1, reserved + "", // 2, reserved + "Print", // 3 + "Modify", // 4 + "Extract", // 5 + "Add/modify annotations/forms", // 6 + "", // 7 + "", // 8 + "Fill interactive form fields", // 9 + "Extract for accessibility", // 10 + "Assemble", // 11 + "Print high quality" // 12 + }; + + /** Flags for user access permissions when revision 2 is specified. */ + public final static String[] USERPERMFLAGS2 = + { + "", // 1, reserved + "", // 2, reserved + "Print", // 3 + "Modify", // 4 + "Extract", // 5 + "Add/modify annotations/forms", // 6 + "", // 7 + "", // 8 + "", // 9 + "", // 10 + "", // 11 + "" // 12 + }; + + /** Flags for annotations */ + public final static String[] ANNOTATIONFLAGS = + { + "Invisible", // 1 + "Hidden", // 2 + "Print", // 3 + "NoZoom", // 4 + "NoRotate", // 5 + "NoView", // 6 + "ReadOnly" // 7 + }; + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfXMPSource.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfXMPSource.java new file mode 100644 index 00000000..f61109ad --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/PdfXMPSource.java @@ -0,0 +1,95 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +//import java.io.InputStream; +import java.io.*; +import edu.harvard.hul.ois.jhove.XMLWrapperStream; + +//import edu.harvard.hul.ois.jhove.XMPSource; + +/** + * Class for providing an InputSource to XMPHandler. + * Only an InputSource based on a Reader is supported. + * + * @author Gary McGath + * + */ +public final class PdfXMPSource + extends edu.harvard.hul.ois.jhove.XMPSource { + + private PdfStream _stream; + private RandomAccessFile _raf; + protected String _encoding; + + /** + * Constructor based on Stream object. + * Since a double read may be necessary, we have + * one version without encoding (before it's known), + * and another with encoding. + * + * @param stream PDFStream containing the XMP + * @param raf The RandomAccessFile object underlying the PDF + */ + public PdfXMPSource(PdfStream stream, + RandomAccessFile raf) + throws UnsupportedEncodingException { + super (new InputStreamReader + (new XMLWrapperStream + (new StreamInputStream (stream, raf), + "XMP", "1.0", null, null))); + //super(rdr); + _stream = stream; + _raf = raf; + } + + + + /** + * Constructor based on Stream object with + * encoding specified. + * + * @param stream PDFStream containing the XMP + * @param raf The RandomAccessFile object underlying the PDF + * @param encoding The character encoding to use + */ + public PdfXMPSource(PdfStream stream, + RandomAccessFile raf, + String encoding) + throws UnsupportedEncodingException { + super (new InputStreamReader + (new StreamInputStream (stream, raf), encoding)); + //super(rdr); + _stream = stream; + _raf = raf; + _encoding = encoding; + } + + + /* (non-Javadoc) + * + * Resets the reader by reinitializing it from the PdfStream. + * + * @see edu.harvard.hul.ois.jhove.XMPSource#resetReader() + */ + protected void resetReader() { + try { + if (_encoding == null) { + _reader = new InputStreamReader + (new StreamInputStream (_stream, _raf)); + } + else { + _reader = new InputStreamReader + (new StreamInputStream + (_stream, _raf), _encoding); + } + } + catch (UnsupportedEncodingException e) { + // Has no business happening if it didn't the first time. + } + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/RunLengthFilterStream.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/RunLengthFilterStream.java new file mode 100644 index 00000000..53e152f3 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/RunLengthFilterStream.java @@ -0,0 +1,29 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import java.io.FilterInputStream; +import java.io.InputStream; + +/** + * This is a stub which may be implemented in the future. + * It appears to be unnecessary for object streams and cross-reference + * streams created by any version of Acrobat through 7.0, and we + * don't look at other types of streams. + * + * @author Gary McGath + * + */ +public class RunLengthFilterStream extends FilterInputStream { + + /** + * @param in + */ + public RunLengthFilterStream(InputStream in) { + super(in); + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/State.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/State.java new file mode 100644 index 00000000..724b84e6 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/State.java @@ -0,0 +1,94 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +/** + * An enumeration class for use with the Tokenizer. Only + * the static instances which are declared within the class + * should ever be created. + */ +public class State +{ + /* **************************************************************** + * PUBLIC CLASS FIELDS. + ******************************************************************/ + + public static final State COMMENT = new State ("COMMENT"); + public static final State E = new State ("E"); + public static final State EN = new State ("EN"); + public static final State END = new State ("END"); + public static final State ENDS = new State ("ENDS"); + public static final State ENDST = new State ("ENDST"); + public static final State ENDSTR = new State ("ENDSTR"); + public static final State ENDSTRE = new State ("ENDSTRE"); + public static final State ENDSTREA = new State ("ENDSTREA"); + public static final State ENDSTREAM = new State ("ENDSTREAM"); + public static final State FRACTIONAL = new State ("FRACTIONAL"); + public static final State GREATER_THAN = new State ("GREATER_THAN"); + public static final State HEXADECIMAL = new State ("HEXADECIMAL"); + public static final State HEX_FE_1 = new State ("HEX_FE_1"); + public static final State HEX_FE_2 = new State ("HEX_FE_2"); + public static final State HEX_PDF_1 = new State ("HEX_PDF_1"); + public static final State HEX_PDF_2 = new State ("HEX_PDF_2"); + public static final State HEX_UTF16_1 = new State ("HEX_UTF16_1"); + public static final State HEX_UTF16_2 = new State ("HEX_UTF16_2"); + public static final State HEX_UTF16_3 = new State ("HEX_UTF16_3"); + public static final State HEX_UTF16_4 = new State ("HEX_UTF16_4"); + public static final State HEX_RAW = new State ("HEX_RAW"); + public static final State KEYWORD = new State ("KEYWORD"); + public static final State LESS_THAN = new State ("LESS_THAN"); + public static final State LITERAL = new State ("LITERAL"); + public static final State LITERAL_FE = new State ("LITERAL_FE"); + public static final State LITERAL_PDF = new State ("LITERAL_PDF"); + public static final State LITERAL_UTF16_1 = new State ("LITERAL_UTF16_1"); + public static final State LITERAL_UTF16_2 = new State ("LITERAL_UTF16_2"); + public static final State NAME = new State ("NAME"); + public static final State NUMERIC = new State ("NUMERIC"); + public static final State STREAM = new State ("STREAM"); + public static final State WHITESPACE = new State ("WHITESPACE"); + + /* **************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + private String _name; + + /* **************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * Constructor. It is private so that no other classes + * can create instances of State. + */ + private State (String name) + { + _name = name; + } + + /* **************************************************************** + * PUBLIC INSTANCE METHODS. + ******************************************************************/ + + /** + * Equality test. + * Two State objects are considered equal only if they + * are the same object. + */ + public boolean equals (State state) + { + return this == state; + } + + /** + * Convert to String representation. + * A State object's String representation is its name. + */ + public String toString () + { + return _name; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/StdStructTypes.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/StdStructTypes.java new file mode 100644 index 00000000..3eec9cc6 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/StdStructTypes.java @@ -0,0 +1,71 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + + +/** + * This class holds the constants for standard structure type names, + * and a static method for determining if a string belongs + * to those names. + */ +public class StdStructTypes +{ + /** + * Array of valid structure type names + */ + public final static String typeNames [] = { + "Document", "Part", "Art", "Sect", + "Div", "BlockQuote", "Caption", "TOC", + "TOCI", "Index", "NonStruct", "Private", + "P", "H", "H1", "H2", "H3", "H4", "H5", "H6", + "L", "LI", "Lbl", "LBody", + "Table", "TR", "TH", "TD", + "Span", "Quote", "Note", "Reference", + "BibEntry", "Code", "Link", + "Figure", "Formula", "Form" + }; + + /** + * The subset of typeNames which denotes a block-level + * element + */ + public final static String blockLevelNames [] = { + "P", "H", "H1", "H2", "H3", "H4", "H5", "H6", + "L", "LI", "Lbl", "LBody", "Table" + }; + + + /* Private constructor, so no instances of this object + can be created */ + private StdStructTypes () + { + } + + /** + * Returns true if s is equal (by an equals() test) + * to some string in typeNames. + */ + public static boolean includes (String s) + { + for (int i = 0; i < typeNames.length; i++) { + if (typeNames[i].equals (s)) { + return true; + } + } + return false; + } + + public static boolean isBlockLevel (String s) + { + for (int i = 0; i < blockLevelNames.length; i++) { + if (blockLevelNames[i].equals (s)) { + return true; + } + } + return false; + } +} + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Stream.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Stream.java new file mode 100644 index 00000000..4532413f --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Stream.java @@ -0,0 +1,227 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003-2005 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +//import edu.harvard.hul.ois.jhove.*; +import java.io.*; +//import java.util.*; +//import java.util.zip.InflaterInputStream; + +/** + * Class to encapsulate a stream token. The content of the + * stream is not saved, only its length and starting offset. + */ +public class Stream + extends Token +{ + /** Length of stream. */ + private long _length; + + /** Starting offset in file. */ + private long _offset; + + /** Number of bytes read so far. */ + private int _bytesRead; + + /** Filters which apply to this stream. */ + private Filter[] _filters; + + /** InputStream which incorporates all the filters. */ + private InputStream _inStream; + + /** Byte array which contains the raw file data for reading. */ + private byte[] _sdata; + + /** Constructor. */ + public Stream () + { + super (); + _length = 0; + _offset = -1; + _filters = new Filter[0]; + _bytesRead = 0; + _sdata = null; + } + + /** Returns the length of the stream. This is 0, unless + * the Stream's setLength method has been called. + */ + public long getLength () + { + return _length; + } + + /** + * Sets the length field. + * This should be the length of the stream proper + * (not counting its dictionary) before filtering, in other words, + * the number of bytes stored in the file. + */ + public void setLength (long length) + { + _length = length; + } + + /** Returns the current offset in the stream. This is -1, unless + * the Stream's setOffset method has been called. + */ + public long getOffset () + { + return _offset; + } + + /** + * Sets the offset field. + */ + public void setOffset (long offset) + { + _offset = offset; + } + + + /** Sets the array of filters used by the stream. + * This must be called before initRead. + */ + public void setFilters (Filter[] filters) + { + _filters = filters; + } + + + /** Prepares for reading the Stream. + * If the filter List includes one which we don't support, throws a + * PdfException. This supports the abbreviated filter names + * in Appendix H of the PDF spec. */ + public void initRead (RandomAccessFile raf) + throws IOException, PdfException + { + _bytesRead = 0; + raf.seek(_offset); + //InputStream is = new RAFInputStream (raf); + /* We can't easily resume reading a filtered stream if we + * seek elsewhere in the file, so the only really + * safe bet is to read it all into memory first. + * Fortunately, _length tells us the number of raw + * bytes we need to read. This also saves rereading + * when we need to reset the stream. */ + if (_sdata == null) { + _sdata = new byte[(int) _length]; + int ln = 0; + while (ln < _length) { + int n = raf.read (_sdata, ln, (int) (_length - ln)); + if (n <= 0) { + break; + } + ln += n; + } + } + InputStream is = new ByteArrayInputStream (_sdata); + for (int i = 0; i < _filters.length; i++) { + Filter filt = (Filter) _filters[i]; + String filtName = filt.getFilterName (); + FilterInputStream fis = null; + /* ASCIIHex-, ASCII85- and RunLengthDecode are currently + * just stubs. If we ever really need them, we should + * consider grabbing the implementations in PDFBox on + * SourceForge, which should (hint to third-party developers + * if you need them) just drop into place with + * the addition of an include. */ + if ("ASCIIHexDecode".equals (filtName) || "AHx".equals (filtName)) { + is = new AsciiHexFilterStream (is); + } + else if ("ASCII85Decode".equals (filtName) || "A85".equals (filtName)) { + is = new Ascii85FilterStream (is); + } + else if ("FlateDecode".equals (filtName) || "Fl".equals (filtName)) { + // InflaterInputStream does only part of the job. + // PdfFlateInputStream enhances it with Predictor support. + is = new PdfFlateInputStream (is, filt.getDecodeParms()); + } + else if ("RunLengthDecode".equals (filtName) || "RL".equals (filtName)) { + is = new RunLengthFilterStream (is); + } + } + _inStream = is; + } + + + /** Reads a byte from the Stream, applying the Filters if any. + */ + public int read() throws IOException + { + + int val = _inStream.read(); + if (val >= 0) { + ++_bytesRead; + } + return val; + } + + /** Reads a sequence of bytes from the Stream, applying the + * Filters if any. + */ + public int read (byte[] b) throws IOException + { + int n = _inStream.read (b); + if (n > 0) { + _bytesRead += n; + } + return n; + } + + /** Skips a specified number of bytes in the stream. */ + public long skipBytes (long n) throws IOException + { + long val = _inStream.skip(n); + _bytesRead += val; + return val; + } + + /** Reads an ASCII string, which may be preceded by white space. + * Will eat the first white space character after the ASCII + * string. */ + public int readAsciiInt () throws IOException, PdfException + { + boolean digitSeen = false; + int val = 0; + for (;;) { + char c = (char) read (); + if (Character.isDigit(c)) { + digitSeen = true; + val = val * 10 + (c - '0'); + } + else if (digitSeen) { + /* Non-digit after a digit; we're done */ + break; + } + else if (!Character.isWhitespace(c)) { + throw new PdfMalformedException + ("Malformed ASCII number in stream"); + } + } + return val; + } + + /** Advances to a specified offset in the stream. The offset + * is defined as the number of decompressed bytes which + * precede the position in the stream. Returns true + * if the advance is successful, false if the + * point has already been passed or some other failure occurs. + */ + public boolean advanceTo (int offset) throws IOException { + if (offset < _bytesRead) { + return false; // can't get there from here + } + else { + while (_bytesRead < offset) { + if (skipBytes (offset - _bytesRead) <= 0) { + break; + } + } + return true; + } + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/StreamInputStream.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/StreamInputStream.java new file mode 100644 index 00000000..1be5cbb6 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/StreamInputStream.java @@ -0,0 +1,55 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + + +package edu.harvard.hul.ois.jhove.module.pdf; + +import java.io.*; + + +/** A StreamInputStream is an InputStream which provides the bytes + * from a PdfStream. It reads bytes from the PdfStream's underlying + * file starting at the beginning of the stream data and providing + * as many bytes as are indicated by its length. No filters are + * applied; just the raw data is read. + * + */ +public class StreamInputStream extends InputStream { + + private RandomAccessFile _file; + private long _startPos; + private long _curPos; + private long _length; + + public StreamInputStream (PdfStream pdfStream, RandomAccessFile file) + { + _file = file; + Stream strm = pdfStream.getStream (); + _startPos = strm.getOffset (); + _curPos = _startPos; + _length = strm.getLength (); + try { + file.seek (_startPos); + } + catch (IOException e) {} + } + + + /** + * Return one byte from the stream. + * When the end of the stream is reached, returns -1. + */ + public int read () throws IOException + { + if (_curPos - _startPos >= _length) { + return -1; + } + else { + _curPos++; + int ch = _file.read (); + return ch; + } + } +} \ No newline at end of file diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/StreamTokenizer.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/StreamTokenizer.java new file mode 100644 index 00000000..394b05f6 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/StreamTokenizer.java @@ -0,0 +1,97 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2005 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import java.io.*; + +/** + * Tokenizer subclass which gets data from an object stream. + * + * @author Gary McGath + * + */ +public class StreamTokenizer extends Tokenizer { + + /** Source from which to read bytes. */ + private Stream _stream; + + /** Backup flag. */ + private boolean _backupFlag; + + /** Last character read. Will be returned again if _backupFlag + * is true. */ + private int _lastChar; + + private static final String NO_STREAM = + "Streams may not be embedded in object streams"; + + public StreamTokenizer (RandomAccessFile file, Stream stream) + { + super (); + _file = file; + _stream = stream; + _backupFlag = false; + } + + /** Streams can occur only in files, not in streams, + * so this should never be called. + */ + protected void initStream (Stream token) + throws IOException, PdfException + { + throw new PdfMalformedException (NO_STREAM); + } + + /** Gets a character from the file, using a buffer. */ + public int readChar () throws IOException + { + if (_backupFlag) { + _backupFlag = false; + return _lastChar; + } + else { + _lastChar = _stream.read (); + return _lastChar; + } + } + + /** + * Set the Tokenizer to a new position in the stream. + * + * @param offset The offset in bytes from the start of the stream. + */ + public void seek (long offset) + throws IOException, PdfException + { + // Advancing in the stream is easy. Backing up requires starting + // the stream over. + if (!_stream.advanceTo ((int) offset)) { + _stream.initRead (_file); + _stream.advanceTo ((int) offset); + } + seekReset (_stream.getOffset()); + } + + /** Sets the offset of a Stream to the current file position. + * Only the file-based tokenizer can do this, so this should never + * be called. + */ + protected void setStreamOffset (Stream token) + throws IOException, PdfException + { + throw new PdfMalformedException (NO_STREAM); + } + + + /** + * Back up a byte so it will be read again. + */ + public void backupChar () + { + _backupFlag = true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/StringValuedToken.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/StringValuedToken.java new file mode 100644 index 00000000..cf570b3f --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/StringValuedToken.java @@ -0,0 +1,49 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import java.util.Vector; + +/** + * Abstract class for all PDF tokens which consist of a character sequence. + */ +public abstract class StringValuedToken + extends Token +{ + protected String _value; + protected Vector _rawBytes; + + public StringValuedToken () + { + super (); + } + + /** + * Get the value of the token as a String. + */ + public String getValue () + { + return _value; + } + + /** + * Get the value of the token's untranslated bytes. This is unsupported + * and will always return null. + */ + public Vector getRawBytes () + { + return _rawBytes; + } + + /** + * Set the value of the token. + */ + public void setValue (String value) + { + _value = value; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/StructureElement.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/StructureElement.java new file mode 100644 index 00000000..a18fe565 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/StructureElement.java @@ -0,0 +1,377 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import edu.harvard.hul.ois.jhove.module.PdfModule; +import java.io.*; +import java.util.*; + +/** + * Class for element of PDF document structure tree. + * + * @see StructureTree + */ +public class StructureElement +{ + private StructureTree _tree; + private PdfDictionary _dict; + private PdfModule _module; + private List children; + private String _structType; + private boolean _structIsInline; + private boolean _attrIsBlock; + + /* Attributes which should occur only in block level elements */ + private static final String blockLevelAttrs [] = { + "SpaceBefore", "SpaceAfter", "StartIndent", + "EndIndent", "TextIndent", "TextAlign", "BBox", + "Width", "Height", "BlockAlign", "InlineAlign" + }; + + + + /** + * Constructor. + * @param dict A PdfDictionary corresponding to a structure + * element + * @param tree The root StructureTree object + */ + public StructureElement (PdfDictionary dict, StructureTree tree) + throws PdfException + { + _tree = tree; + _dict = dict; + _module = tree.getModule (); + _structType = null; + + // If this element has a standard structure type, find it. + try { + PdfObject s = _module.resolveIndirectObject (dict.get ("S")); + Token tok = ((PdfSimpleObject) s).getToken (); + String st = ((Name) tok).getValue (); + st = _tree.dereferenceStructType (st); + if (StdStructTypes.includes (st)) { + _structType = st; + } + } + catch (IOException e) {} + } + + /** + * Build this element's subtree, if any + * This checks the "K" entry in the dictionary and + * locates all referened structure elements. These + * are put into StructureElement objects, which have + * their own subtrees built, and these StructureElements + * are accumulated into children. + */ + public void buildSubtree () throws PdfException + { + PdfObject k = null; + try { + k = _module.resolveIndirectObject (_dict.get ("K")); + } + catch (IOException e) { + throw new PdfInvalidException ("Invalid data in document structure tree"); + } + children = null; + + // The "K" element is complicated, having five variants. + if (k instanceof PdfSimpleObject) { + // A marked-content identifier. We don't explore further. + return; + } + else if (k instanceof PdfDictionary) { + // Could be any of three kinds of dictionaries: + // - A marked-content reference dictionary + // - A PDF object reference dictionary + // - A structure element reference dictionary + // The only one we check seriously is a structure element. + PdfDictionary kdict = (PdfDictionary) k; + if (isStructElem (kdict)) { + StructureElement se = + new StructureElement (kdict, _tree); + se.buildSubtree (); + se.checkAttributes (); + children = new ArrayList (1); + children.add (se); + } + else if (!isMarkedContent (kdict) && !isObjectRef (kdict)) { + throw new PdfInvalidException + ("Unknown element in structure tree"); + } + } + else if (k instanceof PdfArray) { + Vector kvec = ((PdfArray) k).getContent (); + children = new LinkedList (); + for (int i = 0; i < kvec.size (); i++) { + PdfObject kelem = (PdfObject) kvec.elementAt (i); + try { + kelem = _module.resolveIndirectObject (kelem); + } + catch (IOException e) {} + if (kelem instanceof PdfDictionary) { + PdfDictionary kdict = (PdfDictionary) kelem; + if (isStructElem (kdict)) { + StructureElement se = + new StructureElement (kdict, _tree); + se.buildSubtree (); + se.checkAttributes (); + children.add (se); + } + } + } + // It's possible that none of the elements of the array + // were structure elements. In this case, we change + // children to null rather than have to check for an + // empty vector. + if (children.isEmpty ()) { + children = null; + } + } + } + + /** + * Determine if the attributes of this element are + * valid. If errors are detected, throws a PdfInvalidException. + */ + public void checkAttributes () throws PdfException { + final String badattr = "Invalid structure attribute"; + PdfObject attr; + + // Use the variables _structIsInline and _attrIsBlock to + // note when we've got a block-level-only attribute in + // an inline structure element. We initially set + // _structIsInline based on the structure type, but this + // may be overridden by the Placement attribute. + // Figure elements occupy an ambiguous position, so we + // don't mark them as ILSE's. Also, TR, TH and TD are + // defined to be neither BLSE's nor ILSE's. + _attrIsBlock = false; + _structIsInline = !_structType.equals ("Figure") && + !_structType.equals ("TH") && + !_structType.equals ("TD") && + !_structType.equals ("TR") && + !StdStructTypes.isBlockLevel (_structType); + + try { + attr = _module.resolveIndirectObject (_dict.get ("A")); + } + catch (Exception e) { + throw new PdfInvalidException ("Invalid structure attribute reference"); + } + if (attr == null) { + // no attributes is fine + return; + } + if (attr instanceof PdfArray) { + // If we have an array, it may contain elements and + // revision numbers. A revision number may follow + // an element, but there doesn't have to be one. + Vector attrVec = ((PdfArray) attr).getContent (); + for (int i = 0; i < attrVec.size (); i++) { + PdfObject attrElem; + try { + attrElem = _module.resolveIndirectObject + (attrVec.elementAt (i)); + } + catch (IOException e) { + throw new PdfInvalidException (badattr); + } + if (attrElem instanceof PdfDictionary) { + checkAttribute ((PdfDictionary) attrElem); + } + else if (attrElem instanceof PdfSimpleObject) { + try { + Numeric revnum = (Numeric) + ((PdfSimpleObject)attrElem).getToken (); + } + catch (Exception e) { + throw new PdfInvalidException (badattr); + } + } + else { + throw new PdfInvalidException (badattr); + } + } + } + else if (attr instanceof PdfDictionary) { + checkAttribute ((PdfDictionary) attr); + } + else { + throw new PdfInvalidException ("Structure attribute has illegal type"); + } + if (_structIsInline && _attrIsBlock) { + throw new PdfInvalidException ("Block-level attributes in inline structure element"); + } + } + + + /* Check if an attribute dictionary is reasonable. */ + private void checkAttribute (PdfDictionary attr) + throws PdfException + { + try { + // Must have an entry named "O", whose value is a name. + PdfSimpleObject plugin = (PdfSimpleObject) attr.get ("O"); + Name tok = (Name) plugin.getToken (); + + // If it has a Placement entry with a value other than + // "Inline", then we allow block level attributes. + PdfSimpleObject placement = + (PdfSimpleObject) attr.get ("Placement"); + if (placement != null && + !"Inline".equals (placement.getStringValue ())) { + _structIsInline = false; + } + // Though I don't think the Adobe PDF bible actually + // says so, it appears that the "attributes" are + // simply other keys in the attribute dictionary. + // Remember if we see attributes that can't go in BLSE's; + // we'll check later if we're actually in a BLSE. + if (attrIsBlockLevel (attr)) { + _attrIsBlock = true; + } + } + catch (Exception e) { + throw new PdfInvalidException ("Invalid attribute in document structure"); + } + } + + + + /* See if a dictionary is a structure element. + We identify it by the S and P elements, which are + required, and by making sure that the Type element, + if present, has a value of "StructElem". + */ + private boolean isStructElem (PdfDictionary elem) + throws PdfException + { + try { + PdfObject typ = elem.get ("Type"); + if (typ != null) { + if (!"StructElem".equals + (((PdfSimpleObject) typ).getStringValue ())) { + return false; + } + } + + PdfObject s = _module.resolveIndirectObject (elem.get ("S")); + // The structure type is supposed to be one of + // a list of known structure types, or else is + // mapped to one through the role map dictionary. + // For the moment, just make sure it's a name. + if (!(s instanceof PdfSimpleObject)) { + return false; + } + Token tok = ((PdfSimpleObject) s).getToken (); + if (!(tok instanceof Name)) { + return false; + } + // It appears that there really isn't a requirement + // to have structure types belong to the standard types. + // Conditionalize this code out, pending more info. + boolean checkStandardTypes = false; + String st = ((Name) tok).getValue (); + st = _tree.dereferenceStructType (st); + if (!StdStructTypes.includes (st)) { + if (checkStandardTypes) { + throw new PdfInvalidException ("Non-standard structure type name"); + } + } + else { + // The structure type is a standard one. + } + // The parent reference must be an indirect reference. + // The documentation says it must refer to another + // structure element dictionary, but it seems that it + // must also be able to refer to the structure tree root. + // I'll allow both. + PdfObject pref = elem.get ("P"); + if (!(pref instanceof PdfIndirectObj)) { + return false; + } + // Make sure it refers to a dictionary (at least). + PdfDictionary p = (PdfDictionary) + _module.resolveIndirectObject (pref); + PdfSimpleObject ptype = (PdfSimpleObject) p.get ("Type"); + if (ptype != null) { + String typename = ptype.getStringValue (); + if (!"StructTreeRoot".equals (typename) && + !"StructElem".equals (typename)) { + return false; + } + } + // Passed all tests. + return true; + } + catch (Exception e) { + // Some assumption was violated + return false; + } + } + + /* See if an attribute dictionary has attributes which + are permitted only at block level. */ + private boolean attrIsBlockLevel (PdfDictionary attrDict) + { + for (int i = 0; i < blockLevelAttrs.length; i++) { + if (attrDict.get (blockLevelAttrs[i]) != null) { + return true; + } + } + return false; + } + + /* Determine if a dictionary is a marked content dictionary. + See Table 9.11 in the PDF 1.4 book. */ + private boolean isMarkedContent (PdfDictionary dict) + { + try { + PdfSimpleObject typeObj = + (PdfSimpleObject) dict.get ("Type"); + if (!typeObj.getStringValue ().equals ("MCR")) { + return false; + } + // An MCID entry is required. + PdfSimpleObject mcidObj = + (PdfSimpleObject) _module.resolveIndirectObject + (dict.get ("MCID")); + if (mcidObj == null) { + return false; + } + return true; + } + catch (Exception e) { + return false; + } + } + + /* Determine if a dictionary is an object reference dictionary, + as in table 9.12. */ + private boolean isObjectRef (PdfDictionary dict) + { + try { + PdfSimpleObject typeObj = + (PdfSimpleObject) dict.get ("Type"); + if (!typeObj.getStringValue ().equals ("OBJR")) { + return false; + } + // An Obj entry is required. Must be an indirect object. + PdfObject obj = _module.resolveIndirectObject + (dict.get ("Obj")); + if (obj == null) { + return false; + } + return true; + } + catch (Exception e) { + return false; + } + + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/StructureTree.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/StructureTree.java new file mode 100644 index 00000000..82a90c39 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/StructureTree.java @@ -0,0 +1,230 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import edu.harvard.hul.ois.jhove.module.PdfModule; +import java.io.*; +import java.util.*; + +/** + * Class for PDF document structure tree. + * See section 9.6, "Logical Structure," of PDF Reference, + * Version 1.4, for an explanation of the document structure. + * + * @see StructureElement + */ +public class StructureTree +{ + private PdfModule _module; + private RandomAccessFile _raf; + private Parser _parser; + private PdfDictionary _rootDict; + private PdfDictionary _roleMap; + private List children; + private boolean _present; + private boolean _valid; + + /** + * Constructor. If there is a document structure tree, + * this fills in the appropriate information. If there isn't, + * it does nothing. Call isPresent() to determine whether + * there is a document structure tree. A PdfInvalidException may be + * thrown if there is a structure tree but it is invalid. + * + * @param module The PdfModule under which we're operating + * @param raf The document file object + * @param parser The Parser being used + */ + public StructureTree (PdfModule module, + RandomAccessFile raf, + Parser parser) throws PdfException + { + _module = module; + _raf = raf; + _parser = parser; + try { + PdfDictionary docCatDict = module.getCatalogDict (); + // There must be an entry in the catalog dictionary + // named StructTreeRoot. If there isn't, set _present + // to false. + _rootDict = null; + try { + _rootDict = (PdfDictionary) _module.resolveIndirectObject + ((PdfObject) docCatDict.get ("StructTreeRoot")); + } + catch (IOException e) {} + if (_rootDict == null) { + _present = false; + _valid = false; + return; + } + _present = true; + validateRoot (); + getRoleMap (); + children = getChildren (); + _valid = true; + } + catch (Exception e) { + _valid = false; + } + } + + /** + * Returns true if and only if the document + * structure exists. + */ + public boolean isPresent () + { + return _present; + } + + + /** + * Returns true if and only if no errors were + * detected. + */ + public boolean isValid () + { + return _valid; + } + + + /** Returns the module associated with this object. */ + public PdfModule getModule () + { + return _module; + } + + + /** + * Dereference a name in the role map. + * If there is no role map, or if the parameter is not + * mapped by the role map, the original parameter will + * be returned. The string will be looked up through + * multiple levels in the role map. The maximum number + * of levels is limited to 50, in case of circular + * mappings. The value returned will be null if the + * role map contains invalid data or the limit of 50 + * lookups is reached. + */ + public String dereferenceStructType (String st) + { + if (_roleMap == null) { + return st; + } + // There could be a circular mapping, so we limit the + // number of concatenated lookups. + for (int i = 0; i < 50; i++) { + try { + PdfSimpleObject mapped = + (PdfSimpleObject) _roleMap.get (st); + if (mapped == null) { + return st; + } + st = mapped.getStringValue (); + } + catch (Exception e) { + return null; // BAD dictionary! No mapping! + } + } + return null; // Looks like an infinite loop + } + + + /* See if the root is valid. If not, throw a PDFException. */ + private void validateRoot () throws PdfException + { + final String badRoot = "Invalid document structure root"; + try { + PdfSimpleObject typ = + (PdfSimpleObject)_rootDict.get ("Type"); + if (!"StructTreeRoot".equals (typ.getStringValue ())) { + throw new PdfInvalidException (badRoot); + } + } + catch (PdfException e) { + throw e; + } + catch (Exception e) { + throw new PdfInvalidException (badRoot); + } + } + + /** + * Replaces a string with a string to which the role map + * maps it. This may involve multiple levels of lookup. + */ + + /* Build a list of the children of the root. The + elements of the list are StructureElements. + Returns null if there are none. */ + private List getChildren () throws PdfException + { + final String invdata = "Invalid data in document structure root"; + List kidsList = null; + PdfObject kids = null; + try { + kids = _module.resolveIndirectObject + (_rootDict.get ("K")); + } + catch (IOException e) {} + if (kids == null) { + return null; + } + + if (kids instanceof PdfDictionary) { + // Only one child + kidsList = new ArrayList (1); + StructureElement se = new StructureElement + ((PdfDictionary) kids, this); + se.buildSubtree (); + se.checkAttributes (); + kidsList.add (se); + return kidsList; + } + else if (kids instanceof PdfArray) { + // Multiple children + Vector kidsVec = ((PdfArray) kids).getContent (); + kidsList = new ArrayList (kidsVec.size ()); + for (int i = 0; i < kidsVec.size (); i++) { + PdfObject kid; + try { + kid = _module.resolveIndirectObject + ((PdfObject) kidsVec.elementAt (i)); + } + catch (IOException e) { + throw new PdfMalformedException (invdata); + } + StructureElement se = new StructureElement + ((PdfDictionary) kid, this); + se.buildSubtree (); + se.checkAttributes (); + kidsList.add (se); + } + return kidsList; + } + else { + throw new PdfInvalidException (invdata); + } + } + + + /* Extract and save the role map, if any. Throw an + exception if RoleMap names something that isn't + a dictionary. It's legitimate for roleMap to be null. */ + private void getRoleMap () throws PdfException + { + final String invdata = "Invalid RoleMap"; + try { + _roleMap = (PdfDictionary) _module.resolveIndirectObject + (_rootDict.get ("RoleMap")); + } + catch (Exception e) { + throw new PdfInvalidException (invdata); + } + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/TaggedProfile.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/TaggedProfile.java new file mode 100644 index 00000000..bc4c3704 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/TaggedProfile.java @@ -0,0 +1,72 @@ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import edu.harvard.hul.ois.jhove.module.*; + +/** + * PDF profile checker for Tagged PDF documents. + * See section 9.7, "Tagged PDF", of the PDF Reference, + * Version 1.4, for an explanation of tagged PDF. + */ +public final class TaggedProfile extends PdfProfile +{ + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + + /** + * Constructor. + * Creates a TaggedProfile object for subsequent testing. + * + * @param module The module under which we are checking the profile. + * + */ + public TaggedProfile (PdfModule module) + { + super (module); + _profileText = "Tagged PDF"; + } + + /** + * Returns true if the document satisfies the profile. + * We check only the dictionaries, not the stream contents. + * + */ + public boolean satisfiesThisProfile () + { + try { + PdfDictionary docCatDict = _module.getCatalogDict (); + // An entry named markInfo must be in the doc catalog, + // and must be a dictionary. The dictionary must + // contain an entry named Marked, which must have a value + // of true. + PdfDictionary markInfo = (PdfDictionary) + _module.resolveIndirectObject + ((PdfObject) docCatDict.get ("MarkInfo")); + if (markInfo == null) { + return false; + } + PdfSimpleObject marked = + (PdfSimpleObject) markInfo.get ("Marked"); + if (!marked.isTrue ()) { + return false; + } + + // So much for MarkInfo. Now see if there is a + // valid structure tree. + StructureTree stree = new StructureTree (_module, + _raf, _parser); + if (!stree.isPresent () || !stree.isValid ()) { + return false; + } + } + catch (Exception e) { + // An exception thrown anywhere means some assumption + // has been violated, so it doesn't meet the profile. + return false; + } + return true; // passed all tests + } + + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Token.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Token.java new file mode 100644 index 00000000..d90b3fd5 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Token.java @@ -0,0 +1,41 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +/** + * Abstract class to encapsulate lexical tokens from a PDF + * file. Tokens include numbers, strings, names, delimiters (the + * open and close markers for dictionaries and arrays), and streams. + * There are a variety of subclasses for specific kinds of tokens. + */ +public abstract class Token +{ + + /** Superclass constructor */ + public Token () + { + } + + /** + * Returns true if the token is one which the Parser + * treats as a unitary object. Everything but arrays and dictionaries + * is considered a "simple" token for our purposes. + */ + public boolean isSimpleToken () + { + return (! (this instanceof ArrayStart) && + ! (this instanceof ArrayEnd) && + ! (this instanceof DictionaryStart) && + ! (this instanceof DictionaryEnd)); + } + + /** Returns true if this token is within PDF/A implementation + * limits. Always returns true unless overridden. */ + public boolean isPdfACompliant () + { + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Tokenizer.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Tokenizer.java new file mode 100644 index 00000000..3a348e69 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/Tokenizer.java @@ -0,0 +1,903 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import java.io.*; +import java.util.*; + +/** + * Tokenizer for PDF files. + * This is used in conjunction with the Parser, which assembled + * Tokens into higher-level constructs. + */ +public abstract class Tokenizer +{ + /** Mapping between PDFDocEncoding and Unicode code points. */ + public static char [] PDFDOCENCODING = { + '\u0000','\u0001','\u0002','\u0003','\u0004','\u0005','\u0006','\u0007', + '\b' ,'\t' ,'\n' ,'\u000b','\f' ,'\r' ,'\u000e','\u000f', + '\u0010','\u0011','\u0012','\u0013','\u0014','\u0015','\u0016','\u0017', + '\u02d8','\u02c7','\u02c6','\u02d9','\u02dd','\u02db','\u02da','\u02dc', + '\u0020','\u0021','\"' ,'\u0023','\u0024','\u0025','\u0026','\'', + '\u0028','\u0029','\u002a','\u002b','\u002c','\u002d','\u002e','\u002f', + '\u0030','\u0031','\u0032','\u0033','\u0034','\u0035','\u0036','\u0037', + '\u0038','\u0039','\u003a','\u003b','\u003c','\u003d','\u003e','\u003f', + '\u0040','\u0041','\u0042','\u0043','\u0044','\u0045','\u0046','\u0047', + '\u0048','\u0049','\u004a','\u004b','\u004c','\u004d','\u004e','\u004f', + '\u0050','\u0051','\u0052','\u0053','\u0054','\u0055','\u0056','\u0057', + '\u0058','\u0059','\u005a','\u005b','\\' ,'\u005d','\u005e','\u005f', + '\u0060','\u0061','\u0062','\u0063','\u0064','\u0065','\u0066','\u0067', + '\u0068','\u0069','\u006a','\u006b','\u006c','\u006d','\u006e','\u006f', + '\u0070','\u0071','\u0072','\u0073','\u0074','\u0075','\u0076','\u0077', + '\u0078','\u0079','\u007a','\u007b','\u007c','\u007d','\u007e','\u007f', + '\u2022','\u2020','\u2021','\u2026','\u2003','\u2002','\u0192','\u2044', + '\u2039','\u203a','\u2212','\u2030','\u201e','\u201c','\u201d','\u2018', + '\u2019','\u201a','\u2122','\ufb01','\ufb02','\u0141','\u0152','\u0160', + '\u0178','\u017d','\u0131','\u0142','\u0153','\u0161','\u017e','\u009f', + '\u20ac','\u00a1','\u00a2','\u00a3','\u00a4','\u00a5','\u00a6','\u00a7', + '\u00a8','\u00a9','\u00aa','\u00ab','\u00ac','\u00ad','\u00ae','\u00af', + '\u00b0','\u00b1','\u00b2','\u00b3','\u00b4','\u00b5','\u00b6','\u00b7', + '\u00b8','\u00b9','\u00ba','\u00bb','\u00bc','\u00bd','\u00be','\u00bf', + '\u00c0','\u00c1','\u00c2','\u00c3','\u00c4','\u00c5','\u00c6','\u00c7', + '\u00c8','\u00c9','\u00ca','\u00cb','\u00cc','\u00cd','\u00ce','\u00cf', + '\u00d0','\u00d1','\u00d2','\u00d3','\u00d4','\u00d5','\u00d6','\u00d7', + '\u00d8','\u00d9','\u00da','\u00db','\u00dc','\u00dd','\u00de','\u00df', + '\u00e0','\u00e1','\u00e2','\u00e3','\u00e4','\u00e5','\u00e6','\u00e7', + '\u00e8','\u00e9','\u00ea','\u00eb','\u00ec','\u00ed','\u00ef','\u00ef', + '\u00f0','\u00f1','\u00f2','\u00f3','\u00f4','\u00f5','\u00f6','\u00f7', + '\u00f8','\u00f9','\u00fa','\u00fb','\u00fc','\u00fd','\u00fe','\u00ff' + }; + + private static final int CR = 0x0D; + private static final int LF = 0x0A; + private static final int BS = 0x08; + private static final int HT = 0x09; + private static final int FORMFEED = 0x0C; + private static final int ESC = 0X1B; + private static final int POUND_SIGN = 0x23; + private static final int PERCENT_SIGN = 0x25; + private static final int OPEN_PARENTHESIS = 0x28; + private static final int CLOSE_PARENTHESIS = 0x29; + private static final int PLUS = 0x2B; + private static final int MINUS = 0x2D; + private static final int PERIOD = 0x2E; + private static final int SLASH = 0x2F; + private static final int LESS_THAN = 0x3C; + private static final int GREATER_THAN = 0x3E; + private static final int OPEN_BRACKET = 0x5B; + private static final int BACKSLASH = 0x5C; + private static final int CLOSE_BRACKET = 0x5D; + private static final int a = 0x61; + private static final int d = 0x64; + private static final int e = 0x65; + private static final int m = 0x6D; + private static final int n = 0x6E; + private static final int r = 0x72; + private static final int s = 0x73; + private static final int t = 0x74; + //private static final int FE = 0xFE; + //private static final int FF = 0xFF; + + //private static final String CRLF = "" + CR + LF; + + /** Delimiter character codes. */ + private static final int DELIMITER [] = { + PERCENT_SIGN, OPEN_PARENTHESIS, CLOSE_PARENTHESIS, SLASH, LESS_THAN, + GREATER_THAN, OPEN_BRACKET, CLOSE_BRACKET, + 0x7B, /* { */ + 0x7D /* } */ + }; + + /** Whitespace character codes. */ + private static final int WHITESPACE [] = { + 0x00, /* NUL */ + 0x09, /* HT */ + LF, /* LF */ + 0x0C, /* FORMFEED */ + CR, /* CR */ + 0x20 /* SP */ + }; + + /** Source from which to read bytes. */ + protected RandomAccessFile _file; + + /** Character code of current character. */ + protected int _ch; + + /** If true, use the look-ahead character, rather than reading from + the file. */ + private boolean _lookAhead; + + /** Current offset into file for reporting purposes. */ + private long _offset; + + /* Current parse state. */ + private State _state; + + /* White space string. */ + private String _wsString; + + /* PDF/A compliance flag. */ + private boolean _pdfACompliant; + + /* Encryption flag. */ + private boolean _encrypted; + + /* Set of language codes used in UTF strings. */ + private Set _languageCodes; + + /* Level of nesting of parentheses in a literal; zero is the base + level. */ + private int _parenLevel; + + /** Scan mode. If true do not attempt to parse non-whitespace delimited + * tokens, e.g., literal and hexadecimal strings. */ + private boolean _scanMode; + + /** + * Constructor. + */ + public Tokenizer () + { + _state = State.WHITESPACE; + _wsString = ""; + _lookAhead = false; + _ch = 0; + _offset = 0; + _languageCodes = new TreeSet (); + _pdfACompliant = true; + _scanMode = false; + } + + /** + * Parses out and returns a token from the input file. + * If it hits the end of the file, returns null. + * Other parsing problems cause an exception to be thrown. + * When an exception is thrown, the state is changed to + * WHITESPACE, so the parser can get back in sync more easily. + */ + public Token getNext () + throws IOException, PdfException + { + return getNext (0L); + } + + /** + * Parses out and returns a token from the input file. + * If it hits the end of the file, returns null. + * Other parsing problems cause an exception to be thrown. + * When an exception is thrown, the state is changed to + * WHITESPACE, so the parser can get back in sync more easily. + * @param max Maximum allowable size of the token + */ + public Token getNext (long max) + throws IOException, PdfException + { + String tokErr = "Lexical error"; + Token token = null; + StringBuffer buffer = null; + //StringBuffer bkslBuffer = null; + _state = State.WHITESPACE; + _wsString = ""; + /** Numeric sign. */ + boolean negative = false; + /** Floating value. */ + double realValue = 0.0; + /** Integer value. */ + long intValue = 0; + /** Numeric fractional positional unit. */ + double denom = 10.0; + /** Stream length. */ + long length = 0L; + /** Last character seen in stream but one. */ + int prelastch = 0; + /** Last character seen in stream. */ + int lastch = 0; + /** First byte of a UTF-16 character. */ + //int b1 = 0x00; + /** First digit of a hexadecimal string value. */ + //int h1 = 0x00; + /** Variable for UTF-16 chars. */ + //int utfch = 0; + /** Line break flag for the beginning of a data stream. */ + boolean sawLineBreak = false; + /** Carriage return flag for the beginning of a data stream. */ + boolean sawCR = false; + + long startOffset = _offset; + try { + while (true) { + if (max > 0L) { + if (_offset - startOffset > max) { + + /* The token has exceeded the specified maximum size.*/ + + if (token != null && + token instanceof StringValuedToken && + buffer != null) { + ((StringValuedToken) token).setValue ( + buffer.toString ()); + } + else { + token = null; + } + return token; + } + } + + if (!_lookAhead) { + _ch = readChar (); + if (_ch < 0) { + _state = State.WHITESPACE; + throw new PdfMalformedException("Unexpected EOF", + _offset); + } + _offset++; + } + else { + _lookAhead = false; + } + + if (_state == (State.WHITESPACE)) { + + // We are not in the middle of a token. + // Anything we read here starts a token + // or continues whitespace. + + if (isWhitespace (_ch)) { + _wsString += (char) _ch; + } + else if (_ch == OPEN_BRACKET) { + _state = State.WHITESPACE; + _wsString = ""; + + return new ArrayStart (); + } + else if (_ch == CLOSE_BRACKET) { + _state = State.WHITESPACE; + _wsString = ""; + + return new ArrayEnd (); + } + else if (_ch == PERCENT_SIGN) { + _state = State.COMMENT; + buffer = new StringBuffer (); + token = new Comment (); + } + else if (_ch == PLUS || _ch == MINUS) { + _state = State.NUMERIC; + intValue = 0; + negative = ((_ch == MINUS) ? true : false); + token = new Numeric (); + } + else if (_ch == PERIOD) { + _state = State.FRACTIONAL; + realValue = 0.0; + negative = false; + denom = 10.0; + token = new Numeric (); + } + else if (isNumeral (_ch)) { + _state = State.NUMERIC; + intValue = _ch - 48; + denom = 10.0; + token = new Numeric (); + //((Numeric) token).setReal (false); + } + else if (_ch == SLASH) { + _state = State.NAME; + buffer = new StringBuffer (); + token = new Name (); + } + else if (_ch == OPEN_PARENTHESIS) { + if (!_scanMode) { + _state = State.LITERAL; + _parenLevel = 0; + token = new Literal (); + buffer = new StringBuffer (); + } + } + else if (_ch == LESS_THAN) { + _state = State.LESS_THAN; + } + else if (_ch == GREATER_THAN) { + _state = State.GREATER_THAN; + } + else if (!isDelimiter (_ch)) { + _state = State.KEYWORD; + buffer = new StringBuffer (); + buffer.append ((char) _ch); + token = new Keyword (); + } + // end State.WHITESPACE + } + else if (_state == (State.COMMENT)) { + + // We are in a comment. Only a line ender can + // get us out. + + if (_ch == CR || _ch == LF) { + _state = State.WHITESPACE; + _wsString += (char) _ch; + ((StringValuedToken)token).setValue(buffer.toString()); + if (!token.isPdfACompliant()) { + _pdfACompliant = false; + } + return token; + } + else { + buffer.append ((char) _ch); + } + } + else if (_state == (State.FRACTIONAL)) { + + // We are reading a number and have encountered + // a decimal point. + if (isDelimiter (_ch) || isWhitespace (_ch)) { + _state = State.WHITESPACE; + _wsString = "" + (char) _ch; + if (negative) { + realValue = - realValue; + } + ((Numeric) token).setValue (realValue); + + if (isDelimiter (_ch)) { + _lookAhead = true; + } + + if (!token.isPdfACompliant()) { + _pdfACompliant = false; + } + return token; + } + else if (isNumeral (_ch)) { + realValue = realValue + ((_ch - 48)/denom); + denom *= 10.0; + } + else { + // invalid character in a number + _state = State.WHITESPACE; + _wsString = ""; + throw new PdfMalformedException (tokErr, _offset); + } + } + else if (_state == (State.GREATER_THAN)) { + // ">" must be followed by another > as a dict end + if (_ch == GREATER_THAN) { + _state = State.WHITESPACE; + _wsString = ""; + return new DictionaryEnd (); + } + else { + _state = State.WHITESPACE; + _wsString = ""; + throw new PdfMalformedException (tokErr, _offset); + } + } + else if (_state == (State.HEXADECIMAL)) { + // We're in a hexadecimal string. We will + // transition from this state to a state which + // indicates the encoding used. + + if (_ch == GREATER_THAN) { + // A ">" terminates the string. + _state = State.WHITESPACE; + _wsString = ""; + ((Literal) token).convertHex (); + //buffer.append (PDFDOCENCODING[hexToInt (h1, 0x30)]); + //((StringValuedToken)token).setValue(buffer.toString()); + + return token; + } + else if (!isWhitespace (_ch)) { + ((Literal) token).appendHex (_ch); + } + } + else if (_state == (State.KEYWORD)) { + if (isDelimiter (_ch) || isWhitespace (_ch)) { + if (isDelimiter (_ch)) { + _lookAhead = true; + } + if (buffer.toString ().equals ("stream")) { + // Streams can't be nested, so this is (or better be) + // a FileTokenizer. + _state = State.STREAM; + sawLineBreak = (_ch == LF); + sawCR = (_ch == CR); + token = new Stream (); + length = 0L; + lastch = 0; + prelastch = 0; + initStream ((Stream) token); + } + else { + _state = State.WHITESPACE; + _wsString = "" + (char) _ch; + ((StringValuedToken) token).setValue + (buffer.toString ()); + if (!token.isPdfACompliant()) { + _pdfACompliant = false; + } + return token; + } + } + else { + buffer.append ((char) _ch); + } + } + else if (_state == (State.LESS_THAN)) { + // The last character was "<". If followed + // by another "<", it's the opening token + // for a dictionary. Otherwise it's the + // beginning of a hexadecimal character string. + if (_ch == LESS_THAN || _scanMode) { + _state = State.WHITESPACE; + _wsString = ""; + return new DictionaryStart (); + } + else { + _state = State.HEXADECIMAL; + token = new Literal (); + buffer = new StringBuffer (); + ((Literal) token).appendHex (_ch); + //h1 = _ch; + } + } + else if (_state == (State.LITERAL)) { + backupChar (); + _offset += ((Literal) token).processLiteral (this) - 1; + _state = State.WHITESPACE; + _wsString = ""; + return token; + } + else if (_state == (State.NAME)) { + if (_ch == POUND_SIGN) { + // The pound sign can be used as an escape in + // a name; it is followed by two hex characters. + int ch1 = readChar (); + int ch2 = readChar (); + _ch = (hexValue(ch1) << 8) + + hexValue (ch2); + // Will throw a PDFException if not hex + } + if (isDelimiter (_ch) || isWhitespace (_ch)) { + _state = State.WHITESPACE; + ((StringValuedToken)token).setValue(buffer.toString()); + + if (isDelimiter (_ch)) { + _lookAhead = true; + _wsString = ""; + } + else { + _wsString = "" + (char) _ch; + } + + if (!token.isPdfACompliant()) { + _pdfACompliant = false; + } + return token; + } + else { + buffer.append ((char) _ch); + } + } + else if (_state == (State.NUMERIC)) { + if (_ch == PERIOD) { + _state = State.FRACTIONAL; + realValue = intValue; + //((Numeric) token).setReal (true); + denom = 10; + } + else if (isDelimiter (_ch) || isWhitespace (_ch) || + !isNumeral (_ch)) { + if (negative) { + if (_state == State.FRACTIONAL) { + realValue = -realValue; + } + else { + intValue = -intValue; + } + } + if (_state == State.FRACTIONAL) { + ((Numeric) token).setValue (realValue); + } + else { + ((Numeric) token).setValue (intValue); + } + _state = State.WHITESPACE; + + if (isDelimiter (_ch)) { + _lookAhead = true; + _wsString = ""; + } + else { + _wsString = "" + (char) _ch; + } + + if (!token.isPdfACompliant()) { + _pdfACompliant = false; + } + return token; + } + else { + if (_state == State.FRACTIONAL) { + realValue = realValue * 10 + _ch - 48; + } + else { + intValue = intValue * 10 + _ch - 48; + } + } + } + else if (_state == (State.STREAM)) { + if (_ch == e) { + _state = State.E; + } + else { + prelastch = lastch; + lastch = _ch; + setStreamOffset ((Stream) token); + // Check for a CR/LF or just LF at the start of the stream. + // Since we don't know at this point (not having parsed + // the dictionary) whether the data is external, and since + // the PDF spec says that everything between stream and + // endstream is ignored, we don't know if this requirement + // is enforceable here. But PDF/A forbids external streams, + // so we can at least check compliance there. In any case, + // we subtrace the length of the CR/LF from the purported + // stream length. + if (length == 0 && !sawLineBreak) { + if (_ch == LF) { + sawLineBreak = true; + if (!sawCR) { + _pdfACompliant = false; + } + ((Stream) token).setOffset (((Stream) token).getOffset () + 1); + } + else if (_ch == CR) { + sawCR = true; + ((Stream) token).setOffset (((Stream) token).getOffset () + 1); + } + else { + // Coming here indicates an error if the stream + // isn't external; but we don't know whether + // it is. + _pdfACompliant = false; + } + } + else{ + length++; + } + } + } + else if (_state == (State.E)) { + if (_ch == n) { + _state = State.EN; + } + else { + _state = State.STREAM; + length += 2; + } + } + else if (_state == (State.EN)) { + if (_ch == d) { + _state = State.END; + } + else { + _state = State.STREAM; + length += 3; + } + } + else if (_state == (State.END)) { + if (_ch == s) { + _state = State.ENDS; + } + else { + _state = State.STREAM; + length += 4; + } + } + else if (_state == (State.ENDS)) { + if (_ch == t) { + _state = State.ENDST; + } + else { + _state = State.STREAM; + length += 5; + } + } + else if (_state == (State.ENDST)) { + if (_ch == r) { + _state = State.ENDSTR; + } + else { + _state = State.STREAM; + length += 6; + } + } + else if (_state == (State.ENDSTR)) { + if (_ch == e) { + _state = State.ENDSTRE; + } + else { + _state = State.STREAM; + length += 7; + } + } + else if (_state == (State.ENDSTRE)) { + if (_ch == a) { + _state = State.ENDSTREA; + } + else { + _state = State.STREAM; + length += 8; + } + } + else if (_state == (State.ENDSTREA)) { + if (_ch == m) { + _state = State.ENDSTREAM; + } + else { + _state = State.STREAM; + length += 9; + } + } + else if (_state == (State.ENDSTREAM)) { + if (isDelimiter (_ch) || isWhitespace (_ch)) { + _state = State.WHITESPACE; + + /* The line break, if any, before endstream + * is not counted in the length. */ + if (prelastch == CR && lastch == LF) { + length -= 2; + } + else if (lastch == LF || lastch == CR) { + length -= 1; + } + ((Stream) token).setLength (length); + + if (isDelimiter (_ch)) { + _lookAhead = true; + _wsString = ""; + } + else { + _wsString = "" + (char) _ch; + } + + return token; + } + else { + _state = State.STREAM; + } + } + } + } + catch (EOFException e) { + if (token != null && + token instanceof StringValuedToken && + buffer != null) { + ((StringValuedToken) token).setValue (buffer.toString ()); + } + else { + token = null; + } + } + + return token; + } + + /** + * Return the current offset into the file. + */ + public long getOffset () + { + return _offset; + } + + /** + * Return the set of language codes. Members of the set are Strings. + */ + public Set getLanguageCodes () + { + return _languageCodes; + } + + + /** + * Tell this object that the file is or isn't encrypted. + */ + public void setEncrypted (boolean encrypted) + { + _encrypted = encrypted; + } + + /** + * Returns the value of the pdfACompliant flag, which indicates that + * the tokenizer hasn't detected non-compliance. A value of true + * is no guarantee that the file is compliant. + */ + public boolean getPDFACompliant () + { + return _pdfACompliant; + } + + /** + * Set the value of the pdfACompliant flag. This may be used to + * clear previous detection of noncompliance. + */ + public void setPDFACompliant (boolean pdfACompliant) + { + _pdfACompliant = pdfACompliant; + } + + /** + * Returns the value of the last white space string read by the + * tokenizer. Repositioning clears the white space string. + */ + public String getWSString () + { + return _wsString; + } + + /** + * Set the Tokenizer to a new position in the file. + * + * @param offset The offset in bytes from the start of the file. + */ + public abstract void seek (long offset) + throws IOException, PdfException; + + + /** Reset after a seek. */ + protected void seekReset (long offset) + { + _state = State.WHITESPACE; + _wsString = ""; + _lookAhead = false; + _ch = 0; + /* Don't panic, _offset is used only for reporting purposes */ + _offset = offset - 1; + } + + + /** Get a character from the file or stream, using a buffer */ + public abstract int readChar () throws IOException; + + + /** Read a character in one-byte or 2-byte format, as + requested */ + public int readChar1 (boolean utf16) throws IOException + { + if (utf16) { + int ch1 = readChar (); + int ch2 = readChar (); + return (ch1 << 8) | ch2; + } + else { + return readChar (); + } + } + + /** + * Back up a byte so it will be read again. + */ + public abstract void backupChar (); + + /** + * Add a string to the language codes + */ + public void addLanguageCode (String langCode) + { + _languageCodes.add (langCode); + } + + + + /****************************************************************** + * PRIVATE CLASS METHODS. + ******************************************************************/ + + /*private static int hexToInt (int h1, int h2) throws PdfException + { + return 16*hexValue (h1) + hexValue (h2); + } */ + + private static int hexValue (int h) throws PdfException + { + int d = 0; + if (0x30 <= h && h <= 0x39) { + d = h - 0x30; + } + else if (0x41 <= h && h <= 0x46) { + d = h - 0x37; + } + else if (0x61 <= h && h <= 0x66) { + d = h - 0x57; + } + else { + throw new PdfMalformedException ("Invalid character in hex string", + 0); + } + + return d; + } + + /** Return true if ch is a character which isn't white space + but delimits a token. */ + private static boolean isDelimiter (int ch) + { + boolean delimeter = false; + + for (int i=0; itrue
if the document satisfies the profile. + * X-1a compliance is a superset of the requirements of X-1 compliance + * (i.e., X-1a compliant documents are a subset of X-1 compliant + * documents), so we test for X-1a compliance at the same time. + * The result can subsequently be obtained by calling + * isX1aCompliant. + * + */ + public boolean satisfiesThisProfile () + { + _x1aCompliant = false; // guilty till proven innocent + try { + // First off, there must be an OutputIntents array + // in the document catalog dictionary. + PdfDictionary catDict = _module.getCatalogDict (); + PdfArray intentsArray = (PdfArray) _module.resolveIndirectObject + (catDict.get ("OutputIntents")); + if (intentsArray == null) { + return false; + } + + // Check if PDF-X1/a conformance is asserted + PdfDictionary docInfo = _module.getDocInfo(); + try { + PdfSimpleObject conf = (PdfSimpleObject) docInfo.get ("GTS_PDFXConformance"); + String cn = conf.getStringValue (); + if (cn.startsWith ("PDF/X-1a:")) { + _x1aCompliant = true; + } + } + catch (Exception e) { } + + // Next check if the OutputIntents are valid. + if (!outputIntentsOK (intentsArray)) { + return false; + } + + // Do several resource checks. + if (!resourcesOK ()) { + return false; + } + + // Check the trailer dictionary. + if (!trailerDictOK ()) { + return false; + } + + // Check specific requirements on the doc info dictionary. + if (!infoDictOK ("PDF/X-1")) { + return false; + } + + // Check that an acceptable form of encryption (or none) is used. + if (!encryptionOK ()) { + return false; + } + + // Check that bounding boxes are present as required. + // MediaBox is required. + if (!bboxOK (true)) { + return false; + } + + // If the document contains Actions, it's non-conformant + if (_module.getActionsExist ()) { + return false; + } + + // Now for specific X1-a tests + // Encryption dictionary is not allowed. + if (_module.getEncryptionDict () != null) { + _x1aCompliant = false; + } + + // Check that ViewerPreferences meet certain restrictions + // if any BleedBoxes are present. + if (!checkPrefsAgainstBleedBox ()) { + _x1aCompliant = false; + } + + } + catch (Exception e) { + // Any otherwise uncaught exception means nonconformance + return false; + } + return true; + } + + /** + * Returns the result of X-1a compliance testing which was performed in + * the course of satisfiesThisProfile. If + * satisfiesThisProfile hasn't been called, returns + * false. + */ + public boolean isX1aCompliant () + { + return _x1aCompliant; + } + + + /* Walk through the page tree and check all Resources dictionaries + that we find. Along the way, we check several things: + + Color spaces. Any Separation and DeviceN resources we + find must have an AlternateSpace of DeviceGray or + DeviceCMYK. + + Extended graphic states. + + XObjects. + */ + private boolean resourcesOK () + { + PageTreeNode docTreeRoot = _module.getDocumentTree (); + try { + docTreeRoot.startWalk (); + DocNode docNode; + for (;;) { + docNode = docTreeRoot.nextDocNode (); + if (docNode == null) { + break; + } + // Check for node-level resources + PdfDictionary rsrc = docNode.getResources (); + if (rsrc != null) { + + // Check color spaces. + PdfDictionary cs = (PdfDictionary) + _module.resolveIndirectObject + (rsrc.get ("ColorSpace")); + if (!colorSpaceOK (cs)) { + return false; + } + + // Check extended graphics state. + PdfDictionary gs = (PdfDictionary) + _module.resolveIndirectObject + (rsrc.get ("ExtGState")); + if (!extGStateOK (gs)) { + return false; + } + + // Check XObjects. + PdfDictionary xo = (PdfDictionary) + _module.resolveIndirectObject + (rsrc.get ("XObject")); + if (!xObjectsOK (xo)) { + return false; + } + } + + // Check content streams for resources + if (docNode instanceof PageObject) { + List streams = + ((PageObject) docNode).getContentStreams (); + if (streams != null) { + Iterator iter = streams.listIterator (); + while (iter.hasNext ()) { + PdfStream stream = (PdfStream) iter.next (); + PdfDictionary dict = stream.getDict (); + PdfDictionary rs = + (PdfDictionary) dict.get ("Resources"); + if (rs != null) { + PdfDictionary cs = (PdfDictionary) + _module.resolveIndirectObject + (rs.get ("ColorSpace")); + if (!colorSpaceOK (cs)) { + return false; + } + + PdfDictionary gs = (PdfDictionary) + _module.resolveIndirectObject + (rs.get ("ExtGState")); + if (!extGStateOK (gs)) { + return false; + } + + PdfDictionary xo = (PdfDictionary) + _module.resolveIndirectObject + (rs.get ("XObject")); + if (!xObjectsOK (xo)) { + return false; + } + } + // Also check for filters, for X1-a restrictions. + PdfObject filters = + dict.get ("Filter"); + if (!filter1AOK (filters)) { + _x1aCompliant = false; + } + } + } + + // Also check page objects for annotations -- + // in particular, TrapNet annotations. + PdfArray annots = ((PageObject) docNode).getAnnotations (); + if (annots != null) { + Vector annVec = annots.getContent (); + for (int i = 0; i < annVec.size (); i++) { + PdfDictionary annDict = (PdfDictionary) + annVec.elementAt (i); + PdfSimpleObject subtypeObj = (PdfSimpleObject) annDict.get ("Subtype"); + if ("TrapNet".equals (subtypeObj.getStringValue ())) { + // FontFauxing must be absent or 0-length + PdfArray ff = (PdfArray) annDict.get ("FontFauxing"); + if (ff != null) { + Vector ffVec = ff.getContent (); + if (ffVec.size() > 0) { + return false; // a faux pas + } + } + + // Check Appearance dict for TrapNet annotation + PdfDictionary appDict = (PdfDictionary) + annDict.get ("AP"); + if (appDict != null) { + PdfDictionary normalDict = (PdfDictionary) appDict.get ("N"); + if (normalDict != null) { + PdfSimpleObject pcm = + (PdfSimpleObject) normalDict.get ("PCM"); + if (!"DeviceCMYK".equals (pcm.getStringValue ())) { + return false; + } + } + + } + } + } + } + } + } + } + catch (Exception e) { + return false; + } + return true; // passed all tests + } + + + /* Check if a color space dictionary is conformant */ + private boolean colorSpaceOK (PdfDictionary cs) + { + // If it's null, that's fine. + if (cs == null) { + return true; + } + // Walk through the color space dictionary, + // checking Separation and DeviceN resources + Iterator iter = cs.iterator (); + while (iter.hasNext ()) { + PdfObject res = (PdfObject) iter.next (); + if (res instanceof PdfArray) { + Vector resv = ((PdfArray) res).getContent (); + PdfSimpleObject snameobj = (PdfSimpleObject) resv.elementAt (0); + String sname = snameobj.getStringValue (); + if ("Separation".equals (sname) || "DeviceN".equals (sname)) { + PdfSimpleObject altSpaceObj = (PdfSimpleObject) resv.elementAt (2); + String altSpace = altSpaceObj.getStringValue (); + if (! ("DeviceGray".equals (altSpace) || + "DeviceCMYK".equals (altSpace))) { + return false; + } + } + if ("Indexed".equals (sname) || + "Pattern".equals (sname)) { + // Indexed and pattern color spaces must have a + // base colorspace of DeviceCMYK, DeviceGray, + // DeviceN, or Separation. + PdfSimpleObject baseObj = (PdfSimpleObject) + resv.elementAt (1); + String base = baseObj.getStringValue (); + if (! ("DeviceCMYK".equals (base) || + "DeviceGray".equals (base) || + "DeviceN".equals (base) || + "Separation".equals (base))) { + return false; + } + } + } + } + return true; // passed all tests + } + + + + /* Checks a single XObject. */ + protected boolean xObjectOK (PdfDictionary xo) + { + if (xo == null) { + // no XObject means no problem + return true; + } + // Do common tests + if (!super.xObjectOK (xo)) { + return false; + } + // Tests specific to X/1 + try { + + PdfDictionary opi = (PdfDictionary) xo.get ("OPI"); + if (opi == null) { + // If it isn't an OPI object, we don't care + return true; + } + _x1aCompliant = false; // OPI objects aren't allowed in X-1a + // get the version 2.0 dictionary. If it has only + // a 1.3 dictionary, X1 apparently is indifferent. + PdfDictionary opi20 = (PdfDictionary) + _module.resolveIndirectObject (opi.get ("2.0")); + if (opi20 == null) { + return true; + } + // Now what we came for. The Inks entry is optional, + // but if present, must be full_color, registration, + // or an array containing monochrome as its first value. + // If monochrome, all ink names must be CMYK colorants. + // (Unfortunately, the spec doesn't tell us exactly + // what these names should be: C? Cyan? cyan?) + PdfObject inks = _module.resolveIndirectObject + (opi20.get ("Inks")); + if (inks == null) { + return true; + } + if (inks instanceof PdfSimpleObject) { + String inkname = ((PdfSimpleObject) inks).getStringValue (); + if (!("full_color".equals (inkname) || + "registration".equals (inkname))) { + return false; + } + } + else if (inks instanceof PdfArray) { + Vector inkvec = ((PdfArray) inks).getContent (); + PdfSimpleObject inkobj = (PdfSimpleObject) + inkvec.elementAt (0); + if (!("monochrome".equals (inkobj.getStringValue ()))) { + return false; + } + } + + // Next, the referenced file must be included as + // an EmbeddedFile. A file specification can be either + // a dictionary or a string. I don't understand what's + // being said on page 124. EmbeddedFiles maps name strings + // to embedded file streams; but exactly what are the name + // strings it uses? + + PdfObject fileObj = + _module.resolveIndirectObject (opi20.get ("F")); + NameTreeNode embFiles = _module.getEmbeddedFiles (); + // Leave this for now, till I can make some sense of it. + } + catch (Exception e) { + return false; + } + return true; // passed all tests + } + + + + + private boolean encryptionOK () + { + PdfDictionary encryptDict = _module.getEncryptionDict (); + if (encryptDict == null) { + return true; //no encryption is good encryption + } + try { + PdfSimpleObject filter = (PdfSimpleObject) encryptDict.get ("Filter"); + if (!"Standard".equals (filter.getStringValue ())) { + return false; + } + + // the permissions must include bit 3 (printing). In PDF's + // notation, bit 1 is the low-order bit. + PdfSimpleObject perm = (PdfSimpleObject) encryptDict.get ("P"); + if (perm == null) { + // P is required with standard encryption + return false; + } + if ((perm.getIntValue () & 4) == 0) { + return false; + } + } + catch (Exception e) { + return false; + } + return true; + } + + + /* Check for LZW and JBIG2 filters, which are forbidden in X/1a. + This does not affect X/1 compliance. */ + private boolean filter1AOK (PdfObject filters) + { + return !hasFilters (filters, + new String [] { "LZWDecode", "JBIG2Decode" } ); + } + + /** Checks if a Form xobject is valid. This overrides the method in + XProfileBase. */ + protected boolean formObjectOK (PdfDictionary xo) + { + // PDF-X/1-a elements can't have a Ref key in the + // Form dictionary. + if (xo.get ("Ref") != null) { + // This is an external reference XObject. + _x1aCompliant = false; + } + // Form objects aren't restricted in X/1 + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/X1aProfile.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/X1aProfile.java new file mode 100644 index 00000000..8dd242ae --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/X1aProfile.java @@ -0,0 +1,73 @@ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import edu.harvard.hul.ois.jhove.module.*; + +/** + * PDF profile checker for PDF/X-1a documents. + * See ISO Standard 15930-1, "Complete exchange using + * CMYK data (PDF/X-1 and PDF/X-1a)" + * + * This module depends on the PDF/X-1 profiler, since the PDF/X-1 specification + * is PDF/X-1 plus a few additional restrictions. + */ +public final class X1aProfile extends XProfileBase +{ + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + + private X1Profile _x1Profile; + + /** + * Constructor. + * Creates an X1aProfile object for subsequent testing. + * + * @param module The module under which we are checking the profile. + * + */ + public X1aProfile (PdfModule module) + { + super (module, XProfileBase.PDFX1A); + _profileText = "ISO PDF/X-1a"; + } + + /** + * Calling setX1Profile links this X1aProfiler to an X1Profiler. + * + */ + public void setX1Profile (X1Profile x1) + { + _x1Profile = x1; + } + + /** + * Returns true if the document satisfies the profile. + * If setX1Profile hasn't been called, + * creates a temporary X1Profile and tests against that profile first. + * Either way, X1Profile.isX1aCompliant is then called + * to determine the X-1/a compliance status. + * + */ + public boolean satisfiesThisProfile () + { + if (_x1Profile != null) { + // If there is a linked X1Profile, we save time by checking if + // it passed or not. + if (!_x1Profile.isAlreadyOK ()) { + return false; + } + } + else { + // If there isn't a linked X1Profile, create one + // and check it. + _x1Profile = new X1Profile (_module); + if (!_x1Profile.satisfiesProfile (_raf, _parser)) { + return false; + } + } + + return _x1Profile.isX1aCompliant (); + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/X2Profile.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/X2Profile.java new file mode 100644 index 00000000..57b1d634 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/X2Profile.java @@ -0,0 +1,226 @@ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import edu.harvard.hul.ois.jhove.module.*; +import java.util.*; + +/** + * PDF profile checker for PDF/X-2 documents. + * See ISO Standard ISO 15930-2:2003(E), "Graphic technology - + * Prepress digital data exchange Use of PDF - Part 2: + * Partial exchange of printing data (PDF/X-2)" + */ +public final class X2Profile extends XProfileBase +{ + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + + /** + * Constructor. + * Creates an X2Profile object for subsequent testing. + * + * @param module The module under which we are checking the profile. + * + */ + public X2Profile (PdfModule module) + { + super (module, XProfileBase.PDFX2); + _profileText = "ISO PDF/X-2"; + } + + /** + * Returns true if the document satisfies the profile. + * + */ + public boolean satisfiesThisProfile () + { + try { + // First off, there must be an OutputIntents array + // in the document catalog dictionary. + PdfDictionary catDict = _module.getCatalogDict (); + PdfArray intentsArray = (PdfArray) _module.resolveIndirectObject + (catDict.get ("OutputIntents")); + if (intentsArray == null) { + return false; + } + + // Next check if the OutputIntents are valid. + if (!outputIntentsOK (intentsArray)) { + return false; + } + + // Check that bounding boxes are present as required. + // MediaBox is required. + if (!bboxOK (true)) { + return false; + } + + // Check that ViewerPreferences meet certain restrictions + // if any BleedBoxes are present. + if (!checkPrefsAgainstBleedBox ()) { + return false; + } + + // Check resources and other stuff. + if (!resourcesOK ()) { + return false; + } + + // Check the trailer dictionary. + if (!trailerDictOK ()) { + return false; + } + + + // Check specific requirements on the doc info dictionary. + if (!infoDictOK ("PDF/X-2:")) { + return false; + } + + + } + catch (Exception e) { + // Any otherwise uncaught exception means nonconformance + return false; + } + return true; // Placeholder + } + + /* Walk through the page tree and check all Resources dictionaries + that we find. Along the way, we check several things: + + Color spaces. Any Separation and DeviceN resources we + find must have an AlternateSpace of DeviceGray or + DeviceCMYK. + + Extended graphic states. + + XObjects. + */ + private boolean resourcesOK () + { + PageTreeNode docTreeRoot = _module.getDocumentTree (); + try { + docTreeRoot.startWalk (); + DocNode docNode; + for (;;) { + docNode = docTreeRoot.nextDocNode (); + if (docNode == null) { + break; + } + // Check for node-level resources + PdfDictionary rsrc = docNode.getResources (); + if (rsrc != null) { + + + // Check extended graphics state. + PdfDictionary gs = (PdfDictionary) + _module.resolveIndirectObject + (rsrc.get ("ExtGState")); + if (!extGStateOK (gs)) { + return false; + } + + // Check XObjects. + PdfDictionary xo = (PdfDictionary) + _module.resolveIndirectObject + (rsrc.get ("XObject")); + if (!xObjectsOK (xo)) { + return false; + } + } + + // Check content streams for resources + if (docNode instanceof PageObject) { + List streams = + ((PageObject) docNode).getContentStreams (); + if (streams != null) { + Iterator iter = streams.listIterator (); + while (iter.hasNext ()) { + PdfStream stream = (PdfStream) iter.next (); + PdfDictionary dict = stream.getDict (); + PdfDictionary rs = + (PdfDictionary) dict.get ("Resources"); + if (rs != null) { + PdfDictionary gs = (PdfDictionary) + _module.resolveIndirectObject + (rs.get ("ExtGState")); + if (!extGStateOK (gs)) { + return false; + } + + PdfDictionary xo = (PdfDictionary) + _module.resolveIndirectObject + (rs.get ("XObject")); + if (!xObjectsOK (xo)) { + return false; + } + } + + // Also check for filters, to make sure + // there aren't any forbidden LZW filters. + PdfObject filters = + dict.get ("Filter"); + if (!filterOK (filters, true, true)) { + return false; + } + + } + } + + // Also check page objects for annotations -- + // in particular, TrapNet annotations. + PdfArray annots = ((PageObject) docNode).getAnnotations (); + if (annots != null) { + Vector annVec = annots.getContent (); + for (int i = 0; i < annVec.size (); i++) { + PdfDictionary annDict = (PdfDictionary) + _module.resolveIndirectObject + ((PdfObject) annVec.elementAt (i)); + PdfSimpleObject subtypeObj = (PdfSimpleObject) annDict.get ("Subtype"); + if ("TrapNet".equals (subtypeObj.getStringValue ())) { + // FontFauxing must be absent or 0-length + PdfArray ff = (PdfArray) annDict.get ("FontFauxing"); + if (ff != null) { + Vector ffVec = ff.getContent (); + if (ffVec.size() > 0) { + return false; // a faux pas + } + } + } + } + } + } + } + } + catch (Exception e) { + return false; + } + return true; // passed all tests + } + + /** Checks if a Form xobject is valid. This overrides the method in + XProfileBase. */ + protected boolean formObjectOK (PdfDictionary xo) + { + // PDF-X/2 elements can't have an OPI key in Form + // or Image xobjects. + if (xo.get ("OPI") != null) { + return false; + } + if (xo.get ("Ref") != null) { + // This is an external reference XObject. + // All PDF reference XOjbects must have a Page entry. + if (xo.get ("Page") == null) { + return false; + } + // An X/2 external reference XObject must also have a + // Metadata entry. + if (xo.get ("Metadata") == null) { + return false; + } + } + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/X3Profile.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/X3Profile.java new file mode 100644 index 00000000..68f8c0ba --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/X3Profile.java @@ -0,0 +1,230 @@ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import edu.harvard.hul.ois.jhove.module.*; +import java.util.*; + +/** + * PDF profile checker for PDF/X-3 documents. + * See ISO Standard 15930-3, "Complete exchange suitable + * for colour-managed workflows (PDF/X-3)" + */ +public final class X3Profile extends XProfileBase +{ + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + + //private int _opiObjectCount; + + /** + * Constructor. + * Creates an X3Profile object for subsequent testing. + * + * @param module The module under which we are checking the profile. + * + */ + public X3Profile (PdfModule module) + { + super (module, XProfileBase.PDFX3); + _profileText = "ISO PDF/X-3"; + } + + /** + * Returns true if the document satisfies the profile. + * + */ + public boolean satisfiesThisProfile () + { + try { + // First off, there must be an OutputIntents array + // in the document catalog dictionary. + PdfDictionary catDict = _module.getCatalogDict (); + PdfArray intentsArray = (PdfArray) _module.resolveIndirectObject + (catDict.get ("OutputIntents")); + if (intentsArray == null) { + return false; + } + + // Check specific requirements on the doc info dictionary. + if (!infoDictOK ("PDF/X-3")) { + return false; + } + + // Next check if the OutputIntents are valid. + if (!outputIntentsOK (intentsArray)) { + return false; + } + + // Check resources and other stuff. + if (!resourcesOK ()) { + return false; + } + + // Check the trailer dictionary. + if (!trailerDictOK ()) { + return false; + } + + // Encryption dictionary is not allowed. + if (_module.getEncryptionDict () != null) { + return false; + } + + // Check that bounding boxes are present as required. + // MediaBox is not required. + if (!bboxOK (false)) { + return false; + } + + // If the document contains Actions, it's non-conformant + if (_module.getActionsExist ()) { + return false; + } + } + catch (Exception e) { + // Any otherwise uncaught exception means nonconformance + return false; + } + return true; + } + + + + /* Walk through the page tree and check all Resources dictionaries + that we find. Along the way, we check several things: + + Color spaces. Any Separation and DeviceN resources we + find must have an AlternateSpace of DeviceGray or + DeviceCMYK. + + Extended graphic states. + + XObjects. + */ + private boolean resourcesOK () + { + PageTreeNode docTreeRoot = _module.getDocumentTree (); + try { + docTreeRoot.startWalk (); + DocNode docNode; + for (;;) { + docNode = docTreeRoot.nextDocNode (); + if (docNode == null) { + break; + } + // Check for node-level resources + PdfDictionary rsrc = docNode.getResources (); + if (rsrc != null) { + + + // Check extended graphics state. + PdfDictionary gs = (PdfDictionary) + _module.resolveIndirectObject + (rsrc.get ("ExtGState")); + if (!extGStateOK (gs)) { + return false; + } + + // Check XObjects. + PdfDictionary xo = (PdfDictionary) + _module.resolveIndirectObject + (rsrc.get ("XObject")); + if (!xObjectsOK (xo)) { + return false; + } + } + + // Check content streams for resources + if (docNode instanceof PageObject) { + List streams = + ((PageObject) docNode).getContentStreams (); + if (streams != null) { + Iterator iter = streams.listIterator (); + while (iter.hasNext ()) { + PdfStream stream = (PdfStream) iter.next (); + PdfDictionary dict = stream.getDict (); + PdfDictionary rs = + (PdfDictionary) dict.get ("Resources"); + if (rs != null) { + PdfDictionary gs = (PdfDictionary) + _module.resolveIndirectObject + (rs.get ("ExtGState")); + if (!extGStateOK (gs)) { + return false; + } + + PdfDictionary xo = (PdfDictionary) + _module.resolveIndirectObject + (rs.get ("XObject")); + if (!xObjectsOK (xo)) { + return false; + } + } + + // Also check for filters, to make sure + // there aren't any forbidden LZW filters. + PdfObject filters = + dict.get ("Filter"); + if (!filterOK (filters, true, true)) { + return false; + } + + // External streams are also forbidden. + if (dict.get ("F") != null) { + return false; + } + } + } + + // Also check page objects for annotations -- + // in particular, TrapNet annotations. + PdfArray annots = ((PageObject) docNode).getAnnotations (); + if (annots != null) { + Vector annVec = annots.getContent (); + for (int i = 0; i < annVec.size (); i++) { + PdfDictionary annDict = (PdfDictionary) + _module.resolveIndirectObject + ((PdfObject) annVec.elementAt (i)); + PdfSimpleObject subtypeObj = (PdfSimpleObject) annDict.get ("Subtype"); + if ("TrapNet".equals (subtypeObj.getStringValue ())) { + // FontFauxing must be absent or 0-length + PdfArray ff = (PdfArray) annDict.get ("FontFauxing"); + if (ff != null) { + Vector ffVec = ff.getContent (); + if (ffVec.size() > 0) { + return false; // a faux pas + } + } + } + } + } + } + } + } + catch (Exception e) { + return false; + } + return true; // passed all tests + } + + + protected boolean xObjectOK (PdfDictionary xo) + { + if (xo == null) { + // no XObject means no problem + return true; + } + // Do common tests + if (!super.xObjectOK (xo)) { + return false; + } + // OPI objects aren't permitted + if (xo.get ("OPI") != null) { + return false; + } + return true; + } + + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/XProfileBase.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/XProfileBase.java new file mode 100644 index 00000000..fe1495b9 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/XProfileBase.java @@ -0,0 +1,477 @@ + +package edu.harvard.hul.ois.jhove.module.pdf; + +import edu.harvard.hul.ois.jhove.module.*; +import java.util.*; + +/** + * Abstract base class for PDF profilers of the PDF/X family. + * See ISO Standard 15930-1, "Complete exchange using + * CMYK data (PDF/X-1 and PDF/X-1a)" + */ +public abstract class XProfileBase extends PdfProfile +{ + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + + /** Enumerated values for PDF-X type */ + public static int PDFX1 = 1, + PDFX1A = 2, + PDFX2 = 3, + PDFX3 = 4; + + /** PDF-X type used by the subclass. */ + protected int _xType; + + /** Set to true if a BleedBox is found. */ + protected boolean _bleedBoxPresent; + + /** + * Constructor. + * Creates an X1Profile object for subsequent testing. + * + * @param module The module under which we are checking the profile. + * @param xType The type of PDF/X profile being checked + * + */ + public XProfileBase (PdfModule module, int xType) + { + super (module); + _bleedBoxPresent = false; + _xType = xType; + } + + + /** Checks if the entries which are required in the document information + * dictionary by PDF-X/1 and X/3 are there. + * These entries are optional under the PDF specification, + * so they must be checked here. + */ + protected boolean infoDictOK (String xVersion) + { + PdfDictionary docInfo = _module.getDocInfo(); + try { + PdfSimpleObject trapped = + (PdfSimpleObject) docInfo.get ("Trapped"); + PdfSimpleObject xvers = (PdfSimpleObject) docInfo.get ("GTS_PDFXVersion"); + if (docInfo.get ("CreationDate") == null || + xvers == null || + docInfo.get ("ModDate") == null || + docInfo.get ("Title") == null || + trapped == null) { + return false; + } + + // The value of Trapped must be True or False. + // Unknown (and other random values) is prohibited. + String trappedVal = trapped.getStringValue (); + if (!("True".equals (trappedVal) || + "False".equals (trappedVal))) { + return false; + } + + // The value of GTS_PDFXVersion must begin with the value of xVersion. + String vers = xvers.getStringValue (); + if (!vers.startsWith (xVersion)) { + return false; + } + } + catch (Exception e) { + return false; + } + return true; + } + + /** Returns true if a BleedBox has been detected. */ + protected boolean isBleedBoxPresent () + { + return _bleedBoxPresent; + } + + + /** Checks if the OutputIntents of this document conform + * to profile requirements. + * There must be exactly one entry in the dictionary + * which has a subtype of GTS_PDFX. + */ + protected boolean outputIntentsOK (PdfArray intents) + { + Vector intVec = intents.getContent (); + int matchCount = 0; + try { + for (int i = 0; i < intVec.size (); i++) { + PdfDictionary intent = + (PdfDictionary) _module.resolveIndirectObject + ((PdfObject)intVec.elementAt (i)); + PdfSimpleObject sval = (PdfSimpleObject) + _module.resolveIndirectObject (intent.get ("S")); + if (sval != null) { + String subtype = sval.getStringValue (); + if ("GTS_PDFX".equals (subtype)) { + ++matchCount; // there can be only one + + // It must have an OutputConditionIdentifier + PdfSimpleObject outcond = (PdfSimpleObject) + _module.resolveIndirectObject + (intent.get("OutputConditionIdentifier")); + if (outcond == null) + return false; + + // X1 and 1a only: There must be either a RegistryName entry + // or a DestOutputProfile + // entry. Having them both is OK. + if (_xType == PDFX1) { + PdfSimpleObject regName = (PdfSimpleObject) + _module.resolveIndirectObject + (intent.get ("RegistryName")); + PdfStream dop = (PdfStream) + _module.resolveIndirectObject + (intent.get ("DestOutputProfile")); + if (regName == null && dop == null) { + return false; + } + /* This is WRONG for X-1 and X-1a. Was it + * supposed to go somewhere else?? */ +// if (dop != null) { +// // If present, the DestOutputProfile +// // must have an AtoB1Tag entry. +// PdfObject ab1 = dop.getDict().get ("AtoB1Tag"); +// if (ab1 == null) { +// return false; +// } +// } + } + } + } + } + return (matchCount == 1); + } + catch (Exception e) { + return false; + } + } + + /** Checks profile requirements on the trailer dictionary. + */ + protected boolean trailerDictOK () + { + PdfDictionary trailerDict = _module.getTrailerDict (); + if (trailerDict == null) { + return false; // Something is SERIOUSLY wrong if this happens + } + // ID entry is required + if (trailerDict.get ("ID") == null) { + return false; + } + + return true; + } + + /** Checks if the ExtGState resource meets profile requirements. + * It may not have TR, TR2, or HTP entries. + */ + protected boolean extGStateOK (PdfDictionary gs) + { + if (gs == null) { + // no object means no problem + return true; + } + try { + PdfObject tr = gs.get ("TR"); + PdfObject tr2 = gs.get ("TR2"); + PdfObject htp = gs.get ("HTP"); + if (tr != null || tr2 != null || htp != null) { + return false; + } + + // If there is a halftone dictionary, it must meet + // certain requirements + PdfObject ht = gs.get ("HT"); + if (ht instanceof PdfDictionary) { + // HalftoneType must be 1 or 5 + PdfDictionary htd = (PdfDictionary) ht; + PdfSimpleObject htType = (PdfSimpleObject) htd.get ("HalftoneType"); + int htTypeVal = htType.getIntValue (); + if (htTypeVal != 1 && htTypeVal != 5) { + return false; + } + } + + // The HalftoneName entry ist verboten + if (gs.get ("HalftoneName") != null) { + return false; + } + + // The SMask entry is forbidden in X-1a and X-2 + // unless its value is "None" + if (_xType == PDFX1A || _xType == PDFX2) { + PdfSimpleObject smask = (PdfSimpleObject) gs.get ("SMask"); + if (smask != null) { + if (!"None".equals (smask.getStringValue ())) { + return false; + } + } + + // The values of BM, CA, and ca are restricted if + // these keys are present + PdfSimpleObject blendMode = + (PdfSimpleObject) gs.get ("BM"); + if (blendMode != null) { + String bmVal = blendMode.getStringValue (); + if (!"Normal".equals (bmVal) && + !"Compatible".equals (bmVal)) { + return false; + } + } + PdfSimpleObject ca = (PdfSimpleObject) gs.get ("CA"); + double caVal; + if (ca != null) { + caVal = ca.getDoubleValue (); + if (caVal != 1.0) { + return false; + } + } + ca = (PdfSimpleObject) gs.get ("ca"); + if (ca != null) { + caVal = ca.getDoubleValue (); + if (caVal != 1.0) { + return false; + } + } + } + } + catch (Exception e) { + return false; + } + return true; // passed all tests + } + + + /** + * Checks a single XObject for xObjectsOK. Calls imageObjectOK + * and formObjectOK for profile-specific functionality. + */ + protected boolean xObjectOK (PdfDictionary xo) + { + if (xo == null) { + // no XObject means no problem + return true; + } + try { + // PostScript XObjects aren't allowed. + // Image XObjects must meet certain tests. + PdfSimpleObject subtype = (PdfSimpleObject) xo.get ("Subtype"); + if (subtype != null) { + String subtypeVal = subtype.getStringValue (); + if ("PS".equals (subtypeVal)) { + // PS XObjects aren't allowed in any X format. + return false; + } + if ("Image".equals (subtypeVal)) { + if (!imageObjectOK (xo)) { + return false; + } + } + if ("Form".equals (subtypeVal)) { + if (!formObjectOK (xo)) { + return false; + } + } + } + } + catch (Exception e) { + return false; + } + return true; + } + + + /** Checks if a single image XObject fits the profile */ + protected boolean imageObjectOK (PdfDictionary xo) + { + try { + PdfArray alternates = (PdfArray) xo.get ("Alternates"); + if (alternates == null) { + // No alternates means we're fine + return true; + } + Vector altVec = alternates.getContent (); + for (int i = 0; i < altVec.size (); i++) { + PdfDictionary alt = (PdfDictionary) altVec.elementAt (i); + // No alternate may have DefaultForPrinting = true + PdfSimpleObject dfp = + (PdfSimpleObject) alt.get ("DefaultForPrinting"); + if (dfp.isTrue ()) { + return false; + } + } + if (_xType == PDFX2) { + // PDF-X/2 elements can't have an OPI key in Form + // or Image xobjects. + if (xo.get ("OPI") != null) { + return false; + } + } + if (_xType == PDFX1A || _xType == PDFX2) { + // SMask is restricted in PDFX-1/a and X-2 + PdfSimpleObject smask = (PdfSimpleObject) xo.get ("SMask"); + if (smask != null) { + if (!"None".equals (smask.getStringValue ())) { + return false; + } + } + } + } + catch (Exception e) { + return false; + } + return true; // passed all tests + } + + /** Checks the conformance of a form XObject. + * Does nothing; must be overridden if there are + * conditions on such forms. + */ + protected boolean formObjectOK (PdfDictionary fo) + { + return true; + } + + + + /** Checks all the page objects for bounding boxes. If requireMediaBox + * is true, each page has to include + * or inherit a MediaBox. + * Each page must include one but not both of a TrimBox and + * an ArtBox. + */ + protected boolean bboxOK (boolean requireMediaBox) + { + PageTreeNode pgtree = _module.getDocumentTree (); + try { + pgtree.startWalk (); + PageObject pageObject; + for (;;) { + pageObject = pgtree.nextPageObject (); + if (pageObject == null) { + break; + } + if (requireMediaBox) { + // Check for a MediaBox. If there isn't one here, one + // of its ancestors must have one. + PdfArray mbox = pageObject.getMediaBox (); + if (mbox == null) { + return false; + } + } + + // Check for TrimBox or ArtBox. Apply the Highlander rule. + PdfArray tbox = pageObject.getTrimBox (); + PdfArray abox = pageObject.getArtBox (); + if (tbox == null && abox == null) { + return false; + } + if (tbox != null && abox != null) { + return false; + } + + // BleedBox may be in conflict with other + // features. Record here whether any + // BleedBox is found. + if (pageObject.getBleedBox () != null) { + _bleedBoxPresent = true; + } + } + } + catch (Exception e) { + return false; + } + return true; // passed all tests + } + + + /** + * Checks ViewerPreferences dictionary against MediaBox + * and BleedBox. + * In PDF-X1/a and X2, if a BleedBox is present and + * if the ViewerPreferences dictionary contains the + * ViewClip, PrintArea or PrintClip keys, each of those + * keys present shall have the value MediaBox or BleedBox. + * This must be called after bboxOK has checked if any + * BleedBoxes are found. + */ + protected boolean checkPrefsAgainstBleedBox () + { + if (!_bleedBoxPresent) { + // No bleed box, the test isn't necessary. + return true; + } + PdfDictionary viewPrefDict = _module.getViewPrefDict (); + if (viewPrefDict == null) { + // No viewer prefs, passes the test trivially. + return true; + } + try { + PdfSimpleObject[] areas = new PdfSimpleObject[3]; + areas[0] = (PdfSimpleObject) viewPrefDict.get ("ViewArea"); + areas[1] = (PdfSimpleObject) viewPrefDict.get ("ViewClip"); + areas[2] = (PdfSimpleObject) viewPrefDict.get ("PrintArea"); + + for (int i = 0; i < 3; i++) { + if (areas[i] != null) { + String s = areas[i].getStringValue (); + if (!"MediaBox".equals (s) && + !"BleedBox".equals (s)) { + return false; + } + } + } + } + catch (Exception e) { + return false; + } + return true; + } + /** + * Checks for forbidden filters in a Filters dictionary. + */ + protected boolean filterOK (PdfObject filters, + boolean forbidLZW, + boolean forbidJBIG2) + { + String filterName; + try { + if (filters == null) { + return true; + } + if (filters instanceof PdfSimpleObject) { + // Name of just one filter + filterName = ((PdfSimpleObject) filters).getStringValue (); + if ("LZWDecode".equals (filterName)) { + return false; + } + } + else { + // If it's not a name, it must be an array + Vector filterVec = ((PdfArray) filters).getContent (); + for (int i = 0; i < filterVec.size (); i++) { + PdfSimpleObject filter = + (PdfSimpleObject) filterVec.elementAt (i); + filterName = filter.getStringValue (); + if (forbidLZW && "LZWDecode".equals (filterName)) { + return false; + } + if (forbidJBIG2 && "JBIG2Decode".equals (filterName)) { + return false; + } + } + } + } + catch (Exception e) { + return false; + } + return true; // passed all tests + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/build.xml b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/build.xml new file mode 100644 index 00000000..339b9c41 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/build.xml @@ -0,0 +1,32 @@ + + PDF module build file + Jhove - JSTOR/Harvard Object Validation Environment + Version 1.0 2004-02-03 + Copyright 2004 by JSTOR and the President and Fellows of Harvard College + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/package.html b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/package.html new file mode 100644 index 00000000..4d24f69d --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/pdf/package.html @@ -0,0 +1,24 @@ + + + + + + + +Contains supporting classes for the PDF-HUL module. +
+ + + + + +
+ + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/ExifIFD.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/ExifIFD.java new file mode 100644 index 00000000..e261ca25 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/ExifIFD.java @@ -0,0 +1,1040 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003-2007 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; +import java.io.*; +import java.util.*; + +/** + * Encapsulation of a Exif IFD + */ +public class ExifIFD + extends IFD +{ + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + + + /** ExifVersion tag. */ + private static final int + EXPOSURETIME = 33434, + FNUMBER = 33437, + EXPOSUREPROGRAM = 34850, + SPECTRALSENSITIVITY = 34852, + ISOSPEEDRATINGS = 34855, + OECF = 34856, + EXIFVERSION = 36864, + DATETIMEORIGINAL = 36867, + DATETIMEDIGITIZED = 36868, + COMPONENTSCONFIGURATION = 37121, + COMPRESSEDBITSPERPIXEL = 37122, + SHUTTERSPEEDVALUE = 37377, + APERTUREVALUE = 37378, + BRIGHTNESSVALUE = 37379, + EXPOSUREBIASVALUE = 37380, + MAXAPERTUREVALUE = 37381, + SUBJECTDISTANCE = 37382, + METERINGMODE = 37383, + LIGHTSOURCE = 37384, + FLASH = 37385, + FOCALLENGTH = 37386, + SUBJECTAREA = 37396, + MAKERNOTE = 37500, + USERCOMMENT = 37510, + SUBSECTIME = 37520, + SUBSECTIMEORIGINAL = 37521, + SUBSECTIMEDIGITIZED = 37522, + FLASHPIXVERSION = 40960, + COLORSPACE = 40961, + PIXELXDIMENSION = 40962, + PIXELYDIMENSION = 40963, + RELATEDSOUNDFILE = 40964, + FLASHENERGY = 41483, + SPATIALFREQUENCYRESPONSE = 41484, + FOCALPLANEXRESOLUTION = 41486, + FOCALPLANEYRESOLUTION = 41487, + FOCALPLANERESOLUTIONUNIT = 41488, + SUBJECTLOCATION = 41492, + EXPOSUREINDEX = 41493, + SENSINGMETHOD = 41495, + FILESOURCE = 41728, + SCENETYPE = 41729, + CFAPATTERN = 41730, + CUSTOMRENDERED = 41985, + EXPOSUREMODE = 41986, + WHITEBALANCE = 41987, + DIGITALZOOMRATIO = 41988, + FOCALLENGTHIN35MMFILM = 41989, + SCENECAPTURETYPE = 41990, + GAINCONTROL = 41991, + CONTRAST = 41992, + SATURATION = 41993, + SHARPNESS = 41994, + DEVICESETTINGDESCRIPTION = 41995, + SUBJECTDISTANCERANGE = 41996, + IMAGEUNIQUEID = 42016; + + private static final String [] COLORSPACE_L = { + "sRGB", "uncalibrated" + }; + private static final int [] COLORSPACE_INDEX = { + 1, 65535 + }; + public static final String [] COMPONENTSCONFIGURATION_L = { + "Does not exist", "Y", "Cb", "Cr", "R", "G", "B" + }; + public static final String [] CONTRAST_L = { + "normal", "soft", "hard" + }; + public static final String [] CUSTOMRENDERED_L = { + "normal", "custom" + }; + public static final String [] EXPOSUREMODE_L = { + "auto", "manual", "auto bracket" + }; + public static final String [] EXPOSUREPROGRAM_L = { + "unidentified", "manual", "program normal", "aperture priority", + "shutter priority", "program creative", "program action", + "portrait mode", "landscape mode" + }; + public static final String [] FILESOURCE_L = { + "", "", "", "DSC" + }; + public static final String [] FLASH_L = { + "did not fire", + "fired", + "strobe return light not detected", + "strobe return light detected", + "fired, compulsory flash mode", + "fired, compulsory flash mode, return light not detected", + "fired, compulsory flash mode, return light detected", + "did not fire, compulsory flash mode", + "did not fire, auto mode", + "fired, auto mode", + "fired, auto mode, return light not detected", + "fired, auto mode, return light detected", + "no flash function", + "fired, red-eye reduction mode", + "fired, red-eye reduction mode, return light not detected", + "fired, red-eye reduction mode, return light detected", + "fired, compulsory mode", + "fired, compulsory mode, return light not detected", + "fired, compulsory flash mode, return light detected", + "fired, auto mode, red-eye reduction mode", + "fired, auto mode, red-eye reduction mode, return light not detected", + "fired, auto mode, red-eye reduction mode, return light detected", + }; + public static final int[] FLASH_INDEX = { + 0, 1, 5, 7, 9, 13, 15, 16, 24, 25, 29, 31, 32, 65, 69, 71, 73, 77, + 79, 89, 93, 95 + }; + public static final String [] FOCALPLANERESOLUTIONUNIT_L = { + "", "", "inches", "centimeters" + }; + public static final String [] GAINCONTROL_L = { + "none", "low gain up", "high gain up", "low gain down", + "high gain down" + }; + public static final String [] LIGHTSOURCE_L = { + "unknown", "daylight", "fluorescent", "tungsten", + "flash", "fine weather", "cloudy weather", "shade", + "daylight flourescent (D 5700 - 7100K)", + "day white flourescent (N 4600 - 5400K)", + "cool white flourescent (W 3900 - 4500K)", + "white flourescent (WW 3200 - 3700K)", + "standard light A", "standard light B", "standard light C", + "D55", "D65", "D75", "D50", "ISO studio tungsten", "other" + }; + public static final int [] LIGHTSOURCE_INDEX = { + 0, 1, 2, 3, 4, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 23, + 24, 255 + }; + public static final String [] METERINGMODE_L = { + "unidentified", "average", "centre weighted average", "spot", + "multispot", "pattern", "partial", "other" + }; + public static final int [] METERINGMODE_INDEX = { + 0, 1, 2, 3, 4, 5, 6, 255 + }; + public static final String [] SATURATION_L = { + "normal", "soft", "hard" + }; + public static final String [] SCENECAPTURETYPE_L = { + "standard", "landscape", "portrait", "night" + }; + public static final String [] SCENETYPE_L = { + "", "directly photographed image" + }; + public static final String [] SENSINGMETHOD_L = { + "", "not defined", "one-chip color area", + "two-chip color area", "three-chip color area", + "color sequential area", "", "trilinear", "colour sequential linear" + }; + public static final String [] SHARPNESS_L = { + "normal", "soft", "hard" + }; + public static final String [] SUBJECTDISTANCERANGE_L = { + "unknown", "macro", "close", "distant" + }; + public static final String [] WHITEBALANCE_L = { + "auto", "manual" + }; + + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + /** Aperature value tag. */ + private Rational _apertureValue; + private Rational _brightnessValue; + private int [] _cfaPattern; + private int _colorSpace; + private int [] _componentsConfiguration; + private Rational _compressedBitsPerPixel; + private int _contrast; + private int _customRendered; + private String _dateTimeDigitized; + private String _dateTimeOriginal; + private int [] _deviceSettingDescription; + private Rational _digitalZoomRatio; + private String _exifVersion; + private Rational _exposureBiasValue; + private Rational _exposureIndex; + private int _exposureMode; + private int _exposureProgram; + private Rational _exposureTime; + private int _fileSource; + private int _flash; + private Rational _flashEnergy; + private String _flashpixVersion; + private Rational _fNumber; + private Rational _focalLength; + private int _focalLengthIn35mmFilm; + private Rational _focalPlaneXResolution; + private Rational _focalPlaneYResolution; + private int _focalPlaneResolutionUnit; + private int _gainControl; + private String _imageUniqueID; + private int [] _isoSpeedRatings; + private int _lightSource; + private int [] _makerNote; + private Rational _maxApertureValue; + private int _meteringMode; + private int [] _oecf; + private long _pixelXDimension; + private long _pixelYDimension; + private String _relatedSoundFile; + private int _saturation; + private int _sceneCaptureType; + private int _sceneType; + private int _sensingMethod; + private int _sharpness; + private Rational _shutterSpeedValue; + private int [] _spatialFrequencyResponse; + private String _spectralSensitivity; + private int [] _subjectArea; + private Rational _subjectDistance; + private int _subjectDistanceRange; + private int [] _subjectLocation; + private String _subSecTime; + private String _subSecTimeDigitized; + private String _subSecTimeOriginal; + private int [] _userComment; + private int _whiteBalance; + + /* data from standard TIFF tags */ + private String _manufacturer; + private String _model; + private String _software; + private String _artist; + private int _orientation; + + private NisoImageMetadata _niso; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** Instantiate an ExifIFD object. + * @param offset IFD offset + * @param info the RepInfo object + * @param raf TIFF file + * @param bigEndian True if big-endian file + */ + public ExifIFD (long offset, RepInfo info, RandomAccessFile raf, + boolean bigEndian) + { + super (offset, info, raf, bigEndian); + + _colorSpace = NULL; + _contrast = 0; + _customRendered = NULL; + _exifVersion = "0220"; + _exposureMode = NULL; + _exposureProgram = NULL; + _fileSource = NULL; + _flash = NULL; + _flashpixVersion = "0100"; + _focalLengthIn35mmFilm = NULL; + _focalPlaneResolutionUnit = 2; + _gainControl = NULL; + _lightSource = NULL; + _meteringMode = NULL; + _pixelXDimension = NULL; + _pixelYDimension = NULL; + _saturation = NULL; + _sceneCaptureType = NULL; + _sceneType = NULL; + _sensingMethod = NULL; + _sharpness = NULL; + _subjectDistanceRange = NULL; + _whiteBalance = NULL; + _niso = new NisoImageMetadata (); + } + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + ******************************************************************/ + + /** Get the IFD properties. */ + public Property getProperty (boolean rawOutput) + { + List entries = new LinkedList (); + + entries.add (new Property ("ExifVersion", PropertyType.STRING, + _exifVersion)); + entries.add (new Property ("FlashpixVersion", PropertyType.STRING, + _flashpixVersion)); + if (_colorSpace != NULL) { + entries.add (addIntegerProperty ("ColorSpace", _colorSpace, + COLORSPACE_L, COLORSPACE_INDEX, + rawOutput)); + } + if (_componentsConfiguration != null) { + entries.add (new Property ("ComponentsConfiguration", + PropertyType.INTEGER, + PropertyArity.ARRAY, + _componentsConfiguration)); + } + if (_compressedBitsPerPixel != null) { + entries.add (addRationalProperty ("CompressedBitsPerPixel", + _compressedBitsPerPixel, + rawOutput)); + } + if (_pixelXDimension != NULL) { + entries.add (new Property ("PixelXDimension", PropertyType.LONG, + new Long (_pixelXDimension))); + } + if (_pixelYDimension != NULL) { + entries.add (new Property ("PixelYDimension", PropertyType.LONG, + new Long (_pixelYDimension))); + } + if (_makerNote != null) { + entries.add (new Property ("MakerNote", PropertyType.INTEGER, + PropertyArity.ARRAY, _makerNote)); + } + if (_userComment != null) { + Property ucp = makeUserCommentProperty (_userComment, rawOutput); + if (ucp != null) { + entries.add (ucp); + } + } + if (_relatedSoundFile != null) { + entries.add (new Property ("RelatedSoundFile", PropertyType.STRING, + _relatedSoundFile)); + } + if (_dateTimeOriginal != null) { + entries.add (new Property ("DateTimeOriginal", PropertyType.STRING, + _dateTimeOriginal)); + } + if (_dateTimeDigitized != null) { + entries.add (new Property ("DateTimeDigitized", + PropertyType.STRING, + _dateTimeDigitized)); + } + if (_subSecTime != null) { + entries.add (new Property ("SubSecTime", PropertyType.STRING, + _subSecTime)); + } + if (_subSecTimeOriginal != null) { + entries.add (new Property ("SubSecTimeOriginal", + PropertyType.STRING, + _subSecTimeOriginal)); + } + if (_subSecTimeDigitized != null) { + entries.add (new Property ("SubSecTimeDigitized", + PropertyType.STRING, + _subSecTimeDigitized)); + } + if (_imageUniqueID != null) { + entries.add (new Property ("ImageUniqueID",PropertyType.STRING, + _imageUniqueID)); + } + + if (_exposureTime != null) { + entries.add (addRationalProperty ("ExposureTime", _exposureTime, + rawOutput)); + } + if (_fNumber != null) { + entries.add (addRationalProperty ("FNumber", _fNumber, + rawOutput)); + } + if (_exposureProgram != NULL) { + entries.add (addIntegerProperty ("ExposureProgram", + _exposureProgram, + EXPOSUREPROGRAM_L, rawOutput)); + } + if (_spectralSensitivity != null) { + entries.add (new Property ("SpectralSensitivity", + PropertyType.STRING, + _spectralSensitivity)); + } + if (_isoSpeedRatings != null) { + entries.add (new Property ("ISOSpeedRatings", PropertyType.INTEGER, + PropertyArity.ARRAY, _isoSpeedRatings)); + } + if (_oecf != null) { + entries.add (new Property ("OECF", PropertyType.INTEGER, + PropertyArity.ARRAY, _oecf)); + } + if (_shutterSpeedValue != null) { + entries.add (addRationalProperty ("ShutterSpeedValue", + _shutterSpeedValue, rawOutput)); + } + if (_apertureValue != null) { + entries.add (addRationalProperty ("ApertureValue", _apertureValue, + rawOutput)); + } + if (_brightnessValue != null) { + entries.add (addRationalProperty ("BrightnessValue", + _brightnessValue, rawOutput)); + } + if (_exposureBiasValue != null) { + entries.add (addRationalProperty ("ExposureBiasValue", + _exposureBiasValue, + rawOutput)); + } + if (_maxApertureValue != null) { + entries.add (addRationalProperty ("MaxApertureValue", + _maxApertureValue, rawOutput)); + } + if (_subjectDistance != null) { + entries.add (addRationalProperty ("SubjectDistance", + _subjectDistance, rawOutput)); + } + if (_meteringMode != NULL) { + entries.add (addIntegerProperty ("MeteringMode", _meteringMode, + METERINGMODE_L, + METERINGMODE_INDEX, rawOutput)); + } + if (_lightSource != NULL) { + entries.add (addIntegerProperty ("LightSource", _lightSource, + LIGHTSOURCE_L, + LIGHTSOURCE_INDEX, rawOutput)); + } + if (_flash != NULL) { + entries.add (addIntegerProperty ("Flash", _flash, FLASH_L, + FLASH_INDEX, rawOutput)); + } + if (_focalLength != null) { + entries.add (addRationalProperty ("FocalLength", _focalLength, + rawOutput)); + } + if (_subjectArea != null) { + entries.add (new Property ("SubjectArea", PropertyType.INTEGER, + PropertyArity.ARRAY, _subjectArea)); + } + if (_flashEnergy != null) { + entries.add (addRationalProperty ("FlashEnergy", _flashEnergy, + rawOutput)); + } + if (_spatialFrequencyResponse != null) { + entries.add (new Property ("SubjectArea", PropertyType.INTEGER, + PropertyArity.ARRAY, _subjectArea)); + } + if (_focalPlaneXResolution != null) { + entries.add (addRationalProperty ("FocalPlaneXResolution", + _focalPlaneXResolution, + rawOutput)); + } + if (_focalPlaneYResolution != null) { + entries.add (addRationalProperty ("FocalPlaneYResolution", + _focalPlaneYResolution, + rawOutput)); + } + if (_focalPlaneResolutionUnit != NULL) { + entries.add (addIntegerProperty ("FocalPlaneResolutionUnit", + _focalPlaneResolutionUnit, + FOCALPLANERESOLUTIONUNIT_L, + rawOutput)); + } + if (_subjectLocation != null) { + entries.add (new Property ("SubjectLocation", PropertyType.INTEGER, + PropertyArity.ARRAY, _subjectLocation)); + } + if (_exposureIndex != null) { + entries.add (addRationalProperty ("ExposureIndex", _exposureIndex, + rawOutput)); + } + if (_sensingMethod != NULL) { + entries.add (addIntegerProperty ("SensingMethod", _sensingMethod, + SENSINGMETHOD_L, rawOutput)); + } + if (_fileSource != NULL) { + entries.add (addIntegerProperty ("FileSource", _fileSource, + FILESOURCE_L, rawOutput)); + } + if (_sceneType != NULL) { + entries.add (addIntegerProperty ("SceneType", _sceneType, + SCENETYPE_L, rawOutput)); + } + if (_cfaPattern != null) { + entries.add (new Property ("CFAPattern", PropertyType.INTEGER, + PropertyArity.ARRAY, _cfaPattern)); + } + if (_customRendered != NULL) { + entries.add (addIntegerProperty ("CustomRendered", _customRendered, + CUSTOMRENDERED_L, rawOutput)); + } + if (_exposureMode != NULL) { + entries.add (addIntegerProperty ("ExposureMode", _exposureMode, + EXPOSUREMODE_L, rawOutput)); + } + if (_whiteBalance != NULL) { + entries.add (addIntegerProperty ("WhiteBalance", _whiteBalance, + WHITEBALANCE_L, rawOutput)); + } + if (_digitalZoomRatio != null) { + entries.add (addRationalProperty ("DigitalZoomRatio", + _digitalZoomRatio, rawOutput)); + } + if (_focalLengthIn35mmFilm != NULL) { + entries.add (new Property ("FocalLengthIn35mmFilm", + PropertyType.INTEGER, + new Integer (_focalLengthIn35mmFilm))); + } + if (_sceneCaptureType != NULL) { + entries.add (addIntegerProperty ("SceneCaptureType", + _sceneCaptureType, + SCENECAPTURETYPE_L, rawOutput)); + } + if (_gainControl != NULL) { + entries.add (addIntegerProperty ("GainControl", _gainControl, + GAINCONTROL_L, rawOutput)); + } + if (_saturation != NULL) { + entries.add (addIntegerProperty ("Saturation", _saturation, + SATURATION_L, rawOutput)); + } + if (_sharpness != NULL) { + entries.add (addIntegerProperty ("Sharpness", _sharpness, + SHARPNESS_L, rawOutput)); + } + if (_deviceSettingDescription != null) { + entries.add (new Property ("DeviceSettingDescription", + PropertyType.INTEGER, + PropertyArity.ARRAY, + _deviceSettingDescription)); + } + if (_subjectDistanceRange != NULL) { + entries.add (addIntegerProperty ("SubjectDistanceRange", + _subjectDistanceRange, + SUBJECTDISTANCERANGE_L, + rawOutput)); + } + + // properties from standard TIFF tags + if (_manufacturer != null) { + entries.add (new Property ("Make", + PropertyType.STRING, + _manufacturer)); + } + if (_model != null) { + entries.add (new Property ("Model", + PropertyType.STRING, + _model)); + } + if (_software != null) { + entries.add (new Property ("Software", + PropertyType.STRING, + _software)); + } + if (_artist != null) { + entries.add (new Property ("Artist", + PropertyType.STRING, + _artist)); + } + return propertyHeader ("Exif", entries); + } + + + /** Returns the Exif version string (tag 36864). */ + public String getExifVersion () + { + return _exifVersion; + } + + /** Returns the constructed NisoImageMetadata. */ + public NisoImageMetadata getNisoImageMetadata () + { + return _niso; + } + + + /** Returns the Flashpix version string (tag 40960). */ + public String getFlashpixVersion () + { + return _flashpixVersion; + } + + /** returns the colorspace value (tag 40961). */ + public int getColorspace () + { + return _colorSpace; + } + + + + /** Extracts and returns the Exif property list from a standard + * IFD property header. + */ + public List exifProps (Property pHeader) + { + try { + Property[] pArr = (Property []) pHeader.getValue (); + Property entries = pArr[2]; + return (List) entries.getValue (); + } + catch (Exception e) { + // We could get caught here if we somehow tried to get + // the Exif properties from something that wasn't a + // standard property header. + return null; + } + } + + + /** Lookup an IFD tag. */ + public void lookupTag (int tag, int type, long count, long value) + throws TiffException + { + try { + if (tag == APERTUREVALUE) { + checkType (tag, type, RATIONAL); + checkCount (tag, count, 1); + _apertureValue = readRational (count, value); + } + else if (tag == BRIGHTNESSVALUE) { + checkType (tag, type, SRATIONAL); + checkCount (tag, count, 1); + _brightnessValue = readRational (count, value); + } + else if (tag == CFAPATTERN) { + checkType (tag, type, UNDEFINED); + _cfaPattern = readByteArray (type, count, value); + } + else if (tag == COLORSPACE) { + checkType (tag, type, SHORT); + checkCount (tag, count, 1); + _colorSpace = readShort (type, count, value); + } + else if (tag == COLORSPACE) { + checkType (tag, type, SHORT); + checkCount (tag, count, 1); + _colorSpace = readShort (type, count, value); + } + else if (tag == COMPONENTSCONFIGURATION) { + checkType (tag, type, UNDEFINED); + _componentsConfiguration = readByteArray (type, count, value); + } + else if (tag == COMPRESSEDBITSPERPIXEL) { + checkType (tag, type, RATIONAL); + checkCount (tag, count, 1); + _compressedBitsPerPixel = readRational (count, value); + } + else if (tag == CONTRAST) { + checkType (tag, type, SHORT); + checkCount (tag, count, 1); + _contrast = readShort (type, count, value); + } + else if (tag == CUSTOMRENDERED) { + checkType (tag, type, SHORT); + checkCount (tag, count, 1); + _customRendered = readShort (type, count, value); + } + else if (tag == DATETIMEDIGITIZED) { + checkType (tag, type, ASCII); + checkCount (tag, count, 20); + _dateTimeDigitized = readASCII (count, value); + } + else if (tag == DATETIMEORIGINAL) { + checkType (tag, type, ASCII); + checkCount (tag, count, 20); + _dateTimeOriginal = readASCII (count, value); + } + else if (tag == DEVICESETTINGDESCRIPTION) { + checkType (tag, type, UNDEFINED); + _deviceSettingDescription = readByteArray (type, count, value); + } + else if (tag == DIGITALZOOMRATIO) { + checkType (tag, type, RATIONAL); + checkCount (tag, count, 1); + _digitalZoomRatio = readRational (count, value); + } + else if (tag == EXIFVERSION) { + checkType (tag, type, UNDEFINED); + checkCount (tag, count, 4); + int [] iarray = readShortArray (type, count, value); + char [] carray = new char [iarray.length]; + for (int i=0; iGPSInfoIFD object. + * @param offset IFD offset + * @param info The RepInfo object + * @param raf TIFF file + * @param bigEndian True if big-endian file + */ + public GPSInfoIFD (long offset, RepInfo info, RandomAccessFile raf, + boolean bigEndian) + { + super (offset, info, raf, bigEndian); + + _gpsAltitudeRef = NULL; + _gpsDifferential = NULL; + + /* Set Exif defaults. */ + _gpsVersionID = new int [] {2, 2, 0, 0}; + _gpsAltitudeRef = 0; + _gpsSpeedRef = "K"; + _gpsTrackRef = "T"; + _gpsImgDirectionRef = "T"; + _gpsDestBearingRef = "T"; + _gpsDestDistanceRef = "K"; + } + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + ******************************************************************/ + + /** Get the GPSAltitude (6). */ + public Rational getGPSAltitude () + { + return _gpsAltitude; + } + + /** Get the GPSAltitudeRef (5). */ + public int getGPSAltitudeRef () + { + return _gpsAltitudeRef; + } + + /** Get the GPSDateStamp (29). */ + public String getGPSDateStamp () + { + return _gpsDateStamp; + } + + /** Get the GPSDestBearing (24). */ + public Rational getGPSDestBearing () + { + return _gpsDestBearing; + } + + /** Get the GPSDestBearingRef (23). */ + public String getGPSDestBearingRef () + { + return _gpsDestBearingRef; + } + + /** Get the GPSDestDistance (26). */ + public Rational getGPSDestDistance () + { + return _gpsDestDistance; + } + + /** Get the GPSDestDistanceRef (25). */ + public String getGPSDestDistanceRef () + { + return _gpsDestDistanceRef; + } + + /** Get the GPSDestLatitude (20). */ + public Rational [] getGPSDestLatitude () + { + return _gpsDestLatitude; + } + + /** Get the GPSDestLatitudeRef (19). */ + public String getGPSDestLatitudeRef () + { + return _gpsDestLatitudeRef; + } + + /** Get the GPSDestLongitude (22). */ + public Rational [] getGPSDestLongitude () + { + return _gpsDestLongitude; + } + + /** Get the GPSDestLongitudeRef (21). */ + public String getGPSDestLongitudeRef () + { + return _gpsDestLongitudeRef; + } + + /** Get the GPSDifferential (30). */ + public int getGPSDifferential () + { + return _gpsDifferential; + } + + /** Get the GPSDOP (11). */ + public Rational getGPSDOP () + { + return _gpsDOP; + } + + /** Get the GPSImgDirection (17). */ + public Rational getGPSImgDirection () + { + return _gpsImgDirection; + } + + /** Get the GPSImgDirectionRef (16). */ + public String getGPSImgDirectionRef () + { + return _gpsImgDirectionRef; + } + + /** Get the GPSLatitude (2). */ + public Rational [] getGPSLatitude () + { + return _gpsLatitude; + } + + /** Get the GPSLatitudeRef (1). */ + public String getGPSLatitudeRef () + { + return _gpsLatitudeRef; + } + + /** Get the GPSLongitude (4). */ + public Rational [] getGPSLongitude () + { + return _gpsLongitude; + } + + /** Get the GPSLongitudeRef (3). */ + public String getGPSLongitudeRef () + { + return _gpsLongitudeRef; + } + + /** Get the GPSMapDatum (18). */ + public String getGPSMapDatum () + { + return _gpsMapDatum; + } + + /** Get the GPSMeasureMode (10). */ + public String getGPSMeasureMode () + { + return _gpsMeasureMode; + } + + /** Get the GPSProcessingMethod (27). */ + public int [] getGPSProcessingMethod () + { + return _gpsProcessingMethod; + } + + /** Get the GPSSatellites (8). */ + public String getGPSSatellites () + { + return _gpsSatellites; + } + + /** Get the GPSSpeed (13). */ + public Rational getGPSSpeed () + { + return _gpsSpeed; + } + + /** Get the GPSSpeedRef (12). */ + public String getGPSSpeedRef () + { + return _gpsSpeedRef; + } + + /** Get the GPSStatus (9). */ + public String getGPStatus () + { + return _gpsStatus; + } + + /** Get the GPSTimeStamp (7). */ + public Rational [] getGPTimeStamp () + { + return _gpsTimeStamp; + } + + /** Get the GPSTrack (15). */ + public Rational getGPSTrack () + { + return _gpsTrack; + } + + /** Get the GPSTrackRef (14). */ + public String getGPSTrackRef () + { + return _gpsTrackRef; + } + + /** Get the GPSVersionID (1). */ + public int [] getGPSVersionID () + { + return _gpsVersionID; + } + + /** Get the IFD properties. */ + public Property getProperty (boolean rawOutput) + { + List entries = new LinkedList (); + entries.add (new Property ("GPSVersionID", PropertyType.STRING, + Integer.toString (_gpsVersionID[0]) + "." + + Integer.toString (_gpsVersionID[1]) + "." + + Integer.toString (_gpsVersionID[2]) + "." + + Integer.toString (_gpsVersionID[3]))); + if (_gpsLatitudeRef != null) { + entries.add (new Property ("GPSLatitudeRef", PropertyType.STRING, + _gpsLatitudeRef)); + } + if (_gpsLatitude != null) { + entries.add (new Property ("GPSLatitude", PropertyType.RATIONAL, + PropertyArity.ARRAY, _gpsLatitude)); + } + if (_gpsLongitudeRef != null) { + entries.add (new Property ("GPSLongitudeRef", PropertyType.STRING, + _gpsLongitudeRef)); + } + if (_gpsLongitude != null) { + entries.add (new Property ("GPSLongitude", PropertyType.RATIONAL, + PropertyArity.ARRAY, _gpsLongitude)); + } + entries.add (new Property ("GPSAltitudeRef", PropertyType.INTEGER, + new Integer (_gpsAltitudeRef))); + if (_gpsAltitude != null) { + entries.add (new Property ("GPSAltitude", PropertyType.RATIONAL, + _gpsAltitude)); + } + if (_gpsTimeStamp != null) { + entries.add (new Property ("GPSTimeStamp", PropertyType.RATIONAL, + PropertyArity.ARRAY, _gpsTimeStamp)); + } + if (_gpsSatellites != null) { + entries.add (new Property ("GPSSatellites", PropertyType.STRING, + _gpsSatellites)); + } + if (_gpsStatus != null) { + entries.add (new Property ("GPSStatus", PropertyType.STRING, + _gpsStatus)); + } + if (_gpsMeasureMode != null) { + entries.add (new Property ("GPSMeasureMode", PropertyType.STRING, + _gpsMeasureMode)); + } + if (_gpsDOP != null) { + entries.add (new Property ("GPSDOP", PropertyType.RATIONAL, + _gpsDOP)); + } + entries.add (new Property ("GPSSpeedRef", PropertyType.STRING, + _gpsSpeedRef)); + if (_gpsSpeed != null) { + entries.add (new Property ("GPSSpeed", PropertyType.RATIONAL, + _gpsSpeed)); + } + entries.add (new Property ("GPSTrackRef", PropertyType.STRING, + _gpsTrackRef)); + if (_gpsTrack != null) { + entries.add (new Property ("GPSTrack", PropertyType.RATIONAL, + _gpsTrack)); + } + entries.add (new Property ("GPSImgDirectionRef", PropertyType.STRING, + _gpsImgDirectionRef)); + if (_gpsImgDirection != null) { + entries.add (new Property ("GPSImgDirection", + PropertyType.RATIONAL, + _gpsImgDirection)); + } + if (_gpsMapDatum != null) { + entries.add (new Property ("GPSMapDatum", PropertyType.STRING, + _gpsMapDatum)); + } + if (_gpsDestLatitudeRef != null) { + entries.add (new Property ("GPSDestLatitudeRef", + PropertyType.STRING, + _gpsDestLatitudeRef)); + } + if (_gpsDestLatitude != null) { + entries.add (new Property ("GPSDestLatitude", + PropertyType.RATIONAL, + PropertyArity.ARRAY, + _gpsDestLatitude)); + } + if (_gpsDestLongitudeRef != null) { + entries.add (new Property ("GPSDestLongitudeRef", + PropertyType.STRING, + _gpsDestLongitudeRef)); + } + if (_gpsDestLongitude != null) { + entries.add (new Property ("GPSDestLongitude", + PropertyType.RATIONAL, + PropertyArity.ARRAY, + _gpsDestLongitude)); + } + entries.add (new Property ("GPSDestBearingRef", PropertyType.STRING, + _gpsDestBearingRef)); + if (_gpsDestBearing != null) { + entries.add (new Property ("GPSDestBearing", + PropertyType.RATIONAL, + _gpsDestBearing)); + } + entries.add (new Property ("GPSDestDistanceRef", PropertyType.STRING, + _gpsDestDistanceRef)); + if (_gpsDestDistance != null) { + entries.add (new Property ("GPSDestDistance", + PropertyType.RATIONAL, + _gpsDestDistance)); + } + if (_gpsDestDistanceRef != null) { + entries.add (new Property ("GPSDestDistanceRef", + PropertyType.STRING, + _gpsDestDistanceRef)); + } + if (_gpsProcessingMethod != null) { + entries.add (new Property ("GPSProcessingMethod", + PropertyType.INTEGER, + PropertyArity.ARRAY, + _gpsProcessingMethod)); + } + if (_gpsAreaInformation != null) { + entries.add (new Property ("GPSAreaInformation", + PropertyType.INTEGER, + PropertyArity.ARRAY, + _gpsAreaInformation)); + } + if (_gpsDateStamp != null) { + entries.add (new Property ("GPSDateStamp", PropertyType.STRING, + _gpsDateStamp)); + } + entries.add (new Property ("GPSDifferential", PropertyType.INTEGER, + new Integer (_gpsDifferential))); + + return propertyHeader ("GPSInfo", entries); + } + + /** Lookup an IFD tag. */ + public void lookupTag (int tag, int type, long count, long value) + throws TiffException + { + try { + if (tag == GPSALTITUDE) { + checkType (tag, type, RATIONAL); + checkCount (tag, count, 1); + _gpsAltitude = readRational (count, value); + } + else if (tag == GPSALTITUDEREF) { + checkType (tag, type, BYTE); + checkCount (tag, count, 1); + _gpsAltitudeRef = readByte (type, count, value); + } + else if (tag == GPSDATESTAMP) { + checkType (tag, type, ASCII); + checkCount (tag, count, 11); + _gpsDateStamp = readASCII (count, value); + } + else if (tag == GPSDESTBEARING) { + checkType (tag, type, RATIONAL); + checkCount (tag, count, 1); + _gpsDestBearing = readRational (count, value); + } + else if (tag == GPSDESTBEARINGREF) { + checkType (tag, type, ASCII); + checkCount (tag, count, 2); + _gpsDestBearingRef = readASCII (count, value); + } + else if (tag == GPSDESTDISTANCE) { + checkType (tag, type, RATIONAL); + checkCount (tag, count, 1); + _gpsDestDistance = readRational (count, value); + } + else if (tag == GPSDESTDISTANCEREF) { + checkType (tag, type, ASCII); + checkCount (tag, count, 2); + _gpsDestDistanceRef = readASCII (count, value); + } + else if (tag == GPSDESTLATITUDE) { + checkType (tag, type, RATIONAL); + checkCount (tag, count, 3); + _gpsDestLatitude = readRationalArray (count, value); + } + else if (tag == GPSDESTLATITUDEREF) { + checkType (tag, type, ASCII); + checkCount (tag, count, 2); + _gpsDestLatitudeRef = readASCII (count, value); + } + else if (tag == GPSDESTLONGITUDE) { + checkType (tag, type, RATIONAL); + checkCount (tag, count, 3); + _gpsDestLongitude = readRationalArray (count, value); + } + else if (tag == GPSDESTLONGITUDEREF) { + checkType (tag, type, ASCII); + checkCount (tag, count, 2); + _gpsDestLongitudeRef = readASCII (count, value); + } + else if (tag == GPSDIFFERENTIAL) { + checkType (tag, type, SHORT); + checkCount (tag, count, 1); + _gpsDifferential = readShort (type, count, value); + } + else if (tag == GPSDOP) { + checkType (tag, type, RATIONAL); + checkCount (tag, count, 1); + _gpsDOP = readRational (count, value); + } + else if (tag == GPSIMGDIRECTION) { + checkType (tag, type, RATIONAL); + checkCount (tag, count, 1); + _gpsImgDirection = readRational (count, value); + } + else if (tag == GPSIMGDIRECTIONREF) { + checkType (tag, type, ASCII); + checkCount (tag, count, 2); + _gpsImgDirectionRef = readASCII (count, value); + } + else if (tag == GPSLATITUDE) { + checkType (tag, type, RATIONAL); + checkCount (tag, count, 3); + _gpsLatitude = readRationalArray (count, value); + } + else if (tag == GPSLATITUDEREF) { + checkType (tag, type, ASCII); + checkCount (tag, count, 2); + _gpsLatitudeRef = readASCII (count, value); + } + else if (tag == GPSLONGITUDE) { + checkType (tag, type, RATIONAL); + checkCount (tag, count, 3); + _gpsLongitude = readRationalArray (count, value); + } + else if (tag == GPSLONGITUDEREF) { + checkType (tag, type, ASCII); + checkCount (tag, count, 2); + _gpsLongitudeRef = readASCII (count, value); + } + else if (tag == GPSMAPDATUM) { + checkType (tag, type, ASCII); + _gpsMapDatum = readASCII (count, value); + } + else if (tag == GPSMEASUREMODE) { + checkType (tag, type, ASCII); + checkCount (tag, count, 2); + _gpsMeasureMode = readASCII (count, value); + } + else if (tag == GPSPROCESSINGMETHOD) { + checkType (tag, type, UNDEFINED); + _gpsProcessingMethod = readByteArray (type, count, value); + } + else if (tag == GPSSATELLITES) { + checkType (tag, type, ASCII); + _gpsSatellites = readASCII (count, value); + } + else if (tag == GPSSPEED) { + checkType (tag, type, RATIONAL); + checkCount (tag, count, 1); + _gpsSpeed = readRational (count, value); + } + else if (tag == GPSSPEEDREF) { + checkType (tag, type, ASCII); + checkCount (tag, count, 2); + _gpsSpeedRef = readASCII (count, value); + } + else if (tag == GPSSTATUS) { + checkType (tag, type, ASCII); + checkCount (tag, count, 2); + _gpsStatus = readASCII (count, value); + } + else if (tag == GPSTIMESTAMP) { + checkType (tag, type, RATIONAL); + checkCount (tag, count, 3); + _gpsTimeStamp = readRationalArray (count, value); + } + else if (tag == GPSTRACK) { + checkType (tag, type, RATIONAL); + checkCount (tag, count, 1); + _gpsTrack = readRational (count, value); + } + else if (tag == GPSTRACKREF) { + checkType (tag, type, ASCII); + checkCount (tag, count, 2); + _gpsTrackRef = readASCII (count, value); + } + else if (tag == GPSVERSIONID) { + checkType (tag, type, BYTE); + checkCount (tag, count, 4); + _gpsVersionID = readByteArray (type, count, value); + } + else { + _info.setMessage (new ErrorMessage ("Unknown GPSInfo IFD tag", + "Tag = " + tag, value)); + } + } + catch (IOException e) { + throw new TiffException ("Read error for tag" + tag, value); + } + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/GeoTiffStrings.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/GeoTiffStrings.java new file mode 100644 index 00000000..50820ec8 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/GeoTiffStrings.java @@ -0,0 +1,2077 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +/** + * This class contains indexed string arrays for the + * various keys that are used by GeoTIFF. It contains + * no code and never needs to be instantiated. + */ +public class GeoTiffStrings +{ + /* GeoTIFF general codes + * Derived from European Petroleum Survey Group (ESPG) Geodesy parameters, + * v2.1, June 2, 1995 + * Copied 2003-08-18 from + */ + /* 6.3.1.1 Model type codes */ + + public final static int[] MODELTYPE_INDEX ={ + 0, 1, 2, 3, 32767 + }; + public final static String[] MODELTYPE = { + "Undefined", + "Projection coordinate system", + "Geographic latitude-longitude system (FGDC geographic)", + "Geocentric (X,Y,Z) coordinate system (FGDC planar-projected)", + "User-defined" + }; + + /* 6.3.1.2 Raster type codes */ + + public final static int[] RASTERTYPE_INDEX = { + 0, 1, 2, 32767 + }; + public final static String[] RASTERTYPE = { + "Undefined", + "Pixel is area", + "Pixel is point", + "User-defined" + }; + + /* 6.3.1.3 Linear unit codes */ + + public final static int[] LINEARUNITS_INDEX = { + 0, 9001, 9002, 9003, 9004, 9005, 9006, 9007, 9008, 9009, 9010, 9011, + 9012, 9013, 9014, 9015, 32767 + }; + public final static String[] LINEARUNITS = { + "Undefined", + "Meter", + "Foot", + "Foot (US survey)", + "Foot (modified American)", + "Foot (Clarke)", + "Foot (Indian)", + "Link", + "Link (Benoit)", + "Link (Sears)", + "Chain (Benoit)", + "Chain (Sears)", + "Yard (Sears)", + "Yard (Indian)", + "Fathom", + "Mile (International nautical)", + "User-defined" + }; + + /* 6.3.1.4 Angular unit codes */ + + public final static int[] ANGULARUNITS_INDEX = { + 0, 9101, 9102, 9103, 9104, 9105, 9106, 9107, 9108, 32767 + }; + public final static String[] ANGULARUNITS = { + "Undefined", + "Radian", + "Degree", + "Arc minute", + "Second", + "Grad", + "Gon", + "DMS", + "DMS hemisphere", + "User-defined" + }; + + /* 6.3.2.1 Geographic CS type codes */ + + public final static int[] GEOGRAPHICS_INDEX = { + 0, 4001, 4002, 4003, 4004, 4005, 4006, 4007, 4008, 4009, 4010, 4011, + 4012, 4013, 4014, 4015, 4016, 4017, 4018, 4019, 4020, 4021, 4022, + 4023, 4024, 4025, 4026, 4027, 4028, 4029, 4030, 4031, 4032, 4033, + 4034, 4035, 4201, 4202, 4203, 4204, 4205, 4206, 4207, 4208, 4209, + 4210, 4211, 4212, 4213, 4214, 4215, 4216, 4217, 4218, 4219, 4220, + 4221, 4222, 4223, 4224, 4225, 4226, 4227, 4228, 4229, 4230, 4231, + 4232, 4233, 4234, 4235, 4236, 4237, 4238, 4239, 4240, 4241, 4242, + 4243, 4244, 4245, 4246, 4247, 4248, 4249, 4250, 4251, 4252, 4253, + 4254, 4255, 4256, 4257, 4258, 4259, 4260, 4261, 4262, 4263, 4264, + 4265, 4266, 4267, 4268, 4269, 4270, 4271, 4272, 4273, 4274, 4275, + 4276, 4277, 4278, 4279, 4280, 4281, 4282, 4283, 4284, 4285, 4286, + 4287, 4288, 4289, 4290, 4291, 4292, 4293, 4294, 4295, 4296, 4297, + 4298, 4299, 4300, 4301, 4302, 4303, 4304, 4305, 4306, 4307, 4308, + 4309, 4310, 4311, 4312, 4313, 4314, 4315, 4801, 4802, 4803, 4804, + 4805, 4806, 4807, 4808, 4809, 4810, 4811, 4812, 4813, 4901, 4902, + 32767 + }; + public final static String[] GEOGRAPHICS = { + "Undefined", + "Ellipsoid Airy 1830", + "Ellipsoid Airy Modified 1849", + "Ellipsoid Australian National Spheroid", + "Ellipsoid Bessel 1841", + "Ellipsoid Bessel modified", + "Ellipsoid Bessel Namibia", + "Ellipsoid Clarke 1858", + "Ellipsoid Clarke 1866", + "Ellipsoid Clarke 1866 Michigan", + "Ellipsoid Clarke 1880 Benoit", + "Ellipsoid Clarke 1880 IGN", + "Ellipsoid Clarke 1880 RGS", + "Ellipsoid Clarke 1880 arc", + "Ellipsoid Clarke 1880 SGA 1922", + "Ellipsoid Everest 1830-1937 adjustment", + "Ellipsoid Everest 1830-1967 definition", + "Ellipsoid Everest 1830-1975 definition", + "Ellipsoid Everest 1830 modified", + "Ellipsoid GRS 1980", + "Ellipsoid Helmert 1906", + "Ellipsoid Indonesian national spheroid", + "Ellipsoid international 1924", + "Ellipsoid international 1967", + "Ellipsoid Krassowsky 1940", + "Ellipsoid NWL9D", + "Ellipsoid NWL10D", + "Ellipsoid Plessis 1817", + "Ellipsoid Struve 1860", + "Ellipsoid war office", + "Ellipsoid WGS84", + "Ellipsoid GEM10C", + "Ellipsoid OSU86F", + "Ellipsoid OSU91A", + "Ellipsoid Clarke 1880", + "Ellipsoid sphere", + "Adindan", + "AGD66", + "AGD84", + "Ain el Abd", + "Afgooye", + "Agadez", + "Lisbon", + "Aratu", + "Arc 1950", + "Arc 1960", + "Batavia", + "Barbados", + "Beduaram", + "Beijing 1954", + "Belge 1950", + "Bermuda 1957", + "Bern 1898", + "Bogota", + "Bukit Rimpah", + "Camacupa", + "Campo Inchauspe", + "Cape", + "Carthage", + "Chua", + "Corrego Alegre", + "Cote d Ivoire", + "Deir ez Zor", + "Douala", + "Egypt 1907", + "ED50", + "ED87", + "Fahud", + "Gandajika 1970", + "Garoua", + "Guyane Francaise", + "Hu Tzu Shan", + "HD72", + "ID74", + "Indian 1954", + "Indian 1975", + "Jamaica 1875", + "JAD69", + "Kalianpur", + "Kandawala", + "Kertau", + "KOC", + "La Canoa", + "PSAD56", + "Lake", + "Leigon", + "Liberia 1964", + "Lome", + "Luzon 1911", + "Hito XVIII 1963", + "Herat north", + "Mahe 1971", + "Makassar", + "EUREF89", + "Malongo 1987", + "Manoca", + "Merchich", + "Massawa", + "Minna", + "Mhast", + "Monte Mario", + "M poraloko", + "NAD27", + "NAD Michigan", + "NAD83", + "Nahrwan 1967", + "Naparima 1972", + "GD49", + "NGO 1948", + "Datum 73", + "NTF", + "NSWC 9Z 2", + "OSGB 1936", + "OSGB70", + "OS SN80", + "Padang", + "Palestine 1923", + "Pointe Noire", + "GDA94", + "Pulkovo 1942", + "Qatar", + "Qatar 1948", + "Qornoq", + "Loma Quintana", + "Amersfoort", + "RT38", + "SAD69", + "Sapper Hill 1943", + "Schwarzeck", + "Segora", + "Serindung", + "Sudan", + "Tananarive", + "Timbalai 1948", + "TM65", + "TM75", + "Tokyo", + "Trinidad 1903", + "TC 1948", + "Voirol 1875", + "Voirol Unifie", + "Bern 1938", + "Nord Sahara 1959", + "Stockholm 1938", + "Yacare", + "Yoff", + "Zanderij", + "MGI", + "Belge 1972", + "DHDN", + "Conakry 1905", + "WGS 72", + "WGS 72BE", + "WGS 84", + "Bern 1898 Bern", + "Bogota Bogota", + "Lisbon Lisbon", + "Makassar Jakarta", + "MGI Ferro", + "Monte Mario Rome", + "NTF Paris", + "Padang Jakarta", + "Belge 1950 Brussels", + "Tananarive Paris", + "Voirol 1875 Paris", + "Voirol Unifie Paris", + "Batavia Jakarta", + "ATF Paris", + "NDG Paris", + "User-defined" + }; + + /* 6.3.2.2 Geodetic datum codes */ + + public final static int[] GEODETICDATUM_INDEX = { + 6001, 6002, 6003, 6004, 6005, 6006, 6007, 6008, 6009, 6010, 6011, + 6012, 6013, 6014, 6015, 6016, 6017, 6018, 6019, 6020, 6021, 6022, + 6023, 6024, 6025, 6026, 6027, 6028, 6029, 6030, 6031, 6032, 6033, + 6034, 6035, 6201, 6202, 6203, 6204, 6205, 6206, 6207, 6208, 6209, + 6210, 6211, 6212, 6213, 6214, 6215, 6216, 6217, 6218, 6219, 6220, + 6221, 6222, 6223, 6224, 6225, 6226, 6227, 6228, 6229, 6230, 6231, + 6232, 6233, 6234, 6235, 6236, 6237, 6238, 6239, 6240, 6241, 6242, + 6243, 6244, 6245, 6246, 6247, 6248, 6249, 6250, 6251, 6252, 6253, + 6254, 6255, 6256, 6257, 6258, 6259, 6260, 6261, 6262, 6263, 6264, + 6265, 6266, 6267, 6268, 6269, 6270, 6271, 6272, 6273, 6274, 6275, + 6276, 6277, 6278, 6279, 6280, 6281, 6282, 6283, 6284, 6285, 6286, + 6287, 6288, 6289, 6290, 6291, 6292, 6293, 6294, 6295, 6296, 6297, + 6298, 6299, 6300, 6301, 6302, 6303, 6304, 6305, 6306, 6307, 6308, + 6309, 6310, 6311, 6312, 6313, 6314, 6315, 6322, 6324, 6326, 6901, + 6902, 32767 + }; + public final static String[] GEODETICDATUM = { + "Undefined", + "Ellipsoid Airy 1830", + "Ellipsoid Airy modified 1849", + "Ellipsoid Australian national spheroid", + "Ellipsoid Bessel 1841", + "Ellipsoid Bessel modified", + "Ellipsoid Bessel Namibia", + "Ellipsoid Clarke 1858", + "Ellipsoid Clarke 1866", + "Ellipsoid Clarke 1866 Michigan", + "Ellipsoid Clarke 1880 Benoit", + "Ellipsoid Clarke 1880 IGN", + "Ellipsoid Clarke 1880 RGS", + "Ellipsoid Clarke 1880 arc", + "Ellipsoid Clarke 1880 SGA1922", + "Ellipsoid Everest 1830-1937 adjustment", + "Ellipsoid Everest 1830-1967 definition", + "Ellipsoid Everest 1830-1975 definition", + "Ellipsoid Everest 1830 modified", + "Ellipsoid GRS1980", + "Ellipsoid Helmert1906", + "Ellipsoid Indonesian national spheroid", + "Ellipsoid international 1924", + "Ellipsoid international 1967", + "Ellipsoid Krassowsky 1960", + "Ellipsoid NWL9D", + "Ellipsoid NWL10D", + "Ellipsoid Plessis 1817", + "Ellipsoid Struve 1860", + "Ellipsoid war office", + "Ellipsoid WGS84", + "Ellipsoid GEM10C", + "Ellipsoid OSU86F", + "Ellipsoid OSU91A", + "Ellipsoid Clarke 1880", + "Ellipsoid sphere", + "Adindan", + "Australian geodetic 1966", + "Australian geodetic 1984", + "Ain el Abd 1970", + "Afgooye", + "Agadez", + "Lisbon", + "Aratu", + "Arc 1950", + "Arc 1960", + "Batavia", + "Barbados", + "Beduaram", + "Beijing 1954", + "Reseau National Belge 1950", + "Bermuda 1957", + "Bern 1898", + "Bogota", + "Bukit Rimpah", + "Camacupa", + "Campo Inchauspe", + "Cape", + "Carthage", + "Chua", + "Corrego Alegre", + "Cote d'Ivoire", + "Deir ez Zor", + "Douala", + "Egypt 1907", + "European 1950", + "European 1987", + "Fahud", + "Gandajika 1970", + "Garoua", + "Guyane Francaise", + "Hu Tzu Shan", + "Hungarian 1972", + "Indonesian 1974", + "Indian 1954", + "Indian 1975", + "Jamaica 1875", + "Jamaica 1969", + "Kalianpur", + "Kandawala", + "Kertau", + "Kuwait Oil Company", + "La Canoa", + "Provisional S American 1956", + "Lake", + "Leigon", + "Liberia 1964", + "Lome", + "Luzon 1911", + "Hito XVIII 1963", + "Herat north", + "Mahe 1971", + "Makassar", + "European reference system 1989", + "Malongo 1987", + "Manoca", + "Merchich", + "Massawa", + "Minna", + "Mhast", + "Monte Mario", + "M poraloko", + "North American 1927", + "NAD Michigan", + "north American 1983", + "Nahrwan 1967", + "Naparima 1972", + "New Zealand geodetic 1949", + "NGO 1948", + "73", + "Nouvelle triangulation Francaise", + "NSWC 9Z 2", + "OSGB 1936", + "OSGB 1970 SN", + "OS SN 1980", + "Padang 1884", + "Palestine 1923", + "Pointe Noire", + "Geocentric of Australia 1994", + "Pulkovo 1942", + "Qatar", + "Qatar 1948", + "Qornoq", + "Loma Quintana", + "Amersfoort", + "RT38", + "south American 1969", + "Sapper Hill 1943", + "Schwarzeck", + "Segora", + "Serindung", + "Sudan", + "Tananarive 1925", + "Timbalai 1948", + "TM65", + "TM75", + "Tokyo", + "Trinidad 1903", + "Trucial Coast 1948", + "Voirol 1875", + "Voirol Unifie 1960", + "Bern 1938", + "Nord Sahara 1959", + "Stockholm 1938", + "Yacare", + "Yoff", + "Zanderij", + "Militar Geographische Institut", + "Reseau National Belge 1972", + "Deutsche Hauptdreiecksnetz", + "Conakry 1905", + "WGS72", + "WGS72 transit broadcast ephemeris", + "WGS84", + "Ancienne triangulation Francaise", + "Nord de Guerre", + "User-defined" + }; + + /* 6.3.2.3 Ellipsoid codes */ + + public final static int[] ELLIPSOID_INDEX = { + 0,7001, 7002, 7003, 7004, 7005, 7006, 7007, 7008, 7009, 7010, 7011, + 7012, 7013, 7014, 7015, 7016, 7017, 7018, 7019, 7020, 7021, 7022, + 7023, 7024, 7025, 7026, 7027, 7028, 7029, 7030, 7031, 7032, 7033, + 7034, 7035, 32767 + }; + public final static String[] ELLIPSOID = { + "Undefined", + "Airy 1830", + "Airy modified 1849", + "Australian national spheroid", + "Bessel 1841", + "Bessel modified", + "Bessel Namibia", + "Clarke 1858", + "Clarke 1866", + "Clarke 1866 Michigan", + "Clarke 1880 Benoit", + "Clarke 1880 IGN", + "Clarke 1880 RGS", + "Clarke 1880 arc", + "Clarke 1880 SGA 1922", + "Everest 1830-1937 adjustment", + "Everest 1830-1967 definition", + "Everest 1830-1975 definition", + "Everest 1830 modified", + "GRS 1980", + "Helmert 1906", + "Indonesian national spheroid", + "International 1924", + "International 1967", + "Krassowsky 1940", + "NWL 9D", + "NWL 10D", + "Plessis 1817", + "Struve 1860", + "War Office", + "WGS 84", + "GEM 10C", + "OSU86F", + "OSU91A", + "Clarke 1880", + "Sphere", + "User-defined" + }; + + /* 6.3.2.4 Prime meridian codes */ + + public final static int[] PRIMEMERIDIAN_INDEX = { + 0, 8901, 8902, 8903, 8904, 8905, 8906, 8907, 8908, 8909, 8910, 8911, + 32767 + }; + public final static String[] PRIMEMERIDIAN = { + "Undefined", "Greenwich", "Lisbon", "Paris", "Bogota", "Madrid", + "Rome", "Bern", "Jakarta", "Ferro", "Brussels", "Stockholm", + "User-defined" + }; + + /* 6.3.3.1 Projected CS type codes */ + + public final static int[] PROJECTEDCSTYPE_INDEX = { + 0, 20137, 20138, 20248, 20249, 20250, 20251, 20252, 20253, + 20254, 20255, 20256, 20257, 20258, 20348, 20349, 20350, 20351, + 20352, 20353, 20354, 20355, 20356, 20357, 20358, 20437, + 20438, 20439, 20499, 20538, 20539, 20700, 20822, 20823, + 20824, 20973, 20975, 20977, 20979, 20981, 20983, 20985, + 20987, 20989, 20991, 20993, 20995, 21100, 21148, 21149, + 21150, 21413, 21414, 21415, 21416, 21417, 21418, 21419, + 21420, 21421, 21422, 21423, 21473, 21474, 21475, 21476, + 21477, 21478, 21479, 21480, 21481, 21482, 21483, 21500, + 21790, 21817, 21818, 21891, 21892, 21893, 21894, 22032, + 22033, 22191, 22192, 22193, 22194, 22195, 22196, 22197, + 22332, 22391, 22392, 22523, 22524, 22832, 22992, 22993, + 22994, 23028, 23029, 23030, 23031, 23032, 23033, 23034, + 23035, 23036, 23037, 23038, 23239, 23240, 23433, 23846, + 23847, 23848, 23849, 23850, 23851, 23852, 23853, 23886, + 23887, 23888, 23889, 23890, 23891, 23892, 23893, 23894, + 23947, 23948, 24047, 24048, 24100, 24200, 24370, 24371, + 24372, 24373, 24374, 24382, 24383, 24384, 24500, 24547, + 24548, 24720, 24721, 24818, 24819, 24820, 24821, 24877, + 24878, 24879, 24880, 24891, 24892, 24893, 25000, 25231, + 25391, 25392, 25393, 25394, 25395, 25700, 25932, 26191, + 26192, 26193, 26237, 26331, 26332, 26391, 26392, 26393, + 26432, 26591, 26592, 26632, 26692, 26703, 26704, 26705, + 26706, 26707, 26708, 26709, 26710, 26711, 26712, 26713, + 26714, 26715, 26716, 26717, 26718, 26719, 26720, 26721, + 26722, 26729, 26730, 26731, 26732, 26733, 26734, 26735, + 26736, 26737, 26738, 26739, 26740, 26741, 26742, 26743, + 26744, 26745, 26746, 26747, 26748, 26749, 26750, 26751, + 26752, 26753, 26754, 26755, 26756, 26757, 26758, 26759, + 26760, 26761, 26762, 26763, 26764, 26765, 26766, 26767, + 26768, 26769, 26770, 26771, 26772, 26773, 26774, 26774, + 26775, 26775, 26776, 26776, 26777, 26777, 26778, 26779, + 26780, 26781, 26782, 26783, 26784, 26785, 26786, 26787, + 26788, 26789, 26790, 26791, 26792, 26793, 26794, 26795, + 26796, 26797, 26798, 26801, 26802, 26803, 26903, 26904, + 26905, 26906, 26907, 26908, 26909, 26910, 26911, 26912, + 26913, 26914, 26915, 26916, 26917, 26918, 26919, 26920, + 26921, 26922, 26923, 26929, 26930, 26931, 26932, 26933, + 26934, 26935, 26936, 26937, 26938, 26939, 26940, 26941, + 26942, 26943, 26944, 26945, 26946, 26948, 26949, 26950, + 26951, 26952, 26953, 26954, 26955, 26956, 26957, 26958, + 26959, 26960, 26961, 26962, 26963, 26964, 26965, 26966, + 26967, 26968, 26969, 26970, 26971, 26972, 26973, 26974, + 26975, 26976, 26977, 26978, 26979, 26980, 26981, 26982, + 26983, 26984, 26985, 26986, 26987, 26988, 26989, 26990, + 26991, 26992, 26993, 26994, 26995, 26996, 26997, 26998, + 27038, 27039, 27040, 27120, 27200, 27291, 27292, 27429, + 27500, 27581, 27582, 27583, 27591, 27592, 27593, 27700, + 28232, 28348, 28349, 28350, 28351, 28352, 28353, 28354, + 28355, 28356, 28357, 28358, 28404, 28405, 28406, 28407, + 28408, 28409, 28410, 28411, 28412, 28413, 28414, 28415, + 28416, 28417, 28418, 28419, 28420, 28421, 28422, 28423, + 28424, 28425, 28426, 28427, 28428, 28429, 28430, 28431, + 28432, 28464, 28465, 28466, 28467, 28468, 28469, 28470, + 28471, 28472, 28473, 28474, 28475, 28476, 28477, 28478, + 28479, 28480, 28481, 28482, 28483, 28484, 28485, 28486, + 28487, 28488, 28489, 28490, 28491, 28492, 28600, 28991, + 28992, 29118, 29119, 29120, 29121, 29122, 29177, 29178, + 29179, 29180, 29181, 29182, 29183, 29184, 29185, 29220, + 29221, 29333, 29635, 29636, 29700, 29738, 29739, 29800, + 29849, 29850, 29900, 30200, 30339, 30340, 30491, 30492, + 30591, 30592, 30600, 30729, 30730, 30731, 30732, 31028, + 31121, 31291, 31292, 31293, 31300, 31491, 31492, 31493, + 31494, 31495, 32001, 32002, 32003, 32005, 32006, 32007, + 32008, 32009, 32010, 32011, 32012, 32013, 32014, 32015, + 32016, 32017, 32018, 32019, 32020, 32021, 32022, 32023, + 32024, 32025, 32026, 32027, 32028, 32029, 32030, 32031, + 32033, 32034, 32035, 32036, 32037, 32038, 32039, 32040, + 32041, 32042, 32043, 32044, 32045, 32046, 32047, 32048, + 32049, 32050, 32051, 32052, 32053, 32054, 32055, 32056, + 32057, 32058, 32059, 32060, 32100, 32104, 32107, 32108, + 32109, 32110, 32111, 32112, 32113, 32114, 32115, 32116, + 32117, 32118, 32119, 32120, 32121, 32122, 32123, 32124, + 32125, 32126, 32127, 32128, 32129, 32130, 32133, 32134, + 32135, 32136, 32137, 32138, 32139, 32140, 32141, 32142, + 32143, 32144, 32145, 32146, 32147, 32148, 32149, 32150, + 32151, 32152, 32153, 32154, 32155, 32156, 32157, 32158, + 32161, 32201, 32202, 32203, 32204, 32205, 32206, 32207, + 32208, 32209, 32210, 32211, 32212, 32213, 32214, 32215, + 32216, 32217, 32218, 32219, 32220, 32221, 32222, 32223, + 32224, 32225, 32226, 32227, 32228, 32229, 32230, 32231, + 32232, 32233, 32234, 32235, 32236, 32237, 32238, 32239, + 32240, 32241, 32242, 32243, 32244, 32245, 32246, 32247, + 32248, 32249, 32250, 32251, 32252, 32253, 32254, 32255, + 32256, 32257, 32258, 32259, 32260, 32301, 32302, 32303, + 32304, 32305, 32306, 32307, 32308, 32309, 32310, 32311, + 32312, 32313, 32314, 32315, 32316, 32317, 32318, 32319, + 32320, 32321, 32322, 32323, 32324, 32325, 32326, 32327, + 32328, 32329, 32330, 32331, 32332, 32333, 32334, 32335, + 32336, 32337, 32338, 32339, 32340, 32341, 32342, 32343, + 32344, 32345, 32346, 32347, 32348, 32349, 32350, 32351, + 32352, 32353, 32354, 32355, 32356, 32357, 32358, 32359, + 32360, 32401, 32402, 32403, 32404, 32405, 32406, 32407, + 32408, 32409, 32410, 32411, 32412, 32413, 32414, 32415, + 32416, 32417, 32418, 32419, 32420, 32421, 32422, 32423, + 32424, 32425, 32426, 32427, 32428, 32429, 32430, 32431, + 32432, 32433, 32434, 32435, 32436, 32437, 32438, 32439, + 32440, 32441, 32442, 32443, 32444, 32445, 32446, 32447, + 32448, 32449, 32450, 32451, 32452, 32453, 32454, 32455, + 32456, 32457, 32458, 32459, 32460, 32501, 32502, 32503, + 32504, 32505, 32506, 32507, 32508, 32509, 32510, 32511, + 32512, 32513, 32514, 32515, 32516, 32517, 32518, 32519, + 32520, 32521, 32522, 32523, 32524, 32525, 32526, 32527, + 32528, 32529, 32530, 32531, 32532, 32533, 32534, 32535, + 32536, 32537, 32538, 32539, 32540, 32541, 32542, 32543, + 32544, 32545, 32546, 32547, 32548, 32549, 32550, 32551, + 32552, 32553, 32554, 32555, 32556, 32557, 32558, 32559, + 32560, 32601, 32602, 32603, 32604, 32605, 32606, 32607, + 32608, 32609, 32610, 32611, 32612, 32613, 32614, 32615, + 32616, 32617, 32618, 32619, 32620, 32621, 32622, 32623, + 32624, 32625, 32626, 32627, 32628, 32629, 32630, 32631, + 32632, 32633, 32634, 32635, 32636, 32637, 32638, 32639, + 32640, 32641, 32642, 32643, 32644, 32645, 32646, 32647, + 32648, 32649, 32650, 32651, 32652, 32653, 32654, 32655, + 32656, 32657, 32658, 32659, 32660, 32701, 32702, 32703, + 32704, 32705, 32706, 32707, 32708, 32709, 32710, 32711, + 32712, 32713, 32714, 32715, 32716, 32717, 32718, 32719, + 32720, 32721, 32722, 32723, 32724, 32725, 32726, 32727, + 32728, 32729, 32730, 32731, 32732, 32733, 32734, 32735, + 32736, 32737, 32738, 32739, 32740, 32741, 32742, 32743, + 32744, 32745, 32746, 32747, 32748, 32749, 32750, 32751, + 32752, 32753, 32754, 32755, 32756, 32757, 32758, 32759, + 32760, 32767 + }; + public final static String[] PROJECTEDCSTYPE = { + "Undefined", + "Adindan UTM zone 37N", + "Adindan UTM zone 38N", + "AGD66 AMG zone 48", + "AGD66 AMG zone 49", + "AGD66 AMG zone 50", + "AGD66 AMG zone 51", + "AGD66 AMG zone 52", + "AGD66 AMG zone 53", + "AGD66 AMG zone 54", + "AGD66 AMG zone 55", + "AGD66 AMG zone 56", + "AGD66 AMG zone 57", + "AGD66 AMG zone 58", + "AGD84 AMG zone 48", + "AGD84 AMG zone 49", + "AGD84 AMG zone 50", + "AGD84 AMG zone 51", + "AGD84 AMG zone 52", + "AGD84 AMG zone 53", + "AGD84 AMG zone 54", + "AGD84 AMG zone 55", + "AGD84 AMG zone 56", + "AGD84 AMG zone 57", + "AGD84 AMG zone 58", + "Ain el Abd UTM zone 37N", + "Ain el Abd UTM zone 38N", + "Ain el Abd UTM zone 39N", + "Ain el Abd Bahrain Grid", + "Afgooye UTM zone 38N", + "Afgooye UTM zone 39N", + "Lisbon Portugese Grid", + "Aratu UTM zone 22S", + "Aratu UTM zone 23S", + "Aratu UTM zone 24S", + "Arc 1950 Lo13", + "Arc 1950 Lo15", + "Arc 1950 Lo17", + "Arc 1950 Lo19", + "Arc 1950 Lo21", + "Arc 1950 Lo23", + "Arc 1950 Lo25", + "Arc 1950 Lo27", + "Arc 1950 Lo29", + "Arc 1950 Lo31", + "Arc 1950 Lo33", + "Arc 1950 Lo35", + "Batavia NEIEZ", + "Batavia UTM zone 48S", + "Batavia UTM zone 49S", + "Batavia UTM zone 50S", + "Beijing Gauss zone 13", + "Beijing Gauss zone 14", + "Beijing Gauss zone 15", + "Beijing Gauss zone 16", + "Beijing Gauss zone 17", + "Beijing Gauss zone 18", + "Beijing Gauss zone 19", + "Beijing Gauss zone 20", + "Beijing Gauss zone 21", + "Beijing Gauss zone 22", + "Beijing Gauss zone 23", + "Beijing Gauss 13N", + "Beijing Gauss 14N", + "Beijing Gauss 15N", + "Beijing Gauss 16N", + "Beijing Gauss 17N", + "Beijing Gauss 18N", + "Beijing Gauss 19N", + "Beijing Gauss 20N", + "Beijing Gauss 21N", + "Beijing Gauss 22N", + "Beijing Gauss 23N", + "Belge Lambert 50", + "Bern 1898 Swiss Old", + "Bogota UTM zone 17N", + "Bogota UTM zone 18N", + "Bogota Colombia 3W", + "Bogota Colombia Bogota", + "Bogota Colombia 3E", + "Bogota Colombia 6E", + "Camacupa UTM 32S", + "Camacupa UTM 33S", + "C Inchauspe Argentina 1", + "C Inchauspe Argentina 2", + "C Inchauspe Argentina 3", + "C Inchauspe Argentina 4", + "C Inchauspe Argentina 5", + "C Inchauspe Argentina 6", + "C Inchauspe Argentina 7", + "Carthage UTM zone 32N", + "Carthage Nord Tunisie", + "Carthage Sud Tunisie", + "Corrego Alegre UTM 23S", + "Corrego Alegre UTM 24S", + "Douala UTM zone 32N", + "Egypt 1907 red belt", + "Egypt 1907 purple belt", + "Egypt 1907 ext purple", + "ED50 UTM zone 28N", + "ED50 UTM zone 29N", + "ED50 UTM zone 30N", + "ED50 UTM zone 31N", + "ED50 UTM zone 32N", + "ED50 UTM zone 33N", + "ED50 UTM zone 34N", + "ED50 UTM zone 35N", + "ED50 UTM zone 36N", + "ED50 UTM zone 37N", + "ED50 UTM zone 38N", + "Fahud UTM zone 39N", + "Fahud UTM zone 40N", + "Garoua UTM zone 33N", + "ID74 UTM zone 46N", + "ID74 UTM zone 47N", + "ID74 UTM zone 48N", + "ID74 UTM zone 49N", + "ID74 UTM zone 50N", + "ID74 UTM zone 51N", + "ID74 UTM zone 52N", + "ID74 UTM zone 53N", + "ID74 UTM zone 46S", + "ID74 UTM zone 47S", + "ID74 UTM zone 48S", + "ID74 UTM zone 49S", + "ID74 UTM zone 50S", + "ID74 UTM zone 51S", + "ID74 UTM zone 52S", + "ID74 UTM zone 53S", + "ID74 UTM zone 54S", + "Indian 1954 UTM 47N", + "Indian 1954 UTM 48N", + "Indian 1975 UTM 47N", + "Indian 1975 UTM 48N", + "Jamaica 1875 old grid", + "JAD69 Jamaica grid", + "Kalianpur India 0", + "Kalianpur India I", + "Kalianpur India IIa", + "Kalianpur India IIIa", + "Kalianpur India IVa", + "Kalianpur India IIb", + "Kalianpur India IIIb", + "Kalianpur India IVb", + "Kertau Singapore grid", + "Kertau UTM zone 47N", + "Kertau UTM zone 48N", + "La Canoa UTM zone 20N", + "La Canoa UTM zone 21N", + "PSAD56 UTM zone 18N", + "PSAD56 UTM zone 19N", + "PSAD56 UTM zone 20N", + "PSAD56 UTM zone 21N", + "PSAD56 UTM zone 17S", + "PSAD56 UTM zone 18S", + "PSAD56 UTM zone 19S", + "PSAD56 UTM zone 20S", + "PSAD56 Peru west zone", + "PSAD56 Peru central", + "PSAD56 Peru east zone", + "Leigon Ghana grid", + "Lome UTM zone 31N", + "Luzon Philippines I", + "Luzon Philippines II", + "Luzon Philippines III", + "Luzon Philippines IV", + "Luzon Philippines V", + "Makassar NEIEZ", + "Malongo 1987 UTM 32S", + "Merchich Nord Maroc", + "Merchich Sud Maroc", + "Merchich Sahara", + "Massawa UTM zone 37N", + "Minna UTM zone 31N", + "Minna UTM zone 32N", + "Minna Nigeria west", + "Minna Nigeria mid belt", + "Minna Nigeria east", + "Mhast UTM zone 32S", + "Monte Mario Italy 1", + "Monte Mario Italy 2", + "M poraloko UTM 32N", + "M poraloko UTM 32S", + "NAD27 UTM zone 3N", + "NAD27 UTM zone 4N", + "NAD27 UTM zone 5N", + "NAD27 UTM zone 6N", + "NAD27 UTM zone 7N", + "NAD27 UTM zone 8N", + "NAD27 UTM zone 9N", + "NAD27 UTM zone 10N", + "NAD27 UTM zone 11N", + "NAD27 UTM zone 12N", + "NAD27 UTM zone 13N", + "NAD27 UTM zone 14N", + "NAD27 UTM zone 15N", + "NAD27 UTM zone 16N", + "NAD27 UTM zone 17N", + "NAD27 UTM zone 18N", + "NAD27 UTM zone 19N", + "NAD27 UTM zone 20N", + "NAD27 UTM zone 21N", + "NAD27 UTM zone 22N", + "NAD27 Alabama east", + "NAD27 Alabama west", + "NAD27 Alaska zone 1", + "NAD27 Alaska zone 2", + "NAD27 Alaska zone 3", + "NAD27 Alaska zone 4", + "NAD27 Alaska zone 5", + "NAD27 Alaska zone 6", + "NAD27 Alaska zone 7", + "NAD27 Alaska zone 8", + "NAD27 Alaska zone 9", + "NAD27 Alaska zone 10", + "NAD27 California I", + "NAD27 California II", + "NAD27 California III", + "NAD27 California IV", + "NAD27 California V", + "NAD27 California VI", + "NAD27 California VII", + "NAD27 Arizona east", + "NAD27 Arizona central", + "NAD27 Arizona west", + "NAD27 Arkansas north", + "NAD27 Arkansas south", + "NAD27 Colorado north", + "NAD27 Colorado central", + "NAD27 Colorado south", + "NAD27 Connecticut", + "NAD27 Delaware", + "NAD27 Florida east", + "NAD27 Florida west", + "NAD27 Florida north", + "NAD27 Hawaii zone 1", + "NAD27 Hawaii zone 2", + "NAD27 Hawaii zone 3", + "NAD27 Hawaii zone 4", + "NAD27 Hawaii zone 5", + "NAD27 Georgia east", + "NAD27 Georgia west", + "NAD27 Idaho east", + "NAD27 Idaho central", + "NAD27 Idaho west", + "NAD27 Illinois east", + "NAD27 Illinois west", + "NAD27 Indiana east", + "NAD27 BLM 14N feet", + "NAD27 Indiana west", + "NAD27 BLM 15N feet", + "NAD27 Iowa north", + "NAD27 BLM 16N feet", + "NAD27 Iowa south", + "NAD27 BLM 17N feet", + "NAD27 Kansas north", + "NAD27 Kansas south", + "NAD27 Kentucky north", + "NAD27 Kentucky south", + "NAD27 Louisiana north", + "NAD27 Louisiana south", + "NAD27 Maine east", + "NAD27 Maine west", + "NAD27 Maryland", + "NAD27 Massachusetts", + "NAD27 Massachusetts Is", + "NAD27 Michigan north", + "NAD27 Michigan central", + "NAD27 Michigan south", + "NAD27 Minnesota north", + "NAD27 Minnesota Cent", + "NAD27 Minnesota south", + "NAD27 Mississippi east", + "NAD27 Mississippi west", + "NAD27 Missouri east", + "NAD27 Missouri central", + "NAD27 Missouri west", + "NAD Michigan Michigan east", + "NAD Michigan Michigan old central", + "NAD Michigan Michigan west", + "NAD83 UTM zone 3N", + "NAD83 UTM zone 4N", + "NAD83 UTM zone 5N", + "NAD83 UTM zone 6N", + "NAD83 UTM zone 7N", + "NAD83 UTM zone 8N", + "NAD83 UTM zone 9N", + "NAD83 UTM zone 10N", + "NAD83 UTM zone 11N", + "NAD83 UTM zone 12N", + "NAD83 UTM zone 13N", + "NAD83 UTM zone 14N", + "NAD83 UTM zone 15N", + "NAD83 UTM zone 16N", + "NAD83 UTM zone 17N", + "NAD83 UTM zone 18N", + "NAD83 UTM zone 19N", + "NAD83 UTM zone 20N", + "NAD83 UTM zone 21N", + "NAD83 UTM zone 22N", + "NAD83 UTM zone 23N", + "NAD83 Alabama east", + "NAD83 Alabama west", + "NAD83 Alaska zone 1", + "NAD83 Alaska zone 2", + "NAD83 Alaska zone 3", + "NAD83 Alaska zone 4", + "NAD83 Alaska zone 5", + "NAD83 Alaska zone 6", + "NAD83 Alaska zone 7", + "NAD83 Alaska zone 8", + "NAD83 Alaska zone 9", + "NAD83 Alaska zone 10", + "NAD83 California 1", + "NAD83 California 2", + "NAD83 California 3", + "NAD83 California 4", + "NAD83 California 5", + "NAD83 California 6", + "NAD83 Arizona east", + "NAD83 Arizona central", + "NAD83 Arizona west", + "NAD83 Arkansas north", + "NAD83 Arkansas south", + "NAD83 Colorado north", + "NAD83 Colorado central", + "NAD83 Colorado south", + "NAD83 Connecticut", + "NAD83 Delaware", + "NAD83 Florida east", + "NAD83 Florida west", + "NAD83 Florida north", + "NAD83 Hawaii zone 1", + "NAD83 Hawaii zone 2", + "NAD83 Hawaii zone 3", + "NAD83 Hawaii zone 4", + "NAD83 Hawaii zone 5", + "NAD83 Georgia east", + "NAD83 Georgia west", + "NAD83 Idaho east", + "NAD83 Idaho central", + "NAD83 Idaho west", + "NAD83 Illinois east", + "NAD83 Illinois west", + "NAD83 Indiana east", + "NAD83 Indiana west", + "NAD83 Iowa north", + "NAD83 Iowa south", + "NAD83 Kansas north", + "NAD83 Kansas south", + "NAD83 Kentucky north", + "NAD83 Kentucky south", + "NAD83 Louisiana north", + "NAD83 Louisiana south", + "NAD83 Maine east", + "NAD83 Maine west", + "NAD83 Maryland", + "NAD83 Massachusetts", + "NAD83 Massachusetts Is", + "NAD83 Michigan north", + "NAD83 Michigan central", + "NAD83 Michigan south", + "NAD83 Minnesota north", + "NAD83 Minnesota central", + "NAD83 Minnesota south", + "NAD83 Mississippi east", + "NAD83 Mississippi west", + "NAD83 Missouri east", + "NAD83 Missouri central", + "NAD83 Missouri west", + "Nahrwan 1967 UTM 38N", + "Nahrwan 1967 UTM 39N", + "Nahrwan 1967 UTM 40N", + "Naparima UTM 20N", + "GD49 NZ Map Grid", + "GD49 north Island Grid", + "GD49 south Island Grid", + "Datum 73 UTM zone 29N", + "ATF Nord de Guerre", + "NTF France I", + "NTF France II", + "NTF France III", + "NTF Nord France", + "NTF Centre France", + "NTF Sud France", + "British National Grid", + "Point Noire UTM 32S", + "GDA94 MGA zone 48", + "GDA94 MGA zone 49", + "GDA94 MGA zone 50", + "GDA94 MGA zone 51", + "GDA94 MGA zone 52", + "GDA94 MGA zone 53", + "GDA94 MGA zone 54", + "GDA94 MGA zone 55", + "GDA94 MGA zone 56", + "GDA94 MGA zone 57", + "GDA94 MGA zone 58", + "Pulkovo Gauss zone 4", + "Pulkovo Gauss zone 5", + "Pulkovo Gauss zone 6", + "Pulkovo Gauss zone 7", + "Pulkovo Gauss zone 8", + "Pulkovo Gauss zone 9", + "Pulkovo Gauss zone 10", + "Pulkovo Gauss zone 11", + "Pulkovo Gauss zone 12", + "Pulkovo Gauss zone 13", + "Pulkovo Gauss zone 14", + "Pulkovo Gauss zone 15", + "Pulkovo Gauss zone 16", + "Pulkovo Gauss zone 17", + "Pulkovo Gauss zone 18", + "Pulkovo Gauss zone 19", + "Pulkovo Gauss zone 20", + "Pulkovo Gauss zone 21", + "Pulkovo Gauss zone 22", + "Pulkovo Gauss zone 23", + "Pulkovo Gauss zone 24", + "Pulkovo Gauss zone 25", + "Pulkovo Gauss zone 26", + "Pulkovo Gauss zone 27", + "Pulkovo Gauss zone 28", + "Pulkovo Gauss zone 29", + "Pulkovo Gauss zone 30", + "Pulkovo Gauss zone 31", + "Pulkovo Gauss zone 32", + "Pulkovo Gauss 4N", + "Pulkovo Gauss 5N", + "Pulkovo Gauss 6N", + "Pulkovo Gauss 7N", + "Pulkovo Gauss 8N", + "Pulkovo Gauss 9N", + "Pulkovo Gauss 10N", + "Pulkovo Gauss 11N", + "Pulkovo Gauss 12N", + "Pulkovo Gauss 13N", + "Pulkovo Gauss 14N", + "Pulkovo Gauss 15N", + "Pulkovo Gauss 16N", + "Pulkovo Gauss 17N", + "Pulkovo Gauss 18N", + "Pulkovo Gauss 19N", + "Pulkovo Gauss 20N", + "Pulkovo Gauss 21N", + "Pulkovo Gauss 22N", + "Pulkovo Gauss 23N", + "Pulkovo Gauss 24N", + "Pulkovo Gauss 25N", + "Pulkovo Gauss 26N", + "Pulkovo Gauss 27N", + "Pulkovo Gauss 28N", + "Pulkovo Gauss 29N", + "Pulkovo Gauss 30N", + "Pulkovo Gauss 31N", + "Pulkovo Gauss 32N", + "Qatar National Grid", + "RD Netherlands Old", + "RD Netherlands New", + "SAD69 UTM zone 18N", + "SAD69 UTM zone 19N", + "SAD69 UTM zone 20N", + "SAD69 UTM zone 21N", + "SAD69 UTM zone 22N", + "SAD69 UTM zone 17S", + "SAD69 UTM zone 18S", + "SAD69 UTM zone 19S", + "SAD69 UTM zone 20S", + "SAD69 UTM zone 21S", + "SAD69 UTM zone 22S", + "SAD69 UTM zone 23S", + "SAD69 UTM zone 24S", + "SAD69 UTM zone 25S", + "Sapper Hill UTM 20S", + "Sapper Hill UTM 21S", + "Schwarzeck UTM 33S", + "Sudan UTM zone 35N", + "Sudan UTM zone 36N", + "Tananarive Laborde", + "Tananarive UTM 38S", + "Tananarive UTM 39S", + "Timbalai 1948 Borneo", + "Timbalai 1948 UTM 49N", + "Timbalai 1948 UTM 50N", + "TM65 Irish Nat Grid", + "Trinidad 1903 Trinidad", + "TC 1948 UTM zone 39N", + "TC 1948 UTM zone 40N", + "Voirol N Algerie ancien", + "Voirol S Algerie ancien", + "Voirol Unifie N Algerie", + "Voirol Unifie S Algerie", + "Bern 1938 Swiss New", + "Nord Sahara UTM 29N", + "Nord Sahara UTM 30N", + "Nord Sahara UTM 31N", + "Nord Sahara UTM 32N", + "Yoff UTM zone 28N", + "Zanderij UTM zone 21N", + "MGI Austria west", + "MGI Austria central", + "MGI Austria east", + "Belge Lambert 72", + "DHDN Germany zone 1", + "DHDN Germany zone 2", + "DHDN Germany zone 3", + "DHDN Germany zone 4", + "DHDN Germany zone 5", + "NAD27 Montana north", + "NAD27 Montana central", + "NAD27 Montana south", + "NAD27 Nebraska north", + "NAD27 Nebraska south", + "NAD27 Nevada east", + "NAD27 Nevada central", + "NAD27 Nevada west", + "NAD27 New Hampshire", + "NAD27 New Jersey", + "NAD27 New Mexico east", + "NAD27 New Mexico Cent", + "NAD27 New Mexico west", + "NAD27 New York east", + "NAD27 New York central", + "NAD27 New York west", + "NAD27 New York Long Is", + "NAD27 north Carolina", + "NAD27 north Dakota N", + "NAD27 north Dakota S", + "NAD27 Ohio north", + "NAD27 Ohio south", + "NAD27 Oklahoma north", + "NAD27 Oklahoma south", + "NAD27 Oregon north", + "NAD27 Oregon south", + "NAD27 Pennsylvania N", + "NAD27 Pennsylvania S", + "NAD27 Rhode Island", + "NAD27 south Carolina N", + "NAD27 south Carolina S", + "NAD27 south Dakota N", + "NAD27 south Dakota S", + "NAD27 Tennessee", + "NAD27 Texas north", + "NAD27 Texas north Cen", + "NAD27 Texas central", + "NAD27 Texas south Cen", + "NAD27 Texas south", + "NAD27 Utah north", + "NAD27 Utah central", + "NAD27 Utah south", + "NAD27 Vermont", + "NAD27 Virginia north", + "NAD27 Virginia south", + "NAD27 Washington north", + "NAD27 Washington south", + "NAD27 west Virginia N", + "NAD27 west Virginia S", + "NAD27 Wisconsin north", + "NAD27 Wisconsin central", + "NAD27 Wisconsin south", + "NAD27 Wyoming east", + "NAD27 Wyoming E central", + "NAD27 Wyoming W central", + "NAD27 Wyoming west", + "NAD27 Puerto Rico", + "NAD27 St Croix", + "NAD83 Montana", + "NAD83 Nebraska", + "NAD83 Nevada east", + "NAD83 Nevada central", + "NAD83 Nevada west", + "NAD83 New Hampshire", + "NAD83 New Jersey", + "NAD83 New Mexico east", + "NAD83 New Mexico central", + "NAD83 New Mexico west", + "NAD83 New York east", + "NAD83 New York central", + "NAD83 New York west", + "NAD83 New York Long Is", + "NAD83 north Carolina", + "NAD83 north Dakota N", + "NAD83 north Dakota S", + "NAD83 Ohio north", + "NAD83 Ohio south", + "NAD83 Oklahoma north", + "NAD83 Oklahoma south", + "NAD83 Oregon north", + "NAD83 Oregon south", + "NAD83 Pennsylvania N", + "NAD83 Pennsylvania S", + "NAD83 Rhode Island", + "NAD83 south Carolina", + "NAD83 south Dakota N", + "NAD83 south Dakota S", + "NAD83 Tennessee", + "NAD83 Texas north", + "NAD83 Texas north Cen", + "NAD83 Texas central", + "NAD83 Texas south Cen", + "NAD83 Texas south", + "NAD83 Utah north", + "NAD83 Utah central", + "NAD83 Utah south", + "NAD83 Vermont", + "NAD83 Virginia north", + "NAD83 Virginia south", + "NAD83 Washington north", + "NAD83 Washington south", + "NAD83 west Virginia N", + "NAD83 west Virginia S", + "NAD83 Wisconsin north", + "NAD83 Wisconsin Cen", + "NAD83 Wisconsin south", + "NAD83 Wyoming east", + "NAD83 Wyoming E Cen", + "NAD83 Wyoming W Cen", + "NAD83 Wyoming west", + "NAD83 Puerto Rico Virgin Is", + "WGS72 UTM zone 1N", + "WGS72 UTM zone 2N", + "WGS72 UTM zone 3N", + "WGS72 UTM zone 4N", + "WGS72 UTM zone 5N", + "WGS72 UTM zone 6N", + "WGS72 UTM zone 7N", + "WGS72 UTM zone 8N", + "WGS72 UTM zone 9N", + "WGS72 UTM zone 10N", + "WGS72 UTM zone 11N", + "WGS72 UTM zone 12N", + "WGS72 UTM zone 13N", + "WGS72 UTM zone 14N", + "WGS72 UTM zone 15N", + "WGS72 UTM zone 16N", + "WGS72 UTM zone 17N", + "WGS72 UTM zone 18N", + "WGS72 UTM zone 19N", + "WGS72 UTM zone 20N", + "WGS72 UTM zone 21N", + "WGS72 UTM zone 22N", + "WGS72 UTM zone 23N", + "WGS72 UTM zone 24N", + "WGS72 UTM zone 25N", + "WGS72 UTM zone 26N", + "WGS72 UTM zone 27N", + "WGS72 UTM zone 28N", + "WGS72 UTM zone 29N", + "WGS72 UTM zone 30N", + "WGS72 UTM zone 31N", + "WGS72 UTM zone 32N", + "WGS72 UTM zone 33N", + "WGS72 UTM zone 34N", + "WGS72 UTM zone 35N", + "WGS72 UTM zone 36N", + "WGS72 UTM zone 37N", + "WGS72 UTM zone 38N", + "WGS72 UTM zone 39N", + "WGS72 UTM zone 40N", + "WGS72 UTM zone 41N", + "WGS72 UTM zone 42N", + "WGS72 UTM zone 43N", + "WGS72 UTM zone 44N", + "WGS72 UTM zone 45N", + "WGS72 UTM zone 46N", + "WGS72 UTM zone 47N", + "WGS72 UTM zone 48N", + "WGS72 UTM zone 49N", + "WGS72 UTM zone 50N", + "WGS72 UTM zone 51N", + "WGS72 UTM zone 52N", + "WGS72 UTM zone 53N", + "WGS72 UTM zone 54N", + "WGS72 UTM zone 55N", + "WGS72 UTM zone 56N", + "WGS72 UTM zone 57N", + "WGS72 UTM zone 58N", + "WGS72 UTM zone 59N", + "WGS72 UTM zone 60N", + "WGS72 UTM zone 1S", + "WGS72 UTM zone 2S", + "WGS72 UTM zone 3S", + "WGS72 UTM zone 4S", + "WGS72 UTM zone 5S", + "WGS72 UTM zone 6S", + "WGS72 UTM zone 7S", + "WGS72 UTM zone 8S", + "WGS72 UTM zone 9S", + "WGS72 UTM zone 10S", + "WGS72 UTM zone 11S", + "WGS72 UTM zone 12S", + "WGS72 UTM zone 13S", + "WGS72 UTM zone 14S", + "WGS72 UTM zone 15S", + "WGS72 UTM zone 16S", + "WGS72 UTM zone 17S", + "WGS72 UTM zone 18S", + "WGS72 UTM zone 19S", + "WGS72 UTM zone 20S", + "WGS72 UTM zone 21S", + "WGS72 UTM zone 22S", + "WGS72 UTM zone 23S", + "WGS72 UTM zone 24S", + "WGS72 UTM zone 25S", + "WGS72 UTM zone 26S", + "WGS72 UTM zone 27S", + "WGS72 UTM zone 28S", + "WGS72 UTM zone 29S", + "WGS72 UTM zone 30S", + "WGS72 UTM zone 31S", + "WGS72 UTM zone 32S", + "WGS72 UTM zone 33S", + "WGS72 UTM zone 34S", + "WGS72 UTM zone 35S", + "WGS72 UTM zone 36S", + "WGS72 UTM zone 37S", + "WGS72 UTM zone 38S", + "WGS72 UTM zone 39S", + "WGS72 UTM zone 40S", + "WGS72 UTM zone 41S", + "WGS72 UTM zone 42S", + "WGS72 UTM zone 43S", + "WGS72 UTM zone 44S", + "WGS72 UTM zone 45S", + "WGS72 UTM zone 46S", + "WGS72 UTM zone 47S", + "WGS72 UTM zone 48S", + "WGS72 UTM zone 49S", + "WGS72 UTM zone 50S", + "WGS72 UTM zone 51S", + "WGS72 UTM zone 52S", + "WGS72 UTM zone 53S", + "WGS72 UTM zone 54S", + "WGS72 UTM zone 55S", + "WGS72 UTM zone 56S", + "WGS72 UTM zone 57S", + "WGS72 UTM zone 58S", + "WGS72 UTM zone 59S", + "WGS72 UTM zone 60S", + "WGS72BE UTM zone 1N", + "WGS72BE UTM zone 2N", + "WGS72BE UTM zone 3N", + "WGS72BE UTM zone 4N", + "WGS72BE UTM zone 5N", + "WGS72BE UTM zone 6N", + "WGS72BE UTM zone 7N", + "WGS72BE UTM zone 8N", + "WGS72BE UTM zone 9N", + "WGS72BE UTM zone 10N", + "WGS72BE UTM zone 11N", + "WGS72BE UTM zone 12N", + "WGS72BE UTM zone 13N", + "WGS72BE UTM zone 14N", + "WGS72BE UTM zone 15N", + "WGS72BE UTM zone 16N", + "WGS72BE UTM zone 17N", + "WGS72BE UTM zone 18N", + "WGS72BE UTM zone 19N", + "WGS72BE UTM zone 20N", + "WGS72BE UTM zone 21N", + "WGS72BE UTM zone 22N", + "WGS72BE UTM zone 23N", + "WGS72BE UTM zone 24N", + "WGS72BE UTM zone 25N", + "WGS72BE UTM zone 26N", + "WGS72BE UTM zone 27N", + "WGS72BE UTM zone 28N", + "WGS72BE UTM zone 29N", + "WGS72BE UTM zone 30N", + "WGS72BE UTM zone 31N", + "WGS72BE UTM zone 32N", + "WGS72BE UTM zone 33N", + "WGS72BE UTM zone 34N", + "WGS72BE UTM zone 35N", + "WGS72BE UTM zone 36N", + "WGS72BE UTM zone 37N", + "WGS72BE UTM zone 38N", + "WGS72BE UTM zone 39N", + "WGS72BE UTM zone 40N", + "WGS72BE UTM zone 41N", + "WGS72BE UTM zone 42N", + "WGS72BE UTM zone 43N", + "WGS72BE UTM zone 44N", + "WGS72BE UTM zone 45N", + "WGS72BE UTM zone 46N", + "WGS72BE UTM zone 47N", + "WGS72BE UTM zone 48N", + "WGS72BE UTM zone 49N", + "WGS72BE UTM zone 50N", + "WGS72BE UTM zone 51N", + "WGS72BE UTM zone 52N", + "WGS72BE UTM zone 53N", + "WGS72BE UTM zone 54N", + "WGS72BE UTM zone 55N", + "WGS72BE UTM zone 56N", + "WGS72BE UTM zone 57N", + "WGS72BE UTM zone 58N", + "WGS72BE UTM zone 59N", + "WGS72BE UTM zone 60N", + "WGS72BE UTM zone 1S", + "WGS72BE UTM zone 2S", + "WGS72BE UTM zone 3S", + "WGS72BE UTM zone 4S", + "WGS72BE UTM zone 5S", + "WGS72BE UTM zone 6S", + "WGS72BE UTM zone 7S", + "WGS72BE UTM zone 8S", + "WGS72BE UTM zone 9S", + "WGS72BE UTM zone 10S", + "WGS72BE UTM zone 11S", + "WGS72BE UTM zone 12S", + "WGS72BE UTM zone 13S", + "WGS72BE UTM zone 14S", + "WGS72BE UTM zone 15S", + "WGS72BE UTM zone 16S", + "WGS72BE UTM zone 17S", + "WGS72BE UTM zone 18S", + "WGS72BE UTM zone 19S", + "WGS72BE UTM zone 20S", + "WGS72BE UTM zone 21S", + "WGS72BE UTM zone 22S", + "WGS72BE UTM zone 23S", + "WGS72BE UTM zone 24S", + "WGS72BE UTM zone 25S", + "WGS72BE UTM zone 26S", + "WGS72BE UTM zone 27S", + "WGS72BE UTM zone 28S", + "WGS72BE UTM zone 29S", + "WGS72BE UTM zone 30S", + "WGS72BE UTM zone 31S", + "WGS72BE UTM zone 32S", + "WGS72BE UTM zone 33S", + "WGS72BE UTM zone 34S", + "WGS72BE UTM zone 35S", + "WGS72BE UTM zone 36S", + "WGS72BE UTM zone 37S", + "WGS72BE UTM zone 38S", + "WGS72BE UTM zone 39S", + "WGS72BE UTM zone 40S", + "WGS72BE UTM zone 41S", + "WGS72BE UTM zone 42S", + "WGS72BE UTM zone 43S", + "WGS72BE UTM zone 44S", + "WGS72BE UTM zone 45S", + "WGS72BE UTM zone 46S", + "WGS72BE UTM zone 47S", + "WGS72BE UTM zone 48S", + "WGS72BE UTM zone 49S", + "WGS72BE UTM zone 50S", + "WGS72BE UTM zone 51S", + "WGS72BE UTM zone 52S", + "WGS72BE UTM zone 53S", + "WGS72BE UTM zone 54S", + "WGS72BE UTM zone 55S", + "WGS72BE UTM zone 56S", + "WGS72BE UTM zone 57S", + "WGS72BE UTM zone 58S", + "WGS72BE UTM zone 59S", + "WGS72BE UTM zone 60S", + "WGS84 UTM zone 1N", + "WGS84 UTM zone 2N", + "WGS84 UTM zone 3N", + "WGS84 UTM zone 4N", + "WGS84 UTM zone 5N", + "WGS84 UTM zone 6N", + "WGS84 UTM zone 7N", + "WGS84 UTM zone 8N", + "WGS84 UTM zone 9N", + "WGS84 UTM zone 10N", + "WGS84 UTM zone 11N", + "WGS84 UTM zone 12N", + "WGS84 UTM zone 13N", + "WGS84 UTM zone 14N", + "WGS84 UTM zone 15N", + "WGS84 UTM zone 16N", + "WGS84 UTM zone 17N", + "WGS84 UTM zone 18N", + "WGS84 UTM zone 19N", + "WGS84 UTM zone 20N", + "WGS84 UTM zone 21N", + "WGS84 UTM zone 22N", + "WGS84 UTM zone 23N", + "WGS84 UTM zone 24N", + "WGS84 UTM zone 25N", + "WGS84 UTM zone 26N", + "WGS84 UTM zone 27N", + "WGS84 UTM zone 28N", + "WGS84 UTM zone 29N", + "WGS84 UTM zone 30N", + "WGS84 UTM zone 31N", + "WGS84 UTM zone 32N", + "WGS84 UTM zone 33N", + "WGS84 UTM zone 34N", + "WGS84 UTM zone 35N", + "WGS84 UTM zone 36N", + "WGS84 UTM zone 37N", + "WGS84 UTM zone 38N", + "WGS84 UTM zone 39N", + "WGS84 UTM zone 40N", + "WGS84 UTM zone 41N", + "WGS84 UTM zone 42N", + "WGS84 UTM zone 43N", + "WGS84 UTM zone 44N", + "WGS84 UTM zone 45N", + "WGS84 UTM zone 46N", + "WGS84 UTM zone 47N", + "WGS84 UTM zone 48N", + "WGS84 UTM zone 49N", + "WGS84 UTM zone 50N", + "WGS84 UTM zone 51N", + "WGS84 UTM zone 52N", + "WGS84 UTM zone 53N", + "WGS84 UTM zone 54N", + "WGS84 UTM zone 55N", + "WGS84 UTM zone 56N", + "WGS84 UTM zone 57N", + "WGS84 UTM zone 58N", + "WGS84 UTM zone 59N", + "WGS84 UTM zone 60N", + "WGS84 UTM zone 1S", + "WGS84 UTM zone 2S", + "WGS84 UTM zone 3S", + "WGS84 UTM zone 4S", + "WGS84 UTM zone 5S", + "WGS84 UTM zone 6S", + "WGS84 UTM zone 7S", + "WGS84 UTM zone 8S", + "WGS84 UTM zone 9S", + "WGS84 UTM zone 10S", + "WGS84 UTM zone 11S", + "WGS84 UTM zone 12S", + "WGS84 UTM zone 13S", + "WGS84 UTM zone 14S", + "WGS84 UTM zone 15S", + "WGS84 UTM zone 16S", + "WGS84 UTM zone 17S", + "WGS84 UTM zone 18S", + "WGS84 UTM zone 19S", + "WGS84 UTM zone 20S", + "WGS84 UTM zone 21S", + "WGS84 UTM zone 22S", + "WGS84 UTM zone 23S", + "WGS84 UTM zone 24S", + "WGS84 UTM zone 25S", + "WGS84 UTM zone 26S", + "WGS84 UTM zone 27S", + "WGS84 UTM zone 28S", + "WGS84 UTM zone 29S", + "WGS84 UTM zone 30S", + "WGS84 UTM zone 31S", + "WGS84 UTM zone 32S", + "WGS84 UTM zone 33S", + "WGS84 UTM zone 34S", + "WGS84 UTM zone 35S", + "WGS84 UTM zone 36S", + "WGS84 UTM zone 37S", + "WGS84 UTM zone 38S", + "WGS84 UTM zone 39S", + "WGS84 UTM zone 40S", + "WGS84 UTM zone 41S", + "WGS84 UTM zone 42S", + "WGS84 UTM zone 43S", + "WGS84 UTM zone 44S", + "WGS84 UTM zone 45S", + "WGS84 UTM zone 46S", + "WGS84 UTM zone 47S", + "WGS84 UTM zone 48S", + "WGS84 UTM zone 49S", + "WGS84 UTM zone 50S", + "WGS84 UTM zone 51S", + "WGS84 UTM zone 52S", + "WGS84 UTM zone 53S", + "WGS84 UTM zone 54S", + "WGS84 UTM zone 55S", + "WGS84 UTM zone 56S", + "WGS84 UTM zone 57S", + "WGS84 UTM zone 58S", + "WGS84 UTM zone 59S", + "WGS84 UTM zone 60S", + "User-defined" }; + + /* 6.3.3.2 Projection codes */ + + public final static int[] PROJECTION_INDEX = { + 0, 10101, 10102, 10131, 10132, 10201, 10202, 10203, + 10231, 10232, 10233, 10301, 10302, 10331, 10332, 10401, + 10402, 10403, 10404, 10405, 10406, 10407, 10431, 10432, + 10433, 10434, 10435, 10436, 10501, 10502, 10503, 10531, + 10532, 10533, 10600, 10630, 10700, 10730, 10901, 10902, + 10903, 10931, 10932, 10933, 11001, 11002, 11031, 11032, + 11101, 11102, 11103, 11131, 11132, 11133, 11201, 11202, + 11231, 11232, 11301, 11302, 11331, 11332, 11401, 11402, + 11431, 11432, 11501, 11502, 11531, 11532, 11601, 11602, + 11631, 11632, 11701, 11702, 11731, 11732, 11801, 11802, + 11831, 11832, 11900, 11930, 12001, 12002, 12031, 12032, + 12101, 12102, 12103, 12111, 12112, 12113, 12141, 12142, + 12143, 12201, 12202, 12203, 12231, 12232, 12233, 12301, + 12302, 12331, 12332, 12401, 12402, 12403, 12431, 12432, + 12433, 12501, 12502, 12503, 12530, 12601, 12602, 12630, + 12701, 12702, 12703, 12731, 12732, 12733, 12800, 12830, + 12900, 12930, 13001, 13002, 13003, 13031, 13032, 13033, + 13101, 13102, 13103, 13104, 13131, 13132, 13133, 13134, + 13200, 13230, 13301, 13302, 13331, 13332, 13401, 13402, + 13431, 13432, 13501, 13502, 13531, 13532, 13601, 13602, + 13631, 13632, 13701, 13702, 13731, 13732, 13800, 13830, + 13901, 13902, 13930, 14001, 14002, 14031, 14032, 14100, + 14130, 14201, 14202, 14203, 14204, 14205, 14231, 14232, + 14233, 14234, 14235, 14301, 14302, 14303, 14331, 14332, + 14333, 14400, 14430, 14501, 14502, 14531, 14532, 14601, + 14602, 14631, 14632, 14701, 14702, 14731, 14732, 14801, + 14802, 14803, 14831, 14832, 14833, 14901, 14902, 14903, + 14904, 14931, 14932, 14933, 14934, 15001, 15002, 15003, + 15004, 15005, 15006, 15007, 15008, 15009, 15010, 15031, + 15032, 15033, 15034, 15035, 15036, 15037, 15038, 15039, + 15040, 15101, 15102, 15103, 15104, 15105, 15131, 15132, + 15133, 15134, 15135, 15201, 15202, 15230, 15914, 15915, + 15916, 15917, 17348, 17349, 17350, 17351, 17352, 17353, + 17354, 17355, 17356, 17357, 17358, 17448, 17449, 17450, + 17451, 17452, 17453, 17454, 17455, 17456, 17457, 17458, + 18031, 18032, 18033, 18034, 18035, 18036, 18037, 18051, + 18052, 18053, 18054, 18072, 18073, 18074, 18141, 18142, + 19900, 19905, 19912, 32767 + }; + public final static String[] PROJECTION = { + "Undefined", + "Alabama CS27 east", + "Alabama CS27 west", + "Alabama CS83 east", + "Alabama CS83 west", + "Arizona coordinate system east", + "Arizona coordinate system central", + "Arizona coordinate system west", + "Arizona CS83 east", + "Arizona CS83 central", + "Arizona CS83 west", + "Arkansas CS27 north", + "Arkansas CS27 south", + "Arkansas CS83 north", + "Arkansas CS83 south", + "California CS27 I", + "California CS27 II", + "California CS27 III", + "California CS27 IV", + "California CS27 V", + "California CS27 VI", + "California CS27 VII", + "California CS83 1", + "California CS83 2", + "California CS83 3", + "California CS83 4", + "California CS83 5", + "California CS83 6", + "Colorado CS27 north", + "Colorado CS27 central", + "Colorado CS27 south", + "Colorado CS83 north", + "Colorado CS83 central", + "Colorado CS83 south", + "Connecticut CS27", + "Connecticut CS83", + "Delaware CS27", + "Delaware CS83", + "Florida CS27 east", + "Florida CS27 west", + "Florida CS27 north", + "Florida CS83 east", + "Florida CS83 west", + "Florida CS83 north", + "Georgia CS27 east", + "Georgia CS27 west", + "Georgia CS83 east", + "Georgia CS83 west", + "Idaho CS27 east", + "Idaho CS27 central", + "Idaho CS27 west", + "Idaho CS83 east", + "Idaho CS83 central", + "Idaho CS83 west", + "Illinois CS27 east", + "Illinois CS27 west", + "Illinois CS83 east", + "Illinois CS83 west", + "Indiana CS27 east", + "Indiana CS27 west", + "Indiana CS83 east", + "Indiana CS83 west", + "Iowa CS27 north", + "Iowa CS27 south", + "Iowa CS83 north", + "Iowa CS83 south", + "Kansas CS27 north", + "Kansas CS27 south", + "Kansas CS83 north", + "Kansas CS83 south", + "Kentucky CS27 north", + "Kentucky CS27 south", + "Kentucky CS83 north", + "Kentucky CS83 south", + "Louisiana CS27 north", + "Louisiana CS27 south", + "Louisiana CS83 north", + "Louisiana CS83 south", + "Maine CS27 east", + "Maine CS27 west", + "Maine CS83 east", + "Maine CS83 west", + "Maryland CS27", + "Maryland CS83", + "Massachusetts CS27 mainland", + "Massachusetts CS27 island", + "Massachusetts CS83 mainland", + "Massachusetts CS83 island", + "Michigan state plane east", + "Michigan state plane old central", + "Michigan state plane west", + "Michigan CS27 north", + "Michigan CS27 central", + "Michigan CS27 south", + "Michigan CS83 north", + "Michigan CS83 central", + "Michigan CS83 south", + "Minnesota CS27 north", + "Minnesota CS27 central", + "Minnesota CS27 south", + "Minnesota CS83 north", + "Minnesota CS83 central", + "Minnesota CS83 south", + "Mississippi CS27 east", + "Mississippi CS27 west", + "Mississippi CS83 east", + "Mississippi CS83 west", + "Missouri CS27 east", + "Missouri CS27 central", + "Missouri CS27 west", + "Missouri CS83 east", + "Missouri CS83 central", + "Missouri CS83 west", + "Montana CS27 north", + "Montana CS27 central", + "Montana CS27 south", + "Montana CS83", + "Nebraska CS27 north", + "Nebraska CS27 south", + "Nebraska CS83", + "Nevada CS27 east", + "Nevada CS27 central", + "Nevada CS27 west", + "Nevada CS83 east", + "Nevada CS83 central", + "Nevada CS83 west", + "New Hampshire CS27", + "New Hampshire CS83", + "New Jersey CS27", + "New Jersey CS83", + "New Mexico CS27 east", + "New Mexico CS27 central", + "New Mexico CS27 west", + "New Mexico CS83 east", + "New Mexico CS83 central", + "New Mexico CS83 west", + "New York CS27 east", + "New York CS27 central", + "New York CS27 west", + "New York CS27 Long Island", + "New York CS83 east", + "New York CS83 central", + "New York CS83 west", + "New York CS83 Long Island", + "north Carolina CS27", + "north Carolina CS83", + "north Dakota CS27 north", + "north Dakota CS27 south", + "north Dakota CS83 north", + "north Dakota CS83 south", + "Ohio CS27 north", + "Ohio CS27 south", + "Ohio CS83 north", + "Ohio CS83 south", + "Oklahoma CS27 north", + "Oklahoma CS27 south", + "Oklahoma CS83 north", + "Oklahoma CS83 south", + "Oregon CS27 north", + "Oregon CS27 south", + "Oregon CS83 north", + "Oregon CS83 south", + "Pennsylvania CS27 north", + "Pennsylvania CS27 south", + "Pennsylvania CS83 north", + "Pennsylvania CS83 south", + "Rhode Island CS27", + "Rhode Island CS83", + "South Carolina CS27 north", + "South Carolina CS27 south", + "South Carolina CS83", + "South Dakota CS27 north", + "South Dakota CS27 south", + "South Dakota CS83 north", + "South Dakota CS83 south", + "Tennessee CS27", + "Tennessee CS83", + "Texas CS27 north", + "Texas CS27 north central", + "Texas CS27 central", + "Texas CS27 south central", + "Texas CS27 south", + "Texas CS83 north", + "Texas CS83 north central", + "Texas CS83 central", + "Texas CS83 south central", + "Texas CS83 south", + "Utah CS27 north", + "Utah CS27 central", + "Utah CS27 south", + "Utah CS83 north", + "Utah CS83 central", + "Utah CS83 south", + "Vermont CS27", + "Vermont CS83", + "Virginia CS27 north", + "Virginia CS27 south", + "Virginia CS83 north", + "Virginia CS83 south", + "Washington CS27 north", + "Washington CS27 south", + "Washington CS83 north", + "Washington CS83 south", + "west Virginia CS27 north", + "west Virginia CS27 south", + "west Virginia CS83 north", + "west Virginia CS83 south", + "Wisconsin CS27 north", + "Wisconsin CS27 central", + "Wisconsin CS27 south", + "Wisconsin CS83 north", + "Wisconsin CS83 central", + "Wisconsin CS83 south", + "Wyoming CS27 east", + "Wyoming CS27 east central", + "Wyoming CS27 west central", + "Wyoming CS27 west", + "Wyoming CS83 east", + "Wyoming CS83 east central", + "Wyoming CS83 west central", + "Wyoming CS83 west", + "Alaska CS27 1", + "Alaska CS27 2", + "Alaska CS27 3", + "Alaska CS27 4", + "Alaska CS27 5", + "Alaska CS27 6", + "Alaska CS27 7", + "Alaska CS27 8", + "Alaska CS27 9", + "Alaska CS27 10", + "Alaska CS83 1", + "Alaska CS83 2", + "Alaska CS83 3", + "Alaska CS83 4", + "Alaska CS83 5", + "Alaska CS83 6", + "Alaska CS83 7", + "Alaska CS83 8", + "Alaska CS83 9", + "Alaska CS83 10", + "Hawaii CS27 1", + "Hawaii CS27 2", + "Hawaii CS27 3", + "Hawaii CS27 4", + "Hawaii CS27 5", + "Hawaii CS83 1", + "Hawaii CS83 2", + "Hawaii CS83 3", + "Hawaii CS83 4", + "Hawaii CS83 5", + "Puerto Rico CS27", + "St Croix", + "Puerto Rico Virgin Is", + "BLM 14N feet", + "BLM 15N feet", + "BLM 16N feet", + "BLM 17N feet", + "Map grid of Australia 48", + "Map grid of Australia 49", + "Map grid of Australia 50", + "Map grid of Australia 51", + "Map grid of Australia 52", + "Map grid of Australia 53", + "Map grid of Australia 54", + "Map grid of Australia 55", + "Map grid of Australia 56", + "Map grid of Australia 57", + "Map grid of Australia 58", + "Australian map grid 48", + "Australian map grid 49", + "Australian map grid 50", + "Australian map grid 51", + "Australian map grid 52", + "Australian map grid 53", + "Australian map grid 54", + "Australian map grid 55", + "Australian map grid 56", + "Australian map grid 57", + "Australian map grid 58", + "Argentina 1", + "Argentina 2", + "Argentina 3", + "Argentina 4", + "Argentina 5", + "Argentina 6", + "Argentina 7", + "Colombia 3W", + "Colombia Bogota", + "Colombia 3E", + "Colombia 6E", + "Egypt red belt", + "Egypt purple belt", + "Extended Purple Belt", + "New Zealand north island nat grid", + "New Zealand south island nat grid", + "Bahrain grid", + "Netherlands E Indies Equatorial", + "RSO Borneo", + "User-defined" + }; + + /* 6.3.3.3 Coordinate transformation codes */ + + public final static int[] COORDINATETRANSFORMATION_INDEX = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 32767 + }; + public final static String[] COORDINATETRANSFORMATION = { + "Undefined", + "Transverse Mercator", + "Transverse Mercator modified Alaska", + "Oblique Mercator", + "Oblique Mercator Laborde", + "Oblique Mercator Rosenmund", + "Oblique Mercator Spherical", + "Mercator", + "Lambert Conformal Conic 2SP", + "Lambert Conformal conic Helmert", + "Lambert Azimuthal equal area", + "Albers equal area", + "Azimuthal equidistant", + "Equidistant conic", + "Stereographic", + "Polar stereographic", + "Oblique stereographic", + "Equirectangular", + "Cassini Soldner", + "Gnomonic", + "Miller cylindrical", + "Orthographic", + "Polyconic", + "Robinson", + "Sinusoidal", + "Van Der Grinten", + "New Zealand map grid", + "Transverse Mercator south oriented", + "User-defined" }; + + /* 6.3.4.1 Vertical CS type codes */ + + public final static int[] VERTICALCSTYPE_INDEX = { + 0, 5001, 5002, 5003, 5004, 5005, 5006, 5007, + 5008, 5010, 5011, 5012, 5013, 5014, 5015, 5016, + 5017, 5018, 5019, 5020, 5021, 5022, 5023, 5024, + 5025, 5026, 5027, 5028, 5029, 5030, 5031, 5032, + 5033, 5101, 5102, 5103, 5104, 5105, 5106, 32767 + }; + public final static String[] VERTICALCSTYPE = { + "Undefined", + "Airy 1830 ellipsoid", + "Airy modified 1849 ellipsoid", + "ANS ellipsoid", + "Bessel 1841 ellipsoid", + "Bessel Modified ellipsoid", + "Bessel Namibia ellipsoid", + "Clarke 1858 ellipsoid", + "Clarke 1866 ellipsoid", + "Clarke 1880 Benoit ellipsoid", + "Clarke 1880 IGN ellipsoid", + "Clarke 1880 RGS ellipsoid", + "Clarke 1880 arc ellipsoid", + "Clarke 1880 SGA 1922 ellipsoid", + "Everest 1830 1937 adjustment ellipsoid", + "Everest 1830 1967 definition ellipsoid", + "Everest 1830 1975 definition ellipsoid", + "Everest 1830 modified ellipsoid", + "GRS 1980 ellipsoid", + "Helmert 1906 ellipsoid", + "INS ellipsoid", + "International 1924 ellipsoid", + "International 1967 ellipsoid", + "Krassowsky 1940 ellipsoid", + "NWL 9D ellipsoid", + "NWL 10D ellipsoid", + "Plessis 1817 ellipsoid", + "Struve 1860 ellipsoid", + "War Office ellipsoid", + "WGS 84 ellipsoid", + "GEM 10C ellipsoid", + "OSU86F ellipsoid", + "OSU91A ellipsoid", + "Newlyn", + "North American vertical datum 1929", + "North American vertical datum 1988", + "Yellow Sea 1956", + "Baltic Sea", + "Caspian Sea", + "User-defined" + }; + + /* 6.3.4.2 Vertical CS datum codes */ + + public final static int[] VERTICALCSDATUM_INDEX = { + 0, 32767 + }; + public final static String[] VERTICALCSDATUM = { + "Undefined", "User-defined" + }; + + /* Private constructor to prevent instantiation */ + private GeoTiffStrings () + { + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/GlobalParametersIFD.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/GlobalParametersIFD.java new file mode 100644 index 00000000..a704cb92 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/GlobalParametersIFD.java @@ -0,0 +1,157 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; +import java.io.*; +import java.util.*; + +/** + * Encapsulation of a GlobalParameters IFD, as defined by + * TIFF/FX, RFC 2301. + * + * @author Gary McGath + * + */ +public class GlobalParametersIFD extends IFD { + + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + private int _profileType; + private int _faxProfile; + private int _codingMethods; + private String _versionYear; + private int _modeNumber; + + /** Tiff/FX-specific tags. */ + public static final int + PROFILETYPE = 401, + FAXPROFILE = 402, + CODINGMETHODS = 403, + VERSIONYEAR = 404, + MODENUMBER = 405; + + private static final String [] PROFILETYPE_L = { + "Unspecified", "Group 3 Fax" + }; + + private static final String [] FAXPROFILE_L = { + "does not conform to a profile defined for TIFF for facsimile", + "Minimal black & white lossless, Profile S", + "Extended black & white lossless, Profile F", + "Lossless JBIG black & white, Profile J", + "Lossy color and grayscale, Profile C", + "Lossless color and grayscale, Profile L", + "Mixed Raster Content, Profile M" + }; + + private static final String [] CODINGMETHODS_L = { + "unspecified compression", + "1-dimensional coding, ITU-T Rec. T.4 (MH - Modified Huffman)", + "2-dimensional coding, ITU-T Rec. T.4 (MR - Modified Read)", + "2-dimensional coding, ITU-T Rec. T.6 (MMR - Modified MR)", + "ITU-T Rec. T.82 coding, using ITU-T Rec. T.85 (JBIG)", + "ITU-T Rec. T.81 (Baseline JPEG)", + "ITU-T Rec. T.82 coding, using ITU-T Rec. T.43 (JBIG color)" + + }; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** Instantiate a GlobalParametersIFD object. + * @param offset IFD offset + * @param raf TIFF file + * @param bigEndian True if big-endian file + */ + public GlobalParametersIFD (long offset, RepInfo info, + RandomAccessFile raf, + boolean bigEndian) + { + super (offset, info, raf, bigEndian); + + _profileType = NULL; + _faxProfile = NULL; + _codingMethods = NULL; + _versionYear = null; + _modeNumber = NULL; + } + + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + ******************************************************************/ + + /** Get the IFD properties. */ + public Property getProperty(boolean rawOutput) throws TiffException { + List entries = new LinkedList (); + if (_profileType != NULL) { + entries.add (addIntegerProperty ("ProfileType", _profileType, + PROFILETYPE_L, + rawOutput)); + } + if (_faxProfile != NULL) { + entries.add (addIntegerProperty ("FaxProfile", _faxProfile, + FAXPROFILE_L, + rawOutput)); + } + if (_codingMethods != NULL) { + entries.add (addBitmaskProperty ("CodingMethods", _codingMethods, + CODINGMETHODS_L, + rawOutput)); + } + if (_versionYear != null) { + entries.add (new Property ("VersionYear", + PropertyType.STRING, + _versionYear)); + } + if (_modeNumber != NULL) { + entries.add (new Property ("ModeNumber", + PropertyType.INTEGER, + new Integer (_modeNumber))); + } + return propertyHeader ("GlobalParameterIFD", entries); + } + + /** Lookup an IFD tag. */ + public void lookupTag (int tag, int type, long count, long value) + throws TiffException + { + try { + if (tag == PROFILETYPE) { + checkType (tag, type, LONG); + checkCount (tag, count, 1); + _profileType = (int) readLong(type, count, value); + } + else if (tag == FAXPROFILE) { + checkType (tag, type, BYTE); + checkCount (tag, count, 1); + _faxProfile = (int) readByte(type, count, value); + } + else if (tag == CODINGMETHODS) { + checkType (tag, type, LONG); + checkCount (tag, count, 1); + _codingMethods = (int) readLong(type, count, value); + } + else if (tag == VERSIONYEAR) { + checkType (tag, type, BYTE); + checkCount (tag, count, 4); + _versionYear = readASCII(count, value); + } + else if (tag == MODENUMBER) { + checkType (tag, type, BYTE); + checkCount (tag, count, 1); + _modeNumber = (int) readByte(type, count, value); + } + } + catch (IOException e) { + throw new TiffException ("Read error for tag " + tag, value); + } + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/IFD.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/IFD.java new file mode 100644 index 00000000..6392a2f8 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/IFD.java @@ -0,0 +1,1014 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; +import java.io.*; +import java.text.*; +import java.util.*; + +/** + * Encapsulation of a TIFF image file directory (IFD). + */ +public abstract class IFD +{ + + /****************************************************************** + * DEBUGGING FIELDS. + * All debugging fields should be set to false for release code. + ******************************************************************/ + + /* Set to true to allow out-of-sequence tags. */ + private static final boolean debug_allowoutofsequence = false; + + /****************************************************************** + * PUBLIC CLASS FIELDS. + ******************************************************************/ + + /** Standard TIFF IFD. */ + public static final int TIFF = 0; + /** Exif IFD. */ + public static final int EXIF = 1; + /** Exif Interoperability IFD. */ + public static final int INTEROPERABILITY = 2; + /** GPSInfo IFD. */ + public static final int GPSINFO = 3; + /** Global parameters IFD. */ + public static final int GLOBALPARAMETERS = 4; + + + /** Undefined value for integer tags. */ + public static final int NULL = -1; + + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + + /* TIFF data types. */ + + /** TIFF BYTE (unsigned 8-bit) type. */ + public static final int BYTE = 1; + /** TIFF ASCII type. */ + public static final int ASCII = 2; + /** TIFF SHORT (unsigned 16-bit) type. */ + public static final int SHORT = 3; + /** TIFF LONG (unsigned 32-bit) type. */ + public static final int LONG = 4; + /** TIFF RATIONAL (two LONGs) type. */ + public static final int RATIONAL = 5; + /** TIFF SBYTE (signed 8-bit) type. */ + public static final int SBYTE = 6; + /** TIFF UNDEFINED (unsigned 8-bit) type. */ + public static final int UNDEFINED = 7; + /** TIFF SSHORT (signed 16-bit) type. */ + public static final int SSHORT = 8; + /** TIFF SLONG (signed 32-bit) type. */ + public static final int SLONG = 9; + /** TIFF SRATIONAL (two SLONGs) type. */ + public static final int SRATIONAL = 10; + /** TIFF FLOAT (32-bit IEEE floating point) type. */ + public static final int FLOAT = 11; + /** TIFF DOUBLE (64-bit IEEE floating point) type. */ + public static final int DOUBLE = 12; + /** TIFF IFD (LONG) type. */ + public static final int IFD = 13; + + /** TIFF type labels. */ + public static final String TYPE [] = { + "", "BYTE", "ASCII", "SHORT", "LONG", "RATIONAL", "SBYTE", "UNDEFINED", + "SSHORT", "SLONG", "SRATIONAL", "FLOAT", "DOUBLE", "IFD" + }; + + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + /** True if big-endian file. */ + protected boolean _bigEndian; + + /** List of errors. */ + private List _errors; + + /** True if this is the first IFD. */ + private boolean _first; + + /** True if the is the "thumbnail" IFD. */ + private boolean _thumbnail; + + /** Format for converting float to string. */ + private NumberFormat _format; + + /** Representation information. */ + protected RepInfo _info; + + /** Offset of next IFD. */ + protected long _next; + + /** IFD offset. */ + protected long _offset; + + /** Open random access TIFF file. */ + private RandomAccessFile _raf; + + /** TIFF version. */ + protected int _version; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** Instantiate an IFD object. + * @param offset IFD offset + * @param info Representation information + * @param raf TIFF file + * @param bigEndian True if big-endian file + */ + public IFD (long offset, RepInfo info, RandomAccessFile raf, + boolean bigEndian) + { + _offset = offset; + _info = info; + _raf = raf; + _bigEndian = bigEndian; + + _first = false; + _thumbnail = false; + _next = 0L; + _version = 4; + + _errors = new LinkedList (); + + _format = NumberFormat.getInstance (); + _format.setGroupingUsed (false); + _format.setMinimumFractionDigits (0); + } + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + ******************************************************************/ + + /** Get any errors discovered during parsing. */ + public List getErrors () + { + return _errors; + } + + /** Get the offset of the next IFD. */ + public long getNext () + { + return _next; + } + + /** Get the IFD offset. */ + public long getOffset () + { + return _offset; + } + + /** Get the IFD properties. */ + public abstract Property getProperty (boolean rawOutput) + throws TiffException; + + /** Get the TIFF version. */ + public int getVersion () + { + return _version; + } + + /** Return true if this is the first IFD. */ + public boolean isFirst () + { + return _first; + } + + /** Return true if this is the thumbnail IFD. */ + public boolean isThumbnail () + { + return _thumbnail; + } + + + /** Lookup IFD tag. */ + public abstract void lookupTag (int tag, int type, long count, long value) + throws TiffException; + + /** Parse the IFD. Errors are not suppressed, and odd byte offsets for + * tags not allowed. + * + * @return The offset of the next IFD + */ + public long parse () + throws TiffException + { + return parse (false, false); + } + + /** Parse the IFD. + * @param byteOffsetIsValid If true, allow offsets on odd byte boundaries + * @param suppressErrors If true, return IFD even with errors + * @return The offset of the next IFD + */ + public long parse (boolean byteOffsetIsValid, boolean suppressErrors) + throws TiffException + { + try { + return parse (byteOffsetIsValid); + } + catch (TiffException e) { + // If we got a TiffException and we're suppressing errors, + // cover over the exception and issue an info message; + // but we can't follow the IFD chain further. + if (suppressErrors) { + _info.setMessage + (new InfoMessage (e.getMessage(), e.getOffset())); + return 0; + } + else throw e; + } + } + + + /** Parse the IFD. Errors are not suppressed. + * + * @param byteOffsetIsValid If true, allow offsets on odd byte boundaries + * @return The offset of the next IFD + */ + public long parse (boolean byteOffsetIsValid) + throws TiffException + { + /* Start at the IFD offset, read the number of entries, then + * read the entire IFD. + */ + long offset = _offset; + _next = 0L; + byte [] buffer; + int nFields = 0; + try { + _raf.seek (offset); + nFields = ModuleBase.readUnsignedShort (_raf, _bigEndian); + offset += 2; + + int len = 12*nFields; + buffer = new byte[len]; + _raf.read (buffer, 0, len); + + /* Read the offset of the next IFD (or 0 if none). */ + offset += len; + _next = ModuleBase.readUnsignedInt (_raf, _bigEndian); + } + catch (Exception e) { + throw new TiffException ("Premature EOF", offset); + } + + DataInputStream ifdStream = + new DataInputStream (new ByteArrayInputStream (buffer)); + + try { + int prevTag = 0; + for (int i=0; i IFD) { + _info.setMessage (new ErrorMessage ("Unknown data type", + "Type = " + type + ", Tag = " + tag, _offset + 4 + 12*i)); + } + else { + /* Type gives indication of the TIFF version. */ + if (SBYTE <= type && type <= IFD) { + _version = 6; + } + + long count = ModuleBase.readUnsignedInt(ifdStream, + _bigEndian, null); + long value = ModuleBase.readUnsignedInt(ifdStream, + _bigEndian, null); + if (calcValueSize (type, count) > 4) { + /* Value is the word-aligned offset of the actual + * value. */ + if ((value & 1) != 0) { + if (byteOffsetIsValid) { + _info.setMessage (new InfoMessage ( + "Value offset not word-aligned: " + value, + _offset + 10 + 12*i)); + } + else { + throw new TiffException ("Value offset not " + + "word-aligned: " + value, + _offset + 10 + 12*i); + } + } + } + else { + /* Value is the actual value; pass the offset of + * the value. */ + value = _offset + 10 + 12*i; + } + lookupTag (tag, type, count, value); + } + } + } + catch (IOException e) { + throw new TiffException ("Read error", _offset + 2); + } + postParseInitialization (); + + return _next; + } + + /** Sets flag indicating whether this is the first IFD. */ + public void setFirst (boolean first) + { + _first = first; + } + + /** Sets flag indicating whether this is the "thumbnail" IFD. + * The second IFD in the top-level chain is assumed to be + * the Thumbnail IFD. */ + public void setThumbnail (boolean thumbnail) + { + _thumbnail = thumbnail; + } + + /** + * Returns a Property representing a bitmask. + * If rawOutput is true, returns a LIST + * property whose elements are STRING properties. The + * string values of these STRING properties are the + * elements of labels whose indices + * correspond to 1 bits in the bitmask, counting + * the low-order bit as bit 0. + * if rawOutput is false, returns a LONG + * property whose numeric value is value. + */ + protected Property addBitmaskProperty (String name, long value, + String [] labels, boolean rawOutput) + { + Property prop = null; + if (!rawOutput) { + List list = new LinkedList (); + try { + for (int i=0; irawOutput is true, returns + * an INTEGER property, and labels and + * index are unused. Otherwise, + * returns a STRING property, with the + * string being the element of labels + * whose index is value. + */ + protected Property addIntegerProperty (String name, int value, + String [] labels, boolean rawOutput) + { + Property prop = null; + if (!rawOutput) { + try { + prop = new Property (name, PropertyType.STRING, labels[value]); + } + catch (Exception e) { + _errors.add (name + " value out of range: " + value); + } + } + if (prop == null) { + prop = new Property (name, PropertyType.INTEGER, + new Integer (value)); + } + + return prop; + } + + /** + * Returns an Property representing an integer value. + * If rawOutput is true, returns + * an INTEGER property, and labels and + * index are unused. Otherwise, + * returns a STRING property, with the + * string being the element of labels + * whose index is the index of + * value in index. + */ + protected Property addIntegerProperty (String name, int value, + String [] labels, int [] index, + boolean rawOutput) + { + Property prop = null; + if (!rawOutput) { + int n = -1; + for (int i=0; i -1) { + prop = new Property (name, PropertyType.STRING, labels[n]); + } + else { + _errors.add (name + " value out of range: " + value); + } + } + if (prop == null) { + prop = new Property (name, PropertyType.INTEGER, + new Integer (value)); + } + + return prop; + } + + /** + * Returns an ARRAY Property representing an integer array. + * If rawOutput is true, the elements of the property array + * are INTEGER properties, and labels is unused. Otherwise, + * the elements of the array are STRING properties, with the + * elements of value used as indices into + * labels. + */ + protected Property addIntegerArrayProperty (String name, int [] value, + String [] labels, + boolean rawOutput) + { + Property prop = null; + if (!rawOutput) { + String [] s = new String[value.length]; + for (int i=0; i 127) { + sb.append (byteToHex (c)); + } + else { + sb.append ((char) c); + } + } + return sb.toString (); + } + + /** Reads an array of strings from the TIFF file. + * + * @param count Number of strings to read + * @param value Offset from which to read + * + */ + protected String [] readASCIIArray (long count, long value) + throws IOException + { + _raf.seek (value); + + int nstrs = 0; + List list = new LinkedList (); + byte[] buf = new byte[(int) count]; + _raf.read (buf); + StringBuffer strbuf = new StringBuffer (); + for (int i=0; i 127) { + strbuf.append (byteToHex ((byte) b)); + } + else { + strbuf.append ((char) b); + } + } + } + /* We can't use ArrayList.toArray because that returns an + Object[], not a String[] ... sigh. */ + String [] strs = new String[nstrs]; + ListIterator iter = list.listIterator (); + for (int i=0; itrue if file is big-endian, + * false if little-endian. + */ + public boolean isBigEndian () + { + return _bigEndian; + } + + + + /****************************************************************** + * PRIVATE CLASS METHODS. + ******************************************************************/ + + /** + * Check the tag entry count. + * @param tag Tag entry value + * @param count Tag entry count + * @param minCount Tag count + */ + protected static void checkCount (int tag, long count, int minCount) + throws TiffException + { + if (count < minCount) { + throw new TiffException ("Count mismatch for tag " + tag + + "; expecting " + minCount + ", saw " + + count); + } + } + + /** + * Check the tag entry type. + * @param tag Tag entry value + * @param type Tag entry type + * @param expected Tag type + */ + protected static void checkType (int tag, int type, int expected) + throws TiffException + { + /* Readers are supposed to accept BYTE, SHORT or LONG for any + * unsigned integer field. */ + if (type == BYTE || type == SHORT || type == LONG || type == IFD) { + if (expected == BYTE || expected == SHORT || expected == LONG || + expected == IFD) { + return; // it's OK + } + } + if (type != expected) { + throw new TiffException ("Type mismatch for tag " + tag + + "; expecting " + expected + ", saw " + + type); + } + } + + /** + * Check the tag entry type. + * @param tag Tag entry value + * @param type Tag entry type + * @param type1 Tag type + * @param type2 Alternate tag type + */ + protected static void checkType (int tag, int type, int type1, int type2) + throws TiffException + { + if (type != type1 && type != type2) { + throw new TiffException ("Type mismatch for tag " + tag + + "; expecting " + type1 + " or " + + type2 + ", saw " + type); + } + } + + protected static Rational average (Rational r1, Rational r2) + { + long d1 = r1.getDenominator (); + long d2 = r2.getDenominator (); + + Rational f1 = new Rational (r1.getNumerator ()*d2, + r1.getDenominator ()*d2); + Rational f2 = new Rational (r2.getNumerator ()*d1, + r2.getDenominator ()*d1); + + return new Rational ((f1.getNumerator () + f2.getNumerator ())/2, + f1.getDenominator ()); + } + + + /****************************************************************** + * PRIVATE INSTANCE METHODS. + ******************************************************************/ + + /** Represent a byte value as %XX */ + private String byteToHex (byte c) { + int[] nibbles = new int[2]; + nibbles[0] = ((int) c & 0XF0) >> 4; + nibbles[1] = (int) c & 0X0F; + StringBuffer retval = new StringBuffer ("%"); + for (int i = 0; i <= 1; i++) { + int b = nibbles[i]; + if (b >= 10) { + b += (int) 'A' - 10; + } + else { + b += (int) '0'; + } + retval.append ((char) b); + } + return retval.toString(); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/InteroperabilityIFD.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/InteroperabilityIFD.java new file mode 100644 index 00000000..b61d7c14 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/InteroperabilityIFD.java @@ -0,0 +1,87 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; +import java.io.*; +import java.util.*; + +/** + * Encapsulation of an Exif Interoperability IFD (for Exif). + */ +public class InteroperabilityIFD + extends IFD +{ + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + + /** InteroperabilityIndex tag. */ + private static final int INTEROPERABILITYINDEX = 1; + + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + /** Interoperability identification tag (1). */ + private String _interoperabilityIndex; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** Instantiate an InteroperabilityIFD object. + * @param offset IFD offset + * @param info the RepInfo object + * @param raf TIFF file + * @param bigEndian True if big-endian file + */ + public InteroperabilityIFD (long offset, RepInfo info, + RandomAccessFile raf, boolean bigEndian) + { + super (offset, info, raf, bigEndian); + } + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + ******************************************************************/ + + /** Get the InteroperabilityIndex tag (1). */ + public String getInteroperabilityIndex () + { + return _interoperabilityIndex; + } + + /** Get the IFD properties. */ + public Property getProperty (boolean rawOutput) + { + List entries = new LinkedList (); + entries.add (new Property ("Index", PropertyType.STRING, + _interoperabilityIndex)); + + return propertyHeader ("Exif Interoperability", entries); + } + + /** Lookup an IFD tag. */ + public void lookupTag (int tag, int type, long count, long value) + throws TiffException + { + try { + if (tag == INTEROPERABILITYINDEX) { + checkType (tag, type, ASCII); + _interoperabilityIndex = readASCII (count, value); + } + else { + _info.setMessage (new ErrorMessage ("Unknown Exif " + + "Interoperability IFD tag", + "Tag = " + tag, value)); + } + } + catch (IOException e) { + throw new TiffException ("Read error for tag " + tag, value); + } + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffException.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffException.java new file mode 100644 index 00000000..4d94a776 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffException.java @@ -0,0 +1,40 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +/** + * Exception subclass used internally by the TIFF module. + */ +public final class TiffException extends Exception +{ + private long _offset; // File offset at which the exception occurred + + /** + * Create a TiffException. + */ + public TiffException (String m) + { + super(m); + _offset = -1; + } + + /** + * Create a TiffException with specified offset. + */ + public TiffException (String m, long offset) + { + super(m); + _offset = offset; + } + + /** + * Returns the offset at which the exception occurred. + */ + public long getOffset () + { + return _offset; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffFXBase.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffFXBase.java new file mode 100644 index 00000000..3e3a2dcb --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffFXBase.java @@ -0,0 +1,93 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * + * Base class for all profiles under TIFF/FX. + * All TIFF/FX profiles should call TiffFXBase.satisfiesClass + * to establish that common requirements are met. + * + * @author Gary McGath + * + */ +public abstract class TiffFXBase extends TiffProfile { + + /** Tiff/FX-specific tags. */ + public static final int + GLOBALPARAMETERSIFD = 400, + STRIPROWCOUNTS = 559; + + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + + /* Conversion table from units/inch to units/cm */ + private int[] cmInchTab[] = { + {80, 204}, + {160, 408}, + {38, 98}, // really 38.5 + {77, 196}, + {154, 391} + }; + + /** + * Test for common requirements of all Tiff/FX profiles. + * Subclasses should call satisfiesClass() from their + * satisfiesThisProfile() method to avoid redundant code. + * If this method returns false, the IFD does not + * meet the requirements of any TIFF/FX profile. Calling this + * also guarantees that the image length, image width, + * bits per sample, X resolution (sampling frequency), + * Y resolution and page number values are non-null objects. + */ + protected boolean satisfiesClass (TiffIFD ifd) + { + /* Check required tags. */ + NisoImageMetadata niso = ifd.getNisoImageMetadata (); + if (niso.getImageLength () == NisoImageMetadata.NULL || + niso.getStripOffsets () == null || + niso.getImageWidth () == NisoImageMetadata.NULL || + niso.getBitsPerSample () == null || + niso.getColorSpace () == NisoImageMetadata.NULL || + niso.getCompressionScheme () == NisoImageMetadata.NULL || + // colorSpace == photometricInterpretation + niso.getXSamplingFrequency () == null || + niso.getYSamplingFrequency () == null || + ifd.getNewSubfileType () == IFD.NULL || + ifd.getPageNumber () == null) { + return false; + } + + // If compression method is 3, T4 options must be specified + if (niso.getCompressionScheme () == 3) { + if (ifd.getT4Options () == IFD.NULL) { + return false; + } + } + return true; // placeholder + } + + /** + * Convert a units/cm value to a units/inch value. + * For expected values, we use a table lookup to avoid + * rounding problems. If a value isn't in the table, + * we do a rounded conversion. + */ + protected int perCMtoPerInch (int res) + { + for (int i = 0; i < cmInchTab.length; i++) { + int[] pair = cmInchTab[i]; + if (pair[0] == res) { + return pair[1]; + } + } + // No table match; use rounding. + return (int) ((res * 2.54) + 0.5); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffIFD.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffIFD.java new file mode 100644 index 00000000..e0f9bc15 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffIFD.java @@ -0,0 +1,3967 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; +import java.io.*; +import java.util.*; +import org.xml.sax.XMLReader; +import org.xml.sax.SAXException; +import javax.xml.parsers.SAXParserFactory; + +/** + * Encapsulation of standard TIFF IFD. + */ +public class TiffIFD + extends IFD +{ + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + + /** Standard TIFF 6.0 tags. */ + public static final int + NEWSUBFILETYPE = 254, + SUBFILETYPE = 255, + IMAGEWIDTH = 256, + IMAGELENGTH = 257, + BITSPERSAMPLE = 258, + COMPRESSION = 259, + PHOTOMETRICINTERPRETATION = 262, + THRESHHOLDING = 263, + CELLWIDTH = 264, + CELLLENGTH = 265, + FILLORDER = 266, + DOCUMENTNAME = 269, + IMAGEDESCRIPTION = 270, + MAKE = 271, + MODEL = 272, + STRIPOFFSETS = 273, + ORIENTATION = 274, + SAMPLESPERPIXEL = 277, + ROWSPERSTRIP = 278, + STRIPBYTECOUNTS = 279, + MINSAMPLEVALUE = 280, + MAXSAMPLEVALUE = 281, + XRESOLUTION = 282, + YRESOLUTION = 283, + PLANARCONFIGURATION = 284, + PAGENAME = 285, + XPOSITION = 286, + YPOSITION = 287, + FREEOFFSETS = 288, + FREEBYTECOUNTS = 289, + GRAYRESPONSEUNIT = 290, + GRAYRESPONSECURVE = 291, + T4OPTIONS = 292, + T6OPTIONS = 293, + RESOLUTIONUNIT = 296, + PAGENUMBER = 297, + TRANSFERFUNCTION = 301, + SOFTWARE = 305, + DATETIME = 306, + ARTIST = 315, + HOSTCOMPUTER = 316, + PREDICTOR = 317, + WHITEPOINT = 318, + PRIMARYCHROMATICITIES = 319, + COLORMAP = 320, + HALFTONEHINTS = 321, + TILEWIDTH = 322, + TILELENGTH = 323, + TILEOFFSETS = 324, + TILEBYTECOUNTS = 325, + INKSET = 332, + INKNAMES = 333, + NUMBEROFINKS = 334, + DOTRANGE = 336, + TARGETPRINTER = 337, + EXTRASAMPLES = 338, + SAMPLEFORMAT = 339, + SMINSAMPLEVALUE = 340, + SMAXSAMPLEVALUE = 341, + TRANSFERRANGE = 342, + JPEGPROC = 512, + JPEGINTERCHANGEFORMAT = 513, + JPEGINTERCHANGEFORMATLENGTH = 514, + JPEGRESTARTINTERVAL = 515, + JPEGLOSSLESSPREDICTORS = 517, + JPEGPOINTTRANSFORMS = 518, + JPEGQTABLES = 519, + JPEGDCTABLES = 520, + JPEGACTABLES = 521, + YCBCRCOEFFICIENTS = 529, + YCBCRSUBSAMPLING = 530, + YCBCRPOSITIONING = 531, + REFERENCEBLACKWHITE = 532, + COPYRIGHT = 33432; + + /** Fill order tag (266) labels. */ + private static final String [] FILLORDER_L = { + "", "high-order", "low-order" + }; + /** Indexed tag (346) labels. */ + private static final String [] INDEXED_L = { + "not indexed", "indexed" + }; + /** InkSet tag (332) labels. */ + private static final String [] INKSET_L = { + "", "CMYK", "not CMYK" + }; + /** JPEGLosslessPredictors tag (517) labels. */ + private static final String [] JPEGLOSSLESSPREDICTORS_L = { + "", "A", "B", "C", "A+B+C", "A+((B-C)/2)", "B+((A-C)/2)", "(A+B)/2" + }; + /** JPEGProc tag (512) labels. */ + private static final String [] JPEGPROC_L = { + "baseline sequential process", "lossless process with Huffman coding" + }; + private static final int [] JPEGPROC_INDEX = { + 1, 14 + }; + /** NewSubfileType tag (254) bit labels. */ + private static final String [] NEWSUBFILETYPE_L = { + "reduced-resolution image of another image in this file", + "single page of multi-page image", + "transparency mask for another image in this file" + }; + /** OPIProxy tag (351) labels. */ + private static final String [] OPIPROXY_L = { + "no higher-resolution version exists", + "higher-resolution version exists" + }; + /** Predictor tag (317) labels. */ + private static final String [] PREDICTOR_L = { + "", "no prediction scheme", "horizontal differencing" + }; + /** SampleFormat tag (339) labels. */ + private static final String [] SAMPLEFORMAT_L = { + "", "unsigned integer", "signed integer", "IEEE floating point", + "undefined" + }; + /** SubfileType tag (255) labels. */ + private static final String [] SUBFILETYPE_L = { + "", "full-resolution image", "reduced-resolution image", + "single page of multi-page image" + }; + /** Threshholding tag (263) labels. */ + private static final String [] THRESHHOLDING_L = { + "", "no dithering or halftoning", "ordered dithering or halftoning", + "randomized process" + }; + /** YCbCrPositioning tag (531) labels. */ + private static final String [] YCBCRPOSITIONING_L = { + "", "centered", "cosited" + }; + /** YCbCrSubSampling tag (530) labels. */ + private static final String [] YCBCRSUBSAMPLING_HORZ = { + "", "width of chroma image is equal to width of associated luma image", + "width of chroma image is 1/2 the width of associated luma image", "", + "width of chroma image is 1/4 the width of associated luma image" + }; + private static final String [] YCBCRSUBSAMPLING_VERT = { + "", + "length of chroma image is equal to length of associated luma image", + "length of chroma image is 1/2 the length of associated luma image", + "", + "length of chroma image is 1/4 the length of associated luma image" + }; + + /** TIFF/IT tags. */ + private static final int + SITE = 34016, + COLORSEQUENCE = 34017, + IT8HEADER = 34018, + RASTERPADDING = 34019, + BITSPERRUNLENGTH = 34020, + BITSPEREXTENDEDRUNLENGTH = 34021, + COLORTABLE = 34022, + IMAGECOLORINDICATOR = 34023, + BACKGROUNDCOLORINDICATOR = 34024, + IMAGECOLORVALUE = 34025, + BACKGROUNDCOLORVALUE = 34026, + PIXELINTENSITYRANGE = 34027, + TRANSPARENCYINDICATOR = 34028, + COLORCHARACTERIZATION = 34029, + HCUSAGE = 34030; + + public static final String[] BACKGROUNDCOLORINDICATOR_L = { + "background not defined", "Background color defined", + "full transparency, background color not defined" + }; + public static final String [] HCUSAGE_L = { + "high resolution CT contone information", + "line art (line work) information", "trapping information" + }; + public static final String[] IMAGECOLORINDICATOR_L = { + "image not defined", "image color defined", + "full transparency, image color not defined" + }; + /* RasterPadding tag (34019) labels */ + private static final String [] RASTERPADDING_L = { + "1 byte", "2 bytes", "4 bytes", "512 bytes", "1024 bytes" + }; + private static final int [] RASTERPADDING_INDEX = { + 0, 1, 2, 9, 10 + }; + public static final String [] TRANSPARENCYINDICATOR_L = { + "no transparency", "transparency used" + }; + + /** TIFF/EP tags. */ + private static final int + CFAREPEATPATTERNDIM = 33421, + CFAPATTERN = 33422, + BATTERYLEVEL = 33423, + EXPOSURETIME = 33434, + FNUMBER = 33437, + IPTCNAA = 33723, + ICC_PROFILE = 34675, + EXPOSUREPROGRAM = 34850, + SPECTRALSENSITIVITY = 34852, + ISOSPEEDRATINGS = 34855, + OECF = 34856, + INTERLACE = 34857, + TIMEZONEOFFSET = 34858, + SELFTIMERMODE = 34859, + DATETIMEORIGINAL = 36867, + COMPRESSEDBITSPERPIXEL = 37122, + SHUTTERSPEEDVALUE = 37377, + APERTUREVALUE = 37378, + BRIGHTNESSVALUE = 37379, + EXPOSUREBIASVALUE = 37380, + MAXAPERTUREVALUE = 37381, + SUBJECTDISTANCE = 37382, + METERINGMODE = 37383, + LIGHTSOURCE = 37384, + FLASH = 37385, + FOCALLENGTH = 37386, + FLASHENERGY = 37387, + SPATIALFREQUENCYRESPONSE = 37388, + NOISE = 37389, + FOCALPLANEXRESOLUTION = 37390, + FOCALPLANEYRESOLUTION = 37391, + FOCALPLANERESOLUTIONUNIT = 37392, + IMAGENUMBER = 37393, + SECURITYCLASSIFICATION = 37394, + IMAGEHISTORY = 37395, + SUBJECTLOCATION = 37396, + EXPOSUREINDEX = 37397, + TIFFEPSTANDARDID = 37398, + SENSINGMETHOD = 37399; + /** TIFF/EP tag labels. */ + private static final String [] EXPOSUREPROGRAM_L = { + "unidentified", "manual", "program normal", "aperature priority", + "shutter priority", "program creative", "program action", + "portrait mode", "landscape mode" + }; + private static final String [] FLASH_L = { + "did not fire", "fired", "fired, return not sensed", + "fired, return sensed", + "fired, fill flash mode, camera has no flash return sensing capability", + "fired, fill flash mode, return not sensed", + "fired, fill flash mode, return sensed", + "did not fire, flash 'off' mode", + "did not fire, 'auto' mode", + "fired, 'auto' mode, camera has no flash return sensing capability", + "fired, 'auto' mode, return not sensed", + "fired, 'auto' mode, return sensed", + "camera does not have a flash unit" + }; + private static final int [] FLASH_INDEX = { + 0, 1, 5, 7, 9, 13, 15, 16, 24, 25, 29, 31, 32 + }; + private static final String [] FOCALPLANERESOLUTIONUNIT_L = { + "", "inch", "metre", "centimetre", "millimetre", "micrometre" + }; + + /** Exif tags. */ + private static final int + EXIFIFD = 34665, + GPSINFOIFD = 34853, + INTEROPERABILITYIFD = 40965; + + /** GeoTIFF tags. */ + private static final int + GEOKEYDIRECTORYTAG = 34735, + GEODOUBLEPARAMSTAG = 34736, + GEOASCIIPARAMSTAG = 34737, + MODELTIEPOINTTAG = 33922, + MODELPIXELSCALETAG = 33550, + MODELTRANSFORMATIONTAG = 34264; + /** GeoTIFF key values. */ + public static final int + GTMODELTYPEGEOKEY = 1024, + GTRASTERTYPEGEOKEY = 1025, + GTCITATIONGEOKEY = 1026, + GEOGRAPHICTYPEGEOKEY = 2048, + GEOGCITATIONGEOKEY = 2049, + GEOGGEODETICDATUMGEOKEY = 2050, + GEOGPRIMEMERIDIANGEOKEY = 2051, + GEOGLINEARUNITSGEOKEY = 2052, + GEOGLINEARUNITSIZEGEOKEY = 2053, + GEOGANGULARUNITSGEOKEY = 2054, + GEOGANGULARUNITSIZEGEOKEY = 2055, + GEOGELLIPSOIDGEOKEY = 2056, + GEOGSEMIMAJORAXISGEOKEY = 2057, + GEOGSEMIMINORAXISGEOKEY = 2058, + GEOGINVFLATTENINGGEOKEY = 2059, + GEOGAZIMUTHUNITSGEOKEY = 2060, + GEOGPRIMEMERIDIANLONGGEOKEY = 2061, + PROJECTEDCSTYPEGEOKEY = 3072, + PCSCITATIONGEOKEY = 3073, + PROJECTIONGEOKEY = 3074, + PROJCOORDTRANSGEOKEY = 3075, + PROJLINEARUNITSGEOKEY = 3076, + PROJLINEARUNITSIZEGEOKEY = 3077, + PROJSTDPARALLEL1GEOKEY = 3078, + PROJSTDPARALLEL2GEOKEY = 3079, + PROJNATORIGINLONGGEOKEY = 3080, + PROJNATORIGINLATGEOKEY = 3081, + PROJFALSEEASTINGGEOKEY = 3082, + PROJFALSENORTHINGGEOKEY = 3083, + PROJFALSEORIGINLONGGEOKEY = 3084, + PROJFALSEORIGINLATGEOKEY = 3085, + PROJFALSEORIGINEASTINGGEOKEY = 3086, + PROJFALSEORIGINNORTHINGGEOKEY = 3087, + PROJCENTERLONGGEOKEY = 3088, + PROJCENTERLATGEOKEY = 3089, + PROJCENTEREASTINGGEOKEY = 3090, + PROJFALSEORIGINNORTHINGGEOKEY_2 = 3091, + PROJSCALEATNATORIGINGEOKEY = 3092, + PROJSCALEATCENTERGEOKEY = 3093, + PROJAZIMUTHANGLEGEOKEY = 3094, + PROJSTRAIGHTVERTPOLELONGEOKEY = 3095, + VERTICALCSTYPEGEOKEY = 4096, + VERTICALCITATIONGEOKEY = 4097, + VERTICALDATUMGEOKEY = 4098, + VERTICALUNITSGEOKEY = 4099; + + /** PageMaker 6.0 tags. */ + private static final int + SUBIFDS = 330, + CLIPPATH = 343, + XCLIPPATHUNITS = 344, + YCLIPPATHUNITS = 345, + INDEXED = 346, + OPIPROXY = 351, + IMAGEID = 32781; + + /** Photoshop 'Advanced Tiff' tags. */ + private static final int + JPEGTABLES = 347, + IMAGESOURCEDATA = 37724; + + /** More Photoshop TIFF tags. + */ + private static final int + PHOTOSHOPPROPS = 34377, + ANNOTATIONS = 50255; + + /** Class F tags. */ + private static final int + BADFAXLINES = 326, + CLEANFAXDATA = 327, + CONSECUTIVEBADFAXLINES = 328; + + /** XMP tag. */ + private static final int + XMP = 700; + + /** TIFF/FX tags. */ + private static final int + GLOBALPARAMETERSIFD = 400, + STRIPROWCOUNTS = 559, + IMAGELAYER = 34732; + + public static final String [] IMAGELAYER_L = { + "", "Background", "Mask", "Foreground" + }; + + + /** DNG tags. */ + private static final int + DNGVERSION = 50706, + DNGBACKWARDVERSION = 50707, + UNIQUECAMERAMODEL = 50708, + LOCALIZEDCAMERAMODEL = 50709, + CFAPLANECOLOR = 50710, + CFALAYOUT = 50711, + LINEARIZATIONTABLE = 50712, + BLACKLEVELREPEATDIM = 50713, + BLACKLEVEL = 50714, + BLACKLEVELDELTAH = 50715, + BLACKLEVELDELTAV = 50716, + WHITELEVEL = 50717, + DEFAULTSCALE = 50718, + DEFAULTCROPORIGIN = 50719, + DEFAULTCROPSIZE = 50720, + COLORMATRIX1 = 50721, + COLORMATRIX2 = 50722, + CAMERACALIBRATION1 = 50723, + CAMERACALIBRATION2 = 50724, + REDUCTIONMATRIX1 = 50725, + REDUCTIONMATRIX2 = 50726, + ANALOGBALANCE = 50727, + ASSHOTNEUTRAL = 50728, + ASSHOTWHITEXY = 50729, + BASELINEEXPOSURE = 50730, + BASELINENOISE = 50731, + BASELINESHARPNESS = 50732, + BAYERGREENSPLIT = 50733, + LINEARRESPONSELIMIT = 50734, + CAMERASERIALNUMBER = 50735, + LENSINFO = 50736, + CHROMABLURRADIUS = 50737, + ANTIALIASSTRENGTH = 50738, + SHADOWSCALE = 50739, // Undocumented tag + DNGPRIVATEDATA = 50740, + MAKERNOTESAFETY = 50741, + CALIBRATIONILLUMINANT1 = 50778, + CALIBRATIONILLUMINANT2 = 50779, + BESTQUALITYSCALE = 50780; + + public static final String [] CFALAYOUT_L = { + "", "Rectangular", "Staggered Layout A", "Staggered Layout B", + "Staggered Layout C", "Staggered Layout D" + }; + public static final String[] MAKERNOTESAFETY_L = { + "Unsafe", "Safe" + }; + + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ + + /** NISO Z39.87/AIIM 20-2002 image metadata. */ + private NisoImageMetadata _niso; + + /** NewSubfileType tag (254). */ + private long _newSubfileType; + /** SubfileType tag (255). */ + private int _subfileType; + /** PhotometricInterpretation tag (262). */ + private int _photometricInterpretation; + /** Threshholding tag (263). */ + private int _threshholding; + /** Cell width tag (264). */ + private int _cellWidth; + /** Cell length tag (265). */ + private int _cellLength; + /** Fill order tag (266). */ + private int _fillOrder; + /** Document name tag (269). */ + private String _documentName; + /** Image description tag (270). */ + private String _imageDescription; + /** Minimum sample value tag (280). */ + private int [] _minSampleValue; + /** Maximum sample value tag (281). */ + private int [] _maxSampleValue; + /** Page name tag (285). */ + private String _pageName; + /** X position tag (286). */ + private Rational _xPosition; + /** Y position tag (287). */ + private Rational _yPosition; + /** Free offsets tag (288). */ + private long [] _freeOffsets; + /** Free byte counts tag (289). */ + private long [] _freeByteCounts; + /** CCITT Group 3 compression options tag (292). */ + private long _t4Options; + /** CCITT Group 4 compression options tag (293). */ + private long _t6Options; + /** Page number tag (297). */ + private int [] _pageNumber; + /** Transfer function tag (301). */ + private boolean _transferFunction; + /** Date/time tag (306). */ + private String _dateTime; + /** Compression differencing predictor tag (317). */ + private int _predictor; + /** Halftone hints tag (321). */ + private int [] _halftoneHints; + /** Bad fax lines tag (326). */ + private long _badFaxLines; + /** Clean fax data tag (327). */ + private short _cleanFaxData; + /** Consecutive bad fax lines tag (328). */ + private long _consecutiveBadFaxLines; + /** Ink set tag (332). */ + private int _inkSet; + /** InkNames tag (322). */ + private String [] _inkNames; + /** Sub IFDs tag (330). */ + private long [] _subIFDs; + /** Number of inks tag (334). */ + private int _numberOfInks; + /** Dot range tag (336). */ + private int [] _dotRange; + /** Target printer tag (337). */ + private String _targetPrinter; + /** Sample format tag (339). */ + private int [] _sampleFormat; + /** Transfer range tag (342). */ + private int [] _transferRange; + /** Clip path tag (343). */ + private int [] _clipPath; + /** X clip path units tag (344). */ + private long _xClipPathUnits; + /** Y clip path units tag (345). */ + private long _yClipPathUnits; + /** Indexed tag (346). */ + private int _indexed; + /** JPEG tables tag (347). */ + private int [] _jpegTables; + /** OPI proxy tag (351). */ + private int _opiProxy; + /** JPEG Proc tag (512). */ + private int _jpegProc; + /** JPEG interchange format tag (513). */ + private long _jpegInterchangeFormat; + /** JPEG interchange format length tag (514). */ + private long _jpegInterchangeFormatLength; + /** JPEG restart interval tag (515). */ + private int _jpegRestartInterval; + /** JPEG lossless predictors tag (517). */ + private int [] _jpegLosslessPredictors; + /** JPEG point transforms tag (518). */ + private int [] _jpegPointTransforms; + /** JPEG Q tables tag (519). */ + private long [] _jpegQTables; + /** JPEG DC tables tag (520). */ + private long [] _jpegDCTables; + /** JPEG AC tables tag (521). */ + private long [] _jpegACTables; + /** Copyright tag (33432). */ + private String _copyright; + /** Exif IFD tag (34665). */ + private long _exifIFD; + /** GPSInfo IFD tag (34853). */ + private long _gpsInfoIFD; + /** GlobalParametersIFD tag (400). */ + private long _globalParametersIFD; + /** Photoshop Properties tag (34377). */ + private int[] _photoshopProperties; + /** ImageSourceData tag (37724). */ + private int [] _imageSourceData; + /** Exif Interoperability IFD tag (40965). */ + private long _interoperabilityIFD; + /** Annotations tag (50255). */ + private int[] _annotations; + + /* TIFF/IT tags. */ + private int _backgroundColorIndicator; + private int _backgroundColorValue; + private int _bitsPerExtendedRunLength; + private int _bitsPerRunLength; + private String _colorCharacterization; + private String _colorSequence; + private int [] _colorTable; + private long _hcUsage; + private int _imageColorIndicator; + private int _imageColorValue; + private String _it8Header; + private int [] _pixelIntensityRange; + private int _rasterPadding; + private String _site; + private int _transparencyIndicator; + + /* TIFF/EP tags. */ + private Rational _aperatureValue; + private String _batteryLevel; + private int [] _cfaRepeatPatternDim; + private int [] _cfaPattern; + private Rational _compressedBitsPerPixel; + private int _exposureProgram; + private int _flash; + private int _focalPlaneResolutionUnit; + private Rational _focalPlaneXResolution; + private Rational _focalPlaneYResolution; + private int [] _interColourProfile; + private String _imageHistory; + private long _imageNumber; + private int _interlace; + private long [] _iptc; + private int [] _isoSpeedRatings; + private Rational _maxAperatureValue; + private int [] _noise; + private int [] _oecf; + private String _securityClassification; + private int _selfTimerMode; + private Rational _shutterSpeedValue; + private int [] _spatialFrequencyResponse; + private String _spectralSensitivity; + private int [] _subjectLocation; + private String _tiffEPStandardID; + private int [] _timeZoneOffset; + + /* GeoTIFF tags. */ + private String _geoAsciiParamsTag; + private double[] _geoDoubleParamsTag; + private int [] _geoKeyDirectoryTag; + private double[] _modelPixelScaleTag; + private double[] _modelTiepointTag; + private double[] _modelTransformationTag; + + /* XMP property. */ + private Property _xmpProp; + + /* Tiff/FX tag values. */ + private long[] _stripRowCounts; + private int[] _imageLayer; + + /** Exif IFD object. */ + private ExifIFD _theExifIFD; + /** GPSInfo IFD object. */ + private GPSInfoIFD _theGPSInfoIFD; + /** Exif Interoperability IFD. */ + private InteroperabilityIFD _theInteroperabilityIFD; + /** GlobalParameters IFD. */ + private GlobalParametersIFD _theGlobalParametersIFD; + + /* DNG tag values. The spec says that some of these tags go into + * a "raw IFD," which isn't defined. Until this is explained, + * throw it all in here. */ + private int[] _dngVersion; + private int[] _dngBackwardVersion; + private String _uniqueCameraModel; + private String _localizedCameraModel; // Note: This is specified as Unicode + private int[] _cfaPlaneColor; + private int _cfaLayout; + private int[] _linearizationTable; + private int[] _blackLevelRepeatDim; + // BlackLevel can be SHORT or LONG or RATIONAL. + // To avoid having to store multiple versions, we will convert + // SHORT or LONG values to RATIONAL. + private Rational[] _blackLevel; + private Rational[] _blackLevelDeltaH; + private Rational[] _blackLevelDeltaV; + // Though BlackLevel can be RATIONAL, WhiteLevel can't. + // There must be a rational explanation. + private long[] _whiteLevel; + private Rational[] _defaultScale; + private Rational _bestQualityScale; + private Rational[] _defaultCropOrigin; + private Rational[] _defaultCropSize; + private int _calibrationIlluminant1; + private int _calibrationIlluminant2; + private Rational[] _colorMatrix1; + private Rational[] _colorMatrix2; // for calculating revolutions, no doubt + private Rational[] _cameraCalibration1; + private Rational[] _cameraCalibration2; + private Rational[] _reductionMatrix1; + private Rational[] _reductionMatrix2; + private Rational[] _analogBalance; + private Rational[] _asShotNeutral; + private Rational[] _asShotWhiteXY; + private Rational _baselineExposure; + private Rational _baselineNoise; + private Rational _baselineSharpness; + private int _bayerGreenSplit; + private Rational _linearResponseLimit; + private String _cameraSerialNumber; + private Rational[] _lensInfo; + private Rational _chromaBlurRadius; + private Rational _antiAliasStrength; + private int[] _dngPrivateData; + private int _makerNoteSafety; + + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** Instantiate an TiffIFD object. + * @param offset IFD offset + * @param info The RepInfo object + * @param raf TIFF file + * @param bigEndian True if big-endian file + */ + public TiffIFD (long offset, RepInfo info, RandomAccessFile raf, + boolean bigEndian) + { + super (offset, info, raf, bigEndian); + + /* Define a NISO metadata object and set defaults. */ + _niso = new NisoImageMetadata (); + _niso.setMimeType("image/tiff"); + _niso.setCompressionScheme (1); + _niso.setOrientation (1); + _niso.setPlanarConfiguration (1); + _niso.setRowsPerStrip (4294967295L); + _niso.setSamplesPerPixel (1); + _niso.setByteOrder(bigEndian ? "big-endian" : "little-endian"); + + /* Set non-NISO defaults. */ + _photometricInterpretation = NULL; + _cellLength = NULL; + _cellWidth = NULL; + _fillOrder = NULL; + _indexed = 0; + _inkSet = NULL; + _jpegInterchangeFormat = NULL; + _jpegInterchangeFormatLength = NULL; + _jpegProc = NULL; + _jpegRestartInterval = NULL; + _newSubfileType = 0L; + _numberOfInks = NULL; + _opiProxy = NULL; + _predictor = NULL; + _subfileType = NULL; + _t4Options = NULL; + _t6Options = NULL; + _threshholding = 1; + _xClipPathUnits = NULL; + _yClipPathUnits = NULL; + + /* TIFF/IT defaults. */ + _backgroundColorIndicator = 0; + _backgroundColorValue = NULL; + _bitsPerExtendedRunLength = 16; + _bitsPerRunLength = 8; + _hcUsage = NULL; + _imageColorIndicator = 0; + _imageColorValue = NULL; + _rasterPadding = 0; + _transparencyIndicator = 0; + + /* TIFF/EP defaults. */ + _exposureProgram = NULL; + _flash = NULL; + _focalPlaneResolutionUnit = NULL; + _gpsInfoIFD = NULL; + _imageNumber = NULL; + _selfTimerMode = NULL; + + /* Exif defaults. */ + _exifIFD = NULL; + _focalPlaneResolutionUnit = NULL; + _imageNumber = NULL; + _interlace = NULL; + _interoperabilityIFD = NULL; + _globalParametersIFD = NULL; + + /* Class F/RFC 1324 defaults. */ + _badFaxLines = NULL; + _cleanFaxData = NULL; + _consecutiveBadFaxLines = NULL; + + /* XMP default. */ + _xmpProp = null; + + /* Tiff/FX defaults. */ + _stripRowCounts = null; + _imageLayer = null; + + /* DNG defaults. */ + _dngVersion = null; + _dngBackwardVersion = null; + _uniqueCameraModel = null; + _localizedCameraModel = null; + _cfaPlaneColor = null; + _cfaLayout = NULL; + _linearizationTable = null; + _blackLevelRepeatDim = null; + _blackLevel = null; + _blackLevelDeltaH = null; + _blackLevelDeltaV = null; + _whiteLevel = null; + _defaultScale = null; + _bestQualityScale = null; + _defaultCropOrigin = null; + _defaultCropSize = null; + _calibrationIlluminant1 = NULL; + _calibrationIlluminant2 = NULL; + _colorMatrix1 = null; + _colorMatrix2 = null; + _cameraCalibration1 = null; + _cameraCalibration2 = null; + _reductionMatrix1 = null; + _reductionMatrix2 = null; + _analogBalance = null; + _asShotNeutral = null; + _asShotWhiteXY = null; + _baselineExposure = null; + _baselineNoise = null; + _baselineSharpness = null; + _bayerGreenSplit = NULL; + _linearResponseLimit = null; + _cameraSerialNumber = null; + _lensInfo = null; + _chromaBlurRadius = null; + _antiAliasStrength = null; + _dngPrivateData = null; + _makerNoteSafety = NULL; + } + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + ******************************************************************/ + + /** Returns the value of the APERTUREVALUE (37378) tag. Note typo in + * function name. */ + public Rational getAperatureValue () + { + return _aperatureValue; + } + + /** Returns the value of the TIFF/IT BACKGROUNDCOLORINDICATOR + * (34024) tag. */ + public int getBackgroundColorIndicator () + { + return _backgroundColorIndicator; + } + + /** Returns the value of the BACKGROUNDCOLORVALUE + * (34026) tag. */ + public int getBackgroundColorValue () + { + return _backgroundColorValue; + } + + /** Returns the value of the BATTERYLEVEL (33423) tag. */ + public String getBatteryLevel () + { + return _batteryLevel; + } + + /** Returns the value of the BITSPEREXTENDEDRUNLENGTH + * (34021) tag. */ + public int getBitsPerExtendedRunLength () + { + return _bitsPerExtendedRunLength; + } + + /** Returns the value of the BITSPERRUNLENGTH (34020) tag. */ + public int getBitsPerRunLength () + { + return _bitsPerRunLength; + } + + /** Returns the value of the CELLLENGTH (265) tag. */ + public int getCellLength () + { + return _cellLength; + } + + /** Returns the value of the CELLWIDTH (264) tag. */ + public int getCellWidth () + { + return _cellWidth; + } + + /** Returns the value of the CFAPATTERN (33422) tag. */ + public int [] getCFAPattern () + { + return _cfaPattern; + } + + /** Returns the value of the CFAREPEATPATTERNDIM + * (33421) tag. */ + public int [] getCFARepeatPatternDim () + { + return _cfaRepeatPatternDim; + } + + /** Returns the value of the CLIPPATH (343) tag. */ + public int [] getClipPath () + { + return _clipPath; + } + + /** Returns the value of the COLORSEQUENCE + * (34017) tag. */ + public String getColorSequence () + { + return _colorSequence; + } + + /** Returns the value of the COLORTABLE (34022) tag. */ + public int [] getColorTable () + { + return _colorTable; + } + + /** Returns the value of the COMPRESSEDBITSPERPIXEL + * (37122) tag. */ + public Rational getCompressedBitsPerPixel () + { + return _compressedBitsPerPixel; + } + + /** Returns the value of the COPYRIGHT (33432) tag. */ + public String getCopyright () + { + return _copyright; + } + + /** Returns the value of the DATETIME (306) tag. */ + public String getDateTime () + { + return _dateTime; + } + + /** Returns the value of the DOCUMENTNAME (269) tag. */ + public String getDocumentName () + { + return _documentName; + } + + /** Returns the value of the DOTRANGE (336) tag. */ + public int [] getDotRange () + { + return _dotRange; + } + + /** Return the offset of the Exif IFD. */ + public long getExifIFD () + { + return _exifIFD; + } + + /** Return the offset of the GlobalParameters IFD. */ + public long getGlobalParametersIFD () + { + return _globalParametersIFD; + } + + /** Returns the value of the EXPOSUREPROGRAM (34850) tag. */ + public int getExposureProgram () + { + return _exposureProgram; + } + + /** Returns the value of the FILLORDER (266) tag. */ + public int getFillOrder () + { + return _fillOrder; + } + + /** Returns the value of the FOCALPLANERESOLUTIONUNIT + * (37392) tag. */ + public int getFocalPlaneResolutionUnit () + { + return _focalPlaneResolutionUnit; + } + + /** Returns the value of the FOCALPLANEXRESOLUTION + * (37390) tag. */ + public Rational getFocalPlaneXResolution () + { + return _focalPlaneXResolution; + } + + /** Returns the value of the FOCALPLANEYRESOLUTION + * (37390) tag. */ + public Rational getFocalPlaneYResolution () + { + return _focalPlaneYResolution; + } + + /** Returns the value of the GEOKEYDIRECTORYTAG + * (34735) tag. */ + public int [] getGeoKeyDirectoryTag () + { + return _geoKeyDirectoryTag; + } + + /** Return the offset of the GPSInfo IFD. */ + public long getGPSInfoIFD () + { + return _gpsInfoIFD; + } + + /** Returns the value of the IMAGECOLORINDICATOR + * (34023) tag. */ + public int getImageColorIndicator () + { + return _imageColorIndicator; + } + + /** Returns the value of the IMAGECOLORVALUE (34025) tag. */ + public int getImageColorValue () + { + return _imageColorValue; + } + + /** Returns the value of the IMAGEDESCRIPTION (270) tag. */ + public String getImageDescription () + { + return _imageDescription; + } + + /** Returns the value of the IMAGEHISTORY (37395) tag. */ + public String getImageHistory () + { + return _imageHistory; + } + + /** Returns the value of the IMAGELAYER (34732) tag. */ + public int[] getImageLayer () + { + return _imageLayer; + } + + /** Returns the value of the IMAGENUMBER (37393) tag. */ + public long getImageNumber () + { + return _imageNumber; + } + + /** Returns the value of the IMAGESOURCEDATA + * (37724) tag. */ + public int [] getImageSourceData () + { + return _imageSourceData; + } + + /** Returns the value of the PHOTOSHOPPROPS + * (34377) tag. */ + public int [] getPhotoshopProperties () + { + return _photoshopProperties; + } + + /** Returns the value of the ANNOTATIONS + * (50255) tag. */ + public int [] getAnnotations () + { + return _annotations; + } + + /** Returns the value of the INKNAMES (333) tag. */ + public String [] getInkNames () + { + return _inkNames; + } + + /** Returns the value of the INKSET (332) tag. */ + public int getInkSet () + { + return _inkSet; + } + + /** Returns the value of the INTERLACE (34857) tag. */ + public int getInterlace () + { + return _interlace; + } + + /** Returns the offset of the Exif Interoperability IFD. */ + public long getInteroperabilityIFD () + { + return _interoperabilityIFD; + } + + /** Returns the value of the ICC_PROFILE tag. */ + public int [] getInterColourProfile () + { + return _interColourProfile; + } + + /** Returns the value of the INDEXED (364) tag. */ + public int getIndexed () + { + return _indexed; + } + + public long getJpegInterchangeFormat () + { + return _jpegInterchangeFormat; + } + + /** Returns the value of the IPTCNAA (33723) tag. */ + public long [] getIPTCNAA () + { + return _iptc; + } + + /** Returns the value of the ISOSPEEDRATINGS + * (34855) tag. */ + public int [] getISOSpeedRatings () + { + return _isoSpeedRatings; + } + + /** Returns the value of the IT8HEADER (34018) tag. */ + public String getIT8Header () + { + return _it8Header; + } + + /** Returns the value of the JPEGPROC (512) tag. */ + public int getJPEGProc () + { + return _jpegProc; + } + + /** Returns the value of the MAXAPERTUREVALUE (37381) + * tag. Note typo in function name. */ + public Rational getMaxAperatureValue () + { + return _maxAperatureValue; + } + + /** Returns the value of the MODELTIEPOINTTAG (33922) + * tag. */ + public double[] getModelTiepointTag () + { + return _modelTiepointTag; + } + + /** Returns the value of the MODELTRANSFORMATIONTAG + * (34264) tag. */ + public double[] getModelTransformationTag () + { + return _modelTransformationTag; + } + + /** Returns the value of the NEWSUBFILETYPE (254) tag. */ + public long getNewSubfileType () + { + return _newSubfileType; + } + + /** Returns the constructed NisoImageMetadata. */ + public NisoImageMetadata getNisoImageMetadata () + { + return _niso; + } + + /** Returns the value of the NOISE (37389) tag. */ + public int [] getNoise () + { + return _noise; + } + + /** Returns the value of the NUMBEROFINKS (334) tag. */ + public int getNumberOfInks () + { + return _numberOfInks; + } + + /** Returns the value of the OECF (34856) tag. */ + public int [] getOECF () + { + return _oecf; + } + + /** Returns the value of the PAGENAME (285) tag. */ + public String getPageName () + { + return _pageName; + } + + /** Returns the value of the PAGENUMBER (297) tag. */ + public int [] getPageNumber () + { + return _pageNumber; + } + + /** Returns the value of the PIXELINTENSITYRANGE (34027) tag. */ + public int [] getPixelIntensityRange () + { + return _pixelIntensityRange; + } + + /** Returns the value of the RASTERPADDING (34019) tag. */ + public int getRasterPadding () + { + return _rasterPadding; + } + + /** Returns the value of the SECURITYCLASSIFICATION (37394) tag. */ + public String getSecurityClasssification () + { + return _securityClassification; + } + + /** Returns the value of the SELFTIMERMODE (34859) tag. */ + public int getSelfTimerMode () + { + return _selfTimerMode; + } + + /** Returns the value of the SHUTTERSPEEDVALUE (37377) tag. */ + public Rational getShutterSpeedValue () + { + return _shutterSpeedValue; + } + + /** Returns the value of the SITE (34016) tag. */ + public String getSite () + { + return _site; + } + + /** Returns the value of the SPATIALFREQUENCYRESPONSE (37388) tag. */ + public int [] getSpatialFrequencyResponse () + { + return _spatialFrequencyResponse; + } + + /** Returns the value of the SPECTRALSENSITIVITY (34852) tag. */ + public String getSpectralSensitivity () + { + return _spectralSensitivity; + } + + /** Returns the value of the STRIPROWCOUNTS (559) tag. */ + public long[] getStripRowCounts () + { + return _stripRowCounts; + } + + /** Returns the value of the SUBIFDS (330) tag. */ + public long [] getSubIFDs () + { + return _subIFDs; + } + + /** Returns the value of the SUBJECTLOCATION (37396) tag. */ + public int [] getSubjectLocation () + { + return _subjectLocation; + } + + /** Returns the value of the T4OPTIONS (292) tag. */ + public long getT4Options () + { + return _t4Options; + } + + /** Returns the value of the T6OPTIONS (293) tag. */ + public long getT6Options () + { + return _t6Options; + } + + /** Returns the Exif IFD object, or null if none. */ + public ExifIFD getTheExifIFD () + { + return _theExifIFD; + } + + /** Returns the GPS info IFD object, or null if none. */ + public GPSInfoIFD getTheGPSInfoIFD () + { + return _theGPSInfoIFD; + } + + /** Returns the Interoperability IFD object, + * or null if none. */ + public InteroperabilityIFD getTheInteroperabilityIFD () + { + return _theInteroperabilityIFD; + } + + /** Returns the GlobalParameters IFD object, or null if none. */ + public GlobalParametersIFD getTheGlobalParametersIFD () + { + return _theGlobalParametersIFD; + } + + /** Returns the value of the THRESHHOLDING (263) tag. */ + public int getThreshholding () + { + return _threshholding; + } + + /** Returns the value of the TIFFEPSTANDARDID (37398) tag. */ + public String getTIFFEPStandardID () + { + return _tiffEPStandardID; + } + + /** Returns the value of the TIMEZONEOFFSET (34858) tag. */ + public int [] getTimeZoneOffset () + { + return _timeZoneOffset; + } + + /** Returns the value of the TRANSPARENCYINDICATOR (34028) tag. */ + public int getTransparencyIndicator () + { + return _transparencyIndicator; + } + + /** Returns the value of the XCLIPPATHUNITS (344) tag. */ + public long getXClipPathUnits () + { + return _xClipPathUnits; + } + + /** Returns the value of the XPOSITION (286) tag. */ + public Rational getXPosition () + { + return _xPosition; + } + + /** Returns the value of the XPOSITION (287) tag. */ + public Rational getYPosition () + { + return _yPosition; + } + + /** Returns the value of the DNGVERSION (50706) tag. */ + public int[] getDNGVersion () + { + return _dngVersion; + } + + /** Returns the value of the DNG UNIQUECAMERAMODEL (50708) tag. */ + public String getUniqueCameraModel () + { + return _uniqueCameraModel; + } + + /** Returns the value of the CFAPlaneColor (50710) tag. */ + public int[] getCFAPlaneColor () + { + return _cfaPlaneColor; + } + + /** Returns the value of the AsShotNeutral (50728) tag. */ + public Rational[] getAsShotNeutral () + { + return _asShotNeutral; + } + + /** Returns the value of the AsShotWhiteXY (50729) tag. */ + public Rational[] getAsShotWhiteXY () + { + return _asShotWhiteXY; + } + + /** Get the IFD properties. */ + public Property getProperty (boolean rawOutput) + throws TiffException + { + List entries = new LinkedList (); + // This function has gotten obscenely large. Split it up. + addNisoProperties (entries, rawOutput); + addMiscProperties (entries, rawOutput); + addTiffITProperties (entries, rawOutput); + addTiffEPProperties (entries, rawOutput); + addGeoTiffProperties (entries, rawOutput); + addTiffFXProperties (entries, rawOutput); + addDNGProperties (entries, rawOutput); + return propertyHeader ("TIFF", entries); + } + + + private void addNisoProperties (List entries, boolean rawOutput) + { + entries.add (new Property ("NisoImageMetadata", + PropertyType.NISOIMAGEMETADATA, _niso)); + } + + /* Add non-NISO properties. */ + private void addMiscProperties (List entries, boolean rawOutput) + { + if (_imageDescription != null) { + entries.add (new Property ("ImageDescription", PropertyType.STRING, + _imageDescription)); + } + if (_dateTime != null) { + entries.add (new Property ("DateTime", PropertyType.STRING, + _dateTime)); + } + if (_newSubfileType != 0L || rawOutput) { + entries.add (addBitmaskProperty ("NewSubfileType", + _newSubfileType, + NEWSUBFILETYPE_L, rawOutput)); + } + else { + // if 0, always report as a raw number + entries.add (new Property ("NewSubfileType", PropertyType.LONG, + new Long (_newSubfileType))); + } + if (_subfileType != NULL && (_subfileType != 0 || rawOutput)) { + entries.add (addIntegerProperty ("SubfileType", _subfileType - 1, + SUBFILETYPE_L, rawOutput)); + } + else if (_subfileType != NULL) { + // if 0, always report as a raw number + entries.add (new Property ("SubfileType", PropertyType.LONG, + new Long (_subfileType))); + } + if (_documentName != null) { + entries.add (new Property ("DocmentName", PropertyType.STRING, + _documentName)); + } + if (_pageName != null) { + entries.add (new Property ("PageName", PropertyType.STRING, + _pageName)); + } + if (_pageNumber != null) { + entries.add (new Property ("PageNumber", PropertyType.INTEGER, + PropertyArity.ARRAY, _pageNumber)); + } + if (_xPosition != null) { + entries.add (addRationalProperty ("XPosition", _xPosition, + rawOutput)); + } + if (_yPosition != null) { + entries.add (addRationalProperty ("YPosition", _yPosition, + rawOutput)); + } + if (_copyright != null) { + entries.add (new Property ("Copyright", PropertyType.STRING, + _copyright)); + } + if (_fillOrder != NULL) { + entries.add (addIntegerProperty ("FillOrder", _fillOrder, + FILLORDER_L, rawOutput)); + } + entries.add (new Property ("SampleFormat", PropertyType.INTEGER, + PropertyArity.ARRAY, _sampleFormat)); + if (_minSampleValue != null) { + entries.add (new Property ("MinSampleValue", PropertyType.INTEGER, + PropertyArity.ARRAY, _minSampleValue)); + } + if (_maxSampleValue != null) { + entries.add (new Property ("MaxSampleValue", PropertyType.INTEGER, + PropertyArity.ARRAY, _maxSampleValue)); + } + if (_inkSet != NULL) { + entries.add (addIntegerProperty ("InkSet", _inkSet, INKSET_L, + rawOutput)); + } + if (_numberOfInks != NULL) { + entries.add (new Property ("NumberOfInks", PropertyType.INTEGER, + new Integer (_numberOfInks))); + } + if (_inkNames != null) { + entries.add (new Property ("InkNames", PropertyType.STRING, + PropertyArity.ARRAY, _inkNames)); + } + if (_dotRange != null) { + entries.add (new Property ("DotRange", PropertyType.INTEGER, + PropertyArity.ARRAY, _dotRange)); + } + if (_targetPrinter != null) { + entries.add (new Property ("TargetPrinter", PropertyType.STRING, + _targetPrinter)); + } + if (_halftoneHints != null) { + entries.add (new Property ("HalftoneHints", PropertyType.INTEGER, + PropertyArity.ARRAY, _halftoneHints)); + } + if (_cellLength != NULL) { + entries.add (new Property ("CellLength", PropertyType.INTEGER, + new Integer (_cellLength))); + } + if (_cellWidth != NULL) { + entries.add (new Property ("CellWidth", PropertyType.INTEGER, + new Integer (_cellWidth))); + } + if (_transferFunction) { + entries.add (new Property ("TransferFunction", PropertyType.BOOLEAN, + new Boolean (true))); + } + if (_transferRange != null) { + entries.add (new Property ("TransferRange", PropertyType.INTEGER, + PropertyArity.ARRAY, _transferRange)); + } + entries.add (new Property ("Threshholding", PropertyType.INTEGER, + new Integer (_threshholding))); + if (_predictor != NULL) { + entries.add (addIntegerProperty ("Predictor", _predictor, + PREDICTOR_L, rawOutput)); + } + if (_t4Options != NULL) { + entries.add (new Property ("T4Options", PropertyType.LONG, + new Long (_t4Options))); + } + if (_t6Options != NULL) { + entries.add (new Property ("T6Options", PropertyType.LONG, + new Long (_t6Options))); + } + if (_jpegProc != NULL) { + entries.add (addIntegerProperty ("JPEGProc", _jpegProc, + JPEGPROC_L, JPEGPROC_INDEX, + rawOutput)); + } + if (_jpegInterchangeFormat != NULL) { + entries.add (new Property ("JPEGInterchangeFormat", + PropertyType.LONG, + new Long (_jpegInterchangeFormat))); + } + if (_jpegInterchangeFormatLength != NULL) { + entries.add (new Property ("JPEGInterchangeFormatLength", + PropertyType.LONG, + new Long (_jpegInterchangeFormatLength))); + } + if (_jpegRestartInterval != NULL) { + entries.add (new Property ("JPEGRestartInterval", + PropertyType.INTEGER, + new Integer (_jpegRestartInterval))); + } + if (_jpegLosslessPredictors != null) { + entries.add (addIntegerArrayProperty ("JPEGLosslessPredictors", + _jpegLosslessPredictors, + JPEGLOSSLESSPREDICTORS_L, + rawOutput)); + } + if (_jpegPointTransforms != null) { + entries.add (new Property ("JPEGPointTransforms", + PropertyType.INTEGER, + PropertyArity.ARRAY, + _jpegPointTransforms)); + } + if (_jpegQTables != null) { + entries.add (new Property ("JPEGQTables", PropertyType.LONG, + PropertyArity.ARRAY, _jpegQTables)); + } + if (_jpegDCTables != null) { + entries.add (new Property ("JPEGDCTables", PropertyType.LONG, + PropertyArity.ARRAY, _jpegDCTables)); + } + if (_jpegACTables != null) { + entries.add (new Property ("JPEGACTables", PropertyType.LONG, + PropertyArity.ARRAY, _jpegACTables)); + } + if (_jpegTables != null) { + entries.add (new Property ("JPEGTables", PropertyType.INTEGER, + PropertyArity.ARRAY, _jpegTables)); + } + if (_imageSourceData != null) { + entries.add (new Property ("ImageSourceData", PropertyType.INTEGER, + PropertyArity.ARRAY, _imageSourceData)); + } + if (_photoshopProperties != null) { + entries.add (new Property ("PhotoshopProperties", PropertyType.INTEGER, + PropertyArity.ARRAY, _photoshopProperties)); + } + if (_annotations != null) { + entries.add (new Property ("Annotations", PropertyType.INTEGER, + PropertyArity.ARRAY, _annotations)); + } + if (_clipPath != null) { + entries.add (new Property ("ClipPath", PropertyType.INTEGER, + PropertyArity.ARRAY, _clipPath)); + } + if (_xClipPathUnits != NULL) { + entries.add (new Property ("XClipPathUnits", PropertyType.LONG, + new Long (_xClipPathUnits))); + } + if (_yClipPathUnits != NULL) { + entries.add (new Property ("YClipPathUnits", PropertyType.LONG, + new Long (_yClipPathUnits))); + } + if (_cleanFaxData != NULL) { + entries.add (new Property ("CleanFaxData", PropertyType.LONG, + new Long (_cleanFaxData))); + } + if (_badFaxLines != NULL) { + entries.add (new Property ("BadFaxLines", PropertyType.LONG, + new Long (_badFaxLines))); + } + if (_consecutiveBadFaxLines != NULL) { + entries.add (new Property ("ConsecutiveBadFaxLines", + PropertyType.LONG, + new Long (_consecutiveBadFaxLines))); + } + if (_freeByteCounts != null) { + entries.add (new Property ("FreeByteCounts", PropertyType.LONG, + PropertyArity.ARRAY, _freeByteCounts)); + } + if (_freeOffsets != null) { + entries.add (new Property ("FreeOffsets", PropertyType.LONG, + PropertyArity.ARRAY, _freeOffsets)); + } + } + + private void addTiffITProperties (List entries, boolean rawOutput) + { + /* Add TIFF/IT properties. */ + + List itList = new LinkedList (); + if (_site != null) { + itList.add (new Property ("Site", PropertyType.STRING, _site)); + } + if (_colorTable != null) { + itList.add (new Property ("ColorTable", PropertyType.INTEGER, + PropertyArity.ARRAY, _colorTable)); + } + itList.add (addIntegerProperty ("BackgroundColorIndicator", + _backgroundColorIndicator, + BACKGROUNDCOLORINDICATOR_L, + rawOutput)); + if (_backgroundColorValue != NULL) { + itList.add (new Property ("BackgroundColorValue", + PropertyType.INTEGER, + new Integer (_backgroundColorValue))); + } + itList.add (addIntegerProperty ("ImageColorIndicator", + _imageColorIndicator, + IMAGECOLORINDICATOR_L, rawOutput)); + itList.add (addIntegerProperty ("TransparencyIndicator", + _transparencyIndicator, + TRANSPARENCYINDICATOR_L, rawOutput)); + if (_imageColorValue != NULL) { + itList.add (new Property ("ImageColorValue", PropertyType.INTEGER, + new Integer (_imageColorValue))); + } + if (_colorCharacterization != null) { + itList.add (new Property ("ColorCharacterization", + PropertyType.STRING, + _colorCharacterization)); + } + if (_colorSequence != null) { + itList.add (new Property ("ColorSequence", PropertyType.STRING, + _colorSequence)); + } + if (_hcUsage != NULL) { + itList.add (addBitmaskProperty ("HCUsage", _hcUsage, HCUSAGE_L, + rawOutput)); + } + if (_it8Header != null) { + itList.add (new Property ("IT8Header", PropertyType.STRING, + _it8Header)); + } + if (_pixelIntensityRange != null) { + itList.add (new Property ("PixelIntensityRange", + PropertyType.INTEGER, + PropertyArity.ARRAY, + _pixelIntensityRange)); + } + itList.add (addIntegerProperty ("RasterPadding", _rasterPadding, + RASTERPADDING_L, rawOutput)); + itList.add (new Property ("BitsPerRunLength", PropertyType.INTEGER, + new Integer (_bitsPerRunLength))); + itList.add (new Property ("BitsPerExtendedRunLength", + PropertyType.INTEGER, + new Integer (_bitsPerExtendedRunLength))); + entries.add (new Property ("TIFFITProperties", PropertyType.PROPERTY, + PropertyArity.LIST, itList)); + } + + + /* Add TIFF/EP properties. */ + private void addTiffEPProperties (List entries, boolean rawOutput) + { + List epList = new LinkedList (); + if (_cfaRepeatPatternDim != null) { + epList.add (new Property ("CFARepeatPatternDim", + PropertyType.INTEGER, + PropertyArity.ARRAY, + _cfaRepeatPatternDim)); + } + if (_cfaPattern != null) { + epList.add (new Property ("CFAPattern", PropertyType.INTEGER, + PropertyArity.ARRAY, _cfaPattern)); + } + if (_batteryLevel != null) { + epList.add (new Property ("BatteryLevel", PropertyType.STRING, + _batteryLevel)); + } + if (_iptc != null) { + epList.add (new Property ("IPTCNAA", PropertyType.LONG, + PropertyArity.ARRAY, _iptc)); + } + if (_interColourProfile != null) { + epList.add (new Property ("InterColourProfile", + PropertyType.BOOLEAN, + Boolean.TRUE)); + } + if (_exposureProgram != NULL) { + epList.add (addIntegerProperty ("ExposureProgram", + _exposureProgram, + EXPOSUREPROGRAM_L, rawOutput)); + } + if (_spectralSensitivity != null) { + epList.add (new Property ("SpectralSensitivity", + PropertyType.STRING, + _spectralSensitivity)); + } + if (_isoSpeedRatings != null) { + epList.add (new Property ("ISOSpeedRatings", + PropertyType.INTEGER, + PropertyArity.ARRAY, _isoSpeedRatings)); + } + if (_oecf != null) { + epList.add (new Property ("OECF", PropertyType.INTEGER, + PropertyArity.ARRAY, _oecf)); + } + if (_interlace != NULL) { + epList.add (new Property ("Interlace", PropertyType.INTEGER, + new Integer (_interlace))); + } + if (_timeZoneOffset != null) { + epList.add (new Property ("TimeZoneOffset", PropertyType.INTEGER, + PropertyArity.ARRAY, _timeZoneOffset)); + } + if (_selfTimerMode != NULL) { + epList.add (new Property ("SelfTimerMode", PropertyType.INTEGER, + new Integer (_selfTimerMode))); + } + if (_compressedBitsPerPixel != null) { + epList.add (addRationalProperty ("CompressedBitsPerPixel", + _compressedBitsPerPixel, + rawOutput)); + } + if (_shutterSpeedValue != null) { + epList.add (addRationalProperty ("ShutterSpeedValue", + _shutterSpeedValue, rawOutput)); + } + if (_aperatureValue != null) { + epList.add (addRationalProperty ("AperatureValue", + _aperatureValue, rawOutput)); + } + if (_maxAperatureValue != null) { + epList.add (addRationalProperty ("MaxAperatureValue", + _maxAperatureValue, rawOutput)); + } + if (_flash != NULL) { + epList.add (addIntegerProperty ("FLASH", _flash, FLASH_L, + FLASH_INDEX, rawOutput)); + } + if (_spatialFrequencyResponse != null) { + epList.add (new Property ("SpatialFrequencyResponse", + PropertyType.INTEGER, + PropertyArity.ARRAY, + _spatialFrequencyResponse)); + } + if (_noise != null) { + epList.add (new Property ("Noise", PropertyType.INTEGER, + PropertyArity.ARRAY, _noise)); + } + if (_focalPlaneXResolution != null) { + epList.add (addRationalProperty ("FocalPlaneXResolution", + _focalPlaneXResolution, + rawOutput)); + } + if (_focalPlaneYResolution != null) { + epList.add (addRationalProperty ("FocalPlaneYResolution", + _focalPlaneYResolution, + rawOutput)); + } + if (_focalPlaneResolutionUnit != NULL) { + epList.add (addIntegerProperty ("FocalPlaneResolutionUnit", + _focalPlaneResolutionUnit, + FOCALPLANERESOLUTIONUNIT_L, + rawOutput)); + } + if (_imageNumber != NULL) { + epList.add (new Property ("ImageNumber", PropertyType.LONG, + new Long (_imageNumber))); + } + if (_securityClassification != null) { + epList.add (new Property ("SecurityClassification", + PropertyType.STRING, + _securityClassification)); + } + if (_imageHistory != null) { + epList.add (new Property ("ImageHistory", PropertyType.STRING, + _imageHistory)); + } + if (_subjectLocation != null) { + epList.add (new Property ("SubjectLocation", PropertyType.INTEGER, + PropertyArity.ARRAY, _subjectLocation)); + } + if (_tiffEPStandardID != null) { + epList.add (new Property ("TIFFEPSStandardID", + PropertyType.STRING, + _tiffEPStandardID)); + } + if (epList.size () > 0) { + entries.add (new Property ("TIFFEPProperties", + PropertyType.PROPERTY, + PropertyArity.LIST, epList)); + } + + if (_xmpProp != null) { + entries.add (_xmpProp); + } + } + + + private void addGeoTiffProperties (List entries, boolean rawOutput) + throws TiffException + { + /* Add GeoTIFF properties. */ + + List dirList = new LinkedList (); + if (_geoKeyDirectoryTag != null) { + dirList.add (new Property ("Version", PropertyType.INTEGER, + new Integer (_geoKeyDirectoryTag[0]))); + dirList.add (new Property ("Revision", PropertyType.STRING, + Integer.toString (_geoKeyDirectoryTag[1]) + "."+ + Integer.toString (_geoKeyDirectoryTag[2]))); + dirList.add (new Property ("NumberOfKeys", PropertyType.INTEGER, + new Integer (_geoKeyDirectoryTag[3]))); + for (int i=0; i<_geoKeyDirectoryTag[3]; i++) { + int j = i*4 + 4; + int key = _geoKeyDirectoryTag[j]; + int location = _geoKeyDirectoryTag[j+1]; + int count = _geoKeyDirectoryTag[j+2]; + int offset = _geoKeyDirectoryTag[j+3]; + + int ival = 0; + double dval = 0.0; + String sval = "NULL"; + if (location == 0) { + ival = offset; + } + else if (location == 34736) { + dval = _geoDoubleParamsTag[offset]; + } + else if (location == 34737) { + try { + sval = _geoAsciiParamsTag.substring (offset, offset + count-1); + } + catch (Exception e) { + throw new TiffException ("Invalid GeoKeyDirectory tag"); + } + } + + if (key == GTMODELTYPEGEOKEY) { + dirList.add (addIntegerProperty ("GTModelType", ival, + GeoTiffStrings.MODELTYPE, + GeoTiffStrings.MODELTYPE_INDEX, + rawOutput)); + } + else if (key == GTRASTERTYPEGEOKEY) { + dirList.add (addIntegerProperty ("GTRasterType", ival, + GeoTiffStrings.RASTERTYPE, + GeoTiffStrings.RASTERTYPE_INDEX, + rawOutput)); + } + else if (key == GTCITATIONGEOKEY) { + dirList.add (new Property ("GTCitation", + PropertyType.STRING, sval)); + } + else if (key == GEOGRAPHICTYPEGEOKEY) { + dirList.add (addIntegerProperty ("GeographicType", ival, + GeoTiffStrings.GEOGRAPHICS, + GeoTiffStrings.GEOGRAPHICS_INDEX, + rawOutput)); + } + else if (key == GEOGCITATIONGEOKEY) { + dirList.add (new Property ("GeogCitation", + PropertyType.STRING, sval)); + } + else if (key == GEOGGEODETICDATUMGEOKEY) { + dirList.add (addIntegerProperty ("GeogGeodeticDatum", ival, + GeoTiffStrings.GEODETICDATUM, + GeoTiffStrings.GEODETICDATUM_INDEX, + rawOutput)); + } + else if (key == GEOGPRIMEMERIDIANGEOKEY) { + dirList.add (addIntegerProperty ("GeogPrimeMeridian", ival, + GeoTiffStrings.PRIMEMERIDIAN, + GeoTiffStrings.PRIMEMERIDIAN_INDEX, + rawOutput)); + } + else if (key == GEOGPRIMEMERIDIANLONGGEOKEY) { + dirList.add (new Property ("GeogPrimeMeridianLong", + PropertyType.DOUBLE, + new Double (dval))); + } + else if (key == GEOGLINEARUNITSGEOKEY) { + dirList.add (addIntegerProperty ("GeogLinearUnits", ival, + GeoTiffStrings.LINEARUNITS, + GeoTiffStrings.LINEARUNITS_INDEX, + rawOutput)); + } + else if (key == GEOGLINEARUNITSIZEGEOKEY) { + dirList.add (new Property ("GeogLinearUnitSize", + PropertyType.DOUBLE, + new Double (dval))); + } + else if (key == GEOGANGULARUNITSGEOKEY) { + dirList.add (addIntegerProperty ("GeogAngularUnits", ival, + GeoTiffStrings.ANGULARUNITS, + GeoTiffStrings.ANGULARUNITS_INDEX, + rawOutput)); + } + else if (key == GEOGANGULARUNITSIZEGEOKEY) { + dirList.add (new Property ("GeogAngularUnitSize", + PropertyType.DOUBLE, + new Double (dval))); + } + else if (key == GEOGELLIPSOIDGEOKEY) { + dirList.add (addIntegerProperty ("GeogEllipsoid", ival, + GeoTiffStrings.ELLIPSOID, + GeoTiffStrings.ELLIPSOID_INDEX, + rawOutput)); + } + else if (key == GEOGSEMIMAJORAXISGEOKEY) { + dirList.add (new Property ("GeogSemiMajorAxis", + PropertyType.DOUBLE, + new Double (dval))); + } + else if (key == GEOGSEMIMINORAXISGEOKEY) { + dirList.add (new Property ("GeogSemiMinorAxis", + PropertyType.DOUBLE, + new Double (dval))); + } + else if (key == GEOGINVFLATTENINGGEOKEY) { + dirList.add (new Property ("GeogInvFlattening", + PropertyType.DOUBLE, + new Double (dval))); + } + else if (key == GEOGAZIMUTHUNITSGEOKEY) { + dirList.add (addIntegerProperty ("GeogAzimuthUnits", ival, + GeoTiffStrings.ANGULARUNITS, + GeoTiffStrings.ANGULARUNITS_INDEX, + rawOutput)); + } + else if (key == PROJECTEDCSTYPEGEOKEY) { + dirList.add (addIntegerProperty ("ProjectedCSType", ival, + GeoTiffStrings.PROJECTEDCSTYPE, + GeoTiffStrings.PROJECTEDCSTYPE_INDEX, + rawOutput)); + } + else if (key == PCSCITATIONGEOKEY) { + dirList.add (new Property ("PCSCitation", + PropertyType.STRING, sval)); + } + else if (key == PROJECTIONGEOKEY) { + dirList.add (addIntegerProperty ("Projection", ival, + GeoTiffStrings.PROJECTION, + GeoTiffStrings.PROJECTION_INDEX, + rawOutput)); + } + else if (key == PROJCOORDTRANSGEOKEY) { + dirList.add (addIntegerProperty ("ProjCoordTrans", ival, + GeoTiffStrings.COORDINATETRANSFORMATION, + GeoTiffStrings.COORDINATETRANSFORMATION_INDEX, + rawOutput)); + } + else if (key == PROJLINEARUNITSGEOKEY) { + dirList.add (addIntegerProperty ("ProjLinearUnits", ival, + GeoTiffStrings.LINEARUNITS, + GeoTiffStrings.LINEARUNITS_INDEX, + rawOutput)); + } + else if (key == PROJLINEARUNITSIZEGEOKEY) { + dirList.add (new Property ("ProjLinearUnitSize", + PropertyType.DOUBLE, + new Double (dval))); + } + else if (key == PROJSTDPARALLEL1GEOKEY) { + dirList.add (new Property ("ProjStdParallel1", + PropertyType.DOUBLE, + new Double (dval))); + } + else if (key == PROJSTDPARALLEL2GEOKEY) { + dirList.add (new Property ("ProjStdParallel2", + PropertyType.DOUBLE, + new Double (dval))); + } + else if (key == PROJNATORIGINLONGGEOKEY) { + dirList.add (new Property ("ProjNatOriginLong", + PropertyType.DOUBLE, + new Double (dval))); + } + else if (key == PROJNATORIGINLATGEOKEY) { + dirList.add (new Property ("ProjNatOriginLat", + PropertyType.DOUBLE, + new Double (dval))); + } + else if (key == PROJFALSEEASTINGGEOKEY) { + dirList.add (new Property ("ProjFalseEasting", + PropertyType.DOUBLE, + new Double (dval))); + } + else if (key == PROJFALSENORTHINGGEOKEY) { + dirList.add (new Property ("ProjFalseNorthing", + PropertyType.DOUBLE, + new Double (dval))); + } + else if (key == PROJFALSEORIGINLONGGEOKEY) { + dirList.add (new Property ("ProjFalseOriginLong", + PropertyType.DOUBLE, + new Double (dval))); + } + else if (key == PROJFALSEORIGINLATGEOKEY) { + dirList.add (new Property ("ProjFalseOriginLat", + PropertyType.DOUBLE, + new Double (dval))); + } + else if (key == PROJFALSEORIGINEASTINGGEOKEY) { + dirList.add (new Property ("ProjFalseOriginEasting", + PropertyType.DOUBLE, + new Double (dval))); + } + else if (key == PROJFALSEORIGINNORTHINGGEOKEY || + key == PROJFALSEORIGINNORTHINGGEOKEY_2) { + dirList.add (new Property ("ProjFalseOriginNorthing", + PropertyType.DOUBLE, + new Double (dval))); + } + else if (key == PROJCENTERLONGGEOKEY) { + dirList.add (new Property ("ProjCenterLong", + PropertyType.DOUBLE, + new Double (dval))); + } + else if (key == PROJCENTERLATGEOKEY) { + dirList.add (new Property ("ProjCenterLat", + PropertyType.DOUBLE, + new Double (dval))); + } + else if (key == PROJCENTEREASTINGGEOKEY) { + dirList.add (new Property ("ProjCenterEasting", + PropertyType.DOUBLE, + new Double (dval))); + } + else if (key == PROJSCALEATNATORIGINGEOKEY) { + dirList.add (new Property ("ProjScaleAtNatOrigin", + PropertyType.DOUBLE, + new Double (dval))); + } + else if (key == PROJSCALEATCENTERGEOKEY) { + dirList.add (new Property ("ProjScaleAtCenter", + PropertyType.DOUBLE, + new Double (dval))); + } + else if (key == PROJAZIMUTHANGLEGEOKEY) { + dirList.add (new Property ("ProjAzimuthAngle", + PropertyType.DOUBLE, + new Double (dval))); + } + else if (key == PROJSTRAIGHTVERTPOLELONGEOKEY) { + dirList.add (new Property ("ProjStraightVertPoleLong", + PropertyType.DOUBLE, + new Double (dval))); + } + else if (key == VERTICALCSTYPEGEOKEY) { + dirList.add (addIntegerProperty ("VerticalCSType", ival, + GeoTiffStrings.VERTICALCSTYPE, + GeoTiffStrings.VERTICALCSTYPE_INDEX, + rawOutput)); + } + else if (key == VERTICALCITATIONGEOKEY) { + dirList.add (new Property ("VerticalCitation", + PropertyType.STRING, sval)); + } + else if (key == VERTICALDATUMGEOKEY) { + dirList.add (addIntegerProperty ("VerticalDatum", ival, + GeoTiffStrings.VERTICALCSDATUM, + GeoTiffStrings.VERTICALCSDATUM_INDEX, + rawOutput)); + } + else if (key == VERTICALUNITSGEOKEY) { + dirList.add (addIntegerProperty ("VerticalUnits", ival, + GeoTiffStrings.LINEARUNITS, + GeoTiffStrings.LINEARUNITS_INDEX, + rawOutput)); + } + } + } + List geoList = new LinkedList (); + if (dirList.size () > 0) { + geoList.add (new Property ("GeoKeyDirectory", + PropertyType.PROPERTY, + PropertyArity.LIST, dirList)); + } + + if (_modelTiepointTag != null) { + geoList.add (new Property ("ModelTiepointTag", PropertyType.DOUBLE, + PropertyArity.ARRAY,_modelTiepointTag)); + } + if (_modelPixelScaleTag != null) { + geoList.add (new Property ("ModelPixelScaleTag", + PropertyType.DOUBLE, + PropertyArity.ARRAY, + _modelPixelScaleTag)); + } + if (_modelTransformationTag != null) { + geoList.add (new Property ("ModelTransformationTag", + PropertyType.DOUBLE, + PropertyArity.ARRAY, + _modelTransformationTag)); + } + + if (geoList.size () > 0) { + entries.add (new Property ("GeoTIFFProperties", + PropertyType.PROPERTY, + PropertyArity.LIST, geoList)); + } + } + + /* Add Tiff/FX properties */ + private void addTiffFXProperties (List entries, boolean rawOutput) + { + if (_stripRowCounts != null) { + entries.add (new Property ("StripRowCounts", + PropertyType.LONG, + PropertyArity.ARRAY, + _stripRowCounts)); + } + if (_imageLayer != null) { + // Do up ImageLayer as a property with two subproperties. + Property[] layerProps = new Property[2]; + try { + layerProps[0] = addIntegerProperty ("LayerType", _imageLayer[0], + IMAGELAYER_L, + rawOutput); + layerProps[1] = new Property ("OrdinalNumber", + PropertyType.INTEGER, + new Integer (_imageLayer[1])); + + entries.add (new Property ("ImageLayer", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + layerProps)); + } + // Don't blow up on incorrect array size + catch (Exception e) {} + } + } + + /* Adds DNG properties. */ + private void addDNGProperties (List entries, boolean rawOutput) + { + setDNGDefaults (); + List dngList = new LinkedList (); + if (_dngVersion != null) { + dngList.add (new Property ("DNGVersion", + PropertyType.INTEGER, + PropertyArity.ARRAY, + _dngVersion)); + } + if (_dngBackwardVersion != null) { + dngList.add (new Property ("DNGBackwardVersion", + PropertyType.INTEGER, + PropertyArity.ARRAY, + _dngBackwardVersion)); + } + if (_uniqueCameraModel != null) { + dngList.add (new Property ("UniqueCameraModel", + PropertyType.STRING, + _uniqueCameraModel)); + } + if (_localizedCameraModel != null) { + dngList.add (new Property ("LocalizedCameraModel", + PropertyType.STRING, + _localizedCameraModel)); + } + if (_cfaPlaneColor != null) { + dngList.add (new Property ("CFAPlaneColor", + PropertyType.INTEGER, + PropertyArity.ARRAY, + _cfaPlaneColor)); + } + if (_cfaLayout != NULL) { + dngList.add (addIntegerProperty ("CFALayout", _cfaLayout, + CFALAYOUT_L, rawOutput)); + } + if (_linearizationTable != null) { + dngList.add (new Property ("LinearizationTable", + PropertyType.INTEGER, + PropertyArity.ARRAY, + _linearizationTable)); + } + if (_blackLevelRepeatDim != null) { + dngList.add (new Property ("BlackLevelRepeatDim", + PropertyType.INTEGER, + PropertyArity.ARRAY, + _blackLevelRepeatDim)); + } + if (_blackLevel != null) { + dngList.add (new Property ("BlackLevel", + PropertyType.RATIONAL, + PropertyArity.ARRAY, + _blackLevel)); + } + if (_blackLevelDeltaH != null) { + dngList.add (new Property ("BlackLevelDeltaH", + PropertyType.RATIONAL, + PropertyArity.ARRAY, + _blackLevelDeltaH)); + } + if (_blackLevelDeltaV != null) { + dngList.add (new Property ("BlackLevelDeltaV", + PropertyType.RATIONAL, + PropertyArity.ARRAY, + _blackLevelDeltaV)); + } + if (_whiteLevel != null) { + dngList.add (new Property ("WhiteLevel", + PropertyType.LONG, + PropertyArity.ARRAY, + _whiteLevel)); + } + if (_defaultScale != null) { + dngList.add (new Property ("DefaultScale", + PropertyType.RATIONAL, + PropertyArity.ARRAY, + _defaultScale)); + } + if (_bestQualityScale != null) { + dngList.add (new Property ("BestQualityScale", + PropertyType.RATIONAL, + _bestQualityScale)); + } + if (_defaultCropOrigin != null) { + dngList.add (new Property ("DefaultCropOrigin", + PropertyType.RATIONAL, + PropertyArity.ARRAY, + _defaultCropOrigin)); + } + if (_defaultCropSize != null) { + dngList.add (new Property ("DefaultCropSize", + PropertyType.RATIONAL, + PropertyArity.ARRAY, + _defaultCropSize)); + } + if (_calibrationIlluminant1 != NULL) { + dngList.add (new Property ("CalibrationIlluminant1", + PropertyType.INTEGER, + new Integer (_calibrationIlluminant1))); + } + if (_calibrationIlluminant2 != NULL) { + dngList.add (new Property ("CalibrationIlluminant2", + PropertyType.INTEGER, + new Integer (_calibrationIlluminant2))); + } + if (_colorMatrix1 != null) { + dngList.add (new Property ("ColorMatrix1", + PropertyType.RATIONAL, + PropertyArity.ARRAY, + _colorMatrix1)); + } + if (_colorMatrix2 != null) { + dngList.add (new Property ("ColorMatrix2", + PropertyType.RATIONAL, + PropertyArity.ARRAY, + _colorMatrix2)); + } + if (_cameraCalibration1 != null) { + dngList.add (new Property ("CameraCalibration1", + PropertyType.RATIONAL, + PropertyArity.ARRAY, + _cameraCalibration1)); + } + if (_cameraCalibration2 != null) { + dngList.add (new Property ("CameraCalibration2", + PropertyType.RATIONAL, + PropertyArity.ARRAY, + _cameraCalibration2)); + } + if (_reductionMatrix1 != null) { + dngList.add (new Property ("ReductionMatrix1", + PropertyType.RATIONAL, + PropertyArity.ARRAY, + _reductionMatrix1)); + } + if (_reductionMatrix2 != null) { + dngList.add (new Property ("ReductionMatrix2", + PropertyType.RATIONAL, + PropertyArity.ARRAY, + _reductionMatrix2)); + } + if (_analogBalance != null) { + dngList.add (new Property ("AnalogBalance", + PropertyType.RATIONAL, + PropertyArity.ARRAY, + _analogBalance)); + } + if (_asShotNeutral != null) { + dngList.add (new Property ("AsShotNeutral", + PropertyType.RATIONAL, + PropertyArity.ARRAY, + _asShotNeutral)); + } + if (_asShotWhiteXY != null) { + dngList.add (new Property ("AsShotWhiteXY", + PropertyType.RATIONAL, + PropertyArity.ARRAY, + _asShotWhiteXY)); + } + if (_baselineExposure != null) { + dngList.add (new Property ("BaselineExposure", + PropertyType.RATIONAL, + _baselineExposure)); + } + if (_baselineNoise != null) { + dngList.add (new Property ("BaselineNoise", + PropertyType.RATIONAL, + _baselineNoise)); + } + if (_baselineNoise != null) { + dngList.add (new Property ("BaselineSharpness", + PropertyType.RATIONAL, + _baselineSharpness)); + } + if (_bayerGreenSplit != NULL) { + dngList.add (new Property ("BayerGreenSplit", + PropertyType.INTEGER, + new Integer (_bayerGreenSplit))); + } + if (_linearResponseLimit != null) { + dngList.add (new Property ("LinearResponseLimit", + PropertyType.RATIONAL, + _linearResponseLimit)); + } + if (_cameraSerialNumber != null) { + dngList.add (new Property ("CameraSerialNumber", + PropertyType.STRING, + _cameraSerialNumber)); + } + if (_lensInfo != null) { + dngList.add (new Property ("LensInfo", + PropertyType.RATIONAL, + PropertyArity.ARRAY, + _lensInfo)); + } + if (_chromaBlurRadius != null) { + dngList.add (new Property ("ChromaBlurRadius", + PropertyType.RATIONAL, + _chromaBlurRadius)); + } + if (_antiAliasStrength != null) { + dngList.add (new Property ("AntiAliasStrength", + PropertyType.RATIONAL, + _antiAliasStrength)); + } + if (_dngPrivateData != null) { + dngList.add (new Property ("DNGPrivateData", + PropertyType.INTEGER, + _dngPrivateData)); + } + if (_makerNoteSafety != NULL) { + dngList.add (addIntegerProperty ("MakerNoteSafety", _makerNoteSafety, + MAKERNOTESAFETY_L, rawOutput)); + } + if (dngList.size () > 0) { + entries.add (new Property ("DNGProperties", + PropertyType.PROPERTY, + PropertyArity.LIST, dngList)); + } + + } + + + /** ColorPlanes is an undefined primary in the TIFF spec. It's + * actually a non-obvious definition which is something of a + * pain to calculate. + */ + private int calcColorPlanes () + { + if (_photometricInterpretation == TiffProfileDNG.CFA) { + // In this case, it's the number of unique colors + // in the CFA pattern. (You mean you didn't already + // know that?) + int nUnique = 0; + if (_cfaPattern == null) { + // It's broken; return 1 so we don't try to + // allocate zero-length objects. + return 1; + } + int[] uniqueColors = new int[_cfaPattern.length]; + for (int i = 0; i < _cfaPattern.length; i++) { + boolean unique = true; + int color = _cfaPattern[i]; + for (int j = 0; j < nUnique; j++) { + if (color == uniqueColors[j]) { + unique = false; + break; + } + } + if (unique) { + uniqueColors[nUnique++] = color; + } + + } + return nUnique; + } + else { + return _niso.getSamplesPerPixel(); + } + } + + /** Set the default values for any DNG tags that haven't been + * encountered yet. If _dngVersion is zero, apply the IFD 0 + * defaults; if PhotometricInterpretation is CFA or RawLinear, + * apply the Raw IFD defaults. */ + private void setDNGDefaults () + { + if (_dngVersion != null) { + // Apply "IFD 0" defaults + if (_dngBackwardVersion == null) { + _dngBackwardVersion = new int[4]; + // The default value is _dngVersion with the last two + // bytes set to zero. + _dngBackwardVersion[0] = _dngVersion[0]; + _dngBackwardVersion[1] = _dngVersion[1]; + _dngBackwardVersion[2] = 0; + _dngBackwardVersion[3] = 0; + } + + if (_uniqueCameraModel != null && _localizedCameraModel == null) { + _localizedCameraModel = _uniqueCameraModel; + } + + if (_calibrationIlluminant1 == NULL) { + _calibrationIlluminant1 = 0; + } + if (_baselineExposure == null) { + _baselineExposure = new Rational (0, 1); + } + if (_baselineNoise == null) { + _baselineNoise = new Rational (1, 1); + } + if (_baselineSharpness == null) { + _baselineSharpness = new Rational (1, 1); + } + if (_linearResponseLimit == null) { + _linearResponseLimit = new Rational (1, 1); + } + if (_makerNoteSafety == NULL) { + _makerNoteSafety = 0; + } + // There are some IFD 0 defaults which depend on the value of + // "ColorPlanes," which is derived from information in the Raw IFD. + // This would require processing the IFD's out of order, so those + // defaults (AnalogBalance) go unreported. + } + + if (_photometricInterpretation == TiffProfileDNG.CFA || + _photometricInterpretation == TiffProfileDNG.LINEAR_RAW) { + // Apply "Raw IFD" defaults. This really isn't sufficient + // information to establish a file as DNG, but the properties + // are in their own category, so it's fairly harmless to leave + // them in even if it's actually, for example, a TIFF-EP file. + + if (_cfaPlaneColor == null) { + _cfaPlaneColor = new int[] {0, 1, 2}; + } + + if (_cfaLayout == NULL) { + _cfaLayout = 1; + } + + // The size of the LinearizationTable is -- I quote -- N. + // This is NOT useful. Skip that default. + + if (_blackLevelRepeatDim == null) { + _blackLevelRepeatDim = new int[] { 1, 1 }; + } + + Rational zero = new Rational (0, 1); + if (_blackLevel == null) { + _blackLevel = new Rational [_blackLevelRepeatDim[0] * + _blackLevelRepeatDim[1] * + _niso.getSamplesPerPixel()]; + for (int i = 0; i < _blackLevel.length; i++) { + _blackLevel[i] = zero; + } + } + + if (_blackLevelDeltaH == null) { + _blackLevelDeltaH = new Rational [(int) _niso.getImageWidth()]; + for (int i = 0; i < _blackLevelDeltaH.length; i++) { + _blackLevelDeltaH[i] = zero; + } + } + + if (_blackLevelDeltaV == null) { + _blackLevelDeltaV = new Rational [(int) _niso.getImageLength()]; + for (int i = 0; i < _blackLevelDeltaV.length; i++) { + _blackLevelDeltaV[i] = zero; + } + } + + if (_whiteLevel == null) { + _whiteLevel = new long[_niso.getSamplesPerPixel()]; + long defWhite = (1L << _niso.getBitsPerSample()[0] - 1); + for (int i = 0; i < _whiteLevel.length; i++) { + _whiteLevel[i] = defWhite; + } + } + + Rational one = new Rational (1, 1); + if (_defaultScale == null) { + _defaultScale = new Rational[] { one, one }; + } + + if (_bestQualityScale == null) { + _bestQualityScale = one; + } + + if (_defaultCropOrigin == null) { + _defaultCropOrigin = new Rational[] {zero, zero}; + } + + if (_defaultCropSize == null) { + _defaultCropSize = new Rational[2]; + _defaultCropSize[0] = new Rational (_niso.getImageWidth(), 1); + _defaultCropSize[1] = new Rational (_niso.getImageLength(), 1); + } + int colorPlanes = calcColorPlanes (); + if (_cameraCalibration1 == null) { + // Identity matrix with dimension of ColorPlanes*ColorPlanes + _cameraCalibration1 = identityMatrix (colorPlanes); + } + if (_cameraCalibration2 == null) { + // Identity matrix with dimension of ColorPlanes*ColorPlanes + _cameraCalibration2 = identityMatrix (colorPlanes); + } + if (_bayerGreenSplit == NULL && _photometricInterpretation == TiffProfileDNG.CFA) { + _bayerGreenSplit = 0; + } + if (_antiAliasStrength == null) { + _antiAliasStrength = new Rational (1, 1); + } + } + } + + + /** Create a Rational identity matrix of the specified size. */ + private Rational [] identityMatrix (int dim) + { + Rational[] val = new Rational [dim * dim]; + // Set them all to zero, then overwrite the diagonal values + // to one. + int i; + for (i = 0; i < dim * dim; i++) { + val[i] = new Rational (0, 1); + } + for (i = 0; i < dim; i++) { + val[dim * i + i] = new Rational (1, 1); + } + return val; + } + + /** Looks up an IFD tag. */ + public void lookupTag (int tag, int type, long count, long value) + throws TiffException + { + try { + if (tag == APERTUREVALUE) { + checkType (tag, type, RATIONAL); + checkCount (tag, count, 1); + _aperatureValue = readRational (count, value); + } + else if (tag == ARTIST) { + checkType (tag, type, ASCII); + _niso.setImageProducer (readASCII (count, value)); + + if (_version < 5) { + _version = 5; + } + } + else if (tag == BACKGROUNDCOLORINDICATOR) { + checkType (tag, type, BYTE); + checkCount (tag, count, 1); + _backgroundColorIndicator = readByte (type, count, value); + } + else if (tag == BACKGROUNDCOLORVALUE) { + checkType (tag, type, BYTE); + checkCount (tag, count, 1); + _backgroundColorValue = readByte (type, count, value); + } + else if (tag == BADFAXLINES) { + checkType (tag, type, SHORT, LONG); + checkCount (tag, count, 1); + _badFaxLines = readLong (type, count, value); + } + else if (tag == BATTERYLEVEL) { + checkType (tag, type, RATIONAL, ASCII); + if (type == RATIONAL) { + Rational r = readRational (count, value); + _batteryLevel = Double.toString (r.toDouble ()); + } + else { + _batteryLevel = readASCII (count, value); + } + } + else if (tag == BITSPEREXTENDEDRUNLENGTH) { + checkType (tag, type, SHORT); + checkCount (tag, count, 1); + _bitsPerExtendedRunLength = readShort (type, count, value); + } + else if (tag == BITSPERRUNLENGTH) { + checkType (tag, type, SHORT); + checkCount (tag, count, 1); + _bitsPerRunLength = readShort (type, count, value); + } + else if (tag == BITSPERSAMPLE) { + checkType (tag, type, SHORT); + _niso.setBitsPerSample (readShortArray (type, count, value)); + } + else if (tag == BRIGHTNESSVALUE) { + checkType (tag, type, SRATIONAL); + if (count == 1) { + _niso.setBrightness (readSignedRational (count, + value).toDouble ()); + } + else { + Rational [] r = readSignedRationalArray (count, value); + _niso.setBrightness (average (r[0], r[1]).toDouble ()); + } + } + else if (tag == CELLLENGTH) { + checkType (tag, type, SHORT); + _cellLength = readShort (type, count, value); + } + else if (tag == CELLWIDTH) { + checkType (tag, type, SHORT); + _cellWidth = readShort (type, count, value); + } + else if (tag == CFAPATTERN) { + checkType (tag, type, BYTE); + _cfaPattern = readByteArray (type, count, value); + } + else if (tag == CFAREPEATPATTERNDIM) { + checkType (tag, type, SHORT); + checkCount (tag, count, 2); + _cfaRepeatPatternDim = readShortArray (type, count, value); + } + else if (tag == CLEANFAXDATA) { + checkType (tag, type, SHORT); + checkCount (tag, count, 1); + _badFaxLines = readShort (type, count, value); + } + else if (tag == CLIPPATH) { + checkType (tag, type, BYTE); + _clipPath = readByteArray (type, count, value); + } + else if (tag == COLORCHARACTERIZATION) { + checkType (tag, type, ASCII); + _colorCharacterization = readASCII (count, value); + } + else if (tag == COLORSEQUENCE) { + checkType (tag, type, ASCII); + _colorSequence = readASCII (count, value); + } + else if (tag == COLORMAP) { + checkType (tag, type, SHORT); + int [] colorMap = readShortArray (type, count, value); + int [] bitCode = new int [colorMap.length]; + int [] red = new int [colorMap.length]; + int [] green = new int [colorMap.length]; + int [] blue = new int [colorMap.length]; + int len = colorMap.length/3; + int len2= 2*len; + for (int i=0; i key) { + throw new TiffException ("GeoKey " + key + + " out of sequence"); + } + prevKey = key; + } + } + else if (tag == GLOBALPARAMETERSIFD) { + checkType (tag, type, LONG, IFD); + // RFC 2301 allows only IFD, but the latest working + // draft allows LONG. Even though allowing LONG + // technically isn't allowed yet, letting it by seems + // reasonable, since other IFD tags can be LONG. + checkCount (tag, count, 1); + _globalParametersIFD = readLong (type, count, value); + } + else if (tag == GPSINFOIFD) { + checkType (tag, type, LONG); + checkCount (tag, count, 1); + _gpsInfoIFD = readLong (type, count, value); + } + else if (tag == GRAYRESPONSECURVE) { + checkType (tag, type, SHORT); + _niso.setGrayResponseCurve (readShortArray (type, count, + value)); + } + else if (tag == GRAYRESPONSEUNIT) { + checkType (tag, type, SHORT); + checkCount (tag, count, 1); + _niso.setGrayResponseUnit (readShort (type, count, value)); + } + else if (tag == HALFTONEHINTS) { + checkType (tag, type, SHORT); + checkCount (tag, count, 2); + _halftoneHints = readShortArray (type, count, value); + + if (_version < 6) { + _version = 6; + } + } + else if (tag == HCUSAGE) { + checkType (tag, type, LONG); + checkCount (tag, count, 1); + _hcUsage = readLong (type, count, value); + } + else if (tag == HOSTCOMPUTER) { + checkType (tag, type, ASCII); + _niso.setHostComputer (readASCII (count, value)); + + if (_version < 5) { + _version = 5; + } + } + else if (tag == IMAGEDESCRIPTION) { + checkType (tag, type, ASCII); + _imageDescription = readASCII (count, value); + } + else if (tag == IMAGEID) { + checkType (tag, type, ASCII); + _niso.setImageIdentifier (readASCII (count, value)); + } + else if (tag == IMAGECOLORINDICATOR) { + checkType (tag, type, BYTE); + checkCount (tag, count, 1); + _imageColorIndicator = readByte (type, count, value); + } + else if (tag == IMAGECOLORVALUE) { + checkType (tag, type, BYTE); + checkCount (tag, count, 1); + _imageColorValue = readByte (type, count, value); + } + else if (tag == IMAGEHISTORY) { + checkType (tag, type, ASCII); + _imageHistory = readASCII (count, value); + } + else if (tag == IMAGELAYER) { + checkType (tag, type, SHORT, LONG); + checkCount (tag, count, 2); + _imageLayer = readShortArray (type, count, value); + } + else if (tag == IMAGELENGTH) { + checkType (tag, type, SHORT, LONG); + checkCount (tag, count, 1); + _niso.setImageLength (readLong (type, count, value)); + } + else if (tag == IMAGENUMBER) { + checkType (tag, type, LONG); + checkCount (tag, count, 1); + _imageNumber = readLong (type, count, value); + } + else if (tag == IMAGESOURCEDATA) { + checkType (tag, type, UNDEFINED); + //_imageSourceData = readByteArray (type, count, value); + // GDM 16-Sep-2005: + // The ImageSourceData tag sometimes has a gigantic + // amount of data, and we don't actually do anything with + // it in the current version of JHOVE except determine if + // it's there. + _imageSourceData = new int[] {1}; + } + else if (tag == PHOTOSHOPPROPS) { + // Can't find any info on what type is expected. + _photoshopProperties = readByteArray (type, count, value); + } + else if (tag == ANNOTATIONS) { + // Can't find any info on what type is expected. + _annotations = readByteArray (type, count, value); + } + else if (tag == IMAGEWIDTH) { + checkType (tag, type, SHORT, LONG); + checkCount (tag, count, 1); + _niso.setImageWidth (readLong (type, count, value)); + } + else if (tag == INDEXED) { + checkType (tag, type, SHORT); + checkCount (tag, count, 1); + _indexed = readShort (type, count, value); + } + else if (tag == INKNAMES) { + checkType (tag, type, ASCII); + _inkNames = readASCIIArray (count, value); + + if (_version < 6) { + _version = 6; + } + } + else if (tag == INKSET) { + checkType (tag, type, SHORT); + checkCount (tag, count, 1); + _inkSet = readShort (type, count, value); + + if (_version < 6) { + _version = 6; + } + } + else if (tag == ICC_PROFILE) { + checkType (tag, type, UNDEFINED); + _interColourProfile = readByteArray (type, count, value); + } + else if (tag == INTERLACE) { + checkType (tag, type, SHORT); + checkCount (tag, count, 1); + _interlace = readShort (type, count, value); + } + else if (tag == INTEROPERABILITYIFD) { + checkType (tag, type, LONG); + checkCount (tag, count, 1); + _interoperabilityIFD = readLong (type, count, value); + } + else if (tag == IPTCNAA) { + + if (type == ASCII) { + String s = readASCII (count, value); + long [] larray = new long [s.length ()]; + for (int i=0; i 1) { + darray[1] = r[1].toDouble (); + } + else { + darray[1] = darray[0]; + } + } + _niso.setSubjectDistance (darray); + } + else if (tag == SOFTWARE) { + checkType (tag, type, ASCII); + _niso.setScanningSoftware (readASCII (count, value)); + + if (_version < 5) { + _version = 5; + } + } + else if (tag == SPATIALFREQUENCYRESPONSE) { + checkType (tag, type, UNDEFINED); + _spatialFrequencyResponse = readByteArray (type, count, value); + } + else if (tag == SPECTRALSENSITIVITY) { + checkType (tag, type, ASCII); + _spectralSensitivity = readASCII (count, value); + } + else if (tag == STRIPBYTECOUNTS) { + checkType (tag, type, SHORT, LONG); + _niso.setStripByteCounts (readLongArray (type, count, value)); + } + else if (tag == STRIPOFFSETS) { + checkType (tag, type, SHORT, LONG); + _niso.setStripOffsets (readLongArray (type, count, value)); + } + else if (tag == SUBFILETYPE) { + checkType (tag, type, LONG); + checkCount (tag, count, 1); + _subfileType = readShort (type, count, value); + } + else if (tag == SUBIFDS) { + checkType (tag, type, LONG, IFD); + _subIFDs = readLongArray (type, count, value); + } + else if (tag == SUBJECTLOCATION) { + checkType (tag, type, SHORT); + _subjectLocation = readShortArray (type, count, value); + } + else if (tag == T4OPTIONS) { + checkType (tag, type, LONG); + _t4Options = readShort (type, count, value); + } + else if (tag == T6OPTIONS) { + checkType (tag, type, LONG); + _t6Options = readShort (type, count, value); + } + else if (tag == TARGETPRINTER) { + checkType (tag, type, ASCII); + _targetPrinter = readASCII (count, value); + + if (_version < 6) { + _version = 6; + } + } + else if (tag == THRESHHOLDING) { + checkType (tag, type, SHORT); + checkCount (tag, count, 1); + _threshholding = readShort (type, count, value); + } + else if (tag == TIFFEPSTANDARDID) { + checkType (tag, type, SHORT); + checkCount (tag, count, 4); + int [] iarray = readShortArray (type, count, value); + _tiffEPStandardID = Integer.toString (iarray[0]) + "." + + Integer.toString (iarray[1]) + "." + + Integer.toString (iarray[2]) + "." + + Integer.toString (iarray[3]); + } + else if (tag == TILEBYTECOUNTS) { + checkType (tag, type, SHORT, LONG); + _niso.setTileByteCounts (readLongArray (type, count, value)); + + if (_version < 6) { + _version = 6; + } + } + else if (tag == TILELENGTH) { + checkType (tag, type, SHORT, LONG); + checkCount (tag, count, 1); + _niso.setTileLength (readLong (type, count, value)); + + if (_version < 6) { + _version = 6; + } + } + else if (tag == TILEOFFSETS) { + checkType (tag, type, SHORT, LONG); + _niso.setTileOffsets (readLongArray (type, count, value)); + + if (_version < 6) { + _version = 6; + } + } + else if (tag == TILEWIDTH) { + checkType (tag, type, SHORT, LONG); + checkCount (tag, count, 1); + _niso.setTileWidth (readLong (type, count, value)); + + if (_version < 6) { + _version = 6; + } + } + else if (tag == TIMEZONEOFFSET) { + checkType (tag, type, SSHORT); + _timeZoneOffset = readSShortArray (type, count, value); + } + else if (tag == TRANSFERFUNCTION) { + /* Transfer function arrays potentially can have millions + * of elements, so we just report presence */ + checkType (tag, type, SHORT); + _transferFunction = true; + } + else if (tag == TRANSFERRANGE) { + checkType (tag, type, SHORT); + checkCount (tag, count, 6); + _transferRange = readShortArray (type, count, value); + + if (_version < 6) { + _version = 6; + } + } + else if (tag == TRANSPARENCYINDICATOR) { + checkType (tag, type, BYTE); + checkCount (tag, count, 1); + _transparencyIndicator = readByte (type, count, value); + } + else if (tag == WHITEPOINT) { + checkType (tag, type, RATIONAL); + checkCount (tag, count, 2); + Rational [] rarray = readRationalArray (count, value); + _niso.setWhitePointXValue (rarray[0]); + _niso.setWhitePointYValue (rarray[0]); + + if (_version < 5) { + _version = 5; + } + } + else if (tag == XCLIPPATHUNITS) { + checkType (tag, type, LONG); + checkCount (tag, count, 1); + _xClipPathUnits = readLong (type, count, value); + } + else if (tag == XPOSITION) { + checkType (tag, type, RATIONAL); + checkCount (tag, count, 1); + _xPosition = readRational (count, value); + } + else if (tag == XRESOLUTION) { + checkType (tag, type, RATIONAL); + checkCount (tag, count, 1); + _niso.setXSamplingFrequency (readRational (count, + value)); + } + else if (tag == YCBCRCOEFFICIENTS) { + checkType (tag, type, RATIONAL); + checkCount (tag, count, 3); + _niso.setYCbCrCoefficients (readRationalArray (count, value)); + + if (_version < 6) { + _version = 6; + } + } + else if (tag == YCBCRPOSITIONING) { + checkType (tag, type, SHORT); + checkCount (tag, count, 1); + _niso.setYCbCrPositioning (readShort (type, count, value)); + + if (_version < 6) { + _version = 6; + } + } + else if (tag == YCBCRSUBSAMPLING) { + checkType (tag, type, SHORT); + checkCount (tag, count, 2); + _niso.setYCbCrSubSampling(readShortArray (type, count, value)); + + if (_version < 6) { + _version = 6; + } + } + else if (tag == YCLIPPATHUNITS) { + checkType (tag, type, LONG); + checkCount (tag, count, 1); + _yClipPathUnits = readLong (type, count, value); + } + else if (tag == YPOSITION) { + checkType (tag, type, RATIONAL); + checkCount (tag, count, 1); + _yPosition = readRational (count, value); + } + else if (tag == YRESOLUTION) { + checkType (tag, type, RATIONAL); + checkCount (tag, count, 1); + _niso.setYSamplingFrequency (readRational (count, + value)); + } + else if (tag == XMP) { + checkType (tag, type, UNDEFINED, BYTE); + _xmpProp = readXMP (count, value); + } + else if (tag == DNGVERSION) { + checkType (tag, type, BYTE); + checkCount (tag, count, 4); + _dngVersion = readByteArray (type, count, value); + } + else if (tag == DNGBACKWARDVERSION) { + checkType (tag, type, BYTE); + checkCount (tag, count, 4); + _dngBackwardVersion = readByteArray (type, count, value); + } + else if (tag == UNIQUECAMERAMODEL) { + checkType (tag, type, ASCII); + _uniqueCameraModel = readASCII(count, value); + } + else if (tag == LOCALIZEDCAMERAMODEL) { + checkType (tag, type, ASCII, BYTE); + // This tag is specified as UTF-8 + byte[] lcm = readTrueByteArray(type, count, value); + // Trim off trailing null (s) + int len = lcm.length; + while (len > 0 && lcm[len - 1] == 0) { + len--; + } + _localizedCameraModel = new String (lcm, 0, len); + + } + else if (tag == CFAPLANECOLOR) { + checkType (tag, type, BYTE); + _cfaPlaneColor = readByteArray (type, count, value); + } + else if (tag == CFALAYOUT) { + checkType (tag, type, SHORT); + _cfaLayout = readShort(type, count, value); + } + else if (tag == LINEARIZATIONTABLE) { + checkType (tag, type, SHORT); + _linearizationTable = readShortArray(type, count, value); + } + else if (tag == BLACKLEVELREPEATDIM) { + checkType (tag, type, SHORT); + _blackLevelRepeatDim = readShortArray(type, count, value); + } + else if (tag == BLACKLEVEL) { + // Just to make things complicated, this can be SHORT, LONG + // or RATIONAL. To give these a least common (pardon the + // expression) denominator, we convert all to rational. + if (type == RATIONAL) { + _blackLevel = readRationalArray(count, value); + } + else { + checkType (tag, type, SHORT, LONG); + long[] ibl = readLongArray(type, count, value); + _blackLevel = new Rational[(int) count]; + for (int i = 0; i < count; i++) { + _blackLevel[i] = new Rational (ibl[i], 1); + } + } + } + else if (tag == BLACKLEVELDELTAH) { + checkType (tag, type, SRATIONAL); + _blackLevelDeltaH = readSignedRationalArray (count, value); + } + else if (tag == BLACKLEVELDELTAV) { + checkType (tag, type, SRATIONAL); + _blackLevelDeltaV = readSignedRationalArray (count, value); + } + else if (tag == WHITELEVEL) { + checkType (tag, type, SHORT, LONG); + _whiteLevel = readLongArray (type, count, value); + } + else if (tag == DEFAULTSCALE) { + checkType (tag, type, RATIONAL); + checkCount (tag, count, 2); + _defaultScale = readRationalArray (count, value); + } + else if (tag == BESTQUALITYSCALE) { + checkType (tag, type, RATIONAL); + checkCount (tag, count, 1); + _bestQualityScale = readRational (count, value); + } + else if (tag == DEFAULTCROPORIGIN) { + checkCount (tag, count, 2); + // Just to make things complicated, this can be SHORT, LONG + // or RATIONAL. To give these a least common (pardon the + // expression) denominator, we convert all to rational. + if (type == RATIONAL) { + _defaultCropOrigin = readRationalArray(count, value); + } + else { + checkType (tag, type, SHORT, LONG); + long[] lco = readLongArray(type, count, value); + _defaultCropOrigin = new Rational[(int) count]; + for (int i = 0; i < count; i++) { + _defaultCropOrigin[i] = new Rational (lco[i], 1); + } + } + } + else if (tag == DEFAULTCROPSIZE) { + checkCount (tag, count, 2); + if (type == RATIONAL) { + _defaultCropSize = readRationalArray(count, value); + } + else { + checkType (tag, type, SHORT, LONG); + long[] lcs = readLongArray(type, count, value); + _defaultCropSize = new Rational[(int) count]; + for (int i = 0; i < count; i++) { + _defaultCropSize[i] = new Rational (lcs[i], 1); + } + } + } + else if (tag == CALIBRATIONILLUMINANT1) { + checkCount (tag, count, 1); + checkType (tag, type, SHORT); + _calibrationIlluminant1 = readShort(type, count, value); + } + else if (tag == CALIBRATIONILLUMINANT2) { + checkCount (tag, count, 1); + checkType (tag, type, SHORT); + _calibrationIlluminant2 = readShort(type, count, value); + } + else if (tag == COLORMATRIX1) { + checkType (tag, type, SRATIONAL); + _colorMatrix1 = readSignedRationalArray (count, value); + } + else if (tag == COLORMATRIX2) { + checkType (tag, type, SRATIONAL); + _colorMatrix2 = readSignedRationalArray (count, value); + } + else if (tag == CAMERACALIBRATION1) { + checkType (tag, type, SRATIONAL); + _colorMatrix1 = readSignedRationalArray (count, value); + } + else if (tag == CAMERACALIBRATION2) { + checkType (tag, type, SRATIONAL); + _colorMatrix2 = readSignedRationalArray (count, value); + } + else if (tag == REDUCTIONMATRIX1) { + checkType (tag, type, SRATIONAL); + _reductionMatrix1 = readSignedRationalArray (count, value); + } + else if (tag == REDUCTIONMATRIX2) { + checkType (tag, type, SRATIONAL); + _reductionMatrix2 = readSignedRationalArray (count, value); + } + else if (tag == ANALOGBALANCE) { + checkType (tag, type, RATIONAL); + _analogBalance = readRationalArray (count, value); + } + else if (tag == ASSHOTNEUTRAL) { + // this can be either SHORT or RATIONAL + checkType (tag, type, SHORT, RATIONAL); + if (type == SHORT) { + int[] asn = readShortArray (type, count, value); + _asShotNeutral = new Rational [(int) count]; + for (int i = 0; i < count; i++) { + _asShotNeutral[i] = new Rational (asn[i], 1); + } + } + else { + _asShotNeutral = readRationalArray (count, value); + } + } + else if (tag == ASSHOTWHITEXY) { + checkType (tag, type, RATIONAL); + checkCount (tag, count, 2); + _asShotWhiteXY = readRationalArray (count, value); + } + else if (tag == BASELINEEXPOSURE) { + checkType (tag, type, SRATIONAL); + _baselineExposure = readSignedRational (count, value); + } + else if (tag == BASELINENOISE) { + checkType (tag, type, RATIONAL); + _baselineNoise = readRational (count, value); + } + else if (tag == BASELINESHARPNESS) { + checkType (tag, type, RATIONAL); + _baselineSharpness = readRational (count, value); + } + else if (tag == BAYERGREENSPLIT) { + checkType (tag, type, LONG); + _bayerGreenSplit = (int) readLong(type, count, value); + } + else if (tag == LINEARRESPONSELIMIT) { + checkType (tag, type, RATIONAL); + _linearResponseLimit = readRational (count, value); + } + else if (tag == CAMERASERIALNUMBER) { + checkType (tag, type, ASCII); + _cameraSerialNumber = readASCII(count, value); + } + else if (tag == LENSINFO) { + checkType (tag, type, RATIONAL); + checkCount (tag, count, 4); + _lensInfo = readRationalArray (count, value); + } + else if (tag == CHROMABLURRADIUS) { + checkType (tag, type, RATIONAL); + _chromaBlurRadius = readRational (count, value); + } + else if (tag == ANTIALIASSTRENGTH) { + checkType (tag, type, RATIONAL); + _antiAliasStrength = readRational (count, value); + } + else if (tag == SHADOWSCALE) { + _info.setMessage (new InfoMessage ("Undocumented TIFF tag ", + "ShadowScale (50739)")); + } + else if (tag == DNGPRIVATEDATA) { + checkType (tag, type, BYTE); + _dngPrivateData = readByteArray (type, count, value); + } + else if (tag == MAKERNOTESAFETY) { + checkType (tag, type, SHORT); + _makerNoteSafety = readShort (type, count, value); + } + else { + _info.setMessage (new InfoMessage ("Unknown TIFF IFD " + + "tag: " + tag, value)); + } + } + catch (IOException e) { + throw new TiffException ("Read error for tag " + tag, value); + } + } + + /** Perform initializations that have to wait until after the + * IFD has been parsed. + */ + protected void postParseInitialization () + { + int samplesPerPixel = _niso.getSamplesPerPixel (); + int [] bitsPerSample = _niso.getBitsPerSample (); + if (bitsPerSample == null) { + bitsPerSample = new int [samplesPerPixel]; + for (int i=0; i 1) { + return false; + } + int [] bps = niso.getBitsPerSample (); + if (bps == null || (bps[0] > 1)) { + return false; + } + + if (!satisfiesCompression (tifd, new int [] {1, 2, 32773} )) { + return false; + } + + if (!satisfiesPhotometricInterpretation (tifd, new int [] {0, 1} )) { + return false; + } + + if (!satisfiesResolutionUnit (tifd, new int [] {1, 2, 3} )) { + return false; + } + + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassF.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassF.java new file mode 100644 index 00000000..dd1dd29a --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassF.java @@ -0,0 +1,156 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Profile checker for TIFF Class F. + * + * @author Gary McGath + */ +public final class TiffProfileClassF extends TiffProfile +{ + + /* Significant changes February 2, 2004 */ + + /** + * Constructor. + */ + public TiffProfileClassF () + { + super (); + _profileText = "Class F"; + } + + /** + * Returns true if the IFD satisfies the requirements of a + * Class F profile. See the Class F specification for + * details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + + /* Check required tags. */ + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + if (niso.getImageLength () == NisoImageMetadata.NULL || + niso.getStripOffsets () == null || + niso.getRowsPerStrip () == NisoImageMetadata.NULL || + niso.getStripByteCounts () == null || + tifd.getPageNumber () == null || + niso.getScanningSoftware () == null) { + return false; + } + + /* Check required values. */ + if (!satisfiesCompression (tifd, new int[] {3, 4} )) { + return false; + } + + int fo = tifd.getFillOrder(); + if (fo != 1 && fo != 2) { + return false; + } + + int cmpr = niso.getCompressionScheme (); + if (cmpr == 3) { + // T4 options are also known as Group 3 options + long t4opt = tifd.getT4Options (); + if (t4opt != 0 && t4opt != 1 && + t4opt != 4 && t4opt != 5) { + return false; + } + } + else if (cmpr == 4) { long t4opt = tifd.getT4Options (); + // T6 options are also known as Group 4 options + long t6opt = tifd.getT6Options (); + if (t6opt != 2) { + return false; + } + } + + //long wid = niso.getImageWidth (); + if (!satisfiesImageWidth (tifd, + new int [] {1728, 2048, + 2432, 2592, 3072, 3648, 3456, 4096, 4864} )) { + return false; + } + + if (!satisfiesNewSubfileType (tifd, 2)) { + return false; + } + + if (!satisfiesResolutionUnit (tifd, new int [] {2, 3} )) { + return false; + } + + if (!satisfiesXResolution (tifd, + new int[] {204, 200, 300, 400, 408} )) { + return false; + } + + if (!satisfiesYResolution (tifd, + new int[] {98, 196, 100, 200, 300, 391, 400} )) { + return false; + } + + int[] bps = niso.getBitsPerSample (); + if (bps == null || bps[0] != 1 ) { + return false; + } + + if (!satisfiesPhotometricInterpretation (tifd, new int [] {0, 1} )) { + return false; + } + + if (!satisfiesSamplesPerPixel (tifd, 1)) { + return false; + } + + // Only certain combinations of ImageWidth and resolution are + // permitted. + int wid = (int) niso.getImageWidth (); + int xres = (int) niso.getXSamplingFrequency ().toLong (); + int yres = (int) niso.getYSamplingFrequency ().toLong (); + switch (wid) { + case 1728: + case 2048: + case 2432: + if (!(xres == 204 && yres == 391) || + (xres == 200 && yres == 100) || + (xres == 200 && yres == 200)) { + return false; + } + break; + + case 2592: + case 3072: + case 3648: + if (!(xres == 300 && yres == 300)) { + return false; + } + break; + + case 3456: + case 4096: + case 4864: + if (!(xres == 408 && yres == 391) || + (xres == 400 && yres == 400)) { + return false; + } + break; + + default: + break; + } + + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassG.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassG.java new file mode 100644 index 00000000..f261261f --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassG.java @@ -0,0 +1,70 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Profile checker for TIFF Class G (Baseline Grayscale). + * + * @author Gary McGath + */ +public final class TiffProfileClassG extends TiffProfile +{ + public TiffProfileClassG () + { + super (); + _profileText = "Baseline grayscale (Class G)"; + } + + /** + * Returns true if the IFD satisfies the requirements of a + * Class G profile. See the TIFF 6.0 specification for + * details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + + /* Check required tags. */ + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + if (niso.getImageWidth () == NisoImageMetadata.NULL || + niso.getImageLength () == NisoImageMetadata.NULL || + niso.getStripOffsets () == null || + niso.getRowsPerStrip () == NisoImageMetadata.NULL || + niso.getStripByteCounts () == null || + niso.getXSamplingFrequency () == null || + niso.getYSamplingFrequency () == null) { + return false; + } + + /* Check required values. */ + if (niso.getSamplesPerPixel () > 1) { + return false; + } + int [] bps = niso.getBitsPerSample (); + if (bps == null || (bps[0] != 4 && bps[0] != 8)) { + return false; + } + + if (!satisfiesCompression (tifd, new int [] {1, 32773} )) { + return false; + } + + if (!satisfiesPhotometricInterpretation (tifd, new int [] {0, 1} )) { + return false; + } + + if (!satisfiesResolutionUnit (tifd, new int [] {1, 2, 3} )) { + return false; + } + + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassIT.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassIT.java new file mode 100644 index 00000000..2474b369 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassIT.java @@ -0,0 +1,49 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Abstract superclass for Tiff Profile Checkers Tiff IP/whatever. + * + * @author Gary McGath + */ +public abstract class TiffProfileClassIT + extends TiffProfile +{ + public TiffProfileClassIT () + { + super (); + } + + /** + * Returns true if the IFD satisfies the requirements + * which are common to all Tiff IT profiles. + * Subclasses will call this, then apply additional + * tests if it returns true. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + + /* Check required tags. */ + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + if (niso.getImageWidth () == NisoImageMetadata.NULL || + niso.getImageLength () == NisoImageMetadata.NULL || + niso.getStripOffsets () == null || + niso.getStripByteCounts () == null || + niso.getXSamplingFrequency () == null || + niso.getYSamplingFrequency () == null) { + return false; + } + + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITBL.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITBL.java new file mode 100644 index 00000000..8721670e --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITBL.java @@ -0,0 +1,85 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Profile checker for TIFF Class IT-BL. + * + * The TIFF/IT spec states that "TIFF/IT-BP makes use of all + * the features and functionality supported by the TIFF and + * TIFF/IT fields appropriate to binary line art images." + * + * @author Gary McGath + */ +public final class TiffProfileClassITBL extends TiffProfileClassIT +{ + public TiffProfileClassITBL () + { + super (); + _profileText = "TIFF/IT-BL (ISO 12639:1998)"; + } + + /** + * Returns true if the IFD satisfies the requirements + * of the profile. See the documentation for + * details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!super.satisfiesThisProfile (ifd)) { + return false; + } + + TiffIFD tifd = (TiffIFD) ifd; + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + int [] bps = niso.getBitsPerSample (); + if (bps == null || bps[0] != 1) { + return false; + } + + if (!satisfiesCompression (tifd, 32898)) { + return false; + } + + if (!satisfiesPhotometricInterpretation (tifd, new int [] {0, 1} )) { + return false; + } + + if (!satisfiesSamplesPerPixel (tifd, 1)) { + return false; + } + + /* ImageColorIndicator=0,1, or 2; BackgroundColorIndicator=0,1, or 2; + * ImageColorIndicator=1, but only if ImageColorValue is defined; + * BackgroundColorIndicator=1, + * but only if BackgroundColorValue is defined. + */ + int [] valueVec; + if (tifd.getImageColorValue () != IFD.NULL) { + valueVec = new int [] {1}; + } + else { + valueVec = new int [] {0, 1, 2}; + } + if (!satisfiesImageColorIndicator (tifd, valueVec)) { + return false; + } + + if (tifd.getBackgroundColorValue () != IFD.NULL) { + valueVec = new int [] {1}; + } + else { + valueVec = new int [] {0, 1, 2}; + } + if (!satisfiesBackgroundColorIndicator (tifd, valueVec)) { + return false; + } + + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITBLP1.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITBLP1.java new file mode 100644 index 00000000..62b9d9cc --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITBLP1.java @@ -0,0 +1,111 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Profile checker for TIFF Class IT-BL/P1. + * + * The TIFF/IT spec states that "TIFF/IT-BL/P1 is a simplified + * image file format profile for binary line art (BL) image + * data and can be considered a constrained + * subset of TIFF/IT-BL specifically intended for + * simpler implementation." + * + * @author Gary McGath + */ +public final class TiffProfileClassITBLP1 extends TiffProfileClassIT +{ + public TiffProfileClassITBLP1 () + { + super (); + _profileText = "TIFF/IT-BL/P1 (ISO 12639:1998)"; + } + + /** + * Returns true if the IFD satisfies the requirements + * of the profile. See the documentation for + * details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!super.satisfiesThisProfile (ifd)) { + return false; + } + + TiffIFD tifd = (TiffIFD) ifd; + + if (!satisfiesNewSubfileType (tifd, 0)) { + return false; + } + + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + int [] bps = niso.getBitsPerSample (); + if (bps == null || bps[0] != 1) { + return false; + } + + if (!satisfiesCompression (tifd, 32898)) { + return false; + } + + if (!satisfiesPhotometricInterpretation (tifd, 0)) { + return false; + } + + if (!satisfiesOrientation (tifd, 1)) { + return false; + } + + if (!satisfiesSamplesPerPixel (tifd, 1)) { + return false; + } + + if (!satisfiesResolutionUnit (tifd, new int [] {2, 3} )) { + return false; + } + + /* ImageColorIndicator=0,1, or 2; BackgroundColorIndicator=0,1, or 2; + * ImageColorIndicator=1, but only if ImageColorValue is defined; + * BackgroundColorIndicator=1, + *but only if BackgroundColorValue is defined. + */ + int [] valueVec; + if (tifd.getImageColorValue () != IFD.NULL) { + valueVec = new int [] {1}; + } + else { + valueVec = new int [] {0, 1, 2}; + } + if (!satisfiesImageColorIndicator (tifd, valueVec)) { + return false; + } + + if (tifd.getBackgroundColorValue () != IFD.NULL) { + valueVec = new int [] {1}; + } + else { + valueVec = new int [] {0, 1, 2}; + } + if (!satisfiesBackgroundColorIndicator (tifd, valueVec)) { + return false; + } + + /* Tags which must NOT be defined */ + if (tifd.getDocumentName () != null || + niso.getScannerModelName () != null || + tifd.getPageName () != null || + niso.getHostComputer () != null || + tifd.getSite () != null || + tifd.getColorSequence () != null || + tifd.getIT8Header() != null) { + return false; + } + + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITBP.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITBP.java new file mode 100644 index 00000000..d7584ede --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITBP.java @@ -0,0 +1,86 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Profile checker for TIFF Class IT-BP. + * + * The TIFF/IT spec states that "TIFF/IT-BP makes use of all + * the features and functionality supported by the TIFF and + * TIFF/IT fields appropriate to binary picture images." + * + * @author Gary McGath + */ +public final class TiffProfileClassITBP extends TiffProfileClassIT +{ + public TiffProfileClassITBP () + { + super (); + _profileText = "TIFF/IT-BP (ISO 12639:1998)"; + } + + /** + * Returns true if the IFD satisfies the requirements + * of the profile. See the documentation for + * details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!super.satisfiesThisProfile (ifd)) { + return false; + } + + // We now know it's a TiffIFD + TiffIFD tifd = (TiffIFD) ifd; + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + int [] bps = niso.getBitsPerSample (); + if (bps == null || bps[0] != 1) { + return false; + } + + if (!satisfiesCompression (tifd, 1)) { + return false; + } + + if (!satisfiesPhotometricInterpretation (tifd, new int[] {0, 1} )) { + return false; + } + + if (!satisfiesSamplesPerPixel (tifd, 1)) { + return false; + } + + /* ImageColorIndicator=0,1, or 2; BackgroundColorIndicator=0,1, or 2; + * ImageColorIndicator=1, but only if ImageColorValue is defined; + * BackgroundColorIndicator=1, + * but only if BackgroundColorValue is defined. + */ + int [] valueVec; + if (tifd.getImageColorValue () != IFD.NULL) { + valueVec = new int [] {1}; + } + else { + valueVec = new int [] {0, 1, 2}; + } + if (!satisfiesImageColorIndicator (tifd, valueVec)) { + return false; + } + + if (tifd.getBackgroundColorValue () != IFD.NULL) { + valueVec = new int [] {1}; + } + else { + valueVec = new int [] {0, 1, 2}; + } + if (!satisfiesBackgroundColorIndicator (tifd, valueVec)) { + return false; + } + + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITBPP1.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITBPP1.java new file mode 100644 index 00000000..e244719a --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITBPP1.java @@ -0,0 +1,103 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Profile checker for TIFF Class IT-BP/P1. + * + * The TIFF/IT spec states that "TIFF/IT-BP/P1 is a simplified + * image file format profile for binary picture (BP) image + * data and can be considred a constrained + * subset of TIFF/IT-BP specifically intended for + * simpler implementation." + * + * @author Gary McGath + */ +public final class TiffProfileClassITBPP1 extends TiffProfileClassIT +{ + public TiffProfileClassITBPP1 () + { + super (); + _profileText = "TIFF/IT-BP/P1 (ISO 12639:1998)"; + } + + /** + * Returns true if the IFD satisfies the requirements + * of the profile. See the documentation for + * details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!super.satisfiesThisProfile (ifd)) { + return false; + } + + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + if (!satisfiesNewSubfileType (tifd, 0)) { + return false; + } + + if (!satisfiesCompression (tifd, 1)) { + return false; + } + + if (!satisfiesPhotometricInterpretation (tifd, 0)) { + return false; + } + + if (!satisfiesSamplesPerPixel (tifd, 1)) { + return false; + } + + if (!satisfiesResolutionUnit (tifd, new int[] {2, 3} )) { + return false; + } + + /* ImageColorIndicator=0,1, or 2; BackgroundColorIndicator=0,1, or 2; + * ImageColorIndicator=1, but only if ImageColorValue is defined; + * BackgroundColorIndicator=1, + * but only if BackgroundColorValue is defined. + */ + int [] valueVec; + if (tifd.getImageColorValue () != IFD.NULL) { + valueVec = new int [] {1}; + } + else { + valueVec = new int [] {0, 1, 2}; + } + if (!satisfiesImageColorIndicator (tifd, valueVec)) { + return false; + } + + if (tifd.getBackgroundColorValue () != IFD.NULL) { + valueVec = new int [] {1}; + } + else { + valueVec = new int [] {0, 1, 2}; + } + if (!satisfiesBackgroundColorIndicator (tifd, valueVec)) { + return false; + } + + /* Tags which must NOT be defined */ + if (tifd.getDocumentName () != null || + niso.getScannerModelName () != null || + tifd.getPageName () != null || + niso.getHostComputer () != null || + tifd.getSite () != null || + tifd.getColorSequence () != null || + tifd.getIT8Header() != null) { + return false; + } + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITBPP2.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITBPP2.java new file mode 100644 index 00000000..32ee3bee --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITBPP2.java @@ -0,0 +1,98 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Profile checker for TIFF Class IT-BP/P2. + * + * The TIFF/IT spec states that "TIFF/IT-BP/P2 is an extension of + * TIFF/IT-BP/P1." + * + * @author Gary McGath + */ +public final class TiffProfileClassITBPP2 extends TiffProfileClassIT +{ + public TiffProfileClassITBPP2 () + { + super (); + _profileText = "TIFF/IT-BP/P2 (ISO 12639:1998)"; + } + + /** + * Returns true if the IFD satisfies the requirements + * of the profile. See the documentation for + * details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!super.satisfiesThisProfile (ifd)) { + return false; + } + + // We now know it's a TiffIFD + TiffIFD tifd = (TiffIFD) ifd; + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + if (!satisfiesNewSubfileType (tifd, 0)) { + return false; + } + + if (!satisfiesCompression (tifd, new int[] {1, 4, 8} )) { + return false; + } + + if (!satisfiesPhotometricInterpretation (tifd, 0)) { + return false; + } + + if (!satisfiesSamplesPerPixel (tifd, 1)) { + return false; + } + + if (!satisfiesResolutionUnit (tifd, new int[] {2, 3} )) { + return false; + } + + /* ImageColorIndicator=0,1, or 2; BackgroundColorIndicator=0,1, or 2; + * ImageColorIndicator=1, but only if ImageColorValue is defined; + * BackgroundColorIndicator=1, + * but only if BackgroundColorValue is defined. + */ + int [] valueVec; + if (tifd.getImageColorValue () != IFD.NULL) { + valueVec = new int [] {1}; + } + else { + valueVec = new int [] {0, 1, 2}; + } + if (!satisfiesImageColorIndicator (tifd, valueVec)) { + return false; + } + + if (tifd.getBackgroundColorValue () != IFD.NULL) { + valueVec = new int [] {1}; + } + else { + valueVec = new int [] {0, 1, 2}; + } + if (!satisfiesBackgroundColorIndicator (tifd, valueVec)) { + return false; + } + + /* Tags which must NOT be defined */ + if (tifd.getDocumentName () != null || + niso.getScannerModelName () != null || + tifd.getPageName () != null || + niso.getHostComputer () != null || + tifd.getSite () != null || + tifd.getColorSequence () != null || + tifd.getIT8Header() != null) { + return false; + } + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITCT.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITCT.java new file mode 100644 index 00000000..818e6b82 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITCT.java @@ -0,0 +1,76 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Profile checker for TIFF Class IT-CT. + * + * @author Gary McGath + */ +public final class TiffProfileClassITCT extends TiffProfileClassIT +{ + public TiffProfileClassITCT () + { + super (); + _profileText = "TIFF/IT-CT (ISO 12639:1998)"; + } + + /** + * Returns true if the IFD satisfies the requirements + * of the profile. See the documentation for + * details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!super.satisfiesThisProfile (ifd)) { + return false; + } + + // We now know it's a TiffIFD + TiffIFD tifd = (TiffIFD) ifd; + + /* Check required tags.*/ + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + if (niso.getBitsPerSample () == null || + niso.getSamplesPerPixel () == NisoImageMetadata.NULL || + niso.getSamplingFrequencyUnit () == NisoImageMetadata.NULL) { + return false; + } + + /* Check required values. */ + + if (!satisfiesPhotometricInterpretation (tifd, 5)) { + return false; + } + + if (!satisfiesCompression (tifd, new int [] {1, 32895} )) { + return false; + /* NOTE: If compression is 32895, RasterPadding must be + 0, 1, 2, 9, or 10. Fix this when RasterPadding support is + implemented. */ + } + + int inkSet = tifd.getInkSet (); + if (inkSet != 1 && inkSet != 2) { + return false; + } + String seq = tifd.getColorSequence (); + if (seq == null || "CMYK".equals (seq)) { + if (inkSet != 1) { + return false; + } + } + + int spp = niso.getSamplesPerPixel (); + int numInks = tifd.getNumberOfInks (); + if (numInks != NisoImageMetadata.NULL && numInks != spp) { + return false; + } + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITCTP1.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITCTP1.java new file mode 100644 index 00000000..d18fa391 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITCTP1.java @@ -0,0 +1,105 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Profile checker for TIFF Class IT-CT/P1. + * + * @author Gary McGath + */ +public final class TiffProfileClassITCTP1 extends TiffProfileClassIT +{ + public TiffProfileClassITCTP1 () + { + super (); + _profileText = "TIFF/IT-CT/P1 (ISO 12639:1998)"; + } + + /** + * Returns true if the IFD satisfies the requirements + * of the profile. See the documentation for + * details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!super.satisfiesThisProfile (ifd)) { + return false; + } + + // We now know that this is a TiffIFD + TiffIFD tifd = (TiffIFD) ifd; + + /* Check required tags.*/ + if (!satisfiesNewSubfileType (tifd, 0)) { + return false; + } + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + + // bps must be { 8, 8, 8, 8 } + int [] bps = niso.getBitsPerSample (); + if (bps == null || bps.length != 4) { + return false; + } + for (int i = 0; i < 4; i++) { + if (bps[i] != 8) { + return false; + } + } + + if (!satisfiesCompression (tifd, 1 )) { + return false; + } + + if (!satisfiesPhotometricInterpretation (tifd, 5)) { + return false; + } + + if (!satisfiesOrientation (tifd, 1)) { + return false; + } + + if (!satisfiesSamplesPerPixel (tifd, 4)) { + return false; + } + + if (!satisfiesPlanarConfiguration (tifd, 1)) { + return false; + } + + if (!satisfiesResolutionUnit (tifd, new int [] {2, 3} )) { + return false; + } + + int inkSet = tifd.getInkSet (); + if (inkSet != 1) { + return false; + } + + int numInks = tifd.getNumberOfInks (); + if (numInks != 4) { + return false; + } + + if (!satisfiesDotRange (tifd, 0, 255)) { + return false; + } + + /* Tags which must NOT be defined */ + if (tifd.getDocumentName () != null || + niso.getScannerModelName () != null || + tifd.getPageName () != null || + niso.getHostComputer () != null || + tifd.getSite () != null || + tifd.getColorSequence () != null || + tifd.getIT8Header() != null) { + return false; + } + + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITCTP2.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITCTP2.java new file mode 100644 index 00000000..c762bde8 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITCTP2.java @@ -0,0 +1,103 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Profile checker for TIFF Class IT-CT/P2. + * + * @author Gary McGath + */ +public final class TiffProfileClassITCTP2 extends TiffProfileClassIT +{ + public TiffProfileClassITCTP2 () + { + super (); + _profileText = "TIFF/IT-CT/P2 (ISO 12639:2003)"; + } + + /** + * Returns true if the IFD satisfies the requirements + * of the profile. See the documentation for + * details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!super.satisfiesThisProfile (ifd)) { + return false; + } + + // We now know this is a TiffIFD + TiffIFD tifd = (TiffIFD) ifd; + + /* Check required tags.*/ + if (!satisfiesNewSubfileType (tifd, 0)) { + return false; + } + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + + // bps must be { 8, ... } + int [] bps = niso.getBitsPerSample (); + if (bps == null) { + return false; + } + if (bps[0] != 8) { + return false; + } + + if (!satisfiesCompression (tifd, new int[] {1, 7, 8} )) { + return false; + } + + if (!satisfiesPhotometricInterpretation (tifd, 5)) { + return false; + } + + if (!satisfiesOrientation (tifd, 1)) { + return false; + } + + if (!satisfiesSamplesPerPixel (tifd, 4)) { + return false; + } + + if (!satisfiesPlanarConfiguration (tifd, 1)) { + return false; + } + + if (!satisfiesResolutionUnit (tifd, new int [] {2, 3} )) { + return false; + } + + int inkSet = tifd.getInkSet (); + if (inkSet != 1) { + return false; + } + + int numInks = tifd.getNumberOfInks (); + if (numInks != 4) { + return false; + } + + if (!satisfiesDotRange (tifd, 0, 255)) { + return false; + } + + /* Tags which must NOT be defined */ + if (tifd.getDocumentName () != null || + niso.getScannerModelName () != null || + tifd.getPageName () != null || + niso.getHostComputer () != null || + tifd.getSite () != null || + tifd.getColorSequence () != null || + tifd.getIT8Header() != null) { + return false; + } + + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITFP.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITFP.java new file mode 100644 index 00000000..c3aa5fd4 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITFP.java @@ -0,0 +1,98 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Profile checker for TIFF Class IT-FP. + * + * The TIFF/IT spec states that "TIFF/IT-FP provides a mechanism for + * associating image files of the different types that make up a + * final page." Note that Jhove profiles are applied to individual + * IFD levels, so this profile does not check the relationships among + * IFDs which are part of the FP specification. + * + * @author Gary McGath + */ +public final class TiffProfileClassITFP extends TiffProfileClassIT +{ + public TiffProfileClassITFP () + { + super (); + _profileText = "TIFF/IT-FP"; + } + + /** + * Returns true if the IFD satisfies the requirements + * of the profile. See the documentation for + * details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!super.satisfiesThisProfile (ifd)) { + return false; + } + + // We now know this is a TiffIFD + TiffIFD tifd = (TiffIFD) ifd; + + if (tifd.getImageDescription () == null) { + return false; + } + + // BitsPerSample=4 or 8 or {8,8,8} or {8,8,8,8} or undefined + // (consistent with PhotometricInterpretation) + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + int [] bps = niso.getBitsPerSample (); + if (!(bps == null || + (bps.length == 1 && (bps[0] == 4 || bps[0] == 8)) || + (bps.length == 3 && bps[0] == 8 && bps[1] == 8 && bps[2] == 8) || + (bps.length == 4 && bps[0] == 8 && + bps[1] == 8 && bps[2] == 8 && bps[3] == 8))) { + return false; + } + + + // NewSubfileType bit 3=1 + long nsft = tifd.getNewSubfileType (); + if ((nsft & 8) == 0) { + return false; + } + + if (!satisfiesPhotometricInterpretation (tifd, new int [] {0, 1, 2, + 5} )) { + return false; + } + + // SamplesPerPixel=3 or 4 or undefined (consistent with + // PhotometricInterpretation) + if (!satisfiesSamplesPerPixel (tifd, new int [] {3, 4, + NisoImageMetadata.NULL} )) { + return false; + } + + if (!satisfiesCompression (tifd, 1)) { + return false; + } + + if (!satisfiesPlanarConfiguration (tifd, 1)) { + return false; + } + + // InkSet=1, but only if PhotometricInterpretation=5 + // NumberOfInks=4, but only if PhotometricInterpretation=5 + // DotRange={0,255}, but only if PhotometricInterpretation=5 + int pint = niso.getColorSpace (); + int inkSet = tifd.getInkSet (); + int nInks = tifd.getNumberOfInks (); + if (pint == 5 && (inkSet != 1 || nInks != 4 || + !satisfiesDotRange (tifd, 0, 255))) { + return false; + } + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITFPP1.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITFPP1.java new file mode 100644 index 00000000..19972e72 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITFPP1.java @@ -0,0 +1,113 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Profile checker for TIFF Class IT-FP/P1. + * + * TIFF/IT-FP/P1 is a simplified form of TIFF/IT-FP. + * + * @author Gary McGath + */ +public final class TiffProfileClassITFPP1 extends TiffProfileClassIT +{ + public TiffProfileClassITFPP1 () + { + super (); + _profileText = "TIFF/IT-FP/P1 (ISO 12639:1998)"; + } + + /** + * Returns true if the IFD satisfies the requirements + * of the profile. See the documentation for + * details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!super.satisfiesThisProfile (ifd)) { + return false; + } + + // We now know this is a TiffIFD + TiffIFD tifd = (TiffIFD) ifd; + + // Tags which must be present + if (tifd.getImageDescription () == null || + tifd.getXPosition () == null || + tifd.getYPosition () == null) { + return false; + } + + // BitsPerSample=4 or 8 or {8,8,8} or {8,8,8,8} or undefined + // (consistent with PhotometricInterpretation) + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + int [] bps = niso.getBitsPerSample (); + if (!(bps == null || + (bps.length == 1 && (bps[0] == 4 || bps[0] == 8)) || + (bps.length == 3 && bps[0] == 8 && bps[1] == 8 && bps[2] == 8) || + (bps.length == 4 && bps[0] == 8 && + bps[1] == 8 && bps[2] == 8 && bps[3] == 8))) { + return false; + } + + // NewSubfileType bit 3=1 + long nsft = tifd.getNewSubfileType (); + if ((nsft & 8) == 0) { + return false; + } + + if (!satisfiesPhotometricInterpretation (tifd, new int [] {0, 1, 2, + 5} )) { + return false; + } + + + // SamplesPerPixel=3 or 4 or undefined (consistent with + // PhotometricInterpretation) + if (!satisfiesSamplesPerPixel (tifd, new int [] {3, 4, + NisoImageMetadata.NULL} )) { + return false; + } + + if (!satisfiesOrientation (tifd, 1)) { + return false; + } + + if (!satisfiesResolutionUnit (tifd, new int [] {2, 3} )) { + return false; + } + + if (!satisfiesPlanarConfiguration (tifd, 1)) { + return false; + } + + if (!satisfiesCompression (tifd, 1)) { + return false; + } + + // NumberOfInks=4, but only if PhotometricInterpretation=5 + // DotRange={0,255}, but only if PhotometricInterpretation=5 + int pint = niso.getColorSpace (); + int nInks = tifd.getNumberOfInks (); + if (pint == 5 && (nInks != 4 || !satisfiesDotRange (tifd, 0, 255))) { + return false; + } + + + /* Tags which must NOT be defined */ + if (tifd.getDocumentName () != null || + niso.getScannerModelName () != null || + tifd.getPageName () != null || + niso.getHostComputer () != null || + tifd.getSite () != null ) { + return false; + } + + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITFPP2.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITFPP2.java new file mode 100644 index 00000000..1a675c8a --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITFPP2.java @@ -0,0 +1,114 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Profile checker for TIFF Class IT-FP/P2. + * + * TIFF/IT-FP/P2 is a simplified form of TIFF/IT-FP. + * The differences between FP/P1 and FP/P2 are very minor. + * + * @author Gary McGath + */ +public final class TiffProfileClassITFPP2 extends TiffProfileClassIT +{ + public TiffProfileClassITFPP2 () + { + super (); + _profileText = "TIFF/IT-FP/P2 (ISO 12639:2003)"; + } + + /** + * Returns true if the IFD satisfies the requirements + * of the profile. See the documentation for + * details. + * + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!super.satisfiesThisProfile (ifd)) { + return false; + } + + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + + if (tifd.getImageDescription () == null) { + return false; + } + + // BitsPerSample=4 or 8 or {8,8,8} or {8,8,8,8} or undefined + // (consistent with PhotometricInterpretation) + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + int [] bps = niso.getBitsPerSample (); + if (!(bps == null || + (bps.length == 1 && (bps[0] == 4 || bps[0] == 8)) || + (bps.length == 3 && bps[0] == 8 && bps[1] == 8 && bps[2] == 8) || + (bps.length == 4 && bps[0] == 8 && + bps[1] == 8 && bps[2] == 8 && bps[3] == 8))) { + return false; + } + + // NewSubfileType bit 3=1 + long nsft = tifd.getNewSubfileType (); + if ((nsft & 8) == 0) { + return false; + } + + if (!satisfiesPhotometricInterpretation (tifd, new int [] {0, 1, 2, + 5} )) { + return false; + } + + + // SamplesPerPixel=3 or 4 or undefined (consistent with + // PhotometricInterpretation) + if (!satisfiesSamplesPerPixel (tifd, new int [] {3, 4, + NisoImageMetadata.NULL} )) { + return false; + } + + if (!satisfiesOrientation (tifd, 1)) { + return false; + } + + if (!satisfiesResolutionUnit (tifd, new int [] {2, 3} )) { + return false; + } + + if (!satisfiesPlanarConfiguration (tifd, 1)) { + return false; + } + + if (!satisfiesCompression (tifd, 1)) { + return false; + } + + // NumberOfInks=4, but only if PhotometricInterpretation=5 + // DotRange={0,255}, but only if PhotometricInterpretation=5 + int pint = niso.getColorSpace (); + int nInks = tifd.getNumberOfInks (); + if (pint == 5 && (nInks != 4 || !satisfiesDotRange (tifd, 0, 255))) { + return false; + } + + + /* Tags which must NOT be defined */ + if (tifd.getDocumentName () != null || + niso.getScannerModelName () != null || + tifd.getPageName () != null || + niso.getHostComputer () != null || + tifd.getSite () != null ) { + return false; + } + + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITHC.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITHC.java new file mode 100644 index 00000000..60bc1d04 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITHC.java @@ -0,0 +1,90 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Profile checker for TIFF Class IT-HC. + * + * The TIFF/IT spec states that "TIFF/IT-HC makes use of all + * the features and functionality supported by the TIFF and + * TIFF/IT fields appropriate to high resolution continuous + * tone images." + * + * @author Gary McGath + */ +public final class TiffProfileClassITHC extends TiffProfileClassIT +{ + public TiffProfileClassITHC () + { + super (); + _profileText = "TIFF/IT-HC (ISO 12639:1998)"; + } + + /** + * Returns true if the IFD satisfies the requirements + * of the profile. See the documentation for + * details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!super.satisfiesThisProfile (ifd)) { + return false; + } + + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + + /* Check required tags. */ + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + if (niso.getBitsPerSample () == null || + niso.getSamplesPerPixel () == NisoImageMetadata.NULL) { + return false; + } + + if (!satisfiesPhotometricInterpretation (tifd, 5)) { + return false; + } + + if (!satisfiesCompression (tifd, 32895)) { + return false; + } + + if (!satisfiesPlanarConfiguration (tifd, 1)) { + return false; + } + + int inkSet = tifd.getInkSet (); + if (inkSet != 1 && inkSet != 2) { + return false; + } + String seq = tifd.getColorSequence (); + if (seq == null || "CMYK".equals (seq)) { + if (inkSet != 1) { + return false; + } + } + + // Per footnote: If NumberOfInks tag is used, it must have the + // same value as SamplesPerPixel. This requirement doesn't + // apply to P1 or P2. + int spp = niso.getSamplesPerPixel (); + int numInks = tifd.getNumberOfInks (); + if (numInks != IFD.NULL && numInks != spp) { + return false; + } + + int trans = tifd.getTransparencyIndicator (); + if (trans != 0 && trans != 1) { + return false; + } + + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITHCP1.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITHCP1.java new file mode 100644 index 00000000..883bc1a4 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITHCP1.java @@ -0,0 +1,110 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Profile checker for TIFF Class IT-HC/P1. + * + * The TIFF/IT spec states that "TIFF/IT-HC/P1 is a simplified + * image file format profile for high resolution continuous tone + * (HC) image data and can be considered a constrained subset + * of TIFF/IT-HC specifically intended for simpler implementation." + * + * @author Gary McGath + */ +public final class TiffProfileClassITHCP1 extends TiffProfileClassIT +{ + public TiffProfileClassITHCP1 () + { + super (); + _profileText = "TIFF/IT-HC/P1 (ISO 12639:1998)"; + } + + /** + * Returns true if the IFD satisfies the requirements + * of the profile. See the documentation for + * details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!super.satisfiesThisProfile (ifd)) { + return false; + } + + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + + /* Check required tags. */ + if (!satisfiesSamplesPerPixel (tifd, 4)) { + return false; + } + + // BitsPerSample must be {8, 8, 8, 8} + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + int[] bps = niso.getBitsPerSample (); + if (bps == null || bps.length < 4) { + return false; + } + for (int i=0; i<4; i++) { + if (bps[i] != 8) { + return false; + } + } + + if (!satisfiesPhotometricInterpretation (tifd, 5)) { + return false; + } + + if (!satisfiesCompression (tifd, 32895)) { + return false; + } + + if (!satisfiesPlanarConfiguration (tifd, 1)) { + return false; + } + + if (!satisfiesResolutionUnit (tifd, new int [] {2, 3} )) { + return false; + } + + int inkSet = tifd.getInkSet (); + if (inkSet != 1) { + return false; + } + + if (tifd.getNumberOfInks () != 4) { + return false; + } + + // DotRange={0,255} + if (!satisfiesDotRange (tifd, 0, 255)) { + return false; + } + + int trans = tifd.getTransparencyIndicator (); + if (trans != 0 && trans != 1) { + return false; + } + + // The tags DocumentName, Model, PageName, HostComputer, + // Site, and ColorSequence must NOT be defined + + if (tifd.getDocumentName () != null || + niso.getScannerModelName () != null || + tifd.getPageName () != null || + niso.getHostComputer () != null || + tifd.getSite () != null || + tifd.getColorSequence () != null) { + return false; + } + + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITHCP2.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITHCP2.java new file mode 100644 index 00000000..8a36f1a5 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITHCP2.java @@ -0,0 +1,99 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Profile checker for TIFF Class IT-HC/P2. + * + * The TIFF/IT spec states that "TIFF/IT-HC/P2 is an extension of + * TIFF/IT-HC/P1." + * + * @author Gary McGath + */ +public final class TiffProfileClassITHCP2 extends TiffProfileClassIT +{ + public TiffProfileClassITHCP2 () + { + super (); + _profileText = "TIFF/IT-HC/P2 (ISO 12639:2003)"; + } + + /** + * Returns true if the IFD satisfies the requirements + * of the profile. See the documentation for + * details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!super.satisfiesThisProfile (ifd)) { + return false; + } + + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + + /* Check required tags. */ + + // BitsPerSample must be {8, ...} + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + int[] bps = niso.getBitsPerSample (); + if (bps == null || bps[0] != 8) { + return false; + } + + if (!satisfiesPhotometricInterpretation (tifd, 5)) { + return false; + } + + if (!satisfiesCompression (tifd, 32895)) { + return false; + } + + if (!satisfiesPlanarConfiguration (tifd, 1)) { + return false; + } + + if (!satisfiesResolutionUnit (tifd, new int [] {2, 3} )) { + return false; + } + + int inkSet = tifd.getInkSet (); + if (inkSet != 1) { + return false; + } + + if (tifd.getNumberOfInks () != 4) { + return false; + } + + // DotRange={0,255} + if (!satisfiesDotRange (tifd, 0, 255)) { + return false; + } + + int trans = tifd.getTransparencyIndicator (); + if (trans != 0 && trans != 1) { + return false; + } + + // The tags DocumentName, Model, PageName, HostComputer, + // Site, and ColorSequence must NOT be defined + + if (tifd.getDocumentName () != null || + niso.getScannerModelName () != null || + tifd.getPageName () != null || + niso.getHostComputer () != null || + tifd.getSite () != null) { + return false; + } + + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITLW.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITLW.java new file mode 100644 index 00000000..6fb193de --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITLW.java @@ -0,0 +1,87 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Profile checker for TIFF Class IT-LW. + * + * The TIFF/IT spec states that "TIFF/IT-LW makes use of all + * the features and functionality supported by the TIFF and + * TIFF/IT fields appropriate to line art images." + * + * @author Gary McGath + */ +public final class TiffProfileClassITLW extends TiffProfileClassIT +{ + public TiffProfileClassITLW () + { + super (); + _profileText = "TIFF/IT-LW (ISO 12639:1998)"; + } + + /** + * Returns true if the IFD satisfies the requirements + * of the profile. See the documentation for + * details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!super.satisfiesThisProfile (ifd)) { + return false; + } + + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + + /* Check required tags. */ + if (tifd.getColorTable () == null) { + return false; + } + + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + int[] bps = niso.getBitsPerSample (); + if (bps[0] != 8) { + return false; + } + + /* Check required values. */ + if (!satisfiesSamplesPerPixel (tifd, 1)) { + return false; + } + if (!satisfiesPhotometricInterpretation (tifd, 5)) { + return false; + } + /* NOTE: If compression is 32895, RasterPadding must be + * 0, 1, 2, 9, or 10. Fix this when RasterPadding support is + * implemented. */ + if (!satisfiesCompression (tifd, 32896)) { + return false; + } + + int inkSet = tifd.getInkSet (); + if (inkSet != 1 && inkSet != 2) { + return false; + } + String seq = tifd.getColorSequence (); + if (seq == null || "CMYK".equals (seq)) { + if (inkSet != 1) { + return false; + } + } + + // Per footnote h, this applies to LW, LW/P1 and LW/P2 + int spp = niso.getSamplesPerPixel (); + int numInks = tifd.getNumberOfInks (); + if (numInks != IFD.NULL && numInks != spp) { + return false; + } + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITLWP1.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITLWP1.java new file mode 100644 index 00000000..ebbc6554 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITLWP1.java @@ -0,0 +1,97 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Profile checker for TIFF Class IT-LW/P1. + * + * The TIFF/IT spec states that "TIFF/IT-LW/P1 is a simplified + * image file format profile for line art (LW) image data and + * can be considered as a constrained subset of TIFF/IT-LW specified + * for simpler implementation." + * + * @author Gary McGath + */ +public final class TiffProfileClassITLWP1 extends TiffProfileClassIT +{ + public TiffProfileClassITLWP1 () + { + super (); + _profileText = "TIFF/IT-LW/P1 (ISO 12639:1998)"; + } + + /** + * Returns true if the IFD satisfies the requirements + * of the profile. See the documentation for + * details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!super.satisfiesThisProfile (ifd)) { + return false; + } + + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + + /* Check required tags. */ + if (tifd.getColorTable () == null) { + return false; + } + + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + int [] bps = niso.getBitsPerSample (); + if (bps[0] != 8) { + return false; + } + + /* Check required values. */ + if (!satisfiesSamplesPerPixel (tifd, 1)) { + return false; + } + + if (!satisfiesResolutionUnit (tifd, new int [] {2, 3} )) { + return false; + } + + if (!satisfiesNewSubfileType (tifd, 0)) { + return false; + } + + if (!satisfiesPhotometricInterpretation (tifd, 5)) { + return false; + } + + if (!satisfiesCompression (tifd, 32896)) { + return false; + } + + int inkSet = tifd.getInkSet (); + if (inkSet != 1) { + return false; + } + + int bprl = tifd.getBitsPerRunLength (); + if (bprl != 8) { + return false; + } + + int bperl = tifd.getBitsPerExtendedRunLength (); + if (bperl != 16) { + return false; + } + + int numInks = tifd.getNumberOfInks (); + if (numInks != 4 || numInks != niso.getSamplesPerPixel ()) { + return false; + } + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITLWP2.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITLWP2.java new file mode 100644 index 00000000..d5f38889 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITLWP2.java @@ -0,0 +1,88 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Profile checker for TIFF Class IT-LW/P2. + * + * The TIFF/IT spec states that "TIFF/IT-LW/P1 is a simplified + * image file format profile for line art (LW) image data and + * can be considered as a constrained subset of TIFF/IT-LW specified + * for simpler implementation. TIFF/IT-LW/P2 can be considered + * as an extension of TIFF/IT-LW/P1." + * @author Gary McGath + */ +public final class TiffProfileClassITLWP2 extends TiffProfileClassIT +{ + public TiffProfileClassITLWP2 () + { + super (); + _profileText = "TIFF/IT-LW/P2 (ISO 12639:2003)"; + } + + /** + * Returns true if the IFD satisfies the requirements + * of the profile. See the documentation for + * details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!super.satisfiesThisProfile (ifd)) { + return false; + } + + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + + /* Check required tags. */ + if (tifd.getColorTable () == null) { + return false; + } + + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + int [] bps = niso.getBitsPerSample (); + if (bps[0] != 8) { + return false; + } + + /* Check required values. */ + if (!satisfiesSamplesPerPixel (tifd, 1)) { + return false; + } + + if (!satisfiesResolutionUnit (tifd, new int [] {2, 3} )) { + return false; + } + + if (!satisfiesPhotometricInterpretation (tifd, 5)) { + return false; + } + + if (!satisfiesCompression (tifd, 32896)) { + return false; + } + + int inkSet = tifd.getInkSet (); + if (inkSet != 1) { + return false; + } + + int bperl = tifd.getBitsPerExtendedRunLength (); + if (bperl != 16) { + return false; + } + + int numInks = tifd.getNumberOfInks (); + if (numInks != 4 || numInks != niso.getSamplesPerPixel ()) { + return false; + } + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITMP.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITMP.java new file mode 100644 index 00000000..cfe73dba --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITMP.java @@ -0,0 +1,79 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Profile checker for TIFF Class IT-MP. + * + * The TIFF/IT spec states that "TIFF/IT-MP makes use of all + * the features and functionality supported by the TIFF and + * TIFF/IT fields appropriate to monochrome continuous + * tone picture images." + * + * @author Gary McGath + */ +public final class TiffProfileClassITMP extends TiffProfileClassIT +{ + public TiffProfileClassITMP () + { + super (); + _profileText = "TIFF/IT-MP (ISO 12639:1998)"; + } + + /** + * Returns true if the IFD satisfies the requirements + * of the profile. See the documentation for + * details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!super.satisfiesThisProfile (ifd)) { + return false; + } + + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + + /* Check required tags. */ + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + if (niso.getBitsPerSample () == null) { + return false; + } + + if (!satisfiesCompression (tifd, new int [] {1, 32895} )) { + return false; + } + + if (!satisfiesPhotometricInterpretation (tifd, new int [] {0, 1} )) { + return false; + } + + // RasterPadding=0,1,2,9, or 10, but only if Compression=32895 + if (niso.getCompressionScheme () == 32895) { + int pad = tifd.getRasterPadding (); + if (pad != 0 && pad != 1 && pad != 2 && pad != 9 && pad != 10) { + return false; + } + } + + if (!satisfiesImageColorIndicator (tifd, new int [] {0, 1} )) { + return false; + } + + // ImageColorValue is defined if ImageColorIndicator=1 + int ind = tifd.getImageColorIndicator (); + if (ind == 1) { + if (tifd.getImageColorValue () == IFD.NULL) { + return false; + } + } + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITMPP1.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITMPP1.java new file mode 100644 index 00000000..dac0fb87 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITMPP1.java @@ -0,0 +1,112 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Profile checker for TIFF Class IT-MP/P1. + * + * The TIFF/IT spec states that "TIFF/IT-MP/P1 is a simplified + * image file format profile for monochrome continuous tone + * picture image (MP) data and can be considred a constrained + * subset of TIFF/IT-MP specifically intended for + * simpler implementation." + * + * @author Gary McGath + */ +public final class TiffProfileClassITMPP1 extends TiffProfileClassIT +{ + public TiffProfileClassITMPP1 () + { + super (); + _profileText = "TIFF/IT-MP/P1 (ISO 12639:1998)"; + } + + /** + * Returns true if the IFD satisfies the requirements + * of the profile. See the documentation for + * details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!super.satisfiesThisProfile (ifd)) { + return false; + } + + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + + if (!satisfiesNewSubfileType (tifd, 0)) { + return false; + } + + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + int[] bps = niso.getBitsPerSample (); + if (bps == null || bps[0] != 8) { + return false; + } + + if (!satisfiesCompression (tifd, 1 )) { + return false; + } + + if (!satisfiesPhotometricInterpretation (tifd, 0)) { + return false; + } + + if (!satisfiesOrientation (tifd, 1)) { + return false; + } + + if (!satisfiesSamplesPerPixel (tifd, 1)) { + return false; + } + + if (!satisfiesResolutionUnit (tifd, new int [] { 2, 3} )) { + return false; + } + + if (!satisfiesDotRange (tifd, 0, 255)) { + return false; + } + + int ind = tifd.getImageColorIndicator (); + if (ind != 0 && ind != 1) { + return false; + } + + // ImageColorValue is defined if ImageColorIndicator=1 + if (ind == 1) { + if (tifd.getImageColorValue () == IFD.NULL) { + return false; + } + } + + // PixelIntesityRange={0,255} + int [] pir = tifd.getPixelIntensityRange (); + if (pir == null || pir.length < 2) { + return false; + } + if (pir[0] != 0 || pir[1] != 255) { + return false; + } + + // Tags which must NOT be defined + if (tifd.getDocumentName () != null || + niso.getScannerModelName () != null || + tifd.getPageName () != null || + niso.getHostComputer () != null || + tifd.getSite () != null || + tifd.getColorSequence () != null || + tifd.getIT8Header() != null) { + return false; + } + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITMPP2.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITMPP2.java new file mode 100644 index 00000000..5e4ae8c9 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITMPP2.java @@ -0,0 +1,109 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Profile checker for TIFF Class IT-MP/P2. + * + * The TIFF/IT spec states that "TIFF/IT-MP/P2 is an extension of + * TIFF/IT-MP/P1." + * + * @author Gary McGath + */ +public final class TiffProfileClassITMPP2 extends TiffProfileClassIT +{ + public TiffProfileClassITMPP2 () + { + super (); + _profileText = "TIFF/IT-MP/P2 (ISO 12639:2003)"; + } + + /** + * Returns true if the IFD satisfies the requirements + * of the profile. See the documentation for + * details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!super.satisfiesThisProfile (ifd)) { + return false; + } + + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + + if (!satisfiesNewSubfileType (tifd, 0)) { + return false; + } + + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + int[] bps = niso.getBitsPerSample (); + if (bps == null || bps[0] != 8) { + return false; + } + + if (!satisfiesCompression (tifd, new int[] {1, 7, 8} )) { + return false; + } + + if (!satisfiesPhotometricInterpretation (tifd, 0)) { + return false; + } + + if (!satisfiesOrientation (tifd, 1)) { + return false; + } + + if (!satisfiesSamplesPerPixel (tifd, 1)) { + return false; + } + + if (!satisfiesResolutionUnit (tifd, new int [] { 2, 3} )) { + return false; + } + + if (!satisfiesDotRange (tifd, 0, 255)) { + return false; + } + + int ind = tifd.getImageColorIndicator (); + if (ind != 0 && ind != 1) { + return false; + } + + // ImageColorValue is defined if ImageColorIndicator=1 + if (ind == 1) { + if (tifd.getImageColorValue () == IFD.NULL) { + return false; + } + } + + // PixelIntesityRange={0,255} + int [] pir = tifd.getPixelIntensityRange (); + if (pir == null || pir.length < 2) { + return false; + } + if (pir[0] != 0 || pir[1] != 255) { + return false; + } + + // Tags which must NOT be defined + if (tifd.getDocumentName () != null || + niso.getScannerModelName () != null || + tifd.getPageName () != null || + niso.getHostComputer () != null || + tifd.getSite () != null || + tifd.getColorSequence () != null || + tifd.getIT8Header() != null) { + return false; + } + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITSD.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITSD.java new file mode 100644 index 00000000..44cc5451 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITSD.java @@ -0,0 +1,84 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Profile checker for TIFF Class IT-SD. + * + * The TIFF/IT spec states that "TIFF/IT-SD makes use of all + * the features and functionality supported by the TIFF and + * TIFF/IT fields appropriate to prescreened (copydot) + * colour separation images." + * + * @author Gary McGath + */ +public final class TiffProfileClassITSD extends TiffProfileClassIT +{ + public TiffProfileClassITSD () + { + super (); + _profileText = "TIFF/IT-SD (ISO 12639:2003)"; + } + + /** + * Returns true if the IFD satisfies the requirements + * of the profile. See the documentation for + * details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!super.satisfiesThisProfile (ifd)) { + return false; + } + + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + + /* Check required values. */ + + int[] bps = niso.getBitsPerSample (); + if (bps[0] != 1) { + return false; + } + + if (!satisfiesResolutionUnit (tifd, new int[] {2, 3})) { + return false; + } + + if (!satisfiesSamplesPerPixel (tifd, new int[] {1, 4})) { + return false; + } + + if (!satisfiesPhotometricInterpretation (tifd, 5)) { + return false; + } + + if (!satisfiesCompression (tifd, new int [] {1, 4, 8} )) { + return false; + } + + if (!satisfiesPlanarConfiguration (tifd, 2)) { + return false; + } + + int inkSet = tifd.getInkSet (); + if (inkSet != 1 ) { + return false; + } + + String seq = tifd.getColorSequence (); + if (seq != null && !"CMYK".equals (seq) && !"YMCK".equals (seq)) { + return false; + } + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITSDP2.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITSDP2.java new file mode 100644 index 00000000..a1bbe23a --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassITSDP2.java @@ -0,0 +1,102 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Profile checker for TIFF Class IT-SD/P2. + * + * The TIFF/IT spec states that "TIFF/IT-SD/P2 is a simplified file + * format profile for screened data image (SD) data and can be + * considered a constrained subset of TIFF/IT-SD specifically + * intended for simpler implementation." + * + * There is no TIFF/IT-SD/P1. + * + * @author Gary McGath + */ +public final class TiffProfileClassITSDP2 extends TiffProfileClassIT +{ + public TiffProfileClassITSDP2 () + { + super (); + _profileText = "TIFF/IT-SD/P2 (ISO 12639:2003)"; + } + + /** + * Returns true if the IFD satisfies the requirements + * of the profile. See the documentation for + * details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!super.satisfiesThisProfile (ifd)) { + return false; + } + + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + + /* Check required values. */ + + int[] bps = niso.getBitsPerSample (); + if (bps[0] != 1) { + return false; + } + + if (!satisfiesResolutionUnit (tifd, new int[] {2, 3})) { + return false; + } + + if (!satisfiesSamplesPerPixel (tifd, new int[] {1, 4})) { + return false; + } + + if (!satisfiesPhotometricInterpretation (tifd, 5)) { + return false; + } + + if (!satisfiesCompression (tifd, new int [] {1, 4, 8} )) { + return false; + } + + if (!satisfiesPlanarConfiguration (tifd, 2)) { + return false; + } + + int inkSet = tifd.getInkSet (); + if (inkSet != 1 ) { + return false; + } + + int numInks = tifd.getNumberOfInks (); + if (numInks != 4) { + return false; + } + + if (!satisfiesOrientation (tifd, 1)) { + return false; + } + + /* Tags which must NOT be defined */ + if (tifd.getDocumentName () != null || + niso.getScannerModelName () != null || + tifd.getPageName () != null || + niso.getHostComputer () != null || + tifd.getSite () != null || + tifd.getColorSequence () != null || + tifd.getIT8Header () != null) { + return false; + } + + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassP.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassP.java new file mode 100644 index 00000000..529ca663 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassP.java @@ -0,0 +1,71 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Profile checker for TIFF Class P (Baseline Palette color). + * + * @author Gary McGath + */ +public final class TiffProfileClassP extends TiffProfile +{ + public TiffProfileClassP () + { + super (); + _profileText = "Baseline palette-color (Class P)"; + } + + /** + * Returns true if the IFD satisfies the requirements of a + * Class P profile. See the TIFF 6.0 specification for + * details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + + /* Check required tags. */ + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + if (niso.getImageWidth () == NisoImageMetadata.NULL || + niso.getImageLength () == NisoImageMetadata.NULL || + niso.getStripOffsets () == null || + niso.getRowsPerStrip () == NisoImageMetadata.NULL || + niso.getStripByteCounts () == null || + niso.getXSamplingFrequency () == null || + niso.getYSamplingFrequency () == null || + niso.getColormapBitCodeValue () == null || + niso.getColormapRedValue () == null || + niso.getColormapGreenValue () == null || + niso.getColormapBlueValue () == null) { + return false; + } + + /* Check required values. */ + int[] bps = niso.getBitsPerSample (); + if (bps == null || (bps[0] != 4 && bps[0] != 8)) { + return false; + } + + if (!satisfiesCompression (tifd, new int [] {1, 32773} )) { + return false; + } + + if (!satisfiesPhotometricInterpretation (tifd, 3)) { + return false; + } + + if (!satisfiesResolutionUnit (tifd, new int [] {1, 2, 3} )) { + return false; + } + + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassR.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassR.java new file mode 100644 index 00000000..4d85d315 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassR.java @@ -0,0 +1,72 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Profile checker for TIFF Class R (Baseline Palette color). + * + * @author Gary McGath + */ +public final class TiffProfileClassR extends TiffProfile +{ + public TiffProfileClassR () + { + super (); + _profileText = "Baseline RGB (Class R)"; + } + + /** + * Returns true if the IFD satisfies the requirements of a + * Class R profile. See the TIFF 6.0 specification for + * details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + + /* Check required tags. */ + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + if (niso.getImageWidth () == NisoImageMetadata.NULL || + niso.getImageLength () == NisoImageMetadata.NULL || + niso.getStripOffsets () == null || + niso.getRowsPerStrip () == NisoImageMetadata.NULL || + niso.getStripByteCounts () == null || + niso.getXSamplingFrequency () == null || + niso.getYSamplingFrequency () == null) { + return false; + } + + /* Check required values. */ + int [] bps = niso.getBitsPerSample (); + if (bps == null || bps.length < 3 || + bps[0] != 8 || bps[1] != 8 || bps[2] != 8) { + return false; + } + + if (!satisfiesCompression (tifd, new int [] {1, 32773} )) { + return false; + } + + if (!satisfiesPhotometricInterpretation (tifd, 2)) { + return false; + } + + if (niso.getSamplesPerPixel () < 3) { + return false; + } + + if (!satisfiesResolutionUnit (tifd, new int [] {1, 2, 3} )) { + return false; + } + + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassY.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassY.java new file mode 100644 index 00000000..a9af46bf --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileClassY.java @@ -0,0 +1,73 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Profile checker for TIFF Class Y (Baseline YCbCr). + * + * @author Gary McGath + */ +public final class TiffProfileClassY extends TiffProfile +{ + public TiffProfileClassY () + { + super (); + _profileText = "Extension YCbCr (Class Y)"; + } + + /** + * Returns true if the IFD satisfies the requirements of a + * Class Y profile. See the TIFF 6.0 specification for + * details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + + /* Check required tags. */ + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + if (niso.getImageWidth () == NisoImageMetadata.NULL || + niso.getImageLength () == NisoImageMetadata.NULL || + niso.getStripOffsets () == null || + niso.getRowsPerStrip () == NisoImageMetadata.NULL || + niso.getStripByteCounts () == null || + niso.getXSamplingFrequency () == null || + niso.getYSamplingFrequency () == null || + niso.getReferenceBlackWhite () == null) { + return false; + } + + /* Check required values. */ + if (!satisfiesSamplesPerPixel (tifd, 3)) { + return false; + } + + int[] bps = niso.getBitsPerSample (); + if (bps == null || bps.length < 3 || + bps[0] != 8 || bps[1] != 8 || bps[2] != 8) { + return false; + } + + if (!satisfiesCompression (tifd, new int [] {1, 5, 6} )) { + return false; + } + + if (!satisfiesPhotometricInterpretation (tifd, 6)) { + return false; + } + + if (!satisfiesResolutionUnit (tifd, new int [] {1, 2, 3} )) { + return false; + } + + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileDLF.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileDLF.java new file mode 100644 index 00000000..253fff53 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileDLF.java @@ -0,0 +1,80 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Abstract superclass for the profile checkers for TIFF/DLF + */ +public abstract class TiffProfileDLF extends TiffProfile +{ + public TiffProfileDLF () + { + super (); + } + + /** + * Returns true if the IFD satisfies the requirements + * that are common to the bilevel, grayscale, and color + * DLF profiles. The subclasses should call super(ifd) + * first, then do additional checking if it returns true. + * details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + + if (!satisfiesPhotometricInterpretation (tifd, new int[] {0, 1} )) { + return false; + } + return true; // passed all tests + } + + /** Checks for minimum X and Y resolution. + * All of the DLF profiles have similar tests for + * XResolution and YResolution. In all cases the + * values depend on the ResolutionUnit, which must be + * either 2 or 3. + * + * @param tifd The TiffIFD from which to extract the tags. + * @param minUnit2Res The minimum XResolution and YResolution + * when ResolutionUnit is 2 + * @param minUnit3Res The minimum XResolution and YResolution + * when ResolutionUnit is 3 + */ + + protected boolean hasMinimumResolution (TiffIFD tifd, double minUnit2Res, + double minUnit3Res) + { + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + Rational xrat = niso.getXSamplingFrequency (); + Rational yrat = niso.getYSamplingFrequency (); + if (xrat == null || yrat == null) { + return false; + } + + int resUnit = niso.getSamplingFrequencyUnit (); + if (resUnit == 2) { + if (xrat.toDouble() < minUnit2Res || yrat.toDouble() < minUnit2Res) { + return false; + } + } + else if (resUnit == 3) { + if (xrat.toDouble() < minUnit3Res || yrat.toDouble() < minUnit3Res) { + return false; + } + } + else { + return false; // resUnit must be 2 or 3 + } + + return true; // passed all tests + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileDLFBW.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileDLFBW.java new file mode 100644 index 00000000..f7f81829 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileDLFBW.java @@ -0,0 +1,51 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + + + +/** + * Profile checker for TIFF DLF Benchmark for Faithful Digital + * Reproductions of Monographs and Serials: black and white. + */ +public final class TiffProfileDLFBW extends TiffProfileDLF +{ + public TiffProfileDLFBW () + { + super (); + _profileText = "DLF Benchmark for Faithful Digital " + + "Reproductions of Monographs and Serials: " + + "black and white"; + } + + /** + * Returns true if the IFD satisfies the requirements + * of the profile. See the documentation for + * details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + + if (!satisfiesCompression (tifd, new int [] {1, 6} )) { + return false; + } + + if (!satisfiesPhotometricInterpretation (tifd, new int [] {0, 1} )) { + return false; + } + + /* XResolution and YResolution >= 600 (inches) or 1520 (cm) */ + if (!hasMinimumResolution (tifd, 600.0, 1520.0)) { + return false; + } + + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileDLFColor.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileDLFColor.java new file mode 100644 index 00000000..fe32cfd0 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileDLFColor.java @@ -0,0 +1,60 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + + +/** + * Profile checker for TIFF DLF Benchmark for Faithful Digital + * Reproductions of Monographs and Serials: color. + */ +public final class TiffProfileDLFColor extends TiffProfileDLF +{ + public TiffProfileDLFColor () + { + super (); + _profileText = "DLF Benchmark for Faithful Digital " + + "Reproductions of Monographs and Serials: color"; + } + + /** + * Returns true if the IFD satisfies the requirements + * of the profile. See the documentation for + * details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + + if (!satisfiesCompression (tifd, new int [] {1, 5, 32773} )) { + return false; + } + + if (!satisfiesPhotometricInterpretation (tifd, new int [] {2, 6} )) { + return false; + } + + if (!satisfiesSamplesPerPixel (tifd, new int [] {3} )) { + return false; + } + + int[] bps = tifd.getNisoImageMetadata ().getBitsPerSample (); + for (int i=0; i= 300 (in) or 760 (cm) */ + if (!hasMinimumResolution (tifd, 300.0, 760.0)) { + return false; + } + + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileDLFGray.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileDLFGray.java new file mode 100644 index 00000000..b783cf2d --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileDLFGray.java @@ -0,0 +1,60 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + + + +/** + * Profile checker for TIFF DLF Benchmark for Faithful Digital + * Reproductions of Monographs and Serials: grayscale. + */ +public final class TiffProfileDLFGray extends TiffProfileDLF +{ + public TiffProfileDLFGray () + { + super (); + _profileText = "DLF Benchmark for Faithful Digital " + + "Reproductions of Monographs and Serials: " + + "grayscale and white"; + } + + /** + * Returns true if the IFD satisfies the requirements + * of the profile. See the documentation for + * details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + + if (!satisfiesCompression (tifd, new int [] {1, 5, 32773} )) { + return false; + } + + if (!satisfiesPhotometricInterpretation (tifd, new int [] {0, 1} )) { + return false; + } + + if (!satisfiesSamplesPerPixel (tifd, new int [] {1} )) { + return false; + } + + int[] bps = tifd.getNisoImageMetadata ().getBitsPerSample (); + if (bps == null || bps[0] != 8) { + return false; + } + + /* XResolution and YResolution >= 300 (in) or 760 (cm) */ + if (!hasMinimumResolution (tifd, 300.0, 760.0)) { + return false; + } + + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileDNG.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileDNG.java new file mode 100644 index 00000000..38cde5d6 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileDNG.java @@ -0,0 +1,123 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Profile checker for the DNG raw IFD. + * + * + * @author Gary McGath + * + * @see TiffProfileDNG + * + */ +public class TiffProfileDNG extends TiffProfile { + + /** PhotometricInterpretation for CFA space */ + public final static int CFA = 32803; + + /* PhotometricInterpretation for LinearRaw space */ + public final static int LINEAR_RAW = 34892; + + /* Set to true if anything directly contravenes DNG, + * or a previous profile has reported as DNG */ +// private boolean notDNG; + + /* An IFD has been seen with a photometricInterpretation + * specific to DNG. */ +// private boolean photoInterpOK; + + /* Orientation has been specified. */ +// private boolean orientationSeen; + + /* DNGVersion tag has been seen */ +// private boolean dngVersionSeen; + + /* UniqueCameraModel tag has been seen */ +// private boolean uniqueCameraModelSeen; + + /* AsShotNeutral tag has been seen. This isn't required, + * but is mutually exclusive with AsShotWhiteXY. */ + private boolean asShotNeutralSeen; + + /* AsShotWhiteXY tag has been seen. This isn't required, + * but is mutually exclusive with AsShotNeutral. */ + private boolean asShotWhiteXYSeen; + + /** + * + */ + public TiffProfileDNG() { + super(); + _profileText = "DNG 1.0.0.0 (September 2004)"; + //notDNG = false; + //photoInterpOK = false; + //orientationSeen = false; + //dngVersionSeen = false; + //uniqueCameraModelSeen = false; + } + + /** + * Returns true if the IFD satisfies the requirements + * of the profile. See the documentation for + * details. + */ + public boolean satisfiesThisProfile(IFD ifd) { + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + + /* Check if this is the "raw" profile. */ + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + int pInterpretation = niso.getColorSpace (); + if (!(pInterpretation == CFA || pInterpretation == LINEAR_RAW)) { + return false; + } + /* BitsPerSample must be 8 to 32, and same for all samples */ + int[] bps = niso.getBitsPerSample(); + if (bps != null) { + int bpsval = bps[0]; + if (bpsval < 8 || bpsval > 32) { + return false; + } + for (int i = 0; i < bps.length; i++) { + if (bpsval != bps[i]) { + return false; + } + } + } + + /* If the photometric interpretation is CFA, there must be + * certain other tags. */ + if (pInterpretation == CFA) { + if (tifd.getCFAPlaneColor() == null || + tifd.getCFARepeatPatternDim() == null || + tifd.getCFAPattern() == null) { + return false; + } + } + + /* Orientation is required. */ + if (niso.getOrientation() == NisoImageMetadata.NULL) { + return false; + } + + /* Compression must be 1 or 7 */ + int compression = niso.getCompressionScheme (); + if (compression != NisoImageMetadata.NULL) { + if (!(compression == 1 || compression == 7)) { + return false; + } + } + + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileDNGThumb.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileDNGThumb.java new file mode 100644 index 00000000..d86f98d1 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileDNGThumb.java @@ -0,0 +1,68 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; +//import edu.harvard.hul.ois.jhove.module.TiffModule; + +/** + * IFD 0 of a DNG document must satisfy this profile. It doesn't + * actually have to be a "thumbnail" in the sense of containing + * a low-resolution image, but it has to contain the "IFD 0" + * tags specified by DNG. In addition, + * some other document must satisfy TiffProfileDNG. + * + * @author Gary McGath + * @see TiffProfileDNG + */ +public class TiffProfileDNGThumb extends TiffProfile { + + /** + * + */ + public TiffProfileDNGThumb() { + super(); + } + + /** + * Returns true if the IFD satisfies the requirements of a + * DNG thumbnail profile. + */ + public boolean satisfiesThisProfile(IFD ifd) + { + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + /* Check required tags. */ + if (tifd.getDNGVersion() == null) { + return false; + } + + if (tifd.getNewSubfileType() != 1) { + return false; + } + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + + if (tifd.getAsShotNeutral () != null && + tifd.getAsShotWhiteXY () != null) { + // There can be only one + return false; + } + + if (tifd.getUniqueCameraModel () == null) { + return false; + } + + /* The specification says that PhotometricInterpretation + * must be 1 or 2 for a thumbnail -- but there's no requirement + * that this BE a thumbnail. So that requirement appears + * to be moot. */ + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileEP.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileEP.java new file mode 100644 index 00000000..3504ce35 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileEP.java @@ -0,0 +1,148 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + + +/** + * Profile checker for TIFF/EP. + * For TIFF/EP, no default values may be assumed. + * At the moment, we have no way to determine which values + * were defaulted, so defaults are shown even if the file + * satisfies the EP profile. + * + * This class also serves as the base class for DNG, + * which is defined as a restricted subset of TIFF/EP. + */ +public class TiffProfileEP extends TiffProfile +{ + public TiffProfileEP () + { + super (); + _profileText = "TIFF/EP (ISO 12234-2:2001)"; + } + + /** + * Returns true if the IFD satisfies the requirements of a + * TIFF/EP profile. See the TIFF/EP specification for details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + + /* Check required tags. */ + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + if (niso.getImageWidth () == NisoImageMetadata.NULL || + niso.getImageLength () == NisoImageMetadata.NULL || + niso.getBitsPerSample () == null || + tifd.getImageDescription () == null || + niso.getXSamplingFrequency () == null || + niso.getYSamplingFrequency () == null || + niso.getScannerManufacturer () == null || + (niso.getScannerModelName () == null && + niso.getScannerModelNumber () == null) || + niso.getScanningSoftware() == null || + tifd.getImageDescription () == null || + tifd.getCopyright () == null || + niso.getDateTimeCreated () == null || + tifd.getDateTime () == null || + tifd.getTIFFEPStandardID () == null) { + return false; + } + /* Must have either a full complement of strip tags or + * a full complement of tile tags. + */ + if (!(niso.getStripOffsets () != null && + niso.getRowsPerStrip () != NisoImageMetadata.NULL && + niso.getStripByteCounts () != null) && + !(niso.getTileWidth () != NisoImageMetadata.NULL && + niso.getTileLength () != NisoImageMetadata.NULL && + niso.getTileOffsets () != null && + niso.getTileByteCounts () != null)) { + return false; + } + + long subfile = tifd.getNewSubfileType (); + if (subfile != 0 && subfile != 1) { + return false; + } + + if (!satisfiesResolutionUnit (tifd, new int [] {1, 2, 3} )) { + return false; + } + + if (!satisfiesOrientation (tifd, new int [] {NisoImageMetadata.NULL, + 1, 3, 6, 8, 9} )) { + return false; + } + + int pInterpretation = niso.getColorSpace (); + if (!(pInterpretation == 1 || + pInterpretation == 2 || + pInterpretation == 6 || + pInterpretation == 32803 || + pInterpretation > 32767)) { + return false; + } + + int config = niso.getPlanarConfiguration (); + if (config != 1 && config != 2) { + return false; + } + + int method = niso.getSensor (); + if (method == NisoImageMetadata.NULL || method < 0 || method > 8) { + return false; + } + + if (pInterpretation == 32803) { + if (tifd.getCFARepeatPatternDim () == null) { + return false; + } + if (tifd.getCFAPattern () == null) { + return false; + } + } + + /* Make sure PhotometricInterpretation and SamplesPerPixel + * are compatible. + */ + int samplesPerPixel = niso.getSamplesPerPixel (); + if (pInterpretation == 1 || pInterpretation == 32803) { + if (samplesPerPixel != 1) { + return false; + } + } + if (pInterpretation == 2 || pInterpretation == 6) { + if (samplesPerPixel != 3) { + return false; + } + } + if (pInterpretation == 6) { + if (niso.getYCbCrCoefficients() == null || + niso.getYCbCrSubSampling () == null || + niso.getYCbCrPositioning () == NisoImageMetadata.NULL || + niso.getReferenceBlackWhite () == null) { + return false; + } + } + // meteringMode and exposureProgram checks deleted, per Bugzilla #33 + + int compression = niso.getCompressionScheme (); + if (compression != NisoImageMetadata.NULL) { + // Corrected 6-Jan-04 per Bugzilla #33 + if (!(compression == 1 || compression == 7 || + compression > 32767)) { + return false; + } + } + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileExif.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileExif.java new file mode 100644 index 00000000..c9f046c7 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileExif.java @@ -0,0 +1,125 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + + +/** + * Profile checker for Exif. This applies to the main IFD + * of the file. To satisfy the Exif profile, the thumbnail + * IFD must also satisfy TiffProfileExifThumb. + * + * @see TiffProfileExifThumb + * @see TiffProfileExifIFD + */ +public final class TiffProfileExif extends TiffProfile +{ + /* The profile text depends on the version. */ + private String[] profileText = { "Exif 2.0", + "Exif 2.1 (JEIDA-49-1998)", + "Exif 2.2 (JEITA CP-3451)" + }; + private TiffProfileExifIFD _exifIFDProfile; + + public TiffProfileExif () + { + super (); + //_profileText = "Exif 2.2 (JEITA CP-3451)"; + _exifIFDProfile = new TiffProfileExifIFD (); + } + + /** + * Returns true if the IFD satisfies the requirements of an + * Exif profile. See the Exif specification for details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + + /* Check required tags. */ + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + if (niso.getXSamplingFrequency () == null || + niso.getYSamplingFrequency () == null) { + return false; + } + + if (satisfiesCompression (tifd, 1)) { + if (niso.getImageWidth () == NisoImageMetadata.NULL || + niso.getImageLength () == NisoImageMetadata.NULL || + niso.getStripOffsets () == null || + niso.getRowsPerStrip () == NisoImageMetadata.NULL || + niso.getStripByteCounts () == null) { + return false; + } + if (niso.getSamplesPerPixel () != 3) { + return false; + } + /* BitsPerSample must be [8, 8, 8] */ + int[] bps = niso.getBitsPerSample (); + if (bps == null || bps.length < 3 || bps[0] != 8 || bps[1] != 8 || + bps[2] != 8) { + return false; + } + int pInterpretation = niso.getColorSpace (); + if (!(pInterpretation == 2 || pInterpretation == 6)) { + return false; + } + if (pInterpretation == 6) { + if (niso.getYCbCrSubSampling () == null || + niso.getYCbCrPositioning () == NisoImageMetadata.NULL) { + return false; + } + } + } + else { + // If the compression isn't 1, then the JPEGInterchangeFormat + // tag must be present, but other requirements are lifted. + if (tifd.getJpegInterchangeFormat() == NisoImageMetadata.NULL) { + return false; + } + } + + + + + if (!satisfiesResolutionUnit (tifd, new int [] {2, 3} )) { + return false; + } + + /* for the first IFD only, there must be an Exif subifd */ + if (tifd.isFirst ()) { + ExifIFD eifd = tifd.getTheExifIFD (); + if (eifd == null) { + return false; + } + // The Exif IFD must satisfy the profile requirements + if (!_exifIFDProfile.satisfiesThisProfile(eifd)) { + return false; + } + String version = eifd.getExifVersion (); + int idx = 0; + // If we passed the profile, the version will be one of + // the following. + if (version.equals ("0220")) { + idx = 2; + } + else if (version.equals ("0210")) { + idx = 1; + } + else if (version.equals ("0200")) { + idx = 0; + } + _profileText = profileText[idx]; + } + + + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileExifIFD.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileExifIFD.java new file mode 100644 index 00000000..56101e8c --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileExifIFD.java @@ -0,0 +1,67 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +/** + * Profile checker for the Exif IFD of a TIFF file which potentially + * matches the TIFF profile. This is called from TiffProfileExif + * to check the Exif IFD. + * + * @author Gary McGath + * + */ +public class TiffProfileExifIFD extends TiffProfile { + + private int _majVersion; + private int _minVersion; + + public TiffProfileExifIFD () + { + super (); + // This isn't used directly to report a profile, so the + // profile text is irrelevant. + _profileText = null; + _majVersion = -1; + _minVersion = -1; + } + + /** + * Returns true if the IFD satisfies the requirements of an + * Exif profile. See the Exif specification for details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!(ifd instanceof ExifIFD)) { + return false; + } + ExifIFD eifd = (ExifIFD) ifd; + String version = eifd.getExifVersion (); + if (version.equals ("0220")) { + _majVersion = 2; + _minVersion = 2; + } + else if (version.equals ("0210")) { + _majVersion = 2; + _minVersion = 1; + } + else if (version.equals ("0200")) { + _majVersion = 2; + _minVersion = 0; + } + else { + // Other versions aren't accepted + return false; + } + if (!(eifd.getFlashpixVersion ().equals ("0100"))) { + return false; + } + int colspc = eifd.getColorspace (); + if (colspc != 1 && colspc != 65535) { + return false; + } + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileExifThumb.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileExifThumb.java new file mode 100644 index 00000000..621b1174 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileExifThumb.java @@ -0,0 +1,71 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +/** + * Profile checker for the thumbnail IFD of a + * TIFF file potentially meeting the TIFF profile. + * + * This doesn't go into the _profiles list of TiffIFD, + * but rather is one of two (or more?) profiles that must + * be checked to determine if the file meets the Exif + * profile. It should be called only for the "thumbnail" + * IFD, which is the second top-level IFD. + * + * + * @author Gary McGath + * + * @see TiffProfileExif + */ +public class TiffProfileExifThumb extends TiffProfile { + + /** Compression scheme of the main IFD. We need to check + * our compression against the main IFD's compression. */ + int mainCompression; + + public TiffProfileExifThumb () + { + super (); + // This isn't used directly to report a profile, so the + // profile text is irrelevant. + _profileText = null; + } + + + /** + * Record the compression scheme of the main IFD; required + * for comparison. + */ + public void setMainCompression (int comp) + { + mainCompression = comp; + } + + + + /** + * Returns true if the IFD satisfies the requirements of a + * thumbnail IFD for an + * Exif profile. See the Exif specification for details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + if (!satisfiesCompression (tifd, new int [] {1, 6} )) { + return false; + } + + // If the main IFD is uncompressed, the thumbnail must be too + if (mainCompression == 1 && + tifd.getNisoImageMetadata().getCompressionScheme() != 1) { + return false; + } + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileFXC.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileFXC.java new file mode 100644 index 00000000..8645db0d --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileFXC.java @@ -0,0 +1,140 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * + * Profile checker for TIFF FX, Profile C (Baseline Color). + * + * Image data content is not checked for profile conformance. + * Only tags are checked. + * + * @author Gary McGath + * + */ +public class TiffProfileFXC extends TiffFXBase { + + /** + * Constructor. + */ + public TiffProfileFXC () + { + super (); + _profileText = "TIFF-FX (Profile C)"; + _mimeClass = MIME_FX; + } + + + /** + * Returns true if the IFD satisfies the requirements of a + * TIFF/FX C profile. See the TIFF/FX specification for + * details. + */ + public boolean satisfiesThisProfile(IFD ifd) + { + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + if (!satisfiesClass (tifd)) { + return false; + } + if (!satisfiesImageWidth (tifd, new int[] + {864, 1024, 1216, 1728, 2048, 2432, + 2592, 3072, 3456, 3648, 4096, 4864} )) { + return false; + } + + if (!satisfiesSamplesPerPixel(tifd, new int[] {1, 3})) { + return false; + } + + if (!satisfiesCompression (tifd, 7)) { + return false; + } + + if (!satisfiesPhotometricInterpretation(tifd, 10)) { + return false; + } + if (!satisfiesResolutionUnit (tifd, + new int[] {2, 3, NisoImageMetadata.NULL} )) { + return false; + // NOTE: RFC 2301 (1998) allows 2 or 3, but + // the 2003 working draft allows only 2 (inch). + // Watch for change. + } + if (!satisfiesSamplesPerPixel(tifd, + new int[] {1, 3} )) { + return false; + } + // XResolution must be one of the specified values + // and equal YResolution + if (!satisfiesXResolution(tifd, + new int[] {100, 200, 300, 400} )) { + return false; + } + if (!satisfiesFillOrder (tifd, + new int[] {1, 2} )) { + return false; + } + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + long xRes = niso.getXSamplingFrequency ().toLong (); + if (xRes != niso.getYSamplingFrequency ().toLong ()) { + return false; + } + if (niso.getSamplingFrequencyUnit() == 3) { + // Convert from units/cm to units/inch, with rounding + xRes = perCMtoPerInch ((int) xRes); + } + int bps = niso.getBitsPerSample ()[0]; + if (bps != 8 && bps != 12) { + // NOTE: RFC 2301 (1998) allows 8 or 12 bits per + // sample, but the 2003 working draft allows only 8. + // Watch for changes. + return false; + } + + // Check if image width is suitable to resolution + int wid = (int) niso.getImageWidth (); + switch ((int) xRes) { + case 100: + if (wid != 864 && wid != 1024 & wid != 1216) { + return false; + } + break; + case 200: + if (wid != 1728 && wid != 2048 & wid != 2432) { + return false; + } + break; + + case 300: + if (wid != 2592 && wid != 3072 & wid != 3648) { + return false; + } + break; + + case 400: + if (wid != 3456 && wid != 4096 & wid != 4864) { + return false; + } + break; + } + + // By my best reading, the colormap is needed only + // if the Indexed value is 1. + if (tifd.getIndexed() == 1) { + if (niso.getColormapRedValue () == null) { + return false; + } + } + + return true; // passed all tests + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileFXF.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileFXF.java new file mode 100644 index 00000000..15b75fae --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileFXF.java @@ -0,0 +1,105 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * + * Profile checker for TIFF FX, Profile F, aka TIFF-F. + * This supersedes the Class F profile (TiffProfileClassF), + * which will be deprecated and removed from the Jhove + * application. + * + * Image data content is not checked for profile conformance. + * Only tags are checked. + * + * @author Gary McGath + * + */ +public class TiffProfileFXF extends TiffFXBase { + + /** + * Constructor. + */ + public TiffProfileFXF () + { + super (); + _profileText = "TIFF-FX (Profile F)"; + _mimeClass = MIME_FX; + } + + /** + * Returns true if the IFD satisfies the requirements of a + * TIFF/FX S profile. See the TIFF/FX specification for + * details. + */ + public boolean satisfiesThisProfile(IFD ifd) { + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + if (!satisfiesClass (tifd)) { + return false; + } + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + int[] bps = niso.getBitsPerSample (); + if (bps[0] != 1) { + return false; + } + if (!satisfiesCompression (tifd, new int[] {3, 4} )) { + return false; + } + if (!satisfiesFillOrder (tifd, + new int[] {1, 2} )) { + return false; + } + // We've already established that if the compression + // scheme is 3, T4Options exists. But we must establish + // that if it's 4, T6Options exists and has a value of 0. + if (niso.getCompressionScheme () == 4) { + if (tifd.getT6Options () != 0) { + return false; + } + } + + // XResolution, YResolution, and ImageWidth have codependencies. + boolean xywOK = false; // guilty till proven innocent + long xRes = niso.getXSamplingFrequency ().toLong(); + long yRes = niso.getYSamplingFrequency ().toLong(); + if (niso.getSamplingFrequencyUnit() == 3) { + // Convert from units/cm to units/inch, with rounding + xRes = perCMtoPerInch ((int) xRes); + yRes = perCMtoPerInch ((int) yRes); + } + long wid = niso.getImageWidth(); + if ((xRes == 200 && yRes == 100) || + (xRes == 204 && yRes == 98) || + (xRes == 200 && yRes == 200) || + (xRes == 204 && yRes == 196) || + (xRes == 204 && yRes == 391)) { + if (wid == 1728 || wid == 2048 || wid == 2432) { + xywOK = true; + } + } + if (xRes == 300 && yRes == 300) { + if (wid == 2592 || wid == 3072 || wid == 3648) { + xywOK = true; + } + } + if ((xRes == 408 && yRes == 391) || + (xRes == 400 && yRes == 400)) { + if (wid == 3456 || wid == 4096 || wid == 4864) { + xywOK = true; + } + } + if (!xywOK) { + return false; + } + return true; // passed all tests + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileFXJ.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileFXJ.java new file mode 100644 index 00000000..7bb0e500 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileFXJ.java @@ -0,0 +1,107 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Profile checker for TIFF FX, Profile J (lossless JBIG). + * + * Image data content is not checked for profile conformance. + * Only tags are checked. + * + * @author Gary McGath + * + */ +public class TiffProfileFXJ extends TiffFXBase { + + /** + * Constructor. + */ + public TiffProfileFXJ () + { + super (); + _profileText = "TIFF-FX (Profile J)"; + _mimeClass = MIME_FX; + } + + + /** + * Returns true if the IFD satisfies the requirements of a + * TIFF/FX J profile. See the TIFF/FX specification for + * details. + */ + public boolean satisfiesThisProfile(IFD ifd) { + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + if (!satisfiesClass (tifd)) { + return false; + } + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + int[] bps = niso.getBitsPerSample (); + if (bps[0] != 1) { + return false; + } + int cmp = niso.getCompressionScheme(); + if (cmp != 9) { + return false; + } + if (!satisfiesFillOrder (tifd, + new int[] {1, 2} )) { + return false; + // RFC 2301 (1998) is internally inconsistent about + // whether a FillOrder of 1 is permitted. The latest + // working draft allows a FillOrder of 1, so I've + // resolved the conflict in favor of that interpretation. + } + // We've already established that if the compression + // scheme is 3, T4Options exists. But we must establish + // that if it's 4, T6Options exists and has a value of 0. + if (cmp == 4) { + if (tifd.getT6Options () != 0) { + return false; + } + } + + // XResolution, YResolution, and ImageWidth have codependencies. + boolean xywOK = false; // guilty till proven innocent + long xRes = niso.getXSamplingFrequency ().toLong(); + long yRes = niso.getYSamplingFrequency ().toLong(); + if (niso.getSamplingFrequencyUnit() == 3) { + // Convert from units/cm to units/inch, with rounding + xRes = perCMtoPerInch ((int) xRes); + yRes = perCMtoPerInch ((int) yRes); + } + long wid = niso.getImageWidth(); + if ((xRes == 200 && yRes == 100) || + (xRes == 204 && yRes == 98) || + (xRes == 200 && yRes == 200) || + (xRes == 204 && yRes == 196) || + (xRes == 204 && yRes == 391)) { + if (wid == 1728 || wid == 2048 || wid == 2432) { + xywOK = true; + } + } + if (xRes == 300 && yRes == 300) { + if (wid == 2592 || wid == 3072 || wid == 3648) { + xywOK = true; + } + } + if ((xRes == 408 && yRes == 391) || + (xRes == 400 && yRes == 400)) { + if (wid == 3456 || wid == 4096 || wid == 4864) { + xywOK = true; + } + } + if (!xywOK) { + return false; + } + return true; // passed all tests + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileFXL.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileFXL.java new file mode 100644 index 00000000..4ee7c952 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileFXL.java @@ -0,0 +1,114 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * + * Profile checker for TIFF FX, Profile L (Lossless Color). + * + * Image data content is not checked for profile conformance. + * Only tags are checked. + * + * @author Gary McGath + * + */ +public class TiffProfileFXL extends TiffFXBase { + + + /** + * Constructor. + */ + public TiffProfileFXL () + { + super (); + _profileText = "TIFF-FX (Profile L)"; + _mimeClass = MIME_FX; + } + + + + /** + * Returns true if the IFD satisfies the requirements of a + * TIFF/FX L profile. See the TIFF/FX specification for + * details. + */ + public boolean satisfiesThisProfile(IFD ifd) + { + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + if (!satisfiesClass (tifd)) { + return false; + } + if (!satisfiesImageWidth (tifd, new int[] + {864, 1024, 1216, 1728, 2048, 2432, + 2592, 3072, 3456, 3648, 4096, 4864} )) { + return false; + } + + // I can't make sense of whether compression mode 7 is + // allowed (since Profile L implementors are required to + // implement profile C) or not (since only 10 is mentioned + // under Profile L). Since the compression scheme is the + // defining characteristic of JBIG, I assume it must be 10. + if (!satisfiesCompression (tifd, 10 )) { + return false; + } + + if (!satisfiesPhotometricInterpretation(tifd, + new int[] {2, 5, 10 } )) { + return false; + } + if (!satisfiesResolutionUnit (tifd, + new int[] {2, 3, NisoImageMetadata.NULL} )) { + // NOTE: RFC 2301 (1998) allows 2 or 3, but + // the 2003 working draft allows only 2 (inch). + // Watch for change. + return false; + } + if (!satisfiesSamplesPerPixel(tifd, + new int[] {1, 3, 4} )) { + return false; + } + // XResolution must be one of the specified values + // and equal YResolution + if (!satisfiesXResolution(tifd, + new int[] {100, 200, 300, 400} )) { + return false; + } + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + if (niso.getXSamplingFrequency ().toLong () != + niso.getYSamplingFrequency ().toLong ()) { + return false; + } + if (!satisfiesIndexed (tifd, new int[] {0, 1} )) { + return false; + } + if (!satisfiesFillOrder (tifd, + new int[] {1, 2} )) { + return false; + } + int bps = niso.getBitsPerSample ()[0]; + if (bps > 16) { + // NOTE: RFC 2301 (1998) allows 1-16 bits per + // sample, but the 2003 working draft allows only 1-12. + // Watch for changes. + return false; + } + + if (tifd.getIndexed() == 1) { + if (niso.getColormapRedValue () == null) { + return false; + } + } + + return true; // passed all tests + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileFXM.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileFXM.java new file mode 100644 index 00000000..c7766486 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileFXM.java @@ -0,0 +1,115 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * + * Profile checker for TIFF FX, Profile M (Mixed Raster Content). + * + * Image data content is not checked for profile conformance. + * Only tags are checked. + * + * @author Gary McGath + * + */ +public class TiffProfileFXM extends TiffFXBase { + + /** + * Constructor. + */ + public TiffProfileFXM () + { + super (); + _profileText = "TIFF-FX (Profile M)"; + _mimeClass = MIME_FX; + } + + /** + * Returns true if the IFD satisfies the requirements of a + * TIFF/FX M profile. See the TIFF/FX specification for + * details. + * + * Proper validation should check if the subIFDs are appropriate + * to the M profile layer scheme. However, the existing design + * of the TIFF module has almost no understanding of IFD + * hierarchies. This could be an enhancement for a future + * release. + */ + public boolean satisfiesThisProfile(IFD ifd) + { + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + if (!satisfiesClass (tifd)) { + return false; + } + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + if (!satisfiesImageWidth (tifd, new int[] + {864, 1024, 1216, 1728, 2048, 2432, + 2592, 3072, 3456, 3648, 4096, 4864} )) { + return false; + } + if (!satisfiesNewSubfileType(tifd, new long[] {16, 18})) { + return false; + } + if (!satisfiesCompression (tifd, + new int[] {3, 4, 7, 9, 10})) { + return false; + // NOTE: The March 2003 draft allows a compression + // value of 1 if StripByteCounts contains a 0 + // value, i.e., there is no image data. Watch + // for changes. + } + if (!satisfiesSamplesPerPixel (tifd, + new int[] {1, 3, 4} )) { + return false; + } + if (!satisfiesResolutionUnit (tifd, + new int[] {2, 3, NisoImageMetadata.NULL} )) { + return false; + } + if (!satisfiesPhotometricInterpretation (tifd, + new int[] {0, 1, 2, 5, 10} )) { + return false; + // NOTE: The March 2003 draft allows only 0, 2 and + // 10. Watch for change. + } + if (!satisfiesFillOrder (tifd, + new int[] {1, 2} )) { + return false; + } + int bps = niso.getBitsPerSample ()[0]; + if (bps > 16) { + // NOTE: RFC 2301 (1998) allows 1-16 bits per + // sample, but the 2003 working draft allows only 1-12. + // Watch for changes. + return false; + } + int[] imgl = tifd.getImageLayer(); + if (imgl == null || imgl[0] < 1 || imgl[0] > 3) { + return false; + } + + // Can't have both StripRowCounts and RowsPerStrip + if (tifd.getStripRowCounts () != null && + niso.getRowsPerStrip () != NisoImageMetadata.NULL) { + return false; + } + // By my best reading, the colormap is needed only + // if the Indexed value is 1. + if (tifd.getIndexed() == 1) { + if (niso.getColormapRedValue () == null) { + return false; + } + } + + return true; // passed all tests + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileFXS.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileFXS.java new file mode 100644 index 00000000..fa568f9a --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileFXS.java @@ -0,0 +1,98 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + +/** + * Profile checker for TIFF FX, Profile S. + * + * Image data content is not checked for profile conformance. + * Only tags are checked. + * + * @author Gary McGath + * + */ +public class TiffProfileFXS extends TiffFXBase { + + /** + * Constructor. + */ + public TiffProfileFXS () + { + super (); + _profileText = "TIFF-FX (Profile S)"; + _mimeClass = MIME_FX; + } + + + /** + * Returns true if the IFD satisfies the requirements of a + * TIFF/FX S profile. See the TIFF/FX specification for + * details. + */ + public boolean satisfiesThisProfile(IFD ifd) { + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + if (!satisfiesClass (tifd)) { + return false; + } + + // Profile S (but not any other fax profile) requires + // "II", little-endian data. + if (ifd.isBigEndian()) { + return false; + } + + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + int[] bps = niso.getBitsPerSample (); + if (bps[0] != 1) { + return false; + } + if (niso.getStripOffsets().length > 1) { + // Image data must be a single strip + return false; + } + + int resUnit = niso.getSamplingFrequencyUnit(); + if (resUnit != 2 && resUnit != NisoImageMetadata.NULL) { + return false; + } + + if (niso.getCompressionScheme() != 3) { + return false; + } + if (tifd.getFillOrder () != 2) { + return false; + } + if (niso.getImageWidth () != 1728) { + return false; + } + if (niso.getSamplesPerPixel () != 1) { + return false; + } + long xRes = niso.getXSamplingFrequency ().toLong(); + long yRes = niso.getYSamplingFrequency ().toLong(); + // resolution unit must be inches, so no need to + // do metric conversion + if (xRes != 200 && xRes != 204) { + return false; + } + if (yRes != 98 && yRes != 100 && + yRes != 196 && yRes != 200) { + return false; + } + long t4Opt = tifd.getT4Options (); + if ((t4Opt & 0X3) != 0) { + // bits 0 and 1 must be 0 + return false; + } + return true; // passed all tests + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileGeoTIFF.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileGeoTIFF.java new file mode 100644 index 00000000..d6137564 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfileGeoTIFF.java @@ -0,0 +1,48 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + + + +/** + * Profile checker for GeoTIFF. + */ +public final class TiffProfileGeoTIFF extends TiffProfile +{ + public TiffProfileGeoTIFF () + { + super (); + _profileText = "Baseline GeoTIFF 1.0"; + } + + /** + * Returns true if the IFD satisfies the requirements of the + * the profile. See the GeoTIFF specification for details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + + if (tifd.getGeoKeyDirectoryTag () == null) { + return false; + } + + /* Exactly one of modelTiepointTag and modelTransformationTag + * must be present. */ + boolean hasModelTiepoint = + (tifd.getModelTiepointTag() != null); + boolean hasModelTransformation = + (tifd.getModelTransformationTag() != null); + if ((hasModelTiepoint && hasModelTransformation) || + (!hasModelTiepoint && !hasModelTransformation)) { + return false; + } + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfilePagemaker6.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfilePagemaker6.java new file mode 100644 index 00000000..1371c137 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/TiffProfilePagemaker6.java @@ -0,0 +1,128 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.tiff; + +import edu.harvard.hul.ois.jhove.*; + + +/** + * Profile checker for TIFF Pagemaker 6.0. + */ +public final class TiffProfilePagemaker6 extends TiffProfile +{ + public TiffProfilePagemaker6 () + { + super (); + _profileText = "Adobe PageMaker 6.0"; + } + + /** + * Returns true if the IFD satisfies the requirements of the + * profile. See the PageMaker specification for details. + */ + public boolean satisfiesThisProfile (IFD ifd) + { + if (!(ifd instanceof TiffIFD)) { + return false; + } + TiffIFD tifd = (TiffIFD) ifd; + + /* Check required tags. */ + NisoImageMetadata niso = tifd.getNisoImageMetadata (); + long imageLength = niso.getImageLength (); + if (imageLength == NisoImageMetadata.NULL || + niso.getImageWidth () == NisoImageMetadata.NULL) { + return false; + } + + boolean so = (niso.getStripOffsets () != null); + boolean to = (niso.getTileOffsets () != null); + if ((so && to) || (!so && !to)) { + return false; + } + + if (so) { + if (niso.getStripByteCounts () == null) { + return false; + } + long rowsPerStrip = niso.getRowsPerStrip (); + if (rowsPerStrip == NisoImageMetadata.NULL || + rowsPerStrip < 1L || rowsPerStrip > imageLength) { + return false; + } + } + + if (to) { + if (niso.getTileWidth () == NisoImageMetadata.NULL || + niso.getTileLength () == NisoImageMetadata.NULL || + niso.getTileOffsets () == null || + niso.getTileByteCounts () == null) { + return false; + } + } + + /* Check required values. */ + if (!satisfiesCompression (tifd, new int [] {1, 2, 5, 32773, 32895, + 32896} )) { + return false; + } + + int pi = niso.getColorSpace (); + if (pi != 0 && pi != 1 && pi != 2 && pi != 3 && pi != 5 && pi != 8 && + pi != 9) { + return false; + } + + int inkSet = tifd.getInkSet (); + int spp = niso.getSamplesPerPixel (); + if (pi == 0 || pi == 1 || pi == 3) { + if (spp != 1) { + return false; + } + } + else if (pi == 2 || pi == 8 || pi == 9) { + if (spp != 3) { + return false; + } + } + else if (inkSet == 1) { /* Only check for RGB, not hi-fi/multi-ink. */ + if (spp != 4) { + return false; + } + } + + int [] bps = niso.getBitsPerSample (); + if (bps != null) { + if (pi == 0 || pi == 1 || pi == 3) { + for (int i=0; i -1) { + name = TAG_LABELS[n]; + } + else { + name = Integer.toString (tag); + } + return name; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/build.xml b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/build.xml new file mode 100644 index 00000000..0baaed5e --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/build.xml @@ -0,0 +1,33 @@ + + TIFF module build file + Jhove - JSTOR/Harvard Object Validation Environment + Version 1.0 2004-02-03 + Copyright 2004 by JSTOR and the President and Fellows of Harvard College + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/package.html b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/package.html new file mode 100644 index 00000000..3afa2696 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/tiff/package.html @@ -0,0 +1,24 @@ + + + + + + + +Contains supporting classes for the TIFF-HUL module. +
+ + + + + +
+ + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/AXMLChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/AXMLChunk.java new file mode 100644 index 00000000..e8687c46 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/AXMLChunk.java @@ -0,0 +1,59 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.wave; + +import edu.harvard.hul.ois.jhove.module.iff.*; +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.WaveModule; +import java.io.*; +//import java.util.*; + +/** + * Implementation of the WAVE AXML Chunk, which + * contains arbitrary XML metadata, as specified in + * Specification of the Broadcast Wave Format: + * A format for audio data files in broadcasting; + * Supplement 5: <axml> Chunk + * (European Broadcasting Union) + * + * @author Gary McGath + * + */ +public class AXMLChunk extends Chunk { + + /** + * Constructor. + * + * @param module The WaveModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the WAVE data are being read + */ + public AXMLChunk ( + ModuleBase module, + ChunkHeader hdr, + DataInputStream dstrm) { + super(module, hdr, dstrm); + } + + + /** Reads a chunk and puts a BroadcastAudioExtension Property into + * the RepInfo object. + * + * @return false if the chunk is structurally + * invalid, otherwise true + */ + public boolean readChunk(RepInfo info) throws IOException { + WaveModule module = (WaveModule) _module; + byte[] bbuf = new byte[(int) bytesLeft]; + + ModuleBase.readByteBuf (_dstream, bbuf, _module); + String xmlData = new String (bbuf); + module.addWaveProperty (new Property ("XML", + PropertyType.STRING, + xmlData)); + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/AssocDataListChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/AssocDataListChunk.java new file mode 100644 index 00000000..6a83f5d5 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/AssocDataListChunk.java @@ -0,0 +1,106 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.wave; + +import java.io.DataInputStream; +import java.io.IOException; + +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.WaveModule; +import edu.harvard.hul.ois.jhove.module.iff.*; + +/** + * The associated data list ('list') chunk, which is different from + * the RIFF 'LIST' chunk, ListInfoTextChunk. It can contain + * several different types of informational chunks. + * + * @author Gary McGath + * + */ +public class AssocDataListChunk extends Superchunk { + + + /** + * Constructor. + * + * @param module The WaveModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the WAVE data are being read + * @param info RepInfo object for error reporting + */ + public AssocDataListChunk( + ModuleBase module, + ChunkHeader hdr, + DataInputStream dstrm, + RepInfo info) { + super(module, hdr, dstrm, info); + } + + /** Reads the chunk and its nested chunks, and puts appropriate + * properties into the RepInfo object. + * + * @return false if the chunk or a nested chunk + * is structurally + * invalid, otherwise true + */ + public boolean readChunk(RepInfo info) throws IOException { + WaveModule module = (WaveModule) _module; + + // The chunk has a type ID, which is always "adtl". Presumably + // this was intended to allow other list structures (don't ask + // why), but any others will be considered non-conforming. + String typeID = module.read4Chars(_dstream); + if (!"adtl".equals (typeID)) { + info.setMessage (new ErrorMessage ("Unknown list type " + + "in Associated Data List Chunk", + "Type = " + typeID, + _module.getNByte())); + info.setWellFormed (false); + return false; + } + for (;;) { + ChunkHeader chunkh = getNextChunkHeader (); + if (chunkh == null) { + break; + } + Chunk chunk = null; + // The chunk list can include Labels, Notes, and + // Labelled Text. + String id = chunkh.getID(); + int chunkSize = (int) chunkh.getSize (); + if (id.equals ("labl")) { + chunk = new LabelChunk (_module, chunkh, _dstream); + } + else if (id.equals ("note")) { + chunk = new NoteChunk (_module, chunkh, _dstream); + } + else if (id.equals ("ltxt")) { + chunk = new LabeledTextChunk (_module, chunkh, _dstream); + } + + if (chunk == null) { + _module.skipBytes (_dstream, (int) chunkSize, _module); + info.setMessage (new InfoMessage + ("Chunk type '" + id + "' in Associated Data Chunk ignored")); + } + else { + try { + if (!chunk.readChunk (info)) { + return false; + } + } + catch (JhoveException e) { + info.setMessage(new ErrorMessage (e.getMessage())); + info.setWellFormed (false); + return false; + } + + } + } + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/BroadcastExtChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/BroadcastExtChunk.java new file mode 100644 index 00000000..9b6c4b20 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/BroadcastExtChunk.java @@ -0,0 +1,123 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.wave; + + +import edu.harvard.hul.ois.jhove.RepInfo; +import edu.harvard.hul.ois.jhove.module.iff.*; +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.WaveModule; +import java.io.*; +import java.util.*; + +/** + * Implementation of the WAVE Broadcast Audio Extension Chunk. + * + * @author Gary McGath + * + */ +public class BroadcastExtChunk extends Chunk { + + /** + * Constructor. + * + * @param module The WaveModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the WAVE data are being read + */ + public BroadcastExtChunk ( + ModuleBase module, + ChunkHeader hdr, + DataInputStream dstrm) { + super(module, hdr, dstrm); + } + + /** Reads a chunk and puts a BroadcastAudioExtension Property into + * the RepInfo object. + * + * @return false if the chunk is structurally + * invalid, otherwise true + */ + public boolean readChunk(RepInfo info) throws IOException { + WaveModule module = (WaveModule) _module; + byte[] buf256 = new byte[256]; + ModuleBase.readByteBuf (_dstream, buf256, module); + String description = byteBufString(buf256); + byte[] buf32 = new byte[32]; + ModuleBase.readByteBuf (_dstream, buf32, module); + String originator = byteBufString (buf32); + ModuleBase.readByteBuf (_dstream, buf32, module); + String originatorRef = byteBufString (buf32); + byte[] buf10 = new byte[10]; + ModuleBase.readByteBuf (_dstream, buf10, module); + String originationDate = byteBufString (buf10); + byte[] buf8 = new byte[8]; + ModuleBase.readByteBuf (_dstream, buf8, module); + String originationTime = byteBufString (buf8); + // TimeReference is stored as a 64-bit little-endian + // number -- I think + long timeReference = module.readSignedLong (_dstream); + int version = module.readUnsignedShort (_dstream); + module.setBroadcastVersion (version); + byte[] smtpe_umid = new byte[64]; + ModuleBase.readByteBuf (_dstream, smtpe_umid, module); + module.skipBytes (_dstream, 190, module); + String codingHistory = ""; + if (bytesLeft > 602) { + byte[] bufCodingHistory = new byte[(int) bytesLeft - 602]; + ModuleBase.readByteBuf (_dstream, bufCodingHistory, module); + codingHistory = byteBufString (bufCodingHistory); + } + + // Whew -- we've read the whole thing. Now make that into a + // list of Properties. + List plist = new ArrayList (20); + if (description.length () > 0) { + plist.add (new Property + ("Description", PropertyType.STRING, description)); + } + if (originator.length () > 0) { + plist.add (new Property + ("Originator", PropertyType.STRING, originator)); + } + if (originatorRef.length () > 0) { + plist.add (new Property + ("Originator Reference", PropertyType.STRING, originatorRef)); + } + if (originationDate.length () > 0) { + plist.add (new Property + ("OriginationDate", PropertyType.STRING, originationDate)); + } + if (originationTime.length () > 0) { + plist.add (new Property + ("OriginationTime", PropertyType.STRING, originationTime)); + } + plist.add (new Property + ("TimeReference", PropertyType.LONG, new Long (timeReference))); + plist.add (new Property + ("Version", PropertyType.INTEGER, new Integer (version))); + plist.add (new Property ("UMID", + PropertyType.BYTE, + PropertyArity.ARRAY, + smtpe_umid)); + if (codingHistory.length () > 0) { + plist.add (new Property + ("CodingHistory", PropertyType.STRING, codingHistory)); + } + + module.addWaveProperty (new Property ("BroadcastAudioExtension", + PropertyType.PROPERTY, + PropertyArity.LIST, + plist)); + + // set time reference in AES metadata set @author David Ackerman + AESAudioMetadata aes = module.getAESMetadata (); + aes.setStartTime (timeReference); + + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/CartChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/CartChunk.java new file mode 100644 index 00000000..92bf5bfd --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/CartChunk.java @@ -0,0 +1,196 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.wave; + + +import edu.harvard.hul.ois.jhove.RepInfo; +import edu.harvard.hul.ois.jhove.module.iff.*; +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.WaveModule; +import java.io.*; +import java.util.*; + +/** + * Implementation of the WAVE Cart Chunk. + * + * @author Gary McGath + * + */ +public class CartChunk extends Chunk { + + /** Number of timer tags. This is a fixed value specified by the + * chunk definition. */ + private static final int N_TIMER_TAGS = 8; + + /** + * Constructor. + * + * @param module The WaveModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the WAVE data are being read + */ + public CartChunk ( + ModuleBase module, + ChunkHeader hdr, + DataInputStream dstrm) { + super(module, hdr, dstrm); + } + + /** Reads a chunk and puts a Cart Property into + * the RepInfo object. + * + * @return false if the chunk is structurally + * invalid, otherwise true + */ + public boolean readChunk(RepInfo info) throws IOException { + WaveModule module = (WaveModule) _module; + byte[] buf4 = new byte[4]; + ModuleBase.readByteBuf (_dstream, buf4, module); + String version = byteBufString(buf4); + // Title and most other fields within this chunk are ASCII strings + // with a fixed allocation; calling trim() is necessary to get rid + // of nulls. + byte[] buf64 = new byte[64]; + ModuleBase.readByteBuf (_dstream, buf64, module); + String title = byteBufString (buf64); + ModuleBase.readByteBuf (_dstream, buf64, module); + String artist = byteBufString (buf64); + ModuleBase.readByteBuf (_dstream, buf64, module); + String cutID = byteBufString (buf64); + ModuleBase.readByteBuf (_dstream, buf64, module); + String clientID = byteBufString (buf64); + ModuleBase.readByteBuf (_dstream, buf64, module); + String category = byteBufString (buf64); + ModuleBase.readByteBuf (_dstream, buf64, module); + String classification = byteBufString (buf64); + ModuleBase.readByteBuf (_dstream, buf64, module); + String outCue = byteBufString (buf64); + byte[] buf10 = new byte[10]; + ModuleBase.readByteBuf (_dstream, buf10, module); + String startDate = byteBufString (buf10); + byte[] buf8 = new byte[8]; + ModuleBase.readByteBuf (_dstream, buf8, module); + String startTime = byteBufString (buf8); + + ModuleBase.readByteBuf (_dstream, buf10, module); + String endDate = byteBufString (buf10); + ModuleBase.readByteBuf (_dstream, buf8, module); + String endTime = byteBufString (buf8); + + ModuleBase.readByteBuf (_dstream, buf64, module); + String producerAppID = byteBufString (buf64); + ModuleBase.readByteBuf (_dstream, buf64, module); + String producerAppVersion = byteBufString (buf64); + ModuleBase.readByteBuf (_dstream, buf64, module); + String userDef = byteBufString (buf64); + + int levelReference = module.readSignedInt(_dstream); + + List timerTags = new ArrayList (N_TIMER_TAGS); + for (int i = 0; i < N_TIMER_TAGS; i++) { + String timerTagUsage = module.read4Chars(_dstream).trim (); + long timerTagValue = module.readUnsignedInt (_dstream); + if (timerTagUsage.length () > 0) { + Property[] ttprop = new Property[2]; + ttprop[0] = new Property ("Usage", + PropertyType.STRING, + timerTagUsage); + ttprop[1] = new Property ("Value", + PropertyType.LONG, + new Long (timerTagValue)); + + timerTags.add (new Property ("PostTimer", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + ttprop)); + } + } + module.skipBytes (_dstream, 276, module); + + byte[] buf1k = new byte[1024]; + ModuleBase.readByteBuf (_dstream, buf1k, module); + String url = byteBufString (buf1k); + + String tagText = ""; + if (bytesLeft > 2048) { + byte[] bufTagText = new byte[(int) bytesLeft - 2048]; + ModuleBase.readByteBuf (_dstream, bufTagText, module); + tagText = byteBufString (bufTagText); + } + + // Whew -- we've read the whole thing. Now make that into a + // list of Properties. + List plist = new ArrayList (20); + if (version. length () > 0) { + plist.add (new Property ("Version", PropertyType.STRING, version)); + } + if (title.length() > 0) { + plist.add (new Property ("Title", PropertyType.STRING, title)); + } + if (artist.length () > 0) { + plist.add (new Property ("Artist", PropertyType.STRING, artist)); + } + if (cutID.length () > 0) { + plist.add (new Property ("CutID", PropertyType.STRING, cutID)); + } + if (clientID.length () > 0) { + plist.add (new Property ("ClientID", PropertyType.STRING, clientID)); + } + if (category.length() > 0) { + plist.add (new Property ("Category", PropertyType.STRING, category)); + } + if (classification.length () > 0) { + plist.add (new Property + ("Classification", PropertyType.STRING, classification)); + } + if (outCue.length () > 0) { + plist.add (new Property ("OutCue", PropertyType.STRING, outCue)); + } + if (startDate.length () > 0) { + plist.add (new Property ("StartDate", PropertyType.STRING, startDate)); + } + if (startTime.length () > 0) { + plist.add (new Property ("StartTime", PropertyType.STRING, startTime)); + } + if (endDate.length () > 0) { + plist.add (new Property ("EndDate", PropertyType.STRING, endDate)); + } + if (endTime.length () > 0) { + plist.add (new Property ("EndTime", PropertyType.STRING, startTime)); + } + if (producerAppID.length () > 0) { + plist.add (new Property + ("ProducerAppID", PropertyType.STRING, producerAppID)); + } + if (producerAppVersion.length () > 0) { + plist.add (new Property + ("ProducerAppVersion", PropertyType.STRING, producerAppVersion)); + } + if (userDef.length () > 0) { + plist.add (new Property ("UserDef", PropertyType.STRING, userDef)); + } + plist.add (new Property ("LevelReference", PropertyType.INTEGER, + new Integer (levelReference))); + if (timerTags.size() > 0) { + plist.add (new Property ("PostTimers", PropertyType.PROPERTY, + PropertyArity.LIST, + timerTags)); + } + if (url.length () > 0) { + plist.add (new Property ("URL", PropertyType.STRING, url)); + } + if (tagText.length () > 0) { + plist.add (new Property ("TagText", PropertyType.STRING, tagText)); + } + + module.addWaveProperty (new Property ("Cart", + PropertyType.PROPERTY, + PropertyArity.LIST, + plist)); + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/CueChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/CueChunk.java new file mode 100644 index 00000000..322e069e --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/CueChunk.java @@ -0,0 +1,102 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.wave; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.*; +import edu.harvard.hul.ois.jhove.module.WaveModule; + +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.iff.Chunk; +import edu.harvard.hul.ois.jhove.module.iff.ChunkHeader; + +/** + * Implementation of the WAVE Cue chunk, which defines cue + * points in an audio stream. + * + * @author Gary McGath + * + */ +public class CueChunk extends Chunk { + + /** + * Constructor. + * + * @param module The WaveModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the WAVE data are being read + */ + public CueChunk( + ModuleBase module, + ChunkHeader hdr, + DataInputStream dstrm) { + super(module, hdr, dstrm); + } + + /** Reads a chunk and puts a Cue property into + * the RepInfo object. + * + * @return false if the chunk is structurally + * invalid, otherwise true + */ + public boolean readChunk(RepInfo info) throws IOException { + WaveModule module = (WaveModule) _module; + int nPoints = (int) module.readUnsignedInt (_dstream); + List points = new ArrayList (nPoints); + for (int i = 0; i < nPoints; i++) { + // get unique ID for cue point structure + long dwIdent = module.readUnsignedInt (_dstream); + // get position in play order + long dwPos = module.readUnsignedInt (_dstream); + // chunk ID of referenced chunk ('data' or 'slnt') + String fccID = module.read4Chars (_dstream); + // Get offset to start of chunk -- zero if a single + // Data chunk is used + long dwChunkStart = module.readUnsignedInt (_dstream); + // Get offset to start of block containing position + long dwBlockStart = module.readUnsignedInt (_dstream); + // Get offset from start of block to cue point. + // Note from the web page I'm using as a source: + // Unfortunately, the WAVE documentation is much too ambiguous, + // and doesn't define what it means by the term "sample offset". + // This could mean a byte offset, or it could mean counting + // the sample points (for example, in a 16-bit wave, every + // 2 bytes would be 1 sample point), or it could even mean + // sample frames (as the loop offsets in AIFF are specified). + // Who knows? The guy who conjured up the Cue chunk certainly + // isn't saying. I'm assuming that it's a byte offset, + // like the above 2 fields. + long dwSampleOffset = module.readUnsignedInt (_dstream); + Property[] cueProps = new Property[5]; + cueProps[0] = new Property ("ID", + PropertyType.LONG, + new Long (dwIdent)); + cueProps[1] = new Property ("Position", + PropertyType.LONG, + new Long (dwPos)); + cueProps[2] = new Property ("DataChunkID", + PropertyType.STRING, + fccID); + cueProps[3] = new Property ("BlockStart", + PropertyType.LONG, + new Long (dwChunkStart)); + cueProps[4] = new Property ("SampleOffset", + PropertyType.LONG, + new Long (dwBlockStart)); + points.add (new Property ("CuePoint", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + cueProps)); + } + module.addWaveProperty(new Property ("Cue", + PropertyType.PROPERTY, + PropertyArity.LIST, + points)); + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/DataChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/DataChunk.java new file mode 100644 index 00000000..5fe0e4e3 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/DataChunk.java @@ -0,0 +1,75 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.wave; + +import java.io.DataInputStream; +import java.io.IOException; + +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.WaveModule; +import edu.harvard.hul.ois.jhove.module.iff.Chunk; +import edu.harvard.hul.ois.jhove.module.iff.ChunkHeader; + +/** + * Implementation of the WAVE Data Chunk. + * + * Data Chunks may occur either at the top level (i.e., under the RIFF + * chunk) or under a data list chunk. There can be only one top-level + * Data Chunk. + * + * @author Gary McGath + * + */ +public class DataChunk extends Chunk { + + /** + * Constructor. + * + * @param module The WaveModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the WAVE data are being read + */ + public DataChunk( + ModuleBase module, + ChunkHeader hdr, + DataInputStream dstrm) { + super(module, hdr, dstrm); + } + + /* We may want to have another constructor which sets a parent chunk. */ + + /** Reads a chunk and puts a Data property into + * the RepInfo object. + * + * @return false if the chunk is structurally + * invalid, otherwise true + */ + public boolean readChunk(RepInfo info) throws IOException { + WaveModule module = (WaveModule) _module; + Property lenProp = new Property ("DataLength", + PropertyType.LONG, + new Long (bytesLeft)); + // The behavior will be different if we are reading this under + // a 'wavl' chunk. + + // If we have PCM compression, the number of samples is given + // by the number of bytes divided by the sample blocking; otherwise + // we use the Fact chunk to count samples. + if (module.getCompressionCode() == FormatChunk.WAVE_FORMAT_PCM) { + module.addSamples (bytesLeft / module.getBlockAlign ()); + } + module.addWaveProperty(new Property ("Data", + PropertyType.PROPERTY, + lenProp)); + // This must be called precisely at this point in reading the + // data stream to produce an accurate result. + module.markFirstSampleOffset (); + module.skipBytes (_dstream, (int) bytesLeft, module); + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/ExifInfo.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/ExifInfo.java new file mode 100644 index 00000000..113e4445 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/ExifInfo.java @@ -0,0 +1,123 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.wave; + +import edu.harvard.hul.ois.jhove.*; +import java.util.*; + +/** + * Encapsulation of Exif information for a Wave file. Exif sound + * information doesn't closely follow Exif image information, so we + * don't particularly try to force property names to match. + * + * @see edu.harvard.hul.ois.jhove.module.tiff.ExifIFD + * + * @author Gary McGath + * + */ +public class ExifInfo { + + private String _exifVersion; + private String _relatedImageFile; + private String _timeCreated; + private String _manufacturer; + private String _model; + private byte[] _makerNote; + private String _userComment; + + public ExifInfo () + { + + } + + /** Constructs a property and returns it. */ + public Property buildProperty () + { + List entries = new LinkedList (); + + if (_exifVersion != null) { + entries.add (new Property ("ExifVersion", PropertyType.STRING, + _exifVersion)); + } + else { + return null; // Version must be specified + } + if (_relatedImageFile != null) { + entries.add (new Property ("RelatedImageFile", PropertyType.STRING, + _relatedImageFile)); + } + if (_timeCreated != null) { + entries.add (new Property ("TimeCreated", PropertyType.STRING, + _timeCreated)); + } + if (_manufacturer != null) { + entries.add (new Property ("Manufacturer", PropertyType.STRING, + _manufacturer)); + } + if (_model != null) { + entries.add (new Property ("Model", PropertyType.STRING, + _model)); + } + if (_makerNote != null) { + entries.add (new Property ("MakerNote", PropertyType.BYTE, + PropertyArity.ARRAY, + _makerNote)); + } + if (_userComment != null) { + entries.add (new Property ("UserComment", PropertyType.STRING, + _userComment)); + } + + + return new Property ("Exif", PropertyType.PROPERTY, + PropertyArity.LIST, + entries); + } + + + /** Converts the raw 4-byte array into a version string and + * stores it. */ + protected void setExifVersion(String version) { + _exifVersion = version; + } + + /** Sets the related image file name. */ + protected void setRelatedImageFile (String file) + { + _relatedImageFile = file; + } + + /** Sets the creation time as an ASCII string. */ + protected void setTimeCreated (String time) + { + _timeCreated = time; + } + + + /** Sets the manufacturer of the equipment that produced the file. */ + protected void setManufacturer (String file) + { + _manufacturer = file; + } + + /** Sets the model of the equipment that produced the file. */ + protected void setModel (String file) + { + _model = file; + } + + /** Sets the maker note. */ + protected void setMakerNote (byte[] note) + { + _makerNote = note; + } + + /** Sets the user comment. */ + protected void setUserComment (String comment) + { + _userComment = comment; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/ExifMakerNoteChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/ExifMakerNoteChunk.java new file mode 100644 index 00000000..8c74f109 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/ExifMakerNoteChunk.java @@ -0,0 +1,53 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.wave; + +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.WaveModule; +import edu.harvard.hul.ois.jhove.module.iff.Chunk; +import edu.harvard.hul.ois.jhove.module.iff.ChunkHeader; +import java.io.*; + +/** + * This class encapsulates the Exif Maker Note chunk. The format + * of this is manufacturer-depedent, hence is regarded simply as an + * array of integers. + * + * @author Gary McGath + * + */ +public class ExifMakerNoteChunk extends Chunk { + /** + * Constructor. + * + * @param module The WaveModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the WAVE data are being read + */ + public ExifMakerNoteChunk( + ModuleBase module, + ChunkHeader hdr, + DataInputStream dstrm) { + super(module, hdr, dstrm); + } + + + /** Reads a chunk and puts information into the superchunk's + * Exif property. + * + * @return false if the chunk is structurally + * invalid, otherwise true + */ + public boolean readChunk(RepInfo info) throws IOException { + WaveModule module = (WaveModule) _module; + byte[] buf = new byte[(int) bytesLeft]; + ModuleBase.readByteBuf (_dstream, buf, module); + ExifInfo exif = module.getExifInfo (); + module.getExifInfo ().setMakerNote (buf); + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/ExifStringChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/ExifStringChunk.java new file mode 100644 index 00000000..9e4d04a9 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/ExifStringChunk.java @@ -0,0 +1,69 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.wave; + +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.WaveModule; +import edu.harvard.hul.ois.jhove.module.iff.Chunk; +import edu.harvard.hul.ois.jhove.module.iff.ChunkHeader; +import java.io.*; + +/** + * Class for encapsulating Exif chunks whose content consists of + * a null-terminated ASCII string. + * + * @author Gary McGath + * + */ +public class ExifStringChunk extends Chunk { + + private String id; + + + /** + * Constructor. + * + * @param module The WaveModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the WAVE data are being read + */ + public ExifStringChunk( + ModuleBase module, + ChunkHeader hdr, + DataInputStream dstrm) { + super(module, hdr, dstrm); + id = hdr.getID(); + } + + /** Reads a chunk and puts information into the superchunk's + * Exif property. + * + * @return false if the chunk is structurally + * invalid, otherwise true + */ + public boolean readChunk(RepInfo info) throws IOException { + WaveModule module = (WaveModule) _module; + byte[] buf = new byte[(int) bytesLeft]; + ModuleBase.readByteBuf (_dstream, buf, module); + String txt = new String (buf).trim (); + ExifInfo exif = module.getExifInfo (); + if ("erel".equals (id)) { + exif.setRelatedImageFile(txt); + } + else if ("etim".equals (id)) { + exif.setTimeCreated (txt); + } + else if ("ecor".equals (id)) { + exif.setManufacturer(txt); + } + else if ("emdl".equals (id)) { + exif.setModel (txt); + } + module.getExifInfo ().setRelatedImageFile(txt); + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/ExifUserCommentChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/ExifUserCommentChunk.java new file mode 100644 index 00000000..4f17e0e8 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/ExifUserCommentChunk.java @@ -0,0 +1,100 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.wave; + +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.WaveModule; +import edu.harvard.hul.ois.jhove.module.iff.Chunk; +import edu.harvard.hul.ois.jhove.module.iff.ChunkHeader; +import java.io.*; + +/** + * This class encapsulates the Exif User Comment chunk. + * + * @author Gary McGath + * + */ +public class ExifUserCommentChunk extends Chunk { + + // 8-byte codes for encodings; all the existing recognized + // encodings are representable as ASCII strings with null + // padding. "UNICODE" doesn't specify whether it's UTF-8, + // UTF-16, or UTF-32. + private final static String asciiDes = "ASCII"; + private final static String jisDes = "JIS"; + private final static String unicodeDes = "UNICODE"; + + + /** + * Constructor. + * + * @param module The WaveModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the WAVE data are being read + */ + public ExifUserCommentChunk( + ModuleBase module, + ChunkHeader hdr, + DataInputStream dstrm) { + super(module, hdr, dstrm); + } + + + /** Reads a chunk and puts information into the superchunk's + * Exif property. + * + * @return false if the chunk is structurally + * invalid, otherwise true + */ + public boolean readChunk(RepInfo info) throws IOException + { + WaveModule module = (WaveModule) _module; + if (bytesLeft < 8) { + info.setMessage (new ErrorMessage + ("Exif User Comment Chunk is too short")); + info.setWellFormed (false); + return false; + } + // Read the 8-byte encoding designation. + byte[] buf = new byte[8]; + ModuleBase.readByteBuf (_dstream, buf, module); + String encoding = new String (buf).trim (); + bytesLeft -= 8; + + String charset = null; + + // Here we have to do some guessing if the character set isn't + // ASCII. There are three different Unicode encodings and + // even more JIS variants. + if (asciiDes.equals (encoding)) { + charset = "US-ASCII"; + } + else if (jisDes.equals (encoding)) { + charset = "EUC_JP"; + } + else if (unicodeDes.equals (encoding)) { + charset = "UTF-16"; + } + // Read the comment itself. + buf = new byte[(int) bytesLeft]; + ModuleBase.readByteBuf (_dstream, buf, module); + String text = null; + try { + if (charset != null) { + text = new String (buf, charset); + } + } + catch (Exception e) { + // If we can't decode the charset, punt to default. + } + if (text == null) { + text = new String (buf); + } + ExifInfo exif = module.getExifInfo (); + module.getExifInfo ().setUserComment (text); + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/ExifVersionChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/ExifVersionChunk.java new file mode 100644 index 00000000..8e0a2ec0 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/ExifVersionChunk.java @@ -0,0 +1,59 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.wave; + +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.WaveModule; +import edu.harvard.hul.ois.jhove.module.iff.Chunk; +import edu.harvard.hul.ois.jhove.module.iff.ChunkHeader; +import java.io.*; + +/** + * Chunk for Exif version information. + * This chunk may occur only within a LIST chunk of type + * "exif". + * + * @author Gary McGath + * + */ +public class ExifVersionChunk extends Chunk { + + /** + * Constructor. + * + * @param module The WaveModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the WAVE data are being read + */ + public ExifVersionChunk( + ModuleBase module, + ChunkHeader hdr, + DataInputStream dstrm) { + super(module, hdr, dstrm); + } + + /** Reads a chunk and puts information into the superchunk's + * Exif property. + * + * @return false if the chunk is structurally + * invalid, otherwise true + */ + public boolean readChunk(RepInfo info) throws IOException { + WaveModule module = (WaveModule) _module; + if (bytesLeft != 4) { + info.setMessage (new ErrorMessage + ("Incorrect length for Exif Version Chunk")); + info.setWellFormed (false); + return false; + } + byte[] buf = new byte[4]; + ModuleBase.readByteBuf (_dstream, buf, module); + String txt = new String (buf); + module.getExifInfo ().setExifVersion(txt); + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/FactChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/FactChunk.java new file mode 100644 index 00000000..e3a311ef --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/FactChunk.java @@ -0,0 +1,61 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.wave; + +import java.io.DataInputStream; +import java.io.IOException; + +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.WaveModule; +import edu.harvard.hul.ois.jhove.module.iff.Chunk; +import edu.harvard.hul.ois.jhove.module.iff.ChunkHeader; + +/** + * Implementation of the WAVE Fact Chunk. + * The Fact chunk contains information specific to the + * compression scheme. + * + * @author Gary McGath + * + */ +public class FactChunk extends Chunk { + + /** + * Constructor. + * + * @param module The WaveModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the WAVE data are being read + */ + public FactChunk( + ModuleBase module, + ChunkHeader hdr, + DataInputStream dstrm) { + super(module, hdr, dstrm); + } + + /** Reads a chunk and puts a Fact Property into + * the RepInfo object. + * + * @return false if the chunk is structurally + * invalid, otherwise true + */ + public boolean readChunk(RepInfo info) throws IOException { + WaveModule module = (WaveModule) _module; + Property sizeProp = new Property ("Size", + PropertyType.LONG, + new Long(bytesLeft)); + module.addWaveProperty (new Property ("Fact", + PropertyType.PROPERTY, + sizeProp)); + long numSamples = module.readUnsignedInt (_dstream); + module.addSamples (numSamples); + bytesLeft -= 4; + module.skipBytes (_dstream, (int) bytesLeft, module); + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/FormatChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/FormatChunk.java new file mode 100644 index 00000000..0233fe90 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/FormatChunk.java @@ -0,0 +1,228 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.wave; + +import java.io.DataInputStream; +import java.io.IOException; + +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.WaveModule; +import edu.harvard.hul.ois.jhove.module.iff.Chunk; +import edu.harvard.hul.ois.jhove.module.iff.ChunkHeader; + +/** + * Implementation of the WAVE Format Chunk. + * + * @author Gary McGath + * + */ +public class FormatChunk extends Chunk { + + /** Compression code for original Microsoft PCM */ + public final static int WAVE_FORMAT_PCM = 1; + + /** Compression code for MPEG */ + public final static int WAVE_FORMAT_MPEG = 0X50; + + /** Compression code for Microsoft Extensible Wave Format */ + public final static int WAVE_FORMAT_EXTENSIBLE = 0XFFFE; + + /** Table of lossless compression codes. */ + private final static int[] losslessCodecs = { + 0X163, // WMA lossless + 0X1971 // Sonic foundry lossless + }; + + /** + * Constructor. + * + * @param module The WaveModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the WAVE data are being read + */ + public FormatChunk( + WaveModule module, + ChunkHeader hdr, + DataInputStream dstrm) { + super(module, hdr, dstrm); + } + + /** Reads a chunk and puts appropriate Properties into + * the RepInfo object. + * + * @return false if the chunk is structurally + * invalid, otherwise true + */ + public boolean readChunk(RepInfo info) throws IOException, JhoveException { + WaveModule module = (WaveModule) _module; + int validBitsPerSample = -1; + byte[] subformat = null; + long channelMask = -1; + int compressionCode = module.readUnsignedShort (_dstream); + module.setCompressionCode (compressionCode); + int numChannels = module.readUnsignedShort (_dstream); + long sampleRate = module.readUnsignedInt (_dstream); + module.setSampleRate (sampleRate); + long bytesPerSecond = module.readUnsignedInt (_dstream); + int blockAlign = module.readUnsignedShort (_dstream); + module.setBlockAlign (blockAlign); + int bitsPerSample = module.readUnsignedShort (_dstream); + bytesLeft -= 16; + byte[] extraBytes = null; + if (bytesLeft > 0) { + int extraFormatBytes = module.readUnsignedShort (_dstream); + extraBytes = new byte[extraFormatBytes]; + if (compressionCode == WAVE_FORMAT_EXTENSIBLE && bytesLeft >= 22) { + // This is -- or should be -- WAVEFORMATEXTENSIBLE. + // Need to do some additional checks on profile satisfaction. + boolean wfe = true; // accept tentatively + // The next word may be valid bits per sample, samples + // per block, or merely "reserved". Which one it is + // apparently depends on the compression format. I really + // can't figure out how to tell which it is without + // exhaustively researching all compression formats. + validBitsPerSample = module.readUnsignedShort (_dstream); + channelMask = module.readUnsignedInt (_dstream); + // The subformat is a GUID + subformat = new byte[20]; + ModuleBase.readByteBuf(_dstream, subformat, module); + + // Nitpicking profile requirements + if ((((bitsPerSample + 7) / 8) * numChannels) != blockAlign) { + wfe = false; + } + if ((bitsPerSample % 8) != 0) { + // So why was that fancy ceiling arithmetic needed? + // So it can be the same calculation as with WaveFormatEx. + wfe = false; + } + if (validBitsPerSample > bitsPerSample) { + wfe = false; + } + if (wfe) { + module.setWaveFormatExtensible(true); + } + } + else { + if (compressionCode != WAVE_FORMAT_PCM || + (((bitsPerSample + 7) / 8) * numChannels) == blockAlign) { + module.setWaveFormatEx (true); + } + ModuleBase.readByteBuf (_dstream, extraBytes, module); + } + + // Possible pad to maintain even alignment + if ((extraFormatBytes & 1) != 0) { + _module.skipBytes (_dstream, 1, module); + } + } + else { + // no extra bytes signifies the PCM profile. In this + // case, the compression code also needs to be 1 (Microsoft + // PCM). + if (compressionCode == WAVE_FORMAT_PCM && + (((bitsPerSample + 7) / 8) * numChannels) == blockAlign) { + module.setPCMWaveFormat(true); + } + } + + // Set a TENTATIVE flag if this chunk satisfies the broadcast + // wave format. + if (compressionCode == WAVE_FORMAT_PCM || + compressionCode == WAVE_FORMAT_MPEG) { + module.setBroadcastWave (true); + } + + module.addWaveProperty + (module.addIntegerProperty ("CompressionCode", compressionCode, + WaveStrings.COMPRESSION_FORMAT, + WaveStrings.COMPRESSION_INDEX)); + AESAudioMetadata aes = module.getAESMetadata (); + String compName; + try { + compName = WaveStrings.COMPRESSION_FORMAT + [WaveStrings.COMPRESSION_INDEX[compressionCode]]; + } + catch (Exception e) { + throw new JhoveException ("Error in FormatChunk: " + e.getClass().getName()); + } + aes.setAudioDataEncoding(compName); + aes.setNumChannels(numChannels); + setChannelLocations (aes, numChannels); + aes.setSampleRate(sampleRate); + aes.setBitDepth(bitsPerSample); + + // Check which codecs are non-lossy + String qual = "LOSSY"; + for (int i = 0; i < losslessCodecs.length; i++) { + if (compressionCode == losslessCodecs[i]) { + qual = "CODE_REGENERATING"; + } + } + if (compressionCode == WAVE_FORMAT_PCM) { + aes.clearBitrateReduction (); + } + else { + aes.setBitrateReduction (compName, "", "", "", + qual, Long.toString (bytesPerSecond), "FIXED"); + } + + module.addWaveProperty (new Property ("AverageBytesPerSecond", + PropertyType.LONG, + new Long (bytesPerSecond))); + module.addWaveProperty (new Property ("BlockAlign", + PropertyType.INTEGER, + new Integer (blockAlign))); + if (extraBytes != null) { + module.addWaveProperty (new Property ("ExtraFormatBytes", + PropertyType.BYTE, + PropertyArity.ARRAY, + extraBytes)); + } + if (validBitsPerSample != -1) { + // Should this property be called something like + // ValidBitsPersampleOrSamplesPerBlock? + module.addWaveProperty (new Property ("ValidBitsPerSample", + PropertyType.INTEGER, + new Integer (validBitsPerSample))); + } + if (channelMask != -1) { + module.addWaveProperty (new Property ("ChannelMask", + PropertyType.LONG, + new Long (channelMask))); + } + if (subformat != null) { + module.addWaveProperty (new Property ("Subformat", + PropertyType.BYTE, + PropertyArity.ARRAY, + subformat)); + } + return true; + } + + /* Set default channel assignments. This is fairly simple, + * but it's helpful to keep the same structure as the equivalent + * CommonChunk.setChannelLocations function. */ + private void setChannelLocations + (AESAudioMetadata aes, int numChannels) + { + String[] mapLoc = new String[numChannels]; + switch (numChannels) { + case 2: + mapLoc[0] = "LEFT"; + mapLoc[1] = "RIGHT"; + break; + + // If we get some other number of channels, punt. + default: + for (int i = 0; i < numChannels; i++) { + mapLoc[i] = "UNKNOWN"; + } + } + aes.setMapLocations(mapLoc); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/InstrumentChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/InstrumentChunk.java new file mode 100644 index 00000000..a845d9a4 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/InstrumentChunk.java @@ -0,0 +1,79 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.wave; + +import java.io.DataInputStream; +import java.io.IOException; +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.WaveModule; +import edu.harvard.hul.ois.jhove.module.iff.Chunk; +import edu.harvard.hul.ois.jhove.module.iff.ChunkHeader; + +/** + * Implementation of the WAVE Instrument Chunk, which + * gives information about a MIDI instrument. Similar to + * the Sample chunk or the AIFF Instrument chunk, but simpler + * than either. + * + * @author Gary McGath + * + */ +public class InstrumentChunk extends Chunk { + + /** + * Constructor. + * + * @param module The WaveModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the WAVE data are being read + */ + public InstrumentChunk( + ModuleBase module, + ChunkHeader hdr, + DataInputStream dstrm) { + super(module, hdr, dstrm); + } + + + /** Reads a chunk and puts an Instrument property into + * the RepInfo object. + * + * + * @return false if the chunk is structurally + * invalid, otherwise true + */ + public boolean readChunk(RepInfo info) throws IOException { + WaveModule module = (WaveModule) _module; + int unshiftedNote = ModuleBase.readUnsignedByte (_dstream, _module); + int fineTune = ModuleBase.readSignedByte (_dstream, _module); + int gain = ModuleBase.readSignedByte (_dstream, _module); + int lowNote = ModuleBase.readUnsignedByte (_dstream, _module); + int highNote = ModuleBase.readUnsignedByte (_dstream, _module); + int lowVelocity = ModuleBase.readUnsignedByte (_dstream, _module); + int highVelocity = ModuleBase.readUnsignedByte (_dstream, _module); + + Property[] propArr = new Property[7]; + propArr[0] = new Property ("UnshiftedNote", PropertyType.INTEGER, + new Integer (unshiftedNote)); + propArr[1] = new Property ("FineTune", PropertyType.INTEGER, + new Integer (fineTune)); + propArr[2] = new Property ("Gain", PropertyType.INTEGER, + new Integer (gain)); + propArr[3] = new Property ("LowNote", PropertyType.INTEGER, + new Integer (lowNote)); + propArr[4] = new Property ("HighNote", PropertyType.INTEGER, + new Integer (highNote)); + propArr[5] = new Property ("LowVelocity", PropertyType.INTEGER, + new Integer (lowVelocity)); + propArr[6] = new Property ("HighVelocity", PropertyType.INTEGER, + new Integer (highVelocity)); + module.addWaveProperty (new Property ("Instrument", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + propArr)); + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/LabelChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/LabelChunk.java new file mode 100644 index 00000000..ff984f4b --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/LabelChunk.java @@ -0,0 +1,48 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.wave; + +import java.io.DataInputStream; +import java.io.IOException; +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.WaveModule; +import edu.harvard.hul.ois.jhove.module.iff.ChunkHeader; + +/** + * Implementation of the WAVE Label Chunk. + * + * @author Gary McGath + * + */ +public class LabelChunk extends SimpleTextChunk { + + /** + * Constructor. + * + * @param module The WaveModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the WAVE data are being read + */ + public LabelChunk( + ModuleBase module, + ChunkHeader hdr, + DataInputStream dstrm) { + super(module, hdr, dstrm); + } + + /** Reads a chunk and adds to the module's list of labels. + * + * @return false if the chunk is structurally + * invalid, otherwise true + */ + public boolean readChunk(RepInfo info) throws IOException { + WaveModule module = (WaveModule) _module; + Property p = readTextProp (module, "Label"); + module.addLabel (p); + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/LabeledTextChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/LabeledTextChunk.java new file mode 100644 index 00000000..7a460acc --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/LabeledTextChunk.java @@ -0,0 +1,86 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.wave; + +import java.io.*; +import java.util.*; +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.WaveModule; +import edu.harvard.hul.ois.jhove.module.iff.Chunk; +import edu.harvard.hul.ois.jhove.module.iff.ChunkHeader; + +/** + * + * The Labelled Text Chunk, which can occur only in an Associated Data + * List. + * + * @author Gary McGath + * + */ +public class LabeledTextChunk extends Chunk { + + /** + * Constructor. + * + * @param module The WaveModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the WAVE data are being read + */ + public LabeledTextChunk( + ModuleBase module, + ChunkHeader hdr, + DataInputStream dstrm) + { + super(module, hdr, dstrm); + } + + + /** Reads a chunk and puts an MPEG Property into + * the RepInfo object. + * + * @return false if the chunk is structurally + * invalid, otherwise true + */ + public boolean readChunk(RepInfo info) throws IOException + { + WaveModule module = (WaveModule) _module; + long cuePointID = module.readUnsignedInt (_dstream); + long sampleLength = module.readUnsignedInt (_dstream); + long purposeID = module.readUnsignedInt (_dstream); + int country = module.readUnsignedShort (_dstream); + int language = module.readUnsignedShort (_dstream); + int dialect = module.readUnsignedShort (_dstream); + int codePage = module.readUnsignedShort (_dstream); + byte[] buf = new byte[(int) (bytesLeft - 20)]; + ModuleBase.readByteBuf(_dstream, buf, module); + String text = new String (buf).trim (); + + // Make the information into a Property. + List plist = new ArrayList (10); + plist.add (new Property ("CuePointID", PropertyType.LONG, + new Long (cuePointID))); + plist.add (new Property ("SampleLength", PropertyType.LONG, + new Long (sampleLength))); + plist.add (new Property ("PurposeID", PropertyType.LONG, + new Long (purposeID))); + plist.add (new Property ("Country", PropertyType.INTEGER, + new Integer (country))); + plist.add (new Property ("Language", PropertyType.INTEGER, + new Integer (language))); + plist.add (new Property ("Dialect", PropertyType.INTEGER, + new Integer (dialect))); + plist.add (new Property ("CodePage", PropertyType.INTEGER, + new Integer (codePage))); + plist.add (new Property ("Text", PropertyType.STRING, + text)); + module.addLabeledText(new Property ("LabeledTextItem", + PropertyType.PROPERTY, + PropertyArity.LIST, + plist)); + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/LinkChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/LinkChunk.java new file mode 100644 index 00000000..aa2c5379 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/LinkChunk.java @@ -0,0 +1,109 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.wave; + +import edu.harvard.hul.ois.jhove.module.iff.*; +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.WaveModule; +import java.io.*; +import java.util.*; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParserFactory; + +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; + +/** + * Implementation of the WAVE Link Chunk, as specified in + * Specification of the Broadcast Wave Format: + * A format for audio data files in broadcasting; + * Supplement 4: <link> Chunk + * (European Broadcasting Union) + * + * @author Gary McGath + * + */ +public class LinkChunk extends Chunk { + + /** + * Constructor. + * + * @param module The WaveModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the WAVE data are being read + */ + public LinkChunk ( + ModuleBase module, + ChunkHeader hdr, + DataInputStream dstrm) { + super(module, hdr, dstrm); + } + + + /** Reads a chunk and puts a BroadcastAudioExtension Property into + * the RepInfo object. + * + * @return false if the chunk is structurally + * invalid, otherwise true + */ + public boolean readChunk(RepInfo info) throws IOException { + WaveModule module = (WaveModule) _module; + + // We read the XML into a byte array, then use a ByteArrayXMPSource + // to parse it. This isn't XMP, but that code provides a ready-made + // way to generate an XML InputSource. + byte[] buf = new byte[(int) bytesLeft]; + ModuleBase.readByteBuf (_dstream, buf, module); + ByteArrayInputStream bais = new ByteArrayInputStream (buf); + ByteArrayXMPSource xs = new ByteArrayXMPSource (bais); + + try { + // Create an InputSource to feed the parser. + SAXParserFactory factory = + SAXParserFactory.newInstance(); + factory.setNamespaceAware (true); + XMLReader parser = factory.newSAXParser ().getXMLReader (); + LinkChunkHandler handler = new LinkChunkHandler (); + parser.setContentHandler (handler); + parser.parse (xs); + List fileNames = handler.getFileNames (); + String id = handler.getID(); + if (!fileNames.isEmpty ()) { + List plist = new ArrayList (2); + plist.add (new Property ("FileNames", + PropertyType.STRING, + PropertyArity.LIST, + fileNames)); + if (id != null) { + plist.add (new Property ("ID", + PropertyType.STRING, + id)); + } + + module.addWaveProperty (new Property ("Link", + PropertyType.PROPERTY, + PropertyArity.LIST, + plist)); + } + + } + catch (SAXException se) { + info.setMessage (new ErrorMessage + ("SAXException in reading Link Chunk")); + info.setValid (false); + return true; + } + catch (ParserConfigurationException pe) { + info.setMessage (new ErrorMessage + ("ParserConfigurationException in reading Link Chunk")); + info.setValid (false); + return true; + } + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/LinkChunkHandler.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/LinkChunkHandler.java new file mode 100644 index 00000000..51306c59 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/LinkChunkHandler.java @@ -0,0 +1,110 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.wave; + +import java.util.*; +import org.xml.sax.*; +import org.xml.sax.helpers.DefaultHandler; + +/** + * + * This handler parses the data of a WAVE List chunk. + * + * @author Gary McGath + * + */ +public class LinkChunkHandler extends DefaultHandler { + + private StringBuffer _content; + private int state; + private List fileNames; + private String id; + + private final static int STATE_DEFAULT = 0, + STATE_FILE = 1, + STATE_ID = 2; + + public LinkChunkHandler () + { + state = STATE_DEFAULT; + _content = new StringBuffer (); + fileNames = new LinkedList (); + id = null; + } + + + /** Accessor for getting file name list. The value returned + * is meaningful only after parsing. The value returned is + * guaranteed not to be null, but may be empty. */ + public List getFileNames () + { + return fileNames; + } + + + /** Accessor for getting the ID element. The value returned + * may be null, as the ID element is optional. */ + public String getID () + { + return id; + } + + + + /** + * Looks for the first element encountered. Stores + * its name as the value to be returned by getRoot, + * qualified name by preference, local name if the + * qualified name isn't available. + */ + public void startElement (String namespaceURI, + String localName, + String qualifiedName, + Attributes atts) throws SAXException + { + if ("FILE".equals (qualifiedName)) { + state = STATE_FILE; + } + else if ("ID".equals (qualifiedName)) { + state = STATE_ID; + } + } + + + /** + * SAX parser callback method for PC text. + */ + public void characters (char [] ch, int start, int length) + throws SAXException + { + _content.append (ch, start, length); + } + + + /** + * SAX parser callback method. + */ + public void endElement (String namespaceURI, String localName, + String rawName) + throws SAXException + { + switch (state) { + case STATE_FILE: + fileNames.add (_content.toString ()); + break; + case STATE_ID: + id = _content.toString (); + break; + case STATE_DEFAULT: + default: + break; + } + state = STATE_DEFAULT; + _content.setLength (0); + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/ListInfoChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/ListInfoChunk.java new file mode 100644 index 00000000..6df41fe7 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/ListInfoChunk.java @@ -0,0 +1,232 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.wave; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.*; + +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.WaveModule; +import edu.harvard.hul.ois.jhove.module.iff.*; + +/** + * Implementation of the WAVE LIST chunk. + * + * Two chunk types, 'exif' and 'INFO', are supported; + * other list types will be reported as unknown + * and treated as an error. + * + * @author Gary McGath + * + */ +public class ListInfoChunk extends Superchunk { + + /** + * Constructor. + * + * @param module The WaveModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the WAVE data are being read + * @param info RepInfo object for error reporting + */ + public ListInfoChunk( + ModuleBase module, + ChunkHeader hdr, + DataInputStream dstrm, + RepInfo info) { + super(module, hdr, dstrm, info); + } + + /** Reads a chunk and puts appropriate information into + * the RepInfo object. + * + * @return false if the chunk is structurally + * invalid, otherwise true + * + */ + public boolean readChunk(RepInfo info) throws IOException + { + boolean isInfo = false; + boolean isExif = false; + String typeID = ((WaveModule) _module).read4Chars(_dstream); + bytesLeft -= 4; + if ("INFO".equals (typeID)) { + return readInfoChunk (info); + } + else if ("exif".equals (typeID)) { + return readExifChunk (info); + } + else if ("adtl".equals (typeID)) { + return readAdtlChunk (info); + } + else { + info.setMessage (new ErrorMessage ("Unknown list type " + + typeID + " in List Chunk", + _module.getNByte())); + info.setWellFormed (false); + return false; + } + } + + private boolean readInfoChunk (RepInfo info) throws IOException + { + List listInfoProps = new LinkedList (); + WaveModule module = (WaveModule) _module; + // The set of subchunks is somewhat + // open-ended, but apparently all are identical in format, consisting + // of a null-terminated string. These are subsumed under + // ListInfoTextChunk. We accumulate them into a List of Properties. + for (;;) { + ChunkHeader chunkh = getNextChunkHeader (); + if (chunkh == null) { + break; + } + Chunk chunk = null; + String id = chunkh.getID(); + int chunkSize = (int) chunkh.getSize (); + chunk = new ListInfoTextChunk (_module, chunkh, + _dstream, listInfoProps, this); + + if (chunk == null) { + _module.skipBytes (_dstream, (int) chunkSize, _module); + info.setMessage (new InfoMessage + ("Chunk type '" + id + "' in List Info Chunk ignored")); + } + else { + try { + if (!chunk.readChunk (info)) { + return false; + } + } + catch (JhoveException e) { + info.setMessage(new ErrorMessage (e.getMessage())); + info.setWellFormed (false); + return false; + } + } + if ((chunkSize & 1) != 0) { + // Must come out to an even byte boundary + _module.skipBytes (_dstream, 1, _module); + --bytesLeft; + } + } + if (!listInfoProps.isEmpty ()) { + module.addListInfo (listInfoProps); + } + return true; + } + + /* The Exif chunk, unlike the Info chunk, has subchunks which aren't + * homogeneous. */ + private boolean readExifChunk (RepInfo info) throws IOException + { + List exifProps = new LinkedList (); + WaveModule module = (WaveModule) _module; + module.setExifInfo (new ExifInfo ()); + for (;;) { + ChunkHeader chunkh = getNextChunkHeader (); + if (chunkh == null) { + break; + } + Chunk chunk = null; + String id = chunkh.getID(); + int chunkSize = (int) chunkh.getSize (); + + if ("ever".equals (id)) { + chunk = new ExifVersionChunk (_module, chunkh, _dstream); + } + else if ("erel".equals (id) || + "etim".equals (id) || + "ecor".equals (id) || + "emdl".equals (id)) { + chunk = new ExifStringChunk (_module, chunkh, _dstream); + } + else if ("emnt".equals (id)) { + + } + else if ("eucm".equals (id)) { + + } + if (chunk == null) { + _module.skipBytes (_dstream, (int) chunkSize, _module); + info.setMessage (new InfoMessage + ("Chunk type '" + id + "' in Associated Data Chunk ignored")); + } + else { + try { + if (!chunk.readChunk (info)) { + return false; + } + } + catch (JhoveException e) { + info.setMessage(new ErrorMessage (e.getMessage())); + info.setWellFormed (false); + return false; + } + } + } + return false; + } + + /** Reads the chunk and its nested chunks, and puts appropriate + * properties into the RepInfo object. + * + * @return false if the chunk or a nested chunk + * is structurally + * invalid, otherwise true + */ + public boolean readAdtlChunk(RepInfo info) + throws IOException + { + WaveModule module = (WaveModule) _module; + + for (;;) { + ChunkHeader chunkh = getNextChunkHeader (); + if (chunkh == null) { + break; + } + Chunk chunk = null; + // The chunk list can include Labels, Notes, and + // Labelled Text. + String id = chunkh.getID(); + int chunkSize = (int) chunkh.getSize (); + if (id.equals ("labl")) { + chunk = new LabelChunk (_module, chunkh, _dstream); + } + else if (id.equals ("note")) { + chunk = new NoteChunk (_module, chunkh, _dstream); + } + else if (id.equals ("ltxt")) { + chunk = new LabeledTextChunk (_module, chunkh, _dstream); + } + + if (chunk == null) { + _module.skipBytes (_dstream, (int) chunkSize, _module); + info.setMessage (new InfoMessage ("Chunk type '" + id + + "' in Associated Data Chunk ignored")); + } + else { + try { + if (!chunk.readChunk (info)) { + return false; + } + } + catch (JhoveException e) { + info.setMessage(new ErrorMessage (e.getMessage())); + info.setWellFormed (false); + return false; + } + } + if ((chunkSize & 1) != 0) { + // Must come out to an even byte boundary + _module.skipBytes (_dstream, 1, _module); + --bytesLeft; + } + } + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/ListInfoTextChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/ListInfoTextChunk.java new file mode 100644 index 00000000..97f41323 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/ListInfoTextChunk.java @@ -0,0 +1,151 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.wave; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.*; + +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.WaveModule; +import edu.harvard.hul.ois.jhove.module.iff.*; + +/** + * + * This implements any of the subchunks of the ListInfoChunk + * (a LIST chunk with a list type of INFO). + * All such chunks are identical in format, consisting of a + * null-terminated string. About 17 chunk ID's are recognized; + * others will be ignored. + * + * @author Gary McGath + * + */ +public class ListInfoTextChunk extends Chunk { + + private ListInfoChunk _parent; + private List _listInfoProps; + String _chunkID; + + /** + * Constructor. + * + * @param module The WaveModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the WAVE data are being read + * @param listInfoProps A List of the Properties associated with the + * ListInfoChunk + * @param parent The ListInfoChunk within which this Chunk occurs + */ + public ListInfoTextChunk( + ModuleBase module, + ChunkHeader hdr, + DataInputStream dstrm, + List listInfoProps, + ListInfoChunk parent) { + super(module, hdr, dstrm); + _parent = parent; + _chunkID = hdr.getID (); + _listInfoProps = listInfoProps; + } + + + /** Reads a chunk and..... + * + * @return false if the chunk is structurally + * invalid, otherwise true + */ + public boolean readChunk(RepInfo info) throws IOException { + WaveModule module = (WaveModule) _module; + byte[] buf = new byte[(int) bytesLeft]; + ModuleBase.readByteBuf (_dstream, buf, module); + String txt = new String (buf); + txt = txt.trim (); // remove trailing null + String propName = null; + + // Add the string to the property list if we can identify it. + if ("IARL".equals (_chunkID)) { + propName = "ArchivalLocation"; + } + else if ("IART".equals (_chunkID)) { + propName = "Artist"; + } + else if ("ICMS".equals (_chunkID)) { + propName = "Commissioned"; + } + else if ("ICMT".equals (_chunkID)) { + propName = "Comments"; + } + else if ("ICOP".equals (_chunkID)) { + propName = "Copyright"; + } + else if ("ICRD".equals (_chunkID)) { + propName = "CreationDate"; + } + else if ("ICRP".equals (_chunkID)) { + propName = "Cropped"; + } + else if ("IDIM".equals (_chunkID)) { + propName = "Dimensions"; + } + else if ("IDPI".equals (_chunkID)) { + propName = "DotsPerInch"; + } + else if ("IENG".equals (_chunkID)) { + propName = "Engineer"; + } + else if ("IGNR".equals (_chunkID)) { + propName = "Genre"; + } + else if ("IKEY".equals (_chunkID)) { + propName = "Keywords"; + } + else if ("ILGT".equals (_chunkID)) { + propName = "Lightness"; + } + else if ("IMED".equals (_chunkID)) { + propName = "Medium"; + } + else if ("INAM".equals (_chunkID)) { + propName = "Name"; + } + else if ("IPLT".equals (_chunkID)) { + propName = "PaletteSetting"; + } + else if ("IPRD".equals (_chunkID)) { + propName = "Product"; + } + else if ("ISBJ".equals (_chunkID)) { + propName = "Subject"; + } + else if ("ISFT".equals (_chunkID)) { + propName = "Software"; + } + else if ("ISHP".equals (_chunkID)) { + propName = "Sharpness"; + } + else if ("ISRC".equals (_chunkID)) { + propName = "Source"; + } + else if ("ISRF".equals (_chunkID)) { + propName = "SourceForm"; + } + else if ("ITCH".equals (_chunkID)) { + propName = "Technician"; // making this a scratch file? + } + if (propName != null) { + _listInfoProps.add (new Property (propName, + PropertyType.STRING, txt)); + } + else { + info.setMessage (new InfoMessage + ("Chunk type '" + _chunkID + "' in List Info Chunk ignored")); + } + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/MpegChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/MpegChunk.java new file mode 100644 index 00000000..2ace304d --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/MpegChunk.java @@ -0,0 +1,76 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.wave; + +import java.io.IOException; + +import edu.harvard.hul.ois.jhove.RepInfo; +import edu.harvard.hul.ois.jhove.module.iff.*; +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.WaveModule; +import java.io.*; +import java.util.*; + +/** + * Implementation of the WAVE MPEG Audio Extension Chunk. + * + * @author Gary McGath + * + */ +public class MpegChunk extends Chunk { + + /** + * Constructor. + * + * @param module The WaveModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the WAVE data are being read + */ + public MpegChunk ( + ModuleBase module, + ChunkHeader hdr, + DataInputStream dstrm) { + super(module, hdr, dstrm); + } + + + /** Reads a chunk and puts an MPEG Property into + * the RepInfo object. + * + * @return false if the chunk is structurally + * invalid, otherwise true + */ + public boolean readChunk(RepInfo info) throws IOException { + WaveModule module = (WaveModule) _module; + int soundInformation = module.readUnsignedShort(_dstream); + int frameSize = module.readUnsignedShort (_dstream); + int ancillaryDataLength = module.readUnsignedShort (_dstream); + int ancillaryDataDef = module.readUnsignedShort (_dstream); + module.skipBytes (_dstream, 4, module); // reserved + + List propList = new ArrayList (); + propList.add (module.buildBitmaskProperty(soundInformation, + "SoundInformation", + WaveStrings.SOUND_INFORMATION_1, + WaveStrings.SOUND_INFORMATION_0)); + propList.add (new Property ("FrameSize", + PropertyType.INTEGER, + new Integer (frameSize))); + propList.add (new Property ("AncillaryDataLength", + PropertyType.INTEGER, + new Integer (ancillaryDataLength))); + propList.add (module.buildBitmaskProperty(ancillaryDataDef, + "AncillaryDataDef", + WaveStrings.ANCILLARY_DEF_1, + WaveStrings.ANCILLARY_DEF_0)); + module.addWaveProperty (new Property ("MPEG", + PropertyType.PROPERTY, + PropertyArity.LIST, + propList)); + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/NoteChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/NoteChunk.java new file mode 100644 index 00000000..e689cae8 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/NoteChunk.java @@ -0,0 +1,48 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.wave; + +import java.io.DataInputStream; +import java.io.IOException; +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.WaveModule; +import edu.harvard.hul.ois.jhove.module.iff.ChunkHeader; + +/** + * Implementation of the WAVE Note Chunk. + * + * @author Gary McGath + * + */ +public class NoteChunk extends SimpleTextChunk { + + /** + * Constructor. + * + * @param module The WaveModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the WAVE data are being read + */ + public NoteChunk( + ModuleBase module, + ChunkHeader hdr, + DataInputStream dstrm) { + super(module, hdr, dstrm); + } + + /** Reads a chunk and adds to the module's list of Notes. + * + * @return false if the chunk is structurally + * invalid, otherwise true + */ + public boolean readChunk(RepInfo info) throws IOException { + WaveModule module = (WaveModule) _module; + Property p = readTextProp (module, "Note"); + module.addNote (p); + return true; + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/PeakEnvelopeChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/PeakEnvelopeChunk.java new file mode 100644 index 00000000..0ee1e4c4 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/PeakEnvelopeChunk.java @@ -0,0 +1,186 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.wave; + +import edu.harvard.hul.ois.jhove.module.iff.*; +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.WaveModule; +import java.io.*; +import java.util.*; + +/** + * Implementation of the WAVE Peak Envelope ('levl') Chunk. + * + * @author Gary McGath + * + */ +public class PeakEnvelopeChunk extends Chunk { + + + /** + * Constructor. + * + * @param module The WaveModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the WAVE data are being read + */ + public PeakEnvelopeChunk ( + ModuleBase module, + ChunkHeader hdr, + DataInputStream dstrm) { + super(module, hdr, dstrm); + } + + + /** Reads a chunk and puts a BroadcastAudioExtension Property into + * the RepInfo object. + * + * @return false if the chunk is structurally + * invalid, otherwise true + */ + public boolean readChunk(RepInfo info) throws IOException { + WaveModule module = (WaveModule) _module; + long version = module.readUnsignedInt (_dstream); + long format = module.readUnsignedInt (_dstream); + long pointsPerValue = module.readUnsignedInt (_dstream); + long blockSize = module.readUnsignedInt (_dstream); + long peakChannels = module.readUnsignedInt (_dstream); + long numPeakFrames = module.readUnsignedInt (_dstream); + long posPeakOfPeaks = module.readUnsignedInt (_dstream); + long offsetToPeaks = module.readUnsignedInt (_dstream); + byte[] buf28 = new byte[28]; + ModuleBase.readByteBuf (_dstream, buf28, module); + String timestamp = byteBufString (buf28); + module.skipBytes (_dstream, 60, module); + + // The format of the peak data depends on the value of + // format and pointsPerValue. If format = 1, points are + // unsigned byte. If format = 2, points are unsigned short. + // The number of points per peak value is equal to the + // value of pointsPerValue, which must be 1 or 2. + + Property peaksProp = null; + if (bytesLeft > 120) { + int pointBytes = (int) (bytesLeft - 120); + int nPoints = 0; + int nValues = 0; + if (format == 1) { + nPoints = pointBytes; + } + else if (format == 2) { + nPoints = pointBytes / 2; + } + else { + info.setValid (false); + info.setMessage (new ErrorMessage + ("Invalid format value in Peak Envelope Chunk")); + } + if (pointsPerValue == 1) { + nValues = nPoints; + } + else if (pointsPerValue == 2) { + nValues = nPoints / 2; + } + else { + info.setValid (false); + info.setMessage (new ErrorMessage + ("Invalid pointsPerValue in Peak Envelope Chunk")); + } + if (info.getValid() == RepInfo.FALSE) { + module.skipBytes (_dstream, (int) bytesLeft - 120, module); + return true; + } + + // We have two different kinds of property depending on + // pointsPerValue. + if (pointsPerValue == 2) { + Property[] pointArray = new Property[nValues]; + for (int i = 0; i < nValues; i++) { + int[] point = new int[2]; + pointArray[i] = new Property ("Point", + PropertyType.INTEGER, + PropertyArity.ARRAY, + point); + if (format == 1) { + point[0] = + ModuleBase.readUnsignedByte (_dstream, module); + point[1] = + ModuleBase.readUnsignedByte (_dstream, module); + } + else { + point[0] = + module.readUnsignedShort (_dstream); + point[1] = + module.readUnsignedShort (_dstream); + } + } + peaksProp = new Property ("PeakEnvelopeData", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + pointArray); + } + else { + // 1 point per value + int[] pointArray = new int[nValues]; + for (int i = 0; i < nValues; i++) { + if (format == 1) { + pointArray[i] = + ModuleBase.readUnsignedByte (_dstream, module); + } + else { + pointArray[i] = + module.readUnsignedShort (_dstream); + } + } + peaksProp = new Property ("PeakEnvelopeData", + PropertyType.INTEGER, + PropertyArity.ARRAY, + pointArray); + } + } + + // Now put the whole mess together as a List of Properties. + List plist = new ArrayList (20); + plist.add (new Property ("Version", + PropertyType.LONG, + new Long (version))); + plist.add (new Property ("Format", + PropertyType.LONG, + new Long (format))); + plist.add (new Property ("PointsPerValue", + PropertyType.LONG, + new Long (pointsPerValue))); + plist.add (new Property ("BlockSize", + PropertyType.LONG, + new Long (blockSize))); + plist.add (new Property ("PeakChannels", + PropertyType.LONG, + new Long (peakChannels))); + plist.add (new Property ("NumPeakFrames", + PropertyType.LONG, + new Long (numPeakFrames))); + plist.add (new Property ("PosPeakOfPeaks", + PropertyType.LONG, + new Long (posPeakOfPeaks))); + plist.add (new Property ("OffsetToPeaks", + PropertyType.LONG, + new Long (offsetToPeaks))); + if (timestamp.length () > 0) { + plist.add (new Property ("Timestamp", + PropertyType.STRING, + timestamp)); + } + if (peaksProp != null) { + plist.add (peaksProp); + } + module.addWaveProperty (new Property ("PeakEnvelope", + PropertyType.PROPERTY, + PropertyArity.LIST, + plist)); + + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/SampleChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/SampleChunk.java new file mode 100644 index 00000000..0f8ab878 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/SampleChunk.java @@ -0,0 +1,148 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.wave; + +import java.io.DataInputStream; +import java.io.IOException; +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.WaveModule; +import edu.harvard.hul.ois.jhove.module.iff.Chunk; +import edu.harvard.hul.ois.jhove.module.iff.ChunkHeader; + +/** + * Implementation of the WAVE Sample (or Sampler) Chunk, which + * gives information about a MIDI sample. + * + * @author Gary McGath + * + */ +public class SampleChunk extends Chunk { + + /** + * Constructor. + * + * @param module The WaveModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the WAVE data are being read + */ + public SampleChunk( + ModuleBase module, + ChunkHeader hdr, + DataInputStream dstrm) { + super(module, hdr, dstrm); + } + + /** Reads a chunk and puts a Sample property into + * the RepInfo object. + * + * It isn't clear whether multiple + * Sample chunks are allowed (representing different sound samples + * for different notes or note ranges). This module assumes + * they are, so it constructs a Samples property, consisting + * of a list of Sample properties. + * + * @return false if the chunk is structurally + * invalid, otherwise true + */ + public boolean readChunk(RepInfo info) throws IOException { + WaveModule module = (WaveModule) _module; + // read MMA manufacturer and product codes (which we probably won't + // try to resolve) + long manufacturer = module.readUnsignedInt (_dstream); + long product = module.readUnsignedInt (_dstream); + // sample time in nanoseconds + long samplePeriod = module.readUnsignedInt (_dstream); + // read midi unity note (1-127, so why does it get 4 bytes?) + long unityNote = module.readUnsignedInt (_dstream); + // MIDI pitch fraction. This is apparently a fixed-point + // number representing a value between 0 and 1. + long pitchFraction = module.readUnsignedInt (_dstream); + // Get SMPTE format. Maximum value is 30, but it also gets 4 bytes. + long smpteFormat = module.readUnsignedInt (_dstream); + // SMPTE offset consists of four values in a confusing mix + // of signed and unsigned data. The web page I'm working from + // says the frame offset is an unsigned value from 0 to -1. + int sampleOffsetHour = ModuleBase.readSignedByte (_dstream, module); + int sampleOffsetMinute = ModuleBase.readUnsignedByte (_dstream, module); + int sampleOffsetSecond = ModuleBase.readUnsignedByte (_dstream, module); + int sampleOffsetFrames = ModuleBase.readSignedByte (_dstream, module); + // Or should it be unsigned?? + Property[] smpteArr = new Property[4]; + smpteArr[0] = new Property ("Hour", PropertyType.INTEGER, + new Integer (sampleOffsetHour)); + smpteArr[1] = new Property ("Minute", PropertyType.INTEGER, + new Integer (sampleOffsetMinute)); + smpteArr[2] = new Property ("Second", PropertyType.INTEGER, + new Integer (sampleOffsetSecond)); + smpteArr[3] = new Property ("Frames", PropertyType.INTEGER, + new Integer (sampleOffsetFrames)); + + int nLoops = (int) module.readUnsignedInt (_dstream); + long extraBytes = module.readUnsignedInt (_dstream); // no. of extra bytes after loops + + // Build an array of loop properties + Property[] loopProps = new Property[nLoops]; + for (int i = 0; i < nLoops; i++) { + long cuePoint = module.readUnsignedInt (_dstream); + int type = (int) module.readUnsignedInt (_dstream); + int start = (int) module.readUnsignedInt (_dstream); + int end = (int) module.readUnsignedInt (_dstream); + long fraction = module.readUnsignedInt (_dstream); + long playCount = module.readUnsignedInt (_dstream); + + // Build the loop property. + Property[] lp = new Property[6]; + lp[0] = new Property ("CuePointID", PropertyType.LONG, + new Long (cuePoint)); + lp[1] = new Property ("Type", PropertyType.INTEGER, + new Integer (type)); + lp[2] = new Property ("Start", PropertyType.INTEGER, + new Integer (start)); + lp[3] = new Property ("End", PropertyType.INTEGER, + new Integer (end)); + lp[4] = new Property ("Fraction", PropertyType.LONG, + new Long (fraction)); + lp[5] = new Property ("PlayCount", PropertyType.LONG, + new Long (playCount)); + loopProps[i] = new Property ("Loop", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + lp); + } + + Property[] propArr = new Property[9]; + propArr[0] = new Property ("Manufacturer", PropertyType.LONG, + new Long (manufacturer)); + propArr[1] = new Property ("Product", PropertyType.LONG, + new Long (product)); + propArr[2] = new Property ("SamplePeriod", PropertyType.LONG, + new Long (samplePeriod)); + propArr[3] = new Property ("UnityNote", PropertyType.LONG, + new Long (unityNote)); + propArr[4] = new Property ("PitchFraction", PropertyType.LONG, + new Long (pitchFraction)); + propArr[5] = new Property ("SMPTEFormat", PropertyType.LONG, + new Long (smpteFormat)); + propArr[6] = new Property ("SMPTEOffset", PropertyType.PROPERTY, + PropertyArity.ARRAY, + smpteArr); + propArr[7] = new Property ("Loops", PropertyType.PROPERTY, + PropertyArity.ARRAY, + loopProps); + propArr[8] = new Property ("ExtraDataBytes", PropertyType.LONG, + new Long (extraBytes)); + module.addSample (new Property ("Sample", + PropertyType.PROPERTY, + PropertyArity.ARRAY, + propArr)); + // Skip the extra data bytes that follow + // the loop data. + module.skipBytes (_dstream, (int) extraBytes, _module); + + + return true; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/SimpleTextChunk.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/SimpleTextChunk.java new file mode 100644 index 00000000..3442eb87 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/SimpleTextChunk.java @@ -0,0 +1,62 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.wave; + +import java.io.DataInputStream; +import java.io.IOException; + +import edu.harvard.hul.ois.jhove.*; +//import edu.harvard.hul.ois.jhove.RepInfo; +import edu.harvard.hul.ois.jhove.module.WaveModule; +import edu.harvard.hul.ois.jhove.module.iff.Chunk; +import edu.harvard.hul.ois.jhove.module.iff.ChunkHeader; + +/** + * Superclass for the very similar Note and Label chunks. + * + * @author Gary McGath + * + */ +public abstract class SimpleTextChunk extends Chunk { + + /** + * Constructor. + * + * @param module The WaveModule under which this was called + * @param hdr The header for this chunk + * @param dstrm The stream from which the WAVE data are being read + */ + public SimpleTextChunk( + ModuleBase module, + ChunkHeader hdr, + DataInputStream dstrm) { + super(module, hdr, dstrm); + } + + /** Reads the text item, and returns a Property containing the + * cue point ID and the text. */ + protected Property readTextProp (WaveModule module, String propName) + throws IOException + { + long cueID = module.readUnsignedInt (_dstream); + bytesLeft -= 4; + byte[] buf = new byte[(int) bytesLeft]; + ModuleBase.readByteBuf (_dstream, buf, module); + String txt = new String (buf); + Property[] propArr = new Property[2]; + propArr[0] = new Property ("CuePointID", + PropertyType.LONG, + new Long (cueID)); + propArr[1] = new Property ("Text", + PropertyType.STRING, + txt); + return new Property (propName, + PropertyType.PROPERTY, + PropertyArity.ARRAY, + propArr); + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/WaveStrings.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/WaveStrings.java new file mode 100644 index 00000000..e8a99356 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/WaveStrings.java @@ -0,0 +1,494 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.wave; + +/** + * A class for holding arrays of informative strings that will go into + * properties of a WAVE object. + * + * @author Gary McGath + * + */ +public class WaveStrings { + + /** A private constructor just to make sure nobody + instantiates the class by mistake. */ + private WaveStrings() + { + } + + + /** Strings for data compression formats; indexed by + * COMPRESSION_INDEX */ + public final static String[] COMPRESSION_FORMAT = + { "Unknown or unspecified format", // 0 + "PCM audio in integer format", // 1 + "Microsoft adaptive PCM", // 2 + "PCM audio in IEEE floating-point format", // 3 + "VSELP codec for Windows CE 2.0 device", // 4 + "IBM CVSD", // 5 + "Microsoft ALAW", // 6 + "Microsoft MULAW", // 7 + "Microsoft DTS", // 8 + "Microsoft Digital Rights Managed encrypted audio", // 9 + "Microsoft Speech audio codec", // 0XA + "Windows Media RT Voice", // 0xB + "OKI ADPCM", // 0X10 + "Intel ADPCM", // 0X11 + "Videologic Systems ADPCM", // 0X12 + "Sierra ADPCM", // 0X13 + "Antex ADPCM", // 0X14 + "DSP Solutions DIGISTD", // 0X15 + "DSP Solutions DIGIFIX", // 0X16 + "OKI ADPCM chips or firmware", // 0X17 + "ADPCM for Jazz 16 chip set", // 0X18 + "HP CU Codec", // 0X19 + "HP Dynamic Voice", // 0X1A + "Yamaha ADPCM", // 0X20 + "Speech Compression SONARC", // 0X21 + "DSP Group True Speech", // 0X22 + "Echo Speech SC1", // 0X23 + "Ahead Audio File AF36", // 0X24 + "Audio Processing Technology APTX", // 0X25 + "Ahead Audio File AF10", // 0X26 + "Prosody CTI speech card", // 0X27 + "Merging Technologies LRC", // 0X28 + "Dolby AC2", // 0X30 + "Microsoft GSM610", // 0X31 + "Microsoft MSN audio codec", // 0X32 + "Antex ADPCME", // 0X33 + "Control Resources VQLPC", // 0X34 + "DSP Solutions Digireal", // 0X35 + "DSP Solutions DIGIADPCM", // 0X36 + "Control Resources CR10", // 0X37 + "Natural Microsystems VBXADPCM", // 0X38 + "Roland RDAC", // 0X39 + "Echo Speech SC3", // 0X3A + "Rockwell ADPCM", // 0X3B + "Rockwell DIGITALK", // 0X3C + "Xebec Multimedia Solutions", // 0X3D + "Antex G721 ADPCM", // 0X40 + "Antex G728 CELP", // 0X41 + "Microsoft MSG723", // 0X42 + "Microsoft MSG723.1", // 0X43 + "Microsoft MSG729", // 0X44 + "Microsoft MSG726", // 0X45 + "Microsoft MPEG", // 0X50 + "InSoft RT24", // 0X52 + "InSoft PAC", // 0X53 + "ISO/MPEG Layer 3 format tag", // 0X55 + "Lucent G723", // 0X59 + "Cirrus Logic", // 0X60 + "ESS PCM", // 0X61 + "Voxware", // 0X62 + "Canopus ATRACWAVEFORMAT", // 0X63 + "APICOM G726 ADPCM", // 0X64 + "APICOM G722 ADPCM", // 0X65 + "Microsoft DSAT Display", // 0X67 + "Voxware Byte Aligned", // 0X69 + "Voxware AC8", // 0X70 + "Voxware AC10", // 0X71 + "Voxware AC16", // 0X72 + "Voxware AC20", // 0X73 + "Voxware RT24", // 0X74 + "Voxware RT29", // 0X75 + "Voxware RT29HW", // 0X76 + "Voxware VR12", // 0X77 + "Voxware VR18", // 0X78 + "Voxware TQ40", // 0X79 + "Voxware SC3 (7A)", // 0X7A + "Voxware SC3 (7B)", // 0X7B + "SoftSound", // 0X80 + "Voxware TQ60", // 0X81 + "Microsoft MSRT24", // 0X82 + "AT&T G729A", // 0X83 + "Motion Pixels MVI2", // 0X84 + "Datafusion Systems G726", // 0X85 + "Datafusion Systems GSM610", // 0X86 + "Iterated Systems ISI Audio", // 0X88 + "OnLive", // 0X89 + "Multitude FT SX20", // 0X8A + "G.721 ADPCM", // 0X8B + "Convedia G729", // 0X8C + "Congruency Audio Codec", // 0X8D + "Siemens SBC24", // 0X91 + "Sonic Foundry Dolby AC3 SPDIF", // 0X92 + "MediaSonic G723", // 0x93, + "Prosody CTI speech card", // 0X94, + "ZyXEL ADPCM", // 0X97, + "Philips LPCBB", // 0X98, + "Studer Professional Audio Packed", // 0X99, + "Phony Talk", // 0XA0, + "Racal Recorder GSM", // 0XA1, + "Racal Recorder G720.a", // 0XA2, + "Racal G723.1", // 0XA3, + "Racal Tetra ACELP", // 0XA4, + "NEC AAC", // 0XB0, + "Rhetorex ADPCM wave format type", // 0X100, + "BeCubed IRAT", // 0x101, + "Vivo G723", // 0X111, + "Vivo Siren", // 0X112, + "Philips CELP", // 0X120, + "Philips Grundig", // 0X121, + "DEC G723", // 0X123, + "SANYO LD-ADPCM wave type", // 0X125, + "Sipro Lab ACELPNET", // 0X130, + "Sipro Lab ACELP4800", // 0X131, + "Sipro Lab ACELP8V3", // 0X132, + "Sipro Lab ACELPG729", // 0X133, + "Sipro Lab ACELPG729A", // 0X134, + "Sipro Lab Kelvin", // 0X135, + "VoiceAge AMR", // 0X136, + "Dictaphone G726 ADPCM", // 0X140, + "Dictaphone CELP68", // 0X141, + "Dictaphone CELP54", // 0X142, + "Qualcomm Pure Voice", // 0X150, + "Qualcomm Half Rate", // 0X151, + "Related to GSM 6.10", // 0x155, + "Microsoft Audio 1", // 0X160, + "Microsoft Audio 2", // 0X161, + "Microsoft Multichannel WMA", // 0X162, + "WMA lossless", // 0x163 + "WMA Pro over S/PDIF", // 0x164 + "Unisys ADPCM", // 0X170, + "Unisys ULAW", // 0X171, + "Unisys ALAW", // 0X172, + "Unisys NAP 16K", // 0X173, + "SyCom ACM SYC008", // 0X174, + "SyCom ACM SYC701 G726L", // 0X175, + "SyCom ACM SYC701 CELP54", // 0X176, + "SyCom ACM SYC701 CELP68", // 0X177 + "Knowledge Adventure ADPCM", // 0X178 + "Fraunhofer IIS MPEC 2AAC", // 0X180 + "Digital Theater Systems DS", // 0X190, + "Creative Labs ADPCM", // 0X200 + "Fast Speech 8", // 0X202 + "Fast Speech 10", // 0X203 + "UHER ADPCM", // 0X210 + "Quarterdeck", // 0X220 + "I-Link VC", // 0X230 + "Aureal Raw Sport", // 0x240 + "Interactive Products HSX", // 0x250 + "Interactive Products RPELP", // 0x251 + "Cs2", // 0X260 + "Sony SCX", // 0X270 + "Sony SCY", // 0X271 + "Sony ATRAC3", // 0X272 + "Sony SPC", // 0X273 + "Telum", // 0X280 + "Telum IA", // 0X281 + "Norcom Voice Systems ADPCM", // 0x285 + "Fujitsu FM Towns SND", // 0X300 + "Fujitsu (301)", // 0x301 + "Fujitsu (302)", // 0x302 + "Fujitsu (303)", // 0x303 + "Fujitsu (304)", // 0x304 + "Fujitsu (305)", // 0x305 + "Fujitsu (306)", // 0x306 + "Fujitsu (307)", // 0x307 + "Fujitsu (308)", // 0x308 + "Micronas Development", // 0x350 + "Micronas CELP833", // 0x351 + "Brooktree digital audio format", // 0x400 + "QDesign Music", // 0x450 + "AT&T VMPCM", // 0x680 + "AT&T TPC", // 0x681 + "Olivetti SM", // 0x1000 + "Olivetti PCM", // 0x1001 + "Olivetti CELP", // 0x1002 + "Olivetti SBC", // 0x1003 + "Olivetti OPR", // 0x1004 + "Lernout & Hauspie Codec", // 0x1100 + "Lernout & Hauspie CELP", // 0x1101 + "Lernout & Hauspie SB8", // 0x1102 + "Lernout & Hauspie SB12", // 0x1103 + "Lernout & Hauspie SB16", // 0x1104 + "Norris", // 0x1400 + "AT&T Soundspace Musicompress", // 0x1500 + + "Sonic Foundry Lossless", // 0x1971 + "Innings ADPCM", // 0X1979 + "FAST Multimedia DVM", // 0x2000 + "Reserved rangle to 0x2600", // 0x2500 + "Divio's AAC", // 0x4143 + "Nokia adaptive multirate", // 0x4201 + "Divio's G726", // 0x4243 + "3Com NBX", // 0x7000 + "Adaptive multirate", // 0x7a21 + "AMR with silence detection", // 0x7a22 + "Comverse G723.1", // 0xa100 + "Comverse AVQSBC", // 0xa101 + "Comverse old SBC", // 0xa102 + "Symbol Technology's G729A", // 0xa103 + "Voice Age AMR WB", // 0xa104 + "Ingenient's G726", // 0xa105 + "ISO/MPEG-4 advanced audio Coding", // 0xa106 + "Encore Software Ltd's G726", // 0xa107 + "Extensible Wave format" // 0xfffe + }; + + + public final static int[] COMPRESSION_INDEX = + { + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 0XA, + 0xB, + 0X10, + 0X11, + 0X12, + 0X13, + 0X14, + 0X15, + 0X16, + 0X17, + 0X18, + 0X19, + 0X1A, + 0X20, + 0X21, + 0X22, + 0X23, + 0X24, + 0X25, + 0X26, + 0X27, + 0X28, + 0X30, + 0X31, + 0X32, + 0X33, + 0X34, + 0X35, + 0X36, + 0X37, + 0X38, + 0X39, + 0X3A, + 0X3B, + 0X3C, + 0X3D, + 0X40, + 0X41, + 0X42, + 0X43, + 0X44, + 0X45, + 0X50, + 0X52, + 0X53, + 0X55, + 0X59, + 0X60, + 0X61, + 0X62, + 0X63, + 0X64, + 0X65, + 0X67, + 0X69, + 0X70, + 0X71, + 0X72, + 0X73, + 0X74, + 0X75, + 0X76, + 0X77, + 0X78, + 0X79, + 0X7A, + 0X7B, + 0X80, + 0X81, + 0X82, + 0X83, + 0X84, + 0X85, + 0X86, + 0X88, + 0X89, + 0X8A, + 0X8B, + 0X8C, + 0X8D, + 0X91, + 0X92, + 0x93, + 0X94, + 0X97, + 0X98, + 0X99, + 0XA0, + 0XA1, + 0XA2, + 0XA3, + 0XA4, + 0XB0, + 0X100, + 0x101, + 0X111, + 0X112, + 0X120, + 0X121, + 0X123, + 0X125, + 0X130, + 0X131, + 0X132, + 0X133, + 0X134, + 0X135, + 0X136, + 0X140, + 0X141, + 0X142, + 0X150, + 0X151, + 0x155, + 0X160, + 0X161, + 0X162, + 0x163, + 0x164, + 0X170, + 0X171, + 0X172, + 0X173, + 0X174, + 0X175, + 0X176, + 0X177, + 0X178, + 0X180, + 0X190, + 0X200, + 0X202, + 0X203, + 0X210, + 0X220, + 0X230, + 0x240, + 0x250, + 0x251, + 0X260, + 0X270, + 0X271, + 0X272, + 0X273, + 0X280, + 0X281, + 0x285, + 0X300, + 0x301, + 0x302, + 0x303, + 0x304, + 0x305, + 0x306, + 0x307, + 0x308, + 0x350, + 0x351, + 0x400, + 0x450, + 0x680, + 0x681, + 0x1000, + 0x1001, + 0x1002, + 0x1003, + 0x1004, + 0x1100, + 0x1101, + 0x1102, + 0x1103, + 0x1104, + 0x1400, + 0x1500, + 0x1971, + 0X1979, + 0x2000, + 0x2500, + 0x4143, + 0x4201, + 0x4243, + 0x7000, + 0x7a21, + 0x7a22, + 0xa100, + 0xa101, + 0xa102, + 0xa103, + 0xa104, + 0xa105, + 0xa106, + 0xa107, + 0xfffe + }; + + /** Strings for SMPTE formats in the Sample Chunk */ + public final static String[] SMPTE_FORMAT = + { + "No SMPTE offset", + "24 frames per second", + "25 frames per second", + "30 frames per second with frame dropping", + "30 frames per second" + }; + + /** Indices for SMPTE formats in the Sample Chunk */ + public final static int[] SMPTE_FORMAT_INDEX = + { 0, 24, 25, 29, 30 }; + + /** Flags for SoundInformation bits in the MPEG chunk, + * "1" values */ + public final static String[] SOUND_INFORMATION_1 = + { + "Non-homogeneous sound data", + "Padding bit always 0", + "Sample frequency 22.05 or 44.1 KHz", + "Free format is used" + }; + + + /** Flags for SoundInformation bits in the MPEG chunk, + * "0" values */ + public final static String[] SOUND_INFORMATION_0 = + { + "Homogeneous sound data", + "Padding bit may alternate", + "", + "No free format audio frame" + }; + + /** Flags for ancillary data definition in the MPEG chunk, + * "1" values + */ + public final static String[] ANCILLARY_DEF_1 = + { + "Energy of left channel present", + "Private byte is free for internal use" + }; + + + /** Flags for ancillary data definition in the MPEG chunk, + * "0" values + */ + public final static String[] ANCILLARY_DEF_0 = + { + "Energy of left channel absent", + "No private byte free for internal use" + }; +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/build.xml b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/build.xml new file mode 100644 index 00000000..97daaea0 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/build.xml @@ -0,0 +1,32 @@ + + JPEG module build file + Jhove - JSTOR/Harvard Object Validation Environment + Version 1.0 2004-05-05 + Copyright 2004 by JSTOR and the President and Fellows of Harvard College + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/package.html b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/package.html new file mode 100644 index 00000000..c9f825c6 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/wave/package.html @@ -0,0 +1,24 @@ + + + + + + + +Contains supporting classes for the WAVE-HUL module. +
+ + + + + +
+ + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/xml/EntityInfo.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/xml/EntityInfo.java new file mode 100644 index 00000000..88dd4edf --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/xml/EntityInfo.java @@ -0,0 +1,12 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004-2012 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.xml; + +/** A little class so we can treat entity information as a structure */ +public class EntityInfo { + public String publicID; + public String systemID; +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/xml/ProcessingInstructionInfo.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/xml/ProcessingInstructionInfo.java new file mode 100644 index 00000000..199e3cac --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/xml/ProcessingInstructionInfo.java @@ -0,0 +1,12 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004-2012 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.xml; + +/** A little class so we can treat processing instruction information as a structure */ +public class ProcessingInstructionInfo { + public String target; + public String data; +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/xml/SchemaInfo.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/xml/SchemaInfo.java new file mode 100644 index 00000000..890a5e7d --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/xml/SchemaInfo.java @@ -0,0 +1,12 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004-2012 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.xml; + +/** A little class so we can treat schema information as a structure */ +public class SchemaInfo { + public String namespaceURI; + public String location; +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/xml/XhtmlProcessing.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/xml/XhtmlProcessing.java new file mode 100644 index 00000000..4ff9a43e --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/xml/XhtmlProcessing.java @@ -0,0 +1,336 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.xml; + +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.html.HtmlMetadata; +import java.util.*; +import org.xml.sax.Attributes; + +/** + * Methods for processing XHTML elements in an XML document. + * These are closely related in functionality to corresponding + * methods in the XML module. + * + * @author Gary McGath + * @see edu.harvard.hul.ois.jhove.module.html.HtmlDocDesc + */ +public final class XhtmlProcessing { + + /** Process an element and extract metadata */ + /** Process the element to extract any available metadata. */ + protected static void processElement (String localName, + String qualifiedName, + Attributes atts, + HtmlMetadata mdata) + { + if ("html".equals (localName)) { + processHtml (mdata, atts); + } + else if ("meta".equals (localName)) { + processMeta (mdata, atts); + } + else if ("a".equals (localName)) { + processA (mdata, atts); + } + else if ("img".equals (localName)) { + processImg (mdata, atts); + } + else if ("frame".equals (localName)) { + processFrame (mdata, atts); + } + else if ("script".equals (localName)) { + processScript (mdata, atts); + } + else if ("abbr".equals (localName)) { + processAbbr (mdata, atts); + } + else if ("title".equals (localName)) { + processTitle (mdata, atts); + } + else if ("cite".equals (localName)) { + processCite (mdata, atts); + } + + /* Look for certain attributes in any tag. */ + for (int i = 0; i < atts.getLength (); i++) { + String attname = atts.getLocalName (i); + String attval = atts.getValue (i); + if ("lang".equals (attname) && attval != null) { + mdata.addLanguage (attval); + } + } + } + + + /** Process metadata from an HTML tag */ + private static void processHtml (HtmlMetadata mdata, Attributes atts) + { + String lang = null; + for (int i = 0; i < atts.getLength (); i++) { + String attname = atts.getLocalName (i); + String attval = atts.getValue (i); + if ("lang".equals (attname)) { + lang = attval; + } + } + if (lang != null) { + mdata.setLanguage(lang); + } + } + + + /** Process metadata from a META tag */ + private static void processMeta (HtmlMetadata mdata, Attributes atts) + { + String name = null; + String httpeq = null; + String content = null; + for (int i = 0; i < atts.getLength (); i++) { + String attname = atts.getLocalName (i); + String attval = atts.getValue (i); + if ("name".equals (attname)) { + name = attval; + } + if ("http-equiv".equals (attname)) { + httpeq = attval; + } + if ("content".equals (attname)) { + content = attval; + } + } + if (name != null || httpeq != null || content != null) { + List plist = new ArrayList (3); + if (name != null) { + plist.add (new Property ("Name", + PropertyType.STRING, + name)); + } + if (httpeq != null) { + plist.add (new Property ("Httpequiv", + PropertyType.STRING, + httpeq)); + } + if (content != null) { + plist.add (new Property ("Content", + PropertyType.STRING, + content)); + } + mdata.addMeta (new Property ("Meta", + PropertyType.PROPERTY, + PropertyArity.LIST, + plist)); + } + } + + /** Process metadata from an A element. Only elements with an + * HREF attribute are of interest here. We ignore links + * to anchors. */ + private static void processA (HtmlMetadata mdata, Attributes atts) + { + for (int i = 0; i < atts.getLength (); i++) { + String attname = atts.getLocalName (i); + String attval = atts.getValue (i); + if ("href".equals (attname)) { + String link = attval; + if (link.length() > 0 && link.charAt (0) != '#') { + mdata.addLink (link); + } + break; + } + } + } + + /** Process metadata from the IMG element. */ + private static void processImg (HtmlMetadata mdata, Attributes atts) + { + String alt = null; + String longdesc = null; + String src = null; + int height = -1; + int width = -1; + for (int i = 0; i < atts.getLength (); i++) { + String attname = atts.getLocalName (i); + String attval = atts.getValue (i); + if ("alt".equals (attname)) { + alt = attval; + } + else if ("src".equals (attname)) { + src = attval; + } + else if ("longdesc".equals (attname)) { + longdesc = attval; + } + else if ("height".equals (attname)) { + try { + height = Integer.parseInt(attval); + } + catch (Exception e) {} + } + else if ("width".equals (attname)) { + try { + width = Integer.parseInt(attval); + } + catch (Exception e) {} + } + } + List plist = new ArrayList (5); + if (alt != null) { + plist.add (new Property ("Alt", + PropertyType.STRING, + alt)); + } + if (longdesc != null) { + plist.add (new Property ("Longdesc", + PropertyType.STRING, + longdesc)); + } + if (src != null) { + plist.add (new Property ("Src", + PropertyType.STRING, + src)); + } + if (height >= 0) { + plist.add (new Property ("Height", + PropertyType.INTEGER, + new Integer (height))); + } + if (width >= 0) { + plist.add (new Property ("Width", + PropertyType.INTEGER, + new Integer (width))); + } + if (!plist.isEmpty ()) { + mdata.addImage(new Property ("Image", + PropertyType.PROPERTY, + PropertyArity.LIST, + plist)); + } + } + + + /** Process metadata from the FRAME element. */ + private static void processFrame (HtmlMetadata mdata, Attributes atts) + { + String name = null; + String title = null; + String src = null; + String longdesc = null; + int height = -1; + int width = -1; + for (int i = 0; i < atts.getLength (); i++) { + String attname = atts.getLocalName (i); + String attval = atts.getValue (i); + if ("name".equals (attname)) { + name = attval; + } + else if ("title".equals (attname)) { + title = attval; + } + else if ("src".equals (attname)) { + src = attval; + } + else if ("longdesc".equals (attname)) { + longdesc = attval; + } + } + List plist = new ArrayList (4); + if (name != null) { + plist.add (new Property ("Name", + PropertyType.STRING, + name)); + } + if (title != null) { + plist.add (new Property ("Title", + PropertyType.STRING, + title)); + } + if (longdesc != null) { + plist.add (new Property ("Longdesc", + PropertyType.STRING, + longdesc)); + } + if (src != null) { + plist.add (new Property ("Src", + PropertyType.STRING, + src)); + } + if (!plist.isEmpty ()) { + mdata.addFrame(new Property ("Frame", + PropertyType.PROPERTY, + PropertyArity.LIST, + plist)); + } + } + + /** Process metadata from the SCRIPT element. */ + private static void processScript (HtmlMetadata mdata, Attributes atts) + { + for (int i = 0; i < atts.getLength (); i++) { + String attname = atts.getLocalName (i); + String attval = atts.getValue (i); + if ("type".equals (attname)) { + String stype = attval; + if (stype.length() > 0 ) { + mdata.addScript (stype); + } + } + } + } + + /** Processes metadata from the ABBR element. + * This will require the PCdata as well as the attributes, + * so we deposit the Property temporarily in the Metadata. + */ + private static void processAbbr (HtmlMetadata mdata, Attributes atts) + { + List lst = new ArrayList (2); + Property p = new Property ("abbr", + PropertyType.PROPERTY, + PropertyArity.LIST, + lst); + for (int i = 0; i < atts.getLength (); i++) { + String attname = atts.getLocalName (i); + String attval = atts.getValue (i); + if ("title".equals (attname)) { + if (attval.length() > 0 ) { + lst.add (new Property ("title", + PropertyType.STRING, + attval)); + } + // Note: The PCData should be stuck at the + // front of the list when we get it, as + // the "abbr" property. + } + } + mdata.setPropUnderConstruction (p); + } + + /** Processes metadata from the TITLE element. + * This will require PCData, so we deposit the Property + * temporarily in the Metadata. + */ + private static void processTitle (HtmlMetadata mdata, Attributes atts) + { + Property p = new Property ("title", + PropertyType.STRING, + ""); // store property with placeholder value + mdata.setPropUnderConstruction (p); + } + + /** Processes metadata from the CITE element. + * This will require PCData, so we deposit the Property + * temporarily in the Metadata. + */ + private static void processCite (HtmlMetadata mdata, Attributes atts) + { + Property p = new Property ("cite", + PropertyType.STRING, + ""); // store property with placeholder value + mdata.setPropUnderConstruction (p); + } + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/xml/XmlDeclHandler.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/xml/XmlDeclHandler.java new file mode 100644 index 00000000..50f3fac0 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/xml/XmlDeclHandler.java @@ -0,0 +1,105 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.xml; + +import java.util.*; +import org.xml.sax.SAXException; +import org.xml.sax.ext.DeclHandler; + +/** + * + * This implementation of DeclHandler takes care of + * collecting entity declarations. + * + * @author Gary McGath + * + */ +public class XmlDeclHandler implements DeclHandler { + + private List _intEntityDeclarations; + private List _extEntityDeclarations; + + public XmlDeclHandler () + { + _intEntityDeclarations = new LinkedList (); + _extEntityDeclarations = new LinkedList (); + } + + + + /** + * Report an element type declaration. + * Does nothing. + * @see org.xml.sax.ext.DeclHandler#elementDecl(java.lang.String, java.lang.String) + */ + public void elementDecl(String arg0, String arg1) throws SAXException + { + } + + /** + * Adds internal entity declarations to the entity declarations + * list in the form of a String[2], with element 0 being the + * name and element 1 being the value. + */ + public void internalEntityDecl(String name, String value) + throws SAXException { + String[] decl = new String[2]; + decl[0] = name; + decl[1] = value; + _intEntityDeclarations.add (decl); + } + + /** + * Adds external entity declarations to the entity declarations + * list in the form of a String[3], with element 0 being the + * name, element 1 the public ID, and 2 the system ID. + */ + public void externalEntityDecl(String name, String publicID, String systemID) + throws SAXException { + String[] decl = new String[3]; + decl[0] = name; + decl[1] = publicID; + decl[2] = systemID; + _extEntityDeclarations.add (decl); + } + + /** Report an attribute type declaration. + * Does nothing. + * @see org.xml.sax.ext.DeclHandler#attributeDecl(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String) + */ + public void attributeDecl( + String arg0, + String arg1, + String arg2, + String arg3, + String arg4) + throws SAXException + { + + } + + + /** + * Returns list of entity declarations. Each list + * is an array String[2], giving the name and + * value respectively. + */ + public List getInternalEntityDeclarations () + { + return _intEntityDeclarations; + } + + + /** + * Returns list of entity declarations. Each list + * is an array String[3], giving the name, + * public ID, and system ID respectively. + */ + public List getExternalEntityDeclarations () + { + return _extEntityDeclarations; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/xml/XmlDeclStream.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/xml/XmlDeclStream.java new file mode 100644 index 00000000..a49e810e --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/xml/XmlDeclStream.java @@ -0,0 +1,351 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003-4 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.xml; + +import java.io.*; +import java.util.*; + +/** + * This class is layered under the InputSource for the XmlModule + * so that it can detect an XML declaration and character references, + * which aren't reported by other API's. + * + * This is called XmlDeclStream for historical reasons, though it's + * no longer limited to that function. + * + * @author Gary McGath + */ +public class XmlDeclStream extends FilterInputStream { + private static final int CR = 0x0d; // '\r' + private static final int LF = 0x0a; // '\n' + + + private StringBuffer declBuf; + private StringBuffer refBuf; + private boolean seenChars; + + private String _version; + private String _encoding; + private String _standalone; + + /* List of Integers giving character references */ + private List _charRefs; + + /* To try to determine line ending */ + protected boolean _lineEndCR; + protected boolean _lineEndLF; + protected boolean _lineEndCRLF; + protected int _prevChar; + + public XmlDeclStream (InputStream strm) { + super (strm); + declBuf = null; + seenChars = false; + _charRefs = new LinkedList (); + + // No line end types have been discovered. + _lineEndCR = false; + _lineEndLF = false; + _lineEndCRLF = false; + _prevChar = 0; + } + + /** + * Reads the next byte of data from this input stream. + * Processes bytes as it reads them. + */ + public int read () throws IOException + { + int retval = super.read (); + if (retval > 0) { + process (retval); + } + return retval; + } + + + /** + * Reads up to byte.length bytes of data from this + * input stream into an array of bytes. + * Processes bytes as it reads them. + */ + public int read (byte[] b) throws IOException + { + int nbytes = super.read (b); + for (int i = 0; i < nbytes; i++) { + process ((int) b[i]); + } + return nbytes; + } + + + /** + * Reads up to len bytes of data from this + * input stream into an array of bytes. + * Processes bytes as it reads them. + */ + public int read (byte[] b, int off, int len) throws IOException + { + int nbytes = super.read (b, off, len); + for (int i = off; i < off + nbytes; i++) { + process ((int) b[i]); + } + + return nbytes; + } + + + /** + * Returns the character references as a List + * of Integers. No sorting or elimination of + * duplicates is done; this is just all the + * character references in the order they occurred. + */ + public List getCharacterReferences () + { + return _charRefs; + } + + + + /** Accessor functions. */ + + /** Returns the version string. May be null (though it shouldn't + * be in well-formed XML). */ + public String getVersion () + { + return _version; + } + + + /** Returns the encoding string. May be null. */ + public String getEncoding () + { + return _encoding; + } + + + /** Returns the standalone string. May be null. */ + public String getStandalone () + { + return _standalone; + } + + + /* Processes each byte which comes through, looking for an XML + * declaration. When it has a complete one, parses out the + * parameters and makes them available. + * + * The XML declaration must be the first thing in the file. + */ + private void process (int b) + { + /* Determine the line ending type(s). */ + checkLineEnd(b); + _prevChar = b; + + if (!seenChars || declBuf != null) { + if (declBuf == null && b == (int) '<') { + declBuf = new StringBuffer ("<"); + } + else if (declBuf != null) { + declBuf.append ((char) b); + if ((char) b == '>') { + processDecl (); + declBuf = null; + } + } + } + if (refBuf == null && b == (int) '&') { + refBuf = new StringBuffer ("&"); + } + else if (refBuf != null) { + if (refBuf.length() == 1 && b != (int) '#') { + // If & isn't followed by #, it's not a character + // reference. + refBuf = null; + } + else if (b == ';') { + processRef (); + refBuf = null; + } + else { + refBuf.append ((char) b); + } + } + seenChars = true; + } + + /* We have the first thing to be enclosed in angle + * brackets in declBuf. See if it's an XML declaration, + * and if so, extract the interesting information. + */ + private void processDecl () + { + String decl = declBuf.toString (); + if (!decl.startsWith ("")) { + declBuf = null; + } + else { + // get version, encoding, standalone + int off; + int off1 = 0; + off = decl.indexOf ("version"); + if (off > 0) { + _version = extractParam (decl, off); + off1 = off; + } + + // Use of off1 enforces order of attributes + off = decl.indexOf ("encoding", off1); + if (off > 0) { + _encoding = extractParam (decl, off); + off1 = off; + } + + off = decl.indexOf ("standalone", off1); + if (off > 0) { + _standalone = extractParam (decl, off); + } + } + } + + + /* We have a character reference -- or at least something + * that looks vaguely like one -- in refBuf. This includes + * the initial &# but not the final semicolon. + * + * According to the w3c documentation, the 'x' which indicates + * a hexadecimal value must be lower case, but the + * hexadecimal digits may be upper or lower case. + */ + private void processRef () + { + boolean isHex = (refBuf.charAt (2) == 'x'); + int val = 0; + // Copy refBuf to a local variable so we can make sure + // it gets nulled however we return. + StringBuffer refBuf1 = refBuf; + refBuf = null; + if (isHex) { + for (int i = 3; i < refBuf1.length (); i++) { + char ch = Character.toUpperCase (refBuf1.charAt (i)); + if (ch >= 'A' && ch <= 'F') { + val = 16 * val + ((int) ch - 'A' + 10); + } + else if (ch >= '0' && ch <= '9') { + val = 16 * val + ((int) ch - '0'); + } + else { + return; // invalid character in hex ref + } + } + } + else { + // better be decimal + for (int i = 2; i < refBuf1.length (); i++) { + char ch = refBuf1.charAt (i); + if (ch >= '0' && ch <= '9') { + val = 10 * val + ((int) ch - '0'); + } + else { + return; // invalid character in hex ref + } + } + } + _charRefs.add (new Integer (val)); + } + + + + /* extract a parameter (after an equal sign) + * from a string, after the offset off. */ + private String extractParam (String str, int off) + { + int equIdx = str.indexOf ('=', off); + if (equIdx == -1) { + return null; + } + // The parameter may be in single or double quotes, + boolean singleQuote = false; + boolean doubleQuote = false; + int startOff = -1; + for (int i = equIdx + 1; i < str.length(); i++) { + char ch = str.charAt (i); + if (Character.isWhitespace(ch)) { + if (startOff < 0) { + continue; + } + else if (!singleQuote && !doubleQuote) { + // white space, and not in quotes. + return str.substring(startOff, i + 1); + } + } + else if (ch == '\'' && !doubleQuote) { + if (!singleQuote) { + // Start of single-quoted string + singleQuote = true; + startOff = i + 1; + } + else { + // End of single-quoted string + return str.substring (startOff, i); + } + } + else if (ch == '"' && !singleQuote) { + if (!doubleQuote) { + // Start of double-quoted string + doubleQuote = true; + startOff = i + 1; + } + else { + // End of double-quoted string + return str.substring (startOff, i); + } + } + else if (startOff < 0) { + // Non-whitespace character, start of unquoted string + startOff = i; + } + } + return null; // fell off end without finding a valid string + } + /* Accumulate information about line endings. ch is the + current character, and _prevChar the one before it. */ + protected void checkLineEnd (int ch) + { + if (ch == LF) { + if (_prevChar == CR) { + _lineEndCRLF = true; + } + else { + _lineEndLF = true; + } + } + else if (_prevChar == CR) { + _lineEndCR = true; + } + } + + public String getKindOfLineEnd() { + if (_lineEndCR || _lineEndLF || _lineEndCRLF) { + if (_lineEndCRLF) { + return "CRLF"; + } + if (_lineEndCR) { + return "CR"; + } + if (_lineEndLF) { + return "LF"; + } + } + return null; + } + + +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/xml/XmlLexicalHandler.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/xml/XmlLexicalHandler.java new file mode 100644 index 00000000..0722b18e --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/xml/XmlLexicalHandler.java @@ -0,0 +1,123 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.xml; + +import java.util.*; +import org.xml.sax.SAXException; +import org.xml.sax.ext.LexicalHandler; + +/** + * + * This implementation of LexicalHandler takes care of + * comments, DTD's, entities and other stuff for XmlModule. + * The caller has to make sure the LexicalHandler property + * is supported by the SAX implementation, and set that + * property to this class. + * + * @author Gary McGath + * + */ +public class XmlLexicalHandler implements LexicalHandler { + + private List _comments; + private Set _entityNames; + public XmlLexicalHandler () + { + _comments = new LinkedList (); + _entityNames = new HashSet (); + } + + + + /** + * Report the end of a CDATA section. + * Does nothing. + * @see org.xml.sax.ext.LexicalHandler#endCDATA() + */ + public void endCDATA() throws SAXException { + // no action necessary + } + + /** + * Report the end of DTD declarations. + * Does nothing. + * @see org.xml.sax.ext.LexicalHandler#endDTD() + */ + public void endDTD() throws SAXException { + + } + + /** + * Report the start of a CDATA section. + * Does nothing. + * @see org.xml.sax.ext.LexicalHandler#startCDATA() + */ + public void startCDATA() throws SAXException { + // no action necessary + } + + /** + * Gathers comments into the comments list. + * + * @see org.xml.sax.ext.LexicalHandler#comment(char[], int, int) + */ + public void comment(char[] text, int start, int length) throws SAXException { + _comments.add (String.copyValueOf (text, start, length)); + } + + /** + * Accumulates entity names into the entity set. This will be + * used for determining which entities are actually used. + * + * @see org.xml.sax.ext.LexicalHandler#startEntity(java.lang.String) + */ + public void startEntity(String name) throws SAXException + { + _entityNames.add (name); + } + + /** + * Report the end of an entity. + * Does nothing. + * + * @see org.xml.sax.ext.LexicalHandler#endEntity(java.lang.String) + */ + public void endEntity(String name) throws SAXException + { + // No action necessary + } + + + /** + * Report the start of DTD declarations, if any. + * Does nothing. + * @see org.xml.sax.ext.LexicalHandler#startDTD(java.lang.String, java.lang.String, java.lang.String) + */ + public void startDTD(String arg0, String arg1, String arg2) + throws SAXException + { + + } + + + /** + * Returns the value of the comments list, which is + * a List of Strings. + */ + public List getComments () + { + return _comments; + } + + + /** + * Returns the Set of entity names. + */ + public Set getEntityNames () + { + return _entityNames; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/xml/XmlModuleHandler.java b/jhove/classes/edu/harvard/hul/ois/jhove/module/xml/XmlModuleHandler.java new file mode 100644 index 00000000..2fe5caaa --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/xml/XmlModuleHandler.java @@ -0,0 +1,549 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004-2007 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.module.xml; + +//import java.io.*; +import edu.harvard.hul.ois.jhove.*; +import edu.harvard.hul.ois.jhove.module.html.HtmlMetadata; +import edu.harvard.hul.ois.jhove.module.html.DTDMapper; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.*; +import org.xml.sax.helpers.DefaultHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.Attributes; + +/** + * + * This handler does the parsing work of the XML module. + * + * @author Gary McGath + * + */ +public class XmlModuleHandler extends DefaultHandler { + + + /* List of entities String[2], { public ID, system ID} */ + private List _entities; + + /* Map of namespaces, prefix (String) to URI (String) */ + private Map _namespaces; + + /* List of processing instructions. Each element + * is an array of two strings, giving the target + * and data respectively. */ + private List _processingInsts; + + /* List of generated Messages. */ + private List _messages; + + /* Validity flag. */ + private boolean _valid; + + /* Qualified name of the root element. */ + private String _root; + + /* URI for DTD specificiation */ + private String _dtdURI; + + /* List of schema URI's. Each element is a String[2], + * consisting of the namespace URI and the schema location. */ + private List _schemas; + + /* List of unparsed entities. Each is an array String[4]; + * name, public ID, system ID and notation name + * respectively. */ + private List _unparsedEntities; + + /* Error counter. */ + private int _nErrors; + + /* Notations list. Each is an array String[3]: + * name, public ID, and system ID. */ + private List _notations; + + /* List of all the attributes. This is used to + * check on the use of unparsed entities. */ + private Set _attributeVals; + + /* Limit on number of errors to report. */ + private static final int MAXERRORS = 2000; + + /* XHTML flag, only for XHTML documents referred + * by the HTML module. */ + private boolean _xhtmlFlag; + + /* HTMLMetadata object; used only with XHTML documents. */ + private HtmlMetadata _htmlMetadata; + + /* Flag set if we've seen any components. This is an indirect + * way of checking if the "signature" (the XML declaration) + * has been seen. */ + private boolean _sigFlag; + + /* Map from URIs to local schema files */ + private Map _localSchemas; + + + /** + * Constructor. + */ + public XmlModuleHandler () + { + _xhtmlFlag = false; + _htmlMetadata = null; + _entities = new LinkedList (); + _namespaces = new HashMap (); + _processingInsts = new LinkedList (); + _messages = new LinkedList (); + _attributeVals = new HashSet (); + _dtdURI = null; + _root = null; + _valid = true; + _nErrors = 0; + _schemas = new LinkedList (); + _unparsedEntities = new LinkedList (); + _notations = new LinkedList (); + _sigFlag = false; + } + + + /** + * Sets the value of the XHTML flag. Special properties + * are extracted if this is an XHTML document. */ + public void setXhtmlFlag (boolean flag) + { + _xhtmlFlag = flag; + } + + /** + * Sets a map of schema URIs to local files. This information + * comes from jhove.conf parameters. + */ + public void setLocalSchemas (Map schemas) { + _localSchemas = schemas; + } + + /** + * Returns the HTML metadata object. Will be non-null only + * for a document recognized as XHTML. + */ + public HtmlMetadata getHtmlMetadata () + { + return _htmlMetadata; + } + + /** + * Looks for the first element encountered. Stores + * its name as the value to be returned by getRoot, + * qualified name by preference, local name if the + * qualified name isn't available. + */ + public void startElement (String namespaceURI, + String localName, + String qualifiedName, + Attributes atts) throws SAXException + { + // The first element we encounter is the root. + // Save it. + if (_root == null) { + _sigFlag = true; + if (!"".equals(qualifiedName)) { + _root = qualifiedName; + } + else { + _root = localName; + } + } + if (namespaceURI != null) { + SchemaInfo schi = new SchemaInfo(); + schi.namespaceURI = namespaceURI; + schi.location = ""; + if (!hasSchemaURI (schi)) { + _schemas.add(schi); + } + } + if (atts != null) { + int natts = atts.getLength (); + for (int i = 0; i < natts; i++) { + String name = atts.getLocalName (i); + String namespace = atts.getURI (i); // namespace URI + String val = atts.getValue (i); + if ("http://www.w3.org/2001/XMLSchema-instance".equals + (namespace)) { + SchemaInfo schInfo = new SchemaInfo(); + if ("schemaLocation".equals (name)) { + /* val should consist of two tokens, giving the + * URI and the location respectively. + */ + String[] toks = val.split ("\\s", 2); + /* Could be a length 0 or 1 array in pathological + * cases, so convert it to a length-2 array. + * Note that while the schemaLocation attribute + * SHOULD have two white-space separated elements, + * this may not be the case, so always check the + * array length before referencing its elements. + */ + if (toks.length > 0 && toks[0] != null) { + schInfo.namespaceURI = toks[0].trim (); + } + else { + schInfo.namespaceURI = ""; + } + if (toks.length > 1 && toks[1] != null) { + schInfo.location = toks[1].trim (); + } + else { + schInfo.location = ""; + } + if (!hasSchemaURI (schInfo)) { + _schemas.add (schInfo); + } + } + if ("noNamespaceSchemaLocation".equals (name)) { + schInfo.location = "[None]"; + schInfo.namespaceURI = val; + if (!hasSchemaURI(schInfo)) { + _schemas.add (schInfo); + } + } + } + // Collect all attribute values. + _attributeVals.add (val); + } + } + if (_xhtmlFlag) { + if (_htmlMetadata == null) { + _htmlMetadata = new HtmlMetadata (); + } + XhtmlProcessing.processElement + (localName, qualifiedName, atts, _htmlMetadata); + } + } + + + /** The only action taken here is some bookkeeping in connection + * with the HTML metadata.*/ + public void endElement(String namespaceURI, String localName, String qName) + { + if (_htmlMetadata != null) { + _htmlMetadata.finishPropUnderConstruction (); + } + } + + /** Processes PCData characters. This does things only + * in connection with properties under construction in + * HTML metadata. + */ + public void characters(char[] ch, int start, int length) + { + if (_htmlMetadata != null && + _htmlMetadata.getPropUnderConstruction () != null) { + _htmlMetadata.addToPropUnderConstruction + (ch, start, length); + } + } + + /** + * Begin the scope of a prefix-URI Namespace mapping. + * Prefixes mappings are stored in _namespaces. + */ + public void startPrefixMapping(String prefix, + String uri) + throws SAXException + { + //THL we want the root namespace even if it declares no prefix !!! + //if (!"".equals (prefix)) { + _namespaces.put(prefix, uri); + //} + } + + + /** + * Handles a processing instruction. Adds it to + * the list that will be returned by getProcessingInstructions. + * Each element of the list is an array of two Strings. Element 0 of + * the array is the target, and element 1 is the data. + */ + public void processingInstruction(String target, + String data) + throws SAXException + { + _sigFlag = true; + if (data == null) { + data = ""; + } + ProcessingInstructionInfo pi = new ProcessingInstructionInfo(); + pi.target = target; + pi.data = data; + _processingInsts.add (pi); + } + + + /** + * Puts all notations into the notation list. A list entry + * is a String[3], consisting of name, public ID, and system + * ID. + */ + public void notationDecl (String name, String publicID, String systemID) + throws SAXException + { + String[] notArr = new String[3]; + notArr[0] = name; + notArr[1] = publicID; + notArr[2] = systemID; + _notations.add (notArr); + } + + /** Overrides standard resolveEntity. First looks for DTD and + * entity files that are stored as resources, and uses those + * if available. (Faster and more reliable than grabbing them + * over the Net.) If that fails, calls the superclass's resolveEntity. + * Regardless, it then looks for anything + * that appears to be a DTD and puts it in the DTD URI field. + * If the superclass's attempt to resolve the entity results in + * an IOException, we just ignore it. + * + */ + public InputSource resolveEntity(String publicId, + String systemId) + throws SAXException + + { + // Check any custom mapping from the config + File fil = _localSchemas.get(systemId.toLowerCase()); + if (fil != null) { + try { + FileInputStream inStrm = new FileInputStream(fil); + return new InputSource (inStrm); + } + catch (FileNotFoundException e) {} + } + + // Do special-case checking for the XHTML DTD's + if (!_xhtmlFlag) { + if (DTDMapper.isXHTMLDTD (publicId)) { + _xhtmlFlag = true; + } + } + InputSource ent = DTDMapper.publicIDToFile(publicId); + if (ent == null) { + try { + ent = super.resolveEntity(publicId, systemId); + } + catch (SAXException ee) { + throw ee; + } + catch (Exception e) { + // Depending on the JDK version, super.resolveEntity + // may or may not be formally capable of throwing an IOException. + // This hack allows compatibility in either case. + throw new SAXException (e); + } + } + else { + // A little magic so SAX won't give up in advance on + // relative URI's. + ent.setSystemId ("http://hul.harvard.edu/hul"); + } + + // Report in entity properties + EntityInfo entArr = new EntityInfo(); + entArr.publicID = publicId; + entArr.systemID = systemId; + _entities.add (entArr); + if (systemId.endsWith (".dtd")) { + /* Assume that the first system ID in the file with a .dtd + * extension is the actual DTD + */ + if (_dtdURI == null) { + _dtdURI = systemId; + } + } + return ent; + } + + + /** + * Picks up unparsed entity declarations, after calling the + * superclass's unparsedEntityDecl, and puts their information + * into the unparsed entity declaration list as an array of + * four strings: [ name, publicId, systemId, notationName]. + * Null values are converted into empty strings. + */ + public void unparsedEntityDecl (String name, + String publicId, + String systemId, + String notationName) throws SAXException + { + super.unparsedEntityDecl (name, publicId, systemId, notationName); + String[] info = new String[4]; + info[0] = name == null ? "" : name; + info[1] = publicId == null ? "" : publicId; + info[2] = systemId == null ? "" : systemId; + info[3] = notationName == null ? "" : notationName; + _unparsedEntities.add (info); + } + + /** + * Processes a warning. We just add an InfoMessage. + */ + public void warning (SAXParseException e) + { + _messages.add (new InfoMessage (e.getMessage())); + } + + + + /** + * Processes a parsing exception. An ill-formed piece + * of XML will get a fatalError (I think), so we can assume + * that any error here indicates only invalidity. + */ + public void error(SAXParseException e) + { + _valid = false; + if (_nErrors == MAXERRORS) { + _messages.add (new InfoMessage + ("Error messages in excess of " + MAXERRORS + + " not reported")); + } + else if (_nErrors < MAXERRORS) { + int line = e.getLineNumber(); + int col = e.getColumnNumber(); + _messages.add (new ErrorMessage + (e.getMessage ().toString (), + "Line = " + line + + ", Column = " + col)); + } + ++_nErrors; + } + + + /** + * Returns the set of attribute values. + */ + public Set getAttributeValues () + { + return _attributeVals; + } + + + /** + * Returns the list of schemas. The elements of the list + * are Strings, giving the URI's for the schemas. + */ + public List getSchemas () + { + return _schemas; + } + + /** + * Returns the list of unparsed entities. The elements of the + * list are arrays of four Strings, giving the name, public + * ID, system ID and notation name respectively. + */ + public List getUnparsedEntities () + { + return _unparsedEntities; + } + + + /** + * Returns the map of prefixes to namespaces. The keys + * and values are Strings. + */ + public Map getNamespaces () + { + return _namespaces; + } + + + /** + * Returns the DTD URI. May be null. + */ + public String getDTDURI () + { + return _dtdURI; + } + + + + /** Returns the List of processing instructions. Each element + * is an array of two strings, giving the target + * and data respectively. + */ + public List getProcessingInstructions () + { + return _processingInsts; + } + + + /** + * Returns the list of notations. Each is an array String[3]: + * name, public ID, and system ID. + */ + public List getNotations () + { + return _notations; + } + + /** Returns the qualified name of the root element. */ + public String getRoot () + { + return _root; + } + + + + /** Returns the List of messages generated during the parse. */ + public List getMessages () + { + return _messages; + } + + + /** Returns the validity state. If error + * has been called, the return value will be false. + */ + public boolean isValid () + { + return _valid; + } + + /** Returns true if we have seen an element or a + * processing instruction, which implies that we've seen an + * XML declaration. + */ + public boolean getSigFlag () + { + return _sigFlag; + } + + /* Check if we already know about this schema URI. If we do but the new info provides + * a location, quietly stuff the old one into a sewer and pretend it was + * never there. */ + public boolean hasSchemaURI(SchemaInfo newinfo) { + Iterator schmiter = _schemas.iterator(); + while (schmiter.hasNext()) { + SchemaInfo schmi = schmiter.next(); + if (newinfo.namespaceURI.equals (schmi.namespaceURI)) { + if (schmi.location.isEmpty() && !newinfo.location.isEmpty()) { + _schemas.remove(schmi); + return false; // we like the new info better + } + return true; + } + } + return false; + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/xml/build.xml b/jhove/classes/edu/harvard/hul/ois/jhove/module/xml/build.xml new file mode 100644 index 00000000..91d8cbcd --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/xml/build.xml @@ -0,0 +1,32 @@ + + XML module build file + Jhove - JSTOR/Harvard Object Validation Environment + Version 1.0 2004-02-03 + Copyright 2004 by JSTOR and the President and Fellows of Harvard College + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/module/xml/package.html b/jhove/classes/edu/harvard/hul/ois/jhove/module/xml/package.html new file mode 100644 index 00000000..1648e705 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/module/xml/package.html @@ -0,0 +1,24 @@ + + + + + + + +Contains supporting classes for the XML-HUL module. +
+ + + + + +
+ + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/package.html b/jhove/classes/edu/harvard/hul/ois/jhove/package.html new file mode 100644 index 00000000..a0b8f57c --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/package.html @@ -0,0 +1,32 @@ + + + + + + + +Contains the classes needed for building a JHOVE application. +This package must be used with a top-level class, with one or more +output handlers or viewers, and with one or more modules for specific +file formats. + + + +

Related Documentation

+ +For overviews, tutorials, examples, guides, and tool documentation, please see: + + + + + + diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/viewer/AppInfoWindow.java b/jhove/classes/edu/harvard/hul/ois/jhove/viewer/AppInfoWindow.java new file mode 100644 index 00000000..c86414f2 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/viewer/AppInfoWindow.java @@ -0,0 +1,127 @@ +/********************************************************************** + * JhoveView - JSTOR/Harvard Object Validation Environment + * Copyright 2003-2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.viewer; + +import java.awt.*; +import java.awt.event.ActionListener; +import java.awt.event.ActionEvent; +import java.util.*; +import javax.swing.*; +import java.io.*; +import edu.harvard.hul.ois.jhove.*; + +/** + * This window is for presenting information about the JHOVE + * application. + */ +public class AppInfoWindow extends InfoWindow +{ + + private JTextArea texta; + + public AppInfoWindow (App app, JhoveBase jbase) + { + super ("Application Info", app, jbase); + setSaveActionListener ( + new ActionListener() { + public void actionPerformed (ActionEvent e) { + saveInfo (); + } + }); + + texta = new JTextArea (); + texta.setColumns (72); + JScrollPane scrollpane = new JScrollPane (texta); + texta.setFont (new Font ("sansserif", Font.PLAIN, 10)); + texta.setLineWrap (true); + texta.setWrapStyleWord (true); + // Getting Swing to accept what you want for dimensions + // apparently requires setting as many dimension restrictions + // as possible, and hoping it will pay attention to some + // of them. + scrollpane.setMinimumSize (new Dimension (240, 240)); + scrollpane.setMaximumSize (new Dimension (500, 250)); + scrollpane.setPreferredSize (new Dimension (500, 250)); + getContentPane ().add (scrollpane, "Center"); + + // Add a small panel at the bottom, since on some OS's there + // may be stuff near the bottom of a window which will conflict + // with the scroll bar. + JPanel panel = new JPanel (); + panel.setMinimumSize (new Dimension (8, 8)); + getContentPane ().add (panel, "South"); + + showApp (app, jbase); + pack (); + + // Scroll to the top. + texta.setEditable (false); + texta.select (0, 0); + Rectangle r = new Rectangle (0, 0, 1, 1); + texta.scrollRectToVisible (r); + } + + private void showApp (App app, JhoveBase jbase) + { + String appName = app.getName (); + if (appName != null) { + texta.append ("Name: " + appName + eol); + } + String rel = app.getRelease (); + if (rel != null) { + texta.append ("Release: " + rel); + } + Date dt = app.getDate (); + if (dt != null) { + texta.append (" " + _dateFmt.format (dt) + eol); + } + String configFile = jbase.getConfigFile (); + if (configFile != null) { + texta.append ("Configuration: " + configFile + eol); + } + String saxClass = jbase.getSaxClass (); + if (saxClass != null) { + texta.append ("SAX parser: " + saxClass + eol); + } + Iterator iter = jbase.getModuleMap ().keySet ().iterator (); + while (iter.hasNext ()) { + //Module module = jbase.getModuleMap ((String) iter.next ()); + Map moduleMap = jbase.getModuleMap (); + Module module = (Module) moduleMap.get ((String) iter.next ()); + texta.append (" Module: " + module.getName () + " " + + module.getRelease () + eol); + } + // Reporting Handlers makes no sense in the viewer app; skip + String rights = app.getRights (); + if (rights != null) { + texta.append (" Rights: " + rights + eol); + } + } + + /** + * Saves the information to a file + */ + private void saveInfo () + { + PrintWriter wtr = doSaveDialog (); + if (wtr == null) { + return; + } + OutputHandler handler = selectHandler (); + try { + handler.setWriter(wtr); + handler.show(_app); + wtr.close (); + } + catch (Exception e) { + JOptionPane.showMessageDialog + (this, + e.getMessage(), + "Error writing file", + JOptionPane.ERROR_MESSAGE); + } + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/viewer/ConfigWindow.java b/jhove/classes/edu/harvard/hul/ois/jhove/viewer/ConfigWindow.java new file mode 100644 index 00000000..6ea21931 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/viewer/ConfigWindow.java @@ -0,0 +1,649 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2004 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.viewer; + +import java.awt.Dimension; +import java.awt.Font; +import java.awt.GridLayout; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.event.*; +import java.util.*; +import java.io.*; +import java.text.ParseException; +import javax.swing.*; +import javax.swing.table.*; +//import javax.swing.border.*; +import edu.harvard.hul.ois.jhove.ConfigHandler; +import edu.harvard.hul.ois.jhove.ConfigWriter; +import edu.harvard.hul.ois.jhove.ModuleInfo; + +/** + * Window for high-level editing of the application's + * configuration file. + * + * @author Gary McGath + * + */ +public class ConfigWindow extends JDialog { + + private final static String tempDirDefault = ""; + + /* The location of the config file. */ + private File _configFile; + + /* List of modules. An entry in the list is an + * array of 2 Strings, which are the fully qualified + * class and the init string respectively. */ + private List _modules; + + /* List of handlers. This is just a list of Strings + * giving the class. */ + private List _handlers; + + private int _bufferSize; + + private File _homeDir; + private File _tempDir; + private String _encoding; + + /* Display components. */ + + private Box _mainBox; + + private JTable _modTable; + private JTable _hanTable; + private AbstractTableModel _modTableModel; + private AbstractTableModel _hanTableModel; + private JLabel _homeLabel; + private JLabel _tempDirLabel; + private NumericField _bufSizeBox; + private JTextField _encodingBox; + + final static Color _tableColor = new Color (235, 230, 210); + final static Font _pathFont = new Font ("SansSerif", Font.PLAIN, 10); + final static Font _infoFont = new Font ("SansSerif", Font.PLAIN, 12); + + /** + * Constructor. + * + * @param configFile The file which was opened for + * configuration information, or null + * to start with a clean slate. + * + * @param handler A ConfigHandler which has already + * processed the configuration file, + * or null if configFile is null. + */ + public ConfigWindow (JFrame parent, File configFile, ConfigHandler handler) + { + super (parent, "Jhove Configuration", true); + setDefaultCloseOperation (WindowConstants.DISPOSE_ON_CLOSE); + _configFile = configFile; + if (handler != null) { + _modules = handler.getModule (); + _handlers = handler.getHandler (); + _bufferSize = handler.getBufferSize (); + String dir = handler.getJhoveHome(); + if (dir != null) { + _homeDir = new File (dir); + } + dir = handler.getTempDir(); + if (dir != null) { + _tempDir = new File (dir); + } + _encoding = handler.getEncoding (); + } + else { + // Set up defaults + _modules = new ArrayList (10); + _handlers = new ArrayList (5); + _bufferSize = -1; + _homeDir = null; + _tempDir = null; + _encoding = null; + } + + // Set up a Box container for the window top level + _mainBox = Box.createVerticalBox (); + getContentPane().setLayout (new BorderLayout ()); + getContentPane().add (_mainBox, "Center"); + _mainBox.setBorder (BorderFactory.createLineBorder(Color.BLACK)); + + // Keep its size reasonable, taking screen size into account + java.awt.Rectangle screenRect = MainScreen.mainBounds (); + int maxHeight = screenRect.height - 200; + if (maxHeight > 640) { + maxHeight = 640; + } + _mainBox.setMaximumSize (new Dimension (500, maxHeight)); + _mainBox.setPreferredSize (new Dimension (400, maxHeight)); + + addModuleTable (); + _mainBox.add(Box.createRigidArea(new Dimension(0, 6))); + addHandlerTable (); + addHomeDir (); + addTempDir (); + addEncoding (); + addBufferSize (); + addSaveCancel (); + pack (); + } + + + + /* Create a JTable of two columns which lets the + * user add and delete modules, and add it to + * the window. + * Called by the constructor. + */ + private void addModuleTable () + { + JPanel panel = new JPanel (); + panel.setLayout (new BorderLayout ()); + _mainBox.add (panel); + // Use an anonymous class to implement the TableModel + _modTableModel = new AbstractTableModel () { + public int getRowCount () + { + return _modules.size (); + } + public int getColumnCount () + { + return 2; + } + public boolean isCellEditable(int row, int col) + { + return true; + } + public Object getValueAt (int row, int column) + { + ModuleInfo modInfo = _modules.get (row); + String[] tuple = { modInfo.clas, modInfo.init}; + return tuple[column]; + } + public void setValueAt (Object obj, int row, int column) + { + ModuleInfo modInfo = _modules.get(row); + if (column == 0 && obj instanceof String) { + modInfo.clas = (String) obj; + } + if (column == 1 && obj instanceof String) { + modInfo.init = (String) obj; + } + } + }; + + _modTable = new JTable (_modTableModel); +// _modTable.setMaximumSize(new Dimension (250, 150)); + _modTable.setSelectionMode (ListSelectionModel.SINGLE_SELECTION); + _modTable.setCellSelectionEnabled (true); + _modTable.setBackground (_tableColor); + JScrollPane modScrollPane = new JScrollPane (_modTable); + + TableColumnModel colMod = _modTable.getColumnModel(); + colMod.getColumn(0).setHeaderValue("Class"); + colMod.getColumn(1).setHeaderValue("Init"); + _modTable.doLayout (); + // Add a panel with the modules caption and a couple of buttons. + JPanel topPanel = new JPanel (); + topPanel.setLayout (new BorderLayout ()); + topPanel.add (new JLabel ("Modules:"), "West"); + + // Squeeze the buttons over to the right + JPanel rightPanel = new JPanel (); + topPanel.add (rightPanel, "East"); + JButton addButton = new JButton ("Add"); + addButton.addActionListener ( + new ActionListener () { + public void actionPerformed (ActionEvent e) + { + addModule (); + } + } + ); + rightPanel.add (addButton); + JButton delButton = new JButton ("Delete"); + delButton.addActionListener ( + new ActionListener () { + public void actionPerformed (ActionEvent e) + { + deleteModule (); + } + } + ); + + // Make both buttons the same size + addButton.setMinimumSize (delButton.getMinimumSize ()); + addButton.setPreferredSize (delButton.getPreferredSize ()); + + rightPanel.add (delButton); + panel.add (topPanel, "North"); + panel.add (modScrollPane, "Center"); + } + + + + /* Create a JTable of one column which lets the + * user add and delete handlers, and add it to + * the window. + * Called by the constructor. + */ + private void addHandlerTable () + { + JPanel panel = new JPanel (); + panel.setLayout (new BorderLayout ()); + _mainBox.add (panel); + // Use an anonymous class to implement the TableModel + _hanTableModel = new AbstractTableModel () { + public int getRowCount () + { + return _handlers.size (); + } + public int getColumnCount () + { + return 1; + } + public boolean isCellEditable(int row, int col) + { + return true; + } + public Object getValueAt (int row, int column) + { + String[] tuple = (String[]) _handlers.get (row); + if (tuple != null) { + return tuple[0]; + } + else { + return ""; + } + } + public void setValueAt (Object obj, int row, int column) + { + if (obj instanceof String) { + String[] stuff = new String[] { (String) obj }; + _handlers.set (row, stuff); + } + } + }; + + _hanTable = new JTable (_hanTableModel); + _hanTable.setSelectionMode (ListSelectionModel.SINGLE_SELECTION); + _hanTable.setCellSelectionEnabled (true); + _hanTable.setBackground (_tableColor); + JScrollPane hanScrollPane = new JScrollPane (_hanTable); + + TableColumnModel colMod = _hanTable.getColumnModel(); + colMod.getColumn(0).setHeaderValue("Class"); + _hanTable.doLayout (); + // Add a panel with the modules caption and a couple of buttons. + JPanel topPanel = new JPanel (); + topPanel.setLayout (new BorderLayout ()); + topPanel.add (new JLabel ("Handlers:"), "West"); + + // Squeeze the buttons over to the right + JPanel rightPanel = new JPanel (); + topPanel.add (rightPanel, "East"); + JButton addButton = new JButton ("Add"); + addButton.addActionListener ( + new ActionListener () { + public void actionPerformed (ActionEvent e) + { + addHandler (); + } + } + ); + rightPanel.add (addButton); + JButton delButton = new JButton ("Delete"); + delButton.addActionListener ( + new ActionListener () { + public void actionPerformed (ActionEvent e) + { + deleteHandler (); + } + } + ); + + // Make both buttons the same size + addButton.setMinimumSize (delButton.getMinimumSize ()); + addButton.setPreferredSize (delButton.getPreferredSize ()); + + rightPanel.add (delButton); + panel.add (topPanel, "North"); + panel.add (hanScrollPane, "Center"); + } + + + + + /* Add a label and text edit box for the encoding */ + private void addEncoding () + { + JPanel panel = new JPanel (); + _mainBox.add (panel); + panel.add (new JLabel ("Default encoding: ")); + _encodingBox = new JTextField (_encoding == null ? "" : _encoding, 14); + panel.add (_encodingBox); + } + + + + /* Add a label and text edit box for the buffer size */ + private void addBufferSize () + { + JPanel panel = new JPanel (); + _mainBox.add (panel); + panel.add (new JLabel ("Buffer size (-1 for default): ")); + _bufSizeBox = new NumericField (_bufferSize); + panel.add (_bufSizeBox); + } + + /* Add a button and file path string for home directory. + * A home directory is required, but there may not be one + * initially. + * Called by the constructor. */ + private void addHomeDir () + { + JPanel panel = new JPanel (); + _mainBox.add (panel); + panel.setLayout (new GridLayout (1, 2)); + JButton homeButton = new JButton ("Home directory..."); + String homeText = ""; + if (_homeDir != null) { + homeText = _homeDir.getPath(); + } + _homeLabel = new JLabel(homeText); + _homeLabel.setFont (_pathFont); + // Standard trick for not making button take up all available space + JPanel bpan = new JPanel (); + //panel.add (bpan); + panel.add (bpan); + bpan.add (homeButton); + homeButton.addActionListener ( + new ActionListener () { + public void actionPerformed (ActionEvent e) + { + chooseHomeDir (); + } + } + ); + panel.add (_homeLabel); + // Both of these probably need to be class variables + } + + + /* Add a button and file path string for temporary directory. + * Called by the constructor. */ + private void addTempDir () + { + JPanel panel = new JPanel (); + _mainBox.add (panel); + panel.setLayout (new GridLayout (1, 2)); + JButton tempDirButton = new JButton ("Temp directory..."); + String tempDirText = tempDirDefault; + if (_tempDir != null) { + tempDirText = _tempDir.getPath(); + } + _tempDirLabel = new JLabel(tempDirText); + _tempDirLabel.setFont (_pathFont); + // Standard trick for not making button take up all available space + JPanel bpan = new JPanel (); + panel.add (bpan); + bpan.add (tempDirButton); + tempDirButton.addActionListener ( + new ActionListener () { + public void actionPerformed (ActionEvent e) + { + chooseTempDir (); + } + } + ); + panel.add (_tempDirLabel); + } + + + /* Add OK and cancel buttons, and a little information */ + private void addSaveCancel () + { + final ConfigWindow thiscw = this; + JPanel buttonPanel = new JPanel (); + getContentPane().add (buttonPanel, "South"); + buttonPanel.setLayout (new BorderLayout ()); + JLabel changesLabel = new JLabel + ("Changes take effect on relaunch"); + changesLabel.setFont (_infoFont); + buttonPanel.add (changesLabel, "North"); + + JPanel panel = new JPanel (); + buttonPanel.add (panel, "Center"); + panel.setLayout (new GridLayout (1, 3)); + + // Blank panel for positioning + JPanel bpan = new JPanel (); + panel.add (bpan); + + // Add OK button + bpan = new JPanel (); + JButton saveButton = new JButton ("OK"); + getRootPane().setDefaultButton (saveButton); + saveButton.addActionListener ( + new ActionListener () { + public void actionPerformed (ActionEvent e) + { + doSave (); + } + } + ); + bpan.add (saveButton); + panel.add (bpan); + + // Add cancel button + bpan = new JPanel (); + JButton cancelButton = new JButton ("Cancel"); + cancelButton.addActionListener ( + new ActionListener () { + public void actionPerformed (ActionEvent e) + { + thiscw.dispose (); + } + } + ); + + // Make both buttons the same size + saveButton.setMinimumSize (cancelButton.getMinimumSize ()); + saveButton.setPreferredSize (cancelButton.getPreferredSize ()); + bpan.add (cancelButton); + panel.add (bpan); + + } + + + /* Carry out the action of the "Save" button. */ + private void doSave () + { + // Splitting off the output to a ConfigFileWriter class + // makes sense. + try { + // Update values from the editing components + try { + _bufSizeBox.commitEdit (); + } + catch (ParseException e) { + return; // refuse to save if value is invalid + } + //Object bufSizeValue = _bufSizeBox.getValue (); + _bufferSize = ((Long) _bufSizeBox.getValue ()).intValue (); + + _encoding = _encodingBox.getText (); + + // *** Writes to temp file for debugging + //ConfigWriter cw = new ConfigWriter (new File (_homeDir, "temp")); + ConfigWriter cw = new ConfigWriter (_configFile, this); + cw.writeFile (_modules, _handlers, + _homeDir, _tempDir, _encoding, _bufferSize); + } + catch (IOException e) { + JOptionPane.showMessageDialog(this, + e.getMessage (), + "Can't create config", + JOptionPane.ERROR_MESSAGE); + } + + // Close the window + dispose (); + } + + /* Choose the home directory. Since there must be one, + * there is no "default" button. */ + private void chooseHomeDir () + { + JFileChooser chooser = new JFileChooser (); + if (_homeDir != null) { + chooser.setCurrentDirectory (_homeDir); + } + chooser.setDialogTitle ("Select Home Directory"); + chooser.setFileSelectionMode (JFileChooser.DIRECTORIES_ONLY); + if (chooser.showOpenDialog (this) == JFileChooser.APPROVE_OPTION) { + _homeDir = chooser.getSelectedFile(); + _homeLabel.setText (_homeDir.getPath ()); + } + } + + + /* Choose the temp directory. Allow a "default" selection. */ + private void chooseTempDir () + { + final JFileChooser chooser = new JFileChooser (); + if (_homeDir != null) { + chooser.setCurrentDirectory (_tempDir); + } + chooser.setDialogTitle ("Select Temporary Directory"); + // Create a custom panel so we can add the Default button. + JPanel accessory = new JPanel (); + accessory.setPreferredSize(new Dimension (160, 40)); + JButton defaultButton = new JButton ("System Default"); + defaultButton.addActionListener ( + new ActionListener () { + public void actionPerformed (ActionEvent e) + { + // Exit the dialog and clear _tempDir + _tempDir = null; + _tempDirLabel.setText (tempDirDefault); + chooser.cancelSelection (); + } + } + ); + accessory.add (defaultButton); + chooser.setAccessory (accessory); + + chooser.setFileSelectionMode (JFileChooser.DIRECTORIES_ONLY); + if (chooser.showOpenDialog (this) == JFileChooser.APPROVE_OPTION) { + _tempDir = chooser.getSelectedFile(); + _tempDirLabel.setText (_tempDir.getPath ()); + } + } + + /* Add a blank item for a new module */ + private void addModule () + { + ListSelectionModel ls = _modTable.getSelectionModel (); + int selRow = ls.getMinSelectionIndex(); + // If there's no selection, append to the end + if (selRow < 0) { + selRow = _modules.size (); + } +// String[] tuple = new String[2]; +// tuple[0] = ""; // class +// tuple[1] = null; // init + ModuleInfo modInfo = new ModuleInfo ("", null); + _modules.add (selRow, modInfo); + _modTableModel.fireTableRowsInserted(selRow, selRow); + } + + + + /* Delete the selected module line */ + private void deleteModule () + { + ListSelectionModel ls = _modTable.getSelectionModel (); + int selRow = ls.getMinSelectionIndex(); + if (selRow < 0) { + return; // no selection + } + _modules.remove (selRow); + _modTableModel.fireTableRowsDeleted (selRow, selRow); + } + + + /* Add a blank item for a new handler */ + private void addHandler () + { + ListSelectionModel ls = _hanTable.getSelectionModel (); + int selRow = ls.getMinSelectionIndex(); + // If there's no selection, append to the end + if (selRow < 0) { + selRow = _handlers.size (); + } + String[] tuple = { "" }; + _handlers.add (selRow, tuple); + _hanTableModel.fireTableRowsInserted(selRow, selRow); + } + + + + /* Delete the selected handler line */ + private void deleteHandler () + { + ListSelectionModel ls = _hanTable.getSelectionModel (); + int selRow = ls.getMinSelectionIndex(); + if (selRow < 0) { + return; // no selection + } + _handlers.remove (selRow); + _hanTableModel.fireTableRowsDeleted (selRow, selRow); + } + + + + /* When the user chooses to close and save, or just save, + * we come here. + * + * @param configFile The location to save the file, or none + * if a simple "save" was selected. If + * configFile is non-null, it becomes the + * new default location. + */ +// private void saveConfig (File configFile) +// { +// if (configFile != null) { +// _configFile = configFile; +// } +// +// // _configFile may be null. In that case we must put up +// // a dialog to select the config file here, and exit the function +// // if the user cancels. +// try { +// FileOutputStream ostrm = new FileOutputStream (configFile); +// } +// catch (IOException e) { +// JOptionPane.showMessageDialog (this, +// e.getMessage(), "Error saving config", +// JOptionPane.ERROR_MESSAGE); +// +// } +// } + +// private File doConfigFileDialog () +// { +// JFileChooser chooser = new JFileChooser (); +// chooser.setFileSelectionMode (JFileChooser.DIRECTORIES_ONLY); +// if (chooser.showOpenDialog (this) == JFileChooser.APPROVE_OPTION) { +// return chooser.getSelectedFile(); +// } +// +// return null; // placeholder +// } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/viewer/InfoWindow.java b/jhove/classes/edu/harvard/hul/ois/jhove/viewer/InfoWindow.java new file mode 100644 index 00000000..ac8603f1 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/viewer/InfoWindow.java @@ -0,0 +1,264 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.viewer; + +import java.awt.HeadlessException; + +import java.util.*; +import java.io.*; +import java.awt.Dimension; +import java.awt.Toolkit; +import java.awt.event.KeyEvent; +import java.awt.event.ActionListener; +import java.awt.event.ActionEvent; +import java.text.SimpleDateFormat; +import javax.swing.*; +import edu.harvard.hul.ois.jhove.*; + +/** + * An abstract superclass for windows that display information + * and can be saved to a file. + * + * @author Gary McGath + */ +public abstract class InfoWindow extends JFrame +{ + protected App _app; + private JhoveBase _base; + private JMenuItem _saveItem; + private JMenuItem _closeItem; + private JComboBox _handlerBox; + private JComboBox _encodingBox; + private static String _lastEncoding; + private static String _lastHandler; + + protected static final String eol = System.getProperty("line.separator"); + private static final String[] encodings = + { "UTF-8", "ISO-8859-1", "Cp1252", "MacRoman"}; + protected SimpleDateFormat _dateFmt; + + + /** + * + * Constructor. + * The window is created with a File menu that has + * "Save as" and "Close" items. + * + * @param title Window title. Will be truncated to 32 characters. + * @param app The associated App object. + * @param base The associated JhoveBase object. + * + * @throws java.awt.HeadlessException + */ + public InfoWindow(String title, App app, JhoveBase base) + throws HeadlessException { + super(title); + + // Avoid silly window titles from excessively long URI's + if (title.length () > 32) { + setTitle(title.substring(0,29) + "..."); + } + _app = app; + _base = base; + JMenuBar menuBar = new JMenuBar (); + JMenu fileMenu = new JMenu ("File"); + menuBar.add (fileMenu); + _saveItem = new JMenuItem ("Save as..."); + fileMenu.add (_saveItem); + + _closeItem = new JMenuItem ("Close"); + fileMenu.add (_closeItem); + // Make mnemonic control-W, command-W, or whatever-W + _closeItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, + Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); + _closeItem.addActionListener (new ActionListener() { + public void actionPerformed (ActionEvent e) { + closeFromMenu (); + } + }); + + setDefaultCloseOperation (JFrame.HIDE_ON_CLOSE); + setJMenuBar (menuBar); + _dateFmt = new SimpleDateFormat ("yyyy-MM-dd"); + } + + /** Sets the ActionListener for the "Save as" menu item. + * Subclasses need to call this with an appropriate + * ActionListener in order to make the menu item + * functional. + */ + protected void setSaveActionListener (ActionListener listener) + { + _saveItem.addActionListener (listener); + } + + /** Puts up a dialog to save the file. + * If the user requests a file, deletes any old file + * with the same name, creates the new file, and + * returns a PrintWriter to the file. + * The save dialog is customized with two + * JComboBoxes. One lets the user + * select a character encoding, which is used by + * the PrintWriter; the other lets the user + * choose an OutputHandler to control the output + * format (e.g., text or HTML). + * The encodings shown in a JComboBox are + * UTF-8, ISO-8859-1, Cp1252, MacRoman, and + * the default encoding for the locale (if different + * from the above), but the user can type in other + * encodings. If an unknown encoding is selected, + * an error dialog will be displayed. + * OutputHandlers other than the ones known + * to the application can't be specified (what would + * the application do with them?) + */ + protected PrintWriter doSaveDialog () + { + JFileChooser saver = new JFileChooser (); + // On Mac OS, make packages and .apps opaque. + JhoveWindow.makeChooserOpaque (saver); + + File lastDir = _base.getSaveDirectory (); + if (lastDir != null) { + saver.setCurrentDirectory(lastDir); + } + + // Create a custom panel so we can set options. + JPanel accessory = new JPanel (); + accessory.setPreferredSize(new Dimension (180, 120)); + + // Build list of handlers into a popup menu + Vector handlerItems = new Vector (10); + java.util.List handlerList = _base.getHandlerList (); + Iterator iter = handlerList.iterator (); + while (iter.hasNext ()) { + OutputHandler han = (OutputHandler) iter.next (); + handlerItems.add (han.getName ()); + } + _handlerBox = new JComboBox(handlerItems); + _handlerBox.setSize (120, 20); + accessory.add (new JLabel ("Choose output handler")); + if (_lastHandler != null) { + _handlerBox.setSelectedItem (_lastHandler); + } + accessory.add (_handlerBox); + + // Build a list of encodings into a popup menu. + // The default encoding must be the first. + Vector encItems = new Vector (5); + String defEnc = _base.getEncoding (); + if (defEnc != null) { + encItems.add (defEnc); + } + for (int i = 0; i < encodings.length; i++) { + String enc = encodings[i]; + if (!enc.equals (defEnc)) { + encItems.add (enc); + } + } + _encodingBox = new JComboBox (encItems); + if (_lastEncoding != null) { + _encodingBox.setSelectedItem (_lastEncoding); + } + _encodingBox.setSize (120, 20); + _encodingBox.setEditable (true); // Let user type in any encoding + accessory.add (new JLabel ("Select encoding")); + accessory.add (_encodingBox); + + saver.setAccessory(accessory); + saver.setDialogTitle("Save information to file"); + int retval = saver.showSaveDialog (this); + if (retval == JFileChooser.APPROVE_OPTION) { + FileOutputStream os = null; + File file = null; + try { + _base.setSaveDirectory (saver.getCurrentDirectory ()); + file = saver.getSelectedFile(); + if (file.exists ()) { + int opt = JOptionPane.showConfirmDialog + (this, "That file already exists. Replace?", + "Replace", + JOptionPane.OK_CANCEL_OPTION); + if (opt != JOptionPane.OK_OPTION) { + return null; + } + // User requested replacement. Delete the old file. + if (!file.delete ()) { + JOptionPane.showMessageDialog(this, + "Could not delete file", + "File not deleted", + JOptionPane.ERROR_MESSAGE); + return null; + } + } + file.createNewFile(); + String encoding = (String) _encodingBox.getSelectedItem(); + os = new FileOutputStream (file); + OutputStreamWriter writer = new OutputStreamWriter (os, encoding); + return new PrintWriter (writer); + } + catch (UnsupportedEncodingException e) { + JOptionPane.showMessageDialog(this, + "Unknown encoding ", + "File not saved", + JOptionPane.ERROR_MESSAGE); + // Get rid of the file + try { + if (os != null) { + os.close (); + } + file.delete (); + } + catch (Exception f) {} + + return null; + } + catch (IOException e) { + JOptionPane.showMessageDialog(this, + e.getMessage (), + "File not saved", + JOptionPane.ERROR_MESSAGE); + // Get rid of the file + try { + if (os != null) { + os.close (); + } + file.delete (); + } + catch (Exception f) {} + return null; + } + } + return null; + } + + /** Sets up the OutputHandler from the JComboBox + * and returns it. Subclasses should call + * selectHandler to obtain an + * OutputHandler with which to produce data. + */ + protected OutputHandler selectHandler () + { + int modidx = _handlerBox.getSelectedIndex (); + _lastHandler = (String) _handlerBox.getSelectedItem (); + OutputHandler handler = + (OutputHandler) _base.getHandlerMap().get (_lastHandler.toLowerCase ()); + _lastEncoding = (String) _encodingBox.getSelectedItem (); + handler.setEncoding (_lastEncoding); + handler.setApp (_app); + handler.setBase (_base); + return handler; + } + + /** + * Handler for the "Close" menu item. + * Simply hides the window without deleting it. + */ + protected void closeFromMenu () + { + setVisible (false); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/viewer/JhoveWindow.java b/jhove/classes/edu/harvard/hul/ois/jhove/viewer/JhoveWindow.java new file mode 100644 index 00000000..ece0a66c --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/viewer/JhoveWindow.java @@ -0,0 +1,927 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003-2005 by JSTOR and the President and Fellows of Harvard College + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.viewer; + +import java.awt.Container; +import java.awt.Color; +//import java.awt.Cursor; +import java.awt.Toolkit; +import java.awt.event.*; +import java.awt.dnd.*; +import java.awt.datatransfer.*; +import java.io.*; +import java.util.*; +import java.net.*; +import java.util.logging.*; +import javax.swing.*; +import edu.harvard.hul.ois.jhove.*; +import javax.xml.parsers.*; +import org.xml.sax.*; +import org.xml.sax.helpers.*; + +/** + * Main window of JHoveViewer application. + */ +public class JhoveWindow extends JFrame + implements Callback, DropTargetListener { + + private App _app; + private JhoveBase _base; + private AppInfoWindow _appInfoWin; + private ModuleInfoWindow _moduleInfoWin; + private JMenu _moduleSubmenu; + private JMenuItem _openFileItem; + private JMenuItem _openURLItem; + private JMenuItem _closeAllItem; + private ButtonGroup _moduleGroup; + private String syncStr = ""; // object just for synchronizing + private boolean _rawOutput; + private boolean _doChecksum; + + private ProgressWindow _progWind; + private ConfigWindow _configWind; + private PrefsWindow _prefsWindow; + + private File _lastDir; + private String _selectedModule; + private ActionListener _moduleMenuListener; + private JPanel logo; + private ViewHandler _viewHandler; + + // Initial position for view windows. + // Stagger them by adding an increment each time. + private static int viewWinXPos = 24; + private static int viewWinYPos = 24; + // Original positions for cyclying back to. + private static final int viewWinOrigXPos = 24; + private static final int viewWinOrigYPos = 24; + private static final int viewWinXInc = 25; + private static final int viewWinYInc = 22; + private static final int appInfoWinXPos = 50; + private static final int appInfoWinYPos = 45; + private static final int moduleInfoWinXPos = 100; + private static final int moduleInfoWinYPos = 90; + + /** Logger for a module class. */ + protected Logger _logger; + + /* Static instance of InvisibleFilenameFilter */ + private final InvisibleFilenameFilter invisibleFilter = + new JhoveWindow.InvisibleFilenameFilter (); + + public JhoveWindow (App app, JhoveBase base) + { + super ("Jhove"); + _logger = Logger.getLogger ("edu.harvard.hul.ois.jhove.viewer"); + _app = app; + _base = base; + _moduleMenuListener = new ActionListener () { + public void actionPerformed (ActionEvent e) { + _selectedModule = e.getActionCommand (); + } + }; + + _lastDir = null; + _moduleGroup = new ButtonGroup (); + addMenus (); + Container rootPane = getContentPane (); + //rootPane.setLayout (new GridLayout (4, 2)); + setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE); + + // Define a Comparator function for Modules + Comparator modListComparator = new Comparator () { + public int compare (Object o1, Object o2) { + Module m1 = (Module) o1; + Module m2 = (Module) o2; + String name1 = m1.getName (); + String name2 = m2.getName (); + return String.CASE_INSENSITIVE_ORDER.compare (name1, name2); + } + }; + + // Build combo box of available modules + Vector moduleItems = new Vector (10); + java.util.List moduleList = base.getModuleList (); + // Clone the list so we can display it in sorted order + // without munging the app's preferred order + java.util.List menuModuleList = new ArrayList (moduleList.size ()); + menuModuleList.addAll(moduleList); + Collections.sort (menuModuleList, modListComparator); + Iterator iter = menuModuleList.iterator (); + moduleItems.add ("(None selected)"); + JRadioButtonMenuItem modItem = null; + String itemName = null; + + while (iter.hasNext ()) { + Module mod = (Module) iter.next (); + itemName = mod.getName (); + modItem = new JRadioButtonMenuItem (itemName); + modItem.setActionCommand (itemName); + modItem.addActionListener (_moduleMenuListener); + _moduleSubmenu.add (modItem); + _moduleGroup.add (modItem); + //moduleItems.add (mod.getName ()); + } + + logo = new JPanel (); + + // Add the image, which should be in jhove-logo.gif in + // the viewer directory + URL logoURL = JhoveWindow.class.getResource("jhove-logo.gif"); + if (logoURL != null) { + ImageIcon icn = new ImageIcon (logoURL); + icn.setDescription ("Jhove logo"); + setNormalBackground (); + JLabel logoLabel = new JLabel (icn); + logo.add (logoLabel); + } + + // Allow files to be dragged to the logo pane. + DropTarget dt = new DropTarget (logo, this); + + rootPane.add (logo); + pack (); + + + // Set up a companion progress window. This will + // be hidden and displayed as needed. + ActionListener listener = new ActionListener () { + public void actionPerformed (ActionEvent e) { + _base.abort (); + } + }; + _progWind = new ProgressWindow (listener); + + // Set up a Handler which is tailored to this application. + _viewHandler = new ViewHandler(this, _app, _base); + } + + + /** + * Set up the menu bar and menus. + */ + final private void addMenus () + { + final JhoveWindow jthis = this; + JMenuBar menuBar = new JMenuBar (); + JMenu fileMenu = new JMenu ("File"); + menuBar.add (fileMenu); + _openFileItem = new JMenuItem ("Open file..."); + fileMenu.add (_openFileItem); + // The following allows accelerator modifier keys to be set which are + // appropriate to the host OS + _openFileItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, + Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); + + + _openFileItem.addActionListener ( + new ActionListener () { + public void actionPerformed (ActionEvent e) + { + pickAndAnalyzeFile (); + } + } + ); + _openURLItem = new JMenuItem ("Open URL..."); + fileMenu.add (_openURLItem); + _openURLItem.addActionListener ( + new ActionListener () { + public void actionPerformed (ActionEvent e) + { + pickAndAnalyzeURL (); + } + } + ); + + _closeAllItem = new JMenuItem ("Close all document windows"); + fileMenu.add (_closeAllItem); + // Action listeners are added by document windows + + if (!MacStuff.isMacintosh ()) { + // Token attempt at Mac friendliness: the Exit item + // in the File menu is redundant under OS X + JMenuItem quitItem = new JMenuItem ("Exit"); + fileMenu.add (quitItem); + quitItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, + Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); + quitItem.addActionListener ( + new ActionListener () { + public void actionPerformed (ActionEvent e) + { + System.exit (0); + } + } + ); + } + + JMenu editMenu = new JMenu ("Edit"); + menuBar.add (editMenu); + _moduleSubmenu = new JMenu ("Select module"); + editMenu.add (_moduleSubmenu); + JRadioButtonMenuItem noModuleItem = + new JRadioButtonMenuItem ("(Any)"); + noModuleItem.setActionCommand (""); + noModuleItem.setSelected (true); + noModuleItem.addActionListener (_moduleMenuListener); + _moduleSubmenu.add (noModuleItem); + _moduleGroup.add (noModuleItem); + _selectedModule = ""; + // Modules will be added later + + JMenuItem editConfigItem = new JMenuItem ("Edit configuration..."); + editMenu.add (editConfigItem); + editConfigItem.addActionListener ( + new ActionListener () { + public void actionPerformed (ActionEvent e) + { + openConfigWindow (); + } + } + ); + + JMenuItem prefItem = new JMenuItem ("Preferences..."); + editMenu.add (prefItem); + prefItem.addActionListener ( + new ActionListener () { + public void actionPerformed (ActionEvent e) + { + if (_prefsWindow == null) { + _prefsWindow = new PrefsWindow (jthis); + _prefsWindow.setLocation (180, 160); + _prefsWindow.pack (); + } + _prefsWindow.saveAndShow (); + } + } + ); + + JMenu helpMenu = new JMenu ("Help"); + menuBar.add (helpMenu); + JMenuItem aboutModuleItem = new JMenuItem ("About module..."); + helpMenu.add (aboutModuleItem); + aboutModuleItem.addActionListener ( + new ActionListener () { + public void actionPerformed (ActionEvent e) + { + showModuleInfo (); + } + } + ); + JMenuItem aboutAppItem = new JMenuItem ("About Jhove..."); + helpMenu.add (aboutAppItem); + aboutAppItem.addActionListener ( + new ActionListener () { + public void actionPerformed (ActionEvent e) + { + showAppInfo (); + } + } + ); + + setJMenuBar (menuBar); + } + + + /** Set the normal background color. */ + private void setNormalBackground () + { + logo.setBackground (new Color (180, 255, 255)); + } + + /** Set the background color for drag-over. */ + private void setDragBackground () + { + logo.setBackground (new Color (180, 240, 140)); + } + + /** + * Here we let the user pick a file, then analyze it. + */ + public void pickAndAnalyzeFile () + { + // Only one thread can be associated with a JhoveBase. + // Make sure we can't have concurrent threads. + _openFileItem.setEnabled (false); + _openURLItem.setEnabled (false); + File file = null; + synchronized (syncStr) { + JFileChooser chooser = new JFileChooser (); + makeChooserOpaque (chooser); + if (_lastDir != null) { + chooser.setCurrentDirectory (_lastDir); + } + chooser.setDialogTitle ("Pick a file to analyze"); + int ok = chooser.showOpenDialog (this); + if (ok == JFileChooser.APPROVE_OPTION) { + file = chooser.getSelectedFile (); + _lastDir = chooser.getCurrentDirectory (); + ParseThread thr = new ParseThread (this); + thr.setFile (file); + thr.setModule (getSelectedModule ()); + thr.start (); + _base.setCurrentThread (thr); + } + else { + _openFileItem.setEnabled (true); + _openURLItem.setEnabled (true); + return; + } + } + } + + /** + * Makes a JFileChooser dialog treat packages and applications + * as opaque entities. Has no effect on other platforms. + */ + public static void makeChooserOpaque (JFileChooser chooser) + { + // Apple TN 2042 LIES; we need to set both properties. + chooser.putClientProperty + ("JFileChooser.appBundleIsTraversable", "never"); + chooser.putClientProperty + ("JFileChooser.packageIsTraversable", "never"); + } + + + /** + * This method does the actual work of pickAndAnalyzeFile, + * called from a thread so it can run asynchronously. + */ + public void pickAndAnalyzeFile1 (File file, Module module) + { + String name = file.getName (); + _base.resetAbort (); + _progWind.setDocName (name, false); + _progWind.setProgressState (ProgressWindow.PROCESSING, false); + _progWind.setByteCount (-1, true); + _progWind.show (); + +// RepInfo info = new RepInfo (name); + try { + List files = new ArrayList(1); + files.add (file); + openAndParse (files, module); + } + catch (ThreadDeath d) { + _openFileItem.setEnabled (true); + _openURLItem.setEnabled (true); + throw d; + } + _openFileItem.setEnabled (true); + _openURLItem.setEnabled (true); + } + + /** This is called to analyze a List of files. */ + public void pickAndAnalyzeFileList1 (List files, Module module) + { + if (files.isEmpty ()) { + return; + } + // Set up progress window for the first file + File file = (File) files.get (0); + String name = file.getName (); + _base.resetAbort (); + _progWind.setDocName (name, false); + _progWind.setProgressState (ProgressWindow.PROCESSING, false); + _progWind.setByteCount (-1, true); + _progWind.show (); + + try { + openAndParse (files, module); + } + catch (ThreadDeath d) { + _openFileItem.setEnabled (true); + _openURLItem.setEnabled (true); + throw d; + } + _openFileItem.setEnabled (true); + _openURLItem.setEnabled (true); + } + + /** + * This method opens a directory, recursing through multiple + * levels if possible, and feeding individual files to + * pickAndAnalyzeFile1. + */ + public void analyzeDirectory (File file, Module module) + { + // Construct list, excluding files that start with "." + String[] subfiles = file.list (invisibleFilter); + if (subfiles != null) { + // Walk through the directory + for (int i = 0; i < subfiles.length; i++) { + File subfile = new File (file, subfiles[i]); + if (subfile != null) { + if (subfile.isDirectory ()) { + // Recurse through subdirectories + analyzeDirectory (subfile, module); + } + else { + pickAndAnalyzeFile1 (subfile, module); + } + } + } + } + } + + + + /* Here we let the user pick a URL, then analyze it. */ + public void pickAndAnalyzeURL () + { + // There are multithreading issues which haven't been resolved. + // Rather than do a serious rewrite of the code, it's sufficient + // to make sure there can't be more than one file being processed + // at a time. + _openFileItem.setEnabled (false); + _openURLItem.setEnabled (false); + + String uri = null; + synchronized (syncStr) { + String urlStr = (String)JOptionPane.showInputDialog( + this, + "Choose a URL to analyze", + "Select URL", + JOptionPane.PLAIN_MESSAGE, + null, + null, + "http://"); + + if (urlStr == null) { + _openFileItem.setEnabled (true); + _openURLItem.setEnabled (true); + return; // user cancelled + } + uri = urlStr.trim (); + } + ParseThread thr = new ParseThread (this); + thr.setURI (uri); + thr.setModule (getSelectedModule ()); + thr.start (); + } + + + /** + * This method does the actual work of pickAndAnalyzeURL, + * called from a thread so it can run asynchronously. + */ + public void pickAndAnalyzeURL1 (String uri, Module module) + { + _progWind.setDocName (uri.toString (), false); + _progWind.setProgressState (ProgressWindow.DOWNLOADING, false); + _progWind.setContentLength (0, false); + _progWind.setByteCount (0, true); + _progWind.show (); + try { + _base.dispatch (_app, + module, + null, // AboutHandler + _viewHandler, + null, // output file + new String[] {uri}); + } + catch (Exception e) { + reportError ("Error processing URL", e.getMessage ()); + } + _progWind.hide (); + _openFileItem.setEnabled (true); + _openURLItem.setEnabled (true); + } + + + /** + * Implementation of Callback.callback. + * + * @param selector 1 signifies update of byte count. + * 2 signifies change of URI. + * Other values result in no action. + * @param parm If selector = 1, + * must be a Long that evaluates to the number of + * bytes processed to date. + * If selector = 2, must be a String naming + * the object being processed. Will be truncated + * at the left if longer than 64 characters. + */ + public int callback (int selector, Object parm) + { + switch (selector) { + case 1: + long bytecnt = ((Long)parm).longValue (); + _progWind.setByteCount (bytecnt, true); + break; + case 2: + String name = (String) parm; + if (name.length() > 48) { + name = "..." + + name.substring (name.length() - 48, name.length()); + } + _progWind.setDocName(name, true); + break; + default: + break; + } + return 0; + } + + + /** + * Sets the raw output flag. If set to true, raw + * numeric values are displayed; if false, explanatory + * text may be substituted. + */ + public void setRawOutput (boolean rawOutput) + { + _rawOutput = rawOutput; + } + + /** + * Sets the checksum flag. If set to true, checksums are reported. + */ + public void setDoChecksum (boolean checksum) + { + _doChecksum = checksum; + } + + private void openAndParse (List files, /* RepInfo info,*/ Module module) + { + InputStream stream = null; + long lastModified = 0; + + // Turn a list of files into an array of strings. + String[] paths = new String[files.size()]; + Iterator iter = files.iterator (); + for (int i = 0; iter.hasNext (); i++) { + File fil = (File) iter.next (); + paths[i] = fil.getAbsolutePath (); + if (!fil.exists ()) { + _progWind.hide (); + return; // shouldn't happen -- we just picked it! + } + if (!fil.canRead ()) { + _progWind.hide (); + reportError ("File not readable", fil.getName ()); + return; + } + } + + _base.setShowRawFlag (_rawOutput); + _base.setChecksumFlag (_doChecksum); + + /* With the new defaults for the PDF module being + maximum information, it no longer makes sense + to set maximum verbosity, since that would make + all parameter settings ineffective. In fact, + verbosity may be a kludge now that the config + file handles parameters. */ +// if (module != null) { +// module.setVerbosity (Module.MAXIMUM_VERBOSITY); +// // A problem (which I think we've always had): +// // If no particular module is specified, we don't +// // set its verbosity as we should. +// } + /****************************************************** + * Parse formatted object. + ******************************************************/ + + try { + _base.dispatch (_app, + module, + null, // AboutHandler + _viewHandler, + null, // output file + paths); + } + catch (Exception e) { + // Do SOMETHING useful here. + _logger.warning(e.toString ()); + } + + _progWind.hide (); + } + + /* Open a configuration dialog */ + private void openConfigWindow () + { + String configFile = _base.getConfigFile (); + ConfigHandler configHandler = new ConfigHandler (); + XMLReader parser = null; + String saxClass = _base.getSaxClass (); + try { + if (saxClass != null) { + parser = XMLReaderFactory.createXMLReader (saxClass); + } + else { + SAXParserFactory factory = + SAXParserFactory.newInstance(); + factory.setNamespaceAware (true); + parser = factory.newSAXParser ().getXMLReader (); + } + // Need to do this carefully to keep all parsers happy + File config = new File (configFile); + String canonicalPath = config.getCanonicalPath (); + String fileURL = "file://"; + if (canonicalPath.charAt (0) != '/') { + fileURL += '/'; + } + fileURL += canonicalPath; + parser.setContentHandler (configHandler); + parser.setEntityResolver (configHandler); + parser.setFeature ("http://xml.org/sax/features/validation", + true); + parser.parse (fileURL); + } + catch (IOException e) { + reportError ("Config Error", "Cannot read configuration file"); + return; + } + catch (SAXException e) { + reportError ("Config Error", "SAX parser not found: " + saxClass); + return; + } + catch (ParserConfigurationException e) { + reportError ("Config Error", "ParserConfigurationException"); + } + ConfigWindow confWin = new ConfigWindow (this, + new File (configFile), + configHandler); + confWin.setLocation (120, 40); + confWin.setVisible (true); + } + + + private void showModuleInfo () + { + Module module = getSelectedModule (); + if (_moduleInfoWin == null) { + _moduleInfoWin = new ModuleInfoWindow (_app, _base, module); + _moduleInfoWin.setLocation (moduleInfoWinXPos, moduleInfoWinYPos); + } + else { + _moduleInfoWin.showModule (module); + } + _moduleInfoWin.setVisible (true); + } + + + private void showAppInfo () + { + if (_appInfoWin == null) { + _appInfoWin = new AppInfoWindow (_app, _base); + _appInfoWin.setLocation (appInfoWinXPos, appInfoWinYPos); + } + _appInfoWin.show (); + } + + + + private Module getSelectedModule () + { + if (_selectedModule.equals ("")) { + return null; + } + return (Module) _base.getModuleMap().get (_selectedModule.toLowerCase ()); + } + + private void reportError (String title, String msg) + { + synchronized (syncStr) { + JOptionPane.showMessageDialog (this, + msg, title, JOptionPane.ERROR_MESSAGE); + } + } + + + /* DropTargetListener methods. */ + + /** + * Invoked when the drag enters the component. + * Accepts the drag if it's a file which is being + * dragged, and changes the background color to give + * visual feedback. + */ + public void dragEnter (DropTargetDragEvent dtde) + { + DataFlavor[] flavors = dtde.getCurrentDataFlavors (); + if (dataFlavorOK (flavors)) { + setDragBackground (); + dtde.acceptDrag (dtde.getDropAction ()); + } + else { + dtde.rejectDrag (); + } + } + + + /** + * Invoked when the drag leaves the component. + * Restores the default background color. + */ + public void dragExit (DropTargetEvent dte) + { + setNormalBackground (); + } + + + /** + * Does nothing. + */ + public void dragOver (DropTargetDragEvent dtde) + { + } + + + /** + * Called when the thingy is dropped on the component. + * This causes the file to be opened. The default background color + * will be restored; theoretically this should already + * have happened, but Windows appears to require it be + * done here. + */ + public void drop (DropTargetDropEvent dtde) + { + DataFlavor[] flavors = dtde.getCurrentDataFlavors (); + if (dataFlavorOK (flavors)) { + dtde.acceptDrop (dtde.getDropAction ()); + + // Now get the file(s) and open it (them) + Transferable thingy = dtde.getTransferable (); + try { + List fileList = (List) thingy.getTransferData + (DataFlavor.javaFileListFlavor); + ParseThread thr = new ParseThread (this); + thr.setFileList (fileList); + thr.setModule (getSelectedModule ()); + thr.start (); + _base.setCurrentThread (thr); + dtde.dropComplete (true); + } + catch (Exception e) { + // Really shouldn't happen + dtde.dropComplete (false); + return; + } + } + else { + dtde.rejectDrop (); + } + setNormalBackground (); + } + + + /** + * Called if the drop action changes during the drag + * (e.g., by changing the modifier keys). Does nothing, + * as we treat copy and move identically. + */ + public void dropActionChanged (DropTargetDragEvent dtde) + { + + } + + + /** Returns the "Close all document windows" menu item. + * This allows document windows to add themselves as + * listeners. + */ + protected JMenuItem getCloseAllItem () + { + return _closeAllItem; + } + + + /* Called to see if the DropTargetEvent's data flavor is OK */ + private boolean dataFlavorOK (DataFlavor[] flavors) + { + boolean haveFileFlavor = false; + for (int i = 0; i < flavors.length; i++) { + if (flavors[i].isFlavorJavaFileListType()) { + return true; + } + } + return false; + } + + + + /** + * A local class for creating threads. */ + class ParseThread extends Thread { + + private JhoveWindow _win; + private String _uri; + private File _file; + private List _fileList; + private Module _module; + + + /** Constructor. */ + protected ParseThread (JhoveWindow win) + { + _win = win; + } + + + /** The method invoked by running the thread. + * Analyzes the URI, file, or file list provided + * to this thread object. + */ + public void run () + { + _base.resetAbort(); + try { + if (_uri != null) { + _win.pickAndAnalyzeURL1 (_uri, _module); + } + else if (_file != null) { + if (_file.isDirectory ()) { + analyzeDirectory (_file, _module); + } + else { + _win.pickAndAnalyzeFile1 (_file, _module); + } + } + else if (_fileList != null) { + _win.pickAndAnalyzeFileList1 (_fileList, _module); + } + _base.setCurrentThread (null); + } + catch (ThreadDeath d) { + _progWind.hide (); + throw d; + } + } + + + + /** Designates a URI to parse. + * Only one of setURI, setFile, and setFileList should + * be called for a given thread. */ + protected void setURI (String uri) + { + _uri = uri; + } + + /** Designates a file to parse. + * Only one of setURI, setFile, and setFileList should + * be called for a given thread. */ + protected void setFile (File file) + { + _file = file; + } + + + /** Designates a list of files to parse sequentially. + * Only one of setURI, setFile, and setFileList should + * be called for a given thread. */ + protected void setFileList (List fileList) + { + _fileList = fileList; + } + + /** + * Set the module. This is called at the start of + * thread setup, in case the user changes the module + * selection while the thread's running. + */ + protected void setModule (Module module) + { + _module = module; + } + + + + } + + + /** + * Class to filter out filenames that start with a period. + * These are "invisible" file names, at least under Unix, + * and generally shouldn't be included when walking through + * a directory. + */ + protected class InvisibleFilenameFilter implements FilenameFilter + { + public boolean accept (File dir, String name) + { + return (!name.startsWith (".")); + } + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/viewer/MainScreen.java b/jhove/classes/edu/harvard/hul/ois/jhove/viewer/MainScreen.java new file mode 100644 index 00000000..bff7645b --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/viewer/MainScreen.java @@ -0,0 +1,75 @@ +/********************************************************************** + * Jhove - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + * + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.viewer; + +import java.awt.*; + +/** + * Static methods for positioning windows on the main screen. + * + * @author Gary McGath + * + */ +public class MainScreen { + + /** + * Private constructor to prevent instantiation + */ + private MainScreen () + { + + } + + + /** + * Center the window on the main screen. + */ + public static void centerWindow (Window win) + { + Rectangle devBounds = mainBounds (); + Rectangle winBounds = win.getBounds (); + int lmargin = (devBounds.width - winBounds.width) / 2; + int tmargin = (devBounds.height - winBounds.height) / 2; + // Don't go off the edge + if (lmargin < 0) { + lmargin = 0; + } + if (tmargin < 0) { + tmargin = 0; + } + win.setLocation (lmargin, tmargin); + } + + + /** + * Center the window at the top of the main screen. + */ + public static void centerTopWindow (Window win) + { + Rectangle devBounds = mainBounds (); + Rectangle winBounds = win.getBounds (); + int lmargin = (devBounds.width - winBounds.width) / 2; + // Don't go off the edge + if (lmargin < 0) { + lmargin = 0; + } + win.setLocation (lmargin, 0); + } + + + /** + * Returns the bounds of the main monitor device. + */ + public static Rectangle mainBounds () + { + GraphicsEnvironment ge = GraphicsEnvironment. + getLocalGraphicsEnvironment(); + GraphicsDevice dev = ge.getDefaultScreenDevice(); + GraphicsConfiguration conf = dev.getDefaultConfiguration (); + return conf.getBounds (); + } +} diff --git a/jhove/classes/edu/harvard/hul/ois/jhove/viewer/ModuleInfoWindow.java b/jhove/classes/edu/harvard/hul/ois/jhove/viewer/ModuleInfoWindow.java new file mode 100644 index 00000000..0dadb7d7 --- /dev/null +++ b/jhove/classes/edu/harvard/hul/ois/jhove/viewer/ModuleInfoWindow.java @@ -0,0 +1,318 @@ +/********************************************************************** + * JhoveView - JSTOR/Harvard Object Validation Environment + * Copyright 2003 by JSTOR and the President and Fellows of Harvard College + **********************************************************************/ + +package edu.harvard.hul.ois.jhove.viewer; + +import java.awt.Font; +import java.awt.Dimension; +import java.awt.Rectangle; +import java.io.*; +import java.util.*; +import javax.swing.*; +import java.awt.event.ActionListener; +import java.awt.event.ActionEvent; +import edu.harvard.hul.ois.jhove.*; + +/** + * This window is for presenting information about the selected + * module. If no module is selected, a brief message is + * put into the window. + */ +public class ModuleInfoWindow extends InfoWindow{ + + private JTextArea texta; + private int _level; + private Module _module; + + /** + * Constructor. + * + * @param app The associated App object. + * @param base The associated JhoveBase object. + * @param module The Module whose information is to be presented. + */ + public ModuleInfoWindow (App app, JhoveBase base, Module module) + { + super ("Module Info", app, base); + _module = module; + setSaveActionListener ( + new ActionListener() { + public void actionPerformed (ActionEvent e) { + saveInfo (); + } + }); + + texta = new JTextArea (); + texta.setColumns (72); + JScrollPane scrollpane = new JScrollPane (texta); + texta.setFont (new Font ("sansserif", Font.PLAIN, 10)); + texta.setLineWrap (true); + texta.setWrapStyleWord (true); + // Getting Swing to accept what you want for dimensions + // apparently requires setting as many dimension restrictions + // as possible, and hoping it will pay attention to some + // of them. + scrollpane.setMinimumSize (new Dimension (240, 240)); + scrollpane.setMaximumSize (new Dimension (600, 500)); + scrollpane.setPreferredSize (new Dimension (600, 500)); + getContentPane ().add (scrollpane, "Center"); + + // Add a small panel at the bottom, since on some OS's there + // may be stuff near the bottom of a window which will conflict + // with the scroll bar. + JPanel panel = new JPanel (); + panel.setMinimumSize (new Dimension (8, 8)); + getContentPane ().add (panel, "South"); + + showModule (module); + pack (); + + } + + /** Formats and presents the module information in + * the window. */ + public void showModule (Module module) + { + _module = module; + if (module == null) { + texta.setText ("(No module selected)"); + } + else { + _level = 0; + texta.setText (""); + String margin = getIndent (++_level); + texta.append (margin + "Module: " + module.getName () + eol); + texta.append (margin + "Release: " + module.getRelease () + eol); + texta.append (margin + "Date: " + _dateFmt.format (module.getDate ()) + eol); + String [] ss = module.getFormat (); + if (ss.length > 0) { + texta.append (margin + "Format: " + ss[0]); + for (int i = 1; i < ss.length; i++) { + texta.append (", " + ss[i]); + } + texta.append (eol); + } + + String s = module.getCoverage (); + if (s != null) { + texta.append (margin + "Coverage: " + s + eol); + } + ss = module.getMimeType (); + if (ss.length > 0) { + texta.append (margin + "MIMEtype: " + ss[0]); + for (int i=1; i