diff --git a/build.gradle.kts b/build.gradle.kts index 50b6b54..37d9baf 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -21,7 +21,7 @@ plugins { } group = "dev.dediamondpro" -version = "1.0.2" +version = "1.1.0+localtest3" repositories { mavenCentral() diff --git a/src/main/java/dev/dediamondpro/minemark/MineMarkCoreBuilder.java b/src/main/java/dev/dediamondpro/minemark/MineMarkCoreBuilder.java index 9447f17..4003e72 100644 --- a/src/main/java/dev/dediamondpro/minemark/MineMarkCoreBuilder.java +++ b/src/main/java/dev/dediamondpro/minemark/MineMarkCoreBuilder.java @@ -32,8 +32,8 @@ import org.commonmark.renderer.html.UrlSanitizer; import org.jetbrains.annotations.NotNull; +import java.util.LinkedHashMap; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -41,7 +41,7 @@ public class MineMarkCoreBuilder { protected MineMarkCoreBuilder() { } - private final HashMap, ElementLoader> elements = new HashMap<>(); + private final LinkedHashMap, ElementLoader> elements = new LinkedHashMap<>(); private final ArrayList extensions = new ArrayList<>(); private TextElementLoader textElement = null; private boolean withDefaultElements = true; @@ -124,8 +124,9 @@ public MineMarkCoreBuilder withoutDefaultElements() { * * @param urlSanitizer The url sanitizer */ - public void setUrlSanitizer(UrlSanitizer urlSanitizer) { + public MineMarkCoreBuilder setUrlSanitizer(UrlSanitizer urlSanitizer) { this.urlSanitizer = urlSanitizer; + return this; } /** diff --git a/src/main/java/dev/dediamondpro/minemark/MineMarkHtmlParser.java b/src/main/java/dev/dediamondpro/minemark/MineMarkHtmlParser.java index 529c78a..654d3bf 100644 --- a/src/main/java/dev/dediamondpro/minemark/MineMarkHtmlParser.java +++ b/src/main/java/dev/dediamondpro/minemark/MineMarkHtmlParser.java @@ -19,9 +19,12 @@ import dev.dediamondpro.minemark.elements.Element; import dev.dediamondpro.minemark.elements.MineMarkElement; +import dev.dediamondpro.minemark.elements.TagMultiElement; import dev.dediamondpro.minemark.elements.loaders.ElementLoader; import dev.dediamondpro.minemark.elements.loaders.TextElementLoader; import dev.dediamondpro.minemark.style.Style; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.xml.sax.Attributes; import org.xml.sax.helpers.DefaultHandler; @@ -30,7 +33,7 @@ public class MineMarkHtmlParser extends DefaultHandler { private final Map, ElementLoader> elements; - private final TextElementLoader textElement; + private final TextElementLoader textElementLoader; private MineMarkElement markDown; private Element currentElement; private LayoutStyle layoutStyle; @@ -38,8 +41,8 @@ public class MineMarkHtmlParser extends DefaultHandler { private StringBuilder textBuilder = new StringBuilder(); private boolean isPreFormatted = false; - protected MineMarkHtmlParser(TextElementLoader textElement, Map, ElementLoader> elements) { - this.textElement = textElement; + protected MineMarkHtmlParser(TextElementLoader textElementLoader, Map, ElementLoader> elements) { + this.textElementLoader = textElementLoader; this.elements = elements; } @@ -47,8 +50,7 @@ protected MineMarkHtmlParser(TextElementLoader textElement, Map(style, layoutStyle, attributes); - currentElement = markDown; + currentElement = markDown = new MineMarkElement<>(style, layoutStyle, attributes); return; case "br": textBuilder.append("\n"); @@ -58,11 +60,11 @@ public void startElement(String uri, String localName, String qName, Attributes break; } addText(); - ElementLoader elementCreator = findElement(qName); - if (elementCreator == null) { + Element newElement = createElement(style, currentElement.getLayoutStyle(), currentElement, qName, attributes); + if (newElement == null) { return; } - currentElement = elementCreator.get(style, currentElement.getLayoutStyle(), currentElement, qName, attributes); + currentElement = newElement; } @Override @@ -112,21 +114,52 @@ public void characters(char[] ch, int start, int length) { private void addText() { String text = textBuilder.toString(); if (text.isEmpty()) return; - textElement.get(textBuilder.toString(), style, currentElement.getLayoutStyle(), currentElement, "text", null); + textElementLoader.createElement(textBuilder.toString(), style, currentElement.getLayoutStyle(), currentElement, "text", null); textBuilder = new StringBuilder(); } - private ElementLoader findElement(String qName) { - for (Map.Entry, ElementLoader> element : elements.entrySet()) { - if (element.getKey().contains(qName)) { - return element.getValue(); + /** + * Try to create an element for the given tag + * + * @param style The style of the element + * @param layoutStyle The layout style of the element + * @param parent The parent element, null if top level element + * @param qName The name of the HTML tag + * @param attributes The attributes of the HTML tag, null for text + * @return If only one element applies to the given parameters, return that element, + * otherwise return a TagMultiElement with all elements that apply. If no element applies, return null. + */ + private Element createElement(S style, LayoutStyle layoutStyle, @Nullable Element parent, @NotNull String qName, @Nullable Attributes attributes) { + ElementLoader elementLoader = null; + TagMultiElement multipleElement = null; + + for (Map.Entry, ElementLoader> e : elements.entrySet()) { + if (!e.getKey().contains(qName) || !e.getValue().appliesTo(style, layoutStyle, parent, qName, attributes)) { + continue; + } + + if (elementLoader == null) { + elementLoader = e.getValue(); + continue; + } + + if (multipleElement == null) { + multipleElement = new TagMultiElement<>(style, layoutStyle, parent, qName, attributes); + multipleElement.addElement(elementLoader, style, layoutStyle, qName, attributes); } + multipleElement.addElement(e.getValue(), style, layoutStyle, qName, attributes); + } - return null; + return multipleElement != null ? multipleElement : elementLoader != null ? elementLoader.createElement(style, layoutStyle, parent, qName, attributes) : null; } private boolean hasElement(String qName) { - return findElement(qName) != null; + for (Map.Entry, ElementLoader> e : elements.entrySet()) { + if (e.getKey().contains(qName)) { + return true; + } + } + return false; } protected void setStyle(S style, LayoutStyle layoutStyle) { diff --git a/src/main/java/dev/dediamondpro/minemark/elements/TagMultiElement.java b/src/main/java/dev/dediamondpro/minemark/elements/TagMultiElement.java new file mode 100644 index 0000000..c0b08ee --- /dev/null +++ b/src/main/java/dev/dediamondpro/minemark/elements/TagMultiElement.java @@ -0,0 +1,56 @@ +/* + * This file is part of MineMark + * Copyright (C) 2024 DeDiamondPro + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License Version 3 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package dev.dediamondpro.minemark.elements; + + +import dev.dediamondpro.minemark.LayoutData; +import dev.dediamondpro.minemark.LayoutStyle; +import dev.dediamondpro.minemark.elements.loaders.ElementLoader; +import dev.dediamondpro.minemark.style.Style; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.xml.sax.Attributes; + +import java.util.ArrayList; + +/** + * An element that is used to apply multiple elements to the same HTML tag. + */ +public class TagMultiElement extends Element { + private Element deepestChildElement = this; + + public TagMultiElement(@NotNull S style, @NotNull LayoutStyle layoutStyle, @Nullable Element parent, @NotNull String qName, @Nullable Attributes attributes) { + super(style, layoutStyle, parent, qName, attributes); + } + + @Override + public void generateLayout(LayoutData layoutData, R renderData) { + for (Element child : children) { + child.generateLayout(layoutData, renderData); + } + } + + @Override + public @NotNull ArrayList> getChildren() { + return deepestChildElement.children; + } + + public void addElement(@NotNull ElementLoader elementLoader, S style, LayoutStyle layoutStyle, @NotNull String qName, @Nullable Attributes attributes) { + deepestChildElement = elementLoader.createElement(style, layoutStyle, deepestChildElement, qName, attributes); + } +} diff --git a/src/main/java/dev/dediamondpro/minemark/elements/loaders/ElementLoader.java b/src/main/java/dev/dediamondpro/minemark/elements/loaders/ElementLoader.java index 53a6070..8fb6d88 100644 --- a/src/main/java/dev/dediamondpro/minemark/elements/loaders/ElementLoader.java +++ b/src/main/java/dev/dediamondpro/minemark/elements/loaders/ElementLoader.java @@ -26,5 +26,30 @@ import org.xml.sax.Attributes; public interface ElementLoader { - Element get(S style, LayoutStyle layoutStyle, @Nullable Element parent, @NotNull String qName, @Nullable Attributes attributes); + /** + * Create an element from the given parameters + * + * @param style The style of the element + * @param layoutStyle The layout style of the element + * @param parent The parent element, null if top level element + * @param qName The name of the HTML tag + * @param attributes The attributes of the HTML tag, null for text + * @return The created element + */ + Element createElement(S style, LayoutStyle layoutStyle, @Nullable Element parent, @NotNull String qName, @Nullable Attributes attributes); + + + /** + * Check if the element applies to the given parameters and can be created with them + * + * @param style The style of the element + * @param layoutStyle The layout style of the element + * @param parent The parent element, null if top level element + * @param qName The name of the HTML tag + * @param attributes The attributes of the HTML tag, null for text + * @return True if the element applies to the given parameters + */ + default boolean appliesTo(S style, LayoutStyle layoutStyle, @Nullable Element parent, @NotNull String qName, @Nullable Attributes attributes) { + return true; + } } diff --git a/src/main/java/dev/dediamondpro/minemark/elements/loaders/TextElementLoader.java b/src/main/java/dev/dediamondpro/minemark/elements/loaders/TextElementLoader.java index 15e1004..ea1b2e1 100644 --- a/src/main/java/dev/dediamondpro/minemark/elements/loaders/TextElementLoader.java +++ b/src/main/java/dev/dediamondpro/minemark/elements/loaders/TextElementLoader.java @@ -26,5 +26,16 @@ import org.xml.sax.Attributes; public interface TextElementLoader { - Element get(@NotNull String text, S style, LayoutStyle layoutStyle, @Nullable Element parent, @NotNull String qName, @Nullable Attributes attributes); + /** + * Create a text element from the given parameters + * + * @param text The text of the element + * @param style The style of the element + * @param layoutStyle The layout style of the element + * @param parent The parent element, null if top level element + * @param qName The name of the HTML tag + * @param attributes The attributes of the HTML tag, null for text + * @return The created element + */ + Element createElement(@NotNull String text, S style, LayoutStyle layoutStyle, @Nullable Element parent, @NotNull String qName, @Nullable Attributes attributes); }