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