Narrative docs
This commit is contained in:
parent
cfdf0bcf27
commit
12d8e8d19c
|
@ -215,7 +215,7 @@
|
|||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jxr-plugin</artifactId>
|
||||
<version>2.3</version>
|
||||
<version>2.4</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
|
|
|
@ -38,7 +38,7 @@ import ca.uhn.fhir.model.primitive.XhtmlDt;
|
|||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
|
||||
public class DefaultThymeleafNarrativeGenerator extends BaseThymeleafNarrativeGenerator implements INarrativeGenerator {
|
||||
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DefaultThymeleafNarrativeGenerator.class);
|
||||
|
||||
private HashMap<String, String> myDatatypeClassNameToNarrativeTemplate;
|
||||
|
@ -51,6 +51,22 @@ public class DefaultThymeleafNarrativeGenerator extends BaseThymeleafNarrativeGe
|
|||
private TemplateEngine myProfileTemplateEngine;
|
||||
private HashMap<String, String> myProfileToNarrativeTemplate;
|
||||
|
||||
private boolean myCleanWhitespace = true;
|
||||
|
||||
/**
|
||||
* If set to <code>true</code> (which is the default), most whitespace will be trimmed from the generated narrative before it is returned.
|
||||
* <p>
|
||||
* Note that in order to preserve formatting, not all whitespace is trimmed. Repeated whitespace characters (e.g. "\n \n ") will be trimmed to a single space.
|
||||
* </p>
|
||||
*/
|
||||
public boolean isCleanWhitespace() {
|
||||
return myCleanWhitespace;
|
||||
}
|
||||
|
||||
public void setCleanWhitespace(boolean theCleanWhitespace) {
|
||||
myCleanWhitespace = theCleanWhitespace;
|
||||
}
|
||||
|
||||
public DefaultThymeleafNarrativeGenerator() throws IOException {
|
||||
myProfileToNarrativeTemplate = new HashMap<String, String>();
|
||||
myDatatypeClassNameToNarrativeTemplate = new HashMap<String, String>();
|
||||
|
@ -92,15 +108,18 @@ public class DefaultThymeleafNarrativeGenerator extends BaseThymeleafNarrativeGe
|
|||
ourLog.debug("No narrative template available for profile: {}", theProfile);
|
||||
return new NarrativeDt(new XhtmlDt("<div>No narrative available</div>"), NarrativeStatusEnum.EMPTY);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
Context context = new Context();
|
||||
context.setVariable("resource", theResource);
|
||||
|
||||
String result = myProfileTemplateEngine.process(theProfile, context);
|
||||
result = cleanWhitespace(result);
|
||||
ourLog.info(result);
|
||||
// result = result.replace("\n", "").replaceAll("\\s+", " ");//.replace("> ", ">").replace(" <", "<");
|
||||
|
||||
if (myCleanWhitespace) {
|
||||
ourLog.trace("Pre-whitespace cleaning: ", result);
|
||||
result = cleanWhitespace(result);
|
||||
ourLog.trace("Post-whitespace cleaning: ", result);
|
||||
}
|
||||
|
||||
XhtmlDt div = new XhtmlDt(result);
|
||||
return new NarrativeDt(div, NarrativeStatusEnum.GENERATED);
|
||||
|
@ -114,29 +133,43 @@ public class DefaultThymeleafNarrativeGenerator extends BaseThymeleafNarrativeGe
|
|||
}
|
||||
}
|
||||
|
||||
private String cleanWhitespace(String theResult) {
|
||||
static String cleanWhitespace(String theResult) {
|
||||
StringBuilder b = new StringBuilder();
|
||||
boolean inWhitespace=false;
|
||||
boolean inWhitespace = false;
|
||||
boolean betweenTags = false;
|
||||
boolean lastNonWhitespaceCharWasTagEnd = false;
|
||||
for (int i = 0; i < theResult.length(); i++) {
|
||||
char nextChar = theResult.charAt(i);
|
||||
if (nextChar == '>') {
|
||||
b.append(nextChar);
|
||||
betweenTags=true;
|
||||
betweenTags = true;
|
||||
lastNonWhitespaceCharWasTagEnd = true;
|
||||
continue;
|
||||
} else if (nextChar == '\n' || nextChar == '\r') {
|
||||
// if (inWhitespace) {
|
||||
// b.append(' ');
|
||||
// inWhitespace = false;
|
||||
// }
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (betweenTags) {
|
||||
if (Character.isWhitespace(nextChar)) {
|
||||
inWhitespace = true;
|
||||
} else if (nextChar == '<') {
|
||||
if (inWhitespace && !lastNonWhitespaceCharWasTagEnd) {
|
||||
b.append(' ');
|
||||
}
|
||||
inWhitespace = false;
|
||||
b.append(nextChar);
|
||||
inWhitespace=false;
|
||||
betweenTags=false;
|
||||
inWhitespace = false;
|
||||
betweenTags = false;
|
||||
lastNonWhitespaceCharWasTagEnd = false;
|
||||
} else {
|
||||
lastNonWhitespaceCharWasTagEnd = false;
|
||||
if (inWhitespace) {
|
||||
b.append(' ');
|
||||
inWhitespace=false;
|
||||
inWhitespace = false;
|
||||
}
|
||||
b.append(nextChar);
|
||||
}
|
||||
|
@ -156,8 +189,7 @@ public class DefaultThymeleafNarrativeGenerator extends BaseThymeleafNarrativeGe
|
|||
}
|
||||
|
||||
/**
|
||||
* If set to true, will return an empty narrative block for any
|
||||
* profiles where no template is available
|
||||
* If set to true, will return an empty narrative block for any profiles where no template is available
|
||||
*/
|
||||
public boolean isIgnoreMissingTemplates() {
|
||||
return myIgnoreMissingTemplates;
|
||||
|
@ -247,8 +279,7 @@ public class DefaultThymeleafNarrativeGenerator extends BaseThymeleafNarrativeGe
|
|||
// }
|
||||
|
||||
/**
|
||||
* If set to true, will return an empty narrative block for any
|
||||
* profiles where no template is available
|
||||
* If set to true, will return an empty narrative block for any profiles where no template is available
|
||||
*/
|
||||
public void setIgnoreMissingTemplates(boolean theIgnoreMissingTemplates) {
|
||||
myIgnoreMissingTemplates = theIgnoreMissingTemplates;
|
||||
|
@ -263,8 +294,8 @@ public class DefaultThymeleafNarrativeGenerator extends BaseThymeleafNarrativeGe
|
|||
@Override
|
||||
public InputStream getResourceAsStream(TemplateProcessingParameters theTemplateProcessingParameters, String theClassName) {
|
||||
String template = myDatatypeClassNameToNarrativeTemplate.get(theClassName);
|
||||
if (template==null) {
|
||||
throw new NullPointerException("No narrative template exists for datatype: " +theClassName);
|
||||
if (template == null) {
|
||||
throw new NullPointerException("No narrative template exists for datatype: " + theClassName);
|
||||
}
|
||||
return new ReaderInputStream(new StringReader(template));
|
||||
}
|
||||
|
@ -310,7 +341,7 @@ public class DefaultThymeleafNarrativeGenerator extends BaseThymeleafNarrativeGe
|
|||
|
||||
}
|
||||
|
||||
private final class ProfileResourceResolver implements IResourceResolver {
|
||||
private final class ProfileResourceResolver implements IResourceResolver {
|
||||
@Override
|
||||
public String getName() {
|
||||
return getClass().getCanonicalName();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div>
|
||||
<th:block th:each="prefix : ${resource.prefix}" th:text="' ' + ${prefix.value} + ' '">Dr</th:block>
|
||||
<th:block th:each="givenName : ${resource.given}" th:text="' ' + ${givenName.value} + ' '">John</th:block>
|
||||
<b th:each="familyName : ${resource.family}" th:text="' ' + ${#strings.toUpperCase(familyName.value)} + ' '">SMITH</b>
|
||||
<th:block th:each="suffix : ${resource.suffix}" th:text="' ' + ${suffix.value} + ' '">Jr</th:block>
|
||||
<th:block th:each="prefix : ${resource.prefix}" th:text="${prefix.value} + ' '">Dr</th:block>
|
||||
<th:block th:each="givenName : ${resource.given}" th:text="${givenName.value} + ' '">John</th:block>
|
||||
<b th:each="familyName : ${resource.family}" th:text="${#strings.toUpperCase(familyName.value)} + ' '">SMITH</b>
|
||||
<th:block th:each="suffix : ${resource.suffix}" th:text="${suffix.value} + ' '">Jr</th:block>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
package example;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.api.ExtensionDt;
|
||||
import ca.uhn.fhir.model.dstu.composite.AddressDt;
|
||||
import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
|
||||
import ca.uhn.fhir.model.dstu.resource.Patient;
|
||||
import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum;
|
||||
import ca.uhn.fhir.model.dstu.valueset.NarrativeStatusEnum;
|
||||
import ca.uhn.fhir.model.primitive.DateTimeDt;
|
||||
import ca.uhn.fhir.model.primitive.StringDt;
|
||||
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
|
||||
import ca.uhn.fhir.narrative.ThymeleafNarrativeGeneratorTest;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
|
||||
public class Narrative {
|
||||
|
||||
public static void main(String[] args) throws DataFormatException, IOException {
|
||||
|
||||
//START SNIPPET: example1
|
||||
Patient patient = new Patient();
|
||||
patient.addIdentifier("urn:foo", "7000135");
|
||||
patient.addName().addFamily("Smith").addGiven("John").addGiven("Edward");
|
||||
patient.addAddress().addLine("742 Evergreen Terrace").setCity("Springfield").setState("ZZ");
|
||||
|
||||
FhirContext ctx = new FhirContext();
|
||||
|
||||
// Use the narrative generator
|
||||
ctx.setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator());
|
||||
|
||||
// Encode the output, including the narrative
|
||||
String output = ctx.newJsonParser().setPrettyPrint(true).encodeResourceToString(patient);
|
||||
System.out.println(output);
|
||||
//END SNIPPET: example1
|
||||
|
||||
}
|
||||
|
||||
public void simple() {
|
||||
//START SNIPPET: simple
|
||||
Patient pat = new Patient();
|
||||
pat.getText().setStatus(NarrativeStatusEnum.GENERATED);
|
||||
pat.getText().setDiv("<div>This is the narrative text<br/>this is line 2</div>");
|
||||
//END SNIPPET: simple
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document xmlns="http://maven.apache.org/XDOC/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
|
||||
|
||||
<properties>
|
||||
<title>Narrative Generation - HAPI FHIR</title>
|
||||
<author email="jamesagnew@users.sourceforge.net">James Agnew</author>
|
||||
</properties>
|
||||
|
||||
<body>
|
||||
|
||||
<section name="Narrative Generation">
|
||||
|
||||
<macro name="toc">
|
||||
</macro>
|
||||
|
||||
<p>
|
||||
HAPI provides a several ways to add
|
||||
<a href="http://hl7.org/implement/standards/fhir/narrative.html">Narrative Text</a>
|
||||
to your encoded messages.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The simplest way is to simply place the narrative text directly in the resource
|
||||
via the
|
||||
<a href="./apidocs/ca/uhn/fhir/model/api/IResource.html#getText()">getText()</a>
|
||||
method.
|
||||
</p>
|
||||
|
||||
<macro name="snippet">
|
||||
<param name="id" value="simple" />
|
||||
<param name="file" value="src/site/example/java/example/Narrative.java" />
|
||||
</macro>
|
||||
|
||||
</section>
|
||||
|
||||
<section name="Automatic Narrative Generartion">
|
||||
|
||||
<p>
|
||||
HAPI also comes with a built-in mechanism for automatically generating
|
||||
narratives based on your resources.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b>Warning: </b> This built-in capability is a work in progress, and does not cover
|
||||
every type of resource or even every attribute in any resource. You should test it
|
||||
and configure it for your particular use cases.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
HAPI's built-in narrative generation uses the
|
||||
<a href="http://www.thymeleaf.org/">Thymeleaf</a> library
|
||||
for templating narrative texts. Thymeleaf provides a simple
|
||||
HTML (with extra attributes) syntax which is easy to use and
|
||||
meshes well with the HAPI data model.
|
||||
</p>
|
||||
|
||||
<subsection name="A Simple Example">
|
||||
|
||||
<p>
|
||||
Activating HAPI's built-in narrative generator is as simple
|
||||
as calling
|
||||
<a href="./apidocs/ca/uhn/fhir/context/FhirContext.html#setNarrativeGenerator(ca.uhn.fhir.narrative.INarrativeGenerator)">setNarrativeGenerator</a>.
|
||||
</p>
|
||||
|
||||
<macro name="snippet">
|
||||
<param name="id" value="example1" />
|
||||
<param name="file" value="src/site/example/java/example/Narrative.java" />
|
||||
</macro>
|
||||
|
||||
<p>
|
||||
...which produces the following output:
|
||||
</p>
|
||||
|
||||
<source><![CDATA[<Patient xmlns="http://hl7.org/fhir">
|
||||
<text>
|
||||
<status value="generated"/>
|
||||
<div xmlns="http://www.w3.org/1999/xhtml">
|
||||
<div class="hapiHeaderText"> John Edward <b>SMITH </b></div>
|
||||
<table class="hapiPropertyTable">
|
||||
<tbody>
|
||||
<tr><td>Identifier</td><td>7000135</td></tr>
|
||||
<tr><td>Address</td><td><span>742 Evergreen Terrace</span><br/><span>Springfield</span> <span>ZZ</span></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</text>
|
||||
<!-- .... snip ..... -->
|
||||
</Patient>]]></source>
|
||||
|
||||
</subsection>
|
||||
|
||||
<subsection name="Built-in Narrative Templates">
|
||||
|
||||
<p>
|
||||
HAPI currently only comes with built-in support for
|
||||
a few resource types. Our intention is that people enhance these
|
||||
templates and create new ones, and share these back with us so that
|
||||
we can continue to build out the library. To see the current
|
||||
template library, see the source repository
|
||||
<a href="http://sourceforge.net/p/hl7api/fhircode/ci/master/tree/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/">here</a>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Note that these templates expect a few specific CSS definitions
|
||||
to be present in your site's CSS file. See the
|
||||
<a href="http://sourceforge.net/p/hl7api/fhircode/ci/master/tree/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/narrative.css">narrative CSS</a>
|
||||
to see these.
|
||||
</p>
|
||||
|
||||
</subsection>
|
||||
|
||||
</section>
|
||||
|
||||
</body>
|
||||
|
||||
</document>
|
|
@ -33,6 +33,57 @@ public class ThymeleafNarrativeGeneratorTest {
|
|||
gen.setIgnoreMissingTemplates(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrimWhitespace() {
|
||||
|
||||
//@formatter:off
|
||||
String input = "<div>\n" +
|
||||
" <div class=\"hapiHeaderText\">\n" +
|
||||
" \n" +
|
||||
" joe \n" +
|
||||
" john \n" +
|
||||
" <b>BLOW </b>\n" +
|
||||
" \n" +
|
||||
"</div>\n" +
|
||||
" <table class=\"hapiPropertyTable\">\n" +
|
||||
" <tbody>\n" +
|
||||
" <tr>\n" +
|
||||
" <td>Identifier</td>\n" +
|
||||
" <td>123456</td>\n" +
|
||||
" </tr>\n" +
|
||||
" <tr>\n" +
|
||||
" <td>Address</td>\n" +
|
||||
" <td>\n" +
|
||||
" \n" +
|
||||
" <span>123 Fake Street</span><br />\n" +
|
||||
" \n" +
|
||||
" \n" +
|
||||
" <span>Unit 1</span><br />\n" +
|
||||
" \n" +
|
||||
" <span>Toronto</span>\n" +
|
||||
" <span>ON</span>\n" +
|
||||
" <span>Canada</span>\n" +
|
||||
" </td>\n" +
|
||||
" </tr>\n" +
|
||||
" <tr>\n" +
|
||||
" <td>Date of birth</td>\n" +
|
||||
" <td>\n" +
|
||||
" <span>31 March 2014</span>\n" +
|
||||
" </td>\n" +
|
||||
" </tr>\n" +
|
||||
" </tbody>\n" +
|
||||
" </table>\n" +
|
||||
"</div>";
|
||||
//@formatter:on
|
||||
|
||||
String actual = DefaultThymeleafNarrativeGenerator.cleanWhitespace(input);
|
||||
String expected = "<div><div class=\"hapiHeaderText\"> joe john <b>BLOW </b></div><table class=\"hapiPropertyTable\"><tbody><tr><td>Identifier</td><td>123456</td></tr><tr><td>Address</td><td><span>123 Fake Street</span><br /><span>Unit 1</span><br /><span>Toronto</span><span>ON</span><span>Canada</span></td></tr><tr><td>Date of birth</td><td><span>31 March 2014</span></td></tr></tbody></table></div>";
|
||||
|
||||
ourLog.info(actual);
|
||||
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGeneratePatient() throws DataFormatException {
|
||||
Patient value = new Patient();
|
||||
|
@ -66,10 +117,10 @@ public class ThymeleafNarrativeGeneratorTest {
|
|||
@Test
|
||||
public void testGenerateDiagnosticReportWithObservations() throws DataFormatException, IOException {
|
||||
DiagnosticReport value = new DiagnosticReport();
|
||||
|
||||
|
||||
value.getIssued().setValueAsString("2011-02-22T11:13:00");
|
||||
value.setStatus(DiagnosticReportStatusEnum.FINAL);
|
||||
|
||||
|
||||
value.getName().setText("Some Diagnostic Report");
|
||||
{
|
||||
Observation obs = new Observation();
|
||||
|
@ -91,7 +142,7 @@ public class ThymeleafNarrativeGeneratorTest {
|
|||
String output = generateNarrative.getDiv().getValueAsString();
|
||||
|
||||
ourLog.info(output);
|
||||
assertThat(output, StringContains.containsString("<div class=\"hapiHeaderText\">Some Diagnostic Report</div>"));
|
||||
assertThat(output, StringContains.containsString("<div class=\"hapiHeaderText\"> Some Diagnostic Report </div>"));
|
||||
|
||||
// Now try it with the parser
|
||||
|
||||
|
@ -99,7 +150,7 @@ public class ThymeleafNarrativeGeneratorTest {
|
|||
context.setNarrativeGenerator(gen);
|
||||
output = context.newXmlParser().setPrettyPrint(true).encodeResourceToString(value);
|
||||
ourLog.info(output);
|
||||
assertThat(output, StringContains.containsString("<div class=\"hapiHeaderText\">Some Diagnostic Report</div>"));
|
||||
assertThat(output, StringContains.containsString("<div class=\"hapiHeaderText\"> Some Diagnostic Report </div>"));
|
||||
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue