Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨🐛 5e: citations #254

Merged
merged 2 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions src/main/java/dev/ebullient/convert/tools/ParseState.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.databind.JsonNode;

import dev.ebullient.convert.config.TtrpgConfig;
import dev.ebullient.convert.io.Tui;
import dev.ebullient.convert.qute.SourceAndPage;
import dev.ebullient.convert.tools.dnd5e.Tools5eIndexType;
import dev.ebullient.convert.tools.pf2e.Pf2eIndexType;

public class ParseState {

enum ParseStateField implements JsonNodeReader {
footnotes,
page,
source
}
Expand Down Expand Up @@ -149,6 +152,7 @@ private static ParseStateInfo indentList(ParseStateInfo prev, String value) {
}

private final Deque<ParseState.ParseStateInfo> stack = new ArrayDeque<>();
private final Map<String, String> citations = new HashMap<>();

public boolean push(CompendiumSources sources, JsonNode rootNode) {
if (rootNode != null && (rootNode.has("page") || rootNode.has("source"))) {
Expand Down Expand Up @@ -219,7 +223,12 @@ public boolean indentList(String value) {

public void pop(boolean pushed) {
if (pushed) {
stack.removeFirst();
String source = sourcePageString();
ParseStateInfo removed = stack.removeFirst();
if (stack.isEmpty() && !citations.isEmpty()) {
Tui.instance().errorf("%s left unreferenced citations behind", source.isEmpty() ? removed : source);
citations.clear();
}
}
}

Expand Down Expand Up @@ -258,7 +267,7 @@ public String sourcePageString() {
if (current == null || current.page == 0) {
return "";
}
return String.format("<sup>%s p. %s</sup>",
return String.format("%s p. %s",
current.src, current.page);
}

Expand Down Expand Up @@ -308,4 +317,23 @@ public String getPage() {
public SourceAndPage toSourceAndPage() {
return new SourceAndPage(getSource(), getPage());
}

public void addCitation(String key, String citationText) {
String old = citations.put(key, citationText);
if (old != null && !old.equals(citationText)) {
Tui.instance().errorf("Duplicate citation text for %s:\nOLD:\n%s\nNEW:\n%s", key, old, citationText);
}
}

public void popCitations(List<String> footerEntries) {
citations.forEach((k, v) -> {
if (v.startsWith("|")) { // we have a table, assume noted thing is in the footnote
footerEntries.add(v);
return;
}
footerEntries.add(String.format("[%s]: %s",
k, v));
});
citations.clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ List<Spellcasting> monsterSpellcasting() {
spellcasting.spells.put(f.getKey(), spells);
});
}
parseState().popCitations(spellcasting.footerEntries);
casting.add(spellcasting);
});
return casting;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public interface JsonTextReplacement extends JsonTextConverter<Tools5eIndexType>
Pattern optionalFeaturesFilter = Pattern.compile("\\{@filter ([^|}]+)\\|optionalfeatures\\|([^}]+)*}");
Pattern featureTypePattern = Pattern.compile("(?:[Ff]eature )?[Tt]ype=([^|}]+)");
Pattern featureSourcePattern = Pattern.compile("source=([^|}]+)");
Pattern superscriptCitationPattern = Pattern.compile("\\{@(sup|cite) ([^}]+)}");

Tools5eIndex index();

Expand Down Expand Up @@ -155,6 +156,22 @@ default String _replaceTokenText(String input, boolean nested) {
result = abilitySavePattern.matcher(result).replaceAll(this::replaceSkillOrAbility);
result = skillCheckPattern.matcher(result).replaceAll(this::replaceSkillCheck);

result = superscriptCitationPattern.matcher(result).replaceAll((match) -> {
// {@sup {@cite Casting Times|FleeMortals|A}}
// {@sup whatever}
// {@cite Casting Times|FleeMortals|A}
// {@cite Casting Times|FleeMortals|{@sup A}}
if (match.group(1).equals("sup")) {
String text = replaceText(match.group(2));
if (text.startsWith("[^") || text.startsWith("^[")) {
// do not put citations in superscript (obsidian/markdown will do it)
return text;
}
return "<sup>" + text + "</sup>";
}
return handleCitation(match.group(2));
});

result = homebrewPattern.matcher(result).replaceAll((match) -> {
// {@homebrew changes|modifications}, {@homebrew additions} or {@homebrew |removals}
String s = match.group(1);
Expand Down Expand Up @@ -258,7 +275,6 @@ default String _replaceTokenText(String input, boolean nested) {
.replaceAll("\\{@italic ([^}]+)}", "*$1*")
.replaceAll("\\{@s ([^}]+?)}", "~~$1~~")
.replaceAll("\\{@strike ([^}]+)}", "~~$1~~")
.replaceAll("\\{@sup ([^}]+?)}", "<sup>$1</sup>")
.replaceAll("\\{@u ([^}]+?)}", "_$1_")
.replaceAll("\\{@underline ([^}]+?)}", "_$1_")
.replaceAll("\\{@comic ([^}]+?)}", "$1")
Expand Down Expand Up @@ -708,6 +724,32 @@ parts[0], index().rulesVaultRoot(),
}
}

default String handleCitation(String citationTag) {
// Casting Times|FleeMortals|A
// Casting Times|FleeMortals|{@sup A}
String[] parts = citationTag.split("\\|");
if (parts.length < 3) {
tui().errorf("Badly formed citation %s in %s", citationTag, getSources().getKey());
return citationTag;
}
String key = index().getAliasOrDefault(Tools5eIndexType.citation.createKey(parts[0], parts[1]));
String annotation = replaceText(parts[2]).replaceAll("</?sup>", "");
JsonNode jsonSource = index().getNode(key);
if (index().isExcluded(key) || jsonSource == null) {
return annotation;
}
String blockRef = "^" + slugify(key);
List<String> text = new ArrayList<>();
appendToText(text, jsonSource, null);
if (text.get(text.size() - 1).startsWith("^")) {
blockRef = text.get(text.size() - 1);
} else {
text.add(blockRef);
}
parseState().addCitation(key, String.join("\n", text));
return String.format("[%s](#%s)", annotation, blockRef);
}

default String decoratedUaName(String name, Tools5eSources sources) {
Optional<String> uaSource = sources.uaSource();
if (uaSource.isPresent() && !name.contains("(UA")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ private void indexTypes(String filename, JsonNode node) {
Tools5eIndexType.vehicleFluff.withArrayFrom(node, this::addToIndex);

Tools5eIndexType.language.withArrayFrom(node, this::addToIndex);
Tools5eIndexType.citation.withArrayFrom(node, this::addToIndex);

Tools5eIndexType.itemEntry.withArrayFrom(node, this::addToIndex);
Tools5eIndexType.itemTypeAdditionalEntries.withArrayFrom(node, this::addToIndex);
Expand Down Expand Up @@ -306,7 +307,7 @@ void addToIndex(Tools5eIndexType type, JsonNode node) {
// add subclass to alias. Referenced from spells
addAlias(lookupKey, key);
}
if (type == Tools5eIndexType.table || type == Tools5eIndexType.tableGroup) {
if (type == Tools5eIndexType.table || type == Tools5eIndexType.tableGroup || type == Tools5eIndexType.citation) {
SourceAndPage sp = new SourceAndPage(node);
tableIndex.computeIfAbsent(sp, k -> new ArrayList<>()).add(node);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public enum Tools5eIndexType implements IndexType, JsonNodeReader {
card,
charoption,
charoptionFluff,
citation,
classtype("class"),
classFluff, // not really a thing.
classfeature,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ default void appendObjectToText(List<String> text, JsonNode node, String heading

/** Internal */
default void appendTextHeaderBlock(List<String> text, JsonNode node, String heading) {
String pageRef = parseState().sourcePageString();
String pageRef = parseState().sourcePageString("<sup>%s p. %s</sup>");

if (heading == null) {
List<String> inner = new ArrayList<>();
Expand Down
21 changes: 12 additions & 9 deletions src/main/resources/convertData.json
Original file line number Diff line number Diff line change
Expand Up @@ -340,9 +340,12 @@
]
},
"fallbackImage": {
"img/PSZ/Archon of Redemption.png": "img/PSZ/Archon Of Redemption.png",
"img/bestiary/ERLW/Inspired.png": "img/bestiary/ERLW/Inspired.webp",
"img/bestiary/MTF/Merrenoloth.jpg": "img/bestiary/MTF/Merrenoloth.webp",
"img/bestiary/SDW/Lhammaruntosz.jpg": "img/SDW/Lhammaruntosz.png",
"img/items/CRCotN/Medal of the Maze.jpg": "img/items/CRCotN/Medal of the Maze.webp",
"img/PSZ/Archon of Redemption.png": "img/PSZ/Archon Of Redemption.png"
"img/bestiary/VGM/Deep Scion.jpg": "img/bestiary/VGM/Deep Scion.webp",
"img/items/CRCotN/Medal of the Maze.jpg": "img/items/CRCotN/Medal of the Maze.webp"
},
"markerFiles": [
"cultsboons.json",
Expand Down Expand Up @@ -399,30 +402,30 @@
"sources": [
"actions.json",
"adventures.json",
"books.json",
"backgrounds.json",
"fluff-backgrounds.json",
"bestiary",
"bestiary/legendarygroups.json",
"bestiary/template.json",
"books.json",
"class",
"conditionsdiseases.json",
"decks.json",
"deities.json",
"feats.json",
"optionalfeatures.json",
"fluff-backgrounds.json",
"fluff-items.json",
"fluff-races.json",
"generated/gendata-tables.json",
"items.json",
"items-base.json",
"fluff-items.json",
"items.json",
"magicvariants.json",
"objects.json",
"optionalfeatures.json",
"psionics.json",
"races.json",
"fluff-races.json",
"rewards.json",
"skills.json",
"senses.json",
"skills.json",
"spells",
"tables.json",
"trapshazards.json",
Expand Down