package com.openMap1.mapper.converters; import java.util.Hashtable; import java.util.Iterator; import java.util.StringTokenizer; import java.util.Vector; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.Text; import com.openMap1.mapper.core.MapperException; import com.openMap1.mapper.reader.MDLXOReader; import com.openMap1.mapper.structures.MapperWrapper; import com.openMap1.mapper.util.GenUtil; import com.openMap1.mapper.util.XMLOutputFile; import com.openMap1.mapper.util.XMLUtil; import com.openMap1.mapper.util.XPathAPI; import com.openMap1.mapper.util.XSLOutputFile; import com.openMap1.mapper.writer.BasicTemplateFilter; import com.openMap1.mapper.writer.TemplateFilter; import com.openMap1.mapper.MappedStructure; /** * Abstract class that can be used as the superclass of any * class implementing the MapperWrapper interface, which * can be used as a wrapper class on a set of mappings - * to apply an 'in' transform to any instance before it is read by mappings, * or the reverse 'out' transform to any instance * after it has been written by wproc procedures. * * @author robert * */ abstract public class AbstractMapperWrapper implements MapperWrapper{ public static int IN_PRE_SCAN = 0; public static int IN_TRANSFORM = 1; public static int OUT_PRE_SCAN = 2; public static int OUT_TRANSFORM = 3; protected Document inResultDoc; protected Document outResultDoc; public MappedStructure ms() {return ms;} private MappedStructure ms; /** * constants defining the types of files transformed to and from */ public static int XML_TYPE = 0; public static int TEXT_TYPE = 1; public static String V3PREFIX = "v3"; public static String V3NAMESPACEURI = "urn:hl7-org:v3"; //---------------------------------------------------------------------------------------------------------- // constructor //---------------------------------------------------------------------------------------------------------- /** * * @param ms set of mappings which uses this wrapper transform * @param spare spare argument, just in case.... */ public AbstractMapperWrapper(MappedStructure ms, Object spare) throws MapperException { this.ms = ms; // when this wrapper is acting as an output wrapper, it may need access to the input reader's wrapper instance resetSpareArgument(spare); } public void resetSpareArgument(Object spare) throws MapperException { if ((spare != null) && (spare instanceof MDLXOReader)) { MDLXOReader reader = (MDLXOReader)spare; inputWrapper = reader.ms().getWrapper(); } } /** * @return the type of document transformed to and from; * see static constants in class AbstractMapperWrapper. */ abstract public int transformType(); /** * * @return the file extension of the outer document, with initial '*.' */ abstract public String fileExtension(); /** * * @param incoming; must be of class Element or InputStream * @return the result of the in wrapper transform */ abstract public Document transformIn(Object incoming) throws MapperException; /** * @param outgoing the root element produced by the XMLWriter when * writing out from a class model instance (seen through an objectGetter) * @return the result of the out wrapper transform; * must be of class Document or OutputStream */ abstract public Object transformOut(Element outgoing) throws MapperException; /** * When certain XML subtrees are to be passed through a translation * unchanged, they are indexed by a string key by an input wrapper transform * and then made available to an output wrapper transform * to be overridden by any wrapper class providing this facility * @return a table of subtrees, with random string keys */ public Hashtable<String,Element> keptSubtrees() {return keptSubtrees;} /** * for the CDA API for Java, it is necessary to be able to set up the table * converting string keys to XML subtrees, as if an XML instance had been * read in * @param keptSubtrees */ public void setKeptSubtrees(Hashtable<String,Element> keptSubtrees) { if (keptSubtrees != null) { this.keptSubtrees = keptSubtrees; keptSubtreesSet = true; } } protected Hashtable<String,Element> keptSubtrees = null; protected boolean keptSubtreesSet = false; protected int keyIndex = 0; /* may be set non-null when this instance is acting as an output wrapper */ protected MapperWrapper inputWrapper = null; //--------------------------------------------------------------------------------- // XSLT for wrapper transforms //--------------------------------------------------------------------------------- /** * @param xout the XSL output file * @param templateFilter has a boolean method to say if a template should be included * 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" * @throws MapperException */ public void addWrapperInTemplates(XSLOutputFile xout, TemplateFilter templateFilter) throws MapperException { xout.topOut().appendChild(identityTemplate(xout,"inWrapper")); } /** * @param xout the XSL output file * @param templateFilter has a boolean method to say if a template should be included * 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 { xout.topOut().appendChild(identityTemplate(xout,"outWrapper")); } /** * make a standalone wrapper XSLT file at the specified file location * @param xout an empty xsl output file which has been created, and will be written to a location * after the end of this call * @param isInWrapper - true if it is to be an in-wrapper transform, false for an out-wrapper transform * This default implementation makes a wrapper transform which changes nothing. * Subclasses override the called methods to provide templates to change specific tag names. * **** DO NOT OVERRIDE THIS METHOD; OVERRIDE THE METHODS IT CALLS ******** */ public void makeStandaloneWrapperXSLT(XSLOutputFile xout, boolean isInWrapper) throws MapperException { makeHeaderForStandaloneWrapperXSLT( xout, isInWrapper); TemplateFilter templateFilter = new BasicTemplateFilter(ms()); if (isInWrapper) { addWrapperInTemplates(xout,templateFilter); } else { addWrapperOutTemplates(xout,templateFilter); } } public void makeHeaderForStandaloneWrapperXSLT(XSLOutputFile xout, boolean isInWrapper) throws MapperException { // plain starter template of the correct mode xout.topOut().appendChild(starterTemplate(xout,wrapperMode(isInWrapper),false)); } /** * @param isInWrapper * @return the mode to be used for wrapper XSLT templates for in- or out-wrapper */ public static String wrapperMode(boolean isInWrapper) { if (isInWrapper) return "inWrapper"; return "outWrapper"; } /** * write out a plain starter template: <xsl:template match="/"> <xsl:apply-templates mode="outWrapper"/> </xsl:template> * or a starter template with a path parameter: <xsl:template match="/"> <xsl:apply-templates mode="outWrapper"> <xsl:with-param name="path" select="''"/> </xsl:apply-templates> </xsl:template> * @param xout the XSL file being written * @param mode the mode of the templates, typically 'inWrapper' or 'outWrapper' * @param withPathParameter if true, give the applied template a path parameter * @return a starter template, for use when generating standalone wrapper templates, * where the generate templates pass a path as parameter * @throws MapperException */ public static Element starterTemplate(XSLOutputFile xout,String mode,boolean withPathParameter) throws MapperException { Element starterTemp = xout.XSLElement("template"); starterTemp.setAttribute("match", "/"); Element applyEl = xout.XSLElement("apply-templates"); applyEl.setAttribute("mode", mode); starterTemp.appendChild(applyEl); if (withPathParameter) { Element paramEl = xout.XSLElement("with-param"); paramEl.setAttribute("name", "path"); paramEl.setAttribute("select", "''"); applyEl.appendChild(paramEl); } return starterTemp; } /** * @param xout XSLT output file * @param mode mode for the identity template * @return the identity template, which recursively copies an XML without change - * but more specific templates with the same mode can change named elements <!-- modified identity template --> <xsl:template match="*" mode="outWrapper"> <xsl:copy> <xsl:copy-of select="@*" /> <xsl:apply-templates mode="outWrapper"/> </xsl:copy> </xsl:template> * @throws MapperException */ public static Element identityTemplate(XSLOutputFile xout,String mode) throws MapperException { Element identityTemp = xout.XSLElement("template"); identityTemp.setAttribute("match", "*"); identityTemp.setAttribute("mode", mode); Element copyEl = xout.XSLElement("copy"); identityTemp.appendChild(copyEl); Element copyOfEL = xout.XSLElement("copy-of"); copyOfEL.setAttribute("select", "@*"); copyEl.appendChild(copyOfEL); Element applyIdent = xout.XSLElement("apply-templates"); applyIdent.setAttribute("mode", mode); copyEl.appendChild(applyIdent); return identityTemp; } /** * @param xout XSLT output file * @param mode mode for the template * @param oldName node name to be replaced (possibly with condtions in [] after) * @param newName the tag name to replace it * @return the identity template, which recursively copies an XML without change - * but more specific templates with the same mode can change named elements <!-- template to change the name of selected elements --> <xsl:template match="oldName[conditions]" mode="outWrapper"> <newName> <xsl:copy-of select="@*" /> <xsl:apply-templates mode="outWrapper"/> </newName> </xsl:template> * @throws MapperException */ public static Element nameChangeTemplate(XSLOutputFile xout,String mode, String oldName, String newName) throws MapperException { Element renameTemp = xout.XSLElement("template"); renameTemp.setAttribute("match", oldName); renameTemp.setAttribute("mode", mode); Element newNameEl = xout.newElement(newName); renameTemp.appendChild(newNameEl); Element copyOfEL = xout.XSLElement("copy-of"); copyOfEL.setAttribute("select", "@*"); newNameEl.appendChild(copyOfEL); Element applyIdent = xout.XSLElement("apply-templates"); applyIdent.setAttribute("mode", mode); newNameEl.appendChild(applyIdent); return renameTemp; } /** * @param xout XSLT output file * @param mode mode for the identity template * @return an identity template, which recursively copies an XML without change, * and which also passes down the XPath in the source document as a parameter. <!-- special identity template which passes the current path as a parameter --> <xsl:template match="*" mode="inWrapper"> <xsl:param name="path"/> <xsl:variable name="newPath" select="concat($path,'/',local-name(.))"/> <xsl:copy> <xsl:copy-of select="@*" /> <xsl:apply-templates mode="inWrapper"> <xsl:with-param name="path" select="$newPath"/> </xsl:apply-templates> </xsl:copy> </xsl:template> * @throws MapperException */ public static Element identityPathTemplate(XSLOutputFile xout,String mode) throws MapperException { Element identityTemp = xout.XSLElement("template"); identityTemp.setAttribute("match", "*"); identityTemp.setAttribute("mode", mode); Element paramEl = xout.XSLElement("param"); paramEl.setAttribute("name", "path"); identityTemp.appendChild(paramEl); Element varEl = xout.XSLElement("variable"); varEl.setAttribute("name", "newPath"); varEl.setAttribute("select", "concat($path,'/',local-name(.))"); identityTemp.appendChild(varEl); Element copyEl = xout.XSLElement("copy"); identityTemp.appendChild(copyEl); Element copyOfEL = xout.XSLElement("copy-of"); copyOfEL.setAttribute("select", "@*"); copyEl.appendChild(copyOfEL); Element applyIdent = xout.XSLElement("apply-templates"); applyIdent.setAttribute("mode", mode); copyEl.appendChild(applyIdent); Element withEl = xout.XSLElement("with-param"); withEl.setAttribute("name", "path"); withEl.setAttribute("select", "$newPath"); applyIdent.appendChild(withEl); return identityTemp; } /** * add a template with a 'path' parameter to the XSLT transform, of the form (with parameters of this method in {} ): <xsl:template xmlns:v3="urn:hl7-org:v3" match="v3:{matchTagName}{conditions}" mode="outWrapper" priority="{priority}"> <xsl:param name="path"/> <xsl:choose> <xsl:when test="$path='{requiredPath}'> <xsl:variable name="newPath" select="concat($path,'/','{addToPath}')"/> <v3:{tagName}> <xsl:copy-of select="@*" /> <xsl:apply-templates mode="outWrapper"> <xsl:with-param name="path" select="$newPath"/> </xsl:apply-templates> <v3:{tagName}> </xsl:when> <xsl:when ....> .... </xsl:when> </xsl:choose> </xsl:template> * Templates added already are stored in Hashtable templateMatches. * If there is already a template with the same match (same matchTagName and conditions), * but with different path, do not add a new template but add a new 'when' element * to the existing template. * * @param xout output file for the XSLT * @param mode the mode of the template: 'inWrapper' or 'outWrapper' * @param matchTagName tag name to be matched to apply this template * @param conditions extra conditions to be matched to apply this template, inside a [] * @param requiredPath value of 'path' parameter to pick out the 'when' clause which is executed * @param addToPath tag name to add to the 'path' parameter passed on to templates applied by this template * @param tagName tag name to add to the XML output * @param priority priority of this template (should be higher priority with longer conditions) * @param templateMatches Hashtable of templates stored by match conditions, so there is only one template * per match conditions, which may have several <when> elements for different paths * * @return the <when> element which is put in a new or existing template * @throws MapperException */ public static Element addPathTemplate(XSLOutputFile xout, String mode, String matchTagName, String conditions, String requiredPath, String addToPath, String tagName, String priority, Hashtable<String,Element> templateMatches) throws MapperException { // template element Element templateEl = xout.XSLElement("template"); templateEl.setAttribute("mode", mode); templateEl.setAttribute("priority", priority); templateEl.setAttribute("xmlns:v3", V3NAMESPACEURI); // add conditions to the match String match = addPrefix(matchTagName) + conditions; templateEl.setAttribute("match",match); // param element Element paramEl = xout.XSLElement("param"); paramEl.setAttribute("name", "path"); templateEl.appendChild(paramEl); // choose Element Element chooseEl = xout.XSLElement("choose"); templateEl.appendChild(chooseEl); // when subtree Element whenEl = whenSubtree(xout,requiredPath, addToPath, tagName, mode); chooseEl.appendChild(whenEl); /* write the template to the xslt, if a template of this match and mode has not already been written. * If a template with this match and mode has been written, add the <when> element to its <choose> */ String matchAndMode = match + "_" + mode; Element templateWithThisMatchAndMode = templateMatches.get(matchAndMode); if (templateWithThisMatchAndMode == null) { xout.topOut().appendChild(templateEl); templateMatches.put(matchAndMode,templateEl); } else if (templateWithThisMatchAndMode != null) { Element existingChoose = XMLUtil.firstNamedChild(templateWithThisMatchAndMode, "choose"); XMLUtil.appendChildWithDocumentCheck(existingChoose, whenEl); } return whenEl; } /** * If a name has no namespace prefix, it is in the v3 namespace, so add the 'v3' prefix to it. * Otherwise add nothing. * @param name * @return */ private static String addPrefix(String name) { String result = name; StringTokenizer parts = new StringTokenizer(name,":"); if (parts.countTokens() == 1) result = "v3:" + name; return result; } /** * * @param xout * @param requiredPath * @param addToPath * @param tagName * @param firstChild * @param applyMode * @return * @throws MapperException */ private static Element whenSubtree(XSLOutputFile xout, String requiredPath, String addToPath, String tagName, String applyMode) throws MapperException { // make the when test the constrained path of the tag conversion's parent Element whenEl = xout.XSLElement("when"); whenEl.setAttribute("test", "$path='" + requiredPath + "'"); // variable holding the new path Element varEl = xout.XSLElement("variable"); varEl.setAttribute("name", "newPath"); varEl.setAttribute("select", "concat($path,'/','" + addToPath + "')"); whenEl.appendChild(varEl); // make element with modified tag name, in the V3 namespace (if it has no prefix already) Element cdaEl = xout.newElement(addPrefix(tagName)); whenEl.appendChild(cdaEl); // copy the attributes of the element with modified tag name Element copyOfEL = xout.XSLElement("copy-of"); copyOfEL.setAttribute("select", "@*"); cdaEl.appendChild(copyOfEL); // apply-templates to recurse down the output XML tree Element applyEl = xout.XSLElement("apply-templates"); applyEl.setAttribute("mode", applyMode); cdaEl.appendChild(applyEl); // pass on the path as a parameter Element withEl = xout.XSLElement("with-param"); withEl.setAttribute("name", "path"); withEl.setAttribute("select", "$newPath"); applyEl.appendChild(withEl); return whenEl; } /** * Open the xsl file at the given location, and import into the main XSL * file all templates in it (except the starter template if removeStarterTemplate = true). * @param xout XSL output file being populated * @param xslLocation * @param removeStarterTemplate if true, remove the top template with match '/' * (removeStarterTemplate is set false for generation of a standalone wrapper transform) * @throws MapperException */ protected void addTemplatesFromFile(XMLOutputFile xout,String xslLocation, boolean removeStarterTemplate) throws MapperException { try{ Element xslRoot = XMLUtil.getRootElement(xslLocation); // collect all templates and top-level variables in one list Vector<Element> templates = XMLUtil.namedChildElements(xslRoot, "template"); Vector<Element> variables = XMLUtil.namedChildElements(xslRoot, "variable"); for (Iterator<Element> iv = variables.iterator();iv.hasNext();) templates.add(iv.next()); for (Iterator<Element> it = templates.iterator();it.hasNext();) { Element templateOrVariable = it.next(); // if removeStarterTemplate is true, the top template of the standalone XSLT file is not included if ((!(templateOrVariable.getAttribute("match").equals("/")))|(!removeStarterTemplate)) { Element imported = (Element)xout.outDoc().importNode(templateOrVariable, true); xout.topOut().appendChild(imported); } } } catch (Exception ex) {throw new MapperException("Cannot open file " + xslLocation);} } //--------------------------------------------------------------------------------- // Useful methods for subclasses //--------------------------------------------------------------------------------- /** * recursive scan of an XML document, keeping track of the XPath, * so that overrides in implementing classes can do specific * things at specific paths */ protected Element scanDocument(Element el, String path, int scanType) throws MapperException { Element result = processNode(el,path,scanType); /* if processing the node has done special processing which * added child nodes to the result, do not add them again */ if ((result != null) && (XMLUtil.childElements(result).size() > 0)) {} else for (Iterator<Element> ie = XMLUtil.childElements(el).iterator();ie.hasNext();) { Element child = ie.next(); String step = XMLUtil.getLocalName(child); String newPath = path + "/" + step; Element childResult = scanDocument(child,newPath,scanType); if ((result != null) && (childResult != null)) result.appendChild(childResult); } return result; } /** * Process one node in a * recursive scan of an XML document, keeping track of the XPath, * so that overrides in implementing classes can do specific * things at specific paths * @param el * @return */ protected Element processNode(Element el, String path, int scanType) throws MapperException { if (scanType == IN_PRE_SCAN) return inPreScanNode(el,path); else if (scanType == IN_TRANSFORM) return inTransformNode(el,path); else if (scanType == OUT_PRE_SCAN) return outPreScanNode(el,path); else if (scanType == OUT_TRANSFORM) return outTransformNode(el,path); return el; } // to be overridden in implementing classes protected Element inPreScanNode(Element el, String path) throws MapperException {return null;} /** * 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); // 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; } // to be overridden in implementing classes protected Element outPreScanNode(Element el, String path) throws MapperException {return null;} /** * 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); // 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; } /** * * @param source an Element * @return the text of the Element, if it has no child Elements; * otherwise return "" */ protected String textOnly(Element el) { String text = ""; if (XMLUtil.childElements(el).size() == 0) { NodeList nodes = el.getChildNodes(); for (int i = 0; i < nodes.getLength(); i++) if (nodes.item(i) instanceof Text) { Text t = (Text)nodes.item(i); text = text + t.getData(); } } return text; } /** * Follow an XPath which is expected to give a unique value, and return it * @param el * @param path * @return the value, or "" if no node is found * @throws MapperException if more than 1 node is found, or for any XPath failure */ protected String getPathValue(Element el, String path) throws MapperException { boolean isAttribute = path.contains("@"); Vector<Node> targetNodes = new Vector<Node>(); String value = ""; try {targetNodes = XPathAPI.selectNodeVector(el, path, ms().getNamespaceSet());} catch(Exception ex) {throw new MapperException("Failed follow XPath: " + path + " from element " + el.getLocalName() + "; " + ex.getMessage());} if (targetNodes.size() > 1) throw new MapperException("Following XPath: " + path + " from element " + el.getLocalName() + " gave " + targetNodes.size() + " nodes."); if ((targetNodes.size() == 1)) { Node node = targetNodes.get(0); if (isAttribute) value = ((Attr)node).getNodeValue(); else value = textOnly((Element)node); } return value; } //-------------------------------------------------------------------------------------------------------- // Altering the templates produced by XSLGenerator, so they pass through text subtrees unchanged //-------------------------------------------------------------------------------------------------------- /** * Alter the templates written by XSLGenerator so that: * (a) <text> elements in the input are expected to be in the V3 namespace (if they are not already) * (b) the subtrees below <text> elements are passed through unchanged * @param xout the XSL output file * @param fromV3ToNoNamespace true if the transform is from a full CDA message (in the V3 namespace) * to a simplified message (in no namespace); false for the reverse direction */ protected void passThroughTextSubtrees(XSLOutputFile xout, boolean fromV3ToNoNamespace) throws MapperException { trace("Changing templates for text; from V3 is " + fromV3ToNoNamespace); // ensure the V3 namespace is declared on the top element of the XSLT. xout.topOut().setAttribute("xmlns:" + V3PREFIX, AbstractMapperWrapper.V3NAMESPACEURI); Vector<String> textModes = findTextModes(xout,fromV3ToNoNamespace); boolean replaced = enhanceTextTemplates(textModes, xout,fromV3ToNoNamespace); // if any templates have been replaced, add the copy template if (replaced) { if (CDAConverter.SIMPLE_MESSAGE_IN_V3_NAMESPACE) addSimpleCopyTemplate(xout); else addNamespaceChangingCopyTemplates(xout,fromV3ToNoNamespace); } } /** * * @param xout * @return Vector of all the modes for templates for <text> nodes */ private Vector<String> findTextModes(XSLOutputFile xout, boolean fromV3ToNoNamespace) { /* pass over all templates, making sure <text> nodes are expected * in the V3 namespace, and finding the modes of templates applied to them */ Vector<String> textModes = new Vector<String>(); for (Iterator<Element> it = XMLUtil.childElements(xout.topOut()).iterator();it.hasNext();) { Element temp = it.next(); // find a child element of the template not in the xsl namespace (defining an element to be written out) for (Iterator<Element> iu = XMLUtil.childElements(temp).iterator();iu.hasNext();) { Element child = iu.next(); String uri = child.getNamespaceURI(); if ((uri == null)|((uri != null) && (!uri.equals(XMLUtil.XSLURI)))) { // go over child elements of the element being written out NodeList nl = child.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { if (nl.item(i) instanceof Element) { Element el = (Element)nl.item(i); // look for xsl:variable elements whose 'select' ends in 'text', and note their position if ((el.getLocalName() != null) && (el.getLocalName().equals("variable"))) { String select = el.getAttribute("select"); // the last step in the path must be 'text' with or without a prefix StringTokenizer steps = new StringTokenizer(select,"/"); String step = ""; while (steps.hasMoreTokens()) step = steps.nextToken(); if (((step.equals("text"))|(step.endsWith(":text"))) && (i < nl.getLength() -2)) { trace("Node name " + child.getLocalName()); trace("Select " + select); // find the name of the next variable String vName = ((Element)nl.item(i+1)).getAttribute("name"); // find the mode of the apply-templates whose select is that variable String mode = findApplyMode(nl,vName); if (mode != null) { textModes.add(mode); trace("Change template with mode " + mode); } } } } } } } } return textModes; } /** * * @param textModes * @param xout * @return */ private boolean enhanceTextTemplates(Vector<String> textModes, XSLOutputFile xout, boolean fromV3ToNoNamespace) throws MapperException { boolean replaced = false; for (Iterator<Element> it = XMLUtil.childElements(xout.topOut()).iterator();it.hasNext();) { Element temp = it.next(); String mode = temp.getAttribute("mode"); if (GenUtil.inVector(mode, textModes)) { replaced = true; // find the child element with local name 'text' Element textChild = XMLUtil.firstNamedChild(temp, "text"); if (textChild != null) { // remove any child of the <text> element which copied text contents, like <xsl:value-of select="$textConten_13"/> Element valueChild =XMLUtil.firstNamedChild(textChild, "value-of"); if (valueChild != null) textChild.removeChild(valueChild); // add a child to the <text> element, to apply a copy template Element apply = xout.XSLElement("apply-templates"); apply.setAttribute("mode", "copy"); apply.setAttribute("select", "node()"); textChild.appendChild(apply); } // else throw new MapperException("Found no expected text element in template of mode " + mode); } } return replaced; } /** * add two templates: * <xsl:template match="element()" mode="copy"> <xsl:element name="{local-name()}" namespace=""> <xsl:apply-templates mode="copy" select="@*|node()"/> </xsl:element> </xsl:template> <xsl:template match="@*|text()" mode="copy"> <xsl:copy/> </xsl:template> * namespace="" if fromV3ToNoNamespace = true, * or namespace="v3" if fromV3ToNoNamespace = false * * @param xout * @throws MapperException */ private void addNamespaceChangingCopyTemplates(XSLOutputFile xout, boolean fromV3ToNoNamespace) throws MapperException { // first template Element copyElTemplate = xout.XSLElement("template"); copyElTemplate.setAttribute("match", "element()"); copyElTemplate.setAttribute("mode", "copy"); // add an element child, copying the name and with namespace depending on direction of translation Element el = xout.XSLElement("element"); el.setAttribute("name", "{local-name()}"); String namespace= ""; if (!fromV3ToNoNamespace) namespace = V3NAMESPACEURI; el.setAttribute("namespace", namespace); copyElTemplate.appendChild(el); // add a child to apply the copy template again Element apply = xout.XSLElement("apply-templates"); apply.setAttribute("select", "@*|node()"); apply.setAttribute("mode", "copy"); el.appendChild(apply); // second template Element copyOtherTemplate = xout.XSLElement("template"); copyOtherTemplate.setAttribute("match", "@*|text()"); copyOtherTemplate.setAttribute("mode", "copy"); Element copyEl = xout.XSLElement("copy"); copyOtherTemplate.appendChild(copyEl); xout.topOut().appendChild(copyElTemplate); xout.topOut().appendChild(copyOtherTemplate); } /** * add a simple copy template, which does not change namespaces * @param xout * @throws MapperException */ private void addSimpleCopyTemplate(XSLOutputFile xout) throws MapperException { Element copyOtherTemplate = xout.XSLElement("template"); copyOtherTemplate.setAttribute("match", "@*|node()"); copyOtherTemplate.setAttribute("mode", "copy"); Element copyEl = xout.XSLElement("copy"); copyOtherTemplate.appendChild(copyEl); // add a child to apply the copy template again Element apply = xout.XSLElement("apply-templates"); apply.setAttribute("select", "@*|node()"); apply.setAttribute("mode", "copy"); copyEl.appendChild(apply); xout.topOut().appendChild(copyOtherTemplate); } /** * @param nl a list of child elements of an element * @param vName the name of a variable * @return find any apply-templates element in the node list , whose select * matches the variable name, and return its mode; * if not found return null */ private String findApplyMode(NodeList nl,String vName) { String mode = null; for (int i = 0; i < nl.getLength(); i++) { Element el = (Element)nl.item(i); if ((el.getLocalName().equals("apply-templates")) && (el.getAttribute("select").equals("$" + vName))) mode = el.getAttribute("mode"); } return mode; } //----------------------------------------------------------------------------------------- // copying subtrees under <text> nodes unchanged from input to output //----------------------------------------------------------------------------------------- /** * save subtrees below <text> nodes in a Hashtable, to be recovered by the output * wrapper transform. Put the key to the Hashtable as text content of the element */ protected Element saveInputTextSubtree(Element el) throws MapperException { if (keptSubtrees == null) throw new MapperException("Table to save text subtrees has not been initialised"); // retain the subtree in the table, with a generated key String key = "key_" + keyIndex; keyIndex++; keptSubtrees.put(key, el); // copy the element with namespaces, prefixed tag name, attributes but no element subtree, and the text key Element copy = (Element)inResultDoc.importNode(el, false); copy.appendChild(inResultDoc.createTextNode(key)); return copy; } /** * recover a subtree below a <text> element, as saved by the input wrapper transform, * and put it in the output-wrapped result. * @param el <text> element whose text content is the key to the hashtable of subtrees, if it exists * @param path path to this element (for error messages only) * @return <text> element with its subtree, for inclusion in the output * @throws MapperException */ protected Element recoverInputTextSubtree(Element el, String path) throws MapperException { String key = XMLUtil.getText(el); // subtrees have been explicitly set for this output wrapper transform if (keptSubtreesSet) { Element subtree = keptSubtrees.get(key); if (subtree != null) { Element copy = (Element)outResultDoc.importNode(subtree, true); return copy; } // input wrapper failed to save this <text> subtree else throw new MapperException("Found no subtree for <text> node with key '" + key + "' at path '" + path + "'"); } // the input wrapper transform saved text subtrees else if ((inputWrapper != null) && (inputWrapper.keptSubtrees() != null)) { Element subtree = inputWrapper.keptSubtrees().get(key); if (subtree != null) { Element copy = (Element)outResultDoc.importNode(subtree, true); return copy; } // input wrapper failed to save this <text> subtree else throw new MapperException("Found no saved subtree for <text> node with key '" + key + "' at path '" + path + "'"); } // if the input wrapper transform did not save text subtrees, do not fail else { // copy the element with namespaces, prefixed tag name, attributes but no child Elements, and unchanged text content Element copy = (Element)outResultDoc.importNode(el, false); copy.appendChild(outResultDoc.createTextNode(XMLUtil.getText(el))); return copy; } } //----------------------------------------------------------------------------------------- // trivia //----------------------------------------------------------------------------------------- protected void trace(String s) {if (tracing()) System.out.println(s);} protected boolean tracing() {return true;} protected int steps(String path) {return (new StringTokenizer(path,"/").countTokens());} }