Narrative generator docs
This commit is contained in:
parent
12d8e8d19c
commit
5a3de7c3dd
|
@ -1,6 +1,6 @@
|
|||
package ca.uhn.fhir.model.api;
|
||||
|
||||
import org.thymeleaf.util.Validate;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import ca.uhn.fhir.model.api.annotation.Child;
|
||||
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
|
||||
|
|
|
@ -120,6 +120,7 @@ public class ResourceReferenceDt
|
|||
* A reference to a location at which the other resource is found. The reference may a relative reference, in which case it is relative to the service base URL, or an absolute URL that resolves to the location where the resource is found. The reference may be version specific or not. If the reference is not to a FHIR RESTful server, then it should be assumed to be version specific. Internal fragment references (start with '#') refer to contained resources
|
||||
* </p>
|
||||
*/
|
||||
@Override
|
||||
public StringDt getReference() {
|
||||
if (myReference == null) {
|
||||
myReference = new StringDt();
|
||||
|
|
|
@ -56,6 +56,12 @@ import ca.uhn.fhir.model.primitive.DateTimeDt;
|
|||
* <b>Requirements:</b>
|
||||
* Used to track reactions when it is unknown the exact cause but there's a desire to flag/track potential causes. Also used to capture reactions that are significant for inclusion in the health record or as evidence for an allergy or intolerance.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <b>Profile Definition:</b>
|
||||
* <a href="http://hl7.org/fhir/profiles/AdverseReaction">http://hl7.org/fhir/profiles/AdverseReaction</a>
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
@ResourceDef(name="AdverseReaction", profile="http://hl7.org/fhir/profiles/AdverseReaction", id="adversereaction")
|
||||
public class AdverseReaction extends BaseResource implements IResource {
|
||||
|
|
|
@ -48,6 +48,12 @@ import ca.uhn.fhir.model.primitive.StringDt;
|
|||
* <b>Requirements:</b>
|
||||
*
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <b>Profile Definition:</b>
|
||||
* <a href="http://hl7.org/fhir/profiles/Alert">http://hl7.org/fhir/profiles/Alert</a>
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
@ResourceDef(name="Alert", profile="http://hl7.org/fhir/profiles/Alert", id="alert")
|
||||
public class Alert extends BaseResource implements IResource {
|
||||
|
|
|
@ -69,6 +69,12 @@ import ca.uhn.fhir.model.primitive.UriDt;
|
|||
* <b>Requirements:</b>
|
||||
*
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <b>Profile Definition:</b>
|
||||
* <a href="http://hl7.org/fhir/profiles/Conformance">http://hl7.org/fhir/profiles/Conformance</a>
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
@ResourceDef(name="Conformance", profile="http://hl7.org/fhir/profiles/Conformance", id="conformance")
|
||||
public class Conformance extends BaseResource implements IResource {
|
||||
|
|
|
@ -50,6 +50,12 @@ import ca.uhn.fhir.model.primitive.UriDt;
|
|||
* <b>Requirements:</b>
|
||||
* Allows institutions to track their devices.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <b>Profile Definition:</b>
|
||||
* <a href="http://hl7.org/fhir/profiles/Device">http://hl7.org/fhir/profiles/Device</a>
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
@ResourceDef(name="Device", profile="http://hl7.org/fhir/profiles/Device", id="device")
|
||||
public class Device extends BaseResource implements IResource {
|
||||
|
|
|
@ -55,6 +55,12 @@ import ca.uhn.fhir.model.primitive.StringDt;
|
|||
* <b>Requirements:</b>
|
||||
*
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <b>Profile Definition:</b>
|
||||
* <a href="http://hl7.org/fhir/profiles/DiagnosticOrder">http://hl7.org/fhir/profiles/DiagnosticOrder</a>
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
@ResourceDef(name="DiagnosticOrder", profile="http://hl7.org/fhir/profiles/DiagnosticOrder", id="diagnosticorder")
|
||||
public class DiagnosticOrder extends BaseResource implements IResource {
|
||||
|
|
|
@ -57,6 +57,12 @@ import ca.uhn.fhir.model.primitive.StringDt;
|
|||
* <b>Requirements:</b>
|
||||
* To support reporting for any diagnostic report into a clinical data repository.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <b>Profile Definition:</b>
|
||||
* <a href="http://hl7.org/fhir/profiles/DiagnosticReport">http://hl7.org/fhir/profiles/DiagnosticReport</a>
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
@ResourceDef(name="DiagnosticReport", profile="http://hl7.org/fhir/profiles/DiagnosticReport", id="diagnosticreport")
|
||||
public class DiagnosticReport extends BaseResource implements IResource {
|
||||
|
|
|
@ -59,6 +59,12 @@ import ca.uhn.fhir.model.primitive.CodeDt;
|
|||
* <b>Requirements:</b>
|
||||
*
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <b>Profile Definition:</b>
|
||||
* <a href="http://hl7.org/fhir/profiles/Encounter">http://hl7.org/fhir/profiles/Encounter</a>
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
@ResourceDef(name="Encounter", profile="http://hl7.org/fhir/profiles/Encounter", id="encounter")
|
||||
public class Encounter extends BaseResource implements IResource {
|
||||
|
|
|
@ -56,6 +56,12 @@ import ca.uhn.fhir.model.primitive.StringDt;
|
|||
* <b>Requirements:</b>
|
||||
*
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <b>Profile Definition:</b>
|
||||
* <a href="http://hl7.org/fhir/profiles/Group">http://hl7.org/fhir/profiles/Group</a>
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
@ResourceDef(name="Group", profile="http://hl7.org/fhir/profiles/Group", id="group")
|
||||
public class Group extends BaseResource implements IResource {
|
||||
|
|
|
@ -59,6 +59,12 @@ import ca.uhn.fhir.model.primitive.UriDt;
|
|||
* <b>Requirements:</b>
|
||||
*
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <b>Profile Definition:</b>
|
||||
* <a href="http://hl7.org/fhir/profiles/ImagingStudy">http://hl7.org/fhir/profiles/ImagingStudy</a>
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
@ResourceDef(name="ImagingStudy", profile="http://hl7.org/fhir/profiles/ImagingStudy", id="imagingstudy")
|
||||
public class ImagingStudy extends BaseResource implements IResource {
|
||||
|
|
|
@ -57,6 +57,12 @@ import ca.uhn.fhir.model.primitive.StringDt;
|
|||
* <b>Requirements:</b>
|
||||
*
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <b>Profile Definition:</b>
|
||||
* <a href="http://hl7.org/fhir/profiles/Location">http://hl7.org/fhir/profiles/Location</a>
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
@ResourceDef(name="Location", profile="http://hl7.org/fhir/profiles/Location", id="location")
|
||||
public class Location extends BaseResource implements IResource {
|
||||
|
|
|
@ -53,6 +53,12 @@ import ca.uhn.fhir.model.primitive.StringDt;
|
|||
* <b>Requirements:</b>
|
||||
*
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <b>Profile Definition:</b>
|
||||
* <a href="http://hl7.org/fhir/profiles/Media">http://hl7.org/fhir/profiles/Media</a>
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
@ResourceDef(name="Media", profile="http://hl7.org/fhir/profiles/Media", id="media")
|
||||
public class Media extends BaseResource implements IResource {
|
||||
|
|
|
@ -51,6 +51,12 @@ import ca.uhn.fhir.model.primitive.StringDt;
|
|||
* <b>Requirements:</b>
|
||||
*
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <b>Profile Definition:</b>
|
||||
* <a href="http://hl7.org/fhir/profiles/Medication">http://hl7.org/fhir/profiles/Medication</a>
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
@ResourceDef(name="Medication", profile="http://hl7.org/fhir/profiles/Medication", id="medication")
|
||||
public class Medication extends BaseResource implements IResource {
|
||||
|
|
|
@ -67,6 +67,12 @@ import ca.uhn.fhir.model.primitive.StringDt;
|
|||
* <b>Requirements:</b>
|
||||
* Observations are a key aspect of healthcare. This resource is used to capture those that do not require more sophisticated mechanisms.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <b>Profile Definition:</b>
|
||||
* <a href="http://hl7.org/fhir/profiles/Observation">http://hl7.org/fhir/profiles/Observation</a>
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
@ResourceDef(name="Observation", profile="http://hl7.org/fhir/profiles/Observation", id="observation")
|
||||
public class Observation extends BaseResource implements IResource {
|
||||
|
|
|
@ -48,6 +48,12 @@ import ca.uhn.fhir.model.primitive.StringDt;
|
|||
* <b>Requirements:</b>
|
||||
*
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <b>Profile Definition:</b>
|
||||
* <a href="http://hl7.org/fhir/profiles/OperationOutcome">http://hl7.org/fhir/profiles/OperationOutcome</a>
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
@ResourceDef(name="OperationOutcome", profile="http://hl7.org/fhir/profiles/OperationOutcome", id="operationoutcome")
|
||||
public class OperationOutcome extends BaseResource implements IResource {
|
||||
|
|
|
@ -55,6 +55,12 @@ import ca.uhn.fhir.model.primitive.StringDt;
|
|||
* <b>Requirements:</b>
|
||||
*
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <b>Profile Definition:</b>
|
||||
* <a href="http://hl7.org/fhir/profiles/Organization">http://hl7.org/fhir/profiles/Organization</a>
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
@ResourceDef(name="Organization", profile="http://hl7.org/fhir/profiles/Organization", id="organization")
|
||||
public class Organization extends BaseResource implements IResource {
|
||||
|
|
|
@ -64,6 +64,12 @@ import ca.uhn.fhir.model.primitive.IntegerDt;
|
|||
* <b>Requirements:</b>
|
||||
* Tracking patient is the center of the healthcare process
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <b>Profile Definition:</b>
|
||||
* <a href="http://hl7.org/fhir/profiles/Patient">http://hl7.org/fhir/profiles/Patient</a>
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
@ResourceDef(name="Patient", profile="http://hl7.org/fhir/profiles/Patient", id="patient")
|
||||
public class Patient extends BaseResource implements IResource {
|
||||
|
|
|
@ -59,6 +59,12 @@ import ca.uhn.fhir.model.primitive.DateTimeDt;
|
|||
* <b>Requirements:</b>
|
||||
* Need to track doctors, staff, locums etc. for both healthcare practitioners, funders, etc.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <b>Profile Definition:</b>
|
||||
* <a href="http://hl7.org/fhir/profiles/Practitioner">http://hl7.org/fhir/profiles/Practitioner</a>
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
@ResourceDef(name="Practitioner", profile="http://hl7.org/fhir/profiles/Practitioner", id="practitioner")
|
||||
public class Practitioner extends BaseResource implements IResource {
|
||||
|
|
|
@ -67,6 +67,12 @@ import ca.uhn.fhir.model.primitive.UriDt;
|
|||
* <b>Requirements:</b>
|
||||
*
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <b>Profile Definition:</b>
|
||||
* <a href="http://hl7.org/fhir/profiles/Profile">http://hl7.org/fhir/profiles/Profile</a>
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
@ResourceDef(name="Profile", profile="http://hl7.org/fhir/profiles/Profile", id="profile")
|
||||
public class Profile extends BaseResource implements IResource {
|
||||
|
|
|
@ -50,6 +50,12 @@ import ca.uhn.fhir.model.primitive.UriDt;
|
|||
* <b>Requirements:</b>
|
||||
*
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <b>Profile Definition:</b>
|
||||
* <a href="http://hl7.org/fhir/profiles/Query">http://hl7.org/fhir/profiles/Query</a>
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
@ResourceDef(name="Query", profile="http://hl7.org/fhir/profiles/Query", id="query")
|
||||
public class Query extends BaseResource implements IResource {
|
||||
|
|
|
@ -64,6 +64,12 @@ import ca.uhn.fhir.model.primitive.StringDt;
|
|||
* <b>Requirements:</b>
|
||||
* To support structured, hierarchical registration of data gathered using digital forms and other questionnaires.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <b>Profile Definition:</b>
|
||||
* <a href="http://hl7.org/fhir/profiles/Questionnaire">http://hl7.org/fhir/profiles/Questionnaire</a>
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
@ResourceDef(name="Questionnaire", profile="http://hl7.org/fhir/profiles/Questionnaire", id="questionnaire")
|
||||
public class Questionnaire extends BaseResource implements IResource {
|
||||
|
|
|
@ -51,6 +51,12 @@ import ca.uhn.fhir.model.primitive.BoundCodeableConceptDt;
|
|||
* <b>Requirements:</b>
|
||||
* Need to track persons related to the patient or the healthcare process.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <b>Profile Definition:</b>
|
||||
* <a href="http://hl7.org/fhir/profiles/RelatedPerson">http://hl7.org/fhir/profiles/RelatedPerson</a>
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
@ResourceDef(name="RelatedPerson", profile="http://hl7.org/fhir/profiles/RelatedPerson", id="relatedperson")
|
||||
public class RelatedPerson extends BaseResource implements IResource {
|
||||
|
|
|
@ -61,6 +61,12 @@ import ca.uhn.fhir.model.primitive.StringDt;
|
|||
* <b>Requirements:</b>
|
||||
*
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <b>Profile Definition:</b>
|
||||
* <a href="http://hl7.org/fhir/profiles/Specimen">http://hl7.org/fhir/profiles/Specimen</a>
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
@ResourceDef(name="Specimen", profile="http://hl7.org/fhir/profiles/Specimen", id="specimen")
|
||||
public class Specimen extends BaseResource implements IResource {
|
||||
|
|
|
@ -56,6 +56,12 @@ import ca.uhn.fhir.model.primitive.StringDt;
|
|||
* <b>Requirements:</b>
|
||||
*
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <b>Profile Definition:</b>
|
||||
* <a href="http://hl7.org/fhir/profiles/Substance">http://hl7.org/fhir/profiles/Substance</a>
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
@ResourceDef(name="Substance", profile="http://hl7.org/fhir/profiles/Substance", id="substance")
|
||||
public class Substance extends BaseResource implements IResource {
|
||||
|
|
|
@ -57,6 +57,12 @@ import ca.uhn.fhir.model.primitive.UriDt;
|
|||
* <b>Requirements:</b>
|
||||
*
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <b>Profile Definition:</b>
|
||||
* <a href="http://hl7.org/fhir/profiles/ValueSet">http://hl7.org/fhir/profiles/ValueSet</a>
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
@ResourceDef(name="ValueSet", profile="http://hl7.org/fhir/profiles/ValueSet", id="valueset")
|
||||
public class ValueSet extends BaseResource implements IResource {
|
||||
|
|
|
@ -2,6 +2,7 @@ package ca.uhn.fhir.model.primitive;
|
|||
|
||||
import static org.apache.commons.lang3.StringUtils.*;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
|
@ -19,12 +20,17 @@ public class BoundCodeableConceptDt<T extends Enum<?>> extends CodeableConceptDt
|
|||
myBinder = theBinder;
|
||||
}
|
||||
|
||||
public BoundCodeableConceptDt(IValueSetEnumBinder<T> theBinder, T... theValues) {
|
||||
public BoundCodeableConceptDt(IValueSetEnumBinder<T> theBinder, T theValue) {
|
||||
myBinder = theBinder;
|
||||
setValueAsEnum(theValue);
|
||||
}
|
||||
|
||||
public BoundCodeableConceptDt(IValueSetEnumBinder<T> theBinder, Collection<T> theValues) {
|
||||
myBinder = theBinder;
|
||||
setValueAsEnum(theValues);
|
||||
}
|
||||
|
||||
public void setValueAsEnum(T... theValues) {
|
||||
public void setValueAsEnum(Collection<T> theValues) {
|
||||
getCoding().clear();
|
||||
if (theValues == null) {
|
||||
return;
|
||||
|
@ -34,6 +40,14 @@ public class BoundCodeableConceptDt<T extends Enum<?>> extends CodeableConceptDt
|
|||
}
|
||||
}
|
||||
|
||||
public void setValueAsEnum(T theValue) {
|
||||
getCoding().clear();
|
||||
if (theValue == null) {
|
||||
return;
|
||||
}
|
||||
getCoding().add(new CodingDt(myBinder.toSystemString(theValue), myBinder.toCodeString(theValue)));
|
||||
}
|
||||
|
||||
public Set<T> getValueAsEnum() {
|
||||
Set<T> retVal = new HashSet<T>();
|
||||
for (CodingDt next : getCoding()) {
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
package ca.uhn.fhir.model.primitive;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.*;
|
||||
import ca.uhn.fhir.model.api.IValueSetEnumBinder;
|
||||
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
|
||||
import ca.uhn.fhir.model.dstu.composite.CodingDt;
|
||||
|
||||
@DatatypeDef(name = "Coding", isSpecialization=true)
|
||||
public class BoundCodingDt__<T extends Enum<?>> extends CodingDt {
|
||||
|
||||
private IValueSetEnumBinder<T> myBinder;
|
||||
|
||||
public BoundCodingDt__(IValueSetEnumBinder<T> theBinder) {
|
||||
myBinder = theBinder;
|
||||
}
|
||||
|
||||
public BoundCodingDt__(IValueSetEnumBinder<T> theBinder, T theValue) {
|
||||
myBinder = theBinder;
|
||||
setValueAsEnum(theValue);
|
||||
}
|
||||
|
||||
public void setValueAsEnum(T theValue) {
|
||||
setCode(new BoundCodeDt<T>(myBinder, theValue));
|
||||
setSystem(myBinder.toSystemString(theValue));
|
||||
}
|
||||
|
||||
public T getValueAsEnum() {
|
||||
return myBinder.fromCodeString(defaultString(getCode().getValue()), defaultString(getSystem().getValueAsString()));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,9 +1,383 @@
|
|||
package ca.uhn.fhir.narrative;
|
||||
|
||||
public class BaseThymeleafNarrativeGenerator {
|
||||
import static org.apache.commons.lang3.StringUtils.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.StringReader;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.io.input.ReaderInputStream;
|
||||
import org.thymeleaf.Arguments;
|
||||
import org.thymeleaf.Configuration;
|
||||
import org.thymeleaf.TemplateEngine;
|
||||
import org.thymeleaf.TemplateProcessingParameters;
|
||||
import org.thymeleaf.context.Context;
|
||||
import org.thymeleaf.dom.Document;
|
||||
import org.thymeleaf.dom.Element;
|
||||
import org.thymeleaf.dom.Node;
|
||||
import org.thymeleaf.processor.IProcessor;
|
||||
import org.thymeleaf.processor.ProcessorResult;
|
||||
import org.thymeleaf.processor.attr.AbstractAttrProcessor;
|
||||
import org.thymeleaf.resourceresolver.IResourceResolver;
|
||||
import org.thymeleaf.standard.StandardDialect;
|
||||
import org.thymeleaf.standard.expression.IStandardExpression;
|
||||
import org.thymeleaf.standard.expression.IStandardExpressionParser;
|
||||
import org.thymeleaf.standard.expression.StandardExpressions;
|
||||
import org.thymeleaf.templateresolver.TemplateResolver;
|
||||
import org.thymeleaf.util.DOMUtils;
|
||||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.dstu.composite.NarrativeDt;
|
||||
import ca.uhn.fhir.model.dstu.valueset.NarrativeStatusEnum;
|
||||
import ca.uhn.fhir.model.primitive.XhtmlDt;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
|
||||
public class BaseThymeleafNarrativeGenerator implements INarrativeGenerator {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseThymeleafNarrativeGenerator.class);
|
||||
|
||||
private boolean myCleanWhitespace = true;
|
||||
|
||||
private HashMap<String, String> myDatatypeClassNameToNarrativeTemplate;
|
||||
private TemplateEngine myDatatypeTemplateEngine;
|
||||
private boolean myIgnoreFailures = true;
|
||||
|
||||
private boolean myIgnoreMissingTemplates = true;
|
||||
private TemplateEngine myProfileTemplateEngine;
|
||||
private HashMap<String, String> myProfileToNarrativeTemplate;
|
||||
|
||||
public BaseThymeleafNarrativeGenerator() throws IOException {
|
||||
myProfileToNarrativeTemplate = new HashMap<String, String>();
|
||||
myDatatypeClassNameToNarrativeTemplate = new HashMap<String, String>();
|
||||
|
||||
String propFileName = getPropertyFile();
|
||||
loadProperties(propFileName);
|
||||
|
||||
{
|
||||
myProfileTemplateEngine = new TemplateEngine();
|
||||
TemplateResolver resolver = new TemplateResolver();
|
||||
resolver.setResourceResolver(new ProfileResourceResolver());
|
||||
myProfileTemplateEngine.setTemplateResolver(resolver);
|
||||
StandardDialect dialect = new StandardDialect();
|
||||
HashSet<IProcessor> additionalProcessors = new HashSet<IProcessor>();
|
||||
additionalProcessors.add(new MyProcessor());
|
||||
dialect.setAdditionalProcessors(additionalProcessors);
|
||||
myProfileTemplateEngine.setDialect(dialect);
|
||||
myProfileTemplateEngine.initialize();
|
||||
}
|
||||
{
|
||||
myDatatypeTemplateEngine = new TemplateEngine();
|
||||
TemplateResolver resolver = new TemplateResolver();
|
||||
resolver.setResourceResolver(new DatatypeResourceResolver());
|
||||
myDatatypeTemplateEngine.setTemplateResolver(resolver);
|
||||
myDatatypeTemplateEngine.setDialect(new StandardDialect());
|
||||
myDatatypeTemplateEngine.initialize();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public NarrativeDt generateNarrative(String theProfile, IResource theResource) {
|
||||
if (myIgnoreMissingTemplates && !myProfileToNarrativeTemplate.containsKey(theProfile)) {
|
||||
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);
|
||||
|
||||
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);
|
||||
} catch (Exception e) {
|
||||
if (myIgnoreFailures) {
|
||||
ourLog.error("Failed to generate narrative", e);
|
||||
return new NarrativeDt(new XhtmlDt("<div>No narrative available</div>"), NarrativeStatusEnum.EMPTY);
|
||||
} else {
|
||||
throw new DataFormatException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getPropertyFile() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* If set to <code>true</code>, which is the default, if any failure occurs
|
||||
* during narrative generation the generator will suppress any generated
|
||||
* exceptions, and simply return a default narrative indicating that no
|
||||
* narrative is available.
|
||||
*/
|
||||
public boolean isIgnoreFailures() {
|
||||
return myIgnoreFailures;
|
||||
}
|
||||
|
||||
/**
|
||||
* If set to true, will return an empty narrative block for any profiles
|
||||
* where no template is available
|
||||
*/
|
||||
public boolean isIgnoreMissingTemplates() {
|
||||
return myIgnoreMissingTemplates;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 void setCleanWhitespace(boolean theCleanWhitespace) {
|
||||
myCleanWhitespace = theCleanWhitespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* If set to <code>true</code>, which is the default, if any failure occurs
|
||||
* during narrative generation the generator will suppress any generated
|
||||
* exceptions, and simply return a default narrative indicating that no
|
||||
* narrative is available.
|
||||
*/
|
||||
public void setIgnoreFailures(boolean theIgnoreFailures) {
|
||||
myIgnoreFailures = theIgnoreFailures;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
private void loadProperties(String propFileName) throws IOException {
|
||||
Properties file = new Properties();
|
||||
|
||||
InputStream resource = loadResource(propFileName);
|
||||
file.load(resource);
|
||||
for (Object nextKeyObj : file.keySet()) {
|
||||
String nextKey = (String) nextKeyObj;
|
||||
if (nextKey.endsWith(".profile")) {
|
||||
String name = nextKey.substring(0, nextKey.indexOf(".profile"));
|
||||
if (isBlank(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String narrativeName = file.getProperty(name + ".narrative");
|
||||
if (isBlank(narrativeName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String narrative = IOUtils.toString(loadResource(narrativeName));
|
||||
myProfileToNarrativeTemplate.put(file.getProperty(nextKey), narrative);
|
||||
} else if (nextKey.endsWith(".dtclass")) {
|
||||
String name = nextKey.substring(0, nextKey.indexOf(".dtclass"));
|
||||
if (isBlank(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String className = file.getProperty(nextKey);
|
||||
|
||||
Class<?> dtClass;
|
||||
try {
|
||||
dtClass = Class.forName(className);
|
||||
} catch (ClassNotFoundException e) {
|
||||
ourLog.warn("Unknown datatype class '{}' identified in narrative file {}", name, propFileName);
|
||||
continue;
|
||||
}
|
||||
|
||||
String narrativeName = file.getProperty(name + ".dtnarrative");
|
||||
if (isBlank(narrativeName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String narrative = IOUtils.toString(loadResource(narrativeName));
|
||||
myDatatypeClassNameToNarrativeTemplate.put(dtClass.getCanonicalName(), narrative);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private InputStream loadResource(String name) throws IOException {
|
||||
if (name.startsWith("classpath:")) {
|
||||
String cpName = name.substring("classpath:".length());
|
||||
InputStream resource = DefaultThymeleafNarrativeGenerator.class.getResourceAsStream(cpName);
|
||||
if (resource == null) {
|
||||
resource = DefaultThymeleafNarrativeGenerator.class.getResourceAsStream("/" + cpName);
|
||||
if (resource == null) {
|
||||
throw new ConfigurationException("Can not find '" + cpName + "' on classpath");
|
||||
}
|
||||
}
|
||||
return resource;
|
||||
} else if (name.startsWith("file:")) {
|
||||
File file = new File(name.substring("file:".length()));
|
||||
if (file.exists() == false) {
|
||||
throw new IOException("Can not read file: " + file.getAbsolutePath());
|
||||
}
|
||||
return new FileInputStream(file);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid resource name: '" + name + "'");
|
||||
}
|
||||
}
|
||||
|
||||
static String cleanWhitespace(String theResult) {
|
||||
StringBuilder b = new StringBuilder();
|
||||
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;
|
||||
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;
|
||||
lastNonWhitespaceCharWasTagEnd = false;
|
||||
} else {
|
||||
lastNonWhitespaceCharWasTagEnd = false;
|
||||
if (inWhitespace) {
|
||||
b.append(' ');
|
||||
inWhitespace = false;
|
||||
}
|
||||
b.append(nextChar);
|
||||
}
|
||||
} else {
|
||||
b.append(nextChar);
|
||||
}
|
||||
}
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
public class MyProcessor extends AbstractAttrProcessor {
|
||||
|
||||
protected MyProcessor() {
|
||||
super("narrative");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPrecedence() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ProcessorResult processAttribute(Arguments theArguments, Element theElement, String theAttributeName) {
|
||||
final String attributeValue = theElement.getAttributeValue(theAttributeName);
|
||||
|
||||
final Configuration configuration = theArguments.getConfiguration();
|
||||
final IStandardExpressionParser expressionParser = StandardExpressions.getExpressionParser(configuration);
|
||||
|
||||
final IStandardExpression expression = expressionParser.parseExpression(configuration, theArguments, attributeValue);
|
||||
final Object value = expression.execute(configuration, theArguments);
|
||||
|
||||
Context context = new Context();
|
||||
context.setVariable("resource", value);
|
||||
|
||||
String result = myDatatypeTemplateEngine.process(value.getClass().getCanonicalName(), context);
|
||||
Document dom = DOMUtils.getXhtmlDOMFor(new StringReader(result));
|
||||
|
||||
theElement.removeAttribute(theAttributeName);
|
||||
theElement.clearChildren();
|
||||
|
||||
Element firstChild = (Element) dom.getFirstChild();
|
||||
for (Node next : firstChild.getChildren()) {
|
||||
theElement.addChild(next);
|
||||
}
|
||||
|
||||
return ProcessorResult.ok();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final class DatatypeResourceResolver implements IResourceResolver {
|
||||
@Override
|
||||
public String getName() {
|
||||
return getClass().getCanonicalName();
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
return new ReaderInputStream(new StringReader(template));
|
||||
}
|
||||
}
|
||||
|
||||
// public String generateString(Patient theValue) {
|
||||
//
|
||||
// Context context = new Context();
|
||||
// context.setVariable("resource", theValue);
|
||||
// String result =
|
||||
// myProfileTemplateEngine.process("ca/uhn/fhir/narrative/Patient.html",
|
||||
// context);
|
||||
//
|
||||
// ourLog.info("Result: {}", result);
|
||||
//
|
||||
// return result;
|
||||
// }
|
||||
|
||||
private final class ProfileResourceResolver implements IResourceResolver {
|
||||
@Override
|
||||
public String getName() {
|
||||
return getClass().getCanonicalName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getResourceAsStream(TemplateProcessingParameters theTemplateProcessingParameters, String theResourceName) {
|
||||
String template = myProfileToNarrativeTemplate.get(theResourceName);
|
||||
if (template == null) {
|
||||
ourLog.info("No narative template for resource profile: {}", theResourceName);
|
||||
return new ReaderInputStream(new StringReader(""));
|
||||
}
|
||||
return new ReaderInputStream(new StringReader(template));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
package ca.uhn.fhir.narrative;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class CustomThymeleafNarrativeGenerator extends BaseThymeleafNarrativeGenerator {
|
||||
|
||||
/**
|
||||
* Create a new narrative generator
|
||||
*
|
||||
* @param thePropertyFile
|
||||
* The name of the property file, in one of the following
|
||||
* formats:
|
||||
* <ul>
|
||||
* <li>file:/path/to/file/file.properties</li>
|
||||
* <li>classpath:/com/package/file.properties</li>
|
||||
* </ul>
|
||||
* @throws IOException
|
||||
* If the file can not be found/read
|
||||
*/
|
||||
public CustomThymeleafNarrativeGenerator(String thePropertyFile) throws IOException {
|
||||
super();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,100 +1,11 @@
|
|||
package ca.uhn.fhir.narrative;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.StringReader;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.io.input.ReaderInputStream;
|
||||
import org.thymeleaf.Arguments;
|
||||
import org.thymeleaf.Configuration;
|
||||
import org.thymeleaf.TemplateEngine;
|
||||
import org.thymeleaf.TemplateProcessingParameters;
|
||||
import org.thymeleaf.context.Context;
|
||||
import org.thymeleaf.dom.Document;
|
||||
import org.thymeleaf.dom.Element;
|
||||
import org.thymeleaf.dom.Node;
|
||||
import org.thymeleaf.processor.IProcessor;
|
||||
import org.thymeleaf.processor.ProcessorResult;
|
||||
import org.thymeleaf.processor.attr.AbstractAttrProcessor;
|
||||
import org.thymeleaf.resourceresolver.IResourceResolver;
|
||||
import org.thymeleaf.standard.StandardDialect;
|
||||
import org.thymeleaf.standard.expression.IStandardExpression;
|
||||
import org.thymeleaf.standard.expression.IStandardExpressionParser;
|
||||
import org.thymeleaf.standard.expression.StandardExpressions;
|
||||
import org.thymeleaf.templateresolver.TemplateResolver;
|
||||
import org.thymeleaf.util.DOMUtils;
|
||||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.dstu.composite.NarrativeDt;
|
||||
import ca.uhn.fhir.model.dstu.valueset.NarrativeStatusEnum;
|
||||
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;
|
||||
private TemplateEngine myDatatypeTemplateEngine;
|
||||
|
||||
private boolean myIgnoreFailures = true;
|
||||
|
||||
private boolean myIgnoreMissingTemplates = true;
|
||||
|
||||
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>();
|
||||
|
||||
String propFileName = getPropertyFile();
|
||||
loadProperties(propFileName);
|
||||
|
||||
{
|
||||
myProfileTemplateEngine = new TemplateEngine();
|
||||
TemplateResolver resolver = new TemplateResolver();
|
||||
resolver.setResourceResolver(new ProfileResourceResolver());
|
||||
myProfileTemplateEngine.setTemplateResolver(resolver);
|
||||
StandardDialect dialect = new StandardDialect();
|
||||
HashSet<IProcessor> additionalProcessors = new HashSet<IProcessor>();
|
||||
additionalProcessors.add(new MyProcessor());
|
||||
dialect.setAdditionalProcessors(additionalProcessors);
|
||||
myProfileTemplateEngine.setDialect(dialect);
|
||||
myProfileTemplateEngine.initialize();
|
||||
}
|
||||
{
|
||||
myDatatypeTemplateEngine = new TemplateEngine();
|
||||
TemplateResolver resolver = new TemplateResolver();
|
||||
resolver.setResourceResolver(new DatatypeResourceResolver());
|
||||
myDatatypeTemplateEngine.setTemplateResolver(resolver);
|
||||
myDatatypeTemplateEngine.setDialect(new StandardDialect());
|
||||
myDatatypeTemplateEngine.initialize();
|
||||
}
|
||||
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -102,260 +13,4 @@ public class DefaultThymeleafNarrativeGenerator extends BaseThymeleafNarrativeGe
|
|||
return "classpath:ca/uhn/fhir/narrative/narratives.properties";
|
||||
}
|
||||
|
||||
@Override
|
||||
public NarrativeDt generateNarrative(String theProfile, IResource theResource) {
|
||||
if (myIgnoreMissingTemplates && !myProfileToNarrativeTemplate.containsKey(theProfile)) {
|
||||
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);
|
||||
|
||||
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);
|
||||
} catch (Exception e) {
|
||||
if (myIgnoreFailures) {
|
||||
ourLog.error("Failed to generate narrative", e);
|
||||
return new NarrativeDt(new XhtmlDt("<div>No narrative available</div>"), NarrativeStatusEnum.EMPTY);
|
||||
} else {
|
||||
throw new DataFormatException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static String cleanWhitespace(String theResult) {
|
||||
StringBuilder b = new StringBuilder();
|
||||
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;
|
||||
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;
|
||||
lastNonWhitespaceCharWasTagEnd = false;
|
||||
} else {
|
||||
lastNonWhitespaceCharWasTagEnd = false;
|
||||
if (inWhitespace) {
|
||||
b.append(' ');
|
||||
inWhitespace = false;
|
||||
}
|
||||
b.append(nextChar);
|
||||
}
|
||||
} else {
|
||||
b.append(nextChar);
|
||||
}
|
||||
}
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* If set to <code>true</code>, which is the default, if any failure occurs during narrative generation the generator will suppress any generated exceptions, and simply return a default narrative
|
||||
* indicating that no narrative is available.
|
||||
*/
|
||||
public boolean isIgnoreFailures() {
|
||||
return myIgnoreFailures;
|
||||
}
|
||||
|
||||
/**
|
||||
* If set to true, will return an empty narrative block for any profiles where no template is available
|
||||
*/
|
||||
public boolean isIgnoreMissingTemplates() {
|
||||
return myIgnoreMissingTemplates;
|
||||
}
|
||||
|
||||
private void loadProperties(String propFileName) throws IOException {
|
||||
Properties file = new Properties();
|
||||
|
||||
InputStream resource = loadResource(propFileName);
|
||||
file.load(resource);
|
||||
for (Object nextKeyObj : file.keySet()) {
|
||||
String nextKey = (String) nextKeyObj;
|
||||
if (nextKey.endsWith(".profile")) {
|
||||
String name = nextKey.substring(0, nextKey.indexOf(".profile"));
|
||||
if (isBlank(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String narrativeName = file.getProperty(name + ".narrative");
|
||||
if (isBlank(narrativeName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String narrative = IOUtils.toString(loadResource(narrativeName));
|
||||
myProfileToNarrativeTemplate.put(file.getProperty(nextKey), narrative);
|
||||
} else if (nextKey.endsWith(".dtclass")) {
|
||||
String name = nextKey.substring(0, nextKey.indexOf(".dtclass"));
|
||||
if (isBlank(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String className = file.getProperty(nextKey);
|
||||
|
||||
Class<?> dtClass;
|
||||
try {
|
||||
dtClass = Class.forName(className);
|
||||
} catch (ClassNotFoundException e) {
|
||||
ourLog.warn("Unknown datatype class '{}' identified in narrative file {}", name, propFileName);
|
||||
continue;
|
||||
}
|
||||
|
||||
String narrativeName = file.getProperty(name + ".dtnarrative");
|
||||
if (isBlank(narrativeName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String narrative = IOUtils.toString(loadResource(narrativeName));
|
||||
myDatatypeClassNameToNarrativeTemplate.put(dtClass.getCanonicalName(), narrative);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private InputStream loadResource(String name) {
|
||||
if (name.startsWith("classpath:")) {
|
||||
String cpName = name.substring("classpath:".length());
|
||||
InputStream resource = DefaultThymeleafNarrativeGenerator.class.getResourceAsStream(cpName);
|
||||
if (resource == null) {
|
||||
resource = DefaultThymeleafNarrativeGenerator.class.getResourceAsStream("/" + cpName);
|
||||
if (resource == null) {
|
||||
throw new ConfigurationException("Can not find '" + cpName + "' on classpath");
|
||||
}
|
||||
}
|
||||
return resource;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid resource name: '" + name + "'");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If set to <code>true</code>, which is the default, if any failure occurs during narrative generation the generator will suppress any generated exceptions, and simply return a default narrative
|
||||
* indicating that no narrative is available.
|
||||
*/
|
||||
public void setIgnoreFailures(boolean theIgnoreFailures) {
|
||||
myIgnoreFailures = theIgnoreFailures;
|
||||
}
|
||||
|
||||
// public String generateString(Patient theValue) {
|
||||
//
|
||||
// Context context = new Context();
|
||||
// context.setVariable("resource", theValue);
|
||||
// String result = myProfileTemplateEngine.process("ca/uhn/fhir/narrative/Patient.html", context);
|
||||
//
|
||||
// ourLog.info("Result: {}", result);
|
||||
//
|
||||
// return result;
|
||||
// }
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
private final class DatatypeResourceResolver implements IResourceResolver {
|
||||
@Override
|
||||
public String getName() {
|
||||
return getClass().getCanonicalName();
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
return new ReaderInputStream(new StringReader(template));
|
||||
}
|
||||
}
|
||||
|
||||
public class MyProcessor extends AbstractAttrProcessor {
|
||||
|
||||
protected MyProcessor() {
|
||||
super("narrative");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPrecedence() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ProcessorResult processAttribute(Arguments theArguments, Element theElement, String theAttributeName) {
|
||||
final String attributeValue = theElement.getAttributeValue(theAttributeName);
|
||||
|
||||
final Configuration configuration = theArguments.getConfiguration();
|
||||
final IStandardExpressionParser expressionParser = StandardExpressions.getExpressionParser(configuration);
|
||||
|
||||
final IStandardExpression expression = expressionParser.parseExpression(configuration, theArguments, attributeValue);
|
||||
final Object value = expression.execute(configuration, theArguments);
|
||||
|
||||
Context context = new Context();
|
||||
context.setVariable("resource", value);
|
||||
|
||||
String result = myDatatypeTemplateEngine.process(value.getClass().getCanonicalName(), context);
|
||||
Document dom = DOMUtils.getXhtmlDOMFor(new StringReader(result));
|
||||
|
||||
theElement.removeAttribute(theAttributeName);
|
||||
theElement.clearChildren();
|
||||
|
||||
Element firstChild = (Element) dom.getFirstChild();
|
||||
for (Node next : firstChild.getChildren()) {
|
||||
theElement.addChild(next);
|
||||
}
|
||||
|
||||
return ProcessorResult.ok();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final class ProfileResourceResolver implements IResourceResolver {
|
||||
@Override
|
||||
public String getName() {
|
||||
return getClass().getCanonicalName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getResourceAsStream(TemplateProcessingParameters theTemplateProcessingParameters, String theResourceName) {
|
||||
String template = myProfileToNarrativeTemplate.get(theResourceName);
|
||||
if (template == null) {
|
||||
ourLog.info("No narative template for resource profile: {}", theResourceName);
|
||||
return new ReaderInputStream(new StringReader(""));
|
||||
}
|
||||
return new ReaderInputStream(new StringReader(template));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<div>
|
||||
<th:block th:each="line : ${resource.line}">
|
||||
<span th:text="${line} + ' '">123 Fake Street</span><br/>
|
||||
</th:block>
|
||||
<span th:if="${not resource.city.empty}" th:text="${resource.city} + ' '">Toronto</span>
|
||||
<span th:if="${not resource.state.empty}" th:text="${resource.state} + ' '">ON</span>
|
||||
<span th:if="${not resource.country.empty}" th:text="${resource.country}+ ' '">Canada</span>
|
||||
</div>
|
|
@ -14,26 +14,17 @@ a browser.
|
|||
<div class="hapiHeaderText" th:if="${not resource.nameFirstRep.empty}" th:narrative="${resource.nameFirstRep}"/>
|
||||
<table class="hapiPropertyTable">
|
||||
<tbody>
|
||||
<tr th:if="${not #lists.isEmpty(resource.identifier)} and ${not resource.identifier[0].empty}">
|
||||
<tr th:if="${not resource.identifierFirstRep.empty}">
|
||||
<td>Identifier</td>
|
||||
<td th:text="${resource.identifier[0].value.value}">8708660</td>
|
||||
<td th:text="${resource.identifierFirstRep.value.value}">8708660</td>
|
||||
</tr>
|
||||
<tr th:if="${not #lists.isEmpty(resource.address)} and ${not resource.address[0].empty}">
|
||||
<tr th:if="${not resource.addressFirstRep.empty}">
|
||||
<td>Address</td>
|
||||
<td>
|
||||
<th:block th:each="line : ${resource.address[0].line}">
|
||||
<span th:text="${line}">123 Fake Street</span><br/>
|
||||
</th:block>
|
||||
<span th:if="${not resource.address[0].city.empty}" th:text="${resource.address[0].city}">Toronto</span>
|
||||
<span th:if="${not resource.address[0].state.empty}" th:text="${resource.address[0].state}">ON</span>
|
||||
<span th:if="${not resource.address[0].country.empty}" th:text="${resource.address[0].country}">Canada</span>
|
||||
</td>
|
||||
<td th:narrative="${resource.addressFirstRep}"></td>
|
||||
</tr>
|
||||
<tr th:if="${not resource.birthDate.empty}">
|
||||
<td>Date of birth</td>
|
||||
<td>
|
||||
<span th:text="${#dates.format(resource.birthDate.value,'dd MMMM yyyy')}">22 March 2012</span>
|
||||
</td>
|
||||
<td><span th:text="${#dates.format(resource.birthDate.value,'dd MMMM yyyy')}">22 March 2012</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -1,22 +1,37 @@
|
|||
|
||||
################################################
|
||||
# Primitive Datatypes
|
||||
################################################
|
||||
|
||||
string.dtclass=ca.uhn.fhir.model.primitive.StringDt
|
||||
string.dtnarrative=classpath:ca/uhn/fhir/narrative/StringDt.html
|
||||
|
||||
datetime.dtclass=ca.uhn.fhir.model.primitive.DateTimeDt
|
||||
datetime.dtnarrative=classpath:ca/uhn/fhir/narrative/DateTimeDt.html
|
||||
|
||||
diagnosticreport.profile=http://hl7.org/fhir/profiles/DiagnosticReport
|
||||
diagnosticreport.narrative=classpath:ca/uhn/fhir/narrative/DiagnosticReport.html
|
||||
|
||||
humanname.dtclass=ca.uhn.fhir.model.dstu.composite.HumanNameDt
|
||||
humanname.dtnarrative=classpath:ca/uhn/fhir/narrative/HumanNameDt.html
|
||||
|
||||
# Instant uses DateTime narrative
|
||||
instant.dtclass=ca.uhn.fhir.model.primitive.InstantDt
|
||||
instant.dtnarrative=classpath:ca/uhn/fhir/narrative/DateTimeDt.html
|
||||
|
||||
patient.profile=http://hl7.org/fhir/profiles/Patient
|
||||
patient.narrative=classpath:ca/uhn/fhir/narrative/Patient.html
|
||||
################################################
|
||||
# Composite Datatypes
|
||||
################################################
|
||||
|
||||
address.dtclass=ca.uhn.fhir.model.dstu.composite.AddressDt
|
||||
address.dtnarrative=classpath:ca/uhn/fhir/narrative/AddressDt.html
|
||||
|
||||
humanname.dtclass=ca.uhn.fhir.model.dstu.composite.HumanNameDt
|
||||
humanname.dtnarrative=classpath:ca/uhn/fhir/narrative/HumanNameDt.html
|
||||
|
||||
quantity.dtclass=ca.uhn.fhir.model.dstu.composite.QuantityDt
|
||||
quantity.dtnarrative=classpath:ca/uhn/fhir/narrative/QuantityDt.html
|
||||
|
||||
string.dtclass=ca.uhn.fhir.model.primitive.StringDt
|
||||
string.dtnarrative=classpath:ca/uhn/fhir/narrative/StringDt.html
|
||||
################################################
|
||||
# Resources
|
||||
################################################
|
||||
|
||||
patient.profile=http://hl7.org/fhir/profiles/Patient
|
||||
patient.narrative=classpath:ca/uhn/fhir/narrative/Patient.html
|
||||
|
||||
diagnosticreport.profile=http://hl7.org/fhir/profiles/DiagnosticReport
|
||||
diagnosticreport.narrative=classpath:ca/uhn/fhir/narrative/DiagnosticReport.html
|
|
@ -13,7 +13,7 @@ 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.narrative.DefaultThymeleafNarrativeGeneratorTest;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
|
||||
public class Narrative {
|
||||
|
|
|
@ -34,7 +34,8 @@
|
|||
<item name="RESTful Client" href="./doc_rest_client.html" />
|
||||
<item name="RESTful Operations" href="./doc_rest_operations.html" />
|
||||
<item name="Extensions" href="./doc_extensions.html" />
|
||||
<item name="Tinder Code Generator" href="./doc_tinder.html" />
|
||||
<item name="Narrative Generator" href="./doc_narrative.html" />
|
||||
<item name="Tinder Plugin" href="./doc_tinder.html" />
|
||||
</menu>
|
||||
|
||||
<menu name="JavaDocs">
|
||||
|
|
|
@ -50,8 +50,8 @@
|
|||
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.
|
||||
XHTML-based syntax which is easy to use and
|
||||
meshes well with the HAPI-FHIR model objects.
|
||||
</p>
|
||||
|
||||
<subsection name="A Simple Example">
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
package ca.uhn.fhir.narrative;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class BaseThymeleafNarrativeGeneratorTest {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseThymeleafNarrativeGeneratorTest.class);
|
||||
|
||||
@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 = BaseThymeleafNarrativeGenerator.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);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -22,8 +22,8 @@ import ca.uhn.fhir.model.dstu.valueset.ObservationStatusEnum;
|
|||
import ca.uhn.fhir.model.primitive.StringDt;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
|
||||
public class ThymeleafNarrativeGeneratorTest {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ThymeleafNarrativeGeneratorTest.class);
|
||||
public class DefaultThymeleafNarrativeGeneratorTest {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DefaultThymeleafNarrativeGeneratorTest.class);
|
||||
private DefaultThymeleafNarrativeGenerator gen;
|
||||
|
||||
@Before
|
||||
|
@ -33,57 +33,6 @@ 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();
|
|
@ -0,0 +1,21 @@
|
|||
<div>
|
||||
<!--
|
||||
Normal Thymeleaf tags apply. Here, we loop through each of the
|
||||
identifiers that the practitioner has, and create a DIV tag for
|
||||
each one, with the text "Identifier: [value]"
|
||||
-->
|
||||
<div th:each="identifier : ${resource.identifier}" th:text="'Identifier: ' + ${identifier.value}"></div>
|
||||
|
||||
<!--
|
||||
HAPI also defines a custom tag, th:narrative="value", which is
|
||||
used to render a datatype.
|
||||
-->
|
||||
<div th:narrative="${resource.addressFirstRep}"></div>
|
||||
|
||||
<!--
|
||||
HAPI also defines a custom tag, th:narrative="value", which is
|
||||
used to render a datatype.
|
||||
-->
|
||||
<div th:narrative="${resource.nameFirstRep}"></div>
|
||||
|
||||
</div>
|
|
@ -0,0 +1,21 @@
|
|||
|
||||
# Each resource to be defined has a pair or properties.
|
||||
#
|
||||
# The first (name.profile) defines the profile for the resource
|
||||
# to generate a narrative for. This profile URL string can be
|
||||
# found by examining the JavaDoc for the resource in question.
|
||||
#
|
||||
# The second (name.narrative) defines the path/classpath to the
|
||||
# template file.
|
||||
# Format is file:/path/foo.html or classpath:/com/classpath/foo.html
|
||||
#
|
||||
practitioner.profile=http://hl7.org/fhir/profiles/Practitioner
|
||||
practitioner.narrative=file:src/test/resource/narrative/Practitioner.html
|
||||
|
||||
# You may also override/define behaviour for datatypes, but the
|
||||
# format is a bit different.
|
||||
#
|
||||
# For these, the first property (name.dtclass) is the classname
|
||||
# The second property (name.dtnarrative) is the
|
||||
humanname.dtclass=ca.uhn.fhir.model.dstu.composite.HumanNameDt
|
||||
humanname.dtnarrative=classpath:ca/uhn/fhir/narrative/HumanNameDt.html
|
|
@ -27,6 +27,14 @@ import ${import};
|
|||
* <b>Requirements:</b>
|
||||
* ${requirements}
|
||||
* </p>
|
||||
*
|
||||
#if (${profile} != "")
|
||||
* <p>
|
||||
* <b>Profile Definition:</b>
|
||||
* <a href="${profile}">${profile}</a>
|
||||
* </p>
|
||||
*
|
||||
#end
|
||||
*/
|
||||
@ResourceDef(name="${className}", profile="${profile}", id="${id}")
|
||||
public class ${className} extends BaseResource implements IResource {
|
||||
|
|
Loading…
Reference in New Issue