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}