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