package com.openMap1.mapper.converters; import java.io.FileInputStream; import java.io.InputStream; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.StringTokenizer; import java.util.Vector; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.w3c.dom.Document; import org.w3c.dom.Element; import com.openMap1.mapper.core.MapperException; import com.openMap1.mapper.core.NamespaceSet; import com.openMap1.mapper.structures.MapperWrapper; import com.openMap1.mapper.structures.StructureDefinition; import com.openMap1.mapper.util.EclipseFileUtil; import com.openMap1.mapper.util.FileUtil; import com.openMap1.mapper.util.XMLUtil; import com.openMap1.mapper.ElementDef; import com.openMap1.mapper.MappedStructure; import com.openMap1.mapper.MaxMult; import com.openMap1.mapper.MinMult; /** * Class with methods to convert V2 message instances between * the vertical bar form and the XML form * @author robert * */ public class V2Converter extends AbstractMapperWrapper implements MapperWrapper{ // default separators - may be changed by the message header MSH segment private String fieldSeparator = "|"; private String componentSeparator = "^"; private String subComponentSeparator = "&"; private String repetitionSeparator = "~"; private String escapeCharacter = "\\"; private StructureDefinition V2Structure; private ElementDef V2Root; private String rootName; private Document doc; public static String V2_NAMESPACE_URI = "urn:hl7-org:v2xml"; protected boolean tracing() {return false;} /* if strict = true, whenever it encounters a problem in an in-wrapper transform it throws an exception, * i.e stops the transform. * if strict = false, it just writes a message to the console */ private boolean strict = false; private void throwOrWriteMessage(String warning) throws MapperException { if (strict) throw new MapperException(warning); else System.out.println(warning); } //---------------------------------------------------------------------------------------------------- // constructor //---------------------------------------------------------------------------------------------------- public V2Converter(MappedStructure mappedStructure, Object rootNameObj) throws MapperException { super(mappedStructure, rootNameObj); if (!(rootNameObj instanceof String)) throw new MapperException("Second argument of V2Converter constructor is not a String"); V2Structure = mappedStructure.getStructureDefinition(); this.rootName = (String)rootNameObj; V2Root = V2Structure.nameStructure(rootName); if (V2Root == null) throw new MapperException("V2 Structure definition does not define the message '" + rootName + "'"); if (escapeCharacter == null) {} // to avoid a compiler warning that it is unused } //---------------------------------------------------------------------------------------------------- // Small methods in the MapperWrapper interface //---------------------------------------------------------------------------------------------------- /** * @return the type of document transformed to and from; * see static constants in class AbstractMapperWrapper. */ public int transformType() {return AbstractMapperWrapper.TEXT_TYPE;} /** * * @return the file extension of the outer document */ public String fileExtension() {return "*.txt";} //---------------------------------------------------------------------------------------------------- // round trip test //---------------------------------------------------------------------------------------------------- /** * convert a bar-coded V2 message to V2.xml, then back again, * and throw a MapperException at the first discrepancy found */ public void doRoundTripTest(FileInputStream V2BarFile) throws MapperException { doc = XMLUtil.makeOutDoc(); Element root = XMLUtil.newElement(doc, rootName); root.setAttribute("xmlns", V2_NAMESPACE_URI); doc.appendChild(root); Vector<String> lines = FileUtil.getLines(V2BarFile); // fill out the root element of the V2.xml file writeLines(root,V2Root, lines); // use the V2.xml file to make a round trip back to bar coded form String[] newLines = transformOut(root); // compare the start and end of the round trip int minLines = Math.min(lines.size(), newLines.length); for (int l = 0; l < minLines;l++) compareLine(lines.get(l),newLines[l]); if (lines.size() != newLines.length) throw new MapperException("There are " + lines.size() + " segments in the original, " + newLines.length + " segments in the round trip result."); } /** * @param line a segment line of the input V2 message * @param newLine the same line of the round trip result * @throws MapperException if there is any difference between them */ private void compareLine(String line, String newLine) throws MapperException { int minLength = Math.min(line.length(),newLine.length()); for (int c = 0; c < minLength; c++) if (line.charAt(c) != newLine.charAt(c)) throw new MapperException("Round trip discrepancy at the last character of '" + newLine.substring(0, c + 1) + "'; original was '" + line + "'"); if (line.length() != newLine.length()) throw new MapperException ("Round trip line length " + newLine.length() + " does not match input line length " + line.length() + " at line '" + newLine + "'"); } //---------------------------------------------------------------------------------------------------- // writing a V2.xml instance //---------------------------------------------------------------------------------------------------- /** * @param V2BarFileObj input stream of the 'vertical bar' form message * @param rootName name of the root element; equals the message name * @return V2.XML Document * @throws MapperException if, for instance, the actual message structure * does not match the expected structure */ public Document transformIn(Object V2BarFileObj) throws MapperException { if (!(V2BarFileObj instanceof InputStream)) throw new MapperException("Input for making V2.xml instance is not an InputStresm"); InputStream V2BarFile = (InputStream)V2BarFileObj; doc = XMLUtil.makeOutDoc(); Element root = XMLUtil.newElement(doc, rootName); root.setAttribute("xmlns", V2_NAMESPACE_URI); doc.appendChild(root); Vector<String> lines = FileUtil.getLines(V2BarFile); trace("Converting " + lines.size() + " lines to XML"); writeLines(root,V2Root, lines); return doc; } /** * add Elements beneath a root Element, (one per segment but sometimes grouped into * segment groups) for a V2 bar-encoded message * @param V2Root ElementDef of the V2 structure being constructed * @param lines lines from the vertical bar form message, one per segment */ private void writeLines(Element root, ElementDef V2Root, Vector<String> lines) throws MapperException { V2Root = expandElementDefinition(V2Root); int elementDefs = V2Root.getChildElements().size(); int elementDefInMessage = 0; // top-level ElementDef we are currently trying to match boolean[] matched = new boolean[elementDefs]; // track which ElementDefs are matched for (int i = 0; i < elementDefs;i++) matched[i] = false; /* do all segments in the vertical bar message. This is not a simple iteration, * as the line position pos may be moved forward several steps by a segment group */ int pos = 0; while (pos < lines.size()) { String line = lines.get(pos); String segmentName = line.substring(0, 3); trace("line " + pos + ": " + line); // try to match the current segment with the current or some later ElementDef boolean found = false; // true if the segment in the bar encoded message is matched while ((!found) && (elementDefInMessage < elementDefs)) { if (pos > lines.size() - 1) return; int oldPos = pos; // current top-level ElementDef ElementDef currentDef = getChildElementDef(V2Root,elementDefInMessage); trace("Matching ElementDef " + currentDef.getName()); // current top level elementDef is a segment if (currentDef.getName().equals(segmentName)) { found = true; matched[elementDefInMessage] = true; // make the subtree for the matched segment writeSegment(root,line,currentDef); // if this segment cannot be repeated, move on to the next segment definition if (currentDef.getMaxMultiplicity() == MaxMult.ONE) elementDefInMessage++; pos++; } // current top level elementDef is a segment group else if (isSegmentGroup(currentDef)) { /* try to match the current line and subsequent lines with the start of a segment group; * (pos - oldPos) is the number of lines successfully consumed. */ boolean isCompulsory = (currentDef.getMinMultiplicity() == MinMult.ONE); pos = writeSegmentGroup(isCompulsory,root,lines,pos,currentDef); // record successful match of segment group if (pos > oldPos) matched[elementDefInMessage] = true; // match failed; this is OK if the segment group was optional, or has been matched already, but not otherwise else if ((pos == oldPos) && (currentDef.getMinMultiplicity() == MinMult.ONE) && !matched[elementDefInMessage]) { String warning = "Failed to match obligatory segment group '" + currentDef.getName() + "'"; throwOrWriteMessage(warning); } // if this segment group cannot be repeated, move on to the next segment or group definition if (currentDef.getMaxMultiplicity() == MaxMult.ONE) elementDefInMessage++; } // failed to match an obligatory segment or segment group else if (currentDef.getMinMultiplicity() == MinMult.ONE) { String warning = "Failed to match obligatory segment or segment group '" + currentDef.getName() + "'"; throwOrWriteMessage(warning); } // if the current segment definition cannot be matched, try to match the line with the next definition else elementDefInMessage++; } // end of loop over ElementDefs being matched against the current line if (!found) { String warning = ("Cannot match segment name " + segmentName + " of line " + (pos + 1) + ": '" + line + "' with the structure definition"); throwOrWriteMessage(warning); } } // end of loop over line number pos of the bar-coded message // run out of text; check that no later segments or segment groups require more text if (elementDefInMessage + 1 < elementDefs) for (int ed = elementDefInMessage + 1; ed < elementDefs; ed++) checkIfTextNeeded(V2Root.getChildElements().get(ed)); } /** * @param elDef and ElementDef for a segment or segment group in the V2 message definition * @throws MapperException if there is some segment inside the group which requires some text */ private void checkIfTextNeeded(ElementDef elDef) throws MapperException { // no problem if the segment or group is optional if (elDef.getMinMultiplicity() == MinMult.ONE) { if (!isSegmentGroup(elDef)) throw new MapperException("Message text required to match segment " + elDef.getName()); else for (Iterator<ElementDef> it = elDef.getChildElements().iterator();it.hasNext();) checkIfTextNeeded(it.next()); } } private boolean isSegmentGroup(ElementDef elDef) { String nodeType = elDef.getAnnotation("V2NodeType"); return ((nodeType != null) && (nodeType.equals("SegGroup"))); } /** * @param lines Vector of line in the vertical bar message, one per segment * @param pos current position in the list of lines being analysed * @param currentDef ElementDef defining a segment group, that the current line * should be the first segment of * @return the next position in the Vector of lines after this segment group * has been completely analysed (If the position has not been increased, the * analysis is unsuccessful) */ private int writeSegmentGroup(boolean isCompulsory,Element root,Vector<String> lines, int pos,ElementDef groupDef) throws MapperException { int newPos = pos; // newPos is the current unmatched segment if (newPos > lines.size() -1) return newPos; // to avoid spurious checks // make the Element for the segment group (attach it only if something matches) Element segGroup = XMLUtil.newElement(doc, groupDef.getName()); // iterate over all segment definitions and segment group definitions in the group groupDef = expandElementDefinition(groupDef); trace ("Segment group " + groupDef.getName()); List<ElementDef> groupMembers = groupDef.getChildElements(); int defPosLast = -5; for (int defPos = 0; defPos < groupMembers.size(); defPos++) { boolean repeat = (defPos == defPosLast); defPosLast = defPos; ElementDef segDef = groupMembers.get(defPos); trace("Member " + segDef.getName() + " defPos " + defPos + " newPos " + newPos); boolean childIsCompulsory = isCompulsory && (segDef.getMinMultiplicity() == MinMult.ONE); if (isSegmentGroup(segDef)) { int groupStartPos = newPos; newPos = writeSegmentGroup(childIsCompulsory, segGroup, lines, newPos,segDef); // if the segment group has matched and can be repeated, let it try again if ((newPos > groupStartPos) && (segDef.getMaxMultiplicity() == MaxMult.UNBOUNDED)) defPos--; } else if (!isSegmentGroup(segDef)) { if (newPos < lines.size()) { String line = lines.get(newPos); String segName = line.substring(0,3); // match of the text segment with the segment definition if (segName.equals(segDef.getName())) { writeSegment(segGroup,line,segDef); newPos++; // move on to the next line /* if the segment definition can repeat, * make it repeat to try it again against the next text segment */ if (segDef.getMaxMultiplicity() == MaxMult.UNBOUNDED) defPos--; } // if the segment group is compulsory and this child is compulsory, but not matched... else if ((childIsCompulsory) && (!repeat)) { throwOrWriteMessage("In compulsory segment group " + groupDef.getName()+ " failed to match compulsory segment '" + segDef.getName() + "'"); } // failure to match an obligatory segment (in the optional group) is failure to match the group else if ((segDef.getMinMultiplicity() == MinMult.ONE) && (!repeat)) { // some previous segment in the group has matched, and now an obligatory segment has failed if (newPos > pos) { throwOrWriteMessage("In segment group " + groupDef.getName() + " failed to match obligatory segment " + segDef.getName() + " after matching some previous segment"); } // no matches in the optional group - fail and return so the next segment or group can be tried else return newPos; } } // have run out of text to match else if (newPos > lines.size() -1) { if ((segDef.getMinMultiplicity() == MinMult.ONE) && (segDef.getMaxMultiplicity() == MaxMult.ONE)) { throwOrWriteMessage("Run out of message to match structure definition in segment " + segDef.getName() + " of group " + groupDef.getName()); } } } } // only attach the segment group if it contains some segments if (newPos > pos) { trace("Attach group " + groupDef.getName()); root.appendChild(segGroup); } // if a compulsory segment group has not matched, throw an Exception else if ((newPos == pos) && isCompulsory) throwOrWriteMessage("Failed to match compulsory segment group '" + groupDef.getName() + "'"); return newPos; } /** * @param root root of document , or segment group element under which the segment element is written * @param line text of the line in the bar-form message * @param segDef ElementDef defining the segment * @throws MapperException */ private void writeSegment(Element root,String line,ElementDef segDef) throws MapperException { String segmentName = segDef.getName(); trace("Writing segment " + segmentName); Element segElement = XMLUtil.newElement(doc, segmentName); root.appendChild(segElement); int mshDoneAlready = 0; segDef = expandElementDefinition(segDef); List<ElementDef> fieldDefs = segDef.getChildElements(); /* the message header may change the separators from the usual default * A message header MSH|^~\&|LAB|767543|ADT|767543|199003141304-0500||ACK^^ACK|XX3657|P|2.4 * becomes * <MSH> <MSH.1>|</MSH.1> <MSH.2>^~\&</MSH.2> <MSH.3> <HD.1>LAB</HD.1> </MSH.3> etc. */ if (segmentName.equals("MSH")) { fieldSeparator = line.substring(3,4); componentSeparator = line.substring(4,5); repetitionSeparator = line.substring(5,6); escapeCharacter = line.substring(6,7); subComponentSeparator = line.substring(7,8); String msh2 = line.substring(4,8); // put in the MSH.1 field (whose content is just the field separator) and MSH.2 writeField(segElement,fieldSeparator,fieldDefs.get(0),0); writeField(segElement,msh2,fieldDefs.get(1),1); mshDoneAlready = 2; } StringTokenizer st = new StringTokenizer(line,fieldSeparator,true); // return all '|' as tokens st.nextToken(); // remove the first token, which is the segment name st.nextToken(); // remove the first '|' // remove the content of MSH.2 and the next '|' if (segmentName.equals("MSH")) {st.nextToken();st.nextToken();} int fieldPos = mshDoneAlready; // 0 for most segments, 2 for MSH while (st.hasMoreTokens()) { String token = st.nextToken(); // track the field position; 0 after first '|', except for MSH if (token.equals(fieldSeparator)) fieldPos++; // some non-empty string between field separators else { if (fieldPos < fieldDefs.size()) { ElementDef fieldDef = fieldDefs.get(fieldPos); StringTokenizer fr = new StringTokenizer(token,repetitionSeparator); if ((fr.countTokens() > 1) && (fieldDef.getMaxMultiplicity() == MaxMult.ONE)) throwOrWriteMessage("Field " + (fieldPos + 1) + " of segment " + segmentName + " cannot repeat."); while (fr.hasMoreTokens()) writeField(segElement,fr.nextToken(),fieldDef,fieldPos); } else throwOrWriteMessage("Segment " + segmentName + " has more than " + fieldDefs.size() + " fields"); } } } /** * @param segElement Element for the segment * @param fieldText text of one field (non-empty) * @param fieldDef ElementDef defining the field */ private void writeField(Element segElement,String fieldText, ElementDef fieldDef,int fieldPos) throws MapperException { String fieldName = fieldDef.getName(); // System.out.println("Field " + fieldName + ": '" + fieldText + "'"); Element fieldElement = null; // may turn out to be a text element or not fieldDef = expandElementDefinition(fieldDef); List<ElementDef> componentDefs = fieldDef.getChildElements(); // field data type is composite and has components if ((componentDefs != null) && (componentDefs.size() > 0)) { fieldElement = XMLUtil.newElement(doc, fieldName); // field Element contains no text directly StringTokenizer st = new StringTokenizer(fieldText,componentSeparator,true); // return all '^' as tokens int comp = 0; boolean exceededLength = false; while (st.hasMoreTokens()) { String token = st.nextToken(); // track component position if (token.equals(componentSeparator)) comp++; else // non-empty string before or between or after '^' { if (comp < componentDefs.size()) { ElementDef compDef = componentDefs.get(comp); writeComponent(fieldElement,token,compDef); } else // failure condition { exceededLength = true; if (!st.hasMoreTokens()) comp++; // if there is a text field after the last separator } } } if (exceededLength) throwOrWriteMessage("Field '" + fieldName + "' ( field no. " + (fieldPos + 1) + " '" + fieldDef.getDescription() + "', type '" + fieldDef.getType() + "') of segment " + XMLUtil.getLocalName(segElement) + " '" + fieldText + "' " + " has " + comp + " components, more than allowed " + componentDefs.size()); } // field data type is elementary and has no components else { fieldElement = XMLUtil.textElement(doc, fieldName, fieldText); } segElement.appendChild(fieldElement); } /** * @param fieldElement XML Element for the field * @param component String for the component * @param compDef ElementDef defining the component structure * @throws MapperException */ private void writeComponent(Element fieldElement,String component,ElementDef compDef) throws MapperException { String compName = compDef.getName(); Element compElement = null; compDef = expandElementDefinition(compDef); List<ElementDef> subComponentDefs = compDef.getChildElements(); // component data type is composite and has sub-components if ((subComponentDefs != null) && (subComponentDefs.size() > 0)) { compElement = XMLUtil.newElement(doc, compName); // component Element contains no text directly StringTokenizer st = new StringTokenizer(component,subComponentSeparator,true); // return all '~^' as tokens int comp = 0; while (st.hasMoreTokens()) { String token = st.nextToken(); // track component position if (token.equals(subComponentSeparator)) comp++; else // non-empty string before or between '~' { if (comp < subComponentDefs.size()) { ElementDef subCompDef = subComponentDefs.get(comp); Element subComp = XMLUtil.textElement(doc, subCompDef.getName(), token); compElement.appendChild(subComp); } else throwOrWriteMessage("Component has more than " + subComponentDefs.size() + " sub-components"); } } } else { compElement = XMLUtil.textElement(doc, compName, component); } fieldElement.appendChild(compElement); } private ElementDef getChildElementDef(ElementDef parent, int position) { ElementDef child = null; List<ElementDef> children = parent.getChildElements(); if ((position > -1) && (position < children.size()))child = children.get(position); return child; } /** * Expand the structure definition of any ElementDef * @param elementDef */ private ElementDef expandElementDefinition(ElementDef elementDef) throws MapperException { ElementDef newElDef = elementDef; if (!elementDef.isExpanded()) { String type = elementDef.getType(); newElDef = V2Structure.typeStructure(type); if (newElDef == null) throw new MapperException("Cannot find structure definition of type '" + type + "'"); newElDef.setExpanded(true); /* retain the name and the description of the unexpanded element - as the type definition may have been taken * from a different usage of the type in the MWB file */ newElDef.setName(elementDef.getName()); newElDef.setDescription(elementDef.getDescription()); } return newElDef; } //---------------------------------------------------------------------------------------------------- // writing a V2 message instance in bar-hat notation //---------------------------------------------------------------------------------------------------- /** * @param IFolder a folder in the Eclipse workspace * @param V2Root root element of a V2.XML message or segment * @param fileName name of the file to be created * Creates a file containing the message in 'bar-hat' form */ public void makeBarCodedForm(IFolder folder, Element V2Root, String fileName) throws MapperException { String[] messageText = transformOut(V2Root); if (messageText.length < 1) throw new MapperException("V2 message text has no lines"); IFile theFile = folder.getFile(fileName); try{ InputStream firstLine = EclipseFileUtil.textStream(messageText[0]); theFile.create(firstLine, false, null); for (int line = 1; line < messageText.length; line++) EclipseFileUtil.appendLine(messageText[line], theFile); } catch (Exception ex) {throw new MapperException(ex.getMessage());} } /** * @param V2Root Root element of a V2.xml message or segment * @return String array of segment text */ public String[] transformOut(Element V2Root) throws MapperException { /* currently there is no check of the top Element name. For some messages it * is not the message name; e.g. ADT_A28 has top element name ADT_A05. */ // check there is a V2 namespace declaration on the top Element if (!checkV2Namespace(V2Root)) throw new MapperException ("V2.XML document does not declare the V2 namespace '" + V2_NAMESPACE_URI + "'"); ArrayList<String> barForm = new ArrayList<String>(); // can also handle single segments if (isSegmentElement(V2Root)) barForm.add(getSegmentText(V2Root)); // iterate over segments and segment groups, picking out segments else for (Iterator<Element> it = XMLUtil.childElements(V2Root).iterator();it.hasNext();) { Element segEl = it.next(); // segments at the top level if (isSegmentElement(segEl)) barForm.add(getSegmentText(segEl)); else handleSegmentGroup(segEl,barForm); } String[] result = (String[])barForm.toArray(new String[barForm.size()]); // add a final '|' to the final segment of the result result[result.length -1] = result[result.length -1] + fieldSeparator; return result; } /** * @param root root element of some XML * @return true id the root element (or some element beneath it) * declares the V2 namespace */ private boolean checkV2Namespace(Element root) { boolean hasV2Namespace = false; try{ NamespaceSet nss = XMLUtil.getNameSpaceSet(root); if (nss.getByURI(V2_NAMESPACE_URI) != null) hasV2Namespace = true; } catch (Exception ex) {} return hasV2Namespace; } /** * add text for all segments directly in this group, or in groups in this group * @param segEl Element representing a segment group * @param barForm ArrayList of text segments * @throws MapperException */ private void handleSegmentGroup(Element segEl,ArrayList<String> barForm) throws MapperException { for (Iterator<Element> ig = XMLUtil.childElements(segEl).iterator();ig.hasNext();) { Element child = ig.next(); if (isSegmentElement(child)) barForm.add(getSegmentText(child)); else handleSegmentGroup(child,barForm); } } /** * @param el * @return true is el is a segment element. * Its name must be three characters, and it must have a child element whose * name is the same three characters followed by '.N' where N * can be read as an Integer */ private boolean isSegmentElement(Element el) { boolean isSegment = false; String segName = XMLUtil.getLocalName(el); if (segName.length() == 3) { Vector<Element> children = XMLUtil.childElements(el); if ((children != null) && (children.size() > 0)) { String childName = XMLUtil.getLocalName(children.get(0)); StringTokenizer st = new StringTokenizer(childName,"."); if ((st.countTokens() == 2) && (st.nextToken().equals(segName))) try { new Integer(st.nextToken()); isSegment = true; // it is a segment only if this throws no exception } catch (Exception ex) {} } } return isSegment; } /** * @param segEl Element representing a segment in V2.xml * @return the text representing the same segment in bar-coded form * @throws MapperException */ private String getSegmentText(Element segEl) throws MapperException { int minSeparator = 0; String segText = XMLUtil.getLocalName(segEl); // segment name first if (segText.equals("MSH")) {readMessageHeader(segEl); minSeparator = 1;} int maxIndex = maxChildIndex(segEl) + 1; String tagRoot = childTagRoot(segEl); for (int index = 1; index < maxIndex; index++) { // one separator before each field, including the first, except for MSH if (index > minSeparator) segText = segText + fieldSeparator; String tagName= tagRoot + "." + index; Vector<Element> fieldEls = XMLUtil.namedChildElements(segEl, tagName); for (int f = 0; f < fieldEls.size(); f++) { // repeated field if (f > 0) segText = segText + repetitionSeparator; segText = segText + getFieldText(fieldEls.get(f)); } } return segText; } /** * * @param el an element, all of whose child elements have tag names XXX.N, where * N is an integer, 1 or higher * @return the maximum value of N found * @throws MapperException if any child tag name does not have this form */ private int maxChildIndex(Element el) throws MapperException { int maxVal = 0; int val = 0; for (Iterator<Element> it = XMLUtil.childElements(el).iterator();it.hasNext();) { String fieldName = XMLUtil.getLocalName(it.next()); StringTokenizer st = new StringTokenizer(fieldName,"."); if (st.countTokens() != 2) throw new MapperException("Tag name '" + fieldName + "' does not have one '.'"); st.nextToken(); try {val = new Integer(st.nextToken()).intValue();} catch (Exception ex) {throw new MapperException("Cannot extract field number from tag name '" + fieldName + "': " + ex.getMessage());} if (val > maxVal) maxVal = val; } return maxVal; } private String childTagRoot(Element el) throws MapperException { String tag = ""; String nextTag = ""; for (Iterator<Element> it = XMLUtil.childElements(el).iterator();it.hasNext();) { String fieldName = XMLUtil.getLocalName(it.next()); StringTokenizer st = new StringTokenizer(fieldName,"."); if (st.countTokens() != 2) throw new MapperException("Tag name '" + fieldName + "' does not have one '.'"); nextTag = st.nextToken(); if (tag.equals("")) tag = nextTag; if (!tag.equals(nextTag)) throw new MapperException("V2 Child tag names should be the same, but are'" + tag + "' and '" + nextTag + "'"); } return tag; } /** * @param fieldEl Element representing a field in V2.xml * @return text representing the field in bar-encoded form * @throws MapperException */ private String getFieldText(Element fieldEl) throws MapperException { String fieldText = ""; int maxIndex = maxChildIndex(fieldEl) + 1; String tagRoot = childTagRoot(fieldEl); if (!tagRoot.equals("")) { for (int index = 1; index < maxIndex; index++) { if (index > 1) fieldText = fieldText + componentSeparator; String tagName = tagRoot + "." + index; Element compEl = XMLUtil.firstNamedChild(fieldEl, tagName); if (compEl != null) fieldText = fieldText + getComponentText(compEl); } } else fieldText = XMLUtil.getText(fieldEl); // the separator in the place of MSH.1 will get added as a separator if (XMLUtil.getLocalName(fieldEl).equals("MSH.1")) fieldText = ""; return fieldText; } /** * @param componentEl Element representing a component in V2.xml * @return text representing the component in bar-encoded form * @throws MapperException */ private String getComponentText(Element componentEl) throws MapperException { String componentText = ""; Vector<Element> subComponents = XMLUtil.childElements(componentEl); if ((subComponents != null) && (subComponents.size() > 0)) { int lastSubComponentFound = 0; int subComponentNumber = 0; for (Iterator<Element> ic = subComponents.iterator(); ic.hasNext();) { Element subCompEl = ic.next(); String subCompName = XMLUtil.getLocalName(subCompEl); StringTokenizer parts = new StringTokenizer(subCompName,"."); parts.nextToken(); // data type name try {subComponentNumber = new Integer(parts.nextToken()).intValue();} catch (Exception ex) {throw new MapperException("Cannot extract sub-component number from tag name '" + subCompName + "': " + ex.getMessage());} if (lastSubComponentFound != 0) for (int i = lastSubComponentFound; i < subComponentNumber; i++) componentText = componentText + subComponentSeparator; lastSubComponentFound = subComponentNumber; componentText = componentText + XMLUtil.getText(subCompEl); } } else componentText = XMLUtil.getText(componentEl); return componentText; } /** * read and possibly reset any special characters from the message header * @param segEl message header element */ private void readMessageHeader(Element segEl) { Element msh1 = XMLUtil.firstNamedChild(segEl, "MSH.1"); fieldSeparator = XMLUtil.getText(msh1); Element msh2 = XMLUtil.firstNamedChild(segEl, "MSH.2"); String specialChars = XMLUtil.getText(msh2); componentSeparator = specialChars.substring(0,1); repetitionSeparator = specialChars.substring(1,2); escapeCharacter = specialChars.substring(2,3); // don't read the subcomponent separator as '&' has been escaped to '\&' } }