Untitled
unknown
java
2 years ago
18 kB
5
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...