package com.openMap1.mapper.converters;
import java.util.StringTokenizer;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import com.openMap1.mapper.MappedStructure;
import com.openMap1.mapper.core.MapperException;
import com.openMap1.mapper.structures.MapperWrapper;
import com.openMap1.mapper.util.XMLUtil;
import com.openMap1.mapper.util.XSLOutputFile;
import com.openMap1.mapper.writer.TemplateFilter;
/**
* Applies the following in-wrapper transform to OpenHR records, and the inverse
* out-transform:
*
* (1) under <adminDomain>, converts the <person> node for the patient to
* <person_Patient> (there may be other <person> nodes, e.g. for the doctor;
* this one is identified by having the same child <id> as the sibling <patient>
* node)
*
* (2) converts <event> nodes to more specific nodes like <event_weight>
* based on the value at a path from the <event>, e.g if code/@code has value
* '22A..00', convert to <event_weight>
*
* @author Robert
*
*/
public class OpenHRWrapper extends AbstractMapperWrapper implements
MapperWrapper {
private String patientInternalId;
// tag name conversions for <event> elements, depending on their content
private String[][] eventConversions = {
{ "code/@code", "22A..00", "event_weight" },
{ "code/@code", "229..00", "event_height" },
{ "code/@code", "unknown", "event_finding" },
{ "eventType", "MED", "event_medication" } };
private String openHRNamespace="http://www.e-mis.com/emisopen";
// ----------------------------------------------------------------------------------------
// Constructor and initialisation from the Ecore model
// ----------------------------------------------------------------------------------------
public OpenHRWrapper(MappedStructure ms, Object spare)
throws MapperException {
super(ms, spare);
}
/**
* @return the file extension of the outer document, with initial '*.'
*/
public String fileExtension() {
return ("*.xml");
}
/**
* @return the type of document transformed to and from; see static
* constants in class AbstractMapperWrapper.
*/
public int transformType() {
return AbstractMapperWrapper.XML_TYPE;
}
// ----------------------------------------------------------------------------------------
// In-wrapper transform
// ----------------------------------------------------------------------------------------
@Override
public Document transformIn(Object incoming) throws MapperException {
if (!(incoming instanceof Element))
throw new MapperException("Document root is not an Element");
Element mappingRoot = (Element) incoming;
// find the internal id used for the patient
patientInternalId = getPathValue(mappingRoot, "adminDomain/patient/id");
String mappingRootPath = "/openHealthRecord";
inResultDoc = XMLUtil.makeOutDoc();
Element inRoot = scanDocument(mappingRoot, mappingRootPath,
AbstractMapperWrapper.IN_TRANSFORM);
inResultDoc.appendChild(inRoot);
return inResultDoc;
}
/**
* default behaviour is a shallow copy - copying the element name,
* attributes, and text content only if the element has no child elements.
* to be overridden for specific paths in implementing classes
*/
protected Element inTransformNode(Element el, String path)
throws MapperException {
// copy the element with namespaces, prefixed tag name, attributes but
// no text or child Elements
Element copy = (Element) inResultDoc.importNode(el, false);
// make a single <person_Patient> element
if (path.equals("/openHealthRecord/adminDomain/person")) {
// System.out.println("Person has id '" + getPathValue(el,"id") +
// "'");
if (getPathValue(el, "id").equals(patientInternalId))
copy = renameElement(el, "person_Patient", true);
}
// convert <event> elements to specific types of event
if (path.equals("/openHealthRecord/healthDomain/event")) {
String codeValue = getPathValue(el, "code/@code"); // usual case
for (int i = 0; i < eventConversions.length; i++) {
String[] conversion = eventConversions[i];
if (!conversion[0].equals("code/@code"))
codeValue = getPathValue(el, conversion[0]);
if (codeValue.equals(conversion[1]))
copy = renameElement(el, conversion[2], true);
}
}
// if the source element has no child elements but has text, copy the
// text
String text = textOnly(el);
if (!text.equals(""))
copy.appendChild(inResultDoc.createTextNode(text));
return copy;
}
// ----------------------------------------------------------------------------------------
// Out-wrapper transform
// ----------------------------------------------------------------------------------------
@Override
public Object transformOut(Element outgoing) throws MapperException {
String mappingRootPath = "/openHealthRecord";
outResultDoc = XMLUtil.makeOutDoc();
Element outRoot = scanDocument(outgoing, mappingRootPath,
AbstractMapperWrapper.OUT_TRANSFORM);
outResultDoc.appendChild(outRoot);
return outResultDoc;
}
/**
* default behaviour is a shallow copy - copying the element name,
* attributes, and text content only if the element has no child elements.
* to be overridden for specific paths in implementing classes
*/
protected Element outTransformNode(Element el, String path)
throws MapperException {
// copy the element with namespaces, prefixed tag name, attributes but
// no text or child Elements
Element copy = (Element) outResultDoc.importNode(el, false);
// convert a <person_Patient> element back to <person>
if (path.equals("/openHealthRecord/adminDomain/person_Patient")) {
copy = renameElement(el, "person", false);
}
// convert specific types of <event_XX> back to plain <event>
if ((steps(path) == 3)
&& (path.startsWith("/openHealthRecord/healthDomain/event_"))) {
copy = renameElement(el, "event", false);
}
// if the source element has no child elements but has text, copy the
// text
String text = textOnly(el);
if (!text.equals(""))
copy.appendChild(outResultDoc.createTextNode(text));
return copy;
}
/**
* copy an element and all its attributes to the new document, renaming it
* and putting it in the default namespace with no prefix. Do not copy text
* content or child elements
*
* @param el
* @param newName
* @param isIn
* true for the in-transform, false for the out-transform
* @return
* @throws MapperException
*/
protected Element renameElement(Element el, String newName, boolean isIn)
throws MapperException {
String uri = ms().getNamespaceSet().getNamespaceURI("");
Element newEl = null;
if (isIn)
newEl = inResultDoc.createElementNS(uri, newName);
else if (!isIn)
newEl = outResultDoc.createElementNS(uri, newName);
// set all attributes of the constrained element, including namespace
// attributes
for (int a = 0; a < el.getAttributes().getLength(); a++) {
Attr at = (Attr) el.getAttributes().item(a);
newEl.setAttribute(at.getName(), at.getValue());
}
return newEl;
}
// ----------------------------------------------------------------------------------------
// XSLT In-wrapper transform
// ----------------------------------------------------------------------------------------
/**
* @param xout XSLT output being made
* @param templateFilter a filter on the templates, implemented by XSLGeneratorImpl
* append the templates and variables to be included in the XSL
* to do the full transformation, to apply the wrapper transform in the 'in' direction.
* Templates must have mode = "inWrapper"
*/
public void addWrapperInTemplates(XSLOutputFile xout, TemplateFilter templateFilter) throws MapperException
{
// add an identity in-wrapper template for most nodes
super.addWrapperInTemplates(xout, templateFilter);
// change name of the 'person' tag
String oldName = "oms:person[oms:id=parent::node()/oms:patient/oms:id]";
String newName = "oms:person_Patient";
xout.topOut().appendChild(nameChangeTemplate(xout,"inWrapper",oldName,newName));
// change the name of selected event tags
for (int i = 0; i < eventConversions.length;i++)
{
String[] eventConversion = eventConversions[i];
String path = addOMSPrefix(eventConversion[0]);
oldName = "oms:event[" + path + "='" + eventConversion[1] + "']";
newName = "oms:" + eventConversion[2];
xout.topOut().appendChild(nameChangeTemplate(xout,"inWrapper",oldName,newName));
}
}
/**
* add the prefix 'oms:' to element tag names on an XPath
* @param path
* @return
*/
private String addOMSPrefix(String path)
{
StringTokenizer st = new StringTokenizer(path,"/");
String newPath = "";
while (st.hasMoreTokens())
{
String step = st.nextToken();
// no prefix for attribute names
if (step.startsWith("@")) newPath = newPath + step;
// prefix for element names
else newPath = newPath + "oms:" + step;
if (st.hasMoreTokens()) newPath = newPath + "/";
}
return newPath;
}
// ----------------------------------------------------------------------------------------
// XSLT Out-wrapper transform
// ----------------------------------------------------------------------------------------
/**
* @param xout XSLT output being made
* @param templateFilter a filter on the templates to be included, implemented by XSLGeneratorImpl
* append the templates and variables to be included in the XSL
* to do the full transformation, to apply the wrapper transform in the 'out' direction.
* Templates must have mode = "outWrapper"
* @throws MapperException
*/
public void addWrapperOutTemplates(XSLOutputFile xout, TemplateFilter templateFilter) throws MapperException
{
// declare a prefixed namespace for the output OpenHR URI (the same URI is also the default namespace)
xout.topOut().setAttribute("xmlns:ohr", openHRNamespace);
// add an identity out-wrapper template for most nodes
super.addWrapperOutTemplates(xout, templateFilter);
// change back the name of the 'person_Patient' tag
String oldName = "ohr:person_Patient";
String newName = "person";
xout.topOut().appendChild(nameChangeTemplate(xout,"outWrapper",oldName,newName));
// change back the names of selected event tags
for (int i = 0; i < eventConversions.length;i++)
{
String[] eventConversion = eventConversions[i];
oldName = "ohr:" + eventConversion[2];
newName = "event";
xout.topOut().appendChild(nameChangeTemplate(xout,"outWrapper",oldName,newName));
}
}
}