Skip to content

Commit 01778d4

Browse files
gmuldersOlivier Chédru
authored andcommitted
feature unlock hide cells (#54)
1 parent caa7339 commit 01778d4

File tree

7 files changed

+195
-14
lines changed

7 files changed

+195
-14
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package org.dhatim.fastexcel;
2+
3+
import java.io.IOException;
4+
import java.util.Map;
5+
import java.util.Objects;
6+
7+
/**
8+
* Represents the <protection> xml-tag.
9+
*/
10+
public class Protection {
11+
12+
private final Map<ProtectionOption, Boolean> options;
13+
14+
public Protection(Map<ProtectionOption, Boolean> options) {
15+
if (options == null) {
16+
throw new NullPointerException("Options should not be null");
17+
}
18+
this.options = options;
19+
}
20+
21+
@Override
22+
public int hashCode() {
23+
return options.hashCode();
24+
}
25+
26+
@Override
27+
public boolean equals(Object o) {
28+
if (this == o) return true;
29+
if (o == null || getClass() != o.getClass()) return false;
30+
Protection that = (Protection) o;
31+
return Objects.equals(options, that.options);
32+
}
33+
34+
/**
35+
* Write this protection as an XML element.
36+
*
37+
* @param w Output writer.
38+
* @throws IOException If an I/O error occurs.
39+
*/
40+
void write(Writer w) throws IOException {
41+
w.append("<protection ");
42+
for (Map.Entry<ProtectionOption, Boolean> option : options.entrySet()) {
43+
w.append(option.getKey().getName()).append("=\"").append(option.getValue().toString()).append("\" ");
44+
}
45+
w.append("/>");
46+
}
47+
48+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package org.dhatim.fastexcel;
2+
3+
/**
4+
* Represents an attribute on the &lt;protection&gt; xml-tag.
5+
*/
6+
public enum ProtectionOption {
7+
8+
/**
9+
* A boolean value indicating if the cell is hidden. When the cell is hidden and the sheet on which the cell resides
10+
* is protected, then the cell value will be displayed in the cell grid location, but the contents of the cell will
11+
* not be displayed in the formula bar. This is true for all types of cell content, including formula, text, or
12+
* numbers.
13+
*
14+
* Therefore the cell A4 may contain a formula "=SUM(A1:A3)", but if the cell protection property of A4 is marked as
15+
* hidden, and the sheet is protected, then the cell should display the calculated result (for example, "6"), but
16+
* will not display the formula used to calculate the result.
17+
*/
18+
HIDDEN("hidden"),
19+
20+
/**
21+
* A boolean value indicating if the cell is locked. When cells are marked as "locked" and the sheet is protected,
22+
* then the options specified in the Sheet Part's &lt;sheetProtection&gt; element (§3.3.1.81) are prohibited for
23+
* these cells.
24+
*/
25+
LOCKED("locked");
26+
27+
private final String name;
28+
29+
/**
30+
* Constructor that sets the name.
31+
*
32+
* @param name The name of the protection option.
33+
*/
34+
ProtectionOption(String name) {
35+
this.name = name;
36+
}
37+
38+
public String getName() {
39+
return name;
40+
}
41+
}

fastexcel-writer/src/main/java/org/dhatim/fastexcel/Style.java

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ class Style {
4545
*/
4646
private final Alignment alignment;
4747

48+
/**
49+
* The protection settings.
50+
*/
51+
private final Protection protection;
52+
4853
/**
4954
* Constructor.
5055
*
@@ -56,25 +61,31 @@ class Style {
5661
* @param border Index of cached border. Zero if not set.
5762
* @param alignment Alignment. {@code null} if not set.
5863
*/
59-
Style(Style original, int valueFormatting, int font, int fill, int border, Alignment alignment) {
64+
Style(Style original, int valueFormatting, int font, int fill, int border, Alignment alignment, Protection protection) {
6065
this.valueFormatting = (valueFormatting == 0 && original != null) ? original.valueFormatting : valueFormatting;
6166
this.font = (font == 0 && original != null) ? original.font : font;
6267
this.fill = (fill == 0 && original != null) ? original.fill : fill;
6368
this.border = (border == 0 && original != null) ? original.border : border;
6469
this.alignment = (alignment == null && original != null) ? original.alignment : alignment;
70+
this.protection = (protection == null && original != null) ? original.protection : protection;
6571
}
6672

6773
@Override
6874
public int hashCode() {
69-
return Objects.hash(valueFormatting, font, fill, border, alignment);
75+
return Objects.hash(valueFormatting, font, fill, border, alignment, protection);
7076
}
7177

7278
@Override
7379
public boolean equals(Object obj) {
7480
boolean result;
7581
if (obj != null && obj.getClass() == this.getClass()) {
7682
Style other = (Style) obj;
77-
result = Objects.equals(valueFormatting, other.valueFormatting) && Objects.equals(font, other.font) && Objects.equals(fill, other.fill) && Objects.equals(border, other.border) && Objects.equals(alignment, other.alignment);
83+
result = Objects.equals(valueFormatting, other.valueFormatting)
84+
&& Objects.equals(font, other.font)
85+
&& Objects.equals(fill, other.fill)
86+
&& Objects.equals(border, other.border)
87+
&& Objects.equals(alignment, other.alignment)
88+
&& Objects.equals(protection, other.protection);
7889
} else {
7990
result = false;
8091
}
@@ -92,12 +103,19 @@ void write(Writer w) throws IOException {
92103
if (border != 0) {
93104
w.append(" applyBorder=\"1\"");
94105
}
95-
if (alignment == null) {
106+
107+
if (alignment == null && protection == null) {
96108
w.append("/>");
97-
} else {
98-
w.append('>');
109+
return;
110+
}
111+
112+
w.append('>');
113+
if (alignment != null) {
99114
alignment.write(w);
100-
w.append("</xf>");
101115
}
116+
if (protection != null) {
117+
protection.write(w);
118+
}
119+
w.append("</xf>");
102120
}
103121
}

fastexcel-writer/src/main/java/org/dhatim/fastexcel/StyleCache.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.io.IOException;
1919
import java.util.ArrayList;
20+
import java.util.Comparator;
2021
import java.util.List;
2122
import java.util.Map;
2223
import java.util.Map.Entry;
@@ -41,7 +42,7 @@ final class StyleCache {
4142
* Default constructor. Pre-cache Excel-reserved stuff.
4243
*/
4344
StyleCache() {
44-
mergeAndCacheStyle(0, null, Font.DEFAULT, Fill.NONE, Border.NONE, null);
45+
mergeAndCacheStyle(0, null, Font.DEFAULT, Fill.NONE, Border.NONE, null, null);
4546
cacheFill(Fill.GRAY125);
4647
}
4748

@@ -131,9 +132,9 @@ int cacheDxf(Fill f) {
131132
return cacheStuff(dxfs, f);
132133
}
133134

134-
int mergeAndCacheStyle(int currentStyle, String numberingFormat, Font font, Fill fill, Border border, Alignment alignment) {
135+
int mergeAndCacheStyle(int currentStyle, String numberingFormat, Font font, Fill fill, Border border, Alignment alignment, Protection protection) {
135136
Style original = styles.entrySet().stream().filter(e -> e.getValue().equals(currentStyle)).map(Entry::getKey).findFirst().orElse(null);
136-
Style s = new Style(original, cacheValueFormatting(numberingFormat), cacheFont(font), cacheFill(fill), cacheBorder(border), alignment);
137+
Style s = new Style(original, cacheValueFormatting(numberingFormat), cacheFont(font), cacheFill(fill), cacheBorder(border), alignment, protection);
137138
return cacheStuff(styles, s);
138139
}
139140

@@ -150,7 +151,7 @@ int mergeAndCacheStyle(int currentStyle, String numberingFormat, Font font, Fill
150151
private static <T> void writeCache(Writer w, Map<T, Integer> cache, String name, ThrowingConsumer<Entry<T, Integer>> consumer) throws IOException {
151152
w.append('<').append(name).append(" count=\"").append(cache.size()).append("\">");
152153
List<Entry<T, Integer>> entries = new ArrayList<>(cache.entrySet());
153-
entries.sort((e1, e2) -> Integer.compare(e1.getValue(), e2.getValue()));
154+
entries.sort(Comparator.comparingInt(Entry::getValue));
154155
for (Entry<T, Integer> e : entries) {
155156
consumer.accept(e);
156157
}

fastexcel-writer/src/main/java/org/dhatim/fastexcel/StyleSetter.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.dhatim.fastexcel;
1717

1818
import java.math.BigDecimal;
19+
import java.util.EnumMap;
1920
import java.util.EnumSet;
2021
import java.util.Map;
2122
import java.util.Set;
@@ -86,6 +87,11 @@ public class StyleSetter {
8687
*/
8788
private Border border;
8889

90+
/**
91+
* Protection options.
92+
*/
93+
private Map<ProtectionOption, Boolean> protectionOptions;
94+
8995
/**
9096
* Constructor.
9197
*
@@ -307,6 +313,21 @@ public StyleSetter borderColor(BorderSide side, String borderColor) {
307313
return borderElement(side, border.elements.get(side).updateColor(borderColor));
308314
}
309315

316+
/**
317+
* Sets the value for a protection option.
318+
*
319+
* @param option The option to set
320+
* @param value The value to set for the given option.
321+
* @return This style setter.
322+
*/
323+
public StyleSetter protectionOption(ProtectionOption option, Boolean value) {
324+
if (protectionOptions == null) {
325+
protectionOptions = new EnumMap<>(ProtectionOption.class);
326+
}
327+
protectionOptions.put(option, value);
328+
return this;
329+
}
330+
310331
/**
311332
* Merge cells in this style setter's range.
312333
*
@@ -344,9 +365,16 @@ public void set() {
344365
border = Border.NONE;
345366
}
346367

368+
Protection protection;
369+
if (protectionOptions != null) {
370+
protection = new Protection(protectionOptions);
371+
} else {
372+
protection = null;
373+
}
374+
347375
// Compute a map giving new styles for current styles
348376
Set<Integer> currentStyles = range.getStyles();
349-
Map<Integer, Integer> newStyles = currentStyles.stream().collect(Collectors.toMap(Function.identity(), s -> range.getWorksheet().getWorkbook().mergeAndCacheStyle(s, valueFormatting, font, fill, border, alignment)));
377+
Map<Integer, Integer> newStyles = currentStyles.stream().collect(Collectors.toMap(Function.identity(), s -> range.getWorksheet().getWorkbook().mergeAndCacheStyle(s, valueFormatting, font, fill, border, alignment, protection)));
350378

351379
// Apply styles to range
352380
range.applyStyle(newStyles);

fastexcel-writer/src/main/java/org/dhatim/fastexcel/Workbook.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,8 @@ CachedString cacheString(String s) {
185185
* @param alignment Alignment attributes.
186186
* @return Cached style index.
187187
*/
188-
int mergeAndCacheStyle(int currentStyle, String numberingFormat, Font font, Fill fill, Border border, Alignment alignment) {
189-
return styleCache.mergeAndCacheStyle(currentStyle, numberingFormat, font, fill, border, alignment);
188+
int mergeAndCacheStyle(int currentStyle, String numberingFormat, Font font, Fill fill, Border border, Alignment alignment, Protection protection) {
189+
return styleCache.mergeAndCacheStyle(currentStyle, numberingFormat, font, fill, border, alignment, protection);
190190
}
191191

192192
/**

fastexcel-writer/src/test/java/org/dhatim/fastexcel/Correctness.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,7 @@ public void canHideSheet() throws IOException {
500500
assertThat(xwb.getSheetVisibility(3)).isEqualTo(SheetVisibility.VISIBLE);
501501
}
502502

503+
@Test
503504
public void canHideColumns() throws Exception {
504505

505506
byte[] data = writeWorkbook(wb -> {
@@ -610,4 +611,48 @@ public void shouldSetSpecificSheetProtectionOptions() throws IOException {
610611
assertThat(xws.isSelectLockedCellsLocked()).isFalse();
611612
assertThat(xws.isSelectUnlockedCellsLocked()).isFalse();
612613
}
614+
615+
@Test
616+
public void canProtectCells() throws Exception {
617+
618+
byte[] data = writeWorkbook(wb -> {
619+
Worksheet ws = wb.newWorksheet("Worksheet 1");
620+
621+
// Protect the sheet
622+
ws.protect("HorriblePassword");
623+
624+
ws.value(0, 1, "val1");
625+
ws.style(0, 1)
626+
.protectionOption(ProtectionOption.LOCKED, true).set();
627+
628+
ws.value(0, 2, "val2");
629+
ws.style(0, 2)
630+
.protectionOption(ProtectionOption.HIDDEN, true).set();
631+
632+
ws.value(0, 3, "val3");
633+
ws.style(0, 3)
634+
.protectionOption(ProtectionOption.LOCKED, false).set();
635+
636+
ws.value(0, 4, "val4");
637+
ws.style(0, 4)
638+
.protectionOption(ProtectionOption.LOCKED, false)
639+
.protectionOption(ProtectionOption.HIDDEN, false).set();
640+
});
641+
642+
// Check generated workbook with Apache POI
643+
XSSFWorkbook xwb = new XSSFWorkbook(new ByteArrayInputStream(data));
644+
XSSFSheet xws = xwb.getSheetAt(0);
645+
646+
assertFalse("Cell (0, 1) should NOT be hidden", xws.getRow(0).getCell(1).getCellStyle().getHidden());
647+
assertTrue("Cell (0, 1) should be locked", xws.getRow(0).getCell(1).getCellStyle().getLocked());
648+
649+
assertTrue("Cell (0, 2) should be locked (by default)", xws.getRow(0).getCell(2).getCellStyle().getLocked());
650+
assertTrue("Cell (0, 2) should be hidden", xws.getRow(0).getCell(2).getCellStyle().getHidden());
651+
652+
assertFalse("Cell (0, 3) should NOT be hidden", xws.getRow(0).getCell(3).getCellStyle().getHidden());
653+
assertFalse("Cell (0, 3) should NOT be locked", xws.getRow(0).getCell(3).getCellStyle().getLocked());
654+
655+
assertFalse("Cell (0, 4) should NOT be locked", xws.getRow(0).getCell(4).getCellStyle().getLocked());
656+
assertFalse("Cell (0, 4) should NOT be hidden", xws.getRow(0).getCell(4).getCellStyle().getHidden());
657+
}
613658
}

0 commit comments

Comments
 (0)