#21 Übergabe von Fehlerinformation in der API

This commit is contained in:
Andreas Penski (init) 2019-05-17 14:00:59 +02:00 committed by Andreas Penski
parent 8d49b0fea3
commit 3d777fa8e5
10 changed files with 289 additions and 86 deletions

View file

@ -1,11 +1,9 @@
package de.kosit.validationtool.api;
import java.util.List;
import org.w3c.dom.Document;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.sf.saxon.dom.NodeOverNodeInfo;
import net.sf.saxon.s9api.XdmNode;
/**
@ -13,32 +11,33 @@ import net.sf.saxon.s9api.XdmNode;
*
* @author Andreas Penski
*/
@Getter
@RequiredArgsConstructor
public class Result {
public interface Result {
/** Der generierte Report. */
private final XdmNode report;
XdmNode getReport();
/** Das evaluierte Ergebnis. */
private final AcceptRecommendation acceptRecommendation;
AcceptRecommendation getAcceptRecommendation();
/**
* Gibt den Report als W3C-{@link Document} zurück.
*
* @return der Report
*/
public Document getReportDocument() {
return (Document) NodeOverNodeInfo.wrap(getReport().getUnderlyingNode());
}
Document getReportDocument();
/**
* Schnellzugriff auf die Empfehlung zur Weiterverarbeitung des Dokuments.
*
* @return true wenn {@link AcceptRecommendation#ACCEPTABLE}
*/
public boolean isAcceptable() {
return AcceptRecommendation.ACCEPTABLE.equals(acceptRecommendation);
}
boolean isAcceptable();
/**
* Gibt eine Liste mit gefundenen Schema-Validation-Fehler zurück. Diese Liste ist leer, wenn keine Fehler gefunden
* wurden.
*/
List<XmlError> getSchemaViolations();
}

View file

@ -0,0 +1,43 @@
package de.kosit.validationtool.api;
/**
* Fehlerobjekt für die Bereitstellung von Fehlern aus der internen Verarbeitung, bspw. Schema-Validation-Fehler.
*
* @author Andreas Penski
*/
public interface XmlError {
/**
* Gibt die Fehlermeldung zurück.
*
* @return die Fehlermeldung
*/
String getMessage();
/**
* Zeigt den Schweregrad der Fehlermeldung an
*
* @return der Schweregrad
* @see Severity
*/
Severity getSeverity();
/**
* Gibt optional eine Zeilennummer an, aus der der Fehler resultiert.
*
* @return die Zeitelnnummer
*/
Integer getRowNumber();
/**
* Gibt optional eine Spaltennummer an, aus der der Fehler resultiert.
*
* @return die Spaltennummer
*/
Integer getColumnNumber();
enum Severity {
SEVERITY_WARNING, SEVERITY_ERROR, SEVERITY_FATAL_ERROR;
}
}

View file

@ -20,20 +20,17 @@
package de.kosit.validationtool.cmd;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import de.kosit.validationtool.impl.ContentRepository;
import de.kosit.validationtool.impl.HtmlExtraction;
import de.kosit.validationtool.impl.tasks.CheckAction;
import net.sf.saxon.s9api.QName;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.Serializer;
import net.sf.saxon.s9api.XPathExecutable;
import net.sf.saxon.s9api.XPathSelector;
import net.sf.saxon.s9api.XdmItem;
import net.sf.saxon.s9api.XdmNode;
@ -48,49 +45,38 @@ class ExtractHtmlContentAction implements CheckAction {
private static final QName NAME_ATTRIBUTE = new QName("data-report-type");
private final ContentRepository repository;
private final Path outputDirectory;
private XPathExecutable executable;
private HtmlExtraction htmlExtraction;
private ContentRepository repository;
public ExtractHtmlContentAction(final ContentRepository repository, final Path outputDirectory) {
this.outputDirectory = outputDirectory;
this.htmlExtraction = new HtmlExtraction(repository);
this.repository = repository;
}
@Override
public void check(Bag results) {
try {
final XPathSelector selector = getSelector();
final XdmNode xdmSource = results.getReport();
selector.setContextItem(xdmSource);
selector.forEach(m -> print(results.getName(), m));
} catch (SaxonApiException e) {
throw new IllegalStateException("Can not extract html content", e);
}
public void check(final Bag results) {
this.htmlExtraction.extract(results.getReport()).forEach(i -> print(results.getName(), i));
}
private void print(String origName, XdmItem xdmItem) {
XdmNode node = (XdmNode) xdmItem;
private void print(final String origName, final XdmItem xdmItem) {
final XdmNode node = (XdmNode) xdmItem;
final String name = origName + "-" + node.getAttributeValue(NAME_ATTRIBUTE);
final Path file = outputDirectory.resolve(name + ".html");
final Serializer serializer = repository.getProcessor().newSerializer(file.toFile());
final Path file = this.outputDirectory.resolve(name + ".html");
final Serializer serializer = this.repository.getProcessor().newSerializer(file.toFile());
try {
log.info("Writing report html '{}' to {}", name, file.toAbsolutePath());
serializer.serializeNode(node);
} catch (SaxonApiException e) {
} catch (final SaxonApiException e) {
log.info("Error extracting html content to {}", file.toAbsolutePath(), e);
}
}
private XPathSelector getSelector() {
if (executable == null) {
Map<String, String> ns = new HashMap<>();
ns.put("html", "http://www.w3.org/1999/xhtml");
executable = repository.createXPath("//html:html", ns);
}
return executable.load();
}
@Override
public boolean isSkipped(Bag results) {
public boolean isSkipped(final Bag results) {
if (results.getReport() == null) {
log.warn("Can not extract html content. No report document found");
return true;

View file

@ -51,26 +51,26 @@ public class CollectingErrorEventHandler implements ValidationEventHandler, Erro
private static final int DEFAULT_ABORT_COUNT = 50;
private Collection<XMLSyntaxError> errors = new ArrayList<>();
private final Collection<XMLSyntaxError> errors = new ArrayList<>();
private int stopProcessCount = DEFAULT_ABORT_COUNT;
private final int stopProcessCount = DEFAULT_ABORT_COUNT;
private static XMLSyntaxError createError(XMLSyntaxErrorSeverity severity, String message) {
XMLSyntaxError e = new XMLSyntaxError();
private static XMLSyntaxError createError(final XMLSyntaxErrorSeverity severity, final String message) {
final XMLSyntaxError e = new XMLSyntaxError();
e.setSeverity(severity);
e.setMessage(message);
return e;
}
private static XMLSyntaxError createError(XMLSyntaxErrorSeverity severity, SAXParseException exception) {
XMLSyntaxError e = createError(severity, exception.getMessage());
private static XMLSyntaxError createError(final XMLSyntaxErrorSeverity severity, final SAXParseException exception) {
final XMLSyntaxError e = createError(severity, exception.getMessage());
e.setRowNumber(exception.getLineNumber());
e.setColumnNumber(exception.getColumnNumber());
return e;
}
private static XMLSyntaxError createError(XMLSyntaxErrorSeverity severity, TransformerException exception) {
XMLSyntaxError e = createError(severity, exception.getMessage());
private static XMLSyntaxError createError(final XMLSyntaxErrorSeverity severity, final TransformerException exception) {
final XMLSyntaxError e = createError(severity, exception.getMessage());
if (exception.getLocator() != null) {
e.setRowNumber(exception.getLocator().getLineNumber());
e.setColumnNumber(exception.getLocator().getColumnNumber());
@ -78,7 +78,7 @@ public class CollectingErrorEventHandler implements ValidationEventHandler, Erro
return e;
}
private static XMLSyntaxErrorSeverity translateSeverity(int severity) {
private static XMLSyntaxErrorSeverity translateSeverity(final int severity) {
switch (severity) {
case ValidationEvent.WARNING:
return XMLSyntaxErrorSeverity.SEVERITY_WARNING;
@ -92,12 +92,12 @@ public class CollectingErrorEventHandler implements ValidationEventHandler, Erro
}
@Override
public boolean handleEvent(ValidationEvent event) {
XMLSyntaxError e = createError(translateSeverity(event.getSeverity()), event.getMessage());
public boolean handleEvent(final ValidationEvent event) {
final XMLSyntaxError e = createError(translateSeverity(event.getSeverity()), event.getMessage());
e.setColumnNumber(event.getLocator().getColumnNumber());
e.setRowNumber(event.getLocator().getLineNumber());
errors.add(e);
return stopProcessCount != errors.size();
this.errors.add(e);
return this.stopProcessCount != this.errors.size();
}
/**
@ -106,7 +106,7 @@ public class CollectingErrorEventHandler implements ValidationEventHandler, Erro
* @return true wenn mindestens ein Fehler vorhanden ist.
*/
public boolean hasErrors() {
return hasEvents() && errors.stream().anyMatch(e -> e.getSeverity() != XMLSyntaxErrorSeverity.SEVERITY_WARNING);
return hasEvents() && this.errors.stream().anyMatch(e -> e.getSeverityCode() != XMLSyntaxErrorSeverity.SEVERITY_WARNING);
}
/**
@ -115,27 +115,27 @@ public class CollectingErrorEventHandler implements ValidationEventHandler, Erro
* @return true wenn mindestens ein Validierungsereignis aufgetreten ist
*/
public boolean hasEvents() {
return !errors.isEmpty();
return !this.errors.isEmpty();
}
@Override
public void warning(SAXParseException exception) throws SAXException {
errors.add(createError(XMLSyntaxErrorSeverity.SEVERITY_WARNING, exception));
public void warning(final SAXParseException exception) throws SAXException {
this.errors.add(createError(XMLSyntaxErrorSeverity.SEVERITY_WARNING, exception));
}
@Override
public void error(SAXParseException exception) throws SAXException {
errors.add(createError(XMLSyntaxErrorSeverity.SEVERITY_ERROR, exception));
public void error(final SAXParseException exception) throws SAXException {
this.errors.add(createError(XMLSyntaxErrorSeverity.SEVERITY_ERROR, exception));
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
errors.add(createError(XMLSyntaxErrorSeverity.SEVERITY_FATAL_ERROR, exception));
public void fatalError(final SAXParseException exception) throws SAXException {
this.errors.add(createError(XMLSyntaxErrorSeverity.SEVERITY_FATAL_ERROR, exception));
}
@Override
public void message(XdmNode content, boolean terminate, SourceLocator locator) {
XMLSyntaxError e = new XMLSyntaxError();
public void message(final XdmNode content, final boolean terminate, final SourceLocator locator) {
final XMLSyntaxError e = new XMLSyntaxError();
if (locator != null) {
e.setColumnNumber(locator.getColumnNumber());
e.setRowNumber(locator.getLineNumber());
@ -145,24 +145,25 @@ public class CollectingErrorEventHandler implements ValidationEventHandler, Erro
}
@Override
public void warning(TransformerException exception) throws TransformerException {
errors.add(createError(XMLSyntaxErrorSeverity.SEVERITY_WARNING, exception));
public void warning(final TransformerException exception) throws TransformerException {
this.errors.add(createError(XMLSyntaxErrorSeverity.SEVERITY_WARNING, exception));
}
@Override
public void error(TransformerException exception) throws TransformerException {
errors.add(createError(XMLSyntaxErrorSeverity.SEVERITY_ERROR, exception));
public void error(final TransformerException exception) throws TransformerException {
this.errors.add(createError(XMLSyntaxErrorSeverity.SEVERITY_ERROR, exception));
}
@Override
public void fatalError(TransformerException exception) throws TransformerException {
errors.add(createError(XMLSyntaxErrorSeverity.SEVERITY_FATAL_ERROR, exception));
public void fatalError(final TransformerException exception) throws TransformerException {
this.errors.add(createError(XMLSyntaxErrorSeverity.SEVERITY_FATAL_ERROR, exception));
}
public String getErrorDescription() {
final StringJoiner joiner = new StringJoiner("\n");
errors.forEach(e -> joiner
.add(e.getSeverity().value() + " " + e.getMessage() + " At row " + e.getRowNumber() + " at pos " + e.getColumnNumber()));
this.errors.forEach(e -> joiner
.add(e.getSeverityCode().value() + " " + e.getMessage() + " At row " + e.getRowNumber() + " at pos "
+ e.getColumnNumber()));
return joiner.toString();
}
}

View file

@ -20,6 +20,7 @@
package de.kosit.validationtool.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import lombok.Getter;
@ -29,7 +30,9 @@ import de.kosit.validationtool.api.Check;
import de.kosit.validationtool.api.CheckConfiguration;
import de.kosit.validationtool.api.Input;
import de.kosit.validationtool.api.Result;
import de.kosit.validationtool.api.XmlError;
import de.kosit.validationtool.impl.tasks.CheckAction;
import de.kosit.validationtool.impl.tasks.CheckAction.Bag;
import de.kosit.validationtool.impl.tasks.ComputeAcceptanceAction;
import de.kosit.validationtool.impl.tasks.CreateReportAction;
import de.kosit.validationtool.impl.tasks.DocumentParseAction;
@ -41,6 +44,7 @@ import de.kosit.validationtool.model.reportInput.CreateReportInput;
import de.kosit.validationtool.model.reportInput.DocumentIdentificationType;
import de.kosit.validationtool.model.reportInput.EngineType;
import de.kosit.validationtool.model.reportInput.ProcessingError;
import de.kosit.validationtool.model.reportInput.XMLSyntaxError;
import net.sf.saxon.s9api.Processor;
@ -125,7 +129,20 @@ public class DefaultCheck implements Check {
}
t.setFinished(true);
log.info("Finished check of {} in {}ms\n", t.getInput().getName(), System.currentTimeMillis() - started);
return new Result(t.getReport(), t.getAcceptStatus());
return createResult(t);
}
private Result createResult(final Bag t) {
final DefaultResult result = new DefaultResult(t.getReport(), t.getAcceptStatus(), this.contentRepository);
if (t.getSchemaValidationResult() != null) {
result.setSchemaViolations(convertErrors(t.getSchemaValidationResult().getErrors()));
}
return result;
}
private static List<XmlError> convertErrors(final Collection<XMLSyntaxError> errors) {
// noinspection unchecked
return (List<XmlError>) (List<?>) errors;
}
private static void createDocumentIdentification(final CheckAction.Bag transporter) {

View file

@ -0,0 +1,97 @@
package de.kosit.validationtool.impl;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import de.kosit.validationtool.api.AcceptRecommendation;
import de.kosit.validationtool.api.Result;
import de.kosit.validationtool.api.XmlError;
import net.sf.saxon.dom.NodeOverNodeInfo;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.Serializer;
import net.sf.saxon.s9api.XdmNode;
/**
* @author Andreas Penski
*/
public class DefaultResult implements Result {
/** Der generierte Report. */
@Getter
private final XdmNode report;
/** Das evaluierte Ergebnis. */
@Getter
private final AcceptRecommendation acceptRecommendation;
private final HtmlExtraction htmlExtraction;
@Setter(AccessLevel.PACKAGE)
@Getter
private List<XmlError> schemaViolations = new ArrayList<>();
public DefaultResult(final XdmNode report, final AcceptRecommendation recommendation, final ContentRepository repository) {
this.report = report;
this.acceptRecommendation = recommendation;
this.htmlExtraction = new HtmlExtraction(repository);
}
/**
* Gibt den Report als W3C-{@link Document} zurück.
*
* @return der Report
*/
@Override
public Document getReportDocument() {
return (Document) NodeOverNodeInfo.wrap(getReport().getUnderlyingNode());
}
/**
* Schnellzugriff auf die Empfehlung zur Weiterverarbeitung des Dokuments.
*
* @return true wenn {@link AcceptRecommendation#ACCEPTABLE}
*/
@Override
public boolean isAcceptable() {
return AcceptRecommendation.ACCEPTABLE.equals(this.acceptRecommendation);
}
public List<String> extractHtmlAsString() {
return extractHtml().stream().map(this::convertToString).collect(Collectors.toList());
}
private String convertToString(final XdmNode element) {
try {
final StringWriter writer = new StringWriter();
final Serializer serializer = ObjectFactory.createProcessor().newSerializer(writer);
serializer.serializeNode(element);
return writer.toString();
} catch (SaxonApiException e) {
throw new IllegalStateException("Can not convert to string", e);
}
}
public List<Element> extractHtmlAsElement() {
return extractHtml().stream().map(DefaultResult::convertToElement).collect(Collectors.toList());
}
private static Element convertToElement(final XdmNode xdmItem) {
return (Element) NodeOverNodeInfo.wrap(xdmItem.getUnderlyingNode());
}
public List<XdmNode> extractHtml() {
return this.htmlExtraction.extract(getReport());
}
}

View file

@ -0,0 +1,49 @@
package de.kosit.validationtool.impl;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.XPathExecutable;
import net.sf.saxon.s9api.XPathSelector;
import net.sf.saxon.s9api.XdmItem;
import net.sf.saxon.s9api.XdmNode;
/**
* @author Andreas Penski
*/
@RequiredArgsConstructor
public class HtmlExtraction {
private final ContentRepository repository;
private XPathExecutable executable;
public List<XdmNode> extract(XdmNode xdmSource) {
try {
final XPathSelector selector = getSelector();
selector.setContextItem(xdmSource);
return selector.stream().map(this::castToNode).collect(Collectors.toList());
} catch (SaxonApiException e) {
throw new IllegalStateException("Can not extract html content", e);
}
}
private XdmNode castToNode(final XdmItem xdmItem) {
return (XdmNode) xdmItem;
}
private XPathSelector getSelector() {
if (executable == null) {
Map<String, String> ns = new HashMap<>();
ns.put("html", "http://www.w3.org/1999/xhtml");
executable = repository.createXPath("//html:html", ns);
}
return executable.load();
}
}

View file

@ -21,6 +21,7 @@ package de.kosit.validationtool.impl.model;
import org.slf4j.Logger;
import de.kosit.validationtool.api.XmlError;
import de.kosit.validationtool.model.reportInput.XMLSyntaxErrorSeverity;
/**
@ -29,17 +30,17 @@ import de.kosit.validationtool.model.reportInput.XMLSyntaxErrorSeverity;
*
* @author Andreas Penski
*/
public abstract class BaseXMLSyntaxError {
public abstract class BaseXMLSyntaxError implements XmlError {
/**
* Logged den Syntax-Fehler über einen definierten Logger.
*
* @param logger der Logger
*/
public void log(Logger logger) {
String msgTemplate = "{} At row {} at pos {}";
Object[] params = { getMessage(), getRowNumber(), getColumnNumber() };
if (getSeverity() == XMLSyntaxErrorSeverity.SEVERITY_WARNING) {
public void log(final Logger logger) {
final String msgTemplate = "{} At row {} at pos {}";
final Object[] params = { getMessage(), getRowNumber(), getColumnNumber() };
if (getSeverityCode() == XMLSyntaxErrorSeverity.SEVERITY_WARNING) {
logger.warn(msgTemplate, params);
} else {
logger.error(msgTemplate, params);
@ -57,6 +58,7 @@ public abstract class BaseXMLSyntaxError {
*
* @return Spalte des Fehlers
*/
@Override
public abstract Integer getColumnNumber();
/**
@ -64,6 +66,7 @@ public abstract class BaseXMLSyntaxError {
*
* @return Zeile des Fehlers
*/
@Override
public abstract Integer getRowNumber();
/**
@ -71,6 +74,7 @@ public abstract class BaseXMLSyntaxError {
*
* @return Fehlermeldung
*/
@Override
public abstract String getMessage();
/**
@ -78,5 +82,11 @@ public abstract class BaseXMLSyntaxError {
*
* @return severity
*/
public abstract XMLSyntaxErrorSeverity getSeverity();
public abstract XMLSyntaxErrorSeverity getSeverityCode();
@Override
public Severity getSeverity() {
final XMLSyntaxErrorSeverity code = getSeverityCode();
return code != null ? Severity.valueOf(code.name()) : null;
}
}

View file

@ -68,7 +68,7 @@
<xs:complexType name="XMLSyntaxError">
<xs:sequence>
<xs:element name="message" type="xs:normalizedString"/>
<xs:element name="severity" type="in:XMLSyntaxErrorSeverity"/>
<xs:element name="getSeverityCode" type="in:XMLSyntaxErrorSeverity" />
<xs:element name="rowNumber" type="xs:int" minOccurs="0"/>
<xs:element name="columnNumber" type="xs:int" minOccurs="0"/>
</xs:sequence>

View file

@ -42,6 +42,7 @@ public class SimpleScenarioCheck {
final Result result = this.implementation.checkInput(InputFactory.read(Simple.INVALID.toURL()));
assertThat(result).isNotNull();
assertThat(result.getAcceptRecommendation()).isEqualTo(AcceptRecommendation.REJECT);
assertThat(result.getSchemaViolations()).isNotEmpty();
}
@Test