All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.bottlecaps.railroad.RailroadGenerator Maven / Gradle / Ivy

package de.bottlecaps.railroad;

import java.awt.Color;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;

import de.bottlecaps.railroad.core.Download;
import de.bottlecaps.railroad.core.Parser;
import de.bottlecaps.railroad.core.ResourceModuleUriResolver;
import de.bottlecaps.railroad.core.TextWidth;
import de.bottlecaps.railroad.core.XhtmlToZip;
import net.sf.saxon.Configuration;
import net.sf.saxon.lib.Feature;
import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.QName;
import net.sf.saxon.s9api.Serializer;
import net.sf.saxon.s9api.XQueryCompiler;
import net.sf.saxon.s9api.XQueryEvaluator;
import net.sf.saxon.s9api.XQueryExecutable;
import net.sf.saxon.s9api.XdmAtomicValue;
import net.sf.saxon.s9api.XdmEmptySequence;
import net.sf.saxon.s9api.XdmNode;

public class RailroadGenerator
{
  public static final String RR_URL = "https://bottlecaps.de/" + RailroadVersion.PROJECT_NAME;

  public enum OutputType
  {
    XHTML_SVG
      {
        @Override
        protected void produce(Processor processor, XQueryCompiler compiler, XQueryEvaluator xqueryEvaluator, OutputStream output) throws Exception
        {
          Serializer serializer = processor.newSerializer(output);
          serializer.setOutputProperty(Serializer.Property.METHOD, "xhtml");
          serializer.setOutputProperty(Serializer.Property.ENCODING, StandardCharsets.UTF_8.name());
          serializer.setOutputProperty(Serializer.Property.OMIT_XML_DECLARATION, "yes");
          serializer.setOutputProperty(Serializer.Property.VERSION, "1.0");
          serializer.setOutputProperty(Serializer.Property.DOCTYPE_SYSTEM, "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd");
          serializer.setOutputProperty(Serializer.Property.DOCTYPE_PUBLIC, "-//W3C//DTD XHTML 1.0 Transitional//EN");
          serializer.setOutputProperty(Serializer.Property.INDENT, "yes");
          xqueryEvaluator.run(serializer);
        }
      },

    HTML_PNG_ZIP
      {
        @Override
        protected void produce(Processor processor, XQueryCompiler compiler, XQueryEvaluator xqueryEvaluator, OutputStream output) throws Exception
        {
          XdmNode node = (XdmNode) xqueryEvaluator.iterator().next();
          new XhtmlToZip().convert(node.getUnderlyingNode(), output);
        }
      },

    MARKDOWN_SVG
      {
        @Override
        protected void produce(Processor processor, XQueryCompiler compiler, XQueryEvaluator xqueryEvaluator, OutputStream output) throws Exception
        {
          Serializer serializer = processor.newSerializer(output);
          serializer.setOutputProperty(Serializer.Property.METHOD, "text");
          serializer.setOutputProperty(Serializer.Property.ENCODING, StandardCharsets.UTF_8.name());
          XQueryEvaluator toMarkdown = compiler.compile(
            "import module namespace m='de/bottlecaps/railroad/xq/xhtml-to-md.xq';\n" +
            "declare variable $xhtml external;\n" +
            "m:transform($xhtml)").load();
          toMarkdown.setExternalVariable(new QName("xhtml"), (XdmNode) xqueryEvaluator.iterator().next());
          toMarkdown.run(processor.newSerializer(output));
        }
      };

    protected abstract void produce(Processor processor, XQueryCompiler compiler, XQueryEvaluator xqueryEvaluator, OutputStream output) throws Exception;
  }

  private OutputStream output = System.out;
  private OutputType outputType = OutputType.XHTML_SVG;
  private boolean showEbnf = true;
  private boolean factoring = true;
  private boolean recursionElimination = true;
  private boolean inlineLiterals = true;
  private boolean keepEpsilon = true;
  private Color baseColor;
  private int colorOffset;
  private Integer padding;
  private Integer strokeWidth;
  private Integer width;

