Skip to content

Commit 5b1b979

Browse files
committed
Figure numbers working in generated HTML and PDF
1 parent 2ba02be commit 5b1b979

File tree

3 files changed

+136
-17
lines changed

3 files changed

+136
-17
lines changed

src/main/java/quanta/model/TreeNode.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ public TreeNode(SubNode node) {
1212
public SubNode node;
1313
public TreeNode parent;
1414
public LinkedList<TreeNode> children;
15+
public int figNumStart;
1516
}

src/main/java/quanta/service/exports/ExportArchiveBase.java

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ private class MarkdownLink {
9797

9898
// markdown links keyed by link url
9999
private HashMap<String, MarkdownLink> markdownLinks = new HashMap<>();
100+
private HashMap<String, TreeNode> treeItemsByNodeName = new HashMap<>();
101+
private int figNumStart = 1;
100102

101103
private SubNode node;
102104
private SubNode parentSiteNode;
@@ -145,6 +147,7 @@ public String export(String nodeId) {
145147
}
146148

147149
TreeNode rootNode = svc_mongoRead.getSubGraphTree(nodeId, criteria, null, null);
150+
prePocessTree(rootNode);
148151
node = rootNode.node;
149152
baseSlashCount = StringUtils.countMatches(node.getPath(), "/");
150153

@@ -186,6 +189,30 @@ public String export(String nodeId) {
186189
}
187190
}
188191

