i18n framework for HTML generation in renderers - step #1

This commit is contained in:
Grahame Grieve 2023-03-30 10:16:51 +11:00
parent 473b63dbff
commit 1b6137967f
4 changed files with 157 additions and 13 deletions

View File

@ -7,6 +7,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r5.model.CodeSystem; import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode; import org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode;
@ -138,23 +139,29 @@ public class CodeSystemRenderer extends TerminologyRenderer {
} }
} }
private String sentenceForContent(CodeSystemContentMode mode) {
switch (mode) {
case COMPLETE: return "This code system <param name='cs'/> defines the following code<if test='code-count != 1'>s</if>:";
case EXAMPLE: return "This code system <param name='cs'/> provides some example code<if test='code-count != 1'>s</if>:";
case FRAGMENT: return "This code system <param name='cs'/> provides a fragment that includes following code<if test='code-count != 1'>s</if>:";
case NOTPRESENT: return "This code system <param name='cs'/> defines codes, but no codes are represented here";
case SUPPLEMENT: return "This code system <param name='cs'/> defines properties on the following code<if test='code-count != 1'>s</if>:";
}
throw new FHIRException("Unknown CodeSystemContentMode mode");
}
private boolean generateCodeSystemContent(XhtmlNode x, CodeSystem cs, boolean hasExtensions, List<UsedConceptMap> maps, boolean props) throws FHIRFormatError, DefinitionException, IOException { private boolean generateCodeSystemContent(XhtmlNode x, CodeSystem cs, boolean hasExtensions, List<UsedConceptMap> maps, boolean props) throws FHIRFormatError, DefinitionException, IOException {
if (props) { if (props) {
x.para().b().tx(getContext().getWorker().translator().translate("xhtml-gen-cs", "Concepts", getContext().getLang())); x.para().b().tx(getContext().getWorker().translator().translate("xhtml-gen-cs", "Concepts", getContext().getLang()));
} }
XhtmlNode p = x.para(); XhtmlNode p = x.para();
p.tx(getContext().getWorker().translator().translateAndFormat("xhtml-gen-cs", getContext().getLang(), "This code system ")); p.param("cs").code().tx(cs.getUrl());
p.code().tx(cs.getUrl()); p.paramValue("code-count", CodeSystemUtilities.countCodes(cs));
if (cs.getContent() == CodeSystemContentMode.COMPLETE) p.sentenceForParams(sentenceForContent(cs.getContent()));
p.tx(getContext().getWorker().translator().translateAndFormat("xhtml-gen-cs", getContext().getLang(), " defines the following codes")+":"); if (cs.getContent() == CodeSystemContentMode.NOTPRESENT) {
else if (cs.getContent() == CodeSystemContentMode.EXAMPLE)
p.tx(getContext().getWorker().translator().translateAndFormat("xhtml-gen-cs", getContext().getLang(), " defines some example codes")+":");
else if (cs.getContent() == CodeSystemContentMode.FRAGMENT )
p.tx(getContext().getWorker().translator().translateAndFormat("xhtml-gen-cs", getContext().getLang(), " defines many codes, of which the following are a subset")+":");
else if (cs.getContent() == CodeSystemContentMode.NOTPRESENT ) {
p.tx(getContext().getWorker().translator().translateAndFormat("xhtml-gen-cs", getContext().getLang(), " defines many codes, but they are not represented here"));
return false; return false;
} }
XhtmlNode t = x.table( "codes"); XhtmlNode t = x.table( "codes");
boolean definitions = false; boolean definitions = false;
boolean commentS = false; boolean commentS = false;
@ -198,7 +205,7 @@ public class CodeSystemRenderer extends TerminologyRenderer {
if (langs.size() > 0) { if (langs.size() > 0) {
Collections.sort(langs); Collections.sort(langs);
x.para().b().tx("Additional Language Displays"); x.para().b().tx("Additional Language Displays");
t = x.table( "codes"); t = x.table("codes");
XhtmlNode tr = t.tr(); XhtmlNode tr = t.tr();
tr.td().b().tx("Code"); tr.td().b().tx("Code");
for (String lang : langs) for (String lang : langs)

View File

@ -708,5 +708,17 @@ public class CodeSystemUtilities {
} }
return false; return false;
} }
public static int countCodes(CodeSystem cs) {
return countCodes(cs.getConcept());
}
private static int countCodes(List<ConceptDefinitionComponent> concept) {
int t = concept.size();
for (ConceptDefinitionComponent cd : concept) {
t = t + (cd.hasConcept() ? countCodes(cd.getConcept()) : 0);
}
return t;
}
} }

View File

