Browse Source

resolve #273 new feature: protect sheet support

new feature: protect sheet support, relate issue #273
HcySunYang 7 years ago
parent
commit
4dbc78ce0a
5 changed files with 123 additions and 20 deletions
  1. 13 0
      excelize_test.go
  2. 28 0
      lib.go
  3. 41 0
      sheet.go
  4. BIN
      test/Book1.xlsx
  5. 41 20
      xmlWorksheet.go

+ 13 - 0
excelize_test.go

@@ -1208,6 +1208,19 @@ func TestSearchSheet(t *testing.T) {
 	t.Log(xlsx.SearchSheet("Sheet1", "A"))
 }
 
+func TestProtectSheet(t *testing.T) {
+	xlsx := NewFile()
+	xlsx.ProtectSheet("Sheet1", nil)
+	xlsx.ProtectSheet("Sheet1", &FormatSheetProtection{
+		Password:      "password",
+		EditScenarios: false,
+	})
+	err := xlsx.SaveAs("./test/Book_protect_sheet.xlsx")
+	if err != nil {
+		t.Error(err)
+	}
+}
+
 func trimSliceSpace(s []string) []string {
 	for {
 		if len(s) > 0 && s[len(s)-1] == "" {

+ 28 - 0
lib.go

@@ -15,6 +15,8 @@ import (
 	"io"
 	"log"
 	"math"
+	"strconv"
+	"strings"
 	"unicode"
 )
 
@@ -199,3 +201,29 @@ func namespaceStrictToTransitional(content []byte) []byte {
 	}
 	return content
 }
+
+// genSheetPasswd provides a method to generate password for worksheet
+// protection by given plaintext. When an Excel sheet is being protected with
+// a password, a 16-bit (two byte) long hash is generated. To verify a
+// password, it is compared to the hash. Obviously, if the input data volume
+// is great, numerous passwords will match the same hash. Here is the
+// algorithm to create the hash value:
+//
+// take the ASCII values of all characters shift left the first character 1 bit, the second 2 bits and so on (use only the lower 15 bits and rotate all higher bits, the highest bit of the 16-bit value is always 0 [signed short])
+// XOR all these values
+// XOR the count of characters
+// XOR the constant 0xCE4B
+func genSheetPasswd(plaintext string) string {
+	var password int64 = 0x0000
+	var charPos uint = 1
+	for _, v := range plaintext {
+		value := int64(v) << charPos
+		charPos++
+		rotatedBits := value >> 15 // rotated bits beyond bit 15
+		value &= 0x7fff            // first 15 bits
+		password ^= (value | rotatedBits)
+	}
+	password ^= int64(len(plaintext))
+	password ^= 0xCE4B
+	return strings.ToUpper(strconv.FormatInt(password, 16))
+}

+ 41 - 0
sheet.go

@@ -711,6 +711,47 @@ func (f *File) SearchSheet(sheet, value string) []string {
 	return result
 }
 
+// ProtectSheet provides a function to prevent other users from accidentally
+// or deliberately changing, moving, or deleting data in a worksheet. For
+// example protect Sheet1 with protection settings:
+//
+//    xlsx.ProtectSheet("Sheet1", &excelize.FormatSheetProtection{
+//        Password:      "password",
+//        EditScenarios: false,
+//    })
+//
+func (f *File) ProtectSheet(sheet string, settings *FormatSheetProtection) {
+	xlsx := f.workSheetReader(sheet)
+	if settings == nil {
+		settings = &FormatSheetProtection{
+			EditObjects:       true,
+			EditScenarios:     true,
+			SelectLockedCells: true,
+		}
+	}
+	xlsx.SheetProtection = &xlsxSheetProtection{
+		AutoFilter:          settings.AutoFilter,
+		DeleteColumns:       settings.DeleteColumns,
+		DeleteRows:          settings.DeleteRows,
+		FormatCells:         settings.FormatCells,
+		FormatColumns:       settings.FormatColumns,
+		FormatRows:          settings.FormatRows,
+		InsertColumns:       settings.InsertColumns,
+		InsertHyperlinks:    settings.InsertHyperlinks,
+		InsertRows:          settings.InsertRows,
+		Objects:             settings.EditObjects,
+		PivotTables:         settings.PivotTables,
+		Scenarios:           settings.EditScenarios,
+		SelectLockedCells:   settings.SelectLockedCells,
+		SelectUnlockedCells: settings.SelectUnlockedCells,
+		Sheet:               true,
+		Sort:                settings.Sort,
+	}
+	if settings.Password != "" {
+		xlsx.SheetProtection.Password = genSheetPasswd(settings.Password)
+	}
+}
+
 // trimSheetName provides a function to trim invaild characters by given worksheet
 // name.
 func trimSheetName(name string) string {

BIN
test/Book1.xlsx


+ 41 - 20
xmlWorksheet.go

@@ -377,26 +377,27 @@ type xlsxF struct {
 // xlsxSheetProtection collection expresses the sheet protection options to
 // enforce when the sheet is protected.
 type xlsxSheetProtection struct {
-	AlgorithmName      string `xml:"algorithmName,attr,omitempty"`
-	AutoFilter         int    `xml:"autoFilter,attr,omitempty"`
-	DeleteColumns      int    `xml:"deleteColumns,attr,omitempty"`
-	DeleteRows         int    `xml:"deleteRows,attr,omitempty"`
-	FormatCells        int    `xml:"formatCells,attr,omitempty"`
-	FormatColumns      int    `xml:"formatColumns,attr,omitempty"`
-	FormatRows         int    `xml:"formatRows,attr,omitempty"`
-	HashValue          string `xml:"hashValue,attr,omitempty"`
-	InsertColumns      int    `xml:"insertColumns,attr,omitempty"`
-	InsertHyperlinks   int    `xml:"insertHyperlinks,attr,omitempty"`
-	InsertRows         int    `xml:"insertRows,attr,omitempty"`
-	Objects            int    `xml:"objects,attr,omitempty"`
-	PivotTables        int    `xml:"pivotTables,attr,omitempty"`
-	SaltValue          string `xml:"saltValue,attr,omitempty"`
-	Scenarios          int    `xml:"scenarios,attr,omitempty"`
-	SelectLockedCells  int    `xml:"selectLockedCells,attr,omitempty"`
-	SelectUnlockedCell int    `xml:"selectUnlockedCell,attr,omitempty"`
-	Sheet              int    `xml:"sheet,attr,omitempty"`
-	Sort               int    `xml:"sort,attr,omitempty"`
-	SpinCount          int    `xml:"spinCount,attr,omitempty"`
+	AlgorithmName       string `xml:"algorithmName,attr,omitempty"`
+	AutoFilter          bool   `xml:"autoFilter,attr,omitempty"`
+	DeleteColumns       bool   `xml:"deleteColumns,attr,omitempty"`
+	DeleteRows          bool   `xml:"deleteRows,attr,omitempty"`
+	FormatCells         bool   `xml:"formatCells,attr,omitempty"`
+	FormatColumns       bool   `xml:"formatColumns,attr,omitempty"`
+	FormatRows          bool   `xml:"formatRows,attr,omitempty"`
+	HashValue           string `xml:"hashValue,attr,omitempty"`
+	InsertColumns       bool   `xml:"insertColumns,attr,omitempty"`
+	InsertHyperlinks    bool   `xml:"insertHyperlinks,attr,omitempty"`
+	InsertRows          bool   `xml:"insertRows,attr,omitempty"`
+	Objects             bool   `xml:"objects,attr,omitempty"`
+	Password            string `xml:"password,attr,omitempty"`
+	PivotTables         bool   `xml:"pivotTables,attr,omitempty"`
+	SaltValue           string `xml:"saltValue,attr,omitempty"`
+	Scenarios           bool   `xml:"scenarios,attr,omitempty"`
+	SelectLockedCells   bool   `xml:"selectLockedCells,attr,omitempty"`
+	SelectUnlockedCells bool   `xml:"selectUnlockedCells,attr,omitempty"`
+	Sheet               bool   `xml:"sheet,attr,omitempty"`
+	Sort                bool   `xml:"sort,attr,omitempty"`
+	SpinCount           int    `xml:"spinCount,attr,omitempty"`
 }
 
 // xlsxPhoneticPr (Phonetic Properties) represents a collection of phonetic
@@ -599,3 +600,23 @@ type formatConditional struct {
 	MultiRange   string `json:"multi_range,omitempty"`
 	BarColor     string `json:"bar_color,omitempty"`
 }
+
+// FormatSheetProtection directly maps the settings of worksheet protection.
+type FormatSheetProtection struct {
+	AutoFilter          bool
+	DeleteColumns       bool
+	DeleteRows          bool
+	EditObjects         bool
+	EditScenarios       bool
+	FormatCells         bool
+	FormatColumns       bool
+	FormatRows          bool
+	InsertColumns       bool
+	InsertHyperlinks    bool
+	InsertRows          bool
+	Password            string
+	PivotTables         bool
+	SelectLockedCells   bool
+	SelectUnlockedCells bool
+	Sort                bool
+}