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