From 0290b7ee1a6c37ae183ff34b0913e61468b540d8 Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Tue, 18 Apr 2023 16:03:41 +0100 Subject: [PATCH 01/26] FIX: Revert XML SAXParseException reporting - reverted to exception details in the main message with Line and Column numbers in the sub-message; - added dedicated method for reporting SAXParseExceptions to `MessageConstants`; and - fixed integration test results for XML SAXParseException reporting. --- jhove-bbt/scripts/create-1.28-target.sh | 6 ++++++ .../module/jpeg}/ErrorMessages_de.properties | 0 .../module/jpeg}/ErrorMessages_nl.properties | 0 .../module/jpeg}/ErrorMessages_sv.properties | 0 .../hul/ois/jhove/module/XmlModule.java | 8 +------- .../jhove/module/xml/MessageConstants.java | 15 ++++++++++++++ .../jhove/module/xml/XmlModuleHandler.java | 20 ++++++++----------- .../jhove/module/xml/ErrorMessages.properties | 4 ++-- .../module/xml/ErrorMessages_da.properties | 1 + .../module/xml/ErrorMessages_en.properties | 4 ++-- .../module/xml/ErrorMessages_fr.properties | 1 + .../module/xml/ErrorMessages_pt_BR.properties | 1 + 12 files changed, 37 insertions(+), 23 deletions(-) rename jhove-modules/{xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml => jpeg-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/jpeg}/ErrorMessages_de.properties (100%) rename jhove-modules/{xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml => jpeg-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/jpeg}/ErrorMessages_nl.properties (100%) rename jhove-modules/{xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml => jpeg-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/jpeg}/ErrorMessages_sv.properties (100%) diff --git a/jhove-bbt/scripts/create-1.28-target.sh b/jhove-bbt/scripts/create-1.28-target.sh index 86789e286..becf2c8c8 100755 --- a/jhove-bbt/scripts/create-1.28-target.sh +++ b/jhove-bbt/scripts/create-1.28-target.sh @@ -243,3 +243,9 @@ fi # Patch release details of the reporting module. find "${targetRoot}" -type f -name "*.jhove.xml" -exec sed -i 's/jhove\/1.8\/jhove.xsd/jhove\/1.9\/jhove.xsd/' {} \; find "${targetRoot}" -type f -name "audit.jhove.xml" -exec sed -i 's/outputHandler release="1.9"/outputHandler release="1.10"/' {} \; + +# Patch XML reporting tweak differences +find "${targetRoot}" -type f -name "valid-external.dtd.jhove.xml" -exec sed -i 's/The markup in the document preceding the root element must be well-formed. Line = 1/Line = 1/' {} \; +find "${targetRoot}" -type f -name "valid-external.dtd.jhove.xml" -exec sed -i 's/SAXParseException/SAXParseException: The markup in the document preceding the root element must be well-formed./' {} \; +find "${targetRoot}" -type f -name "*parsed-entity.ent.jhove.xml" -exec sed -i 's/Content is not allowed in prolog. Line = 1/Line = 1/' {} \; +find "${targetRoot}" -type f -name "*parsed-entity.ent.jhove.xml" -exec sed -i 's/SAXParseException/SAXParseException: Content is not allowed in prolog./' {} \; diff --git a/jhove-modules/xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml/ErrorMessages_de.properties b/jhove-modules/jpeg-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/jpeg/ErrorMessages_de.properties similarity index 100% rename from jhove-modules/xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml/ErrorMessages_de.properties rename to jhove-modules/jpeg-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/jpeg/ErrorMessages_de.properties diff --git a/jhove-modules/xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml/ErrorMessages_nl.properties b/jhove-modules/jpeg-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/jpeg/ErrorMessages_nl.properties similarity index 100% rename from jhove-modules/xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml/ErrorMessages_nl.properties rename to jhove-modules/jpeg-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/jpeg/ErrorMessages_nl.properties diff --git a/jhove-modules/xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml/ErrorMessages_sv.properties b/jhove-modules/jpeg-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/jpeg/ErrorMessages_sv.properties similarity index 100% rename from jhove-modules/xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml/ErrorMessages_sv.properties rename to jhove-modules/jpeg-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/jpeg/ErrorMessages_sv.properties diff --git a/jhove-modules/xml-hul/src/main/java/edu/harvard/hul/ois/jhove/module/XmlModule.java b/jhove-modules/xml-hul/src/main/java/edu/harvard/hul/ois/jhove/module/XmlModule.java index 6c067232a..87d84d6ae 100644 --- a/jhove-modules/xml-hul/src/main/java/edu/harvard/hul/ois/jhove/module/XmlModule.java +++ b/jhove-modules/xml-hul/src/main/java/edu/harvard/hul/ois/jhove/module/XmlModule.java @@ -396,13 +396,7 @@ public int parse(InputStream stream, RepInfo info, int parseIndex) { if (handler.getSigFlag() && !_parseFromSig) { info.setSigMatch(_name); } - info.setMessage(new ErrorMessage( - MessageConstants.XML_HUL_1, - MessageFormat.format( - MessageConstants.XML_HUL_1_SUB.getMessage(), - spe.getMessage(), - spe.getLineNumber(), - spe.getColumnNumber()))); + info.setMessage(new ErrorMessage(MessageConstants.INSTANCE.makeSaxParseMessage(spe))); info.setWellFormed(false); return 0; } catch (SAXException se) { diff --git a/jhove-modules/xml-hul/src/main/java/edu/harvard/hul/ois/jhove/module/xml/MessageConstants.java b/jhove-modules/xml-hul/src/main/java/edu/harvard/hul/ois/jhove/module/xml/MessageConstants.java index 07fc5baec..ee14bb2e5 100644 --- a/jhove-modules/xml-hul/src/main/java/edu/harvard/hul/ois/jhove/module/xml/MessageConstants.java +++ b/jhove-modules/xml-hul/src/main/java/edu/harvard/hul/ois/jhove/module/xml/MessageConstants.java @@ -1,5 +1,9 @@ package edu.harvard.hul.ois.jhove.module.xml; +import java.text.MessageFormat; + +import org.xml.sax.SAXParseException; + import edu.harvard.hul.ois.jhove.messages.JhoveMessage; import edu.harvard.hul.ois.jhove.messages.JhoveMessageFactory; import edu.harvard.hul.ois.jhove.messages.JhoveMessages; @@ -63,4 +67,15 @@ public enum MessageConstants { .getMessage("XML-HUL-12"); public static final JhoveMessage XML_HUL_13 = messageFactory .getMessage("XML-HUL-13"); + + public final JhoveMessage makeSaxParseMessage(final SAXParseException spe) { + return JhoveMessages.getMessageInstance( + MessageConstants.XML_HUL_1.getId(), + MessageFormat.format(MessageConstants.XML_HUL_1.getMessage(), + spe.getMessage()), + MessageFormat.format( + MessageConstants.XML_HUL_1_SUB.getMessage(), + spe.getLineNumber(), + spe.getColumnNumber())); + } } diff --git a/jhove-modules/xml-hul/src/main/java/edu/harvard/hul/ois/jhove/module/xml/XmlModuleHandler.java b/jhove-modules/xml-hul/src/main/java/edu/harvard/hul/ois/jhove/module/xml/XmlModuleHandler.java index 94435fc83..e8c19b68c 100644 --- a/jhove-modules/xml-hul/src/main/java/edu/harvard/hul/ois/jhove/module/xml/XmlModuleHandler.java +++ b/jhove-modules/xml-hul/src/main/java/edu/harvard/hul/ois/jhove/module/xml/XmlModuleHandler.java @@ -13,7 +13,12 @@ import java.net.URL; import java.net.URLConnection; import java.text.MessageFormat; -import java.util.*; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; import org.xml.sax.Attributes; import org.xml.sax.InputSource; @@ -392,9 +397,7 @@ public void unparsedEntityDecl(String name, String publicId, */ @Override public void warning(SAXParseException spe) { - _messages.add(new InfoMessage( - MessageConstants.XML_HUL_1, - spe.getMessage())); + _messages.add(new InfoMessage(MessageConstants.INSTANCE.makeSaxParseMessage(spe))); } /** @@ -413,14 +416,7 @@ public void error(SAXParseException spe) { MAXERRORS)); _messages.add(new InfoMessage(message)); } else if (_nErrors < MAXERRORS) { - _messages.add(new ErrorMessage( - MessageConstants.XML_HUL_1, - MessageFormat.format( - MessageConstants.XML_HUL_1_SUB.getMessage(), - spe.getMessage(), - spe.getLineNumber(), - spe.getColumnNumber() - ))); + _messages.add(new ErrorMessage(MessageConstants.INSTANCE.makeSaxParseMessage(spe))); } ++_nErrors; } diff --git a/jhove-modules/xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml/ErrorMessages.properties b/jhove-modules/xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml/ErrorMessages.properties index 66279c8d7..761943ccd 100644 --- a/jhove-modules/xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml/ErrorMessages.properties +++ b/jhove-modules/xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml/ErrorMessages.properties @@ -1,5 +1,5 @@ -XML-HUL-1 = SAXParseException -XML-HUL-1-SUB = {0} Line = {1,number,integer}, Column = {2,number,integer}. +XML-HUL-1 = SAXParseException: {0} +XML-HUL-1-SUB = Line = {0,number,integer}, Column = {1,number,integer}. XML-HUL-2 = Error messages in excess of {0,number,integer} not reported. XML-HUL-3 = SAXException: {0} XML-HUL-4 = Not able to determine end-of-line type. diff --git a/jhove-modules/xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml/ErrorMessages_da.properties b/jhove-modules/xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml/ErrorMessages_da.properties index 8b26bd61e..3c907a6f2 100644 --- a/jhove-modules/xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml/ErrorMessages_da.properties +++ b/jhove-modules/xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml/ErrorMessages_da.properties @@ -1,4 +1,5 @@ XML-HUL-1 = SaxParseException: {0} +XML-HUL-1-SUB = Line = {0,number,integer}, Column = {1,number,integer}. XML-HUL-2 = Fejlmeddelelser flere end {0} ikke rapporteret XML-HUL-3 = SaxParseException: {0} XML-HUL-4 = Ikke i stand til at bestemme typen af linjeslutning diff --git a/jhove-modules/xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml/ErrorMessages_en.properties b/jhove-modules/xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml/ErrorMessages_en.properties index 66279c8d7..761943ccd 100644 --- a/jhove-modules/xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml/ErrorMessages_en.properties +++ b/jhove-modules/xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml/ErrorMessages_en.properties @@ -1,5 +1,5 @@ -XML-HUL-1 = SAXParseException -XML-HUL-1-SUB = {0} Line = {1,number,integer}, Column = {2,number,integer}. +XML-HUL-1 = SAXParseException: {0} +XML-HUL-1-SUB = Line = {0,number,integer}, Column = {1,number,integer}. XML-HUL-2 = Error messages in excess of {0,number,integer} not reported. XML-HUL-3 = SAXException: {0} XML-HUL-4 = Not able to determine end-of-line type. diff --git a/jhove-modules/xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml/ErrorMessages_fr.properties b/jhove-modules/xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml/ErrorMessages_fr.properties index 9ef528bdc..70aafaf56 100644 --- a/jhove-modules/xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml/ErrorMessages_fr.properties +++ b/jhove-modules/xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml/ErrorMessages_fr.properties @@ -1,4 +1,5 @@ XML-HUL-1 = SaxParseException : {0} +XML-HUL-1-SUB = Line = {0,number,integer}, Column = {1,number,integer}. XML-HUL-2 = {0} messages d'erreur supplémentaires non signalés XML-HUL-3 = SaxParseException : {0} XML-HUL-4 = Impossible de déterminer le type de fin de ligne diff --git a/jhove-modules/xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml/ErrorMessages_pt_BR.properties b/jhove-modules/xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml/ErrorMessages_pt_BR.properties index 3844b4799..b25b8ecc8 100644 --- a/jhove-modules/xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml/ErrorMessages_pt_BR.properties +++ b/jhove-modules/xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml/ErrorMessages_pt_BR.properties @@ -1,4 +1,5 @@ XML-HUL-1 = SaxParseException: {0} +XML-HUL-1-SUB = Line = {0,number,integer}, Column = {1,number,integer}. XML-HUL-2 = As mensagens de erro acima de {0} não reportadas XML-HUL-3 = SaxParseException: {0} XML-HUL-4 = Não foi possível determinar o tipo de fim de linha From 4510a44238595adfb3e47f495842c1ea8a46c3b1 Mon Sep 17 00:00:00 2001 From: Thomas Ledoux Date: Fri, 8 Mar 2024 12:20:02 +0100 Subject: [PATCH 02/26] Checks the range of the orientation tag to be 1-9, and forces value 9 (unknown) in XML and JSON output to remind valid. Fixes #913 --- .../harvard/hul/ois/jhove/NisoImageMetadata.java | 4 ++-- .../hul/ois/jhove/handler/JsonHandler.java | 10 +++++----- .../harvard/hul/ois/jhove/handler/XmlHandler.java | 15 +++++++++++---- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/NisoImageMetadata.java b/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/NisoImageMetadata.java index bc2eb388b..113ac5b79 100644 --- a/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/NisoImageMetadata.java +++ b/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/NisoImageMetadata.java @@ -185,8 +185,8 @@ public class NisoImageMetadata /** 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" + "left top", "rotated cw 90 deg", "right bottom", "rotated ccw 90 deg", + "unknown" }; /** 6.1.6 planar configuration value labels. */ diff --git a/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/handler/JsonHandler.java b/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/handler/JsonHandler.java index eebd23059..497983268 100644 --- a/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/handler/JsonHandler.java +++ b/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/handler/JsonHandler.java @@ -1648,19 +1648,19 @@ protected JsonObjectBuilder showNisoImageCaptureMetadata( n = niso.getOrientation(); if (n != NisoImageMetadata.NULL) { + if (n > 9 || n < 1) { + n = 9; // force "unknown" for reserved value + } if (bMix10) { mixBuilder.add("mix:orientation", n); } else { - final String[] orient = { "unknown", "normal*", + final String[] orient = { "", "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 - } + "normal, rotated cw 90\u00B0", "unknown" }; mixBuilder.add("mix:orientation", orient[n]); } hasBuilder = true; diff --git a/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/handler/XmlHandler.java b/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/handler/XmlHandler.java index cd98de9fb..68b565e00 100644 --- a/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/handler/XmlHandler.java +++ b/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/handler/XmlHandler.java @@ -1311,6 +1311,9 @@ protected void showNisoBasicImageParameters02(NisoImageMetadata niso, } n = niso.getOrientation(); if (n != NisoImageMetadata.NULL) { + if (n > 9 || n < 1) { + n = 9; // force "unknown" for reserved value + } fileBuf.append(margn4 + element("mix:Orientation", Integer.toString(n)) + EOL); useFileBuf = true; @@ -2717,6 +2720,9 @@ protected void showNisoImageCaptureMetadata10(NisoImageMetadata niso, n = niso.getOrientation(); if (n != NisoImageMetadata.NULL) { + if (n > 9 || n < 1) { + n = 9; // force "unknown" for reserved value + } captureBuffer.append(margn3 + element("mix:orientation", Integer.toString(n)) + EOL); useCaptureBuffer = true; @@ -3780,15 +3786,16 @@ protected void showNisoImageCaptureMetadata20(NisoImageMetadata niso, n = niso.getOrientation(); if (n != NisoImageMetadata.NULL) { - final String[] orient = { "unknown", "normal*", + // Values defined in the MIX 2.0 schema + final String[] orient = { "", "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 + "normal, rotated cw 90\u00B0", "unknown" }; + if (n > 9 || n < 1) { + n = 9; // force "unknown" for reserved value } captureBuffer.append(margn3 + element("mix:orientation", orient[n]) + EOL); From a1bef6fee72b72de3ac074441a38924a8a5c1179 Mon Sep 17 00:00:00 2001 From: Thomas Ledoux Date: Fri, 8 Mar 2024 18:29:53 +0100 Subject: [PATCH 03/26] Display the Tile and Strip information for TIFF files outside Niso info. Fixes #848 --- .../hul/ois/jhove/module/tiff/TiffIFD.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/jhove-modules/tiff-hul/src/main/java/edu/harvard/hul/ois/jhove/module/tiff/TiffIFD.java b/jhove-modules/tiff-hul/src/main/java/edu/harvard/hul/ois/jhove/module/tiff/TiffIFD.java index 908364613..5bc48e037 100644 --- a/jhove-modules/tiff-hul/src/main/java/edu/harvard/hul/ois/jhove/module/tiff/TiffIFD.java +++ b/jhove-modules/tiff-hul/src/main/java/edu/harvard/hul/ois/jhove/module/tiff/TiffIFD.java @@ -1131,6 +1131,8 @@ public Property getProperty(boolean rawOutput) throws TiffException { // This function has gotten obscenely large. Split it up. addNisoProperties(entries, rawOutput); addMiscProperties(entries, rawOutput); + // Add Strip and Tile information from NISO since not displayed in MIX v1.0+ + addNonDisplayedNisoProperties(entries, rawOutput); addTiffITProperties(entries, rawOutput); addTiffEPProperties(entries, rawOutput); addGeoTiffProperties(entries, rawOutput); @@ -1340,6 +1342,58 @@ private void addMiscProperties(List entries, boolean rawOutput) { } } + // Add Strip and Tile information from NISO since not displayed in v1.0 and subsequent + private void addNonDisplayedNisoProperties(List entries, boolean rawOutput) { + if (_niso == null) return; + + int segmentType = _niso.getSegmentType(); + if (segmentType != NULL) { + entries.add(new Property("SegmentType", PropertyType.INTEGER, + segmentType)); + } + long[] stripOffsets = _niso.getStripOffsets(); + if (stripOffsets != null) { + entries.add(new Property("StripOffsets", PropertyType.LONG, + PropertyArity.ARRAY, stripOffsets)); + } + long rowsPerStrip = _niso.getRowsPerStrip(); + if (rowsPerStrip != NULL) { + entries.add(new Property("RowsPerStrip", PropertyType.LONG, + rowsPerStrip)); + } + long[] stripByteCounts = _niso.getStripByteCounts(); + if (stripByteCounts != null) { + entries.add(new Property("StripByteCounts", PropertyType.LONG, + PropertyArity.ARRAY, stripByteCounts)); + } + + long tileWidth = _niso.getTileWidth(); + if (tileWidth != NULL) { + entries.add(new Property("TileWidth", PropertyType.LONG, + tileWidth)); + } + long tileLength = _niso.getTileLength(); + if (tileLength != NULL) { + entries.add(new Property("TileLength", PropertyType.LONG, + tileLength)); + } + long[] tileOffsets = _niso.getTileOffsets(); + if (tileOffsets != null) { + entries.add(new Property("TileOffsets", PropertyType.LONG, + PropertyArity.ARRAY, tileOffsets)); + } + long[] tileByteCounts = _niso.getTileByteCounts(); + if (tileByteCounts != null) { + entries.add(new Property("TileByteCounts", PropertyType.LONG, + PropertyArity.ARRAY, tileByteCounts)); + } + int planarConfiguration = _niso.getPlanarConfiguration(); + if (planarConfiguration != NULL) { + entries.add(new Property("PlanarConfiguration", PropertyType.INTEGER, + planarConfiguration)); + } + } + private void addTiffITProperties(List entries, boolean rawOutput) { /* Add TIFF/IT properties. */ From c0fb4b8ccd083c4861b5796cf5b78c5d78f13bb4 Mon Sep 17 00:00:00 2001 From: Thomas Ledoux Date: Fri, 8 Mar 2024 19:15:51 +0100 Subject: [PATCH 04/26] Manage regression tests --- jhove-bbt/scripts/create-1.30-target.sh | 68 +++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/jhove-bbt/scripts/create-1.30-target.sh b/jhove-bbt/scripts/create-1.30-target.sh index 990f16d09..17ef226ac 100755 --- a/jhove-bbt/scripts/create-1.30-target.sh +++ b/jhove-bbt/scripts/create-1.30-target.sh @@ -114,6 +114,74 @@ if [[ -f "${candidateRoot}/regression/modules/PDF-hul/issue_306.pdf.jhove.xml" ] cp "${candidateRoot}/regression/modules/PDF-hul/issue_306.pdf.jhove.xml" "${targetRoot}/regression/modules/PDF-hul/issue_306.pdf.jhove.xml" fi +# Copy the TIFF Module results changed by https://github.com/openpreserve/jhove/pull/915 +if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/AA_Banner.tif.jhove.xml" ]]; then + cp "${candidateRoot}/regression/modules/TIFF-hul/AA_Banner.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/AA_Banner.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/strike.tif.jhove.xml" ]]; then + cp "${candidateRoot}/regression/modules/TIFF-hul/strike.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/strike.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/testpage-large.tif.jhove.xml" ]]; then + cp "${candidateRoot}/regression/modules/TIFF-hul/testpage-large.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/testpage-large.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/testpage-medium.tif.jhove.xml" ]]; then + cp "${candidateRoot}/regression/modules/TIFF-hul/testpage-medium.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/testpage-medium.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/oxford.tif.jhove.xml" ]]; then + cp "${candidateRoot}/regression/modules/TIFF-hul/oxford.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/oxford.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/jim___gg.tif.jhove.xml" ]]; then + cp "${candidateRoot}/regression/modules/TIFF-hul/jim___gg.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/jim___gg.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/bathy1.tif.jhove.xml" ]]; then + cp "${candidateRoot}/regression/modules/TIFF-hul/bathy1.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/bathy1.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/jim___cg.tif.jhove.xml" ]]; then + cp "${candidateRoot}/regression/modules/TIFF-hul/jim___cg.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/jim___cg.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/quad-tile.tif.jhove.xml" ]]; then + cp "${candidateRoot}/regression/modules/TIFF-hul/quad-tile.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/quad-tile.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/compos.tif.jhove.xml" ]]; then + cp "${candidateRoot}/regression/modules/TIFF-hul/compos.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/compos.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/pagemaker.tif.jhove.xml" ]]; then + cp "${candidateRoot}/regression/modules/TIFF-hul/pagemaker.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/pagemaker.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/jello.tif.jhove.xml" ]]; then + cp "${candidateRoot}/regression/modules/TIFF-hul/jello.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/jello.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/little-endian.tif.jhove.xml" ]]; then + cp "${candidateRoot}/regression/modules/TIFF-hul/little-endian.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/little-endian.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/cramps-tile.tif.jhove.xml" ]]; then + cp "${candidateRoot}/regression/modules/TIFF-hul/cramps-tile.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/cramps-tile.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/jim___ah.tif.jhove.xml" ]]; then + cp "${candidateRoot}/regression/modules/TIFF-hul/jim___ah.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/jim___ah.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/g3test.tif.jhove.xml" ]]; then + cp "${candidateRoot}/regression/modules/TIFF-hul/g3test.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/g3test.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/6mp_soft.tif.jhove.xml" ]]; then + cp "${candidateRoot}/regression/modules/TIFF-hul/6mp_soft.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/6mp_soft.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/ycbcr-cat.tif.jhove.xml" ]]; then + cp "${candidateRoot}/regression/modules/TIFF-hul/ycbcr-cat.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/ycbcr-cat.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/quad-lzw.tif.jhove.xml" ]]; then + cp "${candidateRoot}/regression/modules/TIFF-hul/quad-lzw.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/quad-lzw.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/jim___dg.tif.jhove.xml" ]]; then + cp "${candidateRoot}/regression/modules/TIFF-hul/jim___dg.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/jim___dg.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/fax2d.tif.jhove.xml" ]]; then + cp "${candidateRoot}/regression/modules/TIFF-hul/fax2d.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/fax2d.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/peppers.tif.jhove.xml" ]]; then + cp "${candidateRoot}/regression/modules/TIFF-hul/peppers.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/peppers.tif.jhove.xml" +fi + # Copy the PNG Module results changed by https://github.com/openpreserve/jhove/pull/843 if [[ -f "${candidateRoot}/regression/modules/PNG-gdm/issue_148.png.jhove.xml" ]]; then cp "${candidateRoot}/regression/modules/PNG-gdm/issue_148.png.jhove.xml" "${targetRoot}/regression/modules/PNG-gdm/issue_148.png.jhove.xml" From 23bcc36f782fd64fe53dd1083022fdf0cc430183 Mon Sep 17 00:00:00 2001 From: Thomas Ledoux Date: Fri, 8 Mar 2024 19:34:35 +0100 Subject: [PATCH 05/26] Correct path in regression files --- jhove-bbt/scripts/create-1.30-target.sh | 88 ++++++++++++------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/jhove-bbt/scripts/create-1.30-target.sh b/jhove-bbt/scripts/create-1.30-target.sh index 17ef226ac..eb18adf13 100755 --- a/jhove-bbt/scripts/create-1.30-target.sh +++ b/jhove-bbt/scripts/create-1.30-target.sh @@ -115,71 +115,71 @@ if [[ -f "${candidateRoot}/regression/modules/PDF-hul/issue_306.pdf.jhove.xml" ] fi # Copy the TIFF Module results changed by https://github.com/openpreserve/jhove/pull/915 -if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/AA_Banner.tif.jhove.xml" ]]; then - cp "${candidateRoot}/regression/modules/TIFF-hul/AA_Banner.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/AA_Banner.tif.jhove.xml" +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/AA_Banner.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/AA_Banner.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/AA_Banner.tif.jhove.xml" fi -if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/strike.tif.jhove.xml" ]]; then - cp "${candidateRoot}/regression/modules/TIFF-hul/strike.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/strike.tif.jhove.xml" +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/strike.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/strike.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/strike.tif.jhove.xml" fi -if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/testpage-large.tif.jhove.xml" ]]; then - cp "${candidateRoot}/regression/modules/TIFF-hul/testpage-large.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/testpage-large.tif.jhove.xml" +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/testpage-large.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/testpage-large.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/testpage-large.tif.jhove.xml" fi -if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/testpage-medium.tif.jhove.xml" ]]; then - cp "${candidateRoot}/regression/modules/TIFF-hul/testpage-medium.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/testpage-medium.tif.jhove.xml" +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/testpage-medium.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/testpage-medium.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/testpage-medium.tif.jhove.xml" fi -if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/oxford.tif.jhove.xml" ]]; then - cp "${candidateRoot}/regression/modules/TIFF-hul/oxford.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/oxford.tif.jhove.xml" +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/oxford.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/oxford.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/oxford.tif.jhove.xml" fi -if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/jim___gg.tif.jhove.xml" ]]; then - cp "${candidateRoot}/regression/modules/TIFF-hul/jim___gg.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/jim___gg.tif.jhove.xml" +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/jim___gg.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/jim___gg.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/jim___gg.tif.jhove.xml" fi -if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/bathy1.tif.jhove.xml" ]]; then - cp "${candidateRoot}/regression/modules/TIFF-hul/bathy1.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/bathy1.tif.jhove.xml" +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/bathy1.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/bathy1.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/bathy1.tif.jhove.xml" fi -if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/jim___cg.tif.jhove.xml" ]]; then - cp "${candidateRoot}/regression/modules/TIFF-hul/jim___cg.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/jim___cg.tif.jhove.xml" +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/jim___cg.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/jim___cg.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/jim___cg.tif.jhove.xml" fi -if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/quad-tile.tif.jhove.xml" ]]; then - cp "${candidateRoot}/regression/modules/TIFF-hul/quad-tile.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/quad-tile.tif.jhove.xml" +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/quad-tile.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/quad-tile.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/quad-tile.tif.jhove.xml" fi -if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/compos.tif.jhove.xml" ]]; then - cp "${candidateRoot}/regression/modules/TIFF-hul/compos.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/compos.tif.jhove.xml" +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/compos.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/compos.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/compos.tif.jhove.xml" fi -if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/pagemaker.tif.jhove.xml" ]]; then - cp "${candidateRoot}/regression/modules/TIFF-hul/pagemaker.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/pagemaker.tif.jhove.xml" +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/pagemaker.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/pagemaker.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/pagemaker.tif.jhove.xml" fi -if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/jello.tif.jhove.xml" ]]; then - cp "${candidateRoot}/regression/modules/TIFF-hul/jello.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/jello.tif.jhove.xml" +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/jello.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/jello.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/jello.tif.jhove.xml" fi -if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/little-endian.tif.jhove.xml" ]]; then - cp "${candidateRoot}/regression/modules/TIFF-hul/little-endian.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/little-endian.tif.jhove.xml" +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/little-endian.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/little-endian.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/little-endian.tif.jhove.xml" fi -if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/cramps-tile.tif.jhove.xml" ]]; then - cp "${candidateRoot}/regression/modules/TIFF-hul/cramps-tile.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/cramps-tile.tif.jhove.xml" +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/cramps-tile.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/cramps-tile.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/cramps-tile.tif.jhove.xml" fi -if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/jim___ah.tif.jhove.xml" ]]; then - cp "${candidateRoot}/regression/modules/TIFF-hul/jim___ah.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/jim___ah.tif.jhove.xml" +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/jim___ah.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/jim___ah.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/jim___ah.tif.jhove.xml" fi -if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/g3test.tif.jhove.xml" ]]; then - cp "${candidateRoot}/regression/modules/TIFF-hul/g3test.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/g3test.tif.jhove.xml" +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/g3test.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/g3test.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/g3test.tif.jhove.xml" fi -if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/6mp_soft.tif.jhove.xml" ]]; then - cp "${candidateRoot}/regression/modules/TIFF-hul/6mp_soft.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/6mp_soft.tif.jhove.xml" +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/6mp_soft.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/6mp_soft.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/6mp_soft.tif.jhove.xml" fi -if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/ycbcr-cat.tif.jhove.xml" ]]; then - cp "${candidateRoot}/regression/modules/TIFF-hul/ycbcr-cat.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/ycbcr-cat.tif.jhove.xml" +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/ycbcr-cat.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/ycbcr-cat.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/ycbcr-cat.tif.jhove.xml" fi -if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/quad-lzw.tif.jhove.xml" ]]; then - cp "${candidateRoot}/regression/modules/TIFF-hul/quad-lzw.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/quad-lzw.tif.jhove.xml" +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/quad-lzw.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/quad-lzw.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/quad-lzw.tif.jhove.xml" fi -if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/jim___dg.tif.jhove.xml" ]]; then - cp "${candidateRoot}/regression/modules/TIFF-hul/jim___dg.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/jim___dg.tif.jhove.xml" +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/badfiles/jim___dg.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/badfiles/jim___dg.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/badfiles/jim___dg.tif.jhove.xml" fi -if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/fax2d.tif.jhove.xml" ]]; then - cp "${candidateRoot}/regression/modules/TIFF-hul/fax2d.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/fax2d.tif.jhove.xml" +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/ddap/fax2d.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/ddap/fax2d.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/ddap/fax2d.tif.jhove.xml" fi -if [[ -f "${candidateRoot}/regression/modules/TIFF-hul/peppers.tif.jhove.xml" ]]; then - cp "${candidateRoot}/regression/modules/TIFF-hul/peppers.tif.jhove.xml" "${targetRoot}/regression/modules/TIFF-hul/peppers.tif.jhove.xml" +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/ddap/peppers.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/ddap/peppers.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/ddap/peppers.tif.jhove.xml" fi # Copy the PNG Module results changed by https://github.com/openpreserve/jhove/pull/843 From 94a72ee98cdce9a71ac3c050c3459b5dc322c9a1 Mon Sep 17 00:00:00 2001 From: Thomas Ledoux Date: Fri, 8 Mar 2024 19:52:35 +0100 Subject: [PATCH 06/26] Correct some mmore path in regression files --- jhove-bbt/scripts/create-1.30-target.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/jhove-bbt/scripts/create-1.30-target.sh b/jhove-bbt/scripts/create-1.30-target.sh index eb18adf13..3d6b6aba7 100755 --- a/jhove-bbt/scripts/create-1.30-target.sh +++ b/jhove-bbt/scripts/create-1.30-target.sh @@ -172,14 +172,14 @@ fi if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/quad-lzw.tif.jhove.xml" ]]; then cp "${candidateRoot}/examples/modules/TIFF-hul/quad-lzw.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/quad-lzw.tif.jhove.xml" fi -if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/badfiles/jim___dg.tif.jhove.xml" ]]; then - cp "${candidateRoot}/examples/modules/TIFF-hul/badfiles/jim___dg.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/badfiles/jim___dg.tif.jhove.xml" +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/jim___dg.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/jim___dg.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/jim___dg.tif.jhove.xml" fi -if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/ddap/fax2d.tif.jhove.xml" ]]; then - cp "${candidateRoot}/examples/modules/TIFF-hul/ddap/fax2d.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/ddap/fax2d.tif.jhove.xml" +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/fax2d.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/fax2d.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/fax2d.tif.jhove.xml" fi -if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/ddap/peppers.tif.jhove.xml" ]]; then - cp "${candidateRoot}/examples/modules/TIFF-hul/ddap/peppers.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/ddap/peppers.tif.jhove.xml" +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/peppers.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/peppers.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/peppers.tif.jhove.xml" fi # Copy the PNG Module results changed by https://github.com/openpreserve/jhove/pull/843 From 27d35938aa06cf346170a2c6843feaa9e7efc8cf Mon Sep 17 00:00:00 2001 From: Karen Hanson Date: Wed, 17 Apr 2024 12:40:49 -0400 Subject: [PATCH 07/26] Moved EPUB ErrorMessages.properties to correct location Date formatting error in the EPUB modules JhoveRepInfoReport.toDate() was causing unhandled fatal error due to incorrect location of ErrorMessages.properties. --- .../portico}/jhove/module/epub/ErrorMessages.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename jhove-ext-modules/src/main/resources/{edu/harvard/hul/ois => org/ithaka/portico}/jhove/module/epub/ErrorMessages.properties (99%) diff --git a/jhove-ext-modules/src/main/resources/edu/harvard/hul/ois/jhove/module/epub/ErrorMessages.properties b/jhove-ext-modules/src/main/resources/org/ithaka/portico/jhove/module/epub/ErrorMessages.properties similarity index 99% rename from jhove-ext-modules/src/main/resources/edu/harvard/hul/ois/jhove/module/epub/ErrorMessages.properties rename to jhove-ext-modules/src/main/resources/org/ithaka/portico/jhove/module/epub/ErrorMessages.properties index 7551a314e..4c05dfd38 100644 --- a/jhove-ext-modules/src/main/resources/edu/harvard/hul/ois/jhove/module/epub/ErrorMessages.properties +++ b/jhove-ext-modules/src/main/resources/org/ithaka/portico/jhove/module/epub/ErrorMessages.properties @@ -1,3 +1,3 @@ -EPUB-PTC-1 = FATAL, [An error occurred.] -EPUB-PTC-1-SUB = Exception: %s +EPUB-PTC-1 = FATAL, [An error occurred.] +EPUB-PTC-1-SUB = Exception: %s EPUB-PTC-2 = FATAL, [An undefined error occurred. This may be caused by a system error or indicative of a problem with the file provided.] \ No newline at end of file From c9b2b39b777ee8a2a2227bb2412dc4fb0cec7249 Mon Sep 17 00:00:00 2001 From: Karen Hanson Date: Wed, 17 Apr 2024 12:41:46 -0400 Subject: [PATCH 08/26] Add trim() on JhoveRepInfoReport.toDate() If a content.opf date value has whitespace around it, it is labelled as invalid. This trims whitespace before checking date. --- .../org/ithaka/portico/jhove/module/epub/JhoveRepInfoReport.java | 1 + 1 file changed, 1 insertion(+) diff --git a/jhove-ext-modules/src/main/java/org/ithaka/portico/jhove/module/epub/JhoveRepInfoReport.java b/jhove-ext-modules/src/main/java/org/ithaka/portico/jhove/module/epub/JhoveRepInfoReport.java index 5fa0e80de..e3d5eab2e 100644 --- a/jhove-ext-modules/src/main/java/org/ithaka/portico/jhove/module/epub/JhoveRepInfoReport.java +++ b/jhove-ext-modules/src/main/java/org/ithaka/portico/jhove/module/epub/JhoveRepInfoReport.java @@ -421,6 +421,7 @@ private static Date toDate(String isoDate) { if (isoDate == null || isoDate.length() == 0) { return null; } + isoDate = isoDate.trim(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat(ISO_DATE_PATTERN); Date date; From de950a3d7d374bfb39faf59f89e4cfc348aa26aa Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Fri, 26 Jul 2024 17:18:00 +0100 Subject: [PATCH 09/26] DEV: Development version 1.31 --- jhove-apps/pom.xml | 6 +++--- jhove-core/pom.xml | 2 +- jhove-ext-modules/pom.xml | 2 +- jhove-installer/pom.xml | 6 +++--- jhove-modules/aiff-hul/pom.xml | 2 +- jhove-modules/ascii-hul/pom.xml | 2 +- jhove-modules/gif-hul/pom.xml | 2 +- jhove-modules/html-hul/pom.xml | 2 +- jhove-modules/jpeg-hul/pom.xml | 2 +- jhove-modules/jpeg2000-hul/pom.xml | 2 +- jhove-modules/pdf-hul/pom.xml | 2 +- jhove-modules/pom.xml | 6 +++--- jhove-modules/tiff-hul/pom.xml | 2 +- jhove-modules/utf8-hul/pom.xml | 2 +- jhove-modules/wave-hul/pom.xml | 2 +- jhove-modules/xml-hul/pom.xml | 2 +- pom.xml | 2 +- 17 files changed, 23 insertions(+), 23 deletions(-) diff --git a/jhove-apps/pom.xml b/jhove-apps/pom.xml index 96610605e..fb5c427dc 100644 --- a/jhove-apps/pom.xml +++ b/jhove-apps/pom.xml @@ -5,12 +5,12 @@ org.openpreservation.jhove jhove - 1.30.0 + 1.31.0-SNAPSHOT jhove-apps jar - 1.30.1 + 1.31.0-SNAPSHOT JHOVE Applications @@ -60,7 +60,7 @@ org.openpreservation.jhove jhove-core - 1.30.0 + 1.31.0-SNAPSHOT diff --git a/jhove-core/pom.xml b/jhove-core/pom.xml index c7e17204c..e09a033a4 100644 --- a/jhove-core/pom.xml +++ b/jhove-core/pom.xml @@ -5,7 +5,7 @@ org.openpreservation.jhove jhove - 1.30.0 + 1.31.0-SNAPSHOT jhove-core diff --git a/jhove-ext-modules/pom.xml b/jhove-ext-modules/pom.xml index 97ed7377b..d52517052 100644 --- a/jhove-ext-modules/pom.xml +++ b/jhove-ext-modules/pom.xml @@ -3,7 +3,7 @@ org.openpreservation.jhove jhove - 1.30.0 + 1.31.0-SNAPSHOT jhove-ext-modules diff --git a/jhove-installer/pom.xml b/jhove-installer/pom.xml index df9b0e891..76dc74c70 100644 --- a/jhove-installer/pom.xml +++ b/jhove-installer/pom.xml @@ -5,11 +5,11 @@ org.openpreservation.jhove jhove - 1.30.0 + 1.31.0-SNAPSHOT jhove-installer - 1.30.1 + 1.31.0-SNAPSHOT JHOVE Installer Maven-built IzPack installer for JHOVE. @@ -175,7 +175,7 @@ org.openpreservation.jhove jhove-ext-modules - 1.30.0 + 1.31.0-SNAPSHOT org.openpreservation.jhove.modules diff --git a/jhove-modules/aiff-hul/pom.xml b/jhove-modules/aiff-hul/pom.xml index 1395924e3..72180500d 100644 --- a/jhove-modules/aiff-hul/pom.xml +++ b/jhove-modules/aiff-hul/pom.xml @@ -3,7 +3,7 @@ org.openpreservation.jhove.modules jhove-modules - 1.30.1 + 1.31.0-SNAPSHOT aiff-hul 1.6.2 diff --git a/jhove-modules/ascii-hul/pom.xml b/jhove-modules/ascii-hul/pom.xml index fbdaf3f6e..250678d7b 100644 --- a/jhove-modules/ascii-hul/pom.xml +++ b/jhove-modules/ascii-hul/pom.xml @@ -3,7 +3,7 @@ org.openpreservation.jhove.modules jhove-modules - 1.30.1 + 1.31.0-SNAPSHOT ascii-hul 1.4.2 diff --git a/jhove-modules/gif-hul/pom.xml b/jhove-modules/gif-hul/pom.xml index 2abd4cec3..ad7998d18 100644 --- a/jhove-modules/gif-hul/pom.xml +++ b/jhove-modules/gif-hul/pom.xml @@ -3,7 +3,7 @@ org.openpreservation.jhove.modules jhove-modules - 1.30.1 + 1.31.0-SNAPSHOT gif-hul 1.4.3 diff --git a/jhove-modules/html-hul/pom.xml b/jhove-modules/html-hul/pom.xml index 03ae60382..a8af3637b 100644 --- a/jhove-modules/html-hul/pom.xml +++ b/jhove-modules/html-hul/pom.xml @@ -3,7 +3,7 @@ org.openpreservation.jhove.modules jhove-modules - 1.30.1 + 1.31.0-SNAPSHOT html-hul 1.4.3 diff --git a/jhove-modules/jpeg-hul/pom.xml b/jhove-modules/jpeg-hul/pom.xml index 1b1d663bc..ab0eebcaf 100644 --- a/jhove-modules/jpeg-hul/pom.xml +++ b/jhove-modules/jpeg-hul/pom.xml @@ -3,7 +3,7 @@ org.openpreservation.jhove.modules jhove-modules - 1.30.1 + 1.31.0-SNAPSHOT jpeg-hul 1.5.4 diff --git a/jhove-modules/jpeg2000-hul/pom.xml b/jhove-modules/jpeg2000-hul/pom.xml index 1d4557036..77ed54d56 100644 --- a/jhove-modules/jpeg2000-hul/pom.xml +++ b/jhove-modules/jpeg2000-hul/pom.xml @@ -3,7 +3,7 @@ org.openpreservation.jhove.modules jhove-modules - 1.30.1 + 1.31.0-SNAPSHOT jpeg2000-hul 1.4.4 diff --git a/jhove-modules/pdf-hul/pom.xml b/jhove-modules/pdf-hul/pom.xml index b7102d68d..3e5a435ec 100644 --- a/jhove-modules/pdf-hul/pom.xml +++ b/jhove-modules/pdf-hul/pom.xml @@ -3,7 +3,7 @@ org.openpreservation.jhove.modules jhove-modules - 1.30.1 + 1.31.0-SNAPSHOT pdf-hul 1.12.6 diff --git a/jhove-modules/pom.xml b/jhove-modules/pom.xml index be67b6629..cbdf843cd 100644 --- a/jhove-modules/pom.xml +++ b/jhove-modules/pom.xml @@ -5,13 +5,13 @@ org.openpreservation.jhove jhove - 1.30.0 + 1.31.0-SNAPSHOT org.openpreservation.jhove.modules jhove-modules pom - 1.30.1 + 1.31.0-SNAPSHOT JHOVE Validation Modules The JHOVE HUL validation modules. @@ -19,7 +19,7 @@ org.openpreservation.jhove jhove-core - 1.30.0 + 1.31.0-SNAPSHOT org.junit.vintage diff --git a/jhove-modules/tiff-hul/pom.xml b/jhove-modules/tiff-hul/pom.xml index 1bae3e28d..34ae9f03d 100644 --- a/jhove-modules/tiff-hul/pom.xml +++ b/jhove-modules/tiff-hul/pom.xml @@ -3,7 +3,7 @@ org.openpreservation.jhove.modules jhove-modules - 1.30.1 + 1.31.0-SNAPSHOT tiff-hul 1.9.4 diff --git a/jhove-modules/utf8-hul/pom.xml b/jhove-modules/utf8-hul/pom.xml index b8b5c2d19..3798fb6fd 100644 --- a/jhove-modules/utf8-hul/pom.xml +++ b/jhove-modules/utf8-hul/pom.xml @@ -3,7 +3,7 @@ org.openpreservation.jhove.modules jhove-modules - 1.30.1 + 1.31.0-SNAPSHOT utf8-hul 1.7.3 diff --git a/jhove-modules/wave-hul/pom.xml b/jhove-modules/wave-hul/pom.xml index 33a5a90c6..b03bf26cb 100644 --- a/jhove-modules/wave-hul/pom.xml +++ b/jhove-modules/wave-hul/pom.xml @@ -3,7 +3,7 @@ org.openpreservation.jhove.modules jhove-modules - 1.30.1 + 1.31.0-SNAPSHOT wave-hul 1.8.3 diff --git a/jhove-modules/xml-hul/pom.xml b/jhove-modules/xml-hul/pom.xml index 41cbd791c..cf18c4cca 100644 --- a/jhove-modules/xml-hul/pom.xml +++ b/jhove-modules/xml-hul/pom.xml @@ -3,7 +3,7 @@ org.openpreservation.jhove.modules jhove-modules - 1.30.1 + 1.31.0-SNAPSHOT xml-hul 1.5.4 diff --git a/pom.xml b/pom.xml index 053b0f762..f63ee57db 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ org.openpreservation.jhove jhove - 1.30.0 + 1.31.0-SNAPSHOT pom JHOVE - JSTOR/Harvard Object Validation Environment From 15c06a85b7c08ac99064265f1c25544e4d860ae4 Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Tue, 6 Aug 2024 11:50:00 +0100 Subject: [PATCH 10/26] FIX: Project refs in installer --- jhove-installer/src/main/izpack/install.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jhove-installer/src/main/izpack/install.xml b/jhove-installer/src/main/izpack/install.xml index 41ea703d1..f8ec2b446 100644 --- a/jhove-installer/src/main/izpack/install.xml +++ b/jhove-installer/src/main/izpack/install.xml @@ -107,8 +107,8 @@ Third-party validation modules developed for JHOVE, currently GZIP, PNG and WARC. - - + + From 398133f714160ec05ef12018e90a19e183fa5661 Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Tue, 6 Aug 2024 11:56:00 +0100 Subject: [PATCH 11/26] FIX: Added test script for dev version. --- jhove-bbt/scripts/travis-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jhove-bbt/scripts/travis-test.sh b/jhove-bbt/scripts/travis-test.sh index 94ace84e3..c2ee948d6 100755 --- a/jhove-bbt/scripts/travis-test.sh +++ b/jhove-bbt/scripts/travis-test.sh @@ -13,7 +13,7 @@ TEST_BASELINES_ROOT="${TEST_ROOT}/baselines" TEST_INSTALL_ROOT="${TEST_ROOT}/jhove" CANDIDATE_ROOT="${TEST_ROOT}/candidates" TARGET_ROOT="${TEST_ROOT}/targets" -BASELINE_VERSION=1.28 +BASELINE_VERSION=1.30 # Create the JHOVE test root if it doesn't exist [[ -d "${TEST_ROOT}" ]] || mkdir -p "${TEST_ROOT}" From 99aa31cf9003834bf7fe89913efe49ff20ac0be1 Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Tue, 6 Aug 2024 11:58:00 +0100 Subject: [PATCH 12/26] FIX: Test script added. --- jhove-bbt/scripts/create-1.31-target.sh | 62 +++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100755 jhove-bbt/scripts/create-1.31-target.sh diff --git a/jhove-bbt/scripts/create-1.31-target.sh b/jhove-bbt/scripts/create-1.31-target.sh new file mode 100755 index 000000000..f94d7a97c --- /dev/null +++ b/jhove-bbt/scripts/create-1.31-target.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash + +testRoot="test-root" +paramCandidateVersion="" +paramBaselineVersion="" +baselineRoot="${testRoot}/baselines" +candidateRoot="${testRoot}/candidates" +targetRoot="${testRoot}/targets" +# Check the passed params to avoid disapointment +checkParams () { + OPTIND=1 # Reset in case getopts previously used + + while getopts "h?b:c:" opt; do # Grab the options + case "$opt" in + h|\?) + showHelp + exit 0 + ;; + b) paramBaselineVersion=$OPTARG + ;; + c) paramCandidateVersion=$OPTARG + ;; + esac + done + + if [ -z "$paramBaselineVersion" ] || [ -z "$paramCandidateVersion" ] + then + showHelp + exit 0 + fi + + baselineRoot="${baselineRoot}/${paramBaselineVersion}" + candidateRoot="${candidateRoot}/${paramCandidateVersion}" + targetRoot="${targetRoot}/${paramCandidateVersion}" +} + +# Show usage message +showHelp() { + echo "usage: create-target [-b ] [-c ] [-h|?]" + echo "" + echo " baselineVersion : The version number id for the baseline data." + echo " candidateVersion : The version number id for the candidate data." + echo "" + echo " -h|? : This message." +} + +# Execution starts here +checkParams "$@"; +if [[ -d "${targetRoot}" ]]; then + echo " - removing existing baseline at ${targetRoot}." + rm -rf "${targetRoot}" +fi + +echo "TEST BASELINE: Creating baseline" +# Simply copy baseline for now we're not making any changes +echo " - copying ${baselineRoot} baseline to ${targetRoot}" +cp -R "${baselineRoot}" "${targetRoot}" +# find "${targetRoot}" -type f -name "*.pdf.jhove.xml" -exec sed -i 's/^ PDF-hul<\/reportingModule>$/ PDF-hul<\/reportingModule>/' {} \; +# find "${targetRoot}" -type f -name "audit.jhove.xml" -exec sed -i 's/^ PDF-hul<\/module>$/ PDF-hul<\/module>/' {} \; +# find "${targetRoot}" -type f -name "audit-PDF-hul.jhove.xml" -exec sed -i 's/^ 1.12.5<\/release>$/ 1.12.6<\/release>/' {} \; +# find "${targetRoot}" -type f -name "audit-PDF-hul.jhove.xml" -exec sed -i 's/2024-03-05/2024-07-31/' {} \; + From 23488a24919be49aa68596c174bdcd26fdce7515 Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Tue, 6 Aug 2024 15:09:00 +0100 Subject: [PATCH 13/26] FIX: Tests for fix/848 --- jhove-bbt/scripts/create-1.31-target.sh | 82 +++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 4 deletions(-) diff --git a/jhove-bbt/scripts/create-1.31-target.sh b/jhove-bbt/scripts/create-1.31-target.sh index f94d7a97c..5eeac069e 100755 --- a/jhove-bbt/scripts/create-1.31-target.sh +++ b/jhove-bbt/scripts/create-1.31-target.sh @@ -55,8 +55,82 @@ echo "TEST BASELINE: Creating baseline" # Simply copy baseline for now we're not making any changes echo " - copying ${baselineRoot} baseline to ${targetRoot}" cp -R "${baselineRoot}" "${targetRoot}" -# find "${targetRoot}" -type f -name "*.pdf.jhove.xml" -exec sed -i 's/^ PDF-hul<\/reportingModule>$/ PDF-hul<\/reportingModule>/' {} \; -# find "${targetRoot}" -type f -name "audit.jhove.xml" -exec sed -i 's/^ PDF-hul<\/module>$/ PDF-hul<\/module>/' {} \; -# find "${targetRoot}" -type f -name "audit-PDF-hul.jhove.xml" -exec sed -i 's/^ 1.12.5<\/release>$/ 1.12.6<\/release>/' {} \; -# find "${targetRoot}" -type f -name "audit-PDF-hul.jhove.xml" -exec sed -i 's/2024-03-05/2024-07-31/' {} \; +# Copy the TIFF Module results changed by https://github.com/openpreserve/jhove/pull/915 +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/AA_Banner.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/AA_Banner.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/AA_Banner.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/strike.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/strike.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/strike.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/testpage-large.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/testpage-large.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/testpage-large.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/testpage-medium.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/testpage-medium.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/testpage-medium.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/oxford.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/oxford.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/oxford.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/jim___gg.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/jim___gg.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/jim___gg.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/bathy1.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/bathy1.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/bathy1.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/jim___cg.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/jim___cg.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/jim___cg.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/quad-tile.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/quad-tile.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/quad-tile.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/compos.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/compos.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/compos.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/pagemaker.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/pagemaker.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/pagemaker.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/jello.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/jello.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/jello.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/little-endian.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/little-endian.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/little-endian.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/cramps-tile.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/cramps-tile.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/cramps-tile.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/jim___ah.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/jim___ah.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/jim___ah.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/g3test.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/g3test.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/g3test.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/6mp_soft.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/6mp_soft.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/6mp_soft.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/ycbcr-cat.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/ycbcr-cat.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/ycbcr-cat.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/quad-lzw.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/quad-lzw.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/quad-lzw.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/jim___dg.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/jim___dg.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/jim___dg.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/fax2d.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/fax2d.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/fax2d.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/peppers.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/peppers.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/peppers.tif.jhove.xml" +fi + +declare -a tiff_affected=("examples/modules/TIFF-hul/cramps.tif.jhove.xml" + "examples/modules/TIFF-hul/text.tif.jhove.xml" + "examples/modules/TIFF-hul/testpage-small.tif.jhove.xml" + ) +for filename in "${tiff_affected[@]}" +do + if [[ -f "${candidateRoot}/${filename}" ]]; then + cp "${candidateRoot}/${filename}" "${targetRoot}/${filename}" + fi +done From 431c93662a2929c823e40f72e7bb752542ffd7bb Mon Sep 17 00:00:00 2001 From: Sam Alloing Date: Wed, 7 Aug 2024 14:25:51 +0200 Subject: [PATCH 14/26] Added indirect reference in Destination The current implementation doesn't support indirect reference in destination this commit adds this capability --- .../java/edu/harvard/hul/ois/jhove/module/pdf/Destination.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jhove-modules/pdf-hul/src/main/java/edu/harvard/hul/ois/jhove/module/pdf/Destination.java b/jhove-modules/pdf-hul/src/main/java/edu/harvard/hul/ois/jhove/module/pdf/Destination.java index cf07796b0..4739822ee 100644 --- a/jhove-modules/pdf-hul/src/main/java/edu/harvard/hul/ois/jhove/module/pdf/Destination.java +++ b/jhove-modules/pdf-hul/src/main/java/edu/harvard/hul/ois/jhove/module/pdf/Destination.java @@ -75,6 +75,9 @@ public Destination(final PdfObject destObj, final PdfModule module, _indirect = true; _indirectDest = (PdfSimpleObject) destObj; return; + } else if (!named && destObj instanceof PdfIndirectObj) { + _pageDest = findDirectDest(module, (PdfArray) module.resolveIndirectObject(destObj)); + return; } PdfArray destArray = null; try { From 18a3bc197f4500689a15149ebae774c93415cecc Mon Sep 17 00:00:00 2001 From: Sam Alloing Date: Wed, 7 Aug 2024 15:24:52 +0200 Subject: [PATCH 15/26] forgot to add the IOException to the pull request --- .../java/edu/harvard/hul/ois/jhove/module/pdf/Destination.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jhove-modules/pdf-hul/src/main/java/edu/harvard/hul/ois/jhove/module/pdf/Destination.java b/jhove-modules/pdf-hul/src/main/java/edu/harvard/hul/ois/jhove/module/pdf/Destination.java index 4739822ee..405fd71e6 100644 --- a/jhove-modules/pdf-hul/src/main/java/edu/harvard/hul/ois/jhove/module/pdf/Destination.java +++ b/jhove-modules/pdf-hul/src/main/java/edu/harvard/hul/ois/jhove/module/pdf/Destination.java @@ -67,7 +67,7 @@ public final class Destination { * from a named destination. */ public Destination(final PdfObject destObj, final PdfModule module, - final boolean named) throws PdfException { + final boolean named) throws PdfException, IOException { if (destObj == null) { throw new IllegalArgumentException("Parameter destObj cannot be null."); } From ca4df2ae902941508122e23abde3f47225f2f7e2 Mon Sep 17 00:00:00 2001 From: Sam Alloing Date: Wed, 7 Aug 2024 15:44:30 +0200 Subject: [PATCH 16/26] another IOException needs to be added --- .../main/java/edu/harvard/hul/ois/jhove/module/PdfModule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jhove-modules/pdf-hul/src/main/java/edu/harvard/hul/ois/jhove/module/PdfModule.java b/jhove-modules/pdf-hul/src/main/java/edu/harvard/hul/ois/jhove/module/PdfModule.java index 2c25fd0c3..7dff94973 100644 --- a/jhove-modules/pdf-hul/src/main/java/edu/harvard/hul/ois/jhove/module/PdfModule.java +++ b/jhove-modules/pdf-hul/src/main/java/edu/harvard/hul/ois/jhove/module/PdfModule.java @@ -4285,7 +4285,7 @@ protected boolean doOutlineStuff(RepInfo info) { * 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, RepInfo info) throws PdfException { + protected int resolveIndirectDest(PdfSimpleObject key, RepInfo info) throws PdfException, IOException { if (key == null) { throw new IllegalArgumentException("Argument key can not be null"); } From eb932c7a10da4056ecf7cfc39096355e40261821 Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Wed, 14 Aug 2024 15:26:58 +0100 Subject: [PATCH 17/26] FIX: XHTML DTD detection - fixed issue affecting DTD detection for XHTML documents; - this needed extra cases adding for each of the XHTML DTD definitions; - added string constant for XHTML 1.1; and - added test cases for DTD detection with and without XML declaration. Closes #904 --- jhove-bbt/scripts/create-1.31-target.sh | 14 ++++++++++++-- .../harvard/hul/ois/jhove/module/HtmlModule.java | 13 +++++++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/jhove-bbt/scripts/create-1.31-target.sh b/jhove-bbt/scripts/create-1.31-target.sh index 5eeac069e..f6b110ae5 100755 --- a/jhove-bbt/scripts/create-1.31-target.sh +++ b/jhove-bbt/scripts/create-1.31-target.sh @@ -126,11 +126,21 @@ fi declare -a tiff_affected=("examples/modules/TIFF-hul/cramps.tif.jhove.xml" "examples/modules/TIFF-hul/text.tif.jhove.xml" - "examples/modules/TIFF-hul/testpage-small.tif.jhove.xml" - ) + "examples/modules/TIFF-hul/testpage-small.tif.jhove.xml") for filename in "${tiff_affected[@]}" do if [[ -f "${candidateRoot}/${filename}" ]]; then cp "${candidateRoot}/${filename}" "${targetRoot}/${filename}" fi done + +declare -a xhtml_affected=("errors/modules/HTML-hul/xhtml-trans-no-xml-dec.html.jhove.xml" + "errors/modules/HTML-hul/xhtml-strict-no-xml-dec.html.jhove.xml" + "errors/modules/HTML-hul/xhtml-frames-no-xml-dec.html.jhove.xml" + "errors/modules/HTML-hul/xhtml-1-1-no-xml-dec.html.jhove.xml") +for filename in "${xhtml_affected[@]}" +do + if [[ -f "${candidateRoot}/${filename}" ]]; then + cp "${candidateRoot}/${filename}" "${targetRoot}/${filename}" + fi +done diff --git a/jhove-modules/html-hul/src/main/java/edu/harvard/hul/ois/jhove/module/HtmlModule.java b/jhove-modules/html-hul/src/main/java/edu/harvard/hul/ois/jhove/module/HtmlModule.java index 6c667367c..56b8cdd3a 100644 --- a/jhove-modules/html-hul/src/main/java/edu/harvard/hul/ois/jhove/module/HtmlModule.java +++ b/jhove-modules/html-hul/src/main/java/edu/harvard/hul/ois/jhove/module/HtmlModule.java @@ -102,6 +102,7 @@ public class HtmlModule extends ModuleBase { private static final String HTML_4_0 = "HTML 4.0"; private static final String HTML_4_01 = "HTML 4.01"; private static final String XHTML_1_0 = "XHTML 1.0"; + private static final String XHTML_1_1_STR = "XHTML 1.1"; private static final String NAME = "HTML-hul"; private static final String RELEASE = "1.4.3"; @@ -162,7 +163,7 @@ public class HtmlModule extends ModuleBase { /* 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" }; + XHTML_1_0, XHTML_1_0, XHTML_1_1_STR }; /* Flag to know if the property TextMDMetadata is to be added */ protected boolean _withTextMD = false; @@ -675,7 +676,15 @@ protected int checkDoctype(List elements) { return HTML_4_01_TRANSITIONAL; case "-//W3C//DTD HTML 4.01 FRAMESET//EN": return HTML_4_01_FRAMESET; - default: + case "-//W3C//DTD XHTML 1.0 STRICT//EN": + return XHTML_1_0_STRICT; + case "-//W3C//DTD XHTML 1.0 TRANSITIONAL//EN": + return XHTML_1_0_TRANSITIONAL; + case "-//W3C//DTD XHTML 1.0 FRAMESET//EN": + return XHTML_1_0_FRAMESET; + case "-//W3C//DTD XHTML 1.1//EN": + return XHTML_1_1; + default: break; } } catch (Exception e) { From d727b00b5ed8e8e9f7e79cdafafdb4d338b1478d Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Thu, 15 Aug 2024 11:24:03 +0100 Subject: [PATCH 18/26] FIX: PDF v2 date parsing - PDF-hul now allows dates in the form `D:YYYYMMDDHHmmSSOHH'mm` as well as `D:YYYYMMDDHHmmSSOHH'mm'`; - refactored date parsing routine to allow unit testing; and - added unit tests for date formats. --- .../hul/ois/jhove/module/PdfModule.java | 13 +- .../hul/ois/jhove/module/pdf/Literal.java | 864 +++++++++--------- .../ois/jhove/module/pdf/LiteralTests.java | 36 + 3 files changed, 467 insertions(+), 446 deletions(-) create mode 100644 jhove-modules/pdf-hul/src/test/java/edu/harvard/hul/ois/jhove/module/pdf/LiteralTests.java diff --git a/jhove-modules/pdf-hul/src/main/java/edu/harvard/hul/ois/jhove/module/PdfModule.java b/jhove-modules/pdf-hul/src/main/java/edu/harvard/hul/ois/jhove/module/PdfModule.java index 2c25fd0c3..e0ec8a57f 100644 --- a/jhove-modules/pdf-hul/src/main/java/edu/harvard/hul/ois/jhove/module/PdfModule.java +++ b/jhove-modules/pdf-hul/src/main/java/edu/harvard/hul/ois/jhove/module/PdfModule.java @@ -4344,7 +4344,7 @@ protected void addStringProperty(PdfDictionary dict, * with a string value, to a specified List. */ protected void addDateProperty(PdfDictionary dict, List propList, - String key, String propName) throws PdfException { + String key, String propName) throws PdfInvalidException { if (_encrypted) { String propText = ENCRYPTED; propList.add(new Property(propName, PropertyType.STRING, propText)); @@ -4354,12 +4354,11 @@ protected void addDateProperty(PdfDictionary dict, List propList, Token tok = ((PdfSimpleObject) propObject).getToken(); if (tok instanceof Literal) { Literal lit = (Literal) tok; - Date propDate = lit.parseDate(); - if (propDate != null) { - propList.add(new Property(propName, PropertyType.DATE, propDate)); - // Ignore empty literals as this isn't an error - } else if (!lit.getValue().isEmpty()) { - throw new PdfInvalidException(MessageConstants.PDF_HUL_133, 0); // PDF-HUL-133 + if (!lit.getValue().isEmpty()) { + Date propDate = lit.parseDate(); + if (propDate != null) { + propList.add(new Property(propName, PropertyType.DATE, propDate)); + } } } } diff --git a/jhove-modules/pdf-hul/src/main/java/edu/harvard/hul/ois/jhove/module/pdf/Literal.java b/jhove-modules/pdf-hul/src/main/java/edu/harvard/hul/ois/jhove/module/pdf/Literal.java index 6bc540fd5..3a6f8fc5d 100644 --- a/jhove-modules/pdf-hul/src/main/java/edu/harvard/hul/ois/jhove/module/pdf/Literal.java +++ b/jhove-modules/pdf-hul/src/main/java/edu/harvard/hul/ois/jhove/module/pdf/Literal.java @@ -11,81 +11,85 @@ 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. + * 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 -{ + extends StringValuedToken { /** True if literal is in PDFDocEncoding; false if UTF-16. */ private boolean _pdfDocEncoding; /** Used for accommodating the literal */ private StringBuffer buffer; - /** Indicates if a character for the first half of a hex byte - has already been buffered */ + /** + * 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 firstByte; /** First digit of a hexadecimal string value. */ - //int h1; + // int h1; - /** The state of the tokenization. Only the subset of States which - pertain to Literals are used here. */ + /** + * 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. */ + + /** + * 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' + 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; @@ -99,95 +103,89 @@ public class Literal private static final int FF = 0xFF; /** Creates an instance of a string literal */ - public Literal () - { - super (); + public Literal() { + super(); _pdfDocEncoding = true; - buffer = new StringBuffer (); + buffer = new StringBuffer(); haveHi = false; } /** - * Append a hex character.This is used only for hex literals - * (those that start with '<'). + * 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 - * @throws edu.harvard.hul.ois.jhove.module.pdf.PdfException + * @param ch The integer 8-bit code for a hex character + * @throws edu.harvard.hul.ois.jhove.module.pdf.PdfException */ - public void appendHex (int ch) throws PdfException - { + public void appendHex(int ch) throws PdfException { if (_rawBytes == null) { - _rawBytes = new Vector<> (32); + _rawBytes = new Vector<>(32); } if (haveHi) { - _rawBytes.add(hexToInt (hi, ch)); + _rawBytes.add(hexToInt(hi, ch)); haveHi = false; - } - else { + } 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. + * 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. - * @throws IOException + * @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. + * @throws IOException */ - public long processLiteral (Tokenizer tok) throws IOException - { + public long processLiteral(Tokenizer tok) throws IOException { /** Variable for UTF-16 chars. */ int utfch; /** First byte of a UTF-16 character. */ int b1 = 0x00; - /* Character read from tokenizer. */ + /* Character read from tokenizer. */ int ch; _parenLevel = 0; - _rawBytes = new Vector<> (32); + _rawBytes = new Vector<>(32); _state = State.LITERAL; - long offset = 0; + long offset = 0; for (;;) { - ch = tok.readChar (); + 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 (MessageConstants.PDF_HUL_10.getMessage()); // PDF-HUL-10 + throw new EOFException(MessageConstants.PDF_HUL_10.getMessage()); // PDF-HUL-10 } offset++; 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) { + } else if (ch == CLOSE_PARENTHESIS && --_parenLevel < 0) { // We have an empty string - setPDFDocEncoding (true); + setPDFDocEncoding(true); setValue(buffer.toString()); return offset; - } - else if (ch == BACKSLASH) { - ch = readBackslashSequence (false, tok); + } else if (ch == BACKSLASH) { + ch = readBackslashSequence(false, tok); switch (ch) { case 0: - continue; // invalid character, ignore + continue; // invalid character, ignore case FE: _state = State.LITERAL_FE; break; default: // any other char is treated nonspecially - setPDFDocEncoding (true); - buffer.append (PDFDOCENCODING[ch]); + setPDFDocEncoding(true); + buffer.append(PDFDOCENCODING[ch]); break; } - } - else { + } else { // We now know we're in 8-bit PDF encoding. // Append the character to the buffer. if (ch == OPEN_PARENTHESIS) { @@ -196,65 +194,60 @@ else if (ch == BACKSLASH) { ++_parenLevel; } _state = State.LITERAL_PDF; - setPDFDocEncoding (true); - buffer.append (PDFDOCENCODING[ch]); + setPDFDocEncoding(true); + buffer.append(PDFDOCENCODING[ch]); } - } - else if (_state == (State.LITERAL_FE)) { + } else if (_state == (State.LITERAL_FE)) { switch (ch) { case FF: _state = State.LITERAL_UTF16_1; - setPDFDocEncoding (false); + setPDFDocEncoding(false); break; case BACKSLASH: - ch = readBackslashSequence (false, tok); + ch = readBackslashSequence(false, tok); if (ch == 0) { - continue; // invalid character, ignore - } if (ch == FF) { - _state = State.LITERAL_UTF16_1; - setPDFDocEncoding (false); + continue; // invalid character, ignore } - else { + if (ch == FF) { + _state = State.LITERAL_UTF16_1; + setPDFDocEncoding(false); + } else { // any other char is treated nonspecially - setPDFDocEncoding (true); + setPDFDocEncoding(true); // The FE is just an FE, put it in the buffer - buffer.append (PDFDOCENCODING[FE]); - buffer.append (PDFDOCENCODING[ch]); - } break; + buffer.append(PDFDOCENCODING[FE]); + buffer.append(PDFDOCENCODING[ch]); + } + break; default: _state = State.LITERAL_PDF; - setPDFDocEncoding (true); + setPDFDocEncoding(true); // The FE is just an FE, put it in the buffer - buffer.append (PDFDOCENCODING[FE]); - buffer.append (PDFDOCENCODING[ch]); + buffer.append(PDFDOCENCODING[FE]); + buffer.append(PDFDOCENCODING[ch]); break; } - } - else if (_state == (State.LITERAL_PDF)) { + } else if (_state == (State.LITERAL_PDF)) { if (ch == OPEN_PARENTHESIS) { // Count open parens to be matched by close parens. // Backslash-quoted parens won't get here. ++_parenLevel; - buffer.append (PDFDOCENCODING[ch]); - } - else if (ch == CLOSE_PARENTHESIS && --_parenLevel < 0) { + buffer.append(PDFDOCENCODING[ch]); + } else if (ch == CLOSE_PARENTHESIS && --_parenLevel < 0) { setValue(buffer.toString()); return offset; - } - else if (ch == BACKSLASH) { - ch = readBackslashSequence (false, tok); + } else if (ch == BACKSLASH) { + ch = readBackslashSequence(false, tok); if (ch == 0) { - continue; // invalid character, ignore + continue; // invalid character, ignore } // any other char is treated nonspecially - buffer.append (PDFDOCENCODING[ch]); - } - else { - buffer.append (PDFDOCENCODING[ch]); + 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 + } 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. @@ -263,56 +256,54 @@ else if (_state == (State.LITERAL_UTF16_1)) { setValue(buffer.toString()); return offset; case BACKSLASH: - utfch = readBackslashSequence (true, tok); + utfch = readBackslashSequence(true, tok); if (utfch == 0) { - continue; // invalid character, ignore - } break; + continue; // invalid character, ignore + } + break; default: _state = State.LITERAL_UTF16_2; b1 = ch; break; } - } - else if (_state == (State.LITERAL_UTF16_2)) { + } else if (_state == (State.LITERAL_UTF16_2)) { // Second byte of a UTF16 character. - /* 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. - */ + /* + * 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 (ch == BACKSLASH) { - ch = readBackslashSequence (false, tok); + ch = readBackslashSequence(false, tok); if (ch == 0) { _state = State.LITERAL_UTF16_2; // skip the wrong char and reset to previous state - continue; /* Invalid character, ignore. */ + continue; /* Invalid character, ignore. */ } } 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 + // a language code. Remove the language code // from the stream and save it in a list of codes. if (utfch == ESC) { - readUTFLanguageCode (tok); - } - else { - buffer.append ((char) utfch); + readUTFLanguageCode(tok); + } else { + buffer.append((char) utfch); } } - _rawBytes.add (ch); + _rawBytes.add(ch); } } - - /** - * Convert the raw hex data.Two buffers are saved: _rawBytes + * 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. + * * @throws edu.harvard.hul.ois.jhove.module.pdf.PdfException */ - public void convertHex () throws PdfException - { + public void convertHex() throws PdfException { if (_rawBytes != null) { boolean utf = false; StringBuilder localBuffer = new StringBuilder(); @@ -327,8 +318,7 @@ public void convertHex () throws PdfException if (utf) { // Gather pairs of bytes into characters without conversion for (int i = 2; i < _rawBytes.size(); i += 2) { - localBuffer.append - ((char) (rawByte(i) * 256 + rawByte(i + 1))); + localBuffer.append((char) (rawByte(i) * 256 + rawByte(i + 1))); } } else { // Convert single bytes to PDF encoded characters. @@ -339,311 +329,306 @@ public void convertHex () throws PdfException _value = localBuffer.toString(); } } - - private static int hexToInt (int c1, int c2) throws PdfException - { - return 16 * hexValue (c1) + hexValue (c2); + + private static int hexToInt(int c1, int c2) throws PdfException { + return 16 * hexValue(c1) + hexValue(c2); } - private static int hexValue (int h) throws PdfException - { + 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) { + } else if (0x41 <= h && h <= 0x46) { // letter A-F d = h - 0x37; - } - else if (0x61 <= h && h <= 0x66) { + } else if (0x61 <= h && h <= 0x66) { // letter a-f d = h - 0x57; - } - else { - throw new PdfMalformedException (MessageConstants.PDF_HUL_11); // PDF-HUL-11 + } else { + throw new PdfMalformedException(MessageConstants.PDF_HUL_11); // PDF-HUL-11 } 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 ()) { + /* + * 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 _rawBytes.elementAt(idx); } - /** - * Returns true if this string is in PDFDocEncoding, - * false if UTF-16. + * Returns true if this string is in PDFDocEncoding, + * false if UTF-16. * - * @return isPdfDocEncoding + * @return isPdfDocEncoding */ - public boolean isPDFDocEncoding () - { + public boolean isPDFDocEncoding() { return _pdfDocEncoding; } /** - * Sets the value of pDFDocEncoding. - * @param pdfDocEncoding: boolean if the is in PDFDocEncoding + * Sets the value of pDFDocEncoding. + * + * @param pdfDocEncoding: boolean if the is in PDFDocEncoding */ - public void setPDFDocEncoding (boolean 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. + * 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. * * @return if it's a Date */ - public boolean isDate () - { - return parseDate () != null; + public boolean isDate() { + try { + return parseDate() != null; + } catch (PdfInvalidException e) { + return false; + } } /** - * 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. + * 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. * - * @return date of string value + * @return date of string value */ - public Date parseDate () - { + public Date parseDate() throws PdfInvalidException { + String str = this.getValue(); + if (str == null) { + return null; + } + return parseDate(str); + } + + static final Date parseDate(String dateString) throws PdfInvalidException { + String str = dateString.trim(); + if (str.length() < 4) { + throw new PdfInvalidException(MessageConstants.PDF_HUL_133, 0); // PDF-HUL-133 + } int year = 0; int month = 0; int day = 0; int hour = 0; int minute = 0; int second = 0; - char timezonechar = '?'; // +, -, or Z + char timezonechar = '?'; // +, -, or Z int timezonehour = 0; int timezoneminute = 0; - Calendar cal; - - 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; + 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; - } + 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) { + throw new PdfInvalidException(MessageConstants.PDF_HUL_133, 0); // PDF-HUL-133 + } + 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 != '-') { + throw new PdfInvalidException(MessageConstants.PDF_HUL_133, 0); // PDF-HUL-133 + } + 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("'")) { + throw new PdfInvalidException(MessageConstants.PDF_HUL_133, 0); // PDF-HUL-133 + } + 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.length() > 22) && (!str.substring(charidx + 2, charidx + 3).equals("'"))) { + throw new PdfInvalidException(MessageConstants.PDF_HUL_133, 0); // PDF-HUL-133 + } + } + break wloop; } - 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. + // end of the date. This is too permissive; an exception means + // that the date is not well-formed. catch (Exception e) { - return null; + throw new PdfInvalidException(MessageConstants.PDF_HUL_133, 0); // PDF-HUL-133 } if (datestate < 2) { - return null; // not enough fields + throw new PdfInvalidException(MessageConstants.PDF_HUL_133, 0); // PDF-HUL-133 } + return createTzDate(year, month, day, hour, minute, second, timezonehour, timezoneminute, timezonechar); + } + + static Date createTzDate(final int year, final int month, final int day, final int hour, final int minute, + final int second, final int timezonehour, final int timezoneminute, final char timezonechar) { + Calendar cal; // 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 { + } else { tzStr += timezonechar; - NumberFormat nfmt = NumberFormat.getInstance (); - nfmt.setMinimumIntegerDigits (2); - nfmt.setMaximumIntegerDigits (2); - tzStr += nfmt.format (timezonehour); - tzStr += nfmt.format (timezoneminute); + NumberFormat nfmt = NumberFormat.getInstance(); + nfmt.setMinimumIntegerDigits(2); + nfmt.setMaximumIntegerDigits(2); + tzStr += nfmt.format(timezonehour); + tzStr += nfmt.format(timezoneminute); } - TimeZone tz = TimeZone.getTimeZone (tzStr); - + 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 { + cal = Calendar.getInstance(tz); + } else { // time zone is unspecified - cal = Calendar.getInstance (); + cal = Calendar.getInstance(); } - cal.set (year, month - 1, day, hour, minute, second); - return cal.getTime (); + 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. + /** + * Returns true if this token doesn't violate any + * PDF/A rules, false if it does. + * * @return if it's PDF/A compliant */ - public boolean isPDFACompliant () - { + public boolean isPDFACompliant() { return _pdfACompliant; } - - -/* private void beginBackslashState () - { - octalBufLen = 0; - backslashFlag = true; - } -*/ - + /* + * 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. + /** + * 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); + 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. + // 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); + 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 + } 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 + case 0X6E: // n return LF; - case 0X72: // r + case 0X72: // r return CR; case 0xd: // this is an error for CR - return 0; - case 0X74: // t + return 0; + case 0X74: // t return HT; - case 0X62: // b + case 0X62: // b return BS; - case 0X66: // f + case 0X66: // f return FORMFEED; case OPEN_PARENTHESIS: return OPEN_PARENTHESIS; @@ -656,102 +641,103 @@ private int readBackslashSequence (boolean utf16, Tokenizer tok) } } - - /** 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. + /** + * 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 static void readUTFLanguageCode (Tokenizer tok) throws IOException - { + private static void readUTFLanguageCode(Tokenizer tok) throws IOException { StringBuilder sb = new StringBuilder(); for (;;) { int ch = tok.readChar1(true); - + // 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 (MessageConstants.PDF_HUL_10.getMessage()); // PDF-HUL-10 + throw new EOFException(MessageConstants.PDF_HUL_10.getMessage()); // PDF-HUL-10 } if (ch == ESC) { break; } - sb.append ((char) ch); + sb.append((char) ch); } - tok.addLanguageCode (sb.toString ()); // ****add this to Tokenizer - //_languageCodes.add (sb.toString ()); + 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. + /** + * 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 (); - } - } - } - */ + /* + * 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-modules/pdf-hul/src/test/java/edu/harvard/hul/ois/jhove/module/pdf/LiteralTests.java b/jhove-modules/pdf-hul/src/test/java/edu/harvard/hul/ois/jhove/module/pdf/LiteralTests.java new file mode 100644 index 000000000..339113f86 --- /dev/null +++ b/jhove-modules/pdf-hul/src/test/java/edu/harvard/hul/ois/jhove/module/pdf/LiteralTests.java @@ -0,0 +1,36 @@ +package edu.harvard.hul.ois.jhove.module.pdf; + +import java.net.URISyntaxException; + +import org.junit.Test; + +import edu.harvard.hul.ois.jhove.RepInfo; + +/** + * Tests for the {@link Literal} class. + * + * @author Carl Wilson + * carlwilson AT github + * @version 0.1 Created 13 Mar 2018:11:28:10 + */ + +public class LiteralTests { + /** + * Test method for {@link Literal}. + * Ensures that a valid Date passes. + * + * @throws PdfInvalidException + */ + @Test + public final void testValidDates() throws PdfInvalidException { + Literal.parseDate("D:20180313112810Z"); + Literal.parseDate("D:20180313112810+01'00'"); + Literal.parseDate("D:20180313112810+01'00"); + } + + @Test(expected = PdfInvalidException.class) + public final void testInvalidDate() throws PdfInvalidException { + Literal.parseDate("D:20180313112810+01'00`"); + } + +} From 9c6855f6c901d7be2ab4c3cb3f810e45f8ff312a Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Thu, 15 Aug 2024 11:54:29 +0100 Subject: [PATCH 19/26] FIX: Added assertions for tests. --- .../hul/ois/jhove/module/pdf/LiteralTests.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/jhove-modules/pdf-hul/src/test/java/edu/harvard/hul/ois/jhove/module/pdf/LiteralTests.java b/jhove-modules/pdf-hul/src/test/java/edu/harvard/hul/ois/jhove/module/pdf/LiteralTests.java index 339113f86..820ac0b19 100644 --- a/jhove-modules/pdf-hul/src/test/java/edu/harvard/hul/ois/jhove/module/pdf/LiteralTests.java +++ b/jhove-modules/pdf-hul/src/test/java/edu/harvard/hul/ois/jhove/module/pdf/LiteralTests.java @@ -1,10 +1,12 @@ package edu.harvard.hul.ois.jhove.module.pdf; -import java.net.URISyntaxException; +import static org.junit.Assert.assertNotNull; -import org.junit.Test; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; -import edu.harvard.hul.ois.jhove.RepInfo; +import org.junit.Test; /** * Tests for the {@link Literal} class. @@ -23,9 +25,9 @@ public class LiteralTests { */ @Test public final void testValidDates() throws PdfInvalidException { - Literal.parseDate("D:20180313112810Z"); - Literal.parseDate("D:20180313112810+01'00'"); - Literal.parseDate("D:20180313112810+01'00"); + assertNotNull(Literal.parseDate("D:20180313112810Z")); + assertNotNull(Literal.parseDate("D:20180313112810+01'00'")); + assertNotNull(Literal.parseDate("D:20180313112810+01'00")); } @Test(expected = PdfInvalidException.class) From 7a2efd2aa098a4af04f3a1865db297a0a5efd093 Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Mon, 19 Aug 2024 15:59:26 +0100 Subject: [PATCH 20/26] FIX: Revert XML module reporting style Closes #922 --- .../jhove/module/xml/ErrorMessages_de.properties | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 jhove-modules/xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml/ErrorMessages_de.properties diff --git a/jhove-modules/xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml/ErrorMessages_de.properties b/jhove-modules/xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml/ErrorMessages_de.properties new file mode 100644 index 000000000..bf222c7d2 --- /dev/null +++ b/jhove-modules/xml-hul/src/main/resources/edu/harvard/hul/ois/jhove/module/xml/ErrorMessages_de.properties @@ -0,0 +1,14 @@ +XML-HUL-1 = SAXParseException +XML-HUL-1-SUB = {0} Zeile = {1,number,integer}, Spalte = {2,number,integer}. +XML-HUL-2 = Maximalanzahl {0,number,integer} für Fehlermeldungen erreicht. Weitere Fehler werden nicht mehr angezeigt. +XML-HUL-3 = SAXException: {0} +XML-HUL-4 = Typ des Zeilenendes konnte nicht erkannt werden. +XML-HUL-5 = LexicalHandler-Interface wird durch die XML-Implementierung nicht unterstützt. Dadurch werden eventuell einige Eigenschaften nicht angezeigt. +XML-HUL-6 = DeclHandler-Interface wird durch die XML-Implementierung nicht unterstützt. Dadurch werden eventuell einige Eigenschaften nicht angezeigt. +XML-HUL-7 = Dieser SAX-Parser unterstützt keine XML-Namespaces. +XML-HUL-8 = Dieser SAX-Parser unterstützt keine Validierung. +XML-HUL-9 = Die XML-Implementierung unterstützt keine Identifizierung des XML-Schemas. Dadurch können Dokumente mit XML-Schema als ungültig dargestellt werden. +XML-HUL-10 = Datei nicht gefunden. +XML-HUL-11 = Ungültige Zeichenkodierung. +XML-HUL-12 = Grund für SAXException: {0} +XML-HUL-13 = Kein Grund für SAXException gemeldet. From 7cf576e3e9e75bba2af07ae3c3d2d3bd914b8f32 Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Tue, 20 Aug 2024 17:08:00 +0100 Subject: [PATCH 21/26] FEAT: Updated audio outputs to conform to the AES57-2011 spec Copied from #357 contributed by @pwinckles, thanks! Apologies the PR was old enough to make merging difficult. My only contribution was the JSON handler output. The AES output that JHOVE currently generates does not conform to the AES57-2011 spec. The schema can be found [here](https://www.loc.gov/standards/amdvmd/audiovideoMDschemas.html). This commit updates JHOVE to produce a valid AES57-2011 document. The bulk of the changes are related to how times and durations are reported. The original code generated a structure like this: ```xml 0 0 12 29 121 ``` The same information is now represented like this: ```xml 571951 ``` Another change affects the "channelAssignment" element. This element does not have a "mapLocation" attribute, as the current code adds. Instead, AES57-2011 uses two new attributes "leftRightPostion" and "frontRearPosition". Detailed descriptions of this attributes can be found on [this page](https://rucore.libraries.rutgers.edu/open/projects/openwms/index.php?sec=guides&sub=metadata&pg=t_sound-qual). I could not determine a way to map all of the possible JHOVE "mapLocations" to the new AES attributes (for example, what does "SURROUND" mean?), so I decided to leave them out rather than include potentially inaccurate data. (As an aside, I suspect that the way that JHOVE is currently mapping channel location for 4-channel AIFF audio is incorrect. [Per spec](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/AIFF/Docs/AIFF-1.3.pdf), it should be LEFT, CENTER, RIGHT, SURROUND. JHOVE maps it as LEFT, RIGHT, CENTER, SURROUND.) Fixes #362 #367 Closes #357 --- .../hul/ois/jhove/viewer/RepTreeRoot.java | 2914 ++++++++--------- jhove-bbt/scripts/create-1.31-target.sh | 5 + .../hul/ois/jhove/AESAudioMetadata.java | 1009 +++--- .../hul/ois/jhove/handler/JsonHandler.java | 78 +- .../hul/ois/jhove/handler/TextHandler.java | 78 +- .../hul/ois/jhove/handler/XmlHandler.java | 60 +- .../ois/jhove/handler/JsonHandlerTest.java | 799 +++-- 7 files changed, 2383 insertions(+), 2560 deletions(-) diff --git a/jhove-apps/src/main/java/edu/harvard/hul/ois/jhove/viewer/RepTreeRoot.java b/jhove-apps/src/main/java/edu/harvard/hul/ois/jhove/viewer/RepTreeRoot.java index 5610772a4..9fd303a60 100644 --- a/jhove-apps/src/main/java/edu/harvard/hul/ois/jhove/viewer/RepTreeRoot.java +++ b/jhove-apps/src/main/java/edu/harvard/hul/ois/jhove/viewer/RepTreeRoot.java @@ -37,48 +37,48 @@ */ public class RepTreeRoot extends DefaultMutableTreeNode { - /** - * Serialisation identifier - */ - private static final long serialVersionUID = -4409152022584715925L; - private RepInfo _info; - private JhoveBase _base; - private boolean _rawOutput; - private DateFormat _dateFmt; - - /* Sample rate. */ - private double _sampleRate; - - /** - * Constructor. - * - * @param info - * The RepInfo object whose contents are to be displayed. - * @param base - * The JHOVE base on which we're operating. - */ - public RepTreeRoot(RepInfo info, JhoveBase base) { - super(info.getUri()); - _info = info; - _base = base; - _rawOutput = _base.getShowRawFlag(); - - // Set the DateFormat for displaying the module date. - _dateFmt = DateFormat.getDateInstance(); - - // Snarf everything up into the tree. - - snarfRepInfo(); - } - - private static final String TEXT_MD_METADTA = "TextMDMetadata"; - private static final String FORMAT = "Format: "; - private static final String VERSION = "Version: "; - private static final String BYTE_ORDER = "ByteOrder: "; - private static final String FRAME_COUNT_30 = "FrameCount: 30"; - private static final String TIME_BASE_1000 = "TimeBase: 1000"; - private static final String VIDEO_FIELD_FIELD_1 = "VideoField: FIELD_1"; - private static final String COUNTING_MODE_NTSC_NON_DROP_FRAME= "CountingMode: NTSC_NON_DROP_FRAME"; + /** + * Serialisation identifier + */ + private static final long serialVersionUID = -4409152022584715925L; + private RepInfo _info; + private JhoveBase _base; + private boolean _rawOutput; + private DateFormat _dateFmt; + + /* Sample rate. */ + private double _sampleRate; + + /** + * Constructor. + * + * @param info + * The RepInfo object whose contents are to be displayed. + * @param base + * The JHOVE base on which we're operating. + */ + public RepTreeRoot(RepInfo info, JhoveBase base) { + super(info.getUri()); + _info = info; + _base = base; + _rawOutput = _base.getShowRawFlag(); + + // Set the DateFormat for displaying the module date. + _dateFmt = DateFormat.getDateInstance(); + + // Snarf everything up into the tree. + + snarfRepInfo(); + } + + private static final String TEXT_MD_METADTA = "TextMDMetadata"; + private static final String FORMAT = "Format: "; + private static final String VERSION = "Version: "; + private static final String BYTE_ORDER = "ByteOrder: "; + private static final String FRAME_COUNT_30 = "FrameCount: 30"; + private static final String TIME_BASE_1000 = "TimeBase: 1000"; + private static final String VIDEO_FIELD_FIELD_1 = "VideoField: FIELD_1"; + private static final String COUNTING_MODE_NTSC_NON_DROP_FRAME = "CountingMode: NTSC_NON_DROP_FRAME"; private static final String HOURS = "Hours: "; private static final String MINUTES = "Minutes: "; private static final String SECONDS = "Seconds: "; @@ -88,1442 +88,1398 @@ public RepTreeRoot(RepInfo info, JhoveBase base) { private static final String FILM_FRAMING = "FilmFraming"; private static final String FRAMING_NOT_APPLICABLE = "Framing: NOT_APPLICABLE"; private static final String NTSC_FILM_FRAMING_TYPE = "Type: ntscFilmFramingType"; - - - - /** - * Constructs a DefaultMutableTreeNode representing a property - */ - private DefaultMutableTreeNode propToNode(Property pProp) { - PropertyArity arity = pProp.getArity(); - PropertyType typ = pProp.getType(); - Object pValue = pProp.getValue(); - if (arity == PropertyArity.SCALAR) { - if (null == typ) { - // Simple types: just use name plus string value. - DefaultMutableTreeNode val = new DefaultMutableTreeNode( - pProp.getName() + ": " + pValue.toString()); - return val; - } else { - TextMDMetadata tData; - DefaultMutableTreeNode val; - switch (typ) { - case NISOIMAGEMETADATA: - // NISO Image metadata is a world of its own. - NisoImageMetadata nData = (NisoImageMetadata) pValue; - return nisoToNode(nData); - case AESAUDIOMETADATA: - // AES audio metadata is another world. - AESAudioMetadata aData = (AESAudioMetadata) pValue; - return aesToNode(aData); - case TEXTMDMETADATA: - // textMD metadata is another world. - tData = (TextMDMetadata) pValue; - return textMDToNode(tData); - case PROPERTY: - - if (TEXT_MD_METADTA.equals(pProp.getName())) { - tData = (TextMDMetadata) pValue; - return textMDToNode(tData); - } - // A scalar property of type Property -- seems - // pointless, but we handle it. - val = new DefaultMutableTreeNode(pProp.getName()); - val.add(propToNode((Property) pValue)); - return val; - - default: - - // Simple types: just use name plus string value. - val = new DefaultMutableTreeNode(pProp.getName() + ": " - + pValue.toString()); - return val; - - } - } - } - // Compound properties. The text of the node is the - // property name. - DefaultMutableTreeNode val = new DefaultMutableTreeNode(pProp.getName()); - if (null != arity) - switch (arity) { - case ARRAY: - addArrayMembers(val, pProp); - break; - case LIST: - addListMembers(val, pProp); - break; - case MAP: - addMapMembers(val, pProp); - break; - case SET: - addSetMembers(val, pProp); - break; - default: - break; - } - return val; - } - - /** - * Find the index of an object in its parent. Understands the Jhove property - * structure. - */ - public int getIndexOfChild(Object parent, Object child) { - Property pProp = (Property) parent; - PropertyArity arity = pProp.getArity(); - // For Lists, Maps, and Sets we construct an Iterator. - Iterator iter = null; - if (arity == PropertyArity.SET || arity == PropertyArity.LIST - || arity == PropertyArity.MAP) { - if (null == arity) { - List list = (List) pProp.getValue(); - iter = list.iterator(); - } else - switch (arity) { - case SET: - Set set = (Set) pProp.getValue(); - iter = set.iterator(); - break; - case MAP: - Map map = (Map) pProp.getValue(); - iter = map.values().iterator(); - break; - default: - List list = (List) pProp.getValue(); - iter = list.iterator(); - break; - } - for (int i = 0;; i++) { - if (!iter.hasNext()) { - return 0; // Should never happen - } else if (iter.next() == child) { - return i; - } - } - } - // OK, that was the easy one. Now for that damn array arity. - // In the case of non-object types, we can't actually tell which - // position matches the object, so we return 0 and hope it doesn't - // mess things up too much. - PropertyType propType = pProp.getType(); - java.util.Date[] dateArray = null; - Property[] propArray = null; - Rational[] rationalArray = null; - Object[] objArray = null; - int n = 0; - - if (null == propType) { - return 0; // non-object array type - } else - // if (child instanceof LeafHolder) { - // return ((LeafHolder) child).getPosition (); - // } - // else - switch (propType) { - case DATE: - dateArray = (java.util.Date[]) pProp.getValue(); - n = dateArray.length; - break; - case OBJECT: - objArray = (Object[]) pProp.getValue(); - n = objArray.length; - break; - case RATIONAL: - rationalArray = (Rational[]) pProp.getValue(); - n = rationalArray.length; - break; - case PROPERTY: - propArray = (Property[]) pProp.getValue(); - n = propArray.length; - break; - default: - return 0; // non-object array type - } - - for (int i = 0; i < n; i++) { - Object elem = null; - switch (propType) { - case DATE: - elem = dateArray[i]; - break; - case OBJECT: - elem = objArray[i]; - break; - case RATIONAL: - elem = rationalArray[i]; - break; - case PROPERTY: - elem = propArray[i]; - break; - default: - break; - } - if (elem == child) { - return i; - } - } - return 0; // somehow fell through - } - - private void snarfRepInfo() { - // This node has two children, for the module and the RepInfo - - Module module = _info.getModule(); - if (module != null) { - // Create a subnode for the module, which has three - // leaf children. - DefaultMutableTreeNode moduleNode = new DefaultMutableTreeNode( - "Module"); - moduleNode.add(new DefaultMutableTreeNode(module.getName(), false)); - moduleNode.add(new DefaultMutableTreeNode("Release: " - + module.getRelease(), false)); - moduleNode.add(new DefaultMutableTreeNode("Date: " - + _dateFmt.format(module.getDate()), false)); - add(moduleNode); - } - - DefaultMutableTreeNode infoNode = new DefaultMutableTreeNode("RepInfo"); - infoNode.add(new DefaultMutableTreeNode("URI: " + _info.getUri(), false)); - Date dt = _info.getCreated(); - if (dt != null) { - infoNode.add(new DefaultMutableTreeNode( - "Created: " + dt.toString(), false)); - } - dt = _info.getLastModified(); - if (dt != null) { - infoNode.add(new DefaultMutableTreeNode("LastModified: " - + dt.toString(), false)); - } - long sz = _info.getSize(); - if (sz != -1) { - infoNode.add(new DefaultMutableTreeNode("Size: " - + Long.toString(sz), false)); - } - String s = _info.getFormat(); - if (s != null) { - infoNode.add(new DefaultMutableTreeNode(FORMAT + s, false)); - } - s = _info.getVersion(); - if (s != null) { - infoNode.add(new DefaultMutableTreeNode(VERSION + s, false)); - } - String wfStr; - switch (_info.getWellFormed()) { - case RepInfo.TRUE: - wfStr = "Well-Formed"; - break; - case RepInfo.FALSE: - wfStr = "Not well-formed"; - break; - default: - wfStr = "Unknown"; - break; - } - 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 - } - } - infoNode.add(new DefaultMutableTreeNode("Status: " + wfStr, false)); - - // Report modules that said their signatures match - List sigList = _info.getSigMatch(); - if (sigList != null && sigList.size() > 0) { - DefaultMutableTreeNode sigNode = new DefaultMutableTreeNode( - "SignatureMatches"); - infoNode.add(sigNode); - for (int i = 0; i < sigList.size(); i++) { - DefaultMutableTreeNode sNode = new DefaultMutableTreeNode( - sigList.get(i)); - sigNode.add(sNode); - } - } - // Compile a list of messages and offsets into a subtree - List messageList = _info.getMessage(); - if (messageList != null && messageList.size() > 0) { - DefaultMutableTreeNode msgNode = new DefaultMutableTreeNode( - "Messages"); - infoNode.add(msgNode); - int i; - for (i = 0; i < messageList.size(); i++) { - Message msg = messageList.get(i); - String prefix; - if (msg instanceof InfoMessage) { - prefix = "InfoMessage: "; - } else if (msg instanceof ErrorMessage) { - prefix = "ErrorMessage: "; - } else { - prefix = "Message: "; - } - DefaultMutableTreeNode mNode = new DefaultMutableTreeNode( - prefix + msg.getMessage()); - - if (msg.getId() != null && !msg.getId().isEmpty()) { - mNode.add(new DefaultMutableTreeNode("ID: " - + msg.getId())); - } - - String subMessage = msg.getSubMessage(); - if (subMessage != null) { - mNode.add(new DefaultMutableTreeNode("SubMessage: " - + subMessage)); - } - long offset = -1; - if (msg instanceof ErrorMessage) { - offset = ((ErrorMessage) msg).getOffset(); - } - // - // If the offset is positive, we give the message node - // a child with the offset value. - if (offset >= 0) { - mNode.add(new DefaultMutableTreeNode("Offset: " - + Long.toString(offset))); - } - msgNode.add(mNode); - } - } - - s = _info.getMimeType(); - if (s != null) { - infoNode.add(new DefaultMutableTreeNode("MimeType: " + s, false)); - } - - // Compile a list of profile strings into a string list - List profileList = _info.getProfile(); - if (profileList != null && profileList.size() > 0) { - DefaultMutableTreeNode profNode = new DefaultMutableTreeNode( - "Profiles"); - infoNode.add(profNode); - int i; - for (i = 0; i < profileList.size(); i++) { - profNode.add(new DefaultMutableTreeNode(profileList.get(i), - false)); - } - } - - // Here we come to the property map. We have to walk - // through all the properties recursively, turning - // each into a leaf or subtree. - Map map = _info.getProperty(); - if (map != null) { - Iterator iter = map.keySet().iterator(); - while (iter.hasNext()) { - String key = iter.next(); - Property property = _info.getProperty(key); - infoNode.add(propToNode(property)); - } - } - - List cksumList = _info.getChecksum(); - if (cksumList != null && !cksumList.isEmpty()) { - DefaultMutableTreeNode ckNode = new DefaultMutableTreeNode( - "Checksums"); - infoNode.add(ckNode); - // List cPropList = new LinkedList (); - for (Checksum cksum : cksumList) { - DefaultMutableTreeNode csNode = new DefaultMutableTreeNode( - "Checksum"); - ckNode.add(csNode); - csNode.add(new DefaultMutableTreeNode("Type:" - + cksum.getType().toString(), false)); - csNode.add(new DefaultMutableTreeNode("Checksum: " - + cksum.getValue(), false)); - } - } - - s = _info.getNote(); - if (s != null) { - infoNode.add(new DefaultMutableTreeNode("Note: " + s, false)); - } - add(infoNode); - } - - /* - * Add the members of an array property to a node. The property must be of - * arity ARRAY. - */ - private void addArrayMembers(DefaultMutableTreeNode node, Property p) { - Object pVal = p.getValue(); - PropertyType typ = p.getType(); - if (null != typ) - switch (typ) { - case INTEGER: { - if (pVal instanceof int[]) { - Integer[] vals = Arrays.stream((int[])pVal) // IntStream - .boxed() // Stream - .toArray(Integer[]::new); - addToNode(node, vals); - } else { - addToNode(node, (Integer[]) pVal); - } - break; - } - case LONG: { - if (pVal instanceof long[]) { - Long[] vals = Arrays.stream((long[])pVal) // IntStream - .boxed() // Stream - .toArray(Long[]::new); - addToNode(node, vals); - } else { - addToNode(node, (Long[]) pVal); - } - break; - } - case BOOLEAN: { - addToNode(node, (Boolean[]) pVal); - break; - } - case CHARACTER: { - addToNode(node, (Character[]) pVal); - break; - } - case DOUBLE: { - if (pVal instanceof double[]) { - Double[] vals = Arrays.stream((double[])pVal) // IntStream - .boxed() // Stream - .toArray(Double[]::new); - addToNode(node, vals); - } else { - addToNode(node, (Double[]) pVal); - } - break; - } - case FLOAT: { - addToNode(node, (Float[]) pVal); - break; - } - case SHORT: { - addToNode(node, (Short[]) pVal); - break; - } - case BYTE: { - addToNode(node, (Byte[]) pVal); - break; - } - case STRING: { - addToNode(node, (String[]) pVal); - break; - } - case RATIONAL: { - addToNode(node, (Rational[]) pVal); - break; - } - case PROPERTY: { - addToNode(node, (Property[]) pVal); - break; - } - case NISOIMAGEMETADATA: { - addToNode(node, (NisoImageMetadata[]) pVal); - break; - } - case OBJECT: { - addToNode(node, (Object[]) pVal); - break; - } - default: - break; - } - } - - /* - * Add the members of a list property to a node. The property must be of - * arity LIST. - */ - private void addListMembers(DefaultMutableTreeNode node, Property p) { - List l = (List) p.getValue(); - PropertyType ptyp = p.getType(); - boolean canHaveChildren = Boolean.FALSE; - l.forEach(item -> node.add(getDefaultMutableTreeNode(ptyp, item, - canHaveChildren))); - } - - /* - * Add the members of a set property to a node. The property must be of - * arity SET. - */ - private void addSetMembers(DefaultMutableTreeNode node, Property p) { - Set s = (Set) p.getValue(); - PropertyType ptyp = p.getType(); - boolean canHaveChildren = Boolean.FALSE; - Iterator iter = s.iterator(); - while (iter.hasNext()) { - Object item = iter.next(); - node.add(getDefaultMutableTreeNode(ptyp, item, canHaveChildren)); - } - } - - /* - * Add the members of a map property to a node. The property must be of - * arity MAP. - */ - private void addMapMembers(DefaultMutableTreeNode node, Property p) { - Map m = (Map) p.getValue(); - PropertyType ptyp = p.getType(); - Boolean canHaveChildren = Boolean.TRUE; - // Iterator iter = m.values ().iterator (); - Iterator iter = m.keySet().iterator(); - while (iter.hasNext()) { - DefaultMutableTreeNode itemNode; - String key = (String) iter.next(); - Object item = m.get(key); - itemNode = getDefaultMutableTreeNode(ptyp, item, canHaveChildren); - node.add(itemNode); - - // Add a subnode for the key - itemNode.setAllowsChildren(true); - itemNode.add(new DefaultMutableTreeNode("Key: " + key, false)); - } - } - - /* Function for turning the AES metadata into a subtree. */ - private DefaultMutableTreeNode aesToNode(AESAudioMetadata aes) { - _sampleRate = aes.getSampleRate(); - - DefaultMutableTreeNode val = new DefaultMutableTreeNode( - "AESAudioMetadata", true); - String s = aes.getAnalogDigitalFlag(); - if (s != null) { - val.add(new DefaultMutableTreeNode("AnalogDigitalFlag: " + s, false)); - // The "false" argument signifies this will have no subnodes - } - s = aes.getSchemaVersion(); - if (s != null) { - val.add(new DefaultMutableTreeNode("SchemaVersion: " + s, false)); - } - s = aes.getFormat(); - if (s != null) { - DefaultMutableTreeNode fmt = new DefaultMutableTreeNode(FORMAT - + s, true); - val.add(fmt); - String v = aes.getSpecificationVersion(); - if (v != null) { - fmt.add(new DefaultMutableTreeNode( - "SpecificationVersion: " + v, false)); - } - } - s = aes.getAppSpecificData(); - if (s != null) { - val.add(new DefaultMutableTreeNode("AppSpecificData: " + s, false)); - } - s = aes.getAudioDataEncoding(); - if (s != null) { - val.add(new DefaultMutableTreeNode("AudioDataEncoding: " + s, false)); - } - int in = aes.getByteOrder(); - if (in != AESAudioMetadata.NULL) { - val.add(new DefaultMutableTreeNode(BYTE_ORDER - + (in == AESAudioMetadata.BIG_ENDIAN ? "BIG_ENDIAN" - : "LITTLE_ENDIAN"))); - } - long lin = aes.getFirstSampleOffset(); - if (lin != AESAudioMetadata.NULL) { - val.add(new DefaultMutableTreeNode("FirstSampleOffset: " - + Long.toString(lin))); - } - String[] use = aes.getUse(); - if (use != null) { - DefaultMutableTreeNode u = new DefaultMutableTreeNode("Use", true); - val.add(u); - u.add(new DefaultMutableTreeNode("UseType: " + use[0], false)); - u.add(new DefaultMutableTreeNode("OtherType: " + use[1], false)); - } - s = aes.getPrimaryIdentifier(); - if (s != null) { - String t = aes.getPrimaryIdentifierType(); - DefaultMutableTreeNode pi = new DefaultMutableTreeNode( - "PrimaryIdentifier: " + s, true); - val.add(pi); - if (t != null) { - pi.add(new DefaultMutableTreeNode("IdentifierType: " + t)); - } - } - // 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()) { - AESAudioMetadata.Face f = facelist.get(0); - - DefaultMutableTreeNode face = new DefaultMutableTreeNode("Face", - true); - DefaultMutableTreeNode timeline = new DefaultMutableTreeNode( - "TimeLine", true); - AESAudioMetadata.TimeDesc startTime = f.getStartTime(); - if (startTime != null) { - addAESTimeRange(timeline, startTime, f.getDuration()); - } - face.add(timeline); - - // For the present, assume just one face region - AESAudioMetadata.FaceRegion facergn = f.getFaceRegion(0); - DefaultMutableTreeNode region = new DefaultMutableTreeNode( - "Region", true); - timeline = new DefaultMutableTreeNode("TimeRange", true); - addAESTimeRange(timeline, facergn.getStartTime(), - facergn.getDuration()); - region.add(timeline); - int nchan = aes.getNumChannels(); - if (nchan != AESAudioMetadata.NULL) { - String[] locs = aes.getMapLocations(); - region.add(new DefaultMutableTreeNode("NumChannels: " - + Integer.toString(nchan), false)); - for (String loc : locs) { - // write a stream element for each channel - DefaultMutableTreeNode stream = new DefaultMutableTreeNode( - "Stream", true); - region.add(stream); - stream.add(new DefaultMutableTreeNode("ChannelAssignment: " - + loc, false)); - } - } - face.add(region); - val.add(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 = 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) { - DefaultMutableTreeNode formatList = new DefaultMutableTreeNode( - "FormatList", true); - DefaultMutableTreeNode formatRegion = new DefaultMutableTreeNode( - "FormatRegion", true); - if (bitDepth != AESAudioMetadata.NULL) { - formatRegion.add(new DefaultMutableTreeNode("BitDepth: " - + Integer.toString(bitDepth), false)); - } - if (sampleRate != AESAudioMetadata.NILL) { - formatRegion.add(new DefaultMutableTreeNode("SampleRate: " - + Double.toString(sampleRate), false)); - } - if (wordSize != AESAudioMetadata.NULL) { - formatRegion.add(new DefaultMutableTreeNode("WordSize: " - + Integer.toString(bitDepth), false)); - } - if (bitRed != null) { - DefaultMutableTreeNode br = new DefaultMutableTreeNode( - "BitrateReduction", true); - br.add(new DefaultMutableTreeNode( - "codecName: " + bitRed[0], false)); - br.add(new DefaultMutableTreeNode("codecNameVersion: " - + bitRed[1], false)); - br.add(new DefaultMutableTreeNode( - "codecCreatorApplication: " + bitRed[2], false)); - br.add(new DefaultMutableTreeNode( - "codecCreatorApplicationVersion: " + bitRed[3], - false)); - br.add(new DefaultMutableTreeNode("codecQuality: " - + bitRed[4], false)); - br.add(new DefaultMutableTreeNode("dataRate: " + bitRed[5], - false)); - br.add(new DefaultMutableTreeNode("dataRateMode: " - + bitRed[6], false)); - formatRegion.add(br); - } - formatList.add(formatRegion); - val.add(formatList); - } - } - - return val; - } - - private void addAESTimeRange(DefaultMutableTreeNode parent, - AESAudioMetadata.TimeDesc start, AESAudioMetadata.TimeDesc duration) { - // Put the start time in - DefaultMutableTreeNode node = new DefaultMutableTreeNode("Start", true); - // Put in boilerplate to match the AES schema - node.add(new DefaultMutableTreeNode(FRAME_COUNT_30, false)); - node.add(new DefaultMutableTreeNode(TIME_BASE_1000)); - node.add(new DefaultMutableTreeNode(VIDEO_FIELD_FIELD_1)); - node.add(new DefaultMutableTreeNode( - COUNTING_MODE_NTSC_NON_DROP_FRAME, false)); - node.add(new DefaultMutableTreeNode(HOURS + start.getHours(), false)); - node.add(new DefaultMutableTreeNode(MINUTES + start.getMinutes(), - false)); - node.add(new DefaultMutableTreeNode(SECONDS + start.getSeconds(), - false)); - node.add(new DefaultMutableTreeNode(FRAMES + start.getFrames(), - false)); - - // Do samples a bit more elaborately than is really necessary, - // to maintain parallelism with the xml schema. - DefaultMutableTreeNode snode = new DefaultMutableTreeNode(SAMPLES, - true); - double sr = start.getSampleRate(); - if (sr == 1.0) { - sr = _sampleRate; - } - snode.add(new DefaultMutableTreeNode("SampleRate: S" - + Integer.toString((int) sr), false)); - snode.add(new DefaultMutableTreeNode(NUMBER_OF_SAMPLES - + start.getSamples(), false)); - node.add(snode); - - snode = new DefaultMutableTreeNode(FILM_FRAMING, true); - snode.add(new DefaultMutableTreeNode(FRAMING_NOT_APPLICABLE, false)); - snode.add(new DefaultMutableTreeNode(NTSC_FILM_FRAMING_TYPE, false)); - node.add(snode); - parent.add(node); - - // Duration is optional. - if (duration != null) { - node = new DefaultMutableTreeNode("Duration", true); - // Put in boilerplate to match the AES schema - node.add(new DefaultMutableTreeNode(FRAME_COUNT_30, false)); - node.add(new DefaultMutableTreeNode(TIME_BASE_1000)); - node.add(new DefaultMutableTreeNode(VIDEO_FIELD_FIELD_1)); - node.add(new DefaultMutableTreeNode( - COUNTING_MODE_NTSC_NON_DROP_FRAME, false)); - node.add(new DefaultMutableTreeNode( - HOURS + duration.getHours(), false)); - node.add(new DefaultMutableTreeNode(MINUTES - + duration.getMinutes(), false)); - node.add(new DefaultMutableTreeNode(SECONDS - + duration.getSeconds(), false)); - node.add(new DefaultMutableTreeNode(FRAMES - + duration.getFrames(), false)); - - // Do samples a bit more elaborately than is really necessary, - // to maintain parallelism with the xml schema. - snode = new DefaultMutableTreeNode(SAMPLES, true); - sr = duration.getSampleRate(); - if (sr == 1.0) { - sr = _sampleRate; - } - snode.add(new DefaultMutableTreeNode("SamplesRate S" - + Integer.toString((int) sr), false)); - snode.add(new DefaultMutableTreeNode(NUMBER_OF_SAMPLES - + duration.getSamples(), false)); - node.add(snode); - - snode = new DefaultMutableTreeNode(FILM_FRAMING, true); - snode.add(new DefaultMutableTreeNode(FRAMING_NOT_APPLICABLE, - false)); - snode.add(new DefaultMutableTreeNode(NTSC_FILM_FRAMING_TYPE, - false)); - node.add(snode); - parent.add(node); - } - } - - /* Function for turning the textMD metadata into a subtree. */ - private DefaultMutableTreeNode textMDToNode(TextMDMetadata textMD) { - DefaultMutableTreeNode val = new DefaultMutableTreeNode( - TEXT_MD_METADTA, true); - - DefaultMutableTreeNode u = new DefaultMutableTreeNode("Character_info", - true); - val.add(u); - - String s = textMD.getCharset(); - if (s != null) { - u.add(new DefaultMutableTreeNode("Charset: " + s, false)); - } - s = textMD.getByte_orderString(); - if (s != null) { - u.add(new DefaultMutableTreeNode("Byte_order: " + s, false)); - } - s = textMD.getByte_size(); - if (s != null) { - u.add(new DefaultMutableTreeNode("Byte_size: " + s, false)); - } - s = textMD.getCharacter_size(); - if (s != null) { - u.add(new DefaultMutableTreeNode("Character_size: " + s, false)); - } - s = textMD.getLinebreakString(); - if (s != null) { - u.add(new DefaultMutableTreeNode("Linebreak: " + s, false)); - } - s = textMD.getLanguage(); - if (s != null) { - val.add(new DefaultMutableTreeNode("Language: " + s, false)); - } - s = textMD.getMarkup_basis(); - if (s != null) { - DefaultMutableTreeNode basis = new DefaultMutableTreeNode( - "Markup_basis: " + s, true); - val.add(basis); - s = textMD.getMarkup_basis_version(); - if (s != null) { - basis.add(new DefaultMutableTreeNode(VERSION + s, false)); - } - } - s = textMD.getMarkup_language(); - if (s != null) { - DefaultMutableTreeNode language = new DefaultMutableTreeNode( - "Markup_language: " + s, true); - val.add(language); - s = textMD.getMarkup_language_version(); - if (s != null) { - language.add(new DefaultMutableTreeNode(VERSION + s, false)); - } - } - return val; - } - - /* Function for turning the Niso metadata into a subtree. */ - private DefaultMutableTreeNode nisoToNode(NisoImageMetadata niso) { - DefaultMutableTreeNode val = new DefaultMutableTreeNode( - "NisoImageMetadata", true); - String s = niso.getMimeType(); - if (s != null) { - val.add(new DefaultMutableTreeNode("FormatName: " + s, false)); - } - s = niso.getByteOrder(); - if (s != null) { - val.add(new DefaultMutableTreeNode(BYTE_ORDER + s, false)); - } - - int n = niso.getCompressionScheme(); - if (n != NisoImageMetadata.NULL) { - val.add(new DefaultMutableTreeNode("CompressionScheme: " - + integerRepresentation(n, - NisoImageMetadata.COMPRESSION_SCHEME, - NisoImageMetadata.COMPRESSION_SCHEME_INDEX), false)); - } - if ((n = niso.getCompressionLevel()) != NisoImageMetadata.NULL) { - val.add(new DefaultMutableTreeNode("CompressionLevel: " - + Integer.toString(n), false)); - } - if ((n = niso.getColorSpace()) != NisoImageMetadata.NULL) { - val.add(new DefaultMutableTreeNode("ColorSpace: " - + integerRepresentation(n, NisoImageMetadata.COLORSPACE, - NisoImageMetadata.COLORSPACE_INDEX), false)); - } - if ((s = niso.getProfileName()) != null) { - val.add(new DefaultMutableTreeNode("ProfileName: " + s, false)); - } - if ((s = niso.getProfileURL()) != null) { - val.add(new DefaultMutableTreeNode("ProfileURL: " + s, false)); - } - int[] iarray = niso.getYCbCrSubSampling(); - if (iarray != null) { - DefaultMutableTreeNode nod = (new DefaultMutableTreeNode( - "YCbCrSubSampling")); - val.add(nod); - for (int i = 0; i < iarray.length; i++) { - nod.add(new DefaultMutableTreeNode(Integer.toString(iarray[i]), - false)); - } - } - if ((n = niso.getYCbCrPositioning()) != NisoImageMetadata.NULL) { - val.add(new DefaultMutableTreeNode("YCbCrPositioning: " - + integerRepresentation(n, - NisoImageMetadata.YCBCR_POSITIONING), false)); - } - Rational[] rarray = niso.getYCbCrCoefficients(); - if (rarray != null) { - DefaultMutableTreeNode nod = (new DefaultMutableTreeNode( - "YCbCrCoefficients", true)); - val.add(nod); - for (int i = 0; i < rarray.length; i++) { - nod.add(new DefaultMutableTreeNode(rarray[i].toString(), false)); - } - } - rarray = niso.getReferenceBlackWhite(); - if (rarray != null) { - DefaultMutableTreeNode nod = (new DefaultMutableTreeNode( - "ReferenceBlackWhite", true)); - val.add(nod); - for (int i = 0; i < rarray.length; i++) { - nod.add(new DefaultMutableTreeNode(rarray[i].toString(), false)); - } - } - if ((n = niso.getSegmentType()) != NisoImageMetadata.NULL) { - val.add(new DefaultMutableTreeNode("YSegmentType: " - + integerRepresentation(n, NisoImageMetadata.SEGMENT_TYPE), - false)); - } - long[] larray = niso.getStripOffsets(); - if (larray != null) { - DefaultMutableTreeNode nod = (new DefaultMutableTreeNode( - "StripOffsets", true)); - val.add(nod); - for (int i = 0; i < larray.length; i++) { - nod.add(new DefaultMutableTreeNode(Long.toString(larray[i]), - false)); - } - } - long ln = niso.getRowsPerStrip(); - if (ln != NisoImageMetadata.NULL) { - val.add(new DefaultMutableTreeNode("RowsPerStrip: " - + Long.toString(ln), false)); - } - if ((larray = niso.getStripByteCounts()) != null) { - DefaultMutableTreeNode nod = (new DefaultMutableTreeNode( - "StripByteCounts", true)); - val.add(nod); - for (int i = 0; i < larray.length; i++) { - nod.add(new DefaultMutableTreeNode(Long.toString(larray[i]), - false)); - } - } - if ((ln = niso.getTileWidth()) != NisoImageMetadata.NULL) { - val.add(new DefaultMutableTreeNode("TileWidth: " - + Long.toString(ln))); - } - if ((ln = niso.getTileLength()) != NisoImageMetadata.NULL) { - val.add(new DefaultMutableTreeNode("TileLength: " - + Long.toString(ln))); - } - if ((larray = niso.getTileOffsets()) != null) { - DefaultMutableTreeNode nod = (new DefaultMutableTreeNode( - "TileOffsets", true)); - val.add(nod); - for (int i = 0; i < larray.length; i++) { - nod.add(new DefaultMutableTreeNode(Long.toString(larray[i]), - false)); - } - } - if ((larray = niso.getTileByteCounts()) != null) { - DefaultMutableTreeNode nod = (new DefaultMutableTreeNode( - "TileByteCounts", true)); - val.add(nod); - for (int i = 0; i < larray.length; i++) { - nod.add(new DefaultMutableTreeNode(Long.toString(larray[i]), - false)); - } - } - if ((n = niso.getPlanarConfiguration()) != NisoImageMetadata.NULL) { - val.add(new DefaultMutableTreeNode("PlanarConfiguration: " - + integerRepresentation(n, - NisoImageMetadata.PLANAR_CONFIGURATION), false)); - } - if ((s = niso.getImageIdentifier()) != null) { - val.add(new DefaultMutableTreeNode("ImageIdentifier: " + s, false)); - } - if ((s = niso.getImageIdentifierLocation()) != null) { - val.add(new DefaultMutableTreeNode("ImageIdentifierLocation: " + s, - false)); - } - if ((ln = niso.getFileSize()) != NisoImageMetadata.NULL) { - val.add(new DefaultMutableTreeNode( - "FileSize: " + Long.toString(ln), false)); - } - if ((n = niso.getChecksumMethod()) != NisoImageMetadata.NULL) { - val.add(new DefaultMutableTreeNode("ChecksumMethod: " - + integerRepresentation(n, - NisoImageMetadata.CHECKSUM_METHOD), false)); - } - if ((s = niso.getChecksumValue()) != null) { - val.add(new DefaultMutableTreeNode("ChecksumValue: " + s, false)); - } - if ((n = niso.getOrientation()) != NisoImageMetadata.NULL) { - val.add(new DefaultMutableTreeNode("Orientation: " - + integerRepresentation(n, NisoImageMetadata.ORIENTATION), - false)); - } - if ((n = niso.getDisplayOrientation()) != NisoImageMetadata.NULL) { - val.add(new DefaultMutableTreeNode("DisplayOrientation: " - + integerRepresentation(n, - NisoImageMetadata.DISPLAY_ORIENTATION), false)); - } - if ((ln = niso.getXTargetedDisplayAR()) != NisoImageMetadata.NULL) { - val.add(new DefaultMutableTreeNode("XTargetedDisplayAR: " - + Long.toString(ln), false)); - } - if ((ln = niso.getYTargetedDisplayAR()) != NisoImageMetadata.NULL) { - val.add(new DefaultMutableTreeNode("YTargetedDisplayAR: " - + Long.toString(ln), false)); - } - if ((s = niso.getPreferredPresentation()) != null) { - val.add(new DefaultMutableTreeNode("PreferredPresentation: " + s, - false)); - } - if ((s = niso.getSourceType()) != null) { - val.add(new DefaultMutableTreeNode("SourceType: " + s, false)); - } - if ((s = niso.getImageProducer()) != null) { - val.add(new DefaultMutableTreeNode("ImageProducer: " + s, false)); - } - if ((s = niso.getHostComputer()) != null) { - val.add(new DefaultMutableTreeNode("HostComputer: " + s, false)); - } - if ((s = niso.getOS()) != null) { - val.add(new DefaultMutableTreeNode("OperatingSystem: " + s, false)); - } - if ((s = niso.getOSVersion()) != null) { - val.add(new DefaultMutableTreeNode("OSVersion: " + s, false)); - } - if ((s = niso.getDeviceSource()) != null) { - val.add(new DefaultMutableTreeNode("DeviceSource: " + s, false)); - } - if ((s = niso.getScannerManufacturer()) != null) { - val.add(new DefaultMutableTreeNode("ScannerManufacturer: " + s, - false)); - } - if ((s = niso.getScannerModelName()) != null) { - val.add(new DefaultMutableTreeNode("ScannerModelName: " + s, false)); - } - if ((s = niso.getScannerModelNumber()) != null) { - val.add(new DefaultMutableTreeNode("ScannerModelNumber: " + s, - false)); - } - if ((s = niso.getScannerModelSerialNo()) != null) { - val.add(new DefaultMutableTreeNode("ScannerModelSerialNo: " + s, - false)); - } - if ((s = niso.getScanningSoftware()) != null) { - val.add(new DefaultMutableTreeNode("ScanningSoftware: " + s, false)); - } - if ((s = niso.getScanningSoftwareVersionNo()) != null) { - val.add(new DefaultMutableTreeNode("ScanningSoftwareVersionNo: " - + s, false)); - } - double d = niso.getPixelSize(); - if (d != NisoImageMetadata.NILL) { - val.add(new DefaultMutableTreeNode("PixelSize: " - + Double.toString(d), false)); - } - if ((d = niso.getXPhysScanResolution()) != NisoImageMetadata.NILL) { - val.add(new DefaultMutableTreeNode("XPhysScanResolution: " - + Double.toString(d), false)); - } - if ((d = niso.getYPhysScanResolution()) != NisoImageMetadata.NILL) { - val.add(new DefaultMutableTreeNode("YPhysScanResolution: " - + Double.toString(d), false)); - } - - if ((s = niso.getDigitalCameraManufacturer()) != null) { - val.add(new DefaultMutableTreeNode("DigitalCameraManufacturer: " - + s, false)); - } - if ((s = niso.getDigitalCameraModelName()) != null) { - val.add(new DefaultMutableTreeNode("DigitalCameraModelName: " + s, - false)); - } - if ((s = niso.getDigitalCameraModelNumber()) != null) { - val.add(new DefaultMutableTreeNode( - "DigitalCameraModelNumber: " + s, false)); - } - if ((s = niso.getDigitalCameraModelSerialNo()) != null) { - val.add(new DefaultMutableTreeNode("DigitalCameraModelSerialNo: " - + s, false)); - } - if ((d = niso.getFNumber()) != NisoImageMetadata.NILL) { - val.add(new DefaultMutableTreeNode( - "FNumber: " + Double.toString(d), false)); - } - if ((d = niso.getExposureTime()) != NisoImageMetadata.NILL) { - val.add(new DefaultMutableTreeNode("ExposureTime: " - + Double.toString(d), false)); - } - if ((n = niso.getExposureProgram()) != NisoImageMetadata.NULL) { - val.add(new DefaultMutableTreeNode("ExposureProgram: " - + Integer.toString(n), false)); - } - if ((s = niso.getExifVersion()) != null) { - val.add(new DefaultMutableTreeNode("ExifVersion: " + s, false)); - } - Rational r; - if ((r = niso.getBrightness()) != null) { - val.add(new DefaultMutableTreeNode("Brightness: " + r.toString(), - false)); - } - if ((r = niso.getExposureBias()) != null) { - val.add(new DefaultMutableTreeNode("ExposureBias: " + r.toString(), - false)); - } - - double[] darray = niso.getSubjectDistance(); - if (darray != null) { - DefaultMutableTreeNode nod = new DefaultMutableTreeNode( - "SubjectDistance", true); - val.add(nod); - for (int i = 0; i < darray.length; i++) { - nod.add(new DefaultMutableTreeNode(Double.toString(darray[i]), - false)); - } - } - if ((n = niso.getMeteringMode()) != NisoImageMetadata.NULL) { - val.add(new DefaultMutableTreeNode("MeteringMode: " - + Integer.toString(n), false)); - } - if ((n = niso.getSceneIlluminant()) != NisoImageMetadata.NULL) { - val.add(new DefaultMutableTreeNode("SceneIlluminant: " - + Integer.toString(n), false)); - } - if ((d = niso.getColorTemp()) != NisoImageMetadata.NILL) { - val.add(new DefaultMutableTreeNode("ColorTemp: " - + Double.toString(d), false)); - } - if ((d = niso.getFocalLength()) != NisoImageMetadata.NILL) { - val.add(new DefaultMutableTreeNode("FocalLength: " - + Double.toString(d), false)); - } - if ((n = niso.getFlash()) != NisoImageMetadata.NULL) { - // First bit (0 = Flash did not fire, 1 = Flash fired) - val.add(new DefaultMutableTreeNode("Flash: " - + integerRepresentation(n & 1, NisoImageMetadata.FLASH_20), - false)); - } - if ((r = niso.getFlashEnergy()) != null) { - val.add(new DefaultMutableTreeNode("FlashEnergy: " + r.toString(), - false)); - } - if ((n = niso.getFlashReturn()) != NisoImageMetadata.NULL) { - val.add(new DefaultMutableTreeNode("FlashReturn: " - + integerRepresentation(n, NisoImageMetadata.FLASH_RETURN), - false)); - } - if ((n = niso.getBackLight()) != NisoImageMetadata.NULL) { - val.add(new DefaultMutableTreeNode("BackLight: " - + integerRepresentation(n, NisoImageMetadata.BACKLIGHT), - false)); - } - if ((d = niso.getExposureIndex()) != NisoImageMetadata.NILL) { - val.add(new DefaultMutableTreeNode("ExposureIndex: " - + Double.toString(d), false)); - } - if ((n = niso.getAutoFocus()) != NisoImageMetadata.NULL) { - val.add(new DefaultMutableTreeNode("AutoFocus: " - + Integer.toString(n), false)); - // NisoImageMetadata.AUTOFOCUS - } - if ((d = niso.getXPrintAspectRatio()) != NisoImageMetadata.NILL) { - val.add(new DefaultMutableTreeNode("XPrintAspectRatio: " - + Double.toString(d), false)); - } - if ((d = niso.getYPrintAspectRatio()) != NisoImageMetadata.NILL) { - val.add(new DefaultMutableTreeNode("YPrintAspectRatio: " - + Double.toString(d), false)); - } - - if ((n = niso.getSensor()) != NisoImageMetadata.NULL) { - val.add(new DefaultMutableTreeNode("Sensor: " - + integerRepresentation(n, NisoImageMetadata.SENSOR), false)); - } - if ((s = niso.getDateTimeCreated()) != null) { - val.add(new DefaultMutableTreeNode("DateTimeCreated: " + s, false)); - } - if ((s = niso.getMethodology()) != null) { - val.add(new DefaultMutableTreeNode("Methodology: " + s, false)); - } - if ((n = niso.getSamplingFrequencyPlane()) != NisoImageMetadata.NULL) { - val.add(new DefaultMutableTreeNode("SamplingFrequencyPlane: " - + integerRepresentation(n, - NisoImageMetadata.SAMPLING_FREQUENCY_PLANE), false)); - } - if ((n = niso.getSamplingFrequencyUnit()) != NisoImageMetadata.NULL) { - val.add(new DefaultMutableTreeNode("SamplingFrequencyUnit: " - + integerRepresentation(n, - NisoImageMetadata.SAMPLING_FREQUENCY_UNIT), false)); - } - Rational rat = niso.getXSamplingFrequency(); - if (rat != null) { - val.add(new DefaultMutableTreeNode("XSamplingFrequency: " - + rat.toString(), false)); - } - rat = niso.getYSamplingFrequency(); - if (rat != null) { - val.add(new DefaultMutableTreeNode("YSamplingFrequency: " - + rat.toString(), false)); - } - if ((ln = niso.getImageWidth()) != NisoImageMetadata.NULL) { - val.add(new DefaultMutableTreeNode("ImageWidth: " - + Long.toString(ln), false)); - } - if ((ln = niso.getImageLength()) != NisoImageMetadata.NULL) { - val.add(new DefaultMutableTreeNode("ImageLength: " - + Long.toString(ln), false)); - } - if ((d = niso.getSourceXDimension()) != NisoImageMetadata.NILL) { - val.add(new DefaultMutableTreeNode("SourceXDimension: " - + Double.toString(d), false)); - } - if ((n = niso.getSourceXDimensionUnit()) != NisoImageMetadata.NULL) { - val.add(new DefaultMutableTreeNode("SourceXDimensionUnit: " - + integerRepresentation(n, - NisoImageMetadata.SOURCE_DIMENSION_UNIT), false)); - } - if ((d = niso.getSourceYDimension()) != NisoImageMetadata.NILL) { - val.add(new DefaultMutableTreeNode("SourceYDimension: " - + Double.toString(d), false)); - } - if ((n = niso.getSourceYDimensionUnit()) != NisoImageMetadata.NULL) { - val.add(new DefaultMutableTreeNode("SourceYDimensionUnit: " - + integerRepresentation(n, - NisoImageMetadata.SOURCE_DIMENSION_UNIT), false)); - } - if ((iarray = niso.getBitsPerSample()) != null) { - DefaultMutableTreeNode nod = (new DefaultMutableTreeNode( - "BitsPerSample")); - val.add(nod); - for (int i = 0; i < iarray.length; i++) { - nod.add(new DefaultMutableTreeNode(Integer.toString(iarray[i]), - false)); - //BitsPerSampleUnit Integer is assumed, because of BitsPerSample - // According to the specification, it can also be float. - // This is currently not supported by jHove - nod.add(new DefaultMutableTreeNode("Integer")); - } - } - if ((n = niso.getSamplesPerPixel()) != NisoImageMetadata.NULL) { - val.add(new DefaultMutableTreeNode("SamplesPerPixel: " - + Integer.toString(n), false)); - } - if ((iarray = niso.getExtraSamples()) != null) { - DefaultMutableTreeNode nod = (new DefaultMutableTreeNode( - "ExtraSamples")); - val.add(nod); - for (int i = 0; i < iarray.length; i++) { - nod.add(new DefaultMutableTreeNode(integerRepresentation( - iarray[i], NisoImageMetadata.EXTRA_SAMPLES), false)); - } - } - if ((s = niso.getColormapReference()) != null) { - val.add(new DefaultMutableTreeNode("ColormapReference: " + s)); - } - if ((iarray = niso.getColormapBitCodeValue()) != null) { - DefaultMutableTreeNode nod = (new DefaultMutableTreeNode( - "ColormapBitCodeValue")); - val.add(nod); - for (int i = 0; i < iarray.length; i++) { - nod.add(new DefaultMutableTreeNode(Integer.toString(iarray[i]), - false)); - } - } - if ((iarray = niso.getColormapRedValue()) != null) { - DefaultMutableTreeNode nod = (new DefaultMutableTreeNode( - "ColormapRedValue")); - val.add(nod); - for (int i = 0; i < iarray.length; i++) { - nod.add(new DefaultMutableTreeNode(Integer.toString(iarray[i]), - false)); - } - } - if ((iarray = niso.getColormapGreenValue()) != null) { - DefaultMutableTreeNode nod = (new DefaultMutableTreeNode( - "ColormapGreenValue")); - val.add(nod); - for (int i = 0; i < iarray.length; i++) { - nod.add(new DefaultMutableTreeNode(Integer.toString(iarray[i]), - false)); - } - } - if ((iarray = niso.getColormapBlueValue()) != null) { - DefaultMutableTreeNode nod = (new DefaultMutableTreeNode( - "ColormapBlueValue")); - val.add(nod); - for (int i = 0; i < iarray.length; i++) { - nod.add(new DefaultMutableTreeNode(Integer.toString(iarray[i]), - false)); - } - } - if ((iarray = niso.getGrayResponseCurve()) != null) { - DefaultMutableTreeNode nod = (new DefaultMutableTreeNode( - "GrayResponseCurve")); - val.add(nod); - for (int i = 0; i < iarray.length; i++) { - nod.add(new DefaultMutableTreeNode(Integer.toString(iarray[i]), - false)); - } - } - if ((n = niso.getGrayResponseUnit()) != NisoImageMetadata.NULL) { - val.add(new DefaultMutableTreeNode("GrayResponseUnit: " - + Integer.toString(n), false)); - } - r = niso.getWhitePointXValue(); - if (r != null) { - val.add(new DefaultMutableTreeNode("WhitePointXValue: " - + r.toString(), false)); - } - if ((r = niso.getWhitePointYValue()) != null) { - val.add(new DefaultMutableTreeNode("WhitePointYValue: " - + r.toString(), false)); - } - if ((r = niso.getPrimaryChromaticitiesRedX()) != null) { - val.add(new DefaultMutableTreeNode("PrimaryChromaticitiesRedX: " - + r.toString(), false)); - } - if ((r = niso.getPrimaryChromaticitiesRedY()) != null) { - val.add(new DefaultMutableTreeNode("PrimaryChromaticitiesRedY: " - + r.toString(), false)); - } - if ((r = niso.getPrimaryChromaticitiesGreenX()) != null) { - val.add(new DefaultMutableTreeNode("PrimaryChromaticitiesGreenX: " - + r.toString(), false)); - } - if ((r = niso.getPrimaryChromaticitiesGreenY()) != null) { - val.add(new DefaultMutableTreeNode("PrimaryChromaticitiesGreenY: " - + r.toString(), false)); - } - if ((r = niso.getPrimaryChromaticitiesBlueX()) != null) { - val.add(new DefaultMutableTreeNode("PrimaryChromaticitiesBlueX: " - + r.toString())); - } - if ((r = niso.getPrimaryChromaticitiesBlueY()) != null) { - val.add(new DefaultMutableTreeNode("PrimaryChromaticitiesBlueY: " - + r.toString())); - } - if ((n = niso.getTargetType()) != NisoImageMetadata.NULL) { - val.add(new DefaultMutableTreeNode("TargetType: " - + integerRepresentation(n, NisoImageMetadata.TARGET_TYPE), - false)); - } - if ((s = niso.getTargetIDManufacturer()) != null) { - val.add(new DefaultMutableTreeNode("TargetIDManufacturer: " + s, - false)); - } - if ((s = niso.getTargetIDName()) != null) { - val.add(new DefaultMutableTreeNode("TargetIDName: " + s, false)); - } - if ((s = niso.getTargetIDNo()) != null) { - val.add(new DefaultMutableTreeNode("TargetIDNo: " + s, false)); - } - if ((s = niso.getTargetIDMedia()) != null) { - val.add(new DefaultMutableTreeNode("TargetIDMedia: " + s, false)); - } - if ((s = niso.getImageData()) != null) { - val.add(new DefaultMutableTreeNode("ImageData: " + s, false)); - } - if ((s = niso.getPerformanceData()) != null) { - val.add(new DefaultMutableTreeNode("PerformanceData: " + s, false)); - } - if ((s = niso.getProfiles()) != null) { - val.add(new DefaultMutableTreeNode("Profiles: " + s, false)); - } - if ((s = niso.getDateTimeProcessed()) != null) { - val.add(new DefaultMutableTreeNode("DateTimeProcessed: " + s, false)); - } - if ((s = niso.getSourceData()) != null) { - val.add(new DefaultMutableTreeNode("SourceData: " + s, false)); - } - if ((s = niso.getProcessingAgency()) != null) { - val.add(new DefaultMutableTreeNode("ProcessingAgency: " + s, false)); - } - if ((s = niso.getProcessingSoftwareName()) != null) { - val.add(new DefaultMutableTreeNode("ProcessingSoftwareName: " + s, - false)); - } - if ((s = niso.getProcessingSoftwareVersion()) != null) { - val.add(new DefaultMutableTreeNode("ProcessingSoftwareVersion: " - + s, false)); - } - String[] sarray = niso.getProcessingActions(); - if (sarray != null) { - DefaultMutableTreeNode nod = new DefaultMutableTreeNode( - "ProcessingActions", true); - val.add(nod); - for (int i = 1; i < sarray.length; i++) { - nod.add(new DefaultMutableTreeNode(sarray[i], false)); - } - } - return val; - } - - /* - * Return the string equivalent of an integer if raw output isn't selected, - * or its literal representation if it is. - */ - private String integerRepresentation(int n, String[] labels) { - if (_rawOutput) { - return Integer.toString(n); - } - try { - return labels[n]; - } catch (Exception e) { - return Integer.toString(n); - } - } - - private String integerRepresentation(int n, String[] labels, int[] index) { - if (_rawOutput) { - return Integer.toString(n); - } - try { - int idx = -1; - for (int i = 0; i < index.length; i++) { - if (n == index[i]) { - idx = i; - break; - } - } - if (idx > -1) { - return labels[idx]; - } - } catch (Exception e) { - } - // If we don't get a match, or do get an exception - return Integer.toString(n); - } - - /** - * This method returns a member of a property list based on it's - * PropertyType - * - * @param ptyp - * the propertytype - * @param item - * the item of the list - * @param allowsChildren - * @return - */ - private DefaultMutableTreeNode getDefaultMutableTreeNode(PropertyType ptyp, - Object item, boolean allowsChildren) { - - DefaultMutableTreeNode itemNode; - - if (null == ptyp) { - // Simple objects just need a leaf. - itemNode = (new DefaultMutableTreeNode(item, allowsChildren)); - } else - // Object item = iter.next (); - switch (ptyp) { - case PROPERTY: - itemNode = (propToNode((Property) item)); - break; - case NISOIMAGEMETADATA: - itemNode = (nisoToNode((NisoImageMetadata) item)); - break; - default: - // Simple objects just need a leaf. - itemNode = (new DefaultMutableTreeNode(item, allowsChildren)); - break; - } - - return itemNode; - } - /** - * Method to add an array to a node - * - * @param - * generic method, can be used for arrays of different types - * @param node - * the node to add the elements of the array - * @param array - * the array to be added to the node - */ - private void addToNode(DefaultMutableTreeNode node, E[] array) { - for (E element : array) { - if (element instanceof Property) { - node.add(propToNode((Property) element)); - } else { - node.add(new DefaultMutableTreeNode(element)); - } - } - } + /** + * Constructs a DefaultMutableTreeNode representing a property + */ + private DefaultMutableTreeNode propToNode(Property pProp) { + PropertyArity arity = pProp.getArity(); + PropertyType typ = pProp.getType(); + Object pValue = pProp.getValue(); + if (arity == PropertyArity.SCALAR) { + if (null == typ) { + // Simple types: just use name plus string value. + DefaultMutableTreeNode val = new DefaultMutableTreeNode( + pProp.getName() + ": " + pValue.toString()); + return val; + } else { + TextMDMetadata tData; + DefaultMutableTreeNode val; + switch (typ) { + case NISOIMAGEMETADATA: + // NISO Image metadata is a world of its own. + NisoImageMetadata nData = (NisoImageMetadata) pValue; + return nisoToNode(nData); + case AESAUDIOMETADATA: + // AES audio metadata is another world. + AESAudioMetadata aData = (AESAudioMetadata) pValue; + return aesToNode(aData); + case TEXTMDMETADATA: + // textMD metadata is another world. + tData = (TextMDMetadata) pValue; + return textMDToNode(tData); + case PROPERTY: + + if (TEXT_MD_METADTA.equals(pProp.getName())) { + tData = (TextMDMetadata) pValue; + return textMDToNode(tData); + } + // A scalar property of type Property -- seems + // pointless, but we handle it. + val = new DefaultMutableTreeNode(pProp.getName()); + val.add(propToNode((Property) pValue)); + return val; + + default: + + // Simple types: just use name plus string value. + val = new DefaultMutableTreeNode(pProp.getName() + ": " + + pValue.toString()); + return val; + + } + } + } + // Compound properties. The text of the node is the + // property name. + DefaultMutableTreeNode val = new DefaultMutableTreeNode(pProp.getName()); + if (null != arity) + switch (arity) { + case ARRAY: + addArrayMembers(val, pProp); + break; + case LIST: + addListMembers(val, pProp); + break; + case MAP: + addMapMembers(val, pProp); + break; + case SET: + addSetMembers(val, pProp); + break; + default: + break; + } + return val; + } + + /** + * Find the index of an object in its parent. Understands the Jhove property + * structure. + */ + public int getIndexOfChild(Object parent, Object child) { + Property pProp = (Property) parent; + PropertyArity arity = pProp.getArity(); + // For Lists, Maps, and Sets we construct an Iterator. + Iterator iter = null; + if (arity == PropertyArity.SET || arity == PropertyArity.LIST + || arity == PropertyArity.MAP) { + if (null == arity) { + List list = (List) pProp.getValue(); + iter = list.iterator(); + } else + switch (arity) { + case SET: + Set set = (Set) pProp.getValue(); + iter = set.iterator(); + break; + case MAP: + Map map = (Map) pProp.getValue(); + iter = map.values().iterator(); + break; + default: + List list = (List) pProp.getValue(); + iter = list.iterator(); + break; + } + for (int i = 0;; i++) { + if (!iter.hasNext()) { + return 0; // Should never happen + } else if (iter.next() == child) { + return i; + } + } + } + // OK, that was the easy one. Now for that damn array arity. + // In the case of non-object types, we can't actually tell which + // position matches the object, so we return 0 and hope it doesn't + // mess things up too much. + PropertyType propType = pProp.getType(); + java.util.Date[] dateArray = null; + Property[] propArray = null; + Rational[] rationalArray = null; + Object[] objArray = null; + int n = 0; + + if (null == propType) { + return 0; // non-object array type + } else + // if (child instanceof LeafHolder) { + // return ((LeafHolder) child).getPosition (); + // } + // else + switch (propType) { + case DATE: + dateArray = (java.util.Date[]) pProp.getValue(); + n = dateArray.length; + break; + case OBJECT: + objArray = (Object[]) pProp.getValue(); + n = objArray.length; + break; + case RATIONAL: + rationalArray = (Rational[]) pProp.getValue(); + n = rationalArray.length; + break; + case PROPERTY: + propArray = (Property[]) pProp.getValue(); + n = propArray.length; + break; + default: + return 0; // non-object array type + } + + for (int i = 0; i < n; i++) { + Object elem = null; + switch (propType) { + case DATE: + elem = dateArray[i]; + break; + case OBJECT: + elem = objArray[i]; + break; + case RATIONAL: + elem = rationalArray[i]; + break; + case PROPERTY: + elem = propArray[i]; + break; + default: + break; + } + if (elem == child) { + return i; + } + } + return 0; // somehow fell through + } + + private void snarfRepInfo() { + // This node has two children, for the module and the RepInfo + + Module module = _info.getModule(); + if (module != null) { + // Create a subnode for the module, which has three + // leaf children. + DefaultMutableTreeNode moduleNode = new DefaultMutableTreeNode( + "Module"); + moduleNode.add(new DefaultMutableTreeNode(module.getName(), false)); + moduleNode.add(new DefaultMutableTreeNode("Release: " + + module.getRelease(), false)); + moduleNode.add(new DefaultMutableTreeNode("Date: " + + _dateFmt.format(module.getDate()), false)); + add(moduleNode); + } + + DefaultMutableTreeNode infoNode = new DefaultMutableTreeNode("RepInfo"); + infoNode.add(new DefaultMutableTreeNode("URI: " + _info.getUri(), false)); + Date dt = _info.getCreated(); + if (dt != null) { + infoNode.add(new DefaultMutableTreeNode( + "Created: " + dt.toString(), false)); + } + dt = _info.getLastModified(); + if (dt != null) { + infoNode.add(new DefaultMutableTreeNode("LastModified: " + + dt.toString(), false)); + } + long sz = _info.getSize(); + if (sz != -1) { + infoNode.add(new DefaultMutableTreeNode("Size: " + + Long.toString(sz), false)); + } + String s = _info.getFormat(); + if (s != null) { + infoNode.add(new DefaultMutableTreeNode(FORMAT + s, false)); + } + s = _info.getVersion(); + if (s != null) { + infoNode.add(new DefaultMutableTreeNode(VERSION + s, false)); + } + String wfStr; + switch (_info.getWellFormed()) { + case RepInfo.TRUE: + wfStr = "Well-Formed"; + break; + case RepInfo.FALSE: + wfStr = "Not well-formed"; + break; + default: + wfStr = "Unknown"; + break; + } + 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 + } + } + infoNode.add(new DefaultMutableTreeNode("Status: " + wfStr, false)); + + // Report modules that said their signatures match + List sigList = _info.getSigMatch(); + if (sigList != null && sigList.size() > 0) { + DefaultMutableTreeNode sigNode = new DefaultMutableTreeNode( + "SignatureMatches"); + infoNode.add(sigNode); + for (int i = 0; i < sigList.size(); i++) { + DefaultMutableTreeNode sNode = new DefaultMutableTreeNode( + sigList.get(i)); + sigNode.add(sNode); + } + } + // Compile a list of messages and offsets into a subtree + List messageList = _info.getMessage(); + if (messageList != null && messageList.size() > 0) { + DefaultMutableTreeNode msgNode = new DefaultMutableTreeNode( + "Messages"); + infoNode.add(msgNode); + int i; + for (i = 0; i < messageList.size(); i++) { + Message msg = messageList.get(i); + String prefix; + if (msg instanceof InfoMessage) { + prefix = "InfoMessage: "; + } else if (msg instanceof ErrorMessage) { + prefix = "ErrorMessage: "; + } else { + prefix = "Message: "; + } + DefaultMutableTreeNode mNode = new DefaultMutableTreeNode( + prefix + msg.getMessage()); + + if (msg.getId() != null && !msg.getId().isEmpty()) { + mNode.add(new DefaultMutableTreeNode("ID: " + + msg.getId())); + } + + String subMessage = msg.getSubMessage(); + if (subMessage != null) { + mNode.add(new DefaultMutableTreeNode("SubMessage: " + + subMessage)); + } + long offset = -1; + if (msg instanceof ErrorMessage) { + offset = ((ErrorMessage) msg).getOffset(); + } + // + // If the offset is positive, we give the message node + // a child with the offset value. + if (offset >= 0) { + mNode.add(new DefaultMutableTreeNode("Offset: " + + Long.toString(offset))); + } + msgNode.add(mNode); + } + } + + s = _info.getMimeType(); + if (s != null) { + infoNode.add(new DefaultMutableTreeNode("MimeType: " + s, false)); + } + + // Compile a list of profile strings into a string list + List profileList = _info.getProfile(); + if (profileList != null && profileList.size() > 0) { + DefaultMutableTreeNode profNode = new DefaultMutableTreeNode( + "Profiles"); + infoNode.add(profNode); + int i; + for (i = 0; i < profileList.size(); i++) { + profNode.add(new DefaultMutableTreeNode(profileList.get(i), + false)); + } + } + + // Here we come to the property map. We have to walk + // through all the properties recursively, turning + // each into a leaf or subtree. + Map map = _info.getProperty(); + if (map != null) { + Iterator iter = map.keySet().iterator(); + while (iter.hasNext()) { + String key = iter.next(); + Property property = _info.getProperty(key); + infoNode.add(propToNode(property)); + } + } + + List cksumList = _info.getChecksum(); + if (cksumList != null && !cksumList.isEmpty()) { + DefaultMutableTreeNode ckNode = new DefaultMutableTreeNode( + "Checksums"); + infoNode.add(ckNode); + // List cPropList = new LinkedList (); + for (Checksum cksum : cksumList) { + DefaultMutableTreeNode csNode = new DefaultMutableTreeNode( + "Checksum"); + ckNode.add(csNode); + csNode.add(new DefaultMutableTreeNode("Type:" + + cksum.getType().toString(), false)); + csNode.add(new DefaultMutableTreeNode("Checksum: " + + cksum.getValue(), false)); + } + } + + s = _info.getNote(); + if (s != null) { + infoNode.add(new DefaultMutableTreeNode("Note: " + s, false)); + } + add(infoNode); + } + + /* + * Add the members of an array property to a node. The property must be of + * arity ARRAY. + */ + private void addArrayMembers(DefaultMutableTreeNode node, Property p) { + Object pVal = p.getValue(); + PropertyType typ = p.getType(); + if (null != typ) + switch (typ) { + case INTEGER: { + if (pVal instanceof int[]) { + Integer[] vals = Arrays.stream((int[]) pVal) // IntStream + .boxed() // Stream + .toArray(Integer[]::new); + addToNode(node, vals); + } else { + addToNode(node, (Integer[]) pVal); + } + break; + } + case LONG: { + if (pVal instanceof long[]) { + Long[] vals = Arrays.stream((long[]) pVal) // IntStream + .boxed() // Stream + .toArray(Long[]::new); + addToNode(node, vals); + } else { + addToNode(node, (Long[]) pVal); + } + break; + } + case BOOLEAN: { + addToNode(node, (Boolean[]) pVal); + break; + } + case CHARACTER: { + addToNode(node, (Character[]) pVal); + break; + } + case DOUBLE: { + if (pVal instanceof double[]) { + Double[] vals = Arrays.stream((double[]) pVal) // IntStream + .boxed() // Stream + .toArray(Double[]::new); + addToNode(node, vals); + } else { + addToNode(node, (Double[]) pVal); + } + break; + } + case FLOAT: { + addToNode(node, (Float[]) pVal); + break; + } + case SHORT: { + addToNode(node, (Short[]) pVal); + break; + } + case BYTE: { + addToNode(node, (Byte[]) pVal); + break; + } + case STRING: { + addToNode(node, (String[]) pVal); + break; + } + case RATIONAL: { + addToNode(node, (Rational[]) pVal); + break; + } + case PROPERTY: { + addToNode(node, (Property[]) pVal); + break; + } + case NISOIMAGEMETADATA: { + addToNode(node, (NisoImageMetadata[]) pVal); + break; + } + case OBJECT: { + addToNode(node, (Object[]) pVal); + break; + } + default: + break; + } + } + + /* + * Add the members of a list property to a node. The property must be of + * arity LIST. + */ + private void addListMembers(DefaultMutableTreeNode node, Property p) { + List l = (List) p.getValue(); + PropertyType ptyp = p.getType(); + boolean canHaveChildren = Boolean.FALSE; + l.forEach(item -> node.add(getDefaultMutableTreeNode(ptyp, item, + canHaveChildren))); + } + + /* + * Add the members of a set property to a node. The property must be of + * arity SET. + */ + private void addSetMembers(DefaultMutableTreeNode node, Property p) { + Set s = (Set) p.getValue(); + PropertyType ptyp = p.getType(); + boolean canHaveChildren = Boolean.FALSE; + Iterator iter = s.iterator(); + while (iter.hasNext()) { + Object item = iter.next(); + node.add(getDefaultMutableTreeNode(ptyp, item, canHaveChildren)); + } + } + + /* + * Add the members of a map property to a node. The property must be of + * arity MAP. + */ + private void addMapMembers(DefaultMutableTreeNode node, Property p) { + Map m = (Map) p.getValue(); + PropertyType ptyp = p.getType(); + Boolean canHaveChildren = Boolean.TRUE; + // Iterator iter = m.values ().iterator (); + Iterator iter = m.keySet().iterator(); + while (iter.hasNext()) { + DefaultMutableTreeNode itemNode; + String key = (String) iter.next(); + Object item = m.get(key); + itemNode = getDefaultMutableTreeNode(ptyp, item, canHaveChildren); + node.add(itemNode); + + // Add a subnode for the key + itemNode.setAllowsChildren(true); + itemNode.add(new DefaultMutableTreeNode("Key: " + key, false)); + } + } + + /* Function for turning the AES metadata into a subtree. */ + private DefaultMutableTreeNode aesToNode(AESAudioMetadata aes) { + _sampleRate = aes.getSampleRate(); + + DefaultMutableTreeNode val = new DefaultMutableTreeNode( + "AESAudioMetadata", true); + String s = aes.getAnalogDigitalFlag(); + if (s != null) { + val.add(new DefaultMutableTreeNode("AnalogDigitalFlag: " + s, false)); + // The "false" argument signifies this will have no subnodes + } + s = aes.getSchemaVersion(); + if (s != null) { + val.add(new DefaultMutableTreeNode("SchemaVersion: " + s, false)); + } + s = aes.getFormat(); + if (s != null) { + DefaultMutableTreeNode fmt = new DefaultMutableTreeNode(FORMAT + + s, true); + val.add(fmt); + String v = aes.getSpecificationVersion(); + if (v != null) { + fmt.add(new DefaultMutableTreeNode( + "SpecificationVersion: " + v, false)); + } + } + s = aes.getAppSpecificData(); + if (s != null) { + val.add(new DefaultMutableTreeNode("AppSpecificData: " + s, false)); + } + s = aes.getAudioDataEncoding(); + if (s != null) { + val.add(new DefaultMutableTreeNode("AudioDataEncoding: " + s, false)); + } + int in = aes.getByteOrder(); + if (in != AESAudioMetadata.NULL) { + val.add(new DefaultMutableTreeNode(BYTE_ORDER + + (in == AESAudioMetadata.BIG_ENDIAN ? "BIG_ENDIAN" + : "LITTLE_ENDIAN"))); + } + long lin = aes.getFirstSampleOffset(); + if (lin != AESAudioMetadata.NULL) { + val.add(new DefaultMutableTreeNode("FirstSampleOffset: " + + Long.toString(lin))); + } + String[] use = aes.getUse(); + if (use != null) { + DefaultMutableTreeNode u = new DefaultMutableTreeNode("Use", true); + val.add(u); + u.add(new DefaultMutableTreeNode("UseType: " + use[0], false)); + u.add(new DefaultMutableTreeNode("OtherType: " + use[1], false)); + } + s = aes.getPrimaryIdentifier(); + if (s != null) { + String t = aes.getPrimaryIdentifierType(); + DefaultMutableTreeNode pi = new DefaultMutableTreeNode( + "PrimaryIdentifier: " + s, true); + val.add(pi); + if (t != null) { + pi.add(new DefaultMutableTreeNode("IdentifierType: " + t)); + } + } + // 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()) { + AESAudioMetadata.Face f = facelist.get(0); + + DefaultMutableTreeNode face = new DefaultMutableTreeNode("Face", + true); + DefaultMutableTreeNode timeline = new DefaultMutableTreeNode( + "TimeLine", true); + AESAudioMetadata.TimeDesc startTime = f.getStartTime(); + if (startTime != null) { + addAESTimeRange(timeline, startTime, f.getDuration()); + } + face.add(timeline); + + // For the present, assume just one face region + AESAudioMetadata.FaceRegion facergn = f.getFaceRegion(0); + DefaultMutableTreeNode region = new DefaultMutableTreeNode( + "Region", true); + timeline = new DefaultMutableTreeNode("TimeRange", true); + addAESTimeRange(timeline, facergn.getStartTime(), + facergn.getDuration()); + region.add(timeline); + int nchan = aes.getNumChannels(); + if (nchan != AESAudioMetadata.NULL) { + region.add(new DefaultMutableTreeNode("NumChannels: " + + Integer.toString(nchan), false)); + for (int ch = 0; ch < nchan; ch++) { + // write a stream element for each channel + DefaultMutableTreeNode stream = new DefaultMutableTreeNode( + "Stream", true); + region.add(stream); + stream.add(new DefaultMutableTreeNode("ChannelNum: " + Integer.toString(ch), false)); + } + } + face.add(region); + val.add(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 = 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) { + DefaultMutableTreeNode formatList = new DefaultMutableTreeNode( + "FormatList", true); + DefaultMutableTreeNode formatRegion = new DefaultMutableTreeNode( + "FormatRegion", true); + if (bitDepth != AESAudioMetadata.NULL) { + formatRegion.add(new DefaultMutableTreeNode("BitDepth: " + + Integer.toString(bitDepth), false)); + } + if (sampleRate != AESAudioMetadata.NILL) { + formatRegion.add(new DefaultMutableTreeNode("SampleRate: " + + Double.toString(sampleRate), false)); + } + if (wordSize != AESAudioMetadata.NULL) { + formatRegion.add(new DefaultMutableTreeNode("WordSize: " + + Integer.toString(bitDepth), false)); + } + if (bitRed != null) { + DefaultMutableTreeNode br = new DefaultMutableTreeNode( + "BitrateReduction", true); + br.add(new DefaultMutableTreeNode( + "codecName: " + bitRed[0], false)); + br.add(new DefaultMutableTreeNode("codecNameVersion: " + + bitRed[1], false)); + br.add(new DefaultMutableTreeNode( + "codecCreatorApplication: " + bitRed[2], false)); + br.add(new DefaultMutableTreeNode( + "codecCreatorApplicationVersion: " + bitRed[3], + false)); + br.add(new DefaultMutableTreeNode("codecQuality: " + + bitRed[4], false)); + br.add(new DefaultMutableTreeNode("dataRate: " + bitRed[5], + false)); + br.add(new DefaultMutableTreeNode("dataRateMode: " + + bitRed[6], false)); + formatRegion.add(br); + } + formatList.add(formatRegion); + val.add(formatList); + } + } + + return val; + } + + private void addAESTimeRange(DefaultMutableTreeNode parent, + AESAudioMetadata.TimeDesc start, AESAudioMetadata.TimeDesc duration) { + writeAESTimeRangePart(parent, "StartTime", start); + // Duration is optional. + if (duration != null) { + writeAESTimeRangePart(parent, "Duration", duration); + } + } + + private void writeAESTimeRangePart(DefaultMutableTreeNode parent, + String name, AESAudioMetadata.TimeDesc timeDesc) { + double sampleRate = timeDesc.getSampleRate(); + if (sampleRate == 1.0) { + sampleRate = _sampleRate; + } + + DefaultMutableTreeNode node = new DefaultMutableTreeNode(name, true); + node.add(new DefaultMutableTreeNode( + "Value: " + String.valueOf(timeDesc.getSamples()), false)); + node.add(new DefaultMutableTreeNode( + "EditRate: " + sampleRate, false)); + node.add(new DefaultMutableTreeNode( + "FactorNumerator: 1", false)); + node.add(new DefaultMutableTreeNode( + "FactorDenominator: 1", false)); + + parent.add(node); + } + + /* Function for turning the textMD metadata into a subtree. */ + private DefaultMutableTreeNode textMDToNode(TextMDMetadata textMD) { + DefaultMutableTreeNode val = new DefaultMutableTreeNode( + TEXT_MD_METADTA, true); + + DefaultMutableTreeNode u = new DefaultMutableTreeNode("Character_info", + true); + val.add(u); + + String s = textMD.getCharset(); + if (s != null) { + u.add(new DefaultMutableTreeNode("Charset: " + s, false)); + } + s = textMD.getByte_orderString(); + if (s != null) { + u.add(new DefaultMutableTreeNode("Byte_order: " + s, false)); + } + s = textMD.getByte_size(); + if (s != null) { + u.add(new DefaultMutableTreeNode("Byte_size: " + s, false)); + } + s = textMD.getCharacter_size(); + if (s != null) { + u.add(new DefaultMutableTreeNode("Character_size: " + s, false)); + } + s = textMD.getLinebreakString(); + if (s != null) { + u.add(new DefaultMutableTreeNode("Linebreak: " + s, false)); + } + s = textMD.getLanguage(); + if (s != null) { + val.add(new DefaultMutableTreeNode("Language: " + s, false)); + } + s = textMD.getMarkup_basis(); + if (s != null) { + DefaultMutableTreeNode basis = new DefaultMutableTreeNode( + "Markup_basis: " + s, true); + val.add(basis); + s = textMD.getMarkup_basis_version(); + if (s != null) { + basis.add(new DefaultMutableTreeNode(VERSION + s, false)); + } + } + s = textMD.getMarkup_language(); + if (s != null) { + DefaultMutableTreeNode language = new DefaultMutableTreeNode( + "Markup_language: " + s, true); + val.add(language); + s = textMD.getMarkup_language_version(); + if (s != null) { + language.add(new DefaultMutableTreeNode(VERSION + s, false)); + } + } + return val; + } + + /* Function for turning the Niso metadata into a subtree. */ + private DefaultMutableTreeNode nisoToNode(NisoImageMetadata niso) { + DefaultMutableTreeNode val = new DefaultMutableTreeNode( + "NisoImageMetadata", true); + String s = niso.getMimeType(); + if (s != null) { + val.add(new DefaultMutableTreeNode("FormatName: " + s, false)); + } + s = niso.getByteOrder(); + if (s != null) { + val.add(new DefaultMutableTreeNode(BYTE_ORDER + s, false)); + } + + int n = niso.getCompressionScheme(); + if (n != NisoImageMetadata.NULL) { + val.add(new DefaultMutableTreeNode("CompressionScheme: " + + integerRepresentation(n, + NisoImageMetadata.COMPRESSION_SCHEME, + NisoImageMetadata.COMPRESSION_SCHEME_INDEX), + false)); + } + if ((n = niso.getCompressionLevel()) != NisoImageMetadata.NULL) { + val.add(new DefaultMutableTreeNode("CompressionLevel: " + + Integer.toString(n), false)); + } + if ((n = niso.getColorSpace()) != NisoImageMetadata.NULL) { + val.add(new DefaultMutableTreeNode("ColorSpace: " + + integerRepresentation(n, NisoImageMetadata.COLORSPACE, + NisoImageMetadata.COLORSPACE_INDEX), + false)); + } + if ((s = niso.getProfileName()) != null) { + val.add(new DefaultMutableTreeNode("ProfileName: " + s, false)); + } + if ((s = niso.getProfileURL()) != null) { + val.add(new DefaultMutableTreeNode("ProfileURL: " + s, false)); + } + int[] iarray = niso.getYCbCrSubSampling(); + if (iarray != null) { + DefaultMutableTreeNode nod = (new DefaultMutableTreeNode( + "YCbCrSubSampling")); + val.add(nod); + for (int i = 0; i < iarray.length; i++) { + nod.add(new DefaultMutableTreeNode(Integer.toString(iarray[i]), + false)); + } + } + if ((n = niso.getYCbCrPositioning()) != NisoImageMetadata.NULL) { + val.add(new DefaultMutableTreeNode("YCbCrPositioning: " + + integerRepresentation(n, + NisoImageMetadata.YCBCR_POSITIONING), + false)); + } + Rational[] rarray = niso.getYCbCrCoefficients(); + if (rarray != null) { + DefaultMutableTreeNode nod = (new DefaultMutableTreeNode( + "YCbCrCoefficients", true)); + val.add(nod); + for (int i = 0; i < rarray.length; i++) { + nod.add(new DefaultMutableTreeNode(rarray[i].toString(), false)); + } + } + rarray = niso.getReferenceBlackWhite(); + if (rarray != null) { + DefaultMutableTreeNode nod = (new DefaultMutableTreeNode( + "ReferenceBlackWhite", true)); + val.add(nod); + for (int i = 0; i < rarray.length; i++) { + nod.add(new DefaultMutableTreeNode(rarray[i].toString(), false)); + } + } + if ((n = niso.getSegmentType()) != NisoImageMetadata.NULL) { + val.add(new DefaultMutableTreeNode("YSegmentType: " + + integerRepresentation(n, NisoImageMetadata.SEGMENT_TYPE), + false)); + } + long[] larray = niso.getStripOffsets(); + if (larray != null) { + DefaultMutableTreeNode nod = (new DefaultMutableTreeNode( + "StripOffsets", true)); + val.add(nod); + for (int i = 0; i < larray.length; i++) { + nod.add(new DefaultMutableTreeNode(Long.toString(larray[i]), + false)); + } + } + long ln = niso.getRowsPerStrip(); + if (ln != NisoImageMetadata.NULL) { + val.add(new DefaultMutableTreeNode("RowsPerStrip: " + + Long.toString(ln), false)); + } + if ((larray = niso.getStripByteCounts()) != null) { + DefaultMutableTreeNode nod = (new DefaultMutableTreeNode( + "StripByteCounts", true)); + val.add(nod); + for (int i = 0; i < larray.length; i++) { + nod.add(new DefaultMutableTreeNode(Long.toString(larray[i]), + false)); + } + } + if ((ln = niso.getTileWidth()) != NisoImageMetadata.NULL) { + val.add(new DefaultMutableTreeNode("TileWidth: " + + Long.toString(ln))); + } + if ((ln = niso.getTileLength()) != NisoImageMetadata.NULL) { + val.add(new DefaultMutableTreeNode("TileLength: " + + Long.toString(ln))); + } + if ((larray = niso.getTileOffsets()) != null) { + DefaultMutableTreeNode nod = (new DefaultMutableTreeNode( + "TileOffsets", true)); + val.add(nod); + for (int i = 0; i < larray.length; i++) { + nod.add(new DefaultMutableTreeNode(Long.toString(larray[i]), + false)); + } + } + if ((larray = niso.getTileByteCounts()) != null) { + DefaultMutableTreeNode nod = (new DefaultMutableTreeNode( + "TileByteCounts", true)); + val.add(nod); + for (int i = 0; i < larray.length; i++) { + nod.add(new DefaultMutableTreeNode(Long.toString(larray[i]), + false)); + } + } + if ((n = niso.getPlanarConfiguration()) != NisoImageMetadata.NULL) { + val.add(new DefaultMutableTreeNode("PlanarConfiguration: " + + integerRepresentation(n, + NisoImageMetadata.PLANAR_CONFIGURATION), + false)); + } + if ((s = niso.getImageIdentifier()) != null) { + val.add(new DefaultMutableTreeNode("ImageIdentifier: " + s, false)); + } + if ((s = niso.getImageIdentifierLocation()) != null) { + val.add(new DefaultMutableTreeNode("ImageIdentifierLocation: " + s, + false)); + } + if ((ln = niso.getFileSize()) != NisoImageMetadata.NULL) { + val.add(new DefaultMutableTreeNode( + "FileSize: " + Long.toString(ln), false)); + } + if ((n = niso.getChecksumMethod()) != NisoImageMetadata.NULL) { + val.add(new DefaultMutableTreeNode("ChecksumMethod: " + + integerRepresentation(n, + NisoImageMetadata.CHECKSUM_METHOD), + false)); + } + if ((s = niso.getChecksumValue()) != null) { + val.add(new DefaultMutableTreeNode("ChecksumValue: " + s, false)); + } + if ((n = niso.getOrientation()) != NisoImageMetadata.NULL) { + val.add(new DefaultMutableTreeNode("Orientation: " + + integerRepresentation(n, NisoImageMetadata.ORIENTATION), + false)); + } + if ((n = niso.getDisplayOrientation()) != NisoImageMetadata.NULL) { + val.add(new DefaultMutableTreeNode("DisplayOrientation: " + + integerRepresentation(n, + NisoImageMetadata.DISPLAY_ORIENTATION), + false)); + } + if ((ln = niso.getXTargetedDisplayAR()) != NisoImageMetadata.NULL) { + val.add(new DefaultMutableTreeNode("XTargetedDisplayAR: " + + Long.toString(ln), false)); + } + if ((ln = niso.getYTargetedDisplayAR()) != NisoImageMetadata.NULL) { + val.add(new DefaultMutableTreeNode("YTargetedDisplayAR: " + + Long.toString(ln), false)); + } + if ((s = niso.getPreferredPresentation()) != null) { + val.add(new DefaultMutableTreeNode("PreferredPresentation: " + s, + false)); + } + if ((s = niso.getSourceType()) != null) { + val.add(new DefaultMutableTreeNode("SourceType: " + s, false)); + } + if ((s = niso.getImageProducer()) != null) { + val.add(new DefaultMutableTreeNode("ImageProducer: " + s, false)); + } + if ((s = niso.getHostComputer()) != null) { + val.add(new DefaultMutableTreeNode("HostComputer: " + s, false)); + } + if ((s = niso.getOS()) != null) { + val.add(new DefaultMutableTreeNode("OperatingSystem: " + s, false)); + } + if ((s = niso.getOSVersion()) != null) { + val.add(new DefaultMutableTreeNode("OSVersion: " + s, false)); + } + if ((s = niso.getDeviceSource()) != null) { + val.add(new DefaultMutableTreeNode("DeviceSource: " + s, false)); + } + if ((s = niso.getScannerManufacturer()) != null) { + val.add(new DefaultMutableTreeNode("ScannerManufacturer: " + s, + false)); + } + if ((s = niso.getScannerModelName()) != null) { + val.add(new DefaultMutableTreeNode("ScannerModelName: " + s, false)); + } + if ((s = niso.getScannerModelNumber()) != null) { + val.add(new DefaultMutableTreeNode("ScannerModelNumber: " + s, + false)); + } + if ((s = niso.getScannerModelSerialNo()) != null) { + val.add(new DefaultMutableTreeNode("ScannerModelSerialNo: " + s, + false)); + } + if ((s = niso.getScanningSoftware()) != null) { + val.add(new DefaultMutableTreeNode("ScanningSoftware: " + s, false)); + } + if ((s = niso.getScanningSoftwareVersionNo()) != null) { + val.add(new DefaultMutableTreeNode("ScanningSoftwareVersionNo: " + + s, false)); + } + double d = niso.getPixelSize(); + if (d != NisoImageMetadata.NILL) { + val.add(new DefaultMutableTreeNode("PixelSize: " + + Double.toString(d), false)); + } + if ((d = niso.getXPhysScanResolution()) != NisoImageMetadata.NILL) { + val.add(new DefaultMutableTreeNode("XPhysScanResolution: " + + Double.toString(d), false)); + } + if ((d = niso.getYPhysScanResolution()) != NisoImageMetadata.NILL) { + val.add(new DefaultMutableTreeNode("YPhysScanResolution: " + + Double.toString(d), false)); + } + + if ((s = niso.getDigitalCameraManufacturer()) != null) { + val.add(new DefaultMutableTreeNode("DigitalCameraManufacturer: " + + s, false)); + } + if ((s = niso.getDigitalCameraModelName()) != null) { + val.add(new DefaultMutableTreeNode("DigitalCameraModelName: " + s, + false)); + } + if ((s = niso.getDigitalCameraModelNumber()) != null) { + val.add(new DefaultMutableTreeNode( + "DigitalCameraModelNumber: " + s, false)); + } + if ((s = niso.getDigitalCameraModelSerialNo()) != null) { + val.add(new DefaultMutableTreeNode("DigitalCameraModelSerialNo: " + + s, false)); + } + if ((d = niso.getFNumber()) != NisoImageMetadata.NILL) { + val.add(new DefaultMutableTreeNode( + "FNumber: " + Double.toString(d), false)); + } + if ((d = niso.getExposureTime()) != NisoImageMetadata.NILL) { + val.add(new DefaultMutableTreeNode("ExposureTime: " + + Double.toString(d), false)); + } + if ((n = niso.getExposureProgram()) != NisoImageMetadata.NULL) { + val.add(new DefaultMutableTreeNode("ExposureProgram: " + + Integer.toString(n), false)); + } + if ((s = niso.getExifVersion()) != null) { + val.add(new DefaultMutableTreeNode("ExifVersion: " + s, false)); + } + Rational r; + if ((r = niso.getBrightness()) != null) { + val.add(new DefaultMutableTreeNode("Brightness: " + r.toString(), + false)); + } + if ((r = niso.getExposureBias()) != null) { + val.add(new DefaultMutableTreeNode("ExposureBias: " + r.toString(), + false)); + } + + double[] darray = niso.getSubjectDistance(); + if (darray != null) { + DefaultMutableTreeNode nod = new DefaultMutableTreeNode( + "SubjectDistance", true); + val.add(nod); + for (int i = 0; i < darray.length; i++) { + nod.add(new DefaultMutableTreeNode(Double.toString(darray[i]), + false)); + } + } + if ((n = niso.getMeteringMode()) != NisoImageMetadata.NULL) { + val.add(new DefaultMutableTreeNode("MeteringMode: " + + Integer.toString(n), false)); + } + if ((n = niso.getSceneIlluminant()) != NisoImageMetadata.NULL) { + val.add(new DefaultMutableTreeNode("SceneIlluminant: " + + Integer.toString(n), false)); + } + if ((d = niso.getColorTemp()) != NisoImageMetadata.NILL) { + val.add(new DefaultMutableTreeNode("ColorTemp: " + + Double.toString(d), false)); + } + if ((d = niso.getFocalLength()) != NisoImageMetadata.NILL) { + val.add(new DefaultMutableTreeNode("FocalLength: " + + Double.toString(d), false)); + } + if ((n = niso.getFlash()) != NisoImageMetadata.NULL) { + // First bit (0 = Flash did not fire, 1 = Flash fired) + val.add(new DefaultMutableTreeNode("Flash: " + + integerRepresentation(n & 1, NisoImageMetadata.FLASH_20), + false)); + } + if ((r = niso.getFlashEnergy()) != null) { + val.add(new DefaultMutableTreeNode("FlashEnergy: " + r.toString(), + false)); + } + if ((n = niso.getFlashReturn()) != NisoImageMetadata.NULL) { + val.add(new DefaultMutableTreeNode("FlashReturn: " + + integerRepresentation(n, NisoImageMetadata.FLASH_RETURN), + false)); + } + if ((n = niso.getBackLight()) != NisoImageMetadata.NULL) { + val.add(new DefaultMutableTreeNode("BackLight: " + + integerRepresentation(n, NisoImageMetadata.BACKLIGHT), + false)); + } + if ((d = niso.getExposureIndex()) != NisoImageMetadata.NILL) { + val.add(new DefaultMutableTreeNode("ExposureIndex: " + + Double.toString(d), false)); + } + if ((n = niso.getAutoFocus()) != NisoImageMetadata.NULL) { + val.add(new DefaultMutableTreeNode("AutoFocus: " + + Integer.toString(n), false)); + // NisoImageMetadata.AUTOFOCUS + } + if ((d = niso.getXPrintAspectRatio()) != NisoImageMetadata.NILL) { + val.add(new DefaultMutableTreeNode("XPrintAspectRatio: " + + Double.toString(d), false)); + } + if ((d = niso.getYPrintAspectRatio()) != NisoImageMetadata.NILL) { + val.add(new DefaultMutableTreeNode("YPrintAspectRatio: " + + Double.toString(d), false)); + } + + if ((n = niso.getSensor()) != NisoImageMetadata.NULL) { + val.add(new DefaultMutableTreeNode("Sensor: " + + integerRepresentation(n, NisoImageMetadata.SENSOR), false)); + } + if ((s = niso.getDateTimeCreated()) != null) { + val.add(new DefaultMutableTreeNode("DateTimeCreated: " + s, false)); + } + if ((s = niso.getMethodology()) != null) { + val.add(new DefaultMutableTreeNode("Methodology: " + s, false)); + } + if ((n = niso.getSamplingFrequencyPlane()) != NisoImageMetadata.NULL) { + val.add(new DefaultMutableTreeNode("SamplingFrequencyPlane: " + + integerRepresentation(n, + NisoImageMetadata.SAMPLING_FREQUENCY_PLANE), + false)); + } + if ((n = niso.getSamplingFrequencyUnit()) != NisoImageMetadata.NULL) { + val.add(new DefaultMutableTreeNode("SamplingFrequencyUnit: " + + integerRepresentation(n, + NisoImageMetadata.SAMPLING_FREQUENCY_UNIT), + false)); + } + Rational rat = niso.getXSamplingFrequency(); + if (rat != null) { + val.add(new DefaultMutableTreeNode("XSamplingFrequency: " + + rat.toString(), false)); + } + rat = niso.getYSamplingFrequency(); + if (rat != null) { + val.add(new DefaultMutableTreeNode("YSamplingFrequency: " + + rat.toString(), false)); + } + if ((ln = niso.getImageWidth()) != NisoImageMetadata.NULL) { + val.add(new DefaultMutableTreeNode("ImageWidth: " + + Long.toString(ln), false)); + } + if ((ln = niso.getImageLength()) != NisoImageMetadata.NULL) { + val.add(new DefaultMutableTreeNode("ImageLength: " + + Long.toString(ln), false)); + } + if ((d = niso.getSourceXDimension()) != NisoImageMetadata.NILL) { + val.add(new DefaultMutableTreeNode("SourceXDimension: " + + Double.toString(d), false)); + } + if ((n = niso.getSourceXDimensionUnit()) != NisoImageMetadata.NULL) { + val.add(new DefaultMutableTreeNode("SourceXDimensionUnit: " + + integerRepresentation(n, + NisoImageMetadata.SOURCE_DIMENSION_UNIT), + false)); + } + if ((d = niso.getSourceYDimension()) != NisoImageMetadata.NILL) { + val.add(new DefaultMutableTreeNode("SourceYDimension: " + + Double.toString(d), false)); + } + if ((n = niso.getSourceYDimensionUnit()) != NisoImageMetadata.NULL) { + val.add(new DefaultMutableTreeNode("SourceYDimensionUnit: " + + integerRepresentation(n, + NisoImageMetadata.SOURCE_DIMENSION_UNIT), + false)); + } + if ((iarray = niso.getBitsPerSample()) != null) { + DefaultMutableTreeNode nod = (new DefaultMutableTreeNode( + "BitsPerSample")); + val.add(nod); + for (int i = 0; i < iarray.length; i++) { + nod.add(new DefaultMutableTreeNode(Integer.toString(iarray[i]), + false)); + // BitsPerSampleUnit Integer is assumed, because of BitsPerSample + // According to the specification, it can also be float. + // This is currently not supported by jHove + nod.add(new DefaultMutableTreeNode("Integer")); + } + } + if ((n = niso.getSamplesPerPixel()) != NisoImageMetadata.NULL) { + val.add(new DefaultMutableTreeNode("SamplesPerPixel: " + + Integer.toString(n), false)); + } + if ((iarray = niso.getExtraSamples()) != null) { + DefaultMutableTreeNode nod = (new DefaultMutableTreeNode( + "ExtraSamples")); + val.add(nod); + for (int i = 0; i < iarray.length; i++) { + nod.add(new DefaultMutableTreeNode(integerRepresentation( + iarray[i], NisoImageMetadata.EXTRA_SAMPLES), false)); + } + } + if ((s = niso.getColormapReference()) != null) { + val.add(new DefaultMutableTreeNode("ColormapReference: " + s)); + } + if ((iarray = niso.getColormapBitCodeValue()) != null) { + DefaultMutableTreeNode nod = (new DefaultMutableTreeNode( + "ColormapBitCodeValue")); + val.add(nod); + for (int i = 0; i < iarray.length; i++) { + nod.add(new DefaultMutableTreeNode(Integer.toString(iarray[i]), + false)); + } + } + if ((iarray = niso.getColormapRedValue()) != null) { + DefaultMutableTreeNode nod = (new DefaultMutableTreeNode( + "ColormapRedValue")); + val.add(nod); + for (int i = 0; i < iarray.length; i++) { + nod.add(new DefaultMutableTreeNode(Integer.toString(iarray[i]), + false)); + } + } + if ((iarray = niso.getColormapGreenValue()) != null) { + DefaultMutableTreeNode nod = (new DefaultMutableTreeNode( + "ColormapGreenValue")); + val.add(nod); + for (int i = 0; i < iarray.length; i++) { + nod.add(new DefaultMutableTreeNode(Integer.toString(iarray[i]), + false)); + } + } + if ((iarray = niso.getColormapBlueValue()) != null) { + DefaultMutableTreeNode nod = (new DefaultMutableTreeNode( + "ColormapBlueValue")); + val.add(nod); + for (int i = 0; i < iarray.length; i++) { + nod.add(new DefaultMutableTreeNode(Integer.toString(iarray[i]), + false)); + } + } + if ((iarray = niso.getGrayResponseCurve()) != null) { + DefaultMutableTreeNode nod = (new DefaultMutableTreeNode( + "GrayResponseCurve")); + val.add(nod); + for (int i = 0; i < iarray.length; i++) { + nod.add(new DefaultMutableTreeNode(Integer.toString(iarray[i]), + false)); + } + } + if ((n = niso.getGrayResponseUnit()) != NisoImageMetadata.NULL) { + val.add(new DefaultMutableTreeNode("GrayResponseUnit: " + + Integer.toString(n), false)); + } + r = niso.getWhitePointXValue(); + if (r != null) { + val.add(new DefaultMutableTreeNode("WhitePointXValue: " + + r.toString(), false)); + } + if ((r = niso.getWhitePointYValue()) != null) { + val.add(new DefaultMutableTreeNode("WhitePointYValue: " + + r.toString(), false)); + } + if ((r = niso.getPrimaryChromaticitiesRedX()) != null) { + val.add(new DefaultMutableTreeNode("PrimaryChromaticitiesRedX: " + + r.toString(), false)); + } + if ((r = niso.getPrimaryChromaticitiesRedY()) != null) { + val.add(new DefaultMutableTreeNode("PrimaryChromaticitiesRedY: " + + r.toString(), false)); + } + if ((r = niso.getPrimaryChromaticitiesGreenX()) != null) { + val.add(new DefaultMutableTreeNode("PrimaryChromaticitiesGreenX: " + + r.toString(), false)); + } + if ((r = niso.getPrimaryChromaticitiesGreenY()) != null) { + val.add(new DefaultMutableTreeNode("PrimaryChromaticitiesGreenY: " + + r.toString(), false)); + } + if ((r = niso.getPrimaryChromaticitiesBlueX()) != null) { + val.add(new DefaultMutableTreeNode("PrimaryChromaticitiesBlueX: " + + r.toString())); + } + if ((r = niso.getPrimaryChromaticitiesBlueY()) != null) { + val.add(new DefaultMutableTreeNode("PrimaryChromaticitiesBlueY: " + + r.toString())); + } + if ((n = niso.getTargetType()) != NisoImageMetadata.NULL) { + val.add(new DefaultMutableTreeNode("TargetType: " + + integerRepresentation(n, NisoImageMetadata.TARGET_TYPE), + false)); + } + if ((s = niso.getTargetIDManufacturer()) != null) { + val.add(new DefaultMutableTreeNode("TargetIDManufacturer: " + s, + false)); + } + if ((s = niso.getTargetIDName()) != null) { + val.add(new DefaultMutableTreeNode("TargetIDName: " + s, false)); + } + if ((s = niso.getTargetIDNo()) != null) { + val.add(new DefaultMutableTreeNode("TargetIDNo: " + s, false)); + } + if ((s = niso.getTargetIDMedia()) != null) { + val.add(new DefaultMutableTreeNode("TargetIDMedia: " + s, false)); + } + if ((s = niso.getImageData()) != null) { + val.add(new DefaultMutableTreeNode("ImageData: " + s, false)); + } + if ((s = niso.getPerformanceData()) != null) { + val.add(new DefaultMutableTreeNode("PerformanceData: " + s, false)); + } + if ((s = niso.getProfiles()) != null) { + val.add(new DefaultMutableTreeNode("Profiles: " + s, false)); + } + if ((s = niso.getDateTimeProcessed()) != null) { + val.add(new DefaultMutableTreeNode("DateTimeProcessed: " + s, false)); + } + if ((s = niso.getSourceData()) != null) { + val.add(new DefaultMutableTreeNode("SourceData: " + s, false)); + } + if ((s = niso.getProcessingAgency()) != null) { + val.add(new DefaultMutableTreeNode("ProcessingAgency: " + s, false)); + } + if ((s = niso.getProcessingSoftwareName()) != null) { + val.add(new DefaultMutableTreeNode("ProcessingSoftwareName: " + s, + false)); + } + if ((s = niso.getProcessingSoftwareVersion()) != null) { + val.add(new DefaultMutableTreeNode("ProcessingSoftwareVersion: " + + s, false)); + } + String[] sarray = niso.getProcessingActions(); + if (sarray != null) { + DefaultMutableTreeNode nod = new DefaultMutableTreeNode( + "ProcessingActions", true); + val.add(nod); + for (int i = 1; i < sarray.length; i++) { + nod.add(new DefaultMutableTreeNode(sarray[i], false)); + } + } + return val; + } + + /* + * Return the string equivalent of an integer if raw output isn't selected, + * or its literal representation if it is. + */ + private String integerRepresentation(int n, String[] labels) { + if (_rawOutput) { + return Integer.toString(n); + } + try { + return labels[n]; + } catch (Exception e) { + return Integer.toString(n); + } + } + + private String integerRepresentation(int n, String[] labels, int[] index) { + if (_rawOutput) { + return Integer.toString(n); + } + try { + int idx = -1; + for (int i = 0; i < index.length; i++) { + if (n == index[i]) { + idx = i; + break; + } + } + if (idx > -1) { + return labels[idx]; + } + } catch (Exception e) { + } + // If we don't get a match, or do get an exception + return Integer.toString(n); + } + + /** + * This method returns a member of a property list based on it's + * PropertyType + * + * @param ptyp + * the propertytype + * @param item + * the item of the list + * @param allowsChildren + * @return + */ + private DefaultMutableTreeNode getDefaultMutableTreeNode(PropertyType ptyp, + Object item, boolean allowsChildren) { + + DefaultMutableTreeNode itemNode; + + if (null == ptyp) { + // Simple objects just need a leaf. + itemNode = (new DefaultMutableTreeNode(item, allowsChildren)); + } else + // Object item = iter.next (); + switch (ptyp) { + case PROPERTY: + itemNode = (propToNode((Property) item)); + break; + case NISOIMAGEMETADATA: + itemNode = (nisoToNode((NisoImageMetadata) item)); + break; + default: + // Simple objects just need a leaf. + itemNode = (new DefaultMutableTreeNode(item, allowsChildren)); + break; + } + + return itemNode; + } + + /** + * Method to add an array to a node + * + * @param + * generic method, can be used for arrays of different types + * @param node + * the node to add the elements of the array + * @param array + * the array to be added to the node + */ + private void addToNode(DefaultMutableTreeNode node, E[] array) { + for (E element : array) { + if (element instanceof Property) { + node.add(propToNode((Property) element)); + } else { + node.add(new DefaultMutableTreeNode(element)); + } + } + } } diff --git a/jhove-bbt/scripts/create-1.31-target.sh b/jhove-bbt/scripts/create-1.31-target.sh index 4974f6eef..5fe340f40 100755 --- a/jhove-bbt/scripts/create-1.31-target.sh +++ b/jhove-bbt/scripts/create-1.31-target.sh @@ -161,3 +161,8 @@ do cp "${candidateRoot}/${filename}" "${targetRoot}/${filename}" fi done + +# Copy all of the AIF and WAV results as these are changed by the AES schema changes +cp -rf "${candidateRoot}/examples/modules/AIFF-hul" "${targetRoot}/examples/modules/" +cp -rf "${candidateRoot}/examples/modules/WAVE-hul" "${targetRoot}/examples/modules/" +cp -rf "${candidateRoot}/errors/modules/WAVE-hul" "${targetRoot}/errors/modules/" \ No newline at end of file diff --git a/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/AESAudioMetadata.java b/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/AESAudioMetadata.java index d8795178b..ac3642a49 100644 --- a/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/AESAudioMetadata.java +++ b/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/AESAudioMetadata.java @@ -14,8 +14,7 @@ * @author Gary McGath * */ -public class AESAudioMetadata -{ +public class AESAudioMetadata { /****************************************************************** * PUBLIC CLASS FIELDS. ******************************************************************/ @@ -27,14 +26,13 @@ public class AESAudioMetadata public static final int LITTLE_ENDIAN = 1; /** Analog / digital labels. */ - public static final String [] A_D = { - "ANALOG", "PHYS_DIGITAL", "FILE_DIGITAL" + 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"; + public static final String FILE_NAME = "FILE_NAME", + OTHER = "OTHER"; /** Constant for an undefined integer value. */ public static final int NULL = -1; @@ -47,8 +45,8 @@ public class AESAudioMetadata ******************************************************************/ /** Constant value for the SchemaVersion field */ - public static final String SCHEMA_VERSION = "1.02b"; - + public static final String SCHEMA_VERSION = "1.0.0"; + /** Constant value for the disposition field */ private static final String DEFAULT_DISPOSITION = "validation"; @@ -78,10 +76,10 @@ public class AESAudioMetadata * CLASS CONSTRUCTOR. ******************************************************************/ - /** Instantiate a NisoImageMetadata object. + /** + * Instantiate a NisoImageMetadata object. */ - public AESAudioMetadata () - { + public AESAudioMetadata() { _schemaVersion = SCHEMA_VERSION; _disposition = DEFAULT_DISPOSITION; _analogDigitalFlag = null; @@ -92,151 +90,170 @@ public AESAudioMetadata () _primaryIdentifierType = null; _use = null; - // We add one format region to get started. In practice, + // 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 (); + _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 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 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 (); + public String[] getBitrateReduction(); + /** Returns the sample rate. */ - public double getSampleRate (); + public double getSampleRate(); + /** Returns the word size. */ - int getWordSize (); + int getWordSize(); + /** Returns true if the region is empty. */ - public boolean isEmpty (); + public boolean isEmpty(); + /** Sets the bit depth value. */ - public void setBitDepth (int bitDepth); + public void setBitDepth(int bitDepth); + /** Sets the bitrate reduction information to null (no compression). */ - public void clearBitrateReduction (); + public void clearBitrateReduction(); + /** Sets the bitrate reduction (aka compression type). */ - public void setBitrateReduction (String codecName, + 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); + public void setSampleRate(double sampleRate); + /** Sets the word size. */ - public void setWordSize (int wordSize); + 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 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 long getHours (); - /** Returns the minutes component. */ - public long getMinutes (); - /** Returns the seconds component. */ - public long getSeconds (); - /** Returns the frames component of the fraction of a second. - * We always consider frames to be thirtieths of a second. */ - public long getFrames (); - /** Returns the samples remaining after the frames part of - * the fractional second. */ - public long 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. */ + /** + * Returns the number of samples. + */ + public long getSamples(); + + /** + * Returns the sample rate. + */ + 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 (); - + 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 (); - + public TimeDesc getStartTime(); + /** Returns the duration. */ - public TimeDesc getDuration (); - + 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 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); + 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 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 (); - + public TimeDesc getStartTime(); + /** Returns the duration. */ - public TimeDesc getDuration (); + public TimeDesc getDuration(); - /** Returns the channel map locations. The array length must - * equal the number of channels. */ - public String[] getMapLocations (); + /** + * 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); - + 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); + 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 */ } @@ -245,101 +262,97 @@ public static 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_. + /** + * 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 () { + public FormatRegionImpl() { _bitDepth = NULL; _sampleRate = NILL; _wordSize = NULL; _bitrateReduction = null; } - + /** Returns bit depth. */ @Override - public int getBitDepth () - { + 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 - *
+ + /** + * 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 + *
*/ @Override - public String[] getBitrateReduction () - { + public String[] getBitrateReduction() { return _bitrateReduction; } - + /** Returns sample rate. */ @Override - public double getSampleRate () - { + public double getSampleRate() { return _sampleRate; } - + /** Returns word size. */ @Override - public int getWordSize () - { + public int getWordSize() { return _wordSize; } - - /** Returns true if the FormatRegion contains only - * default values. */ + + /** + * Returns true if the FormatRegion contains only + * default values. + */ @Override - public boolean isEmpty () - { + public boolean isEmpty() { return _bitDepth == NULL && - _sampleRate == NILL && - _wordSize == NULL; + _sampleRate == NILL && + _wordSize == NULL; } /** Sets bit depth. */ @Override - public void setBitDepth (int bitDepth) - { + public void setBitDepth(int bitDepth) { _bitDepth = bitDepth; } /** Sets the bitrate reduction information to null (no compression). */ @Override - public void clearBitrateReduction () - { + public void clearBitrateReduction() { _bitrateReduction = null; } /** Sets the bitrate reduction (compression type). */ @Override - public void setBitrateReduction (String codecName, + public void setBitrateReduction(String codecName, String codecNameVersion, String codecCreatorApplication, String codecCreatorApplicationVersion, String codecQuality, String dataRate, - String dataRateMode) - { + String dataRateMode) { _bitrateReduction = new String[7]; _bitrateReduction[0] = codecName; - _bitrateReduction[1] = codecNameVersion; + _bitrateReduction[1] = codecNameVersion; _bitrateReduction[2] = codecCreatorApplication; _bitrateReduction[3] = codecCreatorApplicationVersion; _bitrateReduction[4] = codecQuality; @@ -349,275 +362,225 @@ public void setBitrateReduction (String codecName, /** Sets sample rate. */ @Override - public void setSampleRate (double sampleRate) - { + public void setSampleRate(double sampleRate) { _sampleRate = sampleRate; } - - + /** Sets word size. */ @Override - public void setWordSize (int wordSize) - { + 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_. + /** + * 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 long _hours; - private long _minutes; - private long _seconds; - private long _frames; + class TimeDescImpl implements TimeDesc { private long _samples; private double _sampleRate; - private long _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 = _sample_count / sample_in_1_hour; - _sample_count -= _hours * sample_in_1_hour; - _minutes = _sample_count / sample_in_1_minute; - _sample_count -= _minutes * sample_in_1_minute; - _seconds = _sample_count / sample_in_1_second; - _sample_count -= _seconds * sample_in_1_second; - _frames = _sample_count / sample_in_1_frame; - _sample_count -= _frames * sample_in_1_frame; - _samples = _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. */ - @Override - public long getHours () { - return _hours; - } - /** Returns the minutes component. */ - @Override - public long getMinutes () { - return _minutes; - } - - /** Returns the seconds component. */ - @Override - public long getSeconds () { - return _seconds; - } + /* + * 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) { + _samples = samples; + _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 + } - /** Returns the frames component of the fraction of a second. - * We always consider frames to be thirtieths of a second. */ - @Override - public long getFrames () { - return _frames; } - /** Returns the samples remaining after the frames part of - * the fractional second. */ + /** + * Returns the number of samples. + */ @Override - public long getSamples () { + public long getSamples() { return _samples; } - - /** Returns the sample rate on which the samples remainder - * is based. */ + + /** + * Returns the sample rate. + */ @Override - public double getSampleRate () { + 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_. + /** + * 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 { private List _regionList; private TimeDesc _startTime; private TimeDesc _duration; private String _direction; - - - /** Constructor. Initially the duration is set - * to null, indicating unknown value. */ - public FaceImpl () - { + + /** + * Constructor. Initially the duration is set + * to null, indicating unknown value. + */ + public FaceImpl() { _regionList = new ArrayList<>(); - _startTime = new TimeDescImpl (0); + _startTime = new TimeDescImpl(0); _duration = null; } - /** Returns an indexed FaceRegion. */ @Override - public FaceRegion getFaceRegion (int i) { - return _regionList.get (i); + public FaceRegion getFaceRegion(int i) { + return _regionList.get(i); } - - /** Adds a FaceRegion. This may be called repeatedly to - * add multiple FaceRegions. */ + + /** + * Adds a FaceRegion. This may be called repeatedly to + * add multiple FaceRegions. + */ @Override - public void addFaceRegion () { - _regionList.add (new FaceRegionImpl ()); + public void addFaceRegion() { + _regionList.add(new FaceRegionImpl()); } - - /** Returns the starting time. Will be zero if not - * explicitly specified. */ + + /** + * Returns the starting time. Will be zero if not + * explicitly specified. + */ @Override - public TimeDesc getStartTime () { + public TimeDesc getStartTime() { return _startTime; } - - /** Returns the duration. May be null if the duration - * is unspecified. */ + + /** + * Returns the duration. May be null if the duration + * is unspecified. + */ @Override - public TimeDesc getDuration () { + public TimeDesc getDuration() { return _duration; } /** Returns the direction. */ @Override - public String getDirection () - { + public String getDirection() { return _direction; } - /** Sets the starting time. This will be converted - * into a TimeDesc. */ + /** + * Sets the starting time. This will be converted + * into a TimeDesc. + */ @Override - public void setStartTime (long samples) - { - _startTime = new TimeDescImpl (samples); + public void setStartTime(long samples) { + _startTime = new TimeDescImpl(samples); } - - /** Sets the duration. This will be converted - * into a TimeDesc. */ + + /** + * Sets the duration. This will be converted + * into a TimeDesc. + */ @Override - public void setDuration (long samples) - { - _duration = new TimeDescImpl (samples); + 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. + /** + * Sets the direction. This must be one of the + * directionTypes. FORWARD is recommended for most + * or all cases. */ @Override - public void setDirection (String direction) - { + 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_. + /** + * 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); + + public FaceRegionImpl() { + _startTime = new TimeDescImpl(0); _duration = null; } - + /** Returns the starting time. */ @Override - public TimeDesc getStartTime () { + public TimeDesc getStartTime() { return _startTime; } - + /** Returns the duration. */ @Override - public TimeDesc getDuration () { + public TimeDesc getDuration() { return _duration; } - - /** Returns the channel map locations. The array length - * will equal the number of channels. */ + + /** + * Returns the channel map locations. The array length + * will equal the number of channels. + */ @Override - public String[] getMapLocations () - { + public String[] getMapLocations() { return _mapLocations; } - /** Sets the duration. This will be converted - * into a TimeDesc. */ + /** + * Sets the duration. This will be converted + * into a TimeDesc. + */ @Override - public void setStartTime (long samples) { - _startTime = new TimeDescImpl (samples); + public void setStartTime(long samples) { + _startTime = new TimeDescImpl(samples); } - - /** Sets the duration. This will be converted - * into a TimeDesc. */ + + /** + * Sets the duration. This will be converted + * into a TimeDesc. + */ @Override - public void setDuration (long samples) - { - _duration = new TimeDescImpl (samples); + public void setDuration(long samples) { + _duration = new TimeDescImpl(samples); } - /** Sets the channel map locations. The array length must - * equal the number of channels. */ + /** + * Sets the channel map locations. The array length must + * equal the number of channels. + */ @Override - public void setMapLocations (String[] locations) - { + public void setMapLocations(String[] locations) { _mapLocations = locations; - } + } /* End of FaceRegionImpl */ } - + /* End of inner classes */ /****************************************************************** @@ -626,345 +589,327 @@ public void setMapLocations (String[] locations) * Accessor methods. ******************************************************************/ - /** Returns analog/digital flag. Value will always be - * "FILE_DIGITAL" in practice. */ - public String getAnalogDigitalFlag () - { + /** + * 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. + + /** + * Returns application-specific data. We assume this is + * representable in String format. */ - public String getAppSpecificData () - { + public String getAppSpecificData() { return _appSpecificData; } - + /** Returns audio data encoding. */ - public String getAudioDataEncoding () - { + 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 - *
+ /** + * 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 () - { + public String[] getBitrateReduction() { return _curFormatRegion.getBitrateReduction(); } /* Returns the sample rate. */ - public double getSampleRate () - { - return _curFormatRegion.getSampleRate (); + public double getSampleRate() { + return _curFormatRegion.getSampleRate(); } /** Return the byte order: 0 = big-endian; 1 = little-endian. */ - public int getByteOrder () - { + public int getByteOrder() { return _byteOrder; } - + /** Returns disposition. */ - public String getDisposition () - { + public String getDisposition() { return _disposition; } - - /** Gets the list of Faces. Normally there will be only one face - * in a digital file. */ - public List getFaceList () - { + + /** + * 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 () - { + public long getFirstSampleOffset() { return _firstSampleOffset; } /** Returns format name. */ - public String getFormat () - { + 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 (). + + /** + * 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 () - { + 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. + + /** + * 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 () - { + public int getNumChannels() { return _numChannels; } /** Returns primary identifier. */ - public String getPrimaryIdentifier () - { + public String getPrimaryIdentifier() { return _primaryIdentifier; } /** Returns primary identifier type. */ - public String getPrimaryIdentifierType () - { + public String getPrimaryIdentifierType() { return _primaryIdentifierType; } /** Returns schema version. */ - public String getSchemaVersion () - { + public String getSchemaVersion() { return _schemaVersion; } - + /** Returns specification version of the document format. */ - public String getSpecificationVersion () - { + 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 () - { + + /** + * 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) - { + + /** + * 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, + public void setBitrateReduction(String codecName, String codecNameVersion, String codecCreatorApplication, String codecCreatorApplicationVersion, String codecQuality, String dataRate, - String dataRateMode) - { - _curFormatRegion.setBitrateReduction (codecName, + 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 (); + public void clearBitrateReduction() { + _curFormatRegion.clearBitrateReduction(); } - /** Sets the byte order. + /** + * Sets the byte order. + * * @param order Byte order: 0 = big-endian, 1 = little-endian */ - public void setByteOrder (int order) - { - _byteOrder = order; + public void setByteOrder(int order) { + _byteOrder = order; } - /** Sets the byte 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; - } + 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) - { + 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) - { + + /** + * 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); + public void setBitDepth(int bitDepth) { + _curFormatRegion.setBitDepth(bitDepth); } - + /** Sets the disposition. */ - public void setDisposition (String 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. + /** + * 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); + public void setDirection(String direction) { + _curFace.setDirection(direction); } - - /** Sets the duration in samples. + + /** + * 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); + 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) - { + public void setFirstSampleOffset(long offset) { _firstSampleOffset = offset; } /** Sets the format name. */ - public void setFormat (String format) - { + 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 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) - { + public void setNumChannels(int numChannels) { _numChannels = numChannels; } /** Sets the primary identifier. */ - public void setPrimaryIdentifier (String primaryIdentifier) - { + public void setPrimaryIdentifier(String primaryIdentifier) { _primaryIdentifier = primaryIdentifier; } - - /** Sets the primary identifier type. If the primary identifier - * type is OTHER, use setOtherPrimaryIdentifierType instead. + + /** + * Sets the primary identifier type. If the primary identifier + * type is OTHER, use setOtherPrimaryIdentifierType instead. */ - public void setPrimaryIdentifierType (String primaryIdentifierType) - { + public void setPrimaryIdentifierType(String primaryIdentifierType) { _primaryIdentifierType = primaryIdentifierType; } - /** Sets the primary identifier type as "OTHER", and - * set the otherType. + /** + * Sets the primary identifier type as "OTHER", and + * set the otherType. */ - public void setOtherPrimaryIdentifierType (String 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) - { + 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. + /** + * 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}; - } - + 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 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); + public void addFormatRegion() { + _curFormatRegion = new FormatRegionImpl(); + _formatList.add(_curFormatRegion); } - - /** Adds a Face. + + /** + * Adds a Face. */ - public void addFace () - { - _curFace = new FaceImpl (); - _faceList.add (_curFace); + public void addFace() { + _curFace = new FaceImpl(); + _faceList.add(_curFace); _curFace.addFaceRegion(); } - + } diff --git a/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/handler/JsonHandler.java b/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/handler/JsonHandler.java index 497983268..c6d4a23fe 100644 --- a/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/handler/JsonHandler.java +++ b/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/handler/JsonHandler.java @@ -1944,13 +1944,11 @@ protected JsonObjectBuilder showAESAudioMetadata(AESAudioMetadata aes) { if (nchan != AESAudioMetadata.NULL) { faceBuilder.add("aes:numChannels", nchan); } - String[] locs = aes.getMapLocations(); JsonArrayBuilder streamsBuilder = Json.createArrayBuilder(); for (int ch = 0; ch < nchan; ch++) { // write a stream description for each channel streamsBuilder.add(Json.createObjectBuilder() - .add("aes:channelNum", ch) - .add("aes:mapLocation", locs[ch])); + .add("aes:channelNum", ch)); } faceBuilder.add("aes:streams", streamsBuilder); aesBuilder.add("aes:face", faceBuilder); @@ -2016,72 +2014,26 @@ private void showAesFormatList(List flist, */ private JsonObjectBuilder writeAESTimeRange( AESAudioMetadata.TimeDesc start, AESAudioMetadata.TimeDesc duration) { - double sr = start.getSampleRate(); - if (sr == 1.0) { - sr = _sampleRate; - } - JsonObjectBuilder timerangeBuilder = Json.createObjectBuilder(); - timerangeBuilder - .add("tcf:startTime", - Json.createObjectBuilder() - .add("tcf:frameCount", 30) - .add("tcf:timeBase", 1000) - .add("tcf:videoField", "FIELD_1") - .add("tcf:countingMode", NTSC_NON_DROP_FRAME) - .add("tcf:hours", start.getHours()) - .add("tcf:minutes", start.getMinutes()) - .add("tcf:seconds", start.getSeconds()) - .add("tcf:frames", start.getFrames()) - .add("tcf:samples", - Json.createObjectBuilder() - .add("tcf:sampleRate", - "S" - + Integer - .toString((int) sr)) - .add("tcf:numberOfSamples", - start.getSamples())) - .add("tcf:filmFraming", - Json.createObjectBuilder() - .add("tcf:framing", - "NOT_APPLICABLE") - .add("tcf:framingType", - "tcf:ntscFilmFramingType"))); + this.writeAESTimeRangePart(timerangeBuilder, "aes:start", start); + if (duration != null) { - sr = duration.getSampleRate(); - if (sr == 1.0) { - sr = _sampleRate; - } - timerangeBuilder - .add("tcf:duration", - Json.createObjectBuilder() - .add("tcf:frameCount", 30) - .add("tcf:timeBase", 1000) - .add("tcf:videoField", "FIELD_1") - .add("tcf:countingMode", - NTSC_NON_DROP_FRAME) - .add("tcf:hours", duration.getHours()) - .add("tcf:minutes", duration.getMinutes()) - .add("tcf:seconds", duration.getSeconds()) - .add("tcf:frames", duration.getFrames()) - .add("tcf:samples", - Json.createObjectBuilder() - .add("tcf:sampleRate", - "S" - + Integer - .toString((int) sr)) - .add("tcf:numberOfSamples", - duration.getSamples())) - .add("tcf:filmFraming", - Json.createObjectBuilder() - .add("tcf:framing", - "NOT_APPLICABLE") - .add("tcf:framingType", - "tcf:ntscFilmFramingType"))); + this.writeAESTimeRangePart(timerangeBuilder, "aes:duration", duration); } return timerangeBuilder; } + private void writeAESTimeRangePart(final JsonObjectBuilder timerangeBuilder, final String name, + AESAudioMetadata.TimeDesc part) { + double sr = (part.getSampleRate() == 1.0) ? _sampleRate : part.getSampleRate(); + timerangeBuilder + .add(name, + Json.createObjectBuilder() + .add("aes:value", part.getSamples()) + .add("aes:editRate", (int) sr) + .add("aes:factorNumerator", "1") + .add("aes:factorDenominator", "1")); + } protected JsonArrayBuilder showArray(int[] iarray) { JsonArrayBuilder aBuilder = Json.createArrayBuilder(); for (int i : iarray) { diff --git a/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/handler/TextHandler.java b/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/handler/TextHandler.java index 66d605543..979c2d8f7 100644 --- a/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/handler/TextHandler.java +++ b/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/handler/TextHandler.java @@ -933,17 +933,23 @@ private void showAESAudioMetadata(AESAudioMetadata aes, String margin, if (startTime != null) { writeAESTimeRange(margn3, startTime, f.getDuration()); } + + // For the present, assume just one face region + AESAudioMetadata.FaceRegion facergn = f.getFaceRegion(0); + _writer.println(margn3 + "Region: "); + _writer.println (margn4 + "TimeRange: "); + writeAESTimeRange(margn4, facergn.getStartTime(), facergn.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++) { + + 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]); } } @@ -997,58 +1003,28 @@ private void showAESAudioMetadata(AESAudioMetadata aes, String margin, /* 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: " + Long.toString(start.getHours())); - _writer.println(margn2 + "Minutes: " - + Long.toString(start.getMinutes())); - _writer.println(margn2 + "Seconds: " - + Long.toString(start.getSeconds())); - _writer.println(margn2 + "Frames: " + Long.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: " - + Long.toString(start.getSamples())); - _writer.println(margn2 + "FilmFraming: NOT_APPLICABLE"); - _writer.println(margn3 + "Type: ntscFilmFramingType"); + writeAESTimeRangePart(baseIndent, "StartTime", start); 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: " - + Long.toString(duration.getHours())); - _writer.println(margn2 + "Minutes: " - + Long.toString(duration.getMinutes())); - _writer.println(margn2 + "Seconds: " - + Long.toString(duration.getSeconds())); - _writer.println(margn2 + "Frames: " - + Long.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: " - + Long.toString(duration.getSamples())); - _writer.println(margn2 + "FilmFraming: NOT_APPLICABLE"); - _writer.println(margn3 + "Type: ntscFilmFramingType"); + writeAESTimeRangePart(baseIndent, "Duration", duration); } } + private void writeAESTimeRangePart(String baseIndent, String name, AESAudioMetadata.TimeDesc timeDesc) { + String margn1 = baseIndent + " "; + String margn2 = margn1 + " "; + + _writer.println (margn1 + name + ": "); + + double sampleRate = timeDesc.getSampleRate(); + if (sampleRate == 1.0) { + sampleRate = _sampleRate; + } + + _writer.println (margn2 + "Value: " + String.valueOf(timeDesc.getSamples())); + _writer.println (margn2 + "EditRate: " + Integer.toString ((int) sampleRate)); + _writer.println (margn2 + "FactorNumerator: 1"); + _writer.println (margn2 + "FactorDenominator: 1"); + } /** * Display the NISO image metadata formatted according to the MIX schema. diff --git a/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/handler/XmlHandler.java b/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/handler/XmlHandler.java index 68b565e00..cce73f5ae 100644 --- a/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/handler/XmlHandler.java +++ b/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/handler/XmlHandler.java @@ -4247,7 +4247,6 @@ protected void showAESAudioMetadata(AESAudioMetadata aes) { _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 }, @@ -4255,7 +4254,7 @@ protected void showAESAudioMetadata(AESAudioMetadata aes) { aes.getAnalogDigitalFlag() }, { "disposition", "Validated by JHOVE" }, - { "schemaVersion", "1.02b" } }; + { "schemaVersion", aes.getSchemaVersion() } }; _writer.println(margin + elementStart("aes:audioObject", attrs)); String s = aes.getFormat(); if (s != null) { @@ -4340,7 +4339,7 @@ protected void showAESAudioMetadata(AESAudioMetadata aes) { AESAudioMetadata.FaceRegion facergn = f.getFaceRegion(0); _writer.println(margn3 + elementStart("aes:region", faceRegionAttrs)); _writer.println(margn4 + elementStart("aes:timeRange")); - writeAESTimeRange(margn3, + writeAESTimeRange(margn4, facergn.getStartTime(), facergn.getDuration()); _writer.println(margn4 + elementEnd("aes:timeRange")); int nchan = aes.getNumChannels(); @@ -4348,7 +4347,6 @@ protected void showAESAudioMetadata(AESAudioMetadata aes) { _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 = { @@ -4359,8 +4357,7 @@ protected void showAESAudioMetadata(AESAudioMetadata aes) { }; _writer.println(margn4 + elementStart("aes:stream", streamAttrs)); String[][] chanAttrs = { - { "channelNum", Integer.toString(ch) }, - { "mapLocation", locs[ch] } + { "channelNum", Integer.toString(ch) } }; _writer.println(margn5 + element("aes:channelAssignment", chanAttrs)); _writer.println(margn4 + elementEnd("aes:stream")); @@ -4386,7 +4383,10 @@ protected void showAESAudioMetadata(AESAudioMetadata aes) { sampleRate != AESAudioMetadata.NILL || wordSize != AESAudioMetadata.NULL) { _writer.println(margn2 + elementStart("aes:formatList")); - String[][] frAttr = { { "ID", formatRegionID } }; + String[][] frAttr = { { "ID", formatRegionID }, + {"xsi:type", "aes:formatRegionType"}, + {"ownerRef", faceRegionID}, + {"label", "JHOVE"}}; _writer.println(margn3 + elementStart("aes:formatRegion", frAttr)); if (bitDepth != AESAudioMetadata.NULL) { _writer.println(margn4 + element("aes:bitDepth", @@ -4427,36 +4427,32 @@ protected void showAESAudioMetadata(AESAudioMetadata aes) { _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[][] attrs = { - { "tcf:frameCount", "30" }, - { "tcf:timeBase", "1000" }, - { "tcf:videoField", "FIELD_1" }, - { "tcf:countingMode", "NTSC_NON_DROP_FRAME" } - }; - _writer.println(margn1 + elementStart("tcf:startTime", attrs)); - _writer.println(margn2 + element("tcf:hours", - Long.toString(start.getHours()))); - _writer.println(margn2 + element("tcf:minutes", - Long.toString(start.getMinutes()))); - _writer.println(margn2 + element("tcf:seconds", - Long.toString(start.getSeconds()))); - _writer.println(margn2 + element("tcf:frames", - Long.toString(start.getFrames()))); - _writer.println(margn1 + elementEnd("tcf:startTime")); - double sr = start.getSampleRate(); - if (sr == 1.0) { - sr = _sampleRate; + + writeAESTimeRangePart(margn1, "aes:startTime", start); + + if (duration != null) { + writeAESTimeRangePart(margn1, "aes:duration", duration); + } + } + + private void writeAESTimeRangePart(String indent, String elementName, AESAudioMetadata.TimeDesc timeDesc) { + double sampleRate = timeDesc.getSampleRate(); + if (sampleRate == 1.0) { + sampleRate = _sampleRate; } + + String[][] attributes = { + {"editRate", formatters.get().format(sampleRate)}, + {"factorNumerator", "1"}, + {"factorDenominator", "1"} + }; + + _writer.println(indent + + element(elementName, attributes, String.valueOf(timeDesc.getSamples()))); } /* diff --git a/jhove-core/src/test/java/edu/harvard/hul/ois/jhove/handler/JsonHandlerTest.java b/jhove-core/src/test/java/edu/harvard/hul/ois/jhove/handler/JsonHandlerTest.java index 7c4fc2606..5dc1a0d7a 100644 --- a/jhove-core/src/test/java/edu/harvard/hul/ois/jhove/handler/JsonHandlerTest.java +++ b/jhove-core/src/test/java/edu/harvard/hul/ois/jhove/handler/JsonHandlerTest.java @@ -45,449 +45,442 @@ @RunWith(JUnit4.class) public class JsonHandlerTest { - private static final Logger LOGGER = Logger.getLogger(JsonHandlerTest.class - .getName()); - - private static final String TIME_PATTERN = "\"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}([+-][0-9]{2}:[0-9]{2})?\""; - private static final String DATE_PATTERN = "\"date\":\"[^\"]+\""; - private static final String DATE_REPLACEMENT = "\"date\":\"2010-01-01\""; - private static final String RELEASE_PATTERN = "\"release\":\"[^\"]+\""; - private static final String RELEASE_REPLACEMENT = "\"release\":\"DUMMY\""; - private static final String DIR_PATTERN = "\"tempDirectory\":\"[^\"]+\""; - private static final String DIR_REPLACEMENT = "\"tempDirectory\":\"DUMMY\""; - private static final String CONF_PATTERN = "\"configuration\":\"[^\"]+\""; - private static final String CONF_REPLACEMENT = "\"configuration\":\"DUMMY\""; - private static final String RIGHTS_PATTERN = "\"rights\":\"[^\"]+\""; - private static final String RIGHTS_REPLACEMENT = "\"rights\":\"DUMMY\""; - private static final String VENDOR_PATTERN = "\"vendor\":\\{[^\\}]+\\}"; - private static final String VENDOR_REPLACEMENT = "\"vendor\":{\"kind\":\"Vendor\"}"; - private static final String DUMMY = "\"DUMMY\""; - private static final String DUMMY_CK = "8747e564eb53cb2f1dcb9aae0779c2aa"; - private static final String BYTESTREAM = "BYTESTREAM"; - private static final String APP_JSON = - "\"name\":\"TEST\",\"release\":\"DUMMY\",\"date\":\"2010-01-01\",\"executionTime\":\"DUMMY\""; - private static final String API_JSON = - "\"app\":{\"api\":{\"version\":\"1.0\",\"date\":\"2010-01-01\"}," + - "\"configuration\":\"DUMMY\",\"jhoveHome\":\"TEST\",\"encoding\":\"utf-8\",\"tempDirectory\":\"DUMMY\"," + - "\"bufferSize\":131072,\"modules\":[{\"module\":\"BYTESTREAM\",\"release\":\"DUMMY\"}]," + - "\"outputHandlers\":[{\"outputHandler\":\"Audit\",\"release\":\"DUMMY\"}," + - "{\"outputHandler\":\"JSON\",\"release\":\"DUMMY\"},{\"outputHandler\":\"TEXT\",\"release\":\"DUMMY\"}," + - "{\"outputHandler\":\"XML\",\"release\":\"DUMMY\"}],\"usage\":\"usage\",\"rights\":\"DUMMY\"}"; - private static final String HANDLER_JSON = - "\"handler\":{\"name\":\"JSON\",\"release\":\"DUMMY\",\"date\":\"2010-01-01\"," + - "\"vendor\":{\"kind\":\"Vendor\",\"name\":\"Bibliothèque nationale de France\",\"type\":\"Educational\"," + - "\"web\":\"http://www.bnf.fr\"},\"note\":\"\"," + - "\"rights\":\"DUMMY\"}"; - private static final String MODULE_JSON = - "\"module\":{\"name\":\"BYTESTREAM\",\"release\":\"DUMMY\",\"date\":\"2010-01-01\"," + - "\"formats\":[\"bytestream\"],\"mimeTypes\":[\"application/octet-stream\"]," + - "\"features\":[\"edu.harvard.hul.ois.jhove.canValidate\",\"edu.harvard.hul.ois.jhove.canCharacterize\"]," + - "\"methodology\":{\"wellFormed\":\"All bytestreams are well-formed\"}," + - "\"vendor\":{\"kind\":\"Vendor\"}," + - "\"note\":\"This is the default format\",\"rights\":\"DUMMY\"}"; - private static final String INFO_JSON = - "{\"uri\":\"file://dummy.file\"," + - "\"reportingModule\":{\"name\":\"BYTESTREAM\",\"release\":\"DUMMY\",\"date\":\"2010-01-01\"}," + - "\"size\":1,\"format\":\"bytestream\",\"status\":\"Well-Formed and valid\",\"sigMatch\":[\"BYTESTREAM\"]," + - "\"mimeType\":\"application/octet-stream\",\"properties\":[{\"checksum\":\"" + - DUMMY_CK + "\",\"type\":\"MD5\"}]}"; - /** Handler string "Find: " */ - private static final String FIND = "Find: "; - - private static App mockApp; - private static JhoveBase je; - - private File outputFile; - private StringWriter outString; - private PrintWriter writer; - private JsonHandler handler; - - @BeforeClass - public static void setUpBeforeClass() throws JhoveException { - mockApp = new App("TEST", "1.0", new int[]{2019,10,28}, "usage", "rights"); - je = new JhoveBase(); - je.setLogLevel("INFO"); - String fileConf = JsonHandlerTest.class.getResource("/jhove_test.conf").getPath(); - LOGGER.info("jhove.conf in:[" + fileConf + "]"); - je.init(fileConf, null); - } - - @Before - public void setUp() throws IOException { - // Prepare for a new test - this.outputFile = File.createTempFile("jhove_", ".json"); - - outString = new StringWriter(); - writer = new PrintWriter(outString); - - this.handler = new JsonHandler(); - this.handler.setApp(mockApp); - this.handler.setBase(je); - this.handler.setWriter(writer); - } - - @After - public void tearDown() { - if (this.outputFile != null && this.outputFile.exists()) { - this.outputFile.delete(); - } - } - - public void buildJson(JsonObjectBuilder builder) { + private static final Logger LOGGER = Logger.getLogger(JsonHandlerTest.class + .getName()); + + private static final String TIME_PATTERN = "\"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}([+-][0-9]{2}:[0-9]{2})?\""; + private static final String DATE_PATTERN = "\"date\":\"[^\"]+\""; + private static final String DATE_REPLACEMENT = "\"date\":\"2010-01-01\""; + private static final String RELEASE_PATTERN = "\"release\":\"[^\"]+\""; + private static final String RELEASE_REPLACEMENT = "\"release\":\"DUMMY\""; + private static final String DIR_PATTERN = "\"tempDirectory\":\"[^\"]+\""; + private static final String DIR_REPLACEMENT = "\"tempDirectory\":\"DUMMY\""; + private static final String CONF_PATTERN = "\"configuration\":\"[^\"]+\""; + private static final String CONF_REPLACEMENT = "\"configuration\":\"DUMMY\""; + private static final String RIGHTS_PATTERN = "\"rights\":\"[^\"]+\""; + private static final String RIGHTS_REPLACEMENT = "\"rights\":\"DUMMY\""; + private static final String VENDOR_PATTERN = "\"vendor\":\\{[^\\}]+\\}"; + private static final String VENDOR_REPLACEMENT = "\"vendor\":{\"kind\":\"Vendor\"}"; + private static final String DUMMY = "\"DUMMY\""; + private static final String DUMMY_CK = "8747e564eb53cb2f1dcb9aae0779c2aa"; + private static final String BYTESTREAM = "BYTESTREAM"; + private static final String APP_JSON = "\"name\":\"TEST\",\"release\":\"DUMMY\",\"date\":\"2010-01-01\",\"executionTime\":\"DUMMY\""; + private static final String API_JSON = "\"app\":{\"api\":{\"version\":\"1.0\",\"date\":\"2010-01-01\"}," + + "\"configuration\":\"DUMMY\",\"jhoveHome\":\"TEST\",\"encoding\":\"utf-8\",\"tempDirectory\":\"DUMMY\"," + + "\"bufferSize\":131072,\"modules\":[{\"module\":\"BYTESTREAM\",\"release\":\"DUMMY\"}]," + + "\"outputHandlers\":[{\"outputHandler\":\"Audit\",\"release\":\"DUMMY\"}," + + "{\"outputHandler\":\"JSON\",\"release\":\"DUMMY\"},{\"outputHandler\":\"TEXT\",\"release\":\"DUMMY\"}," + + "{\"outputHandler\":\"XML\",\"release\":\"DUMMY\"}],\"usage\":\"usage\",\"rights\":\"DUMMY\"}"; + private static final String HANDLER_JSON = "\"handler\":{\"name\":\"JSON\",\"release\":\"DUMMY\",\"date\":\"2010-01-01\"," + + + "\"vendor\":{\"kind\":\"Vendor\",\"name\":\"Bibliothèque nationale de France\",\"type\":\"Educational\"," + + "\"web\":\"http://www.bnf.fr\"},\"note\":\"\"," + + "\"rights\":\"DUMMY\"}"; + private static final String MODULE_JSON = "\"module\":{\"name\":\"BYTESTREAM\",\"release\":\"DUMMY\",\"date\":\"2010-01-01\"," + + + "\"formats\":[\"bytestream\"],\"mimeTypes\":[\"application/octet-stream\"]," + + "\"features\":[\"edu.harvard.hul.ois.jhove.canValidate\",\"edu.harvard.hul.ois.jhove.canCharacterize\"]," + + "\"methodology\":{\"wellFormed\":\"All bytestreams are well-formed\"}," + + "\"vendor\":{\"kind\":\"Vendor\"}," + + "\"note\":\"This is the default format\",\"rights\":\"DUMMY\"}"; + private static final String INFO_JSON = "{\"uri\":\"file://dummy.file\"," + + "\"reportingModule\":{\"name\":\"BYTESTREAM\",\"release\":\"DUMMY\",\"date\":\"2010-01-01\"}," + + "\"size\":1,\"format\":\"bytestream\",\"status\":\"Well-Formed and valid\",\"sigMatch\":[\"BYTESTREAM\"]," + + "\"mimeType\":\"application/octet-stream\",\"properties\":[{\"checksum\":\"" + + DUMMY_CK + "\",\"type\":\"MD5\"}]}"; + /** Handler string "Find: " */ + private static final String FIND = "Find: "; + + private static App mockApp; + private static JhoveBase je; + + private File outputFile; + private StringWriter outString; + private PrintWriter writer; + private JsonHandler handler; + + @BeforeClass + public static void setUpBeforeClass() throws JhoveException { + mockApp = new App("TEST", "1.0", new int[] { 2019, 10, 28 }, "usage", "rights"); + je = new JhoveBase(); + je.setLogLevel("INFO"); + String fileConf = JsonHandlerTest.class.getResource("/jhove_test.conf").getPath(); + LOGGER.info("jhove.conf in:[" + fileConf + "]"); + je.init(fileConf, null); + } + + @Before + public void setUp() throws IOException { + // Prepare for a new test + this.outputFile = File.createTempFile("jhove_", ".json"); + + outString = new StringWriter(); + writer = new PrintWriter(outString); + + this.handler = new JsonHandler(); + this.handler.setApp(mockApp); + this.handler.setBase(je); + this.handler.setWriter(writer); + } + + @After + public void tearDown() { + if (this.outputFile != null && this.outputFile.exists()) { + this.outputFile.delete(); + } + } + + public void buildJson(JsonObjectBuilder builder) { JsonObject jsonObject = builder.build(); JsonWriter jsonWriter = Json.createWriter(writer); jsonWriter.writeObject(jsonObject); - } + } - public void buildJson(JsonArrayBuilder builder) { - JsonObjectBuilder job = Json.createObjectBuilder().add("ARRAY", builder); + public void buildJson(JsonArrayBuilder builder) { + JsonObjectBuilder job = Json.createObjectBuilder().add("ARRAY", builder); JsonObject jsonObject = job.build(); JsonWriter jsonWriter = Json.createWriter(writer); jsonWriter.writeObject(jsonObject); - } + } - @Test - public void testShow() { + @Test + public void testShow() { handler.showHeader(); handler.show(); handler.showFooter(); handler.close(); - - String result = outString.toString().replaceAll(TIME_PATTERN, DUMMY) - .replaceAll(DATE_PATTERN, DATE_REPLACEMENT) - .replaceAll(RELEASE_PATTERN, RELEASE_REPLACEMENT); - assertEquals( - "{\"jhove\":{" + APP_JSON + "}}", result); - - } - - @Test - public void testShowApp() { + + String result = outString.toString().replaceAll(TIME_PATTERN, DUMMY) + .replaceAll(DATE_PATTERN, DATE_REPLACEMENT) + .replaceAll(RELEASE_PATTERN, RELEASE_REPLACEMENT); + assertEquals( + "{\"jhove\":{" + APP_JSON + "}}", result); + + } + + @Test + public void testShowApp() { handler.showHeader(); - handler.show(mockApp); + handler.show(mockApp); handler.showFooter(); handler.close(); - - String result = outString.toString().replaceAll(TIME_PATTERN, DUMMY) - .replaceAll(DATE_PATTERN, DATE_REPLACEMENT) - .replaceAll(RELEASE_PATTERN, RELEASE_REPLACEMENT) - .replaceAll(CONF_PATTERN, CONF_REPLACEMENT) - .replaceAll(RIGHTS_PATTERN, RIGHTS_REPLACEMENT) - .replaceAll(DIR_PATTERN, DIR_REPLACEMENT); - LOGGER.info(FIND + result); - String expected = "{\"jhove\":{" + APP_JSON + "," + API_JSON + "}}"; - - assertEquals(expected, result); - } - - @Test - public void testShowOutputHandler() { - OutputHandler jsonHandler = je.getHandler("JSON"); - + + String result = outString.toString().replaceAll(TIME_PATTERN, DUMMY) + .replaceAll(DATE_PATTERN, DATE_REPLACEMENT) + .replaceAll(RELEASE_PATTERN, RELEASE_REPLACEMENT) + .replaceAll(CONF_PATTERN, CONF_REPLACEMENT) + .replaceAll(RIGHTS_PATTERN, RIGHTS_REPLACEMENT) + .replaceAll(DIR_PATTERN, DIR_REPLACEMENT); + LOGGER.info(FIND + result); + String expected = "{\"jhove\":{" + APP_JSON + "," + API_JSON + "}}"; + + assertEquals(expected, result); + } + + @Test + public void testShowOutputHandler() { + OutputHandler jsonHandler = je.getHandler("JSON"); + handler.showHeader(); - handler.show(jsonHandler); + handler.show(jsonHandler); handler.showFooter(); handler.close(); - - String result = outString.toString().replaceAll(TIME_PATTERN, DUMMY) - .replaceAll(DATE_PATTERN, DATE_REPLACEMENT) - .replaceAll(RELEASE_PATTERN, RELEASE_REPLACEMENT) - .replaceAll(CONF_PATTERN, CONF_REPLACEMENT) - .replaceAll(RIGHTS_PATTERN, RIGHTS_REPLACEMENT) - .replaceAll(DIR_PATTERN, DIR_REPLACEMENT); - LOGGER.info(FIND + result); - String expected = "{\"jhove\":{" + APP_JSON + "," + HANDLER_JSON + "}}"; - - assertEquals(expected, result); - } - - @Test - public void testShowModule() { - Module module = je.getModule(BYTESTREAM); - + + String result = outString.toString().replaceAll(TIME_PATTERN, DUMMY) + .replaceAll(DATE_PATTERN, DATE_REPLACEMENT) + .replaceAll(RELEASE_PATTERN, RELEASE_REPLACEMENT) + .replaceAll(CONF_PATTERN, CONF_REPLACEMENT) + .replaceAll(RIGHTS_PATTERN, RIGHTS_REPLACEMENT) + .replaceAll(DIR_PATTERN, DIR_REPLACEMENT); + LOGGER.info(FIND + result); + String expected = "{\"jhove\":{" + APP_JSON + "," + HANDLER_JSON + "}}"; + + assertEquals(expected, result); + } + + @Test + public void testShowModule() { + Module module = je.getModule(BYTESTREAM); + handler.showHeader(); - handler.show(module); + handler.show(module); handler.showFooter(); handler.close(); - - String result = outString.toString().replaceAll(TIME_PATTERN, DUMMY) - .replaceAll(DATE_PATTERN, DATE_REPLACEMENT) - .replaceAll(RELEASE_PATTERN, RELEASE_REPLACEMENT) - .replaceAll(CONF_PATTERN, CONF_REPLACEMENT) - .replaceAll(RIGHTS_PATTERN, RIGHTS_REPLACEMENT) - .replaceAll(DIR_PATTERN, DIR_REPLACEMENT) - .replaceAll(VENDOR_PATTERN, VENDOR_REPLACEMENT); - LOGGER.info(FIND + result); - String expected = "{\"jhove\":{" + APP_JSON + "," + MODULE_JSON + "}}"; - - assertEquals(expected, result); - } - - @Test - public void testShowRepInfo() { - Module module = je.getModule(BYTESTREAM); - RepInfo info = new RepInfo("file://dummy.file"); - info.setModule(module); - info.setFormat(module.getFormat()[0]); - info.setMimeType(module.getMimeType()[0]); - info.setSigMatch(module.getName()); - info.setChecksum(new Checksum(DUMMY_CK, ChecksumType.MD5)); - info.setSize(1); - + + String result = outString.toString().replaceAll(TIME_PATTERN, DUMMY) + .replaceAll(DATE_PATTERN, DATE_REPLACEMENT) + .replaceAll(RELEASE_PATTERN, RELEASE_REPLACEMENT) + .replaceAll(CONF_PATTERN, CONF_REPLACEMENT) + .replaceAll(RIGHTS_PATTERN, RIGHTS_REPLACEMENT) + .replaceAll(DIR_PATTERN, DIR_REPLACEMENT) + .replaceAll(VENDOR_PATTERN, VENDOR_REPLACEMENT); + LOGGER.info(FIND + result); + String expected = "{\"jhove\":{" + APP_JSON + "," + MODULE_JSON + "}}"; + + assertEquals(expected, result); + } + + @Test + public void testShowRepInfo() { + Module module = je.getModule(BYTESTREAM); + RepInfo info = new RepInfo("file://dummy.file"); + info.setModule(module); + info.setFormat(module.getFormat()[0]); + info.setMimeType(module.getMimeType()[0]); + info.setSigMatch(module.getName()); + info.setChecksum(new Checksum(DUMMY_CK, ChecksumType.MD5)); + info.setSize(1); + handler.showHeader(); - handler.show(info); + handler.show(info); handler.showFooter(); handler.close(); - - String result = outString.toString().replaceAll(TIME_PATTERN, DUMMY) - .replaceAll(DATE_PATTERN, DATE_REPLACEMENT) - .replaceAll(RELEASE_PATTERN, RELEASE_REPLACEMENT) - .replaceAll(CONF_PATTERN, CONF_REPLACEMENT) - .replaceAll(RIGHTS_PATTERN, RIGHTS_REPLACEMENT) - .replaceAll(DIR_PATTERN, DIR_REPLACEMENT) - .replaceAll(VENDOR_PATTERN, VENDOR_REPLACEMENT); - LOGGER.info(FIND + result); - String expected = "{\"jhove\":{" + APP_JSON + ",\"repInfo\":[" + INFO_JSON + "]}}"; - - assertEquals(expected, result); - } - - @Test - public void testShowRepInfos() { - Module module = je.getModule(BYTESTREAM); - RepInfo info = new RepInfo("file://dummy.file"); - info.setModule(module); - info.setFormat(module.getFormat()[0]); - info.setMimeType(module.getMimeType()[0]); - info.setSigMatch(module.getName()); - info.setChecksum(new Checksum(DUMMY_CK, ChecksumType.MD5)); - info.setSize(1); - + + String result = outString.toString().replaceAll(TIME_PATTERN, DUMMY) + .replaceAll(DATE_PATTERN, DATE_REPLACEMENT) + .replaceAll(RELEASE_PATTERN, RELEASE_REPLACEMENT) + .replaceAll(CONF_PATTERN, CONF_REPLACEMENT) + .replaceAll(RIGHTS_PATTERN, RIGHTS_REPLACEMENT) + .replaceAll(DIR_PATTERN, DIR_REPLACEMENT) + .replaceAll(VENDOR_PATTERN, VENDOR_REPLACEMENT); + LOGGER.info(FIND + result); + String expected = "{\"jhove\":{" + APP_JSON + ",\"repInfo\":[" + INFO_JSON + "]}}"; + + assertEquals(expected, result); + } + + @Test + public void testShowRepInfos() { + Module module = je.getModule(BYTESTREAM); + RepInfo info = new RepInfo("file://dummy.file"); + info.setModule(module); + info.setFormat(module.getFormat()[0]); + info.setMimeType(module.getMimeType()[0]); + info.setSigMatch(module.getName()); + info.setChecksum(new Checksum(DUMMY_CK, ChecksumType.MD5)); + info.setSize(1); + handler.showHeader(); - handler.show(info); - handler.show(info); + handler.show(info); + handler.show(info); handler.showFooter(); handler.close(); - - String result = outString.toString().replaceAll(TIME_PATTERN, DUMMY) - .replaceAll(DATE_PATTERN, DATE_REPLACEMENT) - .replaceAll(RELEASE_PATTERN, RELEASE_REPLACEMENT) - .replaceAll(CONF_PATTERN, CONF_REPLACEMENT) - .replaceAll(RIGHTS_PATTERN, RIGHTS_REPLACEMENT) - .replaceAll(DIR_PATTERN, DIR_REPLACEMENT) - .replaceAll(VENDOR_PATTERN, VENDOR_REPLACEMENT); - LOGGER.info(FIND + result); - String expected = "{\"jhove\":{" + APP_JSON + ",\"repInfo\":[" + INFO_JSON + "," + INFO_JSON + "]}}"; - - assertEquals(expected, result); - } - - @Test - public void testShowVendor() { - OutputHandler jsonHandler = je.getHandler("JSON"); - Agent v = jsonHandler.getVendor(); - + + String result = outString.toString().replaceAll(TIME_PATTERN, DUMMY) + .replaceAll(DATE_PATTERN, DATE_REPLACEMENT) + .replaceAll(RELEASE_PATTERN, RELEASE_REPLACEMENT) + .replaceAll(CONF_PATTERN, CONF_REPLACEMENT) + .replaceAll(RIGHTS_PATTERN, RIGHTS_REPLACEMENT) + .replaceAll(DIR_PATTERN, DIR_REPLACEMENT) + .replaceAll(VENDOR_PATTERN, VENDOR_REPLACEMENT); + LOGGER.info(FIND + result); + String expected = "{\"jhove\":{" + APP_JSON + ",\"repInfo\":[" + INFO_JSON + "," + INFO_JSON + "]}}"; + + assertEquals(expected, result); + } + + @Test + public void testShowVendor() { + OutputHandler jsonHandler = je.getHandler("JSON"); + Agent v = jsonHandler.getVendor(); + JsonObjectBuilder json = handler.showAgent(v, "OTHER"); buildJson(json); handler.close(); - - String result = outString.toString(); - LOGGER.info(FIND + result); - final String expected = "{\"kind\":\"OTHER\",\"name\":\"Bibliothèque nationale de France\"," + - "\"type\":\"Educational\",\"web\":\"http://www.bnf.fr\"}"; - - assertEquals(expected, result); - } - - @Test - public void testShowScalarProperty() throws IOException { - final Property prop = new Property("test", PropertyType.INTEGER, PropertyArity.SCALAR, 2); - JsonObjectBuilder b = this.handler.showScalarProperty(prop); - buildJson(b); + + String result = outString.toString(); + LOGGER.info(FIND + result); + final String expected = "{\"kind\":\"OTHER\",\"name\":\"Bibliothèque nationale de France\"," + + "\"type\":\"Educational\",\"web\":\"http://www.bnf.fr\"}"; + + assertEquals(expected, result); + } + + @Test + public void testShowScalarProperty() throws IOException { + final Property prop = new Property("test", PropertyType.INTEGER, PropertyArity.SCALAR, 2); + JsonObjectBuilder b = this.handler.showScalarProperty(prop); + buildJson(b); handler.close(); - - String result = outString.toString(); - LOGGER.info(FIND + result); - final String expected = "{\"test\":2}"; - - assertEquals(expected, result); - } - - @Test - public void testShowListProperty() throws IOException { - final List testList = Arrays.asList(new Double[]{1.0, 2.0}); - Property prop = new Property("test", PropertyType.DOUBLE, PropertyArity.LIST, testList); - JsonObjectBuilder b = this.handler.showListProperty(prop); - buildJson(b); + + String result = outString.toString(); + LOGGER.info(FIND + result); + final String expected = "{\"test\":2}"; + + assertEquals(expected, result); + } + + @Test + public void testShowListProperty() throws IOException { + final List testList = Arrays.asList(new Double[] { 1.0, 2.0 }); + Property prop = new Property("test", PropertyType.DOUBLE, PropertyArity.LIST, testList); + JsonObjectBuilder b = this.handler.showListProperty(prop); + buildJson(b); handler.close(); - - String result = outString.toString(); - LOGGER.info(FIND + result); - final String expected = "{\"test\":[1.0,2.0]}"; - - assertEquals(expected, result); - } - - @Test - public void testShowSetProperty() throws IOException { - // use a LinkedHashSet to be sure of the output order... - final Set testSet = new LinkedHashSet<>( - Arrays.asList(new Rational[]{new Rational(300, 1), new Rational(4, 2)})); - Property prop = new Property("test", PropertyType.RATIONAL, PropertyArity.SET, testSet); - JsonObjectBuilder b = this.handler.showSetProperty(prop); - buildJson(b); + + String result = outString.toString(); + LOGGER.info(FIND + result); + final String expected = "{\"test\":[1.0,2.0]}"; + + assertEquals(expected, result); + } + + @Test + public void testShowSetProperty() throws IOException { + // use a LinkedHashSet to be sure of the output order... + final Set testSet = new LinkedHashSet<>( + Arrays.asList(new Rational[] { new Rational(300, 1), new Rational(4, 2) })); + Property prop = new Property("test", PropertyType.RATIONAL, PropertyArity.SET, testSet); + JsonObjectBuilder b = this.handler.showSetProperty(prop); + buildJson(b); handler.close(); - - String result = outString.toString(); - LOGGER.info(FIND + result); - final String expected = "{\"test\":[[300,1],[4,2]]}"; - - assertEquals(expected, result); - } - - @Test - public void testShowMapProperty() throws IOException { - final Map testMap = Collections.singletonMap("mykey", "myvalue"); - Property prop = new Property("test", PropertyType.STRING, PropertyArity.MAP, testMap); - JsonObjectBuilder b = this.handler.showMapProperty(prop); - buildJson(b); + + String result = outString.toString(); + LOGGER.info(FIND + result); + final String expected = "{\"test\":[[300,1],[4,2]]}"; + + assertEquals(expected, result); + } + + @Test + public void testShowMapProperty() throws IOException { + final Map testMap = Collections.singletonMap("mykey", "myvalue"); + Property prop = new Property("test", PropertyType.STRING, PropertyArity.MAP, testMap); + JsonObjectBuilder b = this.handler.showMapProperty(prop); + buildJson(b); handler.close(); - - String result = outString.toString(); - LOGGER.info(FIND + result); - final String expected = "{\"test\":{\"mykey\":\"myvalue\"}}"; - - assertEquals(expected, result); - } - - @Test - public void testShowArrayMapProperty() throws IOException { - final boolean[] testArray = new boolean[]{true, false, true}; - Property prop = new Property("test", PropertyType.BOOLEAN, PropertyArity.ARRAY, testArray); - JsonObjectBuilder b = this.handler.showArrayProperty(prop); - buildJson(b); + + String result = outString.toString(); + LOGGER.info(FIND + result); + final String expected = "{\"test\":{\"mykey\":\"myvalue\"}}"; + + assertEquals(expected, result); + } + + @Test + public void testShowArrayMapProperty() throws IOException { + final boolean[] testArray = new boolean[] { true, false, true }; + Property prop = new Property("test", PropertyType.BOOLEAN, PropertyArity.ARRAY, testArray); + JsonObjectBuilder b = this.handler.showArrayProperty(prop); + buildJson(b); handler.close(); - - String result = outString.toString(); - LOGGER.info(FIND + result); - final String expected = "{\"test\":[true,false,true]}"; - - assertEquals(expected, result); - } - - @Test - public void testShowTextMD() throws IOException { - final TextMDMetadata textMD = new TextMDMetadata(); - textMD.setCharset("UTF-8"); - textMD.setLanguage("fr"); - - JsonObjectBuilder b = this.handler.showTextMDMetadata(textMD); - buildJson(b); + + String result = outString.toString(); + LOGGER.info(FIND + result); + final String expected = "{\"test\":[true,false,true]}"; + + assertEquals(expected, result); + } + + @Test + public void testShowTextMD() throws IOException { + final TextMDMetadata textMD = new TextMDMetadata(); + textMD.setCharset("UTF-8"); + textMD.setLanguage("fr"); + + JsonObjectBuilder b = this.handler.showTextMDMetadata(textMD); + buildJson(b); handler.close(); - - String result = outString.toString(); - LOGGER.info(FIND + result); - final String expected = "{\"textmd:charset\":\"UTF-8\",\"textmd:byte_order\":\"big\"," + - "\"textmd:linebreak\":\"CR/LF\",\"textmd:language\":\"fre\"}"; - - assertEquals(expected, result); - } - - @Test - public void testShowAESAudioMetadata() throws IOException { - final AESAudioMetadata aes = new AESAudioMetadata(); - aes.setFormat("audio/wav"); - - JsonObjectBuilder b = this.handler.showAESAudioMetadata(aes); - buildJson(b); + + String result = outString.toString(); + LOGGER.info(FIND + result); + final String expected = "{\"textmd:charset\":\"UTF-8\",\"textmd:byte_order\":\"big\"," + + "\"textmd:linebreak\":\"CR/LF\",\"textmd:language\":\"fre\"}"; + + assertEquals(expected, result); + } + + @Test + public void testShowAESAudioMetadata() throws IOException { + final AESAudioMetadata aes = new AESAudioMetadata(); + aes.setFormat("audio/wav"); + + JsonObjectBuilder b = this.handler.showAESAudioMetadata(aes); + buildJson(b); handler.close(); - - String result = outString.toString(); - LOGGER.info(FIND + result); - final String expected = "{\"aes:schemaVersion\":\"1.02b\",\"aes:format\":\"audio/wav\"," + - "\"aes:face\":{\"aes:timeline\":{\"tcf:startTime\":{\"tcf:frameCount\":30,\"tcf:timeBase\":1000," + - "\"tcf:videoField\":\"FIELD_1\",\"tcf:countingMode\":\"NTSC_NON_DROP_FRAME\",\"tcf:hours\":0," + - "\"tcf:minutes\":0,\"tcf:seconds\":0,\"tcf:frames\":0," + - "\"tcf:samples\":{\"tcf:sampleRate\":\"S44100\",\"tcf:numberOfSamples\":0}," + - "\"tcf:filmFraming\":{\"tcf:framing\":\"NOT_APPLICABLE\",\"tcf:framingType\":\"tcf:ntscFilmFramingType\"}}}," + - "\"aes:streams\":[]}}"; - - assertEquals(expected, result); - } - - @Test - public void testShowArrayInt() throws IOException { - final int[] iArrayTest = { 1, 2, 3 }; - JsonArrayBuilder b = this.handler.showArray(iArrayTest); - - buildJson(b); + + String result = outString.toString(); + LOGGER.info(FIND + result); + final String expected = "{\"aes:schemaVersion\":\"1.0.0\",\"aes:format\":\"audio/wav\"," + + "\"aes:face\":{\"aes:timeline\":{\"aes:start\":{\"aes:value\":0,\"aes:editRate\":44100," + + "\"aes:factorNumerator\":\"1\",\"aes:factorDenominator\":\"1\"}}," + + "\"aes:streams\":[]}}"; + + assertEquals(expected, result); + } + + @Test + public void testShowArrayInt() throws IOException { + final int[] iArrayTest = { 1, 2, 3 }; + JsonArrayBuilder b = this.handler.showArray(iArrayTest); + + buildJson(b); handler.close(); - - String result = outString.toString(); - LOGGER.info(FIND + result); - final String expected = "{\"ARRAY\":[1,2,3]}"; - - assertEquals(expected, result); - } - - @Test - public void testShowArrayDouble() throws IOException { - final double[] dArrayTest = { -1.0, 0, 1.0 }; - JsonArrayBuilder b = this.handler.showArray(dArrayTest); - - buildJson(b); + + String result = outString.toString(); + LOGGER.info(FIND + result); + final String expected = "{\"ARRAY\":[1,2,3]}"; + + assertEquals(expected, result); + } + + @Test + public void testShowArrayDouble() throws IOException { + final double[] dArrayTest = { -1.0, 0, 1.0 }; + JsonArrayBuilder b = this.handler.showArray(dArrayTest); + + buildJson(b); handler.close(); - - String result = outString.toString(); - LOGGER.info(FIND + result); - final String expected = "{\"ARRAY\":[-1.0,0.0,1.0]}"; - - assertEquals(expected, result); - } - - @Test - public void testShowArrayString() throws IOException { - final String[] sArrayTest = { null, "", "DUMMY" }; - JsonArrayBuilder b = this.handler.showArray(sArrayTest); - - buildJson(b); + + String result = outString.toString(); + LOGGER.info(FIND + result); + final String expected = "{\"ARRAY\":[-1.0,0.0,1.0]}"; + + assertEquals(expected, result); + } + + @Test + public void testShowArrayString() throws IOException { + final String[] sArrayTest = { null, "", "DUMMY" }; + JsonArrayBuilder b = this.handler.showArray(sArrayTest); + + buildJson(b); handler.close(); - - String result = outString.toString(); - LOGGER.info(FIND + result); - final String expected = "{\"ARRAY\":[null,\"\",\"DUMMY\"]}"; - - assertEquals(expected, result); - } - - - @Test - public void testShowArrayRational() throws IOException { - final Rational[] rArrayTest = { new Rational(1L,1L), new Rational(-1, 2) }; - JsonArrayBuilder b = this.handler.showArray(rArrayTest); - - buildJson(b); + + String result = outString.toString(); + LOGGER.info(FIND + result); + final String expected = "{\"ARRAY\":[null,\"\",\"DUMMY\"]}"; + + assertEquals(expected, result); + } + + @Test + public void testShowArrayRational() throws IOException { + final Rational[] rArrayTest = { new Rational(1L, 1L), new Rational(-1, 2) }; + JsonArrayBuilder b = this.handler.showArray(rArrayTest); + + buildJson(b); handler.close(); - - String result = outString.toString(); - LOGGER.info(FIND + result); - final String expected = "{\"ARRAY\":[[1,1],[-1,2]]}"; - - assertEquals(expected, result); - } - - @Test - public void testShowRational() throws IOException { - final Rational r = new Rational(123456,43211); - JsonArrayBuilder b = this.handler.showRational(r); - - buildJson(b); + + String result = outString.toString(); + LOGGER.info(FIND + result); + final String expected = "{\"ARRAY\":[[1,1],[-1,2]]}"; + + assertEquals(expected, result); + } + + @Test + public void testShowRational() throws IOException { + final Rational r = new Rational(123456, 43211); + JsonArrayBuilder b = this.handler.showRational(r); + + buildJson(b); handler.close(); - - String result = outString.toString(); - LOGGER.info(FIND + result); - final String expected = "{\"ARRAY\":[123456,43211]}"; - - assertEquals(expected, result); - } + + String result = outString.toString(); + LOGGER.info(FIND + result); + final String expected = "{\"ARRAY\":[123456,43211]}"; + + assertEquals(expected, result); + } } From 690e1472ef932aa1ecce30e56ccac18d4958fbb8 Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Wed, 21 Aug 2024 10:08:15 +0100 Subject: [PATCH 22/26] FIX: Codacy review suggestions - removed unused constants; and - improved string comparisons. --- .../hul/ois/jhove/viewer/RepTreeRoot.java | 29 +++---------------- .../hul/ois/jhove/AESAudioMetadata.java | 6 ++-- 2 files changed, 7 insertions(+), 28 deletions(-) diff --git a/jhove-apps/src/main/java/edu/harvard/hul/ois/jhove/viewer/RepTreeRoot.java b/jhove-apps/src/main/java/edu/harvard/hul/ois/jhove/viewer/RepTreeRoot.java index 9fd303a60..c3bbc9900 100644 --- a/jhove-apps/src/main/java/edu/harvard/hul/ois/jhove/viewer/RepTreeRoot.java +++ b/jhove-apps/src/main/java/edu/harvard/hul/ois/jhove/viewer/RepTreeRoot.java @@ -75,19 +75,6 @@ public RepTreeRoot(RepInfo info, JhoveBase base) { private static final String FORMAT = "Format: "; private static final String VERSION = "Version: "; private static final String BYTE_ORDER = "ByteOrder: "; - private static final String FRAME_COUNT_30 = "FrameCount: 30"; - private static final String TIME_BASE_1000 = "TimeBase: 1000"; - private static final String VIDEO_FIELD_FIELD_1 = "VideoField: FIELD_1"; - private static final String COUNTING_MODE_NTSC_NON_DROP_FRAME = "CountingMode: NTSC_NON_DROP_FRAME"; - private static final String HOURS = "Hours: "; - private static final String MINUTES = "Minutes: "; - private static final String SECONDS = "Seconds: "; - private static final String FRAMES = "Frames: "; - private static final String SAMPLES = "Samples"; - private static final String NUMBER_OF_SAMPLES = "NumberOfSamples: "; - private static final String FILM_FRAMING = "FilmFraming"; - private static final String FRAMING_NOT_APPLICABLE = "Framing: NOT_APPLICABLE"; - private static final String NTSC_FILM_FRAMING_TYPE = "Type: ntscFilmFramingType"; /** * Constructs a DefaultMutableTreeNode representing a property @@ -99,9 +86,8 @@ private DefaultMutableTreeNode propToNode(Property pProp) { if (arity == PropertyArity.SCALAR) { if (null == typ) { // Simple types: just use name plus string value. - DefaultMutableTreeNode val = new DefaultMutableTreeNode( + return new DefaultMutableTreeNode( pProp.getName() + ": " + pValue.toString()); - return val; } else { TextMDMetadata tData; DefaultMutableTreeNode val; @@ -214,10 +200,6 @@ public int getIndexOfChild(Object parent, Object child) { if (null == propType) { return 0; // non-object array type } else - // if (child instanceof LeafHolder) { - // return ((LeafHolder) child).getPosition (); - // } - // else switch (propType) { case DATE: dateArray = (java.util.Date[]) pProp.getValue(); @@ -335,7 +317,7 @@ private void snarfRepInfo() { // Report modules that said their signatures match List sigList = _info.getSigMatch(); - if (sigList != null && sigList.size() > 0) { + if (sigList != null && !sigList.isEmpty()) { DefaultMutableTreeNode sigNode = new DefaultMutableTreeNode( "SignatureMatches"); infoNode.add(sigNode); @@ -347,7 +329,7 @@ private void snarfRepInfo() { } // Compile a list of messages and offsets into a subtree List messageList = _info.getMessage(); - if (messageList != null && messageList.size() > 0) { + if (messageList != null && !messageList.isEmpty()) { DefaultMutableTreeNode msgNode = new DefaultMutableTreeNode( "Messages"); infoNode.add(msgNode); @@ -397,7 +379,7 @@ private void snarfRepInfo() { // Compile a list of profile strings into a string list List profileList = _info.getProfile(); - if (profileList != null && profileList.size() > 0) { + if (profileList != null && !profileList.isEmpty()) { DefaultMutableTreeNode profNode = new DefaultMutableTreeNode( "Profiles"); infoNode.add(profNode); @@ -426,7 +408,6 @@ private void snarfRepInfo() { DefaultMutableTreeNode ckNode = new DefaultMutableTreeNode( "Checksums"); infoNode.add(ckNode); - // List cPropList = new LinkedList (); for (Checksum cksum : cksumList) { DefaultMutableTreeNode csNode = new DefaultMutableTreeNode( "Checksum"); @@ -567,7 +548,6 @@ private void addMapMembers(DefaultMutableTreeNode node, Property p) { Map m = (Map) p.getValue(); PropertyType ptyp = p.getType(); Boolean canHaveChildren = Boolean.TRUE; - // Iterator iter = m.values ().iterator (); Iterator iter = m.keySet().iterator(); while (iter.hasNext()) { DefaultMutableTreeNode itemNode; @@ -1446,7 +1426,6 @@ private DefaultMutableTreeNode getDefaultMutableTreeNode(PropertyType ptyp, // Simple objects just need a leaf. itemNode = (new DefaultMutableTreeNode(item, allowsChildren)); } else - // Object item = iter.next (); switch (ptyp) { case PROPERTY: itemNode = (propToNode((Property) item)); diff --git a/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/AESAudioMetadata.java b/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/AESAudioMetadata.java index ac3642a49..633a9d64d 100644 --- a/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/AESAudioMetadata.java +++ b/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/AESAudioMetadata.java @@ -760,9 +760,9 @@ public void setByteOrder(int order) { * Sets the byte order. */ public void setByteOrder(String order) { - if (order.substring(0, 3).toLowerCase().equals("big")) { + if (order.substring(0, 3).equalsIgnoreCase("big")) { _byteOrder = BIG_ENDIAN; - } else if (order.substring(0, 6).toLowerCase().equals("little")) { + } else if (order.substring(0, 6).equalsIgnoreCase("little")) { _byteOrder = LITTLE_ENDIAN; } } @@ -851,7 +851,7 @@ public void setPrimaryIdentifierType(String primaryIdentifierType) { * set the otherType. */ public void setOtherPrimaryIdentifierType(String otherType) { - _primaryIdentifierType = "OTHER"; + _primaryIdentifierType = OTHER; _primaryIdentifierOtherType = otherType; } From 1af0fd423423df4d5fec803c4d2e196e779f1fcc Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Wed, 21 Aug 2024 14:53:10 +0100 Subject: [PATCH 23/26] FIX: Assorted XML issues - added a dedicated method to `XmlModuleHandler` to handle entity resolution; - entity resolution now handles multiple redirects; - added new catcvhes for `307` and `308` redirect status codes. - removed line numbers from XML info messages to prevent pathalogical info message repeats; and - added test data to prevent regressions of the above. Closes #918 --- jhove-bbt/scripts/create-1.31-target.sh | 5 +- .../edu/harvard/hul/ois/jhove/Message.java | 124 +- .../jhove/module/xml/XmlModuleHandler.java | 975 +- .../corpora/errors/modules/XML-hul/0003.xml | 12248 ++++++++++++++++ .../errors/modules/XML-hul/12745764.xml | 4273 ++++++ 5 files changed, 17080 insertions(+), 545 deletions(-) create mode 100644 test-root/corpora/errors/modules/XML-hul/0003.xml create mode 100644 test-root/corpora/errors/modules/XML-hul/12745764.xml diff --git a/jhove-bbt/scripts/create-1.31-target.sh b/jhove-bbt/scripts/create-1.31-target.sh index 5fe340f40..8e0dede59 100755 --- a/jhove-bbt/scripts/create-1.31-target.sh +++ b/jhove-bbt/scripts/create-1.31-target.sh @@ -165,4 +165,7 @@ done # Copy all of the AIF and WAV results as these are changed by the AES schema changes cp -rf "${candidateRoot}/examples/modules/AIFF-hul" "${targetRoot}/examples/modules/" cp -rf "${candidateRoot}/examples/modules/WAVE-hul" "${targetRoot}/examples/modules/" -cp -rf "${candidateRoot}/errors/modules/WAVE-hul" "${targetRoot}/errors/modules/" \ No newline at end of file +cp -rf "${candidateRoot}/errors/modules/WAVE-hul" "${targetRoot}/errors/modules/" + +# Copy the results of the new XML fixes for multiple redirect lookups and to ensure no regression for repeat XML warnings +cp -rf "${candidateRoot}/errors/modules/XML-hul" "${targetRoot}/errors/modules/" diff --git a/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/Message.java b/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/Message.java index 3ee2fedce..5265c04b7 100644 --- a/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/Message.java +++ b/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/Message.java @@ -12,96 +12,96 @@ */ public abstract class Message { - /****************************************************************** - * PUBLIC CLASS FIELDS. - ******************************************************************/ + /****************************************************************** + * PUBLIC CLASS FIELDS. + ******************************************************************/ - /** Value indicating a null offset. */ - public static final long NULL = -1; + /** Value indicating a null offset. */ + public static final long NULL = -1; - /****************************************************************** - * PRIVATE INSTANCE FIELDS. - ******************************************************************/ + /****************************************************************** + * PRIVATE INSTANCE FIELDS. + ******************************************************************/ protected final JhoveMessage jhoveMessage; - /** Additional information. */ + /** Additional information. */ protected final String subMessage; - /** Byte offset to which message applies. */ + /** Byte offset to which message applies. */ protected final long offset; protected final String prefix; - /****************************************************************** - * CLASS CONSTRUCTOR. - ******************************************************************/ - - /** - * Creates a Message with an identifier. - * 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 - * The message text and its identifier. - * @param subMessage - * Human-readable additional information. - * @param offset - * Byte offset associated with the message. - */ + /****************************************************************** + * CLASS CONSTRUCTOR. + ******************************************************************/ + + /** + * Creates a Message with an identifier. + * 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 + * The message text and its identifier. + * @param subMessage + * Human-readable additional information. + * @param offset + * Byte offset associated with the message. + */ protected Message(final JhoveMessage message, final String subMessage, final long offset, final String prefix) { - super(); + super(); this.jhoveMessage = message; this.subMessage = (subMessage.isEmpty()) ? null : subMessage; this.offset = offset; this.prefix = prefix; - } - - /****************************************************************** - * PUBLIC INSTANCE METHODS. - * - * Accessor methods. - ******************************************************************/ - - /** - * Returns the message text. - */ - public String getMessage() { + } + + /****************************************************************** + * PUBLIC INSTANCE METHODS. + * + * Accessor methods. + ******************************************************************/ + + /** + * Returns the message text. + */ + public String getMessage() { return this.jhoveMessage.getMessage(); - } + } - /** - * Returns the submessage text. - */ - public String getSubMessage() { + /** + * Returns the submessage text. + */ + public String getSubMessage() { return this.subMessage; - } + } - /** - * Returns the offset to which this message is related. - */ - public long getOffset() { + /** + * Returns the offset to which this message is related. + */ + public long getOffset() { return this.offset; - } + } - /** - * Returns the message's identifier. - */ - public String getId() { + /** + * Returns the message's identifier. + */ + public String getId() { return this.jhoveMessage.getId(); - } + } - public JhoveMessage getJhoveMessage() { + public JhoveMessage getJhoveMessage() { return this.jhoveMessage; - } + } - public String getPrefix() { + public String getPrefix() { return this.prefix; - } + } @Override public String toString() { diff --git a/jhove-modules/xml-hul/src/main/java/edu/harvard/hul/ois/jhove/module/xml/XmlModuleHandler.java b/jhove-modules/xml-hul/src/main/java/edu/harvard/hul/ois/jhove/module/xml/XmlModuleHandler.java index 22f5e24aa..90f90304a 100644 --- a/jhove-modules/xml-hul/src/main/java/edu/harvard/hul/ois/jhove/module/xml/XmlModuleHandler.java +++ b/jhove-modules/xml-hul/src/main/java/edu/harvard/hul/ois/jhove/module/xml/XmlModuleHandler.java @@ -39,486 +39,497 @@ */ public class XmlModuleHandler extends DefaultHandler { - /** Map of namespace prefixes to URIs. */ - 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 specification. */ - 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; - _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. - */ - @Override - public void startElement(String namespaceURI, String localName, - String qualifiedName, Attributes atts) { - // 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) && (namespaceURI.length() != 0)) { - SchemaInfo schi = new SchemaInfo(); - schi.namespaceURI = namespaceURI; - schi.location = ""; - if (!hasSchemaURI(schi)) { - _schemas.add(schi); - } - } - if (atts != null) { - for (int i = 0; i < atts.getLength(); i++) { - String name = atts.getLocalName(i); - String namespace = atts.getURI(i); - String val = atts.getValue(i); - if ("http://www.w3.org/2001/XMLSchema-instance" - .equals(namespace)) { - if ("schemaLocation".equals(name)) { - // schemaLocation should contain pairs of namespace - // and location URIs separated by white space. Any - // number of such pairs may be declared in a single - // schemaLocation attribute. - String[] uris = val.trim().split("\\s+"); - for (int j = 0; j < uris.length; j += 2) { - SchemaInfo schema = new SchemaInfo(); - schema.namespaceURI = uris[j]; - if (uris.length > j + 1) { - schema.location = uris[j + 1]; - } else { - schema.location = ""; - } - if (!hasSchemaURI(schema)) { - _schemas.add(schema); - } - } - } - if ("noNamespaceSchemaLocation".equals(name)) { - SchemaInfo schema = new SchemaInfo(); - schema.location = val; - schema.namespaceURI = ""; - if (!hasSchemaURI(schema)) { - _schemas.add(schema); - } - } - } - // 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. - */ - @Override - 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. - */ - @Override - 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. - */ - @Override - public void startPrefixMapping(String prefix, String uri) { - // 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. - */ - @Override - public void processingInstruction(String target, String data) { - _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. - */ - @Override - public void notationDecl(String name, String publicID, String systemID) { - String[] notArr = new String[3]; - notArr[0] = name; - notArr[1] = publicID; - notArr[2] = systemID; - _notations.add(notArr); - } - - /** - * Overrides standard resolveEntity method to - * provide special-case handling for external entity resolution. - * - * First looks for external entities that are stored as - * local resources and uses those if available (faster and - * more reliable than using the internet), preferring - * user-supplied resources over packaged resources. Then - * attempts to resolve any URLs that result in redirection - * requests. - * - * Finally, returns null if the entity doesn't - * require special handling, allowing the SAX implementation - * to attempt its own resolution. - */ - @Override - public InputSource resolveEntity(String publicId, String systemId) - throws SAXException - { - // Check for XHTML DTDs - if (!_xhtmlFlag && DTDMapper.isXHTMLDTD(publicId)) { - _xhtmlFlag = true; - } - // Assume that the first system ID in the file with a .dtd - // extension is the actual DTD - if (systemId.endsWith(".dtd") && _dtdURI == null) { - _dtdURI = systemId; - } - - // Attempt to resolve system identifiers to schemas from config. - // This takes precedence, allowing users to override JHOVE resources. - File fil = _localSchemas.get(systemId.toLowerCase()); - if (fil != null) { - try { - FileInputStream inStrm = new FileInputStream(fil); - return new InputSource(inStrm); - } catch (FileNotFoundException fnfe) { - // File locations should be verified before - // being added to the list of local schemas. - } - } - - // Attempt to resolve public identifiers to local JHOVE resources. - InputSource ent = DTDMapper.publicIDToFile(publicId); - - // Attempt to resolve redirected URLs. - if (ent == null) { - try { - URLConnection conn = new URL(systemId).openConnection(); - if (conn instanceof HttpURLConnection) { - int status = ((HttpURLConnection) conn).getResponseCode(); - if (status == HttpURLConnection.HTTP_MOVED_TEMP - || status == HttpURLConnection.HTTP_MOVED_PERM - || status == HttpURLConnection.HTTP_SEE_OTHER) { - - String newUrl = conn.getHeaderField("Location"); - conn = new URL(newUrl).openConnection(); - ent = new InputSource(conn.getInputStream()); - } - } - } - catch (IOException ioe) { - // Malformed URL or bad connection. - throw new SAXException(ioe); - } - } else { - // A little magic so SAX won't give up in advance on - // relative URI's. - ent.setSystemId("http://hul.harvard.edu/hul"); - } - - 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. - */ - @Override - 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. - */ - @Override - public void warning(SAXParseException spe) { - _messages.add(new InfoMessage(MessageConstants.INSTANCE.makeSaxParseMessage(spe))); - } - - /** - * 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. - */ - @Override - public void error(SAXParseException spe) { - _valid = false; - if (_nErrors == MAXERRORS) { - JhoveMessage message = JhoveMessages.getMessageInstance( - MessageConstants.XML_HUL_2.getId(), - MessageFormat.format( - MessageConstants.XML_HUL_2.getMessage(), - MAXERRORS)); - _messages.add(new InfoMessage(message)); - } else if (_nErrors < MAXERRORS) { - _messages.add(new ErrorMessage(MessageConstants.INSTANCE.makeSaxParseMessage(spe))); - } - ++_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) { - for (SchemaInfo schema : _schemas) { - if (newinfo.namespaceURI.equals(schema.namespaceURI)) { - if (schema.location.isEmpty() && !newinfo.location.isEmpty()) { - _schemas.remove(schema); - return false; // we like the new info better - } - return true; - } - } - return false; - } + /** Map of namespace prefixes to URIs. */ + 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 specification. */ + 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; + _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. + */ + @Override + public void startElement(String namespaceURI, String localName, + String qualifiedName, Attributes atts) { + // 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) && (namespaceURI.length() != 0)) { + SchemaInfo schi = new SchemaInfo(); + schi.namespaceURI = namespaceURI; + schi.location = ""; + if (!hasSchemaURI(schi)) { + _schemas.add(schi); + } + } + if (atts != null) { + for (int i = 0; i < atts.getLength(); i++) { + String name = atts.getLocalName(i); + String namespace = atts.getURI(i); + String val = atts.getValue(i); + if ("http://www.w3.org/2001/XMLSchema-instance" + .equals(namespace)) { + if ("schemaLocation".equals(name)) { + // schemaLocation should contain pairs of namespace + // and location URIs separated by white space. Any + // number of such pairs may be declared in a single + // schemaLocation attribute. + String[] uris = val.trim().split("\\s+"); + for (int j = 0; j < uris.length; j += 2) { + SchemaInfo schema = new SchemaInfo(); + schema.namespaceURI = uris[j]; + if (uris.length > j + 1) { + schema.location = uris[j + 1]; + } else { + schema.location = ""; + } + if (!hasSchemaURI(schema)) { + _schemas.add(schema); + } + } + } + if ("noNamespaceSchemaLocation".equals(name)) { + SchemaInfo schema = new SchemaInfo(); + schema.location = val; + schema.namespaceURI = ""; + if (!hasSchemaURI(schema)) { + _schemas.add(schema); + } + } + } + // 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. + */ + @Override + 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. + */ + @Override + 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. + */ + @Override + public void startPrefixMapping(String prefix, String uri) { + // 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. + */ + @Override + public void processingInstruction(String target, String data) { + _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. + */ + @Override + public void notationDecl(String name, String publicID, String systemID) { + String[] notArr = new String[3]; + notArr[0] = name; + notArr[1] = publicID; + notArr[2] = systemID; + _notations.add(notArr); + } + + /** + * Overrides standard resolveEntity method to + * provide special-case handling for external entity resolution. + * + * First looks for external entities that are stored as + * local resources and uses those if available (faster and + * more reliable than using the internet), preferring + * user-supplied resources over packaged resources. Then + * attempts to resolve any URLs that result in redirection + * requests. + * + * Finally, returns null if the entity doesn't + * require special handling, allowing the SAX implementation + * to attempt its own resolution. + */ + @Override + public InputSource resolveEntity(String publicId, String systemId) + throws SAXException { + // Check for XHTML DTDs + if (!_xhtmlFlag && DTDMapper.isXHTMLDTD(publicId)) { + _xhtmlFlag = true; + } + // Assume that the first system ID in the file with a .dtd + // extension is the actual DTD + if (systemId.endsWith(".dtd") && _dtdURI == null) { + _dtdURI = systemId; + } + + // Attempt to resolve system identifiers to schemas from config. + // This takes precedence, allowing users to override JHOVE resources. + File fil = _localSchemas.get(systemId.toLowerCase()); + if (fil != null) { + try { + FileInputStream inStrm = new FileInputStream(fil); + return new InputSource(inStrm); + } catch (FileNotFoundException fnfe) { + // File locations should be verified before + // being added to the list of local schemas. + } + } + + // Attempt to resolve public identifiers to local JHOVE resources. + InputSource ent = DTDMapper.publicIDToFile(publicId); + + // Attempt to resolve redirected URLs. + if (ent == null) { + return this.resolveEntity(systemId); + } else { + // A little magic so SAX won't give up in advance on + // relative URI's. + ent.setSystemId("http://hul.harvard.edu/hul"); + } + + return ent; + } + + private final InputSource resolveEntity(String entityUrl) throws SAXException { + try { + URLConnection conn = new URL(entityUrl).openConnection(); + if (conn instanceof HttpURLConnection) { + int status = ((HttpURLConnection) conn).getResponseCode(); + if (status == HttpURLConnection.HTTP_OK) { + return new InputSource(conn.getInputStream()); + } + if (status == HttpURLConnection.HTTP_MOVED_TEMP + || status == HttpURLConnection.HTTP_MOVED_PERM + || status == HttpURLConnection.HTTP_SEE_OTHER + || status == 307 + || status == 308) { + + String newUrl = conn.getHeaderField("Location"); + return this.resolveEntity(newUrl); + } + } + } catch (IOException ioe) { + // Malformed URL or bad connection. + System.err.println("Error resolving entity: " + ioe.getMessage()); + throw new SAXException(ioe); + } + return null; + } + + /** + * 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. + */ + @Override + 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. + */ + @Override + public void warning(SAXParseException spe) { + JhoveMessage message = JhoveMessages.getMessageInstance( + MessageConstants.XML_HUL_1.getId(), MessageFormat.format( + MessageConstants.XML_HUL_1.getMessage(), spe.getMessage())); + _messages.add(new InfoMessage(message)); + } + + /** + * 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. + */ + @Override + public void error(SAXParseException spe) { + _valid = false; + if (_nErrors == MAXERRORS) { + JhoveMessage message = JhoveMessages.getMessageInstance( + MessageConstants.XML_HUL_2.getId(), + MessageFormat.format( + MessageConstants.XML_HUL_2.getMessage(), + MAXERRORS)); + _messages.add(new InfoMessage(message)); + } else if (_nErrors < MAXERRORS) { + _messages.add(new ErrorMessage(MessageConstants.INSTANCE.makeSaxParseMessage(spe))); + } + ++_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) { + for (SchemaInfo schema : _schemas) { + if (newinfo.namespaceURI.equals(schema.namespaceURI)) { + if (schema.location.isEmpty() && !newinfo.location.isEmpty()) { + _schemas.remove(schema); + return false; // we like the new info better + } + return true; + } + } + return false; + } } diff --git a/test-root/corpora/errors/modules/XML-hul/0003.xml b/test-root/corpora/errors/modules/XML-hul/0003.xml new file mode 100644 index 000000000..7317dfea6 --- /dev/null +++ b/test-root/corpora/errors/modules/XML-hul/0003.xml @@ -0,0 +1,12248 @@ + + + + mm10 + + //docstorage2/impdata1/IN/NZ_17/WT/1920/WT_19200303/PM_01/0003.tif + + + + + CCS Content Conversion Specialists GmbH, Hamburg, Germany + CCS docWorks + 7.2-0.59 + + + + + ABBYY (BIT Software), Russia + FineReader + 12.0 + + + + + CCS Content Conversion Specialists GmbH, Hamburg, Germany + CCS docWorks + 7.2-0.59 + docWorks: Text modified manually, or automatically replaced + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test-root/corpora/errors/modules/XML-hul/12745764.xml b/test-root/corpora/errors/modules/XML-hul/12745764.xml new file mode 100644 index 000000000..6a5eb0e29 --- /dev/null +++ b/test-root/corpora/errors/modules/XML-hul/12745764.xml @@ -0,0 +1,4273 @@ + + + + + + +F + +O + +U + + +o +n +t + +a +v +e +c + +c +e +u +x + +d +e +s + +o +r +g +u +e +s + +; + +t +e +l +s + +f +o +n +t + +c +e +u +x + +d +e + +l +i + +f +o +r +g +e + +& + +d +u + +f +o +u +r +n +e +a +u + +d +e + +f +u +s +i +o +n + +f +i +g +. + +8 +. + +A +u + +r +e +s +t +e + +c + +e +s +t + +l +e +u +r + +u +s +a +g +e +, + +& + +n +o +n + +! +a + +f +i +g +u +r +e +, + +q +u +i + +d +é +c +i +d +e +. + +O +n + +a +p +p +e +l +l +e + +e +n +c +o +r +e + +r +e +g +i +s +t +r +e +s + +l +e +s + +i +n +f +t +r +u +m +e +n +s + +o +u + +c +e +s + +p +e +t +i +t +s + +p +a +r +a +l +l +é +l +i +p +i +p +e +d +e +s + +d +e + +t +e +r +r +e + +c +u +i +t +e +, + +q +u + +o +n + +m +e +t + +d +e +v +a +n +t + +l +e +s + +s +o +u +¬ + +p +i +r +a +u +x + +d +e + +l +a + +m +o +u +f +l +e +. + + +U +n +e + +o +u +v +e +r +t +u +r +e + +f +e +u +l +e + +a +u + +m +i +l +i +e +u + +d +u + +d +ô +m +e +, + +f +a +i +t + +q +u +e + +l +a + +c +h +a +l +e +u +r + +e +s +t + +p +a +r +- +t +o +u +t + +é +g +a +l +e + +d +a +n +s + +1 + +q + +f +o +u +r +n +e +a +u +, + +S +t + +p +l +u +s + +c +o +n +c +e +n +t +r +é +e + +; + +d + +a +i +l +l +e +u +r +s + +i +l + +e +s +t + +p +l +u +s + +a +i +s +é + +d +e + +l +a + +f +e +r +m +e +r +. + +Q +u +a +n +d + +i +l + +y + +e +n + +a + +t +r +o +i +s + +o +u + +q +u +a +t +r +e +, + +i +l + +f +a +u +t + +l +e +s + +t +e +n +i +r + +t +o +u +j +o +u +r +s + +o +u +v +e +r +t +s +, + +o +u + +s +i + +o +n + +l +e +s + +f +e +r +m +e + +d +a +n +s + +l +a + +f +u +i +t +e +, + +n +e + +l +e +s + +p +a +s + +r +o +u +v +r +i +r + +; + +c +a +r + +i +l + +a +r +r +i +v +e + +q +u +e + +f +a + +p +a +r +t +i +e + +d +e + +l +a + +r +e +t +o +r +t +e + +q +u +i + +e +s +t + +v +i +s +- +à +- +v +i +s +, + +& + +q +u +i + +s + +e +s +t + +r +e +f +r +o +i +d +i +e + +p +e +n +d +a +n +t + +q +u + +i +l +s + +o +n +t + +é +t +é + +f +e +r +m +é +s +, + +p +a +r +c +e + +q +u +e + +l +a + +c +h +a +l +e +u +r + +n + +a + +p +l +u +s + +é +t +é + +d +é +t +e +r +m +i +n +é +e + +d +e + +c +e + +c +ó +t +é +- +l +à + +, + +f +e + +f +e +n +d + +p +a +r +c +e + +q +u + +e +l +l +e + +e +s +t + +f +r +a +p +p +é +e + +d + +u +n +e + +c +h +a +l +e +u +r + +s +u +b +i +t +e + +: + +c +e +t + +i +n +c +o +n +v +é +n +i +e +n +t + +a +r +r +i +v +e + +d + +a +u +t +a +n +t + +m +i +e +u +x + +q +u + +e +l +l +e + +e +s +t + +p +l +u +s + +é +p +a +i +í +í +è +, + +p +a +r + +l +a + +r +a +i +s +o +n + +q +u +e + +l +a + +t +a +b +l +e + +i +n +t +e +r +n +e + +n +e + +p +e +u +t + +p +a +s + +ê +t +r +e + +d +i +l +a +t +é +e + +e +n + +m +ê +m +e + +t +e +m +s + +q +u +e + +l + +e +x +t +e +r +n +e +. + +C +e +t + +u +s +a +g +e + +d + +u +n + +s +e +u +l + +r +e +g +i +s +t +r +e + +a +u + +m +i +l +i +e +u + +d +u +d +ô +m +e + +e +s +t + +f +o +r +t + +a +n +c +i +e +n +, + +c +o +m +m +e + +n +o +u +s + +s +a +v +o +n +s + +r +e +m +a +r +q +u +é + +à + +l +a + +s +e +c +t +i +o +n + +d +e +s + +f +o +u +r +n +e +a +u +x + +p +h +i +l +o +s +o +p +h +i +q +u +e +s +. + +P +e +u + +d +' +a +u +¬ + +t +e +u +r +s + +e +n + +o +n +t + +m +i +s + +q +u +a +t +r +e +. + +I +I + +n + +y + +a + +e +u + +q +u +e + +q +u +e +l +q +u +e +s + +m +a +u +v +a +i +s + +a +r +t +i +s +t +e +s + +o +u + +f +o +u +r +n +a +l +i +s +t +e +s +, + +q +u +i + +e +n + +o +n +t + +i +n +t +r +o +d +u +i +t + +c +e + +n +o +m +b +r +e + +d +e + +t +e +m +s + +e +n + +t +e +m +s +. + + +S +i + +l +e +s + +r +e +g +i +s +t +r +e +s + +f +o +n +t + +a +u + +n +o +m +b +r +e + +d +e + +q +u +a +t +r +e +, + +S +t + +t +o +u +t + +a +u +t +o +u +r + +d +u + +d +ô +m +e + +d +u + +f +o +u +r +n +e +a +u + +s +e +r +¬ + +v +a +n +t + +à + +l +a + +d +i +s +t +i +l +l +a +t +i +o +n + +d +u + +v +i +n +a +i +g +r +e +, + +d +e + +l +a + +m +a +n +n +e +, + +d +u + +m +i +e +l +, + +& +c +. + +f +i +g +. + +7 +4 +. +, + +c + +e +s +t + +q +u + +o +n + +n +e + +p +e +u +t + +p +a +s + +l +e +s + +p +l +a +c +e +r + +a +i +l +l +e +u +r +s +, + +q +u + +o +n + +l +e +s + +l +a +i +s +i +e + +o +u +v +e +r +t +s + +c +o +n +t +i +n +u +e +l +l +e +m +e +n +t +, + +& + +q +u + +i +l +n +e + +f +a +u +t + +q +u + +u +n +e + +c +h +a +l +e +u +r + +d +o +u +c +e + +p +o +u +r + +c +e +s + +f +o +r +t +e +s + +d + +o +p +é +r +a +t +i +o +n +s +. + + +Q +u +o +i +q +u + +i +l + +s +o +i +t + +v +r +a +i + +q +u + +o +n + +a +u +g +m +e +n +t +e + +l +e + +f +e +u + +e +n + +o +u +v +r +a +n +t + +l +e +s + +r +e +g +i +s +t +r +e +s +, + +c +e +l +a + +n + +a + +p +o +u +r +¬ + +t +a +n +t + +l +i +e +u + +q +u + +à + +l + +é +g +a +r +d + +d +e + +c +e +u +x + +q +u +i + +n +e + +f +o +n +t + +p +a +s + +t +r +o +p + +g +r +a +n +d +s + +; + +c +a +r + +p +l +u +s + +o +n + +e +n + +o +u +v +r +i +r +o +i +t +, + +& + +p +l +u +s + +o +n + +d +e +v +r +o +i +t + +a +u +g +m +e +n +t +e +r + +l +e + +f +e +u +, + +a +u + +l +i +e +u + +q +u + +o +n + +l +e + +d +i +m +i +n +u +e + +r +é +e +l +l +e +m +e +n +t + +s +i + +o +n + +e +n + +o +u +v +r +e + +t +r +o +p + +o +u + +s + +i +l +s + +f +o +n +t + +t +r +o +p + +g +r +a +n +d +s + +: + +a +i +n +s +i + +i +l + +n + +e +s +t + +q +u +e +s +t +i +o +n + +d +a +n +s + +c +e +t + +a +x +i +o +m +e + +, + +q +u +e + +d +e +s + +r +e +g +i +s +t +r +e +s + +q +u +i + +f +o +n +t + +e +n + +p +r +o +p +o +r +t +i +o +n + +a +v +e +c + +l +e + +r +e +s +t +e +. + + +L +e +s + +r +e +g +i +s +t +r +e +s + +d +o +i +v +e +n +t + +ê +t +r +e + +a +u + +p +l +u +s + +u +n + +t +i +e +r +s + +o +u + +u +n + +q +u +a +r +t + +d +u + +d +i +a +m +è +t +r +e + +d +u + +c +e +n +d +r +i +e +r +, + +d +o +n +t + +j +e + +c +r +o +i +s + +q +u + +o +n + +p +e +u +t + +r +é +g +l +e +r + +l +a + +p +o +r +t +e + +f +u +r + +l +e + +d +i +a +m +è +t +r +e + +d +u + +f +o +u +r +n +e +a +u +. + +C +e +l +u +i + +d +e + +G +l +a +u +b +e +r +, + + + + + + +F +O +U + +2 +j +i + + +p +a +r + +e +x +. + +a + +u +n + +p +i +e +d + +d +e + +d +i +a +m +è +t +r +e + +: + +a +i +n +s +i + +é +g +a +l +e + +d +i +m +e +n +s +i +o +n + +s +u +f +f +i +r +a + +p +o +u +r +T +o +n + +s +o +u +p +i +r +a +i +l + +; + +& + +l +e + +t +i +e +r +s + +o +u + +l +e + +q +u +a +r +t +, + +c +o +m +m +e + +o +n + +a + +d +i +t +, + +p +o +u +r + +l +e + +t +u +y +a +u +. + +Q +u +a +n +t + +a +u + +s +o +u +p +i +r +a +i +l +, + +j +e + +p +e +n +s +e + +q +u + +i +l + +s +u +f +f +i +t + +q +u + +i +l + +f +o +u +r +n +i +s +s +e + +a +u + +f +o +y +e +r +; + +m +a +i +s + +l +e + +f +o +y +e +r + +n + +a + +q +u +e + +c +e +t +t +e + +l +a +r +g +e +u +r +, + +& + +e +l +l +e + +e +s +t + +m +ê +m +e + +d +i +m +i +n +u +é +e + +p +a +r + +l +a + +g +r +i +l +l +e + +S +t + +l +e +s + +c +h +a +r +b +o +n +s +: + +c +e + +f +e +r +a + +d +o +n +c + +a +s +s +e +z + +p +o +u +r + +l +e + +s +o +u +p +i +r +a +i +l +, + +c +e + +s +e +r +a + +m +ê +m +e + +t +r +o +p + +; + +m +a +i +s + +d +a +n +s + +l +e + +c +a +s + +o +ù + +l + +o +n + +n +e + +p +e +u +t + +a +p +p +r +é +c +i +e +r + +a +u + +j +u +s +t +e + +l +a + +q +u +a +n +t +i +t +é + +c +o +n +v +e +¬ + +n +a +b +l +e + +, + +i +l + +v +a +u +t + +m +i +e +u +x + +p +é +c +h +e +r + +p +a +r + +c +e +t + +e +x +c +è +s + +q +u +e + +p +a +r + +l +e + +c +o +n +t +r +a +i +r +e + +; + +S +t + +j +e + +c +r +o +i +s + +q +u + +o +n + +d +o +i +t + +s + +e +n + +t +e +n +i +r + +à + +c +e +t +t +e + +d +i +m +e +n +s +i +o +n + +: + +u +n +e + +p +l +u +s + +g +r +a +n +d +e + +n +e + +f +e +r +o +i +t + +p +a +s + +f +o +n +d +é +e + +e +n + +r +a +i +s +o +n +, + +c +o +m +m +e + +o +n + +v +o +i +t + +a +u + +f +o +u +r +n +e +a +u + +d +e + +B +o +e +r +h +a +a +v +e + +; + +e +l +l +e + +e +s +t + +m +ê +m +e + +n +u +i +s +i +b +l +e +, + +c +o +m +m +e + +i +l + +e +s +t + +a +i +s +é + +d +e + +l +e + +p +e +n +s +e +r + +, + +& + +c +o +m +m +e + +n +o +u +s + +l +e + +d +i +r +o +n +s + +e +n + +p +a +r +l +a +n +t + +d +e +s + +a +t +h +a +n +o +r +s +. + +M +a +i +s + +i +l + +n + +e +n + +e +s +t + +p +a +s + +d +e + +m +ê +m +e + +d +u + +t +u +y +a +u + +o +u + +c +h +e +m +i +n +é +e + +; + +i +l + +n +e + +d +o +i +t + +p +a +s + +a +v +o +i +r + +l +e + +m +ê +m +e + +d +i +a +m +è +t +r +e + +q +u +e + +l +e + +f +o +u +r +¬ + +n +e +a +u +. + +C +e +c +i + +a +u + +r +e +s +t +e + +e +s +t + +u +n +e + +a +f +f +a +i +r +e + +d + +e +x +p +é +- + +r +i +e +n +c +e +, + +f +u +r + +l +a +q +u +e +l +l +e + +o +n + +n + +a + +p +a +s + +e +n +c +o +r +e + +f +a +i +t + +b +e +a +u +c +o +u +p + +d + +o +b +f +e +r +v +a +t +i +o +n +s +. + +O +n + +p +e +u +t + +n +é +a +n +¬ + +m +o +i +n +s + +a +s +s +u +r +e +r + +q +u + +e +n + +f +a +i +s +a +n +t + +u +n + +f +o +u +r +n +e +a +u + +d +e + +m +a +n +i +é +r +é + +q +u + +i +l + +a +i +l +l +e + +t +o +u +j +o +u +r +s + +e +n + +r +é +t +r +é +¬ + +c +i +s +s +a +n +t + +, + +i +l + +a +d +m +e +t +t +r +a + +p +l +u +s + +d + +a +i +r + +q +u + +i +l + +n +e + +l +u +i + +e +n + +f +a +u +t +. + + +A +u + +r +e +s +t +e +, + +s +i + +l + +o +n + +p +e +n +s +e + +q +u + +u +n + +s +o +u +p +i +r +a +i +l + +d +e + +m +ê +m +e + +d +i +a +m +è +t +r +e + +q +u +e + +l +e + +f +o +u +r +n +e +a +u + +n +e + +s +u +f +f +i +s +e + +p +a +s +, + +i +l +f +a +u +d +r +o +i +t + +> + +n +o +n + +l +' +é +l +e +v +e +r + +n +i + +f +a +i +r +e + +p +l +u +s +i +e +u +r +s + +p +o +r +t +e +s + +t +o +u +t + +a +u +t +o +u +r + +d +u + +f +o +l + +d +u + +c +e +n +¬ + +d +r +i +e +r + +, + +c +e +l +a + +f +e +r +o +i +t + +i +n +u +t +i +l +e +, + +m +a +i +s + +a +g +r +a +n +d +i +r + +l +e + +d +i +a +m +è +t +r +e + +d +u + +c +e +n +d +r +i +e +r + +l +u +i +- +m +é +m +e + +: + +& + +p +a +r + +c +e + +m +o +y +e +n + +o +n + +a +u +r +o +i +t + +u +n +e + +p +o +r +t +e + +p +l +u +s + +l +a +r +g +e + +; + +c +a +r + +i +l + +e +s +t + +a +u +s +s +i + +i +n +u +t +i +l +e + +d +e + +l +a + +f +a +i +r +e + +p +l +u +s + +h +a +u +t +e + +q +u +e + +l +a +r +g +e + +q +u +a +n +d + +e +l +l +e + +e +s +t + +d +e + +l +a + +l +a +r +g +e +u +r + +d +u + +c +e +n +¬ + +d +r +i +e +r +, + +q +u +e + +d + +e +n + +m +e +t +t +r +e + +p +l +u +s +i +e +u +r +s + +t +o +u +t + +a +u +¬ + +t +o +u +r + +, + +d +e + +c +e +t +t +e + +m +ê +m +e + +l +a +r +g +e +u +r +. + +C +e +l +a + +n +e + +p +e +u +t + +a +v +o +i +r + +l +i +e +u + +q +u +e + +q +u +a +n +d + +c +h +a +c +u +n +e + +d + +e +l +l +e +s + +n + +a + +q +u + +u +n +e + +p +a +r +t +i +e + +d +u + +d +i +a +m +è +t +r +e + +d +u + +c +e +n +d +r +i +e +r +, + +S +c + +e +n + +c +e + +c +a +s + +e +l +l +e +s + +n +e + +d +o +i +v +e +n +t + +t +a +i +r +e + +e +n +t +r + +e +l +l +e +s + +q +u +e + +l +a + +s +o +m +m +e + +d +e + +f +a + +l +a +r +g +e +u +r +. + + +D +e +s + +d +e +g +r +é +s + +d +u + +f +e +u +. + +C + +e +s +t + +p +a +r + +l +e + +m +o +y +e +n + +d +e +s + +r +e +g +i +s +t +r +e +s + +S +t + +d +u + +s +o +u +p +i +r +a +i +l +, + +c +o +m +m +e + +n +o +i +i +s + +l + +a +v +o +n +s +d +é +j +à + +d +i +t + +e +n + +p +l +u +s + +d + +u +n + +e +n +d +r +o +i +t +, + +q +u + +o +n + +r +é +g +l +é + +l +e +s + +d +i +f +f +é +r +e +n +s + +d +e +g +r +é +s + +d +u + +f +e +u +. + +V +o +y +e +% + +c +ç + +q +u + +o +n + +e +n + +a + +d +i +t + +à + +Y + +a +r +t +i +c +l +e + +F +e +u +. + + +L +e +s + +c +h +y +m +i +s +t +e +s + +f +e + +f +o +n +t + +u +n + +p +e +u + +p +l +u +s + +d +o +n +n +é + +d +e + +p +e +i +n +e + +p +o +u +r + +r +é +g +l +e +r + +l +e +s + +d +e +g +r +é +s + +d +u + +f +e +u +, + +q +u +e + +p +o +u +r + +l +a + +c +o +n +s +t +r +u +c +t +i +o +n + +d +e +s + +f +o +u +r +n +e +a +u +x + +; + +& + +c +e +p +e +n +d +a +n +t + +l + +u +n + +S +t + +l + +a +u +t +r +e + +d +é +v +o +i +e +n +t + +a +i +l +e +s + +e +n +s +e +m +b +l +e +. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 6b5eeb0544c1e96da455aa78d8c5e57c82110eec Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Wed, 21 Aug 2024 16:52:00 +0100 Subject: [PATCH 24/26] FIX: PDF Message offsets - combined handling of all PDFException subclasses into a single `PDFException` handler for `PdfModule.addDestination` method; - added offest and null property recording to the exception handler; - stopped double logging of `PDF-HUL-149` by simply throwing the exception rather than adding it to errors list and throwing it; - added straggling test corpora files; and - updated test script to copy affected PDFs. --- jhove-bbt/scripts/create-1.31-target.sh | 11 +++++++++++ .../hul/ois/jhove/module/PdfModule.java | 13 +------------ .../HTML-hul/xhtml-1-1-no-xml-dec.html | 17 +++++++++++++++++ .../modules/HTML-hul/xhtml-1-1-xml-dec.html | 18 ++++++++++++++++++ .../HTML-hul/xhtml-frames-no-xml-dec.html | 14 ++++++++++++++ .../HTML-hul/xhtml-frames-xml-dec.html | 15 +++++++++++++++ .../HTML-hul/xhtml-strict-no-xml-dec.html | 14 ++++++++++++++ .../HTML-hul/xhtml-strict-xml-dec.html | 15 +++++++++++++++ .../HTML-hul/xhtml-trans-no-xml-dec.html | 14 ++++++++++++++ .../modules/HTML-hul/xhtml-trans-xml-dec.html | 15 +++++++++++++++ .../modules/JPEG-hul/19_e190014.jpg | Bin 0 -> 180037 bytes 11 files changed, 134 insertions(+), 12 deletions(-) create mode 100644 test-root/corpora/errors/modules/HTML-hul/xhtml-1-1-no-xml-dec.html create mode 100644 test-root/corpora/errors/modules/HTML-hul/xhtml-1-1-xml-dec.html create mode 100644 test-root/corpora/errors/modules/HTML-hul/xhtml-frames-no-xml-dec.html create mode 100644 test-root/corpora/errors/modules/HTML-hul/xhtml-frames-xml-dec.html create mode 100644 test-root/corpora/errors/modules/HTML-hul/xhtml-strict-no-xml-dec.html create mode 100644 test-root/corpora/errors/modules/HTML-hul/xhtml-strict-xml-dec.html create mode 100644 test-root/corpora/errors/modules/HTML-hul/xhtml-trans-no-xml-dec.html create mode 100644 test-root/corpora/errors/modules/HTML-hul/xhtml-trans-xml-dec.html create mode 100644 test-root/corpora/regression/modules/JPEG-hul/19_e190014.jpg diff --git a/jhove-bbt/scripts/create-1.31-target.sh b/jhove-bbt/scripts/create-1.31-target.sh index 8e0dede59..48f7049f2 100755 --- a/jhove-bbt/scripts/create-1.31-target.sh +++ b/jhove-bbt/scripts/create-1.31-target.sh @@ -169,3 +169,14 @@ cp -rf "${candidateRoot}/errors/modules/WAVE-hul" "${targetRoot}/errors/modules/ # Copy the results of the new XML fixes for multiple redirect lookups and to ensure no regression for repeat XML warnings cp -rf "${candidateRoot}/errors/modules/XML-hul" "${targetRoot}/errors/modules/" + +# Copy the results of the PDF offset message fix +declare -a pdf_offset_affected=("errors/modules/PDF-hul/pdf-hul-5-govdocs-659152.pdf.jhove.xml" + "errors/modules/PDF-hul/pdf-hul-10-govdocs-803945.pdf.jhove.xml" + "regression/modules/PDF-hul/issue_306.pdf.jhove.xml") +for filename in "${pdf_offset_affected[@]}" +do + if [[ -f "${candidateRoot}/${filename}" ]]; then + cp "${candidateRoot}/${filename}" "${targetRoot}/${filename}" + fi +done diff --git a/jhove-modules/pdf-hul/src/main/java/edu/harvard/hul/ois/jhove/module/PdfModule.java b/jhove-modules/pdf-hul/src/main/java/edu/harvard/hul/ois/jhove/module/PdfModule.java index 571917904..012cc9428 100644 --- a/jhove-modules/pdf-hul/src/main/java/edu/harvard/hul/ois/jhove/module/PdfModule.java +++ b/jhove-modules/pdf-hul/src/main/java/edu/harvard/hul/ois/jhove/module/PdfModule.java @@ -3492,18 +3492,10 @@ protected void addDestination(PdfObject itemObj, String propName, destPg)); } } - } catch (PdfMalformedException e) { + } catch (PdfException e) { propList.add(new Property(propName, PropertyType.STRING, PROP_VAL_NULL)); info.setMessage(new ErrorMessage(e.getJhoveMessage(), _parser.getOffset())); info.setValid(false); - } catch (PdfInvalidException e) { - if (e.getJhoveMessage() != null) { - info.setMessage(new ErrorMessage( - JhoveMessages.getMessageInstance( - e.getJhoveMessage().getId(), e.getJhoveMessage().getMessage(), - e.getJhoveMessage().getSubMessage()))); - info.setValid(false); - } } catch (Exception e) { String msg = e.getClass().getName(); @@ -4302,10 +4294,7 @@ protected int resolveIndirectDest(PdfSimpleObject key, RepInfo info) throws PdfE key.getStringValue()); JhoveMessage message = JhoveMessages.getMessageInstance( MessageConstants.PDF_HUL_149.getId(), mess); - info.setMessage(new ErrorMessage(message)); throw new PdfInvalidException(message); // PDF-HUL-149 - // OR if this is not considered invalid - // return -1; } Destination dest = new Destination(destObj, this, true); return dest.getPageDestObjNumber(); diff --git a/test-root/corpora/errors/modules/HTML-hul/xhtml-1-1-no-xml-dec.html b/test-root/corpora/errors/modules/HTML-hul/xhtml-1-1-no-xml-dec.html new file mode 100644 index 000000000..073974613 --- /dev/null +++ b/test-root/corpora/errors/modules/HTML-hul/xhtml-1-1-no-xml-dec.html @@ -0,0 +1,17 @@ + + + + TITLE + + + + +

Moved to example.org.

+ + diff --git a/test-root/corpora/errors/modules/HTML-hul/xhtml-1-1-xml-dec.html b/test-root/corpora/errors/modules/HTML-hul/xhtml-1-1-xml-dec.html new file mode 100644 index 000000000..d9d134902 --- /dev/null +++ b/test-root/corpora/errors/modules/HTML-hul/xhtml-1-1-xml-dec.html @@ -0,0 +1,18 @@ + + + + + TITLE + + + + +

Moved to example.org.

+ + diff --git a/test-root/corpora/errors/modules/HTML-hul/xhtml-frames-no-xml-dec.html b/test-root/corpora/errors/modules/HTML-hul/xhtml-frames-no-xml-dec.html new file mode 100644 index 000000000..834ce09a0 --- /dev/null +++ b/test-root/corpora/errors/modules/HTML-hul/xhtml-frames-no-xml-dec.html @@ -0,0 +1,14 @@ + + + + + TITLE + + + + +
+ TEXT +
+ + \ No newline at end of file diff --git a/test-root/corpora/errors/modules/HTML-hul/xhtml-frames-xml-dec.html b/test-root/corpora/errors/modules/HTML-hul/xhtml-frames-xml-dec.html new file mode 100644 index 000000000..a70e95b7c --- /dev/null +++ b/test-root/corpora/errors/modules/HTML-hul/xhtml-frames-xml-dec.html @@ -0,0 +1,15 @@ + + + + + + TITLE + + + + +
+ TEXT +
+ + \ No newline at end of file diff --git a/test-root/corpora/errors/modules/HTML-hul/xhtml-strict-no-xml-dec.html b/test-root/corpora/errors/modules/HTML-hul/xhtml-strict-no-xml-dec.html new file mode 100644 index 000000000..eab2c34ff --- /dev/null +++ b/test-root/corpora/errors/modules/HTML-hul/xhtml-strict-no-xml-dec.html @@ -0,0 +1,14 @@ + + + + + TITLE + + + + +
+ TEXT +
+ + \ No newline at end of file diff --git a/test-root/corpora/errors/modules/HTML-hul/xhtml-strict-xml-dec.html b/test-root/corpora/errors/modules/HTML-hul/xhtml-strict-xml-dec.html new file mode 100644 index 000000000..746c647ca --- /dev/null +++ b/test-root/corpora/errors/modules/HTML-hul/xhtml-strict-xml-dec.html @@ -0,0 +1,15 @@ + + + + + + TITLE + + + + +
+ TEXT +
+ + \ No newline at end of file diff --git a/test-root/corpora/errors/modules/HTML-hul/xhtml-trans-no-xml-dec.html b/test-root/corpora/errors/modules/HTML-hul/xhtml-trans-no-xml-dec.html new file mode 100644 index 000000000..793523694 --- /dev/null +++ b/test-root/corpora/errors/modules/HTML-hul/xhtml-trans-no-xml-dec.html @@ -0,0 +1,14 @@ + + + + + TITLE + + + + +
+ TEXT +
+ + \ No newline at end of file diff --git a/test-root/corpora/errors/modules/HTML-hul/xhtml-trans-xml-dec.html b/test-root/corpora/errors/modules/HTML-hul/xhtml-trans-xml-dec.html new file mode 100644 index 000000000..ddac982d5 --- /dev/null +++ b/test-root/corpora/errors/modules/HTML-hul/xhtml-trans-xml-dec.html @@ -0,0 +1,15 @@ + + + + + + TITLE + + + + +
+ TEXT +
+ + \ No newline at end of file diff --git a/test-root/corpora/regression/modules/JPEG-hul/19_e190014.jpg b/test-root/corpora/regression/modules/JPEG-hul/19_e190014.jpg new file mode 100644 index 0000000000000000000000000000000000000000..29a2903eb1457be52e045f28fc86482238f74dbf GIT binary patch literal 180037 zcmeFa2S60b(l|bfiUGwyQWOva0+KTdt0EvOg5;ne>@JyIgk3VM5ln!Bf`EXi zYa)w?B#A2^IcF9&{ChCoy}Nh!e(%2T-T!+I>`YI0O?P#5RdsjObPxRly^}pc+11(t zg4EOmpw$oru|v=@251=wxiNy^m+;Hw5VRbG89+QQNM!gDzY^T9`Vs~p(E4S|p*`Tb z55R0%wgOrO!s;Nr`3nr3FYA{uoEO`d@C6WN{{oK&K--qBT*AQ%ggL*2uY&OQAM)7& z$^s}tejt9|mv|TlF%ZuUu6IGaI0&n*1n%%8~{u__^n?0Q<6WU>x6c6I%99Jc6TLn9u>9#Dt%0Du|VX@+!iP}f$`0l>d= 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 X$)S`<{8+mLKpr^+|8xC@{hIz?#UoC? literal 0 HcmV?d00001 From 028a5faa02a8eb595a0b59cd71a6589c516c5790 Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Thu, 22 Aug 2024 13:53:52 +0100 Subject: [PATCH 25/26] REL: JHOVE v1.32.0-RC1 - updated JHOVE version to 1.32.0-RC1; - bumped other module and handler version numbers: - JSON Handler -> 1.3; - Text Handler -> 1.7; - XML Handler -> 1.12; - HTML-hul -> 1.4.4; - PDF-hul -> 1.12.7; - TIFF-hul -> 1.9.5; - XML-hul -> 1.5.5; - all release dates updated to current date; - added test script with release details update for 1.32 testing; and - updated Dockerfile version. --- Dockerfile | 4 +- jhove-apps/pom.xml | 6 +- jhove-bbt/scripts/create-1.32-target.sh | 213 +++ jhove-core/pom.xml | 2 +- .../hul/ois/jhove/handler/JsonHandler.java | 4 +- .../hul/ois/jhove/handler/TextHandler.java | 4 +- .../hul/ois/jhove/handler/XmlHandler.java | 22 +- jhove-ext-modules/pom.xml | 2 +- jhove-installer/pom.xml | 14 +- jhove-modules/aiff-hul/pom.xml | 2 +- jhove-modules/ascii-hul/pom.xml | 2 +- jhove-modules/gif-hul/pom.xml | 2 +- jhove-modules/html-hul/pom.xml | 6 +- .../hul/ois/jhove/module/HtmlModule.java | 1292 ++++++++--------- jhove-modules/jpeg-hul/pom.xml | 4 +- jhove-modules/jpeg2000-hul/pom.xml | 2 +- jhove-modules/pdf-hul/pom.xml | 4 +- .../hul/ois/jhove/module/PdfModule.java | 4 +- .../ois/jhove/module/pdf/LiteralTests.java | 4 - jhove-modules/pom.xml | 6 +- jhove-modules/tiff-hul/pom.xml | 4 +- .../hul/ois/jhove/module/TiffModule.java | 6 +- jhove-modules/utf8-hul/pom.xml | 4 +- jhove-modules/wave-hul/pom.xml | 2 +- jhove-modules/xml-hul/pom.xml | 4 +- .../hul/ois/jhove/module/XmlModule.java | 4 +- pom.xml | 2 +- 27 files changed, 917 insertions(+), 708 deletions(-) create mode 100755 jhove-bbt/scripts/create-1.32-target.sh diff --git a/Dockerfile b/Dockerfile index 9f8e10ca1..fcdd093c3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ # First build the app on a maven open jdk 11 container FROM maven:3-eclipse-temurin-11-focal as dev-builder ARG JHOVE_VERSION -ENV JHOVE_VERSION=${JHOVE_VERSION:-1.27.0-SNAPSHOT} +ENV JHOVE_VERSION=${JHOVE_VERSION:-1.32.0-RC1} # Copy the current dev source branch to a local build dir COPY . /build/jhove/ @@ -31,7 +31,7 @@ ARG JAVA_OPTS ENV JAVA_OPTS=$JAVA_OPTS # Specify the veraPDF REST version if you want to (to be used in build automation) ARG JHOVE_VERSION -ENV JHOVE_VERSION=${JHOVE_VERSION:-1.27.0-SNAPSHOT} +ENV JHOVE_VERSION=${JHOVE_VERSION:-1.32.0-RC1} # Copy the JRE from the previous stage ENV JAVA_HOME=/opt/java/openjdk diff --git a/jhove-apps/pom.xml b/jhove-apps/pom.xml index fb5c427dc..bd1098bb7 100644 --- a/jhove-apps/pom.xml +++ b/jhove-apps/pom.xml @@ -5,12 +5,12 @@ org.openpreservation.jhove jhove - 1.31.0-SNAPSHOT + 1.32.0-RC1 jhove-apps jar - 1.31.0-SNAPSHOT + 1.32.0-RC1 JHOVE Applications @@ -60,7 +60,7 @@ org.openpreservation.jhove jhove-core - 1.31.0-SNAPSHOT + 1.32.0-RC1 diff --git a/jhove-bbt/scripts/create-1.32-target.sh b/jhove-bbt/scripts/create-1.32-target.sh new file mode 100755 index 000000000..6857fa33d --- /dev/null +++ b/jhove-bbt/scripts/create-1.32-target.sh @@ -0,0 +1,213 @@ +#!/usr/bin/env bash + +testRoot="test-root" +paramCandidateVersion="" +paramBaselineVersion="" +baselineRoot="${testRoot}/baselines" +candidateRoot="${testRoot}/candidates" +targetRoot="${testRoot}/targets" +# Check the passed params to avoid disapointment +checkParams () { + OPTIND=1 # Reset in case getopts previously used + + while getopts "h?b:c:" opt; do # Grab the options + case "$opt" in + h|\?) + showHelp + exit 0 + ;; + b) paramBaselineVersion=$OPTARG + ;; + c) paramCandidateVersion=$OPTARG + ;; + esac + done + + if [ -z "$paramBaselineVersion" ] || [ -z "$paramCandidateVersion" ] + then + showHelp + exit 0 + fi + + baselineRoot="${baselineRoot}/${paramBaselineVersion}" + candidateRoot="${candidateRoot}/${paramCandidateVersion}" + targetRoot="${targetRoot}/${paramCandidateVersion}" +} + +# Show usage message +showHelp() { + echo "usage: create-target [-b ] [-c ] [-h|?]" + echo "" + echo " baselineVersion : The version number id for the baseline data." + echo " candidateVersion : The version number id for the candidate data." + echo "" + echo " -h|? : This message." +} + +# Execution starts here +checkParams "$@"; +if [[ -d "${targetRoot}" ]]; then + echo " - removing existing baseline at ${targetRoot}." + rm -rf "${targetRoot}" +fi + +echo "TEST BASELINE: Creating baseline" +# Simply copy baseline for now we're not making any changes +echo " - copying ${baselineRoot} baseline to ${targetRoot}" +cp -R "${baselineRoot}" "${targetRoot}" + +# Patch release details of the reporting module in the audit file +find "${targetRoot}" -type f -name "audit.jhove.xml" -exec sed -i 's/outputHandler release="1.11">XML/outputHandler release="1.12">XML/' {} \; +find "${targetRoot}" -type f -name "audit.jhove.xml" -exec sed -i 's/outputHandler release="1.2">JSON/outputHandler release="1.3">JSON/' {} \; +find "${targetRoot}" -type f -name "audit.jhove.xml" -exec sed -i 's/outputHandler release="1.6">TEXT/outputHandler release="1.7">TEXT/' {} \; + +# Update release details for HTML module +find "${targetRoot}" -type f -name "*.html.jhove.xml" -exec sed -i 's/HTML-hul<\/reportingModule>/HTML-hul<\/reportingModule>/' {} \; +find "${targetRoot}" -type f -name "audit.jhove.xml" -exec sed -i 's/HTML-hul<\/module>/HTML-hul<\/module>/' {} \; +find "${targetRoot}" -type f -name "audit-HTML-hul.jhove.xml" -exec sed -i 's/1.4.3<\/release>/1.4.4<\/release>/' {} \; +find "${targetRoot}" -type f -name "audit-HTML-hul.jhove.xml" -exec sed -i 's/2023-03-16/2024-08-22/' {} \; + +# Update release details for PDF module +find "${targetRoot}" -type f -name "*.pdf.jhove.xml" -exec sed -i 's/PDF-hul<\/reportingModule>/PDF-hul<\/reportingModule>/' {} \; +find "${targetRoot}" -type f -name "audit.jhove.xml" -exec sed -i 's/PDF-hul<\/module>/PDF-hul<\/module>/' {} \; +find "${targetRoot}" -type f -name "audit-PDF-hul.jhove.xml" -exec sed -i 's/1.12.6<\/release>/1.12.7<\/release>/' {} \; +find "${targetRoot}" -type f -name "audit-PDF-hul.jhove.xml" -exec sed -i 's/2024-07-31/2024-08-22/' {} \; + +# Update release details for TIFF module +find "${targetRoot}" -type f -name "*.tiff.jhove.xml" -exec sed -i 's/TIFF-hul<\/reportingModule>/TIFF-hul<\/reportingModule>/' {} \; +find "${targetRoot}" -type f -name "*.tif.jhove.xml" -exec sed -i 's/TIFF-hul<\/reportingModule>/TIFF-hul<\/reportingModule>/' {} \; +find "${targetRoot}" -type f -name "*.g3.jhove.xml" -exec sed -i 's/TIFF-hul<\/reportingModule>/TIFF-hul<\/reportingModule>/' {} \; +find "${targetRoot}" -type f -name "audit.jhove.xml" -exec sed -i 's/TIFF-hul<\/module>/TIFF-hul<\/module>/' {} \; +find "${targetRoot}" -type f -name "audit-TIFF-hul.jhove.xml" -exec sed -i 's/1.9.4<\/release>/1.9.5<\/release>/' {} \; +find "${targetRoot}" -type f -name "audit-TIFF-hul.jhove.xml" -exec sed -i 's/2023-03-16/2024-08-22/' {} \; + +# Update release details for XML module +find "${targetRoot}" -type f -name "*.xml.jhove.xml" -exec sed -i 's/XML-hul<\/reportingModule>/XML-hul<\/reportingModule>/' {} \; +find "${targetRoot}" -type f -name "audit.jhove.xml" -exec sed -i 's/XML-hul<\/module>/XML-hul<\/module>/' {} \; +find "${targetRoot}" -type f -name "audit-XML-hul.jhove.xml" -exec sed -i 's/1.5.4<\/release>/1.5.5<\/release>/' {} \; +find "${targetRoot}" -type f -name "audit-XML-hul.jhove.xml" -exec sed -i 's/2024-03-05/2024-08-22/' {} \; + +# Copy the TIFF Module results changed by https://github.com/openpreserve/jhove/pull/915 +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/AA_Banner.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/AA_Banner.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/AA_Banner.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/strike.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/strike.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/strike.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/testpage-large.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/testpage-large.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/testpage-large.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/testpage-medium.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/testpage-medium.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/testpage-medium.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/oxford.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/oxford.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/oxford.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/jim___gg.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/jim___gg.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/jim___gg.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/bathy1.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/bathy1.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/bathy1.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/jim___cg.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/jim___cg.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/jim___cg.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/quad-tile.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/quad-tile.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/quad-tile.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/compos.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/compos.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/compos.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/pagemaker.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/pagemaker.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/pagemaker.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/jello.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/jello.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/jello.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/little-endian.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/little-endian.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/little-endian.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/cramps-tile.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/cramps-tile.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/cramps-tile.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/jim___ah.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/jim___ah.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/jim___ah.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/g3test.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/g3test.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/g3test.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/6mp_soft.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/6mp_soft.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/6mp_soft.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/ycbcr-cat.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/ycbcr-cat.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/ycbcr-cat.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/quad-lzw.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/quad-lzw.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/quad-lzw.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/jim___dg.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/jim___dg.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/jim___dg.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/fax2d.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/fax2d.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/fax2d.tif.jhove.xml" +fi +if [[ -f "${candidateRoot}/examples/modules/TIFF-hul/peppers.tif.jhove.xml" ]]; then + cp "${candidateRoot}/examples/modules/TIFF-hul/peppers.tif.jhove.xml" "${targetRoot}/examples/modules/TIFF-hul/peppers.tif.jhove.xml" +fi + +# Copy the TIFF fix affected files from the candidate to the target +declare -a tiff_affected=("examples/modules/TIFF-hul/cramps.tif.jhove.xml" + "examples/modules/TIFF-hul/text.tif.jhove.xml" + "examples/modules/TIFF-hul/testpage-small.tif.jhove.xml") +for filename in "${tiff_affected[@]}" +do + if [[ -f "${candidateRoot}/${filename}" ]]; then + cp "${candidateRoot}/${filename}" "${targetRoot}/${filename}" + fi +done + +# Copy the XHTML fix affected files from the candidate to the target +declare -a xhtml_affected=("errors/modules/HTML-hul/xhtml-trans-no-xml-dec.html.jhove.xml" + "errors/modules/HTML-hul/xhtml-strict-no-xml-dec.html.jhove.xml" + "errors/modules/HTML-hul/xhtml-frames-no-xml-dec.html.jhove.xml" + "errors/modules/HTML-hul/xhtml-1-1-no-xml-dec.html.jhove.xml") +for filename in "${xhtml_affected[@]}" +do + if [[ -f "${candidateRoot}/${filename}" ]]; then + cp "${candidateRoot}/${filename}" "${targetRoot}/${filename}" + fi +done + +# Copy the XML fix affected files from the candidate to the target +declare -a xhtml_affected=("errors/modules/HTML-hul/xhtml-trans-xml-dec.html.jhove.xml" + "errors/modules/HTML-hul/xhtml-strict-xml-dec.html.jhove.xml" + "errors/modules/HTML-hul/xhtml-frames-xml-dec.html.jhove.xml" + "errors/modules/HTML-hul/xhtml-1-1-xml-dec.html.jhove.xml" + "examples/modules/XML-hul/valid-external.dtd.jhove.xml" + "examples/modules/XML-hul/external-unparsed-entity.ent.jhove.xml" + "examples/modules/XML-hul/external-parsed-entity.ent.jhove.xml") +for filename in "${xhtml_affected[@]}" +do + if [[ -f "${candidateRoot}/${filename}" ]]; then + cp "${candidateRoot}/${filename}" "${targetRoot}/${filename}" + fi +done + +# Copy all of the AIF and WAV results as these are changed by the AES schema changes +cp -rf "${candidateRoot}/examples/modules/AIFF-hul" "${targetRoot}/examples/modules/" +cp -rf "${candidateRoot}/examples/modules/WAVE-hul" "${targetRoot}/examples/modules/" +cp -rf "${candidateRoot}/errors/modules/WAVE-hul" "${targetRoot}/errors/modules/" + +# Copy the results of the new XML fixes for multiple redirect lookups and to ensure no regression for repeat XML warnings +cp -rf "${candidateRoot}/errors/modules/XML-hul" "${targetRoot}/errors/modules/" + +# Copy the results of the PDF offset message fix +declare -a pdf_offset_affected=("errors/modules/PDF-hul/pdf-hul-5-govdocs-659152.pdf.jhove.xml" + "errors/modules/PDF-hul/pdf-hul-10-govdocs-803945.pdf.jhove.xml" + "regression/modules/PDF-hul/issue_306.pdf.jhove.xml") +for filename in "${pdf_offset_affected[@]}" +do + if [[ -f "${candidateRoot}/${filename}" ]]; then + cp "${candidateRoot}/${filename}" "${targetRoot}/${filename}" + fi +done diff --git a/jhove-core/pom.xml b/jhove-core/pom.xml index e09a033a4..4ac656bdb 100644 --- a/jhove-core/pom.xml +++ b/jhove-core/pom.xml @@ -5,7 +5,7 @@ org.openpreservation.jhove jhove - 1.31.0-SNAPSHOT + 1.32.0-RC1 jhove-core diff --git a/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/handler/JsonHandler.java b/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/handler/JsonHandler.java index c6d4a23fe..e6cfa2cc6 100644 --- a/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/handler/JsonHandler.java +++ b/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/handler/JsonHandler.java @@ -68,13 +68,13 @@ public class JsonHandler extends HandlerBase { private static final String NAME = "JSON"; /** Handler release identifier. */ - private static final String RELEASE = "1.2"; + private static final String RELEASE = "1.3"; /** String release. */ private static final String RELEASE_CONSTANT = "release"; /** Handler release date. */ - private static final int[] DATE = { 2024, 03, 05 }; + private static final int[] DATE = { 2024, 8, 22 }; private static final String DATE_CONSTANT = "date"; diff --git a/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/handler/TextHandler.java b/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/handler/TextHandler.java index 979c2d8f7..7e5c6030e 100644 --- a/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/handler/TextHandler.java +++ b/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/handler/TextHandler.java @@ -59,8 +59,8 @@ public class TextHandler extends HandlerBase { ******************************************************************/ private static final String NAME = "TEXT"; - private static final String RELEASE = "1.6"; - private static final int[] DATE = { 2018, 03, 29 }; + private static final String RELEASE = "1.7"; + private static final int[] DATE = { 2022, 8, 22 }; private static final String NOTE = "This is the default JHOVE output " + "handler"; private static final String RIGHTS = "Derived from software Copyright 2004-2011 " diff --git a/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/handler/XmlHandler.java b/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/handler/XmlHandler.java index cce73f5ae..e84b4fee3 100644 --- a/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/handler/XmlHandler.java +++ b/jhove-core/src/main/java/edu/harvard/hul/ois/jhove/handler/XmlHandler.java @@ -83,10 +83,10 @@ protected NumberFormat initialValue() { private static final String NAME = "XML"; /** Handler release identifier. */ - private static final String RELEASE = "1.11"; + private static final String RELEASE = "1.12"; /** Handler release date. */ - private static final int[] DATE = { 2024, 03, 05 }; + private static final int[] DATE = { 2024, 8, 22 }; /** Handler informative note. */ private static final String NOTE = "This output handler is defined by the XML Schema " @@ -3786,7 +3786,7 @@ protected void showNisoImageCaptureMetadata20(NisoImageMetadata niso, n = niso.getOrientation(); if (n != NisoImageMetadata.NULL) { - // Values defined in the MIX 2.0 schema + // Values defined in the MIX 2.0 schema final String[] orient = { "", "normal*", "normal, image flipped", "normal, rotated 180\u00B0", "normal, image flipped, rotated 180\u00B0", @@ -4383,10 +4383,10 @@ protected void showAESAudioMetadata(AESAudioMetadata aes) { sampleRate != AESAudioMetadata.NILL || wordSize != AESAudioMetadata.NULL) { _writer.println(margn2 + elementStart("aes:formatList")); - String[][] frAttr = { { "ID", formatRegionID }, - {"xsi:type", "aes:formatRegionType"}, - {"ownerRef", faceRegionID}, - {"label", "JHOVE"}}; + String[][] frAttr = { { "ID", formatRegionID }, + { "xsi:type", "aes:formatRegionType" }, + { "ownerRef", faceRegionID }, + { "label", "JHOVE" } }; _writer.println(margn3 + elementStart("aes:formatRegion", frAttr)); if (bitDepth != AESAudioMetadata.NULL) { _writer.println(margn4 + element("aes:bitDepth", @@ -4446,10 +4446,10 @@ private void writeAESTimeRangePart(String indent, String elementName, AESAudioMe } String[][] attributes = { - {"editRate", formatters.get().format(sampleRate)}, - {"factorNumerator", "1"}, - {"factorDenominator", "1"} - }; + { "editRate", formatters.get().format(sampleRate) }, + { "factorNumerator", "1" }, + { "factorDenominator", "1" } + }; _writer.println(indent + element(elementName, attributes, String.valueOf(timeDesc.getSamples()))); diff --git a/jhove-ext-modules/pom.xml b/jhove-ext-modules/pom.xml index d52517052..662f7edbd 100644 --- a/jhove-ext-modules/pom.xml +++ b/jhove-ext-modules/pom.xml @@ -3,7 +3,7 @@ org.openpreservation.jhove jhove - 1.31.0-SNAPSHOT + 1.32.0-RC1 jhove-ext-modules diff --git a/jhove-installer/pom.xml b/jhove-installer/pom.xml index 76dc74c70..875c5bace 100644 --- a/jhove-installer/pom.xml +++ b/jhove-installer/pom.xml @@ -5,11 +5,11 @@ org.openpreservation.jhove jhove - 1.31.0-SNAPSHOT + 1.32.0-RC1 jhove-installer - 1.31.0-SNAPSHOT + 1.32.0-RC1 JHOVE Installer Maven-built IzPack installer for JHOVE. @@ -22,14 +22,14 @@ 1.6.2 1.4.2 1.4.3 - 1.4.3 + 1.4.4 1.4.4 1.5.4 - 1.12.6 - 1.9.4 + 1.12.7 + 1.9.5 1.7.3 1.8.3 - 1.5.4 + 1.5.5 @@ -175,7 +175,7 @@ org.openpreservation.jhove jhove-ext-modules - 1.31.0-SNAPSHOT + ${project.version} org.openpreservation.jhove.modules diff --git a/jhove-modules/aiff-hul/pom.xml b/jhove-modules/aiff-hul/pom.xml index 72180500d..62c8b5191 100644 --- a/jhove-modules/aiff-hul/pom.xml +++ b/jhove-modules/aiff-hul/pom.xml @@ -3,7 +3,7 @@ org.openpreservation.jhove.modules jhove-modules - 1.31.0-SNAPSHOT + 1.32.0-RC1 aiff-hul 1.6.2 diff --git a/jhove-modules/ascii-hul/pom.xml b/jhove-modules/ascii-hul/pom.xml index 250678d7b..45e59316b 100644 --- a/jhove-modules/ascii-hul/pom.xml +++ b/jhove-modules/ascii-hul/pom.xml @@ -3,7 +3,7 @@ org.openpreservation.jhove.modules jhove-modules - 1.31.0-SNAPSHOT + 1.32.0-RC1 ascii-hul 1.4.2 diff --git a/jhove-modules/gif-hul/pom.xml b/jhove-modules/gif-hul/pom.xml index ad7998d18..519953a30 100644 --- a/jhove-modules/gif-hul/pom.xml +++ b/jhove-modules/gif-hul/pom.xml @@ -3,7 +3,7 @@ org.openpreservation.jhove.modules jhove-modules - 1.31.0-SNAPSHOT + 1.32.0-RC1 gif-hul 1.4.3 diff --git a/jhove-modules/html-hul/pom.xml b/jhove-modules/html-hul/pom.xml index a8af3637b..31e43b42a 100644 --- a/jhove-modules/html-hul/pom.xml +++ b/jhove-modules/html-hul/pom.xml @@ -3,10 +3,10 @@ org.openpreservation.jhove.modules jhove-modules - 1.31.0-SNAPSHOT + 1.32.0-RC1 html-hul - 1.4.3 + 1.4.4 JHOVE HTML Module HUL HTML module developed by Harvard University Library @@ -14,7 +14,7 @@ org.openpreservation.jhove.modules xml-hul - 1.5.4 + 1.5.5 diff --git a/jhove-modules/html-hul/src/main/java/edu/harvard/hul/ois/jhove/module/HtmlModule.java b/jhove-modules/html-hul/src/main/java/edu/harvard/hul/ois/jhove/module/HtmlModule.java index 56b8cdd3a..9d3f8d727 100644 --- a/jhove-modules/html-hul/src/main/java/edu/harvard/hul/ois/jhove/module/HtmlModule.java +++ b/jhove-modules/html-hul/src/main/java/edu/harvard/hul/ois/jhove/module/HtmlModule.java @@ -93,589 +93,589 @@ */ public class HtmlModule extends ModuleBase { - /****************************************************************** - * PRIVATE CLASS FIELDS. - ******************************************************************/ - private static final String TRANSITIONAL = "Transitional"; - private static final String STRICT = "Strict"; - private static final String FRAMESET = "Frameset"; - private static final String HTML_4_0 = "HTML 4.0"; - private static final String HTML_4_01 = "HTML 4.01"; - private static final String XHTML_1_0 = "XHTML 1.0"; - private static final String XHTML_1_1_STR = "XHTML 1.1"; - - private static final String NAME = "HTML-hul"; - private static final String RELEASE = "1.4.3"; - private static final int[] DATE = { 2023, 03, 16 }; - 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. - ******************************************************************/ - - /* 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_STR }; - - /* 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); - - _vendor = Agent.harvardInstance(); - - /* HTML 3.2 spec */ - Document doc = new Document("HTML 3.2 Reference Specification", - DocumentType.REPORT); - Agent w3cAgent = Agent.newW3CInstance(); - doc.setPublisher(w3cAgent); - - Agent dRaggett = new Agent.Builder("Dave Raggett", AgentType.OTHER) - .build(); - 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.Builder("Arnaud Le Hors", AgentType.OTHER) - .build(); - doc.setAuthor(leHors); - Agent jacobs = new Agent.Builder("Ian Jacobs", AgentType.OTHER).build(); - 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. - * - * @return parseInt - */ - @Override - public int parse(InputStream stream, RepInfo info, int parseIndex) { - 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.setDefaultParams(_defaultParams); - try { - xmlMod.applyDefaultParams(); - } catch (Exception e) { - // really shouldn't happen - } - xmlMod.setXhtmlDoctype(_doctype); - return xmlMod.parse(stream, info, parseIndex); - } - // 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( - MessageConstants.JHOVE_1)); - info.setWellFormed(false); // Treat it as completely wrong - return 0; - } - /* 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 ("withtextmd=true".equalsIgnoreCase(param)) { - _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. - */ - setupDataStream(stream, info); - - ParseHtml parser; - HtmlMetadata metadata = null; - HtmlCharStream cstream; - try { - cstream = new HtmlCharStream(_dstream, "ISO-8859-1"); - parser = new ParseHtml(this, cstream); - } catch (UnsupportedEncodingException e) { - info.setMessage(new ErrorMessage( - MessageConstants.JHOVE_2, 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( - MessageConstants.JHOVE_3)); - return 0; - } - type = checkDoctype(elements); - if (type < 0) { - info.setWellFormed(false); - info.setMessage(new ErrorMessage( - MessageConstants.HTML_HUL_15)); - 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( - MessageConstants.HTML_HUL_17)); - info.setWellFormed(false); - return 0; - } - - // CRLF from HtmlCharStream ... - String lineEnd = cstream.getKindOfLineEnd(); - if (lineEnd == null) { - info.setMessage( - new InfoMessage(MessageConstants.HTML_HUL_23)); - _textMD.setLinebreak(TextMDMetadata.NILL); - } else if ("CR".equalsIgnoreCase(lineEnd)) { - _textMD.setLinebreak(TextMDMetadata.LINEBREAK_CR); - } else if ("LF".equalsIgnoreCase(lineEnd)) { - _textMD.setLinebreak(TextMDMetadata.LINEBREAK_LF); - } else if ("CRLF".equalsIgnoreCase(lineEnd)) { - _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( - MessageConstants.HTML_HUL_14)); - info.setWellFormed(false); - return 0; - case 2: // probably XHTML - return 100; - default: - break; - } - info.setMessage(new ErrorMessage( - MessageConstants.HTML_HUL_16)); - info.setValid(false); - // But keep going - } - - HtmlDocDesc docDesc = null; - switch (type) { - case HTML_3_2: - - 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( - MessageConstants.HTML_HUL_22)); - 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.contains("UTF")) { - _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( - MessageConstants.HTML_HUL_18, - "Line = " + t.beginLine + ", column = " + t.beginColumn)); - info.setWellFormed(false); - } catch (TokenMgrError f) { - info.setMessage(new ErrorMessage( - MessageConstants.HTML_HUL_19, - 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); - } - } - - // Set the checksums in the report if they're calculated - setChecksums(this._ckSummer, info); - - 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 - * - * @throws IOException - */ - @Override - 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 (null != str) - switch (str) { - case "-//W3C//DTD HTML 3.2 FINAL//EN": - case "-//W3C//DTD HTML 3.2//EN": - return HTML_3_2; - case "-//W3C//DTD HTML 4.0//EN": - return HTML_4_0_STRICT; - case "-//W3C//DTD HTML 4.0 TRANSITIONAL//EN": - return HTML_4_0_TRANSITIONAL; - case "-//W3C//DTD HTML 4.0 FRAMESET//EN": - return HTML_4_0_FRAMESET; - case "-//W3C//DTD HTML 4.01//EN": - return HTML_4_01_STRICT; - case "-//W3C//DTD HTML 4.01 TRANSITIONAL//EN": - return HTML_4_01_TRANSITIONAL; - case "-//W3C//DTD HTML 4.01 FRAMESET//EN": - return HTML_4_01_FRAMESET; + /****************************************************************** + * PRIVATE CLASS FIELDS. + ******************************************************************/ + private static final String TRANSITIONAL = "Transitional"; + private static final String STRICT = "Strict"; + private static final String FRAMESET = "Frameset"; + private static final String HTML_4_0 = "HTML 4.0"; + private static final String HTML_4_01 = "HTML 4.01"; + private static final String XHTML_1_0 = "XHTML 1.0"; + private static final String XHTML_1_1_STR = "XHTML 1.1"; + + private static final String NAME = "HTML-hul"; + private static final String RELEASE = "1.4.4"; + private static final int[] DATE = { 2024, 8, 22 }; + 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. + ******************************************************************/ + + /* 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_STR }; + + /* 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); + + _vendor = Agent.harvardInstance(); + + /* HTML 3.2 spec */ + Document doc = new Document("HTML 3.2 Reference Specification", + DocumentType.REPORT); + Agent w3cAgent = Agent.newW3CInstance(); + doc.setPublisher(w3cAgent); + + Agent dRaggett = new Agent.Builder("Dave Raggett", AgentType.OTHER) + .build(); + 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.Builder("Arnaud Le Hors", AgentType.OTHER) + .build(); + doc.setAuthor(leHors); + Agent jacobs = new Agent.Builder("Ian Jacobs", AgentType.OTHER).build(); + 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. + * + * @return parseInt + */ + @Override + public int parse(InputStream stream, RepInfo info, int parseIndex) { + 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.setDefaultParams(_defaultParams); + try { + xmlMod.applyDefaultParams(); + } catch (Exception e) { + // really shouldn't happen + } + xmlMod.setXhtmlDoctype(_doctype); + return xmlMod.parse(stream, info, parseIndex); + } + // 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( + MessageConstants.JHOVE_1)); + info.setWellFormed(false); // Treat it as completely wrong + return 0; + } + /* 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 ("withtextmd=true".equalsIgnoreCase(param)) { + _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. + */ + setupDataStream(stream, info); + + ParseHtml parser; + HtmlMetadata metadata = null; + HtmlCharStream cstream; + try { + cstream = new HtmlCharStream(_dstream, "ISO-8859-1"); + parser = new ParseHtml(this, cstream); + } catch (UnsupportedEncodingException e) { + info.setMessage(new ErrorMessage( + MessageConstants.JHOVE_2, 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( + MessageConstants.JHOVE_3)); + return 0; + } + type = checkDoctype(elements); + if (type < 0) { + info.setWellFormed(false); + info.setMessage(new ErrorMessage( + MessageConstants.HTML_HUL_15)); + 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( + MessageConstants.HTML_HUL_17)); + info.setWellFormed(false); + return 0; + } + + // CRLF from HtmlCharStream ... + String lineEnd = cstream.getKindOfLineEnd(); + if (lineEnd == null) { + info.setMessage( + new InfoMessage(MessageConstants.HTML_HUL_23)); + _textMD.setLinebreak(TextMDMetadata.NILL); + } else if ("CR".equalsIgnoreCase(lineEnd)) { + _textMD.setLinebreak(TextMDMetadata.LINEBREAK_CR); + } else if ("LF".equalsIgnoreCase(lineEnd)) { + _textMD.setLinebreak(TextMDMetadata.LINEBREAK_LF); + } else if ("CRLF".equalsIgnoreCase(lineEnd)) { + _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( + MessageConstants.HTML_HUL_14)); + info.setWellFormed(false); + return 0; + case 2: // probably XHTML + return 100; + default: + break; + } + info.setMessage(new ErrorMessage( + MessageConstants.HTML_HUL_16)); + info.setValid(false); + // But keep going + } + + HtmlDocDesc docDesc = null; + switch (type) { + case HTML_3_2: + + 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( + MessageConstants.HTML_HUL_22)); + 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.contains("UTF")) { + _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( + MessageConstants.HTML_HUL_18, + "Line = " + t.beginLine + ", column = " + t.beginColumn)); + info.setWellFormed(false); + } catch (TokenMgrError f) { + info.setMessage(new ErrorMessage( + MessageConstants.HTML_HUL_19, + 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); + } + } + + // Set the checksums in the report if they're calculated + setChecksums(this._ckSummer, info); + + 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 + * + * @throws IOException + */ + @Override + 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 (null != str) + switch (str) { + case "-//W3C//DTD HTML 3.2 FINAL//EN": + case "-//W3C//DTD HTML 3.2//EN": + return HTML_3_2; + case "-//W3C//DTD HTML 4.0//EN": + return HTML_4_0_STRICT; + case "-//W3C//DTD HTML 4.0 TRANSITIONAL//EN": + return HTML_4_0_TRANSITIONAL; + case "-//W3C//DTD HTML 4.0 FRAMESET//EN": + return HTML_4_0_FRAMESET; + case "-//W3C//DTD HTML 4.01//EN": + return HTML_4_01_STRICT; + case "-//W3C//DTD HTML 4.01 TRANSITIONAL//EN": + return HTML_4_01_TRANSITIONAL; + case "-//W3C//DTD HTML 4.01 FRAMESET//EN": + return HTML_4_01_FRAMESET; case "-//W3C//DTD XHTML 1.0 STRICT//EN": return XHTML_1_0_STRICT; case "-//W3C//DTD XHTML 1.0 TRANSITIONAL//EN": @@ -685,68 +685,68 @@ protected int checkDoctype(List elements) { case "-//W3C//DTD XHTML 1.1//EN": return XHTML_1_1; default: - break; - } - } 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); - } - 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; - } - } + break; + } + } 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); + } + 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-modules/jpeg-hul/pom.xml b/jhove-modules/jpeg-hul/pom.xml index ab0eebcaf..3dd165646 100644 --- a/jhove-modules/jpeg-hul/pom.xml +++ b/jhove-modules/jpeg-hul/pom.xml @@ -3,7 +3,7 @@ org.openpreservation.jhove.modules jhove-modules - 1.31.0-SNAPSHOT + 1.32.0-RC1 jpeg-hul 1.5.4 @@ -14,7 +14,7 @@ org.openpreservation.jhove.modules tiff-hul - 1.9.4 + 1.9.5 diff --git a/jhove-modules/jpeg2000-hul/pom.xml b/jhove-modules/jpeg2000-hul/pom.xml index 77ed54d56..5bac0118c 100644 --- a/jhove-modules/jpeg2000-hul/pom.xml +++ b/jhove-modules/jpeg2000-hul/pom.xml @@ -3,7 +3,7 @@ org.openpreservation.jhove.modules jhove-modules - 1.31.0-SNAPSHOT + 1.32.0-RC1 jpeg2000-hul 1.4.4 diff --git a/jhove-modules/pdf-hul/pom.xml b/jhove-modules/pdf-hul/pom.xml index 3e5a435ec..9b9893727 100644 --- a/jhove-modules/pdf-hul/pom.xml +++ b/jhove-modules/pdf-hul/pom.xml @@ -3,10 +3,10 @@ org.openpreservation.jhove.modules jhove-modules - 1.31.0-SNAPSHOT + 1.32.0-RC1 pdf-hul - 1.12.6 + 1.12.7 JHOVE PDF Module HUL PDF module developed by Harvard University Library diff --git a/jhove-modules/pdf-hul/src/main/java/edu/harvard/hul/ois/jhove/module/PdfModule.java b/jhove-modules/pdf-hul/src/main/java/edu/harvard/hul/ois/jhove/module/PdfModule.java index 012cc9428..c3014d307 100644 --- a/jhove-modules/pdf-hul/src/main/java/edu/harvard/hul/ois/jhove/module/PdfModule.java +++ b/jhove-modules/pdf-hul/src/main/java/edu/harvard/hul/ois/jhove/module/PdfModule.java @@ -380,8 +380,8 @@ public class PdfModule extends ModuleBase { ******************************************************************/ private static final String NAME = "PDF-hul"; - private static final String RELEASE = "1.12.6"; - private static final int[] DATE = { 2024, 07, 31 }; + private static final String RELEASE = "1.12.7"; + private static final int[] DATE = { 2024, 8, 22 }; private static final String[] FORMAT = { "PDF", "Portable Document Format" }; private static final String COVERAGE = "PDF 1.0-1.6; " diff --git a/jhove-modules/pdf-hul/src/test/java/edu/harvard/hul/ois/jhove/module/pdf/LiteralTests.java b/jhove-modules/pdf-hul/src/test/java/edu/harvard/hul/ois/jhove/module/pdf/LiteralTests.java index 820ac0b19..930665db2 100644 --- a/jhove-modules/pdf-hul/src/test/java/edu/harvard/hul/ois/jhove/module/pdf/LiteralTests.java +++ b/jhove-modules/pdf-hul/src/test/java/edu/harvard/hul/ois/jhove/module/pdf/LiteralTests.java @@ -2,10 +2,6 @@ import static org.junit.Assert.assertNotNull; -import java.util.Calendar; -import java.util.Date; -import java.util.TimeZone; - import org.junit.Test; /** diff --git a/jhove-modules/pom.xml b/jhove-modules/pom.xml index cbdf843cd..af3133987 100644 --- a/jhove-modules/pom.xml +++ b/jhove-modules/pom.xml @@ -5,13 +5,13 @@ org.openpreservation.jhove jhove - 1.31.0-SNAPSHOT + 1.32.0-RC1 org.openpreservation.jhove.modules jhove-modules pom - 1.31.0-SNAPSHOT + 1.32.0-RC1 JHOVE Validation Modules The JHOVE HUL validation modules. @@ -19,7 +19,7 @@ org.openpreservation.jhove jhove-core - 1.31.0-SNAPSHOT + 1.32.0-RC1 org.junit.vintage diff --git a/jhove-modules/tiff-hul/pom.xml b/jhove-modules/tiff-hul/pom.xml index 34ae9f03d..dc4661644 100644 --- a/jhove-modules/tiff-hul/pom.xml +++ b/jhove-modules/tiff-hul/pom.xml @@ -3,10 +3,10 @@ org.openpreservation.jhove.modules jhove-modules - 1.31.0-SNAPSHOT + 1.32.0-RC1 tiff-hul - 1.9.4 + 1.9.5 JHOVE TIFF Module HUL TIFF module developed by Harvard University Library diff --git a/jhove-modules/tiff-hul/src/main/java/edu/harvard/hul/ois/jhove/module/TiffModule.java b/jhove-modules/tiff-hul/src/main/java/edu/harvard/hul/ois/jhove/module/TiffModule.java index 7d0d6318f..26c61c894 100644 --- a/jhove-modules/tiff-hul/src/main/java/edu/harvard/hul/ois/jhove/module/TiffModule.java +++ b/jhove-modules/tiff-hul/src/main/java/edu/harvard/hul/ois/jhove/module/TiffModule.java @@ -121,8 +121,8 @@ public class TiffModule extends ModuleBase { protected Logger _logger; private static final String NAME = "TIFF-hul"; - private static final String RELEASE = "1.9.4"; - private static final int[] DATE = { 2023, 03, 16 }; + private static final String RELEASE = "1.9.5"; + private static final int[] DATE = { 2024, 8, 22 }; 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, " @@ -1228,7 +1228,7 @@ protected IFD parseIFDChain(long next, RepInfo info, int type, ifd.setThumbnail(true); } list.add(ifd); - + if (list.size() > 50) { throw new TiffException(MessageConstants.TIFF_HUL_60); } diff --git a/jhove-modules/utf8-hul/pom.xml b/jhove-modules/utf8-hul/pom.xml index 3798fb6fd..568545ac3 100644 --- a/jhove-modules/utf8-hul/pom.xml +++ b/jhove-modules/utf8-hul/pom.xml @@ -3,7 +3,7 @@ org.openpreservation.jhove.modules jhove-modules - 1.31.0-SNAPSHOT + 1.32.0-RC1 utf8-hul 1.7.3 @@ -19,7 +19,7 @@ org.openpreservation.jhove.modules pdf-hul - 1.12.1 + 1.12.7 test diff --git a/jhove-modules/wave-hul/pom.xml b/jhove-modules/wave-hul/pom.xml index b03bf26cb..7dae61bda 100644 --- a/jhove-modules/wave-hul/pom.xml +++ b/jhove-modules/wave-hul/pom.xml @@ -3,7 +3,7 @@ org.openpreservation.jhove.modules jhove-modules - 1.31.0-SNAPSHOT + 1.32.0-RC1 wave-hul 1.8.3 diff --git a/jhove-modules/xml-hul/pom.xml b/jhove-modules/xml-hul/pom.xml index cf18c4cca..c074ddec0 100644 --- a/jhove-modules/xml-hul/pom.xml +++ b/jhove-modules/xml-hul/pom.xml @@ -3,10 +3,10 @@ org.openpreservation.jhove.modules jhove-modules - 1.31.0-SNAPSHOT + 1.32.0-RC1 xml-hul - 1.5.4 + 1.5.5 JHOVE XML Module HUL XML module developed by Harvard University Library diff --git a/jhove-modules/xml-hul/src/main/java/edu/harvard/hul/ois/jhove/module/XmlModule.java b/jhove-modules/xml-hul/src/main/java/edu/harvard/hul/ois/jhove/module/XmlModule.java index 374243814..1abf44cff 100644 --- a/jhove-modules/xml-hul/src/main/java/edu/harvard/hul/ois/jhove/module/XmlModule.java +++ b/jhove-modules/xml-hul/src/main/java/edu/harvard/hul/ois/jhove/module/XmlModule.java @@ -49,8 +49,8 @@ public class XmlModule extends ModuleBase { private static final String NAME = "XML-hul"; - private static final String RELEASE = "1.5.4"; - private static final int[] DATE = { 2024, 03, 05 }; + private static final String RELEASE = "1.5.5"; + private static final int[] DATE = { 2024, 8, 22 }; private static final String[] FORMAT = { "XML", "XHTML" }; private static final String COVERAGE = "XML 1.0"; private static final String[] MIMETYPE = { "text/xml", "application/xml", diff --git a/pom.xml b/pom.xml index f63ee57db..02ce407c8 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ org.openpreservation.jhove jhove - 1.31.0-SNAPSHOT + 1.32.0-RC1 pom JHOVE - JSTOR/Harvard Object Validation Environment From f27a819e5d4aa3489e97de49bd9d0ebd86a62ad4 Mon Sep 17 00:00:00 2001 From: Carl Wilson Date: Thu, 22 Aug 2024 14:08:47 +0100 Subject: [PATCH 26/26] FIX: Update version and HTTP links. --- docs/index.html | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/index.html b/docs/index.html index c0222260f..becc09713 100644 --- a/docs/index.html +++ b/docs/index.html @@ -11,17 +11,17 @@
JHOVE logo

Open source file format identification, validation & characterisation

- Download JHOVE + Download JHOVE

Software

-

Currently v1.28 19-05-2023

+

Currently v1.30.1 31-07-2024

Details of the latest release, including release notes can be found on GitHub.

JHOVE is a file format identification, validation and characterisation tool. It is implemented as a - Java + Java application and is usable on any Unix, Windows, or OS X platform with appropriate Java installation.

Supported Formats

@@ -156,21 +156,21 @@

License

JHOVE is made available by the - Open + Open Preservation Foundation under the GNU - Lesser General Public + Lesser General Public License (LGPL).

Note that some previous versions of JHOVE were released under the - GNU - General Public + GNU + General Public License (GPL).

Mailing list

- Subscribe to the JHOVE mailing list. + Subscribe to the JHOVE mailing list.