Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions odf-apps/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
<artifactId>odf-apps</artifactId>
<packaging>jar</packaging>

<name>ODF spreadsheet validator applications.</name>
<description>Applications for open source validation of Open Document Format spreadsheets.</description>
<name>ODF Document validator applications.</name>
<description>Applications for open source validation of Open Document Format documents.</description>

<properties>
<picocli.version>4.7.0</picocli.version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import javax.xml.parsers.ParserConfigurationException;

import org.openpreservation.odf.fmt.Formats;
import org.openpreservation.odf.pkg.PackageParser.ParseException;
import org.openpreservation.odf.validation.Check;
import org.openpreservation.odf.validation.OdfValidator;
Expand All @@ -30,28 +31,49 @@
import com.fasterxml.jackson.core.JsonProcessingException;

import picocli.CommandLine;
import picocli.CommandLine.ArgGroup;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;

@Command(name = "odf-validator", mixinStandardHelpOptions = true, versionProvider = BuildVersionProvider.class, description = "Validates Open Document Format spreadsheets.")
@Command(name = "odf-validator",
mixinStandardHelpOptions = true,
versionProvider = BuildVersionProvider.class,
sortOptions = false,
headerHeading = "Usage:%n",
synopsisHeading = "%n",
descriptionHeading = "%nDescription:%n%n",
parameterListHeading = "%nParameters:%n",
optionListHeading = "Options:%n",
header = "Validates Open Document Format documents.",
description = "Validates Open Document Format documents against the appropriate ODF specifications. May optionally include additional preservation checks.")
class CliValidator implements Callable<Integer> {
private static final MessageFactory FACTORY = Messages
.getInstance("org.openpreservation.odf.apps.messages.Messages");

@Option(names = { "-p", "--profile" }, description = "Validate using additional Spreadsheet preservation profile.")
private boolean profileFlag;
@Parameters(paramLabel = "FILE", arity = "1..*", description = "A list of Open Document Format document files to validate.")
private File[] toValidateFiles;
@Option(names = { "-e", "--extended" }, description = "Process XML documents to allow for extended document validation.")
private boolean extendedFlag;

@ArgGroup(exclusive = true, multiplicity = "0..1", heading = "%nFormat Options (mutually exclusive):%n")
FormatOptions formatOptions = new CliValidator.FormatOptions();

static class FormatOptions {
@Option(names = { "-f", "--format" }, description = {"Validate a particular ODF format only", "This value can be an extension, a MIME type, or a document element name, e.g. spreadsheet."}, defaultValue = "UNKNOWN", converter = FormatsConverter.class)
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Grammatical issue: "a document element name" should be "a document element name" - the example "e.g. spreadsheet" should use "e.g., spreadsheet" (with comma after "e.g.") for proper grammatical style.

Suggested change
@Option(names = { "-f", "--format" }, description = {"Validate a particular ODF format only", "This value can be an extension, a MIME type, or a document element name, e.g. spreadsheet."}, defaultValue = "UNKNOWN", converter = FormatsConverter.class)
@Option(names = { "-f", "--format" }, description = {"Validate a particular ODF format only", "This value can be an extension, a MIME type, or a document element name, e.g., spreadsheet."}, defaultValue = "UNKNOWN", converter = FormatsConverter.class)

Copilot uses AI. Check for mistakes.
private Formats format = Formats.UNKNOWN;
@Option(names = { "-p", "--profile" }, description = {"Validate using additional Spreadsheet preservation profile.", "Cannot be used with the format option as Spreadsheet is forced by the profile."})
private boolean profileFlag;
}

@Option(names = { "-o", "--output" }, description = "Output results as TEXT, JSON or XML.", defaultValue = "TEXT")
private ValidationReports.FormatOption outputFormat = ValidationReports.FormatOption.TEXT;
@Option(names = { "-d", "--debug" }, description = "Enable debug output.")
private boolean debugFlag;
@Option(names = { "-v" }, description = { "Specify multiple -v options to increase verbosity.",
"For example, `-v -v -v` or `-vvv`"})
private boolean[] verbosity;
@Parameters(paramLabel = "FILE", arity = "1..*", description = "A list of Open Document Format spreadsheet files to validate.")
private File[] toValidateFiles;
@Option(names = { "--format" }, description = "Output results as TEXT, JSON or XML.", defaultValue = "TEXT")
private ValidationReports.FormatOption format = ValidationReports.FormatOption.TEXT;

private OdfValidator validator;
private MessageLog appMessages = Messages.messageLogInstance();

Expand All @@ -65,9 +87,9 @@ public Integer call() throws JsonProcessingException {
this.appMessages = Messages.messageLogInstance();
ConsoleFormatter.colourise(FACTORY.getInfo("APP-1", Messages.parameterListInstance().add("file", toValidate.toString())));
try {
ValidationReport validationResult = (!this.profileFlag) ? validatePath(toValidate) : profilePath(toValidate);
ValidationReport validationResult = (!this.formatOptions.profileFlag) ? validatePath(toValidate) : profilePath(toValidate);
if (validationResult != null) {
retStatus = outputValidationReport(toValidate, validationResult, this.format);
retStatus = outputValidationReport(toValidate, validationResult, this.outputFormat);
}
} catch (Throwable t) {
ConsoleFormatter.colourise(toValidate, FACTORY.getFatal("SYS-4", Messages.parameterListInstance().add("file", toValidate.toString())));
Expand All @@ -86,7 +108,7 @@ public Integer call() throws JsonProcessingException {

private ValidationReport validatePath(final Path toValidate) {
try {
return validator.validate(toValidate);
return (this.formatOptions.format != Formats.UNKNOWN) ? validator.validate(toValidate, this.formatOptions.format) : validator.validate(toValidate);
} catch (IllegalArgumentException | FileNotFoundException e) {
this.logMessage(toValidate, Messages.getMessageInstance("APP-2", Severity.ERROR, e.getMessage()));
e.printStackTrace();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.openpreservation.odf.apps;

import org.openpreservation.odf.fmt.Formats;
import org.openpreservation.odf.xml.DocumentType;

import picocli.CommandLine.ITypeConverter;

Comment thread
carlwilson marked this conversation as resolved.
public class FormatsConverter implements ITypeConverter<Formats> {
@Override
public Formats convert(final String value) throws Exception {
Comment thread
carlwilson marked this conversation as resolved.
final String search = value.toLowerCase().trim();
Formats format = Formats.fromExtension(search);
if (format != Formats.UNKNOWN) {
return format;
}
format = Formats.fromMime(search);
if (format != Formats.UNKNOWN) {
return format;
}
DocumentType dt = null;
try {
dt = DocumentType.valueOf(search.toUpperCase());
} catch (final IllegalArgumentException e) {
Comment thread
carlwilson marked this conversation as resolved.
/**
* Simply means that the document type was not found by name, continue searching.
*/
}
if (dt != null) {
return getFirstFormatFromDocumentType(dt);
}
dt = DocumentType.getTypeByBodyElement(search);
if (dt != null) {
return getFirstFormatFromDocumentType(dt);
}
dt = DocumentType.getTypeByMimeString(search);
if (dt != null) {
Comment thread
carlwilson marked this conversation as resolved.
return getFirstFormatFromDocumentType(dt);
}
return Formats.UNKNOWN;
}

final static private Formats getFirstFormatFromDocumentType(final DocumentType dt) {
Comment thread
carlwilson marked this conversation as resolved.
// NOTE: If the DocumentType has multiple associated formats, this will select the first format in the set,
// which may not align with user expectations. To ensure a specific format is chosen, specify the format
// extension or MIME type directly instead of the document type name.
return dt.getFormats().iterator().next();
}
}
4 changes: 2 additions & 2 deletions odf-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
<artifactId>odf-core</artifactId>
<packaging>jar</packaging>

<name>ODF spreadsheet validator core library.</name>
<description>API and library for open source validation of Open Document Format spreadsheets.</description>
<name>ODF document validator core library.</name>
<description>API and library for open source validation of Open Document Format documents.</description>

<dependencies>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-compress -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,17 @@

import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.validation.Schema;

import org.openpreservation.format.xml.ParseResult;
import org.openpreservation.format.xml.XmlParser;
import org.openpreservation.format.xml.XmlParsers;
import org.openpreservation.format.xml.XmlValidator;
import org.openpreservation.odf.Source;
import org.openpreservation.odf.document.Documents;
import org.openpreservation.odf.document.OpenDocument;
Expand All @@ -24,15 +21,9 @@
import org.openpreservation.odf.pkg.OdfPackages;
import org.openpreservation.odf.pkg.PackageParser.ParseException;
import org.openpreservation.odf.validation.messages.Message;
import org.openpreservation.odf.validation.messages.Message.Severity;
import org.openpreservation.odf.validation.messages.MessageFactory;
import org.openpreservation.odf.validation.messages.Messages;
import org.openpreservation.odf.validation.messages.Parameter.ParameterList;
import org.openpreservation.odf.xml.OdfNamespaces;
import org.openpreservation.odf.xml.OdfSchemaFactory;
import org.openpreservation.odf.xml.OdfXmlDocument;
import org.openpreservation.odf.xml.OdfXmlDocuments;
import org.openpreservation.odf.xml.Version;
import org.openpreservation.utils.Checks;
import org.xml.sax.SAXException;

Expand Down
4 changes: 2 additions & 2 deletions odf-reporting/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
<artifactId>odf-reporting</artifactId>
<packaging>jar</packaging>

<name>ODF spreadsheet validator core library.</name>
<description>API and library for open source validation of Open Document Format spreadsheets.</description>
<name>ODF document validator project reporting.</name>
<description>Project reporting for open source validation of Open Document Format documents.</description>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
Expand Down
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
<module>odf-reporting</module>
</modules>

<name>ODF spreadsheet validator.</name>
<description>Open source validation of Open Document Format spreadsheets.</description>
<name>ODF Document validator.</name>
<description>Open source validation of Open Document Format documents.</description>
<url>https://github.com/openpreserve/odf-validator/</url>
<inceptionYear>2022</inceptionYear>
<organization>
Expand Down