Untitled
unknown
java
3 years ago
18 kB
9
Indexable
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
// ------------------------------------------------------ //
public abstract class LearningCard {
protected String title;
protected String body;
public LearningCard(String body, String title) {
this.title = title;
this.body = body;
}
public String getTitle() {
return this.title;
}
/**
* return the front site of the card
*/
public abstract List<String> getFrontContent();
/**
* return the back site of the card
*/
public abstract List<String> getBackContent();
/**
* return combined content of front and back
*/
public abstract List<String> getContent();
/**
* write the content to the console in a meaningful way
*/
public abstract void printToConsole();
}
// ------------------------------------------------------ //
class SimpleCard extends LearningCard {
public SimpleCard(String body, String title) {
super(body, title);
}
@Override
public List<String> getFrontContent() {
List<String> c = new ArrayList<>();
c.add(this.title);
return c;
}
@Override
public List<String> getBackContent() {
List<String> c = new ArrayList<>();
c.add(this.body);
return c;
}
@Override
public List<String> getContent() {
List<String> c = new ArrayList<>();
c.addAll(this.getFrontContent());
c.addAll(this.getBackContent());
return c;
}
@Override
public void printToConsole() {
List<String> c = this.getContent();
System.out.println("=== " + title + " ===");
System.out.printf(
"FRONT: %s\nBACK: %s\n",
c.get(0),
c.get(1));
System.out.println("===");
}
}
// ------------------------------------------------------ //
class QuestionCard extends LearningCard {
private String backContent = "";
private String frontContent = "";
public QuestionCard(String body, String title) {
super(body, title);
parseBody();
}
private void parseBody() {
String[] segments = this.body.split("(?m)^(?=#{2}[^#])");
for (String segment : segments) {
Pattern p = Pattern.compile("^#{2}\\s+(?<subname>.*?)?\\s*?(\\{(?<subtag>[^{}]+)\\})?$\\s*(?s)(?<subbody>.*)\\s*", Pattern.MULTILINE);
Matcher m = p.matcher(segment);
if (!m.matches()) {
continue;
}
String subBody = Objects.toString(m.group("subbody"), "");
String subTitle = Objects.toString(m.group("subname"), "");
String subTag = Objects.toString(m.group("subtag"), "");
setContent(subTag, "**" + subTitle + "**: \n" + subBody);
}
}
/**
* Add content to the right spot depending on tag given
*
* @param tag
* @param content
*/
private void setContent(String tag, String content) {
switch (tag) {
case "BACK" -> this.backContent = content;
case "FRONT" -> this.frontContent = content;
}
}
@Override
public List<String> getFrontContent() {
return List.of(this.frontContent);
}
@Override
public List<String> getBackContent() {
return List.of(backContent);
}
@Override
public List<String> getContent() {
List<String> c = new ArrayList<>();
c.addAll(this.getFrontContent());
c.addAll(this.getBackContent());
return c;
}
@Override
public void printToConsole() {
List<String> c = this.getContent();
System.out.println("=== " + title + " ===");
System.out.printf(
"FRONT: %s\nBACK: %s\n",
c.get(0),
c.get(1));
System.out.println("===");
}
}
class CLOZE extends LearningCard {
private final String frontContent;
private final String backContent;
/**
* title is empty, body is singular LKME
*
* @param body raw body of learning card
* @param title title text of the card
*/
public CLOZE(String body, String title) {
super(body, title);
Pattern p = Pattern.compile("(?<=[^^]|^)\\^(?<word>[^^]\\w*[^^])\\^(?=[^^]|$)", Pattern.MULTILINE);
Matcher m = p.matcher(body);
List<String> gaps = new ArrayList<>();
while (m.find()) {
gaps.add(m.group("word"));
}
String gaptext = m.replaceAll("**_____**");
String fulltext = m.replaceAll("$1");
//Make ^^ => ^ to allow the restricted use of ^ if needed
gaptext = gaptext.replaceAll("\\^{2}", "^");
fulltext = fulltext.replaceAll("\\^{2}", "^");
this.frontContent = gaptext + "\nAllowed words: " + gaps;
this.backContent = fulltext;
}
@Override
public List<String> getFrontContent() {
return List.of(frontContent);
}
@Override
public List<String> getBackContent() {
return List.of(backContent);
}
@Override
public List<String> getContent() {
List<String> c = new ArrayList<>();
c.addAll(getFrontContent());
c.addAll(getBackContent());
return c;
}
@Override
public void printToConsole() {
List<String> c = this.getContent();
System.out.printf("FRONT: %s\nBACK: %s", c.get(0), c.get(1));
}
}
enum ConversionGoal {
HTML
}
record SemiParsedTextFragment<T extends RichText>(String before, T parsed, String after) {
}
interface RichTextParsable {
public Class<? extends Element> getElementClass();
public Point getMatchingRange(String s);
public SemiParsedTextFragment parse(String parsableString);
}
class RichTextParser implements RichTextParsable {
private final Class<? extends Element> elementClass;
/**
* Pattern should have a single capturing group that captures the text that should be considered Element
*/
private final Pattern matchingPattern;
public RichTextParser(Class<? extends Element> elementClass, Pattern matchingPattern) {
this.elementClass = elementClass;
this.matchingPattern = matchingPattern;
}
public Class<? extends Element> getElementClass() {
return null;
}
@Override
public Point getMatchingRange(String s) {
return null;
}
@Override
public SemiParsedTextFragment parse(String parseableString) {
Matcher m = this.matchingPattern.matcher(parseableString);
if (!m.matches()) return new SemiParsedTextFragment(parseableString, null, "");
String rawElem = m.group(1);
int start = m.start();
int end = m.end();
return new SemiParsedTextFragment(parseableString.substring(0, start), null, parseableString.substring(end));
}
}
interface RichText {
String generate(ConversionGoal c);
String generateHTML();
}
abstract class ElementParser<T extends Element> {
public ElementParser(Pattern parsingPattern, List<ElementParser<? extends Element>> allowedChildParsers) {
this.parsingPattern = parsingPattern;
this.allowedChildParsers = allowedChildParsers;
}
/**
* Gives pattern that is used to parse markdown
*
* @return
*/
public Pattern getParserPattern() {
return parsingPattern;
}
/**
* Get position in string that matches
*
* @return x=start, y=end
*/
public Point getMatchRange(String markdown) {
Matcher m = this.parsingPattern.matcher(markdown);
if (!m.matches()) return null;
return new Point(m.start(), m.end());
}
/**
* Parses given markdown and returns result. Everything that wasnt parsed will be given back as strings (before and after) the parsed text
*
* @param markdown the text to parse
* @return the parsing result
* @see SemiParsedTextFragment
*/
abstract SemiParsedTextFragment<T> parse(String markdown);
protected final Pattern parsingPattern;
protected Children parseChildren(String markdown) {
SemiParsedSequence s = new SemiParsedSequence(markdown);
while (!s.fullyParsed()) {
s.parseNext(this.allowedChildParsers);
}
return s.toChildren();
}
/**
* The list of parsers that are allowed to parse the body of a parsed element, allowing recursive parsing (e.g.: Header(Bold(...)))
*/
protected final List<ElementParser<? extends Element>> allowedChildParsers;
}
class SemiParsedSequence {
private final List<Object> sequence;
/**
* Generate from List **removes every entry that isn't String or Element**
*
* @param rawSequence existing list
*/
public SemiParsedSequence(List<Object> rawSequence) {
this.sequence = rawSequence.stream().filter(e -> (e instanceof String) || (e instanceof Element)).toList();
}
/**
* Generate from given markdown
* @param markdown markdown text to generate from
*/
public SemiParsedSequence(String markdown) {
this.sequence = List.of(markdown);
}
public SemiParsedSequence(SemiParsedSequence sequence) {
this.sequence = new ArrayList<>(sequence.sequence);
}
public boolean fullyParsed() {
return this.sequence.stream().anyMatch(e -> e instanceof String);
}
public void parseNext(final List<ElementParser<? extends Element>> allowedParsers) {
if (allowedParsers.isEmpty()) return;
Integer idx = getNextStringPos();
if (idx == null) return;
assert sequence.get(idx) instanceof String;
String markdown = (String) sequence.get(idx);
// Get the parsers who's match matches first
Optional<ElementParser<? extends Element>> _parser =
allowedParsers.stream()
.min(Comparator.comparingInt(x -> x.getMatchRange(markdown).x));
assert _parser.isPresent();
ElementParser<? extends Element> parser = _parser.get();
SemiParsedTextFragment<? extends Element> res = parser.parse(markdown);
insertParsedResult(idx, res);
}
private void insertParsedResult(int idx, SemiParsedTextFragment<? extends Element> parsed) {
// insert parse result in right order into sequence (the element that was at idx will be replaced by the parsed result)
if (parsed.after() != null && !parsed.after().isEmpty()) sequence.add(idx+1, parsed.after());
if (parsed.parsed() != null) sequence.set(idx, parsed.parsed());
if (parsed.before() != null && !parsed.before().isEmpty()) sequence.add(idx, parsed.before());
}
private Integer getNextStringPos() {
for (int i = 0; i < sequence.size(); i++) {
if (sequence.get(i) instanceof String) return i;
}
return null;
}
public Children toChildren() {
if (!this.fullyParsed()) return null;
return new Children(this.sequence.stream().filter(e -> e instanceof Element).map(e -> (Element)e).toList());
}
}
abstract class Element implements RichText {
@Override
public String generate(ConversionGoal c) {
switch (c) {
case HTML -> {
return this.generateHTML();
}
default -> {
return "";
}
}
}
}
class RawElementParser extends ElementParser<RawElementParser.RawElement> {
static final private Pattern PARSING_PATTERN = Pattern.compile(".*", Pattern.MULTILINE);
public RawElementParser() {
super(PARSING_PATTERN, null);
}
@Override
public SemiParsedTextFragment<RawElement> parse(String markdown) {
Matcher m = this.parsingPattern.matcher(markdown);
if (!m.matches()) return new SemiParsedTextFragment<>(markdown, null, null);
return new SemiParsedTextFragment<>(
markdown.substring(0, m.start()), // before
new RawElement(m.group()), // parsed part
markdown.substring(m.end()) // after
);
}
/**
* Used to write raw text
*/
static class RawElement extends Element {
String element;
public RawElement(String element) {
this.element = element;
}
@Override
public String generateHTML() {
return element;
}
}
}
class TextFormattingParser extends ElementParser<TextFormattingParser.TextFormatting> {
// see: https://regex101.com/r/SXX4pF/1
static final private Pattern PARSING_PATTERN = Pattern.compile("(?<=^|[^*~])(?<formattype>[*]{1,3}|~{2})(?<body>.*)\\1(?=[^*~]|$)", Pattern.MULTILINE);
public TextFormattingParser() {
super(PARSING_PATTERN, List.of(new TextFormattingParser(), new RawElementParser() /*TODO: Add link pasrser*/));
}
@Override
SemiParsedTextFragment<TextFormatting> parse(String markdown) {
Matcher m = parsingPattern.matcher(markdown);
if (!m.matches()) return new SemiParsedTextFragment<>(markdown, null, null);
return new SemiParsedTextFragment<>(
markdown.substring(0, m.start()),
new TextFormatting(m.group("formattype"), this.parseChildren(m.group("body"))),
markdown.substring(m.end()));
}
/**
* HTML
*/
static class TextFormatting extends Element {
/**
* Used by parser
* @param decoIndicator indicates which decoration to use (*, **, *** or ~~)
* @param element the child element
*/
public TextFormatting(String decoIndicator, Element element) {
this.deco = TextDeco.valueOf(decoIndicator);
this.element = element;
}
enum TextDeco {
BOLD("**"),
ITALICS("*"),
UNDERLINED("~~"),
BOLDITALIC("***");
public final String label;
TextDeco(String label) {
this.label = label;
}
}
TextDeco deco;
Element element;
public String generateHTML() {
ConversionGoal cGoal = ConversionGoal.HTML;
switch (deco) {
case BOLD -> {
return "<b>" + element.generate(cGoal) + "</b>";
}
case ITALICS -> {
return "<i>" + element.generate(cGoal) + "</i>";
}
case UNDERLINED -> {
return "<u>" + element.generate(cGoal) + "</u>";
}
case BOLDITALIC -> {
return "<b><i>" + element.generate(cGoal) +"</i></b>";
}
default -> {
return element.generate(cGoal);
}
}
}
}
}
class HyperText extends Element {
String url;
RichText element;
@Override
public String generateHTML() {
return String.format("<a href=\"%s\">%s</a>", url, element.generate(ConversionGoal.HTML));
}
}
class Image extends Element {
File imgFile;
@Override
public String generateHTML() {
BufferedImage img;
try {
img = ImageIO.read(imgFile);
} catch (IOException e) {
System.err.printf("Unable to read image at: %s\n", imgFile.getAbsolutePath());
return "";
}
ByteArrayOutputStream resImg = new ByteArrayOutputStream();
try {
ImageIO.write(img, "png", resImg);
} catch (IOException e) {
System.err.printf("Unable to convert image to png: %s", e.getMessage());
}
String b64PngImg = Base64.getEncoder().encodeToString(resImg.toByteArray());
;
return "data:image/png;base64," + b64PngImg;
}
}
class Header extends Element {
int headerLvl = 1;
RichText element;
@Override
public String generateHTML() {
int renderedHeaderLvl;
if (headerLvl < 1) return element.generateHTML();
else renderedHeaderLvl = Math.min(headerLvl, 6);
return String.format("<h{0}>" + element.generateHTML() + "</h{0}>", renderedHeaderLvl);
}
}
class ListContainerElement extends Element {
enum ListType {
UNORDERED,
ORDERED
}
ListType t;
ListElement[] elements;
@Override
public String generateHTML() {
String HTMLListElems = String.join(
"\n",
Arrays.stream(elements)
.map(ListElement::generateHTML)
.toArray(String[]::new));
if (t == ListType.ORDERED) {
return "<ol>\n" + HTMLListElems + "\n</ol>";
}
return "<ul>\n" + HTMLListElems + "\n</ul>";
}
}
class ListElement extends Element {
RichText element;
@Override
public String generateHTML() {
return "<li>" + element.generateHTML() + "</li>";
}
}
class Code extends Element {
enum CodeStyle {
INLINE,
MULTILINE
}
CodeStyle s;
RawElementParser.RawElement e;
@Override
public String generateHTML() {
if (s == CodeStyle.INLINE) return "<code>" + e.generateHTML() + "</code>";
return "<pre>" + e.generateHTML() + "</pre>";
}
}
/**
* Special type of Element that allow to hold multiple elements
*/
class Children extends Element {
private List<Element> children;
public Children(List<Element> children) {
this.children = new ArrayList<>(children);
}
public Children(Element... element) {
this(Arrays.stream(element).toList());
}
@Override
public String generateHTML() {
return children.stream().map(RichText::generateHTML).reduce("", String::concat);
}
}
Editor is loading...