diff --git a/src/main/java/dev/ebullient/convert/tools/ParseState.java b/src/main/java/dev/ebullient/convert/tools/ParseState.java index b196fae20..259a70593 100644 --- a/src/main/java/dev/ebullient/convert/tools/ParseState.java +++ b/src/main/java/dev/ebullient/convert/tools/ParseState.java @@ -26,6 +26,7 @@ public static class ParseStateInfo { boolean inHtmlTable; boolean inMarkdownTable; boolean inList; + int inFeatureType; final String listIndent; final String src; final int page; @@ -68,6 +69,20 @@ private ParseStateInfo setInMarkdownTable(boolean inTable) { return this; } + private ParseStateInfo setInFeatureType(int inFeatureType) { + this.inFeatureType = inFeatureType; + return this; + } + + private ParseStateInfo setTheRest(ParseStateInfo prev) { + this.setInFootnotes(prev.inFootnotes) + .setInHtmlTable(prev.inHtmlTable) + .setInMarkdownTable(prev.inMarkdownTable) + .setInList(prev.inList) + .setInFeatureType(prev.inFeatureType); + return this; + } + private static ParseStateInfo srcAndPage(ParseStateInfo prev, String src, int page) { if (prev == null) { return new ParseState.ParseStateInfo(src, page); @@ -76,21 +91,16 @@ private static ParseStateInfo srcAndPage(ParseStateInfo prev, String src, int pa src == null ? prev.src : src, page, prev.listIndent) - .setInFootnotes(prev.inFootnotes) - .setInHtmlTable(prev.inHtmlTable) - .setInMarkdownTable(prev.inMarkdownTable) - .setInList(prev.inList); + .setTheRest(prev); } private static ParseStateInfo changePage(ParseStateInfo prev, int page) { if (prev == null) { - throw new IllegalStateException("Page without source first?"); + Tui.instance().errorf("Change Page called without someone setting the source first? %s"); + return null; } return new ParseStateInfo(prev.src, page, prev.listIndent) - .setInFootnotes(prev.inFootnotes) - .setInHtmlTable(prev.inHtmlTable) - .setInMarkdownTable(prev.inMarkdownTable) - .setInList(prev.inList); + .setTheRest(prev); } private static ParseStateInfo inFootnotes(ParseStateInfo prev, boolean inFootnotes) { @@ -98,10 +108,8 @@ private static ParseStateInfo inFootnotes(ParseStateInfo prev, boolean inFootnot return new ParseStateInfo().setInFootnotes(inFootnotes); } return new ParseState.ParseStateInfo(prev.src, prev.page, prev.listIndent) - .setInFootnotes(inFootnotes) - .setInHtmlTable(prev.inHtmlTable) - .setInMarkdownTable(prev.inMarkdownTable) - .setInList(prev.inList); + .setTheRest(prev) + .setInFootnotes(inFootnotes); } private static ParseStateInfo inHtmlTable(ParseStateInfo prev, boolean inHtmlTable) { @@ -110,10 +118,8 @@ private static ParseStateInfo inHtmlTable(ParseStateInfo prev, boolean inHtmlTab } return new ParseState.ParseStateInfo(prev.src, prev.page, prev.listIndent) - .setInFootnotes(prev.inFootnotes) - .setInHtmlTable(inHtmlTable) - .setInMarkdownTable(prev.inMarkdownTable) - .setInList(prev.inList); + .setTheRest(prev) + .setInHtmlTable(inHtmlTable); } private static ParseStateInfo inMarkdownTable(ParseStateInfo prev, boolean inMarkdownTable) { @@ -122,10 +128,8 @@ private static ParseStateInfo inMarkdownTable(ParseStateInfo prev, boolean inMar } return new ParseState.ParseStateInfo(prev.src, prev.page, prev.listIndent) - .setInFootnotes(prev.inFootnotes) - .setInHtmlTable(prev.inHtmlTable) - .setInMarkdownTable(inMarkdownTable) - .setInList(prev.inList); + .setTheRest(prev) + .setInMarkdownTable(inMarkdownTable); } private static ParseStateInfo indentList(ParseStateInfo prev) { @@ -133,9 +137,7 @@ private static ParseStateInfo indentList(ParseStateInfo prev) { return new ParseStateInfo().setInList(true); } return new ParseState.ParseStateInfo(prev.src, prev.page, prev.listIndent + " ") - .setInFootnotes(prev.inFootnotes) - .setInHtmlTable(prev.inHtmlTable) - .setInMarkdownTable(prev.inMarkdownTable) + .setTheRest(prev) .setInList(true); } @@ -144,11 +146,18 @@ private static ParseStateInfo indentList(ParseStateInfo prev, String value) { return new ParseStateInfo().setInList(true); } return new ParseState.ParseStateInfo(prev.src, prev.page, value) - .setInFootnotes(prev.inFootnotes) - .setInHtmlTable(prev.inHtmlTable) - .setInMarkdownTable(prev.inMarkdownTable) + .setTheRest(prev) .setInList(true); } + + private static ParseStateInfo pushFeatureType(ParseStateInfo prev) { + if (prev == null) { + return new ParseStateInfo().setInFeatureType(0); + } + return new ParseState.ParseStateInfo(prev.src, prev.page, prev.listIndent) + .setTheRest(prev) + .setInFeatureType(prev.inFeatureType + 1); + } } private final Deque stack = new ArrayDeque<>(); @@ -221,6 +230,11 @@ public boolean indentList(String value) { return true; } + public boolean pushFeatureType() { + stack.addFirst(ParseStateInfo.pushFeatureType(stack.peek())); + return true; + } + public void pop(boolean pushed) { if (pushed) { String source = sourcePageString(); @@ -262,6 +276,11 @@ public boolean inTable() { return current != null && (current.inHtmlTable || current.inMarkdownTable); } + public int featureTypeDepth() { + ParseState.ParseStateInfo current = stack.peek(); + return current == null ? 0 : current.inFeatureType; + } + public String sourcePageString() { ParseState.ParseStateInfo current = stack.peek(); if (current == null || current.page == 0) { diff --git a/src/main/java/dev/ebullient/convert/tools/dnd5e/Json2QuteClass.java b/src/main/java/dev/ebullient/convert/tools/dnd5e/Json2QuteClass.java index 6759a0927..1c97d87a5 100644 --- a/src/main/java/dev/ebullient/convert/tools/dnd5e/Json2QuteClass.java +++ b/src/main/java/dev/ebullient/convert/tools/dnd5e/Json2QuteClass.java @@ -649,24 +649,40 @@ public String getName() { return cfSources.getName(); } - void appendText(JsonSource converter, List text, String pageSource) { + void appendLink(JsonSource converter, List text, String pageSource) { converter.maybeAddBlankLine(text); - text.add("### " + converter.decoratedFeatureTypeName(cfSources, cfNode) + " (Level " + level + ")"); - if (!cfSources.primarySource().equalsIgnoreCase(pageSource)) { - text.add(converter.getLabeledSource(cfSources)); + String x = converter.decoratedFeatureTypeName(cfSources, cfNode); + text.add(String.format("[%s](#%s)", x, converter.toAnchorTag(x + " (Level " + level + ")"))); + } + + void appendText(JsonSource converter, List text, String pageSource) { + boolean pushed = converter.parseState().pushFeatureType(); + try { + converter.maybeAddBlankLine(text); + text.add("### " + converter.decoratedFeatureTypeName(cfSources, cfNode) + " (Level " + level + ")"); + if (!cfSources.primarySource().equalsIgnoreCase(pageSource)) { + text.add(converter.getLabeledSource(cfSources)); + } + text.add(""); + converter.appendToText(text, cfNode.get("entries"), "####"); + } finally { + converter.parseState().pop(pushed); } - text.add(""); - converter.appendToText(text, cfNode.get("entries"), "####"); } public void appendListItemText(JsonSource converter, List text, String pageSource) { - text.add("**" + converter.decoratedFeatureTypeName(cfSources, cfNode) + "**"); - if (!cfSources.primarySource().equalsIgnoreCase(pageSource)) { - text.add(converter.getLabeledSource(cfSources)); + boolean pushed = converter.parseState().pushFeatureType(); + try { + text.add("**" + converter.decoratedFeatureTypeName(cfSources, cfNode) + "**"); + if (!cfSources.primarySource().equalsIgnoreCase(pageSource)) { + text.add(converter.getLabeledSource(cfSources)); + } + text.add(""); + converter.appendToText(text, cfNode.get("entries"), null); + text.add(""); + } finally { + converter.parseState().pop(pushed); } - text.add(""); - converter.appendToText(text, cfNode.get("entries"), null); - text.add(""); } } diff --git a/src/main/java/dev/ebullient/convert/tools/dnd5e/JsonSource.java b/src/main/java/dev/ebullient/convert/tools/dnd5e/JsonSource.java index d7dc20f61..c39d3da93 100644 --- a/src/main/java/dev/ebullient/convert/tools/dnd5e/JsonSource.java +++ b/src/main/java/dev/ebullient/convert/tools/dnd5e/JsonSource.java @@ -64,8 +64,9 @@ default int intOrDefault(JsonNode source, String key, int value) { default int intOrThrow(JsonNode source, String key) { JsonNode result = source.get(key); if (result == null || !result.canConvertToInt()) { - throw new IllegalStateException( - "Missing required field, or field is not a number. Key: " + key + "; value: " + result); + tui().errorf("Missing required field, or field is not a number. Key: %s; value: %s; from %s: %s", + key, result, getSources(), source); + return -999; } return result.asInt(); } @@ -273,7 +274,11 @@ default void appendClassFeatureRef(List text, JsonNode entry, Tools5eInd if (cf == null) { return; // skipped or not found } - if (parseState().inList()) { + if (parseState().featureTypeDepth() > 2) { + tui().errorf("Cycle in class or subclass features found in %s", cf.cfSources); + // this is within an existing feature description. Emit as a link + cf.appendLink(this, text, parseState().getSource(featureType)); + } else if (parseState().inList()) { // emit within an existing list item cf.appendListItemText(this, text, parseState().getSource(featureType)); } else {