Skip to content

Commit

Permalink
Merge pull request #4921 from fishface60/develop
Browse files Browse the repository at this point in the history
Add a Handlebars LibraryTemplateLoader and rework file loading to support partial templates
  • Loading branch information
cwisniew authored Sep 14, 2024
2 parents f01ad8e + 4e6abc5 commit 531498d
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,11 @@
import net.rptools.maptool.model.library.Library;
import net.rptools.maptool.model.library.LibraryManager;
import net.rptools.maptool.util.FunctionUtil;
import net.rptools.maptool.util.HTMLUtil;
import net.rptools.parser.Parser;
import net.rptools.parser.ParserException;
import net.rptools.parser.VariableResolver;
import net.rptools.parser.function.AbstractFunction;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import org.jsoup.parser.Tag;

public class MacroDialogFunctions extends AbstractFunction {
private static final MacroDialogFunctions instance = new MacroDialogFunctions();
Expand Down Expand Up @@ -200,23 +198,7 @@ private String showURL(String name, URL url, String opts, FrameType frameType, b
I18N.getText("macro.function.html5.invalidURI", url.toExternalForm()));
}

htmlString = library.get().readAsString(url).get();

var document = Jsoup.parse(htmlString);
var head = document.select("head").first();
if (head != null) {
String baseURL = url.toExternalForm().replaceFirst("\\?.*", "");
baseURL = baseURL.substring(0, baseURL.lastIndexOf("/") + 1);
var baseElement = new Element(Tag.valueOf("base"), "").attr("href", baseURL);
if (head.children().isEmpty()) {
head.appendChild(baseElement);
} else {
head.child(0).before(baseElement);
}

htmlString = document.html();
}

