├── project ├── build.properties ├── plugins.sbt └── Dependencies.scala ├── parser └── src │ ├── main │ └── scala │ │ └── hl7 │ │ └── v2 │ │ ├── instance │ │ ├── Line.scala │ │ ├── serializer │ │ │ ├── Serializer.scala │ │ │ └── XML.scala │ │ ├── SegOrGroup.scala │ │ ├── Counter.scala │ │ ├── Path.scala │ │ ├── Location.scala │ │ ├── Component.scala │ │ ├── Group.scala │ │ ├── Separators.scala │ │ ├── Value.scala │ │ ├── Field.scala │ │ ├── package.scala │ │ ├── Element.scala │ │ ├── Query.scala │ │ ├── util │ │ │ └── ValueConversionHelpers.scala │ │ ├── EscapeSeqHandler.scala │ │ └── DataElement.scala │ │ └── parser │ │ ├── Parser.scala │ │ └── impl │ │ ├── package.scala │ │ ├── PreProcessor.scala │ │ └── DefaultParser.scala │ └── test │ └── scala │ └── hl7 │ └── v2 │ ├── parser │ └── impl │ │ ├── DefaultParserSpec.scala │ │ ├── ParserSpecHelper.scala │ │ └── ParserSpec.scala │ └── instance │ ├── PathSpec.scala │ ├── EscapeSeqHandlerSpec.scala │ ├── Mocks.scala │ ├── util │ ├── ValueConversionHelpersSpec.scala │ ├── ValueComparatorSpec.scala │ └── ValueFormatCheckersSpec.scala │ └── QuerySpec.scala ├── .gitignore ├── dependencies ├── xml-util-2.1.0 │ ├── xml-util-2.1.0.jar │ └── xml-util-2.1.0.pom └── validation-report-1.1.0 │ ├── validation-report-1.1.0.jar │ └── validation-report-1.1.0.pom ├── validation └── src │ ├── test │ ├── scala │ │ ├── expression │ │ │ ├── DefaultEvaluatorSpec.scala │ │ │ ├── package.scala │ │ │ ├── NOTSpec.scala │ │ │ ├── OperatorSpec.scala │ │ │ ├── PresenceSpec.scala │ │ │ ├── ORSpec.scala │ │ │ ├── PluginSpec.scala │ │ │ ├── IsNULLSpec.scala │ │ │ ├── ANDSpec.scala │ │ │ ├── IMLYSpec.scala │ │ │ ├── FORALLSpec.scala │ │ │ ├── EXISTSpec.scala │ │ │ ├── XORSpec.scala │ │ │ ├── FormatSpec.scala │ │ │ ├── Mocks.scala │ │ │ ├── StringFormatSpec.scala │ │ │ ├── StringListSpec.scala │ │ │ ├── NumberListSpec.scala │ │ │ ├── PathValueSpec.scala │ │ │ └── PlainTextSpec.scala │ │ └── hl7 │ │ │ └── v2 │ │ │ └── validation │ │ │ ├── structure │ │ │ ├── DefaultStructValidatorSpec.scala │ │ │ ├── Helpers.scala │ │ │ └── ValueValidationSpec.scala │ │ │ ├── ValidatorMain.scala │ │ │ └── vs │ │ │ └── SimpleElementValidatorSpec.scala │ ├── java │ │ └── expression │ │ │ ├── PluginFailure.java │ │ │ ├── PluginSuccess.java │ │ │ ├── PluginCustomSuccessNull.java │ │ │ ├── PluginCustomFailure.java │ │ │ ├── PluginCustomSuccessEmpty.java │ │ │ └── PluginCustomFailureMulti.java │ └── resources │ │ ├── rules │ │ └── CContext.xml │ │ └── ORU_R01_Profile.xml │ └── main │ ├── java │ └── hl7 │ │ └── v2 │ │ └── validation │ │ ├── vs │ │ ├── ValueSetSpecException.java │ │ ├── ConfigurableValidation.java │ │ ├── ValueSetNotFoundException.java │ │ ├── EmptyValueSetLibrary.java │ │ ├── ValueSetLibrary.java │ │ ├── EnhancedEntry.java │ │ ├── TripletEntry.java │ │ ├── Validator.java │ │ ├── ComplexElementValidator.java │ │ └── SimpleElementValidator.java │ │ ├── plugin │ │ ├── BooleanValidationPlugin.java │ │ └── ErrorListValidationPlugin.java │ │ └── utils │ │ └── format │ │ └── utils │ │ └── LOINCFormat.java │ ├── scala │ ├── hl7 │ │ └── v2 │ │ │ └── validation │ │ │ ├── content │ │ │ ├── Pattern.scala │ │ │ ├── ConformanceContext.scala │ │ │ ├── EmptyConformanceContext.scala │ │ │ ├── EmptyValidator.scala │ │ │ ├── Validator.scala │ │ │ ├── Constraint.scala │ │ │ └── PatternFinder.scala │ │ │ ├── structure │ │ │ ├── Validator.scala │ │ │ └── ValueValidation.scala │ │ │ ├── report │ │ │ └── Report.scala │ │ │ ├── vs │ │ │ ├── domain.scala │ │ │ └── ValueSetLibraryImpl.scala │ │ │ ├── Utils.scala │ │ │ └── Validator.scala │ └── expression │ │ ├── Evaluator.scala │ │ ├── StringFormatValidator.scala │ │ ├── EvalResult.scala │ │ ├── Operator.scala │ │ ├── domain.scala │ │ └── ContextBasedEvaluator.scala │ └── resources │ └── rules │ └── Commons.xsd ├── profile └── src │ ├── test │ └── scala │ │ └── hl7 │ │ └── v2 │ │ └── profile │ │ ├── Main.scala │ │ └── XMLSerializerSpec.scala │ └── main │ └── scala │ └── hl7 │ └── v2 │ └── profile │ ├── Range.scala │ ├── Usage.scala │ ├── XMLDeserializer.scala │ ├── Req.scala │ ├── ValueSetSpec.scala │ ├── domain.scala │ └── XMLSerializer.scala └── README.md /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.5.8 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.9.0") -------------------------------------------------------------------------------- /parser/src/main/scala/hl7/v2/instance/Line.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.instance 2 | 3 | case class Line(number: Int, content: String) 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .cache 3 | .classpath 4 | .project 5 | .settings/ 6 | .worksheet 7 | .idea/ 8 | *~ 9 | .idea_modules/ 10 | .DS_Store 11 | -------------------------------------------------------------------------------- /dependencies/xml-util-2.1.0/xml-util-2.1.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usnistgov/v2-validation/HEAD/dependencies/xml-util-2.1.0/xml-util-2.1.0.jar -------------------------------------------------------------------------------- /validation/src/test/scala/expression/DefaultEvaluatorSpec.scala: -------------------------------------------------------------------------------- 1 | package expression 2 | 3 | object DefaultEvaluatorSpec extends EvaluatorSpec with DefaultEvaluator 4 | -------------------------------------------------------------------------------- /parser/src/test/scala/hl7/v2/parser/impl/DefaultParserSpec.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.parser.impl 2 | 3 | object DefaultParserSpec extends ParserSpec with DefaultParser 4 | 5 | -------------------------------------------------------------------------------- /dependencies/validation-report-1.1.0/validation-report-1.1.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usnistgov/v2-validation/HEAD/dependencies/validation-report-1.1.0/validation-report-1.1.0.jar -------------------------------------------------------------------------------- /validation/src/main/java/hl7/v2/validation/vs/ValueSetSpecException.java: -------------------------------------------------------------------------------- 1 | package hl7.v2.validation.vs; 2 | 3 | public class ValueSetSpecException extends Exception { 4 | 5 | public ValueSetSpecException(String msg) { 6 | super(msg); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /validation/src/main/java/hl7/v2/validation/plugin/BooleanValidationPlugin.java: -------------------------------------------------------------------------------- 1 | package hl7.v2.validation.plugin; 2 | 3 | import hl7.v2.instance.Element; 4 | 5 | public interface BooleanValidationPlugin { 6 | 7 | public boolean assertion(Element e); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /validation/src/test/scala/hl7/v2/validation/structure/DefaultStructValidatorSpec.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.validation.structure 2 | 3 | object DefaultStructValidatorSpec extends StructValidationSpec with DefaultValidator 4 | object NullValidatorSpec extends NullValidationSpec with DefaultValidator 5 | 6 | -------------------------------------------------------------------------------- /validation/src/test/java/expression/PluginFailure.java: -------------------------------------------------------------------------------- 1 | package expression; 2 | 3 | import hl7.v2.instance.Element; 4 | 5 | /** 6 | * Created by malick on 6/9/15. 7 | */ 8 | public class PluginFailure { 9 | 10 | public boolean assertion(Element context) { 11 | return false; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /validation/src/test/java/expression/PluginSuccess.java: -------------------------------------------------------------------------------- 1 | package expression; 2 | 3 | import hl7.v2.instance.Element; 4 | 5 | /** 6 | * Created by malick on 6/9/15. 7 | */ 8 | public class PluginSuccess { 9 | 10 | public boolean assertion(Element context) { 11 | return true; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /parser/src/main/scala/hl7/v2/instance/serializer/Serializer.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.instance 2 | package serializer 3 | 4 | object Serializer { 5 | 6 | def toXML[A: XML]( value: A ): String = { 7 | val pp = new scala.xml.PrettyPrinter(500, 2) 8 | pp.format( implicitly[XML[A]].xml(value) ) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /validation/src/main/java/hl7/v2/validation/plugin/ErrorListValidationPlugin.java: -------------------------------------------------------------------------------- 1 | package hl7.v2.validation.plugin; 2 | 3 | import java.util.List; 4 | 5 | import hl7.v2.instance.Element; 6 | 7 | public interface ErrorListValidationPlugin { 8 | 9 | public List assertionWithCustomMessages(Element e); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /validation/src/test/java/expression/PluginCustomSuccessNull.java: -------------------------------------------------------------------------------- 1 | package expression; 2 | 3 | import java.util.List; 4 | 5 | import hl7.v2.instance.Element; 6 | 7 | public class PluginCustomSuccessNull { 8 | 9 | public List assertionWithCustomMessages(Element e){ 10 | return null; 11 | } 12 | 13 | 14 | } 15 | -------------------------------------------------------------------------------- /parser/src/main/scala/hl7/v2/instance/SegOrGroup.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.instance 2 | 3 | import hl7.v2.profile.SegRefOrGroup 4 | 5 | /** 6 | * Trait representing either a segment or a group 7 | */ 8 | trait SegOrGroup extends Complex { 9 | def model: SegRefOrGroup 10 | def req = model.req 11 | def reqs = model.reqs 12 | } 13 | -------------------------------------------------------------------------------- /validation/src/test/java/expression/PluginCustomFailure.java: -------------------------------------------------------------------------------- 1 | package expression; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | import hl7.v2.instance.Element; 7 | 8 | public class PluginCustomFailure { 9 | 10 | public List assertionWithCustomMessages(Element e){ 11 | return Arrays.asList("FAIL"); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /parser/src/main/scala/hl7/v2/instance/Counter.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.instance 2 | 3 | /** 4 | * @author root 5 | */ 6 | case class Counter(ls : scala.collection.mutable.Map[String,Int]) { 7 | def countFor(seg : String) : Int = { 8 | ls.get(seg) match { 9 | case Some(x) => ls(seg) = (x+1); x + 1 10 | case None => ls(seg) = 1; 1 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /validation/src/test/java/expression/PluginCustomSuccessEmpty.java: -------------------------------------------------------------------------------- 1 | package expression; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | import hl7.v2.instance.Element; 7 | 8 | public class PluginCustomSuccessEmpty { 9 | 10 | public List assertionWithCustomMessages(Element e){ 11 | return Arrays.asList(); 12 | } 13 | 14 | 15 | } 16 | -------------------------------------------------------------------------------- /validation/src/test/scala/hl7/v2/validation/structure/Helpers.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.validation.structure 2 | 3 | import hl7.v2.validation.report.ConfigurableDetections 4 | import com.typesafe.config.ConfigFactory 5 | 6 | trait Helpers { 7 | implicit val Detections = new ConfigurableDetections(ConfigFactory.load()); 8 | implicit val vsValidation = new hl7.v2.validation.vs.Validator(Detections) 9 | } -------------------------------------------------------------------------------- /validation/src/main/java/hl7/v2/validation/vs/ConfigurableValidation.java: -------------------------------------------------------------------------------- 1 | package hl7.v2.validation.vs; 2 | 3 | import hl7.v2.validation.report.ConfigurableDetections; 4 | 5 | public class ConfigurableValidation { 6 | 7 | protected ConfigurableDetections Detections; 8 | 9 | public ConfigurableValidation(ConfigurableDetections detections) { 10 | super(); 11 | Detections = detections; 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /validation/src/test/java/expression/PluginCustomFailureMulti.java: -------------------------------------------------------------------------------- 1 | package expression; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | import hl7.v2.instance.Element; 7 | 8 | public class PluginCustomFailureMulti { 9 | 10 | public List assertionWithCustomMessages(Element e){ 11 | return Arrays.asList("FAIL"); 12 | } 13 | 14 | 15 | public boolean assertion(Element e){ 16 | return false; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /validation/src/main/java/hl7/v2/validation/vs/ValueSetNotFoundException.java: -------------------------------------------------------------------------------- 1 | package hl7.v2.validation.vs; 2 | 3 | /** 4 | * Exception thrown when the value set cannot be found in the library 5 | * 6 | * @author Salifou Sidi M. Malick 7 | */ 8 | public class ValueSetNotFoundException extends Exception { 9 | 10 | public ValueSetNotFoundException(String id) { 11 | super("The value set '"+id+"' cannot be found in the library"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /validation/src/main/scala/hl7/v2/validation/content/Pattern.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.validation.content 2 | 3 | import expression.Expression 4 | 5 | case class Pattern ( 6 | trigger : Trigger, 7 | constraints : List[Constraint], 8 | contexts : List[Context], 9 | cardinality : Int 10 | ) 11 | 12 | case class Context ( 13 | contextPath : String, 14 | Patterns : List[Pattern] 15 | ) 16 | 17 | case class Trigger ( 18 | errorMessage : String, 19 | expression : Expression 20 | ) 21 | -------------------------------------------------------------------------------- /profile/src/test/scala/hl7/v2/profile/Main.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.profile 2 | 3 | import scala.util.{Failure, Success} 4 | import scala.xml.PrettyPrinter 5 | 6 | object Main extends App { 7 | 8 | val xml = getClass.getResourceAsStream("/Profile.xml") 9 | XMLDeserializer.deserialize(xml) match { 10 | case Success(s) => 11 | val pp = new PrettyPrinter(200, 4) 12 | println( pp.format( XMLSerializer.serialize( s ) ) ) 13 | case Failure(f) => println("Error Message:\n"+f.getMessage) 14 | } 15 | } -------------------------------------------------------------------------------- /parser/src/main/scala/hl7/v2/instance/Path.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.instance 2 | 3 | /** 4 | * @author Salifou Sidi M. Malick 5 | */ 6 | 7 | object Path { 8 | 9 | val format = """[1-9][0-9]*\Q[\E(\Q*\E|([1-9][0-9]*))\Q]\E(\Q.\E[1-9][0-9]*\Q[\E(\Q*\E|([1-9][0-9]*))\Q]\E)*""".r 10 | 11 | val extractor = """([1-9][0-9]*)\Q[\E(\Q*\E|[1-9][0-9]*)\Q]\E(?:\Q.\E(.+))?""".r 12 | 13 | /** 14 | * Returns true if the path is valid 15 | */ 16 | def isValid( path: String ) = format.pattern.matcher( path ).matches 17 | } 18 | -------------------------------------------------------------------------------- /parser/src/main/scala/hl7/v2/parser/Parser.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.parser 2 | 3 | import hl7.v2.instance.Message 4 | import hl7.v2.profile.{ Message => MM } 5 | 6 | import scala.util.Try 7 | 8 | /** 9 | * @author Salifou Sidi M. Malick 10 | */ 11 | trait Parser { 12 | 13 | /** 14 | * Parses the message and returns the message instance model 15 | * @param message - The message to be parsed 16 | * @param model - The message model (profile) 17 | * @return The message instance model 18 | */ 19 | def parse( message: String, model: MM ): Try[Message] 20 | } 21 | -------------------------------------------------------------------------------- /validation/src/test/scala/expression/package.scala: -------------------------------------------------------------------------------- 1 | import expression.EvalResult.{Reason, Trace, Inconclusive} 2 | import hl7.v2.instance.Location 3 | 4 | package object expression { 5 | 6 | /** 7 | * Creates an inconclusive result from a message 8 | */ 9 | def inconclusive(e: Expression, l: Location, m: String): Inconclusive = 10 | Inconclusive( Trace( e, Reason( l, m) :: Nil ) ) 11 | 12 | /** 13 | * Creates an inconclusive result from a throwable 14 | */ 15 | def inconclusive(e: Expression, l: Location, t: Throwable): Inconclusive = 16 | inconclusive( e, l, t.getMessage ) 17 | 18 | } 19 | -------------------------------------------------------------------------------- /parser/src/main/scala/hl7/v2/instance/Location.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.instance 2 | 3 | /** 4 | * Class representing the location of an element 5 | */ 6 | case class Location( 7 | eType: EType, 8 | desc: String, 9 | path: String, 10 | line: Int, 11 | column: Int, 12 | uidPath : String 13 | ) { 14 | val prettyString = s"$eType $path ($desc)" 15 | 16 | } 17 | 18 | object Location { 19 | def apply( 20 | eType: EType, 21 | desc: String, 22 | path: String, 23 | line: Int, 24 | column: Int 25 | ) : Location = { 26 | Location(eType,desc,path,line,column,path) 27 | } 28 | } 29 | 30 | 31 | -------------------------------------------------------------------------------- /validation/src/main/scala/hl7/v2/validation/content/ConformanceContext.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.validation.content 2 | 3 | import hl7.v2.instance.Element 4 | 5 | 6 | trait ConformanceContext { 7 | 8 | /** 9 | * Returns the list of constraints defined for the specified element. 10 | */ 11 | def constraintsFor(e: Element): List[Constraint] 12 | 13 | /** 14 | * Returns the list of predicates defined for the specified element. 15 | */ 16 | def predicatesFor(e: Element): List[Predicate] 17 | 18 | def orderIndifferentConstraints(): List[Context] 19 | 20 | def coConstraintsFor(e: Element): List[CoConstraint] 21 | 22 | def coConstraintsF(): VMap[CoConstraint] 23 | } 24 | -------------------------------------------------------------------------------- /validation/src/main/java/hl7/v2/validation/vs/EmptyValueSetLibrary.java: -------------------------------------------------------------------------------- 1 | package hl7.v2.validation.vs; 2 | 3 | public class EmptyValueSetLibrary implements ValueSetLibrary { 4 | 5 | private static EmptyValueSetLibrary ourInstance = new EmptyValueSetLibrary(); 6 | 7 | public static EmptyValueSetLibrary getInstance() { 8 | return ourInstance; 9 | } 10 | 11 | private EmptyValueSetLibrary() {} 12 | 13 | @Override 14 | public Boolean isExcludedFromTheValidation(String id) { 15 | return false; 16 | } 17 | 18 | @Override 19 | public ValueSet get(String id) throws ValueSetNotFoundException { 20 | throw new ValueSetNotFoundException(id); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /validation/src/main/scala/hl7/v2/validation/structure/Validator.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.validation.structure 2 | 3 | import gov.nist.validation.report.Entry 4 | import hl7.v2.instance.Message 5 | 6 | import scala.concurrent.Future 7 | import hl7.v2.validation.report.ConfigurableDetections 8 | 9 | /** 10 | * The message structure validator 11 | * 12 | * @author Salifou Sidi M. Malick 13 | */ 14 | 15 | trait Validator { 16 | 17 | /** 18 | * Checks the message structure and returns the list of problems. 19 | * 20 | * @param m - The message to be checked 21 | * @return - The list of problems 22 | */ 23 | def checkStructure(m: Message)(implicit Detections : ConfigurableDetections): Future[Seq[Entry]] 24 | } 25 | -------------------------------------------------------------------------------- /parser/src/test/scala/hl7/v2/instance/PathSpec.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.instance 2 | 3 | import hl7.v2.instance.Path.isValid 4 | import org.specs2.mutable.Specification 5 | 6 | /** 7 | * @author Salifou Sidi M. Malick 8 | */ 9 | 10 | class PathSpec extends Specification { 11 | 12 | val valid = Seq( "1[1]", "1[2].2[*].3[*]" ) 13 | val invalid = Seq( "1", "0[1]", "1[0]", "1[1].2", "1[1].", "1[1].1", "1[1].x" ) 14 | 15 | "Path.isValid( x: String ) " should { 16 | s"return true for ${valid.mkString("{ ", ", ", " }")}" in { 17 | valid map ( isValid( _ ) === true ) 18 | } 19 | 20 | s"return false for ${invalid.mkString("{ ", ", ", " }")}" in { 21 | invalid map ( isValid(_) === false ) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /profile/src/main/scala/hl7/v2/profile/Range.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.profile 2 | 3 | case class Range(min: Int, max: String) { 4 | assert( min >= 0, s"Range.min must be greater or equal to zero # $this" ) 5 | assert( "*" == max || max.forall( _.isDigit ), s"Invalid Range.max format # $this" ) 6 | assert( if( "*" == max ) true else max.toInt >= min, 7 | s"Range.min must lower than Range.max # $this" ) 8 | 9 | /** 10 | * Returns true if the range includes i 11 | */ 12 | def includes(i: Int): Boolean = i >= min && (max == "*" || i <= max.toInt) 13 | 14 | /** 15 | * Returns if the range is before and not including i 16 | */ 17 | def isBefore(i: Int): Boolean = if(max == "*") false else i > max.toInt 18 | 19 | override def toString = s"$min..$max" 20 | } 21 | -------------------------------------------------------------------------------- /validation/src/main/scala/hl7/v2/validation/content/EmptyConformanceContext.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.validation.content 2 | 3 | import hl7.v2.instance.Element 4 | 5 | object EmptyConformanceContext extends ConformanceContext { 6 | 7 | /** 8 | * Returns the list of constraints defined for the specified element. 9 | */ 10 | override def constraintsFor(e: Element): List[Constraint] = Nil 11 | def coConstraintsF(): VMap[CoConstraint] = VMap.empty[CoConstraint] 12 | /** 13 | * Returns the list of predicates defined for the specified element. 14 | */ 15 | override def predicatesFor(e: Element): List[Predicate] = Nil 16 | 17 | override def orderIndifferentConstraints(): List[Context] = Nil 18 | 19 | override def coConstraintsFor(e: Element): List[CoConstraint] = Nil 20 | } 21 | -------------------------------------------------------------------------------- /validation/src/main/scala/hl7/v2/validation/content/EmptyValidator.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.validation.content 2 | 3 | import gov.nist.validation.report.Entry 4 | import hl7.v2.instance.Message 5 | 6 | import scala.concurrent.ExecutionContext.Implicits.global 7 | import scala.concurrent.Future 8 | 9 | /** 10 | * An empty content validator. 11 | * 12 | * @author Salifou Sidi M. Malick 13 | */ 14 | 15 | trait EmptyValidator extends Validator { 16 | 17 | /** 18 | * The conformance context used by this validator. 19 | */ 20 | val conformanceContext = EmptyConformanceContext 21 | 22 | /** 23 | * Returns an empty list independently from the message. 24 | * @param m - The message to be checked 25 | * @return An empty list 26 | */ 27 | def checkContent(m: Message): Future[Seq[Entry]] = Future { Nil } 28 | } 29 | -------------------------------------------------------------------------------- /validation/src/main/scala/expression/Evaluator.scala: -------------------------------------------------------------------------------- 1 | package expression 2 | 3 | import hl7.v2.instance.{TimeZone, Separators, Element} 4 | import hl7.v2.validation.vs.ValueSetLibrary 5 | import hl7.v2.validation.report.ConfigurableDetections 6 | import hl7.v2.validation.vs.Validator 7 | 8 | trait Evaluator { 9 | 10 | /** 11 | * Evaluates the expression within the specified context 12 | * and returns the result 13 | * @param e - The expression to be evaluated 14 | * @param c - The context node 15 | * @param l - The value set library 16 | * @param s - The message separators 17 | * @param t - The default Time Zone 18 | * @return The evaluation result 19 | */ 20 | def eval(e: Expression, c: Element)(implicit l: ValueSetLibrary, s: Separators, 21 | t: Option[TimeZone], VSValidator : Validator): EvalResult 22 | } -------------------------------------------------------------------------------- /parser/src/test/scala/hl7/v2/instance/EscapeSeqHandlerSpec.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.instance 2 | 3 | import org.scalacheck.{Prop, Gen} 4 | import org.specs2.ScalaCheck 5 | import org.specs2.Specification 6 | 7 | class EscapeSeqHandlerSpec 8 | extends Specification 9 | with ScalaCheck 10 | with EscapeSeqHandler { def is =s2""" 11 | Escape sequence specification 12 | 13 | Escaping and un-escaping a string should return the same string $e1 14 | 15 | """ 16 | 17 | implicit val separators = Separators( '|', '^', '~', '\\', '&', Some('#') ) 18 | 19 | // A separator generator 20 | private def SepGen = Gen.oneOf("|", "^", "~", "\\", "&", "#") 21 | // The test string generator 22 | def gen = Gen.listOfN( 10, Gen.oneOf( SepGen, Gen.alphaStr ) ) 23 | 24 | def e1 = Prop.forAll( gen ) { (l: List[String]) => 25 | val s = l.mkString 26 | unescape( escape( s ) ) === s 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /profile/src/main/scala/hl7/v2/profile/Usage.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.profile 2 | 3 | sealed trait Usage 4 | 5 | object Usage { 6 | 7 | case object R extends Usage { override def toString = "R" } 8 | case object RE extends Usage { override def toString = "RE" } 9 | case object C extends Usage { override def toString = "C" } 10 | case object X extends Usage { override def toString = "X" } 11 | case object O extends Usage { override def toString = "O" } 12 | case object B extends Usage { override def toString = "B" } 13 | case object W extends Usage { override def toString = "W" } 14 | 15 | def fromString( s: String ): Usage = s match { 16 | case "O" => O 17 | case "R" => R 18 | case "C" => C 19 | case "B" => B 20 | case "W" => W 21 | case "X" => X 22 | case "RE" => RE 23 | case _ => throw new Error(s"Invalid usage '${s}'") 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /validation/src/main/java/hl7/v2/validation/vs/ValueSetLibrary.java: -------------------------------------------------------------------------------- 1 | package hl7.v2.validation.vs; 2 | 3 | /** 4 | * The value set library 5 | * 6 | * @author Salifou Sidi M. Malick 7 | */ 8 | public interface ValueSetLibrary { 9 | 10 | /** 11 | * Returns true if the value set with the specified 12 | * id is excluded from the validation 13 | * @param id - The value set if 14 | * @return True if the value set is excluded from the validation 15 | */ 16 | public Boolean isExcludedFromTheValidation(String id); 17 | 18 | /** 19 | * Returns the value set with the specified id 20 | * @param id - The id of the value set 21 | * @return The value set with the specified id 22 | * @throws ValueSetNotFoundException if the value set is not in the library 23 | */ 24 | public ValueSet get(String id) throws ValueSetNotFoundException; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /parser/src/main/scala/hl7/v2/instance/Component.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.instance 2 | 3 | import hl7.v2.profile.{Datatype, Req, Primitive, Composite} 4 | 5 | /** 6 | * Trait representing a component 7 | */ 8 | sealed trait Component extends Element { 9 | def datatype: Datatype 10 | def req: Req 11 | def location: Location 12 | val instance = 1 13 | } 14 | 15 | /** 16 | * Class representing a simple component 17 | */ 18 | case class SimpleComponent( 19 | datatype: Primitive, 20 | req: Req, 21 | location: Location, 22 | value: Value 23 | ) extends Component with Simple 24 | 25 | /** 26 | * Class representing a complex component 27 | */ 28 | case class ComplexComponent ( 29 | datatype: Composite, 30 | req: Req, 31 | location: Location, 32 | children: List[SimpleComponent], 33 | hasExtra: Boolean 34 | ) extends Component with Complex { 35 | 36 | def reqs = datatype.reqs 37 | } 38 | -------------------------------------------------------------------------------- /parser/src/main/scala/hl7/v2/parser/impl/package.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.parser 2 | 3 | package object impl { 4 | 5 | type Line = (Int, String) 6 | 7 | val lineBreak = """\r?\n|\r\n?|\r\n""".r 8 | 9 | def validLinesRegex(implicit fs: Char) = s"^[A-Z]{2}[A-Z0-9](${quote(fs)}.*)*".r 10 | 11 | def quote(c: Char) = java.util.regex.Pattern.quote( c.toString ) 12 | 13 | /** 14 | * Trim the space on right of the string 15 | */ 16 | def trimRight(s: String) = s.replaceAll("\\s+$", "") 17 | 18 | /** 19 | * Splits the string `s' on the character `sep' and returns a tuple of 20 | * (Int, String) where the Int represent the index of the sub string + 'col' 21 | */ 22 | def split(sep: Char, s: String, col: Int): Array[(Int, String)] = { 23 | var column = col 24 | val trimmed = trimRight( s ) 25 | 26 | trimmed.split(sep) map { ss => 27 | val r = column -> ss 28 | column += ss.length + 1 29 | r 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /project/Dependencies.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | 3 | object Dependencies { 4 | 5 | val resolutionRepos = Seq( 6 | "jitpack.io" at "https://jitpack.io", 7 | "Local Maven Repository" at "file://"+Path.userHome.absolutePath+"/.m2/repository" 8 | ) 9 | 10 | val typesafeConfig = "com.typesafe" % "config" % "1.2.1" 11 | val xmlUtil = "gov.nist" % "xml-util" % "2.1.0" 12 | val junit = "junit" % "junit" % "4.11" 13 | val spec2 = "org.specs2" %% "specs2-core" % "4.19.0" 14 | val spec2sc = "org.specs2" %% "specs2-scalacheck" % "4.19.0" 15 | val scalaCheck = "org.scalacheck" %% "scalacheck" % "1.14.0" 16 | val vreport = "com.github.hl7-tools" % "validation-report" % "1.1.0" 17 | val scala_xml = "org.scala-lang.modules" %% "scala-xml" % "1.3.0" 18 | val stringUtils = "org.apache.commons" % "commons-lang3" % "3.4" 19 | } 20 | 21 | -------------------------------------------------------------------------------- /profile/src/main/scala/hl7/v2/profile/XMLDeserializer.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.profile 2 | 3 | import java.io.InputStream 4 | 5 | import hl7.v2.profile.XMLDeserializerHelper.profile 6 | import nist.xml.util.XOMDocumentBuilder 7 | 8 | import scala.util.Try 9 | 10 | /** 11 | * Module to deserialize an HL7 profile from XML 12 | * 13 | * @author Salifou Sidi M. Malick 14 | */ 15 | 16 | object XMLDeserializer { 17 | 18 | /** 19 | * Returns the profile XSD schema as InputStream 20 | */ 21 | // def xsd: InputStream = getClass.getResourceAsStream("/Profile.xsd") 22 | def xsd_name = "/Profile.xsd" 23 | 24 | /** 25 | * Create a profile from XML 26 | * @param xml - The XML file 27 | * @return A future containing the profile object or a failure 28 | */ 29 | def deserialize( xml: InputStream ): Try[Profile] = { 30 | XOMDocumentBuilder.build(xml, getClass.getResourceAsStream("/Profile.xsd")) map { doc => profile( doc.getRootElement ) } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /parser/src/main/scala/hl7/v2/instance/Group.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.instance 2 | 3 | import hl7.v2.profile.{ Group => GM } 4 | 5 | /** 6 | * Class representing a group 7 | */ 8 | case class Group( 9 | model: GM, 10 | instance: Int, 11 | children: List[SegOrGroup]) extends SegOrGroup { 12 | 13 | // The group should contain an element with position = 1 and instance = 1 14 | // require(children exists { c => c.position == 1 && c.instance == 1 }) 15 | 16 | val hasExtra = false //FIXME: A group cannot have extra unless it is the root right ? 17 | 18 | lazy val head: Segment = 19 | children headOption match { 20 | case Some(s: Segment) => s 21 | case Some(g: Group) => g.head 22 | case None => throw new Error(s"The group head is missing. $this") 23 | case Some(x) => throw new Error(s"Unknown Group element '$x'. $this") 24 | } 25 | lazy val location = head.location 26 | .copy(EType.Group, desc = model.name, path = model.name, uidPath = s"${model.name}[$instance]") 27 | } 28 | -------------------------------------------------------------------------------- /validation/src/main/scala/hl7/v2/validation/content/Validator.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.validation.content 2 | 3 | import gov.nist.validation.report.Entry 4 | import hl7.v2.instance.Message 5 | import hl7.v2.validation.vs.ValueSetLibrary 6 | 7 | import scala.concurrent.Future 8 | import hl7.v2.validation.report.ConfigurableDetections 9 | 10 | 11 | /** 12 | * The message content validator 13 | * 14 | * @author Salifou Sidi M. Malick 15 | */ 16 | 17 | trait Validator { 18 | 19 | /** 20 | * The conformance context used by this validator. 21 | */ 22 | def conformanceContext: ConformanceContext 23 | 24 | implicit def valueSetLibrary: ValueSetLibrary 25 | 26 | /** 27 | * Check the message against the constraints defined 28 | * in the constraint manager and returns the report. 29 | * @param m - The message to be checked 30 | * @return The report 31 | */ 32 | def checkContent(m: Message)(implicit Detections : ConfigurableDetections, VSValidator : hl7.v2.validation.vs.Validator): Future[Seq[Entry]] 33 | } -------------------------------------------------------------------------------- /dependencies/xml-util-2.1.0/xml-util-2.1.0.pom: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | gov.nist 5 | xml-util 6 | jar 7 | xml-util 8 | 2.1.0 9 | xml-util 10 | 11 | gov.nist 12 | 13 | 14 | 15 | org.scala-lang 16 | scala-library 17 | 2.13.10 18 | 19 | 20 | xom 21 | xom 22 | 1.3.7 23 | 24 | 25 | -------------------------------------------------------------------------------- /parser/src/main/scala/hl7/v2/instance/Separators.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.instance 2 | 3 | case class Separators( fs: Char, cs: Char, rs: Char, ec: Char, ss: Char, tc: Option[Char] ) { 4 | 5 | /** 6 | * Returns true if the separators are the ones recommended by HL7 7 | */ 8 | def areRecommended = fs == '|' && cs == '^' && rs == '~' && 9 | ec == '\\' && ss == '&' && tc.getOrElse('#') =='#' 10 | 11 | /** 12 | * Returns the list of separators used more than once 13 | */ 14 | def getDuplicates = { val l = toList; l.diff( l.distinct ).distinct } 15 | 16 | /** 17 | * Returns a list containing the separators 18 | */ 19 | def toList = if( tc.isDefined ) List( fs, cs, rs, ec, ss, tc.get ) 20 | else List( fs, cs, rs, ec, ss) 21 | } 22 | 23 | 24 | object Separators { 25 | 26 | def apply(fs: Char, cs: Char, rs: Char, ec: Char, ss: Char): Separators = 27 | Separators(fs, cs, rs, ec, ss, None) 28 | 29 | def apply(fs: Char, cs: Char, rs: Char, ec: Char, ss: Char, 30 | tc: Char): Separators = Separators(fs, cs, rs, ec, ss, Some(tc)) 31 | } 32 | -------------------------------------------------------------------------------- /validation/src/main/scala/expression/StringFormatValidator.scala: -------------------------------------------------------------------------------- 1 | package expression 2 | import hl7.v2.validation.utils.format.utils.LOINCFormat; 3 | import hl7.v2.validation.utils.format.utils.SNOMEDFormat; 4 | 5 | 6 | final case class UnknownStringFormatException(private val message: String = "", private val cause: Throwable = None.orNull) extends Exception(message, cause) 7 | 8 | sealed trait StringType { 9 | def validate(str : String) : Boolean 10 | } 11 | 12 | 13 | object StringType { 14 | 15 | case class LOINC() extends StringType { 16 | override def toString = "LOINC" 17 | override def validate(str : String) : Boolean = { 18 | LOINCFormat.isValid(str) 19 | } 20 | } 21 | 22 | case class SNOMED() extends StringType { 23 | override def toString = "SNOMED" 24 | override def validate(str : String) : Boolean = { 25 | SNOMEDFormat.isValid(str) 26 | } 27 | } 28 | 29 | def fromString( s: String ): StringType = s match { 30 | case "LOINC" => LOINC() 31 | case "SNOMED" => SNOMED() 32 | case _ => throw new UnknownStringFormatException(s"Unknown string format '${s}'") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /validation/src/test/scala/expression/NOTSpec.scala: -------------------------------------------------------------------------------- 1 | package expression 2 | 3 | import expression.EvalResult.{Inconclusive, Fail, Pass} 4 | import org.specs2.Specification 5 | 6 | trait NOTSpec 7 | extends Specification 8 | with Evaluator 9 | with Mocks { 10 | 11 | /* 12 | NOT expression evaluation specifications 13 | NOT should be inconclusive if the underlining expression is inconclusive $notInconclusive 14 | NOT should pass if the underlining expression fail $notPass 15 | NOT should fail if the underlining expression pass $notFail 16 | */ 17 | 18 | private val exp1 = Presence("2[1]") 19 | private val exp2 = Presence("2[2]") 20 | private val exp3 = Presence("1") 21 | 22 | assert( eval(exp1, c2) == Pass ) 23 | assert( eval(exp2, c2).isInstanceOf[Fail] ) 24 | assert( eval(exp3, c2).isInstanceOf[Inconclusive] ) 25 | 26 | def notInconclusive = eval( NOT(exp3), c2 ) === 27 | inconclusive(Presence("1"), c2.location, "Invalid Path '1'") 28 | 29 | def notPass = eval( NOT(exp2), c2 ) === Pass 30 | 31 | def notFail = eval( NOT(exp1), c2 ) === Failures.not( NOT(exp1), c2 ) 32 | } 33 | -------------------------------------------------------------------------------- /validation/src/main/resources/rules/Commons.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /profile/src/test/scala/hl7/v2/profile/XMLSerializerSpec.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.profile 2 | 3 | import java.io.ByteArrayInputStream 4 | 5 | import nist.xml.util.XOMDocumentBuilder 6 | import org.specs2.Specification 7 | 8 | class XMLSerializerSpec extends Specification { def is = s2""" 9 | 10 | This the spec of XML Serializer and Deserializer 11 | 12 | Deserializing a valid XML should be successful $e1 13 | Serializing a valid model should yield a valid XML $e2 14 | 15 | """ 16 | 17 | def e1 = { 18 | val xml = getClass.getResourceAsStream("/Profile.xml") 19 | val profile = XMLDeserializer.deserialize(xml) 20 | profile mustNotEqual null 21 | } 22 | 23 | def e2 = { 24 | val xml = getClass.getResourceAsStream("/Profile.xml") 25 | 26 | val p = XMLDeserializer.deserialize(xml) 27 | p must beSuccessfulTry 28 | 29 | // val serialized = XMLSerializer.serialize(p.get) 30 | // 31 | // val result = { 32 | // val stream = new ByteArrayInputStream( serialized.toString.getBytes("UTF-8")) 33 | // XOMDocumentBuilder.build(stream, getClass.getResourceAsStream("/Profile.xsd")) 34 | // } 35 | // result must beSuccessfulTry 36 | } 37 | 38 | //private def profile(f: Future[Profile]) = Await.result(f, Duration(300, "millis")) 39 | } -------------------------------------------------------------------------------- /validation/src/main/scala/hl7/v2/validation/report/Report.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.validation.report 2 | 3 | import java.util.{HashMap => JHMap, List => JList, Map => JMap} 4 | 5 | import gov.nist.validation.report.Entry 6 | 7 | import scala.jdk.CollectionConverters.SeqHasAsJava 8 | import scala.jdk.CollectionConverters.ListHasAsScala 9 | 10 | 11 | case class Report( 12 | structure: Seq[Entry], 13 | content: Seq[Entry], 14 | vs: JList[Entry] 15 | ) extends gov.nist.validation.report.Report { 16 | 17 | override def getEntries: JMap[String, JList[Entry]] = { 18 | val map = new JHMap[String, JList[Entry]]() 19 | map.put("structure", structure.asJava) 20 | map.put("content", content.asJava) 21 | map.put("value-set", vs) 22 | map 23 | } 24 | 25 | override def toJson: String = 26 | gov.nist.validation.report.impl.JsonObjectMapper.mapper.writeValueAsString(this) 27 | 28 | override def toText: String = 29 | s""" 30 | |\n\n######## structure check: ${structure.size} problems detected.\n 31 | |${ structure map (e => e.toString) mkString "\n" } 32 | |\n\n######## content check: ${content.size} problems detected.\n 33 | |${ content map (e => e.toString) mkString "\n" } 34 | \n\n|######## value set check: ${vs.size} problems detected.\n 35 | |${ vs.asScala map (e => e.toString) mkString "\n" } 36 | """.stripMargin 37 | 38 | } 39 | -------------------------------------------------------------------------------- /validation/src/test/scala/expression/OperatorSpec.scala: -------------------------------------------------------------------------------- 1 | package expression 2 | 3 | import org.specs2.Specification 4 | 5 | import Operator.EQ 6 | import Operator.GE 7 | import Operator.GT 8 | import Operator.LE 9 | import Operator.LT 10 | import Operator.NE 11 | 12 | import hl7.v2.instance.{Number, TimeZone} 13 | 14 | class OperatorSpec extends Specification { def is = s2""" 15 | Operator specification 16 | 17 | Number( "1" ) LT Number( "2" ) should be true $e1 18 | Number( "2" ) GT Number( "1" ) should be true $e2 19 | Number( "1" ) LE Number( "1" ) should be true $e3 20 | Number( "1" ) GE Number( "1" ) should be true $e4 21 | Number( "1" ) EQ Number( "1" ) should be true $e5 22 | Number( "1" ) NE Number( "2" ) should be true $e6 23 | """ 24 | 25 | implicit val dtz: Option[TimeZone] = None 26 | 27 | def e1 = LT.eval( Number( "1" ), Number( "2" ) ) must beSuccessfulTry.withValue(true) 28 | 29 | def e2 = GT.eval( Number( "2" ), Number( "1" ) ) must beSuccessfulTry.withValue(true) 30 | 31 | def e3 = LE.eval( Number( "1" ), Number( "1" ) ) must beSuccessfulTry.withValue(true) 32 | 33 | def e4 = GE.eval( Number( "1" ), Number( "1" ) ) must beSuccessfulTry.withValue(true) 34 | 35 | def e5 = EQ.eval( Number( "1" ), Number( "1" ) ) must beSuccessfulTry.withValue(true) 36 | 37 | def e6 = NE.eval( Number( "1" ), Number( "2" ) ) must beSuccessfulTry.withValue(true) 38 | } -------------------------------------------------------------------------------- /parser/src/test/scala/hl7/v2/instance/Mocks.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.instance 2 | 3 | import hl7.v2.profile._ 4 | 5 | /** 6 | * @author Salifou Sidi M. Malick 7 | */ 8 | 9 | trait Mocks { 10 | 11 | trait Default { 12 | val reqs = List[Req]() 13 | val location = Location(null ,"desc ...", "Path", -1, -1) 14 | /*val qProps = QProps(QType.DT, "id", "name")*/ 15 | val hasExtra = false 16 | val req = Req(-1, "", Usage.O, None, None, None, Nil) 17 | } 18 | 19 | case class S(override val position: Int, instance: Int, value: Value) 20 | extends Simple with Default 21 | 22 | case class C(override val position: Int, instance: Int, children: List[Element] ) 23 | extends Complex with Default 24 | 25 | val s0 = S( 4, 1, Text("S0") ) 26 | 27 | val c0 = C(2,1, s0 :: Nil) 28 | 29 | val c1 = C( 2, 3, ( 1 to 3 ).toList map { i => S( 1, i, Text(s"S1$i") ) } ) 30 | 31 | val c2 = C(1,1, s0 :: c0 :: c1 :: Nil) 32 | 33 | def elementsDescription = 34 | """s0 -> Simple(4, 1, Text(41) ) 35 | 36 | c0 -> Complex( position= 2, instance= 1, No children) 37 | 38 | c1 -> Complex( position= 2, instance= 3) 39 | 1[1] -> Simple( value=Text(S11) ) 40 | 1[2] -> Simple( value=Text(S12) ) 41 | 1[3] -> Simple( value=Text(S13) ) 42 | 43 | c2 -> Complex( position= 1, instance= 1) 44 | 2[1] -> c0 45 | 2[3] -> c1""" 46 | } -------------------------------------------------------------------------------- /profile/src/main/scala/hl7/v2/profile/Req.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.profile 2 | 3 | case class Req( 4 | position: Int, 5 | description: String, 6 | usage: Usage, 7 | cardinality: Option[Range], 8 | length: Option[Range], 9 | confLength: Option[String], 10 | vsSpec: List[ValueSetSpec], 11 | confRange : Option[Range], 12 | hide: Boolean) 13 | 14 | object Req { 15 | def apply( 16 | position: Int, 17 | description: String, 18 | usage: Usage, 19 | cardinality: Option[Range], 20 | length: Option[Range], 21 | confLength: Option[String], 22 | vsSpec: List[ValueSetSpec]): Req = { 23 | Req(position, description, usage, cardinality, length, confLength, vsSpec, None, false) 24 | } 25 | def apply( 26 | position: Int, 27 | description: String, 28 | usage: Usage, 29 | cardinality: Option[Range], 30 | length: Option[Range], 31 | confLength: Option[String], 32 | vsSpec: List[ValueSetSpec], 33 | confRange : Option[Range]): Req = { 34 | Req(position, description, usage, cardinality, length, confLength, vsSpec, confRange, false) 35 | } 36 | def apply( 37 | position: Int, 38 | description: String, 39 | usage: Usage, 40 | cardinality: Option[Range], 41 | length: Option[Range], 42 | confLength: Option[String], 43 | vsSpec: List[ValueSetSpec], 44 | hide : Boolean): Req = { 45 | Req(position, description, usage, cardinality, length, confLength, vsSpec, None, hide) 46 | } 47 | } -------------------------------------------------------------------------------- /parser/src/main/scala/hl7/v2/instance/Value.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.instance 2 | 3 | import hl7.v2.profile.Primitive 4 | 5 | /** 6 | * @author Salifou Sidi M. Malick 7 | */ 8 | 9 | sealed trait Value extends { 10 | 11 | /** 12 | * The raw string value as defined in the message 13 | */ 14 | def raw: String 15 | 16 | /** 17 | * Returns true if the value is equal to HL7 Null ("") 18 | */ 19 | def isNull: Boolean = raw == Value.NULL 20 | } 21 | 22 | case class Number(raw: String) extends Value 23 | case class Date(raw: String) extends Value 24 | case class Text(raw: String) extends Value 25 | case class FText(raw: String) extends Value 26 | case class Time(raw: String) extends Value 27 | case class DateTime(raw: String) extends Value 28 | 29 | case class TimeZone(raw: String) extends AnyVal 30 | 31 | /** 32 | * Value companion object 33 | */ 34 | object Value { 35 | 36 | val NULL = "\"\"" 37 | 38 | /** 39 | * Create the value from string depending on the data type 40 | */ 41 | //TODO: Update make this generic so the proper type will be returned 42 | def apply(datatype: Primitive, raw: String): Value = 43 | datatype.name match { 44 | case "NM" => Number(raw) 45 | case "DT" => Date(raw) 46 | case "TM" => Time(raw) 47 | case "SI" => Number(raw) 48 | case "DTM"=> DateTime(raw) 49 | case "FT" => FText(raw) 50 | case _ => Text(raw) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /parser/src/main/scala/hl7/v2/instance/Field.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.instance 2 | 3 | import hl7.v2.profile.{Datatype, Req, Composite, Primitive, Varies} 4 | 5 | /** 6 | * Trait representing a field 7 | */ 8 | sealed trait Field extends Element { 9 | def datatype: Datatype 10 | def req: Req 11 | def location: Location 12 | def instance: Int 13 | } 14 | 15 | /** 16 | * Class representing a simple field 17 | */ 18 | case class SimpleField( 19 | datatype: Primitive, 20 | req: Req, 21 | location: Location, 22 | instance: Int, 23 | value: Value 24 | ) extends Field with Simple 25 | 26 | /** 27 | * Class representing Unresolved field 28 | */ 29 | case class UnresolvedField( 30 | datatype: Varies, 31 | req: Req, 32 | location: Location, 33 | instance: Int, 34 | value: Value 35 | ) extends Field 36 | 37 | 38 | /** 39 | * Class representing a complex field 40 | */ 41 | case class ComplexField( 42 | datatype: Composite, 43 | req: Req, 44 | location: Location, 45 | instance: Int, 46 | children: List[Component], 47 | hasExtra: Boolean 48 | ) extends Field with Complex { 49 | 50 | def reqs = datatype.reqs 51 | } 52 | 53 | /** 54 | * Class representing a complex field with a null value 55 | */ 56 | case class NULLComplexField ( 57 | datatype: Composite, 58 | req: Req, 59 | location: Location, 60 | instance: Int 61 | ) extends Field with Complex { 62 | def reqs = datatype.reqs 63 | def children = Nil 64 | def hasExtra = false 65 | } 66 | -------------------------------------------------------------------------------- /parser/src/main/scala/hl7/v2/instance/package.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2 2 | 3 | package object instance { 4 | 5 | val rs = '~' // Field repetition separator 6 | val fs = '|' // Field separator 7 | val cs = '^' // Component separator 8 | val ss = '&' // Sub component separator 9 | val ec = '\\' // Escape character 10 | val tc = '#' // Truncation character 11 | 12 | /** 13 | * Trim the space on right of the string 14 | */ 15 | def trimRight(s: String) = s.replaceAll("\\s+$", "") 16 | 17 | /** 18 | * Splits the string `s' on the character `sep' and returns a tuple of (Int, String) 19 | * where the Int represent the index of the sub string + `col' 20 | */ 21 | def split(sep: Char, s: String, col: Int): Array[(Int, String)] = { 22 | var column = col 23 | //val trimmed = trimRight( s ) 24 | s.split( sep ) map { ss => val r = column -> ss; column += ss.length + 1; r } 25 | } 26 | 27 | // def pad[T]( a: Array[T], x: T, len: Int ) = a.padTo( len, x ) 28 | // 29 | sealed trait EType 30 | object EType{ 31 | //case object Message extends EType { override def toString = "Message" } 32 | case object Group extends EType { override def toString = "Group" } 33 | case object Segment extends EType { override def toString = "Segment" } 34 | case object Field extends EType { override def toString = "Field" } 35 | case object Component extends EType { override def toString = "Component" } 36 | case object SubComponent extends EType { override def toString = "Sub-Component" } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /validation/src/test/resources/rules/CContext.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | Conformance context sample 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | If OBX.3 = XX then OBX.2 = CWE and OBX.5 valued from VS1 22 | 23 | 30 | 31 | </NOT> 32 | <AND> 33 | <PlainText Path="2[1]" Text="CWE" IgnoreCase="false"/> 34 | <ValueSet Path="5[1]" ValueSetID="VS1" BindingStrength="R" BindingLocation="1:4"/> 35 | </AND> 36 | </OR> 37 | </Assertion> 38 | </Constraint> 39 | </ByName> 40 | </Segment> 41 | 42 | </Constraints> 43 | 44 | </ConformanceContext> -------------------------------------------------------------------------------- /parser/src/test/scala/hl7/v2/parser/impl/ParserSpecHelper.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.parser.impl 2 | 3 | import hl7.v2.instance.{Counter, Field, Line, Location, SegOrGroup, Segment, Separators} 4 | import hl7.v2.profile.{Range, SegRefOrGroup, Usage, ValueSetSpec, Req => Requirement, SegmentRef => SM} 5 | import org.specs2.{ScalaCheck, Specification} 6 | 7 | trait ParserSpecHelper extends Specification with ScalaCheck with DefaultParser { 8 | 9 | def check(tc: (String, List[SegOrGroup]), profile: List[SegRefOrGroup]) = { 10 | val stack = PreProcessor.splitOnMSH(tc._1)._2 11 | implicit val s = Separators( '|', '^', '~', '\\', '&', '#') 12 | implicit val ctr = Counter(scala.collection.mutable.Map[String, Int]()) 13 | val result = processChildren(profile, stack.map(x => Line(x._1, x._2)))._1.reverse 14 | result must containTheSameElementsAs(tc._2) 15 | } 16 | 17 | def Req( 18 | position: Int, 19 | description: String, 20 | usage: Usage, 21 | cardinality: Option[Range], 22 | length: Option[Range], 23 | confLength: Option[String], 24 | vsSpec: List[ValueSetSpec], 25 | confRange: Option[Range], 26 | hide: Boolean, 27 | csValueBackwardsCompatible: Option[String]): Requirement = Requirement(position, description, usage, cardinality, length, confLength, vsSpec, confRange, hide) 28 | 29 | def SegmentInstance( 30 | model: SM, 31 | location: Location, 32 | instance: Int, 33 | children: List[Field], 34 | hasExtra: Boolean, 35 | rawMessageValue: String 36 | ): Segment = Segment(model, location, instance, children, hasExtra) 37 | 38 | } 39 | -------------------------------------------------------------------------------- /validation/src/test/scala/expression/PresenceSpec.scala: -------------------------------------------------------------------------------- 1 | package expression 2 | 3 | import expression.EvalResult.Pass 4 | import hl7.v2.instance.{Element, Query} 5 | import Query.query 6 | import org.specs2.Specification 7 | 8 | import scala.util.{Failure, Success} 9 | 10 | trait PresenceSpec extends Specification with Evaluator with Mocks { 11 | 12 | /* 13 | PresenceSpec 14 | Presence evaluation should be inconclusive if the path is invalid $presencePathInvalid 15 | Presence evaluation should be inconclusive if the path is unreachable $presencePathUnreachable 16 | Presence should pass if the path is populated $presencePathPopulated 17 | Presence should fail if the path is not populated $presencePathNotPopulated 18 | */ 19 | 20 | def presencePathInvalid = { 21 | val p = Presence("1") 22 | eval( p, c0) === inconclusive( p, c0.location, "Invalid Path '1'") 23 | } 24 | 25 | def presencePathUnreachable = { 26 | val p = Presence("2[2]") 27 | eval( p, s0 ) === inconclusive(p, s0.location, "Unreachable Path '2[2]'") 28 | } 29 | 30 | def presencePathPopulated = { 31 | assert(isPopulated(c2, "2[1]")) 32 | eval(Presence("2[1]"), c2) === Pass 33 | } 34 | 35 | def presencePathNotPopulated = { 36 | assert(!isPopulated(c2, "2[2]")) 37 | eval(Presence("2[2]"), c2) === Failures.presence(c2, Presence("2[2]")) 38 | } 39 | 40 | private def isPopulated(c: Element, path: String): Boolean = 41 | query(c, path) match { 42 | case Success(l) => l.nonEmpty 43 | case Failure(f) => false 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /validation/src/main/scala/hl7/v2/validation/vs/domain.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.validation.vs 2 | 3 | import java.util.{List => JList} 4 | 5 | import scala.jdk.CollectionConverters.SeqHasAsJava 6 | 7 | /** 8 | * Trait representing a code usage 9 | */ 10 | sealed trait CodeUsage { def description: String } 11 | object CodeUsage { 12 | case object R extends CodeUsage { val description = "Required" } 13 | case object P extends CodeUsage { val description = "Permitted" } 14 | case object E extends CodeUsage { val description = "Excluded" } 15 | } 16 | 17 | /** 18 | * Class representing a code 19 | */ 20 | case class Code(value: String, description: String, usage: CodeUsage, codeSys: String) 21 | 22 | /** 23 | * Trait representing a value set extensibility 24 | */ 25 | sealed trait Extensibility 26 | object Extensibility { 27 | case object Open extends Extensibility 28 | case object Closed extends Extensibility 29 | } 30 | 31 | /** 32 | * Trait representing a value set stability 33 | */ 34 | sealed trait Stability 35 | object Stability { 36 | case object Static extends Stability 37 | case object Dynamic extends Stability 38 | } 39 | 40 | /** 41 | * Class representing a value set 42 | */ 43 | case class ValueSet( 44 | id: String, 45 | extensibility: Option[Extensibility], 46 | stability: Option[Stability], 47 | codes: List[Code] 48 | ) { 49 | 50 | def isEmpty: Boolean = codes.isEmpty 51 | 52 | def getCodes(value: String): JList[Code] = 53 | codes.filter(c => c.value == value).asJava 54 | 55 | def contains(value: String) = codes.exists( c => c.value == value ) 56 | 57 | def codesList() = { 58 | codes.asJava 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /validation/src/main/scala/expression/EvalResult.scala: -------------------------------------------------------------------------------- 1 | package expression 2 | 3 | import hl7.v2.instance.Location 4 | 5 | /** 6 | * Trait representing the result of an expression evaluation 7 | */ 8 | sealed trait EvalResult 9 | 10 | object EvalResult { 11 | 12 | /** 13 | * A successful expression evaluation result 14 | */ 15 | case object Pass extends EvalResult 16 | 17 | 18 | /** 19 | * A failed expression evaluation result 20 | */ 21 | sealed trait Failure extends EvalResult { 22 | def stack: List[Trace] 23 | } 24 | 25 | case class Fail( stack: List[Trace]) extends Failure 26 | case class FailPlugin( stack: List[Trace], message : List[String]) extends Failure 27 | case class EvalData( result : EvalResult, found : String, expected : String) 28 | /** 29 | * An inconclusive expression evaluation result 30 | * 31 | * An expression evaluation is inconclusive if an error (exception) 32 | * occurred during the evaluation. This usually happens when : 33 | * 1) Path resolution fails or returns a complex element 34 | * while a simple element is expected 35 | * 2) Invalid conversion between 'Value' classes. 36 | * Example: attempting to convert Text(X2) to a number 37 | * 3) An operation is not supported. Example: Date(20140326) LT Time(010100) 38 | */ 39 | case class Inconclusive( trace: Trace ) extends EvalResult 40 | 41 | /** 42 | * A trace of a failed or inconclusive expression evaluation 43 | */ 44 | case class Trace( expression: Expression, reasons: List[Reason] ) 45 | 46 | /** 47 | * The reason of a failed or inconclusive expression evaluation 48 | */ 49 | case class Reason( location: Location, message: String ) 50 | 51 | } 52 | -------------------------------------------------------------------------------- /validation/src/main/scala/expression/Operator.scala: -------------------------------------------------------------------------------- 1 | package expression 2 | 3 | import hl7.v2.instance.{TimeZone, Value} 4 | import hl7.v2.instance.util.ValueComparator.compareTo 5 | 6 | import scala.util.Try 7 | 8 | /** 9 | * @author Salifou Sidi M. Malick <salifou.sidi@gmail.com> 10 | */ 11 | 12 | sealed trait Operator { 13 | def eval(v1: Value, v2: Value)(implicit dtz: Option[TimeZone]): Try[Boolean] 14 | } 15 | 16 | object Operator { 17 | 18 | case object LT extends Operator { 19 | def eval(v1: Value, v2: Value)(implicit dtz: Option[TimeZone]) = 20 | compareTo(v1, v2) map ( _ < 0 ) 21 | override def toString = "lower than" 22 | } 23 | 24 | case object GT extends Operator { 25 | def eval(v1: Value, v2: Value)(implicit dtz: Option[TimeZone]) = 26 | compareTo(v1, v2) map ( _ > 0 ) 27 | override def toString = "greater than" 28 | } 29 | 30 | case object LE extends Operator { 31 | def eval(v1: Value, v2: Value)(implicit dtz: Option[TimeZone]) = 32 | compareTo(v1, v2) map ( _ <= 0 ) 33 | override def toString = "lower or equal to" 34 | } 35 | 36 | case object GE extends Operator { 37 | def eval(v1: Value, v2: Value)(implicit dtz: Option[TimeZone]) = 38 | compareTo(v1, v2) map ( _ >= 0 ) 39 | override def toString = "greater or equal to" 40 | } 41 | 42 | case object EQ extends Operator { 43 | def eval(v1: Value, v2: Value)(implicit dtz: Option[TimeZone]) = 44 | compareTo(v1, v2) map ( _ == 0 ) 45 | override def toString = "equal to" 46 | } 47 | 48 | case object NE extends Operator { 49 | def eval(v1: Value, v2: Value)(implicit dtz: Option[TimeZone]) = 50 | compareTo(v1, v2) map ( _ != 0 ) 51 | override def toString = "different from" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /parser/src/test/scala/hl7/v2/instance/util/ValueConversionHelpersSpec.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.instance.util 2 | 3 | import hl7.v2.instance.util.ValueConversionHelpers._ 4 | import org.scalacheck.{Prop, Gen} 5 | import org.specs2.ScalaCheck 6 | import org.specs2.mutable.Specification 7 | 8 | import scala.util.Success 9 | 10 | class ValueConversionHelpersSpec extends Specification with ScalaCheck { 11 | 12 | def sGen = Gen.oneOf("+", "-") 13 | def hGen = Gen.oneOf( 1 to 23 ) 14 | def mGen = Gen.oneOf( 1 to 59 ) 15 | def ssGen = Gen.oneOf( 1 to 9999) 16 | 17 | "timZoneToMilliSeconds" should { 18 | 19 | "Return a failure if the format is invalid" in { 20 | Seq("0000", "+00", "-0") map { s => 21 | timeZoneToMilliSeconds(s) must beFailedTry 22 | } 23 | } 24 | 25 | s"Returns valid result if the time zone is valid" in { 26 | Prop.forAll(sGen, hGen, mGen) { (s: String, h: Int, m: Int) => 27 | val tz = f"$s$h%02d$m%02d" 28 | val r = 1000 * (3600 * h + 60 * m ) 29 | timeZoneToMilliSeconds(tz) === Success( if( s == "-") -r else r ) 30 | } 31 | } 32 | 33 | } 34 | 35 | "timToMilliSeconds" should { 36 | 37 | "Return a failure if the format is invalid" in { 38 | Seq("0", "24") map { s => 39 | timeToMilliSeconds(s) must beFailedTry 40 | } 41 | } 42 | 43 | s"Returns valid result if the time is valid" in { 44 | Prop.forAll(hGen, mGen, mGen, ssGen, sGen, hGen, mGen) { 45 | (hh: Int, mm: Int, ss: Int, ssss: Int, s: String, th: Int, tm: Int ) => 46 | val t = f"$hh%02d$mm%02d$ss%02d.$ssss%04d$s$th%02d$tm%02d" 47 | val tz = 1000 * (3600 * th + 60 * tm ) 48 | val r = ssss + 1000 * (ss + 60 * mm + 3600 * hh) 49 | timeToMilliSeconds(t) === Success( if( s == "-") r - tz else r + tz ) 50 | } 51 | } 52 | 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /parser/src/test/scala/hl7/v2/instance/util/ValueComparatorSpec.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.instance.util 2 | 3 | import org.specs2.{ScalaCheck, Specification} 4 | import ValueComparator._ 5 | import hl7.v2.instance._ 6 | 7 | 8 | class ValueComparatorSpec extends Specification with ScalaCheck { def is = s2""" 9 | 10 | Number Comparison Specification 11 | Comparing number with a Date should fail $n1 12 | Comparing number with a Time should fail $n2 13 | Comparing number with a DateTime should fail $n3 14 | Comparing number with another comparable should fail if one format is invalid $n4 15 | Comparing number with another comparable should succeed if both formats are valid $n5 16 | 17 | 18 | """ 19 | 20 | implicit val dtz = Some( TimeZone("+0000") ) 21 | 22 | def n1 = compareTo(Number("1"), Date("2014")) must 23 | beFailedTry.withThrowable[Exception]{ 24 | "\\QNumber(1) is not comparable to Date(2014).\\E" 25 | } 26 | 27 | def n2 = compareTo(Number("1"), Time("00")) must 28 | beFailedTry.withThrowable[Exception]{ 29 | s"\\QNumber(1) is not comparable to ${Time("00")}.\\E" 30 | } 31 | 32 | def n3 = compareTo(Number("1"), DateTime("2014")) must 33 | beFailedTry.withThrowable[Exception]{ 34 | s"\\QNumber(1) is not comparable to ${DateTime("2014")}.\\E" 35 | } 36 | 37 | def n4 = compareTo(Number("1"), Number("1.0E2")) must 38 | beFailedTry.withThrowable[Exception] { 39 | "\\Q1.0E2 is not a valid Number. The format should be: [+|-]digits[.digits]\\E" 40 | } 41 | 42 | def n5 = prop { (i1: Int, i2: Int) => 43 | (i1 compareTo i2) === compareTo(Number(s"$i1"), Number(s"$i2")).get and 44 | compareTo(Number(s"$i1"), Number(s"$i2")) === compareTo(Number(s"$i1"), Text(s"$i2")) 45 | } 46 | 47 | //TODO: TO BE COMPLETED 48 | 49 | } 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HL7 Validation Library 2 | 3 | ## Dependencies 4 | 5 | The project has two external dependencies : 6 | 7 | ### xml-util 8 | 9 | * __GitHub Repository__ : https://github.com/usnistgov/xml-util 10 | * __Jar File Location__ : /dependencies/xml-util-2.1.0/xml-util-2.1.0.jar 11 | * __POM File Location__ : /dependencies/xml-util-2.1.0/xml-util-2.1.0.pom 12 | * __Maven__ : 13 | ``` 14 | <groupId>gov.nist</groupId> 15 | <artifactId>xml-util</artifactId> 16 | <version>2.1.0</version> 17 | ``` 18 | 19 | ### validation-report 20 | 21 | * __GitHub Repository__ : https://github.com/usnistgov/validation-report 22 | * __Jar File Location__ : /dependencies/validation-report-1.1.0/validation-report-1.1.0.jar 23 | * __POM File Location__ : /dependencies/validation-report-1.1.0/validation-report-1.1.0.pom 24 | * __Maven__ : 25 | ``` 26 | <groupId>com.github.hl7-tools</groupId> 27 | <artifactId>validation-report</artifactId> 28 | <version>1.1.0</version> 29 | ``` 30 | 31 | ## Installing JARs to local M2 32 | 1) Open terminal 33 | 2) Navigate to "dependencies" folder from root of the project (v2-validation) 34 | 3) Install xml-utils, run command : 35 | ``` 36 | mvn install:install-file 37 | -Dfile=xml-util-2.1.0/xml-util-2.1.0.jar 38 | -DpomFile=xml-util-2.1.0/xml-util-2.1.0.pom 39 | ``` 40 | 4) Install validation-report, run command : 41 | ``` 42 | mvn install:install-file 43 | -Dfile=validation-report-1.1.0/validation-report-1.1.0.jar 44 | -DpomFile=validation-report-1.1.0/validation-report-1.1.0.pom 45 | ``` 46 | 47 | ## Build : 48 | 49 | 1) First install Scala Build Tool (sbt) : http://www.scala-sbt.org/ 50 | 2) Make sure you have installed the external dependencies to your maven repository 51 | 3) Run "sbt" command on project root directory 52 | 4) To run tests you can use "test" command 53 | 5) To build the tool you can use "compile" command 54 | -------------------------------------------------------------------------------- /validation/src/test/scala/expression/ORSpec.scala: -------------------------------------------------------------------------------- 1 | package expression 2 | 3 | import expression.EvalResult.{Inconclusive, Fail, Pass} 4 | import org.specs2.Specification 5 | 6 | trait ORSpec 7 | extends Specification 8 | with Evaluator 9 | with Mocks { 10 | 11 | /* 12 | OR expression evaluation specifications 13 | OR should be inconclusive if the first expression is inconclusive $orFirstInconclusive 14 | OR should pass if the first expression passes $orFirstPass 15 | If the first expression fails 16 | OR should be inconclusive if the second is inconclusive $orFirstFailsSecondInconclusive 17 | OR should pass if the second passes $orFirstFailsSecondPasses 18 | OR should fail if the second fails $orFirstFailsSecondFails 19 | */ 20 | 21 | private val exp1 = Presence("2[1]") 22 | private val exp2 = Presence("2[2]") 23 | private val exp3 = Presence("1") 24 | 25 | assert( eval(exp1, c2) == Pass ) 26 | assert( eval(exp2, c2).isInstanceOf[Fail] ) 27 | assert( eval(exp3, c2).isInstanceOf[Inconclusive] ) 28 | 29 | def orFirstInconclusive = Seq(exp1, exp2, exp3) map { e => 30 | eval( OR(exp3, e), c2 ) === inconclusive(exp3, c2.location, "Invalid Path '1'") 31 | } 32 | 33 | def orFirstPasses = Seq(exp1, exp2, exp3) map { e => 34 | eval( OR(exp1, e), c2 ) === Pass 35 | } 36 | 37 | def orFirstFailsSecondInconclusive = eval( OR(exp2, exp3), c2 ) === 38 | inconclusive(exp3, c2.location, "Invalid Path '1'") 39 | 40 | def orFirstFailsSecondPasses = eval( OR(exp2, exp1), c2 ) === Pass 41 | 42 | def orFirstFailsSecondFails = eval( OR(exp2, exp2), c2 ) === { 43 | val f = eval(exp2, c2).asInstanceOf[Fail] 44 | Failures.or(OR(exp2, exp2), c2, f, f) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /validation/src/test/scala/expression/PluginSpec.scala: -------------------------------------------------------------------------------- 1 | package expression 2 | 3 | import expression.EvalResult._ 4 | import org.specs2.Specification 5 | 6 | trait PluginSpec extends Specification with Evaluator with Mocks { 7 | 8 | /* 9 | PluginSpec 10 | Plugin execution should be inconclusive if an exception is raised when evaluation the assertion $pluginNoFunction 11 | Plugin execution should pass if the assertion evaluation is true $pluginPass 12 | Plugin execution should fail if the assertion evaluation is false $pluginFail 13 | Plugin execution should pass if the assertion evaluation returns a NULL list $pluginCustomPassNull 14 | Plugin execution should pass if the assertion evaluation returns an empty list $pluginCustomPassEmpty 15 | Plugin execution should fail if the assertion evaluation returns non-empty list $pluginCustomPassEmpty 16 | Plugin execution should be inconclusive if the implementation contains multiple matching methods $pluginMulti 17 | */ 18 | 19 | def pluginPass = eval( Plugin( "expression.PluginSuccess" ), c1 ) === Pass 20 | 21 | def pluginFail = eval( Plugin( "expression.PluginFailure" ), c1 ) === Fail(Nil) 22 | 23 | def pluginCustomPassNull = eval( Plugin( "expression.PluginCustomSuccessNull" ), c1 ) === Pass 24 | 25 | def pluginCustomPassEmpty = eval( Plugin( "expression.PluginCustomSuccessEmpty" ), c1 ) === Pass 26 | 27 | def pluginCustomFail = eval( Plugin( "expression.PluginCustomFailure" ), c1 ) === FailPlugin(Nil, List("FAIL")) 28 | 29 | def pluginInconclusive = eval( Plugin( "xxx" ), c1 ) must beLike { case Inconclusive(_) => ok } 30 | 31 | def pluginMulti = eval( Plugin( "expression.PluginFailureMulti" ), c1 ) must beLike { case Inconclusive(_) => ok } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /validation/src/test/scala/expression/IsNULLSpec.scala: -------------------------------------------------------------------------------- 1 | package expression 2 | 3 | import expression.EvalResult.Pass 4 | import hl7.v2.instance.{Element, Query} 5 | import Query.query 6 | import org.specs2.Specification 7 | 8 | import scala.util.{Failure, Success} 9 | 10 | trait IsNULLSpec extends Specification with Evaluator with Mocks { 11 | 12 | /* 13 | IsNULL evaluation should be inconclusive if the path is invalid $nullPathInvalid 14 | IsNuLL evaluation should be inconclusive if the path is unreachable $nullPathUnreachable 15 | IsNull evaluation should fail if the path is not populated $nullPathNotPopulated 16 | If the path is populated 17 | IsNull evaluation should pass if the path points to a field and the field is Null $nullPathNullField 18 | IsNull evaluation should fail if the path points to a field and the field is NOT Null $nullPathNotNullField 19 | IsNull evaluation should fail if the path DOES NOT point to a field. $nullPathNotField 20 | */ 21 | 22 | 23 | def nullPathInvalid = { 24 | val p = Presence("1") 25 | eval( p, c0) === inconclusive( p, c0.location, "Invalid Path '1'") 26 | } 27 | 28 | def presencePathUnreachable = { 29 | val p = Presence("2[2]") 30 | eval( p, s0 ) === inconclusive(p, s0.location, "Unreachable Path '2[2]'") 31 | } 32 | 33 | def presencePathPopulated = { 34 | assert(isPopulated(c2, "2[1]")) 35 | eval(Presence("2[1]"), c2) === Pass 36 | } 37 | 38 | def presencePathNotPopulated = { 39 | assert(!isPopulated(c2, "2[2]")) 40 | eval(Presence("2[2]"), c2) === Failures.presence(c2, Presence("2[2]")) 41 | } 42 | 43 | private def isPopulated(c: Element, path: String): Boolean = 44 | query(c, path) match { 45 | case Success(l) => l.nonEmpty 46 | case Failure(f) => false 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /parser/src/main/scala/hl7/v2/instance/Element.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.instance 2 | 3 | import hl7.v2.profile.Req 4 | 5 | //============================================================================== 6 | // Trait representing a generic element 7 | //============================================================================== 8 | 9 | trait Element { 10 | 11 | /** 12 | * The requirements of the element 13 | */ 14 | def req: Req 15 | 16 | /** 17 | * The location of the element 18 | */ 19 | def location: Location 20 | 21 | /** 22 | * The position of the element 23 | */ 24 | def position: Int = req.position 25 | 26 | /** 27 | * The instance number of the element 28 | */ 29 | def instance: Int 30 | 31 | } 32 | 33 | //============================================================================== 34 | // Trait representing a complex element 35 | //============================================================================== 36 | 37 | trait Complex extends Element { 38 | 39 | /** 40 | * Returns true if the complex element has extra children 41 | */ 42 | def hasExtra: Boolean 43 | 44 | /** 45 | * The children of the complex element 46 | */ 47 | def children: List[Element] 48 | 49 | /** 50 | * The requirements of the children. The requirements are 51 | * sorted by the position of the child element. The head 52 | * of the list is the requirement for the first position 53 | * and the last, the requirement for the last position. 54 | */ 55 | def reqs: List[Req] 56 | } 57 | 58 | //============================================================================== 59 | // Trait representing a simple element 60 | //============================================================================== 61 | 62 | trait Simple extends Element { 63 | 64 | /** 65 | * The value of the simple element 66 | * @see hl7.v2.instance.Value 67 | */ 68 | def value: Value 69 | } 70 | -------------------------------------------------------------------------------- /validation/src/test/scala/expression/ANDSpec.scala: -------------------------------------------------------------------------------- 1 | package expression 2 | 3 | import expression.EvalResult.{Inconclusive, Pass, Fail} 4 | import org.specs2.Specification 5 | 6 | trait ANDSpec 7 | extends Specification 8 | with Evaluator 9 | with Mocks { 10 | 11 | /* 12 | AND expression evaluation specifications 13 | AND should be inconclusive if the first expression is inconclusive $andFirstInconclusive 14 | AND should fail in the first expression fails $andFirstFails 15 | If the first expression passes 16 | AND should be inconclusive if the second is inconclusive $andFirstPassesSecondInconclusive 17 | AND should pass if the second passes $andFirstPassesSecondPasses 18 | AND should fail is the second fails $andFirstPassesSecondFails 19 | */ 20 | 21 | private val exp1 = Presence("2[1]") 22 | private val exp2 = Presence("2[2]") 23 | private val exp3 = Presence("1") 24 | 25 | assert( eval(exp1, c2) == Pass ) 26 | assert( eval(exp2, c2).isInstanceOf[Fail] ) 27 | assert( eval(exp3, c2).isInstanceOf[Inconclusive] ) 28 | 29 | def andFirstInconclusive = Seq(exp1, exp2, exp3) map { e => 30 | eval( AND(exp3, e), c2 ) === inconclusive(Presence("1"), c2.location, "Invalid Path '1'") 31 | } 32 | 33 | def andFirstFails = Seq(exp1, exp2, exp3) map { e => 34 | val f = eval(exp2, c2).asInstanceOf[Fail] 35 | eval( AND(exp2, e), c2 ) === Failures.and(AND(exp2, e), c2, f) 36 | } 37 | 38 | def andFirstPassesSecondInconclusive = eval( AND(exp1, exp3), c2 ) === 39 | inconclusive(Presence("1"), c2.location, "Invalid Path '1'") 40 | 41 | def andFirstPassesSecondPasses = eval( AND(exp1, exp1), c2 ) === Pass 42 | 43 | def andFirstPassesSecondFails = { 44 | val f = eval(exp2, c2).asInstanceOf[Fail] 45 | eval( AND(exp1, exp2), c2 ) === Failures.and(AND(exp1, exp2), c2, f) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /profile/src/main/scala/hl7/v2/profile/ValueSetSpec.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.profile 2 | 3 | import scala.util.{Failure, Success, Try} 4 | 5 | /** 6 | * Class representing a value set specification 7 | */ 8 | case class ValueSetSpec( 9 | valueSetId: String, 10 | bindingStrength: Option[BindingStrength], 11 | bindingLocation: Option[BindingLocation] 12 | ) 13 | 14 | /** 15 | * Trait defining a value set binding strength 16 | */ 17 | sealed trait BindingStrength 18 | 19 | /** 20 | * Value set binding strength companion object 21 | */ 22 | object BindingStrength { 23 | case object R extends BindingStrength { val description = "Required" } 24 | case object S extends BindingStrength { val description = "Suggested" } 25 | case object U extends BindingStrength { val description = "Undetermined" } 26 | 27 | def apply(s: String): Try[BindingStrength] = 28 | s match { 29 | case "R" => Success( BindingStrength.R ) 30 | case "S" => Success( BindingStrength.S ) 31 | case "U" => Success( BindingStrength.U ) 32 | case _ => Failure( new Exception( s"Invalid BindingStrength '$s'") ) 33 | } 34 | } 35 | 36 | /** 37 | * Trait defining a value set binding location 38 | */ 39 | sealed trait BindingLocation { def asString: String } 40 | 41 | /** 42 | * Value set binding location companion object 43 | */ 44 | object BindingLocation { 45 | 46 | case class Position(value: Int) extends BindingLocation { 47 | lazy val asString = s"$value" 48 | } 49 | case class XOR(position1: Int, position2: Int) extends BindingLocation { 50 | lazy val asString = s"$position1:$position2" 51 | } 52 | 53 | val pos = """\s*(\d+)\s*""".r 54 | val xor = """\s*(\d+)\s*:\s*(\d+)\s*""".r 55 | 56 | def apply(s: String): Try[BindingLocation] = s match { 57 | case pos(p) => Success( Position(p.toInt) ) 58 | case xor(p1, p2) => Success( XOR(p1.toInt, p2.toInt) ) 59 | case _ => Failure( new Exception(s"Invalid Binding Location '$s'") ) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /validation/src/main/java/hl7/v2/validation/vs/EnhancedEntry.java: -------------------------------------------------------------------------------- 1 | package hl7.v2.validation.vs; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import gov.nist.validation.report.Entry; 7 | import gov.nist.validation.report.Trace; 8 | 9 | public class EnhancedEntry implements Entry { 10 | 11 | private Entry e; 12 | private boolean ok; 13 | 14 | public EnhancedEntry(Entry e, boolean p){ 15 | this.e = e; 16 | this.setOk(p); 17 | } 18 | 19 | 20 | @Override 21 | public String getCategory() { 22 | // TODO Auto-generated method stub 23 | return e.getCategory(); 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | // TODO Auto-generated method stub 29 | return e.toString(); 30 | } 31 | 32 | @Override 33 | public String getClassification() { 34 | // TODO Auto-generated method stub 35 | return e.getClassification(); 36 | } 37 | 38 | @Override 39 | public int getColumn() { 40 | // TODO Auto-generated method stub 41 | return e.getColumn(); 42 | } 43 | 44 | @Override 45 | public String getDescription() { 46 | // TODO Auto-generated method stub 47 | return e.getDescription(); 48 | } 49 | 50 | @Override 51 | public int getLine() { 52 | // TODO Auto-generated method stub 53 | return e.getLine(); 54 | } 55 | 56 | @Override 57 | public Map<String, Object> getMetaData() { 58 | // TODO Auto-generated method stub 59 | return e.getMetaData(); 60 | } 61 | 62 | @Override 63 | public String getPath() { 64 | // TODO Auto-generated method stub 65 | return e.getPath(); 66 | } 67 | 68 | @Override 69 | public List<Trace> getStackTrace() { 70 | // TODO Auto-generated method stub 71 | return e.getStackTrace(); 72 | } 73 | 74 | @Override 75 | public String toJson() throws Exception { 76 | // TODO Auto-generated method stub 77 | return e.toJson(); 78 | } 79 | 80 | @Override 81 | public String toText() { 82 | // TODO Auto-generated method stub 83 | return e.toText(); 84 | } 85 | 86 | public boolean isOk() { 87 | return ok; 88 | } 89 | 90 | public void setOk(boolean ok) { 91 | this.ok = ok; 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /validation/src/test/scala/expression/IMLYSpec.scala: -------------------------------------------------------------------------------- 1 | package expression 2 | 3 | import org.specs2.Specification 4 | import expression.EvalResult.{Inconclusive, Fail, Pass} 5 | /** 6 | * Created by hossam.tamri on 7/16/15. 7 | */ 8 | trait IMPLYSpec extends Specification 9 | with Evaluator 10 | with Mocks { 11 | 12 | /* 13 | IMPLY expression evaluation specifications 14 | IMPLY should be inconclusive if the first expression is inconclusive $implyFirstInconclusive 15 | IMPLY should pass if first expression fails $implyFirstFails 16 | If the first expression passes 17 | IMPLY should be inconclusive if the second is inconclusive $implyFirstPassesSecondInconclusive 18 | IMPLY should fail if the second fails $implyFirstPassesSecondFails 19 | IMPLY should pass if the second passes $implyFirstPassesSecondPasses 20 | 21 | */ 22 | 23 | private val exp1 = Presence("2[1]") 24 | private val exp2 = Presence("2[2]") 25 | private val exp3 = Presence("1") 26 | 27 | assert(eval(exp1, c2) == Pass) 28 | assert(eval(exp2, c2).isInstanceOf[Fail]) 29 | assert(eval(exp3, c2).isInstanceOf[Inconclusive]) 30 | 31 | def implyFirstInconclusive = Seq(exp1, exp2, exp3) map { e => 32 | eval(IMPLY(exp3, e), c2) === inconclusive(exp3, c2.location, "Invalid Path '1'") 33 | } 34 | 35 | // If first passes 36 | def implyFirstPassesSecondInconclusive = eval(IMPLY(exp1, exp3), c2) === 37 | inconclusive(exp3, c2.location, "Invalid Path '1'") 38 | 39 | def implyFirstPassesSecondFails = eval(IMPLY(exp1, exp2), c2) === { 40 | val f1 = eval(NOT(exp1), c2).asInstanceOf[Fail] 41 | val f2 = eval(exp2, c2).asInstanceOf[Fail] 42 | Failures.or(OR(NOT(exp1), exp2), c2, f1,f2) 43 | } 44 | 45 | def implyFirstPassesSecondPasses = eval(IMPLY(exp1, exp1), c2) === Pass 46 | 47 | // If first fails 48 | def implyFirstFails = Seq(exp1, exp2, exp3) map { e => 49 | eval( IMPLY(exp2, e), c2 ) === Pass 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /validation/src/main/scala/hl7/v2/validation/content/Constraint.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.validation.content 2 | 3 | import expression.Expression 4 | 5 | import scala.beans.BeanProperty 6 | import com.typesafe.config.ConfigFactory 7 | 8 | case class Reference( 9 | @BeanProperty chapter: String, 10 | @BeanProperty section: String, 11 | @BeanProperty page: String, 12 | @BeanProperty url: String, 13 | @BeanProperty source: String, 14 | @BeanProperty generatedBy: String, 15 | @BeanProperty referencePath: String, 16 | @BeanProperty testDataCategorization: String 17 | ) 18 | 19 | 20 | /** 21 | * A coConstraint 22 | */ 23 | case class CoConstraint( 24 | description: String, 25 | comments: String, 26 | constraints: List[PlainCoConstraint] 27 | ) 28 | 29 | case class PlainCoConstraint( 30 | key: Expression, 31 | assertions: List[Expression] 32 | ) 33 | 34 | /** 35 | * A constraint describes an assertion that shall always be verified 36 | */ 37 | case class Constraint( 38 | id: String, 39 | reference: Option[Reference], 40 | classification : Option[Classification], 41 | strength: Option[ConstraintStrength], 42 | description: String, 43 | assertion: Expression 44 | ) 45 | 46 | sealed trait ConstraintStrength 47 | object ConstraintStrength { 48 | case class SHALL() extends ConstraintStrength 49 | case class SHOULD() extends ConstraintStrength 50 | } 51 | 52 | 53 | /** 54 | * Class representing a predicate. 55 | */ 56 | case class Predicate( 57 | target: String, 58 | trueUsage: PredicateUsage, 59 | falseUsage: PredicateUsage, 60 | reference: Option[Reference], 61 | description: String, 62 | condition: Expression 63 | ) 64 | 65 | sealed trait Classification 66 | object Classification { 67 | case class W() extends Classification 68 | case class A() extends Classification 69 | } 70 | 71 | /** 72 | * Trait representing allowed values for predicate true and false attributes 73 | */ 74 | sealed trait PredicateUsage 75 | object PredicateUsage { 76 | case object R extends PredicateUsage 77 | case object RE extends PredicateUsage 78 | case object X extends PredicateUsage 79 | case object O extends PredicateUsage 80 | } -------------------------------------------------------------------------------- /parser/src/main/scala/hl7/v2/instance/Query.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.instance 2 | 3 | import scala.util.{Failure, Try} 4 | 5 | /** 6 | * @author Salifou Sidi M. Malick <salifou.sidi@gmail.com> 7 | */ 8 | 9 | object Query { 10 | 11 | /** 12 | * Query the context for the specified path and attempt 13 | * to cast the result as a sequence of simple. 14 | */ 15 | def queryAsSimple(context: Element, path: String): Try[List[Simple]] = { 16 | query( context, path ) flatMap asSimple 17 | } 18 | 19 | 20 | /** 21 | * Query the context for the specified path. 22 | */ 23 | def query( context: Element, path: String ): Try[List[Element]] = Try { 24 | if(path.equals(".")) List(context) 25 | else if( Path.isValid(path) ) _query(context, path) 26 | else throw new Error(s"Invalid Path '$path'") 27 | } 28 | 29 | @throws[Error]("if the path is invalid or unreachable") 30 | private def _query(context: Element, path: String): List[Element] = path match { 31 | case Path.extractor(position, instance, subPath) => 32 | context match { 33 | case s: Simple => throw new Error(s"Unreachable Path '$path'") 34 | case c: Complex => 35 | val list = children( c, position, instance ) 36 | if( subPath == null ) list 37 | else list.foldLeft( List[Element]() ) { (acc, child) => 38 | acc ++ _query(child, subPath) 39 | } 40 | } 41 | case _ => throw new Error(s"Invalid Path '$path'") 42 | } 43 | 44 | /** 45 | * Convert a sequence of `Element' to a sequence of `Simple' element 46 | */ 47 | private def asSimple ( l: List[Element] ): Try[List[Simple]] = Try { 48 | l map { case s: Simple => s; case _ => throw new MatchError(()) } 49 | } orElse Failure( new Error("Path resolution returned at least one complex element") ) 50 | 51 | /** 52 | * Returns the children at the specified position and instance 53 | */ 54 | private 55 | def children(c: Complex, position: String, instance: String): List[Element] = 56 | if( "*" == instance ) 57 | c.children filter ( _.position == position.toInt ) 58 | else c.children.filter { cc => 59 | cc.position == position.toInt && cc.instance == instance.toInt 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /validation/src/main/scala/hl7/v2/validation/Utils.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.validation 2 | 3 | import hl7.v2.instance._ 4 | import hl7.v2.profile 5 | import hl7.v2.profile.Req 6 | 7 | object Utils { 8 | 9 | def defaultLocation(c: Complex, oneLevelPath: String): Option[Location] = { 10 | val position = oneLevelPath takeWhile( _ != '[' ) 11 | c.reqs find( _.position.toString == position ) map { defaultLocation(c, _) } 12 | } 13 | 14 | /** 15 | * Creates and returns the location of the pathPart 16 | */ 17 | // Computes the default location ... the uglyness is due to poor and 18 | // late requirements specification 19 | def defaultLocation(e: Complex, r: Req): Location = 20 | e match { 21 | case m: Message => 22 | val (et, pp) = m.model.structure.filter( _.req == r ) match { 23 | case profile.Group(_, name, _, _) :: xs => (EType.Group, name) 24 | case profile.SegmentRef(_, ref) :: xs => (EType.Segment, ref.name) 25 | case Nil => throw new Error("Default Location not found for " + e.location.prettyString) 26 | } 27 | e.location.copy(et, desc=r.description, path=pp, uidPath=s"$pp[1]") 28 | case g: Group => 29 | val (et, pp) = g.model.structure.filter( _.req == r ) match { 30 | case profile.Group(_, name, _, _) :: xs => (EType.Group, name) 31 | case profile.SegmentRef(_, ref) :: xs => (EType.Segment, ref.name) 32 | case Nil => throw new Error("Default Location not found for " + e.location.prettyString) 33 | } 34 | e.location.copy(et, desc=r.description, path=pp, uidPath=s"$pp[1]") 35 | case s: Segment => 36 | e.location.copy(EType.Field, desc=r.description, 37 | path=s"${e.location.path}-${r.position}", 38 | uidPath=s"${e.location.uidPath}-${r.position}[1]") 39 | case f: Field => 40 | e.location.copy(EType.Component, desc=r.description, 41 | path=s"${e.location.path}.${r.position}", 42 | uidPath=s"${e.location.uidPath}.${r.position}") 43 | case c: Component => 44 | c.location.copy(EType.SubComponent, desc=r.description, 45 | path=s"${c.location.path}.${r.position}", 46 | uidPath=s"${e.location.uidPath}.${r.position}") 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /dependencies/validation-report-1.1.0/validation-report-1.1.0.pom: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <project xmlns="http://maven.apache.org/POM/4.0.0" 3 | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 | xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 5 | <modelVersion>4.0.0</modelVersion> 6 | 7 | <groupId>com.github.hl7-tools</groupId> 8 | <artifactId>validation-report</artifactId> 9 | <version>1.1.0</version> 10 | 11 | <description> 12 | This project describes a validation report API, an abstract representation of a 13 | report entry and various utilities for transforming and manipulating the report. 14 | </description> 15 | 16 | <developers> 17 | <developer> 18 | <name>Salifou Sidi M. Malick</name> 19 | </developer> 20 | </developers> 21 | 22 | <properties> 23 | <finalName>${project.artifactId}</finalName> 24 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 25 | <java.version>1.8</java.version> 26 | <jackson.version>2.13.4</jackson.version> 27 | <junit.version>4.8.1</junit.version> 28 | </properties> 29 | 30 | <build> 31 | <finalName>${finalName}</finalName> 32 | <plugins> 33 | <plugin> 34 | <groupId>org.apache.maven.plugins</groupId> 35 | <artifactId>maven-compiler-plugin</artifactId> 36 | <version>3.2</version> 37 | <configuration> 38 | <source>${java.version}</source> 39 | <target>${java.version}</target> 40 | </configuration> 41 | </plugin> 42 | </plugins> 43 | </build> 44 | 45 | <dependencies> 46 | 47 | <dependency> 48 | <groupId>com.fasterxml.jackson.core</groupId> 49 | <artifactId>jackson-databind</artifactId> 50 | <version>${jackson.version}</version> 51 | </dependency> 52 | 53 | <dependency> 54 | <groupId>junit</groupId> 55 | <artifactId>junit</artifactId> 56 | <version>${junit.version}</version> 57 | <scope>test</scope> 58 | </dependency> 59 | 60 | </dependencies> 61 | 62 | </project> -------------------------------------------------------------------------------- /validation/src/main/scala/expression/domain.scala: -------------------------------------------------------------------------------- 1 | package expression 2 | 3 | import hl7.v2.instance.Value 4 | import hl7.v2.profile.ValueSetSpec 5 | 6 | sealed trait Expression 7 | 8 | case class Presence( path: String ) extends Expression 9 | 10 | case class PlainText( path: String, text: String, ignoreCase: Boolean, atLeastOnce : Boolean = false, notPresentBehavior : String = "PASS") extends Expression 11 | 12 | case class Format( path: String, pattern: String, atLeastOnce : Boolean = false, notPresentBehavior : String = "PASS") extends Expression 13 | 14 | case class NumberList( path: String, csv: List[Double], atLeastOnce : Boolean = false, notPresentBehavior : String = "PASS") extends Expression 15 | 16 | case class StringList( path: String, csv: List[String], atLeastOnce : Boolean = false, notPresentBehavior : String = "PASS") extends Expression 17 | 18 | case class SimpleValue( path: String, operator: Operator, value: Value, atLeastOnce : Boolean = false, notPresentBehavior : String = "PASS" ) extends Expression 19 | 20 | case class PathValue( path1: String, operator: Operator, path2: String, notPresentBehavior : String = "PASS" ) extends Expression 21 | 22 | 23 | case class isNULL(path: String) extends Expression 24 | 25 | // Combination expressions 26 | case class AND( exp1: Expression, exp2: Expression ) extends Expression 27 | 28 | case class OR( exp1: Expression, exp2: Expression ) extends Expression 29 | 30 | case class NOT( exp: Expression ) extends Expression 31 | 32 | //x ⊕ y = (x ∨ y) ∧ ¬(x ∧ y) 33 | case class XOR( exp1: Expression, exp2: Expression ) extends Expression 34 | 35 | //x → y = ¬x ∨ y 36 | case class IMPLY( exp1: Expression, exp2: Expression ) extends Expression 37 | 38 | case class FORALL( list: Expression* ) extends Expression 39 | 40 | case class EXIST( list: Expression* ) extends Expression 41 | 42 | case class Plugin( clazz: String ) extends Expression 43 | 44 | case class SetId(path: String) extends Expression 45 | 46 | case class IZSetId(parent: String, element : String) extends Expression 47 | 48 | case class ValueSet(path: String, spec: ValueSetSpec, notPresentBehavior : String = "PASS") extends Expression 49 | 50 | case class StringFormat(path: String, format: String, atLeastOnce : Boolean = false, notPresentBehavior : String = "PASS") extends Expression 51 | -------------------------------------------------------------------------------- /validation/src/test/scala/expression/FORALLSpec.scala: -------------------------------------------------------------------------------- 1 | package expression 2 | 3 | import expression.EvalResult.{Inconclusive, Fail, Pass} 4 | import org.specs2.Specification 5 | 6 | /** 7 | * Created by hossam.tamri on 7/16/15. 8 | */ 9 | trait FORALLSpec extends Specification 10 | with Evaluator 11 | with Mocks { 12 | 13 | /* 14 | FORALL expression evaluation specifications 15 | FORALL should be inconclusive if one of the expressions is inconclusive $forallInconclusive 16 | FORALL should fail if one of the expressions fails $forallOneFails 17 | FORALL should fail if many of the expressions fails $forallManyFail 18 | FORALL should pass if all the expressions pass $forallAllPass 19 | */ 20 | 21 | private val exp1 = Presence("2[1]") 22 | private val exp2 = Presence("2[2]") 23 | private val exp3 = Presence("1") 24 | private val exp22 = Presence("5[5]") 25 | 26 | assert(eval(exp1, c2) == Pass) 27 | assert(eval(exp22, c2).isInstanceOf[Fail]) 28 | assert(eval(exp2, c2).isInstanceOf[Fail]) 29 | assert(eval(exp3, c2).isInstanceOf[Inconclusive]) 30 | 31 | def forallInconclusive = Seq(exp1, exp2, exp3) map { e => 32 | eval(FORALL(exp3, e, exp1), c2) === inconclusive(exp3, c2.location, "Invalid Path '1'") 33 | } 34 | 35 | def forallOneFails = { 36 | val f = eval(exp2,c2).asInstanceOf[Fail] 37 | eval(FORALL(exp2, exp1,exp1), c2) === Failures.forall(FORALL(exp2, exp1,exp1),c2,f) and 38 | eval(FORALL(exp1, exp2,exp1), c2) === Failures.forall(FORALL(exp1, exp2,exp1),c2,f) and 39 | eval(FORALL(exp1, exp1,exp2), c2) === Failures.forall(FORALL(exp1, exp1,exp2),c2,f) 40 | } 41 | 42 | def forallManyFail = { 43 | val f1 = eval(exp2, c2).asInstanceOf[Fail] 44 | val f2 = eval(exp22, c2).asInstanceOf[Fail] 45 | eval(FORALL(exp2, exp2, exp2), c2) === Failures.forall(FORALL(exp2, exp2,exp2),c2,f1) and 46 | eval(FORALL(exp1, exp2, exp22), c2) === Failures.forall(FORALL(exp1, exp2,exp22),c2,f1) and 47 | eval(FORALL(exp1, exp22, exp2), c2) === Failures.forall(FORALL(exp1, exp22,exp2),c2,f2) and 48 | eval(FORALL(exp22, exp22, exp22), c2) === Failures.forall(FORALL(exp22, exp22,exp22),c2,f2) 49 | } 50 | 51 | def forallAllPass = eval(FORALL(exp1, exp1, exp1), c2) === Pass 52 | 53 | } -------------------------------------------------------------------------------- /validation/src/test/scala/expression/EXISTSpec.scala: -------------------------------------------------------------------------------- 1 | package expression 2 | 3 | import expression.EvalResult.{Inconclusive, Fail, Pass} 4 | import org.specs2.Specification 5 | 6 | /** 7 | * Created by hossam.tamri on 7/16/15. 8 | */ 9 | trait EXISTSpec extends Specification 10 | with Evaluator 11 | with Mocks { 12 | 13 | /* 14 | EXIST expression evaluation specifications 15 | EXIST should be inconclusive if one of the expressions is inconclusive $existInconclusive 16 | EXIST should fail if all the expressions fails $existAllFail 17 | EXIST should pass if one of the expressions passes $existOnePasses 18 | EXIST should pass if many of the expressions pass $existManyPass 19 | */ 20 | 21 | private val exp1 = Presence("2[1]") 22 | private val exp2 = Presence("2[2]") 23 | private val exp3 = Presence("1") 24 | private val exp22 = Presence("5[5]") 25 | 26 | assert(eval(exp1, c2) == Pass) 27 | assert(eval(exp22, c2).isInstanceOf[Fail]) 28 | assert(eval(exp2, c2).isInstanceOf[Fail]) 29 | assert(eval(exp3, c2).isInstanceOf[Inconclusive]) 30 | 31 | def existInconclusive = Seq(exp1, exp2, exp3) map { e => 32 | eval(EXIST(exp3, e, exp1), c2) === inconclusive(exp3, c2.location, "Invalid Path '1'") 33 | } 34 | 35 | def existOnePasses = eval(EXIST(exp1, exp2, exp2), c2) === Pass and 36 | eval(EXIST(exp2, exp1, exp2), c2) === Pass and 37 | eval(EXIST(exp2, exp2, exp1), c2) === Pass 38 | 39 | def existAllFail = { 40 | val f1 = eval(exp2, c2).asInstanceOf[Fail] 41 | val f2 = eval(exp22, c2).asInstanceOf[Fail] 42 | eval(EXIST(exp2, exp2, exp2), c2) === Failures.exist(EXIST(exp2, exp2, exp2),c2,Fail(f1.stack:::f1.stack:::f1.stack)) and 43 | eval(EXIST(exp22, exp22, exp22), c2) === Failures.exist(EXIST(exp22, exp22, exp22),c2,Fail(f2.stack:::f2.stack:::f2.stack)) and 44 | eval(EXIST(exp2, exp22, exp22), c2) === Failures.exist(EXIST(exp2, exp22, exp22),c2,Fail(f1.stack:::f2.stack:::f2.stack)) and 45 | eval(EXIST(exp22, exp2, exp2), c2) === Failures.exist(EXIST(exp22, exp2, exp2),c2,Fail(f2.stack:::f1.stack:::f1.stack)) 46 | } 47 | 48 | def existManyPass = eval(EXIST(exp2, exp1, exp1), c2) === Pass and 49 | eval(EXIST(exp1, exp2, exp1), c2) === Pass and 50 | eval(EXIST(exp1, exp1, exp2), c2) === Pass 51 | 52 | } -------------------------------------------------------------------------------- /validation/src/main/java/hl7/v2/validation/utils/format/utils/LOINCFormat.java: -------------------------------------------------------------------------------- 1 | package hl7.v2.validation.utils.format.utils; 2 | 3 | public class LOINCFormat { 4 | 5 | public static boolean isValid(String code) { 6 | if (!code.matches("\\d{1,5}-\\d")) { 7 | return false; 8 | } 9 | String extract = code.substring(0, code.indexOf("-")); 10 | String checkDigit = mod10(extract); 11 | String loinc = String.format("%s-%s", extract, checkDigit); 12 | return code.equals(loinc); 13 | } 14 | 15 | /** 16 | * Mod10 algorithm for LOINC 17 | * 18 | * @param code 19 | * @return 20 | */ 21 | private static String mod10(String code) { 22 | if (!code.matches("\\d+")) { 23 | return null; 24 | } 25 | if (code.length() > 5) { 26 | return null; 27 | } 28 | // if length < 5, add leading "0" 29 | StringBuffer input = new StringBuffer(code); 30 | while (input.length() < 5) { 31 | input.insert(0, "0"); 32 | } 33 | 34 | // 1. Using the number 12345, assign positions to the digits, from right 35 | // to left. 36 | 37 | // 2. Take the odd digit positions counting from the right (1st, 3rd, 38 | // 5th, etc.) 39 | StringBuffer odd = new StringBuffer(); 40 | for (int i = 0; 2 * i < input.length(); i++) { 41 | odd.insert(0, input.charAt(2 * i)); 42 | } 43 | 44 | // 3.Multiply by 2. 45 | int odd2 = Integer.parseInt(odd.toString()) * 2; 46 | 47 | // 4. Take the even digit positions starting from the right (2nd, 4th, 48 | // etc.). 49 | StringBuffer even = new StringBuffer(); 50 | for (int i = 0; 2 * i + 1 < input.length(); i++) { 51 | even.insert(0, input.charAt(2 * i + 1)); 52 | } 53 | 54 | // 5.Append (4) to the front of the results of (3). 55 | even.append(odd2); 56 | 57 | // 6. Add the digits of (5) together. 58 | double add = 0; 59 | for (int i = 0; i < even.length(); i++) { 60 | add = add + Integer.parseInt(even.substring(i, i + 1)); 61 | } 62 | 63 | // 7. Find the next highest multiple of 10. 64 | double multiple = Math.ceil(add / 10) * 10; 65 | 66 | // 8. Subtract (6) from (7). 67 | Long result = Math.round(multiple - add); 68 | 69 | return result.toString(); 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /parser/src/main/scala/hl7/v2/instance/util/ValueConversionHelpers.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.instance.util 2 | 3 | import hl7.v2.instance.TimeZone 4 | 5 | import scala.util.{Success, Failure, Try} 6 | 7 | /** 8 | * Provides conversion functions for different value classes 9 | */ 10 | object ValueConversionHelpers { 11 | 12 | import hl7.v2.instance.util.ValueFormatCheckers._ 13 | 14 | /** 15 | * Converts number of days to milliseconds 16 | * @param n - The number of days 17 | * @return The number of days in milliseconds 18 | */ 19 | def daysToMilliSeconds(n: Int): Long = n * 86400000 20 | 21 | /** 22 | * Converts the time zone to milli seconds 23 | * @param s - The time zone as string 24 | * @return A Success or a Failure if the format is invalid 25 | */ 26 | def timeZoneToMilliSeconds(s: String): Try[Long] = 27 | checkTimeZoneFormat( s ) map { tz => 28 | val s = tz take 1 29 | val h = (tz drop 1 take 2).toLong 30 | val m = (tz drop 3 ).toLong 31 | val r = 1000 * ( 3600 * h + 60 * m ) 32 | if( "-" == s ) -r else r 33 | } 34 | 35 | /** 36 | * Converts the time to milli seconds by using UTC when Time Zone is missing 37 | * @param s - The time as string 38 | * @return A Success or Failure if the time format is invalid 39 | */ 40 | def timeToMilliSeconds(s: String): Try[Long] = 41 | checkTimeFormat(s) map { ts => 42 | val(tm, tzs) = ts span ( c => c != '+' && c != '-' ) 43 | val tz = if (tzs.isEmpty) 0 else timeZoneToMilliSeconds(tzs).get 44 | 45 | val hh = (tm take 2).toLong 46 | val mm = tm drop 2 take 2 match { case "" => 0 case x => x.toLong } 47 | val ss = tm drop 4 take 2 match { case "" => 0 case x => x.toLong } 48 | val ms = ( tm drop 7 padTo(4, '0') ).toLong 49 | val r = ms + 1000 * (ss + 60 * mm + 3600 * hh) 50 | r + tz 51 | } 52 | 53 | /** 54 | * Converts the time to milli seconds by using dtz as the fallback 55 | * @param s - The time as string 56 | * @return A Success or Failure if the time format is invalid or 57 | * if the time zone is not defined and no default is provide. 58 | */ 59 | def timeToMilliSeconds(s: String, dtz: Option[TimeZone]): Try[Long] = { 60 | val(tm, tzs) = s span ( c => c != '+' && c != '-' ) 61 | defaultTZ(tzs, dtz) flatMap { tz => timeToMilliSeconds(s"$tm$tz") } 62 | } 63 | 64 | private def defaultTZ(s: String, o: Option[TimeZone]): Try[String] = 65 | (s, o) match { 66 | case ("", None) => 67 | Failure(new Exception("Time Zone is missing and no default is set.")) 68 | case ("", Some(x)) => Success(x.raw) 69 | case ( x, _ ) => Success(x) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /validation/src/test/scala/expression/XORSpec.scala: -------------------------------------------------------------------------------- 1 | package expression 2 | 3 | import expression.EvalResult.{Inconclusive, Fail, Pass} 4 | import org.specs2.Specification 5 | 6 | /** 7 | * Created by hossam.tamri on 7/16/15. 8 | */ 9 | trait XORSpec extends Specification 10 | with Evaluator 11 | with Mocks { 12 | 13 | /* 14 | XOR expression evaluation specifications 15 | XOR should be inconclusive if the first expression is inconclusive $xorFirstInconclusive 16 | If the first expression passes 17 | XOR should be inconclusive if the second is inconclusive $xorFirstPassesSecondInconclusive 18 | XOR should pass if the second fails $xorFirstPassesSecondFails 19 | XOR should fail if the second passes $xorFirstPassesSecondPasses 20 | If the first expression fails 21 | XOR should be inconclusive if the second is inconclusive $xorFirstFailsSecondInconclusive 22 | XOR should pass if the second passes $xorFirstFailsSecondPasses 23 | XOR should fail if the second fails $xorFirstFailsSecondFails 24 | */ 25 | 26 | private val exp1 = Presence("2[1]") 27 | private val exp2 = Presence("2[2]") 28 | private val exp3 = Presence("1") 29 | 30 | assert( eval(exp1, c2) == Pass ) 31 | assert( eval(exp2, c2).isInstanceOf[Fail] ) 32 | assert( eval(exp3, c2).isInstanceOf[Inconclusive] ) 33 | 34 | def xorFirstInconclusive = Seq(exp1, exp2, exp3) map { e => 35 | eval( XOR(exp3, e), c2 ) === inconclusive(exp3, c2.location, "Invalid Path '1'") 36 | } 37 | 38 | // If first passes 39 | def xorFirstPassesSecondInconclusive = eval(XOR(exp1,exp3),c2) === 40 | inconclusive(exp3, c2.location, "Invalid Path '1'") 41 | 42 | def xorFirstPassesSecondFails = eval(XOR(exp1,exp2),c2) === Pass 43 | 44 | def xorFirstPassesSecondPasses = eval( XOR(exp1, exp1), c2 ) === { 45 | val firstHand = OR(exp1,exp1) 46 | val secondHand = NOT(AND(exp1,exp1)) 47 | val f = eval(secondHand, c2).asInstanceOf[Fail] 48 | Failures.and(AND(firstHand,secondHand), c2, f) 49 | } 50 | 51 | // If first fails 52 | def xorFirstFailsSecondInconclusive = eval( XOR(exp2, exp3), c2 ) === 53 | inconclusive(exp3, c2.location, "Invalid Path '1'") 54 | 55 | def xorFirstFailsSecondPasses = eval( XOR(exp2, exp1), c2 ) === Pass 56 | 57 | def xorFirstFailsSecondFails = eval( XOR(exp2, exp2), c2 ) === { 58 | val firstHand = OR(exp2,exp2) 59 | val secondHand = NOT(AND(exp2,exp2)) 60 | val f = eval(firstHand, c2).asInstanceOf[Fail] 61 | Failures.and(AND(firstHand,secondHand), c2, f) 62 | } 63 | 64 | 65 | 66 | 67 | } 68 | -------------------------------------------------------------------------------- /validation/src/test/scala/expression/FormatSpec.scala: -------------------------------------------------------------------------------- 1 | package expression 2 | 3 | import expression.EvalResult.Pass 4 | import hl7.v2.instance.Query._ 5 | import hl7.v2.instance.Text 6 | import org.specs2.Specification 7 | 8 | import scala.util.Success 9 | 10 | trait FormatSpec extends Specification with Evaluator with Mocks { 11 | 12 | /* 13 | FormatSpec 14 | Format evaluation should succeed if the path is not populated $formatPathNotPopulated 15 | Format should pass if the value match the pattern $formatMatch 16 | Format should fail if the value doesn't match the pattern $formatNoMatch 17 | If the path is valued to multiple elements 18 | Format should pass if one of the elements matches the pattern and AtLeastOnce = True $formatAtLeastOnceT 19 | Format should fail if one of the elements doesn't match the pattern and AtLeastOnce = False $formatAtLeastOnceF 20 | Format evaluation should fail If not present behavior is FAIL and no element is found $formatNoElmFAIL 21 | Format evaluation should be inconclusive If not present behavior is INCONCLUSIVE and no element is found $formatNoElmINC 22 | Format evaluation should pass If not present behavior is PASS and no element is found $formatNoElmPASS 23 | */ 24 | 25 | //c1.4[1] is not populated 26 | assert( queryAsSimple(c1, "4[1]") == Success(Nil) ) 27 | def formatPathNotPopulated = eval( Format("4[1]", "xx"), c1 ) === Pass 28 | 29 | def formatNoElmFAIL = { 30 | val f = Format("4[1]", "xx", false, "FAIL") 31 | eval(f, c1) === Failures.notPresentBehaviorFail(f, f.path, c1) 32 | } 33 | def formatNoElmINC = { 34 | val f = Format("4[1]", "xx", false, "INCONCLUSIVE") 35 | eval(f, c1) === Failures.notPresentBehaviorInconclusive(f, f.path, c1) 36 | } 37 | def formatNoElmPASS = { 38 | val f = Format("4[1]", "xx", false, "PASS") 39 | eval(f, c1) === Pass 40 | } 41 | 42 | // The following value will be used in the next tests 43 | private val `c1.3[1]` = queryAsSimple(c1, "3[1]").get.head 44 | assert( `c1.3[1]`.value == Text("S3") ) 45 | 46 | def formatMatch = eval( Format("3[1]", "[A-Z0-9]+"), c1 ) === Pass 47 | 48 | def formatNoMatch = { 49 | val e = Format("3[1]", "[a-z0-9]+") 50 | eval( e, c1 ) === Failures.format(e, `c1.3[1]` :: Nil ) 51 | } 52 | 53 | assert( queryAsSimple(c1, "1[*]").isSuccess && queryAsSimple(c1, "1[*]").get.size > 1) 54 | def formatAtLeastOnceF = { 55 | val e = Format("1[*]", "[A-Z0-9]+2", false) 56 | val `c1.1[3]` = queryAsSimple(c1, "1[3]").get.head 57 | assert( `c1.1[3]`.value == Text("S13") ) 58 | val `c1.1[1]` = queryAsSimple(c1, "1[1]").get.head 59 | assert( `c1.1[1]`.value == Text("S11") ) 60 | eval( e, c1 ) === Failures.format(e, `c1.1[1]`::`c1.1[3]`::Nil ) 61 | } 62 | 63 | def formatAtLeastOnceT = { 64 | val e = Format("1[*]", "[A-Z0-9]+2", true) 65 | eval( e, c1 ) === Pass 66 | } 67 | } -------------------------------------------------------------------------------- /validation/src/main/java/hl7/v2/validation/vs/TripletEntry.java: -------------------------------------------------------------------------------- 1 | package hl7.v2.validation.vs; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import gov.nist.validation.report.Entry; 9 | import gov.nist.validation.report.Trace; 10 | import hl7.v2.validation.report.Detections; 11 | 12 | public class TripletEntry implements Entry { 13 | private Entry value; 14 | private Entry codeSys; 15 | 16 | public boolean passValue(){ 17 | return CodedElementValidator.pass(value); 18 | } 19 | 20 | public boolean passCodeSys(){ 21 | return codeSys == null; 22 | } 23 | 24 | public boolean isValid(){ 25 | return this.passValue() && this.passCodeSys(); 26 | } 27 | 28 | public List<Entry> asList(){ 29 | List<Entry> ls = new ArrayList<>(); 30 | if(value != null){ 31 | ls.add(value); 32 | } 33 | if(codeSys != null){ 34 | ls.add(codeSys); 35 | } 36 | return ls; 37 | } 38 | 39 | public TripletEntry toAlert(){ 40 | TripletEntry al = new TripletEntry(); 41 | if(value != null){ 42 | al.setValue(Detections.toAlert(value)); 43 | } 44 | if(codeSys != null){ 45 | al.setCodeSys(Detections.toAlert(codeSys)); 46 | } 47 | return al; 48 | } 49 | 50 | public Entry getValue() { 51 | return value; 52 | } 53 | public void setValue(Entry value) { 54 | this.value = value; 55 | } 56 | public Entry getCodeSys() { 57 | return codeSys; 58 | } 59 | public void setCodeSys(Entry codeSys) { 60 | this.codeSys = codeSys; 61 | } 62 | 63 | public String toString(){ 64 | return "VAL : "+value+"\n"+"CODESYS : "+codeSys; 65 | } 66 | @Override 67 | public String getCategory() { 68 | // TODO Auto-generated method stub 69 | return null; 70 | } 71 | 72 | @Override 73 | public String getClassification() { 74 | // TODO Auto-generated method stub 75 | return null; 76 | } 77 | 78 | @Override 79 | public int getColumn() { 80 | // TODO Auto-generated method stub 81 | return 0; 82 | } 83 | 84 | @Override 85 | public String getDescription() { 86 | // TODO Auto-generated method stub 87 | return null; 88 | } 89 | 90 | @Override 91 | public int getLine() { 92 | // TODO Auto-generated method stub 93 | return 0; 94 | } 95 | 96 | @Override 97 | public Map<String, Object> getMetaData() { 98 | // TODO Auto-generated method stub 99 | return null; 100 | } 101 | 102 | @Override 103 | public String getPath() { 104 | // TODO Auto-generated method stub 105 | return null; 106 | } 107 | 108 | @Override 109 | public List<Trace> getStackTrace() { 110 | // TODO Auto-generated method stub 111 | return null; 112 | } 113 | 114 | @Override 115 | public String toJson() throws Exception { 116 | // TODO Auto-generated method stub 117 | return null; 118 | } 119 | 120 | @Override 121 | public String toText() { 122 | // TODO Auto-generated method stub 123 | return null; 124 | } 125 | 126 | 127 | 128 | 129 | } 130 | -------------------------------------------------------------------------------- /parser/src/main/scala/hl7/v2/instance/EscapeSeqHandler.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.instance 2 | 3 | import scala.annotation.tailrec 4 | 5 | trait EscapeSeqHandler { 6 | 7 | /** 8 | * Returns a new string with HL7 separators escaped 9 | * @param s - The string to be un-escaped 10 | * @param separators - The separators 11 | * @return A new string with HL7 separators escaped 12 | */ 13 | def escape(s: String)(implicit separators: Separators): String = { 14 | 15 | val( fs, cs, rs, ec, ss, otc) = Separators.unapply( separators ).get 16 | 17 | val r = s.replace( ec.toString, s"${ec}E$ec" ) 18 | .replace( fs.toString, s"${ec}F$ec" ) 19 | .replace( cs.toString, s"${ec}S$ec" ) 20 | .replace( ss.toString, s"${ec}T$ec" ) 21 | .replace( rs.toString, s"${ec}R$ec" ) 22 | 23 | otc match { 24 | case Some(x) => r.replace( x.toString, s"${ec}P$ec" ) 25 | case None => r 26 | } 27 | } 28 | 29 | /** 30 | * Returns a new string with HL7 basic escape sequence replaced 31 | * @param s - The string to be un-escaped 32 | * @param separators - The separators 33 | * @return A new string with HL7 basic escape sequence replaced 34 | */ 35 | def unescape(s: String)(implicit separators: Separators): String = { 36 | 37 | val( fs, cs, rs, ec, ss, otc) = Separators.unapply( separators ).get 38 | 39 | val escapeTruncation = otc match { case None => false case _ => true } 40 | val efs = s"${ec}F$ec" 41 | val ecs = s"${ec}S$ec" 42 | val ess = s"${ec}T$ec" 43 | val ers = s"${ec}R$ec" 44 | val eec = s"${ec}E$ec" 45 | val etc = s"${ec}P$ec" 46 | 47 | @tailrec 48 | def f(sb: StringBuilder, s: String): String = 49 | s span ( _ != ec ) match { 50 | case (x, "") => sb.append(x).toString() 51 | case (x, y) if y.take(3) == efs => f( sb.append(x).append(fs), y drop 3 ) 52 | case (x, y) if y.take(3) == ecs => f( sb.append(x).append(cs), y drop 3 ) 53 | case (x, y) if y.take(3) == ess => f( sb.append(x).append(ss), y drop 3 ) 54 | case (x, y) if y.take(3) == ers => f( sb.append(x).append(rs), y drop 3 ) 55 | case (x, y) if y.take(3) == eec => f( sb.append(x).append(ec), y drop 3 ) 56 | case (x, y) if escapeTruncation && y.take(3) == etc => 57 | f( sb.append(x).append( otc.get ), y drop 3 ) 58 | case (x, y) => f( sb.append(x).append(ec), y drop 1 ) 59 | } 60 | 61 | f( new StringBuilder, s ) 62 | } 63 | 64 | } 65 | 66 | //This code is a bogus: \S\F\F\ will unescaped as \S|F\ instead of ^F| 67 | /*def unescape(s: String)(implicit separators: Separators): String = { 68 | val( fs, cs, rs, ec, ss, otc) = Separators.unapply( separators ).get 69 | 70 | val r = s.replaceAllLiterally( s"${ec}F$ec", fs.toString ) 71 | .replaceAllLiterally( s"${ec}S$ec", cs.toString ) 72 | .replaceAllLiterally( s"${ec}T$ec", ss.toString ) 73 | .replaceAllLiterally( s"${ec}R$ec", rs.toString ) 74 | .replaceAllLiterally( s"${ec}E$ec", ec.toString ) 75 | 76 | otc match { 77 | case Some(x) => r.replaceAllLiterally( s"${ec}P$ec", x.toString ) 78 | case None => r 79 | } 80 | }*/ 81 | -------------------------------------------------------------------------------- /validation/src/main/scala/expression/ContextBasedEvaluator.scala: -------------------------------------------------------------------------------- 1 | package expression 2 | 3 | import expression.EvalResult._ 4 | import hl7.v2.instance.Query._ 5 | import hl7.v2.instance._ 6 | import hl7.v2.validation.vs.{ Validator, ValueSetLibrary } 7 | import gov.nist.validation.report.Entry 8 | 9 | import scala.util.{ Failure, Success, Try } 10 | 11 | class ContextBasedEvaluator extends EscapeSeqHandler { 12 | 13 | def eval(e: Expression, c: Element)(implicit l: ValueSetLibrary, s: Separators, 14 | t: Option[TimeZone]): EvalData = { 15 | e match { 16 | case x: PlainText => plainText(x, c) 17 | case x: StringList => stringList(x, c) 18 | case _ => EvalData(Pass, "", "") 19 | } 20 | } 21 | 22 | def plainText(p: PlainText, context: Element)(implicit s: Separators): EvalData = { 23 | queryAsSimple(context, p.path) match { 24 | case Success(ls) => 25 | ls filter (x => notEqual(x, p.text, p.ignoreCase)) match { 26 | case Nil => EvalData(Pass, "", "") 27 | case xs => if (p.atLeastOnce && (xs.size != ls.size)) EvalData(Pass, "", "") else EvalData(Failures.plainText(p, xs), xs.head.value.raw, p.text) 28 | } 29 | case Failure(e) => EvalData(inconclusive(p, context.location, e), "", "") 30 | } 31 | } 32 | 33 | def stringList(sl: StringList, context: Element)(implicit s: Separators): EvalData = 34 | queryAsSimple(context, sl.path) match { 35 | case Success(ls) => 36 | ls filter (x => notInList(x.value.raw, sl.csv)) match { 37 | case Nil => EvalData(Pass, "", "") 38 | case xs => if (sl.atLeastOnce && (xs.size != ls.size)) EvalData(Pass, "", "") else EvalData(Failures.stringList(sl, xs), xs.head.value.raw, sl.csv.toString()) 39 | } 40 | case Failure(e) => EvalData(inconclusive(sl, context.location, e), "", "") 41 | } 42 | 43 | private def notEqual(s: Simple, text: String, cs: Boolean)(implicit separators: Separators): Boolean = 44 | if (cs) !unescape(s.value.raw).equalsIgnoreCase(text) 45 | else unescape(s.value.raw) != text 46 | 47 | private def notInList(s: String, list: List[String])(implicit separators: Separators): Boolean = 48 | !list.contains(unescape(s)) 49 | /** 50 | * Creates an inconclusive result from a message 51 | */ 52 | private def inconclusive(e: Expression, l: Location, m: String): Inconclusive = 53 | Inconclusive(Trace(e, Reason(l, m) :: Nil)) 54 | 55 | /** 56 | * Creates an inconclusive result from a throwable 57 | */ 58 | private def inconclusive(e: Expression, l: Location, t: Throwable): Inconclusive = 59 | inconclusive(e, l, t.getMessage) 60 | 61 | private def inconclusive(sv: SimpleValue, xs: List[(Simple, Try[Boolean])]) = { 62 | val reasons = xs map { 63 | case (s, Failure(e)) => Reason(s.location, e.getMessage) 64 | case _ => ??? //Not gonna happens 65 | } 66 | Inconclusive(Trace(sv, reasons)) 67 | } 68 | 69 | private def inconclusive(pv: PathValue, c: Element, xs1: List[Simple], xs2: List[Simple]) = { 70 | val p1 = s"${c.location.path}.${pv.path1}" 71 | val p2 = s"${c.location.path}.${pv.path2}" 72 | val m = s"path1($p1) and path2($p2) resolution returned respectively ${xs1.length} and ${xs1.length} elements." 73 | val reasons = Reason(c.location, m) :: Nil 74 | Inconclusive(Trace(pv, reasons)) 75 | } 76 | } -------------------------------------------------------------------------------- /parser/src/test/scala/hl7/v2/instance/QuerySpec.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.instance 2 | 3 | import hl7.v2.instance.Query._ 4 | import org.specs2.Specification 5 | 6 | /** 7 | * @author Salifou Sidi M. Malick <salifou.sidi@gmail.com> 8 | */ 9 | 10 | class QuerySpec extends Specification with Mocks { def is = s2""" 11 | 12 | Query Specification 13 | 14 | Given the following elements : 15 | 16 | ${elementsDescription /* See generic.Mocks for details */} 17 | 18 | A query should fail if the path is invalid $q1 19 | A query should succeed if the path is valid $q2 20 | Querying a simple element should fail with unreachable path error $q3 21 | Querying an element with no children should return an empty list $q4 22 | Querying a missing position (c1, "4[*]") should return an empty list $q5 23 | Querying c2 for the path 2[*] should return c0 and c1 $q6 24 | Querying c2 for the path 2[3] should return c1 $q7 25 | Querying c2 for the path 2[*] and casting the result as list of `Simple' should return an error $q8 26 | Querying c2 for the path 4[1] and casting the result as list of `Simple' should succeed $q9 27 | Querying c2 for the path . should return c2 $q10 28 | Querying s0 for the path . should return s0 $q11 29 | Querying c2 for the path . and casting the result as list of `Simple' should return an error $q12 30 | Querying s0 for the path . and casting the result as list of `Simple' should retrun s0 $q13 31 | """ 32 | 33 | def q1 = Seq("1", "1[a]", "0[1]", "1[2].", "1[2].a", "1[1]/2[2]" ) map { p => 34 | query(c1, p) must beFailedTry.withThrowable[Error]( s"Invalid Path '\\Q${p}\\E'" ) 35 | } 36 | 37 | def q2 = Seq("1[1]", "1[*]", "2[2].3[*].4[4]" ) map { p => query(c1, p) must beSuccessfulTry } 38 | 39 | def q3 = query(s0, "1[1].2[3]") must 40 | beFailedTry.withThrowable[Error]( "Unreachable Path '\\Q1[1].2[3]\\E'" ) 41 | 42 | def q4 = query(c0, "1[1]") must beSuccessfulTry.withValue( ===(Seq[Element]()) ) 43 | 44 | def q5 = query(c1, "4[*]") must beSuccessfulTry.withValue( ===(Seq[Element]()) ) 45 | 46 | def q6 = query(c2, "2[*]") must beSuccessfulTry.withValue( ===(Seq[Element](c0, c1)) ) 47 | 48 | def q7 = query(c2, "2[3]") must beSuccessfulTry.withValue( ===(Seq[Element]( c1 )) ) 49 | 50 | def q8 = queryAsSimple(c2, "2[*]") must 51 | beFailedTry.withThrowable[Error]("Path resolution returned at least one complex element") 52 | 53 | def q9 = queryAsSimple(c2, "4[1]") must beSuccessfulTry.withValue( ===( Seq[Simple]( s0 ) ) ) 54 | 55 | def q10 = query(c2, ".") must beSuccessfulTry.withValue( ===( Seq[Element]( c2 ) ) ) 56 | 57 | def q11 = query(s0, ".") must beSuccessfulTry.withValue( ===( Seq[Element]( s0 ) ) ) 58 | 59 | def q12 = queryAsSimple(c2, ".") must 60 | beFailedTry.withThrowable[Error]("Path resolution returned at least one complex element") 61 | 62 | def q13 = queryAsSimple(s0, ".") must beSuccessfulTry.withValue( ===( Seq[Simple]( s0 ) ) ) 63 | } 64 | -------------------------------------------------------------------------------- /parser/src/main/scala/hl7/v2/instance/DataElement.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.instance 2 | 3 | import hl7.v2.profile.{Component => CM, Req, Datatype, Composite, Primitive, Varies} 4 | 5 | object DataElement { 6 | 7 | /** 8 | * Creates and returns a component object 9 | * @param d - The datatype 10 | * @param r - The requirement 11 | * @param l - The location 12 | * @param v - The value 13 | * @param i - The instance number 14 | * @return A component object 15 | */ 16 | def field(d: Datatype, r: Req, l: Location, v: String, i: Int) 17 | (implicit s: Separators): Option[Field] = 18 | v matches emptyField(s.cs, s.ss) match { 19 | case true => None 20 | case false => Some { 21 | d match { 22 | case vr: Varies => UnresolvedField(vr, r, l, i,Text(v)) 23 | case p: Primitive => SimpleField(p, r, l, i, Value(p, v)) 24 | case c: Composite => if(isNull(v)){ 25 | NULLComplexField(c,r,l,i) 26 | } 27 | else { 28 | val (hasExtra, components) = children(l, c.components, v, s.cs) 29 | ComplexField(c, r, l, i, components, hasExtra) 30 | } 31 | } 32 | } 33 | } 34 | 35 | /** 36 | * Creates and returns a component object 37 | * @param d - The datatype 38 | * @param r - The requirement 39 | * @param l - The location 40 | * @param v - The value 41 | * @return A component object 42 | */ 43 | def component(d: Datatype, r: Req, l: Location, v: String) 44 | (implicit s: Separators): Option[Component] = 45 | v matches emptyComponent( s.ss ) match { 46 | case true => None 47 | case false => Some { 48 | d match { 49 | case p: Primitive => SimpleComponent(p, r, l, Value(p, v)) 50 | case c: Composite => 51 | val (hasExtra, x) = children(l, c.components, v, s.ss) 52 | val components = x.asInstanceOf[List[SimpleComponent]] 53 | ComplexComponent(c, r, l, components, hasExtra) 54 | case _ => throw new Error("Invalid datatype " + d.name) 55 | } 56 | } 57 | } 58 | 59 | /** 60 | * Creates and returns the list of components 61 | */ 62 | private def children(l: Location, ml: List[CM], v: String, sep: Char) 63 | (implicit s: Separators): (Boolean, List[Component]) = { 64 | val max = ml.size 65 | val vs = split(sep, v, l.column) 66 | val hasExtra = vs.size > max 67 | val _children = ml zip vs map { t => 68 | val (m, (col, vv)) = t 69 | val pos = m.req.position 70 | val loc = l.copy( eType(l.path), desc=m.name, path=s"${l.path}.$pos", column=col, uidPath = s"${l.uidPath}.$pos" ) 71 | component( m.datatype, m.req, loc, vv ) 72 | } 73 | (hasExtra, _children.flatten) 74 | } 75 | 76 | private def eType(p: String): EType = 77 | if ( p.drop(4).split("\\.").length == 1 ) EType.Component else EType.SubComponent 78 | 79 | /** 80 | * Returns if the value is Null i.e. "" 81 | */ 82 | private def isNull(v: String) = v == Value.NULL 83 | 84 | /** 85 | * Regular expression to match an empty component 86 | */ 87 | private def emptyComponent(ss: Char) = s"(?:\\s*$ss*\\s*)*" 88 | 89 | /** 90 | * Regular expression to match an empty field 91 | */ 92 | private def emptyField(cs: Char, ss: Char) = s"(:?\\s|\\Q$cs\\E|\\Q$ss\\E)*" 93 | 94 | } 95 | -------------------------------------------------------------------------------- /profile/src/main/scala/hl7/v2/profile/domain.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.profile 2 | import java.util.{ Arrays => JArrays, List => JList } 3 | import scala.jdk.CollectionConverters.SeqHasAsJava 4 | 5 | /** 6 | * Trait representing a data type 7 | */ 8 | sealed trait Datatype { 9 | def id: String 10 | def name: String 11 | def desc: String 12 | def version: String 13 | } 14 | 15 | /** 16 | * A primitive data type 17 | */ 18 | case class Varies(id: String, name: String, desc: String, version : String, referenceValue1 : Option[String], referenceValue2 : Option[String]) extends Datatype 19 | 20 | /** 21 | * A primitive data type 22 | */ 23 | case class Primitive( id: String, name: String, desc: String, version : String) extends Datatype 24 | 25 | /** 26 | * A composite data type 27 | */ 28 | case class Composite( 29 | id: String, 30 | name: String, 31 | desc: String, 32 | version : String, 33 | components: List[Component] 34 | ) extends Datatype { 35 | 36 | lazy val reqs: List[Req] = components map ( _.req ) 37 | } 38 | 39 | /** 40 | * A composite data type component 41 | */ 42 | case class Component( name: String, datatype: Datatype, req: Req ) 43 | 44 | /** 45 | * A segment field 46 | */ 47 | case class Field( name: String, datatype: Datatype, req: Req ) 48 | 49 | /** 50 | * Describes the mapping for dynamic data type 51 | * @param position - The position of the element with dynamic data type 52 | * @param reference - The position which defines the data type name to be used 53 | * @param map - The mapping ( data type name -> data type id ) 54 | */ 55 | 56 | case class DynMapping( position: Int, reference: Option[String], secondReference: Option[String], map: Map[(Option[String], Option[String]), Datatype] ) { 57 | def getDatatypes() : JList[Datatype] = { 58 | map.values.toSeq.asJava 59 | } 60 | } 61 | 62 | 63 | /** 64 | * A segment 65 | */ 66 | case class Segment( 67 | id: String, 68 | name: String, 69 | desc: String, 70 | fields: List[Field], 71 | mappings: List[DynMapping] 72 | ) { 73 | 74 | lazy val reqs: List[Req] = fields map ( _.req ) 75 | } 76 | 77 | /** 78 | * Trait representing either a segment reference or a group 79 | */ 80 | sealed trait SegRefOrGroup { 81 | def req: Req 82 | def reqs: List[Req] 83 | } 84 | 85 | /** 86 | * A segment reference 87 | */ 88 | case class SegmentRef( req: Req, ref: Segment ) extends SegRefOrGroup { 89 | def reqs = ref.reqs 90 | } 91 | 92 | /** 93 | * A group 94 | */ 95 | case class Group( 96 | id: String, 97 | name: String, 98 | structure: List[SegRefOrGroup], 99 | req: Req 100 | ) extends SegRefOrGroup { 101 | 102 | lazy val reqs: List[Req] = structure map ( _.req ) 103 | } 104 | 105 | /** 106 | * A message 107 | */ 108 | case class Message ( 109 | id: String, 110 | structId: String, 111 | event: String, 112 | typ : String, 113 | desc : String, 114 | structure: List[SegRefOrGroup] 115 | ) { 116 | 117 | lazy val asGroup = Group(id, structId, structure, 118 | Req(1, structId, Usage.R, None, None, None, Nil)) 119 | } 120 | 121 | /** 122 | * A profile 123 | */ 124 | case class Profile( 125 | id: String, 126 | messages : Map[String, Message], 127 | segments : Map[String, Segment], 128 | datatypes: Map[String, Datatype] 129 | ) 130 | { 131 | def getMessage( id : String) = messages(id) 132 | } 133 | 134 | 135 | -------------------------------------------------------------------------------- /validation/src/test/scala/hl7/v2/validation/structure/ValueValidationSpec.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.validation.structure 2 | 3 | import hl7.v2.instance._ 4 | import hl7.v2.profile.Range 5 | import hl7.v2.validation.structure.ValueValidation._ 6 | import org.specs2.Specification 7 | import hl7.v2.validation.report.ConfigurableDetections 8 | import com.typesafe.config.ConfigFactory 9 | 10 | class ValueValidationSpec extends Specification { def is = s2""" 11 | 12 | Value Validation Specification (Length, Format and Separator in Value ) 13 | 14 | Value validation should not report any error if the value is Null and the location is a Field $e1 15 | Value validation should check and report the format errors $e2 16 | Value validation should check the use of unescaped separators $e3 17 | Length 18 | Length constraint should fail if value size is outside MinLength, MaxLength range $e4 19 | Length constraint should fail if value size is outside ConfLength Range $e42 20 | Length constraint should pass if value size is in ConfLength Range $e43 21 | Length constraint should pass if value size is in MinLength, MaxLength Range $e44 22 | Length validation should fail with spec error if both MinLength, MaxLength and ConfLength are specified $e41 23 | Length validation should fail with spec error if none of MinLength, MaxLength and ConfLength are specified $e45 24 | Value validation length check should be done on an unsecaped value $e5 25 | Value validation length check should take into account trailing whitespaces $e6 26 | """ 27 | 28 | implicit val separators = Separators( '|', '^', '~', '\\', '&', Some('#') ) 29 | implicit val Detections = new ConfigurableDetections(ConfigFactory.load()); 30 | val loc = Location(EType.Field, "The description", "The path", 1, 1) 31 | val lcs = Some( Range(2, "3") ) 32 | val rng = Some( Range(2, "3") ) 33 | val lcn = None 34 | 35 | def e1 = checkValue( Number("\"\""), lcs, None, loc) === Nil 36 | 37 | def e2 = { 38 | val m = "1E5 is not a valid Number. The format should be: [+|-]digits[.digits]" 39 | checkValue( Number("1E5"), lcs, None, loc) === Detections.format(loc, m) :: Nil 40 | } 41 | 42 | def e3 = Seq("|x", "x^s", "x&q") map { s => 43 | checkValue( Text(s), lcs, None, loc) === Detections.unescaped(loc) :: Nil 44 | } 45 | 46 | def e4 = Seq("x", "xxxx") map { s => 47 | checkValue(Text(s), lcs, None, loc) === Detections.length(loc, lcs.get, s) :: Nil 48 | } 49 | 50 | def e41 = Seq("x", "xxxx") map { s => 51 | checkValue(Text(s), lcs, rng, loc) === Detections.lengthSpecErrorXOR(loc) :: Nil 52 | } 53 | 54 | def e45 = Seq("x", "xxxx") map { s => 55 | checkValue(Text(s), None, None, loc) === Detections.lengthSpecErrorNF(loc) :: Nil 56 | } 57 | 58 | def e42 = Seq("x", "xxxx") map { s => 59 | checkValue(Text(s), None, rng, loc) === Detections.length(loc, lcs.get, s) :: Nil 60 | } 61 | 62 | def e43 = Seq("xx", "xxx") map { s => 63 | checkValue(Text(s), None, rng, loc) === Nil 64 | } 65 | 66 | def e44 = Seq("xx", "xxx") map { s => 67 | checkValue(Text(s), lcs, None, loc) === Nil 68 | } 69 | 70 | def e5 = Seq("""x\F\y""", """q\S\""", """\T\w""") map { s => 71 | checkValue( Text(s), lcs, None, loc) === Nil 72 | } 73 | 74 | def e6 = Seq ( 75 | (" ", Nil), 76 | (" x ", Detections.length(loc, lcs.get, " x ") :: Nil) 77 | ) map { t => 78 | checkValue(Text(t._1), lcs, None, loc) === t._2 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /validation/src/test/scala/expression/Mocks.scala: -------------------------------------------------------------------------------- 1 | package expression 2 | 3 | import hl7.v2.instance._ 4 | import hl7.v2.profile.{Usage, Req} 5 | import hl7.v2.validation.vs.EmptyValueSetLibrary 6 | import hl7.v2.validation.vs.Validator 7 | import hl7.v2.validation.report.ConfigurableDetections 8 | import com.typesafe.config.ConfigFactory 9 | 10 | trait Mocks { 11 | 12 | implicit val Detections = new ConfigurableDetections(ConfigFactory.load()); 13 | implicit val vsValidator = new Validator(Detections); 14 | implicit val defaultValueSetLibrary = EmptyValueSetLibrary.getInstance() 15 | implicit val separators = Separators( '|', '^', '~', '\\', '&', Some('#') ) 16 | 17 | implicit val dtz = Some( TimeZone("+0000") ) 18 | 19 | trait Default { 20 | val reqs = List[Req]() 21 | val location = Location(null, "desc ...", "Path", -1, -1) 22 | /*val qProps = QProps(QType.DT, "id", "name")*/ 23 | val hasExtra = false 24 | val req = Req(-1, "", Usage.O, None, None, None, Nil) 25 | } 26 | 27 | case class S(override val position: Int, instance: Int, value: Value) 28 | extends Simple with Default 29 | 30 | case class C(override val position: Int, instance: Int, children: List[Element]) 31 | extends Complex with Default 32 | 33 | val s0 = S( 4, 1, Text("41\\F\\") ) 34 | val s1 = S( 5, 1, Number("51") ) 35 | val s2 = S( 5, 2, Text("52") ) 36 | val s3 = S( 5, 3, Number("S53")) 37 | val s_LOINC = S( 1, 1, Text("90423-5")) 38 | val s_SNOMED = S( 1, 1, Text("119202000")) 39 | 40 | def c1Children = List( 41 | S(1, 1, Text("S11")) , S(1, 2, Text("S12")) , S(1, 3, Text("S13")), 42 | S(2, 1, Number("21")), S(2, 2, Number("22")), S(2, 3, Number("23")), 43 | S(3, 1, Text("S3")) , S(3, 2, Text("S3")) , S(3, 3, Text("S3")) 44 | ) 45 | 46 | val c0 = C(2,1, Nil) 47 | val c1 = C(2,3, c1Children ) 48 | val c2 = C(1,1, s0::s1::s2::c0::c1::Nil) 49 | val c3 = C(2,3, List( 50 | S( 1, 1, Text("90423-5")), S( 1, 2, Text("119202000")) 51 | )) 52 | 53 | def elementsDescription = 54 | """s0 -> Simple(4, 1, Text(41\F\) ) c1 -> Complex( position= 2, instance= 3) 55 | s1 -> Simple(5, 1, Number(51) ) 1[1] -> Simple( value=Text(S11) ) 56 | s2 -> Simple(5, 2, Text(52) ) 1[2] -> Simple( value=Text(S12) ) 57 | s3 -> Simple(5, 3, Number("S53") ) 1[3] -> Simple( value=Text(S13) ) 58 | 2[1] -> Simple( value=Number(21)) 59 | c0 -> Complex( position= 2, instance= 1, No children) 2[2] -> Simple( value=Number(22)) 60 | 2[3] -> Simple( value=Number(23)) 61 | c2 -> Complex( position= 2, instance= 1) 3[1] -> Simple( value=Text(S3) ) 62 | 2[1] -> c0 3[2] -> Simple( value=Text(S3) ) 63 | 2[3] -> c1 3[3] -> Simple( value=Text(S3) ) 64 | 4[1] -> s0 65 | 5[1] -> s1 66 | 5[2] -> s2 67 | 5[3] -> s3""" 68 | 69 | def c1Description = 70 | """ c1 -> Complex( position= 2, instance= 3) 71 | 1[1] -> Simple( value=Text(S11) ) 2[3] -> Simple( value=Number(23)) 72 | 1[2] -> Simple( value=Text(S12) ) 3[1] -> Simple( value=Text(S3) ) 73 | 1[3] -> Simple( value=Text(S13) ) 3[2] -> Simple( value=Text(S3) ) 74 | 2[1] -> Simple( value=Number(21)) 3[3] -> Simple( value=Text(S3) ) 75 | 2[2] -> Simple( value=Number(22))""" 76 | 77 | } -------------------------------------------------------------------------------- /validation/src/test/scala/expression/StringFormatSpec.scala: -------------------------------------------------------------------------------- 1 | package expression 2 | 3 | import expression.EvalResult.Pass 4 | import hl7.v2.instance.{Element, Query, Text} 5 | import Query.{query, queryAsSimple} 6 | import org.specs2.Specification 7 | 8 | import scala.util.{Failure, Success} 9 | import expression.EvalResult.Fail 10 | import expression.EvalResult.Inconclusive 11 | 12 | trait StringFormatSpec extends Specification with Evaluator with Mocks { 13 | 14 | /* 15 | StringFormatSpec 16 | StringFormat should succeed if the path is not populated $stringFormatPathNotPopulated 17 | StringFormat should fail if a LOINC string is invalid $stringFormatLOINCInvalid 18 | StringFormat should pass if a LOINC string is valid $stringFormatLOINCvalid 19 | StringFormat should fail if a SNOMED string is invalid $stringFormatSNOMEDInvalid 20 | StringFormat should pass if a SNOMED string is valid $stringFormatSNOMEDvalid 21 | StringFormat should be inconclusive if a string format is unrecognized $stringFormatUnknown 22 | If the path is valued to multiple elements 23 | StringFormat should pass if one of the elements is in the list and AtLeastOnce = True $stringFormatAtLeastOnceT 24 | StringFormat should fail if one of the elements is not in the list and AtLeastOnce = False $stringFormatAtLeastOnceF 25 | StringFormat evaluation should fail If not present behavior is FAIL and no element is found $stringFormatNoElmFAIL 26 | StringFormat evaluation should be inconclusive If not present behavior is INCONCLUSIVE and no element is found $stringFormatNoElmINC 27 | StringFormat evaluation should pass If not present behavior is PASS and no element is found $stringFormatNoElmPASS 28 | */ 29 | 30 | //c1.4[1] is not populated 31 | assert( queryAsSimple(c1, "4[1]") == Success(Nil) ) 32 | def stringFormatPathNotPopulated = eval( StringFormat("4[1]", "LOINC", false), c1 ) === Pass 33 | 34 | def stringFormatNoElmFAIL = { 35 | val f = StringFormat("4[1]", "LOINC", false, "FAIL") 36 | eval(f, c1) === Failures.notPresentBehaviorFail(f, f.path, c1) 37 | } 38 | def stringFormatNoElmINC = { 39 | val f = StringFormat("4[1]", "LOINC", false, "INCONCLUSIVE") 40 | eval(f, c1) === Failures.notPresentBehaviorInconclusive(f, f.path, c1) 41 | } 42 | def stringFormatNoElmPASS = { 43 | val f = StringFormat("4[1]", "LOINC", false, "PASS") 44 | eval(f, c1) === Pass 45 | } 46 | 47 | def stringFormatLOINCvalid = { 48 | val p = StringFormat(".","LOINC",true) 49 | eval( p, s_LOINC) === Pass 50 | } 51 | 52 | def stringFormatSNOMEDvalid = { 53 | val p = StringFormat(".","SNOMED",true) 54 | eval( p, s_SNOMED) === Pass 55 | } 56 | 57 | def stringFormatLOINCInvalid = { 58 | val p = StringFormat(".","LOINC",true) 59 | eval( p, s0).isInstanceOf[Fail] 60 | } 61 | 62 | def stringFormatSNOMEDInvalid = { 63 | val p = StringFormat(".","SNOMED",true) 64 | eval( p, s0).isInstanceOf[Fail] 65 | } 66 | 67 | def stringFormatUnknown = { 68 | val p = StringFormat(".","UNKNOWN",true) 69 | eval( p, s0).isInstanceOf[Inconclusive] 70 | } 71 | 72 | assert( queryAsSimple(c3, "1[*]").isSuccess && queryAsSimple(c3, "1[*]").get.size > 1) 73 | def stringFormatAtLeastOnceT = { 74 | val p = StringFormat("1[*]", "LOINC", true) 75 | eval( p, c3 ) === Pass 76 | } 77 | 78 | def stringFormatAtLeastOnceF = { 79 | val p = StringFormat("1[*]", "LOINC", false) 80 | val `c3.1[2]` = queryAsSimple(c3, "1[2]").get.head 81 | assert( `c3.1[2]`.value == s_SNOMED.value ) 82 | eval( p, c3 ) === Failures.stringFromat(p, `c3.1[2]`::Nil) 83 | } 84 | } -------------------------------------------------------------------------------- /parser/src/main/scala/hl7/v2/parser/impl/PreProcessor.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.parser.impl 2 | 3 | import hl7.v2.instance.Separators 4 | import hl7.v2.profile.{Group => GM, Message => MM, SegRefOrGroup => SGM, SegmentRef => SM} 5 | import scala.util.Try 6 | import scala.util.Failure 7 | import scala.util.Success 8 | import hl7.v2.profile.Usage 9 | 10 | /** 11 | * @author Salifou Sidi M. Malick <salifou.sidi@gmail.com> 12 | */ 13 | 14 | case class PPR( valid: List[Line], invalid: List[Line], unexpected : List[Line], separators: Separators) 15 | 16 | object PreProcessor { 17 | 18 | /** 19 | * Splits the message into lines and returns a PPR. 20 | * This function will standardize the lines i.e. it will replace the separators 21 | * with the ones recommended by HL7 if necessary. 22 | * 23 | * @param message - The message to be pre-processed 24 | * @return A `Success' containing the PPR or a `Failure' if: 25 | * 1) No MSH segment if defined in the message 26 | * 2) MSH segment contains less than 9 characters 27 | * 3) A character is use twice as a separator 28 | */ 29 | def process(message: String, model : MM): Try[PPR] = 30 | splitOnMSH(message) match { 31 | case (beforeMSH, Nil) => 32 | Failure( new Exception("No MSH Segment found in the message.") ) 33 | case (beforeMSH, xs ) => 34 | getSeparators( xs.head._2 ) map { separators => 35 | implicit val fs = separators.fs 36 | val segNames = messageSegNames(model.structure) 37 | val (correct, invalid) = partition( xs ) 38 | val (unexpected, valid) = correct partition { 39 | seg => segNames.filter (seg._2 startsWith _) isEmpty 40 | } 41 | PPR(valid, beforeMSH:::invalid, unexpected, separators) 42 | } 43 | } 44 | 45 | def messageSegNames(models: List[SGM]) : List[String] = { 46 | def loop(l : List[SGM], names : List[String]) : List[String] = { 47 | l match { 48 | case head::list => head match { 49 | case s : SM => loop(list, s.ref.name::names) 50 | case g : GM => loop(list, loop(g.structure, names)) 51 | } 52 | case Nil => names 53 | } 54 | } 55 | loop(models, Nil) 56 | } 57 | 58 | /** 59 | * Splits the message into lines and returns a pair of list of lines. 60 | * The first list will contain all lines before the MSH segment 61 | */ 62 | def splitOnMSH( message: String ): (List[Line], List[Line]) = 63 | ( (LazyList from 1) zip lineBreak.split( message ) ).toList span { l => 64 | !(l._2 startsWith "MSH") 65 | } 66 | 67 | /** 68 | * Partition the list of lines into list of valid and invalid lines. 69 | */ 70 | private def partition(list: List[Line]) 71 | (implicit fs: Char): (List[Line], List[Line]) = 72 | list partition (l => validLinesRegex.pattern.matcher(l._2).matches) 73 | 74 | /** 75 | * Returns the separators defined in MSH.2 or a Failure 76 | */ 77 | private def getSeparators( msh: String ): Try[Separators] = try { 78 | val fs = msh(3) 79 | val cs = msh(4) 80 | val rs = msh(5) 81 | val ec = msh(6) 82 | val ss = msh(7) 83 | val x = msh(8) 84 | val tc = if( x == fs ) None else Some(x) 85 | val separators = Separators(fs, cs, rs, ec, ss, tc) 86 | separators.getDuplicates match { 87 | case Nil => Success( separators ) 88 | case xs => Failure( 89 | new Exception( s"The following character(s) ['${xs.mkString("', '") 90 | }'] has/have been used more than once as a separator.") 91 | ) 92 | } 93 | } catch { 94 | case _: Throwable => Failure( new Exception("The MSH line contains less than 9 characters.") ) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /validation/src/main/scala/hl7/v2/validation/structure/ValueValidation.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.validation.structure 2 | 3 | import gov.nist.validation.report.Entry 4 | import hl7.v2.instance._ 5 | import hl7.v2.instance.util.ValueFormatCheckers._ 6 | import hl7.v2.profile._ 7 | import hl7.v2.profile.Usage 8 | import hl7.v2.validation.report.ConfigurableDetections 9 | 10 | object ValueValidation extends EscapeSeqHandler { 11 | 12 | def check(s: Simple)(implicit x: Separators, Detections: ConfigurableDetections): List[Entry] = 13 | s.req.usage match { 14 | case Usage.O => Detections.ousage(s.location, s.value.raw) :: Nil 15 | case _ => checkValue(s.value, s.req.length, s.req.confRange, s.location) 16 | } 17 | 18 | /** 19 | * Checks the value format, length and presence of escape characters 20 | * @param v - The value to be checked 21 | * @param lc - The length constraint 22 | * @param l - The location 23 | * @param s - The separators 24 | * @return The list of problem found 25 | */ 26 | def checkValue(v: Value, ln: Option[Range], conf: Option[Range], l: Location)(implicit s: Separators, Detections: ConfigurableDetections): List[Entry] = 27 | v.isNull match { 28 | case true => if (l.eType == EType.Field) //No check if the value is Null and the location is a Field 29 | Nil 30 | else checkFormat(l, v).toList ::: checkLength(l, v, ln, conf).toList 31 | case false => checkFormat(l, v).toList ::: checkLength(l, v, ln, conf).toList 32 | } 33 | 34 | /** 35 | * Checks the length and returns the error if any 36 | * @param l - The location 37 | * @param v - The value 38 | * @param lc - The length constraint 39 | * @param s - The separators 40 | * @return The error if any or None 41 | */ 42 | def checkLength(l: Location, v: Value, ln: Option[Range], conf: Option[Range])(implicit s: Separators, Detections: ConfigurableDetections): Option[Entry] = 43 | ln match { 44 | case Some(lr) => conf match { 45 | case Some(c) => Some(Detections.lengthSpecErrorXOR(l)) 46 | case None => checkRange(l, v, ln) 47 | } 48 | case None => conf match { 49 | case Some(c) => checkRange(l, v, conf) 50 | case None => Some(Detections.lengthSpecErrorNF(l)) 51 | } 52 | } 53 | 54 | def checkRange(l: Location, v: Value, r: Option[Range])(implicit s: Separators, Detections: ConfigurableDetections): Option[Entry] = 55 | r flatMap { range => 56 | val raw = unescape(v.raw) 57 | if (range includes raw.length) None 58 | else Some(Detections.length(l, range, raw)) 59 | } 60 | 61 | /** 62 | * Checks the format including the presence of separators in the value 63 | * @param l - The location 64 | * @param v - The value 65 | * @param s - The separators 66 | * @return The error if any or None 67 | */ 68 | def checkFormat(l: Location, v: Value)(implicit s: Separators, Detections: ConfigurableDetections): Option[Entry] = 69 | v match { 70 | case Number(x) => checkNumber(x) map { m => Detections.format(l, m) } 71 | case Date(x) => checkDate(x) map { m => Detections.format(l, m) } 72 | case Time(x) => checkTime(x) map { m => Detections.format(l, m) } 73 | case DateTime(x) => checkDateTime(x) map { m => Detections.format(l, m) } 74 | case _ if containSeparators(v) && !l.path.equals("MSH-1") && !l.path.equals("MSH-2") => Some(Detections.unescaped(l)) 75 | case _ => None 76 | } 77 | 78 | /** 79 | * Returns true if the value contain unescaped 80 | * field, component or sub-component separator 81 | */ 82 | private def containSeparators(v: Value)(implicit s: Separators): Boolean = 83 | v.raw exists { c => c == s.fs || c == s.cs || c == s.ss || c == s.rs } 84 | } 85 | -------------------------------------------------------------------------------- /parser/src/test/scala/hl7/v2/parser/impl/ParserSpec.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.parser.impl 2 | 3 | trait ParserSpec extends org.specs2.Specification 4 | with LOISpec 5 | with LOIv1Spec 6 | with VXUSpec 7 | with ORUSpec { 8 | def is = s2""" 9 | Segment & Group Parsing 10 | Simple Look ahead to match a group. 11 | GROUP_HEAD is the list of segments at the top of a group profile. 12 | CURRENT_SEGMENT is the segment in the message that the parser is currently on 13 | CURRENT_MODEL is the profile node that the parser is currently on 14 | Example Group Model: 15 | GROUP_1 16 | SEG_A 17 | SEG_B 18 | GROUP_1.1 19 | SEG_C 20 | SEG_D 21 | In this example the GROUP_HEAD of GROUP_1 is [SEG_A, SEG_B], GROUP_1.1 [SEG_C, SEG_D] 22 | Group Matching : 23 | If the CURRENT_MODEL is a Group Model and the CURRENT_SEGMENT is part of the GROUP_HEAD of the CURRENT_MODEL, 24 | the CURRENT_SEGMENT will be considered as being contained in the CURRENT_MODEL's Group Model, otherwise the CURRENT_MODEL will be considered as missing 25 | When no GROUP_HEAD are in the model, perform a look forward inside all the children 26 | 27 | Test Cases 28 | LOI 29 | 30 | JAIMIE_VALID_MESSAGE_LOI $JAIMIE_VALID_MESSAGE_LOI_TEST 31 | JAIMIE_VALID_MESSAGE_WITH_ORDER_PRIOR_LOI $JAIMIE_VALID_MESSAGE_WITH_ORDER_PRIOR_LOI_TEST 32 | 33 | LOIv1 34 | 35 | PT_LOIV1 $PT_LOIV1_TEST 36 | SED_RATE_LOIV1 $SED_RATE_LOIV1_TEST 37 | CBC_LOIV1 $CBC_LOIV1_TEST 38 | LIPID_PANEL_LOIV1 $LIPID_PANEL_LOIV1_TEST 39 | LIPID_PANEL_FI_LOIV1 $LIPID_PANEL_FI_LOIV1_TEST 40 | CULTURE_AND_SUSCEP_LOIV1 $CULTURE_AND_SUSCEP_LOIV1_TEST 41 | REFLEX_HEPATITIS_LOIV1 $REFLEX_HEPATITIS_LOIV1_TEST 42 | PAP_SMEAR_LOIV1 $PAP_SMEAR_LOIV1_TEST 43 | GHP_LOIV1 $GHP_LOIV1_TEST 44 | CREATININE_CLEARANCE_LOIV1 $CREATININE_CLEARANCE_LOIV1_TEST 45 | PROSTATE_BIOPSY_LOIV1 $PROSTATE_BIOPSY_LOIV1_TEST 46 | ALL_SEGMENTS__NO_REPS_LOIV1 $ALL_SEGMENTS__NO_REPS_LOIV1_TEST 47 | ALL_SEGMENTS__SEGMENT_REPETITIONS_LOIV1 $ALL_SEGMENTS__SEGMENT_REPETITIONS_LOIV1_TEST 48 | MULTIPLE_GROUPS_REPS_LOIV1 $MULTIPLE_GROUPS_REPS_LOIV1_TEST 49 | ONE_ORDER_GROUP_WITH_PRIOR_RESULTS_LOIV1 $ONE_ORDER_GROUP_WITH_PRIOR_RESULTS_LOIV1_TEST 50 | ONE_ORDER_GROUP_WITH_MULTIPLE_PRIOR_RESULTS_LOIV1 $ONE_ORDER_GROUP_WITH_MULTIPLE_PRIOR_RESULTS_LOIV1_TEST 51 | ONE_ORDER__ONE_PRIOR_RESULT__MULTIPLE_ORDER_PRIOR_LOIV1 $ONE_ORDER__ONE_PRIOR_RESULT__MULTIPLE_ORDER_PRIOR_LOIV1_TEST 52 | 53 | ORU 54 | 55 | SED_RATE_1_ORU $SED_RATE_1_ORU_TEST 56 | SED_RATE_1__NO_ORC_ORU $SED_RATE_1__NO_ORC_ORU_TEST 57 | SED_RATE_2_ORU $SED_RATE_2_ORU_TEST 58 | SED_RATE_3_ORU $SED_RATE_3_ORU_TEST 59 | CBC_ORU $CBC_ORU_TEST 60 | CULTURE_AND_SUSCEPTIBILITY_2_ORU $CULTURE_AND_SUSCEPTIBILITY_2_ORU_TEST 61 | REFLEX_1_ORU $REFLEX_1_ORU_TEST 62 | REFLEX_1__NO_ORC_ORU $REFLEX_1__NO_ORC_ORU_TEST 63 | REFLEX_1__THE_FIRST_ORC_ORU $REFLEX_1__THE_FIRST_ORC_ORU_TEST 64 | REFLEX_1__THE_SECOND_ORC_PRESENT_ORU $REFLEX_1__THE_SECOND_ORC_PRESENT_ORU_TEST 65 | CCHD_EXAMPLE_MESSAGE_ORU $CCHD_EXAMPLE_MESSAGE_ORU_TEST 66 | 67 | VXU 68 | 69 | CONTEXT_FREE_EXAMPLE_VXU $CONTEXT_FREE_EXAMPLE_VXU_TEST 70 | """ 71 | } 72 | 73 | -------------------------------------------------------------------------------- /validation/src/test/resources/ORU_R01_Profile.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | 3 | <ConformanceProfile ID="xxx" Type="Constrainable" HL7Version="2.5.1" SchemaVersion="2.5" 4 | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 5 | xsi:noNamespaceSchemaLocation="Profile.xsd"> 6 | 7 | <MetaData Name="Test" OrgName="Test" Version="Test" Date="Test"></MetaData> 8 | 9 | <Encodings> 10 | <Encoding>ER7</Encoding> 11 | <Encoding>XML</Encoding> 12 | </Encodings> 13 | 14 | <Messages> 15 | <Message ID="ORU_R01" Type="ORU" Event="R01" StructID="ORU_R01" Description="ORU/ACK - Unsolicited transmission of an observation message"> 16 | <Segment Ref="MSH" Usage="R" Min="1" Max="1"/> 17 | <Segment Ref="SFT" Usage="W" Min="0" Max="*" /> 18 | <Segment Ref="UAC" Usage="X" Min="0" Max="1"/> 19 | <Group ID="1" Name="PATIENT" Usage="R" Min="1" Max="2"> 20 | <Segment Ref="PID" Usage="R" Min="1" Max="1"/> 21 | <Segment Ref="UAC" Usage="R" Min="2" Max="2"/> 22 | </Group> 23 | <Group ID="2" Name="ORDER" Usage="X" Min="1" Max="*"> 24 | <Segment Ref="SFT" Usage="R" Min="0" Max="1"/> 25 | </Group> 26 | </Message> 27 | </Messages> 28 | 29 | <Segments> 30 | <Segment ID="MSH" Label="MSH" Name="MSH" Description="Message Header"> 31 | <Field Name="Field Separator" Datatype="ST" Usage="R" Min="1" Max="1" MinLength="1" MaxLength="1" ItemNo="00001"/> 32 | <Field Name="Encoding Characters" Datatype="ST" Usage="R" Min="1" Max="1" MinLength="4" MaxLength="5" ItemNo="00002"/> 33 | </Segment> 34 | <Segment ID="SFT" Label="SFT" Name="SFT" Description="Software Segment"> 35 | <Field Name="Software Vendor Organization" Datatype="ST" Usage="R" Min="1" Max="1" MinLength="1" MaxLength="*" ItemNo="01834"/> 36 | </Segment> 37 | <Segment ID="UAC" Label="UAC" Name="UAC" Description="User Authentication Credential Segment"> 38 | <Field Name="User Authentication Credential Type Code" Datatype="HD" Usage="X" Min="1" Max="1" MinLength="1" MaxLength="*" ItemNo="02267"/> 39 | </Segment> 40 | <Segment ID="PID" Label="PID" Name="PID" Description="Patient Identification"> 41 | <Field Name="Set ID - PID" Datatype="ST" Usage="R" Min="1" Max="1" MinLength="2" MaxLength="3" ItemNo="00104"/> 42 | <Field Name="Patient ID" Datatype="-" Usage="W" Min="0" Max="1" MinLength="1" MaxLength="*" ItemNo="00105"/> 43 | <Field Name="Patient Identifier List" Datatype="CX" Usage="R" Min="2" Max="3" MinLength="1" MaxLength="*" ItemNo="00106"/> 44 | </Segment> 45 | </Segments> 46 | 47 | <Datatypes> 48 | <Datatype ID="-" Name="-" Description="withdrawn"/> 49 | <Datatype ID="ST" Name="ST" Description="String Data"/> 50 | <Datatype ID="CX" Name="CX" Description="Extended Composite ID with Check Digit"> 51 | <Component Name="ID Number" Datatype="ST" Usage="X" MinLength="1" MaxLength="*" ConfLength="15" /> 52 | <Component Name="Identifier Check Digit" Datatype="ST" Usage="X" MinLength="1" MaxLength="*" ConfLength="4" /> 53 | <Component Name="Check Digit Scheme" Datatype="ST" Usage="C" MinLength="3" MaxLength="3"/> 54 | <Component Name="Assigning Authority" Datatype="HD" Usage="C" MinLength="3" MaxLength="*"/> 55 | </Datatype> 56 | <Datatype ID="HD" Name="HD" Description="Hierarchic Designator"> 57 | <Component Name="Namespace ID" Datatype="ST" Usage="W" MinLength="1" MaxLength="*" ConfLength="20" /> 58 | <Component Name="Universal ID" Datatype="ST" Usage="R" MinLength="3" MaxLength="*" /> 59 | <!-- ConfLength="199" --> 60 | <Component Name="Universal ID Type" Datatype="ST" Usage="X" MinLength="1" MaxLength="6"/> 61 | </Datatype> 62 | </Datatypes> 63 | 64 | </ConformanceProfile> 65 | -------------------------------------------------------------------------------- /validation/src/main/scala/hl7/v2/validation/Validator.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.validation 2 | 3 | import gov.nist.validation.report.Report 4 | import hl7.v2.parser.Parser 5 | import hl7.v2.profile.Profile 6 | 7 | import scala.concurrent.ExecutionContext.Implicits.global 8 | import scala.concurrent.Future 9 | import scala.util.{ Failure, Success } 10 | import hl7.v2.validation.report.ConfigurableDetections 11 | import com.typesafe.config.ConfigFactory 12 | import java.io.Reader 13 | 14 | /** 15 | * Trait defining the message validation 16 | * 17 | * @author Salifou Sidi M. Malick <salifou.sidi@gmail.com> 18 | */ 19 | 20 | trait Validator { this: Parser with structure.Validator with content.Validator => 21 | val profile: Profile 22 | 23 | val valueSetLibrary: vs.ValueSetLibrary 24 | 25 | /** 26 | * Validates the message using the mixed in structure, 27 | * content and value set validators and returns the report. 28 | * @param message - The message to be validated 29 | * @param id - The id of the message as defined in the profile 30 | * @return The validation report 31 | */ 32 | def validate(message: String, id: String): Future[Report] = validate(message, id, null) 33 | def validate(message: String, id: String, configuration: Reader): Future[Report] = 34 | profile.messages get id match { 35 | case None => 36 | val msg = s"No message with id '$id' is defined in the profile" 37 | Future failed new Exception(msg) 38 | case Some(model) => 39 | parse(message, model) match { 40 | case Success(m) => 41 | val defaultConfig = ConfigFactory.load(); 42 | implicit val detections: ConfigurableDetections = if (configuration == null) new ConfigurableDetections(defaultConfig) else new ConfigurableDetections(ConfigFactory.parseReader(configuration).withFallback(defaultConfig).resolve()); 43 | implicit val vsValidator: vs.Validator = new vs.Validator(detections) 44 | val structErrors = checkStructure(m) 45 | val contentErrors = checkContent(m) 46 | val valueSetErrors = Future { vsValidator.checkValueSet(m, valueSetLibrary) } 47 | for { 48 | r1 <- structErrors 49 | r2 <- contentErrors 50 | r3 <- valueSetErrors 51 | } yield report.Report(r1, r2, r3) 52 | case Failure(e) => Future failed e 53 | } 54 | } 55 | } 56 | 57 | /** 58 | * An HL7 message validator which uses an empty value set validator 59 | * and the default implementation of the parser, structure validator, 60 | * content validator and expression evaluator. 61 | */ 62 | class HL7Validator( 63 | val profile: Profile, 64 | val valueSetLibrary: vs.ValueSetLibrary, 65 | val conformanceContext: content.ConformanceContext) extends Validator 66 | with hl7.v2.parser.impl.DefaultParser 67 | with structure.DefaultValidator 68 | with content.DefaultValidator 69 | with expression.DefaultEvaluator 70 | 71 | /** 72 | * A synchronous HL7 message validator which uses an empty value set 73 | * validator and the default implementation of the parser, 74 | * structure validator, content validator and expression evaluator. 75 | */ 76 | class SyncHL7Validator( 77 | val profile: Profile, 78 | val valueSetLibrary: vs.ValueSetLibrary, 79 | val conformanceContext: content.ConformanceContext) extends Validator 80 | with hl7.v2.parser.impl.DefaultParser 81 | with structure.DefaultValidator 82 | with content.DefaultValidator 83 | with expression.DefaultEvaluator { 84 | 85 | import scala.concurrent.Await 86 | import scala.concurrent.duration._ 87 | 88 | @throws[Exception] 89 | def check(message: String, id: String): Report = 90 | Await.result(validate(message, id), 10.second) 91 | 92 | @throws[Exception] 93 | def checkUsingConfiguration(message: String, id: String, configuration: Reader): Report = 94 | Await.result(validate(message, id, configuration), 10.second) 95 | 96 | } 97 | 98 | -------------------------------------------------------------------------------- /validation/src/main/scala/hl7/v2/validation/vs/ValueSetLibraryImpl.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.validation.vs 2 | 3 | import java.io.InputStream 4 | import java.lang.Boolean 5 | 6 | import nist.xml.util.XOMDocumentBuilder 7 | 8 | import scala.util.Try 9 | 10 | case class ValueSetLibraryImpl( 11 | noValidation: Seq[String], 12 | library: Map[String, ValueSet] 13 | ) extends ValueSetLibrary { 14 | 15 | override def isExcludedFromTheValidation(id: String): Boolean = 16 | noValidation contains id 17 | 18 | override def get(id: String): ValueSet = 19 | library get id match { 20 | case Some(x) => x 21 | case None => throw new ValueSetNotFoundException(id) 22 | } 23 | } 24 | 25 | object ValueSetLibraryImpl { 26 | 27 | import nist.xml.util.XOMExtensions._ 28 | 29 | private val xsd = getClass.getResourceAsStream("/vs/ValueSets.xsd") 30 | 31 | /** 32 | * Builds and returns the value set map from the XML file 33 | */ 34 | def apply(vsXML: InputStream): Try[ValueSetLibrary] = { 35 | XOMDocumentBuilder.build( vsXML, getClass.getResourceAsStream("/vs/ValueSets.xsd") ) map { doc => 36 | val root = doc.getRootElement 37 | val noValDef = root.getFirstChildElement("NoValidation") 38 | //val tblSet = root.getFirstChildElement("ValueSetDefinitions") 39 | val tbls = root.getChildElements("ValueSetDefinitions") 40 | val noVal = noValidation( noValDef ) 41 | val lib = tables(tbls) 42 | ValueSetLibraryImpl(noVal, lib) 43 | } 44 | } 45 | 46 | 47 | private def noValidation(e: nu.xom.Element): Seq[String] = 48 | if(e == null) Nil else e.getChildElements("BindingIdentifier").map(_.getValue.trim).toList 49 | 50 | private def tableSet(e: nu.xom.Element): Map[String, ValueSet] = 51 | if( e == null ) Map() 52 | else { 53 | val tableDefs = e.getChildElements("ValueSetDefinition") 54 | tableDefs.foldLeft(Map[String, ValueSet]()) { (acc, x) => 55 | val vs = valueSet(x) 56 | acc + (vs.id -> vs) 57 | } 58 | } 59 | 60 | private def tables(e: nu.xom.Elements): Map[String, ValueSet] = 61 | if( e == null ) Map() 62 | else { 63 | e.foldLeft(Map[String, ValueSet]()){ (acc, x) => 64 | acc ++ tableSet(x) 65 | } 66 | } 67 | 68 | private def valueSet(e: nu.xom.Element): ValueSet = { 69 | val id = e.attribute("BindingIdentifier") 70 | val _stability = stability( e.attribute("Stability") ) 71 | val _extensibility = extensibility( e.attribute("Extensibility") ) 72 | val codes = (e.getChildElements("ValueElement") map code).toList 73 | ValueSet(id, _extensibility, _stability, codes ) 74 | } 75 | 76 | private def stability(s: String): Option[Stability] = 77 | s match { 78 | case "" => None 79 | case "Static" => Some(Stability.Static) 80 | case "Dynamic" => Some(Stability.Dynamic) 81 | case x => throw new Exception(s"Invalid value set stability '$x'") 82 | } 83 | 84 | private def extensibility(s: String): Option[Extensibility] = 85 | s match { 86 | case "" => None 87 | case "Open" => Some(Extensibility.Open) 88 | case "Closed" => Some(Extensibility.Closed) 89 | case x => throw new Exception(s"Invalid value set extensibility '$x'") 90 | } 91 | 92 | private def code(e: nu.xom.Element): Code = { 93 | val value = e.attribute("Value") 94 | val desc = e.attribute("DisplayName") 95 | val usage = codeUsage( e.attribute("Usage") ) 96 | val codeSys = e.attribute("CodeSystem") 97 | Code(value, desc, usage, codeSys) 98 | } 99 | 100 | private def codeUsage(s: String): CodeUsage = s match { 101 | case "R" => CodeUsage.R 102 | case "E" => CodeUsage.E 103 | case "P" => CodeUsage.P 104 | case "" => CodeUsage.R //FIXME This is not needed if the xml is validated against the XSD 105 | case x => throw new Exception(s"Invalid code usage '$x'") 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /parser/src/test/scala/hl7/v2/instance/util/ValueFormatCheckersSpec.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.instance.util 2 | 3 | import hl7.v2.instance.util.ValueFormatCheckers._ 4 | import org.specs2.mutable.Specification 5 | 6 | class ValueFormatCheckersSpec extends Specification { 7 | 8 | "isValidNumberFormat" should { 9 | val valids = Seq("1", "+1", "-1", "1.", "+1.", "-1.", "1.0", "+1.0", "-1.0", ".5", "-.5") 10 | s"returns true for ${valids.mkString("{'", "', '", "'}")}" in { 11 | valids map { s => isValidNumberFormat(s) === true } 12 | } 13 | val invalids = Seq("", " 1", "1 ", "1e7") 14 | s"returns false for ${invalids.mkString("{'", "', '", "'}")}" in { 15 | invalids map { s => isValidNumberFormat(s) === false } 16 | } 17 | } 18 | 19 | "isValidDateFormat" should { 20 | val valids = Seq("0000", "000001", "00000112", "20160229") 21 | s"returns true for ${valids.mkString("{'", "', '", "'}")}" in { 22 | valids map { s => isValidDateFormat(s) === true } 23 | } 24 | val invalids = Seq("12", " 2012", "2012 ", "000013", "00001232") 25 | s"returns false for ${invalids.mkString("{'", "', '", "'}")}" in { 26 | invalids map { s => isValidDateFormat(s) === false } 27 | } 28 | } 29 | 30 | "isValidTimeFormat" should { 31 | val valids = Seq("00", "0000", "000000.0", "000000.0000", "00+0000", "00-0000") 32 | s"returns true for ${valids.mkString("{'", "', '", "'}")}" in { 33 | valids map { s => isValidTimeFormat(s) === true } 34 | } 35 | val invalids = Seq("0", "0060", "005960", " 00", "00 ", "00-123", 36 | "00+12345", "000000.", "000000.00000","+1200", "00+") 37 | s"returns false for ${invalids.mkString("{'", "', '", "'}")}" in { 38 | invalids map { s => isValidTimeFormat(s) === false } 39 | } 40 | } 41 | 42 | "isValidDateTimeFormat" should { 43 | val valids = Seq("2016", "2016+1234", "201601", "20160101", "2016010100", 44 | "201601010000", "20160101000000", "20160101000000.0") 45 | s"returns true for ${valids.mkString("{'", "', '", "'}")}" in { 46 | valids map { s => isValidDateTimeFormat(s) === true } 47 | } 48 | val invalids = Seq("201600", "20160132", "2016010124", "201601010060", 49 | "20160101000060", "20160101000000.", "20160101000000.00000") 50 | s"returns false for ${invalids.mkString("{'", "', '", "'}")}" in { 51 | invalids map { s => isValidDateTimeFormat(s) === false } 52 | } 53 | } 54 | 55 | "isValidTimeZone" should { 56 | val valids = Seq("+0000", "-0000","+2300") 57 | s"returns true for ${valids.mkString("{'", "', '", "'}")}" in { 58 | valids map { s => isValidTimeZone(s) === true } 59 | } 60 | val invalids = Seq("0000", "-000", "+00000", "+2400", "+2360", " +0000", "+0000 ", "+ 0000") 61 | s"returns false for ${invalids.mkString("{'", "', '", "'}")}" in { 62 | invalids map { s => isValidTimeZone(s) === false } 63 | } 64 | } 65 | 66 | "Checking number of days in a Date/DateTime" should { 67 | 68 | val invalidDaysInMonth = Seq("20140431", "20140631", "20140931", "20141131") 69 | 70 | s"returns an error for ${ invalidDaysInMonth.mkString("{", ", ", "}") }" in { 71 | invalidDaysInMonth map { s => 72 | val r = Some(s"$s is not a valid Date. The month ${ 73 | s drop 4 take 2} cannot have 31 days.") 74 | checkDate(s) === r and checkDateTime(s) === r 75 | } 76 | } 77 | 78 | val invalidDaysInFebruary = Seq("20140230", "20140231") 79 | 80 | s"returns an error for ${ invalidDaysInFebruary.mkString("{", ", ", "}") }" in { 81 | invalidDaysInFebruary map { s => 82 | val r = Some(s"$s is not a valid Date. February cannot have ${s drop 6} days.") 83 | checkDate(s) === r and checkDateTime(s) === r 84 | } 85 | } 86 | 87 | val d = "20150229" 88 | 89 | s"returns an error for $d. It has 29 days but it is not a leap year." in { 90 | val r = Some(s"$d is not a valid Date. " + 91 | s"February cannot have 29 days since 2015 is not a leap year.") 92 | checkDate(d) === r and checkDateTime(d) === r 93 | } 94 | 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /validation/src/main/scala/hl7/v2/validation/content/PatternFinder.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.validation.content 2 | 3 | import expression.EvalResult.{ Pass } 4 | import gov.nist.validation.report.{ Entry } 5 | import hl7.v2.instance._ 6 | import hl7.v2.instance.Query._ 7 | import hl7.v2.profile.{ Message => MM } 8 | import scala.jdk.CollectionConverters.SeqHasAsJava 9 | import expression._ 10 | import hl7.v2.validation.vs.ValueSetLibrary 11 | import scala.util.{ Failure, Success } 12 | import hl7.v2.validation.report.ConfigurableDetections 13 | 14 | 15 | trait PatternFinder extends expression.Evaluator { 16 | 17 | implicit def valueSetLibrary: ValueSetLibrary 18 | 19 | def checkContexts(e: Element, c: List[Context], validator: (Element, Constraint) => List[Entry])(implicit s: Separators, 20 | dtz: Option[TimeZone], model: MM, Detections : ConfigurableDetections, VSValidator : hl7.v2.validation.vs.Validator): List[Entry] = { 21 | c.foldLeft(List[Entry]())({ (acc, context) => 22 | checkContext(e,context,validator) ::: acc 23 | }) 24 | } 25 | 26 | 27 | def checkContext(e: Element, c: Context, validator: (Element, Constraint) => List[Entry])(implicit s: Separators, 28 | dtz: Option[TimeZone], model: MM, Detections : ConfigurableDetections, VSValidator : hl7.v2.validation.vs.Validator): List[Entry] = { 29 | query(e, c.contextPath) match { 30 | case Success(Nil) => missingContext(c,e) 31 | case Success(x) => checkPatterns(x, c.Patterns,e, validator) 32 | case Failure(err) => List[Entry](Detections.cntSpecError(e, Constraint("Content",None,None,None,err.getMessage,Presence(c.contextPath)), err.getMessage, Nil.asJava)) 33 | } 34 | } 35 | 36 | def missingContext(c : Context,e: Element)(implicit Detections : ConfigurableDetections): List[Entry] = { 37 | c.Patterns.foldLeft(List[Entry]())({ (acc, p) => 38 | acc ::: List[Entry](Detections.HLcontentErr(p.trigger.errorMessage,e)) 39 | }) 40 | } 41 | 42 | def checkPatterns( el: List[Element], pl: List[Pattern], root: Element, validator: (Element, Constraint) => List[Entry]) (implicit s: Separators, 43 | dtz: Option[TimeZone], model: MM, Detections : ConfigurableDetections, VSValidator : hl7.v2.validation.vs.Validator): List[Entry] = { 44 | 45 | def loop( el: List[Element], pl: List[Pattern]): List[Entry] = { 46 | pl match { 47 | case x::tail => val z = search(x,el,root, validator); z._1 ++ loop(z._2,tail) 48 | case Nil => Nil 49 | } 50 | } 51 | 52 | loop(el,pl) 53 | } 54 | 55 | def search(p : Pattern, el: List[Element], root: Element, validator: (Element, Constraint) => List[Entry]) 56 | (implicit s: Separators, dtz: Option[TimeZone], model: MM, Detections : ConfigurableDetections, VSValidator : hl7.v2.validation.vs.Validator) : (List[Entry], List[Element]) = { 57 | 58 | def find(els: List[Element], exp: Expression): List[Element] = { 59 | els match { 60 | case x::tail => eval(exp, x) match { 61 | case Pass => List[Element](x) ::: find(tail,exp) 62 | case _ => find(tail,exp) 63 | } 64 | case Nil => List[Element]() 65 | } 66 | } 67 | 68 | val _match = find(el,p.trigger.expression) 69 | 70 | if(_match.isEmpty) (List.fill(p.cardinality)(Detections.HLcontentErr(p.trigger.errorMessage, root)), el) 71 | else { 72 | val cstr_entries = _match map { checkConstraints(_,p.constraints,validator) } 73 | val cntx_entries = _match map { checkContexts(_,p.contexts,validator) } 74 | val missing = List.fill(math.max(0, (p.cardinality - _match.size)))(Detections.HLcontentErr(p.trigger.errorMessage, root)) 75 | (cstr_entries.flatten ::: cntx_entries.flatten ::: missing, el diff _match) 76 | } 77 | } 78 | 79 | def checkConstraints(e: Element,constraints: List[Constraint], validator: (Element, Constraint) => List[Entry])(implicit s: Separators, 80 | dtz: Option[TimeZone], model: MM): List[Entry] = { 81 | 82 | constraints.foldLeft(List[Entry]())({ 83 | (acc, c) => acc ::: validator(e,c) 84 | }); 85 | 86 | } 87 | 88 | 89 | } -------------------------------------------------------------------------------- /validation/src/test/scala/expression/StringListSpec.scala: -------------------------------------------------------------------------------- 1 | package expression 2 | 3 | import expression.EvalResult.Pass 4 | import hl7.v2.instance.Text 5 | import hl7.v2.instance.Query._ 6 | import org.specs2.Specification 7 | 8 | import scala.util.Success 9 | 10 | trait StringListSpec extends Specification with Evaluator with Mocks { 11 | 12 | /* 13 | StringListSpec 14 | StringList evaluation should succeed if the path is not populated $stringListPathNotPopulated 15 | StringList evaluation should be inconclusive if the path is complex $stringListPathComplex 16 | StringList evaluation should be inconclusive if the path is invalid $stringListPathInvalid 17 | StringList evaluation should be inconclusive if the path is unreachable $stringListPathUnreachable 18 | StringList should pass if the values are in the list $stringListValueInList 19 | StringList should fail if the values are not in the list $stringListValueNotInList 20 | If the path is valued to multiple elements 21 | StringList should pass if one of the elements is in the list and AtLeastOnce = True $stringListAtLeastOnceT 22 | StringList should fail if one of the elements is not in the list and AtLeastOnce = False $stringListAtLeastOnceF 23 | StringList evaluation should fail If not present behavior is FAIL and no element is found $stringListNoElmFAIL 24 | StringList evaluation should be inconclusive If not present behavior is INCONCLUSIVE and no element is found $stringListNoElmINC 25 | StringList evaluation should pass If not present behavior is PASS and no element is found $stringListNoElmPASS 26 | */ 27 | 28 | //c1.4[1] is not populated 29 | assert( queryAsSimple(c1, "4[1]") == Success(Nil) ) 30 | def stringListPathNotPopulated = eval( StringList("4[1]", Nil), c1 ) === Pass 31 | 32 | def stringListNoElmFAIL = { 33 | val f = StringList("4[1]", Nil, false, "FAIL") 34 | eval(f, c1) === Failures.notPresentBehaviorFail(f, f.path, c1) 35 | } 36 | def stringListNoElmINC = { 37 | val f = StringList("4[1]", Nil, false, "INCONCLUSIVE") 38 | eval(f, c1) === Failures.notPresentBehaviorInconclusive(f, f.path, c1) 39 | } 40 | def stringListNoElmPASS = { 41 | val f = StringList("4[1]", Nil, false, "PASS") 42 | eval(f, c1) === Pass 43 | } 44 | 45 | // c1.2[3] is complex 46 | def stringListPathComplex = { 47 | val p = StringList("2[3]", Nil) 48 | eval( p, c2 ) === 49 | inconclusive(p, c2.location, "Path resolution returned at least one complex element") 50 | } 51 | 52 | // 4 is an invalid path 53 | def stringListPathInvalid = Seq(true, false) map { b => 54 | val p = StringList("4", Nil) 55 | eval( p, c0 ) === inconclusive(p, c0.location, s"Invalid Path '${p.path}'") 56 | } 57 | 58 | // s0 is a simple element, querying it will fail 59 | def stringListPathUnreachable = { 60 | val p = StringList("4[1]", Nil) 61 | eval( p, s0 ) === inconclusive(p, s0.location, s"Unreachable Path '${p.path}'") 62 | } 63 | 64 | // The following value will be used in the next tests 65 | private val `c1.3[1]` = queryAsSimple(c1, "3[1]").get.head 66 | assert( `c1.3[1]`.value == Text("S3") ) 67 | 68 | private val `c2.4[1]` = queryAsSimple(c2, "4[1]").get.head 69 | assert( `c2.4[1]`.value == Text("41\\F\\") ) 70 | 71 | def stringListValueInList = 72 | eval( StringList("3[1]", List("S3")), c1 ) === Pass and 73 | eval( StringList("4[1]", List("41|")), c2 ) === Pass 74 | 75 | def stringListValueNotInList = { 76 | val p = StringList("3[1]", List("s3")) 77 | eval( p, c1 ) === Failures.stringList(p, `c1.3[1]`::Nil) 78 | } 79 | 80 | assert( queryAsSimple(c1, "1[*]").isSuccess && queryAsSimple(c1, "1[*]").get.size > 1) 81 | def stringListAtLeastOnceT = { 82 | val p = StringList("1[*]", List("S12","XX"), true) 83 | eval( p, c1 ) === Pass 84 | } 85 | 86 | def stringListAtLeastOnceF = { 87 | val p = StringList("1[*]", List("S12","XX"), false) 88 | 89 | val `c1.1[1]` = queryAsSimple(c1, "1[1]").get.head 90 | assert( `c1.1[1]`.value == Text("S11") ) 91 | val `c1.1[3]` = queryAsSimple(c1, "1[3]").get.head 92 | assert( `c1.1[3]`.value == Text("S13") ) 93 | eval( p, c1 ) === Failures.stringList(p, `c1.1[1]`::`c1.1[3]`::Nil) 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /validation/src/test/scala/expression/NumberListSpec.scala: -------------------------------------------------------------------------------- 1 | package expression 2 | 3 | import expression.EvalResult.Pass 4 | import hl7.v2.instance.Query._ 5 | import hl7.v2.instance.{Number, Text} 6 | import org.specs2.Specification 7 | 8 | import scala.util.Success 9 | 10 | trait NumberListSpec extends Specification with Evaluator with Mocks { 11 | 12 | /* 13 | NumberListSpec 14 | NumberList evaluation should succeed if the path is not populated $numberListPathNotPopulated 15 | NumberList evaluation should be inconclusive if the path is complex $numberListPathComplex 16 | NumberList evaluation should be inconclusive if the path is invalid $numberListPathInvalid 17 | NumberList evaluation should be inconclusive if the path is unreachable $numberListPathUnreachable 18 | NumberList should be inconclusive if at least one value is not a number $numberListNaN 19 | NumberList should pass if the values are in the list $numberListValueInList 20 | NumberList should fail if the values are not in the list $numberListValueNotInList 21 | If the path is valued to multiple elements 22 | NumberList should pass if one of the elements is in the list and AtLeastOnce = True $numberListAtLeastOnceT 23 | NumberList should fail if one of the elements is not in the list and AtLeastOnce = False $numberListAtLeastOnceF 24 | NumberList evaluation should fail If not present behavior is FAIL and no element is found $numberListNoElmFAIL 25 | NumberList evaluation should be inconclusive If not present behavior is INCONCLUSIVE and no element is found $numberListNoElmINC 26 | NumberList evaluation should pass If not present behavior is PASS and no element is found $numberListNoElmPASS 27 | */ 28 | 29 | //c1.4[1] is not populated 30 | assert( queryAsSimple(c1, "4[1]") == Success(Nil) ) 31 | def numberListPathNotPopulated = eval( NumberList("4[1]", Nil), c1 ) === Pass 32 | 33 | def numberListNoElmFAIL = { 34 | val f = NumberList("4[1]", Nil, false, "FAIL") 35 | eval(f, c1) === Failures.notPresentBehaviorFail(f, f.path, c1) 36 | } 37 | def numberListNoElmINC = { 38 | val f = NumberList("4[1]", Nil, false, "INCONCLUSIVE") 39 | eval(f, c1) === Failures.notPresentBehaviorInconclusive(f, f.path, c1) 40 | } 41 | def numberListNoElmPASS = { 42 | val f = NumberList("4[1]", Nil, false, "PASS") 43 | eval(f, c1) === Pass 44 | } 45 | 46 | // c1.2[3] is complex 47 | def numberListPathComplex = { 48 | val p = NumberList("2[3]", Nil) 49 | eval( p, c2 ) === 50 | inconclusive(p, c2.location, "Path resolution returned at least one complex element") 51 | } 52 | 53 | // 4 is an invalid path 54 | def numberListPathInvalid = Seq(true, false) map { b => 55 | val p = NumberList("4", Nil) 56 | eval( p, c0 ) === inconclusive(p, c0.location, s"Invalid Path '${p.path}'") 57 | } 58 | 59 | // s0 is a simple element, querying it will fail 60 | def numberListPathUnreachable = { 61 | val p = NumberList("4[1]", Nil) 62 | eval( p, s0 ) === inconclusive(p, s0.location, s"Unreachable Path '${p.path}'") 63 | } 64 | 65 | // The following value will be used in the next tests 66 | private val `c1.3[1]` = queryAsSimple(c1, "3[1]").get.head 67 | assert( `c1.3[1]`.value == Text("S3") ) 68 | 69 | private val `c2.5[1]` = queryAsSimple(c2, "5[1]").get.head 70 | assert( `c2.5[1]`.value == Number("51") ) 71 | 72 | def numberListNaN = { 73 | val p = NumberList("3[1]", List(1)) 74 | eval(p, c1) === Failures.numberListNaN(p, `c1.3[1]`::Nil) 75 | } 76 | 77 | def numberListValueInList = eval( NumberList("5[1]", List(51)), c2 ) === Pass 78 | 79 | def numberListValueNotInList = { 80 | val p = NumberList("5[1]", List(52, 53)) 81 | eval(p, c2 ) === Failures.numberList(p, `c2.5[1]`::Nil) 82 | } 83 | 84 | assert( queryAsSimple(c1, "2[*]").isSuccess && queryAsSimple(c1, "2[*]").get.size > 1) 85 | def numberListAtLeastOnceT = { 86 | val p = NumberList("2[*]", List(52, 22),true) 87 | eval(p, c1 ) === Pass 88 | } 89 | 90 | def numberListAtLeastOnceF = { 91 | val p = NumberList("2[*]", List(21, 22),false) 92 | val `c1.2[3]` = queryAsSimple(c1, "2[3]").get.head 93 | assert( `c1.2[3]`.value == Number("23") ) 94 | eval(p, c1 ) === Failures.numberList(p, `c1.2[3]`::Nil) 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /validation/src/main/java/hl7/v2/validation/vs/Validator.java: -------------------------------------------------------------------------------- 1 | package hl7.v2.validation.vs; 2 | 3 | import gov.nist.validation.report.Entry; 4 | import hl7.v2.instance.Complex; 5 | import hl7.v2.instance.Element; 6 | import hl7.v2.instance.Message; 7 | import hl7.v2.instance.Simple; 8 | import hl7.v2.profile.Req; 9 | import hl7.v2.profile.Usage; 10 | import hl7.v2.profile.ValueSetSpec; 11 | import hl7.v2.validation.report.ConfigurableDetections; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | /** 17 | * An implementation of the value set validator 18 | * 19 | * @author Salifou Sidi M. Malick <salifou.sidi@gmail.com> 20 | */ 21 | public class Validator extends ConfigurableValidation { 22 | 23 | ComplexElementValidator complexElementValidator; 24 | SimpleElementValidator simpleElementValidator; 25 | 26 | public Validator(ConfigurableDetections detections) { 27 | super(detections); 28 | this.simpleElementValidator = new SimpleElementValidator(detections); 29 | this.complexElementValidator = new ComplexElementValidator(detections, this.simpleElementValidator); 30 | } 31 | 32 | public List<Entry> listify(Entry e){ 33 | List<Entry> detections = new ArrayList<Entry>(); 34 | detections.add(e); 35 | return detections; 36 | } 37 | 38 | /** 39 | * Checks every data element value set and return the list of problem 40 | * @param message - The message to be validated 41 | * @param library - The value set library 42 | * @return The list of problem detected 43 | */ 44 | public List<Entry> checkValueSet(Message message, ValueSetLibrary library) { 45 | List<Entry> result = new java.util.ArrayList<Entry>(); 46 | checkValueSet(result, message.asGroup(), library); 47 | return result; 48 | } 49 | 50 | /** 51 | * Checks the element against the value set specification and 52 | * return a report entry if a problem is found null otherwise 53 | * @param e - The element to be validated 54 | * @param spec - The value set specification 55 | * @param library - The value set library 56 | * @return A report entry if a problem is found null otherwise 57 | */ 58 | public Entry checkValueSet(Element e, ValueSetSpec spec, ValueSetLibrary library) { 59 | Entry etr = null; 60 | if( e instanceof Simple) 61 | etr = simpleElementValidator.check((Simple) e, spec, library); 62 | else if(e instanceof Complex){ 63 | List<Entry> l = complexElementValidator.check((Complex) e, spec, library); 64 | if( l != null && l.size() > 0) 65 | etr = l.get(0); 66 | } 67 | 68 | if(etr == null || (etr instanceof EnhancedEntry && ((EnhancedEntry) etr).isOk()) ){ 69 | return null; 70 | } 71 | else { 72 | return etr; 73 | } 74 | 75 | } 76 | 77 | 78 | private void checkValueSet(List<Entry> result, Element e, ValueSetLibrary library) { 79 | ValueSetSpec spec = getSpec(e.req()); 80 | 81 | if(spec != null && e.req().usage() instanceof Usage.O$ ){ 82 | return; 83 | } 84 | 85 | 86 | if( e instanceof Simple ) { 87 | Entry x = simpleElementValidator.check((Simple) e, getSpec(e.req()), library); 88 | 89 | if( x != null ){ 90 | if(e.req().usage() instanceof Usage.O$) 91 | result.add(Detections.toAlert(x)); 92 | else 93 | result.add(x); 94 | } 95 | 96 | } 97 | else if(e instanceof Complex) { 98 | List<Entry> l = complexElementValidator.check((Complex) e, getSpec(e.req()), library); 99 | if(l != null) 100 | for(Entry x : l){ 101 | if( x != null ) 102 | if(e.req().usage() instanceof Usage.O$) 103 | result.add(Detections.toAlert(x)); 104 | else 105 | result.add(x); 106 | } 107 | // Check the children 108 | scala.collection.Iterator<Element> it = ((Complex) e).children().iterator(); 109 | while( it.hasNext() ) 110 | checkValueSet(result, it.next(), library); 111 | } 112 | } 113 | 114 | private ValueSetSpec getSpec(Req req) { 115 | return req.vsSpec().isEmpty() ? null : req.vsSpec().head(); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /parser/src/main/scala/hl7/v2/instance/serializer/XML.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.instance 2 | package serializer 3 | 4 | import scala.xml.Elem 5 | 6 | trait XML[A] { 7 | def xml( value: A ): scala.xml.Elem 8 | } 9 | 10 | object XML { 11 | 12 | implicit object componentXML extends XML[Component] { 13 | def xml(c: Component): Elem = 14 | <Component 15 | Location={c.location.toString} 16 | >{ 17 | c match { 18 | case s: SimpleComponent => s.value 19 | case c: ComplexComponent => c.children map implicitly[XML[Component]].xml 20 | } 21 | }</Component> 22 | } 23 | 24 | implicit object fieldXML extends XML[Field] { 25 | def xml(f: Field): Elem = 26 | <Field 27 | Location={f.location.toString} 28 | > { f match { 29 | case s: SimpleField => s.value 30 | case c: ComplexField => c.children. map( implicitly[XML[Component]].xml ) 31 | case n: NULLComplexField => Value.NULL 32 | case u: UnresolvedField => Value.NULL 33 | }} 34 | </Field> 35 | } 36 | 37 | implicit object segXML extends XML[Segment] { 38 | def xml(s: Segment): Elem = 39 | <Segment 40 | Name={s.model.ref.name} 41 | Usage={s.model.req.usage.toString} 42 | Card ={s.model.req.cardinality.toString} 43 | Location={s.location.toString} 44 | > { s.children map implicitly[XML[Field]].xml } </Segment> 45 | } 46 | 47 | implicit object groupXML extends XML[Group] { 48 | def xml(g: Group): Elem = 49 | <Group 50 | Name ={g.model.name} 51 | Usage={g.model.req.usage.toString} 52 | Card ={g.model.req.cardinality.toString} 53 | Location={g.location.toString} 54 | >{ g.children map { 55 | case s: Segment => implicitly[XML[Segment]].xml(s) 56 | case g: Group => implicitly[XML[Group]].xml(g) 57 | case _ => ??? //FIXME 58 | }} 59 | </Group> 60 | } 61 | 62 | implicit object messageXML extends XML[Message] { 63 | def xml(m: Message): Elem = 64 | <Message 65 | ID={m.model.id} 66 | Type={m.model.typ} 67 | Event={m.model.event} 68 | StructID={m.model.structId} 69 | >{ 70 | <Structure> 71 | { m.children map { 72 | case s: Segment => implicitly[XML[Segment]].xml(s) 73 | case g: Group => implicitly[XML[Group]].xml(g) 74 | case _ => ??? //FIXME 75 | }} 76 | </Structure> 77 | <Invalid> 78 | { m.invalid map( l => <Line>{ l.number} # {l.content} </Line> ) } 79 | </Invalid> 80 | <Unexpected> 81 | { m.unexpected map( l => <Line>{ l.number} # {l.content} </Line> ) } 82 | </Unexpected> 83 | }</Message> 84 | } 85 | } 86 | 87 | /* 88 | implicit object componentXML extends XML[Component] { 89 | def xml(c: Component): Elem = 90 | <Component 91 | Name ={c.model.name} 92 | Usage={c.model.req.usage.toString} 93 | DT ={c.model.datatype.name} 94 | Card ={c.model.req.cardinality.getOrElse("").toString} 95 | LEN ={c.model.req.length.getOrElse("").toString} 96 | CLen ={c.model.req.confLength.getOrElse("")} 97 | Table={c.model.req.table.getOrElse("")} 98 | Location={c.location.toString} 99 | >{ 100 | c match { 101 | case s: SimpleComponent => s.value 102 | case c: ComplexComponent => c.children map implicitly[XML[Component]].xml 103 | } 104 | }</Component> 105 | } 106 | 107 | implicit object fieldXML extends XML[Field] { 108 | def xml(f: Field): Elem = 109 | <Field 110 | Name ={f.model.name} 111 | Usage={f.model.req.usage.toString} 112 | DT ={f.model.datatype.name} 113 | Card ={f.model.req.cardinality.getOrElse("").toString} 114 | LEN ={f.model.req.length.getOrElse("").toString} 115 | CLen ={f.model.req.confLength.getOrElse("")} 116 | Table={f.model.req.table.getOrElse("")} 117 | Location={f.location.toString} 118 | > { f match { 119 | case s: SimpleField => s.value 120 | case c: ComplexField => c.children. map( implicitly[XML[Component]].xml ) 121 | }} 122 | </Field> 123 | } 124 | */ -------------------------------------------------------------------------------- /validation/src/test/scala/expression/PathValueSpec.scala: -------------------------------------------------------------------------------- 1 | package expression 2 | 3 | import expression.EvalResult.{Reason, Trace, Inconclusive, Pass} 4 | import hl7.v2.instance.Query._ 5 | import hl7.v2.instance.{Number, Text} 6 | import org.specs2.Specification 7 | 8 | import scala.util.Success 9 | 10 | 11 | trait PathValueSpec extends Specification with Evaluator with Mocks { 12 | 13 | /* 14 | PathValueSpec 15 | PathValue should pass if both paths are not populated $pathValueBothPathNotPopulated 16 | PathValue evaluation should be inconclusive if the path is complex $pathValuePathComplex 17 | PathValue evaluation should be inconclusive if the path is invalid $pathValuePathInvalid 18 | PathValue evaluation should be inconclusive if the path is unreachable $pathValuePathUnreachable 19 | PathValue should fail if only one path is populated $pathValueOnePathPopulated 20 | PathValue should be inconclusive if path1 and path2 resolve to many elements $pathValueManyElems 21 | PathValue should pass if operator = < and path1.value < path2.value $pathValuePass 22 | PathValue should fail if operator = < and path1.value > path2.value $pathValueFail 23 | PathValue evaluation should fail If not present behavior is FAIL and both paths not found $pathValueNoElmFAIL 24 | PathValue evaluation should be inconclusive If not present behavior is INCONCLUSIVE and both paths not found $pathValueNoElmINC 25 | PathValue evaluation should pass If not present behavior is PASS and both paths not found $pathValueNoElmPASS 26 | */ 27 | 28 | //c1.4[1] is not populated 29 | assert( queryAsSimple(c1, "4[1]") == Success(Nil) ) 30 | def pathValueBothPathNotPopulated = 31 | eval( PathValue("4[1]", Operator.LT, "4[1]"), c1 ) === Pass 32 | 33 | def pathValueNoElmFAIL = { 34 | val f = PathValue("4[1]", Operator.LT, "4[1]", "FAIL") 35 | eval(f, c1) === Failures.notPresentBehaviorFail(f, s"[ ${f.path1}, ${f.path2} ]", c1) 36 | } 37 | def pathValueNoElmINC = { 38 | val f = PathValue("4[1]", Operator.LT, "4[1]", "INCONCLUSIVE") 39 | eval(f, c1) === Failures.notPresentBehaviorInconclusive(f, s"[ ${f.path1}, ${f.path2} ]", c1) 40 | } 41 | def pathValueNoElmPASS = { 42 | val f = PathValue("4[1]", Operator.LT, "4[1]", "PASS") 43 | eval(f, c1) === Pass 44 | } 45 | 46 | // c1.2[3] is complex 47 | def pathValuePathComplex = { 48 | val p = PathValue("2[3]", Operator.LT, "2[3]") 49 | eval( p, c2 ) === 50 | inconclusive(p, c2.location, "Path resolution returned at least one complex element") 51 | } 52 | 53 | // 4 is an invalid path 54 | def pathValuePathInvalid = { 55 | val p = PathValue("4", Operator.LT, "4") 56 | eval( p, c0 ) === inconclusive(p, c0.location, s"Invalid Path '${p.path1}'") 57 | } 58 | 59 | // s0 is a simple element, querying it will fail 60 | def pathValuePathUnreachable = Seq(true, false) map { b => 61 | val p = PathValue("4[1]", Operator.LT, "4[1]") 62 | eval( p, s0 ) === inconclusive(p, s0.location, s"Unreachable Path '${p.path1}'") 63 | } 64 | 65 | // The following value will be used in the next tests 66 | 67 | private val `c1.1[1]` = queryAsSimple(c1, "1[1]").get.head 68 | assert( `c1.1[1]`.value == Text("S11") ) 69 | 70 | private val `c1.3[1]` = queryAsSimple(c1, "3[1]").get.head 71 | assert( `c1.3[1]`.value == Text("S3") ) 72 | 73 | private val `c2.5[1]` = queryAsSimple(c2, "5[1]").get.head 74 | assert( `c2.5[1]`.value == Number("51") ) 75 | 76 | 77 | def pathValueOnePathPopulated = { 78 | val p1 = PathValue("3[1]", Operator.LT, "4[1]") 79 | val p2 = PathValue("4[1]", Operator.LT, "3[1]") 80 | eval( p1, c1 ) === Failures.pathValue(p1, `c1.3[1]`, "4[1]") and 81 | eval( p2, c1 ) === Failures.pathValue(p2, `c1.3[1]`, "4[1]") 82 | } 83 | 84 | def pathValueManyElems = { 85 | val p = PathValue("2[*]", Operator.LT, "3[*]") 86 | val p1 = s"${c1.location.path}.${p.path1}" 87 | val p2 = s"${c1.location.path}.${p.path2}" 88 | val m = s"path1($p1) and path2($p2) resolution returned respectively 3 and 3 elements." 89 | eval( p, c1 ) === Inconclusive( Trace(p, Reason(c1.location, m)::Nil ) ) 90 | } 91 | 92 | def pathValuePass = eval(PathValue("3[1]", Operator.EQ, "3[2]"), c1) === Pass 93 | 94 | def pathValueFail = { 95 | val p = PathValue("1[1]", Operator.EQ, "3[1]") 96 | eval(p, c1) === Failures.pathValue(p, `c1.1[1]`, `c1.3[1]`) 97 | } 98 | 99 | } -------------------------------------------------------------------------------- /profile/src/main/scala/hl7/v2/profile/XMLSerializer.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.profile 2 | 3 | import scala.xml.Elem 4 | 5 | /** 6 | * Module to serialize a profile to XML 7 | * 8 | * @author Salifou Sidi M. Malick <salifou.sidi@gmail.com> 9 | */ 10 | 11 | object XMLSerializer { 12 | 13 | //FIXME @Type, @HL7Version and @SchemaVersion are required 14 | def serialize( p: Profile ): Elem = 15 | <ConformanceProfile ID={p.id} 16 | Type="Constrainable" HL7Version="2.5.1" SchemaVersion="2.5" 17 | > 18 | { 19 | <Messages>{ p.messages.values map message }</Messages> 20 | <Segments>{ p.segments.values map segment }</Segments> 21 | <Datatypes>{ p.datatypes.values map datatype }</Datatypes> 22 | } 23 | </ConformanceProfile> 24 | 25 | def message( m: Message ) = 26 | <Message 27 | ID={m.id} 28 | Type={m.typ} 29 | Event={m.event} 30 | StructID={m.structId} 31 | Description={m.desc} 32 | > 33 | { 34 | m.structure map { 35 | case s: SegmentRef => segmentRef(s) 36 | case g: Group => group(g) 37 | } 38 | } 39 | </Message> 40 | 41 | def group( g: Group): Elem = 42 | <Group ID={g.id} 43 | Name={g.name} 44 | Usage={g.req.usage.toString} 45 | Min={g.req.cardinality.get.min.toString} 46 | Max={g.req.cardinality.get.min.toString} 47 | > 48 | { 49 | g.structure map { 50 | case s: SegmentRef => segmentRef(s) 51 | case g: Group => group(g) 52 | } 53 | } 54 | </Group> 55 | 56 | def segmentRef( s: SegmentRef ) = 57 | <Segment 58 | Ref={s.ref.name} 59 | Usage={s.req.usage.toString} 60 | Min={s.req.cardinality.get.min.toString} 61 | Max={s.req.cardinality.get.min.toString} 62 | /> 63 | 64 | def segment( s: Segment ) = 65 | <Segment ID={s.id} Name={s.name} Description={s.desc}> 66 | { s.mappings map dynamicMapping } 67 | { s.fields map field } 68 | </Segment> 69 | 70 | def dynamicMapping( dm: DynMapping ) = 71 | <DynamicMapping> 72 | <Mapping 73 | Position={dm.position.toString} 74 | Reference={dm.reference.toString} 75 | > 76 | <!-- { dm.map map ( t => <Case Value={t._1._1} SecondValue={t._1._2} Datatype={t._2.name}/> ) } --> 77 | </Mapping> 78 | </DynamicMapping> 79 | 80 | def field(f: Field) = 81 | <Field 82 | Name={f.name} 83 | Datatype={f.datatype.name} 84 | Usage={f.req.usage.toString} 85 | MinLength={f.req.length.get.min.toString} 86 | MaxLength={f.req.length.get.max} 87 | ConfLength={f.req.confLength.orNull} 88 | Binding={bindingIdentifier(f.req.vsSpec)} 89 | BindingStrength={bindingStrength(f.req.vsSpec)} 90 | BindingLocation={bindingLocation(f.req.vsSpec)} 91 | Min={f.req.cardinality.get.min.toString} 92 | Max={f.req.cardinality.get.max} 93 | /> 94 | 95 | def datatype(d: Datatype) = 96 | <Datatype ID={d.id} Name={d.name} Description={d.desc} Version={d.version}> 97 | { 98 | d match { 99 | case Primitive(_, _, _, _) => 100 | case Composite(_, _, _, _, xs) => xs map component 101 | case _ => 102 | } 103 | } 104 | </Datatype> 105 | 106 | def component(c: Component) = 107 | <Component 108 | Name={c.name} 109 | Datatype={c.datatype.name} 110 | Usage={c.req.usage.toString} 111 | MinLength={c.req.length.get.min.toString} 112 | MaxLength={c.req.length.get.max} 113 | ConfLength={c.req.confLength.orNull} 114 | Binding={bindingIdentifier(c.req.vsSpec)} 115 | BindingStrength={bindingStrength(c.req.vsSpec)} 116 | BindingLocation={bindingLocation(c.req.vsSpec)} 117 | /> 118 | 119 | private def bindingIdentifier(l: List[ValueSetSpec]): String = l match { 120 | case Nil => null 121 | case x :: xs => x.valueSetId 122 | } 123 | 124 | private def bindingStrength(l: List[ValueSetSpec]): String = l match { 125 | case Nil => null 126 | case x :: xs => (x.bindingStrength map ( _.toString )).orNull 127 | } 128 | 129 | private def bindingLocation(l: List[ValueSetSpec]): String = l match { 130 | case Nil => null 131 | case x :: xs => (x.bindingLocation map ( _.asString )).orNull 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /validation/src/test/scala/hl7/v2/validation/ValidatorMain.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.validation 2 | 3 | import hl7.v2.parser.impl.DefaultParser 4 | import hl7.v2.profile.XMLDeserializer 5 | import hl7.v2.validation.vs.ValueSetLibraryImpl 6 | 7 | import scala.concurrent.Await 8 | import scala.concurrent.ExecutionContext.Implicits.global 9 | import scala.util.{Failure, Success} 10 | 11 | object Main extends App with DefaultParser with structure.DefaultValidator { 12 | 13 | def time[R](block: => R): R = { 14 | val t0 = System.nanoTime() 15 | val result = block // call-by-name 16 | val t1 = System.nanoTime() 17 | println("Elapsed time: " + (t1 - t0) + "ns") 18 | result 19 | } 20 | 21 | val xml = getClass.getResourceAsStream("/Profile.xml") 22 | 23 | val profile = XMLDeserializer.deserialize( xml ) match { 24 | case Success(p) => p 25 | case Failure(e) => throw e 26 | } 27 | 28 | val mm = profile.messages("ORU_R01") 29 | 30 | val m = 31 | """ 32 | /asasa 33 | /MSH|^~\&#|^XXX^ISO^qq|NIST Lab Facility^2.16.840.1.113883.3.72.5.21^ISO||NIST EHR Facility^2.16.840.1.113883.3.72.5.23^ISO|20110531140551-2400|||NIST-LRI-GU-001.00|T|2.5.1|||AL|NE|||||LRI_Common_Component^Profile Component^2.16.840.1.113883.9.16^ISO~LRI_GU_Component^Profile Component^2.16.840.1.113883.9.12^ISO~LRI_RU_Component^Profile Component^2.16.840.1.113883.9.14^ISO 34 | /SFT 35 | /PID|11111~2~3~1~""~4||PATID1234^^^NIST MPI&2.16.840.1.113883.3.72.5.30.2&ISO^MR||Jones^William^A^JR^^^L||19610615|M||2106-3^White^HL70005^CAUC^Caucasian^L 36 | /ORC|1|ORD723222^NIST EHR^2.16.840.1.113883.3.72.5.24^ISO|R-783274^NIST Lab Filler^2.16.840.1.113883.3.72.5.25^ISO|GORD874211^NIST EHR^2.16.840.1.113883.3.72.5.24^ISO||||||||57422^Radon^Nicholas^M^JR^DR^^^NIST-AA-1&2.16.840.1.113883.3.72.5.30.1&ISO^L^^^NPI 37 | /ORC! 38 | /OBR|1|ORD723222^NIST EHR^2.16.840.1.113883.3.72.5.24^ISO|R-783274^NIST Lab Filler^2.16.840.1.113883.3.72.5.25^ISO|30341-2^Erythrocyte sedimentation rate^LN^815115^Erythrocyte sedimentation rate^99USI^^^Erythrocyte sedimentation rate|||20110331140551-0800||||L||7520000^fever of unknown origin^SCT^22546000^fever, origin unknown^99USI^^^Fever of unknown origin|||57422^Radon^Nicholas^M^JR^DR^^^NIST-AA-1&2.16.840.1.113883.3.72.5.30.1&ISO^L^^^NPI||||||20110331160428-0800|||F|||10092^Hamlin^Pafford^M^Sr.^Dr.^^^NIST-AA-1&2.16.840.1.113883.3.72.5.30.1&ISO^L^^^NPI|||||||||||||||||||||CC^Carbon Copy^HL70507^C^Send Copy^L^^^Copied Requested 39 | /NTE|1||Patient is extremely anxious about needles used for drawing blood. 40 | /NTE|2||Patient is extremely anxious about needles used for drawing blood. 41 | /NTE|3||Patient is extremely anxious about needles used for drawing blood. 42 | /TQ1|1||||||20110331150028-0800|20110331152028-0800 43 | /OBX|1|NM|1^Erythrocyte sedimentation rate^xx^815117^ESR^99USI^^^Erythrocyte sedimentation rate||10|mm/h^millimeter per hour^UCUM|0 to 17|N|||F|||20110331140551-0800|||||20110331150551-0800||||Century Hospital^^^^^NIST-AA-1&2.16.840.1.113883.3.72.5.30.1&ISO^XX^^^987|2070 Test Park^^Los Angeles^CA^90067^USA^B^^06037|2343242^Knowsalot^Phil^J.^III^Dr.^^^NIST-AA-1&2.16.840.1.113883.3.72.5.30.1&ISO^L^^^DN 44 | /OBX|2|CWE|XX^Erythrocyte sedimentation rate^xx^815117^ESR^99USI^^^Erythrocyte sedimentation rate||1^^CodeSyss|mm/h^millimeter per hour^UCUM|0 to 17|N|||F|||20110331140551-0800|||||20110331150551-0800||||Century Hospital^^^^^NIST-AA-1&2.16.840.1.113883.3.72.5.30.1&ISO^XX^^^987|2070 Test Park^^Los Angeles^CA^90067^USA^B^^06037|2343242^Knowsalot^Phil^J.^III^Dr.^^^NIST-AA-1&2.16.840.1.113883.3.72.5.30.1&ISO^L^^^DN 45 | /PDQ 46 | /""".stripMargin('/') 47 | 48 | val context1 = getClass.getResourceAsStream("/rules/CContext.xml") 49 | 50 | val context2 = getClass.getResourceAsStream("/rules/ConfContextSample.xml") 51 | 52 | val conformanceContext = content.DefaultConformanceContext().get 53 | 54 | val vsLibStream = getClass.getResourceAsStream("/ValueSets.xml") 55 | val valueSetLibrary = ValueSetLibraryImpl(vsLibStream).get 56 | 57 | val validator = new HL7Validator(profile, valueSetLibrary, conformanceContext) 58 | 59 | 1 to 1 foreach { i => 60 | time { 61 | validator.validate( m, "ORU_R01" ) onComplete { 62 | case Success( report ) => 63 | println( report.toText ) 64 | println( s"\n\n ${ report.toJson } \n\n" ) 65 | case Failure( e ) => 66 | println(s"\n\n[Error] An error occurred while validating the message ... \n\t${e.getMessage}") 67 | } 68 | } 69 | } 70 | 71 | /*import scala.concurrent.duration._ 72 | 73 | 1 to 200 foreach { i => 74 | time { 75 | val r = Await.result( validator.validate( m, "ORU_R01" ), 1.second ) 76 | r.toJson 77 | } 78 | }*/ 79 | } 80 | -------------------------------------------------------------------------------- /validation/src/test/scala/hl7/v2/validation/vs/SimpleElementValidatorSpec.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.validation.vs 2 | 3 | import gov.nist.validation.report.Entry 4 | import hl7.v2.instance.{Location, Simple, Text} 5 | import hl7.v2.profile.{BindingStrength, Req, Usage, ValueSetSpec} 6 | import hl7.v2.validation.report.Detections 7 | import CodeUsage.{E, P, R} 8 | import org.specs2.Specification 9 | import hl7.v2.validation.report.ConfigurableDetections 10 | import com.typesafe.config.ConfigFactory 11 | 12 | class SimpleElementValidatorSpec 13 | extends Specification { def is = s2""" 14 | 15 | Simple Element Value Set validation specification 16 | 17 | Value Set validation on a simple element should: 18 | Abort if the element value is Null $e0 19 | Abort if no value set is defined on the primitive element $e1 20 | Return VSNotFound if the value set cannot be found in the library $e2 21 | Return EmptyVS if the value set is empty $e3 22 | Return CodeNotFound if the code is not in the value set $e4 23 | Return VSSpecError if more than one code is found in the value set $e7 24 | Return no detection if the value is in the value set and the usage is R$e8 25 | Return no detection if the value match HL7nnn or 99ZZZ and vs is 0396 $e9 26 | Return no error if the value set is excluded from the validation $e10 27 | """ 28 | // Return EVS if the code is in the value set but the usage is E $e5 29 | // Return PVS if the code is in the value set but the usage is P $e6 30 | val l = Location(null, "", "", -1, -1) 31 | 32 | val bs: Option[BindingStrength] = None 33 | val stability = Some(Stability.Static) 34 | val extensibility = Some(Extensibility.Closed) 35 | 36 | val vs1 = ValueSet("01", extensibility, stability, Nil) 37 | val vs2 = ValueSet("02", extensibility, stability, codes = List( 38 | Code("A", "", E,"" ), 39 | Code("B", "", P,"" ), 40 | Code("X", "", R,"" ) 41 | )) 42 | val vs3 = ValueSet("03", extensibility, stability, codes = List( 43 | Code("A", "", E,"" ), 44 | Code("A", "", P,"" ) 45 | )) 46 | val vs4 = ValueSet("0396", extensibility, stability, codes = List( 47 | Code("x", "", E,"" ), 48 | Code("x", "", P,"" ) 49 | )) 50 | val vs5 = ValueSet("HL70396", extensibility, stability, codes = List( 51 | Code("x", "", E,"" ), 52 | Code("x", "", P,"" ) 53 | )) 54 | val vs6 = ValueSet("06", extensibility, stability, Nil) 55 | 56 | val noValidation = Seq("06") 57 | 58 | implicit val library = ValueSetLibraryImpl( 59 | noValidation, 60 | Map[String, ValueSet]( 61 | "01" -> vs1, 62 | "02" -> vs2, 63 | "03" -> vs3, 64 | "0396" -> vs4, 65 | "HL70396" -> vs5, 66 | "06" -> vs6 67 | ) 68 | ) 69 | 70 | def e0 = check( "\"\"", "04" ) === null 71 | def e1 = check( "", "" ) === null 72 | def e2 = check( "x", "04" ) === Detections.vsNotFound(l, "x", "04") 73 | def e3 = check( "x", "01" ) === Detections.emptyVS(l, vs1, "01") 74 | def e4 = check( "C", "02" ) === Detections.codeNotFound(l, "C", vs2, "02") 75 | // def e5 = check( "A", "02" ) === new EnhancedEntry(Detections.evs(l, "A", vs2, "02"),true) 76 | // def e6 = check( "B", "02" ) === new EnhancedEntry(Detections.pvs(l, "B", vs2, "02"),true) 77 | def e7 = check( "A", "03" ) === Detections.vsError(l, "Multiple occurrences of the code 'A' found.", vs3, "03") 78 | 79 | def e8 = check( "X", "02" ) === null 80 | 81 | def e9 = Seq("0396", "HL70396") map { vs => 82 | check("HL70001", vs) === null and check("99ZZZ", vs) === null 83 | } 84 | 85 | def e10 = check( "x", "06" ) === Detections.vsNoVal(l, "06") 86 | 87 | def check(s: String, spec: String): Entry = { 88 | val x = simple(s, spec) 89 | val y = x.req.vsSpec match { case Nil => null case z::zs => z } 90 | implicit val Detections = new ConfigurableDetections(ConfigFactory.load()); 91 | implicit val vsValidation = new SimpleElementValidator(Detections) 92 | vsValidation.check(x, y, library) 93 | } 94 | 95 | private def simple(v: String, vsid: String): Simple = 96 | new Simple { 97 | override val location = l 98 | override val value = Text(v) 99 | override val position = -1 100 | override val instance = -1 101 | override val req = vsid match { 102 | case "" => Req(-1, "", Usage.O, None, None, None, Nil) 103 | case x => Req(-1, "", Usage.O, None, None, None, ValueSetSpec(vsid, None, None)::Nil) 104 | } 105 | } 106 | 107 | implicit private def vsSpec(vsid: String): ValueSetSpec = ValueSetSpec(vsid, None, None) 108 | } 109 | -------------------------------------------------------------------------------- /validation/src/main/java/hl7/v2/validation/vs/ComplexElementValidator.java: -------------------------------------------------------------------------------- 1 | package hl7.v2.validation.vs; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | 7 | import gov.nist.validation.report.Entry; 8 | import gov.nist.validation.report.Trace; 9 | import hl7.v2.instance.Complex; 10 | import hl7.v2.instance.Query; 11 | import hl7.v2.instance.Simple; 12 | import hl7.v2.profile.BindingLocation; 13 | import hl7.v2.profile.ValueSetSpec; 14 | import hl7.v2.validation.report.ConfigurableDetections; 15 | 16 | /** 17 | * Module for validating complex element against a value set specification 18 | * 19 | * @author Salifou Sidi M. Malick <salifou.sidi@gmail.com> 20 | */ 21 | public class ComplexElementValidator extends ConfigurableValidation { 22 | 23 | private SimpleElementValidator simpleElementValidator; 24 | private CodedElementValidator codedElementValidator; 25 | 26 | public ComplexElementValidator(ConfigurableDetections detections, SimpleElementValidator simpleElementValidator) { 27 | super(detections); 28 | this.simpleElementValidator = simpleElementValidator; 29 | this.codedElementValidator = new CodedElementValidator(detections, simpleElementValidator, true); 30 | // TODO Auto-generated constructor stub 31 | } 32 | 33 | public List<Entry> listify(Entry e){ 34 | List<Entry> detections = new ArrayList<Entry>(); 35 | detections.add(e); 36 | return detections; 37 | } 38 | 39 | /** 40 | * Checks the complex element against the value set specification and 41 | * returns a detection if a problem is found, null otherwise 42 | * 43 | * @param e 44 | * - The complex element to be validated 45 | * @param spec 46 | * - The value set specification 47 | * @param library 48 | * - The value set library to be used 49 | * @return A detection if a problem is found, null otherwise 50 | */ 51 | public List<Entry> check(Complex e, ValueSetSpec spec, 52 | ValueSetLibrary library) { 53 | if (spec == null) 54 | return null; 55 | 56 | if (CodedElementValidator.isCodedElement(e)){; 57 | return codedElementValidator.check(e, spec, library); 58 | } 59 | else { 60 | 61 | if (spec.valueSetId().contains(":")) { 62 | return listify(Detections 63 | .vsError( 64 | e.location(), 65 | "Value Set Specification error, multiple bindings can only be specified for Coded Elements")); 66 | } 67 | 68 | if (library.isExcludedFromTheValidation(spec.valueSetId())) 69 | return listify(Detections.vsNoVal(e.location(), spec.valueSetId())); 70 | 71 | try { 72 | ValueSet vs = library.get(spec.valueSetId()); 73 | 74 | if (vs.isEmpty()) 75 | return listify(Detections.emptyVS(e.location(), vs, spec)); 76 | 77 | if (spec.bindingLocation().isEmpty()) 78 | return listify(Detections.vsError(e.location(), 79 | "The binding location is missing", vs, spec)); 80 | 81 | BindingLocation bl = spec.bindingLocation().get(); 82 | 83 | if (bl instanceof BindingLocation.Position) { 84 | int p = ((BindingLocation.Position) bl).value(); 85 | return listify(checkPosition(e, p, vs, spec)); 86 | } else if (bl instanceof BindingLocation.XOR) { 87 | int p1 = ((BindingLocation.XOR) bl).position1(); 88 | int p2 = ((BindingLocation.XOR) bl).position2(); 89 | return checkXOR(e, p1, p2, vs, spec); 90 | } else { 91 | String msg = "Invalid binding location " + bl; 92 | return listify(Detections.bindingLocation(e.location(), msg, vs, spec)); 93 | } 94 | 95 | } catch (ValueSetNotFoundException er) { 96 | String msg = "Value set '" + spec.valueSetId() 97 | + "' cannot be found in the library"; 98 | return listify(Detections.vsNotFound(e.location(), msg,spec)); 99 | } 100 | } 101 | } 102 | 103 | private List<Entry> checkXOR(Complex c, int p1, int p2, ValueSet vs, 104 | ValueSetSpec spec) { 105 | List<Entry> detections = new ArrayList<Entry>(); 106 | Entry e1 = checkPosition(c, p1, vs, spec); 107 | Entry e2 = checkPosition(c, p2, vs, spec); 108 | 109 | if (e2 != null){ 110 | detections.add(e2); 111 | } 112 | 113 | if(e1 != null){ 114 | detections.add(e1); 115 | } 116 | 117 | if(e1 == null && e2 == null){ 118 | String msg = "One of the triplet (but not both) should be valued from the" 119 | + " value set '" + vs.id() + "'"; 120 | detections.add(Detections.codedElem(c.location(), msg, vs, spec, null)); 121 | } 122 | 123 | return detections; // No detection 124 | } 125 | 126 | private Entry checkPosition(Complex c, int p, ValueSet vs, 127 | ValueSetSpec spec) { 128 | try { 129 | Simple s1 = CodedElementValidator.query(c, p); 130 | return simpleElementValidator.checkValueSet(s1.location(), s1 131 | .value().raw(), vs, spec); 132 | } catch (Exception e) { 133 | return Detections.bindingLocation(c.location(), e.getMessage(), vs, spec); 134 | } 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /parser/src/main/scala/hl7/v2/parser/impl/DefaultParser.scala: -------------------------------------------------------------------------------- 1 | package hl7.v2.parser.impl 2 | 3 | import hl7.v2.instance._ 4 | import hl7.v2.parser.Parser 5 | import hl7.v2.profile.{ Group => GM, Message => MM, SegRefOrGroup => SGM, SegmentRef => SM } 6 | 7 | import scala.util.Try 8 | 9 | /** 10 | * Default implementation of the parser 11 | * 12 | * @author Salifou Sidi M. Malick <salifou.sidi@gmail.com> 13 | */ 14 | 15 | trait DefaultParser extends Parser { 16 | 17 | /** 18 | * Parses the message and returns the message instance model 19 | * @param message - The message to be parsed 20 | * @param model - The message model (profile) 21 | * @return The message instance model 22 | */ 23 | def parse(message: String, model: MM): Try[Message] = 24 | PreProcessor.process(message, model) map { t => 25 | val PPR(valid, invalid, preUnexpected, separators) = t 26 | implicit val s = separators 27 | implicit val ctr = Counter(scala.collection.mutable.Map[String, Int]()) 28 | val (children, unexpected) = processChildren(model.structure, valid.map(x => Line(x._1, x._2))) 29 | val tz: Option[TimeZone] = None //FIXME Get TZ from MSH.7 30 | val ils = invalid map (x => Line(x._1, x._2)) //FIXME Update PreProcessor to use Line 31 | val uls = (preUnexpected.map(x => Line(x._1, x._2)) ::: unexpected) //FIXME Update PreProcessor to use Line 32 | Message(model, children.reverse, ils, uls, tz, s) 33 | } 34 | 35 | // Type Aliases 36 | type Stack = List[Line] // List[(Int, String)] 37 | type LS = List[Segment] 38 | type LG = List[Group] 39 | type LSG = List[SegOrGroup] 40 | 41 | /** 42 | * Creates the children of a message or a group. Returns a pair consisting 43 | * of the list of children elements and the remaining stack. 44 | */ 45 | 46 | protected def processChildren(models: List[SGM], stack: Stack, head: Boolean = true)(implicit separators: Separators, ctr: Counter): (LSG, Stack) = { 47 | var isHead = head 48 | models.foldLeft((List[SegOrGroup](), stack)) { (acc, x) => 49 | x match { 50 | case sm: SM => 51 | val (ls, s) = processSegment(sm, acc._2, isHead) 52 | isHead = false 53 | (ls ::: acc._1, s) 54 | case gm: GM => 55 | val (lg, s) = processGroup(gm, acc._2) 56 | isHead = false 57 | (lg ::: acc._1, s) 58 | } 59 | } 60 | } 61 | 62 | private def processGroup(gm: GM, stack: Stack)(implicit separators: Separators, ctr: Counter): (List[Group], Stack) = { 63 | def loop(acc: List[Group], s: Stack, i: Int): (List[Group], Stack) = { 64 | if (s.isEmpty) (acc, s) 65 | else if (!isExpected(s.head, gm)) (acc, s) 66 | else 67 | lookFor(gm, s.head) match { 68 | case Some(index) => { 69 | val (children, ss) = processChildren(gm.structure.takeRight(gm.structure.size - index), s, index == 0) 70 | val g = Group(gm, i, children.reverse) 71 | loop(g :: acc, ss, i + 1) 72 | } 73 | case None => (acc, s) 74 | } 75 | } 76 | 77 | loop(Nil, stack, 1) 78 | } 79 | 80 | private def lookFor(gm: GM, l: Line)(implicit separators: Separators, ctr: Counter) = 81 | gm.structure.zipWithIndex find 82 | { x => isExpected(l, x._1) } match { 83 | case Some((m, i)) => Some(i) 84 | case None => None 85 | } 86 | 87 | private def processSegment(sm: SM, stack: Stack, isHead: Boolean)(implicit s: Separators, ctr: Counter): (List[Segment], Stack) = 88 | if (isHead) (segment(sm, stack.head, 1) :: Nil, stack.tail) 89 | else { 90 | val (x, remainingStack) = stack span (l => isExpected(l, sm)) 91 | val ls = x.zipWithIndex map { t => segment(sm, t._1, t._2 + 1) } 92 | (ls.reverse, remainingStack) 93 | } 94 | 95 | /** 96 | * Creates and returns a segment instance 97 | * @param m - The segment model 98 | * @param l - The line number and the segment value as string 99 | * @param i - The instance number 100 | * @param s - The separators 101 | * @return A segment instance 102 | */ 103 | private def segment(m: SM, l: Line, i: Int)(implicit s: Separators, ctr: Counter) = 104 | Segment(m, l.content, i, l.number) 105 | 106 | private def isExpected(l: Line, m: GM): Boolean = m.structure.span(_.isInstanceOf[SM]) match { 107 | case (Nil, grpAndOthers) => grpAndOthers.exists(isExpected(l, _)) 108 | case (headSegments, _) => headSegments.exists(isExpected(l, _)) 109 | } 110 | private def isExpected(l: Line, m: SM ) = l.content startsWith m.ref.name 111 | private def isExpected(l: Line, m: SGM): Boolean = m match { 112 | case s : SM => isExpected(l, s) 113 | case g : GM => isExpected(l, g) 114 | } 115 | 116 | private def isHead(l : Line, m : GM) = l.content startsWith headName(m) 117 | 118 | /** 119 | * Returns the group head name 120 | * @param m - The group model 121 | * @return The group head name 122 | */ 123 | private def headName(m: GM): String = m.structure.head match { 124 | case s: SM => s.ref.name 125 | case g: GM => headName(g) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /validation/src/main/java/hl7/v2/validation/vs/SimpleElementValidator.java: -------------------------------------------------------------------------------- 1 | package hl7.v2.validation.vs; 2 | 3 | import gov.nist.validation.report.Entry; 4 | import hl7.v2.instance.Location; 5 | import hl7.v2.instance.Simple; 6 | import hl7.v2.profile.ValueSetSpec; 7 | import hl7.v2.validation.report.ConfigurableDetections; 8 | 9 | /** 10 | * Module for validating simple element against a value set specification 11 | * 12 | * @author Salifou Sidi M. Malick <salifou.sidi@gmail.com> 13 | */ 14 | public class SimpleElementValidator extends ConfigurableValidation { 15 | 16 | public SimpleElementValidator(ConfigurableDetections detections) { 17 | super(detections); 18 | // TODO Auto-generated constructor stub 19 | } 20 | 21 | /** 22 | * Checks the simple element against the value set specification 23 | * and returns a detection if a problem is found, null otherwise 24 | * @param e - The simple element to be validated 25 | * @param spec - The value set specification 26 | * @param library - The value set library to be used 27 | * @return A detection if a problem is found, null otherwise 28 | */ 29 | public Entry check(Simple e, ValueSetSpec spec, 30 | ValueSetLibrary library) { 31 | if( e.value().isNull() ) 32 | return null; 33 | return check(e.location(), e.value().raw(), spec, library); 34 | } 35 | 36 | /** 37 | * Checks the value against the value set specification and 38 | * returns a detection if a problem is found, null otherwise 39 | * @param location - The location 40 | * @param value - The value to be validated 41 | * @param spec - The value set specification 42 | * @param library - The value set library 43 | * @return A detection if a problem is found, null otherwise 44 | */ 45 | public Entry check(Location location, String value, ValueSetSpec spec, 46 | ValueSetLibrary library) { 47 | 48 | if( spec == null ) 49 | return null; 50 | 51 | if(spec.valueSetId().contains(":")){ 52 | return Detections.vsError(location, "Value Set Specification error, multiple bindings can only be specified for CodedElements"); 53 | } 54 | 55 | 56 | // Return a detection if the value set is excluded from the validation 57 | if( library.isExcludedFromTheValidation( spec.valueSetId() ) ) 58 | return Detections.vsNoVal(location, spec.valueSetId()); 59 | 60 | try { 61 | ValueSet vs = library.get( spec.valueSetId() ); 62 | return checkValueSet(location, value, vs, spec); 63 | } catch ( ValueSetNotFoundException e) { 64 | return Detections.vsNotFound(location, value, spec); 65 | } 66 | } 67 | 68 | public Entry checkValueSet(Location location, String value, ValueSet vs, 69 | ValueSetSpec spec) { 70 | if( vs.isEmpty() ) 71 | return Detections.emptyVS(location, vs, spec); 72 | 73 | if( skipCodeCheck(vs.id(), value) ) 74 | return null; 75 | 76 | return checkCode(location, value, vs, spec); 77 | } 78 | 79 | /** 80 | * Checks the code and return a detection if any otherwise return null 81 | */ 82 | private Entry checkCode(Location location, String value, ValueSet vs, 83 | ValueSetSpec spec) { 84 | java.util.List<Code> codes = vs.getCodes(value); 85 | int nbOfCodes = codes.size(); 86 | 87 | if( nbOfCodes == 0 ) { 88 | if(isOpen(vs)) { 89 | return Detections.codeNotFoundInOpen(location, value, vs, spec); 90 | } else { 91 | return Detections.codeNotFound(location, value, vs, spec); 92 | } 93 | } 94 | 95 | if( nbOfCodes == 1) 96 | return checkCodeUsage(location, value, codes.get(0), vs, spec); 97 | 98 | String msg = "Multiple occurrences of the code '"+value+"' found."; 99 | return Detections.vsError(location, msg, vs, spec); 100 | } 101 | 102 | public boolean isOpen(ValueSet vs){ 103 | try { 104 | Extensibility ex = vs.extensibility().get(); 105 | 106 | return (ex instanceof Extensibility.Open$); 107 | } catch (Exception exp) { 108 | return false; 109 | } 110 | } 111 | 112 | /** 113 | * Checks the code usage and return a detection if any otherwise return null 114 | */ 115 | private Entry checkCodeUsage(Location location, String value, Code code, 116 | ValueSet vs, ValueSetSpec spec) { 117 | if( code.usage() instanceof CodeUsage.E$ ) 118 | return new EnhancedEntry(Detections.evs(location, value, vs, spec),false); 119 | if( code.usage() instanceof CodeUsage.P$ ) 120 | return new EnhancedEntry(Detections.pvs(location, value, vs, spec),true); 121 | return null; 122 | } 123 | 124 | /** 125 | * Returns true if the code check should be skipped 126 | */ 127 | private boolean skipCodeCheck(String vsID, String value) { 128 | return vsID.matches("(HL7)?0396(_[a-zA-Z0-9]+)?") && 129 | (value.matches("HL7[0-9]{4}") || value.matches("99[a-zA-Z0-9]{3}")); 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /validation/src/test/scala/expression/PlainTextSpec.scala: -------------------------------------------------------------------------------- 1 | package expression 2 | 3 | import expression.EvalResult.Pass 4 | import hl7.v2.instance.Query 5 | import Query.queryAsSimple 6 | import hl7.v2.instance.Text 7 | import org.specs2.Specification 8 | 9 | import scala.util.Success 10 | import expression.EvalResult.Inconclusive 11 | import expression.EvalResult.Fail 12 | 13 | trait PlainTextSpec extends Specification with Evaluator with Mocks { 14 | 15 | /* 16 | PlainTextSpec 17 | PlainText evaluation should succeed if the path is not populated $plainTextPathNotPopulated 18 | PlainText evaluation should be inconclusive if the path is complex $plainTextPathComplex 19 | PlainText evaluation should be inconclusive if the path is invalid $plainTextPathInvalid 20 | PlainText evaluation should be inconclusive if the path is unreachable $plainTextPathUnreachable 21 | PlainText evaluation should pass if the values are the same $plainTextSameValue 22 | PlainText evaluation should pass if the values are the same by ignoring the case $plainTextSameValueIC 23 | PlainText evaluation should fail if the values are different $plainTextDifferentValue 24 | PlainText evaluation should fail for same values in different case when case not ignored $plainTextSameValueCNI 25 | If the path is valued to multiple elements 26 | PlainText evaluation should fail if one of the elements value is different than the expected value with AtLeastOnce = False $plainTextAtLeastOnceF 27 | PlainText evaluation should pass if one of the elements value is equal to the expected value with AtLeastOnce = True $plainTextAtLeastOnceT 28 | PlainText evaluation should fail If not present behavior is FAIL and no element is found $plainTextNoElmFAIL 29 | PlainText evaluation should be inconclusive If not present behavior is INCONCLUSIVE and no element is found $plainTextNoElmINC 30 | PlainText evaluation should pass If not present behavior is PASS and no element is found $plainTextNoElmPASS 31 | */ 32 | 33 | //c1.4[1] is not populated 34 | assert( queryAsSimple(c1, "4[1]") == Success(Nil) ) 35 | 36 | def plainTextPathNotPopulated = Seq(true, false) map { b => 37 | eval( PlainText("4[1]", "", b), c1 ) === Pass 38 | } 39 | 40 | def plainTextNoElmFAIL = Seq(true, false) map { b => 41 | val p = PlainText("4[1]", "", b, b, "FAIL") 42 | eval(p , c1 ) === Failures.notPresentBehaviorFail(p, p.path, c1) 43 | } 44 | 45 | def plainTextNoElmINC = Seq(true, false) map { b => 46 | val p = PlainText("4[1]", "", b, b, "INCONCLUSIVE") 47 | eval(p , c1 ) === Failures.notPresentBehaviorInconclusive(p, p.path, c1) 48 | } 49 | 50 | def plainTextNoElmPASS = Seq(true, false) map { b => 51 | eval( PlainText("4[1]", "", b, b, "PASS"), c1 ) === Pass 52 | } 53 | 54 | // c1.2[3] is complex 55 | def plainTextPathComplex = Seq(true, false) map { b => 56 | val p2 = PlainText("2[3]", "", b) 57 | eval( p2, c2 ) === inconclusive(p2, c2.location, "Path resolution returned at least one complex element") 58 | } 59 | 60 | // 4 is an invalid path 61 | def plainTextPathInvalid = Seq(true, false) map { b => 62 | val p3 = PlainText("4", "", b) 63 | eval( p3, c0 ) === inconclusive(p3, c0.location, s"Invalid Path '${p3.path}'") 64 | } 65 | 66 | // s0 is a simple element, querying it will fail 67 | def plainTextPathUnreachable = Seq(true, false) map { b => 68 | val p4 = PlainText("4[1]", "", b) 69 | eval( p4, s0 ) === inconclusive(p4, s0.location, s"Unreachable Path '${p4.path}'") 70 | } 71 | 72 | // The following value will be used in the next tests 73 | private val `c1.3[1]` = queryAsSimple(c1, "3[1]").get.head 74 | assert( `c1.3[1]`.value == Text("S3") ) 75 | 76 | private val `c2.4[1]` = queryAsSimple(c2, "4[1]").get.head 77 | assert( `c2.4[1]`.value == Text("41\\F\\") ) 78 | 79 | private val `c1.1[1]` = queryAsSimple(c1, "1[1]").get.head 80 | assert( `c1.1[1]`.value == Text("S11") ) 81 | 82 | 83 | def plainTextSameValue = Seq(true, false) map { b => 84 | eval( PlainText("3[1]", "S3", b), c1 ) === Pass and 85 | eval( PlainText("4[1]", "41|", b), c2 ) === Pass 86 | } 87 | 88 | def plainTextSameValueIC = eval( PlainText("3[1]", "s3", true ), c1 ) === Pass 89 | 90 | def plainTextDifferentValue = Seq(true, false) map { b => 91 | val p1 = PlainText("3[1]", "X", b) 92 | val p2 = PlainText("4[1]", "41\\F\\", b) 93 | eval( p1, c1 ) === Failures.plainText(p1, `c1.3[1]`:: Nil) and 94 | eval( p2, c2 ) === Failures.plainText(p2, `c2.4[1]`:: Nil) 95 | } 96 | 97 | def plainTextSameValueCNI = { 98 | val p = PlainText("3[1]", "s3", false) 99 | eval( p, c1 ) === Failures.plainText(p, `c1.3[1]`::Nil) 100 | } 101 | 102 | assert( queryAsSimple(c1, "1[*]").isSuccess && queryAsSimple(c1, "1[*]").get.size > 1) 103 | def plainTextAtLeastOnceF = { 104 | val p = PlainText("1[*]", "S12", false, false) 105 | val `c1.1[3]` = queryAsSimple(c1, "1[3]").get.head 106 | assert( `c1.1[3]`.value == Text("S13") ) 107 | eval(p, c1) === Failures.plainText(p, `c1.1[1]`::`c1.1[3]`::Nil) 108 | } 109 | 110 | def plainTextAtLeastOnceT = { 111 | val p = PlainText("1[*]", "S12", false, true) 112 | eval(p, c1) === Pass 113 | } 114 | } --------------------------------------------------------------------------------