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