htmlString = HTMLUtil.fixHTMLBase(library.get().readAsString(url).get(), url);
} catch (InterruptedException | ExecutionException | IOException e) {
throw new ParserException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@
package net.rptools.maptool.client.ui.sheet.stats;

import java.io.IOException;
import java.net.URL;
import javafx.application.Platform;
import net.rptools.maptool.client.AppConstants;
import net.rptools.maptool.client.MapTool;
import net.rptools.maptool.model.Token;
import net.rptools.maptool.model.sheet.stats.StatSheetContext;
import net.rptools.maptool.model.sheet.stats.StatSheetLocation;
import net.rptools.maptool.util.HTMLUtil;
import net.rptools.maptool.util.HandlebarsUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Expand All @@ -39,10 +41,11 @@ public class StatSheet {
* @param content the content of the stat sheet.
* @param location the location of the stat sheet.
*/
public void setContent(Token token, String content, StatSheetLocation location) {
public void setContent(Token token, String content, URL entry, StatSheetLocation location) {
try {
var statSheetContext = new StatSheetContext(token, MapTool.getPlayer(), location);
var output = new HandlebarsUtil<>(content).apply(statSheetContext);
var output =
HTMLUtil.fixHTMLBase(new HandlebarsUtil<>(content, entry).apply(statSheetContext), entry);
Platform.runLater(
() -> {
var overlay =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,18 @@ public void onHoverEnter(TokenHoverEnter event) {
*/
MapTool.getFrame().hideControlPanel();
statSheet = new StatSheet();
var ss = event.token().getStatSheet();
var ssProperties = event.token().getStatSheet();
var ssId = ssProperties.id();
var ssRecord = ssManager.getStatSheet(ssId);
var token = event.token();
if (MapTool.getPlayer().isGM()
|| AppUtil.playerOwns(token)
|| token.getType() != Type.NPC) {
statSheet.setContent(
event.token(), ssManager.getStatSheetContent(ss.id()), ss.location());
event.token(),
ssManager.getStatSheetContent(ssId),
ssRecord.entry(),
ssProperties.location());
}
}
}
Expand Down
43 changes: 0 additions & 43 deletions src/main/java/net/rptools/maptool/model/library/Library.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,12 @@
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import net.rptools.lib.MD5Key;
import net.rptools.maptool.client.MapToolMacroContext;
import net.rptools.maptool.client.macro.MacroManager.MacroDetails;
import net.rptools.maptool.model.Asset;
import net.rptools.maptool.model.Token;
import net.rptools.maptool.model.library.data.LibraryData;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import org.jsoup.parser.Tag;

/** Interface for classes that represents a framework library. */
public interface Library {
Expand Down Expand Up @@ -77,45 +73,6 @@ public interface Library {
*/
CompletableFuture<String> readAsString(URL location) throws IOException;

/**
* Reads the location as a string parses the HTML in the string and sets the base to the correct
* location.
*
* @param location the location to read.
* @return the contents of the location as a string.
* @throws IOException if there is an io error reading the location.
*/
default CompletableFuture<String> readAsHTMLContent(URL location) throws IOException {
String htmlString = "";
try {
Optional<Library> library = new LibraryManager().getLibrary(location).get();
if (library.isEmpty()) {
throw new IOException("Location not found");
}

htmlString = library.get().readAsString(location).get();

var document = Jsoup.parse(htmlString);
var head = document.select("head").first();
if (head != null) {
String baseURL = location.toExternalForm().replaceFirst("\\?.*", "");
baseURL = baseURL.substring(0, baseURL.lastIndexOf("/") + 1);
var baseElement = new Element(Tag.valueOf("base"), "").attr("href", baseURL);
if (head.children().isEmpty()) {
head.appendChild(baseElement);
} else {
head.child(0).before(baseElement);
}

htmlString = document.html();
}

} catch (InterruptedException | ExecutionException e) {
throw new IOException(e);
}
return CompletableFuture.completedFuture(htmlString);
}

/**
* Returns an {@link InputStream} for the location specified.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,6 @@ public CompletableFuture<String> readAsString(URL location) throws IOException {
return addOnLibrary.readAsString(location);
}

@Override
public CompletableFuture<String> readAsHTMLContent(URL location) throws IOException {
return BuiltInLibrary.super.readAsHTMLContent(location);
}

@Override
public CompletableFuture<InputStream> read(URL location) throws IOException {
return addOnLibrary.read(location);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ public Set<StatSheet> getStatSheets(String propertyType) {
* @throws IOException if an error occurs reading the stat sheet.
*/
public void addStatSheet(StatSheet statSheet, Library library) throws IOException {
var html = library.readAsHTMLContent(statSheet.entry()).join();
var html = library.readAsString(statSheet.entry()).join();
statSheets.put(statSheet, html);
}

Expand Down
48 changes: 48 additions & 0 deletions src/main/java/net/rptools/maptool/util/HTMLUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* This software Copyright by the RPTools.net development team, and
* licensed under the Affero GPL Version 3 or, at your option, any later
* version.
*
* MapTool Source Code 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.
*
* You should have received a copy of the GNU Affero General Public
* License * along with this source Code. If not, please visit
* <http://www.gnu.org/licenses/> and specifically the Affero license
* text at <http://www.gnu.org/licenses/agpl.html>.
*/
package net.rptools.maptool.util;

import java.net.URL;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import org.jsoup.parser.Tag;

public class HTMLUtil {

/**
* Parses the HTML in the string and sets the base to the correct location.
*
* @param htmlString the HTML to parse.
* @param url the origin URL to set the base relative to
* @return the fixed-up HTML
*/
public static String fixHTMLBase(String htmlString, URL url) {
var document = Jsoup.parse(htmlString);
var head = document.select("head").first();
if (head != null) {
String baseURL = url.toExternalForm().replaceFirst("\\?.*", "");
baseURL = baseURL.substring(0, baseURL.lastIndexOf("/") + 1);
var baseElement = new Element(Tag.valueOf("base"), "").attr("href", baseURL);
if (head.children().isEmpty()) {
head.appendChild(baseElement);
} else {
head.child(0).before(baseElement);
}

htmlString = document.html();
}
return htmlString;
}
}
83 changes: 79 additions & 4 deletions src/main/java/net/rptools/maptool/util/HandlebarsUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,16 @@
import com.github.jknack.handlebars.context.JavaBeanValueResolver;
import com.github.jknack.handlebars.helper.AssignHelper;
import com.github.jknack.handlebars.helper.ConditionalHelpers;
import com.github.jknack.handlebars.helper.IncludeHelper;
import com.github.jknack.handlebars.helper.NumberHelper;
import com.github.jknack.handlebars.helper.StringHelpers;
import com.github.jknack.handlebars.io.ClassPathTemplateLoader;
import com.github.jknack.handlebars.io.TemplateLoader;
import com.github.jknack.handlebars.io.URLTemplateLoader;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Path;
import java.util.Arrays;
import net.rptools.maptool.model.Token;
import org.apache.logging.log4j.LogManager;
Expand All @@ -41,20 +48,69 @@ public class HandlebarsUtil<T> {
/** Logging class instance. */
private static final Logger log = LogManager.getLogger(Token.class);

/** Handlebars partial template loader that uses Add-On Library URIs */
private static class LibraryTemplateLoader extends URLTemplateLoader {
/** Path to template being resolved, relative paths are resolved relative to its parent. */
Path current;

private LibraryTemplateLoader(String current, String prefix, String suffix) {
if (!current.startsWith("/")) {
current = "/" + current;
}
this.current = new File(current).toPath();
setPrefix(prefix);
setSuffix(suffix);
}

private LibraryTemplateLoader(String current, String prefix) {
this(current, prefix, DEFAULT_SUFFIX);
}

private LibraryTemplateLoader(String current) {
this(current, DEFAULT_PREFIX, DEFAULT_SUFFIX);
}

/** Normalize locations by removing redundant path components */
@Override
protected String normalize(final String location) {
return new File(location).toPath().normalize().toString();
}

/** Resolve possibly relative uri relative to current rooted below prefix */
@Override
public String resolve(final String uri) {
var location = current.resolveSibling(uri).normalize().toString();
if (location.startsWith("/")) {
location = location.substring(1);
}
return getPrefix() + location + getSuffix();
}

@Override
protected URL getResource(String location) throws IOException {
if (location.startsWith("/")) {
location = location.substring(1);
}
return new URL("lib://" + location);
}
}

/**
* Creates a new instance of the utility class.
*
* @param stringTemplate The template to compile.
* @param loader The template loader for loading included partial templates
* @throws IOException If there is an error compiling the template.
*/
public HandlebarsUtil(String stringTemplate) throws IOException {
private HandlebarsUtil(String stringTemplate, TemplateLoader loader) throws IOException {
try {
Handlebars handlebars = new Handlebars();
Handlebars handlebars = new Handlebars(loader);
StringHelpers.register(handlebars);
Arrays.stream(ConditionalHelpers.values())
.forEach(h -> handlebars.registerHelper(h.name(), h));
NumberHelper.register(handlebars);
handlebars.registerHelper(AssignHelper.NAME, AssignHelper.INSTANCE);
handlebars.registerHelper(IncludeHelper.NAME, IncludeHelper.INSTANCE);

template = handlebars.compileInline(stringTemplate);
} catch (IOException e) {
Expand All @@ -63,6 +119,27 @@ public HandlebarsUtil(String stringTemplate) throws IOException {
}
}

/**
* Creates a new instance of the utility class.
*
* @param stringTemplate The template to compile.
* @param entry The lib:// URL of the template to load partial templates relative to
* @throws IOException If there is an error compiling the template.
*/
public HandlebarsUtil(String stringTemplate, URL entry) throws IOException {
this(stringTemplate, new LibraryTemplateLoader(entry.getHost() + entry.getPath()));
}

/**
* Creates a new instance of the utility class.
*
* @param stringTemplate The template to compile.
* @throws IOException If there is an error compiling the template.
*/
public HandlebarsUtil(String stringTemplate) throws IOException {
this(stringTemplate, new ClassPathTemplateLoader());
}

/**
* Applies the template to the given bean.
*
Expand All @@ -72,8 +149,6 @@ public HandlebarsUtil(String stringTemplate) throws IOException {
*/
public String apply(T bean) throws IOException {
try {

Handlebars handlebars = new Handlebars();
var context = Context.newBuilder(bean).resolver(JavaBeanValueResolver.INSTANCE).build();
return template.apply(context);
} catch (IOException e) {
Expand Down

0 comments on commit 531498d

Please sign in to comment.