Skip to content

Commit

Permalink
add support for applying multiple elements to a single HTML tag
Browse files Browse the repository at this point in the history
  • Loading branch information
DeDiamondPro committed Jun 1, 2024
1 parent d55d334 commit 00109df
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 21 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ plugins {
}

group = "dev.dediamondpro"
version = "1.0.2"
version = "1.1.0+localtest3"

repositories {
mavenCentral()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,16 @@
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;

public class MineMarkCoreBuilder<S extends Style, R> {
protected MineMarkCoreBuilder() {
}

private final HashMap<List<String>, ElementLoader<S, R>> elements = new HashMap<>();
private final LinkedHashMap<List<String>, ElementLoader<S, R>> elements = new LinkedHashMap<>();
private final ArrayList<Extension> extensions = new ArrayList<>();
private TextElementLoader<S, R> textElement = null;
private boolean withDefaultElements = true;
Expand Down Expand Up @@ -124,8 +124,9 @@ public MineMarkCoreBuilder<S, R> withoutDefaultElements() {
*
* @param urlSanitizer The url sanitizer
*/
public void setUrlSanitizer(UrlSanitizer urlSanitizer) {
public MineMarkCoreBuilder<S, R> setUrlSanitizer(UrlSanitizer urlSanitizer) {
this.urlSanitizer = urlSanitizer;
return this;
}

/**
Expand Down
63 changes: 48 additions & 15 deletions src/main/java/dev/dediamondpro/minemark/MineMarkHtmlParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -30,25 +33,24 @@

public class MineMarkHtmlParser<S extends Style, R> extends DefaultHandler {
private final Map<List<String>, ElementLoader<S, R>> elements;
private final TextElementLoader<S, R> textElement;
private final TextElementLoader<S, R> textElementLoader;
private MineMarkElement<S, R> markDown;
private Element<S, R> currentElement;
private LayoutStyle layoutStyle;
private S style;
private StringBuilder textBuilder = new StringBuilder();
private boolean isPreFormatted = false;

protected MineMarkHtmlParser(TextElementLoader<S, R> textElement, Map<List<String>, ElementLoader<S, R>> elements) {
this.textElement = textElement;
protected MineMarkHtmlParser(TextElementLoader<S, R> textElementLoader, Map<List<String>, ElementLoader<S, R>> elements) {
this.textElementLoader = textElementLoader;
this.elements = elements;
}

@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) {
switch (qName) {
case "minemark":
markDown = new MineMarkElement<>(style, layoutStyle, attributes);
currentElement = markDown;
currentElement = markDown = new MineMarkElement<>(style, layoutStyle, attributes);
return;
case "br":
textBuilder.append("\n");
Expand All @@ -58,11 +60,11 @@ public void startElement(String uri, String localName, String qName, Attributes
break;
}
addText();
ElementLoader<S, R> elementCreator = findElement(qName);
if (elementCreator == null) {
Element<S, R> newElement = createElement(style, currentElement.getLayoutStyle(), currentElement, qName, attributes);
if (newElement == null) {
return;
}
currentElement = elementCreator.get(style, currentElement.getLayoutStyle(), currentElement, qName, attributes);
currentElement = newElement;
}

@Override
Expand Down Expand Up @@ -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<S, R> findElement(String qName) {
for (Map.Entry<List<String>, ElementLoader<S, R>> 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<S, R> createElement(S style, LayoutStyle layoutStyle, @Nullable Element<S, R> parent, @NotNull String qName, @Nullable Attributes attributes) {
ElementLoader<S, R> elementLoader = null;
TagMultiElement<S, R> multipleElement = null;

for (Map.Entry<List<String>, ElementLoader<S, R>> 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<List<String>, ElementLoader<S, R>> e : elements.entrySet()) {
if (e.getKey().contains(qName)) {
return true;
}
}
return false;
}

protected void setStyle(S style, LayoutStyle layoutStyle) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
*/

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<S extends Style, R> extends Element<S, R> {
private Element<S, R> deepestChildElement = this;

public TagMultiElement(@NotNull S style, @NotNull LayoutStyle layoutStyle, @Nullable Element<S, R> parent, @NotNull String qName, @Nullable Attributes attributes) {
super(style, layoutStyle, parent, qName, attributes);
}

@Override
public void generateLayout(LayoutData layoutData, R renderData) {
for (Element<S, R> child : children) {
child.generateLayout(layoutData, renderData);
}
}

@Override
public @NotNull ArrayList<Element<S, R>> getChildren() {
return deepestChildElement.children;
}

public void addElement(@NotNull ElementLoader<S, R> elementLoader, S style, LayoutStyle layoutStyle, @NotNull String qName, @Nullable Attributes attributes) {
deepestChildElement = elementLoader.createElement(style, layoutStyle, deepestChildElement, qName, attributes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,30 @@
import org.xml.sax.Attributes;

public interface ElementLoader<S extends Style, R> {
Element<S, R> get(S style, LayoutStyle layoutStyle, @Nullable Element<S, R> 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<S, R> createElement(S style, LayoutStyle layoutStyle, @Nullable Element<S, R> 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<S, R> parent, @NotNull String qName, @Nullable Attributes attributes) {
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,16 @@
import org.xml.sax.Attributes;

public interface TextElementLoader<S extends Style, R> {
Element<S, R> get(@NotNull String text, S style, LayoutStyle layoutStyle, @Nullable Element<S, R> 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<S, R> createElement(@NotNull String text, S style, LayoutStyle layoutStyle, @Nullable Element<S, R> parent, @NotNull String qName, @Nullable Attributes attributes);
}

0 comments on commit 00109df

Please sign in to comment.