Skip to content

Commit 20fafa4

Browse files
Merge branch 'dev'
2 parents e718280 + 43374db commit 20fafa4

File tree

10 files changed

+73
-42
lines changed

10 files changed

+73
-42
lines changed

Changelog.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# Change Log
22

3+
## v2.5.5
4+
5+
---
6+
Release Date: **01.10.2025**
7+
8+
- Fixed handling of worksheet protection (selecting locked or unlocked cells)
9+
- Added test case
10+
11+
Note: The default value of `Style.getCellXf().isLocked()` is now true, to be consistent with Excel behavior. This change only affects worksheets with protection enabled and may require
12+
explicit unlocking of cells that should remain editable
13+
314
## v2.5.4
415

516
---

NanoXLSX4j.Demo/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<parent>
66
<artifactId>nanoxlsx4j-root</artifactId>
77
<groupId>ch.rabanti</groupId>
8-
<version>2.5.4</version> <!-- Maintained in lib -->
8+
<version>2.5.5</version> <!-- Maintained in lib -->
99
</parent>
1010
<modelVersion>4.0.0</modelVersion>
1111

NanoXLSX4j.Lib/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>ch.rabanti</groupId>
88
<artifactId>nanoxlsx4j</artifactId>
9-
<version>2.5.4</version> <!-- Maintain also in demo and root project -->
9+
<version>2.5.5</version> <!-- Maintain also in demo and root project -->
1010
<packaging>jar</packaging>
1111

1212
<properties>

