Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/keenwrite.git
src/main/java/com/keenwrite/cmdline/Arguments.java
import static java.lang.String.format;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.nio.file.Files.*;
/**
names = { "--curl-quotes" },
description =
- "Encode quotation marks (regular, modifier, apos, aposhex, quote, quotehex)",
+ "Encode quotation marks (see docs)",
+ paramLabel = "String",
defaultValue = "regular"
)
names = { "-i", "--input" },
description =
- "Source document file path",
+ "Source document path",
paramLabel = "PATH",
defaultValue = "stdin",
required = true
)
private Path mSourcePath;
+
+ @CommandLine.Option(
+ names = { "--html-head" },
+ description =
+ "Document fragment to append to HTML head element",
+ paramLabel = "PATH",
+ defaultValue = "",
+ required = true
+ )
+ private Path mHtmlHeadPath;
+
+ @CommandLine.Option(
+ names = { "--html-foot" },
+ description =
+ "Document fragment to append to HTML body element",
+ paramLabel = "PATH",
+ defaultValue = "",
+ required = true
+ )
+ private Path mHtmlFootPath;
@CommandLine.Option(
names = { "-o", "--output" },
description =
- "Destination document file path",
+ "Destination document path",
paramLabel = "PATH",
defaultValue = "stdout",
names = { "--r-script" },
description =
- "R bootstrap script file path",
+ "R bootstrap script path",
paramLabel = "PATH"
)
names = { "-v", "--variables" },
description =
- "Variables file path",
+ "Variables path",
paramLabel = "PATH"
)
final var locale = lookupLocale( mLocale );
final var rScript = read( mRScriptPath );
+ final var htmlHead = read( mHtmlHeadPath );
+ final var htmlFoot = read( mHtmlFootPath );
return ProcessorContext
.builder()
.with( Mutator::setSourcePath, mSourcePath )
.with( Mutator::setTargetPath, mTargetPath )
+ .with( Mutator::setHtmlHead, htmlHead )
+ .with( Mutator::setHtmlFoot, htmlFoot )
.with( Mutator::setThemeDir, () -> mThemesDir )
.with( Mutator::setCacheDir, () -> mCachesDir )
private static String read( final Path path ) throws IOException {
- return path == null ? "" : Files.readString( path, UTF_8 );
+ return path == null
+ ? ""
+ : canRead( path )
+ ? readString( path, UTF_8 )
+ : "";
+ }
+
+ private static boolean canRead( final Path path ) {
+ return exists( path ) && isRegularFile( path ) && isReadable( path );
}
src/main/java/com/keenwrite/dom/DocumentConverter.java
@Override
- public void tail( final @NotNull Node node, final int depth ) {}
+ public void tail( final @NotNull Node node, final int depth ) {
+ }
};
src/main/java/com/keenwrite/dom/DocumentParser.java
import static java.nio.file.Files.write;
import static javax.xml.transform.OutputKeys.*;
-import static javax.xml.xpath.XPathConstants.NODESET;
-
-/**
- * Responsible for initializing an XML parser.
- */
-public class DocumentParser {
- private static final String LOAD_EXTERNAL_DTD =
- "http://apache.org/xml/features/nonvalidating/load-external-dtd";
- private static final String INDENT_AMOUNT =
- "{http://xml.apache.org/xslt}indent-amount";
- private static final String NAMESPACE = "http://www.w3.org/1999/xhtml";
-
- private static final ByteArrayOutputStream sWriter =
- new ByteArrayOutputStream( 65536 );
- private static final OutputStreamWriter sOutput =
- new OutputStreamWriter( sWriter, UTF_8 );
-
- /**
- * Caches {@link XPathExpression}s to avoid re-compiling.
- */
- private static final Map<String, XPathExpression> sXpaths = new HashMap<>();
-
- private static final DocumentBuilderFactory sDocumentFactory;
- private static DocumentBuilder sDocumentBuilder;
- private static Transformer sTransformer;
- private static final XPath sXpath = XPathFactory.newInstance().newXPath();
-
- public static final DOMImplementation sDomImplementation;
-
- static {
- sDocumentFactory = DocumentBuilderFactory.newInstance();
-
- sDocumentFactory.setValidating( false );
- sDocumentFactory.setAttribute( LOAD_EXTERNAL_DTD, false );
- sDocumentFactory.setNamespaceAware( true );
- sDocumentFactory.setIgnoringComments( true );
- sDocumentFactory.setIgnoringElementContentWhitespace( true );
-
- DOMImplementation domImplementation;
-
- try {
- sDocumentBuilder = sDocumentFactory.newDocumentBuilder();
- domImplementation = sDocumentBuilder.getDOMImplementation();
- sTransformer = TransformerFactory.newInstance().newTransformer();
-
- // Ensure Unicode characters (emojis) are encoded correctly.
- sTransformer.setOutputProperty( ENCODING, UTF_16.toString() );
- sTransformer.setOutputProperty( OMIT_XML_DECLARATION, "yes" );
- sTransformer.setOutputProperty( METHOD, "xml" );
- sTransformer.setOutputProperty( INDENT, "no" );
- sTransformer.setOutputProperty( INDENT_AMOUNT, "2" );
- } catch( final Exception ex ) {
- clue( ex );
- domImplementation = sDocumentBuilder.getDOMImplementation();
- }
-
- sDomImplementation = domImplementation;
- }
-
- public static Document newDocument() {
- return sDocumentBuilder.newDocument();
- }
-
- /**
- * Creates a new document object model based on the given XML document
- * string. This will return an empty document if the document could not
- * be parsed.
- *
- * @param xml The document text to convert into a DOM.
- * @return The DOM that represents the given XML data.
- */
- public static Document parse( final String xml ) {
- assert xml != null;
-
- final var input = new InputSource();
-
- try( final var reader = new StringReader( xml ) ) {
- input.setEncoding( UTF_8.toString() );
- input.setCharacterStream( reader );
-
- return sDocumentBuilder.parse( input );
- } catch( final Throwable t ) {
- clue( t );
-
- return sDocumentBuilder.newDocument();
- }
- }
-
- /**
- * Creates a well-formed XHTML document from a standard HTML document.
- *
- * @param source The HTML source document to transform.
- * @param metadata The metadata contained within the head element.
- * @param locale The localization information for the lang attribute.
- * @return The well-formed XHTML document.
- */
- public static Document create(
- final Document source,
- final Map<String, String> metadata,
- final Locale locale,
- final String pageTitle
- ) {
- final var root = source.getDocumentElement();
- root.setAttribute( "xmlns", NAMESPACE );
-
- final var doc = createXhtmlDocument();
- final var html = doc.getDocumentElement();
- final var head = createElement( doc, "head", null );
-
- html.setAttribute( "lang", locale.getLanguage() );
-
- final var encoding = createEncoding( doc, "UTF-8" );
- head.appendChild( encoding );
-
- for( final var entry : metadata.entrySet() ) {
- final var node = createMeta( doc, entry );
- head.appendChild( node );
- }
-
- final var titleText = Strings.sanitize( pageTitle );
-
- // Empty titles result in <title/>, which some browsers do not parse.
- if( !titleText.isEmpty() ) {
- final var title = createElement( doc, "title", titleText );
- head.appendChild( title );
- }
-
- html.appendChild( head );
-
- final var body = createElement( doc, "body", null );
- final var sourceBody = source.getElementsByTagName( "body" ).item( 0 );
- final var children = sourceBody.getChildNodes();
- final var count = children.getLength();
-
- for( var i = 0; i < count; i++ ) {
- body.appendChild( importNode( doc, children.item( i ) ) );
- }
-
- html.appendChild( body );
-
- return doc;
- }
-
- /**
- * Parses the given file contents into a document object model.
- *
- * @param doc The source XML document to parse.
- * @return The file as a document object model.
- * @throws IOException Could not open the document.
- * @throws SAXException Could not read the XML file content.
- */
- public static Document parse( final File doc )
- throws IOException, SAXException {
- assert doc != null;
-
- try( final var in = new FileInputStream( doc ) ) {
- return parse( in );
- }
- }
-
- /**
- * Parses the given file contents into a document object model. Callers
- * must close the stream.
- *
- * @param doc The source XML document to parse.
- * @return The {@link InputStream} converted to a document object model.
- * @throws IOException Could not open the document.
- * @throws SAXException Could not read the XML file content.
- */
- public static Document parse( final InputStream doc )
- throws IOException, SAXException {
- assert doc != null;
-
- return sDocumentBuilder.parse( doc );
- }
-
- /**
- * Allows an operation to be applied for every node in the document that
- * matches a given tag name pattern.
- *
- * @param document Document to traverse.
- * @param xpath Document elements to find via {@link XPath} expression.
- * @param consumer The consumer to call for each matching document node.
- */
- public static void visit(
- final Document document,
- final CharSequence xpath,
- final Consumer<Node> consumer ) {
- assert document != null;
- assert consumer != null;
-
- try {
- final var expr = compile( xpath );
- final var nodeSet = expr.evaluate( document, NODESET );
-
- if( nodeSet instanceof NodeList nodes ) {
- for( int i = 0, len = nodes.getLength(); i < len; i++ ) {
- consumer.accept( nodes.item( i ) );
- }
- }
- } catch( final Exception ex ) {
- clue( ex );
- }
- }
-
- public static Node createMeta(
- final Document document, final Map.Entry<String, String> entry ) {
- assert document != null;
- assert entry != null;
-
- final var node = document.createElement( "meta" );
-
- node.setAttribute( "name", entry.getKey() );
- node.setAttribute( "content", entry.getValue() );
-
- return node;
- }
-
- public static Node createEncoding(
- final Document document, final String encoding
- ) {
- assert document != null;
- assert encoding != null;
-
- final var node = document.createElement( "meta" );
-
- node.setAttribute( "http-equiv", "Content-Type" );
- node.setAttribute( "content", "text/html; charset=" + encoding );
-
- return node;
- }
-
- public static Element createElement(
- final Document doc, final String nodeName, final String nodeValue ) {
- assert doc != null;
- assert nodeName != null;
- assert !nodeName.isBlank();
-
- final var node = doc.createElement( nodeName );
-
- if( nodeValue != null ) {
- node.setTextContent( nodeValue );
- }
-
- return node;
- }
-
- public static String toString( final Document xhtml ) {
- assert xhtml != null;
-
- try( final var writer = new StringWriter() ) {
- final var result = new StreamResult( writer );
-
- transform( xhtml, result );
-
- return writer.toString();
- } catch( final Exception ex ) {
- clue( ex );
- return "";
- }
- }
-
- public static String transform( final Element root )
- throws IOException, TransformerException {
- assert root != null;
-
- try( final var writer = new StringWriter() ) {
- transform( root.getOwnerDocument(), new StreamResult( writer ) );
-
- return writer.toString();
- }
- }
-
- /**
- * Remove whitespace, comments, and XML/DOCTYPE declarations to make
- * processing work with ConTeXt.
- *
- * @param path The SVG file to process.
- * @throws Exception The file could not be processed.
- */
- public static void sanitize( final Path path ) throws Exception {
- assert path != null;
-
- // Preprocessing the SVG image is a single-threaded operation, no matter
- // how many SVG images are in the document to typeset.
- sWriter.reset();
-
- final var target = new StreamResult( sOutput );
- final var source = sDocumentBuilder.parse( toFile( path ) );
-
- transform( source, target );
- write( path, sWriter.toByteArray() );
- }
-
- /**
- * Converts a string into an {@link XPathExpression}, which may be used to
- * extract elements from a {@link Document} object model.
- *
- * @param cs The string to convert to an {@link XPathExpression}.
- * @return {@code null} if there was an error compiling the xpath.
- */
- public static XPathExpression compile( final CharSequence cs ) {
- assert cs != null;
-
- final var xpath = cs.toString();
-
- return sXpaths.computeIfAbsent(
- xpath, _ -> {
- try {
- return sXpath.compile( xpath );
- } catch( final XPathExpressionException ex ) {
- clue( ex );
- return null;
- }
- }
- );
- }
-
- /**
- * Merges a source document into a target document. This avoids adding an
- * empty XML namespace attribute to elements.
- *
- * @param target The document to envelop the source document.
- * @param source The source document to embed.
- * @return The target document with the source document included.
- */
- private static Node importNode( final Document target, final Node source ) {
- assert target != null;
- assert source != null;
-
- Node result;
- final var nodeType = source.getNodeType();
-
- if( nodeType == Node.ELEMENT_NODE ) {
- final var element = target.createElementNS(
- NAMESPACE,
- source.getNodeName()
- );
-
- final var attrs = source.getAttributes();
-
- if( attrs != null ) {
- final var attrLength = attrs.getLength();
-
- for( var i = 0; i < attrLength; i++ ) {
- final var attr = attrs.item( i );
- element.setAttribute( attr.getNodeName(), attr.getNodeValue() );
- }
- }
-
- final var children = source.getChildNodes();
- final var childLength = children.getLength();
-
- for( var i = 0; i < childLength; i++ ) {
- final var child = importNode(
- target,
- children.item( i )
- );
- element.appendChild( child );
- }
-
- result = element;
- }
- else if( nodeType == Node.TEXT_NODE ) {
- result = target.createTextNode( source.getNodeValue() );
- }
- else {
- result = target.importNode( source, true );
- }
-
- return result;
- }
-
- private static Document createXhtmlDocument() {
- return sDomImplementation.createDocument(
- NAMESPACE,
- "html",
- sDomImplementation.createDocumentType(
- "html", "-//W3C//DTD XHTML 1.0 Strict//EN",
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
- )
- );
- }
-
- /**
- * Streams an instance of {@link Document} as a plain text XML document.
- *
- * @param src The source document to transform.
- * @param dst The destination location to write the transformed version.
- * @throws TransformerException Could not transform the document.
- */
- private static void transform( final Document src, final StreamResult dst )
+import static javax.xml.xpath.XPathConstants.NODE;
+import static javax.xml.xpath.XPathConstants.NODESET;
+
+/**
+ * Responsible for initializing an XML parser.
+ */
+public class DocumentParser {
+ private static final String LOAD_EXTERNAL_DTD =
+ "http://apache.org/xml/features/nonvalidating/load-external-dtd";
+ private static final String INDENT_AMOUNT =
+ "{http://xml.apache.org/xslt}indent-amount";
+ private static final String NAMESPACE = "http://www.w3.org/1999/xhtml";
+
+ private static final XPath XPATH = XPathFactory.newInstance().newXPath();
+
+ private static final ByteArrayOutputStream sWriter =
+ new ByteArrayOutputStream( 65536 );
+ private static final OutputStreamWriter sOutput =
+ new OutputStreamWriter( sWriter, UTF_8 );
+
+ /**
+ * Caches {@link XPathExpression}s to avoid re-compiling.
+ */
+ private static final Map<String, XPathExpression> sXpaths = new HashMap<>();
+
+ private static final DocumentBuilderFactory sDocumentFactory;
+ private static DocumentBuilder sDocumentBuilder;
+ private static Transformer sTransformer;
+ private static final XPath sXpath = XPathFactory.newInstance().newXPath();
+
+ public static final DOMImplementation sDomImplementation;
+
+ static {
+ sDocumentFactory = DocumentBuilderFactory.newInstance();
+
+ sDocumentFactory.setValidating( false );
+ sDocumentFactory.setAttribute( LOAD_EXTERNAL_DTD, false );
+ sDocumentFactory.setNamespaceAware( false );
+ sDocumentFactory.setIgnoringComments( true );
+ sDocumentFactory.setIgnoringElementContentWhitespace( true );
+
+ DOMImplementation domImplementation;
+
+ try {
+ sDocumentBuilder = sDocumentFactory.newDocumentBuilder();
+ domImplementation = sDocumentBuilder.getDOMImplementation();
+ sTransformer = TransformerFactory.newInstance().newTransformer();
+
+ // Ensure Unicode characters (emojis) are encoded correctly.
+ sTransformer.setOutputProperty( ENCODING, UTF_16.toString() );
+ sTransformer.setOutputProperty( OMIT_XML_DECLARATION, "yes" );
+ sTransformer.setOutputProperty( METHOD, "xml" );
+ sTransformer.setOutputProperty( INDENT, "no" );
+ sTransformer.setOutputProperty( INDENT_AMOUNT, "2" );
+ }
+ catch( final Exception ex ) {
+ clue( ex );
+ domImplementation = sDocumentBuilder.getDOMImplementation();
+ }
+
+ sDomImplementation = domImplementation;
+ }
+
+ public static Document newDocument() {
+ return sDocumentBuilder.newDocument();
+ }
+
+ /**
+ * Creates a new document object model based on the given XML document
+ * string. This will return an empty document if the document could not
+ * be parsed.
+ *
+ * @param xml The document text to convert into a DOM.
+ * @return The DOM that represents the given XML data.
+ */
+ public static Document parse( final String xml ) {
+ assert xml != null;
+
+ if( !xml.isBlank() ) {
+ try( final var reader = new StringReader( xml ) ) {
+ final var input = new InputSource();
+
+ input.setEncoding( UTF_8.toString() );
+ input.setCharacterStream( reader );
+
+ return sDocumentBuilder.parse( input );
+ }
+ catch( final Throwable t ) {
+ clue( t );
+ }
+ }
+
+ return sDocumentBuilder.newDocument();
+ }
+
+ /**
+ * Creates a well-formed XHTML document from a standard HTML document.
+ *
+ * @param source The HTML source document to transform.
+ * @param metadata The metadata contained within the head element.
+ * @param locale The localization information for the lang attribute.
+ * @return The well-formed XHTML document.
+ */
+ public static Document create(
+ final Document source,
+ final Map<String, String> metadata,
+ final Locale locale,
+ final String pageTitle
+ ) throws XPathExpressionException {
+ final var target = createXhtmlDocument();
+ final var html = target.getDocumentElement();
+ final var sourceHead = evaluate( "/html/head", source );
+ final var head = target.importNode( sourceHead, true );
+
+ html.setAttribute( "lang", locale.getLanguage() );
+
+ final var encoding = createEncoding( target, "UTF-8" );
+ head.appendChild( encoding );
+
+ for( final var entry : metadata.entrySet() ) {
+ final var node = createMeta( target, entry );
+ head.appendChild( node );
+ }
+
+ final var titleText = Strings.sanitize( pageTitle );
+
+ // Empty titles result in <title/>, which some browsers cannot parse.
+ if( !titleText.isEmpty() ) {
+ final var title = createElement( target, "title", titleText );
+ head.appendChild( title );
+ }
+
+ html.appendChild( head );
+
+ final var body = createElement( target, "body", null );
+ final var sourceBody = source.getElementsByTagName( "body" ).item( 0 );
+ final var children = sourceBody.getChildNodes();
+ final var count = children.getLength();
+
+ for( var i = 0; i < count; i++ ) {
+ body.appendChild( importNode( target, children.item( i ) ) );
+ }
+
+ html.appendChild( body );
+
+ return target;
+ }
+
+ public static Node evaluate( final String xpath, final Document doc ) throws XPathExpressionException {
+ return (Node) XPATH.evaluate( xpath, doc, NODE );
+ }
+
+ /**
+ * Parses the given file contents into a document object model.
+ *
+ * @param doc The source XML document to parse.
+ * @return The file as a document object model.
+ * @throws IOException Could not open the document.
+ * @throws SAXException Could not read the XML file content.
+ */
+ public static Document parse( final File doc )
+ throws IOException, SAXException {
+ assert doc != null;
+
+ try( final var in = new FileInputStream( doc ) ) {
+ return parse( in );
+ }
+ }
+
+ /**
+ * Parses the given file contents into a document object model. Callers
+ * must close the stream.
+ *
+ * @param doc The source XML document to parse.
+ * @return The {@link InputStream} converted to a document object model.
+ * @throws IOException Could not open the document.
+ * @throws SAXException Could not read the XML file content.
+ */
+ public static Document parse( final InputStream doc )
+ throws IOException, SAXException {
+ assert doc != null;
+
+ return sDocumentBuilder.parse( doc );
+ }
+
+ /**
+ * Allows an operation to be applied for every node in the document that
+ * matches a given tag name pattern.
+ *
+ * @param document Document to traverse.
+ * @param xpath Document elements to find via {@link XPath} expression.
+ * @param consumer The consumer to call for each matching document node.
+ */
+ public static void visit(
+ final Document document,
+ final CharSequence xpath,
+ final Consumer<Node> consumer ) {
+ assert document != null;
+ assert consumer != null;
+
+ try {
+ final var expr = compile( xpath );
+ final var nodeSet = expr.evaluate( document, NODESET );
+
+ if( nodeSet instanceof NodeList nodes ) {
+ for( int i = 0, len = nodes.getLength(); i < len; i++ ) {
+ consumer.accept( nodes.item( i ) );
+ }
+ }
+ }
+ catch( final Exception ex ) {
+ clue( ex );
+ }
+ }
+
+ public static Node createMeta(
+ final Document document, final Map.Entry<String, String> entry ) {
+ assert document != null;
+ assert entry != null;
+
+ final var node = createElement( document, "meta", null );
+
+ node.setAttribute( "name", entry.getKey() );
+ node.setAttribute( "content", entry.getValue() );
+
+ return node;
+ }
+
+ public static Node createEncoding(
+ final Document document, final String encoding
+ ) {
+ assert document != null;
+ assert encoding != null;
+
+ final var node = createElement( document, "meta", null );
+
+ node.setAttribute( "http-equiv", "Content-Type" );
+ node.setAttribute( "content", "text/html; charset=" + encoding );
+
+ return node;
+ }
+
+ public static Element createElement(
+ final Document document, final String nodeName, final String nodeValue
+ ) {
+ assert document != null;
+ assert nodeName != null;
+ assert !nodeName.isBlank();
+
+ final var node = document.createElement( nodeName );
+
+ if( nodeValue != null ) {
+ node.setTextContent( nodeValue );
+ }
+
+ return node;
+ }
+
+ public static String toString( final Node xhtml ) {
+ assert xhtml != null;
+
+ try( final var writer = new StringWriter() ) {
+ final var result = new StreamResult( writer );
+
+ transform( xhtml, result );
+
+ return writer.toString();
+ }
+ catch( final Exception ex ) {
+ clue( ex );
+ return "";
+ }
+ }
+
+ public static String transform( final Element root )
+ throws IOException, TransformerException {
+ assert root != null;
+
+ try( final var writer = new StringWriter() ) {
+ transform( root.getOwnerDocument(), new StreamResult( writer ) );
+
+ return writer.toString();
+ }
+ }
+
+ /**
+ * Remove whitespace, comments, and XML/DOCTYPE declarations to make
+ * processing work with ConTeXt.
+ *
+ * @param path The SVG file to process.
+ * @throws Exception The file could not be processed.
+ */
+ public static void sanitize( final Path path ) throws Exception {
+ assert path != null;
+
+ // Preprocessing the SVG image is a single-threaded operation, no matter
+ // how many SVG images are in the document to typeset.
+ sWriter.reset();
+
+ final var target = new StreamResult( sOutput );
+ final var source = sDocumentBuilder.parse( toFile( path ) );
+
+ transform( source, target );
+ write( path, sWriter.toByteArray() );
+ }
+
+ /**
+ * Converts a string into an {@link XPathExpression}, which may be used to
+ * extract elements from a {@link Document} object model.
+ *
+ * @param cs The string to convert to an {@link XPathExpression}.
+ * @return {@code null} if there was an error compiling the xpath.
+ */
+ public static XPathExpression compile( final CharSequence cs ) {
+ assert cs != null;
+
+ final var xpath = cs.toString();
+
+ return sXpaths.computeIfAbsent(
+ xpath, _ -> {
+ try {
+ return sXpath.compile( xpath );
+ }
+ catch( final XPathExpressionException ex ) {
+ clue( ex );
+ return null;
+ }
+ }
+ );
+ }
+
+ /**
+ * Merges a source document into a target document. This avoids adding an
+ * empty XML namespace attribute to elements.
+ *
+ * @param target The document to envelop the source document.
+ * @param source The source document to embed.
+ * @return The target document with the source document included.
+ */
+ private static Node importNode( final Document target, final Node source ) {
+ assert target != null;
+ assert source != null;
+
+ Node result;
+ final var nodeType = source.getNodeType();
+
+ if( nodeType == Node.ELEMENT_NODE ) {
+ final var element = createElement( target, source.getNodeName(), null );
+ final var attrs = source.getAttributes();
+
+ if( attrs != null ) {
+ final var attrLength = attrs.getLength();
+
+ for( var i = 0; i < attrLength; i++ ) {
+ final var attr = attrs.item( i );
+ element.setAttribute( attr.getNodeName(), attr.getNodeValue() );
+ }
+ }
+
+ final var children = source.getChildNodes();
+ final var childLength = children.getLength();
+
+ for( var i = 0; i < childLength; i++ ) {
+ element.appendChild( importNode( target, children.item( i ) ) );
+ }
+
+ result = element;
+ }
+ else if( nodeType == Node.TEXT_NODE ) {
+ result = target.createTextNode( source.getNodeValue() );
+ }
+ else {
+ result = target.importNode( source, true );
+ }
+
+ return result;
+ }
+
+ private static Document createXhtmlDocument() {
+ return sDomImplementation.createDocument(
+ NAMESPACE,
+ "html",
+ sDomImplementation.createDocumentType(
+ "html", "-//W3C//DTD XHTML 1.0 Strict//EN",
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
+ )
+ );
+ }
+
+ /**
+ * Streams an instance of {@link Document} as a plain text XML document.
+ *
+ * @param src The source document to transform.
+ * @param dst The destination location to write the transformed version.
+ * @throws TransformerException Could not transform the document.
+ */
+ private static void transform( final Node src, final StreamResult dst )
throws TransformerException {
sTransformer.transform( new DOMSource( src ), dst );
src/main/java/com/keenwrite/processors/ProcessorContext.java
private Path mSourcePath;
private Path mTargetPath;
+ private String mHtmlHead = "";
+ private String mHtmlFoot = "";
private ExportFormat mExportFormat;
private Supplier<Boolean> mConcatenate = () -> true;
private Supplier<Boolean> mAutoRemove = () -> true;
- public void setSourcePath( final Path sourcePath ) {
- assert sourcePath != null;
- mSourcePath = sourcePath;
+ public void setSourcePath( final Path path ) {
+ assert path != null;
+ mSourcePath = path;
}
- public void setTargetPath( final Path outputPath ) {
- assert outputPath != null;
- mTargetPath = outputPath;
+ public void setTargetPath( final Path path ) {
+ assert path != null;
+ mTargetPath = path;
+ }
+
+ public void setHtmlHead( final String text ) {
+ assert text != null;
+ mHtmlHead = text;
+ }
+
+ public void setHtmlFoot( final String text ) {
+ assert text != null;
+ mHtmlFoot = text;
}
public Path getTargetPath() {
return mMutator.mTargetPath;
+ }
+
+ public String getHtmlHead() {
+ assert mMutator.mHtmlHead != null;
+
+ return mMutator.mHtmlHead;
+ }
+
+ public String getHtmlFoot() {
+ assert mMutator.mHtmlFoot != null;
+
+ return mMutator.mHtmlFoot;
}
src/main/java/com/keenwrite/processors/html/XhtmlProcessor.java
import com.keenwrite.collections.InterpolatingMap;
+import com.keenwrite.dom.DocumentConverter;
import com.keenwrite.dom.DocumentParser;
-import com.keenwrite.events.StatusEvent;
import com.keenwrite.io.MediaTypeExtension;
import com.keenwrite.processors.ExecutorProcessor;
import com.keenwrite.processors.Processor;
import com.keenwrite.processors.ProcessorContext;
import com.keenwrite.ui.heuristics.WordCounter;
import com.keenwrite.util.DataTypeConverter;
import com.whitemagicsoftware.keenquotes.parser.Apostrophe;
-import com.whitemagicsoftware.keenquotes.parser.Contractions;
import org.w3c.dom.Document;
"\"-//W3C//DTD XHTML 1.0 Transitional//EN\" " +
"\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">";
+
+ private static final DocumentConverter CONVERTER = new DocumentConverter();
private final ProcessorContext mContext;
// Download into a cache directory, which can be written to
- // without
- // any possibility of overwriting local image files. Further, the
- // filenames are hashed as a second layer of protection.
+ // without any possibility of overwriting local image files.
+ // Further, the filenames are hashed as a second layer of
+ // protection.
if( getProtocol( src ).isRemote() ) {
location = downloadImage( src );
attr.setTextContent( relative.toString() );
}
- } catch( final Exception ex ) {
+ }
+ catch( final Exception ex ) {
clue( ex );
}
}
);
+
+ final var headText = mContext.getHtmlHead();
+ final var footText = mContext.getHtmlFoot();
+
+ append( headText, doc, "/html/head" );
+ append( footText, doc, "/html/body" );
final var map = mContext.getInterpolatedDefinitions();
return curler.apply( document );
- } catch( final Exception ex ) {
+ }
+ catch( final Exception ex ) {
clue( ex );
}
return html;
+ }
+
+ public void append( final String html, Document target, String path ) {
+ try {
+ final var source = DocumentConverter.parse( html );
+ final var sourceBody = source.head();
+ final var sourceDoc = CONVERTER.fromJsoup( sourceBody );
+
+ final var targetNode = DocumentParser.evaluate( path, target );
+ final var sourceNode = DocumentParser.evaluate( "/html/body", sourceDoc );
+ final var children = sourceNode.getChildNodes();
+
+ for( var i = 0; i < children.getLength(); i++ ) {
+ final var childNode = children.item( i );
+ final var importedNode = target.importNode( childNode, true );
+
+ targetNode.appendChild( importedNode );
+ }
+ }
+ catch( Exception ex ) {
+ clue( ex );
+ }
}
+
/**
return valueOf( WordCounter.create( getLocale() ).count( sb.toString() ) );
- }
-
- /**
- * Creates contracts with a custom set of unambiguous strings.
- *
- * @return List of contractions to use for curling straight quotes.
- */
- private static Contractions createContractions() {
- return new Contractions.Builder().build();
}
}

Injects HTML head and foot content into XHTML document

Author DaveJarvis <email>
Date 2025-08-22 19:35:33 GMT-0700
Commit 6facb3d4076811f3be32728948f7e35658a9eb7b
Parent a4c9d66
Delta 508 lines added, 421 lines removed, 87-line increase