
001package org.hl7.fhir.r5.utils; 002 003import java.io.FileInputStream; 004import java.io.FileNotFoundException; 005import java.io.FileOutputStream; 006import java.io.IOException; 007import java.io.InputStream; 008import java.util.ArrayList; 009import java.util.HashMap; 010import java.util.List; 011import java.util.Map; 012 013import org.hl7.fhir.exceptions.FHIRException; 014import org.hl7.fhir.r5.extensions.ExtensionDefinitions; 015import org.hl7.fhir.r5.formats.IParser.OutputStyle; 016import org.hl7.fhir.r5.formats.JsonParser; 017import org.hl7.fhir.r5.model.CanonicalResource; 018import org.hl7.fhir.r5.model.ConceptMap; 019import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupComponent; 020import org.hl7.fhir.r5.model.ConceptMap.OtherElementComponent; 021import org.hl7.fhir.r5.model.ConceptMap.SourceElementComponent; 022import org.hl7.fhir.r5.model.ConceptMap.TargetElementComponent; 023import org.hl7.fhir.r5.model.DateTimeType; 024import org.hl7.fhir.r5.model.Enumerations.ConceptMapRelationship; 025import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; 026import org.hl7.fhir.r5.model.IdType; 027import org.hl7.fhir.r5.model.StringType; 028import org.hl7.fhir.r5.model.StructureMap; 029import org.hl7.fhir.r5.model.StructureMap.StructureMapGroupComponent; 030import org.hl7.fhir.r5.model.StructureMap.StructureMapGroupRuleComponent; 031import org.hl7.fhir.r5.model.StructureMap.StructureMapGroupRuleDependentComponent; 032import org.hl7.fhir.r5.model.StructureMap.StructureMapGroupRuleSourceComponent; 033import org.hl7.fhir.r5.model.StructureMap.StructureMapGroupRuleTargetComponent; 034import org.hl7.fhir.r5.model.StructureMap.StructureMapTransform; 035import org.hl7.fhir.r5.model.UrlType; 036import org.hl7.fhir.r5.utils.structuremap.StructureMapUtilities; 037import org.hl7.fhir.utilities.CSVReader; 038import org.hl7.fhir.utilities.FileUtilities; 039import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage; 040import org.hl7.fhir.utilities.Utilities; 041import org.hl7.fhir.utilities.filesystem.ManagedFileAccess; 042 043@MarkedToMoveToAdjunctPackage 044public class MappingSheetParser { 045 046 public class MappingRow { 047 private String sequence; 048 private String identifier; 049 private String name; 050 private String dataType; 051 private String cardinality; 052 private String condition; 053 private String attribute; 054 private String type; 055 private String minMax; 056 private String dtMapping; 057 private String vocabMapping; 058 private String derived; 059 private String derivedMapping; 060 private String comments; 061 public String getSequence() { 062 return sequence; 063 } 064 public String getIdentifier() { 065 return identifier; 066 } 067 public String getName() { 068 return name; 069 } 070 public String getDataType() { 071 return dataType; 072 } 073 public String getCardinality() { 074 return cardinality; 075 } 076 public int getCardinalityMin() { 077 return Integer.parseInt(cardinality.split("\\.")[0]); 078 } 079 public String getCardinalityMax() { 080 return cardinality.split("\\.")[2]; 081 } 082 public String getCondition() { 083 return condition; 084 } 085 public String getAttribute() { 086 return attribute; 087 } 088 public String getType() { 089 return type; 090 } 091 public String getMinMax() { 092 return minMax; 093 } 094 public String getDtMapping() { 095 return dtMapping; 096 } 097 public String getVocabMapping() { 098 return vocabMapping; 099 } 100 public String getDerived() { 101 return derived; 102 } 103 public String getDerivedMapping() { 104 return derivedMapping; 105 } 106 public String getComments() { 107 return comments; 108 } 109 110 } 111 112 113 private List<MappingRow> rows = new ArrayList<>(); 114 private Map<String, String> metadata = new HashMap<>(); 115 116 public MappingSheetParser() { 117 super(); 118 } 119 120 public void parse(InputStream stream, String name) throws FHIRException, IOException { 121 CSVReader csv = new CSVReader(stream); 122 checkHeaders1(csv, name); 123 checkHeaders2(csv, name); 124 while (csv.line()) { 125 processRow(csv); 126 } 127 } 128 129 private void checkHeaders1(CSVReader csv, String name) throws FHIRException, IOException { 130 csv.readHeaders(); 131 csv.checkColumn(1, "HL7 v2", "Mapping Sheet "+name); 132 csv.checkColumn(6, "Condition (IF True)", "Mapping Sheet "+name); 133 csv.checkColumn(7, "HL7 FHIR", "Mapping Sheet "+name); 134 csv.checkColumn(14, "Comments", "Mapping Sheet "+name); 135 csv.checkColumn(16, "Name", "Mapping Sheet "+name); 136 csv.checkColumn(17, "Value", "Mapping Sheet "+name); 137 } 138 139 private void checkHeaders2(CSVReader csv, String name) throws FHIRException, IOException { 140 csv.readHeaders(); 141 csv.checkColumn(1, "Display Sequence", "Mapping Sheet "+name); 142 csv.checkColumn(2, "Identifier", "Mapping Sheet "+name); 143 csv.checkColumn(3, "Name", "Mapping Sheet "+name); 144 csv.checkColumn(4, "Data Type", "Mapping Sheet "+name); 145 csv.checkColumn(5, "Cardinality", "Mapping Sheet "+name); 146 csv.checkColumn(7, "FHIR Attribute", "Mapping Sheet "+name); 147 csv.checkColumn(8, "Data Type", "Mapping Sheet "+name); 148 csv.checkColumn(9, "Cardinality", "Mapping Sheet "+name); 149 csv.checkColumn(10, "Data Type Mapping", "Mapping Sheet "+name); 150 csv.checkColumn(11, "Vocabulary Mapping\n(IS, ID, CE, CNE, CWE)", "Mapping Sheet "+name); 151 csv.checkColumn(12, "Derived Mapping", "Mapping Sheet "+name); 152 } 153 154 private void processRow(CSVReader csv) { 155 MappingRow mr = new MappingRow(); 156 mr.sequence = csv.value(1); 157 mr.identifier = csv.value(2); 158 mr.name = csv.value(3); 159 mr.dataType = csv.value(4); 160 mr.cardinality = csv.value(5); 161 mr.condition = csv.value(6); 162 mr.attribute = csv.value(7); 163 mr.type = csv.value(8); 164 mr.minMax = csv.value(9); 165 mr.dtMapping = csv.value(10); 166 mr.vocabMapping = csv.value(11); 167 mr.derived = csv.value(12); 168 if (!Utilities.noString(mr.derived)) { 169 String[] s = mr.derived.split("\\="); 170 mr.derived = s[0].trim(); 171 mr.derivedMapping = s[1].trim(); 172 } 173 mr.comments = csv.value(14); 174 rows.add(mr); 175 if (!org.hl7.fhir.utilities.Utilities.noString(csv.value(16))) 176 metadata.put(csv.value(16), csv.value(17)); 177 } 178 179 public List<MappingRow> getRows() { 180 return rows; 181 } 182 183 public ConceptMap getConceptMap() throws FHIRException { 184 ConceptMap map = new ConceptMap(); 185 loadMetadata(map); 186 if (metadata.containsKey("copyright")) 187 map.setCopyright(metadata.get("copyright")); 188 for (MappingRow row : rows) { 189 SourceElementComponent element = map.getGroupFirstRep().addElement(); 190 element.setCode(row.getIdentifier()); 191 element.setId(row.getSequence()); 192 element.setDisplay(row.getName()+" : "+row.getDataType()+" ["+row.getCardinality()+"]"); 193 element.addExtension(ExtensionDefinitions.EXT_MAPPING_NAME, new StringType(row.getName())); 194 element.addExtension(ExtensionDefinitions.EXT_MAPPING_TYPE, new StringType(row.getDataType())); 195 element.addExtension(ExtensionDefinitions.EXT_MAPPING_CARD, new StringType(row.getCardinality())); 196 if ("N/A".equals(row.getAttribute())) 197 element.setNoMap(true); 198 else { 199 element.getTargetFirstRep().setRelationship(ConceptMapRelationship.RELATEDTO); 200 if (row.getCondition() != null) 201 element.getTargetFirstRep().addDependsOn().setAttribute("http://hl7.org/fhirpath").setValue(new StringType(processCondition(row.getCondition()))); 202 element.getTargetFirstRep().setCode(row.getAttribute()); 203 element.getTargetFirstRep().setDisplay(row.getType()+" : ["+row.getMinMax()+"]"); 204 element.getTargetFirstRep().addExtension(ExtensionDefinitions.EXT_MAPPING_TGTTYPE, new StringType(row.getType())); 205 element.getTargetFirstRep().addExtension(ExtensionDefinitions.EXT_MAPPING_TGTCARD, new StringType(row.getMinMax())); 206 if (row.getDerived() != null) 207 element.getTargetFirstRep().getProductFirstRep().setAttribute(row.getDerived()).setValue(new StringType(row.getDerivedMapping())); 208 if (row.getComments() != null) 209 element.getTargetFirstRep().setComment(row.getComments()); 210 if (row.getDtMapping() != null) 211 element.getTargetFirstRep().addExtension("http://hl7.org/fhir/StructureDefinition/ConceptMap-type-mapping", new UrlType("todo#"+row.getDtMapping())); 212 if (row.getVocabMapping() != null) 213 element.getTargetFirstRep().addExtension("http://hl7.org/fhir/StructureDefinition/ConceptMap-vocab-mapping", new UrlType("todo#"+row.getVocabMapping())); 214 } 215 } 216 return map; 217 } 218 219 private String processCondition(String condition) { 220 if (condition.startsWith("IF ") && condition.endsWith(" IS VALUED")) 221 return "`"+condition.substring(4, condition.length()-10)+"`.exists()"; 222 if (condition.startsWith("IF ") && condition.endsWith(" DOES NOT EXIST")) 223 return "`"+condition.substring(4, condition.length()-15)+"`.exists()"; 224 throw new Error("not processed yet: "+condition); 225 } 226 227 private void loadMetadata(CanonicalResource mr) throws FHIRException { 228 if (metadata.containsKey("id")) 229 mr.setId(metadata.get("id")); 230 if (metadata.containsKey("url")) 231 mr.setUrl(metadata.get("url")); 232 if (metadata.containsKey("name")) 233 mr.setName(metadata.get("name")); 234 if (metadata.containsKey("title")) 235 mr.setTitle(metadata.get("title")); 236 if (metadata.containsKey("version")) 237 mr.setVersion(metadata.get("version")); 238 if (metadata.containsKey("status")) 239 mr.setStatus(PublicationStatus.fromCode(metadata.get("status"))); 240 if (metadata.containsKey("date")) 241 mr.setDateElement(new DateTimeType(metadata.get("date"))); 242 if (metadata.containsKey("publisher")) 243 mr.setPublisher(metadata.get("publisher")); 244 if (metadata.containsKey("description")) 245 mr.setDescription(metadata.get("description")); 246 } 247 248 public StructureMap getStructureMap() throws FHIRException { 249 StructureMap map = new StructureMap(); 250 loadMetadata(map); 251 if (metadata.containsKey("copyright")) 252 map.setCopyright(metadata.get("copyright")); 253 StructureMapGroupComponent grp = map.addGroup(); 254 for (MappingRow row : rows) { 255 StructureMapGroupRuleComponent rule = grp.addRule(); 256 rule.setName(row.getSequence()); 257 StructureMapGroupRuleSourceComponent src = rule.getSourceFirstRep(); 258 src.setContext("src"); 259 src.setElement(row.getIdentifier()); 260 src.setMin(row.getCardinalityMin()); 261 src.setMax(row.getCardinalityMax()); 262 src.setType(row.getDataType()); 263 src.addExtension(ExtensionDefinitions.EXT_MAPPING_NAME, new StringType(row.getName())); 264 if (row.getCondition() != null) { 265 src.setCheck(processCondition(row.getCondition())); 266 } 267 StructureMapGroupRuleTargetComponent tgt = rule.getTargetFirstRep(); 268 tgt.setContext("tgt"); 269 tgt.setElement(row.getAttribute()); 270 tgt.addExtension(ExtensionDefinitions.EXT_MAPPING_TGTTYPE, new StringType(row.getType())); 271 tgt.addExtension(ExtensionDefinitions.EXT_MAPPING_TGTCARD, new StringType(row.getMinMax())); 272 if (row.getDtMapping() != null) { 273 src.setVariable("s"); 274 tgt.setVariable("t"); 275 tgt.setTransform(StructureMapTransform.CREATE); 276 StructureMapGroupRuleDependentComponent dep = rule.addDependent(); 277 dep.setName(row.getDtMapping()); 278 dep.addParameter().setValue(new IdType("s")); 279 dep.addParameter().setValue(new IdType("t")); 280 } else if (row.getVocabMapping() != null) { 281 tgt.setTransform(StructureMapTransform.TRANSLATE); 282 tgt.addParameter().setValue(new StringType(row.getVocabMapping())); 283 tgt.addParameter().setValue(new IdType("src")); 284 } else { 285 tgt.setTransform(StructureMapTransform.COPY); 286 } 287 rule.setDocumentation(row.getComments()); 288 if (row.getDerived() != null) { 289 tgt = rule.addTarget(); 290 tgt.setContext("tgt"); 291 tgt.setElement(row.getDerived()); 292 tgt.setTransform(StructureMapTransform.COPY); 293 tgt.addParameter().setValue(new StringType(row.getDerivedMapping())); 294 } 295 } 296 return map; 297 } 298 299 public boolean isSheet(ConceptMap cm) { 300 if (cm.getGroup().size() != 1) 301 return false; 302 ConceptMapGroupComponent grp = cm.getGroupFirstRep(); 303 for (SourceElementComponent e : grp.getElement()) { 304 if (!e.hasExtension(ExtensionDefinitions.EXT_MAPPING_TYPE)) 305 return false; 306 } 307 return true; 308 } 309 310 public String genSheet(ConceptMap cm) throws FHIRException { 311 StringBuilder b = new StringBuilder(); 312 readConceptMap(cm); 313 b.append("<table class=\"grid\">\r\n"); 314 addHeaderRow1(b); 315 addHeaderRow2(b); 316 for (MappingRow row : rows) 317 addRow(b, row); 318 b.append("</table>\r\n"); 319 return b.toString(); 320 } 321 322 private void addRow(StringBuilder b, MappingRow row) { 323 b.append(" <tr>"); 324 b.append("<td>"+Utilities.escapeXml(nn(row.sequence))+"</td>"); 325 b.append("<td>"+Utilities.escapeXml(nn(row.identifier))+"</td>"); 326 b.append("<td>"+Utilities.escapeXml(nn(row.name))+"</td>"); 327 b.append("<td>"+Utilities.escapeXml(nn(row.dataType))+"</td>"); 328 b.append("<td>"+Utilities.escapeXml(nn(row.cardinality))+"</td>"); 329 b.append("<td>"+Utilities.escapeXml(nn(row.condition))+"</td>"); 330 b.append("<td>"+Utilities.escapeXml(nn(row.attribute))+"</td>"); 331 b.append("<td>"+Utilities.escapeXml(nn(row.type))+"</td>"); 332 b.append("<td>"+Utilities.escapeXml(nn(row.minMax))+"</td>"); 333 b.append("<td>"+Utilities.escapeXml(nn(row.dtMapping))+"</td>"); 334 b.append("<td>"+Utilities.escapeXml(nn(row.vocabMapping))+"</td>"); 335 if (row.derived != null) 336 b.append("<td>"+Utilities.escapeXml(nn(row.derived+"="+row.derivedMapping))+"</td>"); 337 else 338 b.append("<td></td>"); 339 b.append("<td>"+Utilities.escapeXml(nn(row.comments))+"</td>"); 340 b.append("</tr>\r\n"); 341 342 } 343 344 private String nn(String s) { 345 return s == null ? "" : s; 346 } 347 348 private void addHeaderRow1(StringBuilder b) { 349 b.append(" <tr>"); 350 b.append("<td colspan=\"5\" style=\"background-color: lightgreen\"><b>v2</b></td>"); 351 b.append("<td colspan=\"1\"><b>Condition</b></td>"); 352 b.append("<td colspan=\"6\" style=\"background-color: orange\"><b>FHIR</b></td>"); 353 b.append("<td colspan=\"1\"><b>Comments</b></td>"); 354 b.append("</tr>\r\n"); 355 } 356 357 private void addHeaderRow2(StringBuilder b) { 358 b.append(" <tr>"); 359 b.append("<td style=\"background-color: lightgreen\"><b>Display Sequence</b></td>"); 360 b.append("<td style=\"background-color: lightgreen\"><b>Identifier</b></td>"); 361 b.append("<td style=\"background-color: lightgreen\"><b>Name</b></td>"); 362 b.append("<td style=\"background-color: lightgreen\"><b>Data Type</b></td>"); 363 b.append("<td style=\"background-color: lightgreen\"><b>Cardinality</b></td>"); 364 b.append("<td><b></b></td>"); 365 b.append("<td style=\"background-color: orange\"><b>FHIR Attribute</b></td>"); 366 b.append("<td style=\"background-color: orange\"><b>Data Type</b></td>"); 367 b.append("<td style=\"background-color: orange\"><b>Cardinality</b></td>"); 368 b.append("<td style=\"background-color: orange\"><b>Data Type Mapping</b></td>"); 369 b.append("<td style=\"background-color: orange\"><b>Vocabulary Mapping</b></td>"); 370 b.append("<td style=\"background-color: orange\"><b>Derived Mapping</b></td>"); 371 b.append("<td><b></b></td>"); 372 b.append("</tr>\r\n"); 373 } 374 375 private void readConceptMap(ConceptMap cm) throws FHIRException { 376 for (ConceptMapGroupComponent g : cm.getGroup()) { 377 for (SourceElementComponent e : g.getElement()) { 378 if (e.hasId() && e.getTarget().size() == 1 && e.hasExtension(ExtensionDefinitions.EXT_MAPPING_TYPE)) { 379 TargetElementComponent t = e.getTargetFirstRep(); 380 MappingRow row = new MappingRow(); 381 row.sequence = e.getId(); 382 row.identifier = e.getCode(); 383 row.name = e.getExtensionString(ExtensionDefinitions.EXT_MAPPING_NAME); 384 row.dataType = e.getExtensionString(ExtensionDefinitions.EXT_MAPPING_TYPE); 385 row.cardinality = e.getExtensionString(ExtensionDefinitions.EXT_MAPPING_CARD); 386 if (e.getNoMap() == true) { 387 row.attribute = "N/A"; 388 } else { 389 OtherElementComponent dep = getDependency(t, "http://hl7.org/fhirpath"); 390 if (dep != null) 391 row.condition = dep.getValue().primitiveValue(); 392 row.attribute = t.getCode(); 393 row.type = t.getExtensionString(ExtensionDefinitions.EXT_MAPPING_TGTTYPE); 394 row.minMax = t.getExtensionString(ExtensionDefinitions.EXT_MAPPING_TGTCARD); 395 row.dtMapping = t.getExtensionString("http://hl7.org/fhir/StructureDefinition/ConceptMap-type-mapping"); 396 row.vocabMapping = t.getExtensionString("http://hl7.org/fhir/StructureDefinition/ConceptMap-vocab-mapping"); 397 if (t.getProduct().size() > 0) { 398 row.derived = t.getProductFirstRep().getAttribute(); 399 row.derivedMapping = t.getProductFirstRep().getValue().primitiveValue(); 400 } 401 } 402 row.comments = t.getComment(); 403 rows.add(row); 404 } 405 } 406 } 407 } 408 409 410 private OtherElementComponent getDependency(TargetElementComponent t, String prop) { 411 for (OtherElementComponent dep : t.getDependsOn()) { 412 if (prop.equals(dep.getAttribute())) 413 return dep; 414 } 415 return null; 416 } 417 418 private static final String PFX = "<html><link rel=\"stylesheet\" href=\"file:c:\\work\\org.hl7.fhir\\build\\publish\\fhir.css\"/></head><body>\r\n"; 419 private static final String SFX = "<body></html>"; 420 public static void main(String[] args) throws FileNotFoundException, IOException, FHIRException { 421 MappingSheetParser parser = new MappingSheetParser(); 422 parser.parse(ManagedFileAccess.inStream(Utilities.path("[tmp]", "v2-pid.csv")), "v2-pid.csv"); 423 ConceptMap cm = parser.getConceptMap(); 424 StructureMap sm = parser.getStructureMap(); 425 new JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(ManagedFileAccess.outStream(Utilities.path("[tmp]", "sm.json")), sm); 426 new JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(ManagedFileAccess.outStream(Utilities.path("[tmp]", "cm.json")), cm); 427 FileUtilities.stringToFile(StructureMapUtilities.render(sm), Utilities.path("[tmp]", "sm.txt")); 428 FileUtilities.stringToFile(PFX+parser.genSheet(cm)+SFX, Utilities.path("[tmp]", "map.html")); 429 } 430 431}