From 891657749a1192c96160c2de9344c7fa3043295f Mon Sep 17 00:00:00 2001 From: eberhardtj <30794167+eberhardtj@users.noreply.github.com> Date: Thu, 5 Apr 2018 14:30:53 +0200 Subject: [PATCH] Add new module metafacture-xslt for XSL Transformations --- metafacture-runner/build.gradle | 1 + metafacture-xslt/build.gradle | 27 +++ .../java/org/metafacture/xslt/ApplyXslt.java | 43 +++++ .../org/metafacture/xslt/DefaultXsltPipe.java | 131 ++++++++++++++ .../metafacture/xslt/ForwardingSaxPipe.java | 160 ++++++++++++++++++ .../org/metafacture/xslt/SaxEventLogger.java | 141 +++++++++++++++ .../org/metafacture/xslt/StreamToSax.java | 113 +++++++++++++ .../org/metafacture/xslt/XsltEncoder.java | 41 +++++ .../adapter/OutputStreamToObjectReceiver.java | 71 ++++++++ .../xslt/adapter/XmlContentHandler.java | 68 ++++++++ .../main/resources/flux-commands.properties | 19 +++ .../xslt/adapter/XmlContentHandlerTest.java | 87 ++++++++++ .../xslt/mockito/SingleAttributeMatcher.java | 41 +++++ .../metafacture/xslt/xslt/ApplyXsltTest.java | 85 ++++++++++ .../xslt/xslt/StreamToSaxTest.java | 88 ++++++++++ .../xslt/xslt/XsltEncoderTest.java | 115 +++++++++++++ .../src/test/resources/identity.xsl | 8 + .../resources/identityWithoutDeclaration.xsl | 9 + settings.gradle | 1 + 19 files changed, 1249 insertions(+) create mode 100644 metafacture-xslt/build.gradle create mode 100644 metafacture-xslt/src/main/java/org/metafacture/xslt/ApplyXslt.java create mode 100644 metafacture-xslt/src/main/java/org/metafacture/xslt/DefaultXsltPipe.java create mode 100644 metafacture-xslt/src/main/java/org/metafacture/xslt/ForwardingSaxPipe.java create mode 100644 metafacture-xslt/src/main/java/org/metafacture/xslt/SaxEventLogger.java create mode 100644 metafacture-xslt/src/main/java/org/metafacture/xslt/StreamToSax.java create mode 100644 metafacture-xslt/src/main/java/org/metafacture/xslt/XsltEncoder.java create mode 100644 metafacture-xslt/src/main/java/org/metafacture/xslt/adapter/OutputStreamToObjectReceiver.java create mode 100644 metafacture-xslt/src/main/java/org/metafacture/xslt/adapter/XmlContentHandler.java create mode 100644 metafacture-xslt/src/main/resources/flux-commands.properties create mode 100644 metafacture-xslt/src/test/java/org/metafacture/xslt/adapter/XmlContentHandlerTest.java create mode 100644 metafacture-xslt/src/test/java/org/metafacture/xslt/mockito/SingleAttributeMatcher.java create mode 100644 metafacture-xslt/src/test/java/org/metafacture/xslt/xslt/ApplyXsltTest.java create mode 100644 metafacture-xslt/src/test/java/org/metafacture/xslt/xslt/StreamToSaxTest.java create mode 100644 metafacture-xslt/src/test/java/org/metafacture/xslt/xslt/XsltEncoderTest.java create mode 100644 metafacture-xslt/src/test/resources/identity.xsl create mode 100644 metafacture-xslt/src/test/resources/identityWithoutDeclaration.xsl diff --git a/metafacture-runner/build.gradle b/metafacture-runner/build.gradle index ae60b3de1..7713600f3 100644 --- a/metafacture-runner/build.gradle +++ b/metafacture-runner/build.gradle @@ -54,6 +54,7 @@ dependencies { plugins project(':metafacture-strings') plugins project(':metafacture-triples') plugins project(':metafacture-xml') + plugins project(':metafacture-xslt') plugins project(':metamorph') // The plugins configuration needs to be on the runtime classpath: diff --git a/metafacture-xslt/build.gradle b/metafacture-xslt/build.gradle new file mode 100644 index 000000000..541b5366c --- /dev/null +++ b/metafacture-xslt/build.gradle @@ -0,0 +1,27 @@ +/* + * Copyright 2018 Deutsche Nationalbibliothek + * + * Licensed under the Apache License, Version 2.0 the "License"; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +ext.mavenName = 'Metafacture XSLT' +description = 'Modules for XSL transformations on XML documents' + +dependencies { + api project(':metafacture-framework') + implementation 'net.sf.saxon:Saxon-HE:9.8.0-10' + implementation 'org.slf4j:slf4j-api:1.7.21' + testImplementation 'junit:junit:4.12' + testImplementation 'org.mockito:mockito-core:2.5.5' + testImplementation project(':metafacture-xml') +} diff --git a/metafacture-xslt/src/main/java/org/metafacture/xslt/ApplyXslt.java b/metafacture-xslt/src/main/java/org/metafacture/xslt/ApplyXslt.java new file mode 100644 index 000000000..a77bb7f1e --- /dev/null +++ b/metafacture-xslt/src/main/java/org/metafacture/xslt/ApplyXslt.java @@ -0,0 +1,43 @@ +/* + * Copyright 2018 Deutsche Nationalbibliothek + * + * Licensed under the Apache License, Version 2.0 the "License"; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.metafacture.xslt; + +import net.sf.saxon.s9api.Destination; +import net.sf.saxon.s9api.SAXDestination; +import org.metafacture.framework.FluxCommand; +import org.metafacture.framework.XmlReceiver; +import org.metafacture.framework.annotations.Description; +import org.metafacture.framework.annotations.In; +import org.metafacture.framework.annotations.Out; +import org.metafacture.xslt.adapter.XmlContentHandler; + +@In(XmlReceiver.class) +@Out(XmlReceiver.class) +@Description("Transforms generic XML (SAX event stream) by applying a XSLT using a custom stylesheet.") +@FluxCommand("apply-xslt") +public class ApplyXslt extends DefaultXsltPipe +{ + public ApplyXslt(String stylesheetId) + { + super(stylesheetId); + } + + @Override + Destination getDestination() + { + return new SAXDestination(new XmlContentHandler(getReceiver())); + } +} diff --git a/metafacture-xslt/src/main/java/org/metafacture/xslt/DefaultXsltPipe.java b/metafacture-xslt/src/main/java/org/metafacture/xslt/DefaultXsltPipe.java new file mode 100644 index 000000000..5ab7e5e1d --- /dev/null +++ b/metafacture-xslt/src/main/java/org/metafacture/xslt/DefaultXsltPipe.java @@ -0,0 +1,131 @@ +/* + * Copyright 2018 Deutsche Nationalbibliothek + * + * Licensed under the Apache License, Version 2.0 the "License"; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.metafacture.xslt; + +import net.sf.saxon.s9api.*; +import org.metafacture.framework.MetafactureException; +import org.metafacture.framework.Receiver; +import org.metafacture.framework.helpers.DefaultXmlPipe; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; + +import javax.xml.transform.stream.StreamSource; +import java.io.File; + +public abstract class DefaultXsltPipe extends DefaultXmlPipe +{ + private Processor processor; + private DocumentBuilder documentBuilder; + private BuildingContentHandler buildingContentHandler; + private XsltTransformer transformer; + + public DefaultXsltPipe(String stylesheetId) + { + this.processor = new Processor(false); + this.documentBuilder = processor.newDocumentBuilder(); + try + { + this.transformer = processor + .newXsltCompiler() + .compile(new StreamSource(new File(stylesheetId))) + .load(); + } + catch (SaxonApiException e) + { + throw new MetafactureException(e); + } + } + + public Processor getProcessor() + { + return processor; + } + + abstract Destination getDestination(); + + @Override + public void onSetReceiver() + { + transformer.setDestination(getDestination()); + } + + @Override + public void startDocument() + { + try + { + buildingContentHandler = documentBuilder.newBuildingContentHandler(); + buildingContentHandler.startDocument(); + } catch (SaxonApiException|SAXException e) + { + throw new MetafactureException(e); + } + } + + @Override + public void endDocument() + { + try + { + buildingContentHandler.endDocument(); + processor.writeXdmValue(buildingContentHandler.getDocumentNode(), transformer); + } + catch (SAXException|SaxonApiException e) + { + throw new MetafactureException(e); + } + } + + @Override + public void startElement(final String uri, final String localName, + final String qName, final Attributes attributes) + { + try + { + buildingContentHandler.startElement(uri, localName, qName, attributes); + } + catch (SAXException e) + { + throw new MetafactureException(e); + } + } + + @Override + public void endElement(String uri, String localName, String qName) + { + try + { + buildingContentHandler.endElement(uri, localName, qName); + } + catch (SAXException e) + { + throw new MetafactureException(e); + } + } + + @Override + public void characters(char[] ch, int start, int length) + { + try + { + buildingContentHandler.characters(ch, start, length); + } + catch (SAXException e) + { + throw new MetafactureException(e); + } + } +} diff --git a/metafacture-xslt/src/main/java/org/metafacture/xslt/ForwardingSaxPipe.java b/metafacture-xslt/src/main/java/org/metafacture/xslt/ForwardingSaxPipe.java new file mode 100644 index 000000000..91e44bc70 --- /dev/null +++ b/metafacture-xslt/src/main/java/org/metafacture/xslt/ForwardingSaxPipe.java @@ -0,0 +1,160 @@ +/* + * Copyright 2018 Deutsche Nationalbibliothek + * + * Licensed under the Apache License, Version 2.0 the "License"; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.metafacture.xslt; + +import java.io.IOException; + + +import org.metafacture.framework.XmlReceiver; +import org.metafacture.framework.helpers.DefaultXmlPipe; +import org.xml.sax.*; + +public class ForwardingSaxPipe extends DefaultXmlPipe +{ + @Override + public void setDocumentLocator(final Locator locator) { + getReceiver().setDocumentLocator(locator); + } + + @Override + public void startDocument() throws SAXException + { + getReceiver().startDocument(); + } + + @Override + public void endDocument() throws SAXException { + getReceiver().endDocument(); + } + + @Override + public void startPrefixMapping(final String prefix, final String uri) + throws SAXException { + getReceiver().startPrefixMapping(prefix, uri); + } + + @Override + public void endPrefixMapping(final String prefix) throws SAXException { + getReceiver().endPrefixMapping(prefix); + } + + @Override + public void startElement(final String uri, final String localName, final String qName, + final Attributes atts) throws SAXException { + getReceiver().startElement(uri, localName, qName, atts); + } + + @Override + public void endElement(final String uri, final String localName, final String qName) + throws SAXException { + getReceiver().endElement(uri, localName, qName); + } + + @Override + public void characters(final char[] ch, final int start, final int length) + throws SAXException { + getReceiver().characters(ch, start, length); + } + + @Override + public void ignorableWhitespace(final char[] ch, final int start, final int length) + throws SAXException { + getReceiver().ignorableWhitespace(ch, start, length); + } + + @Override + public void processingInstruction(final String target, final String data) + throws SAXException { + getReceiver().processingInstruction(target, data); + } + + @Override + public void skippedEntity(final String name) throws SAXException { + getReceiver().skippedEntity(name); + } + + @Override + public void notationDecl(final String name, final String publicId, final String systemId) + throws SAXException { + getReceiver().notationDecl(name, publicId, systemId); + } + + @Override + public void unparsedEntityDecl(final String name, final String publicId, + final String systemId, final String notationName) throws SAXException { + getReceiver().unparsedEntityDecl(name, publicId, systemId, notationName); + } + + @Override + public InputSource resolveEntity(final String publicId, final String systemId) + throws SAXException, IOException + { + getReceiver().resolveEntity(publicId, systemId); + return null; + } + + @Override + public void warning(final SAXParseException exception) throws SAXException { + getReceiver().warning(exception); + } + + @Override + public void error(final SAXParseException exception) throws SAXException { + getReceiver().error(exception); + } + + @Override + public void fatalError(final SAXParseException exception) throws SAXException { + getReceiver().fatalError(exception); + } + + @Override + public void startDTD(final String name, final String publicId, final String systemId) + throws SAXException { + getReceiver().startDTD(name, publicId, systemId); + } + + @Override + public void endDTD() throws SAXException { + getReceiver().endDTD(); + } + + @Override + public void startEntity(final String name) throws SAXException { + getReceiver().startEntity(name); + } + + @Override + public void endEntity(final String name) throws SAXException { + getReceiver().endEntity(name); + } + + @Override + public void startCDATA() throws SAXException { + getReceiver().startCDATA(); + } + + @Override + public void endCDATA() throws SAXException { + getReceiver().endCDATA(); + } + + @Override + public void comment(final char[] chars, final int start, final int length) + throws SAXException { + getReceiver().comment(chars, start, length); + } +} diff --git a/metafacture-xslt/src/main/java/org/metafacture/xslt/SaxEventLogger.java b/metafacture-xslt/src/main/java/org/metafacture/xslt/SaxEventLogger.java new file mode 100644 index 000000000..ab9211f70 --- /dev/null +++ b/metafacture-xslt/src/main/java/org/metafacture/xslt/SaxEventLogger.java @@ -0,0 +1,141 @@ +/* + * Copyright 2018 Deutsche Nationalbibliothek + * + * Licensed under the Apache License, Version 2.0 the "License"; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.metafacture.xslt; + +import java.util.StringJoiner; + +import org.metafacture.framework.FluxCommand; +import org.metafacture.framework.MetafactureException; +import org.metafacture.framework.XmlReceiver; +import org.metafacture.framework.annotations.Description; +import org.metafacture.framework.annotations.In; +import org.metafacture.framework.annotations.Out; +import org.metafacture.framework.helpers.DefaultXmlPipe; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; + +@In(XmlReceiver.class) +@Out(XmlReceiver.class) +@Description("Logs a stream of SAX events.") +@FluxCommand("log-sax") +public class SaxEventLogger extends DefaultXmlPipe +{ + private static final Logger LOG = LoggerFactory.getLogger(SaxEventLogger.class); + + private String prefix; + + public SaxEventLogger() + { + this(""); + } + + public SaxEventLogger(final String prefix) + { + super(); + this.prefix = prefix; + } + + public void setPrefix(String prefix) + { + this.prefix = prefix; + } + + @Override + public void startDocument() + { + LOG.debug("{} ", prefix); + if (null != getReceiver()) { + try + { + getReceiver().startDocument(); + } catch (SAXException e) + { + throw new MetafactureException(e); + } + } + } + + @Override + public void endDocument() + { + LOG.debug("{} ", prefix); + if (null != getReceiver()) { + try + { + getReceiver().endDocument(); + } catch (SAXException e) + { + throw new MetafactureException(e); + } + } + } + + @Override + public void startElement(final String uri, final String localName, + final String qName, final Attributes attributes) + { + StringJoiner joiner = new StringJoiner(" "); + for (int i = 0; i < attributes.getLength(); ++i) { + final String name = attributes.getLocalName(i); + final String value = attributes.getValue(i); + joiner.add(name + "=" + "\"" + value + "\""); + } + + LOG.debug("{} <{} {}>", prefix, localName, joiner.toString()); + + if (null != getReceiver()) { + try + { + getReceiver().startElement(uri, localName, qName, attributes); + } catch (SAXException e) + { + throw new MetafactureException(e); + } + } + } + + @Override + public void endElement(String uri, String localName, String qName) + { + LOG.debug("{} ", prefix, localName); + if (null != getReceiver()) { + try + { + getReceiver().endElement(uri, localName, qName); + } catch (SAXException e) + { + throw new MetafactureException(e); + } + } + } + + @Override + public void characters(char[] ch, int start, int length) + { + LOG.debug("{} {}", prefix, new String(ch)); + if (null != getReceiver()) { + try + { + getReceiver().characters(ch, start, length); + } catch (SAXException e) + { + throw new MetafactureException(e); + } + } + } +} diff --git a/metafacture-xslt/src/main/java/org/metafacture/xslt/StreamToSax.java b/metafacture-xslt/src/main/java/org/metafacture/xslt/StreamToSax.java new file mode 100644 index 000000000..6d3745fa1 --- /dev/null +++ b/metafacture-xslt/src/main/java/org/metafacture/xslt/StreamToSax.java @@ -0,0 +1,113 @@ +/* + * Copyright 2018 Deutsche Nationalbibliothek + * + * Licensed under the Apache License, Version 2.0 the "License"; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.metafacture.xslt; + +import org.metafacture.framework.FluxCommand; +import org.metafacture.framework.MetafactureException; +import org.metafacture.framework.StreamReceiver; +import org.metafacture.framework.XmlReceiver; +import org.metafacture.framework.annotations.Description; +import org.metafacture.framework.annotations.In; +import org.metafacture.framework.annotations.Out; +import org.metafacture.framework.helpers.DefaultStreamPipe; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +/** + * Encodes a stream into XML events. + */ + +@In(StreamReceiver.class) +@Out(XmlReceiver.class) +@Description("Encodes a stream as generic xml (sax event stream).") +@FluxCommand("stream-to-sax") +public class StreamToSax extends DefaultStreamPipe +{ + @Override + public void startRecord(final String identifier) + { + AttributesImpl attributes = new AttributesImpl(); + attributes.addAttribute("", "id", "id", "ID", identifier); + + try + { + getReceiver().startDocument(); + getReceiver().startElement("", "record", "record", attributes); + } + catch (SAXException e) + { + throw new MetafactureException(e); + } + } + + @Override + public void endRecord() + { + try + { + getReceiver().endElement("", "record", "record"); + getReceiver().endDocument(); + } catch (SAXException e) + { + throw new MetafactureException(e); + } + } + + @Override + public void startEntity(final String name) + { + AttributesImpl attributes = new AttributesImpl(); + attributes.addAttribute("", "name", "name", "CDATA", name); + try + { + getReceiver().startElement("", "entity", "entity", attributes); + } + catch (SAXException e) + { + throw new MetafactureException(e); + } + } + + public @Override void endEntity() + { + try + { + getReceiver().endElement("", "entity", "entity"); + } + catch (SAXException e) + { + throw new MetafactureException(e); + } + } + + @Override + public void literal(final String name, final String value) + { + AttributesImpl attributes = new AttributesImpl(); + attributes.addAttribute("", "name", "name", "CDATA", name); + + try + { + getReceiver().startElement("", "literal", "literal", attributes); + getReceiver().characters(value.toCharArray(),0, value.length()); + getReceiver().endElement("", "literal", "literal"); + } + catch (SAXException e) + { + throw new MetafactureException(e); + } + } +} diff --git a/metafacture-xslt/src/main/java/org/metafacture/xslt/XsltEncoder.java b/metafacture-xslt/src/main/java/org/metafacture/xslt/XsltEncoder.java new file mode 100644 index 000000000..37915a037 --- /dev/null +++ b/metafacture-xslt/src/main/java/org/metafacture/xslt/XsltEncoder.java @@ -0,0 +1,41 @@ +/* + * Copyright 2018 Deutsche Nationalbibliothek + * + * Licensed under the Apache License, Version 2.0 the "License"; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.metafacture.xslt; + +import net.sf.saxon.s9api.Destination; +import org.metafacture.framework.FluxCommand; +import org.metafacture.framework.ObjectReceiver; +import org.metafacture.framework.XmlReceiver; +import org.metafacture.framework.annotations.Description; +import org.metafacture.framework.annotations.In; +import org.metafacture.framework.annotations.Out; +import org.metafacture.xslt.adapter.OutputStreamToObjectReceiver; + +@In(XmlReceiver.class) +@Out(ObjectReceiver.class) +@Description("Transforms generic XML (SAX event stream) by applying a XSLT using a custom stylesheet.") +@FluxCommand("encode-xslt") +public class XsltEncoder extends DefaultXsltPipe> +{ + public XsltEncoder(String stylesheetId) + { + super(stylesheetId); + } + + Destination getDestination() { + return getProcessor().newSerializer(new OutputStreamToObjectReceiver(getReceiver())); + } +} diff --git a/metafacture-xslt/src/main/java/org/metafacture/xslt/adapter/OutputStreamToObjectReceiver.java b/metafacture-xslt/src/main/java/org/metafacture/xslt/adapter/OutputStreamToObjectReceiver.java new file mode 100644 index 000000000..dcac03d70 --- /dev/null +++ b/metafacture-xslt/src/main/java/org/metafacture/xslt/adapter/OutputStreamToObjectReceiver.java @@ -0,0 +1,71 @@ +/* + * Copyright 2018 Deutsche Nationalbibliothek + * + * Licensed under the Apache License, Version 2.0 the "License"; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.metafacture.xslt.adapter; + +import java.io.*; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import org.metafacture.framework.ObjectReceiver; + +public class OutputStreamToObjectReceiver extends OutputStream { + + private ObjectReceiver receiver; + private ByteArrayOutputStream byteArrayOutputStream; + private Charset utf8 = StandardCharsets.UTF_8; + + public OutputStreamToObjectReceiver(ObjectReceiver receiver) + { + this.receiver = receiver; + int KB512 = 524288; + this.byteArrayOutputStream = new ByteArrayOutputStream(KB512); + } + + public OutputStreamToObjectReceiver(ObjectReceiver receiver, Charset utf8) + { + this(receiver); + this.utf8 = utf8; + } + + @Override + public void write(int i) throws IOException { + byteArrayOutputStream.write(i); + } + + @Override + public void write(byte[] bytes) throws IOException { + byteArrayOutputStream.write(bytes); + } + + @Override + public void write(byte[] bytes, int i, int i1 ) { + byteArrayOutputStream.write(bytes, i, i1); + } + + @Override + public void flush() throws IOException + { + byteArrayOutputStream.flush(); + receiver.process(byteArrayOutputStream.toString(utf8.name())); + byteArrayOutputStream.reset(); + } + + @Override + public void close() throws IOException + { + byteArrayOutputStream.close(); + } +} diff --git a/metafacture-xslt/src/main/java/org/metafacture/xslt/adapter/XmlContentHandler.java b/metafacture-xslt/src/main/java/org/metafacture/xslt/adapter/XmlContentHandler.java new file mode 100644 index 000000000..2a052e1b7 --- /dev/null +++ b/metafacture-xslt/src/main/java/org/metafacture/xslt/adapter/XmlContentHandler.java @@ -0,0 +1,68 @@ +/* + * Copyright 2018 Deutsche Nationalbibliothek + * + * Licensed under the Apache License, Version 2.0 the "License"; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.metafacture.xslt.adapter; + + +import org.metafacture.framework.XmlReceiver; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.XMLFilterImpl; + + +/** + * Passes SAX events to a xml receiver class. + */ +public class XmlContentHandler extends XMLFilterImpl +{ + + private XmlReceiver receiver; + + public XmlContentHandler(XmlReceiver receiver) + { + this.receiver = receiver; + } + + @Override + public void startDocument() throws SAXException + { + receiver.startDocument(); + } + + @Override + public void endDocument() throws SAXException + { + receiver.endDocument(); + } + + @Override + public void startElement(final String uri, final String localName, final String qName, final Attributes attributes) + throws SAXException + { + receiver.startElement(uri, localName, qName, attributes); + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException + { + receiver.endElement(uri, localName, qName); + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException + { + receiver.characters(ch, start, length); + } +} diff --git a/metafacture-xslt/src/main/resources/flux-commands.properties b/metafacture-xslt/src/main/resources/flux-commands.properties new file mode 100644 index 000000000..aab73e8be --- /dev/null +++ b/metafacture-xslt/src/main/resources/flux-commands.properties @@ -0,0 +1,19 @@ +# +# Copyright 2018 Deutsche Nationalbibliothek +# +# Licensed under the Apache License, Version 2.0 the "License"; +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +stream-to-sax org.metafacture.xslt.StreamToSax +log-sax org.metafacture.xslt.SaxEventLogger +apply-xslt org.metafacture.xslt.ApplyXslt +encode-xslt org.metafacture.xslt.XsltEncoder diff --git a/metafacture-xslt/src/test/java/org/metafacture/xslt/adapter/XmlContentHandlerTest.java b/metafacture-xslt/src/test/java/org/metafacture/xslt/adapter/XmlContentHandlerTest.java new file mode 100644 index 000000000..6c474f48c --- /dev/null +++ b/metafacture-xslt/src/test/java/org/metafacture/xslt/adapter/XmlContentHandlerTest.java @@ -0,0 +1,87 @@ +/* + * Copyright 2018 Deutsche Nationalbibliothek + * + * Licensed under the Apache License, Version 2.0 the "License"; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.metafacture.xslt.adapter; + +import org.junit.Before; +import org.junit.Test; +import org.metafacture.framework.XmlReceiver; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.xml.sax.helpers.AttributesImpl; +import static org.metafacture.xslt.mockito.SingleAttributeMatcher.hasSingleAttribute; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; + +public class XmlContentHandlerTest +{ + private XmlContentHandler adapter; + + @Mock + private XmlReceiver receiver; + + @Before + public void setUp() + { + MockitoAnnotations.initMocks(this); + adapter = new XmlContentHandler(receiver); + } + + @Test + public void emptyDocument() throws Exception + { + adapter.startDocument(); + adapter.endDocument(); + + verify(receiver).startDocument(); + verify(receiver).endDocument(); + } + + @Test + public void singleElement() throws Exception + { + AttributesImpl atts = new AttributesImpl(); + atts.addAttribute("", "id", "id", "ID", "1"); + adapter.startElement("", "elem", "elem", atts); + adapter.characters("dummy".toCharArray(),0, 5); + adapter.endElement("", "elem", "elem"); + + verify(receiver).startElement(eq(""), eq("elem"), eq("elem"), + argThat(hasSingleAttribute("", "id", "id", "ID", "1"))); + verify(receiver).characters("dummy".toCharArray(), 0, 5); + verify(receiver).endElement("", "elem", "elem"); + } + + @Test + public void singleDocumentWithElement() throws Exception + { + adapter.startDocument(); + AttributesImpl attrs = new AttributesImpl(); + attrs.addAttribute("", "id", "id", "ID", "1"); + adapter.startElement("", "elem", "elem", attrs); + adapter.characters("dummy".toCharArray(),0, 5); + adapter.endElement("", "elem", "elem"); + adapter.endDocument(); + + verify(receiver).startDocument(); + verify(receiver).startElement(eq(""), eq("elem"), eq("elem"), + argThat(hasSingleAttribute("", "id", "id", "ID", "1"))); + verify(receiver).characters("dummy".toCharArray(), 0, 5); + verify(receiver).endElement("", "elem", "elem"); + verify(receiver).endDocument(); + } + +} diff --git a/metafacture-xslt/src/test/java/org/metafacture/xslt/mockito/SingleAttributeMatcher.java b/metafacture-xslt/src/test/java/org/metafacture/xslt/mockito/SingleAttributeMatcher.java new file mode 100644 index 000000000..f42761197 --- /dev/null +++ b/metafacture-xslt/src/test/java/org/metafacture/xslt/mockito/SingleAttributeMatcher.java @@ -0,0 +1,41 @@ +/* + * Copyright 2018 Deutsche Nationalbibliothek + * + * Licensed under the Apache License, Version 2.0 the "License"; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.metafacture.xslt.mockito; + +import org.mockito.ArgumentMatcher; +import org.xml.sax.Attributes; + +public class SingleAttributeMatcher +{ + public static ArgumentMatcher hasSingleAttribute(String uri, String localName, + String qName, + String type, String value) + { + return new ArgumentMatcher() + { + @Override + public boolean matches(Attributes argument) + { + return argument.getLength() == 1 && + argument.getURI(0).equals(uri) && + argument.getLocalName(0).equals(localName) && + argument.getQName(0).equals(qName) && + argument.getType(0).equals(type) && + argument.getValue(0).equals(value); + } + }; + } +} diff --git a/metafacture-xslt/src/test/java/org/metafacture/xslt/xslt/ApplyXsltTest.java b/metafacture-xslt/src/test/java/org/metafacture/xslt/xslt/ApplyXsltTest.java new file mode 100644 index 000000000..d24a8eac4 --- /dev/null +++ b/metafacture-xslt/src/test/java/org/metafacture/xslt/xslt/ApplyXsltTest.java @@ -0,0 +1,85 @@ +/* + * Copyright 2018 Deutsche Nationalbibliothek + * + * Licensed under the Apache License, Version 2.0 the "License"; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.metafacture.xslt.xslt; + +import org.junit.Before; +import org.junit.Test; +import org.metafacture.framework.StreamReceiver; +import org.metafacture.xml.GenericXmlHandler; +import org.metafacture.xslt.ApplyXslt; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.xml.sax.helpers.AttributesImpl; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + + +public class ApplyXsltTest +{ + private String stylesheetId = "src/test/resources/identity.xsl"; + + private ApplyXslt xsltTransformer; + + @Mock + private StreamReceiver receiver; + + @Before + public void setUp() throws Exception + { + System.setProperty("org.culturegraph.metamorph.xml.recordtag", "record"); + MockitoAnnotations.initMocks(this); + xsltTransformer = new ApplyXslt(stylesheetId); + xsltTransformer.setReceiver(new GenericXmlHandler()).setReceiver(receiver); + } + + @Test + public void identityTransformation() throws Exception + { + // Document start + xsltTransformer.startDocument(); + // + AttributesImpl recordAtts = new AttributesImpl(); + recordAtts.addAttribute("", "id", "id", "ID", "record-1"); + xsltTransformer.startElement("", "record", "record", recordAtts); + // + AttributesImpl literalAtts = new AttributesImpl(); + literalAtts.addAttribute("", "name", "name", "CDATA", "name-1"); + xsltTransformer.startElement("", "literal", "literal", literalAtts); + // value-1 + xsltTransformer.characters("value-1".toCharArray(), 0, 7); + // + xsltTransformer.endElement("", "literal", "literal"); + // + xsltTransformer.endElement("", "entity", "entity"); + // + xsltTransformer.endElement("", "record", "record"); + // Document end + xsltTransformer.endDocument(); + + verify(receiver).startRecord("record-1"); + verify(receiver).startEntity("entity"); + verify(receiver).literal("name", "entity-1"); + verify(receiver).startEntity("literal"); + verify(receiver).literal("name", "name-1"); + verify(receiver).literal("value", "value-1"); + verify(receiver, times(2)).endEntity(); + verify(receiver).endRecord(); + } +} diff --git a/metafacture-xslt/src/test/java/org/metafacture/xslt/xslt/StreamToSaxTest.java b/metafacture-xslt/src/test/java/org/metafacture/xslt/xslt/StreamToSaxTest.java new file mode 100644 index 000000000..a14f26576 --- /dev/null +++ b/metafacture-xslt/src/test/java/org/metafacture/xslt/xslt/StreamToSaxTest.java @@ -0,0 +1,88 @@ +/* + * Copyright 2018 Deutsche Nationalbibliothek + * + * Licensed under the Apache License, Version 2.0 the "License"; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.metafacture.xslt.xslt; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.metafacture.framework.XmlReceiver; +import org.metafacture.xslt.StreamToSax; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import static org.metafacture.xslt.mockito.SingleAttributeMatcher.hasSingleAttribute; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; + +public class StreamToSaxTest +{ + private StreamToSax handler; + + @Mock + private XmlReceiver receiver; + + @Before + public void setUp() + { + MockitoAnnotations.initMocks(this); + handler = new StreamToSax(); + handler.setReceiver(receiver); + } + + @After + public void cleanUp() + { + handler.closeStream(); + } + + @Test + public void convertLiteralToSax() throws Exception + { + handler.literal("name-1", "value-1"); + + verify(receiver).startElement(eq(""), eq("literal"), eq("literal"), + argThat(hasSingleAttribute("", "name", "name", "CDATA", "name-1"))); + + String value = "value-1"; + verify(receiver).characters(value.toCharArray(), 0, value.length()); + + verify(receiver).endElement("", "literal", "literal"); + } + + @Test + public void convertEntityToSax() throws Exception + { + handler.startEntity("entity-1"); + handler.endEntity(); + + verify(receiver).startElement(eq(""), eq("entity"), eq("entity"), + argThat(hasSingleAttribute("", "name", "name", "CDATA", "entity-1"))); + verify(receiver).endElement("", "entity", "entity"); + } + + @Test + public void convertRecordToSax() throws Exception + { + handler.startRecord("rec-1"); + handler.endRecord(); + + verify(receiver).startDocument(); + verify(receiver).startElement(eq(""), eq("record"), eq("record"), + argThat(hasSingleAttribute("", "id", "id", "ID", "rec-1"))); + verify(receiver).endElement("", "record", "record"); + verify(receiver).endDocument(); + } +} diff --git a/metafacture-xslt/src/test/java/org/metafacture/xslt/xslt/XsltEncoderTest.java b/metafacture-xslt/src/test/java/org/metafacture/xslt/xslt/XsltEncoderTest.java new file mode 100644 index 000000000..8228f0151 --- /dev/null +++ b/metafacture-xslt/src/test/java/org/metafacture/xslt/xslt/XsltEncoderTest.java @@ -0,0 +1,115 @@ +/* + * Copyright 2018 Deutsche Nationalbibliothek + * + * Licensed under the Apache License, Version 2.0 the "License"; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.metafacture.xslt.xslt; + +import org.junit.Before; +import org.junit.Test; +import org.metafacture.framework.ObjectReceiver; +import org.metafacture.xslt.XsltEncoder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.xml.sax.helpers.AttributesImpl; +import static org.mockito.Mockito.verify; + + +public class XsltEncoderTest +{ + private XsltEncoder xsltTransformer; + + @Mock + private ObjectReceiver receiver; + + @Before + public void setUp() throws Exception + { + MockitoAnnotations.initMocks(this); + + } + + @Test + public void identityTransformation() throws Exception + { + String stylesheetId = "src/test/resources/identity.xsl"; + xsltTransformer = new XsltEncoder(stylesheetId); + xsltTransformer.setReceiver(receiver); + + // Document start + xsltTransformer.startDocument(); + // + AttributesImpl recordAtts = new AttributesImpl(); + recordAtts.addAttribute("", "id", "id", "ID", "record-1"); + xsltTransformer.startElement("", "record", "record", recordAtts); + // + AttributesImpl literalAtts = new AttributesImpl(); + literalAtts.addAttribute("", "name", "name", "CDATA", "name-1"); + xsltTransformer.startElement("", "literal", "literal", literalAtts); + // value-1 + xsltTransformer.characters("value-1".toCharArray(), 0, 7); + // + xsltTransformer.endElement("", "literal", "literal"); + // + xsltTransformer.endElement("", "entity", "entity"); + // + xsltTransformer.endElement("", "record", "record"); + // Document end + xsltTransformer.endDocument(); + + String expected = "value-1"; + + verify(receiver).process(expected); + } + + @Test + public void identityTransformationWithoutDeclaration() throws Exception + { + String stylesheetId = "src/test/resources/identityWithoutDeclaration.xsl"; + xsltTransformer = new XsltEncoder(stylesheetId); + xsltTransformer.setReceiver(receiver); + + // Document start + xsltTransformer.startDocument(); + // + AttributesImpl recordAtts = new AttributesImpl(); + recordAtts.addAttribute("", "id", "id", "ID", "record-1"); + xsltTransformer.startElement("", "record", "record", recordAtts); + // + AttributesImpl literalAtts = new AttributesImpl(); + literalAtts.addAttribute("", "name", "name", "CDATA", "name-1"); + xsltTransformer.startElement("", "literal", "literal", literalAtts); + // value-1 + xsltTransformer.characters("value-1".toCharArray(), 0, 7); + // + xsltTransformer.endElement("", "literal", "literal"); + // + xsltTransformer.endElement("", "entity", "entity"); + // + xsltTransformer.endElement("", "record", "record"); + // Document end + xsltTransformer.endDocument(); + + String expected = "value-1"; + + verify(receiver).process(expected); + } +} diff --git a/metafacture-xslt/src/test/resources/identity.xsl b/metafacture-xslt/src/test/resources/identity.xsl new file mode 100644 index 000000000..13b19c1d1 --- /dev/null +++ b/metafacture-xslt/src/test/resources/identity.xsl @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/metafacture-xslt/src/test/resources/identityWithoutDeclaration.xsl b/metafacture-xslt/src/test/resources/identityWithoutDeclaration.xsl new file mode 100644 index 000000000..b3fa9e9cb --- /dev/null +++ b/metafacture-xslt/src/test/resources/identityWithoutDeclaration.xsl @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/settings.gradle b/settings.gradle index 738d3f046..3000e7a3b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -28,6 +28,7 @@ include ':metafacture-strings' include ':metafacture-formeta' include ':metafacture-formatting' include ':metafacture-xml' +include ':metafacture-xslt' include ':metafacture-triples' include ':metafacture-statistics' include ':metafacture-io'