NanoXLSX4j.Lib/src/main/java/ch/rabanti/nanoxlsx4j/lowLevel/StyleReader.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -332,9 +332,10 @@ private void getCellXfs(XmlDocument.XmlNode node) {
332332
attribute = protectionNode.getAttribute("locked");
333333
if (attribute != null) {
334334
int value = ReaderUtils.parseBinaryBoolean(attribute);
335-
if (value == 1) {
336-
cellXfStyle.setLocked(true);
335+
if (value == 0) {
336+
cellXfStyle.setLocked(false);
337337
}
338+
// else - NoOp - No need to set locked value, since true by default
338339
}
339340
}
340341

NanoXLSX4j.Lib/src/main/java/ch/rabanti/nanoxlsx4j/lowLevel/XlsxWriter.java

Lines changed: 26 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -498,28 +498,26 @@ private String createSheetProtectionString(Worksheet sheet) {
498498
if (!sheet.isUseSheetProtection()) {
499499
return "";
500500
}
501-
HashMap<Worksheet.SheetProtectionValue, Integer> actualLockingValues = new HashMap<>();
502-
if (sheet.getSheetProtectionValues().isEmpty()) {
503-
actualLockingValues.put(Worksheet.SheetProtectionValue.selectLockedCells, 1);
504-
actualLockingValues.put(Worksheet.SheetProtectionValue.selectUnlockedCells, 1);
505-
}
501+
Map<Worksheet.SheetProtectionValue, Integer> actualLockingValues = new HashMap<>();
506502
if (!sheet.getSheetProtectionValues().contains(Worksheet.SheetProtectionValue.objects)) {
507503
actualLockingValues.put(Worksheet.SheetProtectionValue.objects, 1);
508504
}
509505
if (!sheet.getSheetProtectionValues().contains(Worksheet.SheetProtectionValue.scenarios)) {
510506
actualLockingValues.put(Worksheet.SheetProtectionValue.scenarios, 1);
511507
}
512-
if (!sheet.getSheetProtectionValues().contains(Worksheet.SheetProtectionValue.selectLockedCells)) {
513-
if (!actualLockingValues.containsKey(Worksheet.SheetProtectionValue.selectLockedCells)) {
514-
actualLockingValues.put(Worksheet.SheetProtectionValue.selectLockedCells, 1);
515-
}
508+
boolean allowSelectLocked = sheet.getSheetProtectionValues().contains(Worksheet.SheetProtectionValue.selectLockedCells);
509+
boolean allowSelectUnlocked = sheet.getSheetProtectionValues().contains(Worksheet.SheetProtectionValue.selectUnlockedCells);
510+
if (allowSelectLocked && !allowSelectUnlocked) {
511+
// This shouldn't happen in Excel's UI, but handle it by allowing both
512+
allowSelectUnlocked = true;
516513
}
517-
if (!sheet.getSheetProtectionValues().contains(Worksheet.SheetProtectionValue.selectUnlockedCells) ||
518-
!sheet.getSheetProtectionValues().contains(Worksheet.SheetProtectionValue.selectLockedCells)) {
519-
if (!actualLockingValues.containsKey(Worksheet.SheetProtectionValue.selectUnlockedCells)) {
520-
actualLockingValues.put(Worksheet.SheetProtectionValue.selectUnlockedCells, 1);
521-
}
514+
if (!allowSelectLocked) {
515+
actualLockingValues.put(Worksheet.SheetProtectionValue.selectLockedCells, 1);
516+
}
517+
if (!allowSelectUnlocked) {
518+
actualLockingValues.put(Worksheet.SheetProtectionValue.selectUnlockedCells, 1);
522519
}
520+
// Explicit permissions (set to 0 when allowed)
523521
if (sheet.getSheetProtectionValues().contains(Worksheet.SheetProtectionValue.formatCells)) {
524522
actualLockingValues.put(Worksheet.SheetProtectionValue.formatCells, 0);
525523
}
@@ -554,21 +552,17 @@ private String createSheetProtectionString(Worksheet sheet) {
554552
actualLockingValues.put(Worksheet.SheetProtectionValue.pivotTables, 0);
555553
}
556554
StringBuilder sb = new StringBuilder();
557-
sb.append("<sheetProtection");
555+
sb.append("<sheetProtection sheet=\"1\"");
556+
558557
String temp;
559-
Iterator<Map.Entry<Worksheet.SheetProtectionValue, Integer>> itr;
560-
Map.Entry<Worksheet.SheetProtectionValue, Integer> item;
561-
itr = actualLockingValues.entrySet().iterator();
562-
while (itr.hasNext()) {
563-
item = itr.next();
558+
for (Map.Entry<Worksheet.SheetProtectionValue, Integer> item : actualLockingValues.entrySet()) {
564559
temp = item.getKey().name();// Note! If the enum names differs from the OOXML definitions, this method will
565-
// cause invalid OOXML entries
566-
sb.append(" ").append(temp).append("=\"").append(item.getValue()).append("\"");
560+
sb.append(" ").append(temp).append("=\"").append(item.getValue().toString()).append('"');
567561
}
568562
if (!Helper.isNullOrEmpty(sheet.getSheetProtectionPasswordHash())) {
569-
sb.append(" password=\"").append(sheet.getSheetProtectionPasswordHash()).append("\"");
563+
sb.append(" password=\"").append(sheet.getSheetProtectionPasswordHash()).append('"');
570564
}
571-
sb.append(" sheet=\"1\"/>");
565+
sb.append("/>");
572566
return sb.toString();
573567
}
574568

@@ -939,16 +933,14 @@ else if (style.getCellXf().getVerticalAlign() == CellXf.VerticalAlignValue.top)
939933
sb2.append("/>"); // </xf>
940934
alignmentString = sb2.toString();
941935
}
942-
if (style.getCellXf().isHidden() || style.getCellXf().isLocked()) {
943-
if (style.getCellXf().isHidden() && style.getCellXf().isLocked()) {
944-
protectionString = "<protection locked=\"1\" hidden=\"1\"/>";
945-
}
946-
else if (style.getCellXf().isHidden() && !style.getCellXf().isLocked()) {
947-
protectionString = "<protection hidden=\"1\" locked=\"0\"/>";
948-
}
949-
else {
950-
protectionString = "<protection hidden=\"0\" locked=\"1\"/>";
951-
}
936+
if (style.getCellXf().isHidden() && style.getCellXf().isLocked()) {
937+
protectionString = "<protection hidden=\"1\"/>"; // Locked is true by default (no need to define)
938+
}
939+
else if (style.getCellXf().isHidden() && !style.getCellXf().isLocked()) {
940+
protectionString = "<protection hidden=\"1\" locked=\"0\"/>";
941+
}
942+
else if (!style.getCellXf().isHidden() && !style.getCellXf().isLocked()) {
943+
protectionString = "<protection locked=\"0\"/>";
952944
}
953945
sb.append("<xf numFmtId=\"");
954946
if (style.getNumberFormat().isCustomFormat()) {

NanoXLSX4j.Lib/src/main/java/ch/rabanti/nanoxlsx4j/styles/CellXf.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ public CellXf() {
384384
alignment = DEFAULT_ALIGNMENT;
385385
textDirection = DEFAULT_TEXT_DIRECTION;
386386
verticalAlign = DEFAULT_VERTICAL_ALIGNMENT;
387+
locked = true; // Default in Excel
387388
textRotation = 0;
388389
indent = 0;
389390
}

NanoXLSX4j.Lib/src/test/java/ch/rabanti/nanoxlsx4j/styles/CellXfTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import static org.junit.jupiter.api.Assertions.assertFalse;
1212
import static org.junit.jupiter.api.Assertions.assertNotEquals;
1313
import static org.junit.jupiter.api.Assertions.assertThrows;
14+
import static org.junit.jupiter.api.Assertions.assertTrue;
1415

1516
public class CellXfTest {
1617

@@ -52,7 +53,7 @@ void hiddenTest(boolean value) {
5253
)
5354
void lockedTest(boolean value) {
5455
CellXf cellXf = new CellXf();
55-
assertFalse(cellXf.isLocked());
56+
assertTrue(cellXf.isLocked()); // Locked is set to true ba default (has no effect until protection is enabled))
5657
cellXf.setLocked(value);
5758
assertEquals(value, cellXf.isLocked());
5859
}

NanoXLSX4j.Lib/src/test/java/ch/rabanti/nanoxlsx4j/worksheets/WorksheetWriteReadTest.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,7 @@ void sheetNameWriteReadTest() throws Exception {
520520
"true, 'objects:0', 'scenarios:1,selectLockedCells:1,selectUnlockedCells:1', 0",
521521
"true, 'scenarios:0', 'objects:1,selectLockedCells:1,selectUnlockedCells:1', 0",
522522
"true, 'selectLockedCells:0', 'objects:1,scenarios:1', 0",
523-
"true, 'selectUnlockedCells:0', 'objects:1,scenarios:1,selectLockedCells:1,selectUnlockedCells:0', 0",
523+
"true, 'selectUnlockedCells:0', 'objects:1,scenarios:1,selectLockedCells:1', 0",
524524
"false, '', '', 1",
525525
"false, 'autoFilter:0', '', 2",
526526
"true, '', 'objects:1,scenarios:1,selectLockedCells:1,selectUnlockedCells:1', 3",
@@ -565,6 +565,31 @@ void sheetProtectionWriteReadTest(boolean useSheetProtection, String givenProtec
565565
}
566566
}
567567

568+
@Test()
569+
@DisplayName("Test of the 'SheetProtectionValues' when setting selectLockedCells alone (causes auto-fix)")
570+
public void sheetProtectionWriteReadTest2() throws Exception {
571+
int sheetIndex = 0;
572+
Workbook workbook = prepareWorkbook(4, "test");
573+
for (int i = 0; i <= sheetIndex; i++) {
574+
if (sheetIndex == i) {
575+
workbook.setCurrentWorksheet(i);
576+
workbook.getCurrentWorksheet().addAllowedActionOnSheetProtection(Worksheet.SheetProtectionValue.selectUnlockedCells);
577+
workbook.getCurrentWorksheet().addAllowedActionOnSheetProtection(Worksheet.SheetProtectionValue.selectLockedCells);
578+
// Override default (technically invalid)
579+
workbook.getCurrentWorksheet().removeAllowedActionOnSheetProtection(Worksheet.SheetProtectionValue.selectUnlockedCells);
580+
workbook.getCurrentWorksheet().setUseSheetProtection(true);
581+
}
582+
}
583+
Worksheet givenWorksheet = writeAndReadWorksheet(workbook, sheetIndex);
584+
assertEquals(2, givenWorksheet.getSheetProtectionValues().size());
585+
assertTrue(givenWorksheet.isUseSheetProtection());
586+
assertTrue(givenWorksheet.getSheetProtectionValues().contains(Worksheet.SheetProtectionValue.objects));
587+
assertTrue(givenWorksheet.getSheetProtectionValues().contains(Worksheet.SheetProtectionValue.scenarios));
588+
assertFalse(givenWorksheet.getSheetProtectionValues().contains(Worksheet.SheetProtectionValue.selectLockedCells));
589+
assertFalse(givenWorksheet.getSheetProtectionValues().contains(Worksheet.SheetProtectionValue.selectUnlockedCells));
590+
}
591+
592+
568593
@DisplayName("Test of the 'sheetProtectionPasswordHash' property when writing and reading a worksheet")
569594
@ParameterizedTest(name = "Given password \"{0}\" should lead to the same hash on worksheet {1} when writing and reading a worksheet")
570595
@CsvSource(

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ Add the following information to your POM file within the ```<dependencies>``` t
6868
<dependency>
6969
<groupId>ch.rabanti</groupId>
7070
<artifactId>nanoxlsx4j</artifactId>
71-
<version>2.5.4</version>
71+
<version>2.5.5</version>
7272
</dependency>
7373
```
7474

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<groupId>ch.rabanti</groupId>
88
<artifactId>nanoxlsx4j-root</artifactId>
99
<packaging>pom</packaging>
10-
<version>2.5.4</version>
10+
<version>2.5.5</version>
1111
<modules>
1212
<module>NanoXLSX4j.Lib</module>
1313
<!-- Do not add this to build -->

0 commit comments

Comments
 (0)