
ch.rabanti.nanoxlsx4j.lowLevel.LowLevel Maven / Gradle / Ivy
/*
* NanoXLSX4j is a small Java library to write and read XLSX (Microsoft Excel 2007 or newer) files in an easy and native way
* Copyright Raphael Stoeckli © 2018
* This library is licensed under the MIT License.
* You find a copy of the license in project folder or on: http://opensource.org/licenses/MIT
*/
package ch.rabanti.nanoxlsx4j.lowLevel;
import ch.rabanti.nanoxlsx4j.*;
import ch.rabanti.nanoxlsx4j.exception.IOException;
import ch.rabanti.nanoxlsx4j.exception.RangeException;
import ch.rabanti.nanoxlsx4j.exception.StyleException;
import ch.rabanti.nanoxlsx4j.style.*;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* Class for low level handling (XML, formatting, preparing of packing)
This class is only for internal use. Use the high level API (e.g. class Workbook) to manipulate data and create Excel files.
* @author Raphael Stoeckli
*/
public class LowLevel {
// ### P R I V A T E F I E L D S ###
private final SortedMap sharedStrings;
private int sharedStringsTotalCount;
private final Workbook workbook;
private boolean interceptDocuments;
private HashMap interceptedDocuments;
// ### G E T T E R S & S E T T E R S ###
/**
* Gets whether XML documents are intercepted during creation
* @return If true, documents will be intercepted and stored into interceptedDocuments
*/
public boolean getDocumentInterception() {
return interceptDocuments;
}
/**
* Set whether XML documents are intercepted during creation
* @param interceptDocuments If true, documents will be intercepted and stored into interceptedDocuments
*/
public void setDocumentInterception(boolean interceptDocuments)
{
this.interceptDocuments = interceptDocuments;
if (interceptDocuments == true && this.interceptedDocuments == null)
{
this.interceptedDocuments = new HashMap<>();
}
else if (interceptDocuments == false)
{
this.interceptedDocuments = null;
}
}
/**
* Gets the intercepted documents if interceptDocuments is set to true
* @return HashMap with a String as key and a XML document as value
*/
public HashMap getInterceptedDocuments() {
return interceptedDocuments;
}
// ### C O N S T R U C T O R S ###
/**
* Constructor with defined workbook object
* @param workbook Workbook to process
*/
public LowLevel(Workbook workbook)
{
this.workbook = workbook;
this.sharedStrings = new SortedMap();
this.sharedStringsTotalCount = 0;
}
// ### M E T H O D S ###
/**
* Method to append a simple XML tag with an enclosed value to the passed StringBuilder
* @param sb StringBuilder to append
* @param value Value of the XML element
* @param tagName Tag name of the XML element
* @param nameSpace Optional XML name space. Can be empty or null
*/
private void appendXMLtag(StringBuilder sb, String value, String tagName, String nameSpace)
{
if (Helper.isNullOrEmpty(value)) { return; }
if (sb == null || Helper.isNullOrEmpty(tagName)) { return; }
boolean hasNoNs = Helper.isNullOrEmpty(nameSpace);
sb.append('<');
if (hasNoNs == false)
{
sb.append(nameSpace);
sb.append(':');
}
sb.append(tagName).append(">");
sb.append(escapeXMLChars(value));
sb.append("");
if (hasNoNs == false)
{
sb.append(nameSpace);
sb.append(':');
}
sb.append(tagName);
sb.append(">");
}
/**
* Method to create the app-properties (part of meta data) as XML document
* @return Formatted XML document
* @throws IOException Thrown in case of an error while creating the XML document
*/
private Document createAppPropertiesDocument() throws IOException
{
StringBuilder sb = new StringBuilder();
sb.append("");
sb.append(createAppString());
sb.append(" ");
return createXMLDocument(sb.toString(), "APPPROPERTIES");
}
/**
* Method to create the XML string for the app-properties document
* @return String with formatted XML data
*/
private String createAppString()
{
if (this.workbook.getWorkbookMetadata() == null) { return ""; }
Metadata md = this.workbook.getWorkbookMetadata();
StringBuilder sb = new StringBuilder();
appendXMLtag(sb, "0", "TotalTime", null);
appendXMLtag(sb, md.getApplication(), "Application", null);
appendXMLtag(sb, "0", "DocSecurity", null);
appendXMLtag(sb, "false", "ScaleCrop", null);
appendXMLtag(sb, md.getManager(), "Manager", null);
appendXMLtag(sb, md.getCompany(), "Company", null);
appendXMLtag(sb, "false", "LinksUpToDate", null);
appendXMLtag(sb, "false", "SharedDoc", null);
appendXMLtag(sb, md.getHyperlinkBase(), "HyperlinkBase", null);
appendXMLtag(sb, "false", "HyperlinksChanged", null);
appendXMLtag(sb, md.getApplicationVersion(), "AppVersion", null);
return sb.toString();
}
/**
* Method to create the columns as XML string. This is used to define the width of columns
* @param worksheet Worksheet to process
* @return String with formatted XML data
*/
private String createColsString(Worksheet worksheet)
{
if (worksheet.getColumns().size() > 0)
{
String col;
String hidden = "";
StringBuilder sb = new StringBuilder();
for (Map.Entry column : worksheet.getColumns().entrySet())
{
if (column.getValue().getWidth() == worksheet.getDefaultColumnWidth() && column.getValue().isHidden() == false) { continue; }
if (worksheet.getColumns().containsKey(column.getKey()))
{
if (worksheet.getColumns().get(column.getKey()).isHidden() == true)
{
hidden = " hidden=\"1\"";
}
}
col = Integer.toString(column.getKey() + 1); // Add 1 for Address
sb.append(" ");
}
String value = sb.toString();
if (value.length() > 0)
{
return value;
}
else
{
return "";
}
}
else
{
return "";
}
}
/**
* Method to create the core-properties (part of meta data) as XML document
* @return Formatted XML document
* @throws IOException Thrown in case of an error while creating the XML document
*/
private Document createCorePropertiesDocument() throws IOException
{
StringBuilder sb = new StringBuilder();
sb.append("");
sb.append(createCorePropertiesString());
sb.append(" ");
return createXMLDocument(sb.toString(),"COREPROPERTIES");
}
/**
* Method to create the XML string for the core-properties document
* @return String with formatted XML data
*/
private String createCorePropertiesString()
{
if (this.workbook.getWorkbookMetadata() == null) { return ""; }
Metadata md = this.workbook.getWorkbookMetadata();
StringBuilder sb = new StringBuilder();
appendXMLtag(sb, md.getTitle(), "title", "dc");
appendXMLtag(sb, md.getSubject(), "subject", "dc");
appendXMLtag(sb, md.getCreator(), "creator", "dc");
appendXMLtag(sb, md.getCreator(), "lastModifiedBy", "cp");
appendXMLtag(sb, md.getKeywords(), "keywords", "cp");
appendXMLtag(sb, md.getDescription(), "description", "dc");
Calendar cal = new GregorianCalendar();
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'");
df.setCalendar(cal);
Date now = cal.getTime();
String time = df.format(now);
sb.append("").append(time).append(" ");
sb.append("").append(time).append(" ");
appendXMLtag(sb, md.getCategory(), "category", "cp");
appendXMLtag(sb, md.getContentStatus(), "contentStatus", "cp");
return sb.toString();
}
/**
* Method to create the merged cells string of the passed worksheet
* @param sheet Worksheet to process
* @return Formatted string with merged cell ranges
*/
private String createMergedCellsString(Worksheet sheet)
{
if (sheet.getMergedCells().size() < 1)
{
return "";
}
Iterator> itr;
Map.Entry range;
StringBuilder sb = new StringBuilder();
sb.append("");
itr = sheet.getMergedCells().entrySet().iterator();
while (itr.hasNext())
{
range = itr.next();
sb.append("");
}
sb.append(" ");
return sb.toString();
}
/**
* Method to create the XML string for the color-MRU part of the style sheet document (recent colors)
* @return String with formatted XML data
*/
private String createMruColorsString()
{
Font[] fonts = this.workbook.getStyleManager().getFonts();
Fill[] fills = this.workbook.getStyleManager().getFills();
StringBuilder sb = new StringBuilder();
List tempColors = new ArrayList<>();
for (Font font : fonts) {
if (Helper.isNullOrEmpty(font.getColorValue()) == true) { continue; }
if (font.getColorValue().equals(Fill.DEFAULTCOLOR)) { continue; }
if (tempColors.contains(font.getColorValue()) == false) { tempColors.add(font.getColorValue()); }
}
for (Fill fill : fills) {
if (Helper.isNullOrEmpty(fill.getBackgroundColor()) == false)
{
if (fill.getBackgroundColor().equals(Fill.DEFAULTCOLOR) == false)
{
if (tempColors.contains(fill.getBackgroundColor()) == false) { tempColors.add(fill.getBackgroundColor()); }
}
}
if (Helper.isNullOrEmpty(fill.getForegroundColor()) == false)
{
if (fill.getForegroundColor().equals(Fill.DEFAULTCOLOR) == false)
{
if (tempColors.contains(fill.getForegroundColor()) == false) { tempColors.add(fill.getForegroundColor()); }
}
}
}
if (tempColors.size() > 0)
{
sb.append("");
for(int i = 0; i < tempColors.size(); i++)
{
sb.append("");
}
sb.append(" ");
return sb.toString();
}
else
{
return "";
}
}
/**
* Method to create a row string
* @param columnFields List of cells
* @param worksheet Worksheet to process
* @return Formatted row string
*/
private String createRowString(List columnFields, Worksheet worksheet)
{
int rowNumber = columnFields.get(0).getRowNumber();
String height = "";
String hidden = "";
if (worksheet.getRowHeights().containsKey(rowNumber))
{
if (worksheet.getRowHeights().get(rowNumber) != worksheet.getDefaultRowHeight())
{
height = " x14ac:dyDescent=\"0.25\" customHeight=\"1\" ht=\"" + Float.toString(worksheet.getRowHeights().get(rowNumber)) + "\"";
}
}
if (worksheet.getHiddenRows().containsKey(rowNumber))
{
if (worksheet.getHiddenRows().get(rowNumber) == true)
{
hidden = " hidden=\"1\"";
}
}
int colNum = columnFields.size();
StringBuilder sb = new StringBuilder(43 * colNum); // A row string size is according to statistics (random value) 43 times the column number
//StringBuilder sb = new StringBuilder();
if (colNum > 0)
{
sb.append("");
}
else
{
sb.append("");
}
String typeAttribute;
String sValue, tValue;
String value = "";
boolean bVal;
Date dVal;
int col = 0;
Cell item;
for (int i = 0; i < colNum; i++)
{
item = columnFields.get(i);
tValue = " ";
if (item.getCellStyle() != null)
{
sValue = " s=\"" + Integer.toString(item.getCellStyle().getInternalID()) + "\" ";
}
else
{
sValue = "";
}
item.resolveCellType(); // Recalculate the type (for handling DEFAULT)
if (item.getDataType() == Cell.CellType.BOOL)
{
typeAttribute = "b";
tValue = " t=\"" + typeAttribute + "\" ";
bVal = (boolean)item.getValue();
if (bVal == true) { value = "1"; }
else { value = "0"; }
}
// Number casting
else if (item.getDataType() == Cell.CellType.NUMBER)
{
typeAttribute = "n";
tValue = " t=\"" + typeAttribute + "\" ";
Object o = item.getValue();
if (o instanceof Byte) { value = Byte.toString((byte)item.getValue()); }
else if (o instanceof BigDecimal) { value = ((BigDecimal)item.getValue()).toString(); }
else if (o instanceof Double) { value = Double.toString((double)item.getValue()); }
else if (o instanceof Float) { value = Float.toString((float)item.getValue());}
else if (o instanceof Integer) { value = Integer.toString((int)item.getValue()); }
else if (o instanceof Long) { value = Long.toString((long)item.getValue()); }
else if (o instanceof Short) { value = Short.toString((short)item.getValue()); }
}
// Date parsing
else if (item.getDataType() == Cell.CellType.DATE)
{
typeAttribute = "d";
dVal = (Date)item.getValue();
value = Double.toString(Helper.getOADate(dVal));
}
// String parsing
else
{
if (item.getValue() == null)
{
typeAttribute = "str";
value = "";
}
else // handle shared Strings
{
// value = item.getValue().toString();
if (item.getDataType() == Cell.CellType.FORMULA)
{
typeAttribute = "str";
value = item.getValue().toString();
}
else
{
typeAttribute = "s";
value = item.getValue().toString();
if (this.sharedStrings.containsKey(value) == false)
{
this.sharedStrings.add(value, Integer.toString(sharedStrings.size()));
}
value = this.sharedStrings.get(value);
this.sharedStringsTotalCount++;
}
}
tValue = " t=\"" + typeAttribute + "\" ";
}
if (item.getDataType() != Cell.CellType.EMPTY)
{
sb.append("");
if (item.getDataType() == Cell.CellType.FORMULA)
{
sb.append("").append(LowLevel.escapeXMLChars(item.getValue().toString())).append(" ");
}
else
{
sb.append("").append(LowLevel.escapeXMLChars(value)).append(" ");
}
sb.append(" ");
}
else // Empty cell
{
sb.append(" ");
}
col++;
}
sb.append(" ");
return sb.toString();
}
/**
* Method to create shared strings as XML document
* @return Formatted XML document
* @throws IOException Thrown in case of an error while creating the XML document
*/
private Document createSharedStringsDocument() throws IOException
{
StringBuilder sb = new StringBuilder();
sb.append("");
ArrayList keys = this.sharedStrings.getKeys();
//for (Map.Entry str : sharedStrings.entrySet())
for(int i = 0; i < keys.size(); i++)
{
sb.append("");
//sb.append(escapeXMLChars(str.getKey()));
sb.append(escapeXMLChars(keys.get(i)));
sb.append(" ");
}
sb.append(" ");
return createXMLDocument(sb.toString(), "SHAREDSTRINGS");
}
/**
* Method to create the protection string of the passed worksheet
* @param sheet Worksheet to process
* @return Formatted string with protection statement of the worksheet
*/
private String createSheetProtectionString(Worksheet sheet)
{
if (sheet.isUseSheetProtection() == false)
{
return "";
}
HashMap actualLockingValues = new HashMap<>();
if (sheet.getSheetProtectionValues().isEmpty())
{
actualLockingValues.put(Worksheet.SheetProtectionValue.selectLockedCells, 1);
actualLockingValues.put(Worksheet.SheetProtectionValue.selectUnlockedCells, 1);
}
if (sheet.getSheetProtectionValues().contains(Worksheet.SheetProtectionValue.objects) == false)
{
actualLockingValues.put(Worksheet.SheetProtectionValue.objects, 1);
}
if (sheet.getSheetProtectionValues().contains(Worksheet.SheetProtectionValue.scenarios) == false)
{
actualLockingValues.put(Worksheet.SheetProtectionValue.scenarios, 1);
}
if (sheet.getSheetProtectionValues().contains(Worksheet.SheetProtectionValue.selectLockedCells) == false )
{
if (actualLockingValues.containsKey(Worksheet.SheetProtectionValue.selectLockedCells) == false)
{
actualLockingValues.put(Worksheet.SheetProtectionValue.selectLockedCells, 1);
}
}
if (sheet.getSheetProtectionValues().contains(Worksheet.SheetProtectionValue.selectUnlockedCells) == false || sheet.getSheetProtectionValues().contains(Worksheet.SheetProtectionValue.selectLockedCells) == false)
{
if (actualLockingValues.containsKey(Worksheet.SheetProtectionValue.selectUnlockedCells) == false)
{
actualLockingValues.put(Worksheet.SheetProtectionValue.selectUnlockedCells, 1);
}
}
if (sheet.getSheetProtectionValues().contains(Worksheet.SheetProtectionValue.formatCells)) { actualLockingValues.put(Worksheet.SheetProtectionValue.formatCells, 0); }
if (sheet.getSheetProtectionValues().contains(Worksheet.SheetProtectionValue.formatColumns)) { actualLockingValues.put(Worksheet.SheetProtectionValue.formatColumns, 0); }
if (sheet.getSheetProtectionValues().contains(Worksheet.SheetProtectionValue.formatRows)) { actualLockingValues.put(Worksheet.SheetProtectionValue.formatRows, 0); }
if (sheet.getSheetProtectionValues().contains(Worksheet.SheetProtectionValue.insertColumns)) { actualLockingValues.put(Worksheet.SheetProtectionValue.insertColumns, 0); }
if (sheet.getSheetProtectionValues().contains(Worksheet.SheetProtectionValue.insertRows)) { actualLockingValues.put(Worksheet.SheetProtectionValue.insertRows, 0); }
if (sheet.getSheetProtectionValues().contains(Worksheet.SheetProtectionValue.insertHyperlinks)) { actualLockingValues.put(Worksheet.SheetProtectionValue.insertHyperlinks, 0); }
if (sheet.getSheetProtectionValues().contains(Worksheet.SheetProtectionValue.deleteColumns)) { actualLockingValues.put(Worksheet.SheetProtectionValue.deleteColumns, 0); }
if (sheet.getSheetProtectionValues().contains(Worksheet.SheetProtectionValue.deleteRows)) { actualLockingValues.put(Worksheet.SheetProtectionValue.deleteRows, 0); }
if (sheet.getSheetProtectionValues().contains(Worksheet.SheetProtectionValue.sort)) { actualLockingValues.put(Worksheet.SheetProtectionValue.sort, 0); }
if (sheet.getSheetProtectionValues().contains(Worksheet.SheetProtectionValue.autoFilter)) { actualLockingValues.put(Worksheet.SheetProtectionValue.autoFilter, 0); }
if (sheet.getSheetProtectionValues().contains(Worksheet.SheetProtectionValue.pivotTables)) { actualLockingValues.put(Worksheet.SheetProtectionValue.pivotTables, 0); }
StringBuilder sb = new StringBuilder();
sb.append("> itr;
Map.Entry item;
itr = actualLockingValues.entrySet().iterator();
while (itr.hasNext())
{
item = itr.next();
temp = item.getKey().name();// Note! If the enum names differs from the OOXML definitions, this method will cause invalid OOXML entries
sb.append(" ").append(temp).append("=\"").append(item.getValue()).append("\"");
}
if (Helper.isNullOrEmpty(sheet.getSheetProtectionPassword()) == false)
{
String hash = generatePasswordHash(sheet.getSheetProtectionPassword());
sb.append(" password=\"").append(hash).append("\"");
}
sb.append(" sheet=\"1\"/>");
return sb.toString();
}
/**
* Method to create the XML string for the border part of the style sheet document
* @return String with formatted XML data
*/
private String createStyleBorderString()
{
Border[] borderStyles = this.workbook.getStyleManager().getBorders();
StringBuilder sb = new StringBuilder();
for (Border borderStyle : borderStyles) {
if (borderStyle.isDiagonalDown() == true && borderStyle.isDiagonalUp() == false) { sb.append(""); }
else if (borderStyle.isDiagonalDown() == false && borderStyle.isDiagonalUp() == true) { sb.append(""); }
else if (borderStyle.isDiagonalDown() == true && borderStyle.isDiagonalUp() == true) { sb.append(""); }
else { sb.append(""); }
if (borderStyle.getLeftStyle() != Border.StyleValue.none)
{
sb.append("");
if (Helper.isNullOrEmpty(borderStyle.getLeftColor()) == true) { sb.append(""); }
else { sb.append(""); }
sb.append(" ");
}
else
{
sb.append(" ");
}
if (borderStyle.getRightStyle() != Border.StyleValue.none)
{
sb.append("");
if (Helper.isNullOrEmpty(borderStyle.getRightColor()) == true) { sb.append(""); }
else { sb.append(""); }
sb.append(" ");
}
else
{
sb.append(" ");
}
if (borderStyle.getTopStyle() != Border.StyleValue.none)
{
sb.append("");
if (Helper.isNullOrEmpty(borderStyle.getTopColor()) == true) { sb.append(""); }
else { sb.append(""); }
sb.append(" ");
}
else
{
sb.append(" ");
}
if (borderStyle.getBottomStyle() != Border.StyleValue.none)
{
sb.append("");
if (Helper.isNullOrEmpty(borderStyle.getBottomColor()) == true) { sb.append(""); }
else { sb.append(""); }
sb.append(" ");
}
else
{
sb.append(" ");
}
if (borderStyle.getDiagonalStyle() != Border.StyleValue.none)
{
sb.append("");
if (Helper.isNullOrEmpty(borderStyle.getDiagonalColor()) == true) { sb.append(""); }
else { sb.append(""); }
sb.append(" ");
}
else
{
sb.append(" ");
}
sb.append(" ");
}
return sb.toString();
}
/**
* Method to create the XML string for the fill part of the style sheet document
* @return String with formatted XML data
*/
private String createStyleFillString()
{
Fill[] fillStyles = this.workbook.getStyleManager().getFills();
StringBuilder sb = new StringBuilder();
for (Fill fillStyle : fillStyles) {
sb.append("");
sb.append("");
sb.append("");
sb.append(" ");
sb.append(" ");
}
else if (fillStyle.getPatternFill() == Fill.PatternValue.mediumGray || fillStyle.getPatternFill() == Fill.PatternValue.lightGray || fillStyle.getPatternFill() == Fill.PatternValue.gray0625 || fillStyle.getPatternFill() == Fill.PatternValue.darkGray)
{
sb.append(">");
sb.append("");
if (Helper.isNullOrEmpty(fillStyle.getBackgroundColor()) == false)
{
sb.append("");
}
sb.append("");
}
else
{
sb.append("/>");
}
sb.append(" ");
}
return sb.toString();
}
/**
* Method to create the XML string for the font part of the style sheet document
* @return String with formatted XML data
*/
private String createStyleFontString()
{
Font[] fontStyles = this.workbook.getStyleManager().getFonts();
StringBuilder sb = new StringBuilder();
for (Font fontStyle : fontStyles) {
sb.append("");
if (fontStyle.isBold() == true) { sb.append(""); }
if (fontStyle.isItalic() == true) { sb.append(""); }
if (fontStyle.isUnderline() == true) { sb.append(""); }
if (fontStyle.isDoubleUnderline() == true) { sb.append(""); }
if (fontStyle.isStrike() == true) { sb.append(""); }
if (fontStyle.getVerticalAlign() == Font.VerticalAlignValue.subscript) { sb.append(""); }
else if (fontStyle.getVerticalAlign() == Font.VerticalAlignValue.superscript) { sb.append(""); }
sb.append(" ");
if (Helper.isNullOrEmpty(fontStyle.getColorValue()))
{
sb.append(" ");
}
else
{
sb.append("");
}
sb.append("");
sb.append("");
if (fontStyle.getScheme() != Font.SchemeValue.none)
{
if (fontStyle.getScheme() == Font.SchemeValue.major)
{ sb.append(""); }
else if (fontStyle.getScheme() == Font.SchemeValue.minor)
{ sb.append(""); }
}
if (Helper.isNullOrEmpty(fontStyle.getCharset()) == false)
{
sb.append("");
}
sb.append(" ");
}
return sb.toString();
}
/**
* Method to create the XML string for the number format part of the style sheet document
* @return String with formatted XML data
*/
private String createStyleNumberFormatString()
{
NumberFormat[] numberFormatStyles = this.workbook.getStyleManager().getNumberFormats();
StringBuilder sb = new StringBuilder();
for (NumberFormat numberFormatStyle : numberFormatStyles) {
if (numberFormatStyle.isCustomFormat() == true)
{
sb.append(" ");
}
}
return sb.toString();
}
/**
* Method to create a style sheet as XML document
* @return Formatted XML document
* @throws StyleException Thrown if a style was not referenced in the style sheet
* @throws RangeException Thrown if a referenced cell was out of range
* @throws IOException Thrown in case of an error while creating the XML document
*/
private Document createStyleSheetDocument() throws IOException
{
String bordersString = createStyleBorderString();
String fillsString = createStyleFillString();
String fontsString = createStyleFontString();
String numberFormatsString = createStyleNumberFormatString();
int numFormatCount = getNumberFormatStringCounter();
String xfsStings = createStyleXfsString();
String mruColorString = createMruColorsString();
StringBuilder sb = new StringBuilder();
sb.append("");
if (numFormatCount > 0)
{
sb.append("");
sb.append(numberFormatsString).append(" ");
}
sb.append("");
sb.append(fontsString).append(" ");
sb.append("");
sb.append(fillsString).append(" ");
sb.append("");
sb.append(bordersString).append(" ");
sb.append("");
sb.append(xfsStings).append(" ");
if (this.workbook.getWorkbookMetadata() != null)
{
if (Helper.isNullOrEmpty(mruColorString) == false && this.workbook.getWorkbookMetadata().isUseColorMRU() == true)
{
sb.append("");
sb.append(mruColorString);
sb.append(" ");
}
}
sb.append(" ");
return createXMLDocument(sb.toString(), "STYLESHEET");
}
/**
* Method to create the XML string for the XF part of the style sheet document
* @return String with formatted XML data
* @throws RangeException Thrown if a referenced cell was out of range
*/
private String createStyleXfsString()
{
Style[] styles = this.workbook.getStyleManager().getStyles();
StringBuilder sb = new StringBuilder();
StringBuilder sb2;
String alignmentString, protectionString;
int formatNumber, textRotation;
for (Style style : styles) {
textRotation = style.getCellXf().calculateInternalRotation();
alignmentString = "";
protectionString = "";
if (style.getCellXf().getHorizontalAlign() != CellXf.HorizontalAlignValue.none || style.getCellXf().getVerticalAlign() != CellXf.VerticalAlignValue.none || style.getCellXf().getAlignment() != CellXf.TextBreakValue.none || textRotation != 0)
{
sb2 = new StringBuilder();
sb2.append(" "); //
alignmentString = sb2.toString();
}
if (style.getCellXf().isHidden() == true || style.getCellXf().isLocked() == true)
{
if (style.getCellXf().isHidden() == true && style.getCellXf().isLocked() == true)
{
protectionString = "";
}
else if (style.getCellXf().isHidden() == true && style.getCellXf().isLocked() == false)
{
protectionString = "";
}
else
{
protectionString = "";
}
}
sb.append("");
sb.append(alignmentString);
sb.append(protectionString);
sb.append(" ");
}
else
{
sb.append("/>");
}
}
return sb.toString();
}
/**
* Method to create a workbook as XML document
* @return Formatted XML document
* @throws RangeException Thrown if a referenced cell was out of range
* @throws IOException Thrown in case of an error while creating the XML document
*/
private Document createWorkbookDocument() throws IOException
{
if (this.workbook.getWorksheets().isEmpty())
{
throw new RangeException("UnknownRangeException", "The workbook can not be created because no worksheet was defined.");
}
StringBuilder sb = new StringBuilder();
sb.append("");
if (this.workbook.getSelectedWorksheet() > 0)
{
sb.append(" ");
}
if (this.workbook.isWorkbookProtectionUsed() == true)
{
sb.append(" ");
}
sb.append("");
int id;
for (int i = 0; i < this.workbook.getWorksheets().size(); i++)
{
id = this.workbook.getWorksheets().get(i).getSheetID();
sb.append("");
}
sb.append(" ");
sb.append(" ");
return createXMLDocument(sb.toString(), "WORKBOOK");
}
/**
* Method to create a worksheet part as XML document
* @param worksheet worksheet object to process
* @return Formatted XML document
* @throws IOException Thrown in case of an error while creating the XML document
*/
private Document createWorksheetPart(Worksheet worksheet) throws IOException
{
worksheet.recalculateAutoFilter();
worksheet.recalculateColumns();
List> celldata = getSortedSheetData(worksheet);
StringBuilder sb = new StringBuilder();
String line;
sb.append("");
if (worksheet.getSelectedCells() != null)
{
sb.append(" ");
}
sb.append(" ");
String colWidths = createColsString(worksheet);
if (Helper.isNullOrEmpty(colWidths) == false)
{
sb.append("");
sb.append(colWidths);
sb.append(" ");
}
sb.append("");
for(int i = 0; i < celldata.size(); i++)
{
line = createRowString(celldata.get(i), worksheet);
sb.append(line).append("");
}
sb.append(" ");
sb.append(createMergedCellsString(worksheet));
sb.append(createSheetProtectionString(worksheet));
if (worksheet.getAutoFilterRange() != null)
{
sb.append("");
}
sb.append(" ");
//testing.Performance.SaveLoggedValues("LineLength.xlsx");
return createXMLDocument(sb.toString(), "WORKSHEET: " + worksheet.getSheetName());
}
/**
* Creates a XML document from a string
* @param rawInput String to process
* @param title Title for interception / debugging purpose
* @return Formatted XML document
* @throws IOException Thrown in case of an error while creating the XML document
*/
public Document createXMLDocument(String rawInput, String title) throws IOException
{
try
{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = factory.newDocumentBuilder();
InputSource input = new InputSource(new StringReader( rawInput ));
input.setEncoding("UTF-8");
Document doc = docBuilder.parse( input );
doc.setXmlVersion("1.0");
doc.setXmlStandalone(true);
if (this.interceptDocuments == true)
{
this.interceptedDocuments.put(title, doc);
System.out.println("DEBUG: Document '" + title + "' was intercepted");
}
return doc;
}
catch(Exception e)
{
throw new IOException("XmlDocumentException","There was an error while creating the XML document. Please see the inner exception.", e);
}
}
/**
* Gets the number of custom number formats
* @return Number of custom number formats to apply in the style document
*/
private int getNumberFormatStringCounter()
{
NumberFormat[] numberFormatStyles = this.workbook.getStyleManager().getNumberFormats();
int counter = 0;
for (NumberFormat numberFormatStyle : numberFormatStyles) {
if (numberFormatStyle.isCustomFormat() == true)
{
counter++;
}
}
return counter;
}
/**
* Method to sort the cells of a worksheet as preparation for the XML document
* @param sheet Worksheet to process
* @return Two dimensional array of Cell object
*/
private List> getSortedSheetData(Worksheet sheet)
{
List temp = new ArrayList<>();
Map.Entry entry;
Iterator> itr = sheet.getCells().entrySet().iterator();
while (itr.hasNext())
{
entry = itr.next();
temp.add(entry.getValue());
}
Collections.sort(temp);
List line = new ArrayList<>();
List> output = new ArrayList<>();
if (temp.size() > 0)
{
int rowNumber = temp.get(0).getRowNumber();
for (int i = 0; i < temp.size(); i++)
{
if (temp.get(i).getRowNumber() != rowNumber)
{
output.add(line);
line = new ArrayList<>();
rowNumber = temp.get(i).getRowNumber();
}
line.add(temp.get(i));
}
if (line.size() > 0)
{
output.add(line);
}
}
return output;
}
/**
* Method to save the workbook
* @throws IOException Thrown in case of an error
*/
public void save() throws IOException
{
try
{
FileOutputStream dest = new FileOutputStream(this.workbook.getFilename());
saveAsStream(dest);
}
catch (Exception e)
{
throw new IOException("SaveException","There was an error while creating the workbook document during saving to a file. Please see the inner exception:" + e.getMessage(), e);
}
}
public void saveAsStream(OutputStream stream) throws IOException
{
try
{
this.workbook.resolveMergedCells();
Document doc;
Document app = createAppPropertiesDocument();
Document core = createCorePropertiesDocument();
Document styles = createStyleSheetDocument();
Document book = createWorkbookDocument();
String file;
Worksheet sheet;
Packer p = new Packer(this);
Packer.Relationship rel = p.createRelationship("_rels/.rels");
rel.addRelationshipEntry("/xl/workbook.xml", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument");
rel.addRelationshipEntry("/docProps/core.xml", "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties");
rel.addRelationshipEntry("/docProps/app.xml", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties");
rel = p.createRelationship("xl/_rels/workbook.xml.rels");
for(int i = 0; i < this.workbook.getWorksheets().size(); i++)
{
sheet = this.workbook.getWorksheets().get(i);
doc = createWorksheetPart(sheet);
file = "sheet" + Integer.toString(sheet.getSheetID()) + ".xml";
rel.addRelationshipEntry("/xl/worksheets/" + file, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet");
p.addPart("xl/worksheets/" + file, "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml", doc);
}
rel.addRelationshipEntry("/xl/styles.xml", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles");
rel.addRelationshipEntry("/xl/sharedStrings.xml", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings");
p.addPart("docProps/core.xml", "application/vnd.openxmlformats-package.core-properties+xml", core);
p.addPart("docProps/app.xml", "application/vnd.openxmlformats-officedocument.extended-properties+xml", app);
p.addPart("xl/sharedStrings.xml", "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml", createSharedStringsDocument());
p.addPart("xl/workbook.xml", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml", book, false);
p.addPart("xl/styles.xml", "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml", styles);
p.pack(stream);
}
catch (Exception e)
{
throw new IOException("SaveException","There was an error while creating the workbook document during writing to a stream. Please see the inner exception:" + e.getMessage(), e);
}
}
// ### S T A T I C M E T H O D S ###
/**
* Method to convert an XML document to a byte array
* @param document Document to process
* @return array of bytes (UTF-8)
* @throws IOException Thrown if the document could not be converted to a byte array
*/
public static byte[] createBytesFromDocument(Document document) throws IOException
{
try
{
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
ByteArrayOutputStream bs = new ByteArrayOutputStream();
Result output = new StreamResult(bs);
Source input = new DOMSource(document);
transformer.transform(input, output);
bs.flush();
byte[] bytes = bs.toByteArray();
bs.close();
return bytes;
}
catch(Exception e)
{
throw new IOException("ByteSteamException","There was an error while creating the byte array. Please see the inner exception.", e);
}
}
/**
* Method to escape XML characters in an XML attribute
* @param input Input string to process
* @return Escaped string
*/
private static String escapeXMLAttributeChars(String input)
{
input = escapeXMLChars(input); // Sanitize string from illegal characters beside quotes
input = input.replace("\"", """);
return input;
}
/**
* Method to escape XML characters between two XML tags Note: The XML specs allow characters up to the character value of 0x10FFFF. However, the Java char range is only up to 0xFFFF. PicoXLSX4j will neglect all values above this level in the sanitizing check. Illegal characters like 0x1 will be replaced with a white space (0x20)
* @param input Input string to process
* @return Escaped string
*/
private static String escapeXMLChars(String input)
{
int len = input.length();
List illegalCharacters = new ArrayList<>(len);
List characterTypes = new ArrayList<>(len);
int i;
char c;
for (i = 0; i < len; i++)
{
c = input.charAt(i);
if ((c < 0x9) || (c > 0xA && c < 0xD) || (c > 0xD && c < 0x20) || (c > 0xD7FF && c < 0xE000) || (c > 0xFFFD))
{
illegalCharacters.add(i);
characterTypes.add(0);
continue;
} // Note: XML specs allow characters up to 0x10FFFF. However, the Java char range is only up to 0xFFFF; Higher values are neglected here
if (c == 0x3C) // <
{
illegalCharacters.add(i);
characterTypes.add(1);
}
else if (c == 0x3E) // >
{
illegalCharacters.add(i);
characterTypes.add(2);
}
else if (c == 0x26) // &
{
illegalCharacters.add(i);
characterTypes.add(3);
}
}
if (illegalCharacters.isEmpty())
{
return input;
}
StringBuilder sb = new StringBuilder(len);
int lastIndex = 0;
len = illegalCharacters.size();
int j, type;
for (i = 0; i < len; i++)
{
j = illegalCharacters.get(i);
type = characterTypes.get(i);
sb.append(input.substring(lastIndex, j));
if (type == 0)
{
sb.append(' '); // Whitespace as fall back on illegal character
}
else if (type == 1) // replace <
{
sb.append("<");
}
else if (type == 2) // replace >
{
sb.append(">");
}
else if (type == 3) // replace &
{
sb.append("&");
}
lastIndex = j + 1;
}
sb.append(input.substring(lastIndex));
return sb.toString();
}
/**
* Method to generate an Excel internal password hash to protect workbooks or worksheets
* This method is derived from the c++ implementation by Kohei Yoshida (http://kohei.us/2008/01/18/excel-sheet-protection-password-hash/)
* WARNING! Do not use this method to encrypt 'real' passwords or data outside from PicoXLSX4j. This is only a minor security feature. Use a proper cryptography method instead.
* @param password Password as plain text
* @return Encoded password
*/
private static String generatePasswordHash(String password)
{
if (Helper.isNullOrEmpty(password)) { return ""; }
int passwordLength = password.length();
int passwordHash = 0;
char character;
for(int i = passwordLength; i > 0; i--)
{
character = password.charAt(i - 1);
passwordHash = ((passwordHash >> 14) & 0x01) | ((passwordHash << 1) & 0x7fff);
passwordHash ^= character;
}
passwordHash = ((passwordHash >> 14) & 0x01) | ((passwordHash << 1) & 0x7fff);
passwordHash ^= (0x8000 | ('N' << 8) | 'K');
passwordHash ^= passwordLength;
return Integer.toHexString(passwordHash).toUpperCase();
}
}
| | |
© 2015 - 2025 Weber Informatics LLC | Privacy Policy