Merge branch 'narrative_generator'
This commit is contained in:
commit
2d52affd8d
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" path="target/generated-sources/antlr3"/>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="org.eclipse.jst.component.nondependency" value=""/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
|
@ -0,0 +1,2 @@
|
|||
eclipse.preferences.version=1
|
||||
encoding/<project>=UTF-8
|
|
@ -0,0 +1,4 @@
|
|||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
|
@ -0,0 +1,84 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>2.1-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>hapi-fhir-hapi-fhir-narrativegenerator</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>HAPI FHIR - Narrative Generator</name>
|
||||
|
||||
<dependencies>
|
||||
<!-- HAPI DEPENDENCIES -->
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-base</artifactId>
|
||||
<version>2.1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<!-- conformance profile -->
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-structures-dstu2</artifactId>
|
||||
<version>2.1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-structures-dstu3</artifactId>
|
||||
<version>2.1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.antlr</groupId>
|
||||
<artifactId>antlr</artifactId>
|
||||
<version>3.5.1</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.antlr</groupId>
|
||||
<artifactId>antlr3-maven-plugin</artifactId>
|
||||
<version>3.5.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>antlr</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<classFolders>
|
||||
<classFolder>${basedir}/target/classes</classFolder>
|
||||
<classFolder>${basedir}/../hapi-fhir-base/target/classes</classFolder>
|
||||
</classFolders>
|
||||
<sourceFolders>
|
||||
<sourceFolder>${basedir}/src/main/java</sourceFolder>
|
||||
<sourceFolder>${basedir}/../hapi-fhir-base/src/main/java</sourceFolder>
|
||||
</sourceFolders>
|
||||
<dumpOnExit>true</dumpOnExit>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>default-prepare-agent</id>
|
||||
<goals>
|
||||
<goal>prepare-agent</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,274 @@
|
|||
/*
|
||||
* Copyright (c) 2010 by Bart Kiers
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* Project : Liqp; a Liquid Template grammar/parser
|
||||
* Developed by : Bart Kiers, bart@big-o.nl
|
||||
*/
|
||||
tree grammar LiquidWalker;
|
||||
|
||||
options {
|
||||
tokenVocab=Liquid;
|
||||
ASTLabelType=CommonTree;
|
||||
}
|
||||
|
||||
@header {
|
||||
package ca.uhn.fhir.narrative.template.nodes;
|
||||
|
||||
import ca.uhn.fhir.narrative.template.parser.*;
|
||||
import ca.uhn.fhir.narrative.template.tags.*;
|
||||
import ca.uhn.fhir.narrative.template.filters.*;
|
||||
|
||||
import java.util.Map;
|
||||
}
|
||||
|
||||
@members {
|
||||
|
||||
private Map<String, Tag> tags;
|
||||
private Map<String, Filter> filters;
|
||||
private Flavor flavor;
|
||||
|
||||
public LiquidWalker(TreeNodeStream nodes, Map<String, Tag> tags, Map<String, Filter> filters) {
|
||||
this(nodes, tags, filters, Flavor.LIQUID);
|
||||
}
|
||||
|
||||
public LiquidWalker(TreeNodeStream nodes, Map<String, Tag> tags, Map<String, Filter> filters, Flavor flavor) {
|
||||
super(nodes);
|
||||
this.tags = tags;
|
||||
this.filters = filters;
|
||||
this.flavor = flavor;
|
||||
}
|
||||
}
|
||||
|
||||
walk returns [LNode node]
|
||||
: block {$node = $block.node;}
|
||||
;
|
||||
|
||||
block returns [BlockNode node]
|
||||
@init{$node = new BlockNode();}
|
||||
: ^(BLOCK (atom {$node.add($atom.node);})*)
|
||||
;
|
||||
|
||||
atom returns [LNode node]
|
||||
: tag {$node = $tag.node;}
|
||||
| output {$node = $output.node;}
|
||||
| assignment {$node = $assignment.node;}
|
||||
| PLAIN {$node = new AtomNode($PLAIN.text);}
|
||||
;
|
||||
|
||||
tag returns [LNode node]
|
||||
: raw_tag {$node = $raw_tag.node;}
|
||||
| comment_tag {$node = $comment_tag.node;}
|
||||
| if_tag {$node = $if_tag.node;}
|
||||
| unless_tag {$node = $unless_tag.node;}
|
||||
| case_tag {$node = $case_tag.node;}
|
||||
| cycle_tag {$node = $cycle_tag.node;}
|
||||
| for_tag {$node = $for_tag.node;}
|
||||
| table_tag {$node = $table_tag.node;}
|
||||
| capture_tag {$node = $capture_tag.node;}
|
||||
| include_tag {$node = $include_tag.node;}
|
||||
| custom_tag {$node = $custom_tag.node;}
|
||||
| custom_tag_block {$node = $custom_tag_block.node;}
|
||||
| break_tag {$node = $break_tag.node;}
|
||||
| continue_tag {$node = $continue_tag.node;}
|
||||
;
|
||||
|
||||
raw_tag returns [LNode node]
|
||||
: RAW {$node = new TagNode("raw", tags.get("raw"), new AtomNode($RAW.text));}
|
||||
;
|
||||
|
||||
comment_tag returns [LNode node]
|
||||
: COMMENT {$node = new TagNode("comment", tags.get("comment"), new AtomNode($COMMENT.text));}
|
||||
;
|
||||
|
||||
if_tag returns [LNode node]
|
||||
@init{List<LNode> nodes = new ArrayList<LNode>();}
|
||||
: ^(IF e1=expr b1=block {nodes.add($e1.node); nodes.add($b1.node);}
|
||||
(^(ELSIF e2=expr b2=block {nodes.add($e2.node); nodes.add($b2.node);} ))*
|
||||
^(ELSE (b3=block {nodes.add(new AtomNode("TRUE")); nodes.add($b3.node);} )?)
|
||||
)
|
||||
{$node = new TagNode("if", tags.get("if"), nodes.toArray(new LNode[nodes.size()]));}
|
||||
;
|
||||
|
||||
unless_tag returns [LNode node]
|
||||
@init{List<LNode> nodes = new ArrayList<LNode>();}
|
||||
: ^(UNLESS expr b1=block {nodes.add($expr.node); nodes.add($b1.node);}
|
||||
^(ELSE (b2=block {nodes.add(new AtomNode(null)); nodes.add($b2.node);})?))
|
||||
{$node = new TagNode("unless", tags.get("unless"), nodes.toArray(new LNode[nodes.size()]));}
|
||||
;
|
||||
|
||||
case_tag returns [LNode node]
|
||||
@init{List<LNode> nodes = new ArrayList<LNode>();}
|
||||
: ^(CASE expr {nodes.add($expr.node);}
|
||||
(when_tag [nodes] )+
|
||||
^(ELSE (block {nodes.add(nodes.get(0)); nodes.add($block.node);} )?))
|
||||
{$node = new TagNode("case", tags.get("case"), nodes.toArray(new LNode[nodes.size()]));}
|
||||
;
|
||||
|
||||
when_tag[List<LNode> nodes]
|
||||
: ^(WHEN (expr {nodes.add($expr.node);})+ block) {nodes.add($block.node);}
|
||||
;
|
||||
|
||||
cycle_tag returns [LNode node]
|
||||
@init{List<LNode> nodes = new ArrayList<LNode>();}
|
||||
: ^(CYCLE cycle_group {nodes.add($cycle_group.node);} (e=expr {nodes.add($e.node);})+)
|
||||
{$node = new TagNode("cycle", tags.get("cycle"), nodes.toArray(new LNode[nodes.size()]));}
|
||||
;
|
||||
|
||||
cycle_group returns [LNode node]
|
||||
: ^(GROUP expr?) {$node = $expr.node;}
|
||||
;
|
||||
|
||||
for_tag returns [LNode node]
|
||||
: for_array {$node = $for_array.node;}
|
||||
| for_range {$node = $for_range.node;}
|
||||
;
|
||||
|
||||
for_array returns [LNode node]
|
||||
@init{
|
||||
List<LNode> expressions = new ArrayList<LNode>();
|
||||
expressions.add(new AtomNode(true));
|
||||
}
|
||||
: ^(FOR_ARRAY Id lookup {expressions.add(new AtomNode($Id.text)); expressions.add($lookup.node);}
|
||||
for_block {expressions.add($for_block.node1); expressions.add($for_block.node2);}
|
||||
^(ATTRIBUTES (attribute {expressions.add($attribute.node);})*)
|
||||
)
|
||||
{$node = new TagNode("for", tags.get("for"), expressions.toArray(new LNode[expressions.size()]));}
|
||||
;
|
||||
|
||||
for_range returns [LNode node]
|
||||
@init{
|
||||
List<LNode> expressions = new ArrayList<LNode>();
|
||||
expressions.add(new AtomNode(false));
|
||||
}
|
||||
: ^(FOR_RANGE Id from=expr to=expr {expressions.add(new AtomNode($Id.text)); expressions.add($from.node); expressions.add($to.node);}
|
||||
block {expressions.add($block.node);}
|
||||
^(ATTRIBUTES (attribute {expressions.add($attribute.node);})*)
|
||||
)
|
||||
{$node = new TagNode("for", tags.get("for"), expressions.toArray(new LNode[expressions.size()]));}
|
||||
;
|
||||
|
||||
for_block returns [LNode node1, LNode node2]
|
||||
: ^(FOR_BLOCK n1=block n2=block?)
|
||||
{
|
||||
$node1 = $n1.node;
|
||||
$node2 = $n2.node;
|
||||
}
|
||||
;
|
||||
|
||||
attribute returns [LNode node]
|
||||
: ^(Id expr) {$node = new AttributeNode(new AtomNode($Id.text), $expr.node);}
|
||||
;
|
||||
|
||||
table_tag returns [LNode node]
|
||||
@init{
|
||||
List<LNode> expressions = new ArrayList<LNode>();
|
||||
}
|
||||
: ^(TABLE
|
||||
Id {expressions.add(new AtomNode($Id.text));}
|
||||
lookup {expressions.add($lookup.node);}
|
||||
block {expressions.add($block.node);}
|
||||
^(ATTRIBUTES (attribute {expressions.add($attribute.node);})*)
|
||||
)
|
||||
{$node = new TagNode("tablerow", tags.get("tablerow"), expressions.toArray(new LNode[expressions.size()]));}
|
||||
;
|
||||
|
||||
capture_tag returns [LNode node]
|
||||
: ^(CAPTURE Id block) {$node = new TagNode("capture", tags.get("capture"), new AtomNode($Id.text), $block.node);}
|
||||
;
|
||||
|
||||
include_tag returns [LNode node]
|
||||
: ^(INCLUDE file=Str ^(WITH (with=Str)?))
|
||||
{
|
||||
if($with.text != null) {
|
||||
$node = new TagNode("include", tags.get("include"), flavor, new AtomNode($file.text), new AtomNode($with.text));
|
||||
} else {
|
||||
$node = new TagNode("include", tags.get("include"), flavor, new AtomNode($file.text));
|
||||
}
|
||||
}
|
||||
;
|
||||
|
||||
break_tag returns [LNode node]
|
||||
: Break {$node = new AtomNode(Tag.Statement.BREAK);}
|
||||
;
|
||||
|
||||
continue_tag returns [LNode node]
|
||||
: Continue {$node = new AtomNode(Tag.Statement.CONTINUE);}
|
||||
;
|
||||
|
||||
|
||||
custom_tag returns [LNode node]
|
||||
@init{List<LNode> expressions = new ArrayList<LNode>();}
|
||||
: ^(CUSTOM_TAG Id (expr {expressions.add($expr.node);})*)
|
||||
{$node = new TagNode($Id.text, tags.get($Id.text), expressions.toArray(new LNode[expressions.size()]));}
|
||||
;
|
||||
|
||||
custom_tag_block returns [LNode node]
|
||||
@init{List<LNode> expressions = new ArrayList<LNode>();}
|
||||
: ^(CUSTOM_TAG_BLOCK Id (expr {expressions.add($expr.node);})* block {expressions.add($block.node);})
|
||||
{$node = new TagNode($Id.text, tags.get($Id.text), expressions.toArray(new LNode[expressions.size()]));}
|
||||
;
|
||||
|
||||
output returns [OutputNode node]
|
||||
: ^(OUTPUT expr {$node = new OutputNode($expr.node);} ^(FILTERS (filter {$node.addFilter($filter.node);})*))
|
||||
;
|
||||
|
||||
filter returns [FilterNode node]
|
||||
: ^(FILTER Id {$node = new FilterNode($Id.text, filters.get($Id.text));} ^(PARAMS params[$node]?))
|
||||
;
|
||||
|
||||
params[FilterNode node]
|
||||
: (expr {$node.add($expr.node);})+
|
||||
;
|
||||
|
||||
assignment returns [TagNode node]
|
||||
: ^(ASSIGNMENT Id filter? expr) {$node = new TagNode("assign", tags.get("assign"), new AtomNode($Id.text), $filter.node, $expr.node);}
|
||||
;
|
||||
|
||||
expr returns [LNode node]
|
||||
: ^(Or a=expr b=expr) {$node = new OrNode($a.node, $b.node);}
|
||||
| ^(And a=expr b=expr) {$node = new AndNode($a.node, $b.node);}
|
||||
| ^(Eq a=expr b=expr) {$node = new EqNode($a.node, $b.node);}
|
||||
| ^(NEq a=expr b=expr) {$node = new NEqNode($a.node, $b.node);}
|
||||
| ^(LtEq a=expr b=expr) {$node = new LtEqNode($a.node, $b.node);}
|
||||
| ^(Lt a=expr b=expr) {$node = new LtNode($a.node, $b.node);}
|
||||
| ^(GtEq a=expr b=expr) {$node = new GtEqNode($a.node, $b.node);}
|
||||
| ^(Gt a=expr b=expr) {$node = new GtNode($a.node, $b.node);}
|
||||
| ^(Contains a=expr b=expr) {$node = new ContainsNode($a.node, $b.node);}
|
||||
| LongNum {$node = new AtomNode(new Long($LongNum.text));}
|
||||
| DoubleNum {$node = new AtomNode(new Double($DoubleNum.text));}
|
||||
| Str {$node = new AtomNode($Str.text);}
|
||||
| True {$node = new AtomNode(true);}
|
||||
| False {$node = new AtomNode(false);}
|
||||
| Nil {$node = new AtomNode(null);}
|
||||
| NO_SPACE {$node = new AtomNode($NO_SPACE.text);}
|
||||
| lookup {$node = $lookup.node;}
|
||||
| Empty {$node = AtomNode.EMPTY;}
|
||||
;
|
||||
|
||||
lookup returns [LookupNode node]
|
||||
: ^(LOOKUP
|
||||
FluentPathExpression {$node = new LookupNode($FluentPathExpression.text);}
|
||||
)
|
||||
;
|
||||
|
|
@ -0,0 +1,545 @@
|
|||
/*
|
||||
* Copyright (c) 2010 by Bart Kiers
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* Project : Liqp; a Liquid Template grammar/parser
|
||||
* Developed by : Bart Kiers, bart@big-o.nl
|
||||
*/
|
||||
grammar Liquid;
|
||||
|
||||
options {
|
||||
output=AST;
|
||||
ASTLabelType=CommonTree;
|
||||
}
|
||||
|
||||
tokens {
|
||||
ASSIGNMENT;
|
||||
ATTRIBUTES;
|
||||
BLOCK;
|
||||
CAPTURE;
|
||||
CASE;
|
||||
COMMENT;
|
||||
CYCLE;
|
||||
ELSE;
|
||||
FILTERS;
|
||||
FILTER;
|
||||
FOR_ARRAY;
|
||||
FOR_BLOCK;
|
||||
FOR_RANGE;
|
||||
GROUP;
|
||||
IF;
|
||||
ELSIF;
|
||||
INCLUDE;
|
||||
LOOKUP;
|
||||
OUTPUT;
|
||||
PARAMS;
|
||||
PLAIN;
|
||||
RAW;
|
||||
TABLE;
|
||||
UNLESS;
|
||||
WHEN;
|
||||
WITH;
|
||||
NO_SPACE;
|
||||
CUSTOM_TAG;
|
||||
CUSTOM_TAG_BLOCK;
|
||||
}
|
||||
|
||||
@parser::header {
|
||||
package ca.uhn.fhir.narrative.template.parser;
|
||||
}
|
||||
|
||||
@lexer::header {
|
||||
package ca.uhn.fhir.narrative.template.parser;
|
||||
}
|
||||
|
||||
@parser::members {
|
||||
|
||||
private Flavor flavor = Flavor.LIQUID;
|
||||
|
||||
public LiquidParser(Flavor flavor, TokenStream input) {
|
||||
this(input, new RecognizerSharedState());
|
||||
this.flavor = flavor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportError(RecognitionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@lexer::members {
|
||||
private boolean inTag = false;
|
||||
private boolean inRaw = false;
|
||||
|
||||
private boolean openRawEndTagAhead() {
|
||||
|
||||
if(!openTagAhead()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int indexLA = 3;
|
||||
|
||||
while(Character.isSpaceChar(input.LA(indexLA))) {
|
||||
indexLA++;
|
||||
}
|
||||
|
||||
return input.LA(indexLA) == 'e' &&
|
||||
input.LA(indexLA + 1) == 'n' &&
|
||||
input.LA(indexLA + 2) == 'd' &&
|
||||
input.LA(indexLA + 3) == 'r' &&
|
||||
input.LA(indexLA + 4) == 'a' &&
|
||||
input.LA(indexLA + 5) == 'w';
|
||||
}
|
||||
|
||||
private boolean openTagAhead() {
|
||||
return input.LA(1) == '{' && (input.LA(2) == '{' || input.LA(2) == '\u0025');
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportError(RecognitionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
private String strip(String text, boolean singleQuoted) {
|
||||
return text.substring(1, text.length() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* parser rules */
|
||||
parse
|
||||
: block EOF -> block
|
||||
//(t=. {System.out.printf("\%-20s '\%s'\n", tokenNames[$t.type], $t.text);})* EOF
|
||||
;
|
||||
|
||||
block
|
||||
: (options{greedy=true;}: atom)* -> ^(BLOCK atom*)
|
||||
;
|
||||
|
||||
atom
|
||||
: tag
|
||||
| output
|
||||
| assignment
|
||||
| Other -> PLAIN[$Other.text]
|
||||
;
|
||||
|
||||
tag
|
||||
: custom_tag
|
||||
| raw_tag
|
||||
| comment_tag
|
||||
| if_tag
|
||||
| unless_tag
|
||||
| case_tag
|
||||
| cycle_tag
|
||||
| for_tag
|
||||
| table_tag
|
||||
| capture_tag
|
||||
| include_tag
|
||||
| break_tag
|
||||
| continue_tag
|
||||
;
|
||||
|
||||
custom_tag
|
||||
: (TagStart Id (expr (Comma expr)*)? TagEnd -> ^(CUSTOM_TAG Id expr*))
|
||||
((custom_tag_block)=> custom_tag_block -> ^(CUSTOM_TAG_BLOCK Id expr* custom_tag_block))?
|
||||
;
|
||||
|
||||
custom_tag_block
|
||||
: (options{greedy=false;}: atom)* TagStart EndId TagEnd -> ^(BLOCK atom*)
|
||||
;
|
||||
|
||||
raw_tag
|
||||
: TagStart RawStart TagEnd raw_body TagStart RawEnd TagEnd -> raw_body
|
||||
;
|
||||
|
||||
raw_body
|
||||
: other_than_tag_start -> RAW[$other_than_tag_start.text]
|
||||
;
|
||||
|
||||
comment_tag
|
||||
: TagStart CommentStart TagEnd comment_body TagStart CommentEnd TagEnd -> comment_body
|
||||
;
|
||||
|
||||
comment_body
|
||||
: other_than_tag_start -> COMMENT[$other_than_tag_start.text]
|
||||
;
|
||||
|
||||
other_than_tag_start
|
||||
: ~TagStart*
|
||||
;
|
||||
|
||||
if_tag
|
||||
: TagStart IfStart expr TagEnd block elsif_tag* else_tag? TagStart IfEnd TagEnd -> ^(IF expr block elsif_tag* ^(ELSE else_tag?))
|
||||
;
|
||||
|
||||
elsif_tag
|
||||
: TagStart Elsif expr TagEnd block -> ^(ELSIF expr block)
|
||||
;
|
||||
|
||||
else_tag
|
||||
: TagStart Else TagEnd block -> block
|
||||
;
|
||||
|
||||
unless_tag
|
||||
: TagStart UnlessStart expr TagEnd block else_tag? TagStart UnlessEnd TagEnd -> ^(UNLESS expr block ^(ELSE else_tag?))
|
||||
;
|
||||
|
||||
case_tag
|
||||
: TagStart CaseStart expr TagEnd Other? when_tag+ else_tag? TagStart CaseEnd TagEnd -> ^(CASE expr when_tag+ ^(ELSE else_tag?))
|
||||
;
|
||||
|
||||
when_tag
|
||||
: TagStart When term ((Or | Comma) term)* TagEnd block -> ^(WHEN term+ block)
|
||||
;
|
||||
|
||||
cycle_tag
|
||||
: TagStart Cycle cycle_group expr (Comma expr)* TagEnd -> ^(CYCLE cycle_group expr+)
|
||||
;
|
||||
|
||||
cycle_group
|
||||
: ((expr Col)=> expr Col)? -> ^(GROUP expr?)
|
||||
;
|
||||
|
||||
for_tag
|
||||
: for_array
|
||||
| for_range
|
||||
;
|
||||
|
||||
for_array
|
||||
: TagStart ForStart Id In lookup attribute* TagEnd
|
||||
for_block
|
||||
TagStart ForEnd TagEnd
|
||||
-> ^(FOR_ARRAY Id lookup for_block ^(ATTRIBUTES attribute*))
|
||||
;
|
||||
|
||||
for_range
|
||||
: TagStart ForStart Id In OPar expr DotDot expr CPar attribute* TagEnd
|
||||
block
|
||||
TagStart ForEnd TagEnd
|
||||
-> ^(FOR_RANGE Id expr expr block ^(ATTRIBUTES attribute*))
|
||||
;
|
||||
|
||||
for_block
|
||||
: a=block (TagStart Else TagEnd b=block)? -> ^(FOR_BLOCK block block?)
|
||||
;
|
||||
|
||||
attribute
|
||||
: Id Col expr -> ^(Id expr)
|
||||
;
|
||||
|
||||
table_tag
|
||||
: TagStart TableStart Id In lookup attribute* TagEnd block TagStart TableEnd TagEnd -> ^(TABLE Id lookup block ^(ATTRIBUTES attribute*))
|
||||
;
|
||||
|
||||
capture_tag
|
||||
: TagStart CaptureStart ( Id TagEnd block TagStart CaptureEnd TagEnd -> ^(CAPTURE Id block)
|
||||
| Str TagEnd block TagStart CaptureEnd TagEnd -> ^(CAPTURE Id[$Str.text] block)
|
||||
)
|
||||
;
|
||||
|
||||
include_tag
|
||||
: TagStart Include ( {this.flavor == Flavor.JEKYLL}?=>
|
||||
file_name_as_str TagEnd -> ^(INCLUDE file_name_as_str ^(WITH ))
|
||||
| a=Str (With b=Str)? TagEnd -> ^(INCLUDE $a ^(WITH $b?))
|
||||
)
|
||||
;
|
||||
|
||||
break_tag
|
||||
: TagStart Break TagEnd -> Break
|
||||
;
|
||||
|
||||
continue_tag
|
||||
: TagStart Continue TagEnd -> Continue
|
||||
;
|
||||
|
||||
output
|
||||
: OutStart expr filter* OutEnd -> ^(OUTPUT expr ^(FILTERS filter*))
|
||||
;
|
||||
|
||||
filter
|
||||
: Pipe Id params? -> ^(FILTER Id ^(PARAMS params?))
|
||||
;
|
||||
|
||||
params
|
||||
: Col expr (Comma expr)* -> expr+
|
||||
;
|
||||
|
||||
assignment
|
||||
: TagStart Assign Id EqSign expr filter? TagEnd -> ^(ASSIGNMENT Id filter? expr)
|
||||
;
|
||||
|
||||
expr
|
||||
: or_expr
|
||||
;
|
||||
|
||||
or_expr
|
||||
: and_expr (Or^ and_expr)*
|
||||
;
|
||||
|
||||
and_expr
|
||||
: contains_expr (And^ contains_expr)*
|
||||
;
|
||||
|
||||
contains_expr
|
||||
: eq_expr (Contains^ eq_expr)?
|
||||
;
|
||||
|
||||
eq_expr
|
||||
: rel_expr ((Eq | NEq)^ rel_expr)*
|
||||
;
|
||||
|
||||
rel_expr
|
||||
: term ((LtEq | Lt | GtEq | Gt)^ term)?
|
||||
;
|
||||
|
||||
term
|
||||
: DoubleNum
|
||||
| LongNum
|
||||
| Str
|
||||
| True
|
||||
| False
|
||||
| Nil
|
||||
| NoSpace+ -> NO_SPACE[$text]
|
||||
| lookup
|
||||
| Empty
|
||||
| OPar expr CPar -> expr
|
||||
;
|
||||
|
||||
lookup
|
||||
: FluentPathExpression -> ^(LOOKUP FluentPathExpression)
|
||||
;
|
||||
|
||||
id
|
||||
: Id
|
||||
| Continue -> Id[$Continue.text]
|
||||
;
|
||||
|
||||
id2
|
||||
: id
|
||||
| Empty -> Id[$Empty.text]
|
||||
| CaptureStart -> Id[$CaptureStart.text]
|
||||
| CaptureEnd -> Id[$CaptureEnd.text]
|
||||
| CommentStart -> Id[$CommentStart.text]
|
||||
| CommentEnd -> Id[$CommentEnd.text]
|
||||
| RawStart -> Id[$RawStart.text]
|
||||
| RawEnd -> Id[$RawEnd.text]
|
||||
| IfStart -> Id[$IfStart.text]
|
||||
| Elsif -> Id[$Elsif.text]
|
||||
| IfEnd -> Id[$IfEnd.text]
|
||||
| UnlessStart -> Id[$UnlessStart.text]
|
||||
| UnlessEnd -> Id[$UnlessEnd.text]
|
||||
| Else -> Id[$Else.text]
|
||||
| Contains -> Id[$Contains.text]
|
||||
| CaseStart -> Id[$CaseStart.text]
|
||||
| CaseEnd -> Id[$CaseEnd.text]
|
||||
| When -> Id[$When.text]
|
||||
| Cycle -> Id[$Cycle.text]
|
||||
| ForStart -> Id[$ForStart.text]
|
||||
| ForEnd -> Id[$ForEnd.text]
|
||||
| In -> Id[$In.text]
|
||||
| And -> Id[$And.text]
|
||||
| Or -> Id[$Or.text]
|
||||
| TableStart -> Id[$TableStart.text]
|
||||
| TableEnd -> Id[$TableEnd.text]
|
||||
| Assign -> Id[$Assign.text]
|
||||
| True -> Id[$True.text]
|
||||
| False -> Id[$False.text]
|
||||
| Nil -> Id[$Nil.text]
|
||||
| Include -> Id[$Include.text]
|
||||
| With -> Id[$With.text]
|
||||
| EndId -> Id[$EndId.text]
|
||||
| Break -> Id[$Break.text]
|
||||
;
|
||||
|
||||
file_name_as_str
|
||||
: other_than_tag_end -> Str[$text]
|
||||
;
|
||||
|
||||
other_than_tag_end
|
||||
: ~TagEnd+
|
||||
;
|
||||
|
||||
/* lexer rules */
|
||||
OutStart : '{{' {inTag=true;};
|
||||
OutEnd : '}}' {inTag=false;};
|
||||
TagStart : '{%' {inTag=true;};
|
||||
TagEnd : '%}' {inTag=false;};
|
||||
|
||||
Str : {inTag}?=> (SStr | DStr);
|
||||
|
||||
DotDot : {inTag}?=> '..';
|
||||
Dot : {inTag}?=> '.';
|
||||
NEq : {inTag}?=> '!=' | '<>';
|
||||
Eq : {inTag}?=> '==';
|
||||
EqSign : {inTag}?=> '=';
|
||||
GtEq : {inTag}?=> '>=';
|
||||
Gt : {inTag}?=> '>';
|
||||
LtEq : {inTag}?=> '<=';
|
||||
Lt : {inTag}?=> '<';
|
||||
Minus : {inTag}?=> '-';
|
||||
Pipe : {inTag}?=> '|';
|
||||
Col : {inTag}?=> ':';
|
||||
Comma : {inTag}?=> ',';
|
||||
OPar : {inTag}?=> '(';
|
||||
CPar : {inTag}?=> ')';
|
||||
OBr : {inTag}?=> '[';
|
||||
CBr : {inTag}?=> ']';
|
||||
QMark : {inTag}?=> '?';
|
||||
|
||||
DoubleNum : {inTag}?=> '-'? Digit+ ( {input.LA(1) == '.' && input.LA(2) != '.'}?=> '.' Digit*
|
||||
| {$type = LongNum;}
|
||||
);
|
||||
LongNum : {inTag}?=> '-'? Digit+;
|
||||
WS : {inTag}?=> (' ' | '\t' | '\r' | '\n')+ {$channel=HIDDEN;};
|
||||
|
||||
FluentPathExpression
|
||||
: {inTag}?=> (Letter | '_') (Letter | '_' | '-' | Digit | '.' | '[' | ']' | '(' | ')' )*
|
||||
{
|
||||
if($text.equals("capture")) $type = CaptureStart;
|
||||
else if($text.equals("endcapture")) $type = CaptureEnd;
|
||||
else if($text.equals("comment")) $type = CommentStart;
|
||||
else if($text.equals("endcomment")) $type = CommentEnd;
|
||||
else if($text.equals("raw")) { $type = RawStart; inRaw = true; }
|
||||
else if($text.equals("endraw")) { $type = RawEnd; inRaw = false; }
|
||||
else if($text.equals("if")) $type = IfStart;
|
||||
else if($text.equals("elsif")) $type = Elsif;
|
||||
else if($text.equals("endif")) $type = IfEnd;
|
||||
else if($text.equals("unless")) $type = UnlessStart;
|
||||
else if($text.equals("endunless")) $type = UnlessEnd;
|
||||
else if($text.equals("else")) $type = Else;
|
||||
else if($text.equals("contains")) $type = Contains;
|
||||
else if($text.equals("case")) $type = CaseStart;
|
||||
else if($text.equals("endcase")) $type = CaseEnd;
|
||||
else if($text.equals("when")) $type = When;
|
||||
else if($text.equals("cycle")) $type = Cycle;
|
||||
else if($text.equals("for")) $type = ForStart;
|
||||
else if($text.equals("endfor")) $type = ForEnd;
|
||||
else if($text.equals("in")) $type = In;
|
||||
else if($text.equals("and")) $type = And;
|
||||
else if($text.equals("or")) $type = Or;
|
||||
else if($text.equals("tablerow")) $type = TableStart;
|
||||
else if($text.equals("endtablerow")) $type = TableEnd;
|
||||
else if($text.equals("assign")) $type = Assign;
|
||||
else if($text.equals("true")) $type = True;
|
||||
else if($text.equals("false")) $type = False;
|
||||
else if($text.equals("nil")) $type = Nil;
|
||||
else if($text.equals("null")) $type = Nil;
|
||||
else if($text.equals("include")) $type = Include;
|
||||
else if($text.equals("with")) $type = With;
|
||||
else if($text.startsWith("end")) $type = EndId;
|
||||
else if($text.equals("break")) $type = Break;
|
||||
else if($text.startsWith("continue")) $type = Continue;
|
||||
else if($text.startsWith("empty")) $type = Empty;
|
||||
}
|
||||
;
|
||||
|
||||
|
||||
Id
|
||||
: {inTag}?=> (Letter | '_') (Letter | '_' | '-' | Digit)*
|
||||
{
|
||||
if($text.equals("capture")) $type = CaptureStart;
|
||||
else if($text.equals("endcapture")) $type = CaptureEnd;
|
||||
else if($text.equals("comment")) $type = CommentStart;
|
||||
else if($text.equals("endcomment")) $type = CommentEnd;
|
||||
else if($text.equals("raw")) { $type = RawStart; inRaw = true; }
|
||||
else if($text.equals("endraw")) { $type = RawEnd; inRaw = false; }
|
||||
else if($text.equals("if")) $type = IfStart;
|
||||
else if($text.equals("elsif")) $type = Elsif;
|
||||
else if($text.equals("endif")) $type = IfEnd;
|
||||
else if($text.equals("unless")) $type = UnlessStart;
|
||||
else if($text.equals("endunless")) $type = UnlessEnd;
|
||||
else if($text.equals("else")) $type = Else;
|
||||
else if($text.equals("contains")) $type = Contains;
|
||||
else if($text.equals("case")) $type = CaseStart;
|
||||
else if($text.equals("endcase")) $type = CaseEnd;
|
||||
else if($text.equals("when")) $type = When;
|
||||
else if($text.equals("cycle")) $type = Cycle;
|
||||
else if($text.equals("for")) $type = ForStart;
|
||||
else if($text.equals("endfor")) $type = ForEnd;
|
||||
else if($text.equals("in")) $type = In;
|
||||
else if($text.equals("and")) $type = And;
|
||||
else if($text.equals("or")) $type = Or;
|
||||
else if($text.equals("tablerow")) $type = TableStart;
|
||||
else if($text.equals("endtablerow")) $type = TableEnd;
|
||||
else if($text.equals("assign")) $type = Assign;
|
||||
else if($text.equals("true")) $type = True;
|
||||
else if($text.equals("false")) $type = False;
|
||||
else if($text.equals("nil")) $type = Nil;
|
||||
else if($text.equals("null")) $type = Nil;
|
||||
else if($text.equals("include")) $type = Include;
|
||||
else if($text.equals("with")) $type = With;
|
||||
else if($text.startsWith("end")) $type = EndId;
|
||||
else if($text.equals("break")) $type = Break;
|
||||
else if($text.startsWith("continue")) $type = Continue;
|
||||
else if($text.startsWith("empty")) $type = Empty;
|
||||
}
|
||||
;
|
||||
|
||||
Other
|
||||
: ({!inTag && !openTagAhead()}?=> . )+
|
||||
| ({!inTag && inRaw && !openRawEndTagAhead()}?=> . )+
|
||||
;
|
||||
|
||||
NoSpace
|
||||
: ~(' ' | '\t' | '\r' | '\n')
|
||||
;
|
||||
|
||||
/* fragment rules */
|
||||
fragment Letter : 'a'..'z' | 'A'..'Z';
|
||||
fragment Digit : '0'..'9';
|
||||
fragment SStr : '\'' ~'\''* '\'' {setText(strip($text, true));};
|
||||
fragment DStr : '"' ~'"'* '"' {setText(strip($text, false));};
|
||||
|
||||
fragment CommentStart : 'CommentStart';
|
||||
fragment CommentEnd : 'CommentEnd';
|
||||
fragment RawStart : 'RawStart';
|
||||
fragment RawEnd : 'RawEnd';
|
||||
fragment IfStart : 'IfStart';
|
||||
fragment IfEnd : 'IfEnd';
|
||||
fragment Elsif : 'Elsif';
|
||||
fragment UnlessStart : 'UnlessStart';
|
||||
fragment UnlessEnd : 'UnlessEnd';
|
||||
fragment Else : 'Else';
|
||||
fragment Contains : 'contains';
|
||||
fragment CaseStart : 'CaseStart';
|
||||
fragment CaseEnd : 'CaseEnd';
|
||||
fragment When : 'When';
|
||||
fragment Cycle : 'Cycle';
|
||||
fragment ForStart : 'ForStart';
|
||||
fragment ForEnd : 'ForEnd';
|
||||
fragment In : 'In';
|
||||
fragment And : 'And';
|
||||
fragment Or : 'Or';
|
||||
fragment TableStart : 'TableStart';
|
||||
fragment TableEnd : 'TableEnd';
|
||||
fragment Assign : 'Assign';
|
||||
fragment True : 'True';
|
||||
fragment False : 'False';
|
||||
fragment Nil : 'Nil';
|
||||
fragment Include : 'Include';
|
||||
fragment With : 'With';
|
||||
fragment CaptureStart : 'CaptureStart';
|
||||
fragment CaptureEnd : 'CaptureEnd';
|
||||
fragment EndId : 'EndId';
|
||||
fragment Break : 'Break';
|
||||
fragment Continue : 'Continue';
|
||||
fragment Empty : 'Empty';
|
|
@ -0,0 +1,248 @@
|
|||
package ca.uhn.fhir.narrative.template;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import ca.uhn.fhir.narrative.template.nodes.AtomNode;
|
||||
|
||||
/**
|
||||
* An abstract class the Filter and Tag classes extend.
|
||||
* <p/>
|
||||
* It houses some utility methods easily available for said
|
||||
* classes.
|
||||
*/
|
||||
public abstract class LValue {
|
||||
|
||||
/**
|
||||
* Returns true iff a and b are equals, where (int) 1 is
|
||||
* equals to (double) 1.0
|
||||
*
|
||||
* @param a
|
||||
* the first object to compare.
|
||||
* @param b
|
||||
* the second object to compare.
|
||||
*
|
||||
* @return true iff a and b are equals, where (int) 1 is
|
||||
* equals to (double) 1.0
|
||||
*/
|
||||
public static boolean areEqual(Object a, Object b) {
|
||||
|
||||
if (a == b) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (a == null || b == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO refactor the instance-ofs below
|
||||
|
||||
if (a instanceof Number && b instanceof Number) {
|
||||
|
||||
double delta = ((Number) a).doubleValue() - ((Number) b).doubleValue();
|
||||
|
||||
// To account for floating point rounding errors, return true if
|
||||
// the difference between double a and double b is very small.
|
||||
return Math.abs(delta) < 0.00000000001;
|
||||
}
|
||||
|
||||
if (AtomNode.isEmpty(a) && (b instanceof CharSequence)) {
|
||||
return ((CharSequence)b).length() == 0;
|
||||
}
|
||||
|
||||
if (AtomNode.isEmpty(b) && (a instanceof CharSequence)) {
|
||||
return ((CharSequence)a).length() == 0;
|
||||
}
|
||||
|
||||
if (AtomNode.isEmpty(a) && (b instanceof Collection)) {
|
||||
return ((Collection)b).size() == 0;
|
||||
}
|
||||
|
||||
if (AtomNode.isEmpty(b) && (a instanceof Collection)) {
|
||||
return ((Collection)a).size() == 0;
|
||||
}
|
||||
|
||||
if (AtomNode.isEmpty(a) && (b.getClass().isArray())) {
|
||||
return ((Object[])b).length == 0;
|
||||
}
|
||||
|
||||
if (AtomNode.isEmpty(b) && (a.getClass().isArray())) {
|
||||
return ((Object[])a).length == 0;
|
||||
}
|
||||
|
||||
return a.equals(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this value as an array. If a value is already an array,
|
||||
* it is casted to a `Object[]`, if it's a `java.util.List`, it is
|
||||
* converted to an array and in all other cases, `value` is simply
|
||||
* returned as an `Object[]` with a single value in it.
|
||||
*
|
||||
* @param value
|
||||
* the value to convert/cast to an array.
|
||||
*
|
||||
* @return this value as an array.
|
||||
*/
|
||||
public Object[] asArray(Object value) {
|
||||
|
||||
if(value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value.getClass().isArray()) {
|
||||
return (Object[]) value;
|
||||
}
|
||||
|
||||
if (value instanceof List) {
|
||||
return ((List) value).toArray();
|
||||
}
|
||||
|
||||
return new Object[]{value};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert `value` to a boolean. Note that only `nil` and `false`
|
||||
* are `false`, all other values are `true`.
|
||||
*
|
||||
* @param value
|
||||
* the value to convert.
|
||||
*
|
||||
* @return `value` as a boolean.
|
||||
*/
|
||||
public boolean asBoolean(Object value) {
|
||||
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value instanceof Boolean) {
|
||||
return (Boolean) value;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `value` as a Number. Strings will be coerced into
|
||||
* either a Long or Double.
|
||||
*
|
||||
* @param value
|
||||
* the value to cast to a Number.
|
||||
*
|
||||
* @return `value` as a Number.
|
||||
*
|
||||
* @throws NumberFormatException when `value` is a String which could
|
||||
* not be parsed as a Long or Double.
|
||||
*/
|
||||
public Number asNumber(Object value) throws NumberFormatException {
|
||||
|
||||
if(value instanceof Number) {
|
||||
return (Number) value;
|
||||
}
|
||||
|
||||
String str = String.valueOf(value);
|
||||
|
||||
return str.matches("\\d+") ? Long.valueOf(str) : Double.valueOf(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `value` as a String.
|
||||
*
|
||||
* @param value
|
||||
* the value to convert to a String.
|
||||
*
|
||||
* @return `value` as a String.
|
||||
*/
|
||||
public String asString(Object value) {
|
||||
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (!this.isArray(value)) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
Object[] array = this.asArray(value);
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (Object obj : array) {
|
||||
builder.append(this.asString(obj));
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true iff `value` is an array or a java.util.List.
|
||||
*
|
||||
* @param value
|
||||
* the value to check.
|
||||
*
|
||||
* @return true iff `value` is an array or a java.util.List.
|
||||
*/
|
||||
public boolean isArray(Object value) {
|
||||
|
||||
return value != null && (value.getClass().isArray() || value instanceof List);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true iff `value` is a whole number (Integer or Long).
|
||||
*
|
||||
* @param value
|
||||
* the value to check.
|
||||
*
|
||||
* @return true iff `value` is a whole number (Integer or Long).
|
||||
*/
|
||||
public boolean isInteger(Object value) {
|
||||
return value != null && (value instanceof Long || value instanceof Integer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true iff `value` is a Number.
|
||||
*
|
||||
* @param value
|
||||
* the value to check.
|
||||
*
|
||||
* @return true iff `value` is a Number.
|
||||
*/
|
||||
public boolean isNumber(Object value) {
|
||||
|
||||
if(value == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(value instanceof Number) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// valid Long?
|
||||
if(String.valueOf(value).matches("\\d+")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
// valid Double?
|
||||
Double.parseDouble(String.valueOf(value));
|
||||
} catch(Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true iff `value` is a String.
|
||||
*
|
||||
* @param value
|
||||
* the value to check.
|
||||
*
|
||||
* @return true iff `value` is a String.
|
||||
*/
|
||||
public boolean isString(Object value) {
|
||||
|
||||
return value != null && value instanceof CharSequence;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,248 @@
|
|||
package ca.uhn.fhir.narrative.template;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.antlr.runtime.ANTLRFileStream;
|
||||
import org.antlr.runtime.ANTLRStringStream;
|
||||
import org.antlr.runtime.CommonTokenStream;
|
||||
import org.antlr.runtime.RecognitionException;
|
||||
import org.antlr.runtime.tree.CommonTree;
|
||||
import org.antlr.runtime.tree.CommonTreeNodeStream;
|
||||
|
||||
import ca.uhn.fhir.narrative.template.filters.Filter;
|
||||
import ca.uhn.fhir.narrative.template.nodes.LNode;
|
||||
import ca.uhn.fhir.narrative.template.nodes.LiquidWalker;
|
||||
import ca.uhn.fhir.narrative.template.parser.Flavor;
|
||||
import ca.uhn.fhir.narrative.template.parser.LiquidLexer;
|
||||
import ca.uhn.fhir.narrative.template.parser.LiquidParser;
|
||||
import ca.uhn.fhir.narrative.template.tags.Tag;
|
||||
|
||||
/**
|
||||
* The main class of this library. Use one of its static
|
||||
* <code>parse(...)</code> to get a hold of a reference.
|
||||
* <p/>
|
||||
* Also see: https://github.com/Shopify/liquid
|
||||
*/
|
||||
public class Template {
|
||||
|
||||
/**
|
||||
* The root of the AST denoting the Liquid input source.
|
||||
*/
|
||||
private final CommonTree root;
|
||||
|
||||
/**
|
||||
* This instance's tags.
|
||||
*/
|
||||
private final Map<String, Tag> tags;
|
||||
|
||||
/**
|
||||
* This instance's filters.
|
||||
*/
|
||||
private final Map<String, Filter> filters;
|
||||
|
||||
private final Flavor flavor;
|
||||
|
||||
/**
|
||||
* Creates a new Template instance from a given input.
|
||||
* @param input
|
||||
* the file holding the Liquid source.
|
||||
* @param tags
|
||||
* the tags this instance will make use of.
|
||||
* @param filters
|
||||
* the filters this instance will make use of.
|
||||
*/
|
||||
private Template(String input, Map<String, Tag> tags, Map<String, Filter> filters) {
|
||||
this(input, tags, filters, Flavor.LIQUID);
|
||||
}
|
||||
|
||||
private Template(String input, Map<String, Tag> tags, Map<String, Filter> filters, Flavor flavor) {
|
||||
|
||||
this.tags = tags;
|
||||
this.filters = filters;
|
||||
this.flavor = flavor;
|
||||
|
||||
LiquidLexer lexer = new LiquidLexer(new ANTLRStringStream(input));
|
||||
LiquidParser parser = new LiquidParser(flavor, new CommonTokenStream(lexer));
|
||||
|
||||
try {
|
||||
root = (CommonTree) parser.parse().getTree();
|
||||
}
|
||||
catch (RecognitionException e) {
|
||||
throw new RuntimeException("could not parse input: " + input, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Template instance from a given file.
|
||||
*
|
||||
* @param file
|
||||
* the file holding the Liquid source.
|
||||
*/
|
||||
private Template(File file, Map<String, Tag> tags, Map<String, Filter> filters) throws IOException {
|
||||
this(file, tags, filters, Flavor.LIQUID);
|
||||
}
|
||||
|
||||
private Template(File file, Map<String, Tag> tags, Map<String, Filter> filters, Flavor flavor) throws IOException {
|
||||
|
||||
this.tags = tags;
|
||||
this.filters = filters;
|
||||
this.flavor = flavor;
|
||||
|
||||
try {
|
||||
LiquidLexer lexer = new LiquidLexer(new ANTLRFileStream(file.getAbsolutePath()));
|
||||
LiquidParser parser = new LiquidParser(flavor, new CommonTokenStream(lexer));
|
||||
root = (CommonTree) parser.parse().getTree();
|
||||
}
|
||||
catch (RecognitionException e) {
|
||||
throw new RuntimeException("could not parse input from " + file, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root of the AST of the parsed input.
|
||||
*
|
||||
* @return the root of the AST of the parsed input.
|
||||
*/
|
||||
public CommonTree getAST() {
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new Template instance from a given input string.
|
||||
*
|
||||
* @param input
|
||||
* the input string holding the Liquid source.
|
||||
*
|
||||
* @return a new Template instance from a given input string.
|
||||
*/
|
||||
public static Template parse(String input) {
|
||||
return new Template(input, Tag.getTags(), Filter.getFilters());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new Template instance from a given input file.
|
||||
*
|
||||
* @param file
|
||||
* the input file holding the Liquid source.
|
||||
*
|
||||
* @return a new Template instance from a given input file.
|
||||
*/
|
||||
public static Template parse(File file) throws IOException {
|
||||
return parse(file, Flavor.LIQUID);
|
||||
}
|
||||
|
||||
public static Template parse(File file, Flavor flavor) throws IOException {
|
||||
return new Template(file, Tag.getTags(), Filter.getFilters(), flavor);
|
||||
}
|
||||
|
||||
public Template with(Tag tag) {
|
||||
this.tags.put(tag.name, tag);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Template with(Filter filter) {
|
||||
this.filters.put(filter.name, filter);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Renders the template.
|
||||
*
|
||||
* @param context
|
||||
* a Map denoting the (possibly nested)
|
||||
* variables that can be used in this
|
||||
* Template.
|
||||
*
|
||||
* @return a string denoting the rendered template.
|
||||
*/
|
||||
public String render(Map<String, Object> context) {
|
||||
|
||||
LiquidWalker walker = new LiquidWalker(new CommonTreeNodeStream(root), this.tags, this.filters, this.flavor);
|
||||
|
||||
try {
|
||||
LNode node = walker.walk();
|
||||
Object rendered = node.render(context);
|
||||
return rendered == null ? "" : String.valueOf(rendered);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the AST of the parsed
|
||||
* input source.
|
||||
*
|
||||
* @return a string representation of the AST of the parsed
|
||||
* input source.
|
||||
*/
|
||||
public String toStringAST() {
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
walk(root, builder);
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Walks a (sub) tree of the root of the input source and builds
|
||||
* a string representation of the structure of the AST.
|
||||
* <p/>
|
||||
* Note that line breaks and multiple white space characters are
|
||||
* trimmed to a single white space character.
|
||||
*
|
||||
* @param tree
|
||||
* the (sub) tree.
|
||||
* @param builder
|
||||
* the StringBuilder to fill.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void walk(CommonTree tree, StringBuilder builder) {
|
||||
|
||||
List<CommonTree> firstStack = new ArrayList<CommonTree>();
|
||||
firstStack.add(tree);
|
||||
|
||||
List<List<CommonTree>> childListStack = new ArrayList<List<CommonTree>>();
|
||||
childListStack.add(firstStack);
|
||||
|
||||
while (!childListStack.isEmpty()) {
|
||||
|
||||
List<CommonTree> childStack = childListStack.get(childListStack.size() - 1);
|
||||
|
||||
if (childStack.isEmpty()) {
|
||||
childListStack.remove(childListStack.size() - 1);
|
||||
}
|
||||
else {
|
||||
tree = childStack.remove(0);
|
||||
|
||||
String indent = "";
|
||||
|
||||
for (int i = 0; i < childListStack.size() - 1; i++) {
|
||||
indent += (childListStack.get(i).size() > 0) ? "| " : " ";
|
||||
}
|
||||
|
||||
String tokenName = LiquidParser.tokenNames[tree.getType()];
|
||||
String tokenText = tree.getText().replaceAll("\\s+", " ").trim();
|
||||
|
||||
builder.append(indent)
|
||||
.append(childStack.isEmpty() ? "'- " : "|- ")
|
||||
.append(tokenName)
|
||||
.append(!tokenName.equals(tokenText) ? "='" + tokenText + "'" : "")
|
||||
.append("\n");
|
||||
|
||||
if (tree.getChildCount() > 0) {
|
||||
childListStack.add(new ArrayList<CommonTree>((List<CommonTree>) tree.getChildren()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
|
||||
package ca.uhn.fhir.narrative.template;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.antlr.runtime.tree.CommonTree;
|
||||
import org.hl7.fhir.dstu3.exceptions.FHIRException;
|
||||
import org.hl7.fhir.dstu3.hapi.validation.DefaultProfileValidationSupport;
|
||||
import org.hl7.fhir.dstu3.hapi.validation.HapiWorkerContext;
|
||||
import org.hl7.fhir.dstu3.hapi.validation.IValidationSupport;
|
||||
import org.hl7.fhir.dstu3.model.Base;
|
||||
import org.hl7.fhir.dstu3.utils.FluentPathEngine;
|
||||
import org.hl7.fhir.dstu3.utils.IWorkerContext;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.INarrative;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.narrative.INarrativeGenerator;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.util.FhirTerser;
|
||||
|
||||
public class TemplateNarrativeGenerator implements INarrativeGenerator {
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TemplateNarrativeGenerator.class);
|
||||
|
||||
private FhirContext myFhirContext = FhirContext.forDstu3();
|
||||
private IValidationSupport myValidationSupport = new DefaultProfileValidationSupport();
|
||||
|
||||
@Override
|
||||
public void generateNarrative(FhirContext theContext, IBaseResource theResource, INarrative theNarrative) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
public String processLiquid(FhirContext theContext, String theTemplate, IBaseResource theResource) {
|
||||
Template template = Template.parse(theTemplate);
|
||||
|
||||
ourLog.info(template.toStringAST());
|
||||
|
||||
IWorkerContext ctx = new HapiWorkerContext(myFhirContext, myValidationSupport);
|
||||
FluentPathEngine fluentPathEngine = new FluentPathEngine(ctx);
|
||||
|
||||
FhirTerser terser = new FhirTerser(theContext);
|
||||
|
||||
HashMap<String, Object> context = new HashMap<String, Object>();
|
||||
context.put("context", theResource);
|
||||
context.put("terser", terser);
|
||||
context.put("fpEngine", fluentPathEngine);
|
||||
|
||||
return template.render(context);
|
||||
|
||||
}
|
||||
|
||||
class MyContextMap extends HashMap<String, Object> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private List<Base> myContext;
|
||||
private IBaseResource myResource;
|
||||
|
||||
MyContextMap(IBaseResource theResource) {
|
||||
myResource = theResource;
|
||||
myContext = null;
|
||||
}
|
||||
|
||||
MyContextMap(Base theContext, IBaseResource theResource) {
|
||||
myContext = Collections.singletonList(theContext);
|
||||
myResource=theResource;
|
||||
}
|
||||
|
||||
MyContextMap(List<Base> theContext, IBaseResource theResource) {
|
||||
myContext = (theContext);
|
||||
myResource=theResource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(Object theKey) {
|
||||
ourLog.info("Requesting key: {}", theKey);
|
||||
|
||||
if (theKey.equals("resource")) {
|
||||
return new MyContextMap((Base) myResource, myResource);
|
||||
}
|
||||
|
||||
IWorkerContext ctx = new HapiWorkerContext(myFhirContext, myValidationSupport);
|
||||
try {
|
||||
FluentPathEngine fluentPathEngine = new FluentPathEngine(ctx);
|
||||
List<Base> evaluated = new ArrayList<Base>();
|
||||
for (Base nextContext : myContext) {
|
||||
evaluated.addAll(fluentPathEngine.evaluate(nextContext, (String)theKey));
|
||||
}
|
||||
return new MyContextMap(evaluated, myResource);
|
||||
} catch (FHIRException e) {
|
||||
throw new InternalErrorException("Failed to process expression: " + theKey, e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package ca.uhn.fhir.narrative.template.filters;
|
||||
|
||||
class Append extends Filter {
|
||||
|
||||
/*
|
||||
* (Object) append(input, string)
|
||||
*
|
||||
* add one string to another
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object value, Object... params) {
|
||||
|
||||
return super.asString(value) + super.asString(super.get(0, params));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package ca.uhn.fhir.narrative.template.filters;
|
||||
|
||||
class Capitalize extends Filter {
|
||||
|
||||
/*
|
||||
* (Object) capitalize(input)
|
||||
*
|
||||
* capitalize words in the input sentence
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object value, Object... params) {
|
||||
|
||||
String original = super.asString(value);
|
||||
|
||||
if (original.isEmpty()) {
|
||||
return original;
|
||||
}
|
||||
|
||||
char first = original.charAt(0);
|
||||
|
||||
return Character.toUpperCase(first) + original.substring(1);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,255 @@
|
|||
package ca.uhn.fhir.narrative.template.filters;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
class Date extends Filter {
|
||||
|
||||
private static Locale locale = Locale.ENGLISH;
|
||||
private static Set<String> datePatterns = new HashSet<String>();
|
||||
|
||||
private final static java.util.Map<Character, SimpleDateFormat> LIQUID_TO_JAVA_FORMAT =
|
||||
new java.util.HashMap<Character, SimpleDateFormat>();
|
||||
|
||||
static {
|
||||
addDatePattern("yyyy-MM-dd HH:mm:ss");
|
||||
addDatePattern("EEE MMM ddhh:mm:ss yyyy");
|
||||
init();
|
||||
}
|
||||
|
||||
/*
|
||||
* (Object) date(input, format)
|
||||
*
|
||||
* Reformat a date
|
||||
*
|
||||
* %a - The abbreviated weekday name (``Sun'')
|
||||
* %A - The full weekday name (``Sunday'')
|
||||
* %b - The abbreviated month name (``Jan'')
|
||||
* %B - The full month name (``January'')
|
||||
* %c - The preferred local date and time representation
|
||||
* %d - Day of the month (01..31)
|
||||
* %H - Hour of the day, 24-hour clock (00..23)
|
||||
* %I - Hour of the day, 12-hour clock (01..12)
|
||||
* %j - Day of the year (001..366)
|
||||
* %m - Month of the year (01..12)
|
||||
* %M - Minute of the hour (00..59)
|
||||
* %p - Meridian indicator (``AM'' or ``PM'')
|
||||
* %S - Second of the minute (00..60)
|
||||
* %U - Week number of the current year,
|
||||
* starting with the first Sunday as the first
|
||||
* day of the first week (00..53)
|
||||
* %W - Week number of the current year,
|
||||
* starting with the first Monday as the first
|
||||
* day of the first week (00..53)
|
||||
* %w - Day of the week (Sunday is 0, 0..6)
|
||||
* %x - Preferred representation for the date alone, no time
|
||||
* %X - Preferred representation for the time alone, no date
|
||||
* %y - Year without a century (00..99)
|
||||
* %Y - Year with century
|
||||
* %Z - Time zone name
|
||||
* %% - Literal ``%'' character
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object value, Object... params) {
|
||||
|
||||
try {
|
||||
final Long seconds;
|
||||
|
||||
if(super.asString(value).equals("now")) {
|
||||
seconds = System.currentTimeMillis() / 1000L;
|
||||
}
|
||||
else if(super.isNumber(value)) {
|
||||
// No need to divide this by 1000, the param is expected to be in seconds already!
|
||||
seconds = super.asNumber(value).longValue();
|
||||
}
|
||||
else {
|
||||
seconds = trySeconds(super.asString(value)); // formatter.parse(super.asString(value)).getTime() / 1000L;
|
||||
|
||||
if(seconds == null) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
final java.util.Date date = new java.util.Date(seconds * 1000L);
|
||||
final String format = super.asString(super.get(0, params));
|
||||
|
||||
if(format == null || format.trim().isEmpty()) {
|
||||
return value;
|
||||
}
|
||||
|
||||
final java.util.Calendar calendar = java.util.Calendar.getInstance();
|
||||
calendar.setTime(date);
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < format.length(); i++) {
|
||||
|
||||
char ch = format.charAt(i);
|
||||
|
||||
if (ch == '%') {
|
||||
|
||||
i++;
|
||||
|
||||
if (i == format.length()) {
|
||||
// a trailing (single) '%' sign: just append it
|
||||
builder.append("%");
|
||||
break;
|
||||
}
|
||||
|
||||
char next = format.charAt(i);
|
||||
|
||||
SimpleDateFormat javaFormat = LIQUID_TO_JAVA_FORMAT.get(next);
|
||||
|
||||
if (javaFormat == null) {
|
||||
// no valid date-format: append the '%' and the 'next'-char
|
||||
builder.append("%").append(next);
|
||||
}
|
||||
else {
|
||||
builder.append(javaFormat.format(date));
|
||||
}
|
||||
}
|
||||
else {
|
||||
builder.append(ch);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
catch (Exception e) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private static void init() {
|
||||
|
||||
// %% - Literal ``%'' character
|
||||
LIQUID_TO_JAVA_FORMAT.put('%', new SimpleDateFormat("%", locale));
|
||||
|
||||
// %a - The abbreviated weekday name (``Sun'')
|
||||
LIQUID_TO_JAVA_FORMAT.put('a', new SimpleDateFormat("EEE", locale));
|
||||
|
||||
// %A - The full weekday name (``Sunday'')
|
||||
LIQUID_TO_JAVA_FORMAT.put('A', new SimpleDateFormat("EEEE", locale));
|
||||
|
||||
// %b - The abbreviated month name (``Jan'')
|
||||
LIQUID_TO_JAVA_FORMAT.put('b', new SimpleDateFormat("MMM", locale));
|
||||
LIQUID_TO_JAVA_FORMAT.put('h', new SimpleDateFormat("MMM", locale));
|
||||
|
||||
// %B - The full month name (``January'')
|
||||
LIQUID_TO_JAVA_FORMAT.put('B', new SimpleDateFormat("MMMM", locale));
|
||||
|
||||
// %c - The preferred local date and time representation
|
||||
LIQUID_TO_JAVA_FORMAT.put('c', new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy", locale));
|
||||
|
||||
// %d - Day of the month (01..31)
|
||||
LIQUID_TO_JAVA_FORMAT.put('d', new SimpleDateFormat("dd", locale));
|
||||
|
||||
// %H - Hour of the day, 24-hour clock (00..23)
|
||||
LIQUID_TO_JAVA_FORMAT.put('H', new SimpleDateFormat("HH", locale));
|
||||
|
||||
// %I - Hour of the day, 12-hour clock (01..12)
|
||||
LIQUID_TO_JAVA_FORMAT.put('I', new SimpleDateFormat("hh", locale));
|
||||
|
||||
// %j - Day of the year (001..366)
|
||||
LIQUID_TO_JAVA_FORMAT.put('j', new SimpleDateFormat("DDD", locale));
|
||||
|
||||
// %m - Month of the year (01..12)
|
||||
LIQUID_TO_JAVA_FORMAT.put('m', new SimpleDateFormat("MM", locale));
|
||||
|
||||
// %M - Minute of the hour (00..59)
|
||||
LIQUID_TO_JAVA_FORMAT.put('M', new SimpleDateFormat("mm", locale));
|
||||
|
||||
// %p - Meridian indicator (``AM'' or ``PM'')
|
||||
LIQUID_TO_JAVA_FORMAT.put('p', new SimpleDateFormat("a", locale));
|
||||
|
||||
// %S - Second of the minute (00..60)
|
||||
LIQUID_TO_JAVA_FORMAT.put('S', new SimpleDateFormat("ss", locale));
|
||||
|
||||
// %U - Week number of the current year,
|
||||
// starting with the first Sunday as the first
|
||||
// day of the first week (00..53)
|
||||
LIQUID_TO_JAVA_FORMAT.put('U', new SimpleDateFormat("ww", locale));
|
||||
|
||||
// %W - Week number of the current year,
|
||||
// starting with the first Monday as the first
|
||||
// day of the first week (00..53)
|
||||
LIQUID_TO_JAVA_FORMAT.put('W', new SimpleDateFormat("ww", locale));
|
||||
|
||||
// %w - Day of the week (Sunday is 0, 0..6)
|
||||
LIQUID_TO_JAVA_FORMAT.put('w', new SimpleDateFormat("F", locale));
|
||||
|
||||
// %x - Preferred representation for the date alone, no time
|
||||
LIQUID_TO_JAVA_FORMAT.put('x', new SimpleDateFormat("MM/dd/yy", locale));
|
||||
|
||||
// %X - Preferred representation for the time alone, no date
|
||||
LIQUID_TO_JAVA_FORMAT.put('X', new SimpleDateFormat("HH:mm:ss", locale));
|
||||
|
||||
// %y - Year without a century (00..99)
|
||||
LIQUID_TO_JAVA_FORMAT.put('y', new SimpleDateFormat("yy", locale));
|
||||
|
||||
// %Y - Year with century
|
||||
LIQUID_TO_JAVA_FORMAT.put('Y', new SimpleDateFormat("yyyy", locale));
|
||||
|
||||
// %Z - Time zone name
|
||||
LIQUID_TO_JAVA_FORMAT.put('Z', new SimpleDateFormat("z", locale));
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the locale.
|
||||
*
|
||||
* @param locale the new locale.
|
||||
*/
|
||||
public static void setLocale(Locale locale) {
|
||||
Date.locale = locale;
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new Date-pattern to be used when parsing a string to a Date.
|
||||
*
|
||||
* @param pattern the pattern.
|
||||
*/
|
||||
public static void addDatePattern(String pattern) {
|
||||
|
||||
if(pattern == null) {
|
||||
throw new NullPointerException("date-pattern cannot be null");
|
||||
}
|
||||
|
||||
datePatterns.add(pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removed a Date-pattern to be used when parsing a string to a Date.
|
||||
*
|
||||
* @param pattern the pattern.
|
||||
*/
|
||||
public static void removeDatePattern(String pattern) {
|
||||
|
||||
datePatterns.remove(pattern);
|
||||
}
|
||||
|
||||
/*
|
||||
* Try to parse `str` into a Date and return this Date as seconds
|
||||
* since EPOCH, or null if it could not be parsed.
|
||||
*/
|
||||
private Long trySeconds(String str) {
|
||||
|
||||
for(String pattern : datePatterns) {
|
||||
|
||||
SimpleDateFormat parser = new SimpleDateFormat(pattern, locale);
|
||||
|
||||
try {
|
||||
long milliseconds = parser.parse(str).getTime();
|
||||
return milliseconds / 1000L;
|
||||
}
|
||||
catch(Exception e) {
|
||||
// Just ignore and try the next pattern in `datePatterns`.
|
||||
}
|
||||
}
|
||||
|
||||
// Could not parse the string into a meaningful date, return null.
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package ca.uhn.fhir.narrative.template.filters;
|
||||
|
||||
class Divided_By extends Filter {
|
||||
|
||||
/*
|
||||
* divided_by(input, operand)
|
||||
*
|
||||
* division
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object value, Object... params) {
|
||||
|
||||
if(value == null) {
|
||||
value = 0L;
|
||||
}
|
||||
|
||||
super.checkParams(params, 1);
|
||||
|
||||
Object rhsObj = params[0];
|
||||
|
||||
if(super.asNumber(rhsObj).doubleValue() == 0.0) {
|
||||
throw new RuntimeException("Liquid error: divided by 0");
|
||||
}
|
||||
|
||||
if (super.isInteger(value) && super.isInteger(rhsObj)) {
|
||||
return super.asNumber(value).longValue() / super.asNumber(rhsObj).longValue();
|
||||
}
|
||||
|
||||
return super.asNumber(value).doubleValue() / super.asNumber(rhsObj).doubleValue();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package ca.uhn.fhir.narrative.template.filters;
|
||||
|
||||
class Downcase extends Filter {
|
||||
|
||||
/*
|
||||
* downcase(input)
|
||||
*
|
||||
* convert a input string to DOWNCASE
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object value, Object... params) {
|
||||
|
||||
return super.asString(value).toLowerCase();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package ca.uhn.fhir.narrative.template.filters;
|
||||
|
||||
class Escape extends Filter {
|
||||
|
||||
/*
|
||||
* escape(input)
|
||||
*
|
||||
* escape a string
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object value, Object... params) {
|
||||
|
||||
String str = super.asString(value);
|
||||
|
||||
return str.replace("&", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace("\"", """);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package ca.uhn.fhir.narrative.template.filters;
|
||||
|
||||
class Escape_Once extends Filter {
|
||||
|
||||
/*
|
||||
* escape_once(input)
|
||||
*
|
||||
* returns an escaped version of html without affecting
|
||||
* existing escaped entities
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object value, Object... params) {
|
||||
|
||||
String str = super.asString(value);
|
||||
|
||||
return str.replaceAll("&(?!([a-zA-Z]+|#[0-9]+|#x[0-9A-Fa-f]+);)", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace("\"", """);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
package ca.uhn.fhir.narrative.template.filters;
|
||||
|
||||
import ca.uhn.fhir.narrative.template.LValue;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Output markup takes filters. Filters are simple methods. The first
|
||||
* parameter is always the output of the left side of the filter. The
|
||||
* return value of the filter will be the new left value when the next
|
||||
* filter is run. When there are no more filters, the template will
|
||||
* receive the resulting string.
|
||||
* <p/>
|
||||
* -- https://github.com/Shopify/liquid/wiki/Liquid-for-Designers
|
||||
*/
|
||||
public abstract class Filter extends LValue {
|
||||
|
||||
/**
|
||||
* A map holding all filters.
|
||||
*/
|
||||
private static final Map<String, Filter> FILTERS = new HashMap<String, Filter>();
|
||||
|
||||
static {
|
||||
// Initialize all standard filters.
|
||||
registerFilter(new Append());
|
||||
registerFilter(new Capitalize());
|
||||
registerFilter(new Date());
|
||||
registerFilter(new Divided_By());
|
||||
registerFilter(new Downcase());
|
||||
registerFilter(new Escape());
|
||||
registerFilter(new Escape_Once());
|
||||
registerFilter(new First());
|
||||
registerFilter(new H());
|
||||
registerFilter(new Join());
|
||||
registerFilter(new Last());
|
||||
registerFilter(new ca.uhn.fhir.narrative.template.filters.Map());
|
||||
registerFilter(new Minus());
|
||||
registerFilter(new Modulo());
|
||||
registerFilter(new Newline_To_Br());
|
||||
registerFilter(new Plus());
|
||||
registerFilter(new Prepend());
|
||||
registerFilter(new Remove());
|
||||
registerFilter(new Remove_First());
|
||||
registerFilter(new Replace());
|
||||
registerFilter(new Replace_First());
|
||||
registerFilter(new Size());
|
||||
registerFilter(new Sort());
|
||||
registerFilter(new Split());
|
||||
registerFilter(new Strip_HTML());
|
||||
registerFilter(new Strip_Newlines());
|
||||
registerFilter(new Times());
|
||||
registerFilter(new Truncate());
|
||||
registerFilter(new Truncatewords());
|
||||
registerFilter(new Upcase());
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the filter.
|
||||
*/
|
||||
public final String name;
|
||||
|
||||
/**
|
||||
* Used for all package protected filters in the liqp.filters-package
|
||||
* whose name is their class name lower cased.
|
||||
*/
|
||||
protected Filter() {
|
||||
this.name = this.getClass().getSimpleName().toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of a Filter.
|
||||
*
|
||||
* @param name
|
||||
* the name of the filter.
|
||||
*/
|
||||
public Filter(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the filter on the 'value'.
|
||||
*
|
||||
* @param value
|
||||
* the string value `AAA` in: `{{ 'AAA' | f:1,2,3 }}`
|
||||
* @param params
|
||||
* the values [1, 2, 3] in: `{{ 'AAA' | f:1,2,3 }}`
|
||||
*
|
||||
* @return the result of the filter.
|
||||
*/
|
||||
public abstract Object apply(Object value, Object... params);
|
||||
|
||||
/**
|
||||
* Check the number of parameters and throws an exception if needed.
|
||||
*
|
||||
* @param params
|
||||
* the parameters to check.
|
||||
* @param expected
|
||||
* the expected number of parameters.
|
||||
*/
|
||||
public final void checkParams(Object[] params, int expected) {
|
||||
if(params == null || params.length != expected) {
|
||||
throw new RuntimeException("Liquid error: wrong number of arguments (" +
|
||||
(params == null ? 0 : params.length + 1) + " for " + (expected + 1) + ")");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a value at a specific index from an array of parameters.
|
||||
* If no such index exists, a RuntimeException is thrown.
|
||||
*
|
||||
* @param index
|
||||
* the index of the value to be retrieved.
|
||||
* @param params
|
||||
* the values.
|
||||
*
|
||||
* @return a value at a specific index from an array of
|
||||
* parameters.
|
||||
*/
|
||||
protected Object get(int index, Object... params) {
|
||||
|
||||
if (index >= params.length) {
|
||||
throw new RuntimeException("error in filter '" + name +
|
||||
"': cannot get param index: " + index +
|
||||
" from: " + Arrays.toString(params));
|
||||
}
|
||||
|
||||
return params[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a filter with a specific name.
|
||||
*
|
||||
* @param name
|
||||
* the name of the filter to retrieve.
|
||||
*
|
||||
* @return a filter with a specific name.
|
||||
*/
|
||||
public static Filter getFilter(String name) {
|
||||
|
||||
Filter filter = FILTERS.get(name);
|
||||
|
||||
if (filter == null) {
|
||||
throw new RuntimeException("unknown filter: " + name);
|
||||
}
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all default filters.
|
||||
*
|
||||
* @return all default filters.
|
||||
*/
|
||||
public static Map<String, Filter> getFilters() {
|
||||
return new HashMap<String, Filter>(FILTERS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new filter.
|
||||
*
|
||||
* @param filter
|
||||
* the filter to be registered.
|
||||
*/
|
||||
public static void registerFilter(Filter filter) {
|
||||
FILTERS.put(filter.name, filter);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package ca.uhn.fhir.narrative.template.filters;
|
||||
|
||||
class First extends Filter {
|
||||
|
||||
/*
|
||||
* first(array)
|
||||
*
|
||||
* Get the first element of the passed in array
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object value, Object... params) {
|
||||
|
||||
Object[] array = super.asArray(value);
|
||||
|
||||
return array.length == 0 ? null : super.asString(array[0]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package ca.uhn.fhir.narrative.template.filters;
|
||||
|
||||
class H extends Filter {
|
||||
|
||||
/*
|
||||
* h(input)
|
||||
*
|
||||
* Alias for: escape
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object value, Object... params) {
|
||||
|
||||
return Filter.getFilter("escape").apply(value, params);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package ca.uhn.fhir.narrative.template.filters;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
class Join extends Filter {
|
||||
|
||||
/*
|
||||
* join(input, glue = ' ')
|
||||
*
|
||||
* Join elements of the array with certain character between them
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object value, Object... params) {
|
||||
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
Object[] array = super.asArray(value);
|
||||
String glue = params.length == 0 ? " " : super.asString(super.get(0, params));
|
||||
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
|
||||
builder.append(super.asString(array[i]));
|
||||
|
||||
if (i < array.length - 1) {
|
||||
builder.append(glue);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package ca.uhn.fhir.narrative.template.filters;
|
||||
|
||||
class Last extends Filter {
|
||||
|
||||
/*
|
||||
* last(array)
|
||||
*
|
||||
* Get the last element of the passed in array
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object value, Object... params) {
|
||||
|
||||
Object[] array = super.asArray(value);
|
||||
|
||||
return array.length == 0 ? null : super.asString(array[array.length - 1]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package ca.uhn.fhir.narrative.template.filters;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class Map extends Filter {
|
||||
|
||||
/*
|
||||
* map(input, property)
|
||||
*
|
||||
* map/collect on a given property
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object value, Object... params) {
|
||||
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
List<Object> list = new ArrayList<Object>();
|
||||
|
||||
Object[] array = super.asArray(value);
|
||||
|
||||
String key = super.asString(super.get(0, params));
|
||||
|
||||
for (Object obj : array) {
|
||||
|
||||
java.util.Map map = (java.util.Map) obj;
|
||||
|
||||
Object val = map.get(key);
|
||||
|
||||
if (val != null) {
|
||||
list.add(val);
|
||||
}
|
||||
}
|
||||
|
||||
return list.toArray(new Object[list.size()]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package ca.uhn.fhir.narrative.template.filters;
|
||||
|
||||
class Minus extends Filter {
|
||||
|
||||
/*
|
||||
* plus(input, operand)
|
||||
*
|
||||
* subtraction
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object value, Object... params) {
|
||||
|
||||
if(value == null) {
|
||||
value = 0L;
|
||||
}
|
||||
|
||||
super.checkParams(params, 1);
|
||||
|
||||
Object rhsObj = params[0];
|
||||
|
||||
if (super.isInteger(value) && super.isInteger(rhsObj)) {
|
||||
return super.asNumber(value).longValue() - super.asNumber(rhsObj).longValue();
|
||||
}
|
||||
|
||||
return super.asNumber(value).doubleValue() - super.asNumber(rhsObj).doubleValue();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package ca.uhn.fhir.narrative.template.filters;
|
||||
|
||||
class Modulo extends Filter {
|
||||
|
||||
/*
|
||||
* plus(input, operand)
|
||||
*
|
||||
* modulus
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object value, Object... params) {
|
||||
|
||||
if(value == null) {
|
||||
value = 0L;
|
||||
}
|
||||
|
||||
super.checkParams(params, 1);
|
||||
|
||||
Object rhsObj = params[0];
|
||||
|
||||
if (super.isInteger(value) && super.isInteger(rhsObj)) {
|
||||
return super.asNumber(value).longValue() % super.asNumber(rhsObj).longValue();
|
||||
}
|
||||
|
||||
return super.asNumber(value).doubleValue() % super.asNumber(rhsObj).doubleValue();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package ca.uhn.fhir.narrative.template.filters;
|
||||
|
||||
class Newline_To_Br extends Filter {
|
||||
|
||||
/*
|
||||
* newline_to_br(input)
|
||||
*
|
||||
* Add <br /> tags in front of all newlines in input string
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object value, Object... params) {
|
||||
|
||||
return super.asString(value).replaceAll("[\n]", "<br />\n");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package ca.uhn.fhir.narrative.template.filters;
|
||||
|
||||
class Plus extends Filter {
|
||||
|
||||
/*
|
||||
* plus(input, operand)
|
||||
*
|
||||
* addition
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object value, Object... params) {
|
||||
|
||||
if(value == null) {
|
||||
value = 0L;
|
||||
}
|
||||
|
||||
super.checkParams(params, 1);
|
||||
|
||||
Object rhsObj = params[0];
|
||||
|
||||
if (super.isInteger(value) && super.isInteger(rhsObj)) {
|
||||
return super.asNumber(value).longValue() + super.asNumber(rhsObj).longValue();
|
||||
}
|
||||
|
||||
return super.asNumber(value).doubleValue() + super.asNumber(rhsObj).doubleValue();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package ca.uhn.fhir.narrative.template.filters;
|
||||
|
||||
class Prepend extends Filter {
|
||||
|
||||
/*
|
||||
* (Object) append(input, string)
|
||||
*
|
||||
* add one string to another
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object value, Object... params) {
|
||||
|
||||
return super.asString(super.get(0, params)) + super.asString(value);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package ca.uhn.fhir.narrative.template.filters;
|
||||
|
||||
class Remove extends Filter {
|
||||
|
||||
/*
|
||||
* remove(input, string)
|
||||
*
|
||||
* remove a substring
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object value, Object... params) {
|
||||
|
||||
String original = super.asString(value);
|
||||
|
||||
Object needle = super.get(0, params);
|
||||
|
||||
if (needle == null) {
|
||||
throw new RuntimeException("invalid pattern: " + needle);
|
||||
}
|
||||
|
||||
return original.replace(String.valueOf(needle), "");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package ca.uhn.fhir.narrative.template.filters;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
class Remove_First extends Filter {
|
||||
|
||||
/*
|
||||
* remove_first(input, string)
|
||||
*
|
||||
* remove the first occurrences of a substring
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object value, Object... params) {
|
||||
|
||||
String original = super.asString(value);
|
||||
|
||||
Object needle = super.get(0, params);
|
||||
|
||||
if (needle == null) {
|
||||
throw new RuntimeException("invalid pattern: " + needle);
|
||||
}
|
||||
|
||||
return original.replaceFirst(Pattern.quote(String.valueOf(needle)), "");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package ca.uhn.fhir.narrative.template.filters;
|
||||
|
||||
class Replace extends Filter {
|
||||
|
||||
/*
|
||||
* replace(input, string, replacement = '')
|
||||
*
|
||||
* Replace occurrences of a string with another
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object value, Object... params) {
|
||||
|
||||
String original = super.asString(value);
|
||||
|
||||
Object needle = super.get(0, params);
|
||||
String replacement = "";
|
||||
|
||||
if (needle == null) {
|
||||
throw new RuntimeException("invalid pattern: " + needle);
|
||||
}
|
||||
|
||||
if (params.length >= 2) {
|
||||
|
||||
Object obj = super.get(1, params);
|
||||
|
||||
if (obj == null) {
|
||||
throw new RuntimeException("invalid replacement: " + needle);
|
||||
}
|
||||
|
||||
replacement = super.asString(super.get(1, params));
|
||||
}
|
||||
|
||||
return original.replace(String.valueOf(needle), String.valueOf(replacement));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package ca.uhn.fhir.narrative.template.filters;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
class Replace_First extends Filter {
|
||||
|
||||
/*
|
||||
* replace_first(input, string, replacement = '')
|
||||
*
|
||||
* Replace the first occurrences of a string with another
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object value, Object... params) {
|
||||
|
||||
String original = super.asString(value);
|
||||
|
||||
Object needle = super.get(0, params);
|
||||
String replacement = "";
|
||||
|
||||
if (needle == null) {
|
||||
throw new RuntimeException("invalid pattern: " + needle);
|
||||
}
|
||||
|
||||
if (params.length >= 2) {
|
||||
|
||||
Object obj = super.get(1, params);
|
||||
|
||||
if (obj == null) {
|
||||
throw new RuntimeException("invalid replacement: " + needle);
|
||||
}
|
||||
|
||||
replacement = super.asString(super.get(1, params));
|
||||
}
|
||||
|
||||
return original.replaceFirst(Pattern.quote(String.valueOf(needle)),
|
||||
Matcher.quoteReplacement(replacement));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package ca.uhn.fhir.narrative.template.filters;
|
||||
|
||||
class Size extends Filter {
|
||||
|
||||
/*
|
||||
* size(input)
|
||||
*
|
||||
* Return the size of an array or of an string
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object value, Object... params) {
|
||||
|
||||
if (super.isArray(value)) {
|
||||
return super.asArray(value).length;
|
||||
}
|
||||
|
||||
if (super.isString(value)) {
|
||||
return super.asString(value).length();
|
||||
}
|
||||
|
||||
if (super.isNumber(value)) {
|
||||
// we're only using 64 bit longs, no BigIntegers or the like.
|
||||
// So just return 8 (the number of bytes in a long).
|
||||
return 8;
|
||||
}
|
||||
|
||||
// boolean or nil
|
||||
return 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
package ca.uhn.fhir.narrative.template.filters;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Map;
|
||||
|
||||
class Sort extends Filter {
|
||||
|
||||
/*
|
||||
* sort(input, property = nil)
|
||||
*
|
||||
* Sort elements of the array provide optional property with
|
||||
* which to sort an array of hashes or drops
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object value, Object... params) {
|
||||
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if(!super.isArray(value)) {
|
||||
throw new RuntimeException("cannot sort: " + value);
|
||||
}
|
||||
|
||||
Object[] array = super.asArray(value);
|
||||
String property = params.length == 0 ? null : super.asString(params[0]);
|
||||
|
||||
List<Comparable> list = asComparableList(array, property);
|
||||
|
||||
Collections.sort(list);
|
||||
|
||||
return property == null ?
|
||||
list.toArray(new Comparable[list.size()]) :
|
||||
list.toArray(new SortableMap[list.size()]);
|
||||
}
|
||||
|
||||
private List<Comparable> asComparableList(Object[] array, String property) {
|
||||
|
||||
List<Comparable> list = new ArrayList<Comparable>();
|
||||
|
||||
for (Object obj : array) {
|
||||
|
||||
if(obj instanceof java.util.Map && property != null) {
|
||||
list.add(new SortableMap((java.util.Map<String, Comparable>)obj, property));
|
||||
}
|
||||
else {
|
||||
list.add((Comparable) obj);
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
static class SortableMap extends HashMap<String, Comparable> implements Comparable<SortableMap> {
|
||||
|
||||
final String property;
|
||||
|
||||
SortableMap(java.util.Map<String, Comparable> map, String property) {
|
||||
super.putAll(map);
|
||||
this.property = property;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(SortableMap that) {
|
||||
|
||||
Comparable thisValue = this.get(property);
|
||||
Comparable thatValue = that.get(property);
|
||||
|
||||
if(thisValue == null || thatValue == null) {
|
||||
throw new RuntimeException("Liquid error: comparison of Hash with Hash failed");
|
||||
}
|
||||
|
||||
return thisValue.compareTo(thatValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for(java.util.Map.Entry entry : super.entrySet()) {
|
||||
builder.append(entry.getKey()).append(entry.getValue());
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package ca.uhn.fhir.narrative.template.filters;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
class Split extends Filter {
|
||||
|
||||
/*
|
||||
* split(input, delimiter = ' ')
|
||||
*
|
||||
* Split a string on a matching pattern
|
||||
*
|
||||
* E.g. {{ "a~b" | split:'~' | first }} #=> 'a'
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object value, Object... params) {
|
||||
|
||||
String original = super.asString(value);
|
||||
|
||||
String delimiter = super.asString(super.get(0, params));
|
||||
|
||||
return original.split("(?<!^)" + Pattern.quote(delimiter));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package ca.uhn.fhir.narrative.template.filters;
|
||||
|
||||
class Strip_HTML extends Filter {
|
||||
|
||||
/*
|
||||
* strip_html(input)
|
||||
*
|
||||
* Remove all HTML tags from the string
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object value, Object... params) {
|
||||
throw new UnsupportedOperationException();
|
||||
//
|
||||
// String html = super.asString(value);
|
||||
//
|
||||
// return Jsoup.parse(html).text();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package ca.uhn.fhir.narrative.template.filters;
|
||||
|
||||
class Strip_Newlines extends Filter {
|
||||
|
||||
/*
|
||||
* strip_newlines(input) click to toggle source
|
||||
*
|
||||
* Remove all newlines from the string
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object value, Object... params) {
|
||||
|
||||
return super.asString(value).replaceAll("[\r\n]++", "");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package ca.uhn.fhir.narrative.template.filters;
|
||||
|
||||
class Times extends Filter {
|
||||
|
||||
/*
|
||||
* times(input, operand)
|
||||
*
|
||||
* multiplication
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object value, Object... params) {
|
||||
|
||||
if(value == null) {
|
||||
value = 0L;
|
||||
}
|
||||
|
||||
super.checkParams(params, 1);
|
||||
|
||||
Object rhsObj = params[0];
|
||||
|
||||
if (super.isInteger(value) && super.isInteger(rhsObj)) {
|
||||
return super.asNumber(value).longValue() * super.asNumber(rhsObj).longValue();
|
||||
}
|
||||
|
||||
return super.asNumber(value).doubleValue() * super.asNumber(rhsObj).doubleValue();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package ca.uhn.fhir.narrative.template.filters;
|
||||
|
||||
class Truncate extends Filter {
|
||||
|
||||
/*
|
||||
* truncate(input, length = 50, truncate_string = "...")
|
||||
*
|
||||
* Truncate a string down to x characters
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object value, Object... params) {
|
||||
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String text = super.asString(value);
|
||||
int length = 50;
|
||||
String truncateString = "...";
|
||||
|
||||
if (params.length >= 1) {
|
||||
length = super.asNumber(super.get(0, params)).intValue();
|
||||
}
|
||||
|
||||
if (params.length >= 2) {
|
||||
truncateString = super.asString(super.get(1, params));
|
||||
}
|
||||
|
||||
if (truncateString.length() >= length) {
|
||||
return truncateString;
|
||||
}
|
||||
|
||||
if (length == text.length()) {
|
||||
return text;
|
||||
}
|
||||
|
||||
if (length >= (text.length() + truncateString.length())) {
|
||||
return text;
|
||||
}
|
||||
|
||||
int remainingChars = length - truncateString.length();
|
||||
|
||||
return text.substring(0, remainingChars) + truncateString;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package ca.uhn.fhir.narrative.template.filters;
|
||||
|
||||
class Truncatewords extends Filter {
|
||||
|
||||
/*
|
||||
* truncatewords(input, words = 15, truncate_string = "...")
|
||||
*
|
||||
* Truncate a string down to x words
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object value, Object... params) {
|
||||
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String text = super.asString(value);
|
||||
String[] words = text.split("\\s++");
|
||||
int length = 15;
|
||||
String truncateString = "...";
|
||||
|
||||
if (params.length >= 1) {
|
||||
length = super.asNumber(super.get(0, params)).intValue();
|
||||
}
|
||||
|
||||
if (params.length >= 2) {
|
||||
truncateString = super.asString(super.get(1, params));
|
||||
}
|
||||
|
||||
if (length >= words.length) {
|
||||
return text;
|
||||
}
|
||||
|
||||
return join(words, length) + truncateString;
|
||||
}
|
||||
|
||||
private String join(String[] words, int length) {
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
builder.append(words[i]).append(" ");
|
||||
}
|
||||
|
||||
return builder.toString().trim();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package ca.uhn.fhir.narrative.template.filters;
|
||||
|
||||
class Upcase extends Filter {
|
||||
|
||||
/*
|
||||
* upcase(input)
|
||||
*
|
||||
* convert a input string to UPCASE
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object value, Object... params) {
|
||||
|
||||
return super.asString(value).toUpperCase();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package ca.uhn.fhir.narrative.template.nodes;
|
||||
|
||||
import ca.uhn.fhir.narrative.template.LValue;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
class AndNode extends LValue implements LNode {
|
||||
|
||||
private LNode lhs;
|
||||
private LNode rhs;
|
||||
|
||||
public AndNode(LNode lhs, LNode rhs) {
|
||||
this.lhs = lhs;
|
||||
this.rhs = rhs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object render(Map<String, Object> context) {
|
||||
|
||||
Object a = lhs.render(context);
|
||||
Object b = rhs.render(context);
|
||||
|
||||
return super.asBoolean(a) && super.asBoolean(b);
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package ca.uhn.fhir.narrative.template.nodes;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class AtomNode implements LNode {
|
||||
|
||||
public static final AtomNode EMPTY = new AtomNode(new Object());
|
||||
|
||||
private Object value;
|
||||
|
||||
public AtomNode(Object value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static boolean isEmpty(Object o) {
|
||||
return o == EMPTY.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object render(Map<String, Object> context) {
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package ca.uhn.fhir.narrative.template.nodes;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
class AttributeNode implements LNode {
|
||||
|
||||
private LNode key;
|
||||
private LNode value;
|
||||
|
||||
public AttributeNode(LNode key, LNode value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object render(Map<String, Object> context) {
|
||||
|
||||
return new Object[]{
|
||||
key.render(context),
|
||||
value.render(context)
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package ca.uhn.fhir.narrative.template.nodes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import ca.uhn.fhir.narrative.template.tags.Tag;
|
||||
|
||||
public class BlockNode implements LNode {
|
||||
|
||||
private List<LNode> children;
|
||||
|
||||
public BlockNode() {
|
||||
children = new ArrayList<LNode>();
|
||||
}
|
||||
|
||||
public void add(LNode node) {
|
||||
children.add(node);
|
||||
}
|
||||
|
||||
public List<LNode> getChildren() {
|
||||
return new ArrayList<LNode>(children);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object render(Map<String, Object> context) {
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (LNode node : children) {
|
||||
|
||||
Object value = node.render(context);
|
||||
|
||||
if(value == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(value == Tag.Statement.BREAK || value == Tag.Statement.CONTINUE) {
|
||||
return value;
|
||||
}
|
||||
|
||||
else if (value.getClass().isArray()) {
|
||||
|
||||
Object[] array = (Object[]) value;
|
||||
|
||||
for (Object obj : array) {
|
||||
builder.append(String.valueOf(obj));
|
||||
}
|
||||
}
|
||||
else {
|
||||
builder.append(String.valueOf(value));
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package ca.uhn.fhir.narrative.template.nodes;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import ca.uhn.fhir.narrative.template.LValue;
|
||||
|
||||
class ContainsNode extends LValue implements LNode {
|
||||
|
||||
private LNode lhs;
|
||||
private LNode rhs;
|
||||
|
||||
public ContainsNode(LNode lhs, LNode rhs) {
|
||||
this.lhs = lhs;
|
||||
this.rhs = rhs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object render(Map<String, Object> context) {
|
||||
|
||||
Object collection = lhs.render(context);
|
||||
Object needle = rhs.render(context);
|
||||
|
||||
if(super.isArray(collection)) {
|
||||
Object[] array = super.asArray(collection);
|
||||
return Arrays.asList(array).contains(needle);
|
||||
}
|
||||
|
||||
if(super.isString(collection)) {
|
||||
return super.asString(collection).contains(super.asString(needle));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package ca.uhn.fhir.narrative.template.nodes;
|
||||
|
||||
import ca.uhn.fhir.narrative.template.LValue;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
class EqNode implements LNode {
|
||||
|
||||
private LNode lhs;
|
||||
private LNode rhs;
|
||||
|
||||
public EqNode(LNode lhs, LNode rhs) {
|
||||
this.lhs = lhs;
|
||||
this.rhs = rhs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object render(Map<String, Object> context) {
|
||||
|
||||
Object a = lhs.render(context);
|
||||
Object b = rhs.render(context);
|
||||
|
||||
return LValue.areEqual(a, b);
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package ca.uhn.fhir.narrative.template.nodes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import ca.uhn.fhir.narrative.template.filters.Filter;
|
||||
|
||||
public class FilterNode implements LNode {
|
||||
|
||||
private Filter filter;
|
||||
private List<LNode> params;
|
||||
|
||||
public FilterNode(String filterName, Filter filter) {
|
||||
if (filter == null) {
|
||||
throw new IllegalArgumentException("no filter available named: " + filterName);
|
||||
}
|
||||
this.filter = filter;
|
||||
this.params = new ArrayList<LNode>();
|
||||
}
|
||||
|
||||
public void add(LNode param) {
|
||||
params.add(param);
|
||||
}
|
||||
|
||||
public Object apply(Object value, Map<String, Object> variables) {
|
||||
|
||||
List<Object> paramValues = new ArrayList<Object>();
|
||||
|
||||
for (LNode node : params) {
|
||||
paramValues.add(node.render(variables));
|
||||
}
|
||||
|
||||
return filter.apply(value, paramValues.toArray(new Object[paramValues.size()]));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object render(Map<String, Object> context) {
|
||||
throw new RuntimeException("cannot render a filter");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package ca.uhn.fhir.narrative.template.nodes;
|
||||
|
||||
import ca.uhn.fhir.narrative.template.LValue;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
class GtEqNode extends LValue implements LNode {
|
||||
|
||||
private LNode lhs;
|
||||
private LNode rhs;
|
||||
|
||||
public GtEqNode(LNode lhs, LNode rhs) {
|
||||
this.lhs = lhs;
|
||||
this.rhs = rhs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object render(Map<String, Object> context) {
|
||||
|
||||
Object a = lhs.render(context);
|
||||
Object b = rhs.render(context);
|
||||
|
||||
return (a instanceof Number) && (b instanceof Number) &&
|
||||
super.asNumber(a).doubleValue() >= super.asNumber(b).doubleValue();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package ca.uhn.fhir.narrative.template.nodes;
|
||||
|
||||
import ca.uhn.fhir.narrative.template.LValue;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
class GtNode extends LValue implements LNode {
|
||||
|
||||
private LNode lhs;
|
||||
private LNode rhs;
|
||||
|
||||
public GtNode(LNode lhs, LNode rhs) {
|
||||
this.lhs = lhs;
|
||||
this.rhs = rhs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object render(Map<String, Object> context) {
|
||||
|
||||
Object a = lhs.render(context);
|
||||
Object b = rhs.render(context);
|
||||
|
||||
return (a instanceof Number) && (b instanceof Number) &&
|
||||
super.asNumber(a).doubleValue() > super.asNumber(b).doubleValue();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package ca.uhn.fhir.narrative.template.nodes;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Denotes a node in the AST the parse creates from the
|
||||
* input source.
|
||||
*/
|
||||
public interface LNode {
|
||||
|
||||
/**
|
||||
* Renders this AST.
|
||||
*
|
||||
* @param context
|
||||
* the context (variables) with which this
|
||||
* node should be rendered.
|
||||
*
|
||||
* @return an Object denoting the rendered AST.
|
||||
*/
|
||||
Object render(Map<String, Object> context);
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package ca.uhn.fhir.narrative.template.nodes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hl7.fhir.dstu3.exceptions.FHIRException;
|
||||
import org.hl7.fhir.dstu3.model.Base;
|
||||
import org.hl7.fhir.dstu3.model.BooleanType;
|
||||
import org.hl7.fhir.dstu3.utils.FluentPathEngine;
|
||||
import org.hl7.fhir.instance.model.api.IBaseBooleanDatatype;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.util.FhirTerser;
|
||||
|
||||
class LookupNode implements LNode {
|
||||
|
||||
private final String id;
|
||||
|
||||
public LookupNode(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LookupNode.class);
|
||||
|
||||
@Override
|
||||
public Object render(Map<String, Object> context) {
|
||||
|
||||
ourLog.info("Processing path: {}", id);
|
||||
|
||||
FhirTerser terser = (FhirTerser) context.get("terser");
|
||||
FluentPathEngine fpEngine = (FluentPathEngine) context.get("fpEngine");
|
||||
IBaseResource resContext = (IBaseResource) context.get("context");
|
||||
|
||||
List<Base> retVal;
|
||||
try {
|
||||
retVal = fpEngine.evaluate((Base)resContext, id);
|
||||
} catch (FHIRException e) {
|
||||
throw new InternalErrorException(e);
|
||||
}
|
||||
// terser.getValues(resContext, id);
|
||||
|
||||
ourLog.info("Evaluated to: {}", retVal);
|
||||
|
||||
if (retVal.size() == 1) {
|
||||
if (IBaseBooleanDatatype.class.isAssignableFrom(retVal.get(0).getClass())) {
|
||||
IBaseBooleanDatatype bool = (IBaseBooleanDatatype) retVal.get(0);
|
||||
Boolean newRetVal = bool.getValue();
|
||||
ourLog.info("Returning: {}", newRetVal);
|
||||
return newRetVal;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
interface Indexable {
|
||||
Object get(Object value, Map<String, Object> context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
builder.append(id);
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package ca.uhn.fhir.narrative.template.nodes;
|
||||
|
||||
import ca.uhn.fhir.narrative.template.LValue;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
class LtEqNode extends LValue implements LNode {
|
||||
|
||||
private LNode lhs;
|
||||
private LNode rhs;
|
||||
|
||||
public LtEqNode(LNode lhs, LNode rhs) {
|
||||
this.lhs = lhs;
|
||||
this.rhs = rhs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object render(Map<String, Object> context) {
|
||||
|
||||
Object a = lhs.render(context);
|
||||
Object b = rhs.render(context);
|
||||
|
||||
return (a instanceof Number) && (b instanceof Number) &&
|
||||
super.asNumber(a).doubleValue() <= super.asNumber(b).doubleValue();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package ca.uhn.fhir.narrative.template.nodes;
|
||||
|
||||
import ca.uhn.fhir.narrative.template.LValue;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
class LtNode extends LValue implements LNode {
|
||||
|
||||
private LNode lhs;
|
||||
private LNode rhs;
|
||||
|
||||
public LtNode(LNode lhs, LNode rhs) {
|
||||
this.lhs = lhs;
|
||||
this.rhs = rhs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object render(Map<String, Object> context) {
|
||||
|
||||
Object a = lhs.render(context);
|
||||
Object b = rhs.render(context);
|
||||
|
||||
return (a instanceof Number) && (b instanceof Number) &&
|
||||
super.asNumber(a).doubleValue() < super.asNumber(b).doubleValue();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package ca.uhn.fhir.narrative.template.nodes;
|
||||
|
||||
import ca.uhn.fhir.narrative.template.LValue;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
class NEqNode implements LNode {
|
||||
|
||||
private LNode lhs;
|
||||
private LNode rhs;
|
||||
|
||||
public NEqNode(LNode lhs, LNode rhs) {
|
||||
this.lhs = lhs;
|
||||
this.rhs = rhs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object render(Map<String, Object> context) {
|
||||
|
||||
Object a = lhs.render(context);
|
||||
Object b = rhs.render(context);
|
||||
|
||||
return !LValue.areEqual(a, b);
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package ca.uhn.fhir.narrative.template.nodes;
|
||||
|
||||
import ca.uhn.fhir.narrative.template.LValue;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
class OrNode extends LValue implements LNode {
|
||||
|
||||
private LNode lhs;
|
||||
private LNode rhs;
|
||||
|
||||
public OrNode(LNode lhs, LNode rhs) {
|
||||
this.lhs = lhs;
|
||||
this.rhs = rhs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object render(Map<String, Object> context) {
|
||||
|
||||
Object a = lhs.render(context);
|
||||
Object b = rhs.render(context);
|
||||
|
||||
return super.asBoolean(a) || super.asBoolean(b);
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package ca.uhn.fhir.narrative.template.nodes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
class OutputNode implements LNode {
|
||||
|
||||
private LNode expression;
|
||||
private List<FilterNode> filters;
|
||||
|
||||
public OutputNode(LNode expression) {
|
||||
this.expression = expression;
|
||||
this.filters = new ArrayList<FilterNode>();
|
||||
}
|
||||
|
||||
public void addFilter(FilterNode filter) {
|
||||
filters.add(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object render(Map<String, Object> context) {
|
||||
|
||||
Object value = expression.render(context);
|
||||
|
||||
for (FilterNode node : filters) {
|
||||
value = node.apply(value, context);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package ca.uhn.fhir.narrative.template.nodes;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
|
||||
import ca.uhn.fhir.narrative.template.parser.Flavor;
|
||||
import ca.uhn.fhir.narrative.template.tags.Include;
|
||||
import ca.uhn.fhir.narrative.template.tags.Tag;
|
||||
|
||||
class TagNode implements LNode {
|
||||
|
||||
private Tag tag;
|
||||
private LNode[] tokens;
|
||||
private Flavor flavor;
|
||||
|
||||
public TagNode(String tagName, Tag tag, LNode... tokens) {
|
||||
this(tagName, tag, Flavor.LIQUID, tokens);
|
||||
}
|
||||
|
||||
public TagNode(String tagName, Tag tag, Flavor flavor, LNode... tokens) {
|
||||
if (tag == null) {
|
||||
throw new IllegalArgumentException("no tag available named: " + tagName);
|
||||
}
|
||||
this.tag = tag;
|
||||
this.tokens = tokens;
|
||||
this.flavor = flavor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object render(Map<String, Object> context) {
|
||||
|
||||
// Check if the INCLUDES_DIRECTORY_KEY has already been set, and if not,
|
||||
// set it based on the value in the flavor.
|
||||
if (!context.containsKey(Include.INCLUDES_DIRECTORY_KEY)) {
|
||||
context.put(Include.INCLUDES_DIRECTORY_KEY, new File(flavor.snippetsFolderName));
|
||||
}
|
||||
|
||||
return tag.render(context, tokens);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package ca.uhn.fhir.narrative.template.parser;
|
||||
|
||||
public enum Flavor {
|
||||
|
||||
LIQUID("snippets"),
|
||||
JEKYLL("_includes");
|
||||
|
||||
public final String snippetsFolderName;
|
||||
|
||||
Flavor(String snippetsFolderName) {
|
||||
this.snippetsFolderName = snippetsFolderName;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package ca.uhn.fhir.narrative.template.tags;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import ca.uhn.fhir.narrative.template.nodes.FilterNode;
|
||||
import ca.uhn.fhir.narrative.template.nodes.LNode;
|
||||
|
||||
class Assign extends Tag {
|
||||
|
||||
/*
|
||||
* Assigns some value to a variable
|
||||
*/
|
||||
@Override
|
||||
public Object render(Map<String, Object> context, LNode... nodes) {
|
||||
|
||||
String id = String.valueOf(nodes[0].render(context));
|
||||
|
||||
FilterNode filter = null;
|
||||
LNode expression;
|
||||
|
||||
if(nodes.length >= 3) {
|
||||
filter = (FilterNode)nodes[1];
|
||||
expression = nodes[2];
|
||||
}
|
||||
else {
|
||||
expression = nodes[1];
|
||||
}
|
||||
|
||||
Object value = expression.render(context);
|
||||
|
||||
if(filter != null) {
|
||||
value = filter.apply(value, context);
|
||||
}
|
||||
|
||||
context.put(id, value);
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package ca.uhn.fhir.narrative.template.tags;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import ca.uhn.fhir.narrative.template.nodes.LNode;
|
||||
|
||||
class Capture extends Tag {
|
||||
|
||||
/*
|
||||
* Block tag that captures text into a variable
|
||||
*/
|
||||
@Override
|
||||
public Object render(Map<String, Object> context, LNode... nodes) {
|
||||
|
||||
String id = super.asString(nodes[0].render(context));
|
||||
|
||||
LNode block = nodes[1];
|
||||
|
||||
context.put(id, block.render(context));
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package ca.uhn.fhir.narrative.template.tags;
|
||||
|
||||
import ca.uhn.fhir.narrative.template.LValue;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import ca.uhn.fhir.narrative.template.nodes.BlockNode;
|
||||
import ca.uhn.fhir.narrative.template.nodes.LNode;
|
||||
|
||||
class Case extends Tag {
|
||||
|
||||
/*
|
||||
* Block tag, its the standard case...when block
|
||||
*/
|
||||
@Override
|
||||
public Object render(Map<String, Object> context, LNode... nodes) {
|
||||
|
||||
// ^(CASE condition var
|
||||
// ^(WHEN term+ block) 1,2,3 b1
|
||||
// ^(ELSE block?)) b2
|
||||
|
||||
Object condition = nodes[0].render(context);
|
||||
|
||||
for (int i = 1; i < nodes.length; i++) {
|
||||
|
||||
LNode node = nodes[i];
|
||||
|
||||
if(i == nodes.length - 1 && node instanceof BlockNode) {
|
||||
// this must be the trailing (optional) else-block
|
||||
return node.render(context);
|
||||
}
|
||||
else {
|
||||
|
||||
boolean hit = false;
|
||||
|
||||
// Iterate through the list of terms (of which we do not know the size):
|
||||
// - term (',' term)*
|
||||
// - term ('or' term)*
|
||||
// and stop when we encounter a BlockNode
|
||||
while(!(node instanceof BlockNode)) {
|
||||
|
||||
Object whenExpressionValue = node.render(context);
|
||||
|
||||
if (LValue.areEqual(condition, whenExpressionValue)) {
|
||||
hit = true;
|
||||
}
|
||||
|
||||
i++;
|
||||
node = nodes[i];
|
||||
}
|
||||
|
||||
if(hit) {
|
||||
return node.render(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package ca.uhn.fhir.narrative.template.tags;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import ca.uhn.fhir.narrative.template.nodes.LNode;
|
||||
|
||||
class Comment extends Tag {
|
||||
|
||||
/*
|
||||
* Block tag, comments out the text in the block
|
||||
*/
|
||||
@Override
|
||||
public Object render(Map<String, Object> context, LNode... nodes) {
|
||||
return "";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package ca.uhn.fhir.narrative.template.tags;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import ca.uhn.fhir.narrative.template.nodes.LNode;
|
||||
|
||||
class Cycle extends Tag {
|
||||
|
||||
private static final String PREPEND = "\"'";
|
||||
|
||||
/*
|
||||
* Cycle is usually used within a loop to alternate
|
||||
* between values, like colors or DOM classes.
|
||||
*/
|
||||
@Override
|
||||
public Object render(Map<String, Object> context, LNode... nodes) {
|
||||
|
||||
// The group-name is either the first token-expression, or if that is
|
||||
// null (indicating there is no name), give it the name PREPEND followed
|
||||
// by the number of expressions in the cycle-group.
|
||||
String groupName = nodes[0] == null ?
|
||||
PREPEND + (nodes.length - 1) :
|
||||
super.asString(nodes[0].render(context));
|
||||
|
||||
// Prepend a groupName with a single- and double quote as to not
|
||||
// let the groupName conflict with other variable assignments
|
||||
groupName = PREPEND + groupName;
|
||||
|
||||
Object obj = context.remove(groupName);
|
||||
|
||||
List<Object> elements = new ArrayList<Object>();
|
||||
|
||||
for (int i = 1; i < nodes.length; i++) {
|
||||
elements.add(nodes[i].render(context));
|
||||
}
|
||||
|
||||
CycleGroup group;
|
||||
|
||||
if (obj == null) {
|
||||
group = new CycleGroup(elements.size());
|
||||
}
|
||||
else {
|
||||
group = (CycleGroup) obj;
|
||||
}
|
||||
|
||||
context.put(groupName, group);
|
||||
|
||||
return group.next(elements);
|
||||
}
|
||||
|
||||
private static class CycleGroup {
|
||||
|
||||
private final int sizeFirstCycle;
|
||||
private int currentIndex;
|
||||
|
||||
CycleGroup(int sizeFirstCycle) {
|
||||
this.sizeFirstCycle = sizeFirstCycle;
|
||||
this.currentIndex = 0;
|
||||
}
|
||||
|
||||
Object next(List<Object> elements) {
|
||||
|
||||
Object obj;
|
||||
|
||||
if (currentIndex >= elements.size()) {
|
||||
obj = "";
|
||||
}
|
||||
else {
|
||||
obj = elements.get(currentIndex);
|
||||
}
|
||||
|
||||
currentIndex++;
|
||||
|
||||
if (currentIndex == sizeFirstCycle) {
|
||||
currentIndex = 0;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,253 @@
|
|||
package ca.uhn.fhir.narrative.template.tags;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import ca.uhn.fhir.narrative.template.nodes.BlockNode;
|
||||
import ca.uhn.fhir.narrative.template.nodes.LNode;
|
||||
|
||||
class For extends Tag {
|
||||
|
||||
private static final String OFFSET = "offset";
|
||||
private static final String LIMIT = "limit";
|
||||
private static final String CONTINUE = "continue";
|
||||
|
||||
/*
|
||||
* forloop.length # => length of the entire for loop
|
||||
* forloop.index # => index of the current iteration
|
||||
* forloop.index0 # => index of the current iteration (zero based)
|
||||
* forloop.rindex # => how many items are still left?
|
||||
* forloop.rindex0 # => how many items are still left? (zero based)
|
||||
* forloop.first # => is this the first iteration?
|
||||
* forloop.last # => is this the last iteration?
|
||||
*/
|
||||
private static final String FORLOOP = "forloop";
|
||||
private static final String LENGTH = "length";
|
||||
private static final String INDEX = "index";
|
||||
private static final String INDEX0 = "index0";
|
||||
private static final String RINDEX = "rindex";
|
||||
private static final String RINDEX0 = "rindex0";
|
||||
private static final String FIRST = "first";
|
||||
private static final String LAST = "last";
|
||||
private static final String NAME = "name";
|
||||
|
||||
/*
|
||||
* For loop
|
||||
*/
|
||||
@Override
|
||||
public Object render(Map<String, Object> context, LNode... nodes) {
|
||||
|
||||
// The first node in the array denotes whether this is a for-tag
|
||||
// over an array, `for item in array ...`, or a for-tag over a
|
||||
// range, `for i in (4..item.length)`.
|
||||
boolean array = super.asBoolean(nodes[0].render(context));
|
||||
|
||||
String id = super.asString(nodes[1].render(context));
|
||||
|
||||
context.put(FORLOOP, new HashMap<String, Object>());
|
||||
|
||||
Object rendered = array ? renderArray(id, context, nodes) : renderRange(id, context, nodes);
|
||||
|
||||
context.remove(FORLOOP);
|
||||
|
||||
return rendered;
|
||||
}
|
||||
|
||||
private Object renderArray(String id, Map<String, Object> context, LNode... tokens) {
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
// attributes start from index 5
|
||||
Map<String, Integer> attributes = getAttributes(5, context, tokens);
|
||||
|
||||
int offset = attributes.get(OFFSET);
|
||||
int limit = attributes.get(LIMIT);
|
||||
|
||||
Object[] array = super.asArray(tokens[2].render(context));
|
||||
|
||||
LNode block = tokens[3];
|
||||
LNode blockIfEmptyOrNull = tokens[4];
|
||||
|
||||
if(array == null || array.length == 0) {
|
||||
return blockIfEmptyOrNull == null ? null : blockIfEmptyOrNull.render(context);
|
||||
}
|
||||
|
||||
int length = Math.min(limit, array.length);
|
||||
|
||||
Map<String, Object> forLoopMap = (Map<String, Object>)context.get(FORLOOP);
|
||||
|
||||
forLoopMap.put(NAME, id + "-" + tokens[2].toString());
|
||||
|
||||
int continueIndex = offset;
|
||||
|
||||
for (int i = offset, n = 0; n < limit && i < array.length; i++, n++) {
|
||||
|
||||
continueIndex = i;
|
||||
|
||||
boolean first = (i == offset);
|
||||
boolean last = ((n == limit - 1) || (i == array.length - 1));
|
||||
|
||||
context.put(id, array[i]);
|
||||
|
||||
forLoopMap.put(LENGTH, length);
|
||||
forLoopMap.put(INDEX, n + 1);
|
||||
forLoopMap.put(INDEX0, n);
|
||||
forLoopMap.put(RINDEX, length - n);
|
||||
forLoopMap.put(RINDEX0, length - n - 1);
|
||||
forLoopMap.put(FIRST, first);
|
||||
forLoopMap.put(LAST, last);
|
||||
|
||||
List<LNode> children = ((BlockNode)block).getChildren();
|
||||
boolean isBreak = false;
|
||||
|
||||
for (LNode node : children) {
|
||||
|
||||
Object value = node.render(context);
|
||||
|
||||
if(value == Tag.Statement.CONTINUE) {
|
||||
// break from this inner loop: equals continue outer loop!
|
||||
break;
|
||||
}
|
||||
|
||||
if(value == Tag.Statement.BREAK) {
|
||||
// break from inner loop
|
||||
isBreak = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (value != null && value.getClass().isArray()) {
|
||||
|
||||
Object[] arr = (Object[]) value;
|
||||
|
||||
for (Object obj : arr) {
|
||||
builder.append(String.valueOf(obj));
|
||||
}
|
||||
}
|
||||
else {
|
||||
builder.append(super.asString(value));
|
||||
}
|
||||
}
|
||||
|
||||
if(isBreak) {
|
||||
// break from outer loop
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
context.put(CONTINUE, continueIndex + 1);
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private Object renderRange(String id, Map<String, Object> context, LNode... tokens) {
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
// attributes start from index 5
|
||||
Map<String, Integer> attributes = getAttributes(5, context, tokens);
|
||||
|
||||
int offset = attributes.get(OFFSET);
|
||||
int limit = attributes.get(LIMIT);
|
||||
|
||||
LNode block = tokens[4];
|
||||
|
||||
try {
|
||||
int from = super.asNumber(tokens[2].render(context)).intValue();
|
||||
int to = super.asNumber(tokens[3].render(context)).intValue();
|
||||
|
||||
int length = (to - from);
|
||||
|
||||
Map<String, Object> forLoopMap = (Map<String, Object>)context.get(FORLOOP);
|
||||
|
||||
int continueIndex = from + offset;
|
||||
|
||||
for (int i = from + offset, n = 0; i <= to && n < limit; i++, n++) {
|
||||
|
||||
continueIndex = i;
|
||||
|
||||
boolean first = (i == (from + offset));
|
||||
boolean last = ((i == to) || (n == limit - 1));
|
||||
|
||||
context.put(id, i);
|
||||
|
||||
forLoopMap.put(LENGTH, length);
|
||||
forLoopMap.put(INDEX, n + 1);
|
||||
forLoopMap.put(INDEX0, n);
|
||||
forLoopMap.put(RINDEX, length - n);
|
||||
forLoopMap.put(RINDEX0, length - n - 1);
|
||||
forLoopMap.put(FIRST, first);
|
||||
forLoopMap.put(LAST, last);
|
||||
|
||||
List<LNode> children = ((BlockNode)block).getChildren();
|
||||
boolean isBreak = false;
|
||||
|
||||
for (LNode node : children) {
|
||||
|
||||
Object value = node.render(context);
|
||||
|
||||
if(value == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(value == Tag.Statement.CONTINUE) {
|
||||
// break from this inner loop: equals continue outer loop!
|
||||
break;
|
||||
}
|
||||
|
||||
if(value == Tag.Statement.BREAK) {
|
||||
// break from inner loop
|
||||
isBreak = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if(super.isArray(value)) {
|
||||
|
||||
Object[] arr = super.asArray(value);
|
||||
|
||||
for (Object obj : arr) {
|
||||
builder.append(String.valueOf(obj));
|
||||
}
|
||||
}
|
||||
else {
|
||||
builder.append(super.asString(value));
|
||||
}
|
||||
}
|
||||
|
||||
if(isBreak) {
|
||||
// break from outer loop
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
context.put(CONTINUE, continueIndex + 1);
|
||||
}
|
||||
catch (Exception e) {
|
||||
/* just ignore incorrect expressions */
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private Map<String, Integer> getAttributes(int fromIndex, Map<String, Object> context, LNode... tokens) {
|
||||
|
||||
Map<String, Integer> attributes = new HashMap<String, Integer>();
|
||||
|
||||
attributes.put(OFFSET, 0);
|
||||
attributes.put(LIMIT, Integer.MAX_VALUE);
|
||||
|
||||
for (int i = fromIndex; i < tokens.length; i++) {
|
||||
|
||||
Object[] attribute = super.asArray(tokens[i].render(context));
|
||||
|
||||
try {
|
||||
attributes.put(super.asString(attribute[0]), super.asNumber(attribute[1]).intValue());
|
||||
}
|
||||
catch (Exception e) {
|
||||
/* just ignore incorrect attributes */
|
||||
}
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package ca.uhn.fhir.narrative.template.tags;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import ca.uhn.fhir.narrative.template.nodes.LNode;
|
||||
|
||||
class If extends Tag {
|
||||
|
||||
/*
|
||||
* Standard if/else block
|
||||
*/
|
||||
@Override
|
||||
public Object render(Map<String, Object> context, LNode... nodes) {
|
||||
|
||||
for (int i = 0; i < nodes.length - 1; i += 2) {
|
||||
|
||||
Object exprNodeValue = nodes[i].render(context);
|
||||
LNode blockNode = nodes[i + 1];
|
||||
|
||||
if (super.asBoolean(exprNodeValue)) {
|
||||
return blockNode.render(context);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package ca.uhn.fhir.narrative.template.tags;
|
||||
|
||||
import ca.uhn.fhir.narrative.template.Template;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
|
||||
import ca.uhn.fhir.narrative.template.nodes.LNode;
|
||||
|
||||
public class Include extends Tag {
|
||||
|
||||
public static final String INCLUDES_DIRECTORY_KEY = "liqp@includes_directory";
|
||||
public static String DEFAULT_EXTENSION = ".liquid";
|
||||
|
||||
@Override
|
||||
public Object render(Map<String, Object> context, LNode... nodes) {
|
||||
|
||||
// This value will always be defined: either a custom file set by the
|
||||
// user, or else inside TagNode.
|
||||
File includesDirectory = (File)context.get(INCLUDES_DIRECTORY_KEY);
|
||||
|
||||
try {
|
||||
String includeResource = super.asString(nodes[0].render(context));
|
||||
String extension = DEFAULT_EXTENSION;
|
||||
if(includeResource.indexOf('.') > 0) {
|
||||
extension = "";
|
||||
}
|
||||
File includeResourceFile = new File(includesDirectory, includeResource + extension);
|
||||
Template include = Template.parse(includeResourceFile);
|
||||
|
||||
// check if there's a optional "with expression"
|
||||
if(nodes.length > 1) {
|
||||
Object value = nodes[1].render(context);
|
||||
context.put(includeResource, value);
|
||||
}
|
||||
|
||||
return include.render(context);
|
||||
|
||||
} catch(Exception e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package ca.uhn.fhir.narrative.template.tags;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import ca.uhn.fhir.narrative.template.nodes.LNode;
|
||||
|
||||
class Raw extends Tag {
|
||||
|
||||
/*
|
||||
* temporarily disable tag processing to avoid syntax conflicts.
|
||||
*/
|
||||
@Override
|
||||
public Object render(Map<String, Object> context, LNode... nodes) {
|
||||
return nodes[0].render(context);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
package ca.uhn.fhir.narrative.template.tags;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import ca.uhn.fhir.narrative.template.nodes.LNode;
|
||||
|
||||
class Tablerow extends Tag {
|
||||
|
||||
private static final String COLS = "cols";
|
||||
private static final String LIMIT = "limit";
|
||||
|
||||
/*
|
||||
* tablerowloop.length # => length of the entire for loop
|
||||
* tablerowloop.index # => index of the current iteration
|
||||
* tablerowloop.index0 # => index of the current iteration (zero based)
|
||||
* tablerowloop.rindex # => how many items are still left?
|
||||
* tablerowloop.rindex0 # => how many items are still left? (zero based)
|
||||
* tablerowloop.first # => is this the first iteration?
|
||||
* tablerowloop.last # => is this the last iteration?
|
||||
* tablerowloop.col # => index of column in the current row
|
||||
* tablerowloop.col0 # => index of column in the current row (zero based)
|
||||
* tablerowloop.col_first # => is this the first column in the row?
|
||||
* tablerowloop.col_last # => is this the last column in the row?
|
||||
*/
|
||||
private static final String TABLEROWLOOP = "tablerowloop";
|
||||
private static final String LENGTH = "length";
|
||||
private static final String INDEX = "index";
|
||||
private static final String INDEX0 = "index0";
|
||||
private static final String RINDEX = "rindex";
|
||||
private static final String RINDEX0 = "rindex0";
|
||||
private static final String FIRST = "first";
|
||||
private static final String LAST = "last";
|
||||
private static final String COL = "col";
|
||||
private static final String COL0 = "col0";
|
||||
private static final String COL_FIRST = "col_first";
|
||||
private static final String COL_LAST = "col_last";
|
||||
|
||||
/*
|
||||
* Tables
|
||||
*/
|
||||
@Override
|
||||
public Object render(Map<String, Object> context, LNode... nodes) {
|
||||
|
||||
String valueName = super.asString(nodes[0].render(context));
|
||||
Object[] collection = super.asArray(nodes[1].render(context));
|
||||
LNode block = nodes[2];
|
||||
Map<String, Integer> attributes = getAttributes(collection, 3, context, nodes);
|
||||
|
||||
int cols = attributes.get(COLS);
|
||||
int limit = attributes.get(LIMIT);
|
||||
|
||||
Map<String, Object> tablerowloopContext = new HashMap<String, Object>();
|
||||
|
||||
tablerowloopContext.put(LENGTH, collection.length);
|
||||
|
||||
context.put(TABLEROWLOOP, tablerowloopContext);
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
int total = Math.min(collection.length, limit);
|
||||
|
||||
if(total == 0) {
|
||||
|
||||
builder.append("<tr class=\"row1\">\n</tr>\n");
|
||||
}
|
||||
else {
|
||||
|
||||
for(int i = 0, c = 1, r = 0; i < total; i++, c++) {
|
||||
|
||||
context.put(valueName, collection[i]);
|
||||
|
||||
tablerowloopContext.put(INDEX0, i);
|
||||
tablerowloopContext.put(INDEX, i + 1);
|
||||
tablerowloopContext.put(RINDEX0, total - i - 1);
|
||||
tablerowloopContext.put(RINDEX, total - i);
|
||||
tablerowloopContext.put(FIRST, i == 0);
|
||||
tablerowloopContext.put(LAST, i == total - 1);
|
||||
tablerowloopContext.put(COL0, c - 1);
|
||||
tablerowloopContext.put(COL, c);
|
||||
tablerowloopContext.put(COL_FIRST, c == 1);
|
||||
tablerowloopContext.put(COL_LAST, c == cols);
|
||||
|
||||
if(c == 1) {
|
||||
r++;
|
||||
builder.append("<tr class=\"row").append(r).append("\">").append(r == 1 ? "\n" : "");
|
||||
}
|
||||
|
||||
builder.append("<td class=\"col").append(c).append("\">");
|
||||
builder.append(super.asString(block.render(context)));
|
||||
builder.append("</td>");
|
||||
|
||||
if(c == cols || i == total - 1) {
|
||||
builder.append("</tr>\n");
|
||||
c = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.remove(TABLEROWLOOP);
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private Map<String, Integer> getAttributes(Object[] collection, int fromIndex, Map<String, Object> context, LNode... tokens) {
|
||||
|
||||
Map<String, Integer> attributes = new HashMap<String, Integer>();
|
||||
|
||||
attributes.put(COLS, collection.length);
|
||||
attributes.put(LIMIT, Integer.MAX_VALUE);
|
||||
|
||||
for (int i = fromIndex; i < tokens.length; i++) {
|
||||
|
||||
Object[] attribute = super.asArray(tokens[i].render(context));
|
||||
|
||||
try {
|
||||
attributes.put(super.asString(attribute[0]), super.asNumber(attribute[1]).intValue());
|
||||
}
|
||||
catch (Exception e) {
|
||||
/* just ignore incorrect attributes */
|
||||
}
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
package ca.uhn.fhir.narrative.template.tags;
|
||||
|
||||
import ca.uhn.fhir.narrative.template.LValue;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import ca.uhn.fhir.narrative.template.nodes.LNode;
|
||||
|
||||
/**
|
||||
* Tags are used for the logic in a template.
|
||||
*/
|
||||
public abstract class Tag extends LValue {
|
||||
|
||||
public enum Statement {
|
||||
|
||||
BREAK, CONTINUE;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A map holding all tags.
|
||||
*/
|
||||
private static final Map<String, Tag> TAGS = new HashMap<String, Tag>();
|
||||
|
||||
static {
|
||||
// Register all standard tags.
|
||||
registerTag(new Assign());
|
||||
registerTag(new Case());
|
||||
registerTag(new Capture());
|
||||
registerTag(new Comment());
|
||||
registerTag(new Cycle());
|
||||
registerTag(new For());
|
||||
registerTag(new If());
|
||||
registerTag(new Include());
|
||||
registerTag(new Raw());
|
||||
registerTag(new Tablerow());
|
||||
registerTag(new Unless());
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of this tag.
|
||||
*/
|
||||
public final String name;
|
||||
|
||||
/**
|
||||
* Used for all package protected tags in the ca.uhn.fhir.narrative.template.tags-package
|
||||
* whose name is their class name lower cased.
|
||||
*/
|
||||
protected Tag() {
|
||||
this.name = this.getClass().getSimpleName().toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of a Tag.
|
||||
*
|
||||
* @param name
|
||||
* the name of the tag.
|
||||
*/
|
||||
public Tag(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a filter with a specific name.
|
||||
*
|
||||
* @param name
|
||||
* the name of the filter to retrieve.
|
||||
*
|
||||
* @return a filter with a specific name.
|
||||
*/
|
||||
public static Tag getTag(String name) {
|
||||
|
||||
Tag tag = TAGS.get(name);
|
||||
|
||||
if (tag == null) {
|
||||
throw new RuntimeException("unknown tag: " + name);
|
||||
}
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all default tags.
|
||||
*
|
||||
* @return all default tags.
|
||||
*/
|
||||
public static Map<String, Tag> getTags() {
|
||||
return new HashMap<String, Tag>(TAGS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new tag.
|
||||
*
|
||||
* @param tag
|
||||
* the tag to be registered.
|
||||
*/
|
||||
public static void registerTag(Tag tag) {
|
||||
TAGS.put(tag.name, tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders this tag.
|
||||
*
|
||||
* @param context
|
||||
* the context (variables) with which this
|
||||
* node should be rendered.
|
||||
* @param nodes
|
||||
* the nodes of this tag is created with. See
|
||||
* the file `src/grammar/LiquidWalker.g` to see
|
||||
* how each of the tags is created.
|
||||
*
|
||||
* @return an Object denoting the rendered AST.
|
||||
*/
|
||||
public abstract Object render(Map<String, Object> context, LNode... nodes);
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package ca.uhn.fhir.narrative.template.tags;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import ca.uhn.fhir.narrative.template.nodes.LNode;
|
||||
|
||||
class Unless extends Tag {
|
||||
|
||||
/*
|
||||
* Mirror of if statement
|
||||
*/
|
||||
@Override
|
||||
public Object render(Map<String, Object> context, LNode... nodes) {
|
||||
|
||||
for (int i = 0; i < nodes.length - 1; i += 2) {
|
||||
|
||||
Object exprNodeValue = nodes[i].render(context);
|
||||
LNode blockNode = nodes[i + 1];
|
||||
|
||||
if (!super.asBoolean(exprNodeValue)) {
|
||||
return blockNode.render(context);
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
|
||||
package ca.uhn.fhir.narrative.template;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.hl7.fhir.dstu3.model.Patient;
|
||||
import org.junit.Test;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
|
||||
public class TemplateNarrativeGeneratorTest {
|
||||
|
||||
@Test
|
||||
public void testTemplate() throws Exception {
|
||||
String template = IOUtils.toString(getClass().getResourceAsStream("/patient.narrative"), StandardCharsets.UTF_8);
|
||||
|
||||
Patient input = new Patient();
|
||||
input.addName().addFamily("LNAME1");
|
||||
input.addName().addFamily("LNAME2");
|
||||
input.addName().addGiven("FNAME1");
|
||||
input.addName().addGiven("FNAME2");
|
||||
|
||||
TemplateNarrativeGenerator gen = new TemplateNarrativeGenerator();
|
||||
String output = gen.processLiquid(FhirContext.forDstu3(), template, input);
|
||||
|
||||
ourLog.info(output);
|
||||
}
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TemplateNarrativeGeneratorTest.class);
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<div>
|
||||
|
||||
{% if name.empty() == false %}
|
||||
{{ name[0].family }}
|
||||
{{ name[0].given }}
|
||||
{% endif %}
|
||||
|
||||
</div>
|
9
pom.xml
9
pom.xml
|
@ -49,6 +49,10 @@
|
|||
<name>bintray</name>
|
||||
<url>http://dl.bintray.com/dnault/maven</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<description>
|
||||
|
@ -327,6 +331,11 @@
|
|||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.1.7</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.bkiers</groupId>
|
||||
<artifactId>Liqp</artifactId>
|
||||
<version>0.6.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.dnault</groupId>
|
||||
<artifactId>xml-patch</artifactId>
|
||||
|
|
Loading…
Reference in New Issue