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
032
033
034import java.io.ByteArrayOutputStream;
035import java.io.IOException;
036import java.io.OutputStream;
037import java.io.UnsupportedEncodingException;
038import java.util.ArrayList;
039import java.util.Enumeration;
040import java.util.HashMap;
041import java.util.List;
042import java.util.Map;
043
044import org.apache.poi.ss.usermodel.BorderStyle;
045import org.apache.poi.ss.usermodel.Cell;
046import org.apache.poi.ss.usermodel.CellStyle;
047import org.apache.poi.ss.usermodel.ConditionalFormattingRule;
048import org.apache.poi.ss.usermodel.FillPatternType;
049import org.apache.poi.ss.usermodel.Font;
050import org.apache.poi.ss.usermodel.FontFormatting;
051import org.apache.poi.ss.usermodel.IndexedColors;
052import org.apache.poi.ss.usermodel.PatternFormatting;
053import org.apache.poi.ss.usermodel.Row;
054import org.apache.poi.ss.usermodel.Sheet;
055import org.apache.poi.ss.usermodel.SheetConditionalFormatting;
056import org.apache.poi.ss.usermodel.VerticalAlignment;
057import org.apache.poi.ss.usermodel.Workbook;
058import org.apache.poi.ss.util.CellAddress;
059import org.apache.poi.ss.util.CellRangeAddress;
060import org.apache.poi.xssf.usermodel.XSSFRow;
061import org.apache.poi.xssf.usermodel.XSSFSheet;
062import org.apache.poi.xssf.usermodel.XSSFWorkbook;
063import org.hl7.fhir.r4.formats.IParser.OutputStyle;
064import org.hl7.fhir.r4.formats.JsonParser;
065import org.hl7.fhir.r4.formats.XmlParser;
066import org.hl7.fhir.r4.model.CanonicalType;
067import org.hl7.fhir.r4.model.Coding;
068import org.hl7.fhir.r4.model.ElementDefinition;
069import org.hl7.fhir.r4.model.ElementDefinition.AggregationMode;
070import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionConstraintComponent;
071import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionMappingComponent;
072import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent;
073import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent;
074import org.hl7.fhir.r4.model.IdType;
075import org.hl7.fhir.r4.model.StringType;
076import org.hl7.fhir.r4.model.StructureDefinition;
077import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionMappingComponent;
078import org.hl7.fhir.r4.model.Type;
079import org.hl7.fhir.r4.model.UriType;
080import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
081import org.hl7.fhir.utilities.TextStreamWriter;
082import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTAutoFilter;
083import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCustomFilter;
084import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCustomFilters;
085import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTFilterColumn;
086import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTFilters;
087import org.openxmlformats.schemas.spreadsheetml.x2006.main.STFilterOperator;
088
089public class XLSXWriter  extends TextStreamWriter  {
090
091  private StructureDefinition def;
092  private List<StructureDefinitionMappingComponent> mapKeys = new ArrayList<StructureDefinitionMappingComponent>();
093  private Map<String, CellStyle> styles;
094  private OutputStream outStream;
095  private XSSFWorkbook wb = new XSSFWorkbook();
096  private Sheet sheet;
097  private XmlParser xml = new XmlParser();
098  private JsonParser json = new JsonParser();
099  private boolean asXml;
100  private boolean hideMustSupportFalse;
101
102  private static String[] titles = {
103      "Path", "Slice Name", "Alias(s)", "Label", "Min", "Max", "Must Support?", "Is Modifier?", "Is Summary?", "Type(s)", "Short", 
104      "Definition", "Comments", "Requirements", "Default Value", "Meaning When Missing", "Fixed Value", "Pattern", "Example",
105      "Minimum Value", "Maximum Value", "Maximum Length", "Binding Strength", "Binding Description", "Binding Value Set", "Code",
106      "Slicing Discriminator", "Slicing Description", "Slicing Ordered", "Slicing Rules", "Base Path", "Base Min", "Base Max",
107      "Condition(s)", "Constraint(s)"};
108
109  public XLSXWriter(OutputStream out, StructureDefinition def, boolean asXml, boolean hideMustSupportFalse) 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  /*  private void findMapKeys(StructureDefinition def, List<StructureDefinitionMappingComponent> maps, IWorkerContext context) {
187        maps.addAll(def.getMapping());
188        if (def.getBaseDefinition()!=null) {
189          StructureDefinition base = context.fetchResource(StructureDefinition.class, def.getBaseDefinition());
190          findMapKeys(base, maps, context);
191        }
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
265  private String itemList(List l) {
266    StringBuilder s = new StringBuilder();
267    for (int i =0; i< l.size(); i++) {
268      Object o = l.get(i);
269      String val = "";
270      if (o instanceof StringType) {
271        val = ((StringType)o).getValue();
272      } else if (o instanceof UriType) {
273        val = ((UriType)o).getValue();
274      } else if (o instanceof IdType) {
275        val = ((IdType)o).getValue();
276      } else if (o instanceof Enumeration<?>) {
277        val = o.toString();
278      } else if (o instanceof TypeRefComponent) {
279        TypeRefComponent t = (TypeRefComponent)o;
280          val = t.getWorkingCode();
281          if (val == null)
282            val = "";
283        if (val.startsWith("http://hl7.org/fhir/StructureDefinition/"))
284          val = val.substring(40);
285        if (t.hasTargetProfile()) 
286          val = val+ "(" + canonicalList(t.getTargetProfile()) + ")";
287          if (t.hasProfile())
288            val = val + " {" + canonicalList(t.getProfile()) + "}";
289          if (t.hasAggregation()) 
290          val = val + " <<" + aggList(t.getAggregation()) + ">>";
291      } else if (o instanceof Coding) {
292        Coding t = (Coding)o;
293        val = (t.getSystem()==null ? "" : t.getSystem()) + (t.getCode()==null ? "" : "#" + t.getCode()) + (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 = {
394        CellRangeAddress.valueOf(address)
395    };
396
397    ConditionalFormattingRule rule1 = sheetCF.createConditionalFormattingRule("$G2<>\"Y\"");
398    PatternFormatting fill1 = rule1.createPatternFormatting();
399    fill1.setFillBackgroundColor(IndexedColors.GREY_25_PERCENT.index);
400    fill1.setFillPattern(PatternFormatting.SOLID_FOREGROUND);
401
402    ConditionalFormattingRule rule2 = sheetCF.createConditionalFormattingRule("$Q2<>\"\"");
403    FontFormatting font = rule2.createFontFormatting();
404    font.setFontColorIndex(IndexedColors.GREY_25_PERCENT.index);
405    font.setFontStyle(true, false);
406
407    sheetCF.addConditionalFormatting(regions, rule1, rule2);
408
409    sheet.setAutoFilter(new CellRangeAddress(0,sheet.getLastRowNum(), 0, titles.length+def.getMapping().size() - 1));
410    
411    XSSFSheet xSheet = (XSSFSheet)sheet;
412
413    CTAutoFilter sheetFilter = xSheet.getCTWorksheet().getAutoFilter();
414    CTFilterColumn filterColumn1 = sheetFilter.addNewFilterColumn();
415    filterColumn1.setColId(6);
416    CTCustomFilters filters = filterColumn1.addNewCustomFilters();
417    CTCustomFilter filter1 = filters.addNewCustomFilter();
418    filter1.setOperator(STFilterOperator.NOT_EQUAL);
419    filter1.setVal(" ");
420    
421    CTFilterColumn filterColumn2 = sheetFilter.addNewFilterColumn();
422    filterColumn2.setColId(26);
423    CTFilters filters2 = filterColumn2.addNewFilters();
424    filters2.setBlank(true);
425
426    // We have to apply the filter ourselves by hiding the rows: 
427    for (Row row : sheet) {
428      if (row.getRowNum()>0 && (!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}