| implementation "io.sf.carte:echosvg-gvt:${v_echosvg}" | ||
| implementation "io.sf.carte:echosvg-parser:${v_echosvg}" | ||
| - implementation "io.sf.carte:echosvg-script:${v_echosvg}" | ||
| implementation "io.sf.carte:echosvg-svg-dom:${v_echosvg}" | ||
| implementation "io.sf.carte:echosvg-svggen:${v_echosvg}" | ||
| implementation "io.sf.carte:echosvg-transcoder:${v_echosvg}" | ||
| implementation "io.sf.carte:echosvg-util:${v_echosvg}" | ||
| implementation "io.sf.carte:echosvg-xml:${v_echosvg}" | ||
| // Misc. | ||
| implementation 'org.ahocorasick:ahocorasick:0.6.3' | ||
| - implementation 'org.apache.commons:commons-configuration2:2.9.0' | ||
| + implementation 'org.apache.commons:commons-lang3:3.14.0' | ||
| implementation 'com.github.albfernandez:juniversalchardet:2.4.0' | ||
| implementation 'jakarta.validation:jakarta.validation-api:3.0.2' | ||
| testImplementation 'org.assertj:assertj-core:3.24.2' | ||
| testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' | ||
| +} | ||
| + | ||
| +configurations { | ||
| + all*.exclude group: 'org.mozilla', module: 'rhino' | ||
| } | ||
| useJUnitPlatform() | ||
| - doFirst { jvmArgs = moduleSecurity } | ||
| + doFirst { jvmArgs += moduleSecurity } | ||
| testLogging { exceptionFormat = 'full' } | ||
| } | ||
| readonly OPT_JAVA=$(cat << END_OF_ARGS | ||
| +-Dprism.order=sw \ | ||
| +--enable-preview \ | ||
| --add-opens=javafx.controls/javafx.scene.control=ALL-UNNAMED \ | ||
| --add-opens=javafx.controls/javafx.scene.control.skin=ALL-UNNAMED \ |
| java \ | ||
| + -Dprism.order=sw \ | ||
| + --enable-preview \ | ||
| --add-opens=javafx.controls/javafx.scene.control=ALL-UNNAMED \ | ||
| --add-opens=javafx.controls/javafx.scene.control.skin=ALL-UNNAMED \ |
| package com.keenwrite.collections; | ||
| +import com.keenwrite.sigils.PropertyKeyOperator; | ||
| import com.keenwrite.sigils.SigilKeyOperator; | ||
| private transient final SigilKeyOperator mOperator; | ||
| + | ||
| + /** | ||
| + * Creates a new interpolating map using the {@link PropertyKeyOperator}. | ||
| + */ | ||
| + public InterpolatingMap() { | ||
| + this( new PropertyKeyOperator() ); | ||
| + } | ||
| /** | ||
| @Override | ||
| public boolean equals( final Object o ) { | ||
| - if( this == o ) { return true; } | ||
| - if( o == null || getClass() != o.getClass() ) { return false; } | ||
| - if( !super.equals( o ) ) { return false; } | ||
| + if( this == o ) { | ||
| + return true; | ||
| + } | ||
| + | ||
| + if( o == null || getClass() != o.getClass() ) { | ||
| + return false; | ||
| + } | ||
| + | ||
| + if( !super.equals( o ) ) { | ||
| + return false; | ||
| + } | ||
| + | ||
| final InterpolatingMap that = (InterpolatingMap) o; | ||
| return Objects.equals( mOperator, that.mOperator ); | ||
| +/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. | ||
| + * | ||
| + * SPDX-License-Identifier: MIT | ||
| + */ | ||
| +package com.keenwrite.config; | ||
| + | ||
| +import com.keenwrite.collections.InterpolatingMap; | ||
| + | ||
| +import java.io.IOException; | ||
| +import java.io.Reader; | ||
| +import java.util.*; | ||
| + | ||
| +import static java.util.Arrays.*; | ||
| + | ||
| +/** | ||
| + * Responsible for reading and interpolating properties files. | ||
| + */ | ||
| +public class PropertiesConfiguration { | ||
| + private static final String VALUE_SEPARATOR = ","; | ||
| + | ||
| + private final InterpolatingMap mMap = new InterpolatingMap(); | ||
| + | ||
| + public PropertiesConfiguration() {} | ||
| + | ||
| + public void read( final Reader reader ) throws IOException { | ||
| + final var properties = new Properties(); | ||
| + properties.load( reader ); | ||
| + | ||
| + for( final var name : properties.stringPropertyNames() ) { | ||
| + mMap.put( name, properties.getProperty( name ) ); | ||
| + } | ||
| + | ||
| + mMap.interpolate(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the value of a string property. | ||
| + * | ||
| + * @param property The property key. | ||
| + * @param defaultValue The value to return if no property key has been set. | ||
| + * @return The property key value, or defaultValue when no key found. | ||
| + */ | ||
| + public String getString( final String property, final String defaultValue ) { | ||
| + assert property != null; | ||
| + | ||
| + return mMap.getOrDefault( property, defaultValue ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the value of a string property. | ||
| + * | ||
| + * @param property The property key. | ||
| + * @param defaultValue The value to return if no property key has been set. | ||
| + * @return The property key value, or defaultValue when no key found. | ||
| + */ | ||
| + public int getInt( final String property, final int defaultValue ) { | ||
| + assert property != null; | ||
| + | ||
| + return parse( mMap.get( property ), defaultValue ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Convert the generic list of property objects into strings. | ||
| + * | ||
| + * @param property The property value to coerce. | ||
| + * @param defaults The values to use should the property be unset. | ||
| + * @return The list of properties coerced from objects to strings. | ||
| + */ | ||
| + public List<String> getList( | ||
| + final String property, final List<String> defaults ) { | ||
| + assert property != null; | ||
| + | ||
| + final var value = mMap.get( property ); | ||
| + | ||
| + return value == null | ||
| + ? defaults | ||
| + : asList( value.split( VALUE_SEPARATOR ) ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns a list of property names that begin with the given prefix. | ||
| + * Note that the prefix must be separated from other values with a | ||
| + * period. | ||
| + * | ||
| + * @param prefix The prefix to compare against each property name. When | ||
| + * comparing, the prefix value will have a period appended. | ||
| + * @return The list of property names that have the given prefix. | ||
| + */ | ||
| + public Iterator<String> getKeys( final String prefix ) { | ||
| + assert prefix != null; | ||
| + | ||
| + final var result = new HashMap<String, String>(); | ||
| + final var prefixDotted = prefix + '.'; | ||
| + | ||
| + for( final var entry : mMap.entrySet() ) { | ||
| + final var key = entry.getKey(); | ||
| + | ||
| + if( key.startsWith( prefixDotted ) ) { | ||
| + final var value = entry.getValue(); | ||
| + result.put( key, value ); | ||
| + } | ||
| + } | ||
| + | ||
| + return result.keySet().iterator(); | ||
| + } | ||
| + | ||
| + private static int parse( final String s, final int defaultValue ) { | ||
| + try { | ||
| + return s == null || s.isBlank() ? defaultValue : Integer.parseInt( s ); | ||
| + } catch( final NumberFormatException e ) { | ||
| + return defaultValue; | ||
| + } | ||
| + } | ||
| +} | ||
| if( !box.getStyle().isInline() ) { | ||
| final var margin = box.getMargin( getLayoutContext() ); | ||
| - y += margin.top(); | ||
| - x += margin.left(); | ||
| + y += (int) margin.top(); | ||
| + x += (int) margin.left(); | ||
| } | ||
| mScrollLockButton.setMargin( new Insets( 1, 0, 0, 0 ) ); | ||
| mScrollLockButton.addActionListener( | ||
| - e -> fireScrollLockEvent( !mScrollLocked ) | ||
| + _ -> fireScrollLockEvent( !mScrollLocked ) | ||
| ); | ||
| */ | ||
| void opening( final HtmlWriter writer ) { | ||
| - writer.raw( "<p><span class=\"caption\">" ); | ||
| + writer.raw( "<span class=\"caption\">" ); | ||
| } | ||
| /** | ||
| * Closes the caption. | ||
| * | ||
| * @param writer Where to write the closing tags. | ||
| */ | ||
| void closing( final HtmlWriter writer ) { | ||
| - writer.raw( "</span></p>" ); | ||
| + writer.raw( "</span>" ); | ||
| } | ||
| package com.keenwrite.processors.markdown.extensions.captions; | ||
| +import com.keenwrite.processors.markdown.extensions.references.CrossReferenceNode; | ||
| import com.vladsch.flexmark.html.HtmlWriter; | ||
| import com.vladsch.flexmark.html.renderer.CoreNodeRenderer; | ||
| import com.vladsch.flexmark.html.renderer.NodeRendererContext; | ||
| import com.vladsch.flexmark.html.renderer.NodeRenderingHandler; | ||
| +import com.vladsch.flexmark.util.ast.Node; | ||
| import com.vladsch.flexmark.util.data.DataHolder; | ||
| import java.util.HashSet; | ||
| +import java.util.LinkedList; | ||
| import java.util.List; | ||
| import java.util.Set; | ||
| final NodeRendererContext context, | ||
| final HtmlWriter html ) { | ||
| + final var anchors = new LinkedList<Node>(); | ||
| + | ||
| + html.raw( "<p>" ); | ||
| node.opening( html ); | ||
| if( node.hasChildren() ) { | ||
| - context.renderChildren( node ); | ||
| + for( final var child : node.getChildren() ) { | ||
| + if( !child.isOrDescendantOfType( CrossReferenceNode.class ) ) { | ||
| + context.render( child ); | ||
| + } | ||
| + else { | ||
| + anchors.add( child ); | ||
| + } | ||
| + } | ||
| } | ||
| node.closing( html ); | ||
| + | ||
| + for( final var anchor : anchors ) { | ||
| + context.render( anchor ); | ||
| + } | ||
| + | ||
| + html.raw( "</p>" ); | ||
| } | ||
| } | ||
| * Responsible for generating anchor links, either named or cross-referenced. | ||
| */ | ||
| -interface CrossReferenceNode { | ||
| +public interface CrossReferenceNode { | ||
| String getTypeName(); | ||
| final var attr = getRefAttrName(); | ||
| - html.raw( STR. "<a data-type=\"\{ type }\" \{ attr }=\"\{ id }\" />" ); | ||
| + final var clazz = STR. "class=\"\{ attr }\"" ; | ||
| + final var dataType = STR. "data-type=\"\{ type }\"" ; | ||
| + final var refId = STR. "\{ attr }=\"\{ id }\"" ; | ||
| + | ||
| + html.raw( STR. "<a \{ clazz } \{ dataType } \{ refId } />" ); | ||
| } | ||
| } | ||
| package com.keenwrite.service.impl; | ||
| +import com.keenwrite.config.PropertiesConfiguration; | ||
| import com.keenwrite.service.Settings; | ||
| -import org.apache.commons.configuration2.PropertiesConfiguration; | ||
| -import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler; | ||
| -import org.apache.commons.configuration2.convert.ListDelimiterHandler; | ||
| import java.io.InputStreamReader; | ||
| */ | ||
| public final class DefaultSettings implements Settings { | ||
| - | ||
| - private static final char VALUE_SEPARATOR = ','; | ||
| private final PropertiesConfiguration mProperties = loadProperties(); | ||
| - public DefaultSettings() { | ||
| - } | ||
| + public DefaultSettings() {} | ||
| /** | ||
| * | ||
| * @param property The property value to coerce. | ||
| - * @param defaults The defaults values to use should the property be unset. | ||
| + * @param defaults The values to use should the property be unset. | ||
| * @return The list of properties coerced from objects to strings. | ||
| */ | ||
| @Override | ||
| public List<String> getStringSettingList( | ||
| - final String property, final List<String> defaults ) { | ||
| - return getSettings().getList( String.class, property, defaults ); | ||
| + final String property, final List<String> defaults ) { | ||
| + return getSettings().getList( property, defaults ); | ||
| } | ||
| final var url = getPropertySource(); | ||
| final var configuration = new PropertiesConfiguration(); | ||
| + final var encoding = getDefaultEncoding(); | ||
| if( url != null ) { | ||
| try( final var reader = new InputStreamReader( | ||
| - url.openStream(), getDefaultEncoding() ) ) { | ||
| - configuration.setListDelimiterHandler( createListDelimiterHandler() ); | ||
| + url.openStream(), encoding ) ) { | ||
| configuration.read( reader ); | ||
| } catch( final Exception ex ) { | ||
| private Charset getDefaultEncoding() { | ||
| return Charset.defaultCharset(); | ||
| - } | ||
| - | ||
| - private ListDelimiterHandler createListDelimiterHandler() { | ||
| - return new DefaultListDelimiterHandler( VALUE_SEPARATOR ); | ||
| } | ||
| } | ||
| -div.bubblerx:after, div.bubbletx:after { | ||
| +div.bubblerx::after, div.bubbletx::after { | ||
| content: ""; | ||
| position: absolute; | ||
| } | ||
| -div.bubbletx:after { | ||
| +div.bubbletx::after { | ||
| right: -1em; | ||
| border-left: 1em solid #ccc; | ||
| } | ||
| +/* TYPEWRITER ***/ | ||
| div.typewritten { | ||
| font-family: monospace; | ||
| font-size: 16px; | ||
| font-weight: bold; | ||
| } | ||
| - | ||
| """, | ||
| """ | ||
| - <p><a data-type="fig" name="cats" /> <a data-type="fig" href="#cats" /> | ||
| - <a data-type="table" name="dogs" /> <a data-type="table" href="#dogs" /> | ||
| - <a data-type="ocean" name="whale-01" /> <a data-type="ocean" href="#whale-02" /></p> | ||
| + <p><a class="name" data-type="fig" name="cats" /> <a class="href" data-type="fig" href="#cats" /> | ||
| + <a class="name" data-type="table" name="dogs" /> <a class="href" data-type="table" href="#dogs" /> | ||
| + <a class="name" data-type="ocean" name="whale-01" /> <a class="href" data-type="ocean" href="#whale-02" /></p> | ||
| """ | ||
| ), | ||
| args( | ||
| """ | ||
| {#日本:w0mbatß} | ||
| [@日本:w0mbatß] | ||
| """, | ||
| """ | ||
| - <p><a data-type="日本" name="w0mbatß" /> | ||
| - <a data-type="日本" href="#w0mbatß" /></p> | ||
| + <p><a class="name" data-type="日本" name="w0mbatß" /> | ||
| + <a class="href" data-type="日本" href="#w0mbatß" /></p> | ||
| """ | ||
| ), | ||
| """ | ||
| <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. | ||
| - <a data-type="fig" name="cats" /> Sed do eiusmod tempor incididunt ut | ||
| + <a class="name" data-type="fig" name="cats" /> Sed do eiusmod tempor incididunt ut | ||
| labore et dolore magna aliqua. Ut enim ad minim veniam, | ||
| quis nostrud exercitation ullamco laboris nisi ut aliquip | ||
| - ex ea commodo consequat. <a data-type="fig" href="#cats" /></p> | ||
| + ex ea commodo consequat. <a class="href" data-type="fig" href="#cats" /></p> | ||
| """ | ||
| ), | ||
| """, | ||
| """ | ||
| - <p><a data-type="note" name="advancement" /> Advancement isn't | ||
| + <p><a class="name" data-type="note" name="advancement" /> Advancement isn't | ||
| measured by the ingenuity of inventions, but by humanity's ability | ||
| to anticipate and forfend dire aftermaths <em>before</em> using them.</p> | ||
| - <p><a data-type="note" href="#advancement" /></p> | ||
| + <p><a class="href" data-type="note" href="#advancement" /></p> | ||
| <p>To what end?</p> | ||
| """ | ||
| ), | ||
| args( | ||
| """ | ||
| $E=mc^2$ {#eq:label} | ||
| """, | ||
| """ | ||
| - <p><tex>$E=mc^2$</tex> <a data-type="eq" name="label" /></p> | ||
| + <p><tex>$E=mc^2$</tex> <a class="name" data-type="eq" name="label" /></p> | ||
| """ | ||
| ), | ||
| args( | ||
| """ | ||
| $$E=mc^2$$ {#eq:label} | ||
| """, | ||
| """ | ||
| - <p><tex>$$E=mc^2$$</tex> <a data-type="eq" name="label" /></p> | ||
| + <p><tex>$$E=mc^2$$</tex> <a class="name" data-type="eq" name="label" /></p> | ||
| """ | ||
| ), | ||
| args( | ||
| """ | ||
| $$E=mc^2$$ | ||
| :: Caption {#eqn:energy} | ||
| """, | ||
| """ | ||
| - <p><span class="caption">Caption <a data-type="eqn" name="energy" /></span></p> | ||
| + <p><span class="caption">Caption </span><a class="name" data-type="eqn" name="energy" /></p> | ||
| <p><tex>$$E=mc^2$$</tex></p> | ||
| """ | ||
| """, | ||
| """ | ||
| - <p><span class="caption">Source code caption <a data-type="listing" name="haskell1" /></span></p> | ||
| + <p><span class="caption">Source code caption </span><a class="name" data-type="listing" name="haskell1" /></p> | ||
| <pre><code class="language-haskell">main :: IO () | ||
| </code></pre> | ||
| """, | ||
| """ | ||
| - <p><span class="caption">Caption <a data-type="warning" name="sugar" /></span></p><div class="warning"> | ||
| + <p><span class="caption">Caption </span><a class="name" data-type="warning" name="sugar" /></p><div class="warning"> | ||
| <p>Do not eat processed <strong>sugar</strong>.</p> | ||
| <p>Seriously.</p> | ||
| """, | ||
| """ | ||
| - <p><span class="caption">Caption <a data-type="fig" name="label" /></span></p> | ||
| + <p><span class="caption">Caption </span><a class="name" data-type="fig" name="label" /></p> | ||
| <p><img src="tunnel" alt="alt text" /></p> | ||
| """ | ||
| """, | ||
| """ | ||
| - <p><span class="caption">Caption <strong>bold</strong> <a data-type="fig" name="label" /> <em>italics</em></span></p> | ||
| + <p><span class="caption">Caption <strong>bold</strong> <em>italics</em></span><a class="name" data-type="fig" name="label" /></p> | ||
| <p><img src="placekitten" alt="kitteh" /></p> | ||
| """ | ||
| """, | ||
| """ | ||
| - <p><span class="caption">Meschiya Lake - Lucky Devil <a data-type="lyrics" name="blues" /></span></p> | ||
| + <p><span class="caption">Meschiya Lake - Lucky Devil </span><a class="name" data-type="lyrics" name="blues" /></p> | ||
| <blockquote> | ||
| <p>I'd like to be the lucky devil who gets to burn with you.</p> | ||
| """, | ||
| """ | ||
| - <p><span class="caption">Caption <a data-type="tbl" name="label" /></span></p> | ||
| + <p><span class="caption">Caption </span><a class="name" data-type="tbl" name="label" /></p> | ||
| <table> | ||
| <thead> | ||
| """, | ||
| """ | ||
| - <p><span class="caption">Diagram <a data-type="dia" name="seq1" /></span></p> | ||
| + <p><span class="caption">Diagram </span><a class="name" data-type="dia" name="seq1" /></p> | ||
| <pre><code class="language-diagram-plantuml">@startuml | ||
| Alice -> Bob: Request | ||
| Author | DaveJarvis <email> |
|---|---|
| Date | 2023-12-02 14:54:40 GMT-0800 |
| Commit | 6b91f5b6faca0790ff67a72863afe24fb315cd1e |
| Parent | 5f4cd50 |
| Delta | 204 lines added, 52 lines removed, 152-line increase |