Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/keenwrite.git

Eliminate null processors, simplify XML processor

AuthorDaveJarvis <email>
Date2020-11-08 22:00:22 GMT-0800
Commit6602414a70e2c7bc597d396684e085d750b4edcb
Parent30f05e8
Delta102 lines added, 101 lines removed, 1-line increase
src/main/java/com/keenwrite/processors/markdown/MarkdownProcessor.java
* Responsible for parsing a Markdown document and rendering it as HTML.
*/
-public class MarkdownProcessor extends AbstractProcessor<String> {
+public class MarkdownProcessor extends ExecutorProcessor<String> {
private final IParse mParser;
src/main/java/com/keenwrite/processors/DefinitionProcessor.java
* {@code $variable$}.
*/
-public class DefinitionProcessor extends AbstractProcessor<String> {
+public class DefinitionProcessor extends ExecutorProcessor<String> {
private final Map<String, String> mDefinitions;
src/main/java/com/keenwrite/processors/ExecutorProcessor.java
+package com.keenwrite.processors;
+
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Responsible for transforming data through a variety of chained handlers.
+ *
+ * @param <T> The data type to process.
+ */
+public class ExecutorProcessor<T> implements Processor<T> {
+
+ /**
+ * The next link in the processing chain.
+ */
+ private final Processor<T> mNext;
+
+ protected ExecutorProcessor() {
+ this( null );
+ }
+
+ /**
+ * Constructs a new processor having a given successor.
+ *
+ * @param successor The next processor in the chain.
+ */
+ public ExecutorProcessor( final Processor<T> successor ) {
+ mNext = successor;
+ }
+
+ /**
+ * Calls every link in the chain to process the given data.
+ *
+ * @param data The data to transform.
+ * @return The data after processing by every link in the chain.
+ */
+ @Override
+ public T apply( final T data ) {
+ // Avoid infinite recursion.
+ Optional<Processor<T>> handler = next();
+ final var result = new AtomicReference<>( data );
+
+ while( handler.isPresent() ) {
+ handler = handler.flatMap( p -> {
+ result.set( p.apply( result.get() ) );
+ return p.next();
+ } );
+ }
+
+ return result.get();
+ }
+
+ @Override
+ public Optional<Processor<T>> next() {
+ return Optional.ofNullable( mNext );
+ }
+}
src/main/java/com/keenwrite/processors/HtmlPreviewProcessor.java
* chain.
*/
-public class HtmlPreviewProcessor extends AbstractProcessor<String> {
+public class HtmlPreviewProcessor extends ExecutorProcessor<String> {
// There is only one preview panel.
src/main/java/com/keenwrite/processors/IdentityProcessor.java
* end of a processing chain when no more processing is required.
*/
-public class IdentityProcessor extends AbstractProcessor<String> {
+public class IdentityProcessor extends ExecutorProcessor<String> {
public static final IdentityProcessor INSTANCE = new IdentityProcessor();
src/main/java/com/keenwrite/processors/PreformattedProcessor.java
* element.
*/
-public class PreformattedProcessor extends AbstractProcessor<String> {
+public class PreformattedProcessor extends ExecutorProcessor<String> {
/**
src/main/java/com/keenwrite/processors/Processor.java
package com.keenwrite.processors;
+import java.util.Optional;
import java.util.function.UnaryOperator;
/**
* Responsible for processing documents from one known format to another.
* Processes the given content providing a transformation from one document
- * format into another. For example, this could convert from XML to text using
- * an XSLT processor, or from markdown to HTML.
+ * format into another. For example, this could convert Markdown to HTML.
*
- * @param <T> The type of processor to create.
+ * @param <T> The data type to process.
*/
public interface Processor<T> extends UnaryOperator<T> {
/**
- * Adds a document processor to call after this processor finishes processing
- * the document given to the process method.
+ * Returns the next link in the processor chain.
*
- * @return The processor that should transform the document after this
- * instance has finished processing, or {@code null} if this is the last
- * processor in the chain.
+ * @return The processor intended to transform the data after this instance
+ * has finished processing, or {@link Optional#empty} if this is the last
+ * link in the chain.
*/
- default Processor<T> next() {
- return null;
+ default Optional<Processor<T>> next() {
+ return Optional.empty();
}
}
src/main/java/com/keenwrite/processors/ProcessorFactory.java
private Processor<String> createProcessor() {
- final ProcessorContext context = getProcessorContext();
+ final var context = getProcessorContext();
// If the content is not to be exported, then the successor processor
// to SVG. Without conversion would require client-side rendering of
// math (such as using the JavaScript-based KaTeX engine).
- final Processor<String> successor = context.isExportFormat( NONE )
+ final var successor = context.isExportFormat( NONE )
? createHtmlPreviewProcessor()
: createIdentityProcessor();
- return switch( context.getFileType() ) {
+ final var processor = switch( context.getFileType() ) {
case RMARKDOWN -> createRProcessor( successor );
case SOURCE -> createMarkdownProcessor( successor );
case RXML -> createRXMLProcessor( successor );
case XML -> createXMLProcessor( successor );
default -> createPreformattedProcessor( successor );
};
+
+ return new ExecutorProcessor<>( processor );
}
final ProcessorContext context ) {
return new ProcessorFactory( context ).createProcessor();
- }
-
- /**
- * Executes the processing chain, operating on the given string.
- *
- * @param handler The first processor in the chain to call.
- * @param text The initial value of the text to process.
- * @return The final value of the text that was processed by the chain.
- */
- public static String processChain( Processor<String> handler, String text ) {
- while( handler != null && text != null ) {
- text = handler.apply( text );
- handler = handler.next();
- }
-
- return text;
}
src/main/java/com/keenwrite/processors/XmlProcessor.java
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.ProcessingInstruction;
-import javax.xml.stream.events.XMLEvent;
import javax.xml.transform.*;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
-import java.io.File;
import java.io.Reader;
import java.io.StringReader;
* </p>
*/
-public class XmlProcessor extends AbstractProcessor<String>
+public class XmlProcessor extends ExecutorProcessor<String>
implements ErrorListener {
- private final Snitch snitch = Services.load( Snitch.class );
+ private final Snitch mSnitch = Services.load( Snitch.class );
- private XMLInputFactory xmlInputFactory;
- private TransformerFactory transformerFactory;
- private Transformer transformer;
+ private final XMLInputFactory mXmlInputFactory =
+ XMLInputFactory.newInstance();
+ private final TransformerFactory mTransformerFactory =
+ new TransformerFactoryImpl();
+ private Transformer mTransformer;
- private Path path;
+ private final Path mPath;
/**
final ProcessorContext context ) {
super( successor );
- setPath( context.getPath() );
+ mPath = context.getPath();
+
+ // Bubble problems up to the user interface, rather than standard error.
+ mTransformerFactory.setErrorListener( this );
}
// Listen for external file modification events.
- getSnitch().listen( xsl );
+ mSnitch.listen( xsl );
getTransformer( xsl ).transform(
* transformer.
*/
- private Transformer getTransformer( final Path xsl )
+ private synchronized Transformer getTransformer( final Path xsl )
throws TransformerConfigurationException {
- if( this.transformer == null ) {
- this.transformer = createTransformer( xsl );
+ if( mTransformer == null ) {
+ mTransformer = createTransformer( xsl );
}
- return this.transformer;
+ return mTransformer;
}
protected Transformer createTransformer( final Path xsl )
throws TransformerConfigurationException {
- final Source xslt = new StreamSource( xsl.toFile() );
+ final var xslt = new StreamSource( xsl.toFile() );
return getTransformerFactory().newTransformer( xslt );
}
private Path getXslPath( final String filename ) {
- final Path xmlPath = getPath();
- final File xmlDirectory = xmlPath.toFile().getParentFile();
+ final var xmlDirectory = mPath.toFile().getParentFile();
return Paths.get( xmlDirectory.getPath(), filename );
private String getXsltFilename( final String xml )
throws XMLStreamException, XPathException {
-
String result = "";
try( final StringReader sr = new StringReader( xml ) ) {
+ final XMLEventReader reader = createXmlEventReader( sr );
boolean found = false;
int count = 0;
- final XMLEventReader reader = createXMLEventReader( sr );
// If the processing instruction wasn't found in the first 10 lines,
// fail fast. This should iterate twice through the loop.
while( !found && reader.hasNext() && count++ < 10 ) {
- final XMLEvent event = reader.nextEvent();
+ final var event = reader.nextEvent();
if( event.isProcessingInstruction() ) {
- final ProcessingInstruction pi = (ProcessingInstruction) event;
- final String target = pi.getTarget();
+ final var pi = (ProcessingInstruction) event;
+ final var target = pi.getTarget();
if( "xml-stylesheet".equalsIgnoreCase( target ) ) {
}
- private XMLEventReader createXMLEventReader( final Reader reader )
+ private XMLEventReader createXmlEventReader( final Reader reader )
throws XMLStreamException {
- return getXMLInputFactory().createXMLEventReader( reader );
- }
-
- private synchronized XMLInputFactory getXMLInputFactory() {
- if( this.xmlInputFactory == null ) {
- this.xmlInputFactory = createXMLInputFactory();
- }
-
- return this.xmlInputFactory;
- }
-
- private XMLInputFactory createXMLInputFactory() {
- return XMLInputFactory.newInstance();
+ return mXmlInputFactory.createXMLEventReader( reader );
}
private synchronized TransformerFactory getTransformerFactory() {
- if( this.transformerFactory == null ) {
- this.transformerFactory = createTransformerFactory();
- }
-
- return this.transformerFactory;
- }
-
- /**
- * Returns a high-performance XSLT 2 transformation engine.
- *
- * @return An XSL transforming engine.
- */
- private TransformerFactory createTransformerFactory() {
- final TransformerFactory factory = new TransformerFactoryImpl();
-
- // Bubble problems up to the user interface, rather than standard error.
- factory.setErrorListener( this );
-
- return factory;
+ return mTransformerFactory;
}
public void fatalError( final TransformerException ex ) {
throw new RuntimeException( ex );
- }
-
- private void setPath( final Path path ) {
- this.path = path;
- }
-
- private Path getPath() {
- return this.path;
- }
-
- private Snitch getSnitch() {
- return this.snitch;
}
}