package com.openMap1.mapper.converters; import java.util.Hashtable; import java.util.Iterator; import java.util.StringTokenizer; import org.w3c.dom.Element; import com.openMap1.mapper.ElementDef; 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; public class NHS_V3_Converter extends NHS_CDA_Wrapper implements MapperWrapper{ //---------------------------------------------------------------------------------------- // Constructor //---------------------------------------------------------------------------------------- /** * * @param ms set of mappings which uses this wrapper transform * @param spare spare argument, just in case.... */ public NHS_V3_Converter(MappedStructure ms, Object spare) throws MapperException { super(ms,spare); } //---------------------------------------------------------------------------------------- // In-Wrapper Transform //---------------------------------------------------------------------------------------- /** * recursive descent, making the constrained CDA Element and its subtree from the * corresponding element in the CDA document * @param newTagName tag name to be given to the element in the result of the in-transformation * @param cdaElement Element in the CDA xml structure * @return Constrained element corresponding to the CDA element */ protected Element constrainedElement(String newTagName,Element cdaElement,ElementDef elDef) throws MapperException { // move in the element with its new tag name and attributes Element constrainedEl = moveInElementOnly(newTagName,cdaElement); // add child Elements and recurse for (Iterator<Element> ie = XMLUtil.childElements(cdaElement).iterator();ie.hasNext();) { Element cdaChild = ie.next(); String cdaName = XMLUtil.getLocalName(cdaChild); // for elements in the nhs namespace, add the prefix used in the full mapping set, to match that name String uri = cdaChild.getNamespaceURI(); if ((uri != null) && (uri.equals(NHSURI))) cdaName = NHSPREFIX + ":" + cdaName; ElementDef childDef = bestMatchingChildNode(elDef,cdaName); if (childDef != null) { // this tag name has the prefix 'npfitlc:' for element in the nhs namespace String nextTagName = childDef.getName(); Element constrainedChild = null; // for <text> elements, save the child subtree in a table if (nextTagName.equals("text")) constrainedChild = saveInputTextSubtree(cdaChild); // for all other elements, including those in the nhs namespace, recurse. else constrainedChild = constrainedElement(nextTagName,cdaChild,childDef); constrainedEl.appendChild(constrainedChild); } } return constrainedEl; } /** * find the child elementDef which best matches the node of the instance: * - exact match if there is one * - otherwise the node whose name starts with the tag name followed by '_', if there is one * - otherwise null * @param parent * @param tagName * @return */ protected ElementDef bestMatchingChildNode(ElementDef parent, String tagName) throws MapperException { ElementDef match = parent.getNamedChildElement(tagName); if (match == null) { int starters = 0; for (Iterator<ElementDef> it = parent.getChildElements().iterator();it.hasNext();) { ElementDef child = it.next(); if (child.getName().startsWith(tagName + "_")) { starters++; match = child; } } if (starters > 1) throw new MapperException("Mapping set child nodes of '" + parent.getName() + "'give " + starters + " possible matches with instance tag name '" + tagName + "'"); } return match; } //---------------------------------------------------------------------------------------- // Out-Wrapper Transform //---------------------------------------------------------------------------------------- /** * recursive descent, making the V3 Element and its subtree from the * corresponding element in the translation result * @param tagName tag name to be given to the element in the result of the out-transformation * @param resultElement Element in the translation output * @return V3 element corresponding to the translation result element */ protected Element outWrappedV3Element(String tagName,Element resultElement, ElementDef topElementDef) throws MapperException { String tag = outWrappedTagName(tagName); String uri = resultElement.getNamespaceURI(); // create the element in the out-wrapped document Element v3El = outResultDoc.createElementNS(uri, tag); // copy across all attributes to the templated CDA element, including namespace attributes XMLUtil.copyAttributes(resultElement,v3El); // add the NHS namespace declaration to the top element if (tagName.equals(topClassName)) v3El.setAttribute(("xmlns:" + NHSPREFIX),NHSURI); // if there are no child elements, copy any text content if (XMLUtil.childElements(resultElement).size() == 0) XMLUtil.copyText(resultElement, v3El, outResultDoc); // add child Elements and recurse for (Iterator<Element> ie = XMLUtil.childElements(resultElement).iterator();ie.hasNext();) { Element constChild = ie.next(); String nextTagName = XMLUtil.getLocalName(constChild); Element v3Child = null; // for text elements, recover the subtree from a hashtable stored by the input wrapper if (nextTagName.equals("text")) v3Child = recoverInputTextSubtree(constChild, "unknown V3 path"); // otherwise recurse (not using the ElementDef) else v3Child = outWrappedV3Element(nextTagName,constChild,topElementDef); v3El.appendChild(v3Child); } return v3El; } /** * some rather ad hoc changes to get the our-wrapped tag name from the in-wrapped tag name * @param tagName * @return */ private String outWrappedTagName(String tagName) { String result = tagName; // to detect tag names with a template id before a full stop (roles and acts) StringTokenizer points = new StringTokenizer(tagName,"."); // participations and ActRelationships; keep anything before the first '_' if (points.countTokens() == 1) { StringTokenizer underscores = new StringTokenizer(tagName,"_"); // take the whole name, if there is no '_', or anything before the first '_' result = underscores.nextToken(); } // acts and roles else if (points.countTokens() == 2) { String beforePoint = points.nextToken(); String afterPoint = points.nextToken(); StringTokenizer underscores = new StringTokenizer(afterPoint,"_"); // if there are some '_' after the '.', drop the last '_' and what follows it if (underscores.countTokens() > 1) { String lastPiece= ""; while (underscores.hasMoreTokens()) lastPiece = underscores.nextToken(); afterPoint = afterPoint.substring(0, afterPoint.length() - lastPiece.length() -1); } result = beforePoint + "." + afterPoint; } return result; } //---------------------------------------------------------------------------------------- // XSLT versions of wrapper transforms, for inclusion in full XSLT transforms //---------------------------------------------------------------------------------------- protected String wrapperXSLFileName(boolean isIn) { return "not used"; } /** * @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 { // declare namespaces and add templates to pass through text subtrees unchanged; no file of fixed templates super.addWrapperInTemplates(xout, templateFilter,false); // add a general template to copy nodes with no name change xout.topOut().appendChild(identityPathTemplate(xout, "inWrapper")); ElementDef root = ms().getRootElement(); Hashtable<String,Element> templateMatches = new Hashtable<String,Element>(); Hashtable<String,String> nodeNames = nodeNamesToChange(true); addTagNameChangeTemplates(xout,root,templateMatches,true,"",nodeNames); } /** * @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 namespaces and add templates to pass through text subtrees unchanged; no file of fixed templates super.addWrapperOutTemplates(xout, templateFilter,false); // add a general template to copy nodes with no name change, and do not generate any specific ones to do so xout.topOut().appendChild(identityPathTemplate(xout, "outWrapper")); ElementDef root = ms().getRootElement(); Hashtable<String,Element> templateMatches = new Hashtable<String,Element>(); Hashtable<String,String> nodeNames = nodeNamesToChange(false); addTagNameChangeTemplates(xout,root,templateMatches,false,"",nodeNames); } /** * collect the names of all nodes whose names need to be changed for any path, in the in-wrapper or the out-wrapper transform * @param isIn * @return */ private Hashtable<String,String> nodeNamesToChange(boolean isIn) { Hashtable<String,String> nodeNames = new Hashtable<String,String>(); ElementDef root = ms().getRootElement(); addNodeNamesToChange(root,isIn,nodeNames); return nodeNames; } /** * recursive descent of full mapping set, finding all node names which need to change * @param elDef * @param isIn * @param nodeNames */ private void addNodeNamesToChange(ElementDef elDef, boolean isIn, Hashtable<String,String> nodeNames) { String inTagName = elDef.getName(); String outTagName = outWrappedTagName(inTagName); if (!inTagName.equals(outTagName)) { if (isIn) nodeNames.put(outTagName, "1"); else if (!isIn) nodeNames.put(inTagName, "1"); } for (Iterator<ElementDef> it = elDef.getChildElements().iterator();it.hasNext();) { ElementDef childDef = it.next(); addNodeNamesToChange(childDef, isIn, nodeNames); } } /** * * @param xout * @param elDef * @param templateMatches * @param isIn * @param path * @param nodeNames * @throws MapperException */ private void addTagNameChangeTemplates(XSLOutputFile xout,ElementDef elDef, Hashtable<String,Element> templateMatches, boolean isIn, String path, Hashtable<String,String> nodeNames) throws MapperException { String inTagName = elDef.getName(); String prefixedInTagName = V3PREFIX + ":" + inTagName; String outTagName = outWrappedTagName(inTagName); String prefixedOutTagName = V3PREFIX + ":" + outTagName; String priority="3"; String addToPath = ""; if (isIn) addToPath = outTagName; else addToPath = inTagName; String newPath = path + "/" + addToPath; /* if this node name needs to be changed for any path, add a template or a when-condition for this path * (even though the node name might not change for this path) */ if (nodeNames.get(addToPath) != null) { if (isIn) addPathTemplate(xout,"inWrapper",prefixedOutTagName,"",path,addToPath,prefixedInTagName,priority, templateMatches); else if (!isIn) addPathTemplate(xout,"outWrapper",prefixedInTagName,"",path,addToPath,prefixedOutTagName,priority, templateMatches); } for (Iterator<ElementDef> it = elDef.getChildElements().iterator();it.hasNext();) { ElementDef childDef = it.next(); addTagNameChangeTemplates(xout,childDef,templateMatches,isIn,newPath,nodeNames); } } }