(chore) Rückgabe der Schematron-Ergebnisse als komplexes Objekt in der API

This commit is contained in:
Andreas Penski (init) 2019-05-24 11:32:08 +02:00
parent b3e9d3a244
commit 2069c0d04e
11 changed files with 354 additions and 59 deletions

View file

@ -2,6 +2,7 @@ package de.kosit.validationtool.api;
import java.util.List;
import org.oclc.purl.dsdl.svrl.SchematronOutput;
import org.w3c.dom.Document;
import net.sf.saxon.s9api.XdmNode;
@ -40,8 +41,11 @@ public interface Result {
*/
List<XmlError> getSchemaViolations();
// TODO scheitert momentan daran, das intern kein svlr o.ä. zur Verfügung steht
// List<XmlError> getSchematronResult();
/**
* Liefert die Ergebnisse der Schematron-Prüfungen, in der Reihenfolge der Szenario-Konfiguration.
*
* @return Liste mit Schematron-Ergebnissen
*/
List<SchematronOutput> getSchematronResult();
}

View file

@ -27,14 +27,26 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.StringJoiner;
import javax.xml.bind.*;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.JAXBIntrospector;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.ValidationEventHandler;
import javax.xml.bind.annotation.XmlRegistry;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.stream.*;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import org.apache.commons.lang3.StringUtils;
import org.w3c.dom.Document;
import lombok.extern.slf4j.Slf4j;
@ -45,6 +57,32 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ConversionService {
/**
* Exception while serializing/deserializing with jaxb.
*/
public class ConversionExeption extends RuntimeException {
/**
* Constructor.
*
* @param message the message.
* @param cause the cause
*/
public ConversionExeption(final String message, final Exception cause) {
super(message, cause);
}
/**
* Constructor.
*
* @param message the message.
*/
public ConversionExeption(final String message) {
super(message);
}
}
private static final int MAX_LOG_CONTENT = 50;
// context setup
private JAXBContext jaxbContext;
@ -68,7 +106,7 @@ public class ConversionService {
* Initialisiert den default context; Alle Packages mit {@link XmlRegistry XmlRegistries}.
*/
public void initialize() {
Collection<Package> p = new ArrayList<>();
final Collection<Package> p = new ArrayList<>();
p.add(de.kosit.validationtool.model.reportInput.ObjectFactory.class.getPackage());
p.add(de.kosit.validationtool.model.scenarios.ObjectFactory.class.getPackage());
initialize(p);
@ -77,6 +115,7 @@ public class ConversionService {
public void initialize(final Package... context) {
initialize(Arrays.asList(context));
}
/**
* Initialisiert den conversion service mit den angegegebenen Packages.
*
@ -84,7 +123,7 @@ public class ConversionService {
*/
public void initialize(final Collection<Package> context) {
final String[] packages = context != null ? context.stream().map(Package::getName).toArray(String[]::new) : new String[0];
StringJoiner joiner = new StringJoiner(":");
final StringJoiner joiner = new StringJoiner(":");
Arrays.stream(packages).forEach(p -> joiner.add(p));
initialize(joiner.toString());
}
@ -121,11 +160,11 @@ public class ConversionService {
return readXml(xml, type, null, null);
}
public <T> T readXml(final URI xml, final Class<T> type, Schema schema) {
public <T> T readXml(final URI xml, final Class<T> type, final Schema schema) {
return readXml(xml, type, schema, null);
}
public <T> T readXml(final URI xml, final Class<T> type, Schema schema, ValidationEventHandler handler) {
public <T> T readXml(final URI xml, final Class<T> type, final Schema schema, final ValidationEventHandler handler) {
checkInputEmpty(xml);
checkTypeEmpty(type);
CollectingErrorEventHandler defaultHandler = null;
@ -167,11 +206,11 @@ public class ConversionService {
return writeXml(model, null, null);
}
public <T> String writeXml(final T model, Schema schema, ValidationEventHandler handler) {
public <T> String writeXml(final T model, final Schema schema, final ValidationEventHandler handler) {
if (model == null) {
throw new ConversionExeption("Can not serialize null");
}
try ( StringWriter w = new StringWriter() ) {
try ( final StringWriter w = new StringWriter() ) {
final JAXBIntrospector introspector = getJaxbContext().createJAXBIntrospector();
final Marshaller marshaller = getJaxbContext().createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
@ -189,17 +228,17 @@ public class ConversionService {
}
xmlStreamWriter.flush();
return w.toString();
} catch (JAXBException | IOException | XMLStreamException e) {
} catch (final JAXBException | IOException | XMLStreamException e) {
throw new ConversionExeption(String.format("Error serializing Object %s", model.getClass().getName()), e);
}
}
public <T> Document writeDocument(T input) {
public <T> Document writeDocument(final T input) {
if (input == null) {
throw new ConversionExeption("Can not serialize null");
}
DocumentBuilder builder = ObjectFactory.createDocumentBuilder(false);
Document document = builder.newDocument();
final DocumentBuilder builder = ObjectFactory.createDocumentBuilder(false);
final Document document = builder.newDocument();
// Marshal the Object to a Document
Marshaller marshaller = null;
@ -208,34 +247,20 @@ public class ConversionService {
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(input, document);
return document;
} catch (JAXBException e) {
} catch (final JAXBException e) {
throw new ConversionExeption(String.format("Error serializing Object %s to document", input.getClass().getName()), e);
}
}
/**
* Exception while serializing/deserializing with jaxb.
*/
public class ConversionExeption extends RuntimeException {
public <T> T readDocument(final Source source, final Class<T> type) {
try {
final Unmarshaller u = getJaxbContext().createUnmarshaller();
/**
* Constructor.
*
* @param message the message.
* @param cause the cause
*/
public ConversionExeption(final String message, final Exception cause) {
super(message, cause);
}
return u.unmarshal(source, type).getValue();
/**
* Constructor.
*
* @param message the message.
*/
public ConversionExeption(final String message) {
super(message);
} catch (final JAXBException e) {
throw new ConversionExeption(String.format("Can not unmarshal to type %s: %s", type.getSimpleName(),
StringUtils.abbreviate(source.getSystemId(), MAX_LOG_CONTENT)), e);
}
}
}

View file

@ -22,6 +22,7 @@ package de.kosit.validationtool.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@ -88,7 +89,7 @@ public class DefaultCheck implements Check {
this.checkSteps.add(new DocumentParseAction());
this.checkSteps.add(new ScenarioSelectionAction(this.repository));
this.checkSteps.add(new SchemaValidationAction());
this.checkSteps.add(new SchematronValidationAction(configuration.getScenarioRepository()));
this.checkSteps.add(new SchematronValidationAction(configuration.getScenarioRepository(), this.conversionService));
this.checkSteps.add(new ValidateReportInputAction(this.conversionService, this.contentRepository.getReportInputSchema()));
this.checkSteps
.add(new CreateReportAction(processor, this.conversionService, this.repository, configuration.getScenarioRepository()));
@ -137,6 +138,8 @@ public class DefaultCheck implements Check {
if (t.getSchemaValidationResult() != null) {
result.setSchemaViolations(convertErrors(t.getSchemaValidationResult().getErrors()));
}
result.setSchematronResult(t.getReportInput().getValidationResultsSchematron().stream()
.map(e -> e.getResults().getSchematronOutput()).collect(Collectors.toList()));
return result;
}

View file

@ -5,6 +5,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.oclc.purl.dsdl.svrl.SchematronOutput;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@ -41,6 +42,10 @@ public class DefaultResult implements Result {
@Getter
private List<XmlError> schemaViolations = new ArrayList<>();
@Getter
@Setter(AccessLevel.PACKAGE)
private List<SchematronOutput> schematronResult;
public DefaultResult(final XdmNode report, final AcceptRecommendation recommendation, final ContentRepository repository) {
this.report = report;
this.acceptRecommendation = recommendation;
@ -67,17 +72,18 @@ public class DefaultResult implements Result {
return AcceptRecommendation.ACCEPTABLE.equals(this.acceptRecommendation);
}
public List<String> extractHtmlAsString() {
return extractHtml().stream().map(this::convertToString).collect(Collectors.toList());
return extractHtml().stream().map(DefaultResult::convertToString).collect(Collectors.toList());
}
private String convertToString(final XdmNode element) {
private static 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) {
} catch (final SaxonApiException e) {
throw new IllegalStateException("Can not convert to string", e);
}
}

View file

@ -0,0 +1,72 @@
package de.kosit.validationtool.impl.model;
import java.io.Serializable;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.oclc.purl.dsdl.svrl.ActivePattern;
import org.oclc.purl.dsdl.svrl.FailedAssert;
import org.oclc.purl.dsdl.svrl.FiredRule;
/**
* Basis-Klasse um spezifische Erweiterungen an der generierten Klasse {@link org.oclc.purl.dsdl.svrl.SchematronOutput}
* umzusetzen.
*
* @author Andreas Penski
*/
public abstract class BaseOutput {
public abstract List<Serializable> getActivePatternAndFiredRuleAndFailedAssert();
/**
* Gibt die Liste der {@link FailedAssert} zurück
*
* @return Liste mit {@link FailedAssert}
*/
public List<FailedAssert> getFailedAsserts() {
return filter(FailedAssert.class);
}
/**
* Gibt die Liste der {@link FailedAssert} zurück
*
* @return Liste mit {@link FailedAssert}
*/
public List<FiredRule> getFiredRules() {
return filter(FiredRule.class);
}
/**
* Ermittelt, ob es bei der Validierung {@link FailedAssert}s gab.
*
* @return true wenn mindestens ein {@link FailedAssert} vorhanden ist
*/
public boolean hasFailedAsserts() {
return getFailedAsserts().size() > 0;
}
/**
* Gibt die Liste der {@link ActivePattern} zurück
*
* @return Liste mit {@link ActivePattern}
*/
public List<ActivePattern> getActivePatterns() {
return filter(ActivePattern.class);
}
private <T> List<T> filter(final Class<T> type) {
return getActivePatternAndFiredRuleAndFailedAssert().stream().filter(type::isInstance).map(type::cast).collect(Collectors.toList());
}
/**
* Sucht nach einem {@link FailedAssert} mit einem definierten Namen.
*
* @param name der Name
* @return Optional mit dem {@link FailedAssert}
*/
public Optional<FailedAssert> findFailedAssert(final String name) {
return getFailedAsserts().stream().filter(e -> e.getId().equals(name)).findAny();
}
}

View file

@ -23,11 +23,15 @@ import java.net.URI;
import java.util.List;
import java.util.stream.Collectors;
import javax.xml.transform.dom.DOMSource;
import org.oclc.purl.dsdl.svrl.SchematronOutput;
import org.w3c.dom.Document;
import lombok.RequiredArgsConstructor;
import de.kosit.validationtool.impl.CollectingErrorEventHandler;
import de.kosit.validationtool.impl.ConversionService;
import de.kosit.validationtool.impl.ObjectFactory;
import de.kosit.validationtool.impl.RelativeUriResolver;
import de.kosit.validationtool.impl.model.BaseScenario;
@ -50,37 +54,39 @@ public class SchematronValidationAction implements CheckAction {
private final URI repository;
private List<ValidationResultsSchematron> validate(XdmNode document, ScenarioType scenario) {
private final ConversionService conversionService;
private List<ValidationResultsSchematron> validate(final XdmNode document, final ScenarioType scenario) {
return scenario.getSchematronValidations().stream().map(v -> validate(document, v)).collect(Collectors.toList());
}
private ValidationResultsSchematron validate(XdmNode document, BaseScenario.Transformation validation) {
private ValidationResultsSchematron validate(final XdmNode document, final BaseScenario.Transformation validation) {
try {
final XsltTransformer transformer = validation.getExecutable().load();
// resolving nur relative zum Repository
final RelativeUriResolver resolver = new RelativeUriResolver(repository);
final RelativeUriResolver resolver = new RelativeUriResolver(this.repository);
transformer.setURIResolver(resolver);
CollectingErrorEventHandler e = new CollectingErrorEventHandler();
final CollectingErrorEventHandler e = new CollectingErrorEventHandler();
transformer.setMessageListener(e);
Document result = ObjectFactory.createDocumentBuilder(false).newDocument();
final Document result = ObjectFactory.createDocumentBuilder(false).newDocument();
transformer.setDestination(new DOMDestination(result));
transformer.setInitialContextNode(document);
transformer.transform();
ValidationResultsSchematron s = new ValidationResultsSchematron();
final ValidationResultsSchematron s = new ValidationResultsSchematron();
s.setResource(validation.getResourceType());
ValidationResultsSchematron.Results r = new ValidationResultsSchematron.Results();
r.setAny(result.getDocumentElement());
final ValidationResultsSchematron.Results r = new ValidationResultsSchematron.Results();
r.setSchematronOutput(this.conversionService.readDocument(new DOMSource(result), SchematronOutput.class));
s.setResults(r);
return s;
} catch (SaxonApiException e) {
} catch (final SaxonApiException e) {
throw new IllegalStateException("Can not run schematron validation", e);
}
}
@Override
public void check(Bag results) {
public void check(final Bag results) {
final CreateReportInput report = results.getReportInput();
final List<ValidationResultsSchematron> validationResult = validate(results.getParserResult().getObject(),
results.getScenarioSelectionResult().getObject());
@ -88,7 +94,7 @@ public class SchematronValidationAction implements CheckAction {
}
@Override
public boolean isSkipped(Bag results) {
public boolean isSkipped(final Bag results) {
return results.getSchemaValidationResult() == null || results.getSchemaValidationResult().isInvalid();
}
}

View file

@ -45,7 +45,7 @@ public class ValidateReportInputAction implements CheckAction {
private final Schema schema;
@Override
public void check(Bag bag) {
public void check(final Bag bag) {
final Result<Boolean, XMLSyntaxError> results = validate(bag.getReportInput());
if (!results.isValid()) {
log.error("Report input has errors {}", results.getErrors());
@ -60,9 +60,9 @@ public class ValidateReportInputAction implements CheckAction {
* @param <T> der Typ des Objekts
* @return ein Validierungsergebnis
*/
private <T> Result<Boolean, XMLSyntaxError> validate(T object) {
CollectingErrorEventHandler h = new CollectingErrorEventHandler();
final String result = conversionService.writeXml(object, schema, h);
private <T> Result<Boolean, XMLSyntaxError> validate(final T object) {
final CollectingErrorEventHandler h = new CollectingErrorEventHandler();
final String result = this.conversionService.writeXml(object, this.schema, h);
return new Result<>(StringUtils.isNotBlank(result), h.getErrors());
}
}

View file

@ -52,4 +52,11 @@
</jaxb:schemaBindings>
</jaxb:bindings>
<jaxb:bindings schemaLocation="../xsd/svrl-kosit.xsd">
<jaxb:bindings node="//xs:element[@name='schematron-output']/xs:complexType">
<inheritance:extends>de.kosit.validationtool.impl.model.BaseOutput</inheritance:extends>
</jaxb:bindings>
</jaxb:bindings>
</jaxb:bindings>

View file

@ -20,7 +20,8 @@
<!-- $Id$ -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:in="http://www.xoev.de/de/validator/framework/1/createreportinput"
xmlns:s="http://www.xoev.de/de/validator/framework/1/scenarios" targetNamespace="http://www.xoev.de/de/validator/framework/1/createreportinput" version="1.0.0"
xmlns:s="http://www.xoev.de/de/validator/framework/1/scenarios" xmlns:svrl="http://purl.oclc.org/dsdl/svrl"
targetNamespace="http://www.xoev.de/de/validator/framework/1/createreportinput" version="1.0.0"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<xs:import namespace="http://www.xoev.de/de/validator/framework/1/scenarios" schemaLocation="scenarios.xsd"/>
@ -88,7 +89,7 @@
<xs:element name="results">
<xs:complexType>
<xs:sequence>
<xs:any namespace="##any" processContents="skip"/>
<xs:element ref="svrl:schematron-output" />
</xs:sequence>
</xs:complexType>
</xs:element>

View file

@ -0,0 +1,161 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:svrl="http://purl.oclc.org/dsdl/svrl"
targetNamespace="http://purl.oclc.org/dsdl/svrl" version="0.1" elementFormDefault="qualified" attributeFormDefault="unqualified">
<xs:import schemaLocation="xml.xsd" namespace="http://www.w3.org/XML/1998/namespace" />
<xs:simpleType name="FailedAssertRoleType">
<xs:restriction base="xs:token">
<xs:enumeration value="error" />
<xs:enumeration value="fatal" />
<xs:enumeration value="information" />
<xs:enumeration value="warning" />
</xs:restriction>
</xs:simpleType>
<xs:complexType name="rich-text" mixed="true">
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:group ref="svrl:foreign" />
<xs:element ref="svrl:dir" />
<xs:element ref="svrl:span" />
<xs:element ref="svrl:emph" />
</xs:choice>
</xs:sequence>
<xs:attributeGroup ref="svrl:foreign" />
</xs:complexType>
<xs:attributeGroup name="attlist.assert-and-report">
<xs:attribute name="flag" type="xs:NMTOKEN" use="optional" />
<xs:attribute name="id" type="xs:NMTOKEN" use="required" />
<xs:attribute name="location" use="required" />
<xs:attribute name="role" type="svrl:FailedAssertRoleType" use="optional" />
<xs:attribute name="test" use="required" />
</xs:attributeGroup>
<xs:attributeGroup name="foreign">
<xs:attributeGroup ref="svrl:foreign-attributes" />
</xs:attributeGroup>
<xs:group name="foreign">
<xs:sequence>
<xs:group ref="svrl:foreign-element" minOccurs="0" />
</xs:sequence>
</xs:group>
<xs:attributeGroup name="foreign-attributes">
<xs:anyAttribute />
</xs:attributeGroup>
<xs:group name="foreign-element">
<xs:choice>
<xs:any namespace="##other" processContents="skip" />
<xs:any namespace="##local" processContents="skip" />
</xs:choice>
</xs:group>
<xs:element name="active-pattern">
<xs:complexType>
<xs:attribute name="documents" use="optional" />
<xs:attribute name="id" type="xs:NMTOKEN" use="optional" />
<xs:attribute name="name" use="optional" />
<xs:attribute name="role" type="xs:NMTOKEN" use="optional" />
</xs:complexType>
</xs:element>
<xs:element name="diagnostic-reference">
<xs:complexType>
<xs:sequence>
<xs:element ref="svrl:text" />
</xs:sequence>
<xs:attribute name="diagnostic" type="xs:NMTOKEN" use="required" />
</xs:complexType>
</xs:element>
<xs:element name="dir">
<xs:complexType mixed="true">
<xs:attribute name="class" use="optional" />
<xs:attribute name="dir" use="optional" />
</xs:complexType>
</xs:element>
<xs:element name="emph">
<xs:complexType mixed="true">
<xs:attribute name="class" use="optional" />
</xs:complexType>
</xs:element>
<xs:element name="failed-assert">
<xs:complexType>
<xs:sequence>
<xs:element ref="svrl:diagnostic-reference" minOccurs="0" maxOccurs="unbounded" />
<xs:element ref="svrl:property-reference" minOccurs="0" maxOccurs="unbounded" />
<xs:element ref="svrl:text" />
</xs:sequence>
<xs:attributeGroup ref="svrl:attlist.assert-and-report" />
</xs:complexType>
</xs:element>
<xs:element name="fired-rule">
<xs:complexType>
<xs:attribute name="context" use="required" />
<xs:attribute name="flag" type="xs:NMTOKEN" use="optional" />
<xs:attribute name="id" type="xs:NMTOKEN" use="optional" />
<xs:attribute name="name" use="optional" />
<xs:attribute name="role" type="xs:NMTOKEN" use="optional" />
</xs:complexType>
</xs:element>
<xs:element name="ns-prefix-in-attribute-values">
<xs:complexType>
<xs:attribute name="prefix" type="xs:NMTOKEN" use="required" />
<xs:attribute name="uri" use="required" />
</xs:complexType>
</xs:element>
<xs:element name="property-reference">
<xs:complexType>
<xs:sequence>
<xs:element ref="svrl:text" />
</xs:sequence>
<xs:attribute name="property" type="xs:NMTOKEN" use="required" />
<xs:attribute name="role" use="optional" />
<xs:attribute name="scheme" use="optional" />
</xs:complexType>
</xs:element>
<xs:element name="schematron-output">
<xs:complexType>
<xs:sequence>
<xs:element ref="svrl:text" minOccurs="0" maxOccurs="unbounded" />
<xs:element ref="svrl:ns-prefix-in-attribute-values" minOccurs="0" maxOccurs="unbounded" />
<xs:sequence minOccurs="0" maxOccurs="unbounded">
<xs:element ref="svrl:active-pattern" minOccurs="0" />
<xs:sequence minOccurs="0" maxOccurs="unbounded">
<xs:element ref="svrl:fired-rule" minOccurs="0" />
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element ref="svrl:failed-assert" />
<xs:element ref="svrl:successful-report" />
</xs:choice>
</xs:sequence>
</xs:sequence>
</xs:sequence>
<xs:attribute name="phase" type="xs:NMTOKEN" use="optional" />
<xs:attribute name="schemaVersion" use="optional" />
<xs:attribute name="title" use="optional" />
</xs:complexType>
</xs:element>
<xs:element name="span">
<xs:complexType mixed="true">
<xs:attribute name="class" use="required" />
</xs:complexType>
</xs:element>
<xs:element name="successful-report">
<xs:complexType>
<xs:sequence>
<xs:element ref="svrl:diagnostic-reference" minOccurs="0" maxOccurs="unbounded" />
<xs:element ref="svrl:property-reference" minOccurs="0" maxOccurs="unbounded" />
<xs:element ref="svrl:text" />
</xs:sequence>
<xs:attributeGroup ref="svrl:attlist.assert-and-report" />
</xs:complexType>
</xs:element>
<xs:element name="text">
<xs:complexType>
<xs:complexContent>
<xs:extension base="svrl:rich-text">
<xs:attribute ref="xml:space" />
<xs:attribute ref="xml:lang" />
<xs:attribute name="fpi" use="optional" />
<xs:attribute name="icon" use="optional" />
<xs:attribute name="see" use="optional" />
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
</xs:schema>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"
targetNamespace="http://www.w3.org/XML/1998/namespace">
<xs:import namespace="http://purl.oclc.org/dsdl/svrl" schemaLocation="svrl-kosit.xsd" />
<xs:attribute name="space" />
<xs:attribute name="lang" />
<xs:attributeGroup name="local">
<xs:anyAttribute namespace="##other" processContents="skip" />
</xs:attributeGroup>
</xs:schema>