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