The basic idea behind KScript is simple: in a markup document (XML, HTML) the bulk of the content is easiest specified by writing the document directly rather than working with an object model, and specific content is inserted where appropriate. A similar concept underlies PHP and ASP.

KScript development starts with an example of the output that is expected. As an example, let's take a CDA header fragment:

<ClinicalDocument xmlns="urn:hl7-org:v3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">  
  <typeId root="2.16.840.1.113883.1.3" extension="POCD_HD000040"/>  
  <id root="2.16.840.1.113883.1.19.9" extension="[document id]"/>  
  <code code="38959-3" codeSystem="2.16.840.1.113883.6.1" displayName="VA C&P exam.heart"/>  
  <title>Cardiac Exam</title>  
  <effectiveTime value="[Time of production]"/>  
  <confidentialityCode code="N" codeSystem="2.16.840.1.113883.5.25"/>  
  <languageCode code="en-US"/>  
  <recordTarget>    
    <patientRole>      
      <id root="2.16.840.1.113883.1.19.10" extension="[mrn]"/>      
      <id root="2.16.840.1.113883.1.19.11" extension="[mrn]"/>      
      <patient>        
        <name>       
          <given>[given]</given>       
          <family>[family]</family>        
        </name>        
        <administrativeGenderCode code="[gender]" codeSystem="2.16.840.1.113883.5.1"/>        
        <birthTime value="[dob]"/>      
      </patient>    
    </patientRole>  
  </recordTarget>  
</ClinicalDocument>

For a given use case, most of this content is constant in every single document, and a very few data fields change between documents. In the fragment above, the patient details will be different, the document id will be a serially incrementing number, and there will be one or two mrns depending on the details of the patient

In the example above, the replaceable content is marked with square brackets ("[]"). In KScript, these sections are marked with tags <% and %>, and a processor works through the script replacing the marked sections with the result of evaluating the statement inside the tags.

<ClinicalDocument xmlns="urn:hl7-org:v3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">  
  <typeId root="2.16.840.1.113883.1.3" extension="POCD_HD000040"/>  
  <id root="2.16.840.1.113883.1.19.9" extension="<%docid%>"/>  
  <code code="38959-3" codeSystem="2.16.840.1.113883.6.1" displayName="VA C&P exam.heart"/>  
  <title>Cardiac Exam</title>  
  <effectiveTime value="<%now.format("yyyymmddhhnnss")%>"/>  
  <confidentialityCode code="N" codeSystem="2.16.840.1.113883.5.25"/>  
  <languageCode code="en-US"/>  
  <recordTarget>    
    <patientRole>      
      <id root="2.16.840.1.113883.1.19.10" extension="<%mrn1%>"/>      
      <id root="2.16.840.1.113883.1.19.11" extension="<%mrn2%>"/>      
      <patient>        
        <name>       
          <given><%patientGiven%></given>       
          <family><%patientFamily%></family>        
        </name>        
        <administrativeGenderCode code="<%patientGender%>" codeSystem="2.16.840.1.113883.5.1"/>        
        <birthTime value="<%patientDob.format("yyyymmdd")%>"/>      
      </patient>    
    </patientRole>  
  </recordTarget>  
</ClinicalDocument>

In this fragment, the KScript processor will evaluate the values of "docid", "now", "mrn1", "mrn2", "patientGiven", "patientFamily", "patientGender", and "patientDob". The host that executes the KScript defines what functions and types are predefined in the context (the KScript API). In this case, "now" is predefined. The other names must be defined as variables in the script, or an error will occur.

In most cases, the data will be found in a database. So the full script might look like this:

<%
  docid := GetNextKey("exam.cardiac.docid");
  sql := object.type("TSQL").create("select Top 1 Key, PatientGiven, PatientSurname, PatientKey, PatientMRN, PatientDOB, PatientGender from WaitingPatients where Waiting = 1");
  sql.exec;
  if not sql.fetch then
    raiseerror("No patients found");
  "<!-- key:" + sql.Key + "-->";
  mrn1 := sql.PatientKey;
  mrn2 := sql.PatientMRN;
  patientGiven := sql.PatientGiven;
  patientFamily := sql.PatientSurname;
  patientGender := sql.PatientGender;
  patientDob := sql.PatientDOB; // will be a date because of underlying sql field type
