Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@ private String prepareTextForXml(String text) {
case "sheetname":
return "&A";
default:
XmlEscapeHelper xmlEscapeHelper = new XmlEscapeHelper();
return xmlEscapeHelper.escape(text);
return XmlEscapeHelper.escape(text);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,11 @@ public Properties setHyperlinkBase(String hyperlinkBase) {
return this;
}

interface CustomProty<T> {
interface CustomProty {
void write(Writer w, int pid) throws IOException;
}

abstract class AbstractProperty<T> implements CustomProty<T> {
abstract class AbstractProperty<T> implements CustomProty {
protected String key;
protected T value;

Expand Down Expand Up @@ -158,7 +158,9 @@ public TextProperty(String key, String value) {

@Override
public void write(Writer w, int pid) throws IOException {
w.append("<property fmtid=\"{D5CDD505-2E9C-101B-9397-08002B2CF9AE}\" pid=\"" + pid + "\" name=\"" + key + "\"><vt:lpwstr>" + value + "</vt:lpwstr></property>");
w.append("<property fmtid=\"{D5CDD505-2E9C-101B-9397-08002B2CF9AE}\" pid=\"" + pid + "\" name=\"" + key + "\"><vt:lpwstr>");
w.appendEscaped(value);
w.append("</vt:lpwstr></property>");
}
}

Expand Down Expand Up @@ -191,7 +193,7 @@ public BoolProperty(String key, Boolean value) {

@Override
public void write(Writer w, int pid) throws IOException {
w.append("<property fmtid=\"{D5CDD505-2E9C-101B-9397-08002B2CF9AE}\" pid=\"" + pid + "\" name=\""+key+"\"><vt:bool>" + value.toString() + "</vt:bool></property>");
w.append("<property fmtid=\"{D5CDD505-2E9C-101B-9397-08002B2CF9AE}\" pid=\"" + pid + "\" name=\"" + key + "\"><vt:bool>" + value.toString() + "</vt:bool></property>");
}
}

Expand Down
123 changes: 86 additions & 37 deletions fastexcel-writer/src/main/java/org/dhatim/fastexcel/Workbook.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package org.dhatim.fastexcel;

import com.github.rzymek.opczip.OpcOutputStream;

import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
Expand All @@ -33,7 +34,7 @@
/**
* A {@link Workbook} contains one or more {@link Worksheet} objects.
*/
public class Workbook implements Closeable{
public class Workbook implements Closeable {

private int activeTab = 0;
private boolean finished = false;
Expand All @@ -50,13 +51,13 @@ public class Workbook implements Closeable{
/**
* Constructor.
*
* @param os Output stream eventually holding the serialized workbook.
* @param applicationName Name of the application which generated this
* workbook.
* @param os Output stream eventually holding the serialized workbook.
* @param applicationName Name of the application which generated this
* workbook.
* @param applicationVersion Version of the application. Ignored if
* {@code null}. Refer to
* <a href="https://learn.microsoft.com/en-us/previous-versions/office/developer/office-2010/ee881787(v=office.14)?redirectedfrom=MSDN">this
* page</a> for details.
* {@code null}. Refer to
* <a href="https://learn.microsoft.com/en-us/previous-versions/office/developer/office-2010/ee881787(v=office.14)?redirectedfrom=MSDN">this
* page</a> for details.
*/
public Workbook(OutputStream os, String applicationName, String applicationVersion) {
this.os = new OpcOutputStream(os);
Expand Down Expand Up @@ -100,6 +101,7 @@ public void setGlobalDefaultFont(Font font) {
Font.DEFAULT = font;
this.styleCache.replaceDefaultFont(font);
}

public Properties properties() {
return this.properties;
}
Expand All @@ -125,7 +127,7 @@ public void close() throws IOException {
* @throws IOException In case of I/O error.
*/
public void finish() throws IOException {
if (finished){
if (finished) {
return;
}

Expand All @@ -139,21 +141,21 @@ public void finish() throws IOException {

writeFile("[Content_Types].xml", w -> {
w.append("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\"><Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/><Default Extension=\"xml\" ContentType=\"application/xml\"/>");
if(hasComments()){
if (hasComments()) {
w.append("<Default ContentType=\"application/vnd.openxmlformats-officedocument.vmlDrawing\" Extension=\"vml\"/>");
}
w.append("<Override PartName=\"/xl/sharedStrings.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml\"/><Override PartName=\"/xl/styles.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml\"/><Override PartName=\"/xl/workbook.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml\"/>");
for (Worksheet ws : worksheets) {
int index = getIndex(ws);
w.append("<Override PartName=\"/xl/worksheets/sheet").append(index).append(".xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml\"/>");
if(!ws.comments.isEmpty()) {
if (!ws.comments.isEmpty()) {
w.append("<Override ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml\" PartName=\"/xl/comments").append(index).append(".xml\"/>");
w.append("<Override ContentType=\"application/vnd.openxmlformats-officedocument.drawing+xml\" PartName=\"/xl/drawings/drawing").append(index).append(".xml\"/>");
}
if (!ws.tables.isEmpty()) {
for (Map.Entry<String, Table> entry : ws.tables.entrySet()) {
Table table = entry.getValue();
w.append("<Override PartName=\"/xl/tables/table"+table.index+".xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml\"/>");
w.append("<Override PartName=\"/xl/tables/table" + table.index + ".xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml\"/>");
}
}
}
Expand All @@ -165,7 +167,7 @@ public void finish() throws IOException {
w.append("</Types>");
});
writeProperties();
if(properties.hasCustomProperties()){
if (properties.hasCustomProperties()) {
writeFile("docProps/custom.xml", properties::writeCustomProperties);
}

Expand Down Expand Up @@ -201,26 +203,70 @@ private void writeProperties() throws IOException {
w.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
w.append("<Properties xmlns=\"http://schemas.openxmlformats.org/officeDocument/2006/extended-properties\">");
w.append("<Application>").appendEscaped(applicationName).append("</Application>");
w.append(properties.getManager() == null ? "" : ("<Manager>" + properties.getManager() + "</Manager>"));
w.append(properties.getCompany() == null ? "" : ("<Company>" + properties.getCompany() + "</Company>"));
w.append(properties.getHyperlinkBase() == null ? "" : ("<HyperlinkBase>" + properties.getHyperlinkBase() + "</HyperlinkBase>"));
w.append(applicationVersion == null ? "" : ("<AppVersion>" + applicationVersion + "</AppVersion>"));
String manager = properties.getManager();
if (manager != null) {
w.append("<Manager>");
w.appendEscaped(manager);
w.append("</Manager>");
}
String company = properties.getCompany();
if (company != null) {
w.append("<Company>");
w.appendEscaped(company);
w.append("</Company>");
}
String hyperlinkBase = properties.getHyperlinkBase();
if (hyperlinkBase != null) {
w.append("<HyperlinkBase>");
w.appendEscaped(hyperlinkBase);
w.append("</HyperlinkBase>");
}
if (applicationVersion != null) {
w.append("<AppVersion>");
w.appendEscaped(applicationVersion);
w.append("</AppVersion>");
}
w.append("</Properties>");
});
writeFile("docProps/core.xml", w -> {
w.append("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>");
w.append("<cp:coreProperties xmlns:cp=\"http://schemas.openxmlformats.org/package/2006/metadata/core-properties\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:dcterms=\"http://purl.org/dc/terms/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">");
w.append(properties.getTitle() == null ? "" : ("<dc:title>" + properties.getTitle() + "</dc:title>"));
w.append(properties.getSubject() == null ? "" : ("<dc:subject>" + properties.getSubject() + "</dc:subject>"));
String title = properties.getTitle();
if (title != null) {
w.append("<dc:title>");
w.appendEscaped(title);
w.append("</dc:title>");
}
String subject = properties.getSubject();
if (subject != null) {
w.append("<dc:subject>");
w.appendEscaped(subject);
w.append("</dc:subject>");
}
w.append("<dc:creator>");
w.appendEscaped(applicationName);
w.append("</dc:creator>");
w.append(properties.getKeywords() == null ? "" : ("<cp:keywords>" + properties.getKeywords() + "</cp:keywords>"));
w.append(properties.getDescription() == null ? "" : ("<dc:description>" + properties.getDescription() + "</dc:description>"));
String keywords = properties.getKeywords();
if (keywords != null) {
w.append("<cp:keywords>");
w.appendEscaped(keywords);
w.append("</cp:keywords>");
}
String description = properties.getDescription();
if (description != null) {
w.append("<dc:description>");
w.appendEscaped(description);
w.append("</dc:description>");
}
w.append("<dcterms:created xsi:type=\"dcterms:W3CDTF\">");
w.append(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX").withZone(ZoneId.of("UTC")).format(Instant.now()));
w.append("</dcterms:created>");
w.append(properties.getCategory() == null ? "" : ("<cp:category>" + properties.getCategory() + "</cp:category>"));
String category = properties.getCategory();
if (category != null) {
w.append("<cp:category>");
w.appendEscaped(category);
w.append("</cp:category>");
}
w.append("</cp:coreProperties>");
});
}
Expand All @@ -239,19 +285,20 @@ private boolean hasComments() {

/**
* Writes the {@code xl/workbook.xml} file to the zip.
*
* @throws IOException If an I/O error occurs.
*/
private void writeWorkbookFile() throws IOException {
writeFile("xl/workbook.xml", w -> {
w.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<workbook " +
"xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" " +
"xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">" +
"xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" " +
"xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">" +
"<workbookPr date1904=\"false\"/>" +
"<bookViews>" +
"<workbookView activeTab=\"" + activeTab + "\"/>" +
"</bookViews>" +
"<sheets>");
"<bookViews>" +
"<workbookView activeTab=\"" + activeTab + "\"/>" +
"</bookViews>" +
"<sheets>");

for (Worksheet ws : worksheets) {
writeWorkbookSheet(w, ws);
Expand All @@ -265,8 +312,8 @@ private void writeWorkbookFile() throws IOException {
for (Worksheet ws : worksheets) {
int worksheetIndex = getIndex(ws) - 1;
List<Object> repeatingColsAndRows = Stream.of(ws.getRepeatingCols(), ws.getRepeatingRows())
.filter(Objects::nonNull)
.collect(Collectors.toList());
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (!repeatingColsAndRows.isEmpty()) {
w.append("<definedName function=\"false\" hidden=\"false\" localSheetId=\"")
.append(worksheetIndex)
Expand Down Expand Up @@ -312,7 +359,8 @@ private void writeWorkbookFile() throws IOException {

/**
* Writes a {@code sheet} tag to the writer.
* @param w The writer to write to
*
* @param w The writer to write to
* @param ws The WorkSheet that is resembled by the {@code sheet} tag.
* @throws IOException If an I/O error occurs.
*/
Expand All @@ -330,7 +378,7 @@ private void writeWorkbookSheet(Writer w, Worksheet ws) throws IOException {
/**
* Write a new file as a zip entry to the output writer.
*
* @param name File name.
* @param name File name.
* @param consumer Output writer consumer, producing file contents.
* @throws IOException If an I/O error occurs.
*/
Expand All @@ -346,6 +394,7 @@ Writer beginFile(String name) throws IOException {
os.putNextEntry(new ZipEntry(name));
return writer;
}

void endFile() throws IOException {
writer.flush();
os.closeEntry();
Expand All @@ -364,12 +413,12 @@ CachedString cacheString(String s) {
/**
* Merge given style attributes with cached style.
*
* @param currentStyle Current (cached) style index, 0 if none.
* @param currentStyle Current (cached) style index, 0 if none.
* @param numberingFormat Numbering format.
* @param font Font attributes.
* @param fill Fill attributes.
* @param border Border attributes.
* @param alignment Alignment attributes.
* @param font Font attributes.
* @param fill Fill attributes.
* @param border Border attributes.
* @param alignment Alignment attributes.
* @return Cached style index.
*/
int mergeAndCacheStyle(int currentStyle, String numberingFormat, Font font, Fill fill, Border border, Alignment alignment, Protection protection) {
Expand All @@ -392,7 +441,7 @@ int cacheDifferentialFormat(DifferentialFormat differentialFormat) {
* Get unique index of a worksheet.
*
* @param ws Worksheet. It must have been created previously by calling
* {@link #newWorksheet(java.lang.String)} on this workbook.
* {@link #newWorksheet(java.lang.String)} on this workbook.
* @return Worksheet index.
*/
int getIndex(Worksheet ws) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,7 @@ Writer appendEscaped(String s) throws IOException {
*/
private Writer append(String s, boolean escape) throws IOException {
if (escape) {
XmlEscapeHelper xmlEscapeHelper = new XmlEscapeHelper();
sb.append(xmlEscapeHelper.escape(s));
sb.append(XmlEscapeHelper.escape(s));
} else {
sb.append(s);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class XmlEscapeHelper {
* @param text text to be escaped
* @return escaped text
*/
public String escape(final String text) {
public static String escape(final String text) {
int offset = 0;
StringBuilder sb = new StringBuilder();
while (offset < text.length()) {
Expand All @@ -29,7 +29,7 @@ public String escape(final String text) {
*
* @param c Character code point.
*/
private String escape(int c) {
private static String escape(int c) {
if (!(c == 0x9 || c == 0xa || c == 0xD
|| (c >= 0x20 && c <= 0xd7ff)
|| (c >= 0xe000 && c <= 0xfffd)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,23 +235,23 @@ void testForGithubIssue163() throws Exception {

@Test
void testForGithubIssue164() throws Exception {
// try (FileOutputStream fileOutputStream = new FileOutputStream("D://globalDefaultFontTest.xlsx")) {
// try (FileOutputStream fileOutputStream = new FileOutputStream("D://propertiesTest.xlsx")) {
byte[] bytes = writeWorkbook(wb -> {
wb.setGlobalDefaultFont("Arial", 15.5);
//General properties
// General properties
wb.properties()
.setTitle("title property")
.setCategory("categrovy property")
.setSubject("subject property")
.setKeywords("keywords property")
.setDescription("description property")
.setManager("manager property")
.setCompany("company property")
.setHyperlinkBase("hyperlinkBase property");
//Custom properties
.setTitle("title property<=")
.setCategory("categrovy property<=")
.setSubject("subject property<=")
.setKeywords("keywords property<=")
.setDescription("description property<=")
.setManager("manager property<=")
.setCompany("company property<=")
.setHyperlinkBase("https://github.com/search?q=repo%3Adhatim%2Ffastexcel%20fastexcel&type=code");
// Custom properties
wb.properties()
.setTextProperty("Test TextA", "Lucy")
.setTextProperty("Test TextB", "Tony")
.setTextProperty("Test TextB", "Tony<=")
.setDateProperty("Test DateA", Instant.parse("2022-12-22T10:00:00.123456789Z"))
.setDateProperty("Test DateB", Instant.parse("1999-09-09T09:09:09Z"))
.setNumberProperty("Test NumberA", BigDecimal.valueOf(202222.23364646D))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ class XmlEscapeHelperTest {
"<this will be escaped \ud83d\ude01>,&lt;this will be escaped &#x1f601;&gt;",
"nothing+!()happens,nothing+!()happens"})
public void testEscaping(String input, String expected) {
XmlEscapeHelper xmlEscapeHelper = new XmlEscapeHelper();
assertEquals(expected, xmlEscapeHelper.escape(input));
assertEquals(expected, XmlEscapeHelper.escape(input));
}
}