@ -37,6 +37,7 @@ import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.ArrayList;
import java.util.Map; import java.util.Map;
import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRException;
@ -52,7 +53,6 @@ import ca.uhn.fhir.model.primitive.XhtmlDt;
public class XhtmlNode extends XhtmlFluent implements IBaseXhtml { public class XhtmlNode extends XhtmlFluent implements IBaseXhtml {
private static final long serialVersionUID = -4362547161441436492L; private static final long serialVersionUID = -4362547161441436492L;
public static class Location implements Serializable { public static class Location implements Serializable {
private static final long serialVersionUID = -4079302502900219721L; private static final long serialVersionUID = -4079302502900219721L;
private int line; private int line;
@ -87,6 +87,8 @@ public class XhtmlNode extends XhtmlFluent implements IBaseXhtml {
private boolean notPretty; private boolean notPretty;
private boolean seperated; private boolean seperated;
private Boolean emptyExpanded; private Boolean emptyExpanded;
private Map<String, XhtmlNode> namedParams;
private Map<String, String> namedParamValues;
public XhtmlNode() { public XhtmlNode() {
super(); super();
@ -769,5 +771,70 @@ public class XhtmlNode extends XhtmlFluent implements IBaseXhtml {
return span("color: "+color, null); return span("color: "+color, null);
} }
public XhtmlNode param(String name) {
XhtmlNode node = new XhtmlNode(NodeType.Element, "p"); // this node is dead will never appear anywhere, but we are in paragraph mode
if (namedParams == null) {
namedParams = new HashMap<>();
}
namedParams.put(name, node);
return node;
}
public void paramValue(String name, String value) {
if (namedParamValues == null) {
namedParamValues = new HashMap<>();
}
namedParamValues.put(name, value);
}
public void paramValue(String name, int value) {
if (namedParamValues == null) {
namedParamValues = new HashMap<>();
}
namedParamValues.put(name, Integer.toString(value));
}
public void sentenceForParams(String structure) throws FHIRException, IOException {
XhtmlNode script = new XhtmlParser().parseFragment("<div>"+structure+"</div>");
for (XhtmlNode n : script.getChildNodes()) {
if ("param".equals(n.getName())) {
XhtmlNode node = namedParams.get(n.getAttribute("name"));
if (node != null) {
this.getChildNodes().addAll(node.getChildNodes());
}
} else if ("if".equals(n.getName())) {
String test = n.getAttribute("test");
if (passesTest(test)) {
this.getChildNodes().addAll(n.getChildNodes());
}
} else {
this.getChildNodes().add(n);
}
}
namedParams = null;
namedParamValues = null;
}
private boolean passesTest(String test) {
String[] p = test.split("\\s+");
if (p.length != 3) {
return false;
}
if (!namedParamValues.containsKey(p[0])) {
return false;
}
String pv = namedParamValues.get(p[0]);
switch (p[1]) {
case "=": return p[2].equalsIgnoreCase(pv);
case "!=": return !p[2].equalsIgnoreCase(pv);
case "<": return Utilities.isInteger(p[2]) && Utilities.isInteger(pv) && Integer.parseInt(pv) < Integer.parseInt(p[2]);
case "<=": return Utilities.isInteger(p[2]) && Utilities.isInteger(pv) && Integer.parseInt(pv) <= Integer.parseInt(p[2]);
case ">": return Utilities.isInteger(p[2]) && Utilities.isInteger(pv) && Integer.parseInt(pv) > Integer.parseInt(p[2]);
case ">=": return Utilities.isInteger(p[2]) && Utilities.isInteger(pv) && Integer.parseInt(pv) >= Integer.parseInt(p[2]);
}
return false;
}
} }

View File

@ -7,6 +7,7 @@ import java.io.ObjectOutputStream;
import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.xhtml.NodeType;
import org.hl7.fhir.utilities.xhtml.XhtmlComposer; import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
import org.hl7.fhir.utilities.xhtml.XhtmlNode; import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import org.hl7.fhir.utilities.xhtml.XhtmlParser; import org.hl7.fhir.utilities.xhtml.XhtmlParser;
@ -150,5 +151,62 @@ public class XhtmlNodeTest {
Assertions.assertEquals(src.trim(), xml.trim()); Assertions.assertEquals(src.trim(), xml.trim());
} }
@Test
public void testComposeScripted1() throws IOException {
XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
x.para().tx("This is a paragraph");
Assertions.assertEquals("<div><p>This is a paragraph</p></div>", new XhtmlComposer(true, false).compose(x));
}
@Test
public void testComposeScripted2() throws IOException {
XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
XhtmlNode p = x.para();
p.tx("This is ");
p.tx("a paragraph");
Assertions.assertEquals("<div><p>This is a paragraph</p></div>", new XhtmlComposer(true, false).compose(x));
}
@Test
public void testComposeScripted3() throws IOException {
XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
XhtmlNode p = x.para();
p.tx("This is a ");
p.b().tx("long");
p.tx(" paragraph");
Assertions.assertEquals("<div><p>This is a <b>long</b> paragraph</p></div>", new XhtmlComposer(true, false).compose(x));
}
@Test
public void testComposeScripted4() throws IOException {
XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
XhtmlNode p = x.para();
p.param("long").b().tx("long");
p.sentenceForParams("This <b>is</b> a <param name='long'/> paragraph");
Assertions.assertEquals("<div><p>This <b>is</b> a <b>long</b> paragraph</p></div>", new XhtmlComposer(true, false).compose(x));
}
@Test
public void testComposeScripted5() throws IOException {
XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
XhtmlNode p = x.para();
p.param("long").b().tx("long");
p.paramValue("count", "2");
p.sentenceForParams("This <b>is</b> a <param name='long'/> paragraph<if test='count != 1'>s</if>");
Assertions.assertEquals("<div><p>This <b>is</b> a <b>long</b> paragraphs</p></div>", new XhtmlComposer(true, false).compose(x));
}
@Test
public void testComposeScripted6() throws IOException {
XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
XhtmlNode p = x.para();
p.param("long").b().tx("long");
p.paramValue("count", "1");
p.sentenceForParams("This <b>is</b> a <param name='long'/> paragraph<if test='count != 1'>s</if>");
Assertions.assertEquals("<div><p>This <b>is</b> a <b>long</b> paragraph</p></div>", new XhtmlComposer(true, false).compose(x));
}
} }