001package org.hl7.fhir.r4.utils.formats; 002 003/*- 004 * #%L 005 * org.hl7.fhir.r4 006 * %% 007 * Copyright (C) 2014 - 2019 Health Level 7 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023 024import java.io.ByteArrayOutputStream; 025import java.io.IOException; 026import java.io.OutputStream; 027import java.io.UnsupportedEncodingException; 028import java.util.ArrayList; 029import java.util.Enumeration; 030import java.util.HashMap; 031import java.util.List; 032import java.util.Map; 033 034import org.apache.poi.ss.usermodel.BorderStyle; 035import org.apache.poi.ss.usermodel.Cell; 036import org.apache.poi.ss.usermodel.CellStyle; 037import org.apache.poi.ss.usermodel.ConditionalFormattingRule; 038import org.apache.poi.ss.usermodel.FillPatternType; 039import org.apache.poi.ss.usermodel.Font; 040import org.apache.poi.ss.usermodel.FontFormatting; 041import org.apache.poi.ss.usermodel.IndexedColors; 042import org.apache.poi.ss.usermodel.PatternFormatting; 043import org.apache.poi.ss.usermodel.Row; 044import org.apache.poi.ss.usermodel.Sheet; 045import org.apache.poi.ss.usermodel.SheetConditionalFormatting; 046import org.apache.poi.ss.usermodel.VerticalAlignment; 047import org.apache.poi.ss.usermodel.Workbook; 048import org.apache.poi.ss.util.CellAddress; 049import org.apache.poi.ss.util.CellRangeAddress; 050import org.apache.poi.xssf.usermodel.XSSFRow; 051import org.apache.poi.xssf.usermodel.XSSFSheet; 052import org.apache.poi.xssf.usermodel.XSSFWorkbook; 053import org.hl7.fhir.r4.formats.IParser.OutputStyle; 054import org.hl7.fhir.r4.formats.JsonParser; 055import org.hl7.fhir.r4.formats.XmlParser; 056import org.hl7.fhir.r4.model.CanonicalType; 057import org.hl7.fhir.r4.model.Coding; 058import org.hl7.fhir.r4.model.ElementDefinition; 059import org.hl7.fhir.r4.model.ElementDefinition.AggregationMode; 060import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionConstraintComponent; 061import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionMappingComponent; 062import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent; 063import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent; 064import org.hl7.fhir.r4.model.IdType; 065import org.hl7.fhir.r4.model.StringType; 066import org.hl7.fhir.r4.model.StructureDefinition; 067import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionMappingComponent; 068import org.hl7.fhir.r4.model.Type; 069import org.hl7.fhir.r4.model.UriType; 070import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 071import org.hl7.fhir.utilities.TextStreamWriter; 072import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTAutoFilter; 073import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCustomFilter; 074import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCustomFilters; 075import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTFilterColumn; 076import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTFilters; 077import org.openxmlformats.schemas.spreadsheetml.x2006.main.STFilterOperator; 078 079public class XLSXWriter extends TextStreamWriter { 080 081 private StructureDefinition def; 082 private List<StructureDefinitionMappingComponent> mapKeys = new ArrayList<StructureDefinitionMappingComponent>(); 083 private Map<String, CellStyle> styles; 084 private OutputStream outStream; 085 private XSSFWorkbook wb = new XSSFWorkbook(); 086 private Sheet sheet; 087 private XmlParser xml = new XmlParser(); 088 private JsonParser json = new JsonParser(); 089 private boolean asXml; 090 private boolean hideMustSupportFalse; 091 092 private static String[] titles = { 093 "Path", "Slice Name", "Alias(s)", "Label", "Min", "Max", "Must Support?", "Is Modifier?", "Is Summary?", "Type(s)", "Short", 094 "Definition", "Comments", "Requirements", "Default Value", "Meaning When Missing", "Fixed Value", "Pattern", "Example", 095 "Minimum Value", "Maximum Value", "Maximum Length", "Binding Strength", "Binding Description", "Binding Value Set", "Code", 096 "Slicing Discriminator", "Slicing Description", "Slicing Ordered", "Slicing Rules", "Base Path", "Base Min", "Base Max", 097 "Condition(s)", "Constraint(s)"}; 098 099 public XLSXWriter(OutputStream out, StructureDefinition def, boolean asXml, boolean hideMustSupportFalse) throws UnsupportedEncodingException { 100 super(out); 101 outStream = out; 102 this.asXml = asXml; 103 this.def = def; 104 this.hideMustSupportFalse = hideMustSupportFalse; 105 sheet = wb.createSheet("Elements"); 106 styles = createStyles(wb); 107 Row headerRow = sheet.createRow(0); 108 for (int i = 0; i < titles.length; i++) { 109 addCell(headerRow, i, titles[i], styles.get("header")); 110 } 111 int i = titles.length - 1; 112 for (StructureDefinitionMappingComponent map : def.getMapping()) { 113 i++; 114 addCell(headerRow, i, "Mapping: " + map.getName(), styles.get("header")); 115 } 116 } 117 118 private void addCell(Row row, int pos, String content) { 119 addCell(row, pos, content, styles.get("body")); 120 } 121 122 public void addCell(Row row, int pos, boolean b) { 123 addCell(row, pos, b ? "Y" : ""); 124 } 125 126 public void addCell(Row row, int pos, int content) { 127 addCell(row, pos, Integer.toString(content)); 128 } 129 130 private void addCell(Row row, int pos, String content, CellStyle style) { 131 Cell cell = row.createCell(pos); 132 cell.setCellValue(content); 133 cell.setCellStyle(style); 134 } 135 136 /** 137 * create a library of cell styles 138 */ 139 private static Map<String, CellStyle> createStyles(Workbook wb){ 140 Map<String, CellStyle> styles = new HashMap<>(); 141 142 CellStyle style; 143 Font headerFont = wb.createFont(); 144 headerFont.setBold(true); 145 style = createBorderedStyle(wb); 146 style.setFillForegroundColor(IndexedColors.LIGHT_CORNFLOWER_BLUE.getIndex()); 147 style.setFillPattern(FillPatternType.SOLID_FOREGROUND); 148 style.setVerticalAlignment(VerticalAlignment.TOP); 149 style.setWrapText(true); 150 style.setFont(headerFont); 151 styles.put("header", style); 152 153 style = createBorderedStyle(wb); 154 style.setVerticalAlignment(VerticalAlignment.TOP); 155 style.setWrapText(true); 156 styles.put("body", style); 157 158 return styles; 159 } 160 161 private static CellStyle createBorderedStyle(Workbook wb){ 162 BorderStyle thin = BorderStyle.THIN; 163 short black = IndexedColors.GREY_50_PERCENT.getIndex(); 164 165 CellStyle style = wb.createCellStyle(); 166 style.setBorderRight(thin); 167 style.setRightBorderColor(black); 168 style.setBorderBottom(thin); 169 style.setBottomBorderColor(black); 170 style.setBorderLeft(thin); 171 style.setLeftBorderColor(black); 172 style.setBorderTop(thin); 173 style.setTopBorderColor(black); 174 return style; 175 } 176 /* private void findMapKeys(StructureDefinition def, List<StructureDefinitionMappingComponent> maps, IWorkerContext context) { 177 maps.addAll(def.getMapping()); 178 if (def.getBaseDefinition()!=null) { 179 StructureDefinition base = context.fetchResource(StructureDefinition.class, def.getBaseDefinition()); 180 findMapKeys(base, maps, context); 181 } 182 }*/ 183 184 public void processElement(ElementDefinition ed) throws Exception { 185 Row row = sheet.createRow(sheet.getLastRowNum()+1); 186 int i = 0; 187 addCell(row, i++, ed.getPath(), styles.get("body")); 188 addCell(row, i++, ed.getSliceName()); 189 addCell(row, i++, itemList(ed.getAlias())); 190 addCell(row, i++, ed.getLabel()); 191 addCell(row, i++, ed.getMin()); 192 addCell(row, i++, ed.getMax()); 193 addCell(row, i++, ed.getMustSupport() ? "Y" : ""); 194 addCell(row, i++, ed.getIsModifier() ? "Y" : ""); 195 addCell(row, i++, ed.getIsSummary() ? "Y" : ""); 196 addCell(row, i++, itemList(ed.getType())); 197 addCell(row, i++, ed.getShort()); 198 addCell(row, i++, ed.getDefinition()); 199 addCell(row, i++, ed.getComment()); 200 addCell(row, i++, ed.getRequirements()); 201 addCell(row, i++, ed.getDefaultValue()!=null ? renderType(ed.getDefaultValue()) : ""); 202 addCell(row, i++, ed.getMeaningWhenMissing()); 203 addCell(row, i++, ed.hasFixed() ? renderType(ed.getFixed()) : ""); 204 addCell(row, i++, ed.hasPattern() ? renderType(ed.getPattern()) : ""); 205 addCell(row, i++, ed.hasExample() ? renderType(ed.getExample().get(0).getValue()) : ""); // todo...? 206 addCell(row, i++, ed.hasMinValue() ? renderType(ed.getMinValue()) : ""); 207 addCell(row, i++, ed.hasMaxValue() ? renderType(ed.getMaxValue()) : ""); 208 addCell(row, i++, (ed.hasMaxLength() ? Integer.toString(ed.getMaxLength()) : "")); 209 if (ed.hasBinding()) { 210 addCell(row, i++, ed.getBinding().getStrength()!=null ? ed.getBinding().getStrength().toCode() : ""); 211 addCell(row, i++, ed.getBinding().getDescription()); 212 if (ed.getBinding().getValueSet()==null) 213 addCell(row, i++, ""); 214 else 215 addCell(row, i++, ed.getBinding().getValueSet()); 216 } else { 217 addCell(row, i++, ""); 218 addCell(row, i++, ""); 219 addCell(row, i++, ""); 220 } 221 addCell(row, i++, itemList(ed.getCode())); 222 if (ed.hasSlicing()) { 223 addCell(row, i++, itemList(ed.getSlicing().getDiscriminator())); 224 addCell(row, i++, ed.getSlicing().getDescription()); 225 addCell(row, i++, ed.getSlicing().getOrdered()); 226 addCell(row, i++, ed.getSlicing().getRules()!=null ? ed.getSlicing().getRules().toCode() : ""); 227 } else { 228 addCell(row, i++, ""); 229 addCell(row, i++, ""); 230 addCell(row, i++, ""); 231 addCell(row, i++, ""); 232 } 233 if (ed.getBase()!=null) { 234 addCell(row, i++, ed.getBase().getPath()); 235 addCell(row, i++, ed.getBase().getMin()); 236 addCell(row, i++, ed.getBase().getMax()); 237 } else { 238 addCell(row, i++, ""); 239 addCell(row, i++, ""); 240 addCell(row, i++, ""); 241 } 242 addCell(row, i++, itemList(ed.getCondition())); 243 addCell(row, i++, itemList(ed.getConstraint())); 244 for (StructureDefinitionMappingComponent mapKey : def.getMapping()) { 245 String mapString = ""; 246 for (ElementDefinitionMappingComponent map : ed.getMapping()) { 247 if (map.getIdentity().equals(mapKey.getIdentity())) 248 mapString = map.getMap(); 249 } 250 addCell(row, i++, mapString); 251 } 252 } 253 254 255 private String itemList(List l) { 256 StringBuilder s = new StringBuilder(); 257 for (int i =0; i< l.size(); i++) { 258 Object o = l.get(i); 259 String val = ""; 260 if (o instanceof StringType) { 261 val = ((StringType)o).getValue(); 262 } else if (o instanceof UriType) { 263 val = ((UriType)o).getValue(); 264 } else if (o instanceof IdType) { 265 val = ((IdType)o).getValue(); 266 } else if (o instanceof Enumeration<?>) { 267 val = o.toString(); 268 } else if (o instanceof TypeRefComponent) { 269 TypeRefComponent t = (TypeRefComponent)o; 270 val = t.getWorkingCode(); 271 if (val == null) 272 val = ""; 273 if (val.startsWith("http://hl7.org/fhir/StructureDefinition/")) 274 val = val.substring(40); 275 if (t.hasTargetProfile()) 276 val = val+ "(" + canonicalList(t.getTargetProfile()) + ")"; 277 if (t.hasProfile()) 278 val = val + " {" + canonicalList(t.getProfile()) + "}"; 279 if (t.hasAggregation()) 280 val = val + " <<" + aggList(t.getAggregation()) + ">>"; 281 } else if (o instanceof Coding) { 282 Coding t = (Coding)o; 283 val = (t.getSystem()==null ? "" : t.getSystem()) + (t.getCode()==null ? "" : "#" + t.getCode()) + (t.getDisplay()==null ? "" : " (" + t.getDisplay() + ")"); 284 } else if (o instanceof ElementDefinitionConstraintComponent) { 285 ElementDefinitionConstraintComponent c = (ElementDefinitionConstraintComponent)o; 286 val = c.getKey() + ":" + c.getHuman() + " {" + c.getExpression() + "}"; 287 } else if (o instanceof ElementDefinitionSlicingDiscriminatorComponent) { 288 ElementDefinitionSlicingDiscriminatorComponent c = (ElementDefinitionSlicingDiscriminatorComponent)o; 289 val = c.getType().toCode() + ":" + c.getPath() + "}"; 290 291 } else { 292 val = o.toString(); 293 val = val.substring(val.indexOf("[")+1); 294 val = val.substring(0, val.indexOf("]")); 295 } 296 s = s.append(val); 297 if (i == 0) 298 s.append("\n"); 299 } 300 return s.toString(); 301 } 302 303 private String aggList(List<org.hl7.fhir.r4.model.Enumeration<AggregationMode>> list) { 304 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 305 for (org.hl7.fhir.r4.model.Enumeration<AggregationMode> c : list) 306 b.append(c.getValue().toCode()); 307 return b.toString(); 308 } 309 310 private String canonicalList(List<CanonicalType> list) { 311 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder("|"); 312 for (CanonicalType c : list) { 313 String v = c.getValue(); 314 if (v.startsWith("http://hl7.org/fhir/StructureDefinition/")) 315 v = v.substring(40); 316 b.append(v); 317 } 318 return b.toString(); 319 } 320 321 private String renderType(Type value) throws Exception { 322 if (value == null) 323 return ""; 324 if (value.isPrimitive()) 325 return value.primitiveValue(); 326 327 String s = null; 328 ByteArrayOutputStream bs = new ByteArrayOutputStream(); 329 if (asXml) { 330 xml.setOutputStyle(OutputStyle.PRETTY); 331 xml.compose(bs, "", value); 332 bs.close(); 333 s = bs.toString(); 334 s = s.substring(s.indexOf("\n")+2); 335 } else { 336 json.setOutputStyle(OutputStyle.PRETTY); 337 json.compose(bs, value, ""); 338 bs.close(); 339 s = bs.toString(); 340 } 341 return s; 342 } 343 344 private int columnPixels(double columns) { 345 double WIDTH_FACTOR = 256; 346 double PADDING = 180; 347 return (int)Math.floor(columns*WIDTH_FACTOR + PADDING); 348 } 349 350 public void dump() throws IOException { 351 for (int i=0; i<34; i++) { 352 sheet.autoSizeColumn(i); 353 } 354 355 sheet.setColumnHidden(2, true); 356 sheet.setColumnHidden(3, true); 357 sheet.setColumnHidden(30, true); 358 sheet.setColumnHidden(31, true); 359 sheet.setColumnHidden(32, true); 360 361 sheet.setColumnWidth(9, columnPixels(20)); 362 sheet.setColumnWidth(11, columnPixels(100)); 363 sheet.setColumnWidth(12, columnPixels(100)); 364 sheet.setColumnWidth(13, columnPixels(100)); 365 sheet.setColumnWidth(15, columnPixels(20)); 366 sheet.setColumnWidth(16, columnPixels(20)); 367 sheet.setColumnWidth(17, columnPixels(20)); 368 sheet.setColumnWidth(18, columnPixels(20)); 369 sheet.setColumnWidth(34, columnPixels(100)); 370 371 int i = titles.length - 1; 372 for (StructureDefinitionMappingComponent map : def.getMapping()) { 373 i++; 374 sheet.setColumnWidth(i, columnPixels(50)); 375 sheet.autoSizeColumn(i); 376// sheet.setColumnHidden(i, true); 377 } 378 sheet.createFreezePane(2,1); 379 380 if (hideMustSupportFalse) { 381 SheetConditionalFormatting sheetCF = sheet.getSheetConditionalFormatting(); 382 String address = "A2:AI" + Math.max(Integer.valueOf(sheet.getLastRowNum()), 2); 383 CellRangeAddress[] regions = { 384 CellRangeAddress.valueOf(address) 385 }; 386 387 ConditionalFormattingRule rule1 = sheetCF.createConditionalFormattingRule("$G2<>\"Y\""); 388 PatternFormatting fill1 = rule1.createPatternFormatting(); 389 fill1.setFillBackgroundColor(IndexedColors.GREY_25_PERCENT.index); 390 fill1.setFillPattern(PatternFormatting.SOLID_FOREGROUND); 391 392 ConditionalFormattingRule rule2 = sheetCF.createConditionalFormattingRule("$Q2<>\"\""); 393 FontFormatting font = rule2.createFontFormatting(); 394 font.setFontColorIndex(IndexedColors.GREY_25_PERCENT.index); 395 font.setFontStyle(true, false); 396 397 sheetCF.addConditionalFormatting(regions, rule1, rule2); 398 399 sheet.setAutoFilter(new CellRangeAddress(0,sheet.getLastRowNum(), 0, titles.length+def.getMapping().size() - 1)); 400 401 XSSFSheet xSheet = (XSSFSheet)sheet; 402 403 CTAutoFilter sheetFilter = xSheet.getCTWorksheet().getAutoFilter(); 404 CTFilterColumn filterColumn1 = sheetFilter.addNewFilterColumn(); 405 filterColumn1.setColId(6); 406 CTCustomFilters filters = filterColumn1.addNewCustomFilters(); 407 CTCustomFilter filter1 = filters.addNewCustomFilter(); 408 filter1.setOperator(STFilterOperator.NOT_EQUAL); 409 filter1.setVal(" "); 410 411 CTFilterColumn filterColumn2 = sheetFilter.addNewFilterColumn(); 412 filterColumn2.setColId(26); 413 CTFilters filters2 = filterColumn2.addNewFilters(); 414 filters2.setBlank(true); 415 416 // We have to apply the filter ourselves by hiding the rows: 417 for (Row row : sheet) { 418 if (row.getRowNum()>0 && (!row.getCell(6).getStringCellValue().equals("Y") || !row.getCell(26).getStringCellValue().isEmpty())) { 419 ((XSSFRow) row).getCTRow().setHidden(true); 420 } 421 } 422 } 423 sheet.setActiveCell(new CellAddress(sheet.getRow(1).getCell(0))); 424 425 wb.write(outStream); 426 427 flush(); 428 close(); 429 } 430 431}