192+
private void prePocessTree(TreeNode root) {
193+
194+
if (root.node.getAttachments() != null && root.node.getAttachments().size() > 0) {
195+
root.figNumStart = figNumStart;
196+
figNumStart += root.node.getAttachments().size();
197+
}
198+
199+
String nodeName = root.node.getName();
200+
if (nodeName != null) {
201+
if (treeItemsByNodeName.containsKey(nodeName)) {
202+
log.warn("Duplicate node name: " + nodeName + " on nodeId " + root.node.getIdStr());
203+
} else {
204+
treeItemsByNodeName.put(nodeName, root);
205+
}
206+
}
207+
208+
if (root.children == null)
209+
return;
210+
211+
for (TreeNode c : root.children) {
212+
prePocessTree(c);
213+
}
214+
}
215+
189216
private void writeMainFile() {
190217
switch (contentType) {
191218
case "md":
@@ -409,6 +436,7 @@ private boolean processNodeExport(String parentFolder, String deeperPath, TreeNo
409436

410437
String content = node.getContent() != null ? node.getContent() : "";
411438
parseMarkdownLinks(content);
439+
content = injectFigureLinks(content);
412440
content = content.trim();
413441

414442
if (updateHeadings) {
@@ -443,12 +471,14 @@ private boolean processNodeExport(String parentFolder, String deeperPath, TreeNo
443471

444472
// Process all attachments just to insert File Tags into content
445473
if (atts != null) {
474+
int figNum = tn.figNumStart;
446475
for (Attachment att : atts) {
447476
// Process File Tag type attachments here first
448477
if (!"ft".equals(att.getPosition())) {
449478
continue;
450479
}
451-
handleAttachment(node, true, contentVal, deeperPath, targetFolder, writeFile, att);
480+
handleAttachment(node, true, contentVal, deeperPath, targetFolder, writeFile, att, figNum);
481+
figNum++;
452482
}
453483
}
454484

@@ -471,12 +501,14 @@ private boolean processNodeExport(String parentFolder, String deeperPath, TreeNo
471501
// special handling for htmlContent we have to do this File Tag injection AFTER
472502
// the html escaping and processing that's done in the line above
473503
if (atts != null) {
504+
int figNum = tn.figNumStart;
474505
for (Attachment att : atts) {
475506
// Process File Tag type attachments here first
476507
if (!"ft".equals(att.getPosition())) {
477508
continue;
478509
}
479-
handleAttachment(node, true, contentVal, deeperPath, targetFolder, writeFile, att);
510+
handleAttachment(node, true, contentVal, deeperPath, targetFolder, writeFile, att, figNum);
511+
figNum++;
480512
}
481513
}
482514

@@ -498,13 +530,15 @@ private boolean processNodeExport(String parentFolder, String deeperPath, TreeNo
498530
}
499531

500532
if (atts != null) {
533+
int figNum = tn.figNumStart;
501534
for (Attachment att : atts) {
502535
// Skip File Tag type attachments because they'll already have been processed
503536
// above
504537
if ("ft".equals(att.getPosition())) {
505538
continue;
506539
}
507-
handleAttachment(node, false, null, deeperPath, targetFolder, writeFile, att);
540+
handleAttachment(node, false, null, deeperPath, targetFolder, writeFile, att, figNum);
541+
figNum++;
508542
}
509543
}
510544

@@ -524,6 +558,27 @@ private boolean processNodeExport(String parentFolder, String deeperPath, TreeNo
524558
return ret;
525559
}
526560

561+
private String injectFigureLinks(String content) {
562+
// using regex we find the pattern {{figure:[node_name]}} and iterate over all of them where the
563+
// [node_name]
564+
// is a variable that we need to have during iteration
565+
String regex = "\\{\\{figure:([^\\}]+)\\}\\}";
566+
Pattern pattern = Pattern.compile(regex);
567+
Matcher matcher = pattern.matcher(content);
568+
while (matcher.find()) {
569+
String nodeName = matcher.group(1);
570+
log.debug("FIGURE: " + nodeName);
571+
TreeNode tn = treeItemsByNodeName.get(nodeName);
572+
if (tn == null) {
573+
// needs to be a reported error that makes it's way to the screen.
574+
log.warn("Figure node not found: " + nodeName);
575+
continue;
576+
}
577+
content = content.replace("{{figure:" + nodeName + "}}", "Fig. " + tn.figNumStart);
578+
}
579+
return content;
580+
}
581+
527582
private String getTitleFromContent(String content) {
528583
String title = null;
529584
int newLineIdx = content.indexOf("\n");
@@ -780,7 +835,7 @@ private void writeAttachmentFileForNode(String parentFolder, SubNode node, Strin
780835
* content and return the content
781836
*/
782837
private void handleAttachment(SubNode node, boolean injectingTag, Val<String> content, String deeperPath,
783-
String parentFolder, boolean writeFile, Attachment att) {
838+
String parentFolder, boolean writeFile, Attachment att, int figNum) {
784839
String nodeId = node.getIdStr();
785840

786841
String af = getAttachmentFileName(att, node);
@@ -845,6 +900,11 @@ private void handleAttachment(SubNode node, boolean injectingTag, Val<String> co
845900
// mdLink = "\n![" + displayName + "](" + fullUrl + ")\n\n";
846901
String domId = "img_" + nodeId + "_" + att.getKey();
847902
mdLink = "<img id='%s' src='%s' %s/>\n\n".formatted(domId, fullUrl, sizePart);
903+
904+
if (figNum > 0) {
905+
mdLink = "<figure>\n" + mdLink + "<figcaption>Fig. " + figNum + "</figcaption>\n</figure>\n";
906+
}
907+
848908
processMdAtt(injectingTag, content, att, mdLink);
849909
break;
850910
default:

src/main/java/quanta/service/exports/ExportServicePDF.java

Lines changed: 71 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import java.nio.charset.StandardCharsets;
77
import java.util.HashMap;
88
import java.util.List;
9+
import java.util.regex.Matcher;
10+
import java.util.regex.Pattern;
911
import org.apache.commons.lang3.StringUtils;
1012
import org.slf4j.Logger;
1113
import org.slf4j.LoggerFactory;
@@ -42,6 +44,9 @@ public class ExportServicePDF extends ServiceBase {
4244
private ExportRequest req;
4345
private int baseSlashCount = 0;
4446

47+
private HashMap<String, TreeNode> treeItemsByNodeName = new HashMap<>();
48+
private int figNumStart = 1;
49+
4550
/*
4651
* Exports the node specified in the req. If the node specified is "/", or the repository root, then
4752
* we don't expect a filename, because we will generate a timestamped one.
@@ -70,6 +75,8 @@ private void exportNodeToFile(String nodeId) {
7075
TreeNode rootNode = req.isThreadAsPDF() ? svc_mongoRead.getThreadGraphTree(nodeId) : //
7176
svc_mongoRead.getSubGraphTree(nodeId, null, null, null);
7277

78+
prePocessTree(rootNode);
79+
7380
SubNode exportNode = rootNode.node;
7481
String fileName = svc_snUtil.getExportFileName(req.getFileName(), exportNode);
7582
shortFileName = fileName + ".pdf";
@@ -106,10 +113,33 @@ private void exportNodeToFile(String nodeId) {
106113
}
107114
}
108115

116+
private void prePocessTree(TreeNode root) {
117+
if (root.node.getAttachments() != null && root.node.getAttachments().size() > 0) {
118+
root.figNumStart = figNumStart;
119+
figNumStart += root.node.getAttachments().size();
120+
}
121+
122+
String nodeName = root.node.getName();
123+
if (nodeName != null) {
124+
if (treeItemsByNodeName.containsKey(nodeName)) {
125+
log.warn("Duplicate node name: " + nodeName + " on nodeId " + root.node.getIdStr());
126+
} else {
127+
treeItemsByNodeName.put(nodeName, root);
128+
}
129+
}
130+
131+
if (root.children == null)
132+
return;
133+
134+
for (TreeNode c : root.children) {
135+
prePocessTree(c);
136+
}
137+
}
138+
109139
private void recurseNode(TreeNode tn, int level) {
110140
if (tn.node == null)
111141
return;
112-
processNode(tn.node);
142+
processNode(tn);
113143

114144
if (level == 0 && req.isIncludeToc()) {
115145
markdown.append("[TOC]");
@@ -128,12 +158,12 @@ private void recurseNode(TreeNode tn, int level) {
128158
}
129159
}
130160

131-
private void processNode(SubNode node) {
161+
private void processNode(TreeNode tn) {
132162
String nodeMarkdown = req.isDividerLine() ? "\n----\n" : "\n";
133163

134-
String id = req.isIncludeIDs() ? (" (id:" + node.getIdStr() + ")") : "";
164+
String id = req.isIncludeIDs() ? (" (id:" + tn.node.getIdStr() + ")") : "";
135165
if (req.isIncludeOwners()) {
136-
AccountNode accntNode = svc_user.getAccountNode(node.getOwner());
166+
AccountNode accntNode = svc_user.getAccountNode(tn.node.getOwner());
137167
if (accntNode != null) {
138168
nodeMarkdown += "Owner: " + accntNode.getStr(NodeProp.USER) + id + "\n";
139169
}
@@ -143,27 +173,49 @@ private void processNode(SubNode node) {
143173
}
144174
nodeMarkdown += "\n";
145175

146-
String content = node.getContent();
147-
TypeBase plugin = svc_typeMgr.getPluginByType(node.getType());
176+
String content = tn.node.getContent();
177+
TypeBase plugin = svc_typeMgr.getPluginByType(tn.node.getType());
148178
if (plugin != null) {
149-
content = plugin.formatExportText("pdf", node);
179+
content = plugin.formatExportText("pdf", tn.node);
150180
}
151181

152182
if (content != null && req.isUpdateHeadings()) {
153183
content = content.trim();
154-
int slashCount = StringUtils.countMatches(node.getPath(), "/");
184+
int slashCount = StringUtils.countMatches(tn.node.getPath(), "/");
155185
int lev = slashCount - baseSlashCount; // top level node comes here with lev=0
156186
if (lev > 6)
157187
lev = 6;
158188
content = svc_edit.translateHeadingsForLevel(content, lev);
159189
}
160190

161-
content = insertPropertySubstitutions(content, node);
191+
content = insertPropertySubstitutions(content, tn.node);
192+
content = injectFigureLinks(content);
162193
nodeMarkdown += content + "\n";
163-
nodeMarkdown = writeImages(node, nodeMarkdown);
194+
nodeMarkdown = writeImages(tn, nodeMarkdown);
164195
markdown.append(nodeMarkdown);
165196
}
166197

198+
private String injectFigureLinks(String content) {
199+
// using regex we find the pattern {{figure:[node_name]}} and iterate over all of them where the
200+
// [node_name]
201+
// is a variable that we need to have during iteration
202+
String regex = "\\{\\{figure:([^\\}]+)\\}\\}";
203+
Pattern pattern = Pattern.compile(regex);
204+
Matcher matcher = pattern.matcher(content);
205+
while (matcher.find()) {
206+
String nodeName = matcher.group(1);
207+
log.debug("FIGURE: " + nodeName);
208+
TreeNode tn = treeItemsByNodeName.get(nodeName);
209+
if (tn == null) {
210+
// needs to be a reported error that makes it's way to the screen.
211+
log.warn("Figure node not found: " + nodeName);
212+
continue;
213+
}
214+
content = content.replace("{{figure:" + nodeName + "}}", "Fig. " + tn.figNumStart);
215+
}
216+
return content;
217+
}
218+
167219
private String insertPropertySubstitutions(String content, SubNode node) {
168220
HashMap<String, Object> propMap = node.getProps();
169221
if (propMap != null && propMap.keySet() != null) {
@@ -177,13 +229,15 @@ private String insertPropertySubstitutions(String content, SubNode node) {
177229
return content;
178230
}
179231

180-
private String writeImages(SubNode node, String content) {
181-
List<Attachment> atts = node.getOrderedAttachments();
232+
private String writeImages(TreeNode tn, String content) {
233+
List<Attachment> atts = tn.node.getOrderedAttachments();
182234
if (atts == null)
183235
return content;
184236

237+
int figNum = tn.figNumStart - 1;
185238
// process all attachments specifically to embed the image ones
186239
for (Attachment att : atts) {
240+
figNum++;
187241
// Since GIFs are really only ever used for animated GIFs nowadays, and since PDF files cannot
188242
// render them we just always ignore GIF files when generating PDFs.
189243
if (att.getFileName() != null && att.getFileName().toLowerCase().endsWith(".gif")) {
@@ -218,7 +272,7 @@ private String writeImages(SubNode node, String content) {
218272

219273
String src = null;
220274
if (bin != null) {
221-
String path = AppController.API_PATH + "/bin/" + bin + "?nodeId=" + node.getIdStr() + "&token="
275+
String path = AppController.API_PATH + "/bin/" + bin + "?nodeId=" + tn.node.getIdStr() + "&token="
222276
+ URLEncoder.encode(TL.getSC().getUserToken(), StandardCharsets.UTF_8);
223277
src = svc_prop.getProtocolHostAndPort() + path;
224278
} //
@@ -230,6 +284,10 @@ else if (url != null) {
230284

231285
String imgHtml = "\n<img src='" + src + "' " + style + "/>\n";
232286

287+
if (figNum > 0) {
288+
imgHtml = "<figure>\n" + imgHtml + "<figcaption>Fig. " + figNum + "</figcaption>\n</figure>\n";
289+
}
290+
233291
if ("ft".equals(att.getPosition())) {
234292
content = content.replace("{{" + att.getFileName() + "}}", imgHtml);
235293
} else {

0 commit comments

Comments
 (0)