%>
<ClinicalDocument xmlns="urn:hl7-org:v3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">  
  <typeId root="2.16.840.1.113883.1.3" extension="POCD_HD000040"/>  
  <id root="2.16.840.1.113883.1.19.9" extension="<%docid%>"/>  
  <code code="38959-3" codeSystem="2.16.840.1.113883.6.1" displayName="VA C&P exam.heart"/>  
  <title>Cardiac Exam</title>  
  <effectiveTime value="<%now.format("yyyymmddhhnnss")%>"/>  
  <confidentialityCode code="N" codeSystem="2.16.840.1.113883.5.25"/>  
  <languageCode code="en-US"/>  
  <recordTarget>    
    <patientRole>      
      <id root="2.16.840.1.113883.1.19.10" extension="<%mrn1%>"/>      
      <id root="2.16.840.1.113883.1.19.11" extension="<%mrn2%>"/>      
      <patient>        
        <name>       
          <given><%patientGiven%></given>       
          <family><%patientFamily%></family>        
        </name>        
        <administrativeGenderCode code="<%patientGender%>" codeSystem="2.16.840.1.113883.5.1"/>        
        <birthTime value="<%patientDob.format("yyyymmdd")%>"/>      
      </patient>    
    </patientRole>  
  </recordTarget>  
</ClinicalDocument>

(Of course this script leaves much to be done yet - how to connect to a database? how are records in the WaitingPatients table of view managed?)

The syntax inside the script tags is a full programming language, with one difference - any statement may produce an output. If the output isn't captured as a variable value, then the output will go straight into the final page, which is how the line that embeds the key of the waiting record into a comment in the CDA document works.

The conditional scripting functionality can work across tags too. This is how to suppress the second id element in the case where the PatientMRN is null because it's not known due to business process reasons.

<%
  docid := GetNextKey("exam.cardiac.docid");
  sql := object.type("TSQL").create("select Top 1 Key, PatientGiven, PatientSurname, PatientKey, PatientMRN, PatientDOB, PatientGender from WaitingPatients where Waiting = 1");
  sql.exec;
  if not sql.fetch then
    raiseerror("No patients found");
  "<!-- key: " + sql.Key + "-->";
  mrn1 := sql.PatientKey;
  mrn2 := sql.PatientMRN;
  patientGiven := sql.PatientGiven;
  patientFamily := sql.PatientSurname;
  patientGender := sql.PatientGender;
  patientDob := sql.PatientDOB; // will be a date because of underlying sql field type
%>
<ClinicalDocument xmlns="urn:hl7-org:v3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">  
  <typeId root="2.16.840.1.113883.1.3" extension="POCD_HD000040"/>  
  <id root="2.16.840.1.113883.1.19.9" extension="<%docid%>"/>  
  <code code="38959-3" codeSystem="2.16.840.1.113883.6.1" displayName="VA C&P exam.heart"/>  
  <title>Cardiac Exam</title>  
  <effectiveTime value="<%now.format("yyyymmddhhnnss")%>"/>  
  <confidentialityCode code="N" codeSystem="2.16.840.1.113883.5.25"/>  
  <languageCode code="en-US"/>  
  <recordTarget>    
    <patientRole>      
      <id root="2.16.840.1.113883.1.19.10" extension="<%mrn1%>"/>      
<%if mrn2%>      <id root="2.16.840.1.113883.1.19.11" extension="<%mrn2%>"/><%endif%>      
      <patient>        
        <name>       
          <given><%patientGiven%></given>       
          <family><%patientFamily%></family>        
        </name>        
        <administrativeGenderCode code="<%patientGender%>" codeSystem="2.16.840.1.113883.5.1"/>        
        <birthTime value="<%patientDob.format("yyyymmdd")%>"/>      
      </patient>    
    </patientRole>  
  </recordTarget>  
</ClinicalDocument>

The KScript language scales as the complexity of the document grows. The language contructs include if..then..else statements, loops, and exception handling, and there are inbuilt types for booleans, strings, integers, floating point numbers, and collections. The context can define additional types to provide functionality, though it is not possible to define types within KScript itself.


© Kestral Computing P/L 2000-2010.
Keywords: Scripting, KScript