001package org.hl7.fhir.r5.renderers.spreadsheets; 002 003import java.io.IOException; 004import java.io.OutputStream; 005 006import org.hl7.fhir.exceptions.DefinitionException; 007import org.hl7.fhir.r5.model.ElementDefinition; 008import org.hl7.fhir.r5.model.StructureDefinition; 009import org.hl7.fhir.utilities.i18n.I18nConstants; 010 011 012import java.io.ByteArrayOutputStream; 013import java.io.IOException; 014import java.io.OutputStream; 015import java.io.UnsupportedEncodingException; 016import java.util.ArrayList; 017import java.util.Enumeration; 018import java.util.HashMap; 019import java.util.List; 020import java.util.Map; 021 022import org.apache.poi.ss.usermodel.BorderStyle; 023import org.apache.poi.ss.usermodel.Cell; 024import org.apache.poi.ss.usermodel.CellStyle; 025import org.apache.poi.ss.usermodel.ConditionalFormattingRule; 026import org.apache.poi.ss.usermodel.FillPatternType; 027import org.apache.poi.ss.usermodel.Font; 028import org.apache.poi.ss.usermodel.FontFormatting; 029import org.apache.poi.ss.usermodel.IndexedColors; 030import org.apache.poi.ss.usermodel.PatternFormatting; 031import org.apache.poi.ss.usermodel.Row; 032import org.apache.poi.ss.usermodel.Sheet; 033import org.apache.poi.ss.usermodel.SheetConditionalFormatting; 034import org.apache.poi.ss.usermodel.VerticalAlignment; 035import org.apache.poi.ss.usermodel.Workbook; 036import org.apache.poi.ss.util.CellAddress; 037import org.apache.poi.ss.util.CellRangeAddress; 038import org.apache.poi.xssf.usermodel.XSSFRow; 039import org.apache.poi.xssf.usermodel.XSSFSheet; 040import org.apache.poi.xssf.usermodel.XSSFWorkbook; 041import org.checkerframework.common.reflection.qual.ForName; 042import org.hl7.fhir.r5.formats.IParser.OutputStyle; 043import org.hl7.fhir.r5.context.IWorkerContext; 044import org.hl7.fhir.r5.context.SimpleWorkerContext; 045import org.hl7.fhir.r5.formats.JsonParser; 046import org.hl7.fhir.r5.formats.XmlParser; 047import org.hl7.fhir.r5.model.CanonicalType; 048import org.hl7.fhir.r5.model.Coding; 049import org.hl7.fhir.r5.model.DataType; 050import org.hl7.fhir.r5.model.ElementDefinition; 051import org.hl7.fhir.r5.model.ElementDefinition.AggregationMode; 052import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintComponent; 053import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionMappingComponent; 054import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent; 055import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; 056import org.hl7.fhir.r5.model.IdType; 057import org.hl7.fhir.r5.model.StringType; 058import org.hl7.fhir.r5.model.StructureDefinition; 059import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionContextComponent; 060import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionMappingComponent; 061import org.hl7.fhir.r5.model.UriType; 062import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 063import org.hl7.fhir.utilities.TextStreamWriter; 064import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTAutoFilter; 065import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCustomFilter; 066import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCustomFilters; 067import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTFilterColumn; 068import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTFilters; 069import org.openxmlformats.schemas.spreadsheetml.x2006.main.STFilterOperator; 070 071 072public class StructureDefinitionSpreadsheetGenerator extends CanonicalSpreadsheetGenerator { 073 private XmlParser xml = new XmlParser(); 074 private JsonParser json = new JsonParser(); 075 private boolean asXml; 076 private boolean hideMustSupportFalse; 077 private List<StructureDefinitionMappingComponent> mapKeys = new ArrayList<StructureDefinitionMappingComponent>(); 078 079 private static String[] titles = { 080 "ID", "Path", "Slice Name", "Alias(s)", "Label", "Min", "Max", "Must Support?", "Is Modifier?", "Is Summary?", "Type(s)", "Short", 081 "Definition", "Comments", "Requirements", "Default Value", "Meaning When Missing", "Fixed Value", "Pattern", "Example", 082 "Minimum Value", "Maximum Value", "Maximum Length", "Binding Strength", "Binding Description", "Binding Value Set", "Code", 083 "Slicing Discriminator", "Slicing Description", "Slicing Ordered", "Slicing Rules", "Base Path", "Base Min", "Base Max", 084 "Condition(s)", "Constraint(s)"}; 085 086 public StructureDefinitionSpreadsheetGenerator(IWorkerContext context, boolean valuesAsXml, boolean hideMustSupportFalse) { 087 super(context); 088 this.asXml = valuesAsXml; 089 this.hideMustSupportFalse = hideMustSupportFalse; 090 } 091 092 public StructureDefinitionSpreadsheetGenerator renderStructureDefinition(StructureDefinition sd, boolean forMultiple) throws Exception { 093 if (sd == null) { 094 System.out.println("no structure!"); 095 } 096 if (!sd.hasSnapshot()) { 097 throw new DefinitionException(context.formatMessage(I18nConstants.NEEDS_A_SNAPSHOT)); 098 } 099 addStructureDefinitionMetadata(renderCanonicalResource(sd, forMultiple), sd); 100 Sheet sheet = forMultiple && hasSheet("Elements") ? getSheet("Elements") : makeSheet("Elements"); 101 102 if (sheet.getPhysicalNumberOfRows() == 0) { 103 Row headerRow = sheet.createRow(0); 104 int coffset = forMultiple ? 1 : 0; 105 for (int i = 0; i < titles.length; i++) { 106 if (forMultiple) { 107 addCell(headerRow, 0, "Structure.ID", styles.get("header")); 108 } 109 addCell(headerRow, i+coffset, titles[i], styles.get("header")); 110 } 111 if (!forMultiple) { 112 int i = titles.length - 1; 113 for (StructureDefinitionMappingComponent map : sd.getMapping()) { 114 i++; 115 addCell(headerRow, i+coffset, "Mapping: " + map.getName(), styles.get("header")); 116 } 117 } 118 } 119 120 for (ElementDefinition child : sd.getSnapshot().getElement()) { 121 processElement(sheet, sd, child, forMultiple); 122 } 123 if (!forMultiple) { 124 configureSheet(sheet, sd); 125 } 126 return this; 127 } 128 129 public StructureDefinitionSpreadsheetGenerator configure() throws Exception { 130 Sheet sheet = hasSheet("Elements") ? getSheet("Elements") : makeSheet("Elements"); 131 configureSheet(sheet, null); 132 return this; 133 } 134 135 136 private void addStructureDefinitionMetadata(Sheet sheet, StructureDefinition sd) { 137 for (Coding k : sd.getKeyword()) { 138 addMetadataRow(sheet, "Keyword", dr.displayDataType(k)); 139 } 140 addMetadataRow(sheet, "FHIR Version", sd.getFhirVersionElement().asStringValue()); 141 addMetadataRow(sheet, "Kind", sd.getKindElement().asStringValue()); 142 addMetadataRow(sheet, "Type", sd.getType()); 143 addMetadataRow(sheet, "Base Definition", sd.getBaseDefinition()); 144 addMetadataRow(sheet, "Abstract", sd.getAbstractElement().asStringValue()); 145 addMetadataRow(sheet, "Derivation", sd.getDerivationElement().asStringValue()); 146 147 for (StructureDefinitionContextComponent k : sd.getContext()) { 148 addMetadataRow(sheet, "Context", k.getTypeElement().asStringValue()+":"+k.getExpression()); 149 } 150 for (StringType k : sd.getContextInvariant()) { 151 addMetadataRow(sheet, "Context Inv.", k.getValue()); 152 } 153 154 } 155 156 public void processElement(Sheet sheet, StructureDefinition sd, ElementDefinition ed, boolean forMultiple) throws Exception { 157 Row row = sheet.createRow(sheet.getLastRowNum()+1); 158 int i = 0; 159 if (forMultiple) { 160 addCell(row, i++, sd.getId(), styles.get("body")); 161 } 162 addCell(row, i++, ed.getId(), styles.get("body")); 163 addCell(row, i++, ed.getPath()); 164 addCell(row, i++, ed.getSliceName()); 165 addCell(row, i++, itemList(ed.getAlias())); 166 addCell(row, i++, ed.getLabel()); 167 addCell(row, i++, ed.getMin()); 168 addCell(row, i++, ed.getMax()); 169 addCell(row, i++, ed.getMustSupport() ? "Y" : ""); 170 addCell(row, i++, ed.getIsModifier() ? "Y" : ""); 171 addCell(row, i++, ed.getIsSummary() ? "Y" : ""); 172 addCell(row, i++, itemList(ed.getType())); 173 addCell(row, i++, ed.getShort()); 174 addCell(row, i++, ed.getDefinition()); 175 addCell(row, i++, ed.getComment()); 176 addCell(row, i++, ed.getRequirements()); 177 addCell(row, i++, ed.getDefaultValue()!=null ? renderType(ed.getDefaultValue()) : ""); 178 addCell(row, i++, ed.getMeaningWhenMissing()); 179 addCell(row, i++, ed.hasFixed() ? renderType(ed.getFixed()) : ""); 180 addCell(row, i++, ed.hasPattern() ? renderType(ed.getPattern()) : ""); 181 addCell(row, i++, ed.hasExample() ? renderType(ed.getExample().get(0).getValue()) : ""); // todo...? 182 addCell(row, i++, ed.hasMinValue() ? renderType(ed.getMinValue()) : ""); 183 addCell(row, i++, ed.hasMaxValue() ? renderType(ed.getMaxValue()) : ""); 184 addCell(row, i++, (ed.hasMaxLength() ? Integer.toString(ed.getMaxLength()) : "")); 185 if (ed.hasBinding()) { 186 addCell(row, i++, ed.getBinding().getStrength()!=null ? ed.getBinding().getStrength().toCode() : ""); 187 addCell(row, i++, ed.getBinding().getDescription()); 188 if (ed.getBinding().getValueSet()==null) 189 addCell(row, i++, ""); 190 else 191 addCell(row, i++, ed.getBinding().getValueSet()); 192 } else { 193 addCell(row, i++, ""); 194 addCell(row, i++, ""); 195 addCell(row, i++, ""); 196 } 197 addCell(row, i++, itemList(ed.getCode())); 198 if (ed.hasSlicing()) { 199 addCell(row, i++, itemList(ed.getSlicing().getDiscriminator())); 200 addCell(row, i++, ed.getSlicing().getDescription()); 201 addCell(row, i++, ed.getSlicing().getOrdered()); 202 addCell(row, i++, ed.getSlicing().getRules()!=null ? ed.getSlicing().getRules().toCode() : ""); 203 } else { 204 addCell(row, i++, ""); 205 addCell(row, i++, ""); 206 addCell(row, i++, ""); 207 addCell(row, i++, ""); 208 } 209 if (ed.getBase()!=null) { 210 addCell(row, i++, ed.getBase().getPath()); 211 addCell(row, i++, ed.getBase().getMin()); 212 addCell(row, i++, ed.getBase().getMax()); 213 } else { 214 addCell(row, i++, ""); 215 addCell(row, i++, ""); 216 addCell(row, i++, ""); 217 } 218 addCell(row, i++, itemList(ed.getCondition())); 219 addCell(row, i++, itemList(ed.getConstraint())); 220 if (!forMultiple) { 221 for (StructureDefinitionMappingComponent mapKey : sd.getMapping()) { 222 String mapString = ""; 223 for (ElementDefinitionMappingComponent map : ed.getMapping()) { 224 if (map.getIdentity().equals(mapKey.getIdentity())) 225 mapString = map.getMap(); 226 } 227 addCell(row, i++, mapString); 228 } 229 } 230 } 231 232 private String itemList(List l) { 233 StringBuilder s = new StringBuilder(); 234 for (int i =0; i< l.size(); i++) { 235 Object o = l.get(i); 236 String val = ""; 237 if (o instanceof StringType) { 238 val = ((StringType)o).getValue(); 239 } else if (o instanceof UriType) { 240 val = ((UriType)o).getValue(); 241 } else if (o instanceof IdType) { 242 val = ((IdType)o).getValue(); 243 } else if (o instanceof Enumeration<?>) { 244 val = o.toString(); 245 } else if (o instanceof TypeRefComponent) { 246 TypeRefComponent t = (TypeRefComponent)o; 247 val = t.getWorkingCode(); 248 if (val == null) 249 val = ""; 250 if (val.startsWith("http://hl7.org/fhir/StructureDefinition/")) 251 val = val.substring(40); 252 if (t.hasTargetProfile()) 253 val = val+ "(" + canonicalList(t.getTargetProfile()) + ")"; 254 if (t.hasProfile()) 255 val = val + " {" + canonicalList(t.getProfile()) + "}"; 256 if (t.hasAggregation()) 257 val = val + " <<" + aggList(t.getAggregation()) + ">>"; 258 } else if (o instanceof Coding) { 259 Coding t = (Coding)o; 260 val = (t.getSystem()==null ? "" : t.getSystem()) + (t.getCode()==null ? "" : "#" + t.getCode()) + (t.getDisplay()==null ? "" : " (" + t.getDisplay() + ")"); 261 } else if (o instanceof ElementDefinitionConstraintComponent) { 262 ElementDefinitionConstraintComponent c = (ElementDefinitionConstraintComponent)o; 263 val = c.getKey() + ":" + c.getHuman() + " {" + c.getExpression() + "}"; 264 } else if (o instanceof ElementDefinitionSlicingDiscriminatorComponent) { 265 ElementDefinitionSlicingDiscriminatorComponent c = (ElementDefinitionSlicingDiscriminatorComponent)o; 266 val = c.getType().toCode() + ":" + c.getPath() + "}"; 267 268 } else { 269 val = o.toString(); 270 val = val.substring(val.indexOf("[")+1); 271 val = val.substring(0, val.indexOf("]")); 272 } 273 s = s.append(val); 274 if (i == 0) 275 s.append("\n"); 276 } 277 return s.toString(); 278 } 279 280 private String aggList(List<org.hl7.fhir.r5.model.Enumeration<AggregationMode>> list) { 281 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 282 for (org.hl7.fhir.r5.model.Enumeration<AggregationMode> c : list) 283 b.append(c.getValue().toCode()); 284 return b.toString(); 285 } 286 287 private String canonicalList(List<CanonicalType> list) { 288 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder("|"); 289 for (CanonicalType c : list) { 290 String v = c.getValue(); 291 if (v.startsWith("http://hl7.org/fhir/StructureDefinition/")) 292 v = v.substring(40); 293 b.append(v); 294 } 295 return b.toString(); 296 } 297 298 private String renderType(DataType value) throws Exception { 299 if (value == null) 300 return ""; 301 if (value.isPrimitive()) 302 return value.primitiveValue(); 303 304 String s = null; 305 ByteArrayOutputStream bs = new ByteArrayOutputStream(); 306 if (asXml) { 307 xml.setOutputStyle(OutputStyle.PRETTY); 308 xml.compose(bs, "", value); 309 bs.close(); 310 s = bs.toString(); 311 s = s.substring(s.indexOf("\n")+2); 312 } else { 313 json.setOutputStyle(OutputStyle.PRETTY); 314 json.compose(bs, value, ""); 315 bs.close(); 316 s = bs.toString(); 317 } 318 return s; 319 } 320 321 322 public void configureSheet(Sheet sheet, StructureDefinition sd) throws IOException { 323 for (int i=0; i<34; i++) { 324 sheet.autoSizeColumn(i); 325 } 326 sheet.setColumnHidden(2, true); 327 sheet.setColumnHidden(3, true); 328 sheet.setColumnHidden(30, true); 329 sheet.setColumnHidden(31, true); 330 sheet.setColumnHidden(32, true); 331 332 sheet.setColumnWidth(9, columnPixels(20)); 333 sheet.setColumnWidth(11, columnPixels(100)); 334 sheet.setColumnWidth(12, columnPixels(100)); 335 sheet.setColumnWidth(13, columnPixels(100)); 336 sheet.setColumnWidth(15, columnPixels(20)); 337 sheet.setColumnWidth(16, columnPixels(20)); 338 sheet.setColumnWidth(17, columnPixels(20)); 339 sheet.setColumnWidth(18, columnPixels(20)); 340 sheet.setColumnWidth(34, columnPixels(100)); 341 342 if (sd != null) { 343 int i = titles.length - 1; 344 for (StructureDefinitionMappingComponent map : sd.getMapping()) { 345 i++; 346 sheet.setColumnWidth(i, columnPixels(50)); 347 sheet.autoSizeColumn(i); 348 // sheet.setColumnHidden(i, true); 349 } 350 } 351 sheet.createFreezePane(2,1); 352 353 if (hideMustSupportFalse) { 354 SheetConditionalFormatting sheetCF = sheet.getSheetConditionalFormatting(); 355 String address = "A2:AI" + Math.max(Integer.valueOf(sheet.getLastRowNum()), 2); 356 CellRangeAddress[] regions = { 357 CellRangeAddress.valueOf(address) 358 }; 359 360 ConditionalFormattingRule rule1 = sheetCF.createConditionalFormattingRule("$G2<>\"Y\""); 361 PatternFormatting fill1 = rule1.createPatternFormatting(); 362 fill1.setFillBackgroundColor(IndexedColors.GREY_25_PERCENT.index); 363 fill1.setFillPattern(PatternFormatting.SOLID_FOREGROUND); 364 365 ConditionalFormattingRule rule2 = sheetCF.createConditionalFormattingRule("$Q2<>\"\""); 366 FontFormatting font = rule2.createFontFormatting(); 367 font.setFontColorIndex(IndexedColors.GREY_25_PERCENT.index); 368 font.setFontStyle(true, false); 369 370 sheetCF.addConditionalFormatting(regions, rule1, rule2); 371 372 sheet.setAutoFilter(new CellRangeAddress(0,sheet.getLastRowNum(), 0, titles.length+(sd == null ? 0 : sd.getMapping().size() - 1))); 373 374 375 XSSFSheet xSheet = (XSSFSheet)sheet; 376 377 CTAutoFilter sheetFilter = xSheet.getCTWorksheet().getAutoFilter(); 378 CTFilterColumn filterColumn1 = sheetFilter.addNewFilterColumn(); 379 filterColumn1.setColId(6); 380 CTCustomFilters filters = filterColumn1.addNewCustomFilters(); 381 CTCustomFilter filter1 = filters.addNewCustomFilter(); 382 filter1.setOperator(STFilterOperator.NOT_EQUAL); 383 filter1.setVal(" "); 384 385 CTFilterColumn filterColumn2 = sheetFilter.addNewFilterColumn(); 386 filterColumn2.setColId(26); 387 CTFilters filters2 = filterColumn2.addNewFilters(); 388 filters2.setBlank(true); 389 390 // We have to apply the filter ourselves by hiding the rows: 391 for (Row row : sheet) { 392 if (row.getRowNum()>0 && (!row.getCell(6).getStringCellValue().equals("Y") || !row.getCell(26).getStringCellValue().isEmpty())) { 393 ((XSSFRow) row).getCTRow().setHidden(true); 394 } 395 } 396 } 397 if (sheet.getLastRowNum() > 0) { 398 sheet.setActiveCell(new CellAddress(sheet.getRow(1).getCell(0))); 399 } 400 } 401 402}