/**
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
* the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
*
* Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
* graphic logo is a trademark of OpenMRS Inc.
*/
package org.openmrs.module.xforms;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.FileReader;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Hashtable;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.log.CommonsLogLogChute;
import org.kxml2.io.KXmlParser;
import org.kxml2.io.KXmlSerializer;
import org.kxml2.kdom.Document;
import org.kxml2.kdom.Element;
import org.openmrs.Concept;
import org.openmrs.ConceptAnswer;
import org.openmrs.ConceptDatatype;
import org.openmrs.ConceptMap;
import org.openmrs.ConceptName;
import org.openmrs.ConceptSource;
import org.openmrs.Encounter;
import org.openmrs.Form;
import org.openmrs.GlobalProperty;
import org.openmrs.Location;
import org.openmrs.Patient;
import org.openmrs.PatientIdentifierType;
import org.openmrs.Person;
import org.openmrs.PersonAttributeType;
import org.openmrs.PersonName;
import org.openmrs.Relationship;
import org.openmrs.Role;
import org.openmrs.User;
import org.openmrs.api.ConceptService;
import org.openmrs.api.FormService;
import org.openmrs.api.GlobalPropertyListener;
import org.openmrs.api.context.Context;
import org.openmrs.module.xforms.formentry.FormEntryWrapper;
import org.openmrs.module.xforms.util.ConceptUtil;
import org.openmrs.module.xforms.util.LocationUtil;
import org.openmrs.module.xforms.util.XformBuilderUtil;
import org.openmrs.module.xforms.util.XformsUtil;
import org.openmrs.util.OpenmrsConstants.PERSON_TYPE;
import org.openmrs.util.OpenmrsUtil;
import org.xmlpull.v1.XmlPullParser;
//TODO This class is too big. May need breaking into smaller ones.
/**
* Builds xforms from openmrs schema and template files. This class also builds the XForm for
* creating new patients.
*
* @author Daniel Kayiwa
*/
public final class XformBuilder implements GlobalPropertyListener {
/** Namespace for XForms. */
public static final String NAMESPACE_XFORMS = "http://www.w3.org/2002/xforms";
/** Namespace for XML schema. */
public static final String NAMESPACE_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema";
/** Namespace for XML schema instance. */
public static final String NAMESPACE_XML_INSTANCE = "http://www.w3.org/2001/XMLSchema-instance";
/** Namespace for XHTML. */
public static final String NAMESPACE_XHTML = "http://www.w3.org/1999/xhtml";
/** Namespace prefix for openmrs custom types. */
public static final String PREFIX_OPENMRS_TYPE = "openmrstype";
/** Namespace prefix for XForms. */
public static final String PREFIX_XFORMS = "xf";
/** Namespace prefix for XML schema. */
public static final String PREFIX_XML_SCHEMA = "xsd";
/** The second Namespace prefix for XML schema. */
public static final String PREFIX_XML_SCHEMA2 = "xs";
/** Namespace prefix for XML schema instance. */
public static final String PREFIX_XML_INSTANCES = "xsi";
/** Namespace prefix for openmrs. */
public static final String PREFIX_OPENMRS = "openmrs";
/** The character separator for namespace prefix. */
public static final String NAMESPACE_PREFIX_SEPARATOR = ":";
public static final String CONTROL_INPUT = "input";
public static final String CONTROL_SELECT = "select";
public static final String CONTROL_SELECT1 = "select1";
public static final String CONTROL_REPEAT = "repeat";
public static final String NODE_LABEL = "label";
public static final String NODE_HINT = "hint";
public static final String NODE_VALUE = "value";
public static final String NODE_ITEM = "item";
public static final String NODE_HTML = "html";
public static final String NODE_XFORMS = "xforms";
public static final String NODE_SCHEMA = "schema";
public static final String NODE_INSTANCE = "instance";
public static final String NODE_MODEL = "model";
public static final String NODE_BIND = "bind";
public static final String NODE_ENUMERATION = "enumeration";
public static final String NODE_DATE = "date";
public static final String NODE_TIME = "time";
public static final String NODE_SIMPLETYPE = "simpleType";
public static final String NODE_COMPLEXTYPE = "complexType";
public static final String NODE_SEQUENCE = "sequence";
public static final String NODE_RESTRICTION = "restriction";
public static final String NODE_ATTRIBUTE = "attribute";
public static final String NODE_FORM = "form";
public static final String NODE_PATIENT = "patient";
public static final String NODE_XFORMS_VALUE = "xforms_value";
public static final String NODE_OBS = "obs";
public static final String NODE_PROBLEM_LIST = "problem_list";
public static final String NODE_GROUP = "group";
public static final String NODE_MININCLUSIVE = "minInclusive";
public static final String NODE_MAXINCLUSIVE = "maxInclusive";
public static final String ATTRIBUTE_ID = "id";
public static final String ATTRIBUTE_NODESET = "nodeset";
public static final String ATTRIBUTE_NAME = "name";
public static final String ATTRIBUTE_BIND = "bind";
public static final String ATTRIBUTE_REF = "ref";
public static final String ATTRIBUTE_APPEARANCE = "appearance";
public static final String ATTRIBUTE_NILLABLE = "nillable";
public static final String ATTRIBUTE_MAXOCCURS = "maxOccurs";
public static final String ATTRIBUTE_TYPE = "type";
public static final String ATTRIBUTE_FIXED = "fixed";
public static final String ATTRIBUTE_OPENMRS_DATATYPE = "openmrs_datatype";
public static final String ATTRIBUTE_OPENMRS_CONCEPT = "openmrs_concept";
public static final String ATTRIBUTE_OPENMRS_ATTRIBUTE = "openmrs_attribute";
public static final String ATTRIBUTE_OPENMRS_TABLE = "openmrs_table";
public static final String ATTRIBUTE_SUBMISSION = "submission";
public static final String ATTRIBUTE_READONLY = "readonly";
public static final String ATTRIBUTE_LOCKED = "locked";
public static final String ATTRIBUTE_REQUIRED = "required";
public static final String ATTRIBUTE_VISIBLE = "visible";
public static final String ATTRIBUTE_DESCRIPTION_TEMPLATE = "description-template";
public static final String ATTRIBUTE_BASE = "base";
public static final String ATTRIBUTE_XSI_NILL = "xsi:nil";
public static final String ATTRIBUTE_RESOURCE = "resource";
public static final String ATTRIBUTE_MULTIPLE = "multiple";
public static final String ATTRIBUTE_CONSTRAINT = "constraint";
public static final String ATTRIBUTE_MESSAGE = "message";
public static final String ATTRIBUTE_VALUE = "value";
public static final String ATTRIBUTE_CONCEPT_ID = "concept_id";
public static final String ATTRIBUTE_PROVIDER_ID_TYPE = "provider_id_type";
public static final String ATTRIBUTE_UUID = "uuid";
public static final String XPATH_VALUE_TRUE = "true()";
public static final String XPATH_VALUE_FALSE = "false()";
public static final String XPATH_VALUE_LAST = "last()";
public static final String VALUE_TRUE = "true";
public static final String VALUE_FALSE = "false";
public static final String VALUE_PROVIDER_ID_TYPE_PROV_ID = "PROVIDER.ID";
public static final String NODE_SEPARATOR = "/";
public static final String NODE_ENCOUNTER_LOCATION_ID = "encounter.location_id";
public static final String NODE_ENCOUNTER_PROVIDER_ID = "encounter.provider_id";
public static final String NODE_ENCOUNTER_ENCOUNTER_ID = "encounter.encounter_id";
public static final String NODE_ENCOUNTER_ENCOUNTER_DATETIME = "encounter.encounter_datetime";
public static final String NODE_PATIENT_PATIENT_ID = "patient.patient_id";
public static final String NODE_PATIENT_FAMILY_NAME = "patient.family_name";
public static final String NODE_PATIENT_MIDDLE_NAME = "patient.middle_name";
public static final String NODE_PATIENT_GIVEN_NAME = "patient.given_name";
public static final String NODE_PATIENT_BIRTH_DATE = "patient.birthdate";
public static final String NODE_PATIENT_BIRTH_DATE_ESTIMATED = "patient.birthdate_estimated";
public static final String NODE_PATIENT_GENDER = "patient.sex";
public static final String NODE_PATIENT_IDENTIFIER_TYPE = "patient_identifier.identifier_type";
public static final String NODE_PATIENT_IDENTIFIER_TYPE_ID = "patient_identifier.identifier_type_id";
public static final String NODE_PATIENT_ID = "patient_id";
public static final String NODE_FAMILY_NAME = "family_name";
public static final String NODE_MIDDLE_NAME = "middle_name";
public static final String NODE_GIVEN_NAME = "given_name";
public static final String NODE_GENDER = "gender";
public static final String NODE_IDENTIFIER = "identifier";
public static final String NODE_BIRTH_DATE = "birth_date";
public static final String NODE_BIRTH_DATE_ESTIMATED = "birth_date_estimated";
public static final String NODE_BIRTHDATE = "birthdate";
public static final String NODE_BIRTHDATE_ESTIMATED = "birthdate_estimated";
public static final String NODE_LOCATION_ID = "location_id";
public static final String NODE_PROVIDER_ID = "provider_id";
public static final String NODE_IDENTIFIER_TYPE_ID = "patient_identifier_type_id";
public static final String NODE_DEGREE = "degree";
public static final String NODE_FAMILY_NAME2 = "family_name2";
public static final String NODE_FAMILY_NAME_PREFIX = "family_name_prefix";
public static final String NODE_FAMILY_NAME_SUFFIX = "family_name_suffix";
public static final String NODE_PREFIX = "prefix";
public static final String NODE_LOAD = "load";
public static final String DATA_TYPE_DATE = "xsd:date";
public static final String DATA_TYPE_INT = "xsd:int";
public static final String DATA_TYPE_TEXT = "xsd:string";
public static final String DATA_TYPE_BOOLEAN = "xsd:boolean";
public static final String DATA_TYPE_DECIMAL = "xsd:decimal";
public static final String DATA_TYPE_BASE64BINARY = "xsd:base64Binary";
public static final String DATA_TYPE_DATETIME = "xsd:dateTime";
public static final String DATA_TYPE_TIME = "xsd:time";
public static final String MULTIPLE_SELECT_VALUE_SEPARATOR = " ";
/**
* The last five characters of an xml schema complex type name for a concept. e.g weight_kg_type
* where _type is appended to the concept weight_kg.
*/
public static final String COMPLEX_TYPE_NAME_POSTFIX = "_type";
public static final String SIMPLE_TYPE_NAME_POSTFIX = "_type_restricted_type";
/**
* The last eight characters of an xml schema complex type name for a concept. e.g
* problem_added_section where _section is appended to the concept problem_added.
*/
public static final String COMPLEX_SECTION_NAME_POSTFIX = "_section";
/** The complex type node having a list of problems. e.g. Problem Added, Problem Resolved, etc. */
public static final String COMPLEX_TYPE_NAME_PROBLEM_LIST = "problem_list_section";
public static final String MODEL_ID = "openmrs_model";
public static final String INSTANCE_ID = "openmrs_model_instance";
public static final String BINDING_LOCATION_ID = "/form/encounter/encounter.location_id";
public static final String BINDING_PATIENT_ID = "/form/patient/patient.patient_id";
public static final String BINDING_FAMILY_NAME = "/form/patient/patient.family_name";
public static final String BINDING_GIVEN_NAME = "/form/patient/patient.given_name";
public static final String BINDING_MIDDLE_NAME = "/form/patient/patient.middle_name";
public static final String BINDING_GENDER = "/form/patient/patient.sex";
public static final String BINDING_BIRTH_DATE = "/form/patient/patient.birthdate";
public static final String BINDING_BIRTH_DATE_ESTIMATED = "/form/patient/patient.birthdate_estimated";
public static final String BINDING_IDENTIFIER_TYPE = "/form/patient/patient_identifier.identifier_type";
public static final String NODE_NAME_PERSON_ADDRESSES = "person_addresses";
public static final String NODE_NAME_PREFERRED = "preferred";
public static final String NODE_NAME_ADDRESS1 = "address1";
public static final String NODE_NAME_ADDRESS2 = "address2";
public static final String NODE_NAME_CITY_VILLAGE = "city_village";
public static final String NODE_NAME_STATE_PROVINCE = "state_province";
public static final String NODE_NAME_POSTAL_CODE = "postal_code";
public static final String NODE_NAME_COUNTRY = "country";
public static final String NODE_NAME_LATITUDE = "latitude";
public static final String NODE_NAME_LONGITUDE = "longitude";
public static final String NODE_NAME_COUNTY_DISTRICT = "county_district";
public static final String NODE_NAME_NEIGHBORHOOD_CELL = "neighborhood_cell";
public static final String NODE_NAME_REGION = "region";
public static final String NODE_NAME_SUBREGION = "subregion";
public static final String NODE_NAME_TOWNSHIP_DIVISION = "township_division";
public static final String NODE_NAME_PREFIX_PERSON_ADDRESS = "person_address_";
public static final String NODE_NAME_OTHER_IDENTIFIERS = "other_identifiers";
public static final String NODE_NAME_OTHER_IDENTIFIER = "other_identifier";
public static final String NODE_NAME_OTHER_IDENTIFIER_TYPE_ID = "other_identifier_type_id";
public static final String NODE_NAME_OTHER_IDENTIFIER_LOCATION_ID = "other_identifier_location_id";
private static Hashtable<String, String> obsRepeatItems;
private static Hashtable<String, String> nodesets;
private static Hashtable<String, List<String>> repeatSharedKids;
private static Hashtable<String, List<String>> sharedRestrictions;
private static Hashtable<String, String> repeatChildTypes;
private static String ATTRIBUTE_PRELOAD = "jr:preload";
private static String ATTRIBUTE_PRELOAD_PARAMS = "jr:preloadParams";
private static String PRELOAD_PATIENT = "patient";
private static Log log = LogFactory.getLog(XformBuilder.class);
private static Boolean useAutoCompleteForLocations;
private static Boolean useAutoCompleteForProviders;
/**
* Builds an Xform from an openmrs schema and template xml.
*
* @param schemaXml - the schema xml.
* @param templateXml - the template xml.
* @return - the built xform's xml.
*/
public static String getXform4mStrings(String schemaXml, String templateXml) throws Exception {
return getXform4mDocuments(getDocument(new StringReader(schemaXml)), getDocument(new StringReader(templateXml)));
}
/**
* Creates an xform from schema and template files.
*
* @param schemaPathName - the complete path and name of the schema file.
* @param templatePathName - the complete path and name of the template file
* @return the built xform's xml.
*/
public static String getXform4mFiles(String schemaPathName, String templatePathName, String xformAction) {
try {
Document schemaDoc = getDocument(new FileReader(schemaPathName));
Document templateDoc = getDocument(new FileReader(templatePathName));
return getXform4mDocuments(schemaDoc, templateDoc);
}
catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* Converts xml to a documnt object.
*
* @param xml - the xml to convert.
* @return - the Document object containing the xml.
*/
public static Document getDocument(String xml) {
return getDocument(new StringReader(xml));
}
/**
* Sets the value of a node in a document.
*
* @param doc - the document.
* @param name - the name of the node whose value to set.
* @param value - the value to set.
* @return - true if the node with the name was found, else false.
*/
public static boolean setNodeValue(Document doc, String name, String value) {
return setNodeValue(doc.getRootElement(), name, value);
}
/**
* Sets the value of a child node in a parent node.
*
* @param doc - the document.
* @param name - the name of the node whose value to set.
* @param value - the value to set.
* @return - true if the node with the name was found, else false.
*/
public static boolean setNodeValue(Element parentNode, String name, String value) {
Element node = getElement(parentNode, name);
if (node == null)
return false;
setNodeValue(node, value);
return true;
}
public static String getNodeValue(Element parentNode, String name) {
Element node = getElement(parentNode, name);
if (node == null)
return null;
return getTextValue(node);
}
public static String getTextValue(Element node) {
int numOfEntries = node.getChildCount();
for (int i = 0; i < numOfEntries; i++) {
if (node.isText(i))
return node.getText(i);
if (node.getType(i) == Element.ELEMENT) {
String val = getTextValue(node.getElement(i));
if (val != null)
return val;
}
}
return null;
}
/**
* Sets the text value of a node.
*
* @param node - the node whose value to set.
* @param value - the value to set.
*/
public static void setNodeValue(Element node, String value) {
if (value == null)
value = "";
for (int i = 0; i < node.getChildCount(); i++) {
if (node.isText(i)) {
node.removeChild(i);
node.addChild(Element.TEXT, value);
return;
}
}
node.addChild(Element.TEXT, value);
}
/**
* Sets the value of an attribute of a node in a document.
*
* @param doc - the document.
* @param nodeName - the name of the node.
* @param attributeName - the name of the attribute.
* @param value - the value to set.
* @return
*/
public static boolean setNodeAttributeValue(Document doc, String nodeName, String attributeName, String value) {
Element node = getElement(doc.getRootElement(), nodeName);
if (node == null)
return false;
node.setAttribute(null, attributeName, value);
return true;
}
/**
* Gets a child element of a parent node with a given name.
*
* @param parent - the parent element
* @param name - the name of the child.
* @return - the child element.
*/
public static Element getElement(Element parent, String name) {
for (int i = 0; i < parent.getChildCount(); i++) {
if (parent.getType(i) != Element.ELEMENT)
continue;
Element child = (Element) parent.getChild(i);
if (child.getName().equalsIgnoreCase(name))
return child;
child = getElement(child, name);
if (child != null)
return child;
}
return null;
}
/**
* Gets a child element of a parent node with a given attribute prefix.
*
* @param parent - the parent element
* @param attributeName - the name of the attribute.
* @param attributePrefix - the prefix of the attribute.
* @return - the child element.
*/
public static Element getElementByAttributePrefix(Element parent, String attributeName, String attributePrefix,
boolean includeAttribute, String includeAttributeName,
boolean copyIfNotExists, List<String> nonCopyAttributes) {
for (int i = 0; i < parent.getChildCount(); i++) {
if (parent.getType(i) != Element.ELEMENT)
continue;
Element child = (Element) parent.getChild(i);
//Node name may not match due to change of concept name
//and hence we only check for attribute value of concept id
String value = child.getAttributeValue(null, attributeName);
if (value != null && value.startsWith(attributePrefix)) {
String attributeValue = child.getAttributeValue(null, includeAttributeName); //if obs id is already filled, the just look for another node (Problem lists are normally more than one)
if (attributeValue == null || includeAttribute)
return child;
else if (copyIfNotExists && attributeValue != null)
return createCopy(child, nonCopyAttributes);
}
child = getElementByAttributePrefix(child, attributeName, attributePrefix, includeAttribute,
includeAttributeName, copyIfNotExists, nonCopyAttributes);
if (child != null)
return child;
}
return null;
}
/**
* Gets a child element of a parent node with a given attribute value.
*
* @param parent - the parent element
* @param attributeName - the name of the attribute.
* @param attributeValue - the value of the attribute.
* @return - the child element.
*/
public static Element getElementByAttributeValue(Element parent, String attributeName, String attributeValue) {
for (int i = 0; i < parent.getChildCount(); i++) {
if (parent.getType(i) != Element.ELEMENT)
continue;
Element child = (Element) parent.getChild(i);
//Node name may not match due to change of concept name
//and hence we only check for attribute value of concept id
String value = child.getAttributeValue(null, attributeName);
if (value != null && value.equalsIgnoreCase(attributeValue))
return child;
child = getElementByAttributeValue(child, attributeName, attributeValue);
if (child != null)
return child;
}
return null;
}
/**
* Gets a child element of a parent node with a given name.
*
* @param parent - the parent element
* @param name - the name of the child.
* @return - the child element.
*/
public static Element getElementEx(Element parent, String name, String attributeName, String attributeValue) {
for (int i = 0; i < parent.getChildCount(); i++) {
if (parent.getType(i) != Element.ELEMENT)
continue;
Element child = (Element) parent.getChild(i);
if (child.getName().equalsIgnoreCase(name)
&& attributeValue.equalsIgnoreCase(child.getAttributeValue(null, attributeName)))
return child;
child = getElementEx(child, name, attributeName, attributeValue);
if (child != null)
return child;
}
return null;
}
/**
* Builds an Xfrom from an openmrs schema and template document.
*
* @param schemaDoc - the schema document.
* @param templateDoc - the template document.
* @return - the built xform's xml.
*/
public static String getXform4mDocuments(Document schemaDoc, Document templateDoc) throws Exception {
obsRepeatItems = new Hashtable<String, String>();
nodesets = new Hashtable<String, String>();
repeatSharedKids = new Hashtable<String, List<String>>();
sharedRestrictions = new Hashtable<String, List<String>>();
repeatChildTypes = new Hashtable<String, String>();
Element formNode = (Element) templateDoc.getRootElement();
Document doc = new Document();
doc.setEncoding(XformConstants.DEFAULT_CHARACTER_ENCODING);
Element xformsNode = doc.createElement(NAMESPACE_XFORMS, null);
xformsNode.setName(NODE_XFORMS);
xformsNode.setPrefix(PREFIX_XFORMS, NAMESPACE_XFORMS);
xformsNode.setPrefix(PREFIX_XML_SCHEMA, NAMESPACE_XML_SCHEMA);
xformsNode.setPrefix(PREFIX_XML_SCHEMA2, NAMESPACE_XML_SCHEMA);
xformsNode.setPrefix(PREFIX_XML_INSTANCES, NAMESPACE_XML_INSTANCE);
//if(XformsUtil.isJavaRosaSaveFormat())
xformsNode.setPrefix("jr", "http://openrosa.org/javarosa");
doc.addChild(org.kxml2.kdom.Element.ELEMENT, xformsNode);
Element modelNode = doc.createElement(NAMESPACE_XFORMS, null);
modelNode.setName(NODE_MODEL);
modelNode.setAttribute(null, ATTRIBUTE_ID, MODEL_ID);
xformsNode.addChild(Element.ELEMENT, modelNode);
Element groupNode = doc.createElement(NAMESPACE_XFORMS, null);
groupNode.setName(NODE_GROUP);
Element labelNode = doc.createElement(NAMESPACE_XFORMS, null);
labelNode.setName(NODE_LABEL);
labelNode.addChild(Element.TEXT, "Page1");
groupNode.addChild(Element.ELEMENT, labelNode);
xformsNode.addChild(Element.ELEMENT, groupNode);
Element instanceNode = doc.createElement(NAMESPACE_XFORMS, null);
instanceNode.setName(NODE_INSTANCE);
instanceNode.setAttribute(null, ATTRIBUTE_ID, INSTANCE_ID);
modelNode.addChild(Element.ELEMENT, instanceNode);
instanceNode.addChild(Element.ELEMENT, formNode);
Document xformSchemaDoc = new Document();
xformSchemaDoc.setEncoding(XformConstants.DEFAULT_CHARACTER_ENCODING);
Element xformSchemaNode = doc.createElement(NAMESPACE_XML_SCHEMA, null);
xformSchemaNode.setName(NODE_SCHEMA);
xformSchemaDoc.addChild(org.kxml2.kdom.Element.ELEMENT, xformSchemaNode);
Hashtable bindings = new Hashtable();
Hashtable<String, String> problemList = new Hashtable<String, String>();
Hashtable<String, String> problemListItems = new Hashtable<String, String>();
parseTemplate(modelNode, formNode, formNode, bindings, groupNode, problemList, problemListItems, 0);
parseSchema(schemaDoc.getRootElement(), groupNode, modelNode, xformSchemaNode, bindings, problemList,
problemListItems);
//find all conceptId attributes in the document and replace their value with a mapped concept
String prefSourceName = Context.getAdministrationService().getGlobalProperty(
XformConstants.GLOBAL_PROP_KEY_PREFERRED_CONCEPT_SOURCE);
//we only use the mappings if the global property is set
if (StringUtils.isNotBlank(prefSourceName)) {
for (int i = 0; i < formNode.getChildCount(); i++) {
Element childElement = formNode.getElement(i);
if (childElement != null) {
for (int j = 0; j < childElement.getChildCount(); j++) {
if (childElement.getElement(j) != null) {
Element grandChildElement = childElement.getElement(j);
String value = grandChildElement.getAttributeValue(null, ATTRIBUTE_OPENMRS_CONCEPT);
if (StringUtils.isNotBlank(value))
addConceptMapAttributes(grandChildElement, value);
}
}
}
}
}
cleanUp();
return fromDoc2String(doc);
}
private static void cleanUp() {
obsRepeatItems.clear();
nodesets.clear();
repeatSharedKids.clear();
sharedRestrictions.clear();
repeatChildTypes.clear();
}
/**
* Methods replaces the conceptId with a concept source name and source code.
*
* @param element the element with the openmrs_concept attribute
* @param conceptValueString the value of the openmrs_concept attribute
*/
public static void addConceptMapAttributes(Element element, String conceptValueString) {
String[] tokens = StringUtils.split(conceptValueString, "^");
ConceptService cs = Context.getConceptService();
try {
Concept concept = cs.getConcept(Integer.valueOf(tokens[0].trim()));
ConceptSource preferredSource = null;
String prefSourceName = Context.getAdministrationService().getGlobalProperty(
XformConstants.GLOBAL_PROP_KEY_PREFERRED_CONCEPT_SOURCE);
if (StringUtils.isNotBlank(prefSourceName)) {
preferredSource = cs.getConceptSourceByName(prefSourceName);
if (concept.getConceptMappings().size() > 0) {
if (preferredSource != null) {
for (ConceptMap map : concept.getConceptMappings()) {
if (OpenmrsUtil.nullSafeEquals(preferredSource, map.getSource())) {
element.setAttribute(null, ATTRIBUTE_OPENMRS_CONCEPT,
map.getSource().getName() + ":" + map.getSourceCode());
return;
}
}
}
}
}
}
catch (NumberFormatException e) {
log.warn(e.getMessage());
}
}
/**
* Gets the label of an openmrs standard form node
*
* @param name - the name of the node.
* @return - the label.
*/
public static String getDisplayText(String name) {
/*if(name.equalsIgnoreCase(NODE_ENCOUNTER_ENCOUNTER_DATETIME))
return "ENCOUNTER DATE";
else if(name.equalsIgnoreCase(NODE_ENCOUNTER_LOCATION_ID))
return "LOCATION";
else if(name.equalsIgnoreCase(NODE_ENCOUNTER_PROVIDER_ID))
return "PROVIDER";
else if(name.equalsIgnoreCase(NODE_PATIENT_PATIENT_ID))
return "PATIENT ID";
else if(name.equalsIgnoreCase(NODE_PATIENT_MIDDLE_NAME))
return "MIDDLE NAME";
else if(name.equalsIgnoreCase(NODE_PATIENT_GIVEN_NAME))
return "GIVEN NAME";
else if(name.equalsIgnoreCase(NODE_PATIENT_FAMILY_NAME))
return "FAMILY NAME";
else
return name.replace('_', ' ');*/
name = name.replace('.', ' ');
name = name.replace("patient ", "");
name = name.replace("encounter ", "");
name = name.replace("person_address ", "");
name = name.replace("patient_address ", "");
name = name.replace("person_name ", "");
name = name.replace("person_attribute ", "");
name = name.replace("patient_identifier ", "");
name = name.replace('_', ' '); //This is done after the above in order not to make patient_id=id
name = name.toUpperCase();
return name;
}
/**
* Parses an openmrs template and builds the bindings in the model plus UI controls for openmrs
* table field questions.
*
* @param modelElement - the model element to add bindings to.
* @param formNode the form node.
* @param bindings - a hash table to populate with the built bindings.
* @param bodyNode - the body node to add the UI control to.
*/
public static void parseTemplate(Element modelElement, Element formNode, Element formChild, Hashtable bindings,
Element bodyNode, Hashtable<String, String> problemList,
Hashtable<String, String> problemListItems, int level) {
level++;
int numOfEntries = formChild.getChildCount();
for (int i = 0; i < numOfEntries; i++) {
if (formChild.isText(i))
continue; //Ignore all text.
Element child = formChild.getElement(i);
//These two attributes are a must for all nodes to be filled with values.
//eg openmrs_concept="1740^ARV REGIMEN^99DCT" openmrs_datatype="CWE"
if (child.getAttributeValue(null, ATTRIBUTE_OPENMRS_DATATYPE) == null
&& child.getAttributeValue(null, ATTRIBUTE_OPENMRS_CONCEPT) != null)
continue; //These could be like options for multiple select, which take true or false value.
String name = child.getName();
if (name.equals("patient_relationship")) {
RelationshipBuilder.build(modelElement, bodyNode, child);
continue;
}
//If the node has an openmrs_concept attribute but is not called obs,
//Or has the openmrs_attribite and openmrs_table attributes.
if ((child.getAttributeValue(null, ATTRIBUTE_OPENMRS_CONCEPT) != null && level > 1 /*!child.getName().equals(NODE_OBS)*/)
|| (child.getAttributeValue(null, ATTRIBUTE_OPENMRS_ATTRIBUTE) != null && child.getAttributeValue(null,
ATTRIBUTE_OPENMRS_TABLE) != null)) {
if (!name.equalsIgnoreCase(NODE_PROBLEM_LIST)) {
Element bindNode = createBindNode(modelElement, child, bindings, problemList, problemListItems);
if (isMultSelectNode(child))
addMultipleSelectXformValueNode(child);
if (isTableFieldNode(child)) {
setTableFieldDataType(name, bindNode);
setTableFieldBindingAttributes(name, bindNode);
setTableFieldDefaultValue(name, formNode);
if ("identifier".equalsIgnoreCase(child.getAttributeValue(null, ATTRIBUTE_OPENMRS_ATTRIBUTE))
&& "patient_identifier".equalsIgnoreCase(child.getAttributeValue(null,
ATTRIBUTE_OPENMRS_TABLE))) {
bindNode.setAttribute(null, ATTRIBUTE_PRELOAD, PRELOAD_PATIENT);
bindNode.setAttribute(null, ATTRIBUTE_PRELOAD_PARAMS, "patientIdentifier");
}
}
}
}
//if(child.getAttributeValue(null, ATTRIBUTE_OPENMRS_ATTRIBUTE) != null && child.getAttributeValue(null, ATTRIBUTE_OPENMRS_TABLE) != null){
//Build UI controls for the openmrs fixed table fields. The rest of the controls are built from
if (isTableFieldNode(child)) {
Element controlNode = buildTableFieldUIControlNode(child, bodyNode);
if (name.equalsIgnoreCase(NODE_ENCOUNTER_LOCATION_ID) && CONTROL_SELECT1.equals(controlNode.getName()))
populateLocations(controlNode);
else if (name.equalsIgnoreCase(NODE_ENCOUNTER_PROVIDER_ID)) {
populateProviders(controlNode, formNode, modelElement, bodyNode);
//if this is 1.9, we need to add the provider_id_type attribute and set its value, this
//will be used by xml to hl7 xslt to determine if it should include the assigning
//authority so that ORUR01 handler in core considers the id to be a providerId
if (XformsUtil.isOnePointNineAndAbove()) {
((Element) child).setAttribute(null, XformBuilder.ATTRIBUTE_PROVIDER_ID_TYPE,
XformBuilder.VALUE_PROVIDER_ID_TYPE_PROV_ID);
}
} else if (name.equalsIgnoreCase(NODE_ENCOUNTER_ENCOUNTER_DATETIME))
setNodeValue(child, "'today()'"); //Set encounter date defaulting to today
}
parseTemplate(modelElement, formNode, child, bindings, bodyNode, problemList, problemListItems, level);
}
}
/**
* Builds a UI control node for a table field.
*
* @param node - the node whose UI control to build.
* @param bodyNode - the body node to add the UI control to.
* @return - the created UI control node.
*/
private static Element buildTableFieldUIControlNode(Element node, Element bodyNode) {
Element controlNode = bodyNode.createElement(NAMESPACE_XFORMS, null);
String name = node.getName();
//location and provider are not free text.
if (name.equalsIgnoreCase(NODE_ENCOUNTER_LOCATION_ID) || name.equalsIgnoreCase(NODE_ENCOUNTER_PROVIDER_ID)) {
if (useAutoCompleteForNode(name))
controlNode.setName(CONTROL_INPUT);
else
controlNode.setName(CONTROL_SELECT1);
} else {
controlNode.setName(CONTROL_INPUT);
}
controlNode.setAttribute(null, ATTRIBUTE_BIND, name);
//create the label
Element labelNode = bodyNode.createElement(NAMESPACE_XFORMS, null);
labelNode.setName(NODE_LABEL);
labelNode.addChild(Element.TEXT, getDisplayText(name) + " ");
controlNode.addChild(Element.ELEMENT, labelNode);
addControl(bodyNode, controlNode);
return controlNode;
}
/**
* Populates a UI control node with providers.
*
* @param controlNode - the UI control node.
*/
private static void populateProviders(Element controlNode, Element formNode, Element modelNode, Element groupNode) {
try {
//If we are on 1.9 and above, try use the new provider API
if (XformsUtil.isOnePointNineAndAbove()) {
XformBuilderUtil.populateProviders(controlNode);
return;
}
/*if(XformBuilderUtil.populateProviders19(formNode, modelNode, groupNode)){
return;
}*/
List<User> providers = Context.getUserService().getUsersByRole(new Role("Provider"));
for (User provider : providers) {
Integer personId = XformsUtil.getPersonId(provider);
Element itemNode = /*bodyNode*/controlNode.createElement(NAMESPACE_XFORMS, null);
itemNode.setName(NODE_ITEM);
Element node = itemNode.createElement(NAMESPACE_XFORMS, null);
node.setName(NODE_LABEL);
node.addChild(Element.TEXT, getProviderName(provider, personId));
itemNode.addChild(Element.ELEMENT, node);
node = itemNode.createElement(NAMESPACE_XFORMS, null);
node.setName(NODE_VALUE);
node.addChild(Element.TEXT, personId.toString());
itemNode.addChild(Element.ELEMENT, node);
controlNode.addChild(Element.ELEMENT, itemNode);
}
}
catch (Exception ex) {
log.error("Failed to populate providers into the xform", ex);
}
}
/**
* Populates a UI control node with locations.
*
* @param controlNode - the UI control node.
*/
private static void populateLocations(Element controlNode) {
List<Location> locations = Context.getLocationService().getAllLocations(false);
for (Location loc : locations) {
Element itemNode = /*bodyNode*/controlNode.createElement(NAMESPACE_XFORMS, null);
itemNode.setName(NODE_ITEM);
Element node = itemNode.createElement(NAMESPACE_XFORMS, null);
node.setName(NODE_LABEL);
node.addChild(Element.TEXT, getLocationName(loc));
itemNode.addChild(Element.ELEMENT, node);
node = itemNode.createElement(NAMESPACE_XFORMS, null);
node.setName(NODE_VALUE);
node.addChild(Element.TEXT, loc.getLocationId().toString());
itemNode.addChild(Element.ELEMENT, node);
controlNode.addChild(Element.ELEMENT, itemNode);
}
}
/**
* Creates a model binding node.
*
* @param modelElement - the model node to add the binding to.
* @param node - the node whose binding to create.
* @param bindings - a hashtable of node bindings keyed by their names.
* @return - the created binding node.
*/
private static Element createBindNode(Element modelElement, Element node, Hashtable bindings,
Hashtable<String, String> problemList, Hashtable<String, String> problemListItems) {
Element bindNode = modelElement.createElement(NAMESPACE_XFORMS, null);
bindNode.setName(NODE_BIND);
String parentName = ((Element) node.getParent()).getName();
String binding = node.getName();
if (bindings.containsKey(binding)) {
String oldBinding = binding;
binding = parentName + "_" + binding;
if (sharedRestrictions != null) {
List<String> bindingList = sharedRestrictions.get(oldBinding);
if (bindingList == null) {
bindingList = new ArrayList<String>();
sharedRestrictions.put(oldBinding, bindingList);
}
bindingList.add(binding);
}
problemListItems.put(binding, parentName);
} else {
if (!(parentName.equalsIgnoreCase("obs") || parentName.equalsIgnoreCase("patient")
|| parentName.equalsIgnoreCase("encounter") || parentName.equalsIgnoreCase("problem_list") || parentName
.equalsIgnoreCase("orders"))) {
//binding = parentName + "_" + binding;
//TODO Need to investigate why the above commented out code brings the no data node found error in the form designer
}
}
bindNode.setAttribute(null, ATTRIBUTE_ID, binding);
String name = node.getName();
String nodeset = getNodesetAttValue(node);
//For problem list element bindings, we do not add the value part.
if (parentName.equalsIgnoreCase(NODE_PROBLEM_LIST)) {
problemList.put(name, name);
nodeset = getNodePath(node);
}
//Check if this is an item of a problem list.
if (problemList.containsKey(parentName)) {
if (problemListItems.contains(name)) {
List<String> repeats = repeatSharedKids.get(name);
if (repeats == null) {
repeats = new ArrayList<String>();
repeatSharedKids.put(name, repeats);
}
repeats.add(problemListItems.get(name));
}
problemListItems.put(name, parentName);
}
bindNode.setAttribute(null, ATTRIBUTE_NODESET, nodeset);
if (!((Element) ((Element) node.getParent()).getParent()).getName().equals(NODE_PROBLEM_LIST))
modelElement.addChild(Element.ELEMENT, bindNode);
//store the binding node with the key being its id attribute.
bindings.put(binding, bindNode);
if (nodesets != null)
nodesets.put(binding, nodeset);
return bindNode;
}
/**
* Adds a node to hold the xforms value for a multiple select node. The value is a space
* delimited list of selected answers, which will later on be used to fill the true or false
* values as expected by openmrs multiple select questions.
*
* @param child - the multiple select node to add the value node to.
*/
private static void addMultipleSelectXformValueNode(Element node) {
//Element xformsValueNode = modelElement.createElement(null, null);
Element xformsValueNode = node.createElement(null, null);
xformsValueNode.setName(NODE_XFORMS_VALUE);
xformsValueNode.setAttribute(null, ATTRIBUTE_XSI_NILL, VALUE_TRUE);
node.addChild(Element.ELEMENT, xformsValueNode);
}
/**
* Set data types for the openmrs fixed table fields.
*
* @param name - the name of the question node.
* @param bindingNode - the binding node whose type attribute we are to set.
*/
private static void setTableFieldDataType(String name, Element bindNode) {
if (name.equalsIgnoreCase(NODE_ENCOUNTER_ENCOUNTER_DATETIME)) {
bindNode.setAttribute(null, ATTRIBUTE_TYPE, XformsUtil.encounterDateIncludesTime() ? DATA_TYPE_DATETIME
: DATA_TYPE_DATE);
bindNode.setAttribute(null, ATTRIBUTE_CONSTRAINT, ". <= today()");
bindNode.setAttribute(null, (XformsUtil.isJavaRosaSaveFormat() ? "jr:constraintMsg" : ATTRIBUTE_MESSAGE),
"Encounter date cannot be after today");
} else if (name.equalsIgnoreCase(NODE_ENCOUNTER_LOCATION_ID))
bindNode.setAttribute(null, ATTRIBUTE_TYPE, DATA_TYPE_INT);
else if (name.equalsIgnoreCase(NODE_ENCOUNTER_PROVIDER_ID))
bindNode.setAttribute(null, ATTRIBUTE_TYPE, DATA_TYPE_INT);
else if (name.equalsIgnoreCase(NODE_PATIENT_PATIENT_ID))
bindNode.setAttribute(null, ATTRIBUTE_TYPE, DATA_TYPE_INT);
else
bindNode.setAttribute(null, ATTRIBUTE_TYPE, DATA_TYPE_TEXT);
}
/**
* Set required and readonly attributes for the openmrs fixed table fields.
*
* @param name - the name of the question node.
* @param bindingNode - the binding node whose required and readonly attributes we are to set.
*/
private static void setTableFieldBindingAttributes(String name, Element bindNode) {
if (name.equalsIgnoreCase(NODE_ENCOUNTER_ENCOUNTER_DATETIME))
bindNode.setAttribute(null, ATTRIBUTE_REQUIRED, XPATH_VALUE_TRUE);
else if (name.equalsIgnoreCase(NODE_ENCOUNTER_LOCATION_ID))
bindNode.setAttribute(null, ATTRIBUTE_REQUIRED, XPATH_VALUE_TRUE);
else if (name.equalsIgnoreCase(NODE_ENCOUNTER_PROVIDER_ID))
bindNode.setAttribute(null, ATTRIBUTE_REQUIRED, XPATH_VALUE_TRUE);
else if (name.equalsIgnoreCase(NODE_PATIENT_PATIENT_ID)) {
bindNode.setAttribute(null, ATTRIBUTE_REQUIRED, XPATH_VALUE_TRUE);
//bindNode.setAttribute(null, ATTRIBUTE_READONLY, XPATH_VALUE_TRUE);
//bindNode.setAttribute(null, ATTRIBUTE_LOCKED, XPATH_VALUE_TRUE);
bindNode.setAttribute(null, ATTRIBUTE_VISIBLE, XPATH_VALUE_FALSE);
bindNode.setAttribute(null, ATTRIBUTE_PRELOAD, PRELOAD_PATIENT);
bindNode.setAttribute(null, ATTRIBUTE_PRELOAD_PARAMS, "patientId");
} else {
//all table field are readonly on forms since they cant be populated in their tables
//form encounter forms. This population only happens when creating or editing patient.
bindNode.setAttribute(null, ATTRIBUTE_LOCKED, XPATH_VALUE_TRUE);
//The ATTRIBUTE_READONLY prevents firefox from displaying values in the disabled
//widgets. So this is why we are using locked which will still be readonly
//but values can be seen in the widgets.
}
/*else if(name.equalsIgnoreCase(NODE_PATIENT_FAMILY_NAME))
bindNode.setAttribute(null, ATTRIBUTE_READONLY, XPATH_VALUE_TRUE);
else if(name.equalsIgnoreCase(NODE_PATIENT_MIDDLE_NAME))
bindNode.setAttribute(null, ATTRIBUTE_READONLY, XPATH_VALUE_TRUE);
else if(name.equalsIgnoreCase(NODE_PATIENT_GIVEN_NAME))
bindNode.setAttribute(null, ATTRIBUTE_READONLY, XPATH_VALUE_TRUE);
else{
bindNode.setAttribute(null, ATTRIBUTE_READONLY, XPATH_VALUE_TRUE);
bindNode.setAttribute(null, ATTRIBUTE_LOCKED, XPATH_VALUE_TRUE);
}*/
//jr:preload="patient" jr:preloadParams="ID"
if (name.equalsIgnoreCase(NODE_PATIENT_BIRTH_DATE)) {
bindNode.setAttribute(null, ATTRIBUTE_TYPE, DATA_TYPE_DATE);
bindNode.setAttribute(null, ATTRIBUTE_PRELOAD, PRELOAD_PATIENT);
bindNode.setAttribute(null, ATTRIBUTE_PRELOAD_PARAMS, "birthDate");
} else if (name.equalsIgnoreCase(NODE_PATIENT_BIRTH_DATE_ESTIMATED))
bindNode.setAttribute(null, ATTRIBUTE_TYPE, DATA_TYPE_BOOLEAN);
//peloaders
if (name.equalsIgnoreCase(NODE_PATIENT_FAMILY_NAME)) {
bindNode.setAttribute(null, ATTRIBUTE_PRELOAD, PRELOAD_PATIENT);
bindNode.setAttribute(null, ATTRIBUTE_PRELOAD_PARAMS, "familyName");
} else if (name.equalsIgnoreCase(NODE_PATIENT_MIDDLE_NAME)) {
bindNode.setAttribute(null, ATTRIBUTE_PRELOAD, PRELOAD_PATIENT);
bindNode.setAttribute(null, ATTRIBUTE_PRELOAD_PARAMS, "middleName");
} else if (name.equalsIgnoreCase(NODE_PATIENT_GIVEN_NAME)) {
bindNode.setAttribute(null, ATTRIBUTE_PRELOAD, PRELOAD_PATIENT);
bindNode.setAttribute(null, ATTRIBUTE_PRELOAD_PARAMS, "givenName");
} else if (name.equalsIgnoreCase(NODE_PATIENT_GENDER)) {
bindNode.setAttribute(null, ATTRIBUTE_PRELOAD, PRELOAD_PATIENT);
bindNode.setAttribute(null, ATTRIBUTE_PRELOAD_PARAMS, "sex");
}
}
private static void setTableFieldDefaultValue(String name, Element formElement) {
Integer formId = Integer.valueOf(formElement.getAttributeValue(null, ATTRIBUTE_ID));
String val = getFieldDefaultValue(name, formId, true);
if (val != null)
setNodeValue(formElement, name, val);
}
private static String getFieldDefaultValue(String name, Integer formId, boolean forAllPatients) {
XformsService xformsService = (XformsService) Context.getService(XformsService.class);
String val = (String) xformsService.getFieldDefaultValue(formId, name);
if (val == null) {
val = (String) xformsService.getFieldDefaultValue(formId, name.replace('_', ' '));
if (val == null)
return null;
}
if (val.indexOf("$!{") == -1)
return val;
else if (!forAllPatients) {
Integer id = getDefaultValueId(val);
if (id != null)
return id.toString();
}
return null;
}
private static Integer getDefaultValueId(String val) {
int pos1 = val.indexOf('(');
if (pos1 == -1)
return null;
int pos2 = val.indexOf(')');
if (pos2 == -1)
return null;
if ((pos2 - pos1) < 2)
return null;
String id = val.substring(pos1 + 1, pos2);
try {
return Integer.valueOf(id);
}
catch (Exception e) {}
return null;
}
/**
* Check whether a node is an openmrs table field node These are the ones with the attributes:
* openmrs_table and openmrs_attribute e.g. patient_unique_number
* openmrs_table="PATIENT_IDENTIFIER" openmrs_attribute="IDENTIFIER"
*
* @param node - the node to check.
* @return - true if it is, else false.
*/
private static boolean isTableFieldNode(Element node) {
return (node.getAttributeValue(null, ATTRIBUTE_OPENMRS_ATTRIBUTE) != null && node.getAttributeValue(null,
ATTRIBUTE_OPENMRS_TABLE) != null);
}
/**
* Checks whether a node is multiple select or not.
*
* @param child - the node to check.k
* @return - true if multiple select, else false.
*/
private static boolean isMultSelectNode(Element child) {
return (child.getAttributeValue(null, ATTRIBUTE_MULTIPLE) != null && child.getAttributeValue(null,
ATTRIBUTE_MULTIPLE).equals("1"));
}
/**
* Parses the openmrs schema document and builds UI conrols from openmrs concepts.
*
* @param rootNode - the schema document root node.
* @param bodyNode - the xform document body node.
* @param modelNode - the xform model node.
* @param xformSchemaNode - the root node of the xml schema data types.
* @param bindings - a hashtable of node bindings.
*/
private static void parseSchema(Element rootNode, Element bodyNode, Element modelNode, Element xformSchemaNode,
Hashtable bindings, Hashtable<String, String> problemList,
Hashtable<String, String> problemListItems) {
Hashtable<String, Element> repeatControls = new Hashtable<String, Element>();
Hashtable<String, List<String>> duplicateFields = new Hashtable<String, List<String>>();
int numOfEntries = rootNode.getChildCount();
for (int i = 0; i < numOfEntries; i++) {
if (rootNode.isText(i))
continue;
Element child = rootNode.getElement(i);
String name = child.getName();
if (name.equalsIgnoreCase(NODE_COMPLEXTYPE) && isUserDefinedSchemaElement(child)) {
parseComplexType(child, child.getAttributeValue(null, ATTRIBUTE_NAME), bodyNode, xformSchemaNode, bindings,
problemList, problemListItems, repeatControls, modelNode);
parseDuplicateFieldsComplexType(duplicateFields, repeatControls, child, bodyNode, modelNode,
xformSchemaNode, bindings, problemList, problemListItems);
parseSharedRepeatKidsComplexType(child.getAttributeValue(null, ATTRIBUTE_NAME), duplicateFields,
repeatControls, child, bodyNode, modelNode, xformSchemaNode, bindings, problemList, problemListItems);
} else {
String nameAttribute = child.getAttributeValue(null, ATTRIBUTE_NAME);
if (name.equalsIgnoreCase(NODE_SIMPLETYPE)
|| (name.equalsIgnoreCase(NODE_COMPLEXTYPE) && nameAttribute != null
&& nameAttribute.startsWith("_") && !nameAttribute.contains("_section"))/*&& isUserDefinedSchemaElement(child)*/)
xformSchemaNode.addChild(0, Element.ELEMENT, child);
if ("obs_section".equalsIgnoreCase(nameAttribute))
parseObsSectionRepeats(child, bindings, problemList, duplicateFields);
}
if (name.equalsIgnoreCase(NODE_SIMPLETYPE)) {
parseSimpleType(child, child.getAttributeValue(null, ATTRIBUTE_NAME), bindings);
parseDuplicateFieldsSimpleType(duplicateFields, child, bindings);
}
}
}
private static void parseDuplicateFieldsComplexType(Hashtable<String, List<String>> duplicateFields,
Hashtable<String, Element> repeatControls, Element child,
Element bodyNode, Element modelNode, Element xformSchemaNode,
Hashtable bindings, Hashtable<String, String> problemList,
Hashtable<String, String> problemListItems) {
Set<Entry<String, List<String>>> entries = duplicateFields.entrySet();
for (Entry<String, List<String>> entry : entries) {
String key = child.getAttributeValue(null, ATTRIBUTE_NAME);
if (!key.equals(entry.getKey()))
continue;
String suffix = "_type";
int pos = key.indexOf(suffix);
if (pos < 1) {
suffix = "_section";
pos = key.indexOf(suffix);
}
String firstField = key.substring(0, pos);
List<String> fields = entry.getValue();
for (String field : fields) {
if (field.equals(firstField))
continue;
parseComplexType(child, field + suffix, bodyNode, xformSchemaNode, bindings, problemList, problemListItems,
repeatControls, modelNode);
}
}
}
private static void parseSharedRepeatKidsComplexType(String name, Hashtable<String, List<String>> duplicateFields,
Hashtable<String, Element> repeatControls, Element child,
Element bodyNode, Element modelNode, Element xformSchemaNode,
Hashtable bindings, Hashtable<String, String> problemList,
Hashtable<String, String> problemListItems) {
String orgName = name;
name = getBindNodeName(name);
List<String> repeats = repeatSharedKids.get(name);
if (repeats == null)
return;
for (String repeat : repeats) {
problemListItems.put(name, repeat);
parseComplexType(child, orgName, bodyNode, xformSchemaNode, bindings, problemList, problemListItems,
repeatControls, modelNode);
}
}
private static void parseDuplicateFieldsSimpleType(Hashtable<String, List<String>> duplicateFields, Element child,
Hashtable bindings) {
Set<Entry<String, List<String>>> entries = duplicateFields.entrySet();
for (Entry<String, List<String>> entry : entries) {
String key = child.getAttributeValue(null, ATTRIBUTE_NAME);
if (!key.equals(entry.getKey() + "_restricted_type"))
continue;
String firstField = key.substring(0, key.indexOf("_type"));
List<String> fields = entry.getValue();
for (String field : fields) {
if (field.equals(firstField))
continue;
parseSimpleType(child, field + "_type", bindings);
}
}
}
private static void parseObsSectionRepeats(Element complexTypeNode, Hashtable bindings,
Hashtable<String, String> problemList,
Hashtable<String, List<String>> duplicateFields) {
for (int i = 0; i < complexTypeNode.getChildCount(); i++) {
if (complexTypeNode.isText(i))
continue; //ignore text.
Element child = (Element) complexTypeNode.getElement(i);
if (child.getName().equalsIgnoreCase(NODE_SEQUENCE)) {
parseObsSectionSequenceRepeats(child, bindings, problemList, duplicateFields);
return;
}
}
}
private static void parseObsSectionSequenceRepeats(Element sequenceNode, Hashtable bindings,
Hashtable<String, String> problemList,
Hashtable<String, List<String>> duplicateFields) {
Hashtable<String, List<String>> fieldMap = new Hashtable<String, List<String>>();
for (int i = 0; i < sequenceNode.getChildCount(); i++) {
if (sequenceNode.isText(i))
continue; //ignore text.
Element child = (Element) sequenceNode.getElement(i);
String name = child.getAttributeValue(null, ATTRIBUTE_NAME);
if ("unbounded".equalsIgnoreCase(child.getAttributeValue(null, ATTRIBUTE_MAXOCCURS))) {
problemList.put(name, name);
/*obsRepeatItems.put(name, name);
String nodeset = nodesets.get(name);
Element bindNode = (Element)bindings.get(name);
if(nodeset != null && bindNode != null){
if(nodeset.endsWith("/value"))
bindNode.setAttribute(null, ATTRIBUTE_NODESET, nodeset.substring(0, nodeset.length() - 6));
}*/
}
String type = child.getAttributeValue(null, ATTRIBUTE_TYPE);
List<String> fields = fieldMap.get(type);
if (fields == null) {
fields = new ArrayList<String>();
fields.add(name);
fieldMap.put(type, fields);
} else {
fields.add(name);
duplicateFields.put(type, fields);
}
}
}
private static void parseSimpleType(Element simpleTypeNode, String name, Hashtable bindings) {
if (name == null || name.trim().length() == 0)
return;
name = getBindNodeName(name);
if (name == null || name.trim().length() == 0)
return;
for (int i = 0; i < simpleTypeNode.getChildCount(); i++) {
if (simpleTypeNode.isText(i))
continue; //ignore text.
Element child = (Element) simpleTypeNode.getElement(i);
if (child.getName().equalsIgnoreCase(NODE_RESTRICTION))
parseRestriction(child, name, bindings);
}
}
private static void parseRestriction(Element restrictionNode, String name, Hashtable bindings) {
Element bindNode = (Element) bindings.get(name);
if (bindNode != null) {
String type = restrictionNode.getAttributeValue(null, ATTRIBUTE_BASE);
if ("xs:int".equalsIgnoreCase(type) || "xs:float".equalsIgnoreCase(type))
addValidationRuleRanges(name, bindings, bindNode, restrictionNode);
}
}
/**
* Parses a complex type node in an openmrs schema document.
*
* @param complexTypeNode - the complex type node.
* @param name - the name of the complex type node.
* @param bodyNode - the xform body node.
* @param xformSchemaNode - the top node of the xml schema data types.
* @param bindings - a hashtable of node bindings.
*/
private static void parseComplexType(Element complexTypeNode, String name, Element bodyNode, Element xformSchemaNode,
Hashtable bindings, Hashtable<String, String> problemList,
Hashtable<String, String> problemListItems,
Hashtable<String, Element> repeatControls, Element modelNode) {
if (name.equals("patient_relationship_section") || name.equals("relative_section"))
return;
String orgName = name;
name = getBindNodeName(name);
if (name == null)
return;
Element labelNode = null, bindNode = (Element) bindings.get(name);
if (bindNode == null) {
//could be a section like problem_list_section
if (name.equals("problem_list"))
;//addProblemListItems(name, complexTypeNode, bodyNode, modelNode);
String binding = repeatChildTypes.get(orgName);
if (binding != null) {
name = binding;
bindNode = (Element) bindings.get(binding);
} else
return;
}
boolean repeatItem = false;
Element lblNode = null;
String nameAttributeValue = complexTypeNode.getAttributeValue(null, "name");
/*if(nameAttributeValue != null && nameAttributeValue.endsWith("_section")){
problemList.put(name, name);*/
if (problemList.contains(name)) {
lblNode = addProblemListSection(name, bodyNode, repeatControls, modelNode);
repeatItem = true;
}
for (int i = 0; i < complexTypeNode.getChildCount(); i++) {
if (complexTypeNode.isText(i))
continue; //ignore text.
Element node = (Element) complexTypeNode.getChild(i);
if (node.getName().equalsIgnoreCase(NODE_SEQUENCE))
labelNode = parseSequenceNode(name, node, bodyNode, xformSchemaNode, bindNode, problemList,
problemListItems, repeatControls, repeatItem, modelNode);
if (repeatItem)
labelNode = lblNode;
if (labelNode != null && isNodeWithConceptNameAndId(node))
addLabelTextAndHint(labelNode, node);
else if (isNodeWithDataType(node)) {
setDataType(bindNode, node);
List<String> bindingList = sharedRestrictions.get(name);
if (bindingList != null) {
for (String binding : bindingList) {
Element bindingNode = (Element) bindings.get(binding);
if (bindingNode == null)
continue;
setDataType(bindingNode, node);
}
}
}
}
}
private static void addProblemListItems(String name, Element complexTypeNode, Element bodyNode, Element modelNode) {
for (int i = 0; i < complexTypeNode.getChildCount(); i++) {
if (complexTypeNode.isText(i))
continue; //ignore text.
Element node = (Element) complexTypeNode.getChild(i);
if (node.getName().equalsIgnoreCase(NODE_SEQUENCE)) {
for (int j = 0; j < node.getChildCount(); j++) {
if (node.isText(j))
continue; //ignore text.
Element nd = (Element) node.getChild(j);
String itemName = nd.getAttributeValue(null, ATTRIBUTE_NAME);
String type = nd.getAttributeValue(null, ATTRIBUTE_TYPE);
type = type.substring(0, type.length() - 5);
if (type.equals(itemName))
continue; //eg medication_orders and medication_orders_type. But if medication_orders_type is missing, then we shall never have a ui for medication_orders
addProblemListItem(itemName, bodyNode);
}
}
}
}
private static void addProblemListItem(String name, Element bodyNode) {
String nodeset = "problem_list/" + name + "/value";
String id = nodeset.replace('/', '_');
Element select1Node = bodyNode.createElement(NAMESPACE_XFORMS, null);
select1Node.setName(CONTROL_SELECT1);
//select1Node.setAttribute(null, ATTRIBUTE_REF, nodeset);
select1Node.setAttribute(null, ATTRIBUTE_BIND, id);
bodyNode.addChild(Element.ELEMENT, select1Node);
Element labelNode = select1Node.createElement(NAMESPACE_XFORMS, null);
labelNode.setName(NODE_LABEL);
labelNode.addChild(Element.TEXT, name);
select1Node.addChild(Element.ELEMENT, labelNode);
Element itemLabelNode = select1Node.createElement(NAMESPACE_XFORMS, null);
itemLabelNode.setName(NODE_LABEL);
itemLabelNode.addChild(Element.TEXT, name + " value");
Element itemValNode = select1Node.createElement(NAMESPACE_XFORMS, null);
itemValNode.setName(NODE_VALUE);
itemValNode.addChild(Element.TEXT, "value");
Element itemNode = select1Node.createElement(NAMESPACE_XFORMS, null);
itemNode.setName(NODE_ITEM);
itemNode.addChild(Element.ELEMENT, itemLabelNode);
itemNode.addChild(Element.ELEMENT, itemValNode);
select1Node.addChild(Element.ELEMENT, itemNode);
//create bind node
Element bindNode = select1Node.createElement(NAMESPACE_XFORMS, null);
bindNode.setName(NODE_BIND);
bindNode.setAttribute(null, ATTRIBUTE_ID, id);
bindNode.setAttribute(null, ATTRIBUTE_NODESET, "/form/" + nodeset);
bindNode.setAttribute(null, ATTRIBUTE_TYPE, DATA_TYPE_TEXT);
select1Node.addChild(Element.ELEMENT, bindNode);
}
/**
* Checks if the xforms data type is set to any value other than text.
*
* @param bindNode the xforms bind node
* @return
*/
private static boolean isDataTypeSetPrecisely(Element bindNode) {
String type = bindNode.getAttributeValue(null, ATTRIBUTE_TYPE);
if (type != null && !type.equalsIgnoreCase(DATA_TYPE_TEXT))
return true;
return false;
}
/**
* Sets the xforms data type from an openmrs data type
*
* @param bindNode the bind xforms node whose data type to set.
* @param node the schema node having the openmrs data type
*/
public static void setDataType(Element bindNode, Element node) {
//Some types may have been already set to the precise value
//and hence should not be overwritten. eg NM could have been
//set to either int or decimal.
if (isDataTypeSetPrecisely(bindNode))
return;
String fixed = node.getAttributeValue(null, ATTRIBUTE_FIXED);
if ("ED".equalsIgnoreCase(fixed))
bindNode.setAttribute(null, ATTRIBUTE_TYPE, DATA_TYPE_BASE64BINARY);
else if ("BIT".equalsIgnoreCase(fixed))
bindNode.setAttribute(null, ATTRIBUTE_TYPE, DATA_TYPE_BOOLEAN);
else if ("TS".equalsIgnoreCase(fixed))
bindNode.setAttribute(null, ATTRIBUTE_TYPE, DATA_TYPE_DATETIME);
else if ("TM".equalsIgnoreCase(fixed))
bindNode.setAttribute(null, ATTRIBUTE_TYPE, DATA_TYPE_TIME);
else if ("DT".equalsIgnoreCase(fixed))
bindNode.setAttribute(null, ATTRIBUTE_TYPE, DATA_TYPE_DATE);
else if ("NM".equalsIgnoreCase(fixed))
bindNode.setAttribute(null, ATTRIBUTE_TYPE, DATA_TYPE_DECIMAL);
else if ("RP".equalsIgnoreCase(fixed))
bindNode.setAttribute(null, ATTRIBUTE_TYPE, DATA_TYPE_BASE64BINARY);
else if ("ST".equalsIgnoreCase(fixed))
bindNode.setAttribute(null, ATTRIBUTE_TYPE, DATA_TYPE_TEXT);
else if ("CWE".equalsIgnoreCase(fixed))
bindNode.setAttribute(null, ATTRIBUTE_TYPE, DATA_TYPE_TEXT);
else if ("N/A".equalsIgnoreCase(fixed))
bindNode.setAttribute(null, ATTRIBUTE_TYPE, DATA_TYPE_TEXT);
//TODO What of ZZ(Rule) and SM(Structured Numeric)?
}
private static void removeChildNode(Element node, String name) {
for (int index = 0; index < node.getChildCount(); index++) {
if (node.getType(index) == Element.ELEMENT) {
Element n = node.getElement(index);
if (name.equals(n.getAttributeValue(null, ATTRIBUTE_ID))) {
node.removeChild(index);
return;
}
}
}
}
/**
* Gets the binding of a complex or simple type node. An example of such a node would be:
* complexType name="weight_kg_type" or <xs:simpleType name="weight_kg_type_restricted_type">
*
* @param name - the name of the complex type node.
* @param bindings - a hashtable of bingings.
* @return - the binding node.
*/
/*private static Element getBindNode(String name, Hashtable bindings){
if(name == null)
return null;
//We are only dealing with names ending with _type. e.g. education_level_type
//Openmrs appends the _type to the name when creating xml types for each concept
if(name.indexOf(COMPLEX_TYPE_NAME_POSTFIX) != -1){
//remove the _type part. e.g from above the name is education_level
name = name.substring(0, name.length() - COMPLEX_TYPE_NAME_POSTFIX.length());
Element bindNode = (Element)bindings.get(name);
return bindNode;
}
else if(name.indexOf(SIMPLE_TYPE_NAME_POSTFIX) != -1){
//Now dealing with things like weight_kg_type_restricted_type
//remove the _type_restricted_type part. e.g from above the name is weight_kg
name = name.substring(0, name.length() - SIMPLE_TYPE_NAME_POSTFIX.length());
Element bindNode = (Element)bindings.get(name);
return bindNode;
}
else
return null;
}*/
/**
* Gets the binding node name of a complex or simple type node name. An example of such a node
* would be weight_kg for: complexType name="weight_kg_type" or <xs:simpleType
* name="weight_kg_type_restricted_type">
*
* @param name - the name of the complex or simple type node.
* @return - the binding node name.
*/
public static String getBindNodeName(String name) {
if (name == null)
return null;
//Now dealing with things like weight_kg_type_restricted_type
//remove the _type_restricted_type part. e.g from above the name is weight_kg
if (name.indexOf(SIMPLE_TYPE_NAME_POSTFIX) != -1)
return name.substring(0, name.length() - SIMPLE_TYPE_NAME_POSTFIX.length());
else if (name.contains("_restricted_type") && name.contains("_type_")) {
//eg weight_kg_type_1_restricted_type for duplicate weight_kg
String s = name;
name = s.substring(0, s.indexOf("_type_"));
return name + s.substring(name.length() + 5, s.indexOf("_restricted_type"));
} else if (name.contains("_type_")) {
//we are looking for something like weight_kg_type_1
int pos = name.indexOf("_type_");
String suffix = name.substring(pos + 5);
if (isNumeric(suffix.substring(1)))
return name.substring(0, pos) + suffix;
}
//Now we are only dealing with names ending with _type. e.g. education_level_type
//Openmrs appends the _type to the name when creating xml types for each concept
//To handle complicated problem lists that have more than one item, we also handle
//names ending with section.
if (name.indexOf(COMPLEX_TYPE_NAME_POSTFIX) == -1) {
if (name.indexOf(COMPLEX_SECTION_NAME_POSTFIX) == -1)
return null;
return name.substring(0, name.length() - COMPLEX_SECTION_NAME_POSTFIX.length());
}
//remove the _type part. e.g from above the name is education_level
name = name.substring(0, name.length() - COMPLEX_TYPE_NAME_POSTFIX.length());
return name;
}
private static boolean isNumeric(String value) {
try {
Integer.parseInt(value);
return true;
}
catch (Exception ex) {}
return false;
}
/**
* Adds text and hint to a label node.
*
* @param labelNode - the label node.
* @param node - the node having the concept name and id.
*/
private static void addLabelTextAndHint(Element labelNode, Element node) {
String fixedAttributeValue = node.getAttributeValue(null, ATTRIBUTE_FIXED);
labelNode.addChild(Element.TEXT, getConceptName(fixedAttributeValue));
Element parentNode = (Element) labelNode.getParent();
if (parentNode.getName().contains(XformBuilder.CONTROL_SELECT))
parentNode.setAttribute(null, XformBuilder.ATTRIBUTE_CONCEPT_ID, getConceptId(fixedAttributeValue).toString());
String hint = getConceptDescription(node);
if (hint != null && hint.length() > 0) {
Element hintNode = /*bodyNode*/labelNode.createElement(NAMESPACE_XFORMS, null);
hintNode.setName(NODE_HINT);
hintNode.addChild(Element.TEXT, getConceptDescription(node));
labelNode.getParent().addChild(1, Element.ELEMENT, hintNode);
}
}
/**
* Checks if this node has the concept name and id. An example of such a node would be as:
* <xs:attribute name="openmrs_concept" type="xs:string" use="required"
* fixed="5089^WEIGHT (KG)^99DCT" /> where the concept name and id combination we are refering
* to is: 5089^WEIGHT (KG)^99DCT
*
* @param node - the node to check.
* @return true if so, else false.
*/
private static boolean isNodeWithConceptNameAndId(Element node) {
return node.getName().equalsIgnoreCase(NODE_ATTRIBUTE) && node.getAttributeValue(null, ATTRIBUTE_NAME) != null
&& node.getAttributeValue(null, ATTRIBUTE_NAME).equalsIgnoreCase(ATTRIBUTE_OPENMRS_CONCEPT);
}
/**
* Checks if this node has the openmrs data type. An example of such a node would be as:
* <xs:attribute name="openmrs_datatype" type="xs:string" use="required" fixed="NM" /> where the
* data type is pointed to by the fixed attribute
*
* @param node - the node to check.
* @return true if so, else false.
*/
private static boolean isNodeWithDataType(Element node) {
return node.getName().equalsIgnoreCase(NODE_ATTRIBUTE)
&& ATTRIBUTE_OPENMRS_DATATYPE.equalsIgnoreCase(node.getAttributeValue(null, ATTRIBUTE_NAME));
}
/**
* Gets the concept id from a name and id combination.
*
* @param conceptName - the concept name.
* @return - the id
*/
public static Integer getConceptId(String conceptName) {
try {
return Integer.parseInt(conceptName.substring(0, conceptName.indexOf("^")));
}
catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
/**
* Gets the description of a concept.
*
* @param node - the node having the concept.
* @return - the concept description.
*/
private static String getConceptDescription(Element node) {
try {
String name = node.getAttributeValue(null, ATTRIBUTE_FIXED);
Concept concept = Context.getConceptService().getConcept(getConceptId(name));
ConceptName conceptName = concept.getName();
return conceptName.getDescription();
}
catch (Exception ex) {
//ex.printStackTrace();
}
return "";
}
/**
* Gets the name of a concept from the name and id combination value.
*
* @param val - the name and id combination.
* @return - the cencept name.
*/
public static String getConceptName(String val) {
val = val.substring(val.indexOf('^') + 1, val.lastIndexOf('^'));
int pos = val.indexOf('^');
if (pos > 0)
val = val.substring(0, pos);
return val;
}
/**
* Parses a sequence node from an openmrs schema document.
*
* @param name
* @param sequenceNode
* @param bodyNode
* @param xformSchemaNode
* @param bindingNode
* @return the created label node.
*/
private static Element parseSequenceNode(String name, Element sequenceNode, Element bodyNode, Element xformSchemaNode,
Element bindingNode, Hashtable<String, String> problemList,
Hashtable<String, String> problemListItems,
Hashtable<String, Element> repeatControls, boolean repeatItem, Element modelNode) {
Element labelNode = null, controlNode = bodyNode.createElement(NAMESPACE_XFORMS, null);
;
for (int i = 0; i < sequenceNode.getChildCount(); i++) {
if (sequenceNode.isText(i))
continue; //ignore text.
Element node = (Element) sequenceNode.getChild(i);
String itemName = node.getAttributeValue(null, ATTRIBUTE_NAME);
//???????
if (repeatItem) {
if (problemListItems.containsKey(itemName)) {
List<String> repeats = repeatSharedKids.get(itemName);
if (repeats == null) {
repeats = new ArrayList<String>();
repeatSharedKids.put(itemName, repeats);
}
repeats.add(problemListItems.get(itemName));
}
problemListItems.put(itemName, name);
//removeChildNode(modelNode,itemName);
}
//Instead of the value node, multiple select questions have one node
//for each possible select option.
if (!itemName.equalsIgnoreCase(NODE_VALUE)) {
//Assuming sections (those that end with _section) don't have this attribute.
String type = node.getAttributeValue(null, "type"/*"minOccurs"*/);
if (type != null && !repeatItem) {
if (problemList.containsKey(name))
//if(problemListItems.containsKey(name))
return addProblemListSection(name, bodyNode, repeatControls, modelNode);
else
continue;
}
if (repeatItem) {
String binding = name + "_" + itemName;
if (name.equals(problemListItems.get(binding))) {
repeatChildTypes.put(type, binding);
}
}
//if(!(itemName.equalsIgnoreCase(NODE_DATE) || itemName.equalsIgnoreCase(NODE_TIME)) && node.getAttributeValue(null, ATTRIBUTE_OPENMRS_CONCEPT) == null)
if (!itemName.equalsIgnoreCase(NODE_DATE) && !itemName.equalsIgnoreCase(NODE_TIME)
&& node.getChildCount() > 0 /*&& node.getAttributeValue(null, ATTRIBUTE_OPENMRS_CONCEPT) == null*/)
labelNode = parseMultiSelectNode(name, itemName, node, controlNode, bodyNode, labelNode, bindingNode,
problemList, problemListItems, repeatControls, modelNode);
//else if(name.equalsIgnoreCase(COMPLEX_TYPE_NAME_PROBLEM_LIST))
// problemList.put(name, name);
continue;
}
if (node.getAttributeValue(null, ATTRIBUTE_NILLABLE).equalsIgnoreCase("0"))
bindingNode.setAttribute(null, ATTRIBUTE_REQUIRED, XPATH_VALUE_TRUE);
//We are interested in the element whose name attribute is equal to value,
//for single select lists.
labelNode = parseSequenceValueNode(name, node, labelNode, bodyNode, bindingNode, problemList, problemListItems,
repeatControls, modelNode);
}
return labelNode;
}
/**
* Creates a repeat control for problem lists with sections.
*
* @param name
* @param bodyNode
* @param repeatControls
* @return the label node for the control.
*/
private static Element addProblemListSection(String name, Element bodyNode, Hashtable<String, Element> repeatControls,
Element modelNode) {
Element groupNode = bodyNode.createElement(NAMESPACE_XFORMS, null);
groupNode.setName(NODE_GROUP);
Element labelNode = groupNode.createElement(NAMESPACE_XFORMS, null);
labelNode.setName(NODE_LABEL);
groupNode.addChild(Element.ELEMENT, labelNode);
bodyNode.addChild(Element.ELEMENT, groupNode);
Element repeatControl = buildRepeatControl(groupNode, null, name, modelNode);
repeatControls.put(name, repeatControl);
//repeatControl.addChild(Element.ELEMENT, labelNode);
addControl(groupNode, repeatControl);
return labelNode;
}
/**
* Builds an xform input type control from a sequence value node.
*
* @param name - the name of the complex type node we are dealing with.
* @param node - the value node.
* @param type - the type attribute value.
* @param labelNode - the label node.
* @param bindingNode - the binding node.
* @param bodyNode - the body node.
* @return returns the created label node.
*/
private static Element buildSequenceInputControlNode(String name, Element node, String type, Element labelNode,
Element bindingNode, Element bodyNode,
Hashtable<String, String> problemList,
Hashtable<String, String> problemListItems,
Hashtable<String, Element> repeatControls, Element modelNode) {
type = getPrefixedDataType(type);
if (!isDataTypeSetPrecisely(bindingNode))
bindingNode.setAttribute(null, ATTRIBUTE_TYPE, type);
Element inputNode = bodyNode.createElement(NAMESPACE_XFORMS, null);
inputNode.setName(CONTROL_INPUT);
//inputNode.setAttribute(null, ATTRIBUTE_BIND, name);
labelNode = bodyNode.createElement(NAMESPACE_XFORMS, null);
labelNode.setName(NODE_LABEL);
inputNode.addChild(Element.ELEMENT, labelNode);
addRepeatControlNode(name, inputNode, bodyNode, problemList, problemListItems, repeatControls, NODE_VALUE,
modelNode, bindingNode);
return labelNode;
}
private static void addRepeatControlNode(String name, Element controlNode, Element bodyNode,
Hashtable<String, String> problemList,
Hashtable<String, String> problemListItems,
Hashtable<String, Element> repeatControls, String valueNodeName,
Element modelNode, Element bindingNode) {
if (problemList.containsKey(name))
controlNode.setAttribute(null, ATTRIBUTE_REF, valueNodeName);
else if (problemListItems.containsKey(name))
controlNode.setAttribute(null, ATTRIBUTE_BIND, name); //controlNode.setAttribute(null, ATTRIBUTE_REF, name+"/"+valueNodeName);
else
controlNode.setAttribute(null, ATTRIBUTE_BIND, name);
if (problemList.contains(name))
;//addControl(bodyNode,buildRepeatControl(bodyNode,controlNode,name, modelNode));
else if (problemListItems.containsKey(name)) {
String repeatControlName = problemListItems.get(name);
String nodeset = bindingNode.getAttributeValue(null, XformBuilder.ATTRIBUTE_NODESET);
if (nodeset != null && nodeset.contains(repeatControlName)) {
Element repeatControl = repeatControls.get(repeatControlName);
if (repeatControl != null) {
repeatControl.addChild(Element.ELEMENT, controlNode);
String binding = repeatControlName + "_" + name;
String repeatBinding = problemListItems.get(binding);
if (repeatBinding != null && repeatBinding.equals(problemListItems.get(name))) {
controlNode.setAttribute(null, ATTRIBUTE_BIND, binding);
}
}
} else
addControl(bodyNode, controlNode);
} else
addControl(bodyNode, controlNode);
}
/**
* Parses a sequence value node and builds the corresponding xforms UI control.
*
* @param name - the name of the complex type node whose sequence we are parsing.
* @param node - the sequence value node.
* @param labelNode - the label node to build.
* @param bodyNode - the body node.
* @param bindingNode - the binding node.
* @return the created label node.
*/
private static Element parseSequenceValueNode(String name, Element node, Element labelNode, Element bodyNode,
Element bindingNode, Hashtable<String, String> problemList,
Hashtable<String, String> problemListItems,
Hashtable<String, Element> repeatControls, Element modelNode) {
String type = node.getAttributeValue(null, ATTRIBUTE_TYPE);
if (type != null)
labelNode = buildSequenceInputControlNode(name, node, type, labelNode, bindingNode, bodyNode, problemList,
problemListItems, repeatControls, modelNode);
else {
//This is a select1 or select control which don't have the type attribute.
for (int j = 0; j < node.getChildCount(); j++) {
if (node.isText(j))
continue;
Element simpleTypeNode = (Element) node.getChild(j);
if (!simpleTypeNode.getName().equalsIgnoreCase(NODE_SIMPLETYPE))
continue;
return parseSimpleType(name, simpleTypeNode, bodyNode, bindingNode, problemList, problemListItems,
repeatControls, modelNode);
}
}
return labelNode;
}
/**
* Checks if the data type has a namespace prefix, if it does not, it prepends it.
*
* @param type - the data type.
*/
private static String getPrefixedDataType(String type) {
if (type == null)
return null;
if (type.indexOf(NAMESPACE_PREFIX_SEPARATOR) == -1)
type = PREFIX_OPENMRS_TYPE + NAMESPACE_PREFIX_SEPARATOR + type;
else
type = PREFIX_XML_SCHEMA + NAMESPACE_PREFIX_SEPARATOR
+ type.substring(type.indexOf(NAMESPACE_PREFIX_SEPARATOR) + 1);
//type = type.substring(type.indexOf(NAMESPACE_PREFIX_SEPARATOR)+1);
if (type.contains("openmrs"))
type = "xsd:string";
if (type.contains("float"))
type = DATA_TYPE_DECIMAL;
else if (type.contains("int"))
type = DATA_TYPE_INT;
return type;
}
/**
* Parses a simple type node in an openmrs schema document to build select1 and select XForms
* items from xs:enumeration s.
*
* @param name
* @param simpleTypeNode
* @param bodyNode
* @param bindingNode
* @return the xforms label node.
*/
private static Element parseSimpleType(String name, Element simpleTypeNode, Element bodyNode, Element bindingNode,
Hashtable<String, String> problemList,
Hashtable<String, String> problemListItems,
Hashtable<String, Element> repeatControls, Element modelNode) {
for (int i = 0; i < simpleTypeNode.getChildCount(); i++) {
if (simpleTypeNode.isText(i))
continue; //ignore text.
Element child = (Element) simpleTypeNode.getElement(i);
if (child.getName().equalsIgnoreCase(NODE_RESTRICTION))
return parseRestriction(name, (Element) simpleTypeNode.getParent(), child, bodyNode, bindingNode,
problemList, problemListItems, repeatControls, modelNode);
}
return null;
}
/**
* Gets the node having the concept name and id combination for a multiple select item node.
* Such a node would look like: xs:attribute name="openmrs_concept" type="xs:string"
* use="required" fixed="215^JAUNDICE^99DCT"
*
* @param node - the multiple select item node.
* @return the concept node.
*/
private static Element getMultiSelectItemConceptNode(Element node) {
Element retNode;
for (int i = 0; i < node.getChildCount(); i++) {
if (node.isText(i))
continue; //ignore text.
Element child = (Element) node.getChild(i);
if (child.getName().equalsIgnoreCase(NODE_ATTRIBUTE))
return child;
retNode = getMultiSelectItemConceptNode(child);
if (retNode != null)
return retNode;
}
return null;
}
/**
* Parses a multi select node and builds its corresponding items. An example of such a node
* would be: xs:element name="jaundice" default="false" nillable="true" for a complex type whose
* name is eye_exam_findings_type
*
* @param name - the name of the complex type node we are dealing with.
* @param itemName - the name attribute of the node we are dealing with.
* @param selectItemNode - the multiple select item node.
* @param controlNode - the xform UI control
* @param bodyNode - the body node.
* @param labelNode - the label node.
* @param bindingNode - the binding node.
* @return the label node we have created.
*/
private static Element parseMultiSelectNode(String name, String itemName, Element selectItemNode, Element controlNode,
Element bodyNode, Element labelNode, Element bindingNode,
Hashtable<String, String> problemList,
Hashtable<String, String> problemListItems,
Hashtable<String, Element> repeatControls, Element modelNode) {
//If this is the first time we are looping through, create the input control.
//Otherwise just add the items one by one as we get called for each.
if (controlNode.getChildCount() == 0) {
//controlNode = bodyNode.createElement(NAMESPACE_XFORMS, null);
controlNode.setName(CONTROL_SELECT);
//For repeat kids, we set ref instead of bind
if (!(problemList.containsKey(name) || problemListItems.containsKey(name)))
controlNode.setAttribute(null, ATTRIBUTE_BIND, name);
controlNode.setAttribute(null, ATTRIBUTE_APPEARANCE,
Context.getAdministrationService().getGlobalProperty("xforms.multiSelectAppearance"));
labelNode = bodyNode.createElement(NAMESPACE_XFORMS, null);
labelNode.setName(NODE_LABEL);
controlNode.addChild(Element.ELEMENT, labelNode);
//addControl(bodyNode,controlNode); //bodyNode.addChild(Element.ELEMENT, controlNode);
addRepeatControlNode(name, controlNode, bodyNode, problemList, problemListItems, repeatControls,
NODE_XFORMS_VALUE, modelNode, bindingNode);
bindingNode.setAttribute(null, ATTRIBUTE_TYPE, DATA_TYPE_TEXT);
}
buildMultipleSelectItemNode(itemName, selectItemNode, controlNode);
return labelNode;
}
/**
* Builds a multiple select item node.
*
* @param itemName - the name attribute of the multiple select item whose item node we are
* building.
* @param selectItemNode - the xml schema select item node.
* @param controlNode - the xforms control whose item we are building.
*/
private static void buildMultipleSelectItemNode(String itemName, Element selectItemNode, Element controlNode) {
Element node = getMultiSelectItemConceptNode(selectItemNode);
String value = node.getAttributeValue(null, ATTRIBUTE_FIXED);
String label = getConceptName(value);
Element itemLabelNode = /*bodyNode*/controlNode.createElement(NAMESPACE_XFORMS, null);
itemLabelNode.setName(NODE_LABEL);
itemLabelNode.addChild(Element.TEXT, label);
Element itemValNode = /*bodyNode*/controlNode.createElement(NAMESPACE_XFORMS, null);
itemValNode.setName(NODE_VALUE);
itemValNode.addChild(Element.TEXT, itemName /*value*//*binding*/);
Element itemNode = /*bodyNode*/controlNode.createElement(NAMESPACE_XFORMS, null);
itemNode.setName(NODE_ITEM);
itemNode.setAttribute(null, ATTRIBUTE_CONCEPT_ID, getConceptId(value).toString());
itemNode.addChild(Element.ELEMENT, itemLabelNode);
itemNode.addChild(Element.ELEMENT, itemValNode);
controlNode.addChild(Element.ELEMENT, itemNode);
}
/**
* Adds a UI control to the document body.
*
* @param bodyNode - the body node.
* @param controlNode - the UI control.
*/
public static void addControl(Element bodyNode, Element controlNode) {
bodyNode.addChild(Element.ELEMENT, controlNode);
}
/**
* Parses a restriction which has the enumeration nodes. Such a node would look like:
* xs:restriction base="xs:string" It also sets the data type which is normally the base
* attribute.
*
* @param name - the name of the complex type question node whose restriction we are parsing.
* @param valueNode - the node who name attribute is equal to value.
* @param restrictionNode - the restriction node.
* @param bodyNode - the xform body node.
* @param bindingNode - the binding node.
* @return the label node of the created control.
*/
private static Element parseRestriction(String name, Element valueNode, Element restrictionNode, Element bodyNode,
Element bindingNode, Hashtable<String, String> problemList,
Hashtable<String, String> problemListItems,
Hashtable<String, Element> repeatControls, Element modelNode) {
//the base attribute of a restriction has the data type for this question.
String type = restrictionNode.getAttributeValue(null, ATTRIBUTE_BASE);
type = getPrefixedDataType(type);
bindingNode.setAttribute(null, ATTRIBUTE_TYPE, type);
String controlName = CONTROL_SELECT;
String maxOccurs = valueNode.getAttributeValue(null, ATTRIBUTE_MAXOCCURS);
if (maxOccurs != null && maxOccurs.equalsIgnoreCase("1"))
controlName = CONTROL_SELECT1;
if (!hasRestrictions(restrictionNode))
controlName = CONTROL_INPUT;
Element controlNode = bodyNode.createElement(NAMESPACE_XFORMS, null);
controlNode.setName(controlName);
String valueNodeName = NODE_VALUE;
if (controlName.equals(CONTROL_SELECT))
valueNodeName = NODE_XFORMS_VALUE;
if (!controlName.equalsIgnoreCase(CONTROL_INPUT))
controlNode.setAttribute(null, ATTRIBUTE_APPEARANCE,
Context.getAdministrationService()
.getGlobalProperty(XformConstants.GLOBAL_PROP_KEY_SINGLE_SELECT_APPEARANCE));
addRepeatControlNode(name, controlNode, bodyNode, problemList, problemListItems, repeatControls, valueNodeName,
modelNode, bindingNode);
Element labelNode = bodyNode.createElement(NAMESPACE_XFORMS, null);
labelNode.setName(NODE_LABEL);
controlNode.addChild(Element.ELEMENT, labelNode);
addRestrictionEnumerations(restrictionNode, controlNode);
return labelNode;
}
/**
* Checks if a restriction node has enumeration restrictions.
*
* @param restrictionNode the restriction node.
* @return true if it has, else false;
*/
private static boolean hasRestrictions(Element restrictionNode) {
for (int i = 0; i < restrictionNode.getChildCount(); i++) {
if (restrictionNode.isText(i))
continue;
return true;
}
return false;
}
/**
* Adds validation constraints to the xfrom as specified from the openmrs schema. For now these
* are for openmrs numeric concepts (int and float)
*
* @param bindingNode the xforms node to contain the constraint
* @param restrictionNode the openmrs schema node having the allowed range values
*/
private static void addValidationRuleRanges(String name, Hashtable bindings, Element bindingNode, Element restrictionNode) {
String lower = null, upper = null;
for (int i = 0; i < restrictionNode.getChildCount(); i++) {
if (restrictionNode.isText(i))
continue; //ignore text.
Element child = (Element) restrictionNode.getChild(i);
if (child.getName().equalsIgnoreCase(NODE_MININCLUSIVE))
lower = child.getAttributeValue(null, ATTRIBUTE_VALUE);
else if (child.getName().equalsIgnoreCase(NODE_MAXINCLUSIVE))
upper = child.getAttributeValue(null, ATTRIBUTE_VALUE);
}
if (upper != null && lower != null && upper.trim().length() > 0 && lower.trim().length() > 0) {
setValidationRule(bindingNode, upper, lower);
List<String> bindingList = sharedRestrictions.get(name);
if (bindingList != null) {
for (String binding : bindingList) {
Element bindNode = (Element) bindings.get(binding);
if (bindNode == null)
continue;
setValidationRule(bindNode, upper, lower);
}
}
}
}
private static void setValidationRule(Element bindingNode, String upper, String lower) {
bindingNode.setAttribute(null, ATTRIBUTE_CONSTRAINT, ". >= " + lower + " and . <= " + upper);
bindingNode.setAttribute(null, (XformsUtil.isJavaRosaSaveFormat() ? "jr:constraintMsg" : ATTRIBUTE_MESSAGE),
"value should be between " + lower + " and " + upper + " inclusive");
}
/**
* Builds an XForms repeat control for problem list elemtnts.
*
* @param bodyNode
* @param controlNode
* @param name
* @return
*/
private static Element buildRepeatControl(Element bodyNode, Element controlNode, String name, Element modelNode) {
Element repeatControl = bodyNode.createElement(NAMESPACE_XFORMS, null);
repeatControl.setName(CONTROL_REPEAT);
repeatControl.setAttribute(null, ATTRIBUTE_BIND, name);
if (controlNode != null)
repeatControl.addChild(Element.ELEMENT, controlNode);
else if (name.contains("problem_added") || name.contains("problem_resolved"))
addDefaultProblemListChild(name, repeatControl, null, modelNode);
else if (obsRepeatItems.contains(name))
addDefaultProblemListChild(name, repeatControl, "value" /*nodesets.get(name)*/, modelNode);
return repeatControl;
}
public static void addDefaultProblemListChild(String name, Element repeatControl, String nodeset, Element modelNode) {
//add the input node.
Element controlNode = repeatControl.createElement(NAMESPACE_XFORMS, null);
controlNode.setName(CONTROL_INPUT);
nodeset = (nodeset == null ? "problem_list/" + name + "/value" : nodeset);
String id = nodeset.replace('/', '_');
//controlNode.setAttribute(null, ATTRIBUTE_REF, nodeset);
//controlNode.setAttribute(null, ATTRIBUTE_TYPE, DATA_TYPE_TEXT);
controlNode.setAttribute(null, ATTRIBUTE_BIND, id);
repeatControl.addChild(Element.ELEMENT, controlNode);
//add the label.
Element labelNode = controlNode.createElement(NAMESPACE_XFORMS, null);
labelNode.setName(NODE_LABEL);
labelNode.addChild(Element.TEXT, name + " value");
controlNode.addChild(Element.ELEMENT, labelNode);
//create bind node
Element bindNode = controlNode.createElement(NAMESPACE_XFORMS, null);
bindNode.setName(NODE_BIND);
bindNode.setAttribute(null, ATTRIBUTE_ID, id);
bindNode.setAttribute(null, ATTRIBUTE_NODESET, "/form/" + nodeset);
bindNode.setAttribute(null, ATTRIBUTE_TYPE, DATA_TYPE_TEXT);
modelNode.addChild(Element.ELEMENT, bindNode);
}
/**
* Adds enumerations items for a restriction node to an xform control. Such a node is a select
* or select1 kind, and the enumerations become the xform items.
*
* @param restrictionNode - the restriction node.
* @param controlNode - the control node to add the enumerations to.
*/
private static void addRestrictionEnumerations(Element restrictionNode, Element controlNode) {
Element itemValNode = null;
Element itemLabelNode = null;
String valueText = null;
for (int i = 0; i < restrictionNode.getChildCount(); i++) {
//element nodes have the values. e.g. <xs:enumeration value="1360^RE TREATMENT^99DCT" />
if (restrictionNode.getType(i) == Element.ELEMENT) {
Element child = restrictionNode.getElement(i);
if (child.getName().equalsIgnoreCase(NODE_ENUMERATION)) {
itemValNode = /*bodyNode*/controlNode.createElement(NAMESPACE_XFORMS, null);
itemValNode.setName(NODE_VALUE);
valueText = child.getAttributeValue(null, NODE_VALUE);
itemValNode.addChild(Element.TEXT, valueText);
}
}
//Comments have the labels. e.g. <!-- RE TREATMENT -->
if (restrictionNode.getType(i) == Element.COMMENT) {
itemLabelNode = /*bodyNode*/controlNode.createElement(NAMESPACE_XFORMS, null);
itemLabelNode.setName(NODE_LABEL);
itemLabelNode.addChild(Element.TEXT, restrictionNode.getChild(i));
}
//Check if both the labal and value are set. First loop sets value and second label.
if (itemLabelNode != null && itemValNode != null) {
Element itemNode = controlNode.createElement(NAMESPACE_XFORMS, null);
itemNode.setName(NODE_ITEM);
itemNode.setAttribute(null, ATTRIBUTE_CONCEPT_ID, getConceptId(valueText).toString());
controlNode.addChild(Element.ELEMENT, itemNode);
itemNode.addChild(Element.ELEMENT, itemLabelNode);
itemNode.addChild(Element.ELEMENT, itemValNode);
itemLabelNode = null;
itemValNode = null;
}
}
}
/**
* Check if a given node as an openmrs value node.
*
* @param node - the node to check.
* @return - true if it has, else false.
*/
private static boolean hasValueNode(Element node) {
for (int i = 0; i < node.getChildCount(); i++) {
if (node.isText(i))
continue;
Element child = node.getElement(i);
if (child.getName().equalsIgnoreCase(NODE_VALUE))
return true;
}
return false;
}
/**
* Gets the value of the nodeset attribute, for a given node, used for xform bindings.
*
* @param node - the node.
* @return - the value of the nodeset attribite.
*/
private static String getNodesetAttValue(Element node) {
if (hasValueNode(node))
return getNodePath(node) + "/value";
else if (isMultSelectNode(node))
return getNodePath(node) + "/xforms_value";
else
return getNodePath(node);
}
/**
* Gets the path of a node from the instance node.
*
* @param node - the node whose path to get.
* @return - the complete path from the instance node.
*/
public static String getNodePath(Element node) {
String path = node.getName();
Element parent = (Element) node.getParent();
while (parent != null && !parent.getName().equalsIgnoreCase(NODE_INSTANCE)) {
path = parent.getName() + NODE_SEPARATOR + path;
if (parent.getParent() != null && parent.getParent() instanceof Element)
parent = (Element) parent.getParent();
else
parent = null;
}
return NODE_SEPARATOR + path;
}
/**
* Converts an xml document to a string.
*
* @param doc - the document.
* @return the xml string in in the document.
*/
public static String fromDoc2String(Document doc) throws Exception {
KXmlSerializer serializer = new KXmlSerializer();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
try {
serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
serializer.setOutput(dos, XformConstants.DEFAULT_CHARACTER_ENCODING);
doc.write(serializer);
serializer.flush();
}
catch (Exception e) {
e.printStackTrace();
return null;
}
return new String(bos.toByteArray(), XformConstants.DEFAULT_CHARACTER_ENCODING);
/*KXmlSerializer serializer = new KXmlSerializer();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
try{
serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
serializer.setOutput(dos,null);
doc.write(serializer);
serializer.flush();
}
catch(Exception e){
e.printStackTrace();
return null;
}
byte[] byteArr = bos.toByteArray();
char[]charArray = new char[byteArr.length];
for(int i=0; i<byteArr.length; i++)
charArray[i] = (char)byteArr[i];
return String.valueOf(charArray);*/
}
/**
* Gets a document from a stream reader.
*
* @param reader - the reader.
* @return the document.
*/
public static Document getDocument(Reader reader) {
Document doc = new Document();
try {
KXmlParser parser = new KXmlParser();
parser.setInput(reader);
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
doc.parse(parser);
}
catch (Exception e) {
e.printStackTrace();
}
return doc;
}
/**
* Sets the values of patient table fields in an xform. These are the ones with the attributes:
* openmrs_table and openmrs_attribute e.g. <patient_unique_number
* openmrs_table="PATIENT_IDENTIFIER" openmrs_attribute="IDENTIFIER" />
*
* @param formId - the id of the form.
* @param parentNode - the root node of the xform.
* @param patientId - the patient id.
* @param xformsService - the xforms service.
*/
public static void setPatientTableFieldValues(Integer formId, Element parentNode, XformsService xformsService,
VelocityEngine velocityEngine, VelocityContext velocityContext)
throws Exception {
int numOfEntries = parentNode.getChildCount();
for (int i = 0; i < numOfEntries; i++) {
if (parentNode.getType(i) != Element.ELEMENT)
continue;
Element child = (Element) parentNode.getChild(i);
String tableName = child.getAttributeValue(null, ATTRIBUTE_OPENMRS_TABLE);
String columnName = child.getAttributeValue(null, ATTRIBUTE_OPENMRS_ATTRIBUTE);
/*if(tableName != null && columnName != null && isUserDefinedNode(child.getName())){
String filterValue = getFieldDefaultValue(child.getName(), formId,false);
Object value = getPatientValue(xformsService,patientId,tableName,columnName,filterValue);
if(value != null)
setNodeValue(child, value.toString());
}*/
if (tableName != null && columnName != null) {
String name = child.getName().toUpperCase();
String value = xformsService.getFieldDefaultValue(formId, name);
if (value == null && name.contains("_")) {
name = name.replace('_', ' ');
value = xformsService.getFieldDefaultValue(formId, name);
}
if (value != null && value.trim().length() > 0) {
StringWriter w = new StringWriter();
try {
velocityEngine.evaluate(velocityContext, w, XformBuilder.class.getName(), value);
value = w.toString();
if (value != null && value.trim().length() > 0)
setNodeValue(child, value.toString());
}
catch (Exception ex) {
log.error("Failed to evaluate: " + value + " for field: " + name, ex);
}
}
}
setPatientTableFieldValues(formId, child, xformsService, velocityEngine, velocityContext);
}
}
/**
* Gets the value of a patient database table field.
*
* @param xformsService - the xforms service.
* @param patientId - the patient id
* @param tableName - the name of the table.
* @param columnName - the name of column in the table.
* @return - the value
*/
private static Object getPatientValue(XformsService xformsService, Integer patientId, String tableName,
String columnName, String filterValue) {
Object value = null;
try {
value = xformsService.getPatientValue(patientId, tableName, columnName, filterValue);
}
catch (Exception e) {
System.out.println("No column called: " + columnName + " in table: " + tableName);
}
return value;
}
/**
* Checks if a node is a user defined one. That is not one of the standard openmrs nodes.
*
* @param name - the name of the node.
* @return - true if it is a user define one, else false.
*/
public static boolean isUserDefinedNode(String name) {
return !(name.equalsIgnoreCase(NODE_ENCOUNTER_ENCOUNTER_DATETIME)
|| name.equalsIgnoreCase(NODE_ENCOUNTER_LOCATION_ID) || name.equalsIgnoreCase(NODE_ENCOUNTER_PROVIDER_ID)
|| name.equalsIgnoreCase(NODE_PATIENT_MIDDLE_NAME) || name.equalsIgnoreCase(NODE_PATIENT_GIVEN_NAME)
|| name.equalsIgnoreCase(NODE_PATIENT_PATIENT_ID) || name.equalsIgnoreCase(NODE_PATIENT_FAMILY_NAME));
}
/**
* Checks if a schema node is a user defined one. User defines nodes are the ones whose names
* are not the standard openmrs names like form,_header_section,obs_section, etc.
*
* @param node - the node to check.
* @return - true if it is a user defined one, else false.
*/
private static boolean isUserDefinedSchemaElement(Element node) {
if (!(node.getName().equalsIgnoreCase(NODE_SIMPLETYPE) || node.getName().equalsIgnoreCase(NODE_COMPLEXTYPE)))
return false;
String name = node.getAttributeValue(null, ATTRIBUTE_NAME);
return !(name.equalsIgnoreCase("form") || name.equalsIgnoreCase("_header_section")
|| name.equalsIgnoreCase("_other_section") || name.equalsIgnoreCase("_requiredString")
|| name.equalsIgnoreCase("_infopath_boolean") || name.equalsIgnoreCase("encounter_section")
|| name.equalsIgnoreCase("obs_section") || name.equalsIgnoreCase("patient_section"));
}
/**
* Builds an xform for creating a new patient.
*
* @param xformAction - the url to post the xform data to.
* @return - the xml of the new patient xform.
*/
public static String getNewPatientXform() throws Exception {
obsRepeatItems = new Hashtable<String, String>();
nodesets = new Hashtable<String, String>();
repeatSharedKids = new Hashtable<String, List<String>>();
sharedRestrictions = new Hashtable<String, List<String>>();
repeatChildTypes = new Hashtable<String, String>();
Document doc = new Document();
doc.setEncoding(XformConstants.DEFAULT_CHARACTER_ENCODING);
Element xformsNode = doc.createElement(NAMESPACE_XFORMS, null);
xformsNode.setName(NODE_XFORMS);
xformsNode.setPrefix(PREFIX_XFORMS, NAMESPACE_XFORMS);
//if(XformsUtil.isJavaRosaSaveFormat())
xformsNode.setPrefix("jr", "http://openrosa.org/javarosa");
xformsNode.setPrefix(PREFIX_XML_SCHEMA, NAMESPACE_XML_SCHEMA);
xformsNode.setPrefix(PREFIX_XML_SCHEMA2, NAMESPACE_XML_SCHEMA);
xformsNode.setPrefix(PREFIX_XML_INSTANCES, NAMESPACE_XML_INSTANCE);
doc.addChild(org.kxml2.kdom.Element.ELEMENT, xformsNode);
Element modelNode = doc.createElement(NAMESPACE_XFORMS, null);
modelNode.setName(NODE_MODEL);
xformsNode.addChild(Element.ELEMENT, modelNode);
Element instanceNode = doc.createElement(NAMESPACE_XFORMS, null);
instanceNode.setName(NODE_INSTANCE);
modelNode.addChild(Element.ELEMENT, instanceNode);
Element formNode = doc.createElement(null, null);
formNode.setName(NODE_PATIENT);
formNode.setAttribute(null, ATTRIBUTE_NAME, "Patient");
formNode.setAttribute(null, ATTRIBUTE_ID, String.valueOf(XformConstants.PATIENT_XFORM_FORM_ID));
formNode.setAttribute(null, ATTRIBUTE_DESCRIPTION_TEMPLATE,
"${/patient/family_name}$ ${/patient/middle_name}$ ${/patient/given_name}$");
instanceNode.addChild(Element.ELEMENT, formNode);
//This will be the creator for patient record
Element element = formNode.createElement(null, null);
element.setName(XformConstants.NODE_ENTERER);
formNode.addChild(Element.ELEMENT, element);
Element groupNode = doc.createElement(NAMESPACE_XFORMS, null);
groupNode.setName(NODE_GROUP);
Element labelNode = doc.createElement(NAMESPACE_XFORMS, null);
labelNode.setName(NODE_LABEL);
labelNode.addChild(Element.TEXT, "Page1");
groupNode.addChild(Element.ELEMENT, labelNode);
xformsNode.addChild(Element.ELEMENT, groupNode);
addPatientNode(formNode, modelNode, groupNode, NODE_FAMILY_NAME, DATA_TYPE_TEXT, "Family Name",
"The patient family name", true, false, CONTROL_INPUT, null, null, true);
addPatientNode(formNode, modelNode, groupNode, NODE_MIDDLE_NAME, DATA_TYPE_TEXT, "Middle Name",
"The patient middle name", false, false, CONTROL_INPUT, null, null, true);
addPatientNode(formNode, modelNode, groupNode, NODE_GIVEN_NAME, DATA_TYPE_TEXT, "Given Name",
"The patient given name", false, false, CONTROL_INPUT, null, null, true);
addPatientNode(formNode, modelNode, groupNode, NODE_BIRTH_DATE, DATA_TYPE_DATE, "Birth Date",
"The patient birth date", false, false, CONTROL_INPUT, null, null, true);
addPatientNode(formNode, modelNode, groupNode, NODE_BIRTH_DATE_ESTIMATED, DATA_TYPE_BOOLEAN, "Birth Date Estimated",
"Is the patient birth date estimated?", false, false, CONTROL_INPUT, null, null, true);
addPatientNode(formNode, modelNode, groupNode, NODE_IDENTIFIER, DATA_TYPE_TEXT, "Identifier",
"The patient identifier", true, false, CONTROL_INPUT, null, null, true);
addPatientNode(formNode, modelNode, groupNode, NODE_PATIENT_ID, DATA_TYPE_INT, "Patient ID", "The patient ID",
false, true, CONTROL_INPUT, null, null, false);
addPatientNode(formNode, modelNode, groupNode, NODE_GENDER, DATA_TYPE_TEXT, "Gender", "The patient's sex", false,
false, CONTROL_SELECT1, new String[] { "Male", "Female" }, new String[] { "M", "F" }, true);
addPatientNode(formNode, modelNode, groupNode, NODE_DEGREE, DATA_TYPE_TEXT, "Degree", "The patient name degree",
false, false, CONTROL_INPUT, null, null, true);
addPatientNode(formNode, modelNode, groupNode, NODE_FAMILY_NAME2, DATA_TYPE_TEXT, "Family Name 2",
"The patient second family name", false, false, CONTROL_INPUT, null, null, true);
addPatientNode(formNode, modelNode, groupNode, NODE_FAMILY_NAME_PREFIX, DATA_TYPE_TEXT, "Family Name Prefix",
"The patient family name prefix", false, false, CONTROL_INPUT, null, null, true);
addPatientNode(formNode, modelNode, groupNode, NODE_FAMILY_NAME_SUFFIX, DATA_TYPE_TEXT, "Family Name Suffix",
"The patient family name suffix", false, false, CONTROL_INPUT, null, null, true);
addPatientNode(formNode, modelNode, groupNode, NODE_PREFIX, DATA_TYPE_TEXT, "Prefix", "The patient name prefix",
false, false, CONTROL_INPUT, null, null, true);
String[] items, itemValues;
int i = 0;
List<Location> locations = Context.getLocationService().getAllLocations(false);
if (locations != null) {
items = new String[locations.size()];
itemValues = new String[locations.size()];
for (Location loc : locations) {
items[i] = loc.getName();
itemValues[i++] = loc.getLocationId().toString();
}
addPatientNode(formNode, modelNode, groupNode, NODE_LOCATION_ID, DATA_TYPE_INT, "Location",
"The patient's location", true, false, CONTROL_SELECT1, items, itemValues, true);
}
List<PatientIdentifierType> identifierTypes = Context.getPatientService().getAllPatientIdentifierTypes();
if (identifierTypes != null) {
i = 0;
items = new String[identifierTypes.size()];
itemValues = new String[identifierTypes.size()];
for (PatientIdentifierType identifierType : identifierTypes) {
items[i] = identifierType.getName();
itemValues[i++] = identifierType.getPatientIdentifierTypeId().toString();
}
addPatientNode(formNode, modelNode, groupNode, "patient_identifier_type_id", DATA_TYPE_INT, "Identifier Type",
"The patient's identifier type", true, false, CONTROL_SELECT1, items, itemValues, true);
}
addPersonAttributes(formNode, modelNode, groupNode);
addPersonAddresses(formNode, modelNode, groupNode);
addOtherPatientIdentifiers(formNode, modelNode, groupNode);
addEncounterForm(doc, formNode, modelNode, groupNode);
cleanUp();
return XformBuilder.fromDoc2String(doc);
}
private static void addPatientNode(Element formNode, Element modelNode, Element bodyNode, String name, String type,
String label, String hint, boolean required, boolean readonly, String controlType,
String[] items, String[] itemValues, boolean visible) {
addPatientNode(formNode, modelNode, bodyNode, name, type, label, hint, required, readonly, controlType, items,
itemValues, visible, "/" + NODE_PATIENT + "/" + name);
}
/**
* Adds a node to a new patient xform.
*
* @param formNode
* @param modelNode
* @param bodyNode
* @param name
* @param type
* @param label
* @param hint
* @param required
* @param readonly
* @param controlType
* @param items
* @param itemValues
*/
public static void addPatientNode(Element formNode, Element modelNode, Element bodyNode, String name, String type,
String label, String hint, boolean required, boolean readonly, String controlType,
String[] items, String[] itemValues, boolean visible, String nodeset) {
//add the model node
Element element = formNode.createElement(null, null);
element.setName(name);
formNode.addChild(Element.ELEMENT, element);
//if(name.equals("patient_identifier_type_id"))
// element.addChild(Element.TEXT, getDefaultIdentifierType());
//add the model binding
element = modelNode.createElement(NAMESPACE_XFORMS, null);
element.setName(NODE_BIND);
element.setAttribute(null, ATTRIBUTE_ID, name);
element.setAttribute(null, ATTRIBUTE_NODESET, nodeset);
element.setAttribute(null, ATTRIBUTE_TYPE, type);
if (readonly)
element.setAttribute(null, ATTRIBUTE_READONLY, XPATH_VALUE_TRUE);
if (required)
element.setAttribute(null, ATTRIBUTE_REQUIRED, XPATH_VALUE_TRUE);
if (!visible)
element.setAttribute(null, ATTRIBUTE_VISIBLE, XPATH_VALUE_FALSE);
modelNode.addChild(Element.ELEMENT, element);
//add the control
element = bodyNode.createElement(NAMESPACE_XFORMS, null);
element.setName(controlType);
element.setAttribute(null, ATTRIBUTE_BIND, name);
bodyNode.addChild(Element.ELEMENT, element);
//add the label
Element child = element.createElement(NAMESPACE_XFORMS, null);
child.setName(NODE_LABEL);
child.addChild(Element.TEXT, label);
element.addChild(Element.ELEMENT, child);
//add the hint
child = element.createElement(NAMESPACE_XFORMS, null);
child.setName(NODE_HINT);
child.addChild(Element.TEXT, hint);
element.addChild(Element.ELEMENT, child);
//add control items
if (items != null) {
for (int i = 0; i < items.length; i++) {
child = element.createElement(NAMESPACE_XFORMS, null);
child.setName(NODE_ITEM);
element.addChild(Element.ELEMENT, child);
Element elem = element.createElement(NAMESPACE_XFORMS, null);
elem.setName(NODE_LABEL);
elem.addChild(Element.TEXT, items[i]);
child.addChild(Element.ELEMENT, elem);
elem = element.createElement(NAMESPACE_XFORMS, null);
elem.setName(NODE_VALUE);
elem.addChild(Element.TEXT, itemValues[i]);
child.addChild(Element.ELEMENT, elem);
}
}
}
private static String getDefaultIdentifierType() {
return null; //TODO Not high priority for now
}
/**
* Adds person attributes to the patient creator xform.
*
* @param formDataNode the form submit data node.
* @param modelNode the xforms model node.
* @param xformsNode the xforms ui control node.
*/
private static void addPersonAttributes(Element formDataNode, Element modelNode, Element xformsNode) {
List<PersonAttributeType> attributeTypes = Context.getPersonService().getPersonAttributeTypes(PERSON_TYPE.PERSON,
null);
for (PersonAttributeType attribute : attributeTypes)
addPatientAttributeNode(attribute, formDataNode, modelNode, xformsNode);
}
private static void addPersonAddresses(Element formNode, Element modelNode, Element groupNode) {
/*Element dataNode = formNode.createElement(null, null);
dataNode.setName(NODE_NAME_PERSON_ADDRESSES);
formNode.addChild(Element.ELEMENT, dataNode);*/
Element dataNode = formNode;
//addPersonAddress(NODE_NAME_PREFERRED, "Preferred", dataNode, modelNode, groupNode);
addPersonAddress(NODE_NAME_ADDRESS1, "Address 1", dataNode, modelNode, groupNode);
addPersonAddress(NODE_NAME_ADDRESS2, "Address 2", dataNode, modelNode, groupNode);
addPersonAddress(NODE_NAME_CITY_VILLAGE, "City/Village", dataNode, modelNode, groupNode);
addPersonAddress(NODE_NAME_STATE_PROVINCE, "State/Province", dataNode, modelNode, groupNode);
addPersonAddress(NODE_NAME_POSTAL_CODE, "Postal Code", dataNode, modelNode, groupNode);
addPersonAddress(NODE_NAME_COUNTRY, "Country", dataNode, modelNode, groupNode);
addPersonAddress(NODE_NAME_LATITUDE, "Latitude", dataNode, modelNode, groupNode);
addPersonAddress(NODE_NAME_LONGITUDE, "Longitude", dataNode, modelNode, groupNode);
addPersonAddress(NODE_NAME_COUNTY_DISTRICT, "County/District", dataNode, modelNode, groupNode);
addPersonAddress(NODE_NAME_NEIGHBORHOOD_CELL, "Neighborhood Cell", dataNode, modelNode, groupNode);
addPersonAddress(NODE_NAME_REGION, "Region", dataNode, modelNode, groupNode);
addPersonAddress(NODE_NAME_SUBREGION, "Sub Region", dataNode, modelNode, groupNode);
addPersonAddress(NODE_NAME_TOWNSHIP_DIVISION, "Township/Division", dataNode, modelNode, groupNode);
}
private static void addPersonAddress(String name, String text, Element formNode, Element modelNode, Element groupNode) {
name = NODE_NAME_PREFIX_PERSON_ADDRESS + name;
//add the model node
Element dataNode = formNode.createElement(null, null);
dataNode.setName(name);
formNode.addChild(Element.ELEMENT, dataNode);
//add the model binding
Element bindingNode = modelNode.createElement(NAMESPACE_XFORMS, null);
bindingNode.setName(NODE_BIND);
bindingNode.setAttribute(null, ATTRIBUTE_ID, name);
bindingNode.setAttribute(null, ATTRIBUTE_NODESET, "/" + NODE_PATIENT + "/" /*+NODE_NAME_PERSON_ADDRESSES+"/"*/
+ name);
bindingNode.setAttribute(null, ATTRIBUTE_TYPE, DATA_TYPE_TEXT);
modelNode.addChild(Element.ELEMENT, bindingNode);
//add the control
Element element = groupNode.createElement(NAMESPACE_XFORMS, null);
element.setName(CONTROL_INPUT);
element.setAttribute(null, ATTRIBUTE_BIND, name);
groupNode.addChild(Element.ELEMENT, element);
//add the label
Element child = element.createElement(NAMESPACE_XFORMS, null);
child.setName(NODE_LABEL);
child.addChild(Element.TEXT, text);
element.addChild(Element.ELEMENT, child);
//add the hint
/*child = element.createElement(NAMESPACE_XFORMS, null);
child.setName(NODE_HINT);
child.addChild(Element.TEXT, attribute.getDescription());
element.addChild(Element.ELEMENT, child);*/
}
private static void addPatientAttributeNode(PersonAttributeType attribute, Element formNode, Element modelNode,
Element bodyNode) {
String controlName = getPersonAttributeControlType(attribute);
if (controlName == null) {
System.out.println("For attribute=" + attribute.getName() + " No concept found with id="
+ attribute.getForeignKey());
return;
}
String repeatSubName = "";
String name = "person_attribute" + attribute.getPersonAttributeTypeId();
if (controlName.equals(CONTROL_REPEAT)) {
repeatSubName = name;
name = "person_attribute_repeat_section" + attribute.getPersonAttributeTypeId();
}
String type = getPersonAttributeType(attribute);
//add the model node
Element dataNode = formNode.createElement(null, null);
dataNode.setName(name);
formNode.addChild(Element.ELEMENT, dataNode);
if (controlName.equals(CONTROL_REPEAT)) {
Element node = dataNode.createElement(null, null);
node.setName(repeatSubName);
dataNode.addChild(Element.ELEMENT, node);
dataNode = node;
}
//add the model binding
Element bindingNode = modelNode.createElement(NAMESPACE_XFORMS, null);
bindingNode.setName(NODE_BIND);
bindingNode.setAttribute(null, ATTRIBUTE_ID, name);
if (repeatSubName.length() > 0)
repeatSubName = "/" + repeatSubName;
bindingNode.setAttribute(null, ATTRIBUTE_NODESET, "/" + NODE_PATIENT + "/" + name + repeatSubName);
if (!controlName.equals(CONTROL_REPEAT))
bindingNode.setAttribute(null, ATTRIBUTE_TYPE, type);
modelNode.addChild(Element.ELEMENT, bindingNode);
//add the control
Element element = bodyNode.createElement(NAMESPACE_XFORMS, null);
Element newBodyNode = bodyNode;
if (controlName.equals(CONTROL_REPEAT)) {
Element groupNode = bodyNode.createElement(NAMESPACE_XFORMS, null);
groupNode.setName(NODE_GROUP);
Element labelNode = element.createElement(NAMESPACE_XFORMS, null);
labelNode.setName(NODE_LABEL);
labelNode.addChild(Element.TEXT, name);
groupNode.addChild(Element.ELEMENT, labelNode);
bodyNode.addChild(Element.ELEMENT, groupNode);
Element repeatNode = groupNode.createElement(NAMESPACE_XFORMS, null);
repeatNode.setName(CONTROL_REPEAT);
repeatNode.setAttribute(null, ATTRIBUTE_BIND, name);
groupNode.addChild(Element.ELEMENT, repeatNode);
element = repeatNode;
newBodyNode = repeatNode;
} else {
element.setName(controlName);
element.setAttribute(null, ATTRIBUTE_BIND, name);
bodyNode.addChild(Element.ELEMENT, element);
dataNode = formNode;
}
//add the label
Element child = element.createElement(NAMESPACE_XFORMS, null);
child.setName(NODE_LABEL);
child.addChild(Element.TEXT, attribute.getName());
element.addChild(Element.ELEMENT, child);
//add the hint
child = element.createElement(NAMESPACE_XFORMS, null);
child.setName(NODE_HINT);
child.addChild(Element.TEXT, attribute.getDescription());
element.addChild(Element.ELEMENT, child);
if (attribute.getFormat() != null) {
if ("org.openmrs.Location".equals(attribute.getFormat().trim()))
populateLocations(element);
else if ("org.openmrs.Concept".equals(attribute.getFormat().trim()))
pupulateConceptOptions(element, attribute.getForeignKey(), dataNode, modelNode, newBodyNode);
}
}
private static void pupulateConceptOptions(Element controlNode, Integer conceptId, Element formNode, Element modelNode,
Element bodyNode) {
if (conceptId == null)
return;
Concept concept = Context.getConceptService().getConcept(conceptId);
if (concept == null)
return;
pupulateConceptOptions(controlNode, concept, formNode, modelNode, bodyNode);
}
private static void pupulateConceptOptions(Element controlNode, Concept concept, Element formNode, Element modelNode,
Element bodyNode) {
if (concept == null)
return;
if (concept.isSet())
populateConceptSet(controlNode, concept, formNode, modelNode, bodyNode);
else {
Collection<ConceptAnswer> conceptAnswers = concept.getAnswers();
for (ConceptAnswer conceptAnswer : conceptAnswers) {
Concept answerConcept = conceptAnswer.getAnswerConcept();
if (answerConcept == null)
continue;
Element itemNode = controlNode.createElement(NAMESPACE_XFORMS, null);
itemNode.setName(NODE_ITEM);
itemNode.setAttribute(null, ATTRIBUTE_CONCEPT_ID, answerConcept.getConceptId().toString());
Element node = itemNode.createElement(NAMESPACE_XFORMS, null);
node.setName(NODE_LABEL);
node.addChild(Element.TEXT, answerConcept.getName().getName());
itemNode.addChild(Element.ELEMENT, node);
node = itemNode.createElement(NAMESPACE_XFORMS, null);
node.setName(NODE_VALUE);
node.addChild(Element.TEXT, answerConcept.getConceptId().toString());
itemNode.addChild(Element.ELEMENT, node);
controlNode.addChild(Element.ELEMENT, itemNode);
}
}
}
private static void populateConceptSet(Element controlNode, Concept concept, Element formNode, Element modelNode,
Element bodyNode) {
List<Concept> conceptSet = Context.getConceptService().getConceptsInSet(concept);
for (Concept c : conceptSet) {
String name = "person_attribute_concept" + c.getConceptId();
String type = getConceptDataType(c);
//add the model node
Element element = formNode.createElement(null, null);
element.setName(name);
formNode.addChild(Element.ELEMENT, element);
//add the model binding
/*element = modelNode.createElement(NAMESPACE_XFORMS, null);
element.setName(NODE_BIND);
element.setAttribute(null, ATTRIBUTE_ID, name);
element.setAttribute(null, ATTRIBUTE_NODESET, "/"+NODE_PATIENT+"/"+name);
element.setAttribute(null, ATTRIBUTE_TYPE, type);
modelNode.addChild(Element.ELEMENT, element);*/
//add the control
element = bodyNode.createElement(NAMESPACE_XFORMS, null);
element.setName(getConceptControlType(c));
element.setAttribute(null, ATTRIBUTE_BIND, name);
element.setAttribute(null, ATTRIBUTE_TYPE, type);
bodyNode.addChild(Element.ELEMENT, element);
//add the label
Element child = element.createElement(NAMESPACE_XFORMS, null);
child.setName(NODE_LABEL);
child.addChild(Element.TEXT, c.getName().getName());
element.addChild(Element.ELEMENT, child);
//add the hint
child = element.createElement(NAMESPACE_XFORMS, null);
child.setName(NODE_HINT);
child.addChild(Element.TEXT, c.getName().getDescription());
element.addChild(Element.ELEMENT, child);
pupulateConceptOptions(element, c, formNode, modelNode, bodyNode);
}
}
private static String getPersonAttributeType(PersonAttributeType attribute) {
String type = attribute.getFormat();
if (type.equals("java.lang.Integer"))
return DATA_TYPE_INT;
else if (type.equals("java.lang.Double") || type.equals("java.lang.Float"))
return DATA_TYPE_DECIMAL;
else if (type.equals("java.lang.Boolean"))
return DATA_TYPE_BOOLEAN;
else if (type.equals("java.util.Date") || type.equals("java.sql.Date"))
return DATA_TYPE_DATE;
return DATA_TYPE_TEXT;
}
private static String getConceptDataType(Concept concept) {
ConceptDatatype datatype = concept.getDatatype();
if (datatype.isNumeric())
return DATA_TYPE_DECIMAL;
else if (datatype.isBoolean())
return DATA_TYPE_BOOLEAN;
else if (datatype.isDate())
return DATA_TYPE_DATE;
else if (datatype.getHl7Abbreviation().equals("ED"))
return DATA_TYPE_BASE64BINARY;
return DATA_TYPE_TEXT;
}
private static String getPersonAttributeControlType(PersonAttributeType attribute) {
String type = attribute.getFormat();
if (type == null)
type = "";
else
type = type.trim();
if (type.equals("org.openmrs.Location"))
return CONTROL_SELECT1;
else if ((type.equals("org.openmrs.Concept") && attribute.getForeignKey() != null)) {
Concept concept = Context.getConceptService().getConcept(attribute.getForeignKey());
if (concept == null)
return null;
if (concept.isSet())
return CONTROL_REPEAT;
return CONTROL_SELECT1;
}
return CONTROL_INPUT;
}
private static String getConceptControlType(Concept concept) {
if (concept.isSet())
return CONTROL_REPEAT;
else if (concept.getAnswers() != null && concept.getAnswers().size() > 0)
return CONTROL_SELECT1;
return CONTROL_INPUT;
}
public static Document setPatientFieldValues(Patient patient, Form form, Document doc, XformsService xformsService)
throws Exception {
//EasyFactoryConfiguration config = new EasyFactoryConfiguration();
VelocityEngine velocityEngine = new VelocityEngine();
velocityEngine.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS,
"org.apache.velocity.runtime.log.CommonsLogLogChute");
velocityEngine.setProperty(CommonsLogLogChute.LOGCHUTE_COMMONS_LOG_NAME, "xforms_velocity");
velocityEngine.init();
VelocityContext velocityContext = new VelocityContext();
velocityContext.put("calendar", Calendar.getInstance());
velocityContext.put("patient", patient);
velocityContext.put("form", form);
velocityContext.put("obs", new ObsHistory(patient));
velocityContext.put("concept", new ConceptUtil());
velocityContext.put("location", new LocationUtil());
velocityContext.put(
"timestamp",
new SimpleDateFormat(Context.getAdministrationService().getGlobalProperty(
XformConstants.GLOBAL_PROP_KEY_DATE_TIME_SUBMIT_FORMAT, XformConstants.DEFAULT_DATE_TIME_SUBMIT_FORMAT)));
velocityContext.put(
"date",
new SimpleDateFormat(Context.getAdministrationService().getGlobalProperty(
XformConstants.GLOBAL_PROP_KEY_DATE_SUBMIT_FORMAT, XformConstants.DEFAULT_DATE_SUBMIT_FORMAT)));
velocityContext.put(
"time",
new SimpleDateFormat(Context.getAdministrationService().getGlobalProperty(
XformConstants.GLOBAL_PROP_KEY_TIME_SUBMIT_FORMAT, XformConstants.DEFAULT_TIME_SUBMIT_FORMAT)));
velocityContext.put(
"displayTimestamp",
new SimpleDateFormat(Context.getAdministrationService().getGlobalProperty(
XformConstants.GLOBAL_PROP_KEY_DATE_TIME_DISPLAY_FORMAT, XformConstants.DEFAULT_DATE_TIME_DISPLAY_FORMAT)));
velocityContext.put(
"displayDate",
new SimpleDateFormat(Context.getAdministrationService().getGlobalProperty(
XformConstants.GLOBAL_PROP_KEY_DATE_DISPLAY_FORMAT, XformConstants.DEFAULT_DATE_DISPLAY_FORMAT)));
velocityContext.put(
"displayTime",
new SimpleDateFormat(Context.getAdministrationService().getGlobalProperty(
XformConstants.GLOBAL_PROP_KEY_TIME_DISPLAY_FORMAT, XformConstants.DEFAULT_TIME_DISPLAY_FORMAT)));
List<Encounter> encounters = Context.getEncounterService().getEncountersByPatientId(patient.getPatientId(), false);
velocityContext.put("patientEncounters", encounters);
List<Relationship> relationships = Context.getPersonService().getRelationshipsByPerson(patient);
// change Person objects to Patient objects if applicable
for (Relationship rel : relationships) {
Person otherPerson = null;
if (rel.getPersonA().equals(patient)) {
otherPerson = rel.getPersonB();
if (otherPerson.isPatient())
rel.setPersonB(Context.getPatientService().getPatient(otherPerson.getPersonId()));
}
else {
otherPerson = rel.getPersonA();
if (otherPerson.isPatient())
rel.setPersonA(Context.getPatientService().getPatient(otherPerson.getPersonId()));
}
}
// we need at least one empty relationship in InfoPath
if (relationships.isEmpty()) {
relationships = new ArrayList<Relationship>();
relationships.add(new Relationship());
}
velocityContext.put("relationships", relationships);
//TODO Uncomment this after relation ship widget ticket is done.
//and fully test to see if forms still submit all the way from OpenMRS 1.6
/*EventCartridge eventCartridge = new EventCartridge();
eventCartridge.addEventHandler(new VelocityExceptionHandler());
velocityContext.attachEventCartridge(eventCartridge);
String xml = fromDoc2String(doc);
try {
StringWriter w = new StringWriter();
velocityEngine.evaluate(velocityContext, w, XformBuilder.class.getName(), xml);
doc = getDocument(w.toString());
}
catch (Exception e) {
log.error("Error evaluating default values for form " + form.getName() + "["
+ form.getFormId() + "]", e);
}*/
setPatientTableFieldValues(form.getFormId(), doc.getRootElement(), xformsService, velocityEngine, velocityContext);
return doc;
}
public static Element createCopy(Element element, List<String> nonCopyAttributes) {
Element copy = element.getParent().createElement(null, null);
copy.setName(element.getName());
element.getParent().addChild(Element.ELEMENT, copy);
copyAttributes(element, copy, nonCopyAttributes);
copyChildren(element, copy, nonCopyAttributes);
return copy;
}
private static void copyChildren(Element srcNode, Element dstNode, List<String> nonCopyAttributes) {
for (int index = 0; index < srcNode.getChildCount(); index++) {
if (srcNode.getType(index) != Element.ELEMENT)
continue;
Element child = (Element) srcNode.getChild(index);
Element newChild = dstNode.createElement(null, null);
newChild.setName(child.getName());
dstNode.addChild(Element.ELEMENT, newChild);
copyAttributes(child, newChild, nonCopyAttributes);
copyChildren(child, newChild, nonCopyAttributes);
}
}
private static void copyAttributes(Element srcNode, Element dstNode, List<String> nonCopyAttributes) {
for (int index = 0; index < srcNode.getAttributeCount(); index++) {
String name = srcNode.getAttributeName(index);
if (!nonCopyAttributes.contains(name))
dstNode.setAttribute(null, name, srcNode.getAttributeValue(null, name));
}
}
/**
* Adds and encounter section to the patient registration form for entering obs during new
* patient registration.
*
* @param doc
* @param formNode
* @param modelNode
* @param groupNode
*/
private static void addEncounterForm(Document doc, Element formNode, Element modelNode, Element groupNode) {
String formId = Context.getAdministrationService().getGlobalProperty("xforms.patientRegEncounterFormId", "0");
if ("0".equals(formId))
return;
FormService formService = (FormService) Context.getService(FormService.class);
Form form = formService.getForm(Integer.parseInt(formId));
if (form == null)
return;
String templateXml = FormEntryWrapper.getFormTemplate(form);
Document templateDoc = XformBuilder.getDocument(templateXml);
Element rootNode = templateDoc.getRootElement();
for (int index = 0; index < rootNode.getChildCount(); index++) {
if (rootNode.getType(index) != Element.ELEMENT)
continue;
Element child = (Element) rootNode.getChild(index);
if (child.getName().equalsIgnoreCase("patient")) {
removeNonUsedPatientChildNodes(child);
break;
}
}
formNode.addChild(org.kxml2.kdom.Element.ELEMENT, rootNode);
Document xformSchemaDoc = new Document();
xformSchemaDoc.setEncoding(XformConstants.DEFAULT_CHARACTER_ENCODING);
Element xformSchemaNode = doc.createElement(NAMESPACE_XML_SCHEMA, null);
xformSchemaNode.setName(NODE_SCHEMA);
xformSchemaDoc.addChild(org.kxml2.kdom.Element.ELEMENT, xformSchemaNode);
Hashtable bindings = new Hashtable();
Hashtable<String, String> problemList = new Hashtable<String, String>();
Hashtable<String, String> problemListItems = new Hashtable<String, String>();
parseTemplate(modelNode, formNode, formNode, bindings, groupNode, problemList, problemListItems, 0);
Document schemaDoc = XformBuilder.getDocument(XformsUtil.getSchema(form));
parseSchema(schemaDoc.getRootElement(), groupNode, modelNode, xformSchemaNode, bindings, problemList,
problemListItems);
removeNonUsedUINodes(groupNode);
}
/**
* Removes data nodes that are kids of the patient node for obs entry on a patient registration
* form.
*
* @param patientNode the patient node.
*/
private static void removeNonUsedPatientChildNodes(Element patientNode) {
for (int index = 0; index < patientNode.getChildCount(); index++) {
if (patientNode.getType(index) != Element.ELEMENT)
continue;
Element child = (Element) patientNode.getChild(index);
if (!child.getName().equalsIgnoreCase("patient.patient_id")) {
patientNode.removeChild(index);
index -= 1;
}
}
}
/**
* Removes ui nodes that are not necessary for obs entry on a patient registration form.
*
* @param groupNode the group node having the ui nodes.
*/
private static void removeNonUsedUINodes(Element groupNode) {
for (int index = 0; index < groupNode.getChildCount(); index++) {
if (groupNode.getType(index) != Element.ELEMENT)
continue;
Element child = (Element) groupNode.getChild(index);
String value = child.getAttributeValue(null, "bind");
if ("encounter.location_id".equalsIgnoreCase(value) || "patient.patient_id".equalsIgnoreCase(value)) {
groupNode.removeChild(index);
index -= 1;
}
}
}
/**
* Gets the name of the location as displayed in xforms.
*
* @param location the location object.
* @return the location display text.
*/
public static String getLocationName(Location location) {
return location.getName() + " [" + location.getLocationId() + "]";
}
/**
* Gets the name of the provider as it will be displayed in xforms.
*
* @param provider the provider.
* @param personId the id of the person this provider represents.
* @return the display formatted provider name.
*/
public static String getProviderName(User provider, Integer personId) {
PersonName personName = provider.getPersonName(); //This may be null for some users that have not last and first names.
return (personName != null ? personName.toString() : provider.getUsername()) + " [" + personId + "]";
}
private static void addOtherPatientIdentifiers(Element formNode, Element modelNode, Element groupNode) {
String name = NODE_NAME_OTHER_IDENTIFIERS;
//add the model node
Element dataNode = formNode.createElement(null, null);
dataNode.setName(name);
formNode.addChild(Element.ELEMENT, dataNode);
//add the model binding
Element bindingNode = modelNode.createElement(NAMESPACE_XFORMS, null);
bindingNode.setName(NODE_BIND);
bindingNode.setAttribute(null, ATTRIBUTE_ID, name);
bindingNode.setAttribute(null, ATTRIBUTE_NODESET, "/" + NODE_PATIENT + "/" /*+NODE_NAME_PERSON_ADDRESSES+"/"*/
+ name);
bindingNode.setAttribute(null, ATTRIBUTE_TYPE, DATA_TYPE_TEXT);
modelNode.addChild(Element.ELEMENT, bindingNode);
//Create repeat group node
Element repeatGroupNode = groupNode.createElement(NAMESPACE_XFORMS, null);
repeatGroupNode.setName(NODE_GROUP);
groupNode.addChild(Element.ELEMENT, repeatGroupNode);
//add the repeat group label
Element labelNode = repeatGroupNode.createElement(NAMESPACE_XFORMS, null);
labelNode.setName(NODE_LABEL);
labelNode.addChild(Element.TEXT, "Other dentifiers");
repeatGroupNode.addChild(Element.ELEMENT, labelNode);
//add repeat node.
Element repeatNode = repeatGroupNode.createElement(NAMESPACE_XFORMS, null);
repeatNode.setName(CONTROL_REPEAT);
repeatNode.setAttribute(null, ATTRIBUTE_BIND, name);
repeatGroupNode.addChild(Element.ELEMENT, repeatNode);
addPatientNode(dataNode, modelNode, repeatNode, NODE_NAME_OTHER_IDENTIFIER, DATA_TYPE_TEXT, "Identifier",
"The patient's other identifier value", false, false, CONTROL_INPUT, null, null, true, "/" + NODE_PATIENT + "/"
+ name + "/other_identifier");
String[] items, itemValues;
List<PatientIdentifierType> identifierTypes = Context.getPatientService().getAllPatientIdentifierTypes();
if (identifierTypes != null) {
int i = 0;
items = new String[identifierTypes.size()];
itemValues = new String[identifierTypes.size()];
for (PatientIdentifierType identifierType : identifierTypes) {
items[i] = identifierType.getName();
itemValues[i++] = identifierType.getPatientIdentifierTypeId().toString();
}
addPatientNode(dataNode, modelNode, repeatNode, NODE_NAME_OTHER_IDENTIFIER_TYPE_ID, DATA_TYPE_INT,
"Identifier Type", "The patient's other identifier type", false, false, CONTROL_SELECT1, items, itemValues,
true, "/" + NODE_PATIENT + "/" + name + "/other_identifier_type_id");
}
List<Location> locations = Context.getLocationService().getAllLocations();
if (locations != null) {
int i = 0;
items = new String[locations.size()];
itemValues = new String[locations.size()];
for (Location location : locations) {
items[i] = getLocationName(location);
itemValues[i++] = location.getLocationId().toString();
}
addPatientNode(dataNode, modelNode, repeatNode, NODE_NAME_OTHER_IDENTIFIER_LOCATION_ID, DATA_TYPE_INT,
"Identifier Location", "The patient's other identifier location", false, false, CONTROL_SELECT1, items,
itemValues, true, "/" + NODE_PATIENT + "/" + name + "/other_identifier_location_id");
}
}
/**
* @see org.openmrs.api.GlobalPropertyListener#globalPropertyChanged(org.openmrs.GlobalProperty)
*/
@Override
public void globalPropertyChanged(GlobalProperty gp) {
if (XformConstants.XFORM_GP_USE_AUTOCOMPLETE_FOR_LOCATIONS.equals(gp.getProperty()))
useAutoCompleteForLocations = null;
else
useAutoCompleteForProviders = null;
}
/**
* @see org.openmrs.api.GlobalPropertyListener#globalPropertyDeleted(java.lang.String)
*/
@Override
public void globalPropertyDeleted(String gpName) {
if (XformConstants.XFORM_GP_USE_AUTOCOMPLETE_FOR_LOCATIONS.equals(gpName))
useAutoCompleteForLocations = null;
else
useAutoCompleteForProviders = null;
}
/**
* @see org.openmrs.api.GlobalPropertyListener#supportsPropertyName(java.lang.String)
*/
@Override
public boolean supportsPropertyName(String gpName) {
return XformConstants.XFORM_GP_USE_AUTOCOMPLETE_FOR_LOCATIONS.equals(gpName)
|| XformConstants.XFORM_GP_USE_AUTOCOMPLETE_FOR_PROVIDERS.equals(gpName);
}
/**
* Determines whether to use an autocomplete field or a select for an xforms UI node
*
* @param nodeName the name of the xforms node
* @return true if it should be an autocomplete otherwise false
*/
private static boolean useAutoCompleteForNode(String nodeName) {
if (nodeName.equalsIgnoreCase(NODE_ENCOUNTER_LOCATION_ID) && XformsUtil.usesJquery()) {
if (useAutoCompleteForLocations == null) {
useAutoCompleteForLocations = Boolean.valueOf(Context.getAdministrationService().getGlobalProperty(
XformConstants.XFORM_GP_USE_AUTOCOMPLETE_FOR_LOCATIONS));
}
return useAutoCompleteForLocations;
} else if (nodeName.equalsIgnoreCase(NODE_ENCOUNTER_PROVIDER_ID) && XformsUtil.isOnePointNineAndAbove()) {
if (useAutoCompleteForProviders == null) {
useAutoCompleteForProviders = Boolean.valueOf(Context.getAdministrationService().getGlobalProperty(
XformConstants.XFORM_GP_USE_AUTOCOMPLETE_FOR_PROVIDERS));
}
return useAutoCompleteForProviders;
}
//This is not a location or provider element
return false;
}
}