  /**
   * @param grammar input grammar in W3C EBNF notation
   */
  public void generate(String grammar) throws Exception
  {
    if (grammar == null)
      throw new IllegalArgumentException("grammar cannot be null");
    if (grammar.isEmpty())
      throw new IllegalArgumentException("grammar cannot be empty");

    Configuration configuration = new Configuration();
    configuration.registerExtensionFunction(new Parser.SaxonDefinition_Grammar());
    Processor processor = new Processor(configuration);
    processor.setConfigurationProperty(Feature.XSD_VERSION, "1.1");
    new TextWidth.SaxonInitializer().initialize(processor.getUnderlyingConfiguration());

    XQueryCompiler compiler = processor.newXQueryCompiler();
    compiler.setModuleURIResolver(ResourceModuleUriResolver.instance);
    String query =
        "import module namespace i='de/bottlecaps/railroad/xq/basic-interface.xq';\n" +
        "declare variable $ebnf external;\n" +
        "declare variable $show-ebnf external;\n" +
        "declare variable $recursion-elimination external;\n" +
        "declare variable $factoring external;\n" +
        "declare variable $inline external;\n" +
        "declare variable $keep external;\n" +
        "declare variable $width external;\n" +
        "declare variable $color external;\n" +
        "declare variable $spread external;\n" +
        "i:ebnf-to-xhtml($ebnf, $show-ebnf, $recursion-elimination, $factoring, $inline, $keep, $width, $color, $spread, '" + RR_URL + "')";
    XQueryExecutable executable = compiler.compile(query);
    XQueryEvaluator xqueryEvaluator = executable.load();

    xqueryEvaluator.setExternalVariable(new QName("ebnf"), new XdmAtomicValue(grammar));
    xqueryEvaluator.setExternalVariable(new QName("show-ebnf"), new XdmAtomicValue(showEbnf));
    xqueryEvaluator.setExternalVariable(new QName("recursion-elimination"), new XdmAtomicValue(recursionElimination));
    xqueryEvaluator.setExternalVariable(new QName("factoring"), new XdmAtomicValue(factoring));
    xqueryEvaluator.setExternalVariable(new QName("inline"), new XdmAtomicValue(inlineLiterals));
    xqueryEvaluator.setExternalVariable(new QName("keep"), new XdmAtomicValue(keepEpsilon));
    xqueryEvaluator.setExternalVariable(new QName("width"), width == null ? XdmEmptySequence.getInstance() : new XdmAtomicValue(width));
    xqueryEvaluator.setExternalVariable(new QName("color"), baseColor == null ? XdmEmptySequence.getInstance() : new XdmAtomicValue(toHexString(baseColor)));
    xqueryEvaluator.setExternalVariable(new QName("spread"), new XdmAtomicValue(colorOffset));
    xqueryEvaluator.setExternalVariable(new QName("de/bottlecaps/railroad/xq/ast-to-svg.xq", "version"), new XdmAtomicValue(RailroadVersion.VERSION));
    xqueryEvaluator.setExternalVariable(new QName("de/bottlecaps/railroad/xq/ast-to-svg.xq", "java-version"), new XdmAtomicValue(Download.javaVersion()));
    xqueryEvaluator.setExternalVariable(new QName("de/bottlecaps/railroad/xq/ast-to-svg.xq", "date"), new XdmAtomicValue(RailroadVersion.DATE));
    if (padding != null)
      xqueryEvaluator.setExternalVariable(new QName("de/bottlecaps/railroad/xq/ast-to-svg.xq", "padding"), new XdmAtomicValue(padding));
    if (strokeWidth != null)
      xqueryEvaluator.setExternalVariable(new QName("de/bottlecaps/railroad/xq/ast-to-svg.xq", "stroke-width"), new XdmAtomicValue(strokeWidth));

    outputType.produce(processor, compiler, xqueryEvaluator, output);
  }

  private String toHexString(Color color)
  {
    return String.format("#%02x%02x%02x", color.getRed(), color.getGreen(), color.getBlue());
  }

  /**
   * @param output output stream, defaults to {@code System.out}
   */
  public void setOutput(OutputStream output)
  {
    this.output = output;
  }

  /**
   * @param outputType type of output file, defaults to {@link OutputType#XHTML_SVG}
   */
  public void setOutputType(OutputType outputType)
  {
    if (outputType == null)
      throw new IllegalArgumentException("outputType cannot be null");

    this.outputType = outputType;
  }

  /**
   * @param showEbnf whether to show EBNF next to generated diagrams, defaults to {@code true}
   */
  public void setShowEbnf(boolean showEbnf)
  {
    this.showEbnf = showEbnf;
  }

  /**
   * @param factoring whether to enable left and right factoring, defaults to {@code true}.
   */
  public void setFactoring(boolean factoring)
  {
    this.factoring = factoring;
  }

  /**
   * @param recursionElimination whether to enable direct recursion elimination, defaults to {@code true}
   */
  public void setRecursionElimination(boolean recursionElimination)
  {
    this.recursionElimination = recursionElimination;
  }

  /**
   * @param inlineLiterals whether to inline nonterminals that derive to single literals, defaults to {@code true}.
   */
  public void setInlineLiterals(boolean inlineLiterals)
  {
    this.inlineLiterals = inlineLiterals;
  }

  /**
   * @param keepEpsilon whether to keep nonterminal references that derive to epsilon (nothing) only, defaults to {@code true}.
   */
  public void setKeepEpsilon(boolean keepEpsilon)
  {
    this.keepEpsilon = keepEpsilon;
  }

  /**
   * @param baseColor the base color to use, defaults to {@code RGB 255,219,77} / {@code HSL 48,100%,65%}
   */
  public void setBaseColor(Color baseColor)
  {
    this.baseColor = baseColor;
  }

  /**
   * @param colorOffset hue offset to secondary color in degrees, defaults to {@code 0}
   */
  public void setColorOffset(int colorOffset)
  {
    this.colorOffset = colorOffset;
  }

  /**
   * @param padding padding to apply, defaults to {@code 10}
   */
  public void setPadding(int padding)
  {
    this.padding = padding;
  }

  /**
   * @param strokeWidth stroke width to set, defaults to {@code 1}
   */
  public void setStrokeWidth(int strokeWidth)
  {
    this.strokeWidth = strokeWidth;
  }

  /**
   * @param width if exceeded, generator tries to break graphics into multiple lines; defaults to {@code 922}
   */
  public void setWidth(int width)
  {
    this.width = width;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy