mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-24 16:03:43 +08:00
Merge pull request #16423 from gouqi11/addExcelutilsAddCellStyleByColmun
fix(excelutils): 添加按列修改单元格格式方法
This commit is contained in:
@@ -62,6 +62,7 @@ require (
|
||||
github.com/vishvananda/netlink v1.1.0
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74
|
||||
github.com/vmihailenco/msgpack v4.0.4+incompatible
|
||||
github.com/xuri/excelize/v2 v2.7.1
|
||||
go.etcd.io/etcd/api/v3 v3.5.0
|
||||
go.etcd.io/etcd/client/v3 v3.5.0
|
||||
golang.org/x/crypto v0.8.0
|
||||
@@ -220,6 +221,8 @@ require (
|
||||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a // indirect
|
||||
github.com/richardlehane/mscfb v1.0.4 // indirect
|
||||
github.com/richardlehane/msoleps v1.0.3 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
@@ -237,6 +240,8 @@ require (
|
||||
github.com/vmware/govmomi v0.20.1 // indirect
|
||||
github.com/willf/bitset v1.1.9 // indirect
|
||||
github.com/willf/bloom v2.0.3+incompatible // indirect
|
||||
github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 // indirect
|
||||
github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.0 // indirect
|
||||
go.opencensus.io v0.22.4 // indirect
|
||||
|
||||
@@ -621,6 +621,11 @@ github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0
|
||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
|
||||
github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
|
||||
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||
github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM=
|
||||
github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
@@ -721,11 +726,18 @@ github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPyS
|
||||
github.com/willf/bloom v0.0.0-20170505221640-54e3b963ee16/go.mod h1:MmAltL9pDMNTrvUkxdg0k0q5I0suxmuwp3KbyrZLOZ8=
|
||||
github.com/willf/bloom v2.0.3+incompatible h1:QDacWdqcAUI1MPOwIQZRy9kOR7yxfyEmxX8Wdm2/JPA=
|
||||
github.com/willf/bloom v2.0.3+incompatible/go.mod h1:MmAltL9pDMNTrvUkxdg0k0q5I0suxmuwp3KbyrZLOZ8=
|
||||
github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 h1:6932x8ltq1w4utjmfMPVj09jdMlkY0aiA6+Skbtl3/c=
|
||||
github.com/xuri/efp v0.0.0-20220603152613-6918739fd470/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
||||
github.com/xuri/excelize/v2 v2.7.1 h1:gm8q0UCAyaTt3MEF5wWMjVdmthm2EHAWesGSKS9tdVI=
|
||||
github.com/xuri/excelize/v2 v2.7.1/go.mod h1:qc0+2j4TvAUrBw36ATtcTeC1VCM0fFdAXZOmcF4nTpY=
|
||||
github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 h1:OAmKAfT06//esDdpi/DZ8Qsdt4+M5+ltca05dA5bG2M=
|
||||
github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.etcd.io/etcd/api/v3 v3.5.0 h1:GsV3S+OfZEOCNXdtNkBSR7kgLobAa/SO6tCxRa0GAYw=
|
||||
@@ -758,6 +770,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
|
||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -772,6 +785,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
|
||||
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@@ -794,6 +809,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180524181706-dfa909b99c79/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -833,8 +849,11 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R
|
||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -855,6 +874,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180724212812-e072cadbbdc8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -913,15 +933,20 @@ golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -931,6 +956,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -988,6 +1015,7 @@ golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc
|
||||
golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/360EntSecGroup-Skylar/excelize"
|
||||
excelize "github.com/xuri/excelize/v2"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/errors"
|
||||
@@ -184,7 +184,10 @@ func AddNewSheet(data []jsonutils.JSONObject, keys []string, texts []string, she
|
||||
|
||||
func AddChartWithSheet(key, sheet string, chartType ExcelChartType, f *excelize.File) (*excelize.File, error) {
|
||||
// 获取对应sheet中的所有header
|
||||
keys := f.GetRows(sheet)
|
||||
keys, err := f.GetRows(sheet)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GetRows")
|
||||
}
|
||||
if len(keys) == 0 {
|
||||
return nil, errors.Errorf("empty sheet")
|
||||
}
|
||||
@@ -212,7 +215,9 @@ func AddChartWithSheet(key, sheet string, chartType ExcelChartType, f *excelize.
|
||||
},
|
||||
}
|
||||
paramObj := jsonutils.Marshal(params)
|
||||
err := f.AddChart(sheet, "A1", paramObj.PrettyString())
|
||||
excelChart := &excelize.Chart{}
|
||||
paramObj.Unmarshal(excelChart)
|
||||
err = f.AddChart(sheet, "A1", excelChart)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -223,3 +228,42 @@ func CheckChartType(chartType string) bool {
|
||||
_, isExist := ChartMap[ExcelChartType(chartType)]
|
||||
return isExist
|
||||
}
|
||||
|
||||
// 按列设置单元格格式
|
||||
func SetCellStyleWithColumnKey(keys []string, sheet, style string, f *excelize.File) (*excelize.File, error) {
|
||||
styleID, err := f.NewStyle(&excelize.Style{CustomNumFmt: &style})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "NewStyle")
|
||||
}
|
||||
// 优先按行获取所有数据
|
||||
rows, err := f.GetRows(sheet)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GetRows")
|
||||
}
|
||||
if len(rows) == 0 {
|
||||
return nil, errors.Errorf("rows is empty")
|
||||
}
|
||||
// 所需设置key的索引
|
||||
columnIndex := []int{}
|
||||
// 遍历第一行的所有值
|
||||
for index, value := range rows[0] {
|
||||
for _, key := range keys {
|
||||
// 若第一行的值与目标key相等
|
||||
if key == value {
|
||||
columnIndex = append(columnIndex, index)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 遍历所需设置的索引
|
||||
for _, index := range columnIndex {
|
||||
// 根据索引获取excel的列名
|
||||
colName, err := excelize.ColumnNumberToName(index + 1)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "ColumnNumberToName")
|
||||
}
|
||||
if err := f.SetCellStyle(sheet, fmt.Sprintf("%s%d", colName, 2), fmt.Sprintf("%s%d", colName, len(rows)), styleID); err != nil {
|
||||
return nil, errors.Wrap(err, "SetCellStyle")
|
||||
}
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
language: go
|
||||
go:
|
||||
- stable
|
||||
- tip
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
A reader for Microsoft's Compound File Binary File Format.
|
||||
|
||||
Example usage:
|
||||
|
||||
file, _ := os.Open("test/test.doc")
|
||||
defer file.Close()
|
||||
doc, err := mscfb.New(file)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for entry, err := doc.Next(); err == nil; entry, err = doc.Next() {
|
||||
buf := make([]byte, 512)
|
||||
i, _ := doc.Read(buf)
|
||||
if i > 0 {
|
||||
fmt.Println(buf[:i])
|
||||
}
|
||||
fmt.Println(entry.Name)
|
||||
}
|
||||
|
||||
The Compound File Binary File Format is also known as the Object Linking and Embedding (OLE) or Component Object Model (COM) format and was used by early MS software such as MS Office. See [http://msdn.microsoft.com/en-us/library/dd942138.aspx](http://msdn.microsoft.com/en-us/library/dd942138.aspx) for more details
|
||||
|
||||
Install with `go get github.com/richardlehane/mscfb`
|
||||
|
||||
[](https://travis-ci.org/richardlehane/mscfb)
|
||||
+534
@@ -0,0 +1,534 @@
|
||||
// Copyright 2013 Richard Lehane. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mscfb
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf16"
|
||||
|
||||
"github.com/richardlehane/msoleps/types"
|
||||
)
|
||||
|
||||
//objectType types
|
||||
const (
|
||||
unknown uint8 = 0x0 // this means unallocated - typically zeroed dir entries
|
||||
storage uint8 = 0x1 // this means dir
|
||||
stream uint8 = 0x2 // this means file
|
||||
rootStorage uint8 = 0x5 // this means root
|
||||
)
|
||||
|
||||
// color flags
|
||||
const (
|
||||
red uint8 = 0x0
|
||||
black uint8 = 0x1
|
||||
)
|
||||
|
||||
const lenDirEntry int = 64 + 4*4 + 16 + 4 + 8*2 + 4 + 8
|
||||
|
||||
type directoryEntryFields struct {
|
||||
rawName [32]uint16 //64 bytes, unicode string encoded in UTF-16. If root, "Root Entry\0" w
|
||||
nameLength uint16 //2 bytes
|
||||
objectType uint8 //1 byte Must be one of the types specified above
|
||||
color uint8 //1 byte Must be 0x00 RED or 0x01 BLACK
|
||||
leftSibID uint32 //4 bytes, Dir? Stream ID of left sibling, if none set to NOSTREAM
|
||||
rightSibID uint32 //4 bytes, Dir? Stream ID of right sibling, if none set to NOSTREAM
|
||||
childID uint32 //4 bytes, Dir? Stream ID of child object, if none set to NOSTREAM
|
||||
clsid types.Guid // Contains an object class GUID (must be set to zeroes for stream object)
|
||||
stateBits [4]byte // user-defined flags for storage object
|
||||
create types.FileTime // Windows FILETIME structure
|
||||
modify types.FileTime // Windows FILETIME structure
|
||||
startingSectorLoc uint32 // if a stream object, first sector location. If root, first sector of ministream
|
||||
streamSize [8]byte // if a stream, size of user-defined data. If root, size of ministream
|
||||
}
|
||||
|
||||
func makeDirEntry(b []byte) *directoryEntryFields {
|
||||
d := &directoryEntryFields{}
|
||||
for i := range d.rawName {
|
||||
d.rawName[i] = binary.LittleEndian.Uint16(b[i*2 : i*2+2])
|
||||
}
|
||||
d.nameLength = binary.LittleEndian.Uint16(b[64:66])
|
||||
d.objectType = uint8(b[66])
|
||||
d.color = uint8(b[67])
|
||||
d.leftSibID = binary.LittleEndian.Uint32(b[68:72])
|
||||
d.rightSibID = binary.LittleEndian.Uint32(b[72:76])
|
||||
d.childID = binary.LittleEndian.Uint32(b[76:80])
|
||||
d.clsid = types.MustGuid(b[80:96])
|
||||
copy(d.stateBits[:], b[96:100])
|
||||
d.create = types.MustFileTime(b[100:108])
|
||||
d.modify = types.MustFileTime(b[108:116])
|
||||
d.startingSectorLoc = binary.LittleEndian.Uint32(b[116:120])
|
||||
copy(d.streamSize[:], b[120:128])
|
||||
return d
|
||||
}
|
||||
|
||||
func (r *Reader) setDirEntries() error {
|
||||
c := 20
|
||||
if r.header.numDirectorySectors > 0 {
|
||||
c = int(r.header.numDirectorySectors)
|
||||
}
|
||||
de := make([]*File, 0, c)
|
||||
cycles := make(map[uint32]bool)
|
||||
num := int(r.sectorSize / 128)
|
||||
sn := r.header.directorySectorLoc
|
||||
for sn != endOfChain {
|
||||
buf, err := r.readAt(fileOffset(r.sectorSize, sn), int(r.sectorSize))
|
||||
if err != nil {
|
||||
return Error{ErrRead, "directory entries read error (" + err.Error() + ")", fileOffset(r.sectorSize, sn)}
|
||||
}
|
||||
for i := 0; i < num; i++ {
|
||||
f := &File{r: r}
|
||||
f.directoryEntryFields = makeDirEntry(buf[i*128:])
|
||||
fixFile(r.header.majorVersion, f)
|
||||
f.curSector = f.startingSectorLoc
|
||||
de = append(de, f)
|
||||
}
|
||||
nsn, err := r.findNext(sn, false)
|
||||
if err != nil {
|
||||
return Error{ErrRead, "directory entries error finding sector (" + err.Error() + ")", int64(nsn)}
|
||||
}
|
||||
if nsn <= sn {
|
||||
if nsn == sn || cycles[nsn] {
|
||||
return Error{ErrRead, "directory entries sector cycle", int64(nsn)}
|
||||
}
|
||||
cycles[nsn] = true
|
||||
}
|
||||
sn = nsn
|
||||
}
|
||||
r.direntries = de
|
||||
return nil
|
||||
}
|
||||
|
||||
func fixFile(v uint16, f *File) {
|
||||
fixName(f)
|
||||
if f.objectType != stream {
|
||||
return
|
||||
}
|
||||
// if the MSCFB major version is 4, then this can be a uint64 otherwise is a uint32 and the least signficant bits can contain junk
|
||||
if v > 3 {
|
||||
f.Size = int64(binary.LittleEndian.Uint64(f.streamSize[:]))
|
||||
} else {
|
||||
f.Size = int64(binary.LittleEndian.Uint32(f.streamSize[:4]))
|
||||
}
|
||||
}
|
||||
|
||||
func fixName(f *File) {
|
||||
// From the spec:
|
||||
// "The length [name] MUST be a multiple of 2, and include the terminating null character in the count.
|
||||
// This length MUST NOT exceed 64, the maximum size of the Directory Entry Name field."
|
||||
if f.nameLength < 4 || f.nameLength > 64 {
|
||||
return
|
||||
}
|
||||
nlen := int(f.nameLength/2 - 1)
|
||||
f.Initial = f.rawName[0]
|
||||
var slen int
|
||||
if !unicode.IsPrint(rune(f.Initial)) {
|
||||
slen = 1
|
||||
}
|
||||
f.Name = string(utf16.Decode(f.rawName[slen:nlen]))
|
||||
}
|
||||
|
||||
func (r *Reader) traverse() error {
|
||||
r.File = make([]*File, 0, len(r.direntries))
|
||||
var (
|
||||
recurse func(int, []string)
|
||||
err error
|
||||
counter int
|
||||
)
|
||||
recurse = func(i int, path []string) {
|
||||
// prevent cycles, number of recurse calls can't exceed number of directory entries
|
||||
counter++
|
||||
if counter > len(r.direntries) {
|
||||
err = Error{ErrTraverse, "traversal counter overflow", int64(i)}
|
||||
return
|
||||
}
|
||||
if i < 0 || i >= len(r.direntries) {
|
||||
err = Error{ErrTraverse, "illegal traversal index", int64(i)}
|
||||
return
|
||||
}
|
||||
file := r.direntries[i]
|
||||
if file.leftSibID != noStream {
|
||||
recurse(int(file.leftSibID), path)
|
||||
}
|
||||
r.File = append(r.File, file)
|
||||
file.Path = path
|
||||
if file.childID != noStream {
|
||||
if i > 0 {
|
||||
recurse(int(file.childID), append(path, file.Name))
|
||||
} else {
|
||||
recurse(int(file.childID), path)
|
||||
}
|
||||
}
|
||||
if file.rightSibID != noStream {
|
||||
recurse(int(file.rightSibID), path)
|
||||
}
|
||||
return
|
||||
}
|
||||
recurse(0, []string{})
|
||||
return err
|
||||
}
|
||||
|
||||
// File represents a MSCFB directory entry
|
||||
type File struct {
|
||||
Name string // stream or directory name
|
||||
Initial uint16 // the first character in the name (identifies special streams such as MSOLEPS property sets)
|
||||
Path []string // file path
|
||||
Size int64 // size of stream
|
||||
i int64 // bytes read
|
||||
curSector uint32 // next sector for Read | Write
|
||||
rem int64 // offset in current sector remaining previous Read | Write
|
||||
*directoryEntryFields
|
||||
r *Reader
|
||||
}
|
||||
|
||||
type fileInfo struct{ *File }
|
||||
|
||||
func (fi fileInfo) Name() string { return fi.File.Name }
|
||||
func (fi fileInfo) Size() int64 {
|
||||
if fi.objectType != stream {
|
||||
return 0
|
||||
}
|
||||
return fi.File.Size
|
||||
}
|
||||
func (fi fileInfo) IsDir() bool { return fi.mode().IsDir() }
|
||||
func (fi fileInfo) ModTime() time.Time { return fi.Modified() }
|
||||
func (fi fileInfo) Mode() os.FileMode { return fi.File.mode() }
|
||||
func (fi fileInfo) Sys() interface{} { return nil }
|
||||
|
||||
func (f *File) mode() os.FileMode {
|
||||
if f.objectType != stream {
|
||||
return os.ModeDir | 0777
|
||||
}
|
||||
return 0666
|
||||
}
|
||||
|
||||
// FileInfo for this directory entry. Useful for IsDir() (whether a directory entry is a stream (file) or a storage object (dir))
|
||||
func (f *File) FileInfo() os.FileInfo {
|
||||
return fileInfo{f}
|
||||
}
|
||||
|
||||
// ID returns this directory entry's CLSID field
|
||||
func (f *File) ID() string {
|
||||
return f.clsid.String()
|
||||
}
|
||||
|
||||
// Created returns this directory entry's created field
|
||||
func (f *File) Created() time.Time {
|
||||
return f.create.Time()
|
||||
}
|
||||
|
||||
// Created returns this directory entry's modified field
|
||||
func (f *File) Modified() time.Time {
|
||||
return f.modify.Time()
|
||||
}
|
||||
|
||||
// Read this directory entry
|
||||
// Returns 0, io.EOF if no stream is available (i.e. for a storage object)
|
||||
func (f *File) Read(b []byte) (int, error) {
|
||||
if f.Size < 1 || f.i >= f.Size {
|
||||
return 0, io.EOF
|
||||
}
|
||||
sz := len(b)
|
||||
if int64(sz) > f.Size-f.i {
|
||||
sz = int(f.Size - f.i)
|
||||
}
|
||||
// get sectors and lengths for reads
|
||||
str, err := f.stream(sz)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// now read
|
||||
var idx, i int
|
||||
for _, v := range str {
|
||||
jdx := idx + int(v[1])
|
||||
if jdx < idx || jdx > sz {
|
||||
return 0, Error{ErrRead, "bad read length", int64(jdx)}
|
||||
}
|
||||
j, err := f.r.ra.ReadAt(b[idx:jdx], v[0])
|
||||
i = i + j
|
||||
if err != nil {
|
||||
f.i += int64(i)
|
||||
return i, Error{ErrRead, "underlying reader fail (" + err.Error() + ")", int64(idx)}
|
||||
}
|
||||
idx = jdx
|
||||
}
|
||||
f.i += int64(i)
|
||||
if i != sz {
|
||||
err = Error{ErrRead, "bytes read do not match expected read size", int64(i)}
|
||||
} else if i < len(b) {
|
||||
err = io.EOF
|
||||
}
|
||||
return i, err
|
||||
}
|
||||
|
||||
// Write to this directory entry
|
||||
// Depends on the io.ReaderAt supplied to mscfb.New() being a WriterAt too
|
||||
// Returns 0, io.EOF if no stream is available (i.e. for a storage object)
|
||||
func (f *File) Write(b []byte) (int, error) {
|
||||
if f.Size < 1 || f.i >= f.Size {
|
||||
return 0, io.EOF
|
||||
}
|
||||
if f.r.wa == nil {
|
||||
wa, ok := f.r.ra.(io.WriterAt)
|
||||
if !ok {
|
||||
return 0, Error{ErrWrite, "mscfb.New must be given ReaderAt convertible to a io.WriterAt in order to write", 0}
|
||||
}
|
||||
f.r.wa = wa
|
||||
}
|
||||
sz := len(b)
|
||||
if int64(sz) > f.Size-f.i {
|
||||
sz = int(f.Size - f.i)
|
||||
}
|
||||
// get sectors and lengths for writes
|
||||
str, err := f.stream(sz)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// now read
|
||||
var idx, i int
|
||||
for _, v := range str {
|
||||
jdx := idx + int(v[1])
|
||||
if jdx < idx || jdx > sz {
|
||||
return 0, Error{ErrWrite, "bad write length", int64(jdx)}
|
||||
}
|
||||
j, err := f.r.wa.WriteAt(b[idx:jdx], v[0])
|
||||
i = i + j
|
||||
if err != nil {
|
||||
f.i += int64(i)
|
||||
return i, Error{ErrWrite, "underlying writer fail (" + err.Error() + ")", int64(idx)}
|
||||
}
|
||||
idx = jdx
|
||||
}
|
||||
f.i += int64(i)
|
||||
if i != sz {
|
||||
err = Error{ErrWrite, "bytes written do not match expected write size", int64(i)}
|
||||
} else if i < len(b) {
|
||||
err = io.EOF
|
||||
}
|
||||
return i, err
|
||||
}
|
||||
|
||||
// ReadAt reads p bytes at offset off from start of file. Does not affect seek place for other reads/writes.
|
||||
func (f *File) ReadAt(p []byte, off int64) (n int, err error) {
|
||||
// memorize place
|
||||
mi, mrem, mcur := f.i, f.rem, f.curSector
|
||||
_, err = f.Seek(off, 0)
|
||||
if err == nil {
|
||||
n, err = f.Read(p)
|
||||
}
|
||||
f.i, f.rem, f.curSector = mi, mrem, mcur
|
||||
return n, err
|
||||
}
|
||||
|
||||
// WriteAt reads p bytes at offset off from start of file. Does not affect seek place for other reads/writes.
|
||||
func (f *File) WriteAt(p []byte, off int64) (n int, err error) {
|
||||
// memorize place
|
||||
mi, mrem, mcur := f.i, f.rem, f.curSector
|
||||
_, err = f.Seek(off, 0)
|
||||
if err == nil {
|
||||
n, err = f.Write(p)
|
||||
}
|
||||
f.i, f.rem, f.curSector = mi, mrem, mcur
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Seek sets the offset for the next Read or Write to offset, interpreted according to whence: 0 means relative to the
|
||||
// start of the file, 1 means relative to the current offset, and 2 means relative to the end. Seek returns the new
|
||||
// offset relative to the start of the file and an error, if any.
|
||||
func (f *File) Seek(offset int64, whence int) (int64, error) {
|
||||
var abs int64
|
||||
switch whence {
|
||||
default:
|
||||
return 0, Error{ErrSeek, "invalid whence", int64(whence)}
|
||||
case 0:
|
||||
abs = offset
|
||||
case 1:
|
||||
abs = f.i + offset
|
||||
case 2:
|
||||
abs = f.Size - offset
|
||||
}
|
||||
switch {
|
||||
case abs < 0:
|
||||
return f.i, Error{ErrSeek, "can't seek before start of File", abs}
|
||||
case abs >= f.Size:
|
||||
return f.i, Error{ErrSeek, "can't seek past File length", abs}
|
||||
case abs == f.i:
|
||||
return abs, nil
|
||||
case abs > f.i:
|
||||
t := f.i
|
||||
f.i = abs
|
||||
return f.i, f.seek(abs - t)
|
||||
}
|
||||
if f.rem >= f.i-abs {
|
||||
f.rem = f.rem - (f.i - abs)
|
||||
f.i = abs
|
||||
return f.i, nil
|
||||
}
|
||||
f.rem = 0
|
||||
f.curSector = f.startingSectorLoc
|
||||
f.i = abs
|
||||
return f.i, f.seek(abs)
|
||||
}
|
||||
|
||||
func (f *File) seek(sz int64) error {
|
||||
// calculate ministream and sector size
|
||||
var mini bool
|
||||
var ss int64
|
||||
if f.Size < miniStreamCutoffSize {
|
||||
mini = true
|
||||
ss = 64
|
||||
} else {
|
||||
ss = int64(f.r.sectorSize)
|
||||
}
|
||||
|
||||
var j int64
|
||||
var err error
|
||||
// if we have a remainder in the current sector, use it first
|
||||
if f.rem > 0 {
|
||||
if ss-f.rem <= sz {
|
||||
f.curSector, err = f.r.findNext(f.curSector, mini)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
j += ss - f.rem
|
||||
f.rem = 0
|
||||
if j == sz {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
f.rem += sz
|
||||
return nil
|
||||
}
|
||||
if f.curSector == endOfChain {
|
||||
return Error{ErrRead, "unexpected early end of chain", int64(f.curSector)}
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
// check if we are at the last sector
|
||||
if sz-j < ss {
|
||||
f.rem = sz - j
|
||||
return nil
|
||||
} else {
|
||||
j += ss
|
||||
f.curSector, err = f.r.findNext(f.curSector, mini)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// we might be at the last sector if there is no remainder, if so can return
|
||||
if j == sz {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return offsets and lengths for read or write
|
||||
func (f *File) stream(sz int) ([][2]int64, error) {
|
||||
// calculate ministream, cap for sector slice, and sector size
|
||||
var mini bool
|
||||
var l int
|
||||
var ss int64
|
||||
if f.Size < miniStreamCutoffSize {
|
||||
mini = true
|
||||
l = sz/64 + 2
|
||||
ss = 64
|
||||
} else {
|
||||
l = sz/int(f.r.sectorSize) + 2
|
||||
ss = int64(f.r.sectorSize)
|
||||
}
|
||||
|
||||
sectors := make([][2]int64, 0, l)
|
||||
var i, j int
|
||||
|
||||
// if we have a remainder from a previous read, use it first
|
||||
if f.rem > 0 {
|
||||
offset, err := f.r.getOffset(f.curSector, mini)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ss-f.rem >= int64(sz) {
|
||||
sectors = append(sectors, [2]int64{offset + f.rem, int64(sz)})
|
||||
} else {
|
||||
sectors = append(sectors, [2]int64{offset + f.rem, ss - f.rem})
|
||||
}
|
||||
if ss-f.rem <= int64(sz) {
|
||||
f.curSector, err = f.r.findNext(f.curSector, mini)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
j += int(ss - f.rem)
|
||||
f.rem = 0
|
||||
} else {
|
||||
f.rem += int64(sz)
|
||||
}
|
||||
if sectors[0][1] == int64(sz) {
|
||||
return sectors, nil
|
||||
}
|
||||
if f.curSector == endOfChain {
|
||||
return nil, Error{ErrRead, "unexpected early end of chain", int64(f.curSector)}
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
for {
|
||||
// emergency brake!
|
||||
if i >= cap(sectors) {
|
||||
return nil, Error{ErrRead, "index overruns sector length", int64(i)}
|
||||
}
|
||||
// grab the next offset
|
||||
offset, err := f.r.getOffset(f.curSector, mini)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// check if we are at the last sector
|
||||
if sz-j < int(ss) {
|
||||
sectors = append(sectors, [2]int64{offset, int64(sz - j)})
|
||||
f.rem = int64(sz - j)
|
||||
return compressChain(sectors), nil
|
||||
} else {
|
||||
sectors = append(sectors, [2]int64{offset, ss})
|
||||
j += int(ss)
|
||||
f.curSector, err = f.r.findNext(f.curSector, mini)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// we might be at the last sector if there is no remainder, if so can return
|
||||
if j == sz {
|
||||
return compressChain(sectors), nil
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
func compressChain(locs [][2]int64) [][2]int64 {
|
||||
l := len(locs)
|
||||
for i, x := 0, 0; i < l && x+1 < len(locs); i++ {
|
||||
if locs[x][0]+locs[x][1] == locs[x+1][0] {
|
||||
locs[x][1] = locs[x][1] + locs[x+1][1]
|
||||
for j := range locs[x+1 : len(locs)-1] {
|
||||
locs[x+1+j] = locs[j+x+2]
|
||||
}
|
||||
locs = locs[:len(locs)-1]
|
||||
} else {
|
||||
x += 1
|
||||
}
|
||||
}
|
||||
return locs
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
// +build gofuzz
|
||||
|
||||
// fuzzing with https://github.com/dvyukov/go-fuzz
|
||||
package mscfb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
func Fuzz(data []byte) int {
|
||||
doc, err := New(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
if doc != nil {
|
||||
panic("doc != nil on error " + err.Error())
|
||||
}
|
||||
return 0
|
||||
}
|
||||
buf := &bytes.Buffer{}
|
||||
for entry, err := doc.Next(); ; entry, err = doc.Next() {
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return 1
|
||||
}
|
||||
if entry != nil {
|
||||
panic("entry != nil on error " + err.Error())
|
||||
}
|
||||
}
|
||||
buf.Reset()
|
||||
buf.ReadFrom(entry)
|
||||
}
|
||||
return 1
|
||||
}
|
||||
+396
@@ -0,0 +1,396 @@
|
||||
// Copyright 2013 Richard Lehane. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package mscfb implements a reader for Microsoft's Compound File Binary File Format (http://msdn.microsoft.com/en-us/library/dd942138.aspx).
|
||||
//
|
||||
// The Compound File Binary File Format is also known as the Object Linking and Embedding (OLE) or Component Object Model (COM) format and was used by many
|
||||
// early MS software such as MS Office.
|
||||
//
|
||||
// Example:
|
||||
// file, _ := os.Open("test/test.doc")
|
||||
// defer file.Close()
|
||||
// doc, err := mscfb.New(file)
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// for entry, err := doc.Next(); err == nil; entry, err = doc.Next() {
|
||||
// buf := make([]byte, 512)
|
||||
// i, _ := entry.Read(buf)
|
||||
// if i > 0 {
|
||||
// fmt.Println(buf[:i])
|
||||
// }
|
||||
// fmt.Println(entry.Name)
|
||||
// }
|
||||
package mscfb
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func fileOffset(ss, sn uint32) int64 {
|
||||
return int64((sn + 1) * ss)
|
||||
}
|
||||
|
||||
const (
|
||||
signature uint64 = 0xE11AB1A1E011CFD0
|
||||
miniStreamSectorSize uint32 = 64
|
||||
miniStreamCutoffSize int64 = 4096
|
||||
dirEntrySize uint32 = 128 //128 bytes
|
||||
)
|
||||
|
||||
const (
|
||||
maxRegSect uint32 = 0xFFFFFFFA // Maximum regular sector number
|
||||
difatSect uint32 = 0xFFFFFFFC //Specifies a DIFAT sector in the FAT
|
||||
fatSect uint32 = 0xFFFFFFFD // Specifies a FAT sector in the FAT
|
||||
endOfChain uint32 = 0xFFFFFFFE // End of linked chain of sectors
|
||||
freeSect uint32 = 0xFFFFFFFF // Speficies unallocated sector in the FAT, Mini FAT or DIFAT
|
||||
maxRegStreamID uint32 = 0xFFFFFFFA // maximum regular stream ID
|
||||
noStream uint32 = 0xFFFFFFFF // empty pointer
|
||||
)
|
||||
|
||||
const lenHeader int = 8 + 16 + 10 + 6 + 12 + 8 + 16 + 109*4
|
||||
|
||||
type headerFields struct {
|
||||
signature uint64
|
||||
_ [16]byte //CLSID - ignore, must be null
|
||||
minorVersion uint16 //Version number for non-breaking changes. This field SHOULD be set to 0x003E if the major version field is either 0x0003 or 0x0004.
|
||||
majorVersion uint16 //Version number for breaking changes. This field MUST be set to either 0x0003 (version 3) or 0x0004 (version 4).
|
||||
_ [2]byte //byte order - ignore, must be little endian
|
||||
sectorSize uint16 //This field MUST be set to 0x0009, or 0x000c, depending on the Major Version field. This field specifies the sector size of the compound file as a power of 2. If Major Version is 3, then the Sector Shift MUST be 0x0009, specifying a sector size of 512 bytes. If Major Version is 4, then the Sector Shift MUST be 0x000C, specifying a sector size of 4096 bytes.
|
||||
_ [2]byte // ministream sector size - ignore, must be 64 bytes
|
||||
_ [6]byte // reserved - ignore, not used
|
||||
numDirectorySectors uint32 //This integer field contains the count of the number of directory sectors in the compound file. If Major Version is 3, then the Number of Directory Sectors MUST be zero. This field is not supported for version 3 compound files.
|
||||
numFatSectors uint32 //This integer field contains the count of the number of FAT sectors in the compound file.
|
||||
directorySectorLoc uint32 //This integer field contains the starting sector number for the directory stream.
|
||||
_ [4]byte // transaction - ignore, not used
|
||||
_ [4]byte // mini stream size cutooff - ignore, must be 4096 bytes
|
||||
miniFatSectorLoc uint32 //This integer field contains the starting sector number for the mini FAT.
|
||||
numMiniFatSectors uint32 //This integer field contains the count of the number of mini FAT sectors in the compound file.
|
||||
difatSectorLoc uint32 //This integer field contains the starting sector number for the DIFAT.
|
||||
numDifatSectors uint32 //This integer field contains the count of the number of DIFAT sectors in the compound file.
|
||||
initialDifats [109]uint32 //The first 109 difat sectors are included in the header
|
||||
}
|
||||
|
||||
func makeHeader(b []byte) *headerFields {
|
||||
h := &headerFields{}
|
||||
h.signature = binary.LittleEndian.Uint64(b[:8])
|
||||
h.minorVersion = binary.LittleEndian.Uint16(b[24:26])
|
||||
h.majorVersion = binary.LittleEndian.Uint16(b[26:28])
|
||||
h.sectorSize = binary.LittleEndian.Uint16(b[30:32])
|
||||
h.numDirectorySectors = binary.LittleEndian.Uint32(b[40:44])
|
||||
h.numFatSectors = binary.LittleEndian.Uint32(b[44:48])
|
||||
h.directorySectorLoc = binary.LittleEndian.Uint32(b[48:52])
|
||||
h.miniFatSectorLoc = binary.LittleEndian.Uint32(b[60:64])
|
||||
h.numMiniFatSectors = binary.LittleEndian.Uint32(b[64:68])
|
||||
h.difatSectorLoc = binary.LittleEndian.Uint32(b[68:72])
|
||||
h.numDifatSectors = binary.LittleEndian.Uint32(b[72:76])
|
||||
var idx int
|
||||
for i := 76; i < 512; i = i + 4 {
|
||||
h.initialDifats[idx] = binary.LittleEndian.Uint32(b[i : i+4])
|
||||
idx++
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
type header struct {
|
||||
*headerFields
|
||||
difats []uint32
|
||||
miniFatLocs []uint32
|
||||
miniStreamLocs []uint32 // chain of sectors containing the ministream
|
||||
}
|
||||
|
||||
func (r *Reader) setHeader() error {
|
||||
buf, err := r.readAt(0, lenHeader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.header = &header{headerFields: makeHeader(buf)}
|
||||
// sanity check - check signature
|
||||
if r.header.signature != signature {
|
||||
return Error{ErrFormat, "bad signature", int64(r.header.signature)}
|
||||
}
|
||||
// check for legal sector size
|
||||
if r.header.sectorSize == 0x0009 || r.header.sectorSize == 0x000c {
|
||||
r.sectorSize = uint32(1 << r.header.sectorSize)
|
||||
} else {
|
||||
return Error{ErrFormat, "illegal sector size", int64(r.header.sectorSize)}
|
||||
}
|
||||
// check for DIFAT overflow
|
||||
if r.header.numDifatSectors > 0 {
|
||||
sz := (r.sectorSize / 4) - 1
|
||||
if int(r.header.numDifatSectors*sz+109) < 0 {
|
||||
return Error{ErrFormat, "DIFAT int overflow", int64(r.header.numDifatSectors)}
|
||||
}
|
||||
if r.header.numDifatSectors*sz+109 > r.header.numFatSectors+sz {
|
||||
return Error{ErrFormat, "num DIFATs exceeds FAT sectors", int64(r.header.numDifatSectors)}
|
||||
}
|
||||
}
|
||||
// check for mini FAT overflow
|
||||
if r.header.numMiniFatSectors > 0 {
|
||||
if int(r.sectorSize/4*r.header.numMiniFatSectors) < 0 {
|
||||
return Error{ErrFormat, "mini FAT int overflow", int64(r.header.numMiniFatSectors)}
|
||||
}
|
||||
if r.header.numMiniFatSectors > r.header.numFatSectors*(r.sectorSize/miniStreamSectorSize) {
|
||||
return Error{ErrFormat, "num mini FATs exceeds FAT sectors", int64(r.header.numFatSectors)}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Reader) setDifats() error {
|
||||
r.header.difats = r.header.initialDifats[:]
|
||||
// return early if no extra DIFAT sectors
|
||||
if r.header.numDifatSectors == 0 {
|
||||
return nil
|
||||
}
|
||||
sz := (r.sectorSize / 4) - 1
|
||||
n := make([]uint32, 109, r.header.numDifatSectors*sz+109)
|
||||
copy(n, r.header.difats)
|
||||
r.header.difats = n
|
||||
off := r.header.difatSectorLoc
|
||||
for i := 0; i < int(r.header.numDifatSectors); i++ {
|
||||
buf, err := r.readAt(fileOffset(r.sectorSize, off), int(r.sectorSize))
|
||||
if err != nil {
|
||||
return Error{ErrFormat, "error setting DIFAT(" + err.Error() + ")", int64(off)}
|
||||
}
|
||||
for j := 0; j < int(sz); j++ {
|
||||
r.header.difats = append(r.header.difats, binary.LittleEndian.Uint32(buf[j*4:j*4+4]))
|
||||
}
|
||||
off = binary.LittleEndian.Uint32(buf[len(buf)-4:])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// set the ministream FAT and sector slices in the header
|
||||
func (r *Reader) setMiniStream() error {
|
||||
// do nothing if there is no ministream
|
||||
if r.direntries[0].startingSectorLoc == endOfChain || r.header.miniFatSectorLoc == endOfChain || r.header.numMiniFatSectors == 0 {
|
||||
return nil
|
||||
}
|
||||
// build a slice of minifat sectors (akin to the DIFAT slice)
|
||||
c := int(r.header.numMiniFatSectors)
|
||||
r.header.miniFatLocs = make([]uint32, c)
|
||||
r.header.miniFatLocs[0] = r.header.miniFatSectorLoc
|
||||
for i := 1; i < c; i++ {
|
||||
loc, err := r.findNext(r.header.miniFatLocs[i-1], false)
|
||||
if err != nil {
|
||||
return Error{ErrFormat, "setting mini stream (" + err.Error() + ")", int64(r.header.miniFatLocs[i-1])}
|
||||
}
|
||||
r.header.miniFatLocs[i] = loc
|
||||
}
|
||||
// build a slice of ministream sectors
|
||||
c = int(r.sectorSize / 4 * r.header.numMiniFatSectors)
|
||||
r.header.miniStreamLocs = make([]uint32, 0, c)
|
||||
cycles := make(map[uint32]bool)
|
||||
sn := r.direntries[0].startingSectorLoc
|
||||
for sn != endOfChain {
|
||||
r.header.miniStreamLocs = append(r.header.miniStreamLocs, sn)
|
||||
nsn, err := r.findNext(sn, false)
|
||||
if err != nil {
|
||||
return Error{ErrFormat, "setting mini stream (" + err.Error() + ")", int64(sn)}
|
||||
}
|
||||
if nsn <= sn {
|
||||
if nsn == sn || cycles[nsn] {
|
||||
return Error{ErrRead, "cycle detected in mini stream", int64(nsn)}
|
||||
}
|
||||
cycles[nsn] = true
|
||||
}
|
||||
sn = nsn
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Reader) readAt(offset int64, length int) ([]byte, error) {
|
||||
if r.slicer {
|
||||
b, err := r.ra.(slicer).Slice(offset, length)
|
||||
if err != nil {
|
||||
return nil, Error{ErrRead, "slicer read error (" + err.Error() + ")", offset}
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
if length > len(r.buf) {
|
||||
return nil, Error{ErrRead, "read length greater than read buffer", int64(length)}
|
||||
}
|
||||
if _, err := r.ra.ReadAt(r.buf[:length], offset); err != nil {
|
||||
return nil, Error{ErrRead, err.Error(), offset}
|
||||
}
|
||||
return r.buf[:length], nil
|
||||
}
|
||||
|
||||
func (r *Reader) getOffset(sn uint32, mini bool) (int64, error) {
|
||||
if mini {
|
||||
num := r.sectorSize / 64
|
||||
sec := int(sn / num)
|
||||
if sec >= len(r.header.miniStreamLocs) {
|
||||
return 0, Error{ErrRead, "minisector number is outside minisector range", int64(sec)}
|
||||
}
|
||||
dif := sn % num
|
||||
return int64((r.header.miniStreamLocs[sec]+1)*r.sectorSize + dif*64), nil
|
||||
}
|
||||
return fileOffset(r.sectorSize, sn), nil
|
||||
}
|
||||
|
||||
// check the FAT sector for the next sector in a chain
|
||||
func (r *Reader) findNext(sn uint32, mini bool) (uint32, error) {
|
||||
entries := r.sectorSize / 4
|
||||
index := int(sn / entries) // find position in DIFAT or minifat array
|
||||
var sect uint32
|
||||
if mini {
|
||||
if index < 0 || index >= len(r.header.miniFatLocs) {
|
||||
return 0, Error{ErrRead, "minisector index is outside miniFAT range", int64(index)}
|
||||
}
|
||||
sect = r.header.miniFatLocs[index]
|
||||
} else {
|
||||
if index < 0 || index >= len(r.header.difats) {
|
||||
return 0, Error{ErrRead, "FAT index is outside DIFAT range", int64(index)}
|
||||
}
|
||||
sect = r.header.difats[index]
|
||||
}
|
||||
fatIndex := sn % entries // find position within FAT or MiniFAT sector
|
||||
offset := fileOffset(r.sectorSize, sect) + int64(fatIndex*4)
|
||||
buf, err := r.readAt(offset, 4)
|
||||
if err != nil {
|
||||
return 0, Error{ErrRead, "bad read finding next sector (" + err.Error() + ")", offset}
|
||||
}
|
||||
return binary.LittleEndian.Uint32(buf), nil
|
||||
}
|
||||
|
||||
// Reader provides sequential access to the contents of a MS compound file (MSCFB)
|
||||
type Reader struct {
|
||||
slicer bool
|
||||
sectorSize uint32
|
||||
buf []byte
|
||||
header *header
|
||||
File []*File // File is an ordered slice of final directory entries.
|
||||
direntries []*File // unordered raw directory entries
|
||||
entry int
|
||||
|
||||
ra io.ReaderAt
|
||||
wa io.WriterAt
|
||||
}
|
||||
|
||||
// New returns a MSCFB reader
|
||||
func New(ra io.ReaderAt) (*Reader, error) {
|
||||
r := &Reader{ra: ra}
|
||||
if _, ok := ra.(slicer); ok {
|
||||
r.slicer = true
|
||||
} else {
|
||||
r.buf = make([]byte, lenHeader)
|
||||
}
|
||||
if err := r.setHeader(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// resize the buffer to 4096 if sector size isn't 512
|
||||
if !r.slicer && int(r.sectorSize) > len(r.buf) {
|
||||
r.buf = make([]byte, r.sectorSize)
|
||||
}
|
||||
if err := r.setDifats(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := r.setDirEntries(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := r.setMiniStream(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := r.traverse(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// ID returns the CLSID (class ID) field from the root directory entry
|
||||
func (r *Reader) ID() string {
|
||||
return r.File[0].ID()
|
||||
}
|
||||
|
||||
// Created returns the created field from the root directory entry
|
||||
func (r *Reader) Created() time.Time {
|
||||
return r.File[0].Created()
|
||||
}
|
||||
|
||||
// Modified returns the last modified field from the root directory entry
|
||||
func (r *Reader) Modified() time.Time {
|
||||
return r.File[0].Modified()
|
||||
}
|
||||
|
||||
// Next iterates to the next directory entry.
|
||||
// This isn't necessarily an adjacent *File within the File slice, but is based on the Left Sibling, Right Sibling and Child information in directory entries.
|
||||
func (r *Reader) Next() (*File, error) {
|
||||
r.entry++
|
||||
if r.entry >= len(r.File) {
|
||||
return nil, io.EOF
|
||||
}
|
||||
return r.File[r.entry], nil
|
||||
}
|
||||
|
||||
// Read the current directory entry
|
||||
func (r *Reader) Read(b []byte) (n int, err error) {
|
||||
if r.entry >= len(r.File) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return r.File[r.entry].Read(b)
|
||||
}
|
||||
|
||||
// Debug provides granular information from an mscfb file to assist with debugging
|
||||
func (r *Reader) Debug() map[string][]uint32 {
|
||||
ret := map[string][]uint32{
|
||||
"sector size": []uint32{r.sectorSize},
|
||||
"mini fat locs": r.header.miniFatLocs,
|
||||
"mini stream locs": r.header.miniStreamLocs,
|
||||
"directory sector": []uint32{r.header.directorySectorLoc},
|
||||
"mini stream start/size": []uint32{r.File[0].startingSectorLoc, binary.LittleEndian.Uint32(r.File[0].streamSize[:])},
|
||||
}
|
||||
for f, err := r.Next(); err == nil; f, err = r.Next() {
|
||||
ret[f.Name+" start/size"] = []uint32{f.startingSectorLoc, binary.LittleEndian.Uint32(f.streamSize[:])}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
const (
|
||||
// ErrFormat reports issues with the MSCFB's header structures
|
||||
ErrFormat = iota
|
||||
// ErrRead reports issues attempting to read MSCFB streams
|
||||
ErrRead
|
||||
// ErrSeek reports seek issues
|
||||
ErrSeek
|
||||
// ErrWrite reports write issues
|
||||
ErrWrite
|
||||
// ErrTraverse reports issues attempting to traverse the child-parent-sibling relations
|
||||
// between MSCFB storage objects
|
||||
ErrTraverse
|
||||
)
|
||||
|
||||
type Error struct {
|
||||
typ int
|
||||
msg string
|
||||
val int64
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
return "mscfb: " + e.msg + "; " + strconv.FormatInt(e.val, 10)
|
||||
}
|
||||
|
||||
// Typ gives the type of MSCFB error
|
||||
func (e Error) Typ() int {
|
||||
return e.typ
|
||||
}
|
||||
|
||||
// Slicer interface avoids a copy by obtaining a byte slice directly from the underlying reader
|
||||
type slicer interface {
|
||||
Slice(offset int64, length int) ([]byte, error)
|
||||
}
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
// Copyright 2014 Richard Lehane. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
//The CURRENCY type specifies currency information. It is represented as an 8-byte integer, scaled by 10,000, to give a fixed-point number with 15 digits to the left of the decimal point, and four digits to the right. This representation provides a range of 922337203685477.5807 to –922337203685477.5808. For example, $5.25 is stored as the value 52500.
|
||||
|
||||
type Currency int64
|
||||
|
||||
func (c Currency) String() string {
|
||||
return "$" + strconv.FormatFloat(float64(c)/10000, 'f', -1, 64)
|
||||
}
|
||||
|
||||
func (c Currency) Type() string {
|
||||
return "Currency"
|
||||
}
|
||||
|
||||
func (c Currency) Length() int {
|
||||
return 8
|
||||
}
|
||||
|
||||
func MakeCurrency(b []byte) (Type, error) {
|
||||
if len(b) < 8 {
|
||||
return Currency(0), ErrType
|
||||
}
|
||||
return Currency(binary.LittleEndian.Uint64(b[:8])), nil
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
// Copyright 2014 Richard Lehane. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"time"
|
||||
)
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/cc237601.aspx
|
||||
type Date float64
|
||||
|
||||
func (d Date) Time() time.Time {
|
||||
start := time.Date(1899, 12, 30, 0, 0, 0, 0, time.UTC)
|
||||
day := float64(time.Hour * 24)
|
||||
dur := time.Duration(day * float64(d))
|
||||
return start.Add(dur)
|
||||
}
|
||||
|
||||
func (d Date) String() string {
|
||||
return d.Time().String()
|
||||
}
|
||||
|
||||
func (d Date) Type() string {
|
||||
return "Date"
|
||||
}
|
||||
|
||||
func (d Date) Length() int {
|
||||
return 8
|
||||
}
|
||||
|
||||
func MakeDate(b []byte) (Type, error) {
|
||||
if len(b) < 8 {
|
||||
return Date(0), ErrType
|
||||
}
|
||||
return Date(binary.LittleEndian.Uint64(b[:8])), nil
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
// Copyright 2014 Richard Lehane. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/cc237603.aspx
|
||||
type Decimal struct {
|
||||
res [2]byte
|
||||
scale byte
|
||||
sign byte
|
||||
high32 uint32
|
||||
low64 uint64
|
||||
}
|
||||
|
||||
func (d Decimal) Type() string {
|
||||
return "Decimal"
|
||||
}
|
||||
|
||||
func (d Decimal) Length() int {
|
||||
return 16
|
||||
}
|
||||
|
||||
func (d Decimal) String() string {
|
||||
h, l, b := new(big.Int), new(big.Int), new(big.Int)
|
||||
l.SetUint64(d.low64)
|
||||
h.Lsh(big.NewInt(int64(d.high32)), 64)
|
||||
b.Add(h, l)
|
||||
q, f, r := new(big.Rat), new(big.Rat), new(big.Rat)
|
||||
q.SetFloat64(math.Pow10(int(d.scale)))
|
||||
r.Quo(f.SetInt(b), q)
|
||||
if d.sign == 0x80 {
|
||||
r.Neg(r)
|
||||
}
|
||||
return r.FloatString(20)
|
||||
}
|
||||
|
||||
func MakeDecimal(b []byte) (Type, error) {
|
||||
if len(b) < 16 {
|
||||
return Decimal{}, ErrType
|
||||
}
|
||||
return Decimal{
|
||||
res: [2]byte{b[0], b[1]},
|
||||
scale: b[2],
|
||||
sign: b[3],
|
||||
high32: binary.LittleEndian.Uint32(b[4:8]),
|
||||
low64: binary.LittleEndian.Uint64(b[8:16]),
|
||||
}, nil
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
// Copyright 2014 Richard Lehane. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Win FILETIME type
|
||||
// http://msdn.microsoft.com/en-us/library/cc230324.aspx
|
||||
type FileTime struct {
|
||||
Low uint32 // Windows FILETIME structure
|
||||
High uint32 // Windows FILETIME structure
|
||||
}
|
||||
|
||||
const (
|
||||
tick uint64 = 10000000
|
||||
gregToUnix uint64 = 11644473600
|
||||
)
|
||||
|
||||
func winToUnix(low, high uint32) int64 {
|
||||
gregTime := ((uint64(high) << 32) + uint64(low)) / tick
|
||||
if gregTime < gregToUnix {
|
||||
return 0
|
||||
}
|
||||
return int64(gregTime - gregToUnix)
|
||||
}
|
||||
|
||||
func (f FileTime) Time() time.Time {
|
||||
return time.Unix(winToUnix(f.Low, f.High), 0)
|
||||
}
|
||||
|
||||
func (f FileTime) String() string {
|
||||
return f.Time().String()
|
||||
}
|
||||
|
||||
func (f FileTime) Type() string {
|
||||
return "FileTime"
|
||||
}
|
||||
|
||||
func (f FileTime) Length() int {
|
||||
return 8
|
||||
}
|
||||
|
||||
func MakeFileTime(b []byte) (Type, error) {
|
||||
if len(b) < 8 {
|
||||
return FileTime{}, ErrType
|
||||
}
|
||||
return MustFileTime(b), nil
|
||||
}
|
||||
|
||||
func MustFileTime(b []byte) FileTime {
|
||||
return FileTime{
|
||||
Low: binary.LittleEndian.Uint32(b[:4]),
|
||||
High: binary.LittleEndian.Uint32(b[4:8]),
|
||||
}
|
||||
}
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
// Copyright 2014 Richard Lehane. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Win GUID and UUID type
|
||||
// http://msdn.microsoft.com/en-us/library/cc230326.aspx
|
||||
type Guid struct {
|
||||
DataA uint32
|
||||
DataB uint16
|
||||
DataC uint16
|
||||
DataD [8]byte
|
||||
}
|
||||
|
||||
func (g Guid) String() string {
|
||||
buf := make([]byte, 8)
|
||||
binary.BigEndian.PutUint32(buf[:4], g.DataA)
|
||||
binary.BigEndian.PutUint16(buf[4:6], g.DataB)
|
||||
binary.BigEndian.PutUint16(buf[6:], g.DataC)
|
||||
return strings.ToUpper("{" +
|
||||
hex.EncodeToString(buf[:4]) +
|
||||
"-" +
|
||||
hex.EncodeToString(buf[4:6]) +
|
||||
"-" +
|
||||
hex.EncodeToString(buf[6:]) +
|
||||
"-" +
|
||||
hex.EncodeToString(g.DataD[:2]) +
|
||||
"-" +
|
||||
hex.EncodeToString(g.DataD[2:]) +
|
||||
"}")
|
||||
}
|
||||
|
||||
func (g Guid) Type() string {
|
||||
return "Guid"
|
||||
}
|
||||
|
||||
func (g Guid) Length() int {
|
||||
return 16
|
||||
}
|
||||
|
||||
func GuidFromString(str string) (Guid, error) {
|
||||
gerr := "Invalid GUID: expecting in format {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, got " + str
|
||||
if len(str) != 38 {
|
||||
return Guid{}, errors.New(gerr + "; bad length, should be 38 chars")
|
||||
}
|
||||
trimmed := strings.Trim(str, "{}")
|
||||
parts := strings.Split(trimmed, "-")
|
||||
if len(parts) != 5 {
|
||||
return Guid{}, errors.New(gerr + "; expecting should five '-' separators")
|
||||
}
|
||||
buf, err := hex.DecodeString(strings.Join(parts, ""))
|
||||
if err != nil {
|
||||
return Guid{}, errors.New(gerr + "; error decoding hex: " + err.Error())
|
||||
}
|
||||
return makeGuid(buf, binary.BigEndian), nil
|
||||
}
|
||||
|
||||
func MakeGuid(b []byte) (Type, error) {
|
||||
if len(b) < 16 {
|
||||
return Guid{}, ErrType
|
||||
}
|
||||
return makeGuid(b, binary.LittleEndian), nil
|
||||
}
|
||||
|
||||
func makeGuid(b []byte, order binary.ByteOrder) Guid {
|
||||
g := Guid{
|
||||
DataA: order.Uint32(b[:4]),
|
||||
DataB: order.Uint16(b[4:6]),
|
||||
DataC: order.Uint16(b[6:8]),
|
||||
DataD: [8]byte{},
|
||||
}
|
||||
copy(g.DataD[:], b[8:])
|
||||
return g
|
||||
}
|
||||
|
||||
func MustGuidFromString(str string) Guid {
|
||||
g, err := GuidFromString(str)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
func MustGuid(b []byte) Guid {
|
||||
return makeGuid(b, binary.LittleEndian)
|
||||
}
|
||||
|
||||
func GuidFromName(n string) (Guid, error) {
|
||||
n = strings.ToLower(n)
|
||||
buf, err := charConvert([]byte(n))
|
||||
if err != nil {
|
||||
return Guid{}, err
|
||||
}
|
||||
return makeGuid(buf, binary.LittleEndian), nil
|
||||
}
|
||||
|
||||
func charConvert(in []byte) ([]byte, error) {
|
||||
if len(in) != 26 {
|
||||
return nil, errors.New("invalid GUID: expecting 26 characters")
|
||||
}
|
||||
out := make([]byte, 16)
|
||||
var idx, shift uint
|
||||
var b byte
|
||||
for _, v := range in {
|
||||
this, ok := characterMapping[v]
|
||||
if !ok {
|
||||
return nil, errors.New("invalid Guid: invalid character")
|
||||
}
|
||||
b = b | this<<shift
|
||||
if shift >= 3 {
|
||||
out[idx] = b
|
||||
idx++
|
||||
b = this >> (8 - shift) // write any remainder back to b, or 0 if shift is 3
|
||||
}
|
||||
shift = shift + 5
|
||||
if shift > 7 {
|
||||
shift = shift - 8
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
const (
|
||||
charA byte = iota
|
||||
charB
|
||||
charC
|
||||
charD
|
||||
charE
|
||||
charF
|
||||
charG
|
||||
charH
|
||||
charI
|
||||
charJ
|
||||
charK
|
||||
charL
|
||||
charM
|
||||
charN
|
||||
charO
|
||||
charP
|
||||
charQ
|
||||
charR
|
||||
charS
|
||||
charT
|
||||
charU
|
||||
charV
|
||||
charW
|
||||
charX
|
||||
charY
|
||||
charZ
|
||||
char0
|
||||
char1
|
||||
char2
|
||||
char3
|
||||
char4
|
||||
char5
|
||||
)
|
||||
|
||||
var characterMapping = map[byte]byte{
|
||||
'a': charA,
|
||||
'b': charB,
|
||||
'c': charC,
|
||||
'd': charD,
|
||||
'e': charE,
|
||||
'f': charF,
|
||||
'g': charG,
|
||||
'h': charH,
|
||||
'i': charI,
|
||||
'j': charJ,
|
||||
'k': charK,
|
||||
'l': charL,
|
||||
'm': charM,
|
||||
'n': charN,
|
||||
'o': charO,
|
||||
'p': charP,
|
||||
'q': charQ,
|
||||
'r': charR,
|
||||
's': charS,
|
||||
't': charT,
|
||||
'u': charU,
|
||||
'v': charV,
|
||||
'w': charW,
|
||||
'x': charX,
|
||||
'y': charY,
|
||||
'z': charZ,
|
||||
'0': char0,
|
||||
'1': char1,
|
||||
'2': char2,
|
||||
'3': char3,
|
||||
'4': char4,
|
||||
'5': char5,
|
||||
}
|
||||
+274
@@ -0,0 +1,274 @@
|
||||
// Copyright 2014 Richard Lehane. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Null struct{}
|
||||
|
||||
func (i Null) Type() string {
|
||||
return "Null"
|
||||
}
|
||||
|
||||
func (i Null) Length() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (i Null) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type Bool bool
|
||||
|
||||
func (i Bool) Type() string {
|
||||
return "Boolean"
|
||||
}
|
||||
|
||||
func (i Bool) Length() int {
|
||||
return 2
|
||||
}
|
||||
|
||||
func (i Bool) String() string {
|
||||
if i {
|
||||
return "true"
|
||||
}
|
||||
return "false"
|
||||
}
|
||||
|
||||
func MakeBool(b []byte) (Type, error) {
|
||||
if len(b) < 2 {
|
||||
return Bool(false), ErrType
|
||||
}
|
||||
switch binary.LittleEndian.Uint16(b[:2]) {
|
||||
case 0xFFFF:
|
||||
return Bool(true), nil
|
||||
case 0x0000:
|
||||
return Bool(false), nil
|
||||
}
|
||||
return Bool(false), ErrType
|
||||
}
|
||||
|
||||
type I1 int8
|
||||
|
||||
func (i I1) Type() string {
|
||||
return "Int8"
|
||||
}
|
||||
|
||||
func (i I1) String() string {
|
||||
return strconv.Itoa(int(i))
|
||||
}
|
||||
|
||||
func (i I1) Length() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func MakeI1(b []byte) (Type, error) {
|
||||
if len(b) < 1 {
|
||||
return I1(0), ErrType
|
||||
}
|
||||
return I1(b[0]), nil
|
||||
}
|
||||
|
||||
type I2 int16
|
||||
|
||||
func (i I2) Type() string {
|
||||
return "Int16"
|
||||
}
|
||||
|
||||
func (i I2) Length() int {
|
||||
return 2
|
||||
}
|
||||
|
||||
func (i I2) String() string {
|
||||
return strconv.Itoa(int(i))
|
||||
}
|
||||
|
||||
func MakeI2(b []byte) (Type, error) {
|
||||
if len(b) < 2 {
|
||||
return I2(0), ErrType
|
||||
}
|
||||
return I2(binary.LittleEndian.Uint16(b[:2])), nil
|
||||
}
|
||||
|
||||
type I4 int32
|
||||
|
||||
func (i I4) Type() string {
|
||||
return "Int32"
|
||||
}
|
||||
|
||||
func (i I4) Length() int {
|
||||
return 4
|
||||
}
|
||||
|
||||
func (i I4) String() string {
|
||||
return strconv.Itoa(int(i))
|
||||
}
|
||||
|
||||
func MakeI4(b []byte) (Type, error) {
|
||||
if len(b) < 4 {
|
||||
return I4(0), ErrType
|
||||
}
|
||||
return I4(binary.LittleEndian.Uint32(b[:4])), nil
|
||||
}
|
||||
|
||||
type I8 int64
|
||||
|
||||
func (i I8) Type() string {
|
||||
return "Int64"
|
||||
}
|
||||
|
||||
func (i I8) Length() int {
|
||||
return 8
|
||||
}
|
||||
|
||||
func (i I8) String() string {
|
||||
return strconv.FormatInt(int64(i), 10)
|
||||
}
|
||||
|
||||
func MakeI8(b []byte) (Type, error) {
|
||||
if len(b) < 8 {
|
||||
return I8(0), ErrType
|
||||
}
|
||||
return I8(binary.LittleEndian.Uint64(b[:8])), nil
|
||||
}
|
||||
|
||||
type UI1 uint8
|
||||
|
||||
func (i UI1) Type() string {
|
||||
return "Uint8"
|
||||
}
|
||||
|
||||
func (i UI1) Length() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func (i UI1) String() string {
|
||||
return strconv.Itoa(int(i))
|
||||
}
|
||||
|
||||
func MakeUI1(b []byte) (Type, error) {
|
||||
if len(b) < 1 {
|
||||
return UI1(0), ErrType
|
||||
}
|
||||
return UI1(b[0]), nil
|
||||
}
|
||||
|
||||
type UI2 uint16
|
||||
|
||||
func (i UI2) Type() string {
|
||||
return "Uint16"
|
||||
}
|
||||
|
||||
func (i UI2) Length() int {
|
||||
return 2
|
||||
}
|
||||
|
||||
func (i UI2) String() string {
|
||||
return strconv.Itoa(int(i))
|
||||
}
|
||||
|
||||
func MakeUI2(b []byte) (Type, error) {
|
||||
if len(b) < 2 {
|
||||
return UI2(0), ErrType
|
||||
}
|
||||
return UI2(binary.LittleEndian.Uint16(b[:2])), nil
|
||||
}
|
||||
|
||||
type UI4 uint32
|
||||
|
||||
func (i UI4) Type() string {
|
||||
return "Uint32"
|
||||
}
|
||||
|
||||
func (i UI4) Length() int {
|
||||
return 4
|
||||
}
|
||||
|
||||
func (i UI4) String() string {
|
||||
return strconv.FormatUint(uint64(i), 10)
|
||||
}
|
||||
|
||||
func MakeUI4(b []byte) (Type, error) {
|
||||
if len(b) < 4 {
|
||||
return UI4(0), ErrType
|
||||
}
|
||||
return UI4(binary.LittleEndian.Uint32(b[:4])), nil
|
||||
}
|
||||
|
||||
type UI8 uint64
|
||||
|
||||
func (i UI8) Type() string {
|
||||
return "Uint64"
|
||||
}
|
||||
|
||||
func (i UI8) Length() int {
|
||||
return 8
|
||||
}
|
||||
|
||||
func (i UI8) String() string {
|
||||
return strconv.FormatUint(uint64(i), 10)
|
||||
}
|
||||
|
||||
func MakeUI8(b []byte) (Type, error) {
|
||||
if len(b) < 8 {
|
||||
return UI8(0), ErrType
|
||||
}
|
||||
return UI8(binary.LittleEndian.Uint64(b[:8])), nil
|
||||
}
|
||||
|
||||
type R4 float32
|
||||
|
||||
func (r R4) Type() string {
|
||||
return "Float32"
|
||||
}
|
||||
|
||||
func (r R4) Length() int {
|
||||
return 4
|
||||
}
|
||||
|
||||
func (r R4) String() string {
|
||||
return strconv.FormatFloat(float64(r), 'f', -1, 32)
|
||||
}
|
||||
|
||||
func MakeR4(b []byte) (Type, error) {
|
||||
if len(b) < 4 {
|
||||
return R4(0), ErrType
|
||||
}
|
||||
return R4(binary.LittleEndian.Uint32(b[:4])), nil
|
||||
}
|
||||
|
||||
type R8 float64
|
||||
|
||||
func (r R8) Type() string {
|
||||
return "Float64"
|
||||
}
|
||||
|
||||
func (r R8) Length() int {
|
||||
return 8
|
||||
}
|
||||
|
||||
func (r R8) String() string {
|
||||
return strconv.FormatFloat(float64(r), 'f', -1, 64)
|
||||
}
|
||||
|
||||
func MakeR8(b []byte) (Type, error) {
|
||||
if len(b) < 8 {
|
||||
return R8(0), ErrType
|
||||
}
|
||||
return R8(binary.LittleEndian.Uint64(b[:8])), nil
|
||||
}
|
||||
+270
@@ -0,0 +1,270 @@
|
||||
// Copyright 2014 Richard Lehane. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"strings"
|
||||
"unicode/utf16"
|
||||
)
|
||||
|
||||
func nullTerminated(s string) string {
|
||||
return s[:strings.Index(s, "\x00")]
|
||||
}
|
||||
|
||||
type UnicodeString []uint16
|
||||
|
||||
func (s UnicodeString) Type() string {
|
||||
return "UnicodeString"
|
||||
}
|
||||
|
||||
func (s UnicodeString) Length() int {
|
||||
return 4 + len(s)*2
|
||||
}
|
||||
|
||||
func (s UnicodeString) String() string {
|
||||
if len(s) == 0 {
|
||||
return ""
|
||||
}
|
||||
return nullTerminated(string(utf16.Decode(s)))
|
||||
}
|
||||
|
||||
func MakeUnicode(b []byte) (Type, error) {
|
||||
if len(b) < 4 {
|
||||
return UnicodeString{}, ErrType
|
||||
}
|
||||
l := int(binary.LittleEndian.Uint32(b[:4]))
|
||||
if l == 0 {
|
||||
return UnicodeString{}, nil
|
||||
}
|
||||
if len(b) < l*2+4 {
|
||||
return UnicodeString{}, ErrType
|
||||
}
|
||||
s := make(UnicodeString, l)
|
||||
for i := range s {
|
||||
start := i*2 + 4
|
||||
s[i] = binary.LittleEndian.Uint16(b[start : start+2])
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
type CodeString struct {
|
||||
id CodePageID
|
||||
Chars []byte
|
||||
}
|
||||
|
||||
func (s *CodeString) SetId(i CodePageID) {
|
||||
s.id = i
|
||||
}
|
||||
|
||||
func (s *CodeString) Encoding() string {
|
||||
return CodePageIDs[s.id]
|
||||
}
|
||||
|
||||
func (s *CodeString) Type() string {
|
||||
return "CodeString"
|
||||
}
|
||||
|
||||
func (s *CodeString) Length() int {
|
||||
return 4 + len(s.Chars)
|
||||
}
|
||||
|
||||
func (s *CodeString) String() string {
|
||||
if len(s.Chars) == 0 {
|
||||
return ""
|
||||
}
|
||||
if s.id == 1200 {
|
||||
chars := make([]uint16, len(s.Chars)/2)
|
||||
for i := range chars {
|
||||
chars[i] = binary.LittleEndian.Uint16(s.Chars[i*2 : i*2+2])
|
||||
}
|
||||
return nullTerminated(string(utf16.Decode(chars)))
|
||||
}
|
||||
return nullTerminated(string(s.Chars))
|
||||
}
|
||||
|
||||
func MakeCodeString(b []byte) (Type, error) {
|
||||
if len(b) < 4 {
|
||||
return &CodeString{}, ErrType
|
||||
}
|
||||
s := &CodeString{}
|
||||
l := int(binary.LittleEndian.Uint32(b[:4]))
|
||||
if l == 0 {
|
||||
return s, nil
|
||||
}
|
||||
if len(b) < l+4 {
|
||||
return s, ErrType
|
||||
}
|
||||
s.Chars = make([]byte, l)
|
||||
copy(s.Chars, b[4:l+4])
|
||||
return s, nil
|
||||
}
|
||||
|
||||
type CodePageID uint16
|
||||
|
||||
var CodePageIDs map[CodePageID]string = map[CodePageID]string{
|
||||
37: "IBM037 - IBM EBCDIC US-Canada",
|
||||
437: "IBM437 - OEM United States",
|
||||
500: "IBM500 - IBM EBCDIC International",
|
||||
708: "ASMO-708 - Arabic (ASMO 708)",
|
||||
709: "Arabic (ASMO-449+, BCON V4)",
|
||||
710: "Arabic - Transparent Arabic",
|
||||
720: "DOS-720 - Arabic (Transparent ASMO); Arabic (DOS)",
|
||||
737: "ibm737 - OEM Greek (formerly 437G); Greek (DOS)",
|
||||
775: "ibm775 - OEM Baltic; Baltic (DOS)",
|
||||
850: "ibm850 - OEM Multilingual Latin 1; Western European (DOS)",
|
||||
852: "ibm852 - OEM Latin 2; Central European (DOS)",
|
||||
855: "IBM855 - OEM Cyrillic (primarily Russian)",
|
||||
857: "ibm857 - OEM Turkish; Turkish (DOS)",
|
||||
858: "IBM00858 - OEM Multilingual Latin 1 + Euro symbol",
|
||||
860: "IBM860 - OEM Portuguese; Portuguese (DOS)",
|
||||
861: "ibm861 - OEM Icelandic; Icelandic (DOS)",
|
||||
862: "DOS-862 - OEM Hebrew; Hebrew (DOS)",
|
||||
863: "IBM863 - OEM French Canadian; French Canadian (DOS)",
|
||||
864: "IBM864 - OEM Arabic; Arabic (864)",
|
||||
865: "IBM865 - OEM Nordic; Nordic (DOS)",
|
||||
866: "cp866 - OEM Russian; Cyrillic (DOS)",
|
||||
869: "ibm869 - OEM Modern Greek; Greek, Modern (DOS)",
|
||||
870: "IBM870 - IBM EBCDIC Multilingual/ROECE (Latin 2); IBM EBCDIC Multilingual Latin 2",
|
||||
874: "windows-874 - ANSI/OEM Thai (ISO 8859-11); Thai (Windows)",
|
||||
875: "cp875 - IBM EBCDIC Greek Modern",
|
||||
932: "shift_jis - ANSI/OEM Japanese; Japanese (Shift-JIS)",
|
||||
936: "gb2312 - ANSI/OEM Simplified Chinese (PRC, Singapore); Chinese Simplified (GB2312)",
|
||||
949: "ks_c_5601-1987 - ANSI/OEM Korean (Unified Hangul Code)",
|
||||
950: "big5 - ANSI/OEM Traditional Chinese (Taiwan; Hong Kong SAR, PRC); Chinese Traditional (Big5)",
|
||||
1026: "IBM1026 - IBM EBCDIC Turkish (Latin 5)",
|
||||
1047: "IBM01047 - BM EBCDIC Latin 1/Open System",
|
||||
1140: "IBM01140 - IBM EBCDIC US-Canada (037 + Euro symbol); IBM EBCDIC (US-Canada-Euro)",
|
||||
1141: "IBM01141 - IBM EBCDIC Germany (20273 + Euro symbol); IBM EBCDIC (Germany-Euro)",
|
||||
1142: "IBM01142 - IBM EBCDIC Denmark-Norway (20277 + Euro symbol); IBM EBCDIC (Denmark-Norway-Euro)",
|
||||
1143: "IBM01143 - IBM EBCDIC Finland-Sweden (20278 + Euro symbol); IBM EBCDIC (Finland-Sweden-Euro)",
|
||||
1144: "IBM01144 - IBM EBCDIC Italy (20280 + Euro symbol); IBM EBCDIC (Italy-Euro)",
|
||||
1145: "IBM01145 - IBM EBCDIC Latin America-Spain (20284 + Euro symbol); IBM EBCDIC (Spain-Euro)",
|
||||
1146: "IBM01146 - IBM EBCDIC United Kingdom (20285 + Euro symbol); IBM EBCDIC (UK-Euro)",
|
||||
1147: "IBM01147 - IBM EBCDIC France (20297 + Euro symbol); IBM EBCDIC (France-Euro)",
|
||||
1148: "IBM01148 - IBM EBCDIC International (500 + Euro symbol); IBM EBCDIC (International-Euro)",
|
||||
1149: "IBM01149 - IBM EBCDIC Icelandic (20871 + Euro symbol); IBM EBCDIC (Icelandic-Euro)",
|
||||
1200: "utf-16 - Unicode UTF-16, little endian byte order (BMP of ISO 10646); available only to managed applications",
|
||||
1201: "unicodeFFFE - Unicode UTF-16, big endian byte order; available only to managed applications",
|
||||
1250: "windows-1250 - ANSI Central European; Central European (Windows)",
|
||||
1251: "windows-1251 - ANSI Cyrillic; Cyrillic (Windows)",
|
||||
1252: "windows-1252 - ANSI Latin 1; Western European (Windows)",
|
||||
1253: "windows-1253 - ANSI Greek; Greek (Windows)",
|
||||
1254: "windows-1254 - ANSI Turkish; Turkish (Windows)",
|
||||
1255: "windows-1255 - ANSI Hebrew; Hebrew (Windows)",
|
||||
1256: "windows-1256 - ANSI Arabic; Arabic (Windows)",
|
||||
1257: "windows-1257 - ANSI Baltic; Baltic (Windows)",
|
||||
1258: "windows-1258 - ANSI/OEM Vietnamese; Vietnamese (Windows)",
|
||||
1361: "Johab - Korean (Johab)",
|
||||
10000: "macintosh - MAC Roman; Western European (Mac)",
|
||||
10001: "x-mac-japanese - Japanese (Mac)",
|
||||
10002: "x-mac-chinesetrad - MAC Traditional Chinese (Big5); Chinese Traditional (Mac)",
|
||||
10003: "x-mac-korean - Korean (Mac)",
|
||||
10004: "x-mac-arabic - Arabic (Mac)",
|
||||
10005: "x-mac-hebrew - Hebrew (Mac)",
|
||||
10006: "x-mac-greek - Greek (Mac)",
|
||||
10007: "x-mac-cyrillic - Cyrillic (Mac)",
|
||||
10008: "x-mac-chinesesimp - MAC Simplified Chinese (GB 2312); Chinese Simplified (Mac)",
|
||||
10010: "x-mac-romanian - Romanian (Mac)",
|
||||
10017: "x-mac-ukrainian - Ukrainian (Mac)",
|
||||
10021: "x-mac-thai - Thai (Mac)",
|
||||
10029: "x-mac-ce - MAC Latin 2; Central European (Mac)",
|
||||
10079: "x-mac-icelandic - Icelandic (Mac)",
|
||||
10081: "x-mac-turkish - Turkish (Mac)",
|
||||
10082: "x-mac-croatian - Croatian (Mac)",
|
||||
12000: "utf-32 - Unicode UTF-32, little endian byte order; available only to managed applications",
|
||||
12001: "utf-32BE - Unicode UTF-32, big endian byte order; available only to managed applications",
|
||||
20000: "x-Chinese_CNS - CNS Taiwan; Chinese Traditional (CNS)",
|
||||
20001: "x-cp20001 - TCA Taiwan",
|
||||
20002: "x_Chinese-Eten - Eten Taiwan; Chinese Traditional (Eten)",
|
||||
20003: "x-cp20003 - IBM5550 Taiwan",
|
||||
20004: "x-cp20004 - TeleText Taiwan",
|
||||
20005: "x-cp20005 - Wang Taiwan",
|
||||
20105: "x-IA5 - IA5 (IRV International Alphabet No. 5, 7-bit); Western European (IA5)",
|
||||
20106: "x-IA5-German - IA5 German (7-bit)",
|
||||
20107: "x-IA5-Swedish - IA5 Swedish (7-bit)",
|
||||
20108: "x-IA5-Norwegian - IA5 Norwegian (7-bit)",
|
||||
20127: "us-ascii - US-ASCII (7-bit)",
|
||||
20261: "x-cp20261 - T.61",
|
||||
20269: "x-cp20269 - ISO 6937 Non-Spacing Accent",
|
||||
20273: "IBM273 - IBM EBCDIC Germany",
|
||||
20277: "IBM277 - IBM EBCDIC Denmark-Norway",
|
||||
20278: "IBM278 - IBM EBCDIC Finland-Sweden",
|
||||
20280: "IBM280 - IBM EBCDIC Italy",
|
||||
20284: "IBM284 - IBM EBCDIC Latin America-Spain",
|
||||
20285: "IBM285 - IBM EBCDIC United Kingdom",
|
||||
20290: "IBM290 - IBM EBCDIC Japanese Katakana Extended",
|
||||
20297: "IBM297 - IBM EBCDIC France",
|
||||
20420: "IBM420 - IBM EBCDIC Arabic",
|
||||
20423: "IBM423 - IBM EBCDIC Greek",
|
||||
20424: "IBM424 - IBM EBCDIC Hebrew",
|
||||
20833: "x-EBCDIC-KoreanExtended - IBM EBCDIC Korean Extended",
|
||||
20838: "IBM-Thai - IBM EBCDIC Thai",
|
||||
20866: "koi8-r - Russian (KOI8-R); Cyrillic (KOI8-R)",
|
||||
20871: "IBM871 - IBM EBCDIC Icelandic",
|
||||
20880: "IBM880 - IBM EBCDIC Cyrillic Russian",
|
||||
20905: "IBM905 - IBM EBCDIC Turkish",
|
||||
20924: "IBM00924 - IBM EBCDIC Latin 1/Open System (1047 + Euro symbol)",
|
||||
20932: "EUC-JP - Japanese (JIS 0208-1990 and 0212-1990)",
|
||||
20936: "x-cp20936 - Simplified Chinese (GB2312); Chinese Simplified (GB2312-80)",
|
||||
20949: "x-cp20949 - Korean Wansung",
|
||||
21025: "cp1025 - IBM EBCDIC Cyrillic Serbian-Bulgarian",
|
||||
21027: "(deprecated)",
|
||||
21866: "koi8-u - Ukrainian (KOI8-U); Cyrillic (KOI8-U)",
|
||||
28591: "iso-8859-1 - ISO 8859-1 Latin 1; Western European (ISO)",
|
||||
28592: "iso-8859-2 - ISO 8859-2 Central European; Central European (ISO)",
|
||||
28593: "iso-8859-3 - ISO 8859-3 Latin 3",
|
||||
28594: "iso-8859-4 - ISO 8859-4 Baltic",
|
||||
28595: "iso-8859-5 - ISO 8859-5 Cyrillic",
|
||||
28596: "iso-8859-6 - ISO 8859-6 Arabic",
|
||||
28597: "iso-8859-7 - ISO 8859-7 Greek",
|
||||
28598: "iso-8859-8 - ISO 8859-8 Hebrew; Hebrew (ISO-Visual)",
|
||||
28599: "iso-8859-9 - ISO 8859-9 Turkish",
|
||||
28603: "iso-8859-13 - ISO 8859-13 Estonian",
|
||||
28605: "iso-8859-15 - ISO 8859-15 Latin 9",
|
||||
29001: "x-Europa - Europa 3",
|
||||
38598: "iso-8859-8-i - ISO 8859-8 Hebrew; Hebrew (ISO-Logical)",
|
||||
50220: "iso-2022-jp - ISO 2022 Japanese with no halfwidth Katakana; Japanese (JIS)",
|
||||
50221: "csISO2022JP - ISO 2022 Japanese with halfwidth Katakana; Japanese (JIS-Allow 1 byte Kana)",
|
||||
50222: "iso-2022-jp - ISO 2022 Japanese JIS X 0201-1989; Japanese (JIS-Allow 1 byte Kana - SO/SI)",
|
||||
50225: "iso-2022-kr - ISO 2022 Korean",
|
||||
50227: "x-cp50227 - ISO 2022 Simplified Chinese; Chinese Simplified (ISO 2022)",
|
||||
50229: "ISO 2022 - Traditional Chinese",
|
||||
50930: "EBCDIC - Japanese (Katakana) Extended",
|
||||
50931: "EBCDIC - US-Canada and Japanese",
|
||||
50933: "EBCDIC - Korean Extended and Korean",
|
||||
50935: "EBCDIC - Simplified Chinese Extended and Simplified Chinese",
|
||||
50936: "EBCDIC - Simplified Chinese",
|
||||
50937: "EBCDIC - US-Canada and Traditional Chinese",
|
||||
50939: "EBCDIC - Japanese (Latin) Extended and Japanese",
|
||||
51932: "euc-jp - EUC Japanese",
|
||||
51936: "EUC-CN - EUC Simplified Chinese; Chinese Simplified (EUC)",
|
||||
51949: "euc-kr - EUC Korean",
|
||||
51950: "EUC - Traditional Chinese",
|
||||
52936: "hz-gb-2312 - HZ-GB2312 Simplified Chinese; Chinese Simplified (HZ)",
|
||||
54936: "GB18030 - Windows XP and later: GB18030 Simplified Chinese (4 byte); Chinese Simplified (GB18030)",
|
||||
57002: "x-iscii-de - ISCII Devanagari",
|
||||
57003: "x-iscii-be - ISCII Bengali",
|
||||
57004: "x-iscii-ta - ISCII Tamil",
|
||||
57005: "x-iscii-te - ISCII Telugu",
|
||||
57006: "x-iscii-as - ISCII Assamese",
|
||||
57007: "x-iscii-or - ISCII Oriya",
|
||||
57008: "x-iscii-ka - ISCII Kannada",
|
||||
57009: "x-iscii-ma - ISCII Malayalam",
|
||||
57010: "x-iscii-gu - ISCII Gujarati",
|
||||
57011: "x-iscii-pa - ISCII Punjabi",
|
||||
65000: "utf-7 - Unicode (UTF-7)",
|
||||
65001: "utf-8 - Unicode (UTF-8)",
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
// Copyright 2014 Richard Lehane. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// MakeVariant is defined in vectorArray.go. It calls Evaluate, which refers to the MakeTypes map, so must add at runtime
|
||||
func init() { MakeTypes[VT_VARIANT] = MakeVariant }
|
||||
|
||||
var (
|
||||
ErrType = errors.New("msoleps: error coercing byte stream to type")
|
||||
ErrUnknownType = errors.New("msoleps: unknown type error")
|
||||
)
|
||||
|
||||
type Type interface {
|
||||
String() string
|
||||
Type() string
|
||||
Length() int
|
||||
}
|
||||
|
||||
const (
|
||||
scalar uint16 = iota
|
||||
vector
|
||||
array
|
||||
)
|
||||
|
||||
func Evaluate(b []byte) (Type, error) {
|
||||
if len(b) < 4 {
|
||||
return I1(0), ErrType
|
||||
}
|
||||
id := TypeID(binary.LittleEndian.Uint16(b[:2]))
|
||||
f, ok := MakeTypes[id]
|
||||
if !ok {
|
||||
return I1(0), ErrUnknownType
|
||||
}
|
||||
switch binary.LittleEndian.Uint16(b[2:4]) {
|
||||
case vector:
|
||||
return MakeVector(f, b[4:])
|
||||
case array:
|
||||
return MakeArray(f, b[4:])
|
||||
case scalar:
|
||||
if id != VT_VARIANT { // a VT_VARIANT can only be in a vector or array
|
||||
return f(b[4:])
|
||||
}
|
||||
}
|
||||
return I1(0), ErrUnknownType
|
||||
|
||||
}
|
||||
|
||||
type TypeID uint16
|
||||
|
||||
const (
|
||||
VT_EMPTY TypeID = iota // 0x00
|
||||
VT_NULL
|
||||
VT_I2
|
||||
VT_I4
|
||||
VT_R4
|
||||
VT_R8
|
||||
VT_CY
|
||||
VT_DATE
|
||||
VT_BSTR
|
||||
_
|
||||
VT_ERROR
|
||||
VT_BOOL
|
||||
VT_VARIANT
|
||||
_
|
||||
VT_DECIMAL
|
||||
_
|
||||
VT_I1
|
||||
VT_U1
|
||||
VT_UI2
|
||||
VT_UI4
|
||||
VT_I8
|
||||
VT_UI8
|
||||
VT_INT
|
||||
VT_UINT //0x17
|
||||
_ = iota + 5
|
||||
VT_LPSTR //0x1E
|
||||
VT_LPWSTR
|
||||
VT_FILETIME = iota + 0x25 // 0x40
|
||||
VT_BLOB
|
||||
VT_STREAM
|
||||
VT_STORAGE
|
||||
VT_STREAMED_OBJECT
|
||||
VT_STORED_OBJECT
|
||||
VT_BLOB_OBJECT
|
||||
VT_CF
|
||||
VT_CLSID
|
||||
VT_VERSIONED_STREAM // 0x49
|
||||
)
|
||||
|
||||
type MakeType func([]byte) (Type, error)
|
||||
|
||||
var MakeTypes map[TypeID]MakeType = map[TypeID]MakeType{
|
||||
VT_I2: MakeI2,
|
||||
VT_I4: MakeI4,
|
||||
VT_R4: MakeR4,
|
||||
VT_R8: MakeR8,
|
||||
VT_CY: MakeCurrency,
|
||||
VT_DATE: MakeDate,
|
||||
VT_BSTR: MakeCodeString,
|
||||
VT_BOOL: MakeBool,
|
||||
VT_DECIMAL: MakeDecimal,
|
||||
VT_I1: MakeI1,
|
||||
VT_U1: MakeUI1,
|
||||
VT_UI2: MakeUI2,
|
||||
VT_UI4: MakeUI4,
|
||||
VT_I8: MakeI8,
|
||||
VT_UI8: MakeUI8,
|
||||
VT_INT: MakeI4,
|
||||
VT_UINT: MakeUI4,
|
||||
VT_LPSTR: MakeCodeString,
|
||||
VT_LPWSTR: MakeUnicode,
|
||||
VT_FILETIME: MakeFileTime,
|
||||
VT_CLSID: MakeGuid,
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
// Copyright 2015 Richard Lehane. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
type Vector []Type
|
||||
|
||||
func (v Vector) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (v Vector) Type() string {
|
||||
if len(v) > 0 {
|
||||
return "Vector of " + v[0].Type()
|
||||
}
|
||||
return "Vector (empty)"
|
||||
}
|
||||
|
||||
func (v Vector) Length() int {
|
||||
ret := 4
|
||||
for _, t := range v {
|
||||
ret += t.Length()
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func MakeVector(f MakeType, b []byte) (Type, error) {
|
||||
if len(b) < 4 {
|
||||
return Vector{}, ErrType
|
||||
}
|
||||
l := int(binary.LittleEndian.Uint32(b[:4]))
|
||||
v := make(Vector, l)
|
||||
place := 4
|
||||
for i := 0; i < l; i++ {
|
||||
t, err := f(b[place:])
|
||||
if err != nil {
|
||||
return Vector{}, ErrType
|
||||
}
|
||||
v[i] = t
|
||||
place += t.Length()
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
type Array [][]Type
|
||||
|
||||
func (a Array) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (a Array) Type() string {
|
||||
if len(a) > 0 && len(a[0]) > 0 {
|
||||
return "Array of " + a[0][0].Type()
|
||||
}
|
||||
return "Array (empty)"
|
||||
}
|
||||
|
||||
func (a Array) Length() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// TODO: Array not implemented yet
|
||||
func MakeArray(f MakeType, b []byte) (Type, error) {
|
||||
return Array{}, nil
|
||||
}
|
||||
|
||||
type Variant struct {
|
||||
t Type
|
||||
}
|
||||
|
||||
func (v Variant) String() string {
|
||||
return "Typed Property Value containing " + v.t.String()
|
||||
}
|
||||
|
||||
func (v Variant) Type() string {
|
||||
return "Typed Property Value containing " + v.t.Type()
|
||||
}
|
||||
|
||||
func (v Variant) Length() int {
|
||||
return 4 + v.t.Length()
|
||||
}
|
||||
|
||||
func MakeVariant(b []byte) (Type, error) {
|
||||
if len(b) < 4 || binary.LittleEndian.Uint16(b[2:4]) != scalar { // only scalar values allowed
|
||||
return Variant{}, ErrType
|
||||
}
|
||||
id := TypeID(binary.LittleEndian.Uint16(b[:2]))
|
||||
if id == VT_VARIANT {
|
||||
return Variant{}, ErrType // no recursive types allowed
|
||||
}
|
||||
f, ok := MakeTypes[id]
|
||||
if !ok {
|
||||
return Variant{}, ErrUnknownType
|
||||
}
|
||||
t, err := f(b[4:])
|
||||
if err != nil {
|
||||
return Variant{}, err
|
||||
}
|
||||
return Variant{t}, nil
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others’ private information, such as a physical or email address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [xuri.me](https://xuri.me). All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
Community Impact: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
|
||||
|
||||
Consequence: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
Community Impact: A violation through a single incident or series of actions.
|
||||
|
||||
Consequence: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
Community Impact: A serious violation of community standards, including sustained inappropriate behavior.
|
||||
|
||||
Consequence: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
Community Impact: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
Consequence: A permanent ban from any sort of public interaction within the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the Contributor Covenant, version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html).
|
||||
|
||||
Community Impact Guidelines were inspired by Mozilla’s code of conduct enforcement ladder.
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). Translations are available at [https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations).
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2017 - 2022 Ri Xu All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of efp nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
# PR Details
|
||||
|
||||
<!--- Provide a general summary of your changes in the Title above -->
|
||||
|
||||
## Description
|
||||
|
||||
<!--- Describe your changes in detail -->
|
||||
|
||||
## Related Issue
|
||||
|
||||
<!--- This project only accepts pull requests related to open issues -->
|
||||
<!--- If suggesting a new feature or change, please discuss it in an issue first -->
|
||||
<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
|
||||
<!--- Please link to the issue here: -->
|
||||
|
||||
## Motivation and Context
|
||||
|
||||
<!--- Why is this change required? What problem does it solve? -->
|
||||
|
||||
## How Has This Been Tested
|
||||
|
||||
<!--- Please describe in detail how you tested your changes. -->
|
||||
<!--- Include details of your testing environment, and the tests you ran to -->
|
||||
<!--- see how your change affects other areas of the code, etc. -->
|
||||
|
||||
## Types of changes
|
||||
|
||||
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
|
||||
|
||||
- [ ] Docs change / refactoring / dependency upgrade
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
||||
|
||||
## Checklist
|
||||
|
||||
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
|
||||
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
|
||||
|
||||
- [ ] My code follows the code style of this project.
|
||||
- [ ] My change requires a change to the documentation.
|
||||
- [ ] I have updated the documentation accordingly.
|
||||
- [ ] I have read the **CONTRIBUTING** document.
|
||||
- [ ] I have added tests to cover my changes.
|
||||
- [ ] All new and existing tests passed.
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
# EFP (Excel Formula Parser)
|
||||
|
||||
[](https://github.com/xuri/efp/actions?workflow=Go)
|
||||
[](https://codecov.io/gh/xuri/efp)
|
||||
[](https://goreportcard.com/report/github.com/xuri/efp)
|
||||
[](https://pkg.go.dev/github.com/xuri/efp)
|
||||
[](https://opensource.org/licenses/BSD-3-Clause)
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2Fxuri%2Fefp?ref=badge_shield)
|
||||
|
||||
Using EFP (Excel Formula Parser) you can get an Abstract Syntax Tree (AST) from Excel formula.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
go get github.com/xuri/efp
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "github.com/xuri/efp"
|
||||
|
||||
func main() {
|
||||
ps := efp.ExcelParser()
|
||||
ps.Parse("=SUM(A3+B9*2)/2")
|
||||
println(ps.PrettyPrint())
|
||||
}
|
||||
```
|
||||
|
||||
Get AST
|
||||
|
||||
```text
|
||||
SUM <Function> <Start>
|
||||
A3 <Operand> <Range>
|
||||
+ <OperatorInfix> <Math>
|
||||
B9 <Operand> <Range>
|
||||
* <OperatorInfix> <Math>
|
||||
2 <Operand> <Number>
|
||||
<Function> <Stop>
|
||||
/ <OperatorInfix> <Math>
|
||||
2 <Operand> <Number>
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Open a pull request to fix a bug, or open an issue to discuss a new feature or change.
|
||||
|
||||
## Credits
|
||||
|
||||
EFP (Excel Formula Parser) is a Golang port of [E. W. Bachtal's](https://ewbi.blogs.com/develops/2004/12/excel_formula_p.html) Excel formula parser.
|
||||
|
||||
## Licenses
|
||||
|
||||
This program is under the terms of the BSD 3-Clause License. See [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause).
|
||||
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2Fxuri%2Fefp?ref=badge_large)
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We will dive into any security-related issue as long as your efp version is still supported by us. When reporting an issue, include as much information as possible, but no need to fill fancy forms or answer tedious questions. Just tell us what you found, how to reproduce it, and any concerns you have about it. We will respond as soon as possible and follow up with any missing information.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please e-mail us directly at `xuri.me@gmail.com` or use the security issue template on GitHub. In general, public disclosure is made after the issue has been fully identified and a patch is ready to be released. A security issue gets the highest priority assigned and a reply regarding the vulnerability is given within a typical 24 hours. Thank you!
|
||||
+671
@@ -0,0 +1,671 @@
|
||||
// Package efp (Excel Formula Parser) tokenize an Excel formula using an
|
||||
// implementation of E. W. Bachtal's algorithm, found here:
|
||||
// https://ewbi.blogs.com/develops/2004/12/excel_formula_p.html
|
||||
//
|
||||
// Go language version by Ri Xu: https://xuri.me
|
||||
package efp
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// QuoteDouble, QuoteSingle and other's constants are token definitions.
|
||||
const (
|
||||
// Character constants
|
||||
QuoteDouble = "\""
|
||||
QuoteSingle = "'"
|
||||
BracketClose = "]"
|
||||
BracketOpen = "["
|
||||
BraceOpen = "{"
|
||||
BraceClose = "}"
|
||||
ParenOpen = "("
|
||||
ParenClose = ")"
|
||||
Semicolon = ";"
|
||||
Whitespace = " "
|
||||
Comma = ","
|
||||
ErrorStart = "#"
|
||||
|
||||
OperatorsSN = "+-"
|
||||
OperatorsInfix = "+-*/^&=><"
|
||||
OperatorsPostfix = "%"
|
||||
|
||||
// Token type
|
||||
TokenTypeNoop = "Noop"
|
||||
TokenTypeOperand = "Operand"
|
||||
TokenTypeFunction = "Function"
|
||||
TokenTypeSubexpression = "Subexpression"
|
||||
TokenTypeArgument = "Argument"
|
||||
TokenTypeOperatorPrefix = "OperatorPrefix"
|
||||
TokenTypeOperatorInfix = "OperatorInfix"
|
||||
TokenTypeOperatorPostfix = "OperatorPostfix"
|
||||
TokenTypeWhitespace = "Whitespace"
|
||||
TokenTypeUnknown = "Unknown"
|
||||
|
||||
// Token subtypes
|
||||
TokenSubTypeStart = "Start"
|
||||
TokenSubTypeStop = "Stop"
|
||||
TokenSubTypeText = "Text"
|
||||
TokenSubTypeNumber = "Number"
|
||||
TokenSubTypeLogical = "Logical"
|
||||
TokenSubTypeError = "Error"
|
||||
TokenSubTypeRange = "Range"
|
||||
TokenSubTypeMath = "Math"
|
||||
TokenSubTypeConcatenation = "Concatenation"
|
||||
TokenSubTypeIntersection = "Intersection"
|
||||
TokenSubTypeUnion = "Union"
|
||||
)
|
||||
|
||||
// Token encapsulate a formula token.
|
||||
type Token struct {
|
||||
TValue string
|
||||
TType string
|
||||
TSubType string
|
||||
}
|
||||
|
||||
// Tokens directly maps the ordered list of tokens.
|
||||
// Attributes:
|
||||
//
|
||||
// items - Ordered list
|
||||
// index - Current position in the list
|
||||
//
|
||||
type Tokens struct {
|
||||
Index int
|
||||
Items []Token
|
||||
}
|
||||
|
||||
// Parser inheritable container. TokenStack directly maps a LIFO stack of
|
||||
// tokens.
|
||||
type Parser struct {
|
||||
Formula string
|
||||
Tokens Tokens
|
||||
TokenStack Tokens
|
||||
Offset int
|
||||
Token string
|
||||
InString bool
|
||||
InPath bool
|
||||
InRange bool
|
||||
InError bool
|
||||
}
|
||||
|
||||
// fToken provides function to encapsulate a formula token.
|
||||
func fToken(value, tokenType, subType string) Token {
|
||||
return Token{
|
||||
TValue: value,
|
||||
TType: tokenType,
|
||||
TSubType: subType,
|
||||
}
|
||||
}
|
||||
|
||||
// fTokens provides function to handle an ordered list of tokens.
|
||||
func fTokens() Tokens {
|
||||
return Tokens{
|
||||
Index: -1,
|
||||
}
|
||||
}
|
||||
|
||||
// add provides function to add a token to the end of the list.
|
||||
func (tk *Tokens) add(value, tokenType, subType string) Token {
|
||||
token := fToken(value, tokenType, subType)
|
||||
tk.addRef(token)
|
||||
return token
|
||||
}
|
||||
|
||||
// addRef provides function to add a token to the end of the list.
|
||||
func (tk *Tokens) addRef(token Token) {
|
||||
tk.Items = append(tk.Items, token)
|
||||
}
|
||||
|
||||
// reset provides function to reset the index to -1.
|
||||
func (tk *Tokens) reset() {
|
||||
tk.Index = -1
|
||||
}
|
||||
|
||||
// BOF provides function to check whether beginning of list.
|
||||
func (tk *Tokens) BOF() bool {
|
||||
return tk.Index <= 0
|
||||
}
|
||||
|
||||
// EOF provides function to check whether end of list.
|
||||
func (tk *Tokens) EOF() bool {
|
||||
return tk.Index >= (len(tk.Items) - 1)
|
||||
}
|
||||
|
||||
// moveNext provides function to move the index along one.
|
||||
func (tk *Tokens) moveNext() bool {
|
||||
if tk.EOF() {
|
||||
return false
|
||||
}
|
||||
tk.Index++
|
||||
return true
|
||||
}
|
||||
|
||||
// current return the current token.
|
||||
func (tk *Tokens) current() *Token {
|
||||
if tk.Index == -1 {
|
||||
return nil
|
||||
}
|
||||
return &tk.Items[tk.Index]
|
||||
}
|
||||
|
||||
// next return the next token (leave the index unchanged).
|
||||
func (tk *Tokens) next() *Token {
|
||||
if tk.EOF() {
|
||||
return nil
|
||||
}
|
||||
return &tk.Items[tk.Index+1]
|
||||
}
|
||||
|
||||
// previous return the previous token (leave the index unchanged).
|
||||
func (tk *Tokens) previous() *Token {
|
||||
if tk.Index < 1 {
|
||||
return nil
|
||||
}
|
||||
return &tk.Items[tk.Index-1]
|
||||
}
|
||||
|
||||
// push provides function to push a token onto the stack.
|
||||
func (tk *Tokens) push(token Token) {
|
||||
tk.Items = append(tk.Items, token)
|
||||
}
|
||||
|
||||
// pop provides function to pop a token off the stack.
|
||||
func (tk *Tokens) pop() Token {
|
||||
if len(tk.Items) == 0 {
|
||||
return Token{
|
||||
TType: TokenTypeFunction,
|
||||
TSubType: TokenSubTypeStop,
|
||||
}
|
||||
}
|
||||
t := tk.Items[len(tk.Items)-1]
|
||||
tk.Items = tk.Items[:len(tk.Items)-1]
|
||||
return fToken("", t.TType, TokenSubTypeStop)
|
||||
}
|
||||
|
||||
// token provides function to non-destructively return the top item on the
|
||||
// stack.
|
||||
func (tk *Tokens) token() *Token {
|
||||
if len(tk.Items) > 0 {
|
||||
return &tk.Items[len(tk.Items)-1]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// value return the top token's value.
|
||||
func (tk *Tokens) value() string {
|
||||
if tk.token() == nil {
|
||||
return ""
|
||||
}
|
||||
return tk.token().TValue
|
||||
}
|
||||
|
||||
// tp return the top token's type.
|
||||
func (tk *Tokens) tp() string {
|
||||
if tk.token() == nil {
|
||||
return ""
|
||||
}
|
||||
return tk.token().TType
|
||||
}
|
||||
|
||||
// subtype return the top token's subtype.
|
||||
func (tk *Tokens) subtype() string {
|
||||
if tk.token() == nil {
|
||||
return ""
|
||||
}
|
||||
return tk.token().TSubType
|
||||
}
|
||||
|
||||
// ExcelParser provides function to parse an Excel formula into a stream of
|
||||
// tokens.
|
||||
func ExcelParser() Parser {
|
||||
return Parser{}
|
||||
}
|
||||
|
||||
// getTokens return a token stream (list).
|
||||
func (ps *Parser) getTokens() Tokens {
|
||||
ps.Formula = strings.TrimSpace(ps.Formula)
|
||||
f := []rune(ps.Formula)
|
||||
if len(f) > 0 {
|
||||
if string(f[0]) != "=" {
|
||||
ps.Formula = "=" + ps.Formula
|
||||
}
|
||||
}
|
||||
|
||||
// state-dependent character evaluation (order is important)
|
||||
for !ps.EOF() {
|
||||
|
||||
// double-quoted strings
|
||||
// embeds are doubled
|
||||
// end marks token
|
||||
if ps.InString {
|
||||
if ps.currentChar() == QuoteDouble {
|
||||
if ps.nextChar() == QuoteDouble {
|
||||
ps.Token += QuoteDouble
|
||||
ps.Offset++
|
||||
} else {
|
||||
ps.InString = false
|
||||
ps.Tokens.add(ps.Token, TokenTypeOperand, TokenSubTypeText)
|
||||
ps.Token = ""
|
||||
}
|
||||
} else {
|
||||
ps.Token += ps.currentChar()
|
||||
}
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
|
||||
// single-quoted strings (links)
|
||||
// embeds are double
|
||||
// end does not mark a token
|
||||
if ps.InPath {
|
||||
if ps.currentChar() == QuoteSingle {
|
||||
if ps.nextChar() == QuoteSingle {
|
||||
ps.Token += QuoteSingle
|
||||
ps.Offset++
|
||||
} else {
|
||||
ps.InPath = false
|
||||
}
|
||||
} else {
|
||||
ps.Token += ps.currentChar()
|
||||
}
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
|
||||
// bracketed strings (range offset or linked workbook name)
|
||||
// no embeds (changed to "()" by Excel)
|
||||
// end does not mark a token
|
||||
if ps.InRange {
|
||||
if ps.currentChar() == BracketClose {
|
||||
ps.InRange = false
|
||||
}
|
||||
ps.Token += ps.currentChar()
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
|
||||
// error values
|
||||
// end marks a token, determined from absolute list of values
|
||||
if ps.InError {
|
||||
ps.Token += ps.currentChar()
|
||||
ps.Offset++
|
||||
if inStrSlice([]string{",#NULL!,", ",#DIV/0!,", ",#VALUE!,", ",#REF!,", ",#NAME?,", ",#NUM!,", ",#N/A,"}, Comma+ps.Token+Comma) != -1 {
|
||||
ps.InError = false
|
||||
ps.Tokens.add(ps.Token, TokenTypeOperand, TokenSubTypeError)
|
||||
ps.Token = ""
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// scientific notation check
|
||||
if strings.ContainsAny(ps.currentChar(), OperatorsSN) && len(ps.Token) > 1 {
|
||||
r, _ := regexp.Compile(`^[1-9]{1}(\.[0-9]+)?E{1}$`)
|
||||
if r.MatchString(ps.Token) {
|
||||
ps.Token += ps.currentChar()
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// independent character evaluation (order not important)
|
||||
// establish state-dependent character evaluations
|
||||
if ps.currentChar() == QuoteDouble {
|
||||
if len(ps.Token) > 0 {
|
||||
// not expected
|
||||
ps.Tokens.add(ps.Token, TokenTypeUnknown, "")
|
||||
ps.Token = ""
|
||||
}
|
||||
ps.InString = true
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
|
||||
if ps.currentChar() == QuoteSingle {
|
||||
if len(ps.Token) > 0 {
|
||||
// not expected
|
||||
ps.Tokens.add(ps.Token, TokenTypeUnknown, "")
|
||||
ps.Token = ""
|
||||
}
|
||||
ps.InPath = true
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
|
||||
if ps.currentChar() == BracketOpen {
|
||||
ps.InRange = true
|
||||
ps.Token += ps.currentChar()
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
|
||||
if ps.currentChar() == ErrorStart {
|
||||
if len(ps.Token) > 0 {
|
||||
// not expected
|
||||
ps.Tokens.add(ps.Token, TokenTypeUnknown, "")
|
||||
ps.Token = ""
|
||||
}
|
||||
ps.InError = true
|
||||
ps.Token += ps.currentChar()
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
|
||||
// mark start and end of arrays and array rows
|
||||
if ps.currentChar() == BraceOpen {
|
||||
if len(ps.Token) > 0 {
|
||||
// not expected
|
||||
ps.Tokens.add(ps.Token, TokenTypeUnknown, "")
|
||||
ps.Token = ""
|
||||
}
|
||||
ps.TokenStack.push(ps.Tokens.add("ARRAY", TokenTypeFunction, TokenSubTypeStart))
|
||||
ps.TokenStack.push(ps.Tokens.add("ARRAYROW", TokenTypeFunction, TokenSubTypeStart))
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
|
||||
if ps.currentChar() == Semicolon {
|
||||
if len(ps.Token) > 0 {
|
||||
ps.Tokens.add(ps.Token, TokenTypeOperand, "")
|
||||
ps.Token = ""
|
||||
}
|
||||
ps.Tokens.addRef(ps.TokenStack.pop())
|
||||
ps.Tokens.add(Comma, TokenTypeArgument, "")
|
||||
ps.TokenStack.push(ps.Tokens.add("ARRAYROW", TokenTypeFunction, TokenSubTypeStart))
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
|
||||
if ps.currentChar() == BraceClose {
|
||||
if len(ps.Token) > 0 {
|
||||
ps.Tokens.add(ps.Token, TokenTypeOperand, "")
|
||||
ps.Token = ""
|
||||
}
|
||||
ps.Tokens.addRef(ps.TokenStack.pop())
|
||||
ps.Tokens.addRef(ps.TokenStack.pop())
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
|
||||
// trim white-space
|
||||
if ps.currentChar() == Whitespace {
|
||||
if len(ps.Token) > 0 {
|
||||
ps.Tokens.add(ps.Token, TokenTypeOperand, "")
|
||||
ps.Token = ""
|
||||
}
|
||||
ps.Tokens.add("", TokenTypeWhitespace, "")
|
||||
ps.Offset++
|
||||
for (ps.currentChar() == Whitespace) && (!ps.EOF()) {
|
||||
ps.Offset++
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// multi-character comparators
|
||||
if inStrSlice([]string{",>=,", ",<=,", ",<>,"}, Comma+ps.doubleChar()+Comma) != -1 {
|
||||
if len(ps.Token) > 0 {
|
||||
ps.Tokens.add(ps.Token, TokenTypeOperand, "")
|
||||
ps.Token = ""
|
||||
}
|
||||
ps.Tokens.add(ps.doubleChar(), TokenTypeOperatorInfix, TokenSubTypeLogical)
|
||||
ps.Offset += 2
|
||||
continue
|
||||
}
|
||||
|
||||
// standard infix operators
|
||||
if strings.ContainsAny(OperatorsInfix, ps.currentChar()) {
|
||||
if len(ps.Token) > 0 {
|
||||
ps.Tokens.add(ps.Token, TokenTypeOperand, "")
|
||||
ps.Token = ""
|
||||
}
|
||||
ps.Tokens.add(ps.currentChar(), TokenTypeOperatorInfix, "")
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
|
||||
// standard postfix operators
|
||||
if ps.currentChar() == OperatorsPostfix {
|
||||
if len(ps.Token) > 0 {
|
||||
ps.Tokens.add(ps.Token, TokenTypeOperand, "")
|
||||
ps.Token = ""
|
||||
}
|
||||
ps.Tokens.add(ps.currentChar(), TokenTypeOperatorPostfix, "")
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
|
||||
// start subexpression or function
|
||||
if ps.currentChar() == ParenOpen {
|
||||
if len(ps.Token) > 0 {
|
||||
ps.TokenStack.push(ps.Tokens.add(ps.Token, TokenTypeFunction, TokenSubTypeStart))
|
||||
ps.Token = ""
|
||||
} else {
|
||||
ps.TokenStack.push(ps.Tokens.add("", TokenTypeSubexpression, TokenSubTypeStart))
|
||||
}
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
|
||||
// function, subexpression, array parameters
|
||||
if ps.currentChar() == Comma {
|
||||
if len(ps.Token) > 0 {
|
||||
ps.Tokens.add(ps.Token, TokenTypeOperand, "")
|
||||
ps.Token = ""
|
||||
}
|
||||
if ps.TokenStack.tp() != TokenTypeFunction {
|
||||
ps.Tokens.add(ps.currentChar(), TokenTypeOperatorInfix, TokenSubTypeUnion)
|
||||
} else {
|
||||
ps.Tokens.add(ps.currentChar(), TokenTypeArgument, "")
|
||||
}
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
|
||||
// stop subexpression
|
||||
if ps.currentChar() == ParenClose {
|
||||
if len(ps.Token) > 0 {
|
||||
ps.Tokens.add(ps.Token, TokenTypeOperand, "")
|
||||
ps.Token = ""
|
||||
}
|
||||
ps.Tokens.addRef(ps.TokenStack.pop())
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
|
||||
// token accumulation
|
||||
ps.Token += ps.currentChar()
|
||||
ps.Offset++
|
||||
}
|
||||
|
||||
// dump remaining accumulation
|
||||
if len(ps.Token) > 0 {
|
||||
ps.Tokens.add(ps.Token, TokenTypeOperand, "")
|
||||
}
|
||||
|
||||
// move all tokens to a new collection, excluding all unnecessary white-space tokens
|
||||
tokens2 := fTokens()
|
||||
|
||||
for ps.Tokens.moveNext() {
|
||||
token := ps.Tokens.current()
|
||||
|
||||
if token.TType == TokenTypeWhitespace {
|
||||
if ps.Tokens.BOF() || ps.Tokens.EOF() {
|
||||
} else if !(((ps.Tokens.previous().TType == TokenTypeFunction) && (ps.Tokens.previous().TSubType == TokenSubTypeStop)) || ((ps.Tokens.previous().TType == TokenTypeSubexpression) && (ps.Tokens.previous().TSubType == TokenSubTypeStop)) || (ps.Tokens.previous().TType == TokenTypeOperand)) {
|
||||
} else if !(((ps.Tokens.next().TType == TokenTypeFunction) && (ps.Tokens.next().TSubType == TokenSubTypeStart)) || ((ps.Tokens.next().TType == TokenTypeSubexpression) && (ps.Tokens.next().TSubType == TokenSubTypeStart)) || (ps.Tokens.next().TType == TokenTypeOperand)) {
|
||||
} else {
|
||||
tokens2.add(token.TValue, TokenTypeOperatorInfix, TokenSubTypeIntersection)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
tokens2.addRef(Token{
|
||||
TValue: token.TValue,
|
||||
TType: token.TType,
|
||||
TSubType: token.TSubType,
|
||||
})
|
||||
}
|
||||
|
||||
// switch infix "-" operator to prefix when appropriate, switch infix "+"
|
||||
// operator to noop when appropriate, identify operand and infix-operator
|
||||
// subtypes, pull "@" from in front of function names
|
||||
for tokens2.moveNext() {
|
||||
token := tokens2.current()
|
||||
if (token.TType == TokenTypeOperatorInfix) && (token.TValue == "-") {
|
||||
if tokens2.BOF() {
|
||||
token.TType = TokenTypeOperatorPrefix
|
||||
} else if ((tokens2.previous().TType == TokenTypeFunction) && (tokens2.previous().TSubType == TokenSubTypeStop)) || ((tokens2.previous().TType == TokenTypeSubexpression) && (tokens2.previous().TSubType == TokenSubTypeStop)) || (tokens2.previous().TType == TokenTypeOperatorPostfix) || (tokens2.previous().TType == TokenTypeOperand) {
|
||||
token.TSubType = TokenSubTypeMath
|
||||
} else {
|
||||
token.TType = TokenTypeOperatorPrefix
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if (token.TType == TokenTypeOperatorInfix) && (token.TValue == "+") {
|
||||
if tokens2.BOF() {
|
||||
token.TType = TokenTypeNoop
|
||||
} else if (tokens2.previous().TType == TokenTypeFunction) && (tokens2.previous().TSubType == TokenSubTypeStop) || ((tokens2.previous().TType == TokenTypeSubexpression) && (tokens2.previous().TSubType == TokenSubTypeStop) || (tokens2.previous().TType == TokenTypeOperatorPostfix) || (tokens2.previous().TType == TokenTypeOperand)) {
|
||||
token.TSubType = TokenSubTypeMath
|
||||
} else {
|
||||
token.TType = TokenTypeNoop
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if (token.TType == TokenTypeOperatorInfix) && (len(token.TSubType) == 0) {
|
||||
if strings.ContainsAny(token.TValue[0:1], "<>=") {
|
||||
token.TSubType = TokenSubTypeLogical
|
||||
} else if token.TValue == "&" {
|
||||
token.TSubType = TokenSubTypeConcatenation
|
||||
} else {
|
||||
token.TSubType = TokenSubTypeMath
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if (token.TType == TokenTypeOperand) && (len(token.TSubType) == 0) {
|
||||
if _, err := strconv.ParseFloat(token.TValue, 64); err != nil {
|
||||
if (token.TValue == "TRUE") || (token.TValue == "FALSE") {
|
||||
token.TSubType = TokenSubTypeLogical
|
||||
} else {
|
||||
token.TSubType = TokenSubTypeRange
|
||||
}
|
||||
} else {
|
||||
token.TSubType = TokenSubTypeNumber
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if token.TType == TokenTypeFunction {
|
||||
if (len(token.TValue) > 0) && token.TValue[0:1] == "@" {
|
||||
token.TValue = token.TValue[1:]
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
tokens2.reset()
|
||||
|
||||
// move all tokens to a new collection, excluding all no-ops
|
||||
tokens := fTokens()
|
||||
for tokens2.moveNext() {
|
||||
if tokens2.current().TType != TokenTypeNoop {
|
||||
tokens.addRef(Token{
|
||||
TValue: tokens2.current().TValue,
|
||||
TType: tokens2.current().TType,
|
||||
TSubType: tokens2.current().TSubType,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
tokens.reset()
|
||||
return tokens
|
||||
}
|
||||
|
||||
// doubleChar provides function to get two characters after the current
|
||||
// position.
|
||||
func (ps *Parser) doubleChar() string {
|
||||
if len([]rune(ps.Formula)) >= ps.Offset+2 {
|
||||
return string([]rune(ps.Formula)[ps.Offset : ps.Offset+2])
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// currentChar provides function to get the character of the current position.
|
||||
func (ps *Parser) currentChar() string {
|
||||
return string([]rune(ps.Formula)[ps.Offset])
|
||||
}
|
||||
|
||||
// nextChar provides function to get the next character of the current position.
|
||||
func (ps *Parser) nextChar() string {
|
||||
if len([]rune(ps.Formula)) >= ps.Offset+2 {
|
||||
return string([]rune(ps.Formula)[ps.Offset+1 : ps.Offset+2])
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// EOF provides function to check whether end of tokens stack.
|
||||
func (ps *Parser) EOF() bool {
|
||||
return ps.Offset >= len([]rune(ps.Formula))
|
||||
}
|
||||
|
||||
// Parse provides function to parse formula as a token stream (list).
|
||||
func (ps *Parser) Parse(formula string) []Token {
|
||||
ps.Formula = formula
|
||||
ps.Tokens = ps.getTokens()
|
||||
return ps.Tokens.Items
|
||||
}
|
||||
|
||||
// PrettyPrint provides function to pretty the parsed result with the indented
|
||||
// format.
|
||||
func (ps *Parser) PrettyPrint() string {
|
||||
indent := 0
|
||||
output := ""
|
||||
for _, t := range ps.Tokens.Items {
|
||||
if t.TSubType == TokenSubTypeStop {
|
||||
indent--
|
||||
}
|
||||
for i := 0; i < indent; i++ {
|
||||
output += "\t"
|
||||
}
|
||||
output += t.TValue + " <" + t.TType + "> <" + t.TSubType + ">" + "\n"
|
||||
if t.TSubType == TokenSubTypeStart {
|
||||
indent++
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
// Render provides function to get formatted formula after parsed.
|
||||
func (ps *Parser) Render() string {
|
||||
output := ""
|
||||
for _, t := range ps.Tokens.Items {
|
||||
if t.TType == TokenTypeFunction && t.TSubType == TokenSubTypeStart {
|
||||
output += t.TValue + ParenOpen
|
||||
} else if t.TType == TokenTypeFunction && t.TSubType == TokenSubTypeStop {
|
||||
output += ParenClose
|
||||
} else if t.TType == TokenTypeSubexpression && t.TSubType == TokenSubTypeStart {
|
||||
output += ParenOpen
|
||||
} else if t.TType == TokenTypeSubexpression && t.TSubType == TokenSubTypeStop {
|
||||
output += ParenClose
|
||||
} else if t.TType == TokenTypeOperand && t.TSubType == TokenSubTypeText {
|
||||
output += QuoteDouble + t.TValue + QuoteDouble
|
||||
} else if t.TType == TokenTypeOperatorInfix && t.TSubType == TokenSubTypeIntersection {
|
||||
output += Whitespace
|
||||
} else {
|
||||
output += t.TValue
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
// inStrSlice provides a method to check if an element is present in an array,
|
||||
// and return the index of its location, otherwise return -1.
|
||||
func inStrSlice(a []string, x string) int {
|
||||
for idx, n := range a {
|
||||
if x == n {
|
||||
return idx
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
.DS_Store
|
||||
.idea
|
||||
*.json
|
||||
*.out
|
||||
*.test
|
||||
~$*.xlsx
|
||||
test/*.png
|
||||
test/BadWorkbook.SaveAsEmptyStruct.xlsx
|
||||
test/Encryption*.xlsx
|
||||
test/excelize-*
|
||||
test/Test*.xlam
|
||||
test/Test*.xlsm
|
||||
test/Test*.xlsx
|
||||
test/Test*.xltm
|
||||
test/Test*.xltx
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others’ private information, such as a physical or email address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [xuri.me](https://xuri.me). All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
Community Impact: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
|
||||
|
||||
Consequence: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
Community Impact: A violation through a single incident or series of actions.
|
||||
|
||||
Consequence: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
Community Impact: A serious violation of community standards, including sustained inappropriate behavior.
|
||||
|
||||
Consequence: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
Community Impact: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
Consequence: A permanent ban from any sort of public interaction within the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the Contributor Covenant, version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html).
|
||||
|
||||
Community Impact Guidelines were inspired by Mozilla’s code of conduct enforcement ladder.
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). Translations are available at [https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations).
|
||||
+463
@@ -0,0 +1,463 @@
|
||||
# Contributing to excelize
|
||||
|
||||
Want to hack on excelize? Awesome! This page contains information about reporting issues as well as some tips and
|
||||
guidelines useful to experienced open source contributors. Finally, make sure
|
||||
you read our [community guidelines](#community-guidelines) before you
|
||||
start participating.
|
||||
|
||||
## Topics
|
||||
|
||||
* [Reporting Security Issues](#reporting-security-issues)
|
||||
* [Design and Cleanup Proposals](#design-and-cleanup-proposals)
|
||||
* [Reporting Issues](#reporting-other-issues)
|
||||
* [Quick Contribution Tips and Guidelines](#quick-contribution-tips-and-guidelines)
|
||||
* [Community Guidelines](#community-guidelines)
|
||||
|
||||
## Reporting security issues
|
||||
|
||||
The excelize maintainers take security seriously. If you discover a security
|
||||
issue, please bring it to their attention right away!
|
||||
|
||||
Please **DO NOT** file a public issue, instead send your report privately to
|
||||
[xuri.me](https://xuri.me).
|
||||
|
||||
Security reports are greatly appreciated and we will publicly thank you for them.
|
||||
We currently do not offer a paid security bounty program, but are not
|
||||
ruling it out in the future.
|
||||
|
||||
## Reporting other issues
|
||||
|
||||
A great way to contribute to the project is to send a detailed report when you
|
||||
encounter an issue. We always appreciate a well-written, thorough bug report,
|
||||
and will thank you for it!
|
||||
|
||||
Check that [our issue database](https://github.com/xuri/excelize/issues)
|
||||
doesn't already include that problem or suggestion before submitting an issue.
|
||||
If you find a match, you can use the "subscribe" button to get notified on
|
||||
updates. Do *not* leave random "+1" or "I have this too" comments, as they
|
||||
only clutter the discussion, and don't help resolving it. However, if you
|
||||
have ways to reproduce the issue or have additional information that may help
|
||||
resolving the issue, please leave a comment.
|
||||
|
||||
When reporting issues, always include the output of `go env`.
|
||||
|
||||
Also include the steps required to reproduce the problem if possible and
|
||||
applicable. This information will help us review and fix your issue faster.
|
||||
When sending lengthy log-files, consider posting them as a gist [https://gist.github.com](https://gist.github.com).
|
||||
Don't forget to remove sensitive data from your logfiles before posting (you can
|
||||
replace those parts with "REDACTED").
|
||||
|
||||
## Quick contribution tips and guidelines
|
||||
|
||||
This section gives the experienced contributor some tips and guidelines.
|
||||
|
||||
### Pull requests are always welcome
|
||||
|
||||
Not sure if that typo is worth a pull request? Found a bug and know how to fix
|
||||
it? Do it! We will appreciate it. Any significant improvement should be
|
||||
documented as [a GitHub issue](https://github.com/xuri/excelize/issues) before
|
||||
anybody starts working on it.
|
||||
|
||||
We are always thrilled to receive pull requests. We do our best to process them
|
||||
quickly. If your pull request is not accepted on the first try,
|
||||
don't get discouraged!
|
||||
|
||||
### Design and cleanup proposals
|
||||
|
||||
You can propose new designs for existing excelize features. You can also design
|
||||
entirely new features. We really appreciate contributors who want to refactor or
|
||||
otherwise cleanup our project.
|
||||
|
||||
We try hard to keep excelize lean and focused. Excelize can't do everything for
|
||||
everybody. This means that we might decide against incorporating a new feature.
|
||||
However, there might be a way to implement that feature *on top of* excelize.
|
||||
|
||||
### Conventions
|
||||
|
||||
Fork the repository and make changes on your fork in a feature branch:
|
||||
|
||||
* If it's a bug fix branch, name it XXXX-something where XXXX is the number of
|
||||
the issue.
|
||||
* If it's a feature branch, create an enhancement issue to announce
|
||||
your intentions, and name it XXXX-something where XXXX is the number of the
|
||||
issue.
|
||||
|
||||
Submit unit tests for your changes. Go has a great test framework built in; use
|
||||
it! Take a look at existing tests for inspiration. Run the full test on your branch before
|
||||
submitting a pull request.
|
||||
|
||||
Update the documentation when creating or modifying features. Test your
|
||||
documentation changes for clarity, concision, and correctness, as well as a
|
||||
clean documentation build.
|
||||
|
||||
Write clean code. Universally formatted code promotes ease of writing, reading,
|
||||
and maintenance. Always run `gofmt -s -w file.go` on each changed file before
|
||||
committing your changes. Most editors have plug-ins that do this automatically.
|
||||
|
||||
Pull request descriptions should be as clear as possible and include a reference
|
||||
to all the issues that they address.
|
||||
|
||||
### Successful Changes
|
||||
|
||||
Before contributing large or high impact changes, make the effort to coordinate
|
||||
with the maintainers of the project before submitting a pull request. This
|
||||
prevents you from doing extra work that may or may not be merged.
|
||||
|
||||
Large PRs that are just submitted without any prior communication is unlikely
|
||||
to be successful.
|
||||
|
||||
While pull requests are the methodology for submitting changes to code, changes
|
||||
are much more likely to be accepted if they are accompanied by additional
|
||||
engineering work. While we don't define this explicitly, most of these goals
|
||||
are accomplished through the communication of the design goals and subsequent
|
||||
solutions. Oftentimes, it helps to first state the problem before presenting
|
||||
solutions.
|
||||
|
||||
Typically, the best methods of accomplishing this are to submit an issue,
|
||||
stating the problem. This issue can include a problem statement and a
|
||||
checklist with requirements. If solutions are proposed, alternatives should be
|
||||
listed and eliminated. Even if the criteria for elimination of a solution is
|
||||
frivolous, say so.
|
||||
|
||||
Larger changes typically work best with design documents. These are focused on
|
||||
providing context to the design at the time the feature was conceived and can
|
||||
inform future documentation contributions.
|
||||
|
||||
### Commit Messages
|
||||
|
||||
Commit messages must start with a capitalized and short summary
|
||||
written in the imperative, followed by an optional, more detailed explanatory
|
||||
text which is separated from the summary by an empty line.
|
||||
|
||||
Commit messages should follow best practices, including explaining the context
|
||||
of the problem and how it was solved, including in caveats or follow-up changes
|
||||
required. They should tell the story of the change and provide readers
|
||||
understanding of what led to it.
|
||||
|
||||
In practice, the best approach to maintaining a nice commit message is to
|
||||
leverage a `git add -p` and `git commit --amend` to formulate a solid
|
||||
changeset. This allows one to piece together a change, as information becomes
|
||||
available.
|
||||
|
||||
If you squash a series of commits, don't just submit that. Re-write the commit
|
||||
message, as if the series of commits was a single stroke of brilliance.
|
||||
|
||||
That said, there is no requirement to have a single commit for a PR, as long as
|
||||
each commit tells the story. For example, if there is a feature that requires a
|
||||
package, it might make sense to have the package in a separate commit then have
|
||||
a subsequent commit that uses it.
|
||||
|
||||
Remember, you're telling part of the story with the commit message. Don't make
|
||||
your chapter weird.
|
||||
|
||||
### Review
|
||||
|
||||
Code review comments may be added to your pull request. Discuss, then make the
|
||||
suggested modifications and push additional commits to your feature branch. Post
|
||||
a comment after pushing. New commits show up in the pull request automatically,
|
||||
but the reviewers are notified only when you comment.
|
||||
|
||||
Pull requests must be cleanly rebased on top of master without multiple branches
|
||||
mixed into the PR.
|
||||
|
||||
**Git tip**: If your PR no longer merges cleanly, use `rebase master` in your
|
||||
feature branch to update your pull request rather than `merge master`.
|
||||
|
||||
Before you make a pull request, squash your commits into logical units of work
|
||||
using `git rebase -i` and `git push -f`. A logical unit of work is a consistent
|
||||
set of patches that should be reviewed together: for example, upgrading the
|
||||
version of a vendored dependency and taking advantage of its now available new
|
||||
feature constitute two separate units of work. Implementing a new function and
|
||||
calling it in another file constitute a single logical unit of work. The very
|
||||
high majority of submissions should have a single commit, so if in doubt: squash
|
||||
down to one.
|
||||
|
||||
After every commit, make sure the test passes. Include documentation
|
||||
changes in the same pull request so that a revert would remove all traces of
|
||||
the feature or fix.
|
||||
|
||||
Include an issue reference like `Closes #XXXX` or `Fixes #XXXX` in commits that
|
||||
close an issue. Including references automatically closes the issue on a merge.
|
||||
|
||||
Please see the [Coding Style](#coding-style) for further guidelines.
|
||||
|
||||
### Merge approval
|
||||
|
||||
The excelize maintainers use LGTM (Looks Good To Me) in comments on the code review to
|
||||
indicate acceptance.
|
||||
|
||||
### Sign your work
|
||||
|
||||
The sign-off is a simple line at the end of the explanation for the patch. Your
|
||||
signature certifies that you wrote the patch or otherwise have the right to pass
|
||||
it on as an open-source patch. The rules are pretty simple: if you can certify
|
||||
the below (from [developercertificate.org](https://developercertificate.org)):
|
||||
|
||||
```text
|
||||
Developer Certificate of Origin
|
||||
Version 1.1
|
||||
|
||||
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
|
||||
|
||||
Developer's Certificate of Origin 1.1
|
||||
|
||||
By making a contribution to this project, I certify that:
|
||||
|
||||
(a) The contribution was created in whole or in part by me and I
|
||||
have the right to submit it under the open source license
|
||||
indicated in the file; or
|
||||
|
||||
(b) The contribution is based upon previous work that, to the best
|
||||
of my knowledge, is covered under an appropriate open source
|
||||
license and I have the right under that license to submit that
|
||||
work with modifications, whether created in whole or in part
|
||||
by me, under the same open source license (unless I am
|
||||
permitted to submit under a different license), as indicated
|
||||
in the file; or
|
||||
|
||||
(c) The contribution was provided directly to me by some other
|
||||
person who certified (a), (b) or (c) and I have not modified
|
||||
it.
|
||||
|
||||
(d) I understand and agree that this project and the contribution
|
||||
are public and that a record of the contribution (including all
|
||||
personal information I submit with it, including my sign-off) is
|
||||
maintained indefinitely and may be redistributed consistent with
|
||||
this project or the open source license(s) involved.
|
||||
```
|
||||
|
||||
Then you just add a line to every git commit message:
|
||||
|
||||
```text
|
||||
Signed-off-by: Ri Xu https://xuri.me
|
||||
```
|
||||
|
||||
Use your real name (sorry, no pseudonyms or anonymous contributions.)
|
||||
|
||||
If you set your `user.name` and `user.email` git configs, you can sign your
|
||||
commit automatically with `git commit -s`.
|
||||
|
||||
### How can I become a maintainer
|
||||
|
||||
First, all maintainers have 3 things
|
||||
|
||||
* They share responsibility in the project's success.
|
||||
* They have made a long-term, recurring time investment to improve the project.
|
||||
* They spend that time doing whatever needs to be done, not necessarily what
|
||||
is the most interesting or fun.
|
||||
|
||||
Maintainers are often under-appreciated, because their work is harder to appreciate.
|
||||
It's easy to appreciate a really cool and technically advanced feature. It's harder
|
||||
to appreciate the absence of bugs, the slow but steady improvement in stability,
|
||||
or the reliability of a release process. But those things distinguish a good
|
||||
project from a great one.
|
||||
|
||||
Don't forget: being a maintainer is a time investment. Make sure you
|
||||
will have time to make yourself available. You don't have to be a
|
||||
maintainer to make a difference on the project!
|
||||
|
||||
If you want to become a maintainer, contact [xuri.me](https://xuri.me) and given an introduction of you.
|
||||
|
||||
## Community guidelines
|
||||
|
||||
We want to keep the community awesome, growing and collaborative. We need
|
||||
your help to keep it that way. To help with this we've come up with some general
|
||||
guidelines for the community as a whole:
|
||||
|
||||
* Be nice: Be courteous, respectful and polite to fellow community members:
|
||||
no regional, racial, gender, or other abuse will be tolerated. We like
|
||||
nice people way better than mean ones!
|
||||
|
||||
* Encourage diversity and participation: Make everyone in our community feel
|
||||
welcome, regardless of their background and the extent of their
|
||||
contributions, and do everything possible to encourage participation in
|
||||
our community.
|
||||
|
||||
* Keep it legal: Basically, don't get us in trouble. Share only content that
|
||||
you own, do not share private or sensitive information, and don't break
|
||||
the law.
|
||||
|
||||
* Stay on topic: Make sure that you are posting to the correct channel and
|
||||
avoid off-topic discussions. Remember when you update an issue or respond
|
||||
to an email you are potentially sending to a large number of people. Please
|
||||
consider this before you update. Also remember that nobody likes spam.
|
||||
|
||||
* Don't send email to the maintainers: There's no need to send email to the
|
||||
maintainers to ask them to investigate an issue or to take a look at a
|
||||
pull request. Instead of sending an email, GitHub mentions should be
|
||||
used to ping maintainers to review a pull request, a proposal or an
|
||||
issue.
|
||||
|
||||
### Guideline violations — 3 strikes method
|
||||
|
||||
The point of this section is not to find opportunities to punish people, but we
|
||||
do need a fair way to deal with people who are making our community suck.
|
||||
|
||||
1. First occurrence: We'll give you a friendly, but public reminder that the
|
||||
behavior is inappropriate according to our guidelines.
|
||||
|
||||
2. Second occurrence: We will send you a private message with a warning that
|
||||
any additional violations will result in removal from the community.
|
||||
|
||||
3. Third occurrence: Depending on the violation, we may need to delete or ban
|
||||
your account.
|
||||
|
||||
**Notes:**
|
||||
|
||||
* Obvious spammers are banned on first occurrence. If we don't do this, we'll
|
||||
have spam all over the place.
|
||||
|
||||
* Violations are forgiven after 6 months of good behavior, and we won't hold a
|
||||
grudge.
|
||||
|
||||
* People who commit minor infractions will get some education, rather than
|
||||
hammering them in the 3 strikes process.
|
||||
|
||||
* The rules apply equally to everyone in the community, no matter how much
|
||||
you've contributed.
|
||||
|
||||
* Extreme violations of a threatening, abusive, destructive or illegal nature
|
||||
will be addressed immediately and are not subject to 3 strikes or forgiveness.
|
||||
|
||||
* Contact [xuri.me](https://xuri.me) to report abuse or appeal violations. In the case of
|
||||
appeals, we know that mistakes happen, and we'll work with you to come up with a
|
||||
fair solution if there has been a misunderstanding.
|
||||
|
||||
## Coding Style
|
||||
|
||||
Unless explicitly stated, we follow all coding guidelines from the Go
|
||||
community. While some of these standards may seem arbitrary, they somehow seem
|
||||
to result in a solid, consistent codebase.
|
||||
|
||||
It is possible that the code base does not currently comply with these
|
||||
guidelines. We are not looking for a massive PR that fixes this, since that
|
||||
goes against the spirit of the guidelines. All new contributions should make a
|
||||
best effort to clean up and make the code base better than they left it.
|
||||
Obviously, apply your best judgement. Remember, the goal here is to make the
|
||||
code base easier for humans to navigate and understand. Always keep that in
|
||||
mind when nudging others to comply.
|
||||
|
||||
The rules:
|
||||
|
||||
1. All code should be formatted with `gofmt -s`.
|
||||
2. All code should pass the default levels of
|
||||
[`go vet`](https://pkg.go.dev/cmd/vet).
|
||||
3. All code should follow the guidelines covered in [Effective
|
||||
Go](https://go.dev/doc/effective_go) and [Go Code Review
|
||||
Comments](https://github.com/golang/go/wiki/CodeReviewComments).
|
||||
4. Comment the code. Tell us the why, the history and the context.
|
||||
5. Document _all_ declarations and methods, even private ones. Declare
|
||||
expectations, caveats and anything else that may be important. If a type
|
||||
gets exported, having the comments already there will ensure it's ready.
|
||||
6. Variable name length should be proportional to its context and no longer.
|
||||
`noCommaALongVariableNameLikeThisIsNotMoreClearWhenASimpleCommentWouldDo`.
|
||||
In practice, short methods will have short variable names and globals will
|
||||
have longer names.
|
||||
7. No underscores in package names. If you need a compound name, step back,
|
||||
and re-examine why you need a compound name. If you still think you need a
|
||||
compound name, lose the underscore.
|
||||
8. No utils or helpers packages. If a function is not general enough to
|
||||
warrant its own package, it has not been written generally enough to be a
|
||||
part of a util package. Just leave it unexported and well-documented.
|
||||
9. All tests should run with `go test` and outside tooling should not be
|
||||
required. No, we don't need another unit testing framework. Assertion
|
||||
packages are acceptable if they provide _real_ incremental value.
|
||||
10. Even though we call these "rules" above, they are actually just
|
||||
guidelines. Since you've read all the rules, you now know that.
|
||||
|
||||
If you are having trouble getting into the mood of idiomatic Go, we recommend
|
||||
reading through [Effective Go](https://go.dev/doc/effective_go). The
|
||||
[Go Blog](https://go.dev/blog/) is also a great resource. Drinking the
|
||||
kool-aid is a lot easier than going thirsty.
|
||||
|
||||
## Code Review Comments and Effective Go Guidelines
|
||||
|
||||
[CodeLingo](https://www.codelingo.io) automatically checks every pull request against the following guidelines from [Effective Go](https://go.dev/doc/effective_go) and [Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments).
|
||||
|
||||
### Package Comment
|
||||
|
||||
Every package should have a package comment, a block comment preceding the package clause.
|
||||
For multi-file packages, the package comment only needs to be present in one file, and any one will do.
|
||||
The package comment should introduce the package and provide information relevant to the package as a
|
||||
whole. It will appear first on the godoc page and should set up the detailed documentation that follows.
|
||||
|
||||
### Single Method Interface Name
|
||||
|
||||
By convention, one-method interfaces are named by the method name plus an -er suffix
|
||||
or similar modification to construct an agent noun: Reader, Writer, Formatter, CloseNotifier etc.
|
||||
|
||||
There are a number of such names and it's productive to honor them and the function names they capture.
|
||||
Read, Write, Close, Flush, String and so on have canonical signatures and meanings. To avoid confusion,
|
||||
don't give your method one of those names unless it has the same signature and meaning. Conversely,
|
||||
if your type implements a method with the same meaning as a method on a well-known type, give it the
|
||||
same name and signature; call your string-converter method String not ToString.
|
||||
|
||||
### Avoid Annotations in Comments
|
||||
|
||||
Comments do not need extra formatting such as banners of stars. The generated output
|
||||
may not even be presented in a fixed-width font, so don't depend on spacing for alignment—godoc,
|
||||
like gofmt, takes care of that. The comments are uninterpreted plain text, so HTML and other
|
||||
annotations such as _this_ will reproduce verbatim and should not be used. One adjustment godoc
|
||||
does do is to display indented text in a fixed-width font, suitable for program snippets.
|
||||
The package comment for the fmt package uses this to good effect.
|
||||
|
||||
### Comment First Word as Subject
|
||||
|
||||
Doc comments work best as complete sentences, which allow a wide variety of automated presentations.
|
||||
The first sentence should be a one-sentence summary that starts with the name being declared.
|
||||
|
||||
### Good Package Name
|
||||
|
||||
It's helpful if everyone using the package can use the same name
|
||||
to refer to its contents, which implies that the package name should
|
||||
be good: short, concise, and evocative. By convention, packages are
|
||||
given lower case, single-word names; there should be no need for
|
||||
underscores or mixedCaps. Err on the side of brevity, since everyone
|
||||
using your package will be typing that name. And don't worry about
|
||||
collisions a priori. The package name is only the default name for
|
||||
imports; it need not be unique across all source code, and in the
|
||||
rare case of a collision the importing package can choose a different
|
||||
name to use locally. In any case, confusion is rare because the file
|
||||
name in the import determines just which package is being used.
|
||||
|
||||
### Avoid Renaming Imports
|
||||
|
||||
Avoid renaming imports except to avoid a name collision; good package names
|
||||
should not require renaming. In the event of collision, prefer to rename the
|
||||
most local or project-specific import.
|
||||
|
||||
### Context as First Argument
|
||||
|
||||
Values of the context.Context type carry security credentials, tracing information,
|
||||
deadlines, and cancellation signals across API and process boundaries. Go programs
|
||||
pass Contexts explicitly along the entire function call chain from incoming RPCs
|
||||
and HTTP requests to outgoing requests.
|
||||
|
||||
Most functions that use a Context should accept it as their first parameter.
|
||||
|
||||
### Do Not Discard Errors
|
||||
|
||||
Do not discard errors using _ variables. If a function returns an error,
|
||||
check it to make sure the function succeeded. Handle the error, return it, or,
|
||||
in truly exceptional situations, panic.
|
||||
|
||||
### Go Error Format
|
||||
|
||||
Error strings should not be capitalized (unless beginning with proper nouns
|
||||
or acronyms) or end with punctuation, since they are usually printed following
|
||||
other context. That is, use fmt.Errorf("something bad") not fmt.Errorf("Something bad"),
|
||||
so that log.Printf("Reading %s: %v", filename, err) formats without a spurious
|
||||
capital letter mid-message. This does not apply to logging, which is implicitly
|
||||
line-oriented and not combined inside other messages.
|
||||
|
||||
### Use Crypto Rand
|
||||
|
||||
Do not use package math/rand to generate keys, even
|
||||
throwaway ones. Unseeded, the generator is completely predictable.
|
||||
Seeded with time.Nanoseconds(), there are just a few bits of entropy.
|
||||
Instead, use crypto/rand's Reader, and if you need text, print to
|
||||
hexadecimal or base64.
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2016-2023 The excelize Authors.
|
||||
Copyright (c) 2011-2017 Geoffrey J. Teale
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
# PR Details
|
||||
|
||||
<!--- Provide a general summary of your changes in the Title above -->
|
||||
|
||||
## Description
|
||||
|
||||
<!--- Describe your changes in detail -->
|
||||
|
||||
## Related Issue
|
||||
|
||||
<!--- This project only accepts pull requests related to open issues -->
|
||||
<!--- If suggesting a new feature or change, please discuss it in an issue first -->
|
||||
<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
|
||||
<!--- Please link to the issue here: -->
|
||||
|
||||
## Motivation and Context
|
||||
|
||||
<!--- Why is this change required? What problem does it solve? -->
|
||||
|
||||
## How Has This Been Tested
|
||||
|
||||
<!--- Please describe in detail how you tested your changes. -->
|
||||
<!--- Include details of your testing environment, and the tests you ran to -->
|
||||
<!--- see how your change affects other areas of the code, etc. -->
|
||||
|
||||
## Types of changes
|
||||
|
||||
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
|
||||
|
||||
- [ ] Docs change / refactoring / dependency upgrade
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
||||
|
||||
## Checklist
|
||||
|
||||
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
|
||||
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
|
||||
|
||||
- [ ] My code follows the code style of this project.
|
||||
- [ ] My change requires a change to the documentation.
|
||||
- [ ] I have updated the documentation accordingly.
|
||||
- [ ] I have read the **CONTRIBUTING** document.
|
||||
- [ ] I have added tests to cover my changes.
|
||||
- [ ] All new and existing tests passed.
|
||||
+246
@@ -0,0 +1,246 @@
|
||||
<p align="center"><img width="650" src="./excelize.svg" alt="Excelize logo"></p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/xuri/excelize/actions/workflows/go.yml"><img src="https://github.com/xuri/excelize/actions/workflows/go.yml/badge.svg" alt="Build Status"></a>
|
||||
<a href="https://codecov.io/gh/qax-os/excelize"><img src="https://codecov.io/gh/qax-os/excelize/branch/master/graph/badge.svg" alt="Code Coverage"></a>
|
||||
<a href="https://goreportcard.com/report/github.com/xuri/excelize/v2"><img src="https://goreportcard.com/badge/github.com/xuri/excelize/v2" alt="Go Report Card"></a>
|
||||
<a href="https://pkg.go.dev/github.com/xuri/excelize/v2"><img src="https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white" alt="go.dev"></a>
|
||||
<a href="https://opensource.org/licenses/BSD-3-Clause"><img src="https://img.shields.io/badge/license-bsd-orange.svg" alt="Licenses"></a>
|
||||
<a href="https://www.paypal.com/paypalme/xuri"><img src="https://img.shields.io/badge/Donate-PayPal-green.svg" alt="Donate"></a>
|
||||
</p>
|
||||
|
||||
# Excelize
|
||||
|
||||
## Introduction
|
||||
|
||||
Excelize is a library written in pure Go providing a set of functions that allow you to write to and read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports complex components by high compatibility, and provided streaming API for generating or reading data from a worksheet with huge amounts of data. This library needs Go version 1.16 or later. The full docs can be seen using go's built-in documentation tool, or online at [go.dev](https://pkg.go.dev/github.com/xuri/excelize/v2) and [docs reference](https://xuri.me/excelize/).
|
||||
|
||||
## Basic Usage
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
go get github.com/xuri/excelize
|
||||
```
|
||||
|
||||
- If your packages are managed using [Go Modules](https://go.dev/blog/using-go-modules), please install with following command.
|
||||
|
||||
```bash
|
||||
go get github.com/xuri/excelize/v2
|
||||
```
|
||||
|
||||
### Create spreadsheet
|
||||
|
||||
Here is a minimal example usage that will create spreadsheet file.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/xuri/excelize/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
f := excelize.NewFile()
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}()
|
||||
// Create a new sheet.
|
||||
index, err := f.NewSheet("Sheet2")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
// Set value of a cell.
|
||||
f.SetCellValue("Sheet2", "A2", "Hello world.")
|
||||
f.SetCellValue("Sheet1", "B2", 100)
|
||||
// Set active sheet of the workbook.
|
||||
f.SetActiveSheet(index)
|
||||
// Save spreadsheet by the given path.
|
||||
if err := f.SaveAs("Book1.xlsx"); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Reading spreadsheet
|
||||
|
||||
The following constitutes the bare to read a spreadsheet document.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/xuri/excelize/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
f, err := excelize.OpenFile("Book1.xlsx")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
// Close the spreadsheet.
|
||||
if err := f.Close(); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}()
|
||||
// Get value from cell by given worksheet name and cell reference.
|
||||
cell, err := f.GetCellValue("Sheet1", "B2")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(cell)
|
||||
// Get all the rows in the Sheet1.
|
||||
rows, err := f.GetRows("Sheet1")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
for _, row := range rows {
|
||||
for _, colCell := range row {
|
||||
fmt.Print(colCell, "\t")
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Add chart to spreadsheet file
|
||||
|
||||
With Excelize chart generation and management is as easy as a few lines of code. You can build charts based on data in your worksheet or generate charts without any data in your worksheet at all.
|
||||
|
||||
<p align="center"><img width="650" src="./test/images/chart.png" alt="Excelize"></p>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/xuri/excelize/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
f := excelize.NewFile()
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}()
|
||||
for idx, row := range [][]interface{}{
|
||||
{nil, "Apple", "Orange", "Pear"}, {"Small", 2, 3, 3},
|
||||
{"Normal", 5, 2, 4}, {"Large", 6, 7, 8},
|
||||
} {
|
||||
cell, err := excelize.CoordinatesToCellName(1, idx+1)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
f.SetSheetRow("Sheet1", cell, &row)
|
||||
}
|
||||
if err := f.AddChart("Sheet1", "E1", &excelize.Chart{
|
||||
Type: excelize.Col3DClustered,
|
||||
Series: []excelize.ChartSeries{
|
||||
{
|
||||
Name: "Sheet1!$A$2",
|
||||
Categories: "Sheet1!$B$1:$D$1",
|
||||
Values: "Sheet1!$B$2:$D$2",
|
||||
},
|
||||
{
|
||||
Name: "Sheet1!$A$3",
|
||||
Categories: "Sheet1!$B$1:$D$1",
|
||||
Values: "Sheet1!$B$3:$D$3",
|
||||
},
|
||||
{
|
||||
Name: "Sheet1!$A$4",
|
||||
Categories: "Sheet1!$B$1:$D$1",
|
||||
Values: "Sheet1!$B$4:$D$4",
|
||||
}},
|
||||
Title: excelize.ChartTitle{
|
||||
Name: "Fruit 3D Clustered Column Chart",
|
||||
},
|
||||
}); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
// Save spreadsheet by the given path.
|
||||
if err := f.SaveAs("Book1.xlsx"); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Add picture to spreadsheet file
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
_ "image/gif"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
|
||||
"github.com/xuri/excelize/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
f, err := excelize.OpenFile("Book1.xlsx")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
// Close the spreadsheet.
|
||||
if err := f.Close(); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}()
|
||||
// Insert a picture.
|
||||
if err := f.AddPicture("Sheet1", "A2", "image.png", nil); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
// Insert a picture to worksheet with scaling.
|
||||
if err := f.AddPicture("Sheet1", "D2", "image.jpg",
|
||||
&excelize.GraphicOptions{ScaleX: 0.5, ScaleY: 0.5}); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
// Insert a picture offset in the cell with printing support.
|
||||
enable, disable := true, false
|
||||
if err := f.AddPicture("Sheet1", "H2", "image.gif",
|
||||
&excelize.GraphicOptions{
|
||||
PrintObject: &enable,
|
||||
LockAspectRatio: false,
|
||||
OffsetX: 15,
|
||||
OffsetY: 10,
|
||||
Locked: &disable,
|
||||
}); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
// Save the spreadsheet with the origin path.
|
||||
if err = f.Save(); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Open a pull request to fix a bug, or open an issue to discuss a new feature or change. XML is compliant with [part 1 of the 5th edition of the ECMA-376 Standard for Office Open XML](https://www.ecma-international.org/publications-and-standards/standards/ecma-376/).
|
||||
|
||||
## Licenses
|
||||
|
||||
This program is under the terms of the BSD 3-Clause License. See [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause).
|
||||
|
||||
The Excel logo is a trademark of [Microsoft Corporation](https://aka.ms/trademarks-usage). This artwork is an adaptation.
|
||||
|
||||
gopher.{ai,svg,png} was created by [Takuya Ueda](https://twitter.com/tenntenn). Licensed under the [Creative Commons 3.0 Attributions license](http://creativecommons.org/licenses/by/3.0/).
|
||||
+246
@@ -0,0 +1,246 @@
|
||||
<p align="center"><img width="650" src="./excelize.svg" alt="Excelize logo"></p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/xuri/excelize/actions/workflows/go.yml"><img src="https://github.com/xuri/excelize/actions/workflows/go.yml/badge.svg" alt="Build Status"></a>
|
||||
<a href="https://codecov.io/gh/qax-os/excelize"><img src="https://codecov.io/gh/qax-os/excelize/branch/master/graph/badge.svg" alt="Code Coverage"></a>
|
||||
<a href="https://goreportcard.com/report/github.com/xuri/excelize/v2"><img src="https://goreportcard.com/badge/github.com/xuri/excelize/v2" alt="Go Report Card"></a>
|
||||
<a href="https://pkg.go.dev/github.com/xuri/excelize/v2"><img src="https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white" alt="go.dev"></a>
|
||||
<a href="https://opensource.org/licenses/BSD-3-Clause"><img src="https://img.shields.io/badge/license-bsd-orange.svg" alt="Licenses"></a>
|
||||
<a href="https://www.paypal.com/paypalme/xuri"><img src="https://img.shields.io/badge/Donate-PayPal-green.svg" alt="Donate"></a>
|
||||
</p>
|
||||
|
||||
# Excelize
|
||||
|
||||
## 简介
|
||||
|
||||
Excelize 是 Go 语言编写的用于操作 Office Excel 文档基础库,基于 ECMA-376,ISO/IEC 29500 国际标准。可以使用它来读取、写入由 Microsoft Excel™ 2007 及以上版本创建的电子表格文档。支持 XLAM / XLSM / XLSX / XLTM / XLTX 等多种文档格式,高度兼容带有样式、图片(表)、透视表、切片器等复杂组件的文档,并提供流式读写函数,用于处理包含大规模数据的工作簿。可应用于各类报表平台、云计算、边缘计算等系统。使用本类库要求使用的 Go 语言为 1.16 或更高版本,完整的使用文档请访问 [go.dev](https://pkg.go.dev/github.com/xuri/excelize/v2) 或查看 [参考文档](https://xuri.me/excelize/)。
|
||||
|
||||
## 快速上手
|
||||
|
||||
### 安装
|
||||
|
||||
```bash
|
||||
go get github.com/xuri/excelize
|
||||
```
|
||||
|
||||
- 如果您使用 [Go Modules](https://go.dev/blog/using-go-modules) 管理软件包,请使用下面的命令来安装最新版本。
|
||||
|
||||
```bash
|
||||
go get github.com/xuri/excelize/v2
|
||||
```
|
||||
|
||||
### 创建 Excel 文档
|
||||
|
||||
下面是一个创建 Excel 文档的简单例子:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/xuri/excelize/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
f := excelize.NewFile()
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}()
|
||||
// 创建一个工作表
|
||||
index, err := f.NewSheet("Sheet2")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
// 设置单元格的值
|
||||
f.SetCellValue("Sheet2", "A2", "Hello world.")
|
||||
f.SetCellValue("Sheet1", "B2", 100)
|
||||
// 设置工作簿的默认工作表
|
||||
f.SetActiveSheet(index)
|
||||
// 根据指定路径保存文件
|
||||
if err := f.SaveAs("Book1.xlsx"); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 读取 Excel 文档
|
||||
|
||||
下面是读取 Excel 文档的例子:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/xuri/excelize/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
f, err := excelize.OpenFile("Book1.xlsx")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
// 关闭工作簿
|
||||
if err := f.Close(); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}()
|
||||
// 获取工作表中指定单元格的值
|
||||
cell, err := f.GetCellValue("Sheet1", "B2")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(cell)
|
||||
// 获取 Sheet1 上所有单元格
|
||||
rows, err := f.GetRows("Sheet1")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
for _, row := range rows {
|
||||
for _, colCell := range row {
|
||||
fmt.Print(colCell, "\t")
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 在 Excel 文档中创建图表
|
||||
|
||||
使用 Excelize 生成图表十分简单,仅需几行代码。您可以根据工作表中的已有数据构建图表,或向工作表中添加数据并创建图表。
|
||||
|
||||
<p align="center"><img width="650" src="./test/images/chart.png" alt="使用 Excelize 在 Excel 电子表格文档中创建图表"></p>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/xuri/excelize/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
f := excelize.NewFile()
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}()
|
||||
for idx, row := range [][]interface{}{
|
||||
{nil, "Apple", "Orange", "Pear"}, {"Small", 2, 3, 3},
|
||||
{"Normal", 5, 2, 4}, {"Large", 6, 7, 8},
|
||||
} {
|
||||
cell, err := excelize.CoordinatesToCellName(1, idx+1)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
f.SetSheetRow("Sheet1", cell, &row)
|
||||
}
|
||||
if err := f.AddChart("Sheet1", "E1", &excelize.Chart{
|
||||
Type: excelize.Col3DClustered,
|
||||
Series: []excelize.ChartSeries{
|
||||
{
|
||||
Name: "Sheet1!$A$2",
|
||||
Categories: "Sheet1!$B$1:$D$1",
|
||||
Values: "Sheet1!$B$2:$D$2",
|
||||
},
|
||||
{
|
||||
Name: "Sheet1!$A$3",
|
||||
Categories: "Sheet1!$B$1:$D$1",
|
||||
Values: "Sheet1!$B$3:$D$3",
|
||||
},
|
||||
{
|
||||
Name: "Sheet1!$A$4",
|
||||
Categories: "Sheet1!$B$1:$D$1",
|
||||
Values: "Sheet1!$B$4:$D$4",
|
||||
}},
|
||||
Title: excelize.ChartTitle{
|
||||
Name: "Fruit 3D Clustered Column Chart",
|
||||
},
|
||||
}); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
// 根据指定路径保存文件
|
||||
if err := f.SaveAs("Book1.xlsx"); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 向 Excel 文档中插入图片
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
_ "image/gif"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
|
||||
"github.com/xuri/excelize/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
f, err := excelize.OpenFile("Book1.xlsx")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
// 关闭工作簿
|
||||
if err := f.Close(); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}()
|
||||
// 插入图片
|
||||
if err := f.AddPicture("Sheet1", "A2", "image.png", nil); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
// 在工作表中插入图片,并设置图片的缩放比例
|
||||
if err := f.AddPicture("Sheet1", "D2", "image.jpg",
|
||||
&excelize.GraphicOptions{ScaleX: 0.5, ScaleY: 0.5}); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
// 在工作表中插入图片,并设置图片的打印属性
|
||||
enable, disable := true, false
|
||||
if err := f.AddPicture("Sheet1", "H2", "image.gif",
|
||||
&excelize.GraphicOptions{
|
||||
PrintObject: &enable,
|
||||
LockAspectRatio: false,
|
||||
OffsetX: 15,
|
||||
OffsetY: 10,
|
||||
Locked: &disable,
|
||||
}); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
// 保存工作簿
|
||||
if err = f.Save(); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 社区合作
|
||||
|
||||
欢迎您为此项目贡献代码,提出建议或问题、修复 Bug 以及参与讨论对新功能的想法。 XML 符合标准: [part 1 of the 5th edition of the ECMA-376 Standard for Office Open XML](https://www.ecma-international.org/publications-and-standards/standards/ecma-376/)。
|
||||
|
||||
## 开源许可
|
||||
|
||||
本项目遵循 BSD 3-Clause 开源许可协议,访问 [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) 查看许可协议文件。
|
||||
|
||||
Excel 徽标是 [Microsoft Corporation](https://aka.ms/trademarks-usage) 的商标,项目的图片是一种改编。
|
||||
|
||||
gopher.{ai,svg,png} 由 [Takuya Ueda](https://twitter.com/tenntenn) 创作,遵循 [Creative Commons 3.0 Attributions license](http://creativecommons.org/licenses/by/3.0/) 创作共用授权条款。
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We will dive into any security-related issue as long as your Excelize version is still supported by us. When reporting an issue, include as much information as possible, but no need to fill fancy forms or answer tedious questions. Just tell us what you found, how to reproduce it, and any concerns you have about it. We will respond as soon as possible and follow up with any missing information.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please e-mail us directly at `xuri.me@gmail.com` or use the security issue template on GitHub. In general, public disclosure is made after the issue has been fully identified and a patch is ready to be released. A security issue gets the highest priority assigned and a reply regarding the vulnerability is given within a typical 24 hours. Thank you!
|
||||
+425
@@ -0,0 +1,425 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type adjustDirection bool
|
||||
|
||||
const (
|
||||
columns adjustDirection = false
|
||||
rows adjustDirection = true
|
||||
)
|
||||
|
||||
// adjustHelper provides a function to adjust rows and columns dimensions,
|
||||
// hyperlinks, merged cells and auto filter when inserting or deleting rows or
|
||||
// columns.
|
||||
//
|
||||
// sheet: Worksheet name that we're editing
|
||||
// column: Index number of the column we're inserting/deleting before
|
||||
// row: Index number of the row we're inserting/deleting before
|
||||
// offset: Number of rows/column to insert/delete negative values indicate deletion
|
||||
//
|
||||
// TODO: adjustPageBreaks, adjustComments, adjustDataValidations, adjustProtectedCells
|
||||
func (f *File) adjustHelper(sheet string, dir adjustDirection, num, offset int) error {
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sheetID := f.getSheetID(sheet)
|
||||
if dir == rows {
|
||||
err = f.adjustRowDimensions(ws, num, offset)
|
||||
} else {
|
||||
err = f.adjustColDimensions(ws, num, offset)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.adjustHyperlinks(ws, sheet, dir, num, offset)
|
||||
f.adjustTable(ws, sheet, dir, num, offset)
|
||||
if err = f.adjustMergeCells(ws, dir, num, offset); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = f.adjustAutoFilter(ws, dir, num, offset); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = f.adjustCalcChain(dir, num, offset, sheetID); err != nil {
|
||||
return err
|
||||
}
|
||||
checkSheet(ws)
|
||||
_ = checkRow(ws)
|
||||
|
||||
if ws.MergeCells != nil && len(ws.MergeCells.Cells) == 0 {
|
||||
ws.MergeCells = nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// adjustCols provides a function to update column style when inserting or
|
||||
// deleting columns.
|
||||
func (f *File) adjustCols(ws *xlsxWorksheet, col, offset int) error {
|
||||
if ws.Cols == nil {
|
||||
return nil
|
||||
}
|
||||
for i := 0; i < len(ws.Cols.Col); i++ {
|
||||
if offset > 0 {
|
||||
if ws.Cols.Col[i].Max+1 == col {
|
||||
ws.Cols.Col[i].Max += offset
|
||||
continue
|
||||
}
|
||||
if ws.Cols.Col[i].Min >= col {
|
||||
ws.Cols.Col[i].Min += offset
|
||||
ws.Cols.Col[i].Max += offset
|
||||
continue
|
||||
}
|
||||
if ws.Cols.Col[i].Min < col && ws.Cols.Col[i].Max >= col {
|
||||
ws.Cols.Col[i].Max += offset
|
||||
}
|
||||
}
|
||||
if offset < 0 {
|
||||
if ws.Cols.Col[i].Min == col && ws.Cols.Col[i].Max == col {
|
||||
if len(ws.Cols.Col) > 1 {
|
||||
ws.Cols.Col = append(ws.Cols.Col[:i], ws.Cols.Col[i+1:]...)
|
||||
} else {
|
||||
ws.Cols.Col = nil
|
||||
}
|
||||
i--
|
||||
continue
|
||||
}
|
||||
if ws.Cols.Col[i].Min > col {
|
||||
ws.Cols.Col[i].Min += offset
|
||||
ws.Cols.Col[i].Max += offset
|
||||
continue
|
||||
}
|
||||
if ws.Cols.Col[i].Min <= col && ws.Cols.Col[i].Max >= col {
|
||||
ws.Cols.Col[i].Max += offset
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// adjustColDimensions provides a function to update column dimensions when
|
||||
// inserting or deleting rows or columns.
|
||||
func (f *File) adjustColDimensions(ws *xlsxWorksheet, col, offset int) error {
|
||||
for rowIdx := range ws.SheetData.Row {
|
||||
for _, v := range ws.SheetData.Row[rowIdx].C {
|
||||
if cellCol, _, _ := CellNameToCoordinates(v.R); col <= cellCol {
|
||||
if newCol := cellCol + offset; newCol > 0 && newCol > MaxColumns {
|
||||
return ErrColumnNumber
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for rowIdx := range ws.SheetData.Row {
|
||||
for colIdx, v := range ws.SheetData.Row[rowIdx].C {
|
||||
if cellCol, cellRow, _ := CellNameToCoordinates(v.R); col <= cellCol {
|
||||
if newCol := cellCol + offset; newCol > 0 {
|
||||
ws.SheetData.Row[rowIdx].C[colIdx].R, _ = CoordinatesToCellName(newCol, cellRow)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return f.adjustCols(ws, col, offset)
|
||||
}
|
||||
|
||||
// adjustRowDimensions provides a function to update row dimensions when
|
||||
// inserting or deleting rows or columns.
|
||||
func (f *File) adjustRowDimensions(ws *xlsxWorksheet, row, offset int) error {
|
||||
totalRows := len(ws.SheetData.Row)
|
||||
if totalRows == 0 {
|
||||
return nil
|
||||
}
|
||||
lastRow := &ws.SheetData.Row[totalRows-1]
|
||||
if newRow := lastRow.R + offset; lastRow.R >= row && newRow > 0 && newRow >= TotalRows {
|
||||
return ErrMaxRows
|
||||
}
|
||||
for i := 0; i < len(ws.SheetData.Row); i++ {
|
||||
r := &ws.SheetData.Row[i]
|
||||
if newRow := r.R + offset; r.R >= row && newRow > 0 {
|
||||
f.adjustSingleRowDimensions(r, newRow)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// adjustSingleRowDimensions provides a function to adjust single row dimensions.
|
||||
func (f *File) adjustSingleRowDimensions(r *xlsxRow, num int) {
|
||||
r.R = num
|
||||
for i, col := range r.C {
|
||||
colName, _, _ := SplitCellName(col.R)
|
||||
r.C[i].R, _ = JoinCellName(colName, num)
|
||||
}
|
||||
}
|
||||
|
||||
// adjustHyperlinks provides a function to update hyperlinks when inserting or
|
||||
// deleting rows or columns.
|
||||
func (f *File) adjustHyperlinks(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset int) {
|
||||
// short path
|
||||
if ws.Hyperlinks == nil || len(ws.Hyperlinks.Hyperlink) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// order is important
|
||||
if offset < 0 {
|
||||
for i := len(ws.Hyperlinks.Hyperlink) - 1; i >= 0; i-- {
|
||||
linkData := ws.Hyperlinks.Hyperlink[i]
|
||||
colNum, rowNum, _ := CellNameToCoordinates(linkData.Ref)
|
||||
|
||||
if (dir == rows && num == rowNum) || (dir == columns && num == colNum) {
|
||||
f.deleteSheetRelationships(sheet, linkData.RID)
|
||||
if len(ws.Hyperlinks.Hyperlink) > 1 {
|
||||
ws.Hyperlinks.Hyperlink = append(ws.Hyperlinks.Hyperlink[:i],
|
||||
ws.Hyperlinks.Hyperlink[i+1:]...)
|
||||
} else {
|
||||
ws.Hyperlinks = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ws.Hyperlinks == nil {
|
||||
return
|
||||
}
|
||||
for i := range ws.Hyperlinks.Hyperlink {
|
||||
link := &ws.Hyperlinks.Hyperlink[i] // get reference
|
||||
colNum, rowNum, _ := CellNameToCoordinates(link.Ref)
|
||||
if dir == rows {
|
||||
if rowNum >= num {
|
||||
link.Ref, _ = CoordinatesToCellName(colNum, rowNum+offset)
|
||||
}
|
||||
} else {
|
||||
if colNum >= num {
|
||||
link.Ref, _ = CoordinatesToCellName(colNum+offset, rowNum)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// adjustTable provides a function to update the table when inserting or
|
||||
// deleting rows or columns.
|
||||
func (f *File) adjustTable(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset int) {
|
||||
if ws.TableParts == nil || len(ws.TableParts.TableParts) == 0 {
|
||||
return
|
||||
}
|
||||
for idx := 0; idx < len(ws.TableParts.TableParts); idx++ {
|
||||
tbl := ws.TableParts.TableParts[idx]
|
||||
target := f.getSheetRelationshipsTargetByID(sheet, tbl.RID)
|
||||
tableXML := strings.ReplaceAll(target, "..", "xl")
|
||||
content, ok := f.Pkg.Load(tableXML)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
t := xlsxTable{}
|
||||
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))).
|
||||
Decode(&t); err != nil && err != io.EOF {
|
||||
return
|
||||
}
|
||||
coordinates, err := rangeRefToCoordinates(t.Ref)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// Remove the table when deleting the header row of the table
|
||||
if dir == rows && num == coordinates[0] {
|
||||
ws.TableParts.TableParts = append(ws.TableParts.TableParts[:idx], ws.TableParts.TableParts[idx+1:]...)
|
||||
ws.TableParts.Count = len(ws.TableParts.TableParts)
|
||||
idx--
|
||||
continue
|
||||
}
|
||||
coordinates = f.adjustAutoFilterHelper(dir, coordinates, num, offset)
|
||||
x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
|
||||
if y2-y1 < 2 || x2-x1 < 1 {
|
||||
ws.TableParts.TableParts = append(ws.TableParts.TableParts[:idx], ws.TableParts.TableParts[idx+1:]...)
|
||||
ws.TableParts.Count = len(ws.TableParts.TableParts)
|
||||
idx--
|
||||
continue
|
||||
}
|
||||
t.Ref, _ = f.coordinatesToRangeRef([]int{x1, y1, x2, y2})
|
||||
if t.AutoFilter != nil {
|
||||
t.AutoFilter.Ref = t.Ref
|
||||
}
|
||||
_, _ = f.setTableHeader(sheet, true, x1, y1, x2)
|
||||
table, _ := xml.Marshal(t)
|
||||
f.saveFileList(tableXML, table)
|
||||
}
|
||||
}
|
||||
|
||||
// adjustAutoFilter provides a function to update the auto filter when
|
||||
// inserting or deleting rows or columns.
|
||||
func (f *File) adjustAutoFilter(ws *xlsxWorksheet, dir adjustDirection, num, offset int) error {
|
||||
if ws.AutoFilter == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
coordinates, err := rangeRefToCoordinates(ws.AutoFilter.Ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
|
||||
|
||||
if (dir == rows && y1 == num && offset < 0) || (dir == columns && x1 == num && x2 == num) {
|
||||
ws.AutoFilter = nil
|
||||
for rowIdx := range ws.SheetData.Row {
|
||||
rowData := &ws.SheetData.Row[rowIdx]
|
||||
if rowData.R > y1 && rowData.R <= y2 {
|
||||
rowData.Hidden = false
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
coordinates = f.adjustAutoFilterHelper(dir, coordinates, num, offset)
|
||||
x1, y1, x2, y2 = coordinates[0], coordinates[1], coordinates[2], coordinates[3]
|
||||
|
||||
ws.AutoFilter.Ref, err = f.coordinatesToRangeRef([]int{x1, y1, x2, y2})
|
||||
return err
|
||||
}
|
||||
|
||||
// adjustAutoFilterHelper provides a function for adjusting auto filter to
|
||||
// compare and calculate cell reference by the given adjust direction, operation
|
||||
// reference and offset.
|
||||
func (f *File) adjustAutoFilterHelper(dir adjustDirection, coordinates []int, num, offset int) []int {
|
||||
if dir == rows {
|
||||
if coordinates[1] >= num {
|
||||
coordinates[1] += offset
|
||||
}
|
||||
if coordinates[3] >= num {
|
||||
coordinates[3] += offset
|
||||
}
|
||||
return coordinates
|
||||
}
|
||||
if coordinates[0] >= num {
|
||||
coordinates[0] += offset
|
||||
}
|
||||
if coordinates[2] >= num {
|
||||
coordinates[2] += offset
|
||||
}
|
||||
return coordinates
|
||||
}
|
||||
|
||||
// adjustMergeCells provides a function to update merged cells when inserting
|
||||
// or deleting rows or columns.
|
||||
func (f *File) adjustMergeCells(ws *xlsxWorksheet, dir adjustDirection, num, offset int) error {
|
||||
if ws.MergeCells == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := 0; i < len(ws.MergeCells.Cells); i++ {
|
||||
mergedCells := ws.MergeCells.Cells[i]
|
||||
mergedCellsRef := mergedCells.Ref
|
||||
if !strings.Contains(mergedCellsRef, ":") {
|
||||
mergedCellsRef += ":" + mergedCellsRef
|
||||
}
|
||||
coordinates, err := rangeRefToCoordinates(mergedCellsRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
|
||||
if dir == rows {
|
||||
if y1 == num && y2 == num && offset < 0 {
|
||||
f.deleteMergeCell(ws, i)
|
||||
i--
|
||||
continue
|
||||
}
|
||||
|
||||
y1, y2 = f.adjustMergeCellsHelper(y1, y2, num, offset)
|
||||
} else {
|
||||
if x1 == num && x2 == num && offset < 0 {
|
||||
f.deleteMergeCell(ws, i)
|
||||
i--
|
||||
continue
|
||||
}
|
||||
|
||||
x1, x2 = f.adjustMergeCellsHelper(x1, x2, num, offset)
|
||||
}
|
||||
if x1 == x2 && y1 == y2 {
|
||||
f.deleteMergeCell(ws, i)
|
||||
i--
|
||||
continue
|
||||
}
|
||||
mergedCells.rect = []int{x1, y1, x2, y2}
|
||||
if mergedCells.Ref, err = f.coordinatesToRangeRef([]int{x1, y1, x2, y2}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// adjustMergeCellsHelper provides a function for adjusting merge cells to
|
||||
// compare and calculate cell reference by the given pivot, operation reference and
|
||||
// offset.
|
||||
func (f *File) adjustMergeCellsHelper(p1, p2, num, offset int) (int, int) {
|
||||
if p2 < p1 {
|
||||
p1, p2 = p2, p1
|
||||
}
|
||||
|
||||
if offset >= 0 {
|
||||
if num <= p1 {
|
||||
p1 += offset
|
||||
p2 += offset
|
||||
} else if num <= p2 {
|
||||
p2 += offset
|
||||
}
|
||||
return p1, p2
|
||||
}
|
||||
if num < p1 || (num == p1 && num == p2) {
|
||||
p1 += offset
|
||||
p2 += offset
|
||||
} else if num <= p2 {
|
||||
p2 += offset
|
||||
}
|
||||
return p1, p2
|
||||
}
|
||||
|
||||
// deleteMergeCell provides a function to delete merged cell by given index.
|
||||
func (f *File) deleteMergeCell(ws *xlsxWorksheet, idx int) {
|
||||
if idx < 0 {
|
||||
return
|
||||
}
|
||||
if len(ws.MergeCells.Cells) > idx {
|
||||
ws.MergeCells.Cells = append(ws.MergeCells.Cells[:idx], ws.MergeCells.Cells[idx+1:]...)
|
||||
ws.MergeCells.Count = len(ws.MergeCells.Cells)
|
||||
}
|
||||
}
|
||||
|
||||
// adjustCalcChain provides a function to update the calculation chain when
|
||||
// inserting or deleting rows or columns.
|
||||
func (f *File) adjustCalcChain(dir adjustDirection, num, offset, sheetID int) error {
|
||||
if f.CalcChain == nil {
|
||||
return nil
|
||||
}
|
||||
for index, c := range f.CalcChain.C {
|
||||
if c.I != sheetID {
|
||||
continue
|
||||
}
|
||||
colNum, rowNum, err := CellNameToCoordinates(c.R)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dir == rows && num <= rowNum {
|
||||
if newRow := rowNum + offset; newRow > 0 {
|
||||
f.CalcChain.C[index].R, _ = CoordinatesToCellName(colNum, newRow)
|
||||
}
|
||||
}
|
||||
if dir == columns && num <= colNum {
|
||||
if newCol := colNum + offset; newCol > 0 {
|
||||
f.CalcChain.C[index].R, _ = CoordinatesToCellName(newCol, rowNum)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+17988
File diff suppressed because it is too large
Load Diff
+83
@@ -0,0 +1,83 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
)
|
||||
|
||||
// calcChainReader provides a function to get the pointer to the structure
|
||||
// after deserialization of xl/calcChain.xml.
|
||||
func (f *File) calcChainReader() (*xlsxCalcChain, error) {
|
||||
if f.CalcChain == nil {
|
||||
f.CalcChain = new(xlsxCalcChain)
|
||||
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathCalcChain)))).
|
||||
Decode(f.CalcChain); err != nil && err != io.EOF {
|
||||
return f.CalcChain, err
|
||||
}
|
||||
}
|
||||
return f.CalcChain, nil
|
||||
}
|
||||
|
||||
// calcChainWriter provides a function to save xl/calcChain.xml after
|
||||
// serialize structure.
|
||||
func (f *File) calcChainWriter() {
|
||||
if f.CalcChain != nil && f.CalcChain.C != nil {
|
||||
output, _ := xml.Marshal(f.CalcChain)
|
||||
f.saveFileList(defaultXMLPathCalcChain, output)
|
||||
}
|
||||
}
|
||||
|
||||
// deleteCalcChain provides a function to remove cell reference on the
|
||||
// calculation chain.
|
||||
func (f *File) deleteCalcChain(index int, cell string) error {
|
||||
calc, err := f.calcChainReader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if calc != nil {
|
||||
calc.C = xlsxCalcChainCollection(calc.C).Filter(func(c xlsxCalcChainC) bool {
|
||||
return !((c.I == index && c.R == cell) || (c.I == index && cell == "") || (c.I == 0 && c.R == cell))
|
||||
})
|
||||
}
|
||||
if len(calc.C) == 0 {
|
||||
f.CalcChain = nil
|
||||
f.Pkg.Delete(defaultXMLPathCalcChain)
|
||||
content, err := f.contentTypesReader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
content.Lock()
|
||||
defer content.Unlock()
|
||||
for k, v := range content.Overrides {
|
||||
if v.PartName == "/xl/calcChain.xml" {
|
||||
content.Overrides = append(content.Overrides[:k], content.Overrides[k+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type xlsxCalcChainCollection []xlsxCalcChainC
|
||||
|
||||
// Filter provides a function to filter calculation chain.
|
||||
func (c xlsxCalcChainCollection) Filter(fn func(v xlsxCalcChainC) bool) []xlsxCalcChainC {
|
||||
var results []xlsxCalcChainC
|
||||
for _, v := range c {
|
||||
if fn(v) {
|
||||
results = append(results, v)
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
+1549
File diff suppressed because it is too large
Load Diff
+1105
File diff suppressed because it is too large
Load Diff
+776
@@ -0,0 +1,776 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/mohae/deepcopy"
|
||||
)
|
||||
|
||||
// Define the default cell size and EMU unit of measurement.
|
||||
const (
|
||||
defaultColWidth float64 = 9.140625
|
||||
defaultColWidthPixels float64 = 64
|
||||
defaultRowHeight float64 = 15
|
||||
defaultRowHeightPixels float64 = 20
|
||||
EMU int = 9525
|
||||
)
|
||||
|
||||
// Cols defines an iterator to a sheet
|
||||
type Cols struct {
|
||||
err error
|
||||
curCol, totalCols, totalRows, stashCol int
|
||||
rawCellValue bool
|
||||
sheet string
|
||||
f *File
|
||||
sheetXML []byte
|
||||
sst *xlsxSST
|
||||
}
|
||||
|
||||
// GetCols gets the value of all cells by columns on the worksheet based on the
|
||||
// given worksheet name, returned as a two-dimensional array, where the value
|
||||
// of the cell is converted to the `string` type. If the cell format can be
|
||||
// applied to the value of the cell, the applied value will be used, otherwise
|
||||
// the original value will be used.
|
||||
//
|
||||
// For example, get and traverse the value of all cells by columns on a
|
||||
// worksheet named
|
||||
// 'Sheet1':
|
||||
//
|
||||
// cols, err := f.GetCols("Sheet1")
|
||||
// if err != nil {
|
||||
// fmt.Println(err)
|
||||
// return
|
||||
// }
|
||||
// for _, col := range cols {
|
||||
// for _, rowCell := range col {
|
||||
// fmt.Print(rowCell, "\t")
|
||||
// }
|
||||
// fmt.Println()
|
||||
// }
|
||||
func (f *File) GetCols(sheet string, opts ...Options) ([][]string, error) {
|
||||
cols, err := f.Cols(sheet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results := make([][]string, 0, 64)
|
||||
for cols.Next() {
|
||||
col, _ := cols.Rows(opts...)
|
||||
results = append(results, col)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// Next will return true if the next column is found.
|
||||
func (cols *Cols) Next() bool {
|
||||
cols.curCol++
|
||||
return cols.curCol <= cols.totalCols
|
||||
}
|
||||
|
||||
// Error will return an error when the error occurs.
|
||||
func (cols *Cols) Error() error {
|
||||
return cols.err
|
||||
}
|
||||
|
||||
// Rows return the current column's row values.
|
||||
func (cols *Cols) Rows(opts ...Options) ([]string, error) {
|
||||
var rowIterator rowXMLIterator
|
||||
if cols.stashCol >= cols.curCol {
|
||||
return rowIterator.cells, rowIterator.err
|
||||
}
|
||||
cols.rawCellValue = getOptions(opts...).RawCellValue
|
||||
if cols.sst, rowIterator.err = cols.f.sharedStringsReader(); rowIterator.err != nil {
|
||||
return rowIterator.cells, rowIterator.err
|
||||
}
|
||||
decoder := cols.f.xmlNewDecoder(bytes.NewReader(cols.sheetXML))
|
||||
for {
|
||||
token, _ := decoder.Token()
|
||||
if token == nil {
|
||||
break
|
||||
}
|
||||
switch xmlElement := token.(type) {
|
||||
case xml.StartElement:
|
||||
rowIterator.inElement = xmlElement.Name.Local
|
||||
if rowIterator.inElement == "row" {
|
||||
rowIterator.cellCol = 0
|
||||
rowIterator.cellRow++
|
||||
attrR, _ := attrValToInt("r", xmlElement.Attr)
|
||||
if attrR != 0 {
|
||||
rowIterator.cellRow = attrR
|
||||
}
|
||||
}
|
||||
if cols.rowXMLHandler(&rowIterator, &xmlElement, decoder); rowIterator.err != nil {
|
||||
return rowIterator.cells, rowIterator.err
|
||||
}
|
||||
case xml.EndElement:
|
||||
if xmlElement.Name.Local == "sheetData" {
|
||||
return rowIterator.cells, rowIterator.err
|
||||
}
|
||||
}
|
||||
}
|
||||
return rowIterator.cells, rowIterator.err
|
||||
}
|
||||
|
||||
// columnXMLIterator defined runtime use field for the worksheet column SAX parser.
|
||||
type columnXMLIterator struct {
|
||||
err error
|
||||
cols Cols
|
||||
cellCol, curRow, row int
|
||||
}
|
||||
|
||||
// columnXMLHandler parse the column XML element of the worksheet.
|
||||
func columnXMLHandler(colIterator *columnXMLIterator, xmlElement *xml.StartElement) {
|
||||
colIterator.err = nil
|
||||
inElement := xmlElement.Name.Local
|
||||
if inElement == "row" {
|
||||
colIterator.row++
|
||||
for _, attr := range xmlElement.Attr {
|
||||
if attr.Name.Local == "r" {
|
||||
if colIterator.curRow, colIterator.err = strconv.Atoi(attr.Value); colIterator.err != nil {
|
||||
return
|
||||
}
|
||||
colIterator.row = colIterator.curRow
|
||||
}
|
||||
}
|
||||
colIterator.cols.totalRows = colIterator.row
|
||||
colIterator.cellCol = 0
|
||||
}
|
||||
if inElement == "c" {
|
||||
colIterator.cellCol++
|
||||
for _, attr := range xmlElement.Attr {
|
||||
if attr.Name.Local == "r" {
|
||||
if colIterator.cellCol, _, colIterator.err = CellNameToCoordinates(attr.Value); colIterator.err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if colIterator.cellCol > colIterator.cols.totalCols {
|
||||
colIterator.cols.totalCols = colIterator.cellCol
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// rowXMLHandler parse the row XML element of the worksheet.
|
||||
func (cols *Cols) rowXMLHandler(rowIterator *rowXMLIterator, xmlElement *xml.StartElement, decoder *xml.Decoder) {
|
||||
if rowIterator.inElement == "c" {
|
||||
rowIterator.cellCol++
|
||||
for _, attr := range xmlElement.Attr {
|
||||
if attr.Name.Local == "r" {
|
||||
if rowIterator.cellCol, rowIterator.cellRow, rowIterator.err = CellNameToCoordinates(attr.Value); rowIterator.err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
blank := rowIterator.cellRow - len(rowIterator.cells)
|
||||
for i := 1; i < blank; i++ {
|
||||
rowIterator.cells = append(rowIterator.cells, "")
|
||||
}
|
||||
if rowIterator.cellCol == cols.curCol {
|
||||
colCell := xlsxC{}
|
||||
_ = decoder.DecodeElement(&colCell, xmlElement)
|
||||
val, _ := colCell.getValueFrom(cols.f, cols.sst, cols.rawCellValue)
|
||||
rowIterator.cells = append(rowIterator.cells, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cols returns a columns iterator, used for streaming reading data for a
|
||||
// worksheet with a large data. This function is concurrency safe. For
|
||||
// example:
|
||||
//
|
||||
// cols, err := f.Cols("Sheet1")
|
||||
// if err != nil {
|
||||
// fmt.Println(err)
|
||||
// return
|
||||
// }
|
||||
// for cols.Next() {
|
||||
// col, err := cols.Rows()
|
||||
// if err != nil {
|
||||
// fmt.Println(err)
|
||||
// }
|
||||
// for _, rowCell := range col {
|
||||
// fmt.Print(rowCell, "\t")
|
||||
// }
|
||||
// fmt.Println()
|
||||
// }
|
||||
func (f *File) Cols(sheet string) (*Cols, error) {
|
||||
if err := checkSheetName(sheet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name, ok := f.getSheetXMLPath(sheet)
|
||||
if !ok {
|
||||
return nil, ErrSheetNotExist{sheet}
|
||||
}
|
||||
if ws, ok := f.Sheet.Load(name); ok && ws != nil {
|
||||
worksheet := ws.(*xlsxWorksheet)
|
||||
worksheet.Lock()
|
||||
defer worksheet.Unlock()
|
||||
output, _ := xml.Marshal(worksheet)
|
||||
f.saveFileList(name, f.replaceNameSpaceBytes(name, output))
|
||||
}
|
||||
var colIterator columnXMLIterator
|
||||
colIterator.cols.sheetXML = f.readBytes(name)
|
||||
decoder := f.xmlNewDecoder(bytes.NewReader(colIterator.cols.sheetXML))
|
||||
for {
|
||||
token, _ := decoder.Token()
|
||||
if token == nil {
|
||||
break
|
||||
}
|
||||
switch xmlElement := token.(type) {
|
||||
case xml.StartElement:
|
||||
columnXMLHandler(&colIterator, &xmlElement)
|
||||
if colIterator.err != nil {
|
||||
return &colIterator.cols, colIterator.err
|
||||
}
|
||||
case xml.EndElement:
|
||||
if xmlElement.Name.Local == "sheetData" {
|
||||
colIterator.cols.f = f
|
||||
colIterator.cols.sheet = sheet
|
||||
return &colIterator.cols, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return &colIterator.cols, nil
|
||||
}
|
||||
|
||||
// GetColVisible provides a function to get visible of a single column by given
|
||||
// worksheet name and column name. This function is concurrency safe. For
|
||||
// example, get visible state of column D in Sheet1:
|
||||
//
|
||||
// visible, err := f.GetColVisible("Sheet1", "D")
|
||||
func (f *File) GetColVisible(sheet, col string) (bool, error) {
|
||||
colNum, err := ColumnNameToNumber(col)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
ws.Lock()
|
||||
defer ws.Unlock()
|
||||
if ws.Cols == nil {
|
||||
return true, err
|
||||
}
|
||||
visible := true
|
||||
for c := range ws.Cols.Col {
|
||||
colData := &ws.Cols.Col[c]
|
||||
if colData.Min <= colNum && colNum <= colData.Max {
|
||||
visible = !colData.Hidden
|
||||
}
|
||||
}
|
||||
return visible, err
|
||||
}
|
||||
|
||||
// SetColVisible provides a function to set visible columns by given worksheet
|
||||
// name, columns range and visibility. This function is concurrency safe.
|
||||
//
|
||||
// For example hide column D on Sheet1:
|
||||
//
|
||||
// err := f.SetColVisible("Sheet1", "D", false)
|
||||
//
|
||||
// Hide the columns from D to F (included):
|
||||
//
|
||||
// err := f.SetColVisible("Sheet1", "D:F", false)
|
||||
func (f *File) SetColVisible(sheet, columns string, visible bool) error {
|
||||
min, max, err := f.parseColRange(columns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ws.Lock()
|
||||
defer ws.Unlock()
|
||||
colData := xlsxCol{
|
||||
Min: min,
|
||||
Max: max,
|
||||
Width: float64Ptr(defaultColWidth),
|
||||
Hidden: !visible,
|
||||
CustomWidth: true,
|
||||
}
|
||||
if ws.Cols == nil {
|
||||
cols := xlsxCols{}
|
||||
cols.Col = append(cols.Col, colData)
|
||||
ws.Cols = &cols
|
||||
return nil
|
||||
}
|
||||
ws.Cols.Col = flatCols(colData, ws.Cols.Col, func(fc, c xlsxCol) xlsxCol {
|
||||
fc.BestFit = c.BestFit
|
||||
fc.Collapsed = c.Collapsed
|
||||
fc.CustomWidth = c.CustomWidth
|
||||
fc.OutlineLevel = c.OutlineLevel
|
||||
fc.Phonetic = c.Phonetic
|
||||
fc.Style = c.Style
|
||||
fc.Width = c.Width
|
||||
return fc
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetColOutlineLevel provides a function to get outline level of a single
|
||||
// column by given worksheet name and column name. For example, get outline
|
||||
// level of column D in Sheet1:
|
||||
//
|
||||
// level, err := f.GetColOutlineLevel("Sheet1", "D")
|
||||
func (f *File) GetColOutlineLevel(sheet, col string) (uint8, error) {
|
||||
level := uint8(0)
|
||||
colNum, err := ColumnNameToNumber(col)
|
||||
if err != nil {
|
||||
return level, err
|
||||
}
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if ws.Cols == nil {
|
||||
return level, err
|
||||
}
|
||||
for c := range ws.Cols.Col {
|
||||
colData := &ws.Cols.Col[c]
|
||||
if colData.Min <= colNum && colNum <= colData.Max {
|
||||
level = colData.OutlineLevel
|
||||
}
|
||||
}
|
||||
return level, err
|
||||
}
|
||||
|
||||
// parseColRange parse and convert column range with column name to the column number.
|
||||
func (f *File) parseColRange(columns string) (min, max int, err error) {
|
||||
colsTab := strings.Split(columns, ":")
|
||||
min, err = ColumnNameToNumber(colsTab[0])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
max = min
|
||||
if len(colsTab) == 2 {
|
||||
if max, err = ColumnNameToNumber(colsTab[1]); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if max < min {
|
||||
min, max = max, min
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SetColOutlineLevel provides a function to set outline level of a single
|
||||
// column by given worksheet name and column name. The value of parameter
|
||||
// 'level' is 1-7. For example, set outline level of column D in Sheet1 to 2:
|
||||
//
|
||||
// err := f.SetColOutlineLevel("Sheet1", "D", 2)
|
||||
func (f *File) SetColOutlineLevel(sheet, col string, level uint8) error {
|
||||
if level > 7 || level < 1 {
|
||||
return ErrOutlineLevel
|
||||
}
|
||||
colNum, err := ColumnNameToNumber(col)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
colData := xlsxCol{
|
||||
Min: colNum,
|
||||
Max: colNum,
|
||||
OutlineLevel: level,
|
||||
CustomWidth: true,
|
||||
}
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ws.Cols == nil {
|
||||
cols := xlsxCols{}
|
||||
cols.Col = append(cols.Col, colData)
|
||||
ws.Cols = &cols
|
||||
return err
|
||||
}
|
||||
ws.Cols.Col = flatCols(colData, ws.Cols.Col, func(fc, c xlsxCol) xlsxCol {
|
||||
fc.BestFit = c.BestFit
|
||||
fc.Collapsed = c.Collapsed
|
||||
fc.CustomWidth = c.CustomWidth
|
||||
fc.Hidden = c.Hidden
|
||||
fc.Phonetic = c.Phonetic
|
||||
fc.Style = c.Style
|
||||
fc.Width = c.Width
|
||||
return fc
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// SetColStyle provides a function to set style of columns by given worksheet
|
||||
// name, columns range and style ID. This function is concurrency safe. Note
|
||||
// that this will overwrite the existing styles for the columns, it won't
|
||||
// append or merge style with existing styles.
|
||||
//
|
||||
// For example set style of column H on Sheet1:
|
||||
//
|
||||
// err = f.SetColStyle("Sheet1", "H", style)
|
||||
//
|
||||
// Set style of columns C:F on Sheet1:
|
||||
//
|
||||
// err = f.SetColStyle("Sheet1", "C:F", style)
|
||||
func (f *File) SetColStyle(sheet, columns string, styleID int) error {
|
||||
min, max, err := f.parseColRange(columns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s, err := f.stylesReader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Lock()
|
||||
if styleID < 0 || s.CellXfs == nil || len(s.CellXfs.Xf) <= styleID {
|
||||
s.Unlock()
|
||||
return newInvalidStyleID(styleID)
|
||||
}
|
||||
s.Unlock()
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ws.Lock()
|
||||
if ws.Cols == nil {
|
||||
ws.Cols = &xlsxCols{}
|
||||
}
|
||||
ws.Cols.Col = flatCols(xlsxCol{
|
||||
Min: min,
|
||||
Max: max,
|
||||
Width: float64Ptr(defaultColWidth),
|
||||
Style: styleID,
|
||||
}, ws.Cols.Col, func(fc, c xlsxCol) xlsxCol {
|
||||
fc.BestFit = c.BestFit
|
||||
fc.Collapsed = c.Collapsed
|
||||
fc.CustomWidth = c.CustomWidth
|
||||
fc.Hidden = c.Hidden
|
||||
fc.OutlineLevel = c.OutlineLevel
|
||||
fc.Phonetic = c.Phonetic
|
||||
fc.Width = c.Width
|
||||
return fc
|
||||
})
|
||||
ws.Unlock()
|
||||
if rows := len(ws.SheetData.Row); rows > 0 {
|
||||
for col := min; col <= max; col++ {
|
||||
from, _ := CoordinatesToCellName(col, 1)
|
||||
to, _ := CoordinatesToCellName(col, rows)
|
||||
err = f.SetCellStyle(sheet, from, to, styleID)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// SetColWidth provides a function to set the width of a single column or
|
||||
// multiple columns. This function is concurrency safe. For example:
|
||||
//
|
||||
// err := f.SetColWidth("Sheet1", "A", "H", 20)
|
||||
func (f *File) SetColWidth(sheet, startCol, endCol string, width float64) error {
|
||||
min, max, err := f.parseColRange(startCol + ":" + endCol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if width > MaxColumnWidth {
|
||||
return ErrColumnWidth
|
||||
}
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ws.Lock()
|
||||
defer ws.Unlock()
|
||||
col := xlsxCol{
|
||||
Min: min,
|
||||
Max: max,
|
||||
Width: float64Ptr(width),
|
||||
CustomWidth: true,
|
||||
}
|
||||
if ws.Cols == nil {
|
||||
cols := xlsxCols{}
|
||||
cols.Col = append(cols.Col, col)
|
||||
ws.Cols = &cols
|
||||
return err
|
||||
}
|
||||
ws.Cols.Col = flatCols(col, ws.Cols.Col, func(fc, c xlsxCol) xlsxCol {
|
||||
fc.BestFit = c.BestFit
|
||||
fc.Collapsed = c.Collapsed
|
||||
fc.Hidden = c.Hidden
|
||||
fc.OutlineLevel = c.OutlineLevel
|
||||
fc.Phonetic = c.Phonetic
|
||||
fc.Style = c.Style
|
||||
return fc
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// flatCols provides a method for the column's operation functions to flatten
|
||||
// and check the worksheet columns.
|
||||
func flatCols(col xlsxCol, cols []xlsxCol, replacer func(fc, c xlsxCol) xlsxCol) []xlsxCol {
|
||||
var fc []xlsxCol
|
||||
for i := col.Min; i <= col.Max; i++ {
|
||||
c := deepcopy.Copy(col).(xlsxCol)
|
||||
c.Min, c.Max = i, i
|
||||
fc = append(fc, c)
|
||||
}
|
||||
inFlat := func(colID int, cols []xlsxCol) (int, bool) {
|
||||
for idx, c := range cols {
|
||||
if c.Max == colID && c.Min == colID {
|
||||
return idx, true
|
||||
}
|
||||
}
|
||||
return -1, false
|
||||
}
|
||||
for _, column := range cols {
|
||||
for i := column.Min; i <= column.Max; i++ {
|
||||
if idx, ok := inFlat(i, fc); ok {
|
||||
fc[idx] = replacer(fc[idx], column)
|
||||
continue
|
||||
}
|
||||
c := deepcopy.Copy(column).(xlsxCol)
|
||||
c.Min, c.Max = i, i
|
||||
fc = append(fc, c)
|
||||
}
|
||||
}
|
||||
return fc
|
||||
}
|
||||
|
||||
// positionObjectPixels calculate the vertices that define the position of a
|
||||
// graphical object within the worksheet in pixels.
|
||||
//
|
||||
// +------------+------------+
|
||||
// | A | B |
|
||||
// +-----+------------+------------+
|
||||
// | |(x1,y1) | |
|
||||
// | 1 |(A1)._______|______ |
|
||||
// | | | | |
|
||||
// | | | | |
|
||||
// +-----+----| OBJECT |-----+
|
||||
// | | | | |
|
||||
// | 2 | |______________. |
|
||||
// | | | (B2)|
|
||||
// | | | (x2,y2)|
|
||||
// +-----+------------+------------+
|
||||
//
|
||||
// Example of an object that covers some range reference from cell A1 to B2.
|
||||
//
|
||||
// Based on the width and height of the object we need to calculate 8 vars:
|
||||
//
|
||||
// colStart, rowStart, colEnd, rowEnd, x1, y1, x2, y2.
|
||||
//
|
||||
// We also calculate the absolute x and y position of the top left vertex of
|
||||
// the object. This is required for images.
|
||||
//
|
||||
// The width and height of the cells that the object occupies can be
|
||||
// variable and have to be taken into account.
|
||||
//
|
||||
// The values of col_start and row_start are passed in from the calling
|
||||
// function. The values of col_end and row_end are calculated by
|
||||
// subtracting the width and height of the object from the width and
|
||||
// height of the underlying cells.
|
||||
//
|
||||
// colStart # Col containing upper left corner of object.
|
||||
// x1 # Distance to left side of object.
|
||||
//
|
||||
// rowStart # Row containing top left corner of object.
|
||||
// y1 # Distance to top of object.
|
||||
//
|
||||
// colEnd # Col containing lower right corner of object.
|
||||
// x2 # Distance to right side of object.
|
||||
//
|
||||
// rowEnd # Row containing bottom right corner of object.
|
||||
// y2 # Distance to bottom of object.
|
||||
//
|
||||
// width # Width of object frame.
|
||||
// height # Height of object frame.
|
||||
func (f *File) positionObjectPixels(sheet string, col, row, x1, y1, width, height int) (int, int, int, int, int, int) {
|
||||
// Adjust start column for offsets that are greater than the col width.
|
||||
for x1 >= f.getColWidth(sheet, col) {
|
||||
x1 -= f.getColWidth(sheet, col)
|
||||
col++
|
||||
}
|
||||
|
||||
// Adjust start row for offsets that are greater than the row height.
|
||||
for y1 >= f.getRowHeight(sheet, row) {
|
||||
y1 -= f.getRowHeight(sheet, row)
|
||||
row++
|
||||
}
|
||||
|
||||
// Initialized end cell to the same as the start cell.
|
||||
colEnd, rowEnd := col, row
|
||||
|
||||
width += x1
|
||||
height += y1
|
||||
|
||||
// Subtract the underlying cell widths to find end cell of the object.
|
||||
for width >= f.getColWidth(sheet, colEnd+1) {
|
||||
colEnd++
|
||||
width -= f.getColWidth(sheet, colEnd)
|
||||
}
|
||||
|
||||
// Subtract the underlying cell heights to find end cell of the object.
|
||||
for height >= f.getRowHeight(sheet, rowEnd+1) {
|
||||
rowEnd++
|
||||
height -= f.getRowHeight(sheet, rowEnd)
|
||||
}
|
||||
|
||||
// The end vertices are whatever is left from the width and height.
|
||||
x2 := width
|
||||
y2 := height
|
||||
return col, row, colEnd, rowEnd, x2, y2
|
||||
}
|
||||
|
||||
// getColWidth provides a function to get column width in pixels by given
|
||||
// sheet name and column number.
|
||||
func (f *File) getColWidth(sheet string, col int) int {
|
||||
ws, _ := f.workSheetReader(sheet)
|
||||
ws.Lock()
|
||||
defer ws.Unlock()
|
||||
if ws.Cols != nil {
|
||||
var width float64
|
||||
for _, v := range ws.Cols.Col {
|
||||
if v.Min <= col && col <= v.Max && v.Width != nil {
|
||||
width = *v.Width
|
||||
}
|
||||
}
|
||||
if width != 0 {
|
||||
return int(convertColWidthToPixels(width))
|
||||
}
|
||||
}
|
||||
// Optimization for when the column widths haven't changed.
|
||||
return int(defaultColWidthPixels)
|
||||
}
|
||||
|
||||
// GetColStyle provides a function to get column style ID by given worksheet
|
||||
// name and column name. This function is concurrency safe.
|
||||
func (f *File) GetColStyle(sheet, col string) (int, error) {
|
||||
var styleID int
|
||||
colNum, err := ColumnNameToNumber(col)
|
||||
if err != nil {
|
||||
return styleID, err
|
||||
}
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return styleID, err
|
||||
}
|
||||
ws.Lock()
|
||||
defer ws.Unlock()
|
||||
if ws.Cols != nil {
|
||||
for _, v := range ws.Cols.Col {
|
||||
if v.Min <= colNum && colNum <= v.Max {
|
||||
styleID = v.Style
|
||||
}
|
||||
}
|
||||
}
|
||||
return styleID, err
|
||||
}
|
||||
|
||||
// GetColWidth provides a function to get column width by given worksheet name
|
||||
// and column name. This function is concurrency safe.
|
||||
func (f *File) GetColWidth(sheet, col string) (float64, error) {
|
||||
colNum, err := ColumnNameToNumber(col)
|
||||
if err != nil {
|
||||
return defaultColWidth, err
|
||||
}
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return defaultColWidth, err
|
||||
}
|
||||
ws.Lock()
|
||||
defer ws.Unlock()
|
||||
if ws.Cols != nil {
|
||||
var width float64
|
||||
for _, v := range ws.Cols.Col {
|
||||
if v.Min <= colNum && colNum <= v.Max && v.Width != nil {
|
||||
width = *v.Width
|
||||
}
|
||||
}
|
||||
if width != 0 {
|
||||
return width, err
|
||||
}
|
||||
}
|
||||
// Optimization for when the column widths haven't changed.
|
||||
return defaultColWidth, err
|
||||
}
|
||||
|
||||
// InsertCols provides a function to insert new columns before the given column
|
||||
// name and number of columns. For example, create two columns before column
|
||||
// C in Sheet1:
|
||||
//
|
||||
// err := f.InsertCols("Sheet1", "C", 2)
|
||||
//
|
||||
// Use this method with caution, which will affect changes in references such
|
||||
// as formulas, charts, and so on. If there is any referenced value of the
|
||||
// worksheet, it will cause a file error when you open it. The excelize only
|
||||
// partially updates these references currently.
|
||||
func (f *File) InsertCols(sheet, col string, n int) error {
|
||||
num, err := ColumnNameToNumber(col)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n < 1 || n > MaxColumns {
|
||||
return ErrColumnNumber
|
||||
}
|
||||
return f.adjustHelper(sheet, columns, num, n)
|
||||
}
|
||||
|
||||
// RemoveCol provides a function to remove single column by given worksheet
|
||||
// name and column index. For example, remove column C in Sheet1:
|
||||
//
|
||||
// err := f.RemoveCol("Sheet1", "C")
|
||||
//
|
||||
// Use this method with caution, which will affect changes in references such
|
||||
// as formulas, charts, and so on. If there is any referenced value of the
|
||||
// worksheet, it will cause a file error when you open it. The excelize only
|
||||
// partially updates these references currently.
|
||||
func (f *File) RemoveCol(sheet, col string) error {
|
||||
num, err := ColumnNameToNumber(col)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for rowIdx := range ws.SheetData.Row {
|
||||
rowData := &ws.SheetData.Row[rowIdx]
|
||||
for colIdx := range rowData.C {
|
||||
colName, _, _ := SplitCellName(rowData.C[colIdx].R)
|
||||
if colName == col {
|
||||
rowData.C = append(rowData.C[:colIdx], rowData.C[colIdx+1:]...)[:len(rowData.C)-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return f.adjustHelper(sheet, columns, num, -1)
|
||||
}
|
||||
|
||||
// convertColWidthToPixels provides function to convert the width of a cell
|
||||
// from user's units to pixels. Excel rounds the column width to the nearest
|
||||
// pixel. If the width hasn't been set by the user we use the default value.
|
||||
// If the column is hidden it has a value of zero.
|
||||
func convertColWidthToPixels(width float64) float64 {
|
||||
var padding float64 = 5
|
||||
var pixels float64
|
||||
var maxDigitWidth float64 = 7
|
||||
if width == 0 {
|
||||
return pixels
|
||||
}
|
||||
if width < 1 {
|
||||
pixels = (width * 12) + 0.5
|
||||
return math.Ceil(pixels)
|
||||
}
|
||||
pixels = (width*maxDigitWidth + 0.5) + padding
|
||||
return math.Ceil(pixels)
|
||||
}
|
||||
+426
@@ -0,0 +1,426 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetComments retrieves all comments in a worksheet by given worksheet name.
|
||||
func (f *File) GetComments(sheet string) ([]Comment, error) {
|
||||
var comments []Comment
|
||||
sheetXMLPath, ok := f.getSheetXMLPath(sheet)
|
||||
if !ok {
|
||||
return comments, newNoExistSheetError(sheet)
|
||||
}
|
||||
commentsXML := f.getSheetComments(filepath.Base(sheetXMLPath))
|
||||
if !strings.HasPrefix(commentsXML, "/") {
|
||||
commentsXML = "xl" + strings.TrimPrefix(commentsXML, "..")
|
||||
}
|
||||
commentsXML = strings.TrimPrefix(commentsXML, "/")
|
||||
cmts, err := f.commentsReader(commentsXML)
|
||||
if err != nil {
|
||||
return comments, err
|
||||
}
|
||||
if cmts != nil {
|
||||
for _, cmt := range cmts.CommentList.Comment {
|
||||
comment := Comment{}
|
||||
if cmt.AuthorID < len(cmts.Authors.Author) {
|
||||
comment.Author = cmts.Authors.Author[cmt.AuthorID]
|
||||
}
|
||||
comment.Cell = cmt.Ref
|
||||
comment.AuthorID = cmt.AuthorID
|
||||
if cmt.Text.T != nil {
|
||||
comment.Text += *cmt.Text.T
|
||||
}
|
||||
for _, text := range cmt.Text.R {
|
||||
if text.T != nil {
|
||||
run := RichTextRun{Text: text.T.Val}
|
||||
if text.RPr != nil {
|
||||
run.Font = newFont(text.RPr)
|
||||
}
|
||||
comment.Runs = append(comment.Runs, run)
|
||||
}
|
||||
}
|
||||
comments = append(comments, comment)
|
||||
}
|
||||
}
|
||||
return comments, nil
|
||||
}
|
||||
|
||||
// getSheetComments provides the method to get the target comment reference by
|
||||
// given worksheet file path.
|
||||
func (f *File) getSheetComments(sheetFile string) string {
|
||||
rels, _ := f.relsReader("xl/worksheets/_rels/" + sheetFile + ".rels")
|
||||
if sheetRels := rels; sheetRels != nil {
|
||||
sheetRels.Lock()
|
||||
defer sheetRels.Unlock()
|
||||
for _, v := range sheetRels.Relationships {
|
||||
if v.Type == SourceRelationshipComments {
|
||||
return v.Target
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// AddComment provides the method to add comment in a sheet by given worksheet
|
||||
// index, cell and format set (such as author and text). Note that the max
|
||||
// author length is 255 and the max text length is 32512. For example, add a
|
||||
// comment in Sheet1!$A$30:
|
||||
//
|
||||
// err := f.AddComment("Sheet1", excelize.Comment{
|
||||
// Cell: "A12",
|
||||
// Author: "Excelize",
|
||||
// Runs: []excelize.RichTextRun{
|
||||
// {Text: "Excelize: ", Font: &excelize.Font{Bold: true}},
|
||||
// {Text: "This is a comment."},
|
||||
// },
|
||||
// })
|
||||
func (f *File) AddComment(sheet string, comment Comment) error {
|
||||
// Read sheet data.
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
commentID := f.countComments() + 1
|
||||
drawingVML := "xl/drawings/vmlDrawing" + strconv.Itoa(commentID) + ".vml"
|
||||
sheetRelationshipsComments := "../comments" + strconv.Itoa(commentID) + ".xml"
|
||||
sheetRelationshipsDrawingVML := "../drawings/vmlDrawing" + strconv.Itoa(commentID) + ".vml"
|
||||
if ws.LegacyDrawing != nil {
|
||||
// The worksheet already has a comments relationships, use the relationships drawing ../drawings/vmlDrawing%d.vml.
|
||||
sheetRelationshipsDrawingVML = f.getSheetRelationshipsTargetByID(sheet, ws.LegacyDrawing.RID)
|
||||
commentID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingVML, "../drawings/vmlDrawing"), ".vml"))
|
||||
drawingVML = strings.ReplaceAll(sheetRelationshipsDrawingVML, "..", "xl")
|
||||
} else {
|
||||
// Add first comment for given sheet.
|
||||
sheetXMLPath, _ := f.getSheetXMLPath(sheet)
|
||||
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels"
|
||||
rID := f.addRels(sheetRels, SourceRelationshipDrawingVML, sheetRelationshipsDrawingVML, "")
|
||||
f.addRels(sheetRels, SourceRelationshipComments, sheetRelationshipsComments, "")
|
||||
f.addSheetNameSpace(sheet, SourceRelationship)
|
||||
f.addSheetLegacyDrawing(sheet, rID)
|
||||
}
|
||||
commentsXML := "xl/comments" + strconv.Itoa(commentID) + ".xml"
|
||||
var rows, cols int
|
||||
for _, runs := range comment.Runs {
|
||||
for _, subStr := range strings.Split(runs.Text, "\n") {
|
||||
rows++
|
||||
if chars := len(subStr); chars > cols {
|
||||
cols = chars
|
||||
}
|
||||
}
|
||||
}
|
||||
if err = f.addDrawingVML(commentID, drawingVML, comment.Cell, rows+1, cols); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = f.addComment(commentsXML, comment); err != nil {
|
||||
return err
|
||||
}
|
||||
return f.addContentTypePart(commentID, "comments")
|
||||
}
|
||||
|
||||
// DeleteComment provides the method to delete comment in a sheet by given
|
||||
// worksheet name. For example, delete the comment in Sheet1!$A$30:
|
||||
//
|
||||
// err := f.DeleteComment("Sheet1", "A30")
|
||||
func (f *File) DeleteComment(sheet, cell string) error {
|
||||
if err := checkSheetName(sheet); err != nil {
|
||||
return err
|
||||
}
|
||||
sheetXMLPath, ok := f.getSheetXMLPath(sheet)
|
||||
if !ok {
|
||||
return newNoExistSheetError(sheet)
|
||||
}
|
||||
commentsXML := f.getSheetComments(filepath.Base(sheetXMLPath))
|
||||
if !strings.HasPrefix(commentsXML, "/") {
|
||||
commentsXML = "xl" + strings.TrimPrefix(commentsXML, "..")
|
||||
}
|
||||
commentsXML = strings.TrimPrefix(commentsXML, "/")
|
||||
cmts, err := f.commentsReader(commentsXML)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cmts != nil {
|
||||
for i := 0; i < len(cmts.CommentList.Comment); i++ {
|
||||
cmt := cmts.CommentList.Comment[i]
|
||||
if cmt.Ref != cell {
|
||||
continue
|
||||
}
|
||||
if len(cmts.CommentList.Comment) > 1 {
|
||||
cmts.CommentList.Comment = append(
|
||||
cmts.CommentList.Comment[:i],
|
||||
cmts.CommentList.Comment[i+1:]...,
|
||||
)
|
||||
i--
|
||||
continue
|
||||
}
|
||||
cmts.CommentList.Comment = nil
|
||||
}
|
||||
f.Comments[commentsXML] = cmts
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// addDrawingVML provides a function to create comment as
|
||||
// xl/drawings/vmlDrawing%d.vml by given commit ID and cell.
|
||||
func (f *File) addDrawingVML(commentID int, drawingVML, cell string, lineCount, colCount int) error {
|
||||
col, row, err := CellNameToCoordinates(cell)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
yAxis := col - 1
|
||||
xAxis := row - 1
|
||||
vml := f.VMLDrawing[drawingVML]
|
||||
if vml == nil {
|
||||
vml = &vmlDrawing{
|
||||
XMLNSv: "urn:schemas-microsoft-com:vml",
|
||||
XMLNSo: "urn:schemas-microsoft-com:office:office",
|
||||
XMLNSx: "urn:schemas-microsoft-com:office:excel",
|
||||
XMLNSmv: "http://macVmlSchemaUri",
|
||||
Shapelayout: &xlsxShapelayout{
|
||||
Ext: "edit",
|
||||
IDmap: &xlsxIDmap{
|
||||
Ext: "edit",
|
||||
Data: commentID,
|
||||
},
|
||||
},
|
||||
Shapetype: &xlsxShapetype{
|
||||
ID: "_x0000_t202",
|
||||
Coordsize: "21600,21600",
|
||||
Spt: 202,
|
||||
Path: "m0,0l0,21600,21600,21600,21600,0xe",
|
||||
Stroke: &xlsxStroke{
|
||||
Joinstyle: "miter",
|
||||
},
|
||||
VPath: &vPath{
|
||||
Gradientshapeok: "t",
|
||||
Connecttype: "rect",
|
||||
},
|
||||
},
|
||||
}
|
||||
// load exist comment shapes from xl/drawings/vmlDrawing%d.vml
|
||||
d, err := f.decodeVMLDrawingReader(drawingVML)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d != nil {
|
||||
for _, v := range d.Shape {
|
||||
s := xlsxShape{
|
||||
ID: "_x0000_s1025",
|
||||
Type: "#_x0000_t202",
|
||||
Style: "position:absolute;73.5pt;width:108pt;height:59.25pt;z-index:1;visibility:hidden",
|
||||
Fillcolor: "#FBF6D6",
|
||||
Strokecolor: "#EDEAA1",
|
||||
Val: v.Val,
|
||||
}
|
||||
vml.Shape = append(vml.Shape, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
sp := encodeShape{
|
||||
Fill: &vFill{
|
||||
Color2: "#FBFE82",
|
||||
Angle: -180,
|
||||
Type: "gradient",
|
||||
Fill: &oFill{
|
||||
Ext: "view",
|
||||
Type: "gradientUnscaled",
|
||||
},
|
||||
},
|
||||
Shadow: &vShadow{
|
||||
On: "t",
|
||||
Color: "black",
|
||||
Obscured: "t",
|
||||
},
|
||||
Path: &vPath{
|
||||
Connecttype: "none",
|
||||
},
|
||||
Textbox: &vTextbox{
|
||||
Style: "mso-direction-alt:auto",
|
||||
Div: &xlsxDiv{
|
||||
Style: "text-align:left",
|
||||
},
|
||||
},
|
||||
ClientData: &xClientData{
|
||||
ObjectType: "Note",
|
||||
Anchor: fmt.Sprintf(
|
||||
"%d, 23, %d, 0, %d, %d, %d, 5",
|
||||
1+yAxis, 1+xAxis, 2+yAxis+lineCount, colCount+yAxis, 2+xAxis+lineCount),
|
||||
AutoFill: "True",
|
||||
Row: xAxis,
|
||||
Column: yAxis,
|
||||
},
|
||||
}
|
||||
s, _ := xml.Marshal(sp)
|
||||
shape := xlsxShape{
|
||||
ID: "_x0000_s1025",
|
||||
Type: "#_x0000_t202",
|
||||
Style: "position:absolute;73.5pt;width:108pt;height:59.25pt;z-index:1;visibility:hidden",
|
||||
Fillcolor: "#FBF6D6",
|
||||
Strokecolor: "#EDEAA1",
|
||||
Val: string(s[13 : len(s)-14]),
|
||||
}
|
||||
vml.Shape = append(vml.Shape, shape)
|
||||
f.VMLDrawing[drawingVML] = vml
|
||||
return err
|
||||
}
|
||||
|
||||
// addComment provides a function to create chart as xl/comments%d.xml by
|
||||
// given cell and format sets.
|
||||
func (f *File) addComment(commentsXML string, comment Comment) error {
|
||||
if comment.Author == "" {
|
||||
comment.Author = "Author"
|
||||
}
|
||||
if len(comment.Author) > MaxFieldLength {
|
||||
comment.Author = comment.Author[:MaxFieldLength]
|
||||
}
|
||||
cmts, err := f.commentsReader(commentsXML)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var authorID int
|
||||
if cmts == nil {
|
||||
cmts = &xlsxComments{Authors: xlsxAuthor{Author: []string{comment.Author}}}
|
||||
}
|
||||
if inStrSlice(cmts.Authors.Author, comment.Author, true) == -1 {
|
||||
cmts.Authors.Author = append(cmts.Authors.Author, comment.Author)
|
||||
authorID = len(cmts.Authors.Author) - 1
|
||||
}
|
||||
defaultFont, err := f.GetDefaultFont()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chars, cmt := 0, xlsxComment{
|
||||
Ref: comment.Cell,
|
||||
AuthorID: authorID,
|
||||
Text: xlsxText{R: []xlsxR{}},
|
||||
}
|
||||
if comment.Text != "" {
|
||||
if len(comment.Text) > TotalCellChars {
|
||||
comment.Text = comment.Text[:TotalCellChars]
|
||||
}
|
||||
cmt.Text.T = stringPtr(comment.Text)
|
||||
chars += len(comment.Text)
|
||||
}
|
||||
for _, run := range comment.Runs {
|
||||
if chars == TotalCellChars {
|
||||
break
|
||||
}
|
||||
if chars+len(run.Text) > TotalCellChars {
|
||||
run.Text = run.Text[:TotalCellChars-chars]
|
||||
}
|
||||
chars += len(run.Text)
|
||||
r := xlsxR{
|
||||
RPr: &xlsxRPr{
|
||||
Sz: &attrValFloat{Val: float64Ptr(9)},
|
||||
Color: &xlsxColor{
|
||||
Indexed: 81,
|
||||
},
|
||||
RFont: &attrValString{Val: stringPtr(defaultFont)},
|
||||
Family: &attrValInt{Val: intPtr(2)},
|
||||
},
|
||||
T: &xlsxT{Val: run.Text, Space: xml.Attr{
|
||||
Name: xml.Name{Space: NameSpaceXML, Local: "space"},
|
||||
Value: "preserve",
|
||||
}},
|
||||
}
|
||||
if run.Font != nil {
|
||||
r.RPr = newRpr(run.Font)
|
||||
}
|
||||
cmt.Text.R = append(cmt.Text.R, r)
|
||||
}
|
||||
cmts.CommentList.Comment = append(cmts.CommentList.Comment, cmt)
|
||||
f.Comments[commentsXML] = cmts
|
||||
return err
|
||||
}
|
||||
|
||||
// countComments provides a function to get comments files count storage in
|
||||
// the folder xl.
|
||||
func (f *File) countComments() int {
|
||||
c1, c2 := 0, 0
|
||||
f.Pkg.Range(func(k, v interface{}) bool {
|
||||
if strings.Contains(k.(string), "xl/comments") {
|
||||
c1++
|
||||
}
|
||||
return true
|
||||
})
|
||||
for rel := range f.Comments {
|
||||
if strings.Contains(rel, "xl/comments") {
|
||||
c2++
|
||||
}
|
||||
}
|
||||
if c1 < c2 {
|
||||
return c2
|
||||
}
|
||||
return c1
|
||||
}
|
||||
|
||||
// decodeVMLDrawingReader provides a function to get the pointer to the
|
||||
// structure after deserialization of xl/drawings/vmlDrawing%d.xml.
|
||||
func (f *File) decodeVMLDrawingReader(path string) (*decodeVmlDrawing, error) {
|
||||
if f.DecodeVMLDrawing[path] == nil {
|
||||
c, ok := f.Pkg.Load(path)
|
||||
if ok && c != nil {
|
||||
f.DecodeVMLDrawing[path] = new(decodeVmlDrawing)
|
||||
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(c.([]byte)))).
|
||||
Decode(f.DecodeVMLDrawing[path]); err != nil && err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return f.DecodeVMLDrawing[path], nil
|
||||
}
|
||||
|
||||
// vmlDrawingWriter provides a function to save xl/drawings/vmlDrawing%d.xml
|
||||
// after serialize structure.
|
||||
func (f *File) vmlDrawingWriter() {
|
||||
for path, vml := range f.VMLDrawing {
|
||||
if vml != nil {
|
||||
v, _ := xml.Marshal(vml)
|
||||
f.Pkg.Store(path, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// commentsReader provides a function to get the pointer to the structure
|
||||
// after deserialization of xl/comments%d.xml.
|
||||
func (f *File) commentsReader(path string) (*xlsxComments, error) {
|
||||
if f.Comments[path] == nil {
|
||||
content, ok := f.Pkg.Load(path)
|
||||
if ok && content != nil {
|
||||
f.Comments[path] = new(xlsxComments)
|
||||
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))).
|
||||
Decode(f.Comments[path]); err != nil && err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return f.Comments[path], nil
|
||||
}
|
||||
|
||||
// commentsWriter provides a function to save xl/comments%d.xml after
|
||||
// serialize structure.
|
||||
func (f *File) commentsWriter() {
|
||||
for path, c := range f.Comments {
|
||||
if c != nil {
|
||||
v, _ := xml.Marshal(c)
|
||||
f.saveFileList(path, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
+1018
File diff suppressed because it is too large
Load Diff
+351
@@ -0,0 +1,351 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"unicode/utf16"
|
||||
)
|
||||
|
||||
// DataValidationType defined the type of data validation.
|
||||
type DataValidationType int
|
||||
|
||||
// Data validation types.
|
||||
const (
|
||||
_DataValidationType = iota
|
||||
typeNone // inline use
|
||||
DataValidationTypeCustom
|
||||
DataValidationTypeDate
|
||||
DataValidationTypeDecimal
|
||||
typeList // inline use
|
||||
DataValidationTypeTextLength
|
||||
DataValidationTypeTime
|
||||
// DataValidationTypeWhole Integer
|
||||
DataValidationTypeWhole
|
||||
)
|
||||
|
||||
// DataValidationErrorStyle defined the style of data validation error alert.
|
||||
type DataValidationErrorStyle int
|
||||
|
||||
// Data validation error styles.
|
||||
const (
|
||||
_ DataValidationErrorStyle = iota
|
||||
DataValidationErrorStyleStop
|
||||
DataValidationErrorStyleWarning
|
||||
DataValidationErrorStyleInformation
|
||||
)
|
||||
|
||||
// Data validation error styles.
|
||||
const (
|
||||
styleStop = "stop"
|
||||
styleWarning = "warning"
|
||||
styleInformation = "information"
|
||||
)
|
||||
|
||||
// DataValidationOperator operator enum.
|
||||
type DataValidationOperator int
|
||||
|
||||
// Data validation operators.
|
||||
const (
|
||||
_DataValidationOperator = iota
|
||||
DataValidationOperatorBetween
|
||||
DataValidationOperatorEqual
|
||||
DataValidationOperatorGreaterThan
|
||||
DataValidationOperatorGreaterThanOrEqual
|
||||
DataValidationOperatorLessThan
|
||||
DataValidationOperatorLessThanOrEqual
|
||||
DataValidationOperatorNotBetween
|
||||
DataValidationOperatorNotEqual
|
||||
)
|
||||
|
||||
// formulaEscaper mimics the Excel escaping rules for data validation,
|
||||
// which converts `"` to `""` instead of `"`.
|
||||
var formulaEscaper = strings.NewReplacer(
|
||||
`&`, `&`,
|
||||
`<`, `<`,
|
||||
`>`, `>`,
|
||||
`"`, `""`,
|
||||
)
|
||||
|
||||
// NewDataValidation return data validation struct.
|
||||
func NewDataValidation(allowBlank bool) *DataValidation {
|
||||
return &DataValidation{
|
||||
AllowBlank: allowBlank,
|
||||
ShowErrorMessage: false,
|
||||
ShowInputMessage: false,
|
||||
}
|
||||
}
|
||||
|
||||
// SetError set error notice.
|
||||
func (dv *DataValidation) SetError(style DataValidationErrorStyle, title, msg string) {
|
||||
dv.Error = &msg
|
||||
dv.ErrorTitle = &title
|
||||
strStyle := styleStop
|
||||
switch style {
|
||||
case DataValidationErrorStyleStop:
|
||||
strStyle = styleStop
|
||||
case DataValidationErrorStyleWarning:
|
||||
strStyle = styleWarning
|
||||
case DataValidationErrorStyleInformation:
|
||||
strStyle = styleInformation
|
||||
|
||||
}
|
||||
dv.ShowErrorMessage = true
|
||||
dv.ErrorStyle = &strStyle
|
||||
}
|
||||
|
||||
// SetInput set prompt notice.
|
||||
func (dv *DataValidation) SetInput(title, msg string) {
|
||||
dv.ShowInputMessage = true
|
||||
dv.PromptTitle = &title
|
||||
dv.Prompt = &msg
|
||||
}
|
||||
|
||||
// SetDropList data validation list.
|
||||
func (dv *DataValidation) SetDropList(keys []string) error {
|
||||
formula := strings.Join(keys, ",")
|
||||
if MaxFieldLength < len(utf16.Encode([]rune(formula))) {
|
||||
return ErrDataValidationFormulaLength
|
||||
}
|
||||
dv.Formula1 = fmt.Sprintf(`<formula1>"%s"</formula1>`, formulaEscaper.Replace(formula))
|
||||
dv.Type = convDataValidationType(typeList)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetRange provides function to set data validation range in drop list, only
|
||||
// accepts int, float64, or string data type formula argument.
|
||||
func (dv *DataValidation) SetRange(f1, f2 interface{}, t DataValidationType, o DataValidationOperator) error {
|
||||
var formula1, formula2 string
|
||||
switch v := f1.(type) {
|
||||
case int:
|
||||
formula1 = fmt.Sprintf("<formula1>%d</formula1>", v)
|
||||
case float64:
|
||||
if math.Abs(v) > math.MaxFloat32 {
|
||||
return ErrDataValidationRange
|
||||
}
|
||||
formula1 = fmt.Sprintf("<formula1>%.17g</formula1>", v)
|
||||
case string:
|
||||
formula1 = fmt.Sprintf("<formula1>%s</formula1>", v)
|
||||
default:
|
||||
return ErrParameterInvalid
|
||||
}
|
||||
switch v := f2.(type) {
|
||||
case int:
|
||||
formula2 = fmt.Sprintf("<formula2>%d</formula2>", v)
|
||||
case float64:
|
||||
if math.Abs(v) > math.MaxFloat32 {
|
||||
return ErrDataValidationRange
|
||||
}
|
||||
formula2 = fmt.Sprintf("<formula2>%.17g</formula2>", v)
|
||||
case string:
|
||||
formula2 = fmt.Sprintf("<formula2>%s</formula2>", v)
|
||||
default:
|
||||
return ErrParameterInvalid
|
||||
}
|
||||
dv.Formula1, dv.Formula2 = formula1, formula2
|
||||
dv.Type = convDataValidationType(t)
|
||||
dv.Operator = convDataValidationOperator(o)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSqrefDropList provides set data validation on a range with source
|
||||
// reference range of the worksheet by given data validation object and
|
||||
// worksheet name. The data validation object can be created by
|
||||
// NewDataValidation function. For example, set data validation on
|
||||
// Sheet1!A7:B8 with validation criteria source Sheet1!E1:E3 settings, create
|
||||
// in-cell dropdown by allowing list source:
|
||||
//
|
||||
// dv := excelize.NewDataValidation(true)
|
||||
// dv.Sqref = "A7:B8"
|
||||
// dv.SetSqrefDropList("$E$1:$E$3")
|
||||
// err := f.AddDataValidation("Sheet1", dv)
|
||||
func (dv *DataValidation) SetSqrefDropList(sqref string) {
|
||||
dv.Formula1 = fmt.Sprintf("<formula1>%s</formula1>", sqref)
|
||||
dv.Type = convDataValidationType(typeList)
|
||||
}
|
||||
|
||||
// SetSqref provides function to set data validation range in drop list.
|
||||
func (dv *DataValidation) SetSqref(sqref string) {
|
||||
if dv.Sqref == "" {
|
||||
dv.Sqref = sqref
|
||||
} else {
|
||||
dv.Sqref = fmt.Sprintf("%s %s", dv.Sqref, sqref)
|
||||
}
|
||||
}
|
||||
|
||||
// convDataValidationType get excel data validation type.
|
||||
func convDataValidationType(t DataValidationType) string {
|
||||
typeMap := map[DataValidationType]string{
|
||||
typeNone: "none",
|
||||
DataValidationTypeCustom: "custom",
|
||||
DataValidationTypeDate: "date",
|
||||
DataValidationTypeDecimal: "decimal",
|
||||
typeList: "list",
|
||||
DataValidationTypeTextLength: "textLength",
|
||||
DataValidationTypeTime: "time",
|
||||
DataValidationTypeWhole: "whole",
|
||||
}
|
||||
|
||||
return typeMap[t]
|
||||
}
|
||||
|
||||
// convDataValidationOperator get excel data validation operator.
|
||||
func convDataValidationOperator(o DataValidationOperator) string {
|
||||
typeMap := map[DataValidationOperator]string{
|
||||
DataValidationOperatorBetween: "between",
|
||||
DataValidationOperatorEqual: "equal",
|
||||
DataValidationOperatorGreaterThan: "greaterThan",
|
||||
DataValidationOperatorGreaterThanOrEqual: "greaterThanOrEqual",
|
||||
DataValidationOperatorLessThan: "lessThan",
|
||||
DataValidationOperatorLessThanOrEqual: "lessThanOrEqual",
|
||||
DataValidationOperatorNotBetween: "notBetween",
|
||||
DataValidationOperatorNotEqual: "notEqual",
|
||||
}
|
||||
|
||||
return typeMap[o]
|
||||
}
|
||||
|
||||
// AddDataValidation provides set data validation on a range of the worksheet
|
||||
// by given data validation object and worksheet name. The data validation
|
||||
// object can be created by NewDataValidation function.
|
||||
//
|
||||
// Example 1, set data validation on Sheet1!A1:B2 with validation criteria
|
||||
// settings, show error alert after invalid data is entered with "Stop" style
|
||||
// and custom title "error body":
|
||||
//
|
||||
// dv := excelize.NewDataValidation(true)
|
||||
// dv.Sqref = "A1:B2"
|
||||
// dv.SetRange(10, 20, excelize.DataValidationTypeWhole, excelize.DataValidationOperatorBetween)
|
||||
// dv.SetError(excelize.DataValidationErrorStyleStop, "error title", "error body")
|
||||
// err := f.AddDataValidation("Sheet1", dv)
|
||||
//
|
||||
// Example 2, set data validation on Sheet1!A3:B4 with validation criteria
|
||||
// settings, and show input message when cell is selected:
|
||||
//
|
||||
// dv = excelize.NewDataValidation(true)
|
||||
// dv.Sqref = "A3:B4"
|
||||
// dv.SetRange(10, 20, excelize.DataValidationTypeWhole, excelize.DataValidationOperatorGreaterThan)
|
||||
// dv.SetInput("input title", "input body")
|
||||
// err = f.AddDataValidation("Sheet1", dv)
|
||||
//
|
||||
// Example 3, set data validation on Sheet1!A5:B6 with validation criteria
|
||||
// settings, create in-cell dropdown by allowing list source:
|
||||
//
|
||||
// dv = excelize.NewDataValidation(true)
|
||||
// dv.Sqref = "A5:B6"
|
||||
// dv.SetDropList([]string{"1", "2", "3"})
|
||||
// err = f.AddDataValidation("Sheet1", dv)
|
||||
func (f *File) AddDataValidation(sheet string, dv *DataValidation) error {
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if nil == ws.DataValidations {
|
||||
ws.DataValidations = new(xlsxDataValidations)
|
||||
}
|
||||
ws.DataValidations.DataValidation = append(ws.DataValidations.DataValidation, dv)
|
||||
ws.DataValidations.Count = len(ws.DataValidations.DataValidation)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetDataValidations returns data validations list by given worksheet name.
|
||||
func (f *File) GetDataValidations(sheet string) ([]*DataValidation, error) {
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ws.DataValidations == nil || len(ws.DataValidations.DataValidation) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
return ws.DataValidations.DataValidation, err
|
||||
}
|
||||
|
||||
// DeleteDataValidation delete data validation by given worksheet name and
|
||||
// reference sequence. All data validations in the worksheet will be deleted
|
||||
// if not specify reference sequence parameter.
|
||||
func (f *File) DeleteDataValidation(sheet string, sqref ...string) error {
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ws.DataValidations == nil {
|
||||
return nil
|
||||
}
|
||||
if sqref == nil {
|
||||
ws.DataValidations = nil
|
||||
return nil
|
||||
}
|
||||
delCells, err := f.flatSqref(sqref[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dv := ws.DataValidations
|
||||
for i := 0; i < len(dv.DataValidation); i++ {
|
||||
var applySqref []string
|
||||
colCells, err := f.flatSqref(dv.DataValidation[i].Sqref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for col, cells := range delCells {
|
||||
for _, cell := range cells {
|
||||
idx := inCoordinates(colCells[col], cell)
|
||||
if idx != -1 {
|
||||
colCells[col] = append(colCells[col][:idx], colCells[col][idx+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, col := range colCells {
|
||||
applySqref = append(applySqref, f.squashSqref(col)...)
|
||||
}
|
||||
dv.DataValidation[i].Sqref = strings.Join(applySqref, " ")
|
||||
if len(applySqref) == 0 {
|
||||
dv.DataValidation = append(dv.DataValidation[:i], dv.DataValidation[i+1:]...)
|
||||
i--
|
||||
}
|
||||
}
|
||||
dv.Count = len(dv.DataValidation)
|
||||
if dv.Count == 0 {
|
||||
ws.DataValidations = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// squashSqref generates cell reference sequence by given cells coordinates list.
|
||||
func (f *File) squashSqref(cells [][]int) []string {
|
||||
if len(cells) == 1 {
|
||||
cell, _ := CoordinatesToCellName(cells[0][0], cells[0][1])
|
||||
return []string{cell}
|
||||
} else if len(cells) == 0 {
|
||||
return []string{}
|
||||
}
|
||||
var res []string
|
||||
l, r := 0, 0
|
||||
for i := 1; i < len(cells); i++ {
|
||||
if cells[i][0] == cells[r][0] && cells[i][1]-cells[r][1] > 1 {
|
||||
curr, _ := f.coordinatesToRangeRef(append(cells[l], cells[r]...))
|
||||
if l == r {
|
||||
curr, _ = CoordinatesToCellName(cells[l][0], cells[l][1])
|
||||
}
|
||||
res = append(res, curr)
|
||||
l, r = i, i
|
||||
} else {
|
||||
r++
|
||||
}
|
||||
}
|
||||
curr, _ := f.coordinatesToRangeRef(append(cells[l], cells[r]...))
|
||||
if l == r {
|
||||
curr, _ = CoordinatesToCellName(cells[l][0], cells[l][1])
|
||||
}
|
||||
return append(res, curr)
|
||||
}
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
nanosInADay = float64((24 * time.Hour) / time.Nanosecond)
|
||||
dayNanoseconds = 24 * time.Hour
|
||||
maxDuration = 290 * 364 * dayNanoseconds
|
||||
roundEpsilon = 1e-9
|
||||
)
|
||||
|
||||
var (
|
||||
daysInMonth = []int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
|
||||
excel1900Epoc = time.Date(1899, time.December, 30, 0, 0, 0, 0, time.UTC)
|
||||
excel1904Epoc = time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
excelMinTime1900 = time.Date(1899, time.December, 31, 0, 0, 0, 0, time.UTC)
|
||||
excelBuggyPeriodStart = time.Date(1900, time.March, 1, 0, 0, 0, 0, time.UTC).Add(-time.Nanosecond)
|
||||
)
|
||||
|
||||
// timeToExcelTime provides a function to convert time to Excel time.
|
||||
func timeToExcelTime(t time.Time, date1904 bool) (float64, error) {
|
||||
date := excelMinTime1900
|
||||
if date1904 {
|
||||
date = excel1904Epoc
|
||||
}
|
||||
if t.Before(date) {
|
||||
return 0, nil
|
||||
}
|
||||
tt, diff, result := t, t.Sub(date), 0.0
|
||||
for diff >= maxDuration {
|
||||
result += float64(maxDuration / dayNanoseconds)
|
||||
tt = tt.Add(-maxDuration)
|
||||
diff = tt.Sub(date)
|
||||
}
|
||||
|
||||
rem := diff % dayNanoseconds
|
||||
result += float64(diff-rem)/float64(dayNanoseconds) + float64(rem)/float64(dayNanoseconds)
|
||||
|
||||
// Excel dates after 28th February 1900 are actually one day out.
|
||||
// Excel behaves as though the date 29th February 1900 existed, which it didn't.
|
||||
// Microsoft intentionally included this bug in Excel so that it would remain compatible with the spreadsheet
|
||||
// program that had the majority market share at the time; Lotus 1-2-3.
|
||||
// https://www.myonlinetraininghub.com/excel-date-and-time
|
||||
if !date1904 && t.After(excelBuggyPeriodStart) {
|
||||
result++
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// shiftJulianToNoon provides a function to process julian date to noon.
|
||||
func shiftJulianToNoon(julianDays, julianFraction float64) (float64, float64) {
|
||||
switch {
|
||||
case -0.5 < julianFraction && julianFraction < 0.5:
|
||||
julianFraction += 0.5
|
||||
case julianFraction >= 0.5:
|
||||
julianDays++
|
||||
julianFraction -= 0.5
|
||||
case julianFraction <= -0.5:
|
||||
julianDays--
|
||||
julianFraction += 1.5
|
||||
}
|
||||
return julianDays, julianFraction
|
||||
}
|
||||
|
||||
// fractionOfADay provides a function to return the integer values for hour,
|
||||
// minutes, seconds and nanoseconds that comprised a given fraction of a day.
|
||||
// values would round to 1 us.
|
||||
func fractionOfADay(fraction float64) (hours, minutes, seconds, nanoseconds int) {
|
||||
const (
|
||||
c1us = 1e3
|
||||
c1s = 1e9
|
||||
c1day = 24 * 60 * 60 * c1s
|
||||
)
|
||||
|
||||
frac := int64(c1day*fraction + c1us/2)
|
||||
nanoseconds = int((frac%c1s)/c1us) * c1us
|
||||
frac /= c1s
|
||||
seconds = int(frac % 60)
|
||||
frac /= 60
|
||||
minutes = int(frac % 60)
|
||||
hours = int(frac / 60)
|
||||
return
|
||||
}
|
||||
|
||||
// julianDateToGregorianTime provides a function to convert julian date to
|
||||
// gregorian time.
|
||||
func julianDateToGregorianTime(part1, part2 float64) time.Time {
|
||||
part1I, part1F := math.Modf(part1)
|
||||
part2I, part2F := math.Modf(part2)
|
||||
julianDays := part1I + part2I
|
||||
julianFraction := part1F + part2F
|
||||
julianDays, julianFraction = shiftJulianToNoon(julianDays, julianFraction)
|
||||
day, month, year := doTheFliegelAndVanFlandernAlgorithm(int(julianDays))
|
||||
hours, minutes, seconds, nanoseconds := fractionOfADay(julianFraction)
|
||||
return time.Date(year, time.Month(month), day, hours, minutes, seconds, nanoseconds, time.UTC)
|
||||
}
|
||||
|
||||
// doTheFliegelAndVanFlandernAlgorithm; By this point generations of
|
||||
// programmers have repeated the algorithm sent to the editor of
|
||||
// "Communications of the ACM" in 1968 (published in CACM, volume 11, number
|
||||
// 10, October 1968, p.657). None of those programmers seems to have found it
|
||||
// necessary to explain the constants or variable names set out by Henry F.
|
||||
// Fliegel and Thomas C. Van Flandern. Maybe one day I'll buy that jounal and
|
||||
// expand an explanation here - that day is not today.
|
||||
func doTheFliegelAndVanFlandernAlgorithm(jd int) (day, month, year int) {
|
||||
l := jd + 68569
|
||||
n := (4 * l) / 146097
|
||||
l = l - (146097*n+3)/4
|
||||
i := (4000 * (l + 1)) / 1461001
|
||||
l = l - (1461*i)/4 + 31
|
||||
j := (80 * l) / 2447
|
||||
d := l - (2447*j)/80
|
||||
l = j / 11
|
||||
m := j + 2 - (12 * l)
|
||||
y := 100*(n-49) + i + l
|
||||
return d, m, y
|
||||
}
|
||||
|
||||
// timeFromExcelTime provides a function to convert an excelTime
|
||||
// representation (stored as a floating point number) to a time.Time.
|
||||
func timeFromExcelTime(excelTime float64, date1904 bool) time.Time {
|
||||
var date time.Time
|
||||
wholeDaysPart := int(excelTime)
|
||||
// Excel uses Julian dates prior to March 1st 1900, and Gregorian
|
||||
// thereafter.
|
||||
if wholeDaysPart <= 61 {
|
||||
const OFFSET1900 = 15018.0
|
||||
const OFFSET1904 = 16480.0
|
||||
const MJD0 float64 = 2400000.5
|
||||
var date time.Time
|
||||
if date1904 {
|
||||
date = julianDateToGregorianTime(MJD0, excelTime+OFFSET1904)
|
||||
} else {
|
||||
date = julianDateToGregorianTime(MJD0, excelTime+OFFSET1900)
|
||||
}
|
||||
return date
|
||||
}
|
||||
floatPart := excelTime - float64(wholeDaysPart) + roundEpsilon
|
||||
if date1904 {
|
||||
date = excel1904Epoc
|
||||
} else {
|
||||
date = excel1900Epoc
|
||||
}
|
||||
durationPart := time.Duration(nanosInADay * floatPart)
|
||||
date = date.AddDate(0, 0, wholeDaysPart).Add(durationPart)
|
||||
if date.Nanosecond()/1e6 > 500 {
|
||||
return date.Round(time.Second)
|
||||
}
|
||||
return date.Truncate(time.Second)
|
||||
}
|
||||
|
||||
// ExcelDateToTime converts a float-based excel date representation to a time.Time.
|
||||
func ExcelDateToTime(excelDate float64, use1904Format bool) (time.Time, error) {
|
||||
if excelDate < 0 {
|
||||
return time.Time{}, newInvalidExcelDateError(excelDate)
|
||||
}
|
||||
return timeFromExcelTime(excelDate, use1904Format), nil
|
||||
}
|
||||
|
||||
// isLeapYear determine if leap year for a given year.
|
||||
func isLeapYear(y int) bool {
|
||||
if y == y/400*400 {
|
||||
return true
|
||||
}
|
||||
if y == y/100*100 {
|
||||
return false
|
||||
}
|
||||
return y == y/4*4
|
||||
}
|
||||
|
||||
// getDaysInMonth provides a function to get the days by a given year and
|
||||
// month number.
|
||||
func getDaysInMonth(y, m int) int {
|
||||
if m == 2 && isLeapYear(y) {
|
||||
return 29
|
||||
}
|
||||
return daysInMonth[m-1]
|
||||
}
|
||||
|
||||
// validateDate provides a function to validate if a valid date by a given
|
||||
// year, month, and day number.
|
||||
func validateDate(y, m, d int) bool {
|
||||
if m < 1 || m > 12 {
|
||||
return false
|
||||
}
|
||||
if d < 1 {
|
||||
return false
|
||||
}
|
||||
return d <= getDaysInMonth(y, m)
|
||||
}
|
||||
|
||||
// formatYear converts the given year number into a 4-digit format.
|
||||
func formatYear(y int) int {
|
||||
if y < 1900 {
|
||||
if y < 30 {
|
||||
y += 2000
|
||||
} else {
|
||||
y += 1900
|
||||
}
|
||||
}
|
||||
return y
|
||||
}
|
||||
+273
@@ -0,0 +1,273 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// SetAppProps provides a function to set document application properties. The
|
||||
// properties that can be set are:
|
||||
//
|
||||
// Property | Description
|
||||
// -------------------+--------------------------------------------------------------------------
|
||||
// Application | The name of the application that created this document.
|
||||
// |
|
||||
// ScaleCrop | Indicates the display mode of the document thumbnail. Set this element
|
||||
// | to 'true' to enable scaling of the document thumbnail to the display. Set
|
||||
// | this element to 'false' to enable cropping of the document thumbnail to
|
||||
// | show only sections that will fit the display.
|
||||
// |
|
||||
// DocSecurity | Security level of a document as a numeric value. Document security is
|
||||
// | defined as:
|
||||
// | 1 - Document is password protected.
|
||||
// | 2 - Document is recommended to be opened as read-only.
|
||||
// | 3 - Document is enforced to be opened as read-only.
|
||||
// | 4 - Document is locked for annotation.
|
||||
// |
|
||||
// Company | The name of a company associated with the document.
|
||||
// |
|
||||
// LinksUpToDate | Indicates whether hyperlinks in a document are up-to-date. Set this
|
||||
// | element to 'true' to indicate that hyperlinks are updated. Set this
|
||||
// | element to 'false' to indicate that hyperlinks are outdated.
|
||||
// |
|
||||
// HyperlinksChanged | Specifies that one or more hyperlinks in this part were updated
|
||||
// | exclusively in this part by a producer. The next producer to open this
|
||||
// | document shall update the hyperlink relationships with the new
|
||||
// | hyperlinks specified in this part.
|
||||
// |
|
||||
// AppVersion | Specifies the version of the application which produced this document.
|
||||
// | The content of this element shall be of the form XX.YYYY where X and Y
|
||||
// | represent numerical values, or the document shall be considered
|
||||
// | non-conformant.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// err := f.SetAppProps(&excelize.AppProperties{
|
||||
// Application: "Microsoft Excel",
|
||||
// ScaleCrop: true,
|
||||
// DocSecurity: 3,
|
||||
// Company: "Company Name",
|
||||
// LinksUpToDate: true,
|
||||
// HyperlinksChanged: true,
|
||||
// AppVersion: "16.0000",
|
||||
// })
|
||||
func (f *File) SetAppProps(appProperties *AppProperties) error {
|
||||
var (
|
||||
app *xlsxProperties
|
||||
err error
|
||||
field string
|
||||
fields []string
|
||||
immutable, mutable reflect.Value
|
||||
output []byte
|
||||
)
|
||||
app = new(xlsxProperties)
|
||||
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsApp)))).
|
||||
Decode(app); err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
fields = []string{"Application", "ScaleCrop", "DocSecurity", "Company", "LinksUpToDate", "HyperlinksChanged", "AppVersion"}
|
||||
immutable, mutable = reflect.ValueOf(*appProperties), reflect.ValueOf(app).Elem()
|
||||
for _, field = range fields {
|
||||
immutableField := immutable.FieldByName(field)
|
||||
switch immutableField.Kind() {
|
||||
case reflect.Bool:
|
||||
mutable.FieldByName(field).SetBool(immutableField.Bool())
|
||||
case reflect.Int:
|
||||
mutable.FieldByName(field).SetInt(immutableField.Int())
|
||||
default:
|
||||
mutable.FieldByName(field).SetString(immutableField.String())
|
||||
}
|
||||
}
|
||||
app.Vt = NameSpaceDocumentPropertiesVariantTypes.Value
|
||||
output, err = xml.Marshal(app)
|
||||
f.saveFileList(defaultXMLPathDocPropsApp, output)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetAppProps provides a function to get document application properties.
|
||||
func (f *File) GetAppProps() (ret *AppProperties, err error) {
|
||||
app := new(xlsxProperties)
|
||||
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsApp)))).
|
||||
Decode(app); err != nil && err != io.EOF {
|
||||
return
|
||||
}
|
||||
ret, err = &AppProperties{
|
||||
Application: app.Application,
|
||||
ScaleCrop: app.ScaleCrop,
|
||||
DocSecurity: app.DocSecurity,
|
||||
Company: app.Company,
|
||||
LinksUpToDate: app.LinksUpToDate,
|
||||
HyperlinksChanged: app.HyperlinksChanged,
|
||||
AppVersion: app.AppVersion,
|
||||
}, nil
|
||||
return
|
||||
}
|
||||
|
||||
// SetDocProps provides a function to set document core properties. The
|
||||
// properties that can be set are:
|
||||
//
|
||||
// Property | Description
|
||||
// ----------------+-----------------------------------------------------------
|
||||
// Title | The name given to the resource.
|
||||
// |
|
||||
// Subject | The topic of the content of the resource.
|
||||
// |
|
||||
// Creator | An entity primarily responsible for making the content of
|
||||
// | the resource.
|
||||
// |
|
||||
// Keywords | A delimited set of keywords to support searching and
|
||||
// | indexing. This is typically a list of terms that are not
|
||||
// | available elsewhere in the properties.
|
||||
// |
|
||||
// Description | An explanation of the content of the resource.
|
||||
// |
|
||||
// LastModifiedBy | The user who performed the last modification. The
|
||||
// | identification is environment-specific.
|
||||
// |
|
||||
// Language | The language of the intellectual content of the resource.
|
||||
// |
|
||||
// Identifier | An unambiguous reference to the resource within a given
|
||||
// | context.
|
||||
// |
|
||||
// Revision | The topic of the content of the resource.
|
||||
// |
|
||||
// ContentStatus | The status of the content. For example: Values might
|
||||
// | include "Draft", "Reviewed" and "Final"
|
||||
// |
|
||||
// Category | A categorization of the content of this package.
|
||||
// |
|
||||
// Version | The version number. This value is set by the user or by
|
||||
// | the application.
|
||||
// |
|
||||
// Created | The created time of the content of the resource which
|
||||
// | represent in ISO 8601 UTC format, for example
|
||||
// | "2019-06-04T22:00:10Z".
|
||||
// |
|
||||
// Modified | The modified time of the content of the resource which
|
||||
// | represent in ISO 8601 UTC format, for example
|
||||
// | "2019-06-04T22:00:10Z".
|
||||
// |
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// err := f.SetDocProps(&excelize.DocProperties{
|
||||
// Category: "category",
|
||||
// ContentStatus: "Draft",
|
||||
// Created: "2019-06-04T22:00:10Z",
|
||||
// Creator: "Go Excelize",
|
||||
// Description: "This file created by Go Excelize",
|
||||
// Identifier: "xlsx",
|
||||
// Keywords: "Spreadsheet",
|
||||
// LastModifiedBy: "Go Author",
|
||||
// Modified: "2019-06-04T22:00:10Z",
|
||||
// Revision: "0",
|
||||
// Subject: "Test Subject",
|
||||
// Title: "Test Title",
|
||||
// Language: "en-US",
|
||||
// Version: "1.0.0",
|
||||
// })
|
||||
func (f *File) SetDocProps(docProperties *DocProperties) error {
|
||||
var (
|
||||
core *decodeCoreProperties
|
||||
err error
|
||||
field, val string
|
||||
fields []string
|
||||
immutable, mutable reflect.Value
|
||||
newProps *xlsxCoreProperties
|
||||
output []byte
|
||||
)
|
||||
|
||||
core = new(decodeCoreProperties)
|
||||
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsCore)))).
|
||||
Decode(core); err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
newProps = &xlsxCoreProperties{
|
||||
Dc: NameSpaceDublinCore,
|
||||
Dcterms: NameSpaceDublinCoreTerms,
|
||||
Dcmitype: NameSpaceDublinCoreMetadataInitiative,
|
||||
XSI: NameSpaceXMLSchemaInstance,
|
||||
Title: core.Title,
|
||||
Subject: core.Subject,
|
||||
Creator: core.Creator,
|
||||
Keywords: core.Keywords,
|
||||
Description: core.Description,
|
||||
LastModifiedBy: core.LastModifiedBy,
|
||||
Language: core.Language,
|
||||
Identifier: core.Identifier,
|
||||
Revision: core.Revision,
|
||||
ContentStatus: core.ContentStatus,
|
||||
Category: core.Category,
|
||||
Version: core.Version,
|
||||
}
|
||||
if core.Created != nil {
|
||||
newProps.Created = &xlsxDcTerms{Type: core.Created.Type, Text: core.Created.Text}
|
||||
}
|
||||
if core.Modified != nil {
|
||||
newProps.Modified = &xlsxDcTerms{Type: core.Modified.Type, Text: core.Modified.Text}
|
||||
}
|
||||
fields = []string{
|
||||
"Category", "ContentStatus", "Creator", "Description", "Identifier", "Keywords",
|
||||
"LastModifiedBy", "Revision", "Subject", "Title", "Language", "Version",
|
||||
}
|
||||
immutable, mutable = reflect.ValueOf(*docProperties), reflect.ValueOf(newProps).Elem()
|
||||
for _, field = range fields {
|
||||
if val = immutable.FieldByName(field).String(); val != "" {
|
||||
mutable.FieldByName(field).SetString(val)
|
||||
}
|
||||
}
|
||||
if docProperties.Created != "" {
|
||||
newProps.Created = &xlsxDcTerms{Type: "dcterms:W3CDTF", Text: docProperties.Created}
|
||||
}
|
||||
if docProperties.Modified != "" {
|
||||
newProps.Modified = &xlsxDcTerms{Type: "dcterms:W3CDTF", Text: docProperties.Modified}
|
||||
}
|
||||
output, err = xml.Marshal(newProps)
|
||||
f.saveFileList(defaultXMLPathDocPropsCore, output)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// GetDocProps provides a function to get document core properties.
|
||||
func (f *File) GetDocProps() (ret *DocProperties, err error) {
|
||||
core := new(decodeCoreProperties)
|
||||
|
||||
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsCore)))).
|
||||
Decode(core); err != nil && err != io.EOF {
|
||||
return
|
||||
}
|
||||
ret, err = &DocProperties{
|
||||
Category: core.Category,
|
||||
ContentStatus: core.ContentStatus,
|
||||
Creator: core.Creator,
|
||||
Description: core.Description,
|
||||
Identifier: core.Identifier,
|
||||
Keywords: core.Keywords,
|
||||
LastModifiedBy: core.LastModifiedBy,
|
||||
Revision: core.Revision,
|
||||
Subject: core.Subject,
|
||||
Title: core.Title,
|
||||
Language: core.Language,
|
||||
Version: core.Version,
|
||||
}, nil
|
||||
if core.Created != nil {
|
||||
ret.Created = core.Created.Text
|
||||
}
|
||||
if core.Modified != nil {
|
||||
ret.Modified = core.Modified.Text
|
||||
}
|
||||
return
|
||||
}
|
||||
+1439
File diff suppressed because it is too large
Load Diff
+250
@@ -0,0 +1,250 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// newInvalidColumnNameError defined the error message on receiving the
|
||||
// invalid column name.
|
||||
func newInvalidColumnNameError(col string) error {
|
||||
return fmt.Errorf("invalid column name %q", col)
|
||||
}
|
||||
|
||||
// newInvalidRowNumberError defined the error message on receiving the invalid
|
||||
// row number.
|
||||
func newInvalidRowNumberError(row int) error {
|
||||
return fmt.Errorf("invalid row number %d", row)
|
||||
}
|
||||
|
||||
// newInvalidCellNameError defined the error message on receiving the invalid
|
||||
// cell name.
|
||||
func newInvalidCellNameError(cell string) error {
|
||||
return fmt.Errorf("invalid cell name %q", cell)
|
||||
}
|
||||
|
||||
// newInvalidExcelDateError defined the error message on receiving the data
|
||||
// with negative values.
|
||||
func newInvalidExcelDateError(dateValue float64) error {
|
||||
return fmt.Errorf("invalid date value %f, negative values are not supported", dateValue)
|
||||
}
|
||||
|
||||
// newInvalidTableNameError defined the error message on receiving the invalid
|
||||
// table name.
|
||||
func newInvalidTableNameError(name string) error {
|
||||
return fmt.Errorf("invalid table name %q", name)
|
||||
}
|
||||
|
||||
// newUnsupportedChartType defined the error message on receiving the chart
|
||||
// type are unsupported.
|
||||
func newUnsupportedChartType(chartType ChartType) error {
|
||||
return fmt.Errorf("unsupported chart type %d", chartType)
|
||||
}
|
||||
|
||||
// newUnzipSizeLimitError defined the error message on unzip size exceeds the
|
||||
// limit.
|
||||
func newUnzipSizeLimitError(unzipSizeLimit int64) error {
|
||||
return fmt.Errorf("unzip size exceeds the %d bytes limit", unzipSizeLimit)
|
||||
}
|
||||
|
||||
// newInvalidStyleID defined the error message on receiving the invalid style
|
||||
// ID.
|
||||
func newInvalidStyleID(styleID int) error {
|
||||
return fmt.Errorf("invalid style ID %d", styleID)
|
||||
}
|
||||
|
||||
// newFieldLengthError defined the error message on receiving the field length
|
||||
// overflow.
|
||||
func newFieldLengthError(name string) error {
|
||||
return fmt.Errorf("field %s must be less than or equal to 255 characters", name)
|
||||
}
|
||||
|
||||
// newCellNameToCoordinatesError defined the error message on converts
|
||||
// alphanumeric cell name to coordinates.
|
||||
func newCellNameToCoordinatesError(cell string, err error) error {
|
||||
return fmt.Errorf("cannot convert cell %q to coordinates: %v", cell, err)
|
||||
}
|
||||
|
||||
// newNoExistSheetError defined the error message on receiving the non existing
|
||||
// sheet name.
|
||||
func newNoExistSheetError(name string) error {
|
||||
return fmt.Errorf("sheet %s does not exist", name)
|
||||
}
|
||||
|
||||
// newNotWorksheetError defined the error message on receiving a sheet which
|
||||
// not a worksheet.
|
||||
func newNotWorksheetError(name string) error {
|
||||
return fmt.Errorf("sheet %s is not a worksheet", name)
|
||||
}
|
||||
|
||||
// newStreamSetRowError defined the error message on the stream writer
|
||||
// receiving the non-ascending row number.
|
||||
func newStreamSetRowError(row int) error {
|
||||
return fmt.Errorf("row %d has already been written", row)
|
||||
}
|
||||
|
||||
// newViewIdxError defined the error message on receiving a invalid sheet view
|
||||
// index.
|
||||
func newViewIdxError(viewIndex int) error {
|
||||
return fmt.Errorf("view index %d out of range", viewIndex)
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrStreamSetColWidth defined the error message on set column width in
|
||||
// stream writing mode.
|
||||
ErrStreamSetColWidth = errors.New("must call the SetColWidth function before the SetRow function")
|
||||
// ErrStreamSetPanes defined the error message on set panes in stream
|
||||
// writing mode.
|
||||
ErrStreamSetPanes = errors.New("must call the SetPanes function before the SetRow function")
|
||||
// ErrColumnNumber defined the error message on receive an invalid column
|
||||
// number.
|
||||
ErrColumnNumber = fmt.Errorf(`the column number must be greater than or equal to %d and less than or equal to %d`, MinColumns, MaxColumns)
|
||||
// ErrColumnWidth defined the error message on receive an invalid column
|
||||
// width.
|
||||
ErrColumnWidth = fmt.Errorf("the width of the column must be less than or equal to %d characters", MaxColumnWidth)
|
||||
// ErrOutlineLevel defined the error message on receive an invalid outline
|
||||
// level number.
|
||||
ErrOutlineLevel = errors.New("invalid outline level")
|
||||
// ErrCoordinates defined the error message on invalid coordinates tuples
|
||||
// length.
|
||||
ErrCoordinates = errors.New("coordinates length must be 4")
|
||||
// ErrExistsSheet defined the error message on given sheet already exists.
|
||||
ErrExistsSheet = errors.New("the same name sheet already exists")
|
||||
// ErrTotalSheetHyperlinks defined the error message on hyperlinks count
|
||||
// overflow.
|
||||
ErrTotalSheetHyperlinks = errors.New("over maximum limit hyperlinks in a worksheet")
|
||||
// ErrInvalidFormula defined the error message on receive an invalid
|
||||
// formula.
|
||||
ErrInvalidFormula = errors.New("formula not valid")
|
||||
// ErrAddVBAProject defined the error message on add the VBA project in
|
||||
// the workbook.
|
||||
ErrAddVBAProject = errors.New("unsupported VBA project")
|
||||
// ErrMaxRows defined the error message on receive a row number exceeds maximum limit.
|
||||
ErrMaxRows = errors.New("row number exceeds maximum limit")
|
||||
// ErrMaxRowHeight defined the error message on receive an invalid row
|
||||
// height.
|
||||
ErrMaxRowHeight = fmt.Errorf("the height of the row must be less than or equal to %d points", MaxRowHeight)
|
||||
// ErrImgExt defined the error message on receive an unsupported image
|
||||
// extension.
|
||||
ErrImgExt = errors.New("unsupported image extension")
|
||||
// ErrWorkbookFileFormat defined the error message on receive an
|
||||
// unsupported workbook file format.
|
||||
ErrWorkbookFileFormat = errors.New("unsupported workbook file format")
|
||||
// ErrMaxFilePathLength defined the error message on receive the file path
|
||||
// length overflow.
|
||||
ErrMaxFilePathLength = errors.New("file path length exceeds maximum limit")
|
||||
// ErrUnknownEncryptMechanism defined the error message on unsupported
|
||||
// encryption mechanism.
|
||||
ErrUnknownEncryptMechanism = errors.New("unknown encryption mechanism")
|
||||
// ErrUnsupportedEncryptMechanism defined the error message on unsupported
|
||||
// encryption mechanism.
|
||||
ErrUnsupportedEncryptMechanism = errors.New("unsupported encryption mechanism")
|
||||
// ErrUnsupportedHashAlgorithm defined the error message on unsupported
|
||||
// hash algorithm.
|
||||
ErrUnsupportedHashAlgorithm = errors.New("unsupported hash algorithm")
|
||||
// ErrUnsupportedNumberFormat defined the error message on unsupported number format
|
||||
// expression.
|
||||
ErrUnsupportedNumberFormat = errors.New("unsupported number format token")
|
||||
// ErrPasswordLengthInvalid defined the error message on invalid password
|
||||
// length.
|
||||
ErrPasswordLengthInvalid = errors.New("password length invalid")
|
||||
// ErrParameterRequired defined the error message on receive the empty
|
||||
// parameter.
|
||||
ErrParameterRequired = errors.New("parameter is required")
|
||||
// ErrParameterInvalid defined the error message on receive the invalid
|
||||
// parameter.
|
||||
ErrParameterInvalid = errors.New("parameter is invalid")
|
||||
// ErrDefinedNameScope defined the error message on not found defined name
|
||||
// in the given scope.
|
||||
ErrDefinedNameScope = errors.New("no defined name on the scope")
|
||||
// ErrDefinedNameDuplicate defined the error message on the same name
|
||||
// already exists on the scope.
|
||||
ErrDefinedNameDuplicate = errors.New("the same name already exists on the scope")
|
||||
// ErrCustomNumFmt defined the error message on receive the empty custom number format.
|
||||
ErrCustomNumFmt = errors.New("custom number format can not be empty")
|
||||
// ErrFontLength defined the error message on the length of the font
|
||||
// family name overflow.
|
||||
ErrFontLength = fmt.Errorf("the length of the font family name must be less than or equal to %d", MaxFontFamilyLength)
|
||||
// ErrFontSize defined the error message on the size of the font is invalid.
|
||||
ErrFontSize = fmt.Errorf("font size must be between %d and %d points", MinFontSize, MaxFontSize)
|
||||
// ErrSheetIdx defined the error message on receive the invalid worksheet
|
||||
// index.
|
||||
ErrSheetIdx = errors.New("invalid worksheet index")
|
||||
// ErrUnprotectSheet defined the error message on worksheet has set no
|
||||
// protection.
|
||||
ErrUnprotectSheet = errors.New("worksheet has set no protect")
|
||||
// ErrUnprotectSheetPassword defined the error message on remove sheet
|
||||
// protection with password verification failed.
|
||||
ErrUnprotectSheetPassword = errors.New("worksheet protect password not match")
|
||||
// ErrGroupSheets defined the error message on group sheets.
|
||||
ErrGroupSheets = errors.New("group worksheet must contain an active worksheet")
|
||||
// ErrDataValidationFormulaLength defined the error message for receiving a
|
||||
// data validation formula length that exceeds the limit.
|
||||
ErrDataValidationFormulaLength = fmt.Errorf("data validation must be 0-%d characters", MaxFieldLength)
|
||||
// ErrDataValidationRange defined the error message on set decimal range
|
||||
// exceeds limit.
|
||||
ErrDataValidationRange = errors.New("data validation range exceeds limit")
|
||||
// ErrCellCharsLength defined the error message for receiving a cell
|
||||
// characters length that exceeds the limit.
|
||||
ErrCellCharsLength = fmt.Errorf("cell value must be 0-%d characters", TotalCellChars)
|
||||
// ErrOptionsUnzipSizeLimit defined the error message for receiving
|
||||
// invalid UnzipSizeLimit and UnzipXMLSizeLimit.
|
||||
ErrOptionsUnzipSizeLimit = errors.New("the value of UnzipSizeLimit should be greater than or equal to UnzipXMLSizeLimit")
|
||||
// ErrSave defined the error message for saving file.
|
||||
ErrSave = errors.New("no path defined for file, consider File.WriteTo or File.Write")
|
||||
// ErrAttrValBool defined the error message on marshal and unmarshal
|
||||
// boolean type XML attribute.
|
||||
ErrAttrValBool = errors.New("unexpected child of attrValBool")
|
||||
// ErrSparklineType defined the error message on receive the invalid
|
||||
// sparkline Type parameters.
|
||||
ErrSparklineType = errors.New("parameter 'Type' must be 'line', 'column' or 'win_loss'")
|
||||
// ErrSparklineLocation defined the error message on missing Location
|
||||
// parameters
|
||||
ErrSparklineLocation = errors.New("parameter 'Location' is required")
|
||||
// ErrSparklineRange defined the error message on missing sparkline Range
|
||||
// parameters
|
||||
ErrSparklineRange = errors.New("parameter 'Range' is required")
|
||||
// ErrSparkline defined the error message on receive the invalid sparkline
|
||||
// parameters.
|
||||
ErrSparkline = errors.New("must have the same number of 'Location' and 'Range' parameters")
|
||||
// ErrSparklineStyle defined the error message on receive the invalid
|
||||
// sparkline Style parameters.
|
||||
ErrSparklineStyle = errors.New("parameter 'Style' must between 0-35")
|
||||
// ErrWorkbookPassword defined the error message on receiving the incorrect
|
||||
// workbook password.
|
||||
ErrWorkbookPassword = errors.New("the supplied open workbook password is not correct")
|
||||
// ErrSheetNameInvalid defined the error message on receive the sheet name
|
||||
// contains invalid characters.
|
||||
ErrSheetNameInvalid = errors.New("the sheet can not contain any of the characters :\\/?*[or]")
|
||||
// ErrSheetNameSingleQuote defined the error message on the first or last
|
||||
// character of the sheet name was a single quote.
|
||||
ErrSheetNameSingleQuote = errors.New("the first or last character of the sheet name can not be a single quote")
|
||||
// ErrSheetNameBlank defined the error message on receive the blank sheet
|
||||
// name.
|
||||
ErrSheetNameBlank = errors.New("the sheet name can not be blank")
|
||||
// ErrSheetNameLength defined the error message on receiving the sheet
|
||||
// name length exceeds the limit.
|
||||
ErrSheetNameLength = fmt.Errorf("the sheet name length exceeds the %d characters limit", MaxSheetNameLength)
|
||||
// ErrTableNameLength defined the error message on receiving the table name
|
||||
// length exceeds the limit.
|
||||
ErrTableNameLength = fmt.Errorf("the table name length exceeds the %d characters limit", MaxFieldLength)
|
||||
// ErrCellStyles defined the error message on cell styles exceeds the limit.
|
||||
ErrCellStyles = fmt.Errorf("the cell styles exceeds the %d limit", MaxCellStyles)
|
||||
// ErrUnprotectWorkbook defined the error message on workbook has set no
|
||||
// protection.
|
||||
ErrUnprotectWorkbook = errors.New("workbook has set no protect")
|
||||
// ErrUnprotectWorkbookPassword defined the error message on remove workbook
|
||||
// protection with password verification failed.
|
||||
ErrUnprotectWorkbookPassword = errors.New("workbook protect password not match")
|
||||
)
|
||||
+547
@@ -0,0 +1,547 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
//
|
||||
// See https://xuri.me/excelize for more information about this package.
|
||||
package excelize
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/net/html/charset"
|
||||
)
|
||||
|
||||
// File define a populated spreadsheet file struct.
|
||||
type File struct {
|
||||
sync.Mutex
|
||||
options *Options
|
||||
xmlAttr map[string][]xml.Attr
|
||||
checked map[string]bool
|
||||
sheetMap map[string]string
|
||||
streams map[string]*StreamWriter
|
||||
tempFiles sync.Map
|
||||
sharedStringsMap map[string]int
|
||||
sharedStringItem [][]uint
|
||||
sharedStringTemp *os.File
|
||||
CalcChain *xlsxCalcChain
|
||||
Comments map[string]*xlsxComments
|
||||
ContentTypes *xlsxTypes
|
||||
Drawings sync.Map
|
||||
Path string
|
||||
SharedStrings *xlsxSST
|
||||
Sheet sync.Map
|
||||
SheetCount int
|
||||
Styles *xlsxStyleSheet
|
||||
Theme *xlsxTheme
|
||||
DecodeVMLDrawing map[string]*decodeVmlDrawing
|
||||
VMLDrawing map[string]*vmlDrawing
|
||||
WorkBook *xlsxWorkbook
|
||||
Relationships sync.Map
|
||||
Pkg sync.Map
|
||||
CharsetReader charsetTranscoderFn
|
||||
}
|
||||
|
||||
// charsetTranscoderFn set user-defined codepage transcoder function for open
|
||||
// the spreadsheet from non-UTF-8 encoding.
|
||||
type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, err error)
|
||||
|
||||
// Options define the options for open and reading spreadsheet.
|
||||
//
|
||||
// MaxCalcIterations specifies the maximum iterations for iterative
|
||||
// calculation, the default value is 0.
|
||||
//
|
||||
// Password specifies the password of the spreadsheet in plain text.
|
||||
//
|
||||
// RawCellValue specifies if apply the number format for the cell value or get
|
||||
// the raw value.
|
||||
//
|
||||
// UnzipSizeLimit specifies the unzip size limit in bytes on open the
|
||||
// spreadsheet, this value should be greater than or equal to
|
||||
// UnzipXMLSizeLimit, the default size limit is 16GB.
|
||||
//
|
||||
// UnzipXMLSizeLimit specifies the memory limit on unzipping worksheet and
|
||||
// shared string table in bytes, worksheet XML will be extracted to system
|
||||
// temporary directory when the file size is over this value, this value
|
||||
// should be less than or equal to UnzipSizeLimit, the default value is
|
||||
// 16MB.
|
||||
type Options struct {
|
||||
MaxCalcIterations uint
|
||||
Password string
|
||||
RawCellValue bool
|
||||
UnzipSizeLimit int64
|
||||
UnzipXMLSizeLimit int64
|
||||
}
|
||||
|
||||
// OpenFile take the name of an spreadsheet file and returns a populated
|
||||
// spreadsheet file struct for it. For example, open spreadsheet with
|
||||
// password protection:
|
||||
//
|
||||
// f, err := excelize.OpenFile("Book1.xlsx", excelize.Options{Password: "password"})
|
||||
//
|
||||
// Close the file by Close function after opening the spreadsheet.
|
||||
func OpenFile(filename string, opts ...Options) (*File, error) {
|
||||
file, err := os.Open(filepath.Clean(filename))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err := OpenReader(file, opts...)
|
||||
if err != nil {
|
||||
closeErr := file.Close()
|
||||
if closeErr == nil {
|
||||
return f, err
|
||||
}
|
||||
return f, closeErr
|
||||
}
|
||||
f.Path = filename
|
||||
return f, file.Close()
|
||||
}
|
||||
|
||||
// newFile is object builder
|
||||
func newFile() *File {
|
||||
return &File{
|
||||
options: &Options{UnzipSizeLimit: UnzipSizeLimit, UnzipXMLSizeLimit: StreamChunkSize},
|
||||
xmlAttr: make(map[string][]xml.Attr),
|
||||
checked: make(map[string]bool),
|
||||
sheetMap: make(map[string]string),
|
||||
tempFiles: sync.Map{},
|
||||
Comments: make(map[string]*xlsxComments),
|
||||
Drawings: sync.Map{},
|
||||
sharedStringsMap: make(map[string]int),
|
||||
Sheet: sync.Map{},
|
||||
DecodeVMLDrawing: make(map[string]*decodeVmlDrawing),
|
||||
VMLDrawing: make(map[string]*vmlDrawing),
|
||||
Relationships: sync.Map{},
|
||||
CharsetReader: charset.NewReaderLabel,
|
||||
}
|
||||
}
|
||||
|
||||
// checkOpenReaderOptions check and validate options field value for open
|
||||
// reader.
|
||||
func (f *File) checkOpenReaderOptions() error {
|
||||
if f.options.UnzipSizeLimit == 0 {
|
||||
f.options.UnzipSizeLimit = UnzipSizeLimit
|
||||
if f.options.UnzipXMLSizeLimit > f.options.UnzipSizeLimit {
|
||||
f.options.UnzipSizeLimit = f.options.UnzipXMLSizeLimit
|
||||
}
|
||||
}
|
||||
if f.options.UnzipXMLSizeLimit == 0 {
|
||||
f.options.UnzipXMLSizeLimit = StreamChunkSize
|
||||
if f.options.UnzipSizeLimit < f.options.UnzipXMLSizeLimit {
|
||||
f.options.UnzipXMLSizeLimit = f.options.UnzipSizeLimit
|
||||
}
|
||||
}
|
||||
if f.options.UnzipXMLSizeLimit > f.options.UnzipSizeLimit {
|
||||
return ErrOptionsUnzipSizeLimit
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OpenReader read data stream from io.Reader and return a populated
|
||||
// spreadsheet file.
|
||||
func OpenReader(r io.Reader, opts ...Options) (*File, error) {
|
||||
b, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f := newFile()
|
||||
f.options = getOptions(opts...)
|
||||
if err = f.checkOpenReaderOptions(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if bytes.Contains(b, oleIdentifier) {
|
||||
if b, err = Decrypt(b, f.options); err != nil {
|
||||
return nil, ErrWorkbookFileFormat
|
||||
}
|
||||
}
|
||||
zr, err := zip.NewReader(bytes.NewReader(b), int64(len(b)))
|
||||
if err != nil {
|
||||
if len(f.options.Password) > 0 {
|
||||
return nil, ErrWorkbookPassword
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
file, sheetCount, err := f.ReadZipReader(zr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.SheetCount = sheetCount
|
||||
for k, v := range file {
|
||||
f.Pkg.Store(k, v)
|
||||
}
|
||||
if f.CalcChain, err = f.calcChainReader(); err != nil {
|
||||
return f, err
|
||||
}
|
||||
if f.sheetMap, err = f.getSheetMap(); err != nil {
|
||||
return f, err
|
||||
}
|
||||
if f.Styles, err = f.stylesReader(); err != nil {
|
||||
return f, err
|
||||
}
|
||||
f.Theme, err = f.themeReader()
|
||||
return f, err
|
||||
}
|
||||
|
||||
// getOptions provides a function to parse the optional settings for open
|
||||
// and reading spreadsheet.
|
||||
func getOptions(opts ...Options) *Options {
|
||||
options := &Options{}
|
||||
for _, opt := range opts {
|
||||
options = &opt
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
// CharsetTranscoder Set user defined codepage transcoder function for open
|
||||
// XLSX from non UTF-8 encoding.
|
||||
func (f *File) CharsetTranscoder(fn charsetTranscoderFn) *File { f.CharsetReader = fn; return f }
|
||||
|
||||
// Creates new XML decoder with charset reader.
|
||||
func (f *File) xmlNewDecoder(rdr io.Reader) (ret *xml.Decoder) {
|
||||
ret = xml.NewDecoder(rdr)
|
||||
ret.CharsetReader = f.CharsetReader
|
||||
return
|
||||
}
|
||||
|
||||
// setDefaultTimeStyle provides a function to set default numbers format for
|
||||
// time.Time type cell value by given worksheet name, cell reference and
|
||||
// number format code.
|
||||
func (f *File) setDefaultTimeStyle(sheet, cell string, format int) error {
|
||||
s, err := f.GetCellStyle(sheet, cell)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if s == 0 {
|
||||
style, _ := f.NewStyle(&Style{NumFmt: format})
|
||||
err = f.SetCellStyle(sheet, cell, cell, style)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// workSheetReader provides a function to get the pointer to the structure
|
||||
// after deserialization by given worksheet name.
|
||||
func (f *File) workSheetReader(sheet string) (ws *xlsxWorksheet, err error) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
var (
|
||||
name string
|
||||
ok bool
|
||||
)
|
||||
if err = checkSheetName(sheet); err != nil {
|
||||
return
|
||||
}
|
||||
if name, ok = f.getSheetXMLPath(sheet); !ok {
|
||||
err = newNoExistSheetError(sheet)
|
||||
return
|
||||
}
|
||||
if worksheet, ok := f.Sheet.Load(name); ok && worksheet != nil {
|
||||
ws = worksheet.(*xlsxWorksheet)
|
||||
return
|
||||
}
|
||||
for _, sheetType := range []string{"xl/chartsheets", "xl/dialogsheet", "xl/macrosheet"} {
|
||||
if strings.HasPrefix(name, sheetType) {
|
||||
err = newNotWorksheetError(sheet)
|
||||
return
|
||||
}
|
||||
}
|
||||
ws = new(xlsxWorksheet)
|
||||
if _, ok := f.xmlAttr[name]; !ok {
|
||||
d := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readBytes(name))))
|
||||
f.xmlAttr[name] = append(f.xmlAttr[name], getRootElement(d)...)
|
||||
}
|
||||
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readBytes(name)))).
|
||||
Decode(ws); err != nil && err != io.EOF {
|
||||
return
|
||||
}
|
||||
err = nil
|
||||
if f.checked == nil {
|
||||
f.checked = make(map[string]bool)
|
||||
}
|
||||
if ok = f.checked[name]; !ok {
|
||||
checkSheet(ws)
|
||||
if err = checkRow(ws); err != nil {
|
||||
return
|
||||
}
|
||||
f.checked[name] = true
|
||||
}
|
||||
f.Sheet.Store(name, ws)
|
||||
return
|
||||
}
|
||||
|
||||
// checkSheet provides a function to fill each row element and make that is
|
||||
// continuous in a worksheet of XML.
|
||||
func checkSheet(ws *xlsxWorksheet) {
|
||||
var row int
|
||||
var r0 xlsxRow
|
||||
for i, r := range ws.SheetData.Row {
|
||||
if i == 0 && r.R == 0 {
|
||||
r0 = r
|
||||
ws.SheetData.Row = ws.SheetData.Row[1:]
|
||||
continue
|
||||
}
|
||||
if r.R != 0 && r.R > row {
|
||||
row = r.R
|
||||
continue
|
||||
}
|
||||
if r.R != row {
|
||||
row++
|
||||
}
|
||||
}
|
||||
sheetData := xlsxSheetData{Row: make([]xlsxRow, row)}
|
||||
row = 0
|
||||
for _, r := range ws.SheetData.Row {
|
||||
if r.R == row && row > 0 {
|
||||
sheetData.Row[r.R-1].C = append(sheetData.Row[r.R-1].C, r.C...)
|
||||
continue
|
||||
}
|
||||
if r.R != 0 {
|
||||
sheetData.Row[r.R-1] = r
|
||||
row = r.R
|
||||
continue
|
||||
}
|
||||
row++
|
||||
r.R = row
|
||||
sheetData.Row[row-1] = r
|
||||
}
|
||||
for i := 1; i <= row; i++ {
|
||||
sheetData.Row[i-1].R = i
|
||||
}
|
||||
checkSheetR0(ws, &sheetData, &r0)
|
||||
}
|
||||
|
||||
// checkSheetR0 handle the row element with r="0" attribute, cells in this row
|
||||
// could be disorderly, the cell in this row can be used as the value of
|
||||
// which cell is empty in the normal rows.
|
||||
func checkSheetR0(ws *xlsxWorksheet, sheetData *xlsxSheetData, r0 *xlsxRow) {
|
||||
for _, cell := range r0.C {
|
||||
if col, row, err := CellNameToCoordinates(cell.R); err == nil {
|
||||
rows, rowIdx := len(sheetData.Row), row-1
|
||||
for r := rows; r < row; r++ {
|
||||
sheetData.Row = append(sheetData.Row, xlsxRow{R: r + 1})
|
||||
}
|
||||
columns, colIdx := len(sheetData.Row[rowIdx].C), col-1
|
||||
for c := columns; c < col; c++ {
|
||||
sheetData.Row[rowIdx].C = append(sheetData.Row[rowIdx].C, xlsxC{})
|
||||
}
|
||||
if !sheetData.Row[rowIdx].C[colIdx].hasValue() {
|
||||
sheetData.Row[rowIdx].C[colIdx] = cell
|
||||
}
|
||||
}
|
||||
}
|
||||
ws.SheetData = *sheetData
|
||||
}
|
||||
|
||||
// setRels provides a function to set relationships by given relationship ID,
|
||||
// XML path, relationship type, target and target mode.
|
||||
func (f *File) setRels(rID, relPath, relType, target, targetMode string) int {
|
||||
rels, _ := f.relsReader(relPath)
|
||||
if rels == nil || rID == "" {
|
||||
return f.addRels(relPath, relType, target, targetMode)
|
||||
}
|
||||
rels.Lock()
|
||||
defer rels.Unlock()
|
||||
var ID int
|
||||
for i, rel := range rels.Relationships {
|
||||
if rel.ID == rID {
|
||||
rels.Relationships[i].Type = relType
|
||||
rels.Relationships[i].Target = target
|
||||
rels.Relationships[i].TargetMode = targetMode
|
||||
ID, _ = strconv.Atoi(strings.TrimPrefix(rID, "rId"))
|
||||
break
|
||||
}
|
||||
}
|
||||
return ID
|
||||
}
|
||||
|
||||
// addRels provides a function to add relationships by given XML path,
|
||||
// relationship type, target and target mode.
|
||||
func (f *File) addRels(relPath, relType, target, targetMode string) int {
|
||||
uniqPart := map[string]string{
|
||||
SourceRelationshipSharedStrings: "/xl/sharedStrings.xml",
|
||||
}
|
||||
rels, _ := f.relsReader(relPath)
|
||||
if rels == nil {
|
||||
rels = &xlsxRelationships{}
|
||||
}
|
||||
rels.Lock()
|
||||
defer rels.Unlock()
|
||||
var rID int
|
||||
for idx, rel := range rels.Relationships {
|
||||
ID, _ := strconv.Atoi(strings.TrimPrefix(rel.ID, "rId"))
|
||||
if ID > rID {
|
||||
rID = ID
|
||||
}
|
||||
if relType == rel.Type {
|
||||
if partName, ok := uniqPart[rel.Type]; ok {
|
||||
rels.Relationships[idx].Target = partName
|
||||
return rID
|
||||
}
|
||||
}
|
||||
}
|
||||
rID++
|
||||
var ID bytes.Buffer
|
||||
ID.WriteString("rId")
|
||||
ID.WriteString(strconv.Itoa(rID))
|
||||
rels.Relationships = append(rels.Relationships, xlsxRelationship{
|
||||
ID: ID.String(),
|
||||
Type: relType,
|
||||
Target: target,
|
||||
TargetMode: targetMode,
|
||||
})
|
||||
f.Relationships.Store(relPath, rels)
|
||||
return rID
|
||||
}
|
||||
|
||||
// UpdateLinkedValue fix linked values within a spreadsheet are not updating in
|
||||
// Office Excel application. This function will be remove value tag when met a
|
||||
// cell have a linked value. Reference
|
||||
// https://social.technet.microsoft.com/Forums/office/en-US/e16bae1f-6a2c-4325-8013-e989a3479066/excel-2010-linked-cells-not-updating
|
||||
//
|
||||
// Notice: after opening generated workbook, Excel will update the linked value
|
||||
// and generate a new value and will prompt to save the file or not.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// <row r="19" spans="2:2">
|
||||
// <c r="B19">
|
||||
// <f>SUM(Sheet2!D2,Sheet2!D11)</f>
|
||||
// <v>100</v>
|
||||
// </c>
|
||||
// </row>
|
||||
//
|
||||
// to
|
||||
//
|
||||
// <row r="19" spans="2:2">
|
||||
// <c r="B19">
|
||||
// <f>SUM(Sheet2!D2,Sheet2!D11)</f>
|
||||
// </c>
|
||||
// </row>
|
||||
func (f *File) UpdateLinkedValue() error {
|
||||
wb, err := f.workbookReader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// recalculate formulas
|
||||
wb.CalcPr = nil
|
||||
for _, name := range f.GetSheetList() {
|
||||
ws, err := f.workSheetReader(name)
|
||||
if err != nil {
|
||||
if err.Error() == newNotWorksheetError(name).Error() {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
for indexR := range ws.SheetData.Row {
|
||||
for indexC, col := range ws.SheetData.Row[indexR].C {
|
||||
if col.F != nil && col.V != "" {
|
||||
ws.SheetData.Row[indexR].C[indexC].V = ""
|
||||
ws.SheetData.Row[indexR].C[indexC].T = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddVBAProject provides the method to add vbaProject.bin file which contains
|
||||
// functions and/or macros. The file extension should be XLSM or XLTM. For
|
||||
// example:
|
||||
//
|
||||
// codeName := "Sheet1"
|
||||
// if err := f.SetSheetProps("Sheet1", &excelize.SheetPropsOptions{
|
||||
// CodeName: &codeName,
|
||||
// }); err != nil {
|
||||
// fmt.Println(err)
|
||||
// return
|
||||
// }
|
||||
// file, err := os.ReadFile("vbaProject.bin")
|
||||
// if err != nil {
|
||||
// fmt.Println(err)
|
||||
// return
|
||||
// }
|
||||
// if err := f.AddVBAProject(file); err != nil {
|
||||
// fmt.Println(err)
|
||||
// return
|
||||
// }
|
||||
// if err := f.SaveAs("macros.xlsm"); err != nil {
|
||||
// fmt.Println(err)
|
||||
// return
|
||||
// }
|
||||
func (f *File) AddVBAProject(file []byte) error {
|
||||
var err error
|
||||
// Check vbaProject.bin exists first.
|
||||
if !bytes.Contains(file, oleIdentifier) {
|
||||
return ErrAddVBAProject
|
||||
}
|
||||
rels, err := f.relsReader(f.getWorkbookRelsPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rels.Lock()
|
||||
defer rels.Unlock()
|
||||
var rID int
|
||||
var ok bool
|
||||
for _, rel := range rels.Relationships {
|
||||
if rel.Target == "vbaProject.bin" && rel.Type == SourceRelationshipVBAProject {
|
||||
ok = true
|
||||
continue
|
||||
}
|
||||
t, _ := strconv.Atoi(strings.TrimPrefix(rel.ID, "rId"))
|
||||
if t > rID {
|
||||
rID = t
|
||||
}
|
||||
}
|
||||
rID++
|
||||
if !ok {
|
||||
rels.Relationships = append(rels.Relationships, xlsxRelationship{
|
||||
ID: "rId" + strconv.Itoa(rID),
|
||||
Target: "vbaProject.bin",
|
||||
Type: SourceRelationshipVBAProject,
|
||||
})
|
||||
}
|
||||
f.Pkg.Store("xl/vbaProject.bin", file)
|
||||
return err
|
||||
}
|
||||
|
||||
// setContentTypePartProjectExtensions provides a function to set the content
|
||||
// type for relationship parts and the main document part.
|
||||
func (f *File) setContentTypePartProjectExtensions(contentType string) error {
|
||||
var ok bool
|
||||
content, err := f.contentTypesReader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
content.Lock()
|
||||
defer content.Unlock()
|
||||
for _, v := range content.Defaults {
|
||||
if v.Extension == "bin" {
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
for idx, o := range content.Overrides {
|
||||
if o.PartName == "/xl/workbook.xml" {
|
||||
content.Overrides[idx].ContentType = contentType
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
content.Defaults = append(content.Defaults, xlsxDefault{
|
||||
Extension: "bin",
|
||||
ContentType: ContentTypeVBA,
|
||||
})
|
||||
}
|
||||
return err
|
||||
}
|
||||
+1
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 24 KiB |
+232
@@ -0,0 +1,232 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// NewFile provides a function to create new file by default template.
|
||||
// For example:
|
||||
//
|
||||
// f := NewFile()
|
||||
func NewFile() *File {
|
||||
f := newFile()
|
||||
f.Pkg.Store("_rels/.rels", []byte(xml.Header+templateRels))
|
||||
f.Pkg.Store(defaultXMLPathDocPropsApp, []byte(xml.Header+templateDocpropsApp))
|
||||
f.Pkg.Store(defaultXMLPathDocPropsCore, []byte(xml.Header+templateDocpropsCore))
|
||||
f.Pkg.Store(defaultXMLPathWorkbookRels, []byte(xml.Header+templateWorkbookRels))
|
||||
f.Pkg.Store("xl/theme/theme1.xml", []byte(xml.Header+templateTheme))
|
||||
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(xml.Header+templateSheet))
|
||||
f.Pkg.Store(defaultXMLPathStyles, []byte(xml.Header+templateStyles))
|
||||
f.Pkg.Store(defaultXMLPathWorkbook, []byte(xml.Header+templateWorkbook))
|
||||
f.Pkg.Store(defaultXMLPathContentTypes, []byte(xml.Header+templateContentTypes))
|
||||
f.SheetCount = 1
|
||||
f.CalcChain, _ = f.calcChainReader()
|
||||
f.ContentTypes, _ = f.contentTypesReader()
|
||||
f.Styles, _ = f.stylesReader()
|
||||
f.WorkBook, _ = f.workbookReader()
|
||||
f.Relationships = sync.Map{}
|
||||
rels, _ := f.relsReader(defaultXMLPathWorkbookRels)
|
||||
f.Relationships.Store(defaultXMLPathWorkbookRels, rels)
|
||||
f.sheetMap["Sheet1"] = "xl/worksheets/sheet1.xml"
|
||||
ws, _ := f.workSheetReader("Sheet1")
|
||||
f.Sheet.Store("xl/worksheets/sheet1.xml", ws)
|
||||
f.Theme, _ = f.themeReader()
|
||||
return f
|
||||
}
|
||||
|
||||
// Save provides a function to override the spreadsheet with origin path.
|
||||
func (f *File) Save(opts ...Options) error {
|
||||
if f.Path == "" {
|
||||
return ErrSave
|
||||
}
|
||||
for i := range opts {
|
||||
f.options = &opts[i]
|
||||
}
|
||||
return f.SaveAs(f.Path, *f.options)
|
||||
}
|
||||
|
||||
// SaveAs provides a function to create or update to a spreadsheet at the
|
||||
// provided path.
|
||||
func (f *File) SaveAs(name string, opts ...Options) error {
|
||||
if len(name) > MaxFilePathLength {
|
||||
return ErrMaxFilePathLength
|
||||
}
|
||||
f.Path = name
|
||||
if _, ok := supportedContentTypes[strings.ToLower(filepath.Ext(f.Path))]; !ok {
|
||||
return ErrWorkbookFileFormat
|
||||
}
|
||||
file, err := os.OpenFile(filepath.Clean(name), os.O_WRONLY|os.O_TRUNC|os.O_CREATE, os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
return f.Write(file, opts...)
|
||||
}
|
||||
|
||||
// Close closes and cleanup the open temporary file for the spreadsheet.
|
||||
func (f *File) Close() error {
|
||||
var err error
|
||||
if f.sharedStringTemp != nil {
|
||||
if err := f.sharedStringTemp.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
f.tempFiles.Range(func(k, v interface{}) bool {
|
||||
if err = os.Remove(v.(string)); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
for _, stream := range f.streams {
|
||||
_ = stream.rawData.Close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Write provides a function to write to an io.Writer.
|
||||
func (f *File) Write(w io.Writer, opts ...Options) error {
|
||||
_, err := f.WriteTo(w, opts...)
|
||||
return err
|
||||
}
|
||||
|
||||
// WriteTo implements io.WriterTo to write the file.
|
||||
func (f *File) WriteTo(w io.Writer, opts ...Options) (int64, error) {
|
||||
for i := range opts {
|
||||
f.options = &opts[i]
|
||||
}
|
||||
if len(f.Path) != 0 {
|
||||
contentType, ok := supportedContentTypes[strings.ToLower(filepath.Ext(f.Path))]
|
||||
if !ok {
|
||||
return 0, ErrWorkbookFileFormat
|
||||
}
|
||||
if err := f.setContentTypePartProjectExtensions(contentType); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
if f.options != nil && f.options.Password != "" {
|
||||
buf, err := f.WriteToBuffer()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return buf.WriteTo(w)
|
||||
}
|
||||
if err := f.writeDirectToWriter(w); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// WriteToBuffer provides a function to get bytes.Buffer from the saved file,
|
||||
// and it allocates space in memory. Be careful when the file size is large.
|
||||
func (f *File) WriteToBuffer() (*bytes.Buffer, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
zw := zip.NewWriter(buf)
|
||||
|
||||
if err := f.writeToZip(zw); err != nil {
|
||||
return buf, zw.Close()
|
||||
}
|
||||
|
||||
if f.options != nil && f.options.Password != "" {
|
||||
if err := zw.Close(); err != nil {
|
||||
return buf, err
|
||||
}
|
||||
b, err := Encrypt(buf.Bytes(), f.options)
|
||||
if err != nil {
|
||||
return buf, err
|
||||
}
|
||||
buf.Reset()
|
||||
buf.Write(b)
|
||||
return buf, nil
|
||||
}
|
||||
return buf, zw.Close()
|
||||
}
|
||||
|
||||
// writeDirectToWriter provides a function to write to io.Writer.
|
||||
func (f *File) writeDirectToWriter(w io.Writer) error {
|
||||
zw := zip.NewWriter(w)
|
||||
if err := f.writeToZip(zw); err != nil {
|
||||
_ = zw.Close()
|
||||
return err
|
||||
}
|
||||
return zw.Close()
|
||||
}
|
||||
|
||||
// writeToZip provides a function to write to zip.Writer
|
||||
func (f *File) writeToZip(zw *zip.Writer) error {
|
||||
f.calcChainWriter()
|
||||
f.commentsWriter()
|
||||
f.contentTypesWriter()
|
||||
f.drawingsWriter()
|
||||
f.vmlDrawingWriter()
|
||||
f.workBookWriter()
|
||||
f.workSheetWriter()
|
||||
f.relsWriter()
|
||||
_ = f.sharedStringsLoader()
|
||||
f.sharedStringsWriter()
|
||||
f.styleSheetWriter()
|
||||
f.themeWriter()
|
||||
|
||||
for path, stream := range f.streams {
|
||||
fi, err := zw.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var from io.Reader
|
||||
from, err = stream.rawData.Reader()
|
||||
if err != nil {
|
||||
_ = stream.rawData.Close()
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(fi, from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var err error
|
||||
f.Pkg.Range(func(path, content interface{}) bool {
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if _, ok := f.streams[path.(string)]; ok {
|
||||
return true
|
||||
}
|
||||
var fi io.Writer
|
||||
fi, err = zw.Create(path.(string))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_, err = fi.Write(content.([]byte))
|
||||
return true
|
||||
})
|
||||
f.tempFiles.Range(func(path, content interface{}) bool {
|
||||
if _, ok := f.Pkg.Load(path); ok {
|
||||
return true
|
||||
}
|
||||
var fi io.Writer
|
||||
fi, err = zw.Create(path.(string))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_, err = fi.Write(f.readBytes(path.(string)))
|
||||
return true
|
||||
})
|
||||
return err
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
// Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package excelize
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"math"
|
||||
)
|
||||
|
||||
// HSLModel converts any color.Color to a HSL color.
|
||||
var HSLModel = color.ModelFunc(hslModel)
|
||||
|
||||
// HSL represents a cylindrical coordinate of points in an RGB color model.
|
||||
//
|
||||
// Values are in the range 0 to 1.
|
||||
type HSL struct {
|
||||
H, S, L float64
|
||||
}
|
||||
|
||||
// RGBA returns the alpha-premultiplied red, green, blue and alpha values
|
||||
// for the HSL.
|
||||
func (c HSL) RGBA() (uint32, uint32, uint32, uint32) {
|
||||
r, g, b := HSLToRGB(c.H, c.S, c.L)
|
||||
return uint32(r) * 0x101, uint32(g) * 0x101, uint32(b) * 0x101, 0xffff
|
||||
}
|
||||
|
||||
// hslModel converts a color.Color to HSL.
|
||||
func hslModel(c color.Color) color.Color {
|
||||
if _, ok := c.(HSL); ok {
|
||||
return c
|
||||
}
|
||||
r, g, b, _ := c.RGBA()
|
||||
h, s, l := RGBToHSL(uint8(r>>8), uint8(g>>8), uint8(b>>8))
|
||||
return HSL{h, s, l}
|
||||
}
|
||||
|
||||
// RGBToHSL converts an RGB triple to a HSL triple.
|
||||
func RGBToHSL(r, g, b uint8) (h, s, l float64) {
|
||||
fR := float64(r) / 255
|
||||
fG := float64(g) / 255
|
||||
fB := float64(b) / 255
|
||||
max := math.Max(math.Max(fR, fG), fB)
|
||||
min := math.Min(math.Min(fR, fG), fB)
|
||||
l = (max + min) / 2
|
||||
if max == min {
|
||||
// Achromatic.
|
||||
h, s = 0, 0
|
||||
} else {
|
||||
// Chromatic.
|
||||
d := max - min
|
||||
if l > 0.5 {
|
||||
s = d / (2.0 - max - min)
|
||||
} else {
|
||||
s = d / (max + min)
|
||||
}
|
||||
switch max {
|
||||
case fR:
|
||||
h = (fG - fB) / d
|
||||
if fG < fB {
|
||||
h += 6
|
||||
}
|
||||
case fG:
|
||||
h = (fB-fR)/d + 2
|
||||
case fB:
|
||||
h = (fR-fG)/d + 4
|
||||
}
|
||||
h /= 6
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// HSLToRGB converts an HSL triple to a RGB triple.
|
||||
func HSLToRGB(h, s, l float64) (r, g, b uint8) {
|
||||
var fR, fG, fB float64
|
||||
if s == 0 {
|
||||
fR, fG, fB = l, l, l
|
||||
} else {
|
||||
var q float64
|
||||
if l < 0.5 {
|
||||
q = l * (1 + s)
|
||||
} else {
|
||||
q = l + s - s*l
|
||||
}
|
||||
p := 2*l - q
|
||||
fR = hueToRGB(p, q, h+1.0/3)
|
||||
fG = hueToRGB(p, q, h)
|
||||
fB = hueToRGB(p, q, h-1.0/3)
|
||||
}
|
||||
r = uint8((fR * 255) + 0.5)
|
||||
g = uint8((fG * 255) + 0.5)
|
||||
b = uint8((fB * 255) + 0.5)
|
||||
return
|
||||
}
|
||||
|
||||
// hueToRGB is a helper function for HSLToRGB.
|
||||
func hueToRGB(p, q, t float64) float64 {
|
||||
if t < 0 {
|
||||
t++
|
||||
}
|
||||
if t > 1 {
|
||||
t--
|
||||
}
|
||||
if t < 1.0/6 {
|
||||
return p + (q-p)*6*t
|
||||
}
|
||||
if t < 0.5 {
|
||||
return q
|
||||
}
|
||||
if t < 2.0/3 {
|
||||
return p + (q-p)*(2.0/3-t)*6
|
||||
}
|
||||
return p
|
||||
}
|
||||
+813
@@ -0,0 +1,813 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"container/list"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ReadZipReader extract spreadsheet with given options.
|
||||
func (f *File) ReadZipReader(r *zip.Reader) (map[string][]byte, int, error) {
|
||||
var (
|
||||
err error
|
||||
docPart = map[string]string{
|
||||
"[content_types].xml": defaultXMLPathContentTypes,
|
||||
"xl/sharedstrings.xml": defaultXMLPathSharedStrings,
|
||||
}
|
||||
fileList = make(map[string][]byte, len(r.File))
|
||||
worksheets int
|
||||
unzipSize int64
|
||||
)
|
||||
for _, v := range r.File {
|
||||
fileSize := v.FileInfo().Size()
|
||||
unzipSize += fileSize
|
||||
if unzipSize > f.options.UnzipSizeLimit {
|
||||
return fileList, worksheets, newUnzipSizeLimitError(f.options.UnzipSizeLimit)
|
||||
}
|
||||
fileName := strings.ReplaceAll(v.Name, "\\", "/")
|
||||
if partName, ok := docPart[strings.ToLower(fileName)]; ok {
|
||||
fileName = partName
|
||||
}
|
||||
if strings.EqualFold(fileName, defaultXMLPathSharedStrings) && fileSize > f.options.UnzipXMLSizeLimit {
|
||||
if tempFile, err := f.unzipToTemp(v); err == nil {
|
||||
f.tempFiles.Store(fileName, tempFile)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(fileName, "xl/worksheets/sheet") {
|
||||
worksheets++
|
||||
if fileSize > f.options.UnzipXMLSizeLimit && !v.FileInfo().IsDir() {
|
||||
if tempFile, err := f.unzipToTemp(v); err == nil {
|
||||
f.tempFiles.Store(fileName, tempFile)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
if fileList[fileName], err = readFile(v); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
}
|
||||
return fileList, worksheets, nil
|
||||
}
|
||||
|
||||
// unzipToTemp unzip the zip entity to the system temporary directory and
|
||||
// returned the unzipped file path.
|
||||
func (f *File) unzipToTemp(zipFile *zip.File) (string, error) {
|
||||
tmp, err := os.CreateTemp(os.TempDir(), "excelize-")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
rc, err := zipFile.Open()
|
||||
if err != nil {
|
||||
return tmp.Name(), err
|
||||
}
|
||||
if _, err = io.Copy(tmp, rc); err != nil {
|
||||
return tmp.Name(), err
|
||||
}
|
||||
if err = rc.Close(); err != nil {
|
||||
return tmp.Name(), err
|
||||
}
|
||||
return tmp.Name(), tmp.Close()
|
||||
}
|
||||
|
||||
// readXML provides a function to read XML content as bytes.
|
||||
func (f *File) readXML(name string) []byte {
|
||||
if content, _ := f.Pkg.Load(name); content != nil {
|
||||
return content.([]byte)
|
||||
}
|
||||
if content, ok := f.streams[name]; ok {
|
||||
return content.rawData.buf.Bytes()
|
||||
}
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
// readBytes read file as bytes by given path.
|
||||
func (f *File) readBytes(name string) []byte {
|
||||
content := f.readXML(name)
|
||||
if len(content) != 0 {
|
||||
return content
|
||||
}
|
||||
file, err := f.readTemp(name)
|
||||
if err != nil {
|
||||
return content
|
||||
}
|
||||
content, _ = io.ReadAll(file)
|
||||
f.Pkg.Store(name, content)
|
||||
_ = file.Close()
|
||||
return content
|
||||
}
|
||||
|
||||
// readTemp read file from system temporary directory by given path.
|
||||
func (f *File) readTemp(name string) (file *os.File, err error) {
|
||||
path, ok := f.tempFiles.Load(name)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
file, err = os.Open(path.(string))
|
||||
return
|
||||
}
|
||||
|
||||
// saveFileList provides a function to update given file content in file list
|
||||
// of spreadsheet.
|
||||
func (f *File) saveFileList(name string, content []byte) {
|
||||
f.Pkg.Store(name, append([]byte(xml.Header), content...))
|
||||
}
|
||||
|
||||
// Read file content as string in an archive file.
|
||||
func readFile(file *zip.File) ([]byte, error) {
|
||||
rc, err := file.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dat := make([]byte, 0, file.FileInfo().Size())
|
||||
buff := bytes.NewBuffer(dat)
|
||||
_, _ = io.Copy(buff, rc)
|
||||
return buff.Bytes(), rc.Close()
|
||||
}
|
||||
|
||||
// SplitCellName splits cell name to column name and row number.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// excelize.SplitCellName("AK74") // return "AK", 74, nil
|
||||
func SplitCellName(cell string) (string, int, error) {
|
||||
alpha := func(r rune) bool {
|
||||
return ('A' <= r && r <= 'Z') || ('a' <= r && r <= 'z') || (r == 36)
|
||||
}
|
||||
if strings.IndexFunc(cell, alpha) == 0 {
|
||||
i := strings.LastIndexFunc(cell, alpha)
|
||||
if i >= 0 && i < len(cell)-1 {
|
||||
col, rowStr := strings.ReplaceAll(cell[:i+1], "$", ""), cell[i+1:]
|
||||
if row, err := strconv.Atoi(rowStr); err == nil && row > 0 {
|
||||
return col, row, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", -1, newInvalidCellNameError(cell)
|
||||
}
|
||||
|
||||
// JoinCellName joins cell name from column name and row number.
|
||||
func JoinCellName(col string, row int) (string, error) {
|
||||
normCol := strings.Map(func(rune rune) rune {
|
||||
switch {
|
||||
case 'A' <= rune && rune <= 'Z':
|
||||
return rune
|
||||
case 'a' <= rune && rune <= 'z':
|
||||
return rune - 32
|
||||
}
|
||||
return -1
|
||||
}, col)
|
||||
if len(col) == 0 || len(col) != len(normCol) {
|
||||
return "", newInvalidColumnNameError(col)
|
||||
}
|
||||
if row < 1 {
|
||||
return "", newInvalidRowNumberError(row)
|
||||
}
|
||||
return normCol + strconv.Itoa(row), nil
|
||||
}
|
||||
|
||||
// ColumnNameToNumber provides a function to convert Excel sheet column name
|
||||
// (case-insensitive) to int. The function returns an error if column name
|
||||
// incorrect.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// excelize.ColumnNameToNumber("AK") // returns 37, nil
|
||||
func ColumnNameToNumber(name string) (int, error) {
|
||||
if len(name) == 0 {
|
||||
return -1, newInvalidColumnNameError(name)
|
||||
}
|
||||
col := 0
|
||||
multi := 1
|
||||
for i := len(name) - 1; i >= 0; i-- {
|
||||
r := name[i]
|
||||
if r >= 'A' && r <= 'Z' {
|
||||
col += int(r-'A'+1) * multi
|
||||
} else if r >= 'a' && r <= 'z' {
|
||||
col += int(r-'a'+1) * multi
|
||||
} else {
|
||||
return -1, newInvalidColumnNameError(name)
|
||||
}
|
||||
multi *= 26
|
||||
}
|
||||
if col > MaxColumns {
|
||||
return -1, ErrColumnNumber
|
||||
}
|
||||
return col, nil
|
||||
}
|
||||
|
||||
// ColumnNumberToName provides a function to convert the integer to Excel
|
||||
// sheet column title.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// excelize.ColumnNumberToName(37) // returns "AK", nil
|
||||
func ColumnNumberToName(num int) (string, error) {
|
||||
if num < MinColumns || num > MaxColumns {
|
||||
return "", ErrColumnNumber
|
||||
}
|
||||
var col string
|
||||
for num > 0 {
|
||||
col = string(rune((num-1)%26+65)) + col
|
||||
num = (num - 1) / 26
|
||||
}
|
||||
return col, nil
|
||||
}
|
||||
|
||||
// CellNameToCoordinates converts alphanumeric cell name to [X, Y] coordinates
|
||||
// or returns an error.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// excelize.CellNameToCoordinates("A1") // returns 1, 1, nil
|
||||
// excelize.CellNameToCoordinates("Z3") // returns 26, 3, nil
|
||||
func CellNameToCoordinates(cell string) (int, int, error) {
|
||||
colName, row, err := SplitCellName(cell)
|
||||
if err != nil {
|
||||
return -1, -1, newCellNameToCoordinatesError(cell, err)
|
||||
}
|
||||
if row > TotalRows {
|
||||
return -1, -1, ErrMaxRows
|
||||
}
|
||||
col, err := ColumnNameToNumber(colName)
|
||||
return col, row, err
|
||||
}
|
||||
|
||||
// CoordinatesToCellName converts [X, Y] coordinates to alpha-numeric cell
|
||||
// name or returns an error.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// excelize.CoordinatesToCellName(1, 1) // returns "A1", nil
|
||||
// excelize.CoordinatesToCellName(1, 1, true) // returns "$A$1", nil
|
||||
func CoordinatesToCellName(col, row int, abs ...bool) (string, error) {
|
||||
if col < 1 || row < 1 {
|
||||
return "", fmt.Errorf("invalid cell reference [%d, %d]", col, row)
|
||||
}
|
||||
sign := ""
|
||||
for _, a := range abs {
|
||||
if a {
|
||||
sign = "$"
|
||||
}
|
||||
}
|
||||
colName, err := ColumnNumberToName(col)
|
||||
return sign + colName + sign + strconv.Itoa(row), err
|
||||
}
|
||||
|
||||
// rangeRefToCoordinates provides a function to convert range reference to a
|
||||
// pair of coordinates.
|
||||
func rangeRefToCoordinates(ref string) ([]int, error) {
|
||||
rng := strings.Split(strings.ReplaceAll(ref, "$", ""), ":")
|
||||
if len(rng) < 2 {
|
||||
return nil, ErrParameterInvalid
|
||||
}
|
||||
return cellRefsToCoordinates(rng[0], rng[1])
|
||||
}
|
||||
|
||||
// cellRefsToCoordinates provides a function to convert cell range to a
|
||||
// pair of coordinates.
|
||||
func cellRefsToCoordinates(firstCell, lastCell string) ([]int, error) {
|
||||
coordinates := make([]int, 4)
|
||||
var err error
|
||||
coordinates[0], coordinates[1], err = CellNameToCoordinates(firstCell)
|
||||
if err != nil {
|
||||
return coordinates, err
|
||||
}
|
||||
coordinates[2], coordinates[3], err = CellNameToCoordinates(lastCell)
|
||||
return coordinates, err
|
||||
}
|
||||
|
||||
// sortCoordinates provides a function to correct the cell range, such
|
||||
// correct C1:B3 to B1:C3.
|
||||
func sortCoordinates(coordinates []int) error {
|
||||
if len(coordinates) != 4 {
|
||||
return ErrCoordinates
|
||||
}
|
||||
if coordinates[2] < coordinates[0] {
|
||||
coordinates[2], coordinates[0] = coordinates[0], coordinates[2]
|
||||
}
|
||||
if coordinates[3] < coordinates[1] {
|
||||
coordinates[3], coordinates[1] = coordinates[1], coordinates[3]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// coordinatesToRangeRef provides a function to convert a pair of coordinates
|
||||
// to range reference.
|
||||
func (f *File) coordinatesToRangeRef(coordinates []int, abs ...bool) (string, error) {
|
||||
if len(coordinates) != 4 {
|
||||
return "", ErrCoordinates
|
||||
}
|
||||
firstCell, err := CoordinatesToCellName(coordinates[0], coordinates[1], abs...)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
lastCell, err := CoordinatesToCellName(coordinates[2], coordinates[3], abs...)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return firstCell + ":" + lastCell, err
|
||||
}
|
||||
|
||||
// getDefinedNameRefTo convert defined name to reference range.
|
||||
func (f *File) getDefinedNameRefTo(definedNameName string, currentSheet string) (refTo string) {
|
||||
var workbookRefTo, worksheetRefTo string
|
||||
for _, definedName := range f.GetDefinedName() {
|
||||
if definedName.Name == definedNameName {
|
||||
// worksheet scope takes precedence over scope workbook when both definedNames exist
|
||||
if definedName.Scope == "Workbook" {
|
||||
workbookRefTo = definedName.RefersTo
|
||||
}
|
||||
if definedName.Scope == currentSheet {
|
||||
worksheetRefTo = definedName.RefersTo
|
||||
}
|
||||
}
|
||||
}
|
||||
refTo = workbookRefTo
|
||||
if worksheetRefTo != "" {
|
||||
refTo = worksheetRefTo
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// flatSqref convert reference sequence to cell reference list.
|
||||
func (f *File) flatSqref(sqref string) (cells map[int][][]int, err error) {
|
||||
var coordinates []int
|
||||
cells = make(map[int][][]int)
|
||||
for _, ref := range strings.Fields(sqref) {
|
||||
rng := strings.Split(ref, ":")
|
||||
switch len(rng) {
|
||||
case 1:
|
||||
var col, row int
|
||||
col, row, err = CellNameToCoordinates(rng[0])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cells[col] = append(cells[col], []int{col, row})
|
||||
case 2:
|
||||
if coordinates, err = rangeRefToCoordinates(ref); err != nil {
|
||||
return
|
||||
}
|
||||
_ = sortCoordinates(coordinates)
|
||||
for c := coordinates[0]; c <= coordinates[2]; c++ {
|
||||
for r := coordinates[1]; r <= coordinates[3]; r++ {
|
||||
cells[c] = append(cells[c], []int{c, r})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// inCoordinates provides a method to check if a coordinate is present in
|
||||
// coordinates array, and return the index of its location, otherwise
|
||||
// return -1.
|
||||
func inCoordinates(a [][]int, x []int) int {
|
||||
for idx, n := range a {
|
||||
if x[0] == n[0] && x[1] == n[1] {
|
||||
return idx
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// inStrSlice provides a method to check if an element is present in an array,
|
||||
// and return the index of its location, otherwise return -1.
|
||||
func inStrSlice(a []string, x string, caseSensitive bool) int {
|
||||
for idx, n := range a {
|
||||
if !caseSensitive && strings.EqualFold(x, n) {
|
||||
return idx
|
||||
}
|
||||
if x == n {
|
||||
return idx
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// inFloat64Slice provides a method to check if an element is present in a
|
||||
// float64 array, and return the index of its location, otherwise return -1.
|
||||
func inFloat64Slice(a []float64, x float64) int {
|
||||
for idx, n := range a {
|
||||
if x == n {
|
||||
return idx
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// boolPtr returns a pointer to a bool with the given value.
|
||||
func boolPtr(b bool) *bool { return &b }
|
||||
|
||||
// intPtr returns a pointer to an int with the given value.
|
||||
func intPtr(i int) *int { return &i }
|
||||
|
||||
// uintPtr returns a pointer to an int with the given value.
|
||||
func uintPtr(i uint) *uint { return &i }
|
||||
|
||||
// float64Ptr returns a pointer to a float64 with the given value.
|
||||
func float64Ptr(f float64) *float64 { return &f }
|
||||
|
||||
// stringPtr returns a pointer to a string with the given value.
|
||||
func stringPtr(s string) *string { return &s }
|
||||
|
||||
// MarshalXML convert the boolean data type to literal values 0 or 1 on
|
||||
// serialization.
|
||||
func (avb attrValBool) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
attr := xml.Attr{
|
||||
Name: xml.Name{
|
||||
Space: start.Name.Space,
|
||||
Local: "val",
|
||||
},
|
||||
Value: "0",
|
||||
}
|
||||
if avb.Val != nil {
|
||||
if *avb.Val {
|
||||
attr.Value = "1"
|
||||
} else {
|
||||
attr.Value = "0"
|
||||
}
|
||||
}
|
||||
start.Attr = []xml.Attr{attr}
|
||||
if err := e.EncodeToken(start); err != nil {
|
||||
return err
|
||||
}
|
||||
return e.EncodeToken(start.End())
|
||||
}
|
||||
|
||||
// UnmarshalXML convert the literal values true, false, 1, 0 of the XML
|
||||
// attribute to boolean data type on deserialization.
|
||||
func (avb *attrValBool) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
for {
|
||||
t, err := d.Token()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
found := false
|
||||
switch t.(type) {
|
||||
case xml.StartElement:
|
||||
return ErrAttrValBool
|
||||
case xml.EndElement:
|
||||
found = true
|
||||
}
|
||||
if found {
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, attr := range start.Attr {
|
||||
if attr.Name.Local == "val" {
|
||||
if attr.Value == "" {
|
||||
val := true
|
||||
avb.Val = &val
|
||||
} else {
|
||||
val, err := strconv.ParseBool(attr.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
avb.Val = &val
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
defaultVal := true
|
||||
avb.Val = &defaultVal
|
||||
return nil
|
||||
}
|
||||
|
||||
// namespaceStrictToTransitional provides a method to convert Strict and
|
||||
// Transitional namespaces.
|
||||
func namespaceStrictToTransitional(content []byte) []byte {
|
||||
namespaceTranslationDic := map[string]string{
|
||||
StrictNameSpaceDocumentPropertiesVariantTypes: NameSpaceDocumentPropertiesVariantTypes.Value,
|
||||
StrictNameSpaceDrawingMLMain: NameSpaceDrawingMLMain,
|
||||
StrictNameSpaceExtendedProperties: NameSpaceExtendedProperties,
|
||||
StrictNameSpaceSpreadSheet: NameSpaceSpreadSheet.Value,
|
||||
StrictSourceRelationship: SourceRelationship.Value,
|
||||
StrictSourceRelationshipChart: SourceRelationshipChart,
|
||||
StrictSourceRelationshipComments: SourceRelationshipComments,
|
||||
StrictSourceRelationshipExtendProperties: SourceRelationshipExtendProperties,
|
||||
StrictSourceRelationshipImage: SourceRelationshipImage,
|
||||
StrictSourceRelationshipOfficeDocument: SourceRelationshipOfficeDocument,
|
||||
}
|
||||
for s, n := range namespaceTranslationDic {
|
||||
content = bytesReplace(content, []byte(s), []byte(n), -1)
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
// bytesReplace replace source bytes with given target.
|
||||
func bytesReplace(s, source, target []byte, n int) []byte {
|
||||
if n == 0 {
|
||||
return s
|
||||
}
|
||||
|
||||
if len(source) < len(target) {
|
||||
return bytes.Replace(s, source, target, n)
|
||||
}
|
||||
|
||||
if n < 0 {
|
||||
n = len(s)
|
||||
}
|
||||
|
||||
var wid, i, j, w int
|
||||
for i, j = 0, 0; i < len(s) && j < n; j++ {
|
||||
wid = bytes.Index(s[i:], source)
|
||||
if wid < 0 {
|
||||
break
|
||||
}
|
||||
|
||||
w += copy(s[w:], s[i:i+wid])
|
||||
w += copy(s[w:], target)
|
||||
i += wid + len(source)
|
||||
}
|
||||
|
||||
w += copy(s[w:], s[i:])
|
||||
return s[:w]
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
||||
|
||||
// getRootElement extract root element attributes by given XML decoder.
|
||||
func getRootElement(d *xml.Decoder) []xml.Attr {
|
||||
tokenIdx := 0
|
||||
for {
|
||||
token, _ := d.Token()
|
||||
if token == nil {
|
||||
break
|
||||
}
|
||||
switch startElement := token.(type) {
|
||||
case xml.StartElement:
|
||||
tokenIdx++
|
||||
if tokenIdx == 1 {
|
||||
return startElement.Attr
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// genXMLNamespace generate serialized XML attributes with a multi namespace
|
||||
// by given element attributes.
|
||||
func genXMLNamespace(attr []xml.Attr) string {
|
||||
var rootElement string
|
||||
for _, v := range attr {
|
||||
if lastSpace := getXMLNamespace(v.Name.Space, attr); lastSpace != "" {
|
||||
if lastSpace == NameSpaceXML {
|
||||
lastSpace = "xml"
|
||||
}
|
||||
rootElement += fmt.Sprintf("%s:%s=\"%s\" ", lastSpace, v.Name.Local, v.Value)
|
||||
continue
|
||||
}
|
||||
rootElement += fmt.Sprintf("%s=\"%s\" ", v.Name.Local, v.Value)
|
||||
}
|
||||
return strings.TrimSpace(rootElement) + ">"
|
||||
}
|
||||
|
||||
// getXMLNamespace extract XML namespace from specified element name and attributes.
|
||||
func getXMLNamespace(space string, attr []xml.Attr) string {
|
||||
for _, attribute := range attr {
|
||||
if attribute.Value == space {
|
||||
return attribute.Name.Local
|
||||
}
|
||||
}
|
||||
return space
|
||||
}
|
||||
|
||||
// replaceNameSpaceBytes provides a function to replace the XML root element
|
||||
// attribute by the given component part path and XML content.
|
||||
func (f *File) replaceNameSpaceBytes(path string, contentMarshal []byte) []byte {
|
||||
sourceXmlns := []byte(`xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`)
|
||||
targetXmlns := []byte(templateNamespaceIDMap)
|
||||
if attr, ok := f.xmlAttr[path]; ok {
|
||||
targetXmlns = []byte(genXMLNamespace(attr))
|
||||
}
|
||||
return bytesReplace(contentMarshal, sourceXmlns, bytes.ReplaceAll(targetXmlns, []byte(" mc:Ignorable=\"r\""), []byte{}), -1)
|
||||
}
|
||||
|
||||
// addNameSpaces provides a function to add an XML attribute by the given
|
||||
// component part path.
|
||||
func (f *File) addNameSpaces(path string, ns xml.Attr) {
|
||||
exist := false
|
||||
mc := false
|
||||
ignore := -1
|
||||
if attr, ok := f.xmlAttr[path]; ok {
|
||||
for i, attribute := range attr {
|
||||
if attribute.Name.Local == ns.Name.Local && attribute.Name.Space == ns.Name.Space {
|
||||
exist = true
|
||||
}
|
||||
if attribute.Name.Local == "Ignorable" && getXMLNamespace(attribute.Name.Space, attr) == "mc" {
|
||||
ignore = i
|
||||
}
|
||||
if attribute.Name.Local == "mc" && attribute.Name.Space == "xmlns" {
|
||||
mc = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !exist {
|
||||
f.xmlAttr[path] = append(f.xmlAttr[path], ns)
|
||||
if !mc {
|
||||
f.xmlAttr[path] = append(f.xmlAttr[path], SourceRelationshipCompatibility)
|
||||
}
|
||||
if ignore == -1 {
|
||||
f.xmlAttr[path] = append(f.xmlAttr[path], xml.Attr{
|
||||
Name: xml.Name{Local: "Ignorable", Space: "mc"},
|
||||
Value: ns.Name.Local,
|
||||
})
|
||||
return
|
||||
}
|
||||
f.setIgnorableNameSpace(path, ignore, ns)
|
||||
}
|
||||
}
|
||||
|
||||
// setIgnorableNameSpace provides a function to set XML namespace as ignorable
|
||||
// by the given attribute.
|
||||
func (f *File) setIgnorableNameSpace(path string, index int, ns xml.Attr) {
|
||||
ignorableNS := []string{"c14", "cdr14", "a14", "pic14", "x14", "xdr14", "x14ac", "dsp", "mso14", "dgm14", "x15", "x12ac", "x15ac", "xr", "xr2", "xr3", "xr4", "xr5", "xr6", "xr7", "xr8", "xr9", "xr10", "xr11", "xr12", "xr13", "xr14", "xr15", "x15", "x16", "x16r2", "mo", "mx", "mv", "o", "v"}
|
||||
if inStrSlice(strings.Fields(f.xmlAttr[path][index].Value), ns.Name.Local, true) == -1 && inStrSlice(ignorableNS, ns.Name.Local, true) != -1 {
|
||||
f.xmlAttr[path][index].Value = strings.TrimSpace(fmt.Sprintf("%s %s", f.xmlAttr[path][index].Value, ns.Name.Local))
|
||||
}
|
||||
}
|
||||
|
||||
// addSheetNameSpace add XML attribute for worksheet.
|
||||
func (f *File) addSheetNameSpace(sheet string, ns xml.Attr) {
|
||||
name, _ := f.getSheetXMLPath(sheet)
|
||||
f.addNameSpaces(name, ns)
|
||||
}
|
||||
|
||||
// isNumeric determines whether an expression is a valid numeric type and get
|
||||
// the precision for the numeric.
|
||||
func isNumeric(s string) (bool, int, float64) {
|
||||
if strings.Contains(s, "_") {
|
||||
return false, 0, 0
|
||||
}
|
||||
var decimal big.Float
|
||||
_, ok := decimal.SetString(s)
|
||||
if !ok {
|
||||
return false, 0, 0
|
||||
}
|
||||
var noScientificNotation string
|
||||
flt, _ := decimal.Float64()
|
||||
noScientificNotation = strconv.FormatFloat(flt, 'f', -1, 64)
|
||||
return true, len(strings.ReplaceAll(noScientificNotation, ".", "")), flt
|
||||
}
|
||||
|
||||
var (
|
||||
bstrExp = regexp.MustCompile(`_x[a-fA-F\d]{4}_`)
|
||||
bstrEscapeExp = regexp.MustCompile(`x[a-fA-F\d]{4}_`)
|
||||
)
|
||||
|
||||
// bstrUnmarshal parses the binary basic string, this will trim escaped string
|
||||
// literal which not permitted in an XML 1.0 document. The basic string
|
||||
// variant type can store any valid Unicode character. Unicode's characters
|
||||
// that cannot be directly represented in XML as defined by the XML 1.0
|
||||
// specification, shall be escaped using the Unicode numerical character
|
||||
// representation escape character format _xHHHH_, where H represents a
|
||||
// hexadecimal character in the character's value. For example: The Unicode
|
||||
// character 8 is not permitted in an XML 1.0 document, so it shall be
|
||||
// escaped as _x0008_. To store the literal form of an escape sequence, the
|
||||
// initial underscore shall itself be escaped (i.e. stored as _x005F_). For
|
||||
// example: The string literal _x0008_ would be stored as _x005F_x0008_.
|
||||
func bstrUnmarshal(s string) (result string) {
|
||||
matches, l, cursor := bstrExp.FindAllStringSubmatchIndex(s, -1), len(s), 0
|
||||
for _, match := range matches {
|
||||
result += s[cursor:match[0]]
|
||||
subStr := s[match[0]:match[1]]
|
||||
if subStr == "_x005F_" {
|
||||
cursor = match[1]
|
||||
result += "_"
|
||||
continue
|
||||
}
|
||||
if bstrExp.MatchString(subStr) {
|
||||
cursor = match[1]
|
||||
v, _ := strconv.Unquote(`"\u` + s[match[0]+2:match[1]-1] + `"`)
|
||||
result += v
|
||||
}
|
||||
}
|
||||
if cursor < l {
|
||||
result += s[cursor:]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// bstrMarshal encode the escaped string literal which not permitted in an XML
|
||||
// 1.0 document.
|
||||
func bstrMarshal(s string) (result string) {
|
||||
matches, l, cursor := bstrExp.FindAllStringSubmatchIndex(s, -1), len(s), 0
|
||||
for _, match := range matches {
|
||||
result += s[cursor:match[0]]
|
||||
subStr := s[match[0]:match[1]]
|
||||
if subStr == "_x005F_" {
|
||||
cursor = match[1]
|
||||
if match[1]+6 <= l && bstrEscapeExp.MatchString(s[match[1]:match[1]+6]) {
|
||||
_, err := strconv.Unquote(`"\u` + s[match[1]+1:match[1]+5] + `"`)
|
||||
if err == nil {
|
||||
result += subStr + "x005F" + subStr
|
||||
continue
|
||||
}
|
||||
}
|
||||
result += subStr + "x005F_"
|
||||
continue
|
||||
}
|
||||
if bstrExp.MatchString(subStr) {
|
||||
cursor = match[1]
|
||||
if _, err := strconv.Unquote(`"\u` + s[match[0]+2:match[1]-1] + `"`); err == nil {
|
||||
result += "_x005F" + subStr
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
if cursor < l {
|
||||
result += s[cursor:]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Stack defined an abstract data type that serves as a collection of elements.
|
||||
type Stack struct {
|
||||
list *list.List
|
||||
}
|
||||
|
||||
// NewStack create a new stack.
|
||||
func NewStack() *Stack {
|
||||
l := list.New()
|
||||
return &Stack{l}
|
||||
}
|
||||
|
||||
// Push a value onto the top of the stack.
|
||||
func (stack *Stack) Push(value interface{}) {
|
||||
stack.list.PushBack(value)
|
||||
}
|
||||
|
||||
// Pop the top item of the stack and return it.
|
||||
func (stack *Stack) Pop() interface{} {
|
||||
e := stack.list.Back()
|
||||
if e != nil {
|
||||
stack.list.Remove(e)
|
||||
return e.Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Peek view the top item on the stack.
|
||||
func (stack *Stack) Peek() interface{} {
|
||||
e := stack.list.Back()
|
||||
if e != nil {
|
||||
return e.Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Len return the number of items in the stack.
|
||||
func (stack *Stack) Len() int {
|
||||
return stack.list.Len()
|
||||
}
|
||||
|
||||
// Empty the stack.
|
||||
func (stack *Stack) Empty() bool {
|
||||
return stack.list.Len() == 0
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
+293
@@ -0,0 +1,293 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import "strings"
|
||||
|
||||
// Rect gets merged cell rectangle coordinates sequence.
|
||||
func (mc *xlsxMergeCell) Rect() ([]int, error) {
|
||||
var err error
|
||||
if mc.rect == nil {
|
||||
mergedCellsRef := mc.Ref
|
||||
if !strings.Contains(mergedCellsRef, ":") {
|
||||
mergedCellsRef += ":" + mergedCellsRef
|
||||
}
|
||||
mc.rect, err = rangeRefToCoordinates(mergedCellsRef)
|
||||
}
|
||||
return mc.rect, err
|
||||
}
|
||||
|
||||
// MergeCell provides a function to merge cells by given range reference and
|
||||
// sheet name. Merging cells only keeps the upper-left cell value, and
|
||||
// discards the other values. For example create a merged cell of D3:E9 on
|
||||
// Sheet1:
|
||||
//
|
||||
// err := f.MergeCell("Sheet1", "D3", "E9")
|
||||
//
|
||||
// If you create a merged cell that overlaps with another existing merged cell,
|
||||
// those merged cells that already exist will be removed. The cell references
|
||||
// tuple after merging in the following range will be: A1(x3,y1) D1(x2,y1)
|
||||
// A8(x3,y4) D8(x2,y4)
|
||||
//
|
||||
// B1(x1,y1) D1(x2,y1)
|
||||
// +------------------------+
|
||||
// | |
|
||||
// A4(x3,y3) | C4(x4,y3) |
|
||||
// +------------------------+ |
|
||||
// | | | |
|
||||
// | |B5(x1,y2) | D5(x2,y2)|
|
||||
// | +------------------------+
|
||||
// | |
|
||||
// |A8(x3,y4) C8(x4,y4)|
|
||||
// +------------------------+
|
||||
func (f *File) MergeCell(sheet, hCell, vCell string) error {
|
||||
rect, err := rangeRefToCoordinates(hCell + ":" + vCell)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Correct the range reference, such correct C1:B3 to B1:C3.
|
||||
_ = sortCoordinates(rect)
|
||||
|
||||
hCell, _ = CoordinatesToCellName(rect[0], rect[1])
|
||||
vCell, _ = CoordinatesToCellName(rect[2], rect[3])
|
||||
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ws.Lock()
|
||||
defer ws.Unlock()
|
||||
ref := hCell + ":" + vCell
|
||||
if ws.MergeCells != nil {
|
||||
ws.MergeCells.Cells = append(ws.MergeCells.Cells, &xlsxMergeCell{Ref: ref, rect: rect})
|
||||
} else {
|
||||
ws.MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: ref, rect: rect}}}
|
||||
}
|
||||
ws.MergeCells.Count = len(ws.MergeCells.Cells)
|
||||
return err
|
||||
}
|
||||
|
||||
// UnmergeCell provides a function to unmerge a given range reference.
|
||||
// For example unmerge range reference D3:E9 on Sheet1:
|
||||
//
|
||||
// err := f.UnmergeCell("Sheet1", "D3", "E9")
|
||||
//
|
||||
// Attention: overlapped range will also be unmerged.
|
||||
func (f *File) UnmergeCell(sheet, hCell, vCell string) error {
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ws.Lock()
|
||||
defer ws.Unlock()
|
||||
rect1, err := rangeRefToCoordinates(hCell + ":" + vCell)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Correct the range reference, such correct C1:B3 to B1:C3.
|
||||
_ = sortCoordinates(rect1)
|
||||
|
||||
// return nil since no MergeCells in the sheet
|
||||
if ws.MergeCells == nil {
|
||||
return nil
|
||||
}
|
||||
if err = f.mergeOverlapCells(ws); err != nil {
|
||||
return err
|
||||
}
|
||||
i := 0
|
||||
for _, mergeCell := range ws.MergeCells.Cells {
|
||||
if mergeCell == nil {
|
||||
continue
|
||||
}
|
||||
mergedCellsRef := mergeCell.Ref
|
||||
if !strings.Contains(mergedCellsRef, ":") {
|
||||
mergedCellsRef += ":" + mergedCellsRef
|
||||
}
|
||||
rect2, _ := rangeRefToCoordinates(mergedCellsRef)
|
||||
if isOverlap(rect1, rect2) {
|
||||
continue
|
||||
}
|
||||
ws.MergeCells.Cells[i] = mergeCell
|
||||
i++
|
||||
}
|
||||
ws.MergeCells.Cells = ws.MergeCells.Cells[:i]
|
||||
ws.MergeCells.Count = len(ws.MergeCells.Cells)
|
||||
if ws.MergeCells.Count == 0 {
|
||||
ws.MergeCells = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetMergeCells provides a function to get all merged cells from a worksheet
|
||||
// currently.
|
||||
func (f *File) GetMergeCells(sheet string) ([]MergeCell, error) {
|
||||
var mergeCells []MergeCell
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return mergeCells, err
|
||||
}
|
||||
if ws.MergeCells != nil {
|
||||
if err = f.mergeOverlapCells(ws); err != nil {
|
||||
return mergeCells, err
|
||||
}
|
||||
mergeCells = make([]MergeCell, 0, len(ws.MergeCells.Cells))
|
||||
for i := range ws.MergeCells.Cells {
|
||||
ref := ws.MergeCells.Cells[i].Ref
|
||||
cell := strings.Split(ref, ":")[0]
|
||||
val, _ := f.GetCellValue(sheet, cell)
|
||||
mergeCells = append(mergeCells, []string{ref, val})
|
||||
}
|
||||
}
|
||||
return mergeCells, err
|
||||
}
|
||||
|
||||
// overlapRange calculate overlap range of merged cells, and returns max
|
||||
// column and rows of the range.
|
||||
func overlapRange(ws *xlsxWorksheet) (row, col int, err error) {
|
||||
var rect []int
|
||||
for _, mergeCell := range ws.MergeCells.Cells {
|
||||
if mergeCell == nil {
|
||||
continue
|
||||
}
|
||||
if rect, err = mergeCell.Rect(); err != nil {
|
||||
return
|
||||
}
|
||||
x1, y1, x2, y2 := rect[0], rect[1], rect[2], rect[3]
|
||||
if x1 > col {
|
||||
col = x1
|
||||
}
|
||||
if x2 > col {
|
||||
col = x2
|
||||
}
|
||||
if y1 > row {
|
||||
row = y1
|
||||
}
|
||||
if y2 > row {
|
||||
row = y2
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// flatMergedCells convert merged cells range reference to cell-matrix.
|
||||
func flatMergedCells(ws *xlsxWorksheet, matrix [][]*xlsxMergeCell) error {
|
||||
for i, cell := range ws.MergeCells.Cells {
|
||||
rect, err := cell.Rect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
x1, y1, x2, y2 := rect[0]-1, rect[1]-1, rect[2]-1, rect[3]-1
|
||||
var overlapCells []*xlsxMergeCell
|
||||
for x := x1; x <= x2; x++ {
|
||||
for y := y1; y <= y2; y++ {
|
||||
if matrix[x][y] != nil {
|
||||
overlapCells = append(overlapCells, matrix[x][y])
|
||||
}
|
||||
matrix[x][y] = cell
|
||||
}
|
||||
}
|
||||
if len(overlapCells) != 0 {
|
||||
newCell := cell
|
||||
for _, overlapCell := range overlapCells {
|
||||
newCell = mergeCell(cell, overlapCell)
|
||||
}
|
||||
newRect, _ := newCell.Rect()
|
||||
x1, y1, x2, y2 := newRect[0]-1, newRect[1]-1, newRect[2]-1, newRect[3]-1
|
||||
for x := x1; x <= x2; x++ {
|
||||
for y := y1; y <= y2; y++ {
|
||||
matrix[x][y] = newCell
|
||||
}
|
||||
}
|
||||
ws.MergeCells.Cells[i] = newCell
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// mergeOverlapCells merge overlap cells.
|
||||
func (f *File) mergeOverlapCells(ws *xlsxWorksheet) error {
|
||||
rows, cols, err := overlapRange(ws)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rows == 0 || cols == 0 {
|
||||
return nil
|
||||
}
|
||||
matrix := make([][]*xlsxMergeCell, cols)
|
||||
for i := range matrix {
|
||||
matrix[i] = make([]*xlsxMergeCell, rows)
|
||||
}
|
||||
_ = flatMergedCells(ws, matrix)
|
||||
mergeCells := ws.MergeCells.Cells[:0]
|
||||
for _, cell := range ws.MergeCells.Cells {
|
||||
rect, _ := cell.Rect()
|
||||
x1, y1, x2, y2 := rect[0]-1, rect[1]-1, rect[2]-1, rect[3]-1
|
||||
if matrix[x1][y1] == cell {
|
||||
mergeCells = append(mergeCells, cell)
|
||||
for x := x1; x <= x2; x++ {
|
||||
for y := y1; y <= y2; y++ {
|
||||
matrix[x][y] = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ws.MergeCells.Count, ws.MergeCells.Cells = len(mergeCells), mergeCells
|
||||
return nil
|
||||
}
|
||||
|
||||
// mergeCell merge two cells.
|
||||
func mergeCell(cell1, cell2 *xlsxMergeCell) *xlsxMergeCell {
|
||||
rect1, _ := cell1.Rect()
|
||||
rect2, _ := cell2.Rect()
|
||||
|
||||
if rect1[0] > rect2[0] {
|
||||
rect1[0], rect2[0] = rect2[0], rect1[0]
|
||||
}
|
||||
|
||||
if rect1[2] < rect2[2] {
|
||||
rect1[2], rect2[2] = rect2[2], rect1[2]
|
||||
}
|
||||
|
||||
if rect1[1] > rect2[1] {
|
||||
rect1[1], rect2[1] = rect2[1], rect1[1]
|
||||
}
|
||||
|
||||
if rect1[3] < rect2[3] {
|
||||
rect1[3], rect2[3] = rect2[3], rect1[3]
|
||||
}
|
||||
hCell, _ := CoordinatesToCellName(rect1[0], rect1[1])
|
||||
vCell, _ := CoordinatesToCellName(rect1[2], rect1[3])
|
||||
return &xlsxMergeCell{rect: rect1, Ref: hCell + ":" + vCell}
|
||||
}
|
||||
|
||||
// MergeCell define a merged cell data.
|
||||
// It consists of the following structure.
|
||||
// example: []string{"D4:E10", "cell value"}
|
||||
type MergeCell []string
|
||||
|
||||
// GetCellValue returns merged cell value.
|
||||
func (m *MergeCell) GetCellValue() string {
|
||||
return (*m)[1]
|
||||
}
|
||||
|
||||
// GetStartAxis returns the top left cell reference of merged range, for
|
||||
// example: "C2".
|
||||
func (m *MergeCell) GetStartAxis() string {
|
||||
return strings.Split((*m)[0], ":")[0]
|
||||
}
|
||||
|
||||
// GetEndAxis returns the bottom right cell reference of merged range, for
|
||||
// example: "D4".
|
||||
func (m *MergeCell) GetEndAxis() string {
|
||||
return strings.Split((*m)[0], ":")[1]
|
||||
}
|
||||
+972
@@ -0,0 +1,972 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/xuri/nfp"
|
||||
)
|
||||
|
||||
// languageInfo defined the required fields of localization support for number format.
|
||||
type languageInfo struct {
|
||||
apFmt string
|
||||
tags []string
|
||||
localMonth func(t time.Time, abbr int) string
|
||||
}
|
||||
|
||||
// numberFormat directly maps the number format parser runtime required
|
||||
// fields.
|
||||
type numberFormat struct {
|
||||
section []nfp.Section
|
||||
t time.Time
|
||||
sectionIdx int
|
||||
date1904, isNumeric, hours, seconds bool
|
||||
number float64
|
||||
ap, localCode, result, value, valueSectionType string
|
||||
}
|
||||
|
||||
var (
|
||||
// supportedTokenTypes list the supported number format token types currently.
|
||||
supportedTokenTypes = []string{
|
||||
nfp.TokenSubTypeLanguageInfo,
|
||||
nfp.TokenTypeColor,
|
||||
nfp.TokenTypeCurrencyLanguage,
|
||||
nfp.TokenTypeDateTimes,
|
||||
nfp.TokenTypeElapsedDateTimes,
|
||||
nfp.TokenTypeGeneral,
|
||||
nfp.TokenTypeLiteral,
|
||||
nfp.TokenTypeTextPlaceHolder,
|
||||
nfp.TokenTypeZeroPlaceHolder,
|
||||
}
|
||||
// supportedLanguageInfo directly maps the supported language ID and tags.
|
||||
supportedLanguageInfo = map[string]languageInfo{
|
||||
"36": {tags: []string{"af"}, localMonth: localMonthsNameAfrikaans, apFmt: apFmtAfrikaans},
|
||||
"445": {tags: []string{"bn-IN"}, localMonth: localMonthsNameBangla, apFmt: nfp.AmPm[0]},
|
||||
"4": {tags: []string{"zh-Hans"}, localMonth: localMonthsNameChinese1, apFmt: nfp.AmPm[2]},
|
||||
"7804": {tags: []string{"zh"}, localMonth: localMonthsNameChinese1, apFmt: nfp.AmPm[2]},
|
||||
"804": {tags: []string{"zh-CN"}, localMonth: localMonthsNameChinese1, apFmt: nfp.AmPm[2]},
|
||||
"1004": {tags: []string{"zh-SG"}, localMonth: localMonthsNameChinese2, apFmt: nfp.AmPm[2]},
|
||||
"7C04": {tags: []string{"zh-Hant"}, localMonth: localMonthsNameChinese3, apFmt: nfp.AmPm[2]},
|
||||
"C04": {tags: []string{"zh-HK"}, localMonth: localMonthsNameChinese2, apFmt: nfp.AmPm[2]},
|
||||
"1404": {tags: []string{"zh-MO"}, localMonth: localMonthsNameChinese3, apFmt: nfp.AmPm[2]},
|
||||
"404": {tags: []string{"zh-TW"}, localMonth: localMonthsNameChinese3, apFmt: nfp.AmPm[2]},
|
||||
"9": {tags: []string{"en"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]},
|
||||
"1000": {tags: []string{
|
||||
"aa", "aa-DJ", "aa-ER", "aa-ER", "aa-NA", "agq", "agq-CM", "ak", "ak-GH", "sq-ML",
|
||||
"gsw-LI", "gsw-CH", "ar-TD", "ar-KM", "ar-DJ", "ar-ER", "ar-IL", "ar-MR", "ar-PS",
|
||||
"ar-SO", "ar-SS", "ar-SD", "ar-001", "ast", "ast-ES", "asa", "asa-TZ", "ksf", "ksf-CM",
|
||||
"bm", "bm-Latn-ML", "bas", "bas-CM", "bem", "bem-ZM", "bez", "bez-TZ", "byn", "byn-ER",
|
||||
"brx", "brx-IN", "ca-AD", "ca-FR", "ca-IT", "ceb", "ceb-Latn", "ceb-Latn-PH", "tzm-Latn-MA",
|
||||
"ccp", "ccp-Cakm", "ccp-Cakm-BD", "ccp-Cakm-IN", "ce-RU", "cgg", "cgg-UG", "cu-RU", "swc",
|
||||
"swc-CD", "kw", "ke-GB", "da-GL", "dua", "dua-CM", "nl-AW", "nl-BQ", "nl-CW", "nl-SX",
|
||||
"nl-SR", "dz", "ebu", "ebu-KE", "en-AS", "en-AI", "en-AG", "en-AT", "en-BS", "en-BB",
|
||||
"en-BE", "en-BM", "en-BW", "en-IO", "en-VG", "en-BI", "en-CM", "en-KY", "en-CX", "en-CC",
|
||||
"en-CK", "en-CY", "en-DK", "en-DM", "en-ER", "en-150", "en-FK", "en-FI", "en-FJ", "en-GM",
|
||||
"en-DE", "en-GH", "en-GI", "en-GD", "en-GU", "en-GG", "en-GY", "en-IM", "en-IL", "en-JE",
|
||||
"en-KE", "en-KI", "en-LS", "en-LR", "en-MO", "en-MG", "en-MW", "en-MT", "en-MH", "en-MU",
|
||||
"en-FM", "en-MS", "en-NA", "en-NR", "en-NL", "en-NG", "en-NU", "en-NF", "en-MP", "en-PK",
|
||||
"en-PW", "en-PG", "en-PN", "en-PR", "en-RW", "en-KN", "en-LC", "en-VC", "en-WS", "en-SC",
|
||||
"en-SL", "en-SX", "en-SI", "en-SB", "en-SS", "en-SH", "en-SD", "en-SZ", "en-SE", "en-CH",
|
||||
"en-TZ", "en-TK", "en-TO", "en-TC", "en-TV", "en-UG", "en-UM", "en-VI", "en-VU", "en-001",
|
||||
"en-ZM", "eo", "eo-001", "ee", "ee-GH", "ee-TG", "ewo", "ewo-CM", "fo-DK", "fr-DZ",
|
||||
"fr-BJ", "fr-BF", "fr-BI", "fr-CF", "fr-TD", "fr-KM", "fr-CG", "fr-DJ", "fr-GQ", "fr-GF",
|
||||
"fr-PF", "fr-GA", "fr-GP", "fr-GN", "fr-MG", "fr-MQ", "fr-MR", "fr-MU", "fr-YT", "fr-NC",
|
||||
"fr-NE", "fr-RW", "fr-BL", "fr-MF", "fr-PM", "fr-SC", "fr-SY", "fr-TG", "fr-TN", "fr-VU",
|
||||
"fr-WF", "fur", "fur-IT", "ff-Latn-BF", "ff-CM", "ff-Latn-CM", "ff-Latn-GM", "ff-Latn-GH",
|
||||
"ff-GN", "ff-Latn-GN", "ff-Latn-GW", "ff-Latn-LR", "ff-MR", "ff-Latn-MR", "ff-Latn-NE",
|
||||
"ff-Latn-SL", "lg", "lg-UG", "de-BE", "de-IT", "el-CY", "guz", "guz-KE", "ha-Latn-GH",
|
||||
"ha-Latn-NG", "ia-FR", "ia-001", "it-SM", "it-VA", "jv", "jv-Latn", "jv-Latn-ID", "dyo",
|
||||
"dyo-SN", "kea", "kea-CV", "kab", "kab-DZ", "kkj", "kkj-CM", "kln", "kln-KE", "kam",
|
||||
"kam-KE", "ks-Arab-IN", "ki", "ki-KE", "sw-TZ", "sw-UG", "ko-KP", "khq", "khq-ML", "ses",
|
||||
"ses-ML", "nmg", "nmq-CM", "ku-Arab-IR", "lkt", "lkt-US", "lag", "lag-TZ", "ln", "ln-AO",
|
||||
"ln-CF", "ln-CD", "nds", "nds-DE", "nds-NL", "lu", "lu-CD", "luo", "luo", "luo-KE", "luy",
|
||||
"luy-KE", "jmc", "jmc-TZ", "mgh", "mgh-MZ", "kde", "kde-TZ", "mg", "mg-MG", "gv", "gv-IM",
|
||||
"mas", "mas-KE", "mas-TZ", "mas-IR", "mer", "mer-KE", "mgo", "mgo-CM", "mfe", "mfe-MU",
|
||||
"mua", "mua-CM", "nqo", "nqo-GN", "nqa", "naq-NA", "nnh", "nnh-CM", "jgo", "jgo-CM",
|
||||
"lrc-IQ", "lrc-IR", "nd", "nd-ZW", "nb-SJ", "nus", "nus-SD", "nus-SS", "nyn", "nyn-UG",
|
||||
"om-KE", "os", "os-GE", "os-RU", "ps-PK", "fa-AF", "pt-AO", "pt-CV", "pt-GQ", "pt-GW",
|
||||
"pt-LU", "pt-MO", "pt-MZ", "pt-ST", "pt-CH", "pt-TL", "prg-001", "ksh", "ksh-DE", "rof",
|
||||
"rof-TZ", "rn", "rn-BI", "ru-BY", "ru-KZ", "ru-KG", "ru-UA", "rwk", "rwk-TZ", "ssy",
|
||||
"ssy-ER", "saq", "saq-KE", "sg", "sq-CF", "sbp", "sbp-TZ", "seh", "seh-MZ", "ksb", "ksb-TZ",
|
||||
"sn", "sn-Latn", "sn-Latn-ZW", "xog", "xog-UG", "so-DJ", "so-ET", "so-KE", "nr", "nr-ZA",
|
||||
"st-LS", "es-BZ", "es-BR", "es-PH", "zgh", "zgh-Tfng-MA", "zgh-Tfng", "ss", "ss-ZA",
|
||||
"ss-SZ", "sv-AX", "shi", "shi-Tfng", "shi-Tfng-MA", "shi-Latn", "shi-Latn-MA", "dav",
|
||||
"dav-KE", "ta-MY", "ta-SG", "twq", "twq-NE", "teo", "teo-KE", "teo-UG", "bo-IN", "tig",
|
||||
"tig-ER", "to", "to-TO", "tr-CY", "uz-Arab", "us-Arab-AF", "vai", "vai-Vaii",
|
||||
"vai-Vaii-LR", "vai-Latn-LR", "vai-Latn", "vo", "vo-001", "vun", "vun-TZ", "wae",
|
||||
"wae-CH", "wal", "wae-ET", "yav", "yav-CM", "yo-BJ", "dje", "dje-NE",
|
||||
}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]},
|
||||
"C09": {tags: []string{"en-AU"}, localMonth: localMonthsNameEnglish, apFmt: strings.ToLower(nfp.AmPm[0])},
|
||||
"2829": {tags: []string{"en-BZ"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]},
|
||||
"1009": {tags: []string{"en-CA"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]},
|
||||
"2409": {tags: []string{"en-029"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]},
|
||||
"3C09": {tags: []string{"en-HK"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]},
|
||||
"4009": {tags: []string{"en-IN"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]},
|
||||
"1809": {tags: []string{"en-IE"}, localMonth: localMonthsNameEnglish, apFmt: strings.ToLower(nfp.AmPm[0])},
|
||||
"2009": {tags: []string{"en-JM"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]},
|
||||
"4409": {tags: []string{"en-MY"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]},
|
||||
"1409": {tags: []string{"en-NZ"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]},
|
||||
"3409": {tags: []string{"en-PH"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]},
|
||||
"4809": {tags: []string{"en-SG"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]},
|
||||
"1C09": {tags: []string{"en-ZA"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]},
|
||||
"2C09": {tags: []string{"en-TT"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]},
|
||||
"4C09": {tags: []string{"en-AE"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]},
|
||||
"809": {tags: []string{"en-GB"}, localMonth: localMonthsNameEnglish, apFmt: strings.ToLower(nfp.AmPm[0])},
|
||||
"409": {tags: []string{"en-US"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]},
|
||||
"3009": {tags: []string{"en-ZW"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]},
|
||||
"C": {tags: []string{"fr"}, localMonth: localMonthsNameFrench, apFmt: nfp.AmPm[0]},
|
||||
"7": {tags: []string{"de"}, localMonth: localMonthsNameGerman, apFmt: nfp.AmPm[0]},
|
||||
"C07": {tags: []string{"de-AT"}, localMonth: localMonthsNameAustria, apFmt: nfp.AmPm[0]},
|
||||
"407": {tags: []string{"de-DE"}, localMonth: localMonthsNameGerman, apFmt: nfp.AmPm[0]},
|
||||
"3C": {tags: []string{"ga"}, localMonth: localMonthsNameIrish, apFmt: apFmtIrish},
|
||||
"83C": {tags: []string{"ga-IE"}, localMonth: localMonthsNameIrish, apFmt: apFmtIrish},
|
||||
"10": {tags: []string{"it"}, localMonth: localMonthsNameItalian, apFmt: nfp.AmPm[0]},
|
||||
"11": {tags: []string{"ja"}, localMonth: localMonthsNameChinese3, apFmt: apFmtJapanese},
|
||||
"411": {tags: []string{"ja-JP"}, localMonth: localMonthsNameChinese3, apFmt: apFmtJapanese},
|
||||
"12": {tags: []string{"ko"}, localMonth: localMonthsNameKorean, apFmt: apFmtKorean},
|
||||
"412": {tags: []string{"ko-KR"}, localMonth: localMonthsNameKorean, apFmt: apFmtKorean},
|
||||
"7C50": {tags: []string{"mn-Mong"}, localMonth: localMonthsNameTraditionalMongolian, apFmt: nfp.AmPm[0]},
|
||||
"850": {tags: []string{"mn-Mong-CN"}, localMonth: localMonthsNameTraditionalMongolian, apFmt: nfp.AmPm[0]},
|
||||
"C50": {tags: []string{"mn-Mong-MN"}, localMonth: localMonthsNameTraditionalMongolian, apFmt: nfp.AmPm[0]},
|
||||
"19": {tags: []string{"ru"}, localMonth: localMonthsNameRussian, apFmt: nfp.AmPm[0]},
|
||||
"819": {tags: []string{"ru-MD"}, localMonth: localMonthsNameRussian, apFmt: nfp.AmPm[0]},
|
||||
"419": {tags: []string{"ru-RU"}, localMonth: localMonthsNameRussian, apFmt: nfp.AmPm[0]},
|
||||
"A": {tags: []string{"es"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanish},
|
||||
"2C0A": {tags: []string{"es-AR"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanish},
|
||||
"200A": {tags: []string{"es-VE"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanish},
|
||||
"400A": {tags: []string{"es-BO"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanish},
|
||||
"340A": {tags: []string{"es-CL"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanish},
|
||||
"240A": {tags: []string{"es-CO"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanish},
|
||||
"140A": {tags: []string{"es-CR"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanish},
|
||||
"5C0A": {tags: []string{"es-CU"}, localMonth: localMonthsNameSpanish, apFmt: apFmtCuba},
|
||||
"1C0A": {tags: []string{"es-DO"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanish},
|
||||
"300A": {tags: []string{"es-EC"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanish},
|
||||
"440A": {tags: []string{"es-SV"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanish},
|
||||
"1E": {tags: []string{"th"}, localMonth: localMonthsNameThai, apFmt: nfp.AmPm[0]},
|
||||
"41E": {tags: []string{"th-TH"}, localMonth: localMonthsNameThai, apFmt: nfp.AmPm[0]},
|
||||
"51": {tags: []string{"bo"}, localMonth: localMonthsNameTibetan, apFmt: apFmtTibetan},
|
||||
"451": {tags: []string{"bo-CN"}, localMonth: localMonthsNameTibetan, apFmt: apFmtTibetan},
|
||||
"1F": {tags: []string{"tr"}, localMonth: localMonthsNameTurkish, apFmt: apFmtTurkish},
|
||||
"41F": {tags: []string{"tr-TR"}, localMonth: localMonthsNameTurkish, apFmt: apFmtTurkish},
|
||||
"52": {tags: []string{"cy"}, localMonth: localMonthsNameWelsh, apFmt: apFmtWelsh},
|
||||
"452": {tags: []string{"cy-GB"}, localMonth: localMonthsNameWelsh, apFmt: apFmtWelsh},
|
||||
"2A": {tags: []string{"vi"}, localMonth: localMonthsNameVietnamese, apFmt: apFmtVietnamese},
|
||||
"42A": {tags: []string{"vi-VN"}, localMonth: localMonthsNameVietnamese, apFmt: apFmtVietnamese},
|
||||
"88": {tags: []string{"wo"}, localMonth: localMonthsNameWolof, apFmt: apFmtWolof},
|
||||
"488": {tags: []string{"wo-SN"}, localMonth: localMonthsNameWolof, apFmt: apFmtWolof},
|
||||
"34": {tags: []string{"xh"}, localMonth: localMonthsNameXhosa, apFmt: nfp.AmPm[0]},
|
||||
"434": {tags: []string{"xh-ZA"}, localMonth: localMonthsNameXhosa, apFmt: nfp.AmPm[0]},
|
||||
"78": {tags: []string{"ii"}, localMonth: localMonthsNameYi, apFmt: apFmtYi},
|
||||
"478": {tags: []string{"ii-CN"}, localMonth: localMonthsNameYi, apFmt: apFmtYi},
|
||||
"35": {tags: []string{"zu"}, localMonth: localMonthsNameZulu, apFmt: nfp.AmPm[0]},
|
||||
"435": {tags: []string{"zu-ZA"}, localMonth: localMonthsNameZulu, apFmt: nfp.AmPm[0]},
|
||||
}
|
||||
// monthNamesBangla list the month names in the Bangla.
|
||||
monthNamesBangla = []string{
|
||||
"\u099C\u09BE\u09A8\u09C1\u09AF\u09BC\u09BE\u09B0\u09C0",
|
||||
"\u09AB\u09C7\u09AC\u09CD\u09B0\u09C1\u09AF\u09BC\u09BE\u09B0\u09C0",
|
||||
"\u09AE\u09BE\u09B0\u09CD\u099A",
|
||||
"\u098F\u09AA\u09CD\u09B0\u09BF\u09B2",
|
||||
"\u09AE\u09C7",
|
||||
"\u099C\u09C1\u09A8",
|
||||
"\u099C\u09C1\u09B2\u09BE\u0987",
|
||||
"\u0986\u0997\u09B8\u09CD\u099F",
|
||||
"\u09B8\u09C7\u09AA\u09CD\u099F\u09C7\u09AE\u09CD\u09AC\u09B0",
|
||||
"\u0985\u0995\u09CD\u099F\u09CB\u09AC\u09B0",
|
||||
"\u09A8\u09AD\u09C7\u09AE\u09CD\u09AC\u09B0",
|
||||
"\u09A1\u09BF\u09B8\u09C7\u09AE\u09CD\u09AC\u09B0",
|
||||
}
|
||||
// monthNamesAfrikaans list the month names in the Afrikaans.
|
||||
monthNamesAfrikaans = []string{"Januarie", "Februarie", "Maart", "April", "Mei", "Junie", "Julie", "Augustus", "September", "Oktober", "November", "Desember"}
|
||||
// monthNamesAfrikaansAbbr lists the month name abbreviations in Afrikaans
|
||||
monthNamesAfrikaansAbbr = []string{"Jan.", "Feb.", "Maa.", "Apr.", "Mei", "Jun.", "Jul.", "Aug.", "Sep.", "Okt.", "Nov.", "Des."}
|
||||
// monthNamesChinese list the month names in the Chinese.
|
||||
monthNamesChinese = []string{"一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "十二"}
|
||||
// monthNamesChineseAbbr1 list the month number and character abbreviation in Chinese
|
||||
monthNamesChineseAbbrPlus = []string{"0月", "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月"}
|
||||
// monthNamesChinesePlus list the month names in Chinese plus the character 月
|
||||
monthNamesChinesePlus = []string{"一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"}
|
||||
// monthNamesKoreanAbbrPlus lists out the month number plus 월 for the Korean language
|
||||
monthNamesKoreanAbbrPlus = []string{"0월", "1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월"}
|
||||
// monthNamesTradMongolian lists the month number for use with traditional Mongolian
|
||||
monthNamesTradMongolian = []string{"M01", "M02", "M03", "M04", "M05", "M06", "M07", "M08", "M09", "M10", "M11", "M12"}
|
||||
// monthNamesFrench list the month names in the French.
|
||||
monthNamesFrench = []string{"janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre"}
|
||||
// monthNamesFrenchAbbr lists the month name abbreviations in French
|
||||
monthNamesFrenchAbbr = []string{"janv.", "févr.", "mars", "avri.", "mai", "juin", "juil.", "août", "sept.", "octo.", "nove.", "déce."}
|
||||
// monthNamesGerman list the month names in the German.
|
||||
monthNamesGerman = []string{"Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"}
|
||||
// monthNamesGermanAbbr list the month abbreviations in German
|
||||
monthNamesGermanAbbr = []string{"Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"}
|
||||
// monthNamesAustria list the month names in the Austria.
|
||||
monthNamesAustria = []string{"Jänner", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"}
|
||||
// monthNamesAustriaAbbr list the month name abbreviations in Austrian
|
||||
monthNamesAustriaAbbr = []string{"Jän", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"}
|
||||
// monthNamesIrish list the month names in the Irish.
|
||||
monthNamesIrish = []string{"Eanáir", "Feabhra", "Márta", "Aibreán", "Bealtaine", "Meitheamh", "Iúil", "Lúnasa", "Meán Fómhair", "Deireadh Fómhair", "Samhain", "Nollaig"}
|
||||
// monthNamesIrishAbbr lists the month abbreviations in Irish
|
||||
monthNamesIrishAbbr = []string{"Ean", "Feabh", "Márta", "Aib", "Beal", "Meith", "Iúil", "Lún", "MFómh", "DFómh", "Samh", "Noll"}
|
||||
// monthNamesItalian list the month names in the Italian.
|
||||
monthNamesItalian = []string{"gennaio", "febbraio", "marzo", "aprile", "maggio", "giugno", "luglio", "agosto", "settembre", "ottobre", "novembre", "dicembre"}
|
||||
// monthNamesItalianAbbr list the month name abbreviations in Italian
|
||||
monthNamesItalianAbbr = []string{"gen", "feb", "mar", "apr", "mag", "giu", "lug", "ago", "set", "ott", "nov", "dic"}
|
||||
// monthNamesRussian list the month names in the Russian.
|
||||
monthNamesRussian = []string{"январь", "февраль", "март", "апрель", "май", "июнь", "июль", "август", "сентябрь", "октябрь", "ноябрь", "декабрь"}
|
||||
// monthNamesRussianAbbr list the month abbreviations for Russian.
|
||||
monthNamesRussianAbbr = []string{"янв.", "фев.", "март", "апр.", "май", "июнь", "июль", "авг.", "сен.", "окт.", "ноя.", "дек."}
|
||||
// monthNamesSpanish list the month names in the Spanish.
|
||||
monthNamesSpanish = []string{"enero", "febrero", "marzo", "abril", "mayo", "junio", "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre"}
|
||||
// monthNamesSpanishAbbr list the month abbreviations in Spanish
|
||||
monthNamesSpanishAbbr = []string{"ene", "feb", "mar", "abr", "may", "jun", "jul", "ago", "sep", "oct", "nov", "dic"}
|
||||
// monthNamesThai list the month names in the Thai.
|
||||
monthNamesThai = []string{
|
||||
"\u0e21\u0e01\u0e23\u0e32\u0e04\u0e21",
|
||||
"\u0e01\u0e38\u0e21\u0e20\u0e32\u0e1e\u0e31\u0e19\u0e18\u0e4c",
|
||||
"\u0e21\u0e35\u0e19\u0e32\u0e04\u0e21",
|
||||
"\u0e40\u0e21\u0e29\u0e32\u0e22\u0e19",
|
||||
"\u0e1e\u0e24\u0e29\u0e20\u0e32\u0e04\u0e21",
|
||||
"\u0e21\u0e34\u0e16\u0e38\u0e19\u0e32\u0e22\u0e19",
|
||||
"\u0e01\u0e23\u0e01\u0e0e\u0e32\u0e04\u0e21",
|
||||
"\u0e2a\u0e34\u0e07\u0e2b\u0e32\u0e04\u0e21",
|
||||
"\u0e01\u0e31\u0e19\u0e22\u0e32\u0e22\u0e19",
|
||||
"\u0e15\u0e38\u0e25\u0e32\u0e04\u0e21",
|
||||
"\u0e1e\u0e24\u0e28\u0e08\u0e34\u0e01\u0e32\u0e22\u0e19",
|
||||
"\u0e18\u0e31\u0e19\u0e27\u0e32\u0e04\u0e21",
|
||||
}
|
||||
// monthNamesTibetan list the month names in the Tibetan.
|
||||
monthNamesTibetan = []string{
|
||||
"\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f51\u0f44\u0f0b\u0f54\u0f7c\u0f0b",
|
||||
"\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f42\u0f49\u0f72\u0f66\u0f0b\u0f54\u0f0b",
|
||||
"\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f42\u0f66\u0f74\u0f58\u0f0b\u0f54\u0f0b",
|
||||
"\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f56\u0f5e\u0f72\u0f0b\u0f54\u0f0b",
|
||||
"\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f63\u0f94\u0f0b\u0f54\u0f0b",
|
||||
"\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f51\u0fb2\u0f74\u0f42\u0f0b\u0f54\u0f0b",
|
||||
"\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f56\u0f51\u0f74\u0f53\u0f0b\u0f54\u0f0b",
|
||||
"\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f56\u0f62\u0f92\u0fb1\u0f51\u0f0b\u0f54\u0f0b",
|
||||
"\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f51\u0f42\u0f74\u0f0b\u0f54\u0f0b",
|
||||
"\u0f66\u0fa4\u0fb1\u0f72\u0f0b\u0f5f\u0fb3\u0f0b\u0f56\u0f45\u0f74\u0f0b\u0f54\u0f0d",
|
||||
"\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f56\u0f45\u0f74\u0f0b\u0f42\u0f45\u0f72\u0f42\u0f0b\u0f54\u0f0b",
|
||||
"\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f56\u0f45\u0f74\u0f0b\u0f42\u0f49\u0f72\u0f66\u0f0b\u0f54\u0f0b",
|
||||
}
|
||||
// monthNamesTibetanAbbr lists the month name abbreviations in Tibetan
|
||||
monthNamesTibetanAbbr = []string{
|
||||
"\u0f5f\u0fb3\u0f0b\u0f21",
|
||||
"\u0f5f\u0fb3\u0f0b\u0f22",
|
||||
"\u0f5f\u0fb3\u0f0b\u0f23",
|
||||
"\u0f5f\u0fb3\u0f0b\u0f24",
|
||||
"\u0f5f\u0fb3\u0f0b\u0f25",
|
||||
"\u0f5f\u0fb3\u0f0b\u0f26",
|
||||
"\u0f5f\u0fb3\u0f0b\u0f27",
|
||||
"\u0f5f\u0fb3\u0f0b\u0f28",
|
||||
"\u0f5f\u0fb3\u0f0b\u0f29",
|
||||
"\u0f5f\u0fb3\u0f0b\u0f21\u0f20",
|
||||
"\u0f5f\u0fb3\u0f0b\u0f21\u0f21",
|
||||
"\u0f5f\u0fb3\u0f0b\u0f21\u0f22",
|
||||
}
|
||||
// monthNamesTurkish list the month names in the Turkish.
|
||||
monthNamesTurkish = []string{"Ocak", "Şubat", "Mart", "Nisan", "Mayıs", "Haziran", "Temmuz", "Ağustos", "Eylül", "Ekim", "Kasım", "Aralık"}
|
||||
// monthNamesTurkishAbbr lists the month name abbreviations in Turkish, this prevents string concatenation
|
||||
monthNamesTurkishAbbr = []string{"Oca", "Şub", "Mar", "Nis", "May", "Haz", "Tem", "Ağu", "Eyl", "Eki", "Kas", "Ara"}
|
||||
// monthNamesVietnamese list the month name used for Vietnamese
|
||||
monthNamesVietnamese = []string{"Tháng 1", "Tháng 2", "Tháng 3", "Tháng 4", "Tháng 5", "Tháng 6", "Tháng 7", "Tháng 8", "Tháng 9", "Tháng 10", "Tháng 11", "Tháng 12"}
|
||||
// monthNamesVietnameseAbbr3 list the mid-form abbreviation for Vietnamese months
|
||||
monthNamesVietnameseAbbr3 = []string{"Thg 1", "Thg 2", "Thg 3", "Thg 4", "Thg 5", "Thg 6", "Thg 7", "Thg 8", "Thg 9", "Thg 10", "Thg 11", "Thg 12"}
|
||||
// monthNamesVietnameseAbbr5 list the short-form abbreviation for Vietnamese months
|
||||
monthNamesVietnameseAbbr5 = []string{"T 1", "T 2", "T 3", "T 4", "T 5", "T 6", "T 7", "T 8", "T 9", "T 10", "T 11", "T 12"}
|
||||
// monthNamesWelsh list the month names in the Welsh.
|
||||
monthNamesWelsh = []string{"Ionawr", "Chwefror", "Mawrth", "Ebrill", "Mai", "Mehefin", "Gorffennaf", "Awst", "Medi", "Hydref", "Tachwedd", "Rhagfyr"}
|
||||
// monthNamesWelshAbbr lists the month name abbreviations in Welsh, this prevents string concatenation
|
||||
monthNamesWelshAbbr = []string{"Ion", "Chwef", "Maw", "Ebr", "Mai", "Meh", "Gorff", "Awst", "Medi", "Hyd", "Tach", "Rhag"}
|
||||
// monthNamesWolof list the month names in the Wolof.
|
||||
monthNamesWolof = []string{"Samwiye", "Fewriye", "Maars", "Awril", "Me", "Suwe", "Sullet", "Ut", "Septàmbar", "Oktoobar", "Noowàmbar", "Desàmbar"}
|
||||
// monthNamesWolofAbbr list the month name abbreviations in Wolof, this prevents string concatenation
|
||||
monthNamesWolofAbbr = []string{"Sam.", "Few.", "Maa", "Awr.", "Me", "Suw", "Sul.", "Ut", "Sept.", "Okt.", "Now.", "Des."}
|
||||
// monthNamesXhosa list the month names in the Xhosa.
|
||||
monthNamesXhosa = []string{"uJanuwari", "uFebuwari", "uMatshi", "uAprili", "uMeyi", "uJuni", "uJulayi", "uAgasti", "uSeptemba", "uOktobha", "uNovemba", "uDisemba"}
|
||||
// monthNamesXhosaAbbr list the month abbreviations in the Xhosa, this prevents string concatenation
|
||||
monthNamesXhosaAbbr = []string{"uJan.", "uFeb.", "uMat.", "uEpr.", "uMey.", "uJun.", "uJul.", "uAg.", "uSep.", "uOkt.", "uNov.", "uDis."}
|
||||
// monthNamesYi list the month names in the Yi.
|
||||
monthNamesYi = []string{"\ua2cd", "\ua44d", "\ua315", "\ua1d6", "\ua26c", "\ua0d8", "\ua3c3", "\ua246", "\ua22c", "\ua2b0", "\ua2b0\ua2aa", "\ua2b0\ua44b"}
|
||||
// monthNamesYiSuffix lists the month names in Yi with the "\ua1aa" suffix
|
||||
monthNamesYiSuffix = []string{"\ua2cd\ua1aa", "\ua44d\ua1aa", "\ua315\ua1aa", "\ua1d6\ua1aa", "\ua26c\ua1aa", "\ua0d8\ua1aa", "\ua3c3\ua1aa", "\ua246\ua1aa", "\ua22c\ua1aa", "\ua2b0\ua1aa", "\ua2b0\ua2aa\ua1aa", "\ua2b0\ua44b\ua1aa"}
|
||||
// monthNamesZulu list the month names in the Zulu.
|
||||
monthNamesZulu = []string{"Januwari", "Febhuwari", "Mashi", "Ephreli", "Meyi", "Juni", "Julayi", "Agasti", "Septemba", "Okthoba", "Novemba", "Disemba"}
|
||||
// monthNamesZuluAbbr list the month name abbreviations in Zulu
|
||||
monthNamesZuluAbbr = []string{"Jan", "Feb", "Mas", "Eph", "Mey", "Jun", "Jul", "Agas", "Sep", "Okt", "Nov", "Dis"}
|
||||
// apFmtAfrikaans defined the AM/PM name in the Afrikaans.
|
||||
apFmtAfrikaans = "vm./nm."
|
||||
// apFmtCuba defined the AM/PM name in the Cuba.
|
||||
apFmtCuba = "a.m./p.m."
|
||||
// apFmtIrish defined the AM/PM name in the Irish.
|
||||
apFmtIrish = "r.n./i.n."
|
||||
// apFmtJapanese defined the AM/PM name in the Japanese.
|
||||
apFmtJapanese = "午前/午後"
|
||||
// apFmtKorean defined the AM/PM name in the Korean.
|
||||
apFmtKorean = "오전/오후"
|
||||
// apFmtSpanish defined the AM/PM name in the Spanish.
|
||||
apFmtSpanish = "a. m./p. m."
|
||||
// apFmtTibetan defined the AM/PM name in the Tibetan.
|
||||
apFmtTibetan = "\u0f66\u0f94\u0f0b\u0f51\u0fb2\u0f7c\u0f0b/\u0f55\u0fb1\u0f72\u0f0b\u0f51\u0fb2\u0f7c\u0f0b"
|
||||
// apFmtTurkish defined the AM/PM name in the Turkish.
|
||||
apFmtTurkish = "\u00F6\u00F6/\u00F6\u0053"
|
||||
// apFmtVietnamese defined the AM/PM name in the Vietnamese.
|
||||
apFmtVietnamese = "SA/CH"
|
||||
// apFmtWolof defined the AM/PM name in the Wolof.
|
||||
apFmtWolof = "Sub/Ngo"
|
||||
// apFmtYi defined the AM/PM name in the Yi.
|
||||
apFmtYi = "\ua3b8\ua111/\ua06f\ua2d2"
|
||||
// apFmtWelsh defined the AM/PM name in the Welsh.
|
||||
apFmtWelsh = "yb/yh"
|
||||
)
|
||||
|
||||
// prepareNumberic split the number into two before and after parts by a
|
||||
// decimal point.
|
||||
func (nf *numberFormat) prepareNumberic(value string) {
|
||||
if nf.isNumeric, _, _ = isNumeric(value); !nf.isNumeric {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// format provides a function to return a string parse by number format
|
||||
// expression. If the given number format is not supported, this will return
|
||||
// the original cell value.
|
||||
func format(value, numFmt string, date1904 bool) string {
|
||||
p := nfp.NumberFormatParser()
|
||||
nf := numberFormat{section: p.Parse(numFmt), value: value, date1904: date1904}
|
||||
nf.number, nf.valueSectionType = nf.getValueSectionType(value)
|
||||
nf.prepareNumberic(value)
|
||||
for i, section := range nf.section {
|
||||
nf.sectionIdx = i
|
||||
if section.Type != nf.valueSectionType {
|
||||
continue
|
||||
}
|
||||
if nf.isNumeric {
|
||||
switch section.Type {
|
||||
case nfp.TokenSectionPositive:
|
||||
return nf.positiveHandler()
|
||||
case nfp.TokenSectionNegative:
|
||||
return nf.negativeHandler()
|
||||
default:
|
||||
return nf.zeroHandler()
|
||||
}
|
||||
}
|
||||
return nf.textHandler()
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// positiveHandler will be handling positive selection for a number format
|
||||
// expression.
|
||||
func (nf *numberFormat) positiveHandler() (result string) {
|
||||
nf.t, nf.hours, nf.seconds = timeFromExcelTime(nf.number, nf.date1904), false, false
|
||||
for i, token := range nf.section[nf.sectionIdx].Items {
|
||||
if inStrSlice(supportedTokenTypes, token.TType, true) == -1 || token.TType == nfp.TokenTypeGeneral {
|
||||
result = nf.value
|
||||
return
|
||||
}
|
||||
if token.TType == nfp.TokenTypeCurrencyLanguage {
|
||||
if err := nf.currencyLanguageHandler(i, token); err != nil {
|
||||
result = nf.value
|
||||
return
|
||||
}
|
||||
}
|
||||
if token.TType == nfp.TokenTypeDateTimes {
|
||||
nf.dateTimesHandler(i, token)
|
||||
}
|
||||
if token.TType == nfp.TokenTypeElapsedDateTimes {
|
||||
nf.elapsedDateTimesHandler(token)
|
||||
}
|
||||
if token.TType == nfp.TokenTypeLiteral {
|
||||
nf.result += token.TValue
|
||||
continue
|
||||
}
|
||||
if token.TType == nfp.TokenTypeZeroPlaceHolder && token.TValue == strings.Repeat("0", len(token.TValue)) {
|
||||
if isNum, precision, decimal := isNumeric(nf.value); isNum {
|
||||
if nf.number < 1 {
|
||||
nf.result += "0"
|
||||
continue
|
||||
}
|
||||
if precision > 15 {
|
||||
nf.result += strconv.FormatFloat(decimal, 'f', -1, 64)
|
||||
} else {
|
||||
nf.result += fmt.Sprintf("%.f", nf.number)
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
result = nf.result
|
||||
return
|
||||
}
|
||||
|
||||
// currencyLanguageHandler will be handling currency and language types tokens for a number
|
||||
// format expression.
|
||||
func (nf *numberFormat) currencyLanguageHandler(i int, token nfp.Token) (err error) {
|
||||
for _, part := range token.Parts {
|
||||
if inStrSlice(supportedTokenTypes, part.Token.TType, true) == -1 {
|
||||
err = ErrUnsupportedNumberFormat
|
||||
return
|
||||
}
|
||||
if _, ok := supportedLanguageInfo[strings.ToUpper(part.Token.TValue)]; !ok {
|
||||
err = ErrUnsupportedNumberFormat
|
||||
return
|
||||
}
|
||||
nf.localCode = strings.ToUpper(part.Token.TValue)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// localAmPm return AM/PM name by supported language ID.
|
||||
func (nf *numberFormat) localAmPm(ap string) string {
|
||||
if languageInfo, ok := supportedLanguageInfo[nf.localCode]; ok {
|
||||
return languageInfo.apFmt
|
||||
}
|
||||
return ap
|
||||
}
|
||||
|
||||
// localMonthsNameEnglish returns the English name of the month.
|
||||
func localMonthsNameEnglish(t time.Time, abbr int) string {
|
||||
if abbr == 3 {
|
||||
return t.Month().String()[:3]
|
||||
}
|
||||
if abbr == 4 {
|
||||
return t.Month().String()
|
||||
}
|
||||
return t.Month().String()[:1]
|
||||
}
|
||||
|
||||
// localMonthsNameAfrikaans returns the Afrikaans name of the month.
|
||||
func localMonthsNameAfrikaans(t time.Time, abbr int) string {
|
||||
if abbr == 3 {
|
||||
return monthNamesAfrikaansAbbr[int(t.Month())-1]
|
||||
}
|
||||
if abbr == 4 {
|
||||
return monthNamesAfrikaans[int(t.Month())-1]
|
||||
}
|
||||
return monthNamesAfrikaansAbbr[int(t.Month())-1][:1]
|
||||
}
|
||||
|
||||
// localMonthsNameAustria returns the Austria name of the month.
|
||||
func localMonthsNameAustria(t time.Time, abbr int) string {
|
||||
if abbr == 3 {
|
||||
return monthNamesAustriaAbbr[int(t.Month())-1]
|
||||
}
|
||||
if abbr == 4 {
|
||||
return monthNamesAustria[int(t.Month())-1]
|
||||
}
|
||||
return monthNamesAustriaAbbr[int(t.Month())-1][:1]
|
||||
}
|
||||
|
||||
// localMonthsNameBangla returns the German name of the month.
|
||||
func localMonthsNameBangla(t time.Time, abbr int) string {
|
||||
if abbr == 3 || abbr == 4 {
|
||||
return monthNamesBangla[int(t.Month())-1]
|
||||
}
|
||||
return string([]rune(monthNamesBangla[int(t.Month())-1])[:1])
|
||||
}
|
||||
|
||||
// localMonthsNameFrench returns the French name of the month.
|
||||
func localMonthsNameFrench(t time.Time, abbr int) string {
|
||||
if abbr == 3 {
|
||||
month := monthNamesFrench[int(t.Month())-1]
|
||||
if len([]rune(month)) <= 4 {
|
||||
return monthNamesFrench[int(t.Month())-1]
|
||||
}
|
||||
return monthNamesFrenchAbbr[int(t.Month())-1]
|
||||
}
|
||||
if abbr == 4 {
|
||||
return monthNamesFrench[int(t.Month())-1]
|
||||
}
|
||||
return monthNamesFrenchAbbr[int(t.Month())-1][:1]
|
||||
}
|
||||
|
||||
// localMonthsNameIrish returns the Irish name of the month.
|
||||
func localMonthsNameIrish(t time.Time, abbr int) string {
|
||||
if abbr == 3 {
|
||||
return monthNamesIrishAbbr[int(t.Month()-1)]
|
||||
}
|
||||
if abbr == 4 {
|
||||
return monthNamesIrish[int(t.Month())-1]
|
||||
}
|
||||
return monthNamesIrishAbbr[int(t.Month())-1][:1]
|
||||
}
|
||||
|
||||
// localMonthsNameItalian returns the Italian name of the month.
|
||||
func localMonthsNameItalian(t time.Time, abbr int) string {
|
||||
if abbr == 3 {
|
||||
return monthNamesItalianAbbr[int(t.Month())-1]
|
||||
}
|
||||
if abbr == 4 {
|
||||
return monthNamesItalian[int(t.Month())-1]
|
||||
}
|
||||
return monthNamesItalianAbbr[int(t.Month())-1][:1]
|
||||
}
|
||||
|
||||
// localMonthsNameGerman returns the German name of the month.
|
||||
func localMonthsNameGerman(t time.Time, abbr int) string {
|
||||
if abbr == 3 {
|
||||
return monthNamesGermanAbbr[int(t.Month())-1]
|
||||
}
|
||||
if abbr == 4 {
|
||||
return monthNamesGerman[int(t.Month())-1]
|
||||
}
|
||||
return monthNamesGermanAbbr[int(t.Month())-1][:1]
|
||||
}
|
||||
|
||||
// localMonthsNameChinese1 returns the Chinese name of the month.
|
||||
func localMonthsNameChinese1(t time.Time, abbr int) string {
|
||||
if abbr == 3 {
|
||||
return monthNamesChineseAbbrPlus[int(t.Month())]
|
||||
}
|
||||
if abbr == 4 {
|
||||
return monthNamesChinesePlus[int(t.Month())-1]
|
||||
}
|
||||
return monthNamesChinese[int(t.Month())-1]
|
||||
}
|
||||
|
||||
// localMonthsNameChinese2 returns the Chinese name of the month.
|
||||
func localMonthsNameChinese2(t time.Time, abbr int) string {
|
||||
if abbr == 3 || abbr == 4 {
|
||||
return monthNamesChinesePlus[int(t.Month())-1]
|
||||
}
|
||||
return monthNamesChinese[int(t.Month())-1]
|
||||
}
|
||||
|
||||
// localMonthsNameChinese3 returns the Chinese name of the month.
|
||||
func localMonthsNameChinese3(t time.Time, abbr int) string {
|
||||
if abbr == 3 || abbr == 4 {
|
||||
return monthNamesChineseAbbrPlus[int(t.Month())]
|
||||
}
|
||||
return strconv.Itoa(int(t.Month()))
|
||||
}
|
||||
|
||||
// localMonthsNameKorean returns the Korean name of the month.
|
||||
func localMonthsNameKorean(t time.Time, abbr int) string {
|
||||
if abbr == 3 || abbr == 4 {
|
||||
return monthNamesKoreanAbbrPlus[int(t.Month())]
|
||||
}
|
||||
return strconv.Itoa(int(t.Month()))
|
||||
}
|
||||
|
||||
// localMonthsNameTraditionalMongolian returns the Traditional Mongolian name of the month.
|
||||
func localMonthsNameTraditionalMongolian(t time.Time, abbr int) string {
|
||||
if abbr == 5 {
|
||||
return "M"
|
||||
}
|
||||
return monthNamesTradMongolian[int(t.Month()-1)]
|
||||
}
|
||||
|
||||
// localMonthsNameRussian returns the Russian name of the month.
|
||||
func localMonthsNameRussian(t time.Time, abbr int) string {
|
||||
if abbr == 3 {
|
||||
month := monthNamesRussian[int(t.Month())-1]
|
||||
if len([]rune(month)) <= 4 {
|
||||
return month
|
||||
}
|
||||
return monthNamesRussianAbbr[int(t.Month())-1]
|
||||
}
|
||||
if abbr == 4 {
|
||||
return monthNamesRussian[int(t.Month())-1]
|
||||
}
|
||||
return string([]rune(monthNamesRussian[int(t.Month())-1])[:1])
|
||||
}
|
||||
|
||||
// localMonthsNameSpanish returns the Spanish name of the month.
|
||||
func localMonthsNameSpanish(t time.Time, abbr int) string {
|
||||
if abbr == 3 {
|
||||
return monthNamesSpanishAbbr[int(t.Month())-1]
|
||||
}
|
||||
if abbr == 4 {
|
||||
return monthNamesSpanish[int(t.Month())-1]
|
||||
}
|
||||
return monthNamesSpanishAbbr[int(t.Month())-1][:1]
|
||||
}
|
||||
|
||||
// localMonthsNameThai returns the Thai name of the month.
|
||||
func localMonthsNameThai(t time.Time, abbr int) string {
|
||||
if abbr == 3 {
|
||||
r := []rune(monthNamesThai[int(t.Month())-1])
|
||||
return string(r[:1]) + "." + string(r[len(r)-2:len(r)-1]) + "."
|
||||
}
|
||||
if abbr == 4 {
|
||||
return monthNamesThai[int(t.Month())-1]
|
||||
}
|
||||
return string([]rune(monthNamesThai[int(t.Month())-1])[:1])
|
||||
}
|
||||
|
||||
// localMonthsNameTibetan returns the Tibetan name of the month.
|
||||
func localMonthsNameTibetan(t time.Time, abbr int) string {
|
||||
if abbr == 3 {
|
||||
return monthNamesTibetanAbbr[int(t.Month())-1]
|
||||
}
|
||||
if abbr == 5 {
|
||||
if t.Month() == 10 {
|
||||
return "\u0f66"
|
||||
}
|
||||
return "\u0f5f"
|
||||
}
|
||||
return monthNamesTibetan[int(t.Month())-1]
|
||||
}
|
||||
|
||||
// localMonthsNameTurkish returns the Turkish name of the month.
|
||||
func localMonthsNameTurkish(t time.Time, abbr int) string {
|
||||
if abbr == 3 {
|
||||
return monthNamesTurkishAbbr[int(t.Month())-1]
|
||||
}
|
||||
if abbr == 4 {
|
||||
return monthNamesTurkish[int(t.Month())-1]
|
||||
}
|
||||
return string([]rune(monthNamesTurkishAbbr[int(t.Month())-1])[:1])
|
||||
}
|
||||
|
||||
// localMonthsNameWelsh returns the Welsh name of the month.
|
||||
func localMonthsNameWelsh(t time.Time, abbr int) string {
|
||||
if abbr == 3 {
|
||||
return monthNamesWelshAbbr[int(t.Month())-1]
|
||||
}
|
||||
if abbr == 4 {
|
||||
return monthNamesWelsh[int(t.Month())-1]
|
||||
}
|
||||
return monthNamesWelshAbbr[int(t.Month())-1][:1]
|
||||
}
|
||||
|
||||
// localMonthsNameVietnamese returns the Vietnamese name of the month.
|
||||
func localMonthsNameVietnamese(t time.Time, abbr int) string {
|
||||
if abbr == 3 {
|
||||
return monthNamesVietnameseAbbr3[int(t.Month()-1)]
|
||||
}
|
||||
if abbr == 5 {
|
||||
return monthNamesVietnameseAbbr5[int(t.Month()-1)]
|
||||
}
|
||||
return monthNamesVietnamese[int(t.Month()-1)]
|
||||
}
|
||||
|
||||
// localMonthsNameWolof returns the Wolof name of the month.
|
||||
func localMonthsNameWolof(t time.Time, abbr int) string {
|
||||
if abbr == 3 {
|
||||
return monthNamesWolofAbbr[int(t.Month())-1]
|
||||
}
|
||||
if abbr == 4 {
|
||||
return monthNamesWolof[int(t.Month())-1]
|
||||
}
|
||||
return monthNamesWolof[int(t.Month())-1][:1]
|
||||
}
|
||||
|
||||
// localMonthsNameXhosa returns the Xhosa name of the month.
|
||||
func localMonthsNameXhosa(t time.Time, abbr int) string {
|
||||
if abbr == 3 {
|
||||
return monthNamesXhosaAbbr[int(t.Month())-1]
|
||||
}
|
||||
if abbr == 4 {
|
||||
return monthNamesXhosa[int(t.Month())-1]
|
||||
}
|
||||
return "u"
|
||||
}
|
||||
|
||||
// localMonthsNameYi returns the Yi name of the month.
|
||||
func localMonthsNameYi(t time.Time, abbr int) string {
|
||||
if abbr == 3 || abbr == 4 {
|
||||
return monthNamesYiSuffix[int(t.Month()-1)]
|
||||
}
|
||||
return string([]rune(monthNamesYi[int(t.Month())-1])[:1])
|
||||
}
|
||||
|
||||
// localMonthsNameZulu returns the Zulu name of the month.
|
||||
func localMonthsNameZulu(t time.Time, abbr int) string {
|
||||
if abbr == 3 {
|
||||
return monthNamesZuluAbbr[int(t.Month()-1)]
|
||||
}
|
||||
if abbr == 4 {
|
||||
return monthNamesZulu[int(t.Month())-1]
|
||||
}
|
||||
return monthNamesZuluAbbr[int(t.Month())-1][:1]
|
||||
}
|
||||
|
||||
// localMonthName return months name by supported language ID.
|
||||
func (nf *numberFormat) localMonthsName(abbr int) string {
|
||||
if languageInfo, ok := supportedLanguageInfo[nf.localCode]; ok {
|
||||
return languageInfo.localMonth(nf.t, abbr)
|
||||
}
|
||||
return localMonthsNameEnglish(nf.t, abbr)
|
||||
}
|
||||
|
||||
// dateTimesHandler will be handling date and times types tokens for a number
|
||||
// format expression.
|
||||
func (nf *numberFormat) dateTimesHandler(i int, token nfp.Token) {
|
||||
if idx := inStrSlice(nfp.AmPm, strings.ToUpper(token.TValue), false); idx != -1 {
|
||||
if nf.ap == "" {
|
||||
nextHours := nf.hoursNext(i)
|
||||
aps := strings.Split(nf.localAmPm(token.TValue), "/")
|
||||
nf.ap = aps[0]
|
||||
if nextHours >= 12 {
|
||||
nf.ap = aps[1]
|
||||
}
|
||||
}
|
||||
nf.result += nf.ap
|
||||
return
|
||||
}
|
||||
if strings.Contains(strings.ToUpper(token.TValue), "M") {
|
||||
l := len(token.TValue)
|
||||
if l == 1 && !nf.hours && !nf.secondsNext(i) {
|
||||
nf.result += strconv.Itoa(int(nf.t.Month()))
|
||||
return
|
||||
}
|
||||
if l == 2 && !nf.hours && !nf.secondsNext(i) {
|
||||
nf.result += fmt.Sprintf("%02d", int(nf.t.Month()))
|
||||
return
|
||||
}
|
||||
if l == 3 {
|
||||
nf.result += nf.localMonthsName(3)
|
||||
return
|
||||
}
|
||||
if l == 4 || l > 5 {
|
||||
nf.result += nf.localMonthsName(4)
|
||||
return
|
||||
}
|
||||
if l == 5 {
|
||||
nf.result += nf.localMonthsName(5)
|
||||
return
|
||||
}
|
||||
}
|
||||
nf.yearsHandler(i, token)
|
||||
nf.daysHandler(i, token)
|
||||
nf.hoursHandler(i, token)
|
||||
nf.minutesHandler(token)
|
||||
nf.secondsHandler(token)
|
||||
}
|
||||
|
||||
// yearsHandler will be handling years in the date and times types tokens for a
|
||||
// number format expression.
|
||||
func (nf *numberFormat) yearsHandler(i int, token nfp.Token) {
|
||||
years := strings.Contains(strings.ToUpper(token.TValue), "Y")
|
||||
if years && len(token.TValue) <= 2 {
|
||||
nf.result += strconv.Itoa(nf.t.Year())[2:]
|
||||
return
|
||||
}
|
||||
if years && len(token.TValue) > 2 {
|
||||
nf.result += strconv.Itoa(nf.t.Year())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// daysHandler will be handling days in the date and times types tokens for a
|
||||
// number format expression.
|
||||
func (nf *numberFormat) daysHandler(i int, token nfp.Token) {
|
||||
if strings.Contains(strings.ToUpper(token.TValue), "D") {
|
||||
switch len(token.TValue) {
|
||||
case 1:
|
||||
nf.result += strconv.Itoa(nf.t.Day())
|
||||
return
|
||||
case 2:
|
||||
nf.result += fmt.Sprintf("%02d", nf.t.Day())
|
||||
return
|
||||
case 3:
|
||||
nf.result += nf.t.Weekday().String()[:3]
|
||||
return
|
||||
default:
|
||||
nf.result += nf.t.Weekday().String()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// hoursHandler will be handling hours in the date and times types tokens for a
|
||||
// number format expression.
|
||||
func (nf *numberFormat) hoursHandler(i int, token nfp.Token) {
|
||||
nf.hours = strings.Contains(strings.ToUpper(token.TValue), "H")
|
||||
if nf.hours {
|
||||
h := nf.t.Hour()
|
||||
ap, ok := nf.apNext(i)
|
||||
if ok {
|
||||
nf.ap = ap[0]
|
||||
if h >= 12 {
|
||||
nf.ap = ap[1]
|
||||
}
|
||||
if h > 12 {
|
||||
h -= 12
|
||||
}
|
||||
}
|
||||
if nf.ap != "" && nf.hoursNext(i) == -1 && h > 12 {
|
||||
h -= 12
|
||||
}
|
||||
switch len(token.TValue) {
|
||||
case 1:
|
||||
nf.result += strconv.Itoa(h)
|
||||
return
|
||||
default:
|
||||
nf.result += fmt.Sprintf("%02d", h)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// minutesHandler will be handling minutes in the date and times types tokens
|
||||
// for a number format expression.
|
||||
func (nf *numberFormat) minutesHandler(token nfp.Token) {
|
||||
if strings.Contains(strings.ToUpper(token.TValue), "M") {
|
||||
nf.hours = false
|
||||
switch len(token.TValue) {
|
||||
case 1:
|
||||
nf.result += strconv.Itoa(nf.t.Minute())
|
||||
return
|
||||
default:
|
||||
nf.result += fmt.Sprintf("%02d", nf.t.Minute())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// secondsHandler will be handling seconds in the date and times types tokens
|
||||
// for a number format expression.
|
||||
func (nf *numberFormat) secondsHandler(token nfp.Token) {
|
||||
nf.seconds = strings.Contains(strings.ToUpper(token.TValue), "S")
|
||||
if nf.seconds {
|
||||
switch len(token.TValue) {
|
||||
case 1:
|
||||
nf.result += strconv.Itoa(nf.t.Second())
|
||||
return
|
||||
default:
|
||||
nf.result += fmt.Sprintf("%02d", nf.t.Second())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// elapsedDateTimesHandler will be handling elapsed date and times types tokens
|
||||
// for a number format expression.
|
||||
func (nf *numberFormat) elapsedDateTimesHandler(token nfp.Token) {
|
||||
if strings.Contains(strings.ToUpper(token.TValue), "H") {
|
||||
nf.result += fmt.Sprintf("%.f", nf.t.Sub(excel1900Epoc).Hours())
|
||||
return
|
||||
}
|
||||
if strings.Contains(strings.ToUpper(token.TValue), "M") {
|
||||
nf.result += fmt.Sprintf("%.f", nf.t.Sub(excel1900Epoc).Minutes())
|
||||
return
|
||||
}
|
||||
if strings.Contains(strings.ToUpper(token.TValue), "S") {
|
||||
nf.result += fmt.Sprintf("%.f", nf.t.Sub(excel1900Epoc).Seconds())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// hoursNext detects if a token of type hours exists after a given tokens list.
|
||||
func (nf *numberFormat) hoursNext(i int) int {
|
||||
tokens := nf.section[nf.sectionIdx].Items
|
||||
for idx := i + 1; idx < len(tokens); idx++ {
|
||||
if tokens[idx].TType == nfp.TokenTypeDateTimes {
|
||||
if strings.Contains(strings.ToUpper(tokens[idx].TValue), "H") {
|
||||
t := timeFromExcelTime(nf.number, false)
|
||||
return t.Hour()
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// apNext detects if a token of type AM/PM exists after a given tokens list.
|
||||
func (nf *numberFormat) apNext(i int) ([]string, bool) {
|
||||
tokens := nf.section[nf.sectionIdx].Items
|
||||
for idx := i + 1; idx < len(tokens); idx++ {
|
||||
if tokens[idx].TType == nfp.TokenTypeDateTimes {
|
||||
if strings.Contains(strings.ToUpper(tokens[idx].TValue), "H") {
|
||||
return nil, false
|
||||
}
|
||||
if i := inStrSlice(nfp.AmPm, tokens[idx].TValue, false); i != -1 {
|
||||
return strings.Split(nf.localAmPm(tokens[idx].TValue), "/"), true
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// secondsNext detects if a token of type seconds exists after a given tokens
|
||||
// list.
|
||||
func (nf *numberFormat) secondsNext(i int) bool {
|
||||
tokens := nf.section[nf.sectionIdx].Items
|
||||
for idx := i + 1; idx < len(tokens); idx++ {
|
||||
if tokens[idx].TType == nfp.TokenTypeDateTimes {
|
||||
return strings.Contains(strings.ToUpper(tokens[idx].TValue), "S")
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// negativeHandler will be handling negative selection for a number format
|
||||
// expression.
|
||||
func (nf *numberFormat) negativeHandler() (result string) {
|
||||
for _, token := range nf.section[nf.sectionIdx].Items {
|
||||
if inStrSlice(supportedTokenTypes, token.TType, true) == -1 || token.TType == nfp.TokenTypeGeneral {
|
||||
result = nf.value
|
||||
return
|
||||
}
|
||||
if token.TType == nfp.TokenTypeLiteral {
|
||||
nf.result += token.TValue
|
||||
continue
|
||||
}
|
||||
if token.TType == nfp.TokenTypeZeroPlaceHolder && token.TValue == strings.Repeat("0", len(token.TValue)) {
|
||||
if isNum, precision, decimal := isNumeric(nf.value); isNum {
|
||||
if math.Abs(nf.number) < 1 {
|
||||
nf.result += "0"
|
||||
continue
|
||||
}
|
||||
if precision > 15 {
|
||||
nf.result += strings.TrimLeft(strconv.FormatFloat(decimal, 'f', -1, 64), "-")
|
||||
} else {
|
||||
nf.result += fmt.Sprintf("%.f", math.Abs(nf.number))
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
result = nf.result
|
||||
return
|
||||
}
|
||||
|
||||
// zeroHandler will be handling zero selection for a number format expression.
|
||||
func (nf *numberFormat) zeroHandler() string {
|
||||
return nf.value
|
||||
}
|
||||
|
||||
// textHandler will be handling text selection for a number format expression.
|
||||
func (nf *numberFormat) textHandler() (result string) {
|
||||
for _, token := range nf.section[nf.sectionIdx].Items {
|
||||
if token.TType == nfp.TokenTypeLiteral {
|
||||
result += token.TValue
|
||||
}
|
||||
if token.TType == nfp.TokenTypeTextPlaceHolder {
|
||||
result += nf.value
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// getValueSectionType returns its applicable number format expression section
|
||||
// based on the given value.
|
||||
func (nf *numberFormat) getValueSectionType(value string) (float64, string) {
|
||||
isNum, _, _ := isNumeric(value)
|
||||
if !isNum {
|
||||
return 0, nfp.TokenSectionText
|
||||
}
|
||||
number, _ := strconv.ParseFloat(value, 64)
|
||||
if number > 0 {
|
||||
return number, nfp.TokenSectionPositive
|
||||
}
|
||||
if number < 0 {
|
||||
return number, nfp.TokenSectionNegative
|
||||
}
|
||||
return number, nfp.TokenSectionZero
|
||||
}
|
||||
+782
@@ -0,0 +1,782 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"image"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// parseGraphicOptions provides a function to parse the format settings of
|
||||
// the picture with default value.
|
||||
func parseGraphicOptions(opts *GraphicOptions) *GraphicOptions {
|
||||
if opts == nil {
|
||||
return &GraphicOptions{
|
||||
PrintObject: boolPtr(true),
|
||||
Locked: boolPtr(true),
|
||||
ScaleX: defaultPictureScale,
|
||||
ScaleY: defaultPictureScale,
|
||||
}
|
||||
}
|
||||
if opts.PrintObject == nil {
|
||||
opts.PrintObject = boolPtr(true)
|
||||
}
|
||||
if opts.Locked == nil {
|
||||
opts.Locked = boolPtr(true)
|
||||
}
|
||||
if opts.ScaleX == 0 {
|
||||
opts.ScaleX = defaultPictureScale
|
||||
}
|
||||
if opts.ScaleY == 0 {
|
||||
opts.ScaleY = defaultPictureScale
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
// AddPicture provides the method to add picture in a sheet by given picture
|
||||
// format set (such as offset, scale, aspect ratio setting and print settings)
|
||||
// and file path, supported image types: BMP, EMF, EMZ, GIF, JPEG, JPG, PNG,
|
||||
// SVG, TIF, TIFF, WMF, and WMZ. This function is concurrency safe. For example:
|
||||
//
|
||||
// package main
|
||||
//
|
||||
// import (
|
||||
// "fmt"
|
||||
// _ "image/gif"
|
||||
// _ "image/jpeg"
|
||||
// _ "image/png"
|
||||
//
|
||||
// "github.com/xuri/excelize/v2"
|
||||
// )
|
||||
//
|
||||
// func main() {
|
||||
// f := excelize.NewFile()
|
||||
// defer func() {
|
||||
// if err := f.Close(); err != nil {
|
||||
// fmt.Println(err)
|
||||
// }
|
||||
// }()
|
||||
// // Insert a picture.
|
||||
// if err := f.AddPicture("Sheet1", "A2", "image.jpg", nil); err != nil {
|
||||
// fmt.Println(err)
|
||||
// return
|
||||
// }
|
||||
// // Insert a picture scaling in the cell with location hyperlink.
|
||||
// enable := true
|
||||
// if err := f.AddPicture("Sheet1", "D2", "image.png",
|
||||
// &excelize.GraphicOptions{
|
||||
// ScaleX: 0.5,
|
||||
// ScaleY: 0.5,
|
||||
// Hyperlink: "#Sheet2!D8",
|
||||
// HyperlinkType: "Location",
|
||||
// },
|
||||
// ); err != nil {
|
||||
// fmt.Println(err)
|
||||
// return
|
||||
// }
|
||||
// // Insert a picture offset in the cell with external hyperlink, printing and positioning support.
|
||||
// if err := f.AddPicture("Sheet1", "H2", "image.gif",
|
||||
// &excelize.GraphicOptions{
|
||||
// PrintObject: &enable,
|
||||
// LockAspectRatio: false,
|
||||
// OffsetX: 15,
|
||||
// OffsetY: 10,
|
||||
// Hyperlink: "https://github.com/xuri/excelize",
|
||||
// HyperlinkType: "External",
|
||||
// Positioning: "oneCell",
|
||||
// },
|
||||
// ); err != nil {
|
||||
// fmt.Println(err)
|
||||
// return
|
||||
// }
|
||||
// if err := f.SaveAs("Book1.xlsx"); err != nil {
|
||||
// fmt.Println(err)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// The optional parameter "AutoFit" specifies if you make image size auto-fits the
|
||||
// cell, the default value of that is 'false'.
|
||||
//
|
||||
// The optional parameter "Hyperlink" specifies the hyperlink of the image.
|
||||
//
|
||||
// The optional parameter "HyperlinkType" defines two types of
|
||||
// hyperlink "External" for website or "Location" for moving to one of the
|
||||
// cells in this workbook. When the "HyperlinkType" is "Location",
|
||||
// coordinates need to start with "#".
|
||||
//
|
||||
// The optional parameter "Positioning" defines two types of the position of an
|
||||
// image in an Excel spreadsheet, "oneCell" (Move but don't size with
|
||||
// cells) or "absolute" (Don't move or size with cells). If you don't set this
|
||||
// parameter, the default positioning is move and size with cells.
|
||||
//
|
||||
// The optional parameter "PrintObject" indicates whether the image is printed
|
||||
// when the worksheet is printed, the default value of that is 'true'.
|
||||
//
|
||||
// The optional parameter "LockAspectRatio" indicates whether lock aspect
|
||||
// ratio for the image, the default value of that is 'false'.
|
||||
//
|
||||
// The optional parameter "Locked" indicates whether lock the image. Locking
|
||||
// an object has no effect unless the sheet is protected.
|
||||
//
|
||||
// The optional parameter "OffsetX" specifies the horizontal offset of the
|
||||
// image with the cell, the default value of that is 0.
|
||||
//
|
||||
// The optional parameter "ScaleX" specifies the horizontal scale of images,
|
||||
// the default value of that is 1.0 which presents 100%.
|
||||
//
|
||||
// The optional parameter "OffsetY" specifies the vertical offset of the
|
||||
// image with the cell, the default value of that is 0.
|
||||
//
|
||||
// The optional parameter "ScaleY" specifies the vertical scale of images,
|
||||
// the default value of that is 1.0 which presents 100%.
|
||||
func (f *File) AddPicture(sheet, cell, name string, opts *GraphicOptions) error {
|
||||
var err error
|
||||
// Check picture exists first.
|
||||
if _, err = os.Stat(name); os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
ext, ok := supportedImageTypes[strings.ToLower(path.Ext(name))]
|
||||
if !ok {
|
||||
return ErrImgExt
|
||||
}
|
||||
file, _ := os.ReadFile(filepath.Clean(name))
|
||||
return f.AddPictureFromBytes(sheet, cell, &Picture{Extension: ext, File: file, Format: opts})
|
||||
}
|
||||
|
||||
// AddPictureFromBytes provides the method to add picture in a sheet by given
|
||||
// picture format set (such as offset, scale, aspect ratio setting and print
|
||||
// settings), file base name, extension name and file bytes, supported image
|
||||
// types: EMF, EMZ, GIF, JPEG, JPG, PNG, SVG, TIF, TIFF, WMF, and WMZ. For
|
||||
// example:
|
||||
//
|
||||
// package main
|
||||
//
|
||||
// import (
|
||||
// "fmt"
|
||||
// _ "image/jpeg"
|
||||
// "os"
|
||||
//
|
||||
// "github.com/xuri/excelize/v2"
|
||||
// )
|
||||
//
|
||||
// func main() {
|
||||
// f := excelize.NewFile()
|
||||
// defer func() {
|
||||
// if err := f.Close(); err != nil {
|
||||
// fmt.Println(err)
|
||||
// }
|
||||
// }()
|
||||
// file, err := os.ReadFile("image.jpg")
|
||||
// if err != nil {
|
||||
// fmt.Println(err)
|
||||
// return
|
||||
// }
|
||||
// if err := f.AddPictureFromBytes("Sheet1", "A2", &excelize.Picture{
|
||||
// Extension: ".jpg",
|
||||
// File: file,
|
||||
// Format: &excelize.GraphicOptions{AltText: "Excel Logo"},
|
||||
// }); err != nil {
|
||||
// fmt.Println(err)
|
||||
// return
|
||||
// }
|
||||
// if err := f.SaveAs("Book1.xlsx"); err != nil {
|
||||
// fmt.Println(err)
|
||||
// }
|
||||
// }
|
||||
func (f *File) AddPictureFromBytes(sheet, cell string, pic *Picture) error {
|
||||
var drawingHyperlinkRID int
|
||||
var hyperlinkType string
|
||||
ext, ok := supportedImageTypes[strings.ToLower(pic.Extension)]
|
||||
if !ok {
|
||||
return ErrImgExt
|
||||
}
|
||||
options := parseGraphicOptions(pic.Format)
|
||||
img, _, err := image.DecodeConfig(bytes.NewReader(pic.File))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Read sheet data.
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ws.Lock()
|
||||
// Add first picture for given sheet, create xl/drawings/ and xl/drawings/_rels/ folder.
|
||||
drawingID := f.countDrawings() + 1
|
||||
drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml"
|
||||
drawingID, drawingXML = f.prepareDrawing(ws, drawingID, sheet, drawingXML)
|
||||
drawingRels := "xl/drawings/_rels/drawing" + strconv.Itoa(drawingID) + ".xml.rels"
|
||||
mediaStr := ".." + strings.TrimPrefix(f.addMedia(pic.File, ext), "xl")
|
||||
drawingRID := f.addRels(drawingRels, SourceRelationshipImage, mediaStr, hyperlinkType)
|
||||
// Add picture with hyperlink.
|
||||
if options.Hyperlink != "" && options.HyperlinkType != "" {
|
||||
if options.HyperlinkType == "External" {
|
||||
hyperlinkType = options.HyperlinkType
|
||||
}
|
||||
drawingHyperlinkRID = f.addRels(drawingRels, SourceRelationshipHyperLink, options.Hyperlink, hyperlinkType)
|
||||
}
|
||||
ws.Unlock()
|
||||
err = f.addDrawingPicture(sheet, drawingXML, cell, ext, drawingRID, drawingHyperlinkRID, img, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = f.addContentTypePart(drawingID, "drawings"); err != nil {
|
||||
return err
|
||||
}
|
||||
f.addSheetNameSpace(sheet, SourceRelationship)
|
||||
return err
|
||||
}
|
||||
|
||||
// deleteSheetRelationships provides a function to delete relationships in
|
||||
// xl/worksheets/_rels/sheet%d.xml.rels by given worksheet name and
|
||||
// relationship index.
|
||||
func (f *File) deleteSheetRelationships(sheet, rID string) {
|
||||
name, ok := f.getSheetXMLPath(sheet)
|
||||
if !ok {
|
||||
name = strings.ToLower(sheet) + ".xml"
|
||||
}
|
||||
rels := "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels"
|
||||
sheetRels, _ := f.relsReader(rels)
|
||||
if sheetRels == nil {
|
||||
sheetRels = &xlsxRelationships{}
|
||||
}
|
||||
sheetRels.Lock()
|
||||
defer sheetRels.Unlock()
|
||||
for k, v := range sheetRels.Relationships {
|
||||
if v.ID == rID {
|
||||
sheetRels.Relationships = append(sheetRels.Relationships[:k], sheetRels.Relationships[k+1:]...)
|
||||
}
|
||||
}
|
||||
f.Relationships.Store(rels, sheetRels)
|
||||
}
|
||||
|
||||
// addSheetLegacyDrawing provides a function to add legacy drawing element to
|
||||
// xl/worksheets/sheet%d.xml by given worksheet name and relationship index.
|
||||
func (f *File) addSheetLegacyDrawing(sheet string, rID int) {
|
||||
ws, _ := f.workSheetReader(sheet)
|
||||
ws.LegacyDrawing = &xlsxLegacyDrawing{
|
||||
RID: "rId" + strconv.Itoa(rID),
|
||||
}
|
||||
}
|
||||
|
||||
// addSheetDrawing provides a function to add drawing element to
|
||||
// xl/worksheets/sheet%d.xml by given worksheet name and relationship index.
|
||||
func (f *File) addSheetDrawing(sheet string, rID int) {
|
||||
ws, _ := f.workSheetReader(sheet)
|
||||
ws.Drawing = &xlsxDrawing{
|
||||
RID: "rId" + strconv.Itoa(rID),
|
||||
}
|
||||
}
|
||||
|
||||
// addSheetPicture provides a function to add picture element to
|
||||
// xl/worksheets/sheet%d.xml by given worksheet name and relationship index.
|
||||
func (f *File) addSheetPicture(sheet string, rID int) error {
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ws.Picture = &xlsxPicture{
|
||||
RID: "rId" + strconv.Itoa(rID),
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// countDrawings provides a function to get drawing files count storage in the
|
||||
// folder xl/drawings.
|
||||
func (f *File) countDrawings() int {
|
||||
var c1, c2 int
|
||||
f.Pkg.Range(func(k, v interface{}) bool {
|
||||
if strings.Contains(k.(string), "xl/drawings/drawing") {
|
||||
c1++
|
||||
}
|
||||
return true
|
||||
})
|
||||
f.Drawings.Range(func(rel, value interface{}) bool {
|
||||
if strings.Contains(rel.(string), "xl/drawings/drawing") {
|
||||
c2++
|
||||
}
|
||||
return true
|
||||
})
|
||||
if c1 < c2 {
|
||||
return c2
|
||||
}
|
||||
return c1
|
||||
}
|
||||
|
||||
// addDrawingPicture provides a function to add picture by given sheet,
|
||||
// drawingXML, cell, file name, width, height relationship index and format
|
||||
// sets.
|
||||
func (f *File) addDrawingPicture(sheet, drawingXML, cell, ext string, rID, hyperlinkRID int, img image.Config, opts *GraphicOptions) error {
|
||||
col, row, err := CellNameToCoordinates(cell)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
width, height := img.Width, img.Height
|
||||
if opts.AutoFit {
|
||||
width, height, col, row, err = f.drawingResize(sheet, cell, float64(width), float64(height), opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
width = int(float64(width) * opts.ScaleX)
|
||||
height = int(float64(height) * opts.ScaleY)
|
||||
}
|
||||
col--
|
||||
row--
|
||||
colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, col, row, opts.OffsetX, opts.OffsetY, width, height)
|
||||
content, cNvPrID, err := f.drawingParser(drawingXML)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
twoCellAnchor := xdrCellAnchor{}
|
||||
twoCellAnchor.EditAs = opts.Positioning
|
||||
from := xlsxFrom{}
|
||||
from.Col = colStart
|
||||
from.ColOff = opts.OffsetX * EMU
|
||||
from.Row = rowStart
|
||||
from.RowOff = opts.OffsetY * EMU
|
||||
to := xlsxTo{}
|
||||
to.Col = colEnd
|
||||
to.ColOff = x2 * EMU
|
||||
to.Row = rowEnd
|
||||
to.RowOff = y2 * EMU
|
||||
twoCellAnchor.From = &from
|
||||
twoCellAnchor.To = &to
|
||||
pic := xlsxPic{}
|
||||
pic.NvPicPr.CNvPicPr.PicLocks.NoChangeAspect = opts.LockAspectRatio
|
||||
pic.NvPicPr.CNvPr.ID = cNvPrID
|
||||
pic.NvPicPr.CNvPr.Descr = opts.AltText
|
||||
pic.NvPicPr.CNvPr.Name = "Picture " + strconv.Itoa(cNvPrID)
|
||||
if hyperlinkRID != 0 {
|
||||
pic.NvPicPr.CNvPr.HlinkClick = &xlsxHlinkClick{
|
||||
R: SourceRelationship.Value,
|
||||
RID: "rId" + strconv.Itoa(hyperlinkRID),
|
||||
}
|
||||
}
|
||||
pic.BlipFill.Blip.R = SourceRelationship.Value
|
||||
pic.BlipFill.Blip.Embed = "rId" + strconv.Itoa(rID)
|
||||
if ext == ".svg" {
|
||||
pic.BlipFill.Blip.ExtList = &xlsxEGOfficeArtExtensionList{
|
||||
Ext: []xlsxCTOfficeArtExtension{
|
||||
{
|
||||
URI: ExtURISVG,
|
||||
SVGBlip: xlsxCTSVGBlip{
|
||||
XMLNSaAVG: NameSpaceDrawing2016SVG.Value,
|
||||
Embed: pic.BlipFill.Blip.Embed,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
pic.SpPr.PrstGeom.Prst = "rect"
|
||||
|
||||
twoCellAnchor.Pic = &pic
|
||||
twoCellAnchor.ClientData = &xdrClientData{
|
||||
FLocksWithSheet: *opts.Locked,
|
||||
FPrintsWithSheet: *opts.PrintObject,
|
||||
}
|
||||
content.Lock()
|
||||
defer content.Unlock()
|
||||
content.TwoCellAnchor = append(content.TwoCellAnchor, &twoCellAnchor)
|
||||
f.Drawings.Store(drawingXML, content)
|
||||
return err
|
||||
}
|
||||
|
||||
// countMedia provides a function to get media files count storage in the
|
||||
// folder xl/media/image.
|
||||
func (f *File) countMedia() int {
|
||||
count := 0
|
||||
f.Pkg.Range(func(k, v interface{}) bool {
|
||||
if strings.Contains(k.(string), "xl/media/image") {
|
||||
count++
|
||||
}
|
||||
return true
|
||||
})
|
||||
return count
|
||||
}
|
||||
|
||||
// addMedia provides a function to add a picture into folder xl/media/image by
|
||||
// given file and extension name. Duplicate images are only actually stored once
|
||||
// and drawings that use it will reference the same image.
|
||||
func (f *File) addMedia(file []byte, ext string) string {
|
||||
count := f.countMedia()
|
||||
var name string
|
||||
f.Pkg.Range(func(k, existing interface{}) bool {
|
||||
if !strings.HasPrefix(k.(string), "xl/media/image") {
|
||||
return true
|
||||
}
|
||||
if bytes.Equal(file, existing.([]byte)) {
|
||||
name = k.(string)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
if name != "" {
|
||||
return name
|
||||
}
|
||||
media := "xl/media/image" + strconv.Itoa(count+1) + ext
|
||||
f.Pkg.Store(media, file)
|
||||
return media
|
||||
}
|
||||
|
||||
// setContentTypePartImageExtensions provides a function to set the content
|
||||
// type for relationship parts and the Main Document part.
|
||||
func (f *File) setContentTypePartImageExtensions() error {
|
||||
imageTypes := map[string]string{
|
||||
"bmp": "image/", "jpeg": "image/", "png": "image/", "gif": "image/",
|
||||
"svg": "image/", "tiff": "image/", "emf": "image/x-", "wmf": "image/x-",
|
||||
"emz": "image/x-", "wmz": "image/x-",
|
||||
}
|
||||
content, err := f.contentTypesReader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
content.Lock()
|
||||
defer content.Unlock()
|
||||
for _, file := range content.Defaults {
|
||||
delete(imageTypes, file.Extension)
|
||||
}
|
||||
for extension, prefix := range imageTypes {
|
||||
content.Defaults = append(content.Defaults, xlsxDefault{
|
||||
Extension: extension,
|
||||
ContentType: prefix + extension,
|
||||
})
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// setContentTypePartVMLExtensions provides a function to set the content type
|
||||
// for relationship parts and the Main Document part.
|
||||
func (f *File) setContentTypePartVMLExtensions() error {
|
||||
var vml bool
|
||||
content, err := f.contentTypesReader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
content.Lock()
|
||||
defer content.Unlock()
|
||||
for _, v := range content.Defaults {
|
||||
if v.Extension == "vml" {
|
||||
vml = true
|
||||
}
|
||||
}
|
||||
if !vml {
|
||||
content.Defaults = append(content.Defaults, xlsxDefault{
|
||||
Extension: "vml",
|
||||
ContentType: ContentTypeVML,
|
||||
})
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// addContentTypePart provides a function to add content type part
|
||||
// relationships in the file [Content_Types].xml by given index.
|
||||
func (f *File) addContentTypePart(index int, contentType string) error {
|
||||
setContentType := map[string]func() error{
|
||||
"comments": f.setContentTypePartVMLExtensions,
|
||||
"drawings": f.setContentTypePartImageExtensions,
|
||||
}
|
||||
partNames := map[string]string{
|
||||
"chart": "/xl/charts/chart" + strconv.Itoa(index) + ".xml",
|
||||
"chartsheet": "/xl/chartsheets/sheet" + strconv.Itoa(index) + ".xml",
|
||||
"comments": "/xl/comments" + strconv.Itoa(index) + ".xml",
|
||||
"drawings": "/xl/drawings/drawing" + strconv.Itoa(index) + ".xml",
|
||||
"table": "/xl/tables/table" + strconv.Itoa(index) + ".xml",
|
||||
"pivotTable": "/xl/pivotTables/pivotTable" + strconv.Itoa(index) + ".xml",
|
||||
"pivotCache": "/xl/pivotCache/pivotCacheDefinition" + strconv.Itoa(index) + ".xml",
|
||||
"sharedStrings": "/xl/sharedStrings.xml",
|
||||
}
|
||||
contentTypes := map[string]string{
|
||||
"chart": ContentTypeDrawingML,
|
||||
"chartsheet": ContentTypeSpreadSheetMLChartsheet,
|
||||
"comments": ContentTypeSpreadSheetMLComments,
|
||||
"drawings": ContentTypeDrawing,
|
||||
"table": ContentTypeSpreadSheetMLTable,
|
||||
"pivotTable": ContentTypeSpreadSheetMLPivotTable,
|
||||
"pivotCache": ContentTypeSpreadSheetMLPivotCacheDefinition,
|
||||
"sharedStrings": ContentTypeSpreadSheetMLSharedStrings,
|
||||
}
|
||||
s, ok := setContentType[contentType]
|
||||
if ok {
|
||||
if err := s(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
content, err := f.contentTypesReader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
content.Lock()
|
||||
defer content.Unlock()
|
||||
for _, v := range content.Overrides {
|
||||
if v.PartName == partNames[contentType] {
|
||||
return err
|
||||
}
|
||||
}
|
||||
content.Overrides = append(content.Overrides, xlsxOverride{
|
||||
PartName: partNames[contentType],
|
||||
ContentType: contentTypes[contentType],
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// getSheetRelationshipsTargetByID provides a function to get Target attribute
|
||||
// value in xl/worksheets/_rels/sheet%d.xml.rels by given worksheet name and
|
||||
// relationship index.
|
||||
func (f *File) getSheetRelationshipsTargetByID(sheet, rID string) string {
|
||||
name, ok := f.getSheetXMLPath(sheet)
|
||||
if !ok {
|
||||
name = strings.ToLower(sheet) + ".xml"
|
||||
}
|
||||
rels := "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels"
|
||||
sheetRels, _ := f.relsReader(rels)
|
||||
if sheetRels == nil {
|
||||
sheetRels = &xlsxRelationships{}
|
||||
}
|
||||
sheetRels.Lock()
|
||||
defer sheetRels.Unlock()
|
||||
for _, v := range sheetRels.Relationships {
|
||||
if v.ID == rID {
|
||||
return v.Target
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetPictures provides a function to get picture meta info and raw content
|
||||
// embed in spreadsheet by given worksheet and cell name. This function
|
||||
// returns the image contents as []byte data types. This function is
|
||||
// concurrency safe. For example:
|
||||
//
|
||||
// f, err := excelize.OpenFile("Book1.xlsx")
|
||||
// if err != nil {
|
||||
// fmt.Println(err)
|
||||
// return
|
||||
// }
|
||||
// defer func() {
|
||||
// if err := f.Close(); err != nil {
|
||||
// fmt.Println(err)
|
||||
// }
|
||||
// }()
|
||||
// pics, err := f.GetPictures("Sheet1", "A2")
|
||||
// if err != nil {
|
||||
// fmt.Println(err)
|
||||
// }
|
||||
// for idx, pic := range pics {
|
||||
// name := fmt.Sprintf("image%d%s", idx+1, pic.Extension)
|
||||
// if err := os.WriteFile(name, pic.File, 0644); err != nil {
|
||||
// fmt.Println(err)
|
||||
// }
|
||||
// }
|
||||
func (f *File) GetPictures(sheet, cell string) ([]Picture, error) {
|
||||
col, row, err := CellNameToCoordinates(cell)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
col--
|
||||
row--
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ws.Drawing == nil {
|
||||
return nil, err
|
||||
}
|
||||
target := f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID)
|
||||
drawingXML := strings.ReplaceAll(target, "..", "xl")
|
||||
drawingRelationships := strings.ReplaceAll(
|
||||
strings.ReplaceAll(target, "../drawings", "xl/drawings/_rels"), ".xml", ".xml.rels")
|
||||
|
||||
return f.getPicture(row, col, drawingXML, drawingRelationships)
|
||||
}
|
||||
|
||||
// DeletePicture provides a function to delete all pictures in a cell by given
|
||||
// worksheet name and cell reference. Note that the image file won't be deleted
|
||||
// from the document currently.
|
||||
func (f *File) DeletePicture(sheet, cell string) error {
|
||||
col, row, err := CellNameToCoordinates(cell)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
col--
|
||||
row--
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ws.Drawing == nil {
|
||||
return err
|
||||
}
|
||||
drawingXML := strings.ReplaceAll(f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID), "..", "xl")
|
||||
return f.deleteDrawing(col, row, drawingXML, "Pic")
|
||||
}
|
||||
|
||||
// getPicture provides a function to get picture base name and raw content
|
||||
// embed in spreadsheet by given coordinates and drawing relationships.
|
||||
func (f *File) getPicture(row, col int, drawingXML, drawingRelationships string) (pics []Picture, err error) {
|
||||
var (
|
||||
wsDr *xlsxWsDr
|
||||
ok bool
|
||||
deWsDr *decodeWsDr
|
||||
drawRel *xlsxRelationship
|
||||
deTwoCellAnchor *decodeTwoCellAnchor
|
||||
)
|
||||
|
||||
if wsDr, _, err = f.drawingParser(drawingXML); err != nil {
|
||||
return
|
||||
}
|
||||
if pics = f.getPicturesFromWsDr(row, col, drawingRelationships, wsDr); len(pics) > 0 {
|
||||
return
|
||||
}
|
||||
deWsDr = new(decodeWsDr)
|
||||
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(drawingXML)))).
|
||||
Decode(deWsDr); err != nil && err != io.EOF {
|
||||
return
|
||||
}
|
||||
err = nil
|
||||
for _, anchor := range deWsDr.TwoCellAnchor {
|
||||
deTwoCellAnchor = new(decodeTwoCellAnchor)
|
||||
if err = f.xmlNewDecoder(strings.NewReader("<decodeTwoCellAnchor>" + anchor.Content + "</decodeTwoCellAnchor>")).
|
||||
Decode(deTwoCellAnchor); err != nil && err != io.EOF {
|
||||
return
|
||||
}
|
||||
if err = nil; deTwoCellAnchor.From != nil && deTwoCellAnchor.Pic != nil {
|
||||
if deTwoCellAnchor.From.Col == col && deTwoCellAnchor.From.Row == row {
|
||||
drawRel = f.getDrawingRelationships(drawingRelationships, deTwoCellAnchor.Pic.BlipFill.Blip.Embed)
|
||||
if _, ok = supportedImageTypes[strings.ToLower(filepath.Ext(drawRel.Target))]; ok {
|
||||
pic := Picture{Extension: filepath.Ext(drawRel.Target), Format: &GraphicOptions{}}
|
||||
if buffer, _ := f.Pkg.Load(strings.ReplaceAll(drawRel.Target, "..", "xl")); buffer != nil {
|
||||
pic.File = buffer.([]byte)
|
||||
pic.Format.AltText = deTwoCellAnchor.Pic.NvPicPr.CNvPr.Descr
|
||||
pics = append(pics, pic)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// getPicturesFromWsDr provides a function to get picture base name and raw
|
||||
// content in worksheet drawing by given coordinates and drawing
|
||||
// relationships.
|
||||
func (f *File) getPicturesFromWsDr(row, col int, drawingRelationships string, wsDr *xlsxWsDr) (pics []Picture) {
|
||||
var (
|
||||
ok bool
|
||||
anchor *xdrCellAnchor
|
||||
drawRel *xlsxRelationship
|
||||
)
|
||||
wsDr.Lock()
|
||||
defer wsDr.Unlock()
|
||||
for _, anchor = range wsDr.TwoCellAnchor {
|
||||
if anchor.From != nil && anchor.Pic != nil {
|
||||
if anchor.From.Col == col && anchor.From.Row == row {
|
||||
if drawRel = f.getDrawingRelationships(drawingRelationships,
|
||||
anchor.Pic.BlipFill.Blip.Embed); drawRel != nil {
|
||||
if _, ok = supportedImageTypes[strings.ToLower(filepath.Ext(drawRel.Target))]; ok {
|
||||
pic := Picture{Extension: filepath.Ext(drawRel.Target), Format: &GraphicOptions{}}
|
||||
if buffer, _ := f.Pkg.Load(strings.ReplaceAll(drawRel.Target, "..", "xl")); buffer != nil {
|
||||
pic.File = buffer.([]byte)
|
||||
pic.Format.AltText = anchor.Pic.NvPicPr.CNvPr.Descr
|
||||
pics = append(pics, pic)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// getDrawingRelationships provides a function to get drawing relationships
|
||||
// from xl/drawings/_rels/drawing%s.xml.rels by given file name and
|
||||
// relationship ID.
|
||||
func (f *File) getDrawingRelationships(rels, rID string) *xlsxRelationship {
|
||||
if drawingRels, _ := f.relsReader(rels); drawingRels != nil {
|
||||
drawingRels.Lock()
|
||||
defer drawingRels.Unlock()
|
||||
for _, v := range drawingRels.Relationships {
|
||||
if v.ID == rID {
|
||||
return &v
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// drawingsWriter provides a function to save xl/drawings/drawing%d.xml after
|
||||
// serialize structure.
|
||||
func (f *File) drawingsWriter() {
|
||||
f.Drawings.Range(func(path, d interface{}) bool {
|
||||
if d != nil {
|
||||
v, _ := xml.Marshal(d.(*xlsxWsDr))
|
||||
f.saveFileList(path.(string), v)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// drawingResize calculate the height and width after resizing.
|
||||
func (f *File) drawingResize(sheet, cell string, width, height float64, opts *GraphicOptions) (w, h, c, r int, err error) {
|
||||
var mergeCells []MergeCell
|
||||
mergeCells, err = f.GetMergeCells(sheet)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var rng []int
|
||||
var inMergeCell bool
|
||||
if c, r, err = CellNameToCoordinates(cell); err != nil {
|
||||
return
|
||||
}
|
||||
cellWidth, cellHeight := f.getColWidth(sheet, c), f.getRowHeight(sheet, r)
|
||||
for _, mergeCell := range mergeCells {
|
||||
if inMergeCell {
|
||||
continue
|
||||
}
|
||||
if inMergeCell, err = f.checkCellInRangeRef(cell, mergeCell[0]); err != nil {
|
||||
return
|
||||
}
|
||||
if inMergeCell {
|
||||
rng, _ = cellRefsToCoordinates(mergeCell.GetStartAxis(), mergeCell.GetEndAxis())
|
||||
_ = sortCoordinates(rng)
|
||||
}
|
||||
}
|
||||
if inMergeCell {
|
||||
cellWidth, cellHeight = 0, 0
|
||||
c, r = rng[0], rng[1]
|
||||
for col := rng[0]; col <= rng[2]; col++ {
|
||||
cellWidth += f.getColWidth(sheet, col)
|
||||
}
|
||||
for row := rng[1]; row <= rng[3]; row++ {
|
||||
cellHeight += f.getRowHeight(sheet, row)
|
||||
}
|
||||
}
|
||||
if float64(cellWidth) < width {
|
||||
asp := float64(cellWidth) / width
|
||||
width, height = float64(cellWidth), height*asp
|
||||
}
|
||||
if float64(cellHeight) < height {
|
||||
asp := float64(cellHeight) / height
|
||||
height, width = float64(cellHeight), width*asp
|
||||
}
|
||||
width, height = width-float64(opts.OffsetX), height-float64(opts.OffsetY)
|
||||
w, h = int(width*opts.ScaleX), int(height*opts.ScaleY)
|
||||
return
|
||||
}
|
||||
+721
@@ -0,0 +1,721 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// PivotTableOptions directly maps the format settings of the pivot table.
|
||||
//
|
||||
// PivotTableStyleName: The built-in pivot table style names
|
||||
//
|
||||
// PivotStyleLight1 - PivotStyleLight28
|
||||
// PivotStyleMedium1 - PivotStyleMedium28
|
||||
// PivotStyleDark1 - PivotStyleDark28
|
||||
type PivotTableOptions struct {
|
||||
pivotTableSheetName string
|
||||
DataRange string
|
||||
PivotTableRange string
|
||||
Rows []PivotTableField
|
||||
Columns []PivotTableField
|
||||
Data []PivotTableField
|
||||
Filter []PivotTableField
|
||||
RowGrandTotals bool
|
||||
ColGrandTotals bool
|
||||
ShowDrill bool
|
||||
UseAutoFormatting bool
|
||||
PageOverThenDown bool
|
||||
MergeItem bool
|
||||
CompactData bool
|
||||
ShowError bool
|
||||
ShowRowHeaders bool
|
||||
ShowColHeaders bool
|
||||
ShowRowStripes bool
|
||||
ShowColStripes bool
|
||||
ShowLastColumn bool
|
||||
PivotTableStyleName string
|
||||
}
|
||||
|
||||
// PivotTableField directly maps the field settings of the pivot table.
|
||||
// Subtotal specifies the aggregation function that applies to this data
|
||||
// field. The default value is sum. The possible values for this attribute
|
||||
// are:
|
||||
//
|
||||
// Average
|
||||
// Count
|
||||
// CountNums
|
||||
// Max
|
||||
// Min
|
||||
// Product
|
||||
// StdDev
|
||||
// StdDevp
|
||||
// Sum
|
||||
// Var
|
||||
// Varp
|
||||
//
|
||||
// Name specifies the name of the data field. Maximum 255 characters
|
||||
// are allowed in data field name, excess characters will be truncated.
|
||||
type PivotTableField struct {
|
||||
Compact bool
|
||||
Data string
|
||||
Name string
|
||||
Outline bool
|
||||
Subtotal string
|
||||
DefaultSubtotal bool
|
||||
}
|
||||
|
||||
// AddPivotTable provides the method to add pivot table by given pivot table
|
||||
// options. Note that the same fields can not in Columns, Rows and Filter
|
||||
// fields at the same time.
|
||||
//
|
||||
// For example, create a pivot table on the range reference Sheet1!$G$2:$M$34
|
||||
// with the range reference Sheet1!$A$1:$E$31 as the data source, summarize by
|
||||
// sum for sales:
|
||||
//
|
||||
// package main
|
||||
//
|
||||
// import (
|
||||
// "fmt"
|
||||
// "math/rand"
|
||||
//
|
||||
// "github.com/xuri/excelize/v2"
|
||||
// )
|
||||
//
|
||||
// func main() {
|
||||
// f := excelize.NewFile()
|
||||
// defer func() {
|
||||
// if err := f.Close(); err != nil {
|
||||
// fmt.Println(err)
|
||||
// }
|
||||
// }()
|
||||
// // Create some data in a sheet
|
||||
// month := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
|
||||
// year := []int{2017, 2018, 2019}
|
||||
// types := []string{"Meat", "Dairy", "Beverages", "Produce"}
|
||||
// region := []string{"East", "West", "North", "South"}
|
||||
// f.SetSheetRow("Sheet1", "A1", &[]string{"Month", "Year", "Type", "Sales", "Region"})
|
||||
// for row := 2; row < 32; row++ {
|
||||
// f.SetCellValue("Sheet1", fmt.Sprintf("A%d", row), month[rand.Intn(12)])
|
||||
// f.SetCellValue("Sheet1", fmt.Sprintf("B%d", row), year[rand.Intn(3)])
|
||||
// f.SetCellValue("Sheet1", fmt.Sprintf("C%d", row), types[rand.Intn(4)])
|
||||
// f.SetCellValue("Sheet1", fmt.Sprintf("D%d", row), rand.Intn(5000))
|
||||
// f.SetCellValue("Sheet1", fmt.Sprintf("E%d", row), region[rand.Intn(4)])
|
||||
// }
|
||||
// if err := f.AddPivotTable(&excelize.PivotTableOptions{
|
||||
// DataRange: "Sheet1!$A$1:$E$31",
|
||||
// PivotTableRange: "Sheet1!$G$2:$M$34",
|
||||
// Rows: []excelize.PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
|
||||
// Filter: []excelize.PivotTableField{{Data: "Region"}},
|
||||
// Columns: []excelize.PivotTableField{{Data: "Type", DefaultSubtotal: true}},
|
||||
// Data: []excelize.PivotTableField{{Data: "Sales", Name: "Summarize", Subtotal: "Sum"}},
|
||||
// RowGrandTotals: true,
|
||||
// ColGrandTotals: true,
|
||||
// ShowDrill: true,
|
||||
// ShowRowHeaders: true,
|
||||
// ShowColHeaders: true,
|
||||
// ShowLastColumn: true,
|
||||
// }); err != nil {
|
||||
// fmt.Println(err)
|
||||
// }
|
||||
// if err := f.SaveAs("Book1.xlsx"); err != nil {
|
||||
// fmt.Println(err)
|
||||
// }
|
||||
// }
|
||||
func (f *File) AddPivotTable(opts *PivotTableOptions) error {
|
||||
// parameter validation
|
||||
_, pivotTableSheetPath, err := f.parseFormatPivotTableSet(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pivotTableID := f.countPivotTables() + 1
|
||||
pivotCacheID := f.countPivotCache() + 1
|
||||
|
||||
sheetRelationshipsPivotTableXML := "../pivotTables/pivotTable" + strconv.Itoa(pivotTableID) + ".xml"
|
||||
pivotTableXML := strings.ReplaceAll(sheetRelationshipsPivotTableXML, "..", "xl")
|
||||
pivotCacheXML := "xl/pivotCache/pivotCacheDefinition" + strconv.Itoa(pivotCacheID) + ".xml"
|
||||
err = f.addPivotCache(pivotCacheXML, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// workbook pivot cache
|
||||
workBookPivotCacheRID := f.addRels(f.getWorkbookRelsPath(), SourceRelationshipPivotCache, fmt.Sprintf("/xl/pivotCache/pivotCacheDefinition%d.xml", pivotCacheID), "")
|
||||
cacheID := f.addWorkbookPivotCache(workBookPivotCacheRID)
|
||||
|
||||
pivotCacheRels := "xl/pivotTables/_rels/pivotTable" + strconv.Itoa(pivotTableID) + ".xml.rels"
|
||||
// rId not used
|
||||
_ = f.addRels(pivotCacheRels, SourceRelationshipPivotCache, fmt.Sprintf("../pivotCache/pivotCacheDefinition%d.xml", pivotCacheID), "")
|
||||
err = f.addPivotTable(cacheID, pivotTableID, pivotTableXML, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pivotTableSheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(pivotTableSheetPath, "xl/worksheets/") + ".rels"
|
||||
f.addRels(pivotTableSheetRels, SourceRelationshipPivotTable, sheetRelationshipsPivotTableXML, "")
|
||||
if err = f.addContentTypePart(pivotTableID, "pivotTable"); err != nil {
|
||||
return err
|
||||
}
|
||||
return f.addContentTypePart(pivotCacheID, "pivotCache")
|
||||
}
|
||||
|
||||
// parseFormatPivotTableSet provides a function to validate pivot table
|
||||
// properties.
|
||||
func (f *File) parseFormatPivotTableSet(opts *PivotTableOptions) (*xlsxWorksheet, string, error) {
|
||||
if opts == nil {
|
||||
return nil, "", ErrParameterRequired
|
||||
}
|
||||
pivotTableSheetName, _, err := f.adjustRange(opts.PivotTableRange)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("parameter 'PivotTableRange' parsing error: %s", err.Error())
|
||||
}
|
||||
opts.pivotTableSheetName = pivotTableSheetName
|
||||
dataRange := f.getDefinedNameRefTo(opts.DataRange, pivotTableSheetName)
|
||||
if dataRange == "" {
|
||||
dataRange = opts.DataRange
|
||||
}
|
||||
dataSheetName, _, err := f.adjustRange(dataRange)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("parameter 'DataRange' parsing error: %s", err.Error())
|
||||
}
|
||||
dataSheet, err := f.workSheetReader(dataSheetName)
|
||||
if err != nil {
|
||||
return dataSheet, "", err
|
||||
}
|
||||
pivotTableSheetPath, ok := f.getSheetXMLPath(pivotTableSheetName)
|
||||
if !ok {
|
||||
return dataSheet, pivotTableSheetPath, fmt.Errorf("sheet %s does not exist", pivotTableSheetName)
|
||||
}
|
||||
return dataSheet, pivotTableSheetPath, err
|
||||
}
|
||||
|
||||
// adjustRange adjust range, for example: adjust Sheet1!$E$31:$A$1 to Sheet1!$A$1:$E$31
|
||||
func (f *File) adjustRange(rangeStr string) (string, []int, error) {
|
||||
if len(rangeStr) < 1 {
|
||||
return "", []int{}, ErrParameterRequired
|
||||
}
|
||||
rng := strings.Split(rangeStr, "!")
|
||||
if len(rng) != 2 {
|
||||
return "", []int{}, ErrParameterInvalid
|
||||
}
|
||||
trimRng := strings.ReplaceAll(rng[1], "$", "")
|
||||
coordinates, err := rangeRefToCoordinates(trimRng)
|
||||
if err != nil {
|
||||
return rng[0], []int{}, err
|
||||
}
|
||||
x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
|
||||
if x1 == x2 && y1 == y2 {
|
||||
return rng[0], []int{}, ErrParameterInvalid
|
||||
}
|
||||
|
||||
// Correct the range, such correct C1:B3 to B1:C3.
|
||||
if x2 < x1 {
|
||||
x1, x2 = x2, x1
|
||||
}
|
||||
|
||||
if y2 < y1 {
|
||||
y1, y2 = y2, y1
|
||||
}
|
||||
return rng[0], []int{x1, y1, x2, y2}, nil
|
||||
}
|
||||
|
||||
// getPivotFieldsOrder provides a function to get order list of pivot table
|
||||
// fields.
|
||||
func (f *File) getPivotFieldsOrder(opts *PivotTableOptions) ([]string, error) {
|
||||
var order []string
|
||||
dataRange := f.getDefinedNameRefTo(opts.DataRange, opts.pivotTableSheetName)
|
||||
if dataRange == "" {
|
||||
dataRange = opts.DataRange
|
||||
}
|
||||
dataSheet, coordinates, err := f.adjustRange(dataRange)
|
||||
if err != nil {
|
||||
return order, fmt.Errorf("parameter 'DataRange' parsing error: %s", err.Error())
|
||||
}
|
||||
for col := coordinates[0]; col <= coordinates[2]; col++ {
|
||||
coordinate, _ := CoordinatesToCellName(col, coordinates[1])
|
||||
name, err := f.GetCellValue(dataSheet, coordinate)
|
||||
if err != nil {
|
||||
return order, err
|
||||
}
|
||||
order = append(order, name)
|
||||
}
|
||||
return order, nil
|
||||
}
|
||||
|
||||
// addPivotCache provides a function to create a pivot cache by given properties.
|
||||
func (f *File) addPivotCache(pivotCacheXML string, opts *PivotTableOptions) error {
|
||||
// validate data range
|
||||
definedNameRef := true
|
||||
dataRange := f.getDefinedNameRefTo(opts.DataRange, opts.pivotTableSheetName)
|
||||
if dataRange == "" {
|
||||
definedNameRef = false
|
||||
dataRange = opts.DataRange
|
||||
}
|
||||
dataSheet, coordinates, err := f.adjustRange(dataRange)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parameter 'DataRange' parsing error: %s", err.Error())
|
||||
}
|
||||
// data range has been checked
|
||||
order, _ := f.getPivotFieldsOrder(opts)
|
||||
hCell, _ := CoordinatesToCellName(coordinates[0], coordinates[1])
|
||||
vCell, _ := CoordinatesToCellName(coordinates[2], coordinates[3])
|
||||
pc := xlsxPivotCacheDefinition{
|
||||
SaveData: false,
|
||||
RefreshOnLoad: true,
|
||||
CreatedVersion: pivotTableVersion,
|
||||
RefreshedVersion: pivotTableVersion,
|
||||
MinRefreshableVersion: pivotTableVersion,
|
||||
CacheSource: &xlsxCacheSource{
|
||||
Type: "worksheet",
|
||||
WorksheetSource: &xlsxWorksheetSource{
|
||||
Ref: hCell + ":" + vCell,
|
||||
Sheet: dataSheet,
|
||||
},
|
||||
},
|
||||
CacheFields: &xlsxCacheFields{},
|
||||
}
|
||||
if definedNameRef {
|
||||
pc.CacheSource.WorksheetSource = &xlsxWorksheetSource{Name: opts.DataRange}
|
||||
}
|
||||
for _, name := range order {
|
||||
rowOptions, rowOk := f.getPivotTableFieldOptions(name, opts.Rows)
|
||||
columnOptions, colOk := f.getPivotTableFieldOptions(name, opts.Columns)
|
||||
sharedItems := xlsxSharedItems{
|
||||
Count: 0,
|
||||
}
|
||||
s := xlsxString{}
|
||||
if (rowOk && !rowOptions.DefaultSubtotal) || (colOk && !columnOptions.DefaultSubtotal) {
|
||||
s = xlsxString{
|
||||
V: "",
|
||||
}
|
||||
sharedItems.Count++
|
||||
sharedItems.S = &s
|
||||
}
|
||||
|
||||
pc.CacheFields.CacheField = append(pc.CacheFields.CacheField, &xlsxCacheField{
|
||||
Name: name,
|
||||
SharedItems: &sharedItems,
|
||||
})
|
||||
}
|
||||
pc.CacheFields.Count = len(pc.CacheFields.CacheField)
|
||||
pivotCache, err := xml.Marshal(pc)
|
||||
f.saveFileList(pivotCacheXML, pivotCache)
|
||||
return err
|
||||
}
|
||||
|
||||
// addPivotTable provides a function to create a pivot table by given pivot
|
||||
// table ID and properties.
|
||||
func (f *File) addPivotTable(cacheID, pivotTableID int, pivotTableXML string, opts *PivotTableOptions) error {
|
||||
// validate pivot table range
|
||||
_, coordinates, err := f.adjustRange(opts.PivotTableRange)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parameter 'PivotTableRange' parsing error: %s", err.Error())
|
||||
}
|
||||
|
||||
hCell, _ := CoordinatesToCellName(coordinates[0], coordinates[1])
|
||||
vCell, _ := CoordinatesToCellName(coordinates[2], coordinates[3])
|
||||
|
||||
pivotTableStyle := func() string {
|
||||
if opts.PivotTableStyleName == "" {
|
||||
return "PivotStyleLight16"
|
||||
}
|
||||
return opts.PivotTableStyleName
|
||||
}
|
||||
pt := xlsxPivotTableDefinition{
|
||||
Name: fmt.Sprintf("Pivot Table%d", pivotTableID),
|
||||
CacheID: cacheID,
|
||||
RowGrandTotals: &opts.RowGrandTotals,
|
||||
ColGrandTotals: &opts.ColGrandTotals,
|
||||
UpdatedVersion: pivotTableVersion,
|
||||
MinRefreshableVersion: pivotTableVersion,
|
||||
ShowDrill: &opts.ShowDrill,
|
||||
UseAutoFormatting: &opts.UseAutoFormatting,
|
||||
PageOverThenDown: &opts.PageOverThenDown,
|
||||
MergeItem: &opts.MergeItem,
|
||||
CreatedVersion: pivotTableVersion,
|
||||
CompactData: &opts.CompactData,
|
||||
ShowError: &opts.ShowError,
|
||||
DataCaption: "Values",
|
||||
Location: &xlsxLocation{
|
||||
Ref: hCell + ":" + vCell,
|
||||
FirstDataCol: 1,
|
||||
FirstDataRow: 1,
|
||||
FirstHeaderRow: 1,
|
||||
},
|
||||
PivotFields: &xlsxPivotFields{},
|
||||
RowItems: &xlsxRowItems{
|
||||
Count: 1,
|
||||
I: []*xlsxI{
|
||||
{
|
||||
[]*xlsxX{{}, {}},
|
||||
},
|
||||
},
|
||||
},
|
||||
ColItems: &xlsxColItems{
|
||||
Count: 1,
|
||||
I: []*xlsxI{{}},
|
||||
},
|
||||
PivotTableStyleInfo: &xlsxPivotTableStyleInfo{
|
||||
Name: pivotTableStyle(),
|
||||
ShowRowHeaders: opts.ShowRowHeaders,
|
||||
ShowColHeaders: opts.ShowColHeaders,
|
||||
ShowRowStripes: opts.ShowRowStripes,
|
||||
ShowColStripes: opts.ShowColStripes,
|
||||
ShowLastColumn: opts.ShowLastColumn,
|
||||
},
|
||||
}
|
||||
|
||||
// pivot fields
|
||||
_ = f.addPivotFields(&pt, opts)
|
||||
|
||||
// count pivot fields
|
||||
pt.PivotFields.Count = len(pt.PivotFields.PivotField)
|
||||
|
||||
// data range has been checked
|
||||
_ = f.addPivotRowFields(&pt, opts)
|
||||
_ = f.addPivotColFields(&pt, opts)
|
||||
_ = f.addPivotPageFields(&pt, opts)
|
||||
_ = f.addPivotDataFields(&pt, opts)
|
||||
|
||||
pivotTable, err := xml.Marshal(pt)
|
||||
f.saveFileList(pivotTableXML, pivotTable)
|
||||
return err
|
||||
}
|
||||
|
||||
// addPivotRowFields provides a method to add row fields for pivot table by
|
||||
// given pivot table options.
|
||||
func (f *File) addPivotRowFields(pt *xlsxPivotTableDefinition, opts *PivotTableOptions) error {
|
||||
// row fields
|
||||
rowFieldsIndex, err := f.getPivotFieldsIndex(opts.Rows, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, fieldIdx := range rowFieldsIndex {
|
||||
if pt.RowFields == nil {
|
||||
pt.RowFields = &xlsxRowFields{}
|
||||
}
|
||||
pt.RowFields.Field = append(pt.RowFields.Field, &xlsxField{
|
||||
X: fieldIdx,
|
||||
})
|
||||
}
|
||||
|
||||
// count row fields
|
||||
if pt.RowFields != nil {
|
||||
pt.RowFields.Count = len(pt.RowFields.Field)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// addPivotPageFields provides a method to add page fields for pivot table by
|
||||
// given pivot table options.
|
||||
func (f *File) addPivotPageFields(pt *xlsxPivotTableDefinition, opts *PivotTableOptions) error {
|
||||
// page fields
|
||||
pageFieldsIndex, err := f.getPivotFieldsIndex(opts.Filter, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pageFieldsName := f.getPivotTableFieldsName(opts.Filter)
|
||||
for idx, pageField := range pageFieldsIndex {
|
||||
if pt.PageFields == nil {
|
||||
pt.PageFields = &xlsxPageFields{}
|
||||
}
|
||||
pt.PageFields.PageField = append(pt.PageFields.PageField, &xlsxPageField{
|
||||
Name: pageFieldsName[idx],
|
||||
Fld: pageField,
|
||||
})
|
||||
}
|
||||
|
||||
// count page fields
|
||||
if pt.PageFields != nil {
|
||||
pt.PageFields.Count = len(pt.PageFields.PageField)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// addPivotDataFields provides a method to add data fields for pivot table by
|
||||
// given pivot table options.
|
||||
func (f *File) addPivotDataFields(pt *xlsxPivotTableDefinition, opts *PivotTableOptions) error {
|
||||
// data fields
|
||||
dataFieldsIndex, err := f.getPivotFieldsIndex(opts.Data, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dataFieldsSubtotals := f.getPivotTableFieldsSubtotal(opts.Data)
|
||||
dataFieldsName := f.getPivotTableFieldsName(opts.Data)
|
||||
for idx, dataField := range dataFieldsIndex {
|
||||
if pt.DataFields == nil {
|
||||
pt.DataFields = &xlsxDataFields{}
|
||||
}
|
||||
pt.DataFields.DataField = append(pt.DataFields.DataField, &xlsxDataField{
|
||||
Name: dataFieldsName[idx],
|
||||
Fld: dataField,
|
||||
Subtotal: dataFieldsSubtotals[idx],
|
||||
})
|
||||
}
|
||||
|
||||
// count data fields
|
||||
if pt.DataFields != nil {
|
||||
pt.DataFields.Count = len(pt.DataFields.DataField)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// inPivotTableField provides a method to check if an element is present in
|
||||
// pivot table fields list, and return the index of its location, otherwise
|
||||
// return -1.
|
||||
func inPivotTableField(a []PivotTableField, x string) int {
|
||||
for idx, n := range a {
|
||||
if x == n.Data {
|
||||
return idx
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// addPivotColFields create pivot column fields by given pivot table
|
||||
// definition and option.
|
||||
func (f *File) addPivotColFields(pt *xlsxPivotTableDefinition, opts *PivotTableOptions) error {
|
||||
if len(opts.Columns) == 0 {
|
||||
if len(opts.Data) <= 1 {
|
||||
return nil
|
||||
}
|
||||
pt.ColFields = &xlsxColFields{}
|
||||
// in order to create pivot table in case there is no input from Columns
|
||||
pt.ColFields.Count = 1
|
||||
pt.ColFields.Field = append(pt.ColFields.Field, &xlsxField{
|
||||
X: -2,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
pt.ColFields = &xlsxColFields{}
|
||||
|
||||
// col fields
|
||||
colFieldsIndex, err := f.getPivotFieldsIndex(opts.Columns, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, fieldIdx := range colFieldsIndex {
|
||||
pt.ColFields.Field = append(pt.ColFields.Field, &xlsxField{
|
||||
X: fieldIdx,
|
||||
})
|
||||
}
|
||||
|
||||
// in order to create pivot in case there is many Columns and Data
|
||||
if len(opts.Data) > 1 {
|
||||
pt.ColFields.Field = append(pt.ColFields.Field, &xlsxField{
|
||||
X: -2,
|
||||
})
|
||||
}
|
||||
|
||||
// count col fields
|
||||
pt.ColFields.Count = len(pt.ColFields.Field)
|
||||
return err
|
||||
}
|
||||
|
||||
// addPivotFields create pivot fields based on the column order of the first
|
||||
// row in the data region by given pivot table definition and option.
|
||||
func (f *File) addPivotFields(pt *xlsxPivotTableDefinition, opts *PivotTableOptions) error {
|
||||
order, err := f.getPivotFieldsOrder(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
x := 0
|
||||
for _, name := range order {
|
||||
if inPivotTableField(opts.Rows, name) != -1 {
|
||||
rowOptions, ok := f.getPivotTableFieldOptions(name, opts.Rows)
|
||||
var items []*xlsxItem
|
||||
if !ok || !rowOptions.DefaultSubtotal {
|
||||
items = append(items, &xlsxItem{X: &x})
|
||||
} else {
|
||||
items = append(items, &xlsxItem{T: "default"})
|
||||
}
|
||||
|
||||
pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{
|
||||
Name: f.getPivotTableFieldName(name, opts.Rows),
|
||||
Axis: "axisRow",
|
||||
DataField: inPivotTableField(opts.Data, name) != -1,
|
||||
Compact: &rowOptions.Compact,
|
||||
Outline: &rowOptions.Outline,
|
||||
DefaultSubtotal: &rowOptions.DefaultSubtotal,
|
||||
Items: &xlsxItems{
|
||||
Count: len(items),
|
||||
Item: items,
|
||||
},
|
||||
})
|
||||
continue
|
||||
}
|
||||
if inPivotTableField(opts.Filter, name) != -1 {
|
||||
pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{
|
||||
Axis: "axisPage",
|
||||
DataField: inPivotTableField(opts.Data, name) != -1,
|
||||
Name: f.getPivotTableFieldName(name, opts.Columns),
|
||||
Items: &xlsxItems{
|
||||
Count: 1,
|
||||
Item: []*xlsxItem{
|
||||
{T: "default"},
|
||||
},
|
||||
},
|
||||
})
|
||||
continue
|
||||
}
|
||||
if inPivotTableField(opts.Columns, name) != -1 {
|
||||
columnOptions, ok := f.getPivotTableFieldOptions(name, opts.Columns)
|
||||
var items []*xlsxItem
|
||||
if !ok || !columnOptions.DefaultSubtotal {
|
||||
items = append(items, &xlsxItem{X: &x})
|
||||
} else {
|
||||
items = append(items, &xlsxItem{T: "default"})
|
||||
}
|
||||
pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{
|
||||
Name: f.getPivotTableFieldName(name, opts.Columns),
|
||||
Axis: "axisCol",
|
||||
DataField: inPivotTableField(opts.Data, name) != -1,
|
||||
Compact: &columnOptions.Compact,
|
||||
Outline: &columnOptions.Outline,
|
||||
DefaultSubtotal: &columnOptions.DefaultSubtotal,
|
||||
Items: &xlsxItems{
|
||||
Count: len(items),
|
||||
Item: items,
|
||||
},
|
||||
})
|
||||
continue
|
||||
}
|
||||
if inPivotTableField(opts.Data, name) != -1 {
|
||||
pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{
|
||||
DataField: true,
|
||||
})
|
||||
continue
|
||||
}
|
||||
pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{})
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// countPivotTables provides a function to get drawing files count storage in
|
||||
// the folder xl/pivotTables.
|
||||
func (f *File) countPivotTables() int {
|
||||
count := 0
|
||||
f.Pkg.Range(func(k, v interface{}) bool {
|
||||
if strings.Contains(k.(string), "xl/pivotTables/pivotTable") {
|
||||
count++
|
||||
}
|
||||
return true
|
||||
})
|
||||
return count
|
||||
}
|
||||
|
||||
// countPivotCache provides a function to get drawing files count storage in
|
||||
// the folder xl/pivotCache.
|
||||
func (f *File) countPivotCache() int {
|
||||
count := 0
|
||||
f.Pkg.Range(func(k, v interface{}) bool {
|
||||
if strings.Contains(k.(string), "xl/pivotCache/pivotCacheDefinition") {
|
||||
count++
|
||||
}
|
||||
return true
|
||||
})
|
||||
return count
|
||||
}
|
||||
|
||||
// getPivotFieldsIndex convert the column of the first row in the data region
|
||||
// to a sequential index by given fields and pivot option.
|
||||
func (f *File) getPivotFieldsIndex(fields []PivotTableField, opts *PivotTableOptions) ([]int, error) {
|
||||
var pivotFieldsIndex []int
|
||||
orders, err := f.getPivotFieldsOrder(opts)
|
||||
if err != nil {
|
||||
return pivotFieldsIndex, err
|
||||
}
|
||||
for _, field := range fields {
|
||||
if pos := inStrSlice(orders, field.Data, true); pos != -1 {
|
||||
pivotFieldsIndex = append(pivotFieldsIndex, pos)
|
||||
}
|
||||
}
|
||||
return pivotFieldsIndex, nil
|
||||
}
|
||||
|
||||
// getPivotTableFieldsSubtotal prepare fields subtotal by given pivot table fields.
|
||||
func (f *File) getPivotTableFieldsSubtotal(fields []PivotTableField) []string {
|
||||
field := make([]string, len(fields))
|
||||
enums := []string{"average", "count", "countNums", "max", "min", "product", "stdDev", "stdDevp", "sum", "var", "varp"}
|
||||
inEnums := func(enums []string, val string) string {
|
||||
for _, enum := range enums {
|
||||
if strings.EqualFold(enum, val) {
|
||||
return enum
|
||||
}
|
||||
}
|
||||
return "sum"
|
||||
}
|
||||
for idx, fld := range fields {
|
||||
field[idx] = inEnums(enums, fld.Subtotal)
|
||||
}
|
||||
return field
|
||||
}
|
||||
|
||||
// getPivotTableFieldsName prepare fields name list by given pivot table
|
||||
// fields.
|
||||
func (f *File) getPivotTableFieldsName(fields []PivotTableField) []string {
|
||||
field := make([]string, len(fields))
|
||||
for idx, fld := range fields {
|
||||
if len(fld.Name) > MaxFieldLength {
|
||||
field[idx] = fld.Name[:MaxFieldLength]
|
||||
continue
|
||||
}
|
||||
field[idx] = fld.Name
|
||||
}
|
||||
return field
|
||||
}
|
||||
|
||||
// getPivotTableFieldName prepare field name by given pivot table fields.
|
||||
func (f *File) getPivotTableFieldName(name string, fields []PivotTableField) string {
|
||||
fieldsName := f.getPivotTableFieldsName(fields)
|
||||
for idx, field := range fields {
|
||||
if field.Data == name {
|
||||
return fieldsName[idx]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// getPivotTableFieldOptions return options for specific field by given field name.
|
||||
func (f *File) getPivotTableFieldOptions(name string, fields []PivotTableField) (options PivotTableField, ok bool) {
|
||||
for _, field := range fields {
|
||||
if field.Data == name {
|
||||
options, ok = field, true
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// addWorkbookPivotCache add the association ID of the pivot cache in workbook.xml.
|
||||
func (f *File) addWorkbookPivotCache(RID int) int {
|
||||
wb, _ := f.workbookReader()
|
||||
if wb.PivotCaches == nil {
|
||||
wb.PivotCaches = &xlsxPivotCaches{}
|
||||
}
|
||||
cacheID := 1
|
||||
for _, pivotCache := range wb.PivotCaches.PivotCache {
|
||||
if pivotCache.CacheID > cacheID {
|
||||
cacheID = pivotCache.CacheID
|
||||
}
|
||||
}
|
||||
cacheID++
|
||||
wb.PivotCaches.PivotCache = append(wb.PivotCaches.PivotCache, xlsxPivotCache{
|
||||
CacheID: cacheID,
|
||||
RID: fmt.Sprintf("rId%d", RID),
|
||||
})
|
||||
return cacheID
|
||||
}
|
||||
+849
@@ -0,0 +1,849 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/mohae/deepcopy"
|
||||
)
|
||||
|
||||
// GetRows return all the rows in a sheet by given worksheet name, returned as
|
||||
// a two-dimensional array, where the value of the cell is converted to the
|
||||
// string type. If the cell format can be applied to the value of the cell,
|
||||
// the applied value will be used, otherwise the original value will be used.
|
||||
// GetRows fetched the rows with value or formula cells, the continually blank
|
||||
// cells in the tail of each row will be skipped, so the length of each row
|
||||
// may be inconsistent.
|
||||
//
|
||||
// For example, get and traverse the value of all cells by rows on a worksheet
|
||||
// named 'Sheet1':
|
||||
//
|
||||
// rows, err := f.GetRows("Sheet1")
|
||||
// if err != nil {
|
||||
// fmt.Println(err)
|
||||
// return
|
||||
// }
|
||||
// for _, row := range rows {
|
||||
// for _, colCell := range row {
|
||||
// fmt.Print(colCell, "\t")
|
||||
// }
|
||||
// fmt.Println()
|
||||
// }
|
||||
func (f *File) GetRows(sheet string, opts ...Options) ([][]string, error) {
|
||||
rows, err := f.Rows(sheet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results, cur, max := make([][]string, 0, 64), 0, 0
|
||||
for rows.Next() {
|
||||
cur++
|
||||
row, err := rows.Columns(opts...)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
results = append(results, row)
|
||||
if len(row) > 0 {
|
||||
max = cur
|
||||
}
|
||||
}
|
||||
return results[:max], rows.Close()
|
||||
}
|
||||
|
||||
// Rows defines an iterator to a sheet.
|
||||
type Rows struct {
|
||||
err error
|
||||
curRow, seekRow int
|
||||
needClose, rawCellValue bool
|
||||
sheet string
|
||||
f *File
|
||||
tempFile *os.File
|
||||
sst *xlsxSST
|
||||
decoder *xml.Decoder
|
||||
token xml.Token
|
||||
curRowOpts, seekRowOpts RowOpts
|
||||
}
|
||||
|
||||
// Next will return true if find the next row element.
|
||||
func (rows *Rows) Next() bool {
|
||||
rows.seekRow++
|
||||
if rows.curRow >= rows.seekRow {
|
||||
rows.curRowOpts = rows.seekRowOpts
|
||||
return true
|
||||
}
|
||||
for {
|
||||
token, _ := rows.decoder.Token()
|
||||
if token == nil {
|
||||
return false
|
||||
}
|
||||
switch xmlElement := token.(type) {
|
||||
case xml.StartElement:
|
||||
if xmlElement.Name.Local == "row" {
|
||||
rows.curRow++
|
||||
if rowNum, _ := attrValToInt("r", xmlElement.Attr); rowNum != 0 {
|
||||
rows.curRow = rowNum
|
||||
}
|
||||
rows.token = token
|
||||
rows.curRowOpts = extractRowOpts(xmlElement.Attr)
|
||||
return true
|
||||
}
|
||||
case xml.EndElement:
|
||||
if xmlElement.Name.Local == "sheetData" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetRowOpts will return the RowOpts of the current row.
|
||||
func (rows *Rows) GetRowOpts() RowOpts {
|
||||
return rows.curRowOpts
|
||||
}
|
||||
|
||||
// Error will return the error when the error occurs.
|
||||
func (rows *Rows) Error() error {
|
||||
return rows.err
|
||||
}
|
||||
|
||||
// Close closes the open worksheet XML file in the system temporary
|
||||
// directory.
|
||||
func (rows *Rows) Close() error {
|
||||
if rows.tempFile != nil {
|
||||
return rows.tempFile.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Columns return the current row's column values. This fetches the worksheet
|
||||
// data as a stream, returns each cell in a row as is, and will not skip empty
|
||||
// rows in the tail of the worksheet.
|
||||
func (rows *Rows) Columns(opts ...Options) ([]string, error) {
|
||||
if rows.curRow > rows.seekRow {
|
||||
return nil, nil
|
||||
}
|
||||
var rowIterator rowXMLIterator
|
||||
var token xml.Token
|
||||
rows.rawCellValue = getOptions(opts...).RawCellValue
|
||||
if rows.sst, rowIterator.err = rows.f.sharedStringsReader(); rowIterator.err != nil {
|
||||
return rowIterator.cells, rowIterator.err
|
||||
}
|
||||
for {
|
||||
if rows.token != nil {
|
||||
token = rows.token
|
||||
} else if token, _ = rows.decoder.Token(); token == nil {
|
||||
break
|
||||
}
|
||||
switch xmlElement := token.(type) {
|
||||
case xml.StartElement:
|
||||
rowIterator.inElement = xmlElement.Name.Local
|
||||
if rowIterator.inElement == "row" {
|
||||
rowNum := 0
|
||||
if rowNum, rowIterator.err = attrValToInt("r", xmlElement.Attr); rowNum != 0 {
|
||||
rows.curRow = rowNum
|
||||
} else if rows.token == nil {
|
||||
rows.curRow++
|
||||
}
|
||||
rows.token = token
|
||||
rows.seekRowOpts = extractRowOpts(xmlElement.Attr)
|
||||
if rows.curRow > rows.seekRow {
|
||||
rows.token = nil
|
||||
return rowIterator.cells, rowIterator.err
|
||||
}
|
||||
}
|
||||
if rows.rowXMLHandler(&rowIterator, &xmlElement, rows.rawCellValue); rowIterator.err != nil {
|
||||
rows.token = nil
|
||||
return rowIterator.cells, rowIterator.err
|
||||
}
|
||||
rows.token = nil
|
||||
case xml.EndElement:
|
||||
if xmlElement.Name.Local == "sheetData" {
|
||||
return rowIterator.cells, rowIterator.err
|
||||
}
|
||||
}
|
||||
}
|
||||
return rowIterator.cells, rowIterator.err
|
||||
}
|
||||
|
||||
// extractRowOpts extract row element attributes.
|
||||
func extractRowOpts(attrs []xml.Attr) RowOpts {
|
||||
rowOpts := RowOpts{Height: defaultRowHeight}
|
||||
if styleID, err := attrValToInt("s", attrs); err == nil && styleID > 0 && styleID < MaxCellStyles {
|
||||
rowOpts.StyleID = styleID
|
||||
}
|
||||
if hidden, err := attrValToBool("hidden", attrs); err == nil {
|
||||
rowOpts.Hidden = hidden
|
||||
}
|
||||
if height, err := attrValToFloat("ht", attrs); err == nil {
|
||||
rowOpts.Height = height
|
||||
}
|
||||
return rowOpts
|
||||
}
|
||||
|
||||
// appendSpace append blank characters to slice by given length and source slice.
|
||||
func appendSpace(l int, s []string) []string {
|
||||
for i := 1; i < l; i++ {
|
||||
s = append(s, "")
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// ErrSheetNotExist defines an error of sheet that does not exist
|
||||
type ErrSheetNotExist struct {
|
||||
SheetName string
|
||||
}
|
||||
|
||||
func (err ErrSheetNotExist) Error() string {
|
||||
return fmt.Sprintf("sheet %s does not exist", err.SheetName)
|
||||
}
|
||||
|
||||
// rowXMLIterator defined runtime use field for the worksheet row SAX parser.
|
||||
type rowXMLIterator struct {
|
||||
err error
|
||||
inElement string
|
||||
cellCol, cellRow int
|
||||
cells []string
|
||||
}
|
||||
|
||||
// rowXMLHandler parse the row XML element of the worksheet.
|
||||
func (rows *Rows) rowXMLHandler(rowIterator *rowXMLIterator, xmlElement *xml.StartElement, raw bool) {
|
||||
if rowIterator.inElement == "c" {
|
||||
rowIterator.cellCol++
|
||||
colCell := xlsxC{}
|
||||
_ = rows.decoder.DecodeElement(&colCell, xmlElement)
|
||||
if colCell.R != "" {
|
||||
if rowIterator.cellCol, _, rowIterator.err = CellNameToCoordinates(colCell.R); rowIterator.err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
blank := rowIterator.cellCol - len(rowIterator.cells)
|
||||
if val, _ := colCell.getValueFrom(rows.f, rows.sst, raw); val != "" || colCell.F != nil {
|
||||
rowIterator.cells = append(appendSpace(blank, rowIterator.cells), val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rows returns a rows iterator, used for streaming reading data for a
|
||||
// worksheet with a large data. This function is concurrency safe. For
|
||||
// example:
|
||||
//
|
||||
// rows, err := f.Rows("Sheet1")
|
||||
// if err != nil {
|
||||
// fmt.Println(err)
|
||||
// return
|
||||
// }
|
||||
// for rows.Next() {
|
||||
// row, err := rows.Columns()
|
||||
// if err != nil {
|
||||
// fmt.Println(err)
|
||||
// }
|
||||
// for _, colCell := range row {
|
||||
// fmt.Print(colCell, "\t")
|
||||
// }
|
||||
// fmt.Println()
|
||||
// }
|
||||
// if err = rows.Close(); err != nil {
|
||||
// fmt.Println(err)
|
||||
// }
|
||||
func (f *File) Rows(sheet string) (*Rows, error) {
|
||||
if err := checkSheetName(sheet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name, ok := f.getSheetXMLPath(sheet)
|
||||
if !ok {
|
||||
return nil, ErrSheetNotExist{sheet}
|
||||
}
|
||||
if ws, ok := f.Sheet.Load(name); ok && ws != nil {
|
||||
worksheet := ws.(*xlsxWorksheet)
|
||||
worksheet.Lock()
|
||||
defer worksheet.Unlock()
|
||||
// Flush data
|
||||
output, _ := xml.Marshal(worksheet)
|
||||
f.saveFileList(name, f.replaceNameSpaceBytes(name, output))
|
||||
}
|
||||
var err error
|
||||
rows := Rows{f: f, sheet: name}
|
||||
rows.needClose, rows.decoder, rows.tempFile, err = f.xmlDecoder(name)
|
||||
return &rows, err
|
||||
}
|
||||
|
||||
// getFromStringItem build shared string item offset list from system temporary
|
||||
// file at one time, and return value by given to string index.
|
||||
func (f *File) getFromStringItem(index int) string {
|
||||
if f.sharedStringTemp != nil {
|
||||
if len(f.sharedStringItem) <= index {
|
||||
return strconv.Itoa(index)
|
||||
}
|
||||
offsetRange := f.sharedStringItem[index]
|
||||
buf := make([]byte, offsetRange[1]-offsetRange[0])
|
||||
if _, err := f.sharedStringTemp.ReadAt(buf, int64(offsetRange[0])); err != nil {
|
||||
return strconv.Itoa(index)
|
||||
}
|
||||
return string(buf)
|
||||
}
|
||||
needClose, decoder, tempFile, err := f.xmlDecoder(defaultXMLPathSharedStrings)
|
||||
if needClose && err == nil {
|
||||
defer tempFile.Close()
|
||||
}
|
||||
f.sharedStringItem = [][]uint{}
|
||||
f.sharedStringTemp, _ = os.CreateTemp(os.TempDir(), "excelize-")
|
||||
f.tempFiles.Store(defaultTempFileSST, f.sharedStringTemp.Name())
|
||||
var (
|
||||
inElement string
|
||||
i, offset uint
|
||||
)
|
||||
for {
|
||||
token, _ := decoder.Token()
|
||||
if token == nil {
|
||||
break
|
||||
}
|
||||
switch xmlElement := token.(type) {
|
||||
case xml.StartElement:
|
||||
inElement = xmlElement.Name.Local
|
||||
if inElement == "si" {
|
||||
si := xlsxSI{}
|
||||
_ = decoder.DecodeElement(&si, &xmlElement)
|
||||
|
||||
startIdx := offset
|
||||
n, _ := f.sharedStringTemp.WriteString(si.String())
|
||||
offset += uint(n)
|
||||
f.sharedStringItem = append(f.sharedStringItem, []uint{startIdx, offset})
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
return f.getFromStringItem(index)
|
||||
}
|
||||
|
||||
// xmlDecoder creates XML decoder by given path in the zip from memory data
|
||||
// or system temporary file.
|
||||
func (f *File) xmlDecoder(name string) (bool, *xml.Decoder, *os.File, error) {
|
||||
var (
|
||||
content []byte
|
||||
err error
|
||||
tempFile *os.File
|
||||
)
|
||||
if content = f.readXML(name); len(content) > 0 {
|
||||
return false, f.xmlNewDecoder(bytes.NewReader(content)), tempFile, err
|
||||
}
|
||||
tempFile, err = f.readTemp(name)
|
||||
return true, f.xmlNewDecoder(tempFile), tempFile, err
|
||||
}
|
||||
|
||||
// SetRowHeight provides a function to set the height of a single row. For
|
||||
// example, set the height of the first row in Sheet1:
|
||||
//
|
||||
// err := f.SetRowHeight("Sheet1", 1, 50)
|
||||
func (f *File) SetRowHeight(sheet string, row int, height float64) error {
|
||||
if row < 1 {
|
||||
return newInvalidRowNumberError(row)
|
||||
}
|
||||
if height > MaxRowHeight {
|
||||
return ErrMaxRowHeight
|
||||
}
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
prepareSheetXML(ws, 0, row)
|
||||
|
||||
rowIdx := row - 1
|
||||
ws.SheetData.Row[rowIdx].Ht = float64Ptr(height)
|
||||
ws.SheetData.Row[rowIdx].CustomHeight = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// getRowHeight provides a function to get row height in pixels by given sheet
|
||||
// name and row number.
|
||||
func (f *File) getRowHeight(sheet string, row int) int {
|
||||
ws, _ := f.workSheetReader(sheet)
|
||||
ws.Lock()
|
||||
defer ws.Unlock()
|
||||
for i := range ws.SheetData.Row {
|
||||
v := &ws.SheetData.Row[i]
|
||||
if v.R == row && v.Ht != nil {
|
||||
return int(convertRowHeightToPixels(*v.Ht))
|
||||
}
|
||||
}
|
||||
// Optimization for when the row heights haven't changed.
|
||||
return int(defaultRowHeightPixels)
|
||||
}
|
||||
|
||||
// GetRowHeight provides a function to get row height by given worksheet name
|
||||
// and row number. For example, get the height of the first row in Sheet1:
|
||||
//
|
||||
// height, err := f.GetRowHeight("Sheet1", 1)
|
||||
func (f *File) GetRowHeight(sheet string, row int) (float64, error) {
|
||||
if row < 1 {
|
||||
return defaultRowHeightPixels, newInvalidRowNumberError(row)
|
||||
}
|
||||
ht := defaultRowHeight
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return ht, err
|
||||
}
|
||||
if ws.SheetFormatPr != nil && ws.SheetFormatPr.CustomHeight {
|
||||
ht = ws.SheetFormatPr.DefaultRowHeight
|
||||
}
|
||||
if row > len(ws.SheetData.Row) {
|
||||
return ht, nil // it will be better to use 0, but we take care with BC
|
||||
}
|
||||
for _, v := range ws.SheetData.Row {
|
||||
if v.R == row && v.Ht != nil {
|
||||
return *v.Ht, nil
|
||||
}
|
||||
}
|
||||
// Optimization for when the row heights haven't changed.
|
||||
return ht, nil
|
||||
}
|
||||
|
||||
// sharedStringsReader provides a function to get the pointer to the structure
|
||||
// after deserialization of xl/sharedStrings.xml.
|
||||
func (f *File) sharedStringsReader() (*xlsxSST, error) {
|
||||
var err error
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
relPath := f.getWorkbookRelsPath()
|
||||
if f.SharedStrings == nil {
|
||||
var sharedStrings xlsxSST
|
||||
ss := f.readXML(defaultXMLPathSharedStrings)
|
||||
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(ss))).
|
||||
Decode(&sharedStrings); err != nil && err != io.EOF {
|
||||
return f.SharedStrings, err
|
||||
}
|
||||
if sharedStrings.Count == 0 {
|
||||
sharedStrings.Count = len(sharedStrings.SI)
|
||||
}
|
||||
if sharedStrings.UniqueCount == 0 {
|
||||
sharedStrings.UniqueCount = sharedStrings.Count
|
||||
}
|
||||
f.SharedStrings = &sharedStrings
|
||||
for i := range sharedStrings.SI {
|
||||
if sharedStrings.SI[i].T != nil {
|
||||
f.sharedStringsMap[sharedStrings.SI[i].T.Val] = i
|
||||
}
|
||||
}
|
||||
if err = f.addContentTypePart(0, "sharedStrings"); err != nil {
|
||||
return f.SharedStrings, err
|
||||
}
|
||||
rels, err := f.relsReader(relPath)
|
||||
if err != nil {
|
||||
return f.SharedStrings, err
|
||||
}
|
||||
for _, rel := range rels.Relationships {
|
||||
if rel.Target == "/xl/sharedStrings.xml" {
|
||||
return f.SharedStrings, nil
|
||||
}
|
||||
}
|
||||
// Update workbook.xml.rels
|
||||
f.addRels(relPath, SourceRelationshipSharedStrings, "/xl/sharedStrings.xml", "")
|
||||
}
|
||||
|
||||
return f.SharedStrings, nil
|
||||
}
|
||||
|
||||
// SetRowVisible provides a function to set visible of a single row by given
|
||||
// worksheet name and Excel row number. For example, hide row 2 in Sheet1:
|
||||
//
|
||||
// err := f.SetRowVisible("Sheet1", 2, false)
|
||||
func (f *File) SetRowVisible(sheet string, row int, visible bool) error {
|
||||
if row < 1 {
|
||||
return newInvalidRowNumberError(row)
|
||||
}
|
||||
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
prepareSheetXML(ws, 0, row)
|
||||
ws.SheetData.Row[row-1].Hidden = !visible
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRowVisible provides a function to get visible of a single row by given
|
||||
// worksheet name and Excel row number. For example, get visible state of row
|
||||
// 2 in Sheet1:
|
||||
//
|
||||
// visible, err := f.GetRowVisible("Sheet1", 2)
|
||||
func (f *File) GetRowVisible(sheet string, row int) (bool, error) {
|
||||
if row < 1 {
|
||||
return false, newInvalidRowNumberError(row)
|
||||
}
|
||||
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if row > len(ws.SheetData.Row) {
|
||||
return false, nil
|
||||
}
|
||||
return !ws.SheetData.Row[row-1].Hidden, nil
|
||||
}
|
||||
|
||||
// SetRowOutlineLevel provides a function to set outline level number of a
|
||||
// single row by given worksheet name and Excel row number. The value of
|
||||
// parameter 'level' is 1-7. For example, outline row 2 in Sheet1 to level 1:
|
||||
//
|
||||
// err := f.SetRowOutlineLevel("Sheet1", 2, 1)
|
||||
func (f *File) SetRowOutlineLevel(sheet string, row int, level uint8) error {
|
||||
if row < 1 {
|
||||
return newInvalidRowNumberError(row)
|
||||
}
|
||||
if level > 7 || level < 1 {
|
||||
return ErrOutlineLevel
|
||||
}
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
prepareSheetXML(ws, 0, row)
|
||||
ws.SheetData.Row[row-1].OutlineLevel = level
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRowOutlineLevel provides a function to get outline level number of a
|
||||
// single row by given worksheet name and Excel row number. For example, get
|
||||
// outline number of row 2 in Sheet1:
|
||||
//
|
||||
// level, err := f.GetRowOutlineLevel("Sheet1", 2)
|
||||
func (f *File) GetRowOutlineLevel(sheet string, row int) (uint8, error) {
|
||||
if row < 1 {
|
||||
return 0, newInvalidRowNumberError(row)
|
||||
}
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if row > len(ws.SheetData.Row) {
|
||||
return 0, nil
|
||||
}
|
||||
return ws.SheetData.Row[row-1].OutlineLevel, nil
|
||||
}
|
||||
|
||||
// RemoveRow provides a function to remove single row by given worksheet name
|
||||
// and Excel row number. For example, remove row 3 in Sheet1:
|
||||
//
|
||||
// err := f.RemoveRow("Sheet1", 3)
|
||||
//
|
||||
// Use this method with caution, which will affect changes in references such
|
||||
// as formulas, charts, and so on. If there is any referenced value of the
|
||||
// worksheet, it will cause a file error when you open it. The excelize only
|
||||
// partially updates these references currently.
|
||||
func (f *File) RemoveRow(sheet string, row int) error {
|
||||
if row < 1 {
|
||||
return newInvalidRowNumberError(row)
|
||||
}
|
||||
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if row > len(ws.SheetData.Row) {
|
||||
return f.adjustHelper(sheet, rows, row, -1)
|
||||
}
|
||||
keep := 0
|
||||
for rowIdx := 0; rowIdx < len(ws.SheetData.Row); rowIdx++ {
|
||||
v := &ws.SheetData.Row[rowIdx]
|
||||
if v.R != row {
|
||||
ws.SheetData.Row[keep] = *v
|
||||
keep++
|
||||
}
|
||||
}
|
||||
ws.SheetData.Row = ws.SheetData.Row[:keep]
|
||||
return f.adjustHelper(sheet, rows, row, -1)
|
||||
}
|
||||
|
||||
// InsertRows provides a function to insert new rows after the given Excel row
|
||||
// number starting from 1 and number of rows. For example, create two rows
|
||||
// before row 3 in Sheet1:
|
||||
//
|
||||
// err := f.InsertRows("Sheet1", 3, 2)
|
||||
//
|
||||
// Use this method with caution, which will affect changes in references such
|
||||
// as formulas, charts, and so on. If there is any referenced value of the
|
||||
// worksheet, it will cause a file error when you open it. The excelize only
|
||||
// partially updates these references currently.
|
||||
func (f *File) InsertRows(sheet string, row, n int) error {
|
||||
if row < 1 {
|
||||
return newInvalidRowNumberError(row)
|
||||
}
|
||||
if row >= TotalRows || n >= TotalRows {
|
||||
return ErrMaxRows
|
||||
}
|
||||
if n < 1 {
|
||||
return ErrParameterInvalid
|
||||
}
|
||||
return f.adjustHelper(sheet, rows, row, n)
|
||||
}
|
||||
|
||||
// DuplicateRow inserts a copy of specified row (by its Excel row number) below
|
||||
//
|
||||
// err := f.DuplicateRow("Sheet1", 2)
|
||||
//
|
||||
// Use this method with caution, which will affect changes in references such
|
||||
// as formulas, charts, and so on. If there is any referenced value of the
|
||||
// worksheet, it will cause a file error when you open it. The excelize only
|
||||
// partially updates these references currently.
|
||||
func (f *File) DuplicateRow(sheet string, row int) error {
|
||||
return f.DuplicateRowTo(sheet, row, row+1)
|
||||
}
|
||||
|
||||
// DuplicateRowTo inserts a copy of specified row by it Excel number
|
||||
// to specified row position moving down exists rows after target position
|
||||
//
|
||||
// err := f.DuplicateRowTo("Sheet1", 2, 7)
|
||||
//
|
||||
// Use this method with caution, which will affect changes in references such
|
||||
// as formulas, charts, and so on. If there is any referenced value of the
|
||||
// worksheet, it will cause a file error when you open it. The excelize only
|
||||
// partially updates these references currently.
|
||||
func (f *File) DuplicateRowTo(sheet string, row, row2 int) error {
|
||||
if row < 1 {
|
||||
return newInvalidRowNumberError(row)
|
||||
}
|
||||
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if row2 < 1 || row == row2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var ok bool
|
||||
var rowCopy xlsxRow
|
||||
|
||||
for i, r := range ws.SheetData.Row {
|
||||
if r.R == row {
|
||||
rowCopy = deepcopy.Copy(ws.SheetData.Row[i]).(xlsxRow)
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err := f.adjustHelper(sheet, rows, row2, 1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
idx2 := -1
|
||||
for i, r := range ws.SheetData.Row {
|
||||
if r.R == row2 {
|
||||
idx2 = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if idx2 == -1 && len(ws.SheetData.Row) >= row2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
rowCopy.C = append(make([]xlsxC, 0, len(rowCopy.C)), rowCopy.C...)
|
||||
f.adjustSingleRowDimensions(&rowCopy, row2)
|
||||
|
||||
if idx2 != -1 {
|
||||
ws.SheetData.Row[idx2] = rowCopy
|
||||
} else {
|
||||
ws.SheetData.Row = append(ws.SheetData.Row, rowCopy)
|
||||
}
|
||||
return f.duplicateMergeCells(sheet, ws, row, row2)
|
||||
}
|
||||
|
||||
// duplicateMergeCells merge cells in the destination row if there are single
|
||||
// row merged cells in the copied row.
|
||||
func (f *File) duplicateMergeCells(sheet string, ws *xlsxWorksheet, row, row2 int) error {
|
||||
if ws.MergeCells == nil {
|
||||
return nil
|
||||
}
|
||||
if row > row2 {
|
||||
row++
|
||||
}
|
||||
for _, rng := range ws.MergeCells.Cells {
|
||||
coordinates, err := rangeRefToCoordinates(rng.Ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if coordinates[1] < row2 && row2 < coordinates[3] {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(ws.MergeCells.Cells); i++ {
|
||||
mergedCells := ws.MergeCells.Cells[i]
|
||||
coordinates, _ := rangeRefToCoordinates(mergedCells.Ref)
|
||||
x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
|
||||
if y1 == y2 && y1 == row {
|
||||
from, _ := CoordinatesToCellName(x1, row2)
|
||||
to, _ := CoordinatesToCellName(x2, row2)
|
||||
if err := f.MergeCell(sheet, from, to); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkRow provides a function to check and fill each column element for all
|
||||
// rows and make that is continuous in a worksheet of XML. For example:
|
||||
//
|
||||
// <row r="15" spans="1:22" x14ac:dyDescent="0.2">
|
||||
// <c r="A15" s="2" />
|
||||
// <c r="B15" s="2" />
|
||||
// <c r="F15" s="1" />
|
||||
// <c r="G15" s="1" />
|
||||
// </row>
|
||||
//
|
||||
// in this case, we should to change it to
|
||||
//
|
||||
// <row r="15" spans="1:22" x14ac:dyDescent="0.2">
|
||||
// <c r="A15" s="2" />
|
||||
// <c r="B15" s="2" />
|
||||
// <c r="C15" s="2" />
|
||||
// <c r="D15" s="2" />
|
||||
// <c r="E15" s="2" />
|
||||
// <c r="F15" s="1" />
|
||||
// <c r="G15" s="1" />
|
||||
// </row>
|
||||
//
|
||||
// Notice: this method could be very slow for large spreadsheets (more than
|
||||
// 3000 rows one sheet).
|
||||
func checkRow(ws *xlsxWorksheet) error {
|
||||
for rowIdx := range ws.SheetData.Row {
|
||||
rowData := &ws.SheetData.Row[rowIdx]
|
||||
|
||||
colCount := len(rowData.C)
|
||||
if colCount == 0 {
|
||||
continue
|
||||
}
|
||||
// check and fill the cell without r attribute in a row element
|
||||
rCount := 0
|
||||
for idx, cell := range rowData.C {
|
||||
rCount++
|
||||
if cell.R != "" {
|
||||
lastR, _, err := CellNameToCoordinates(cell.R)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if lastR > rCount {
|
||||
rCount = lastR
|
||||
}
|
||||
continue
|
||||
}
|
||||
rowData.C[idx].R, _ = CoordinatesToCellName(rCount, rowIdx+1)
|
||||
}
|
||||
lastCol, _, err := CellNameToCoordinates(rowData.C[colCount-1].R)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if colCount < lastCol {
|
||||
sourceList := rowData.C
|
||||
targetList := make([]xlsxC, 0, lastCol)
|
||||
|
||||
rowData.C = ws.SheetData.Row[rowIdx].C[:0]
|
||||
|
||||
for colIdx := 0; colIdx < lastCol; colIdx++ {
|
||||
cellName, err := CoordinatesToCellName(colIdx+1, rowIdx+1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targetList = append(targetList, xlsxC{R: cellName})
|
||||
}
|
||||
|
||||
rowData.C = targetList
|
||||
|
||||
for colIdx := range sourceList {
|
||||
colData := &sourceList[colIdx]
|
||||
colNum, _, err := CellNameToCoordinates(colData.R)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ws.SheetData.Row[rowIdx].C[colNum-1] = *colData
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// hasAttr determine if row non-default attributes.
|
||||
func (r *xlsxRow) hasAttr() bool {
|
||||
return r.Spans != "" || r.S != 0 || r.CustomFormat || r.Ht != nil ||
|
||||
r.Hidden || r.CustomHeight || r.OutlineLevel != 0 || r.Collapsed ||
|
||||
r.ThickTop || r.ThickBot || r.Ph
|
||||
}
|
||||
|
||||
// SetRowStyle provides a function to set the style of rows by given worksheet
|
||||
// name, row range, and style ID. Note that this will overwrite the existing
|
||||
// styles for the rows, it won't append or merge style with existing styles.
|
||||
//
|
||||
// For example set style of row 1 on Sheet1:
|
||||
//
|
||||
// err := f.SetRowStyle("Sheet1", 1, 1, styleID)
|
||||
//
|
||||
// Set style of rows 1 to 10 on Sheet1:
|
||||
//
|
||||
// err := f.SetRowStyle("Sheet1", 1, 10, styleID)
|
||||
func (f *File) SetRowStyle(sheet string, start, end, styleID int) error {
|
||||
if end < start {
|
||||
start, end = end, start
|
||||
}
|
||||
if start < 1 {
|
||||
return newInvalidRowNumberError(start)
|
||||
}
|
||||
if end > TotalRows {
|
||||
return ErrMaxRows
|
||||
}
|
||||
s, err := f.stylesReader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
if styleID < 0 || s.CellXfs == nil || len(s.CellXfs.Xf) <= styleID {
|
||||
return newInvalidStyleID(styleID)
|
||||
}
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
prepareSheetXML(ws, 0, end)
|
||||
for row := start - 1; row < end; row++ {
|
||||
ws.SheetData.Row[row].S = styleID
|
||||
ws.SheetData.Row[row].CustomFormat = true
|
||||
for i := range ws.SheetData.Row[row].C {
|
||||
if _, rowNum, err := CellNameToCoordinates(ws.SheetData.Row[row].C[i].R); err == nil && rowNum-1 == row {
|
||||
ws.SheetData.Row[row].C[i].S = styleID
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// convertRowHeightToPixels provides a function to convert the height of a
|
||||
// cell from user's units to pixels. If the height hasn't been set by the user
|
||||
// we use the default value. If the row is hidden it has a value of zero.
|
||||
func convertRowHeightToPixels(height float64) float64 {
|
||||
var pixels float64
|
||||
if height == 0 {
|
||||
return pixels
|
||||
}
|
||||
pixels = math.Ceil(4.0 / 3.0 * height)
|
||||
return pixels
|
||||
}
|
||||
+489
@@ -0,0 +1,489 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// parseShapeOptions provides a function to parse the format settings of the
|
||||
// shape with default value.
|
||||
func parseShapeOptions(opts *Shape) (*Shape, error) {
|
||||
if opts == nil {
|
||||
return nil, ErrParameterInvalid
|
||||
}
|
||||
if opts.Width == 0 {
|
||||
opts.Width = defaultShapeSize
|
||||
}
|
||||
if opts.Height == 0 {
|
||||
opts.Height = defaultShapeSize
|
||||
}
|
||||
if opts.Format.PrintObject == nil {
|
||||
opts.Format.PrintObject = boolPtr(true)
|
||||
}
|
||||
if opts.Format.Locked == nil {
|
||||
opts.Format.Locked = boolPtr(false)
|
||||
}
|
||||
if opts.Format.ScaleX == 0 {
|
||||
opts.Format.ScaleX = defaultPictureScale
|
||||
}
|
||||
if opts.Format.ScaleY == 0 {
|
||||
opts.Format.ScaleY = defaultPictureScale
|
||||
}
|
||||
if opts.Line.Width == nil {
|
||||
opts.Line.Width = float64Ptr(defaultShapeLineWidth)
|
||||
}
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
// AddShape provides the method to add shape in a sheet by given worksheet
|
||||
// index, shape format set (such as offset, scale, aspect ratio setting and
|
||||
// print settings) and properties set. For example, add text box (rect shape)
|
||||
// in Sheet1:
|
||||
//
|
||||
// lineWidth := 1.2
|
||||
// err := f.AddShape("Sheet1", "G6",
|
||||
// &excelize.Shape{
|
||||
// Type: "rect",
|
||||
// Line: excelize.ShapeLine{Color: "4286F4", Width: &lineWidth},
|
||||
// Fill: excelize.Fill{Color: []string{"8EB9FF"}, Pattern: 1},
|
||||
// Paragraph: []excelize.RichTextRun{
|
||||
// {
|
||||
// Text: "Rectangle Shape",
|
||||
// Font: &excelize.Font{
|
||||
// Bold: true,
|
||||
// Italic: true,
|
||||
// Family: "Times New Roman",
|
||||
// Size: 18,
|
||||
// Color: "777777",
|
||||
// Underline: "sng",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// Width: 180,
|
||||
// Height: 40,
|
||||
// },
|
||||
// )
|
||||
//
|
||||
// The following shows the type of shape supported by excelize:
|
||||
//
|
||||
// accentBorderCallout1 (Callout 1 with Border and Accent Shape)
|
||||
// accentBorderCallout2 (Callout 2 with Border and Accent Shape)
|
||||
// accentBorderCallout3 (Callout 3 with Border and Accent Shape)
|
||||
// accentCallout1 (Callout 1 Shape)
|
||||
// accentCallout2 (Callout 2 Shape)
|
||||
// accentCallout3 (Callout 3 Shape)
|
||||
// actionButtonBackPrevious (Back or Previous Button Shape)
|
||||
// actionButtonBeginning (Beginning Button Shape)
|
||||
// actionButtonBlank (Blank Button Shape)
|
||||
// actionButtonDocument (Document Button Shape)
|
||||
// actionButtonEnd (End Button Shape)
|
||||
// actionButtonForwardNext (Forward or Next Button Shape)
|
||||
// actionButtonHelp (Help Button Shape)
|
||||
// actionButtonHome (Home Button Shape)
|
||||
// actionButtonInformation (Information Button Shape)
|
||||
// actionButtonMovie (Movie Button Shape)
|
||||
// actionButtonReturn (Return Button Shape)
|
||||
// actionButtonSound (Sound Button Shape)
|
||||
// arc (Curved Arc Shape)
|
||||
// bentArrow (Bent Arrow Shape)
|
||||
// bentConnector2 (Bent Connector 2 Shape)
|
||||
// bentConnector3 (Bent Connector 3 Shape)
|
||||
// bentConnector4 (Bent Connector 4 Shape)
|
||||
// bentConnector5 (Bent Connector 5 Shape)
|
||||
// bentUpArrow (Bent Up Arrow Shape)
|
||||
// bevel (Bevel Shape)
|
||||
// blockArc (Block Arc Shape)
|
||||
// borderCallout1 (Callout 1 with Border Shape)
|
||||
// borderCallout2 (Callout 2 with Border Shape)
|
||||
// borderCallout3 (Callout 3 with Border Shape)
|
||||
// bracePair (Brace Pair Shape)
|
||||
// bracketPair (Bracket Pair Shape)
|
||||
// callout1 (Callout 1 Shape)
|
||||
// callout2 (Callout 2 Shape)
|
||||
// callout3 (Callout 3 Shape)
|
||||
// can (Can Shape)
|
||||
// chartPlus (Chart Plus Shape)
|
||||
// chartStar (Chart Star Shape)
|
||||
// chartX (Chart X Shape)
|
||||
// chevron (Chevron Shape)
|
||||
// chord (Chord Shape)
|
||||
// circularArrow (Circular Arrow Shape)
|
||||
// cloud (Cloud Shape)
|
||||
// cloudCallout (Callout Cloud Shape)
|
||||
// corner (Corner Shape)
|
||||
// cornerTabs (Corner Tabs Shape)
|
||||
// cube (Cube Shape)
|
||||
// curvedConnector2 (Curved Connector 2 Shape)
|
||||
// curvedConnector3 (Curved Connector 3 Shape)
|
||||
// curvedConnector4 (Curved Connector 4 Shape)
|
||||
// curvedConnector5 (Curved Connector 5 Shape)
|
||||
// curvedDownArrow (Curved Down Arrow Shape)
|
||||
// curvedLeftArrow (Curved Left Arrow Shape)
|
||||
// curvedRightArrow (Curved Right Arrow Shape)
|
||||
// curvedUpArrow (Curved Up Arrow Shape)
|
||||
// decagon (Decagon Shape)
|
||||
// diagStripe (Diagonal Stripe Shape)
|
||||
// diamond (Diamond Shape)
|
||||
// dodecagon (Dodecagon Shape)
|
||||
// donut (Donut Shape)
|
||||
// doubleWave (Double Wave Shape)
|
||||
// downArrow (Down Arrow Shape)
|
||||
// downArrowCallout (Callout Down Arrow Shape)
|
||||
// ellipse (Ellipse Shape)
|
||||
// ellipseRibbon (Ellipse Ribbon Shape)
|
||||
// ellipseRibbon2 (Ellipse Ribbon 2 Shape)
|
||||
// flowChartAlternateProcess (Alternate Process Flow Shape)
|
||||
// flowChartCollate (Collate Flow Shape)
|
||||
// flowChartConnector (Connector Flow Shape)
|
||||
// flowChartDecision (Decision Flow Shape)
|
||||
// flowChartDelay (Delay Flow Shape)
|
||||
// flowChartDisplay (Display Flow Shape)
|
||||
// flowChartDocument (Document Flow Shape)
|
||||
// flowChartExtract (Extract Flow Shape)
|
||||
// flowChartInputOutput (Input Output Flow Shape)
|
||||
// flowChartInternalStorage (Internal Storage Flow Shape)
|
||||
// flowChartMagneticDisk (Magnetic Disk Flow Shape)
|
||||
// flowChartMagneticDrum (Magnetic Drum Flow Shape)
|
||||
// flowChartMagneticTape (Magnetic Tape Flow Shape)
|
||||
// flowChartManualInput (Manual Input Flow Shape)
|
||||
// flowChartManualOperation (Manual Operation Flow Shape)
|
||||
// flowChartMerge (Merge Flow Shape)
|
||||
// flowChartMultidocument (Multi-Document Flow Shape)
|
||||
// flowChartOfflineStorage (Offline Storage Flow Shape)
|
||||
// flowChartOffpageConnector (Off-Page Connector Flow Shape)
|
||||
// flowChartOnlineStorage (Online Storage Flow Shape)
|
||||
// flowChartOr (Or Flow Shape)
|
||||
// flowChartPredefinedProcess (Predefined Process Flow Shape)
|
||||
// flowChartPreparation (Preparation Flow Shape)
|
||||
// flowChartProcess (Process Flow Shape)
|
||||
// flowChartPunchedCard (Punched Card Flow Shape)
|
||||
// flowChartPunchedTape (Punched Tape Flow Shape)
|
||||
// flowChartSort (Sort Flow Shape)
|
||||
// flowChartSummingJunction (Summing Junction Flow Shape)
|
||||
// flowChartTerminator (Terminator Flow Shape)
|
||||
// foldedCorner (Folded Corner Shape)
|
||||
// frame (Frame Shape)
|
||||
// funnel (Funnel Shape)
|
||||
// gear6 (Gear 6 Shape)
|
||||
// gear9 (Gear 9 Shape)
|
||||
// halfFrame (Half Frame Shape)
|
||||
// heart (Heart Shape)
|
||||
// heptagon (Heptagon Shape)
|
||||
// hexagon (Hexagon Shape)
|
||||
// homePlate (Home Plate Shape)
|
||||
// horizontalScroll (Horizontal Scroll Shape)
|
||||
// irregularSeal1 (Irregular Seal 1 Shape)
|
||||
// irregularSeal2 (Irregular Seal 2 Shape)
|
||||
// leftArrow (Left Arrow Shape)
|
||||
// leftArrowCallout (Callout Left Arrow Shape)
|
||||
// leftBrace (Left Brace Shape)
|
||||
// leftBracket (Left Bracket Shape)
|
||||
// leftCircularArrow (Left Circular Arrow Shape)
|
||||
// leftRightArrow (Left Right Arrow Shape)
|
||||
// leftRightArrowCallout (Callout Left Right Arrow Shape)
|
||||
// leftRightCircularArrow (Left Right Circular Arrow Shape)
|
||||
// leftRightRibbon (Left Right Ribbon Shape)
|
||||
// leftRightUpArrow (Left Right Up Arrow Shape)
|
||||
// leftUpArrow (Left Up Arrow Shape)
|
||||
// lightningBolt (Lightning Bolt Shape)
|
||||
// line (Line Shape)
|
||||
// lineInv (Line Inverse Shape)
|
||||
// mathDivide (Divide Math Shape)
|
||||
// mathEqual (Equal Math Shape)
|
||||
// mathMinus (Minus Math Shape)
|
||||
// mathMultiply (Multiply Math Shape)
|
||||
// mathNotEqual (Not Equal Math Shape)
|
||||
// mathPlus (Plus Math Shape)
|
||||
// moon (Moon Shape)
|
||||
// nonIsoscelesTrapezoid (Non-Isosceles Trapezoid Shape)
|
||||
// noSmoking (No Smoking Shape)
|
||||
// notchedRightArrow (Notched Right Arrow Shape)
|
||||
// octagon (Octagon Shape)
|
||||
// parallelogram (Parallelogram Shape)
|
||||
// pentagon (Pentagon Shape)
|
||||
// pie (Pie Shape)
|
||||
// pieWedge (Pie Wedge Shape)
|
||||
// plaque (Plaque Shape)
|
||||
// plaqueTabs (Plaque Tabs Shape)
|
||||
// plus (Plus Shape)
|
||||
// quadArrow (Quad-Arrow Shape)
|
||||
// quadArrowCallout (Callout Quad-Arrow Shape)
|
||||
// rect (Rectangle Shape)
|
||||
// ribbon (Ribbon Shape)
|
||||
// ribbon2 (Ribbon 2 Shape)
|
||||
// rightArrow (Right Arrow Shape)
|
||||
// rightArrowCallout (Callout Right Arrow Shape)
|
||||
// rightBrace (Right Brace Shape)
|
||||
// rightBracket (Right Bracket Shape)
|
||||
// round1Rect (One Round Corner Rectangle Shape)
|
||||
// round2DiagRect (Two Diagonal Round Corner Rectangle Shape)
|
||||
// round2SameRect (Two Same-side Round Corner Rectangle Shape)
|
||||
// roundRect (Round Corner Rectangle Shape)
|
||||
// rtTriangle (Right Triangle Shape)
|
||||
// smileyFace (Smiley Face Shape)
|
||||
// snip1Rect (One Snip Corner Rectangle Shape)
|
||||
// snip2DiagRect (Two Diagonal Snip Corner Rectangle Shape)
|
||||
// snip2SameRect (Two Same-side Snip Corner Rectangle Shape)
|
||||
// snipRoundRect (One Snip One Round Corner Rectangle Shape)
|
||||
// squareTabs (Square Tabs Shape)
|
||||
// star10 (Ten Pointed Star Shape)
|
||||
// star12 (Twelve Pointed Star Shape)
|
||||
// star16 (Sixteen Pointed Star Shape)
|
||||
// star24 (Twenty Four Pointed Star Shape)
|
||||
// star32 (Thirty Two Pointed Star Shape)
|
||||
// star4 (Four Pointed Star Shape)
|
||||
// star5 (Five Pointed Star Shape)
|
||||
// star6 (Six Pointed Star Shape)
|
||||
// star7 (Seven Pointed Star Shape)
|
||||
// star8 (Eight Pointed Star Shape)
|
||||
// straightConnector1 (Straight Connector 1 Shape)
|
||||
// stripedRightArrow (Striped Right Arrow Shape)
|
||||
// sun (Sun Shape)
|
||||
// swooshArrow (Swoosh Arrow Shape)
|
||||
// teardrop (Teardrop Shape)
|
||||
// trapezoid (Trapezoid Shape)
|
||||
// triangle (Triangle Shape)
|
||||
// upArrow (Up Arrow Shape)
|
||||
// upArrowCallout (Callout Up Arrow Shape)
|
||||
// upDownArrow (Up Down Arrow Shape)
|
||||
// upDownArrowCallout (Callout Up Down Arrow Shape)
|
||||
// uturnArrow (U-Turn Arrow Shape)
|
||||
// verticalScroll (Vertical Scroll Shape)
|
||||
// wave (Wave Shape)
|
||||
// wedgeEllipseCallout (Callout Wedge Ellipse Shape)
|
||||
// wedgeRectCallout (Callout Wedge Rectangle Shape)
|
||||
// wedgeRoundRectCallout (Callout Wedge Round Rectangle Shape)
|
||||
//
|
||||
// The following shows the type of text underline supported by excelize:
|
||||
//
|
||||
// none
|
||||
// words
|
||||
// sng
|
||||
// dbl
|
||||
// heavy
|
||||
// dotted
|
||||
// dottedHeavy
|
||||
// dash
|
||||
// dashHeavy
|
||||
// dashLong
|
||||
// dashLongHeavy
|
||||
// dotDash
|
||||
// dotDashHeavy
|
||||
// dotDotDash
|
||||
// dotDotDashHeavy
|
||||
// wavy
|
||||
// wavyHeavy
|
||||
// wavyDbl
|
||||
func (f *File) AddShape(sheet, cell string, opts *Shape) error {
|
||||
options, err := parseShapeOptions(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Read sheet data.
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Add first shape for given sheet, create xl/drawings/ and xl/drawings/_rels/ folder.
|
||||
drawingID := f.countDrawings() + 1
|
||||
drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml"
|
||||
sheetRelationshipsDrawingXML := "../drawings/drawing" + strconv.Itoa(drawingID) + ".xml"
|
||||
|
||||
if ws.Drawing != nil {
|
||||
// The worksheet already has a shape or chart relationships, use the relationships drawing ../drawings/drawing%d.xml.
|
||||
sheetRelationshipsDrawingXML = f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID)
|
||||
drawingID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingXML, "../drawings/drawing"), ".xml"))
|
||||
drawingXML = strings.ReplaceAll(sheetRelationshipsDrawingXML, "..", "xl")
|
||||
} else {
|
||||
// Add first shape for given sheet.
|
||||
sheetXMLPath, _ := f.getSheetXMLPath(sheet)
|
||||
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels"
|
||||
rID := f.addRels(sheetRels, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "")
|
||||
f.addSheetDrawing(sheet, rID)
|
||||
f.addSheetNameSpace(sheet, SourceRelationship)
|
||||
}
|
||||
if err = f.addDrawingShape(sheet, drawingXML, cell, options); err != nil {
|
||||
return err
|
||||
}
|
||||
return f.addContentTypePart(drawingID, "drawings")
|
||||
}
|
||||
|
||||
// addDrawingShape provides a function to add preset geometry by given sheet,
|
||||
// drawingXMLand format sets.
|
||||
func (f *File) addDrawingShape(sheet, drawingXML, cell string, opts *Shape) error {
|
||||
fromCol, fromRow, err := CellNameToCoordinates(cell)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
colIdx := fromCol - 1
|
||||
rowIdx := fromRow - 1
|
||||
|
||||
width := int(float64(opts.Width) * opts.Format.ScaleX)
|
||||
height := int(float64(opts.Height) * opts.Format.ScaleY)
|
||||
|
||||
colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, colIdx, rowIdx, opts.Format.OffsetX, opts.Format.OffsetY,
|
||||
width, height)
|
||||
content, cNvPrID, err := f.drawingParser(drawingXML)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
twoCellAnchor := xdrCellAnchor{}
|
||||
twoCellAnchor.EditAs = opts.Format.Positioning
|
||||
from := xlsxFrom{}
|
||||
from.Col = colStart
|
||||
from.ColOff = opts.Format.OffsetX * EMU
|
||||
from.Row = rowStart
|
||||
from.RowOff = opts.Format.OffsetY * EMU
|
||||
to := xlsxTo{}
|
||||
to.Col = colEnd
|
||||
to.ColOff = x2 * EMU
|
||||
to.Row = rowEnd
|
||||
to.RowOff = y2 * EMU
|
||||
twoCellAnchor.From = &from
|
||||
twoCellAnchor.To = &to
|
||||
var solidColor string
|
||||
if len(opts.Fill.Color) == 1 {
|
||||
solidColor = opts.Fill.Color[0]
|
||||
}
|
||||
shape := xdrSp{
|
||||
Macro: opts.Macro,
|
||||
NvSpPr: &xdrNvSpPr{
|
||||
CNvPr: &xlsxCNvPr{
|
||||
ID: cNvPrID,
|
||||
Name: "Shape " + strconv.Itoa(cNvPrID),
|
||||
},
|
||||
CNvSpPr: &xdrCNvSpPr{
|
||||
TxBox: true,
|
||||
},
|
||||
},
|
||||
SpPr: &xlsxSpPr{
|
||||
PrstGeom: xlsxPrstGeom{
|
||||
Prst: opts.Type,
|
||||
},
|
||||
},
|
||||
Style: &xdrStyle{
|
||||
LnRef: setShapeRef(opts.Line.Color, 2),
|
||||
FillRef: setShapeRef(solidColor, 1),
|
||||
EffectRef: setShapeRef("", 0),
|
||||
FontRef: &aFontRef{
|
||||
Idx: "minor",
|
||||
SchemeClr: &attrValString{
|
||||
Val: stringPtr("tx1"),
|
||||
},
|
||||
},
|
||||
},
|
||||
TxBody: &xdrTxBody{
|
||||
BodyPr: &aBodyPr{
|
||||
VertOverflow: "clip",
|
||||
HorzOverflow: "clip",
|
||||
Wrap: "none",
|
||||
RtlCol: false,
|
||||
Anchor: "t",
|
||||
},
|
||||
},
|
||||
}
|
||||
if *opts.Line.Width != 1 {
|
||||
shape.SpPr.Ln = xlsxLineProperties{
|
||||
W: f.ptToEMUs(*opts.Line.Width),
|
||||
}
|
||||
}
|
||||
defaultFont, err := f.GetDefaultFont()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(opts.Paragraph) < 1 {
|
||||
opts.Paragraph = []RichTextRun{
|
||||
{
|
||||
Font: &Font{
|
||||
Bold: false,
|
||||
Italic: false,
|
||||
Underline: "none",
|
||||
Family: defaultFont,
|
||||
Size: 11,
|
||||
Color: "000000",
|
||||
},
|
||||
Text: " ",
|
||||
},
|
||||
}
|
||||
}
|
||||
for _, p := range opts.Paragraph {
|
||||
u := "none"
|
||||
font := &Font{}
|
||||
if p.Font != nil {
|
||||
font = p.Font
|
||||
}
|
||||
if idx := inStrSlice(supportedDrawingUnderlineTypes, font.Underline, true); idx != -1 {
|
||||
u = supportedDrawingUnderlineTypes[idx]
|
||||
}
|
||||
text := p.Text
|
||||
if text == "" {
|
||||
text = " "
|
||||
}
|
||||
paragraph := &aP{
|
||||
R: &aR{
|
||||
RPr: aRPr{
|
||||
I: font.Italic,
|
||||
B: font.Bold,
|
||||
Lang: "en-US",
|
||||
AltLang: "en-US",
|
||||
U: u,
|
||||
Sz: font.Size * 100,
|
||||
Latin: &xlsxCTTextFont{Typeface: font.Family},
|
||||
},
|
||||
T: text,
|
||||
},
|
||||
EndParaRPr: &aEndParaRPr{
|
||||
Lang: "en-US",
|
||||
},
|
||||
}
|
||||
srgbClr := strings.ReplaceAll(strings.ToUpper(font.Color), "#", "")
|
||||
if len(srgbClr) == 6 {
|
||||
paragraph.R.RPr.SolidFill = &aSolidFill{
|
||||
SrgbClr: &attrValString{
|
||||
Val: stringPtr(srgbClr),
|
||||
},
|
||||
}
|
||||
}
|
||||
shape.TxBody.P = append(shape.TxBody.P, paragraph)
|
||||
}
|
||||
twoCellAnchor.Sp = &shape
|
||||
twoCellAnchor.ClientData = &xdrClientData{
|
||||
FLocksWithSheet: *opts.Format.Locked,
|
||||
FPrintsWithSheet: *opts.Format.PrintObject,
|
||||
}
|
||||
content.TwoCellAnchor = append(content.TwoCellAnchor, &twoCellAnchor)
|
||||
f.Drawings.Store(drawingXML, content)
|
||||
return err
|
||||
}
|
||||
|
||||
// setShapeRef provides a function to set color with hex model by given actual
|
||||
// color value.
|
||||
func setShapeRef(color string, i int) *aRef {
|
||||
if color == "" {
|
||||
return &aRef{
|
||||
Idx: 0,
|
||||
ScrgbClr: &aScrgbClr{
|
||||
R: 0,
|
||||
G: 0,
|
||||
B: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
return &aRef{
|
||||
Idx: i,
|
||||
SrgbClr: &attrValString{
|
||||
Val: stringPtr(strings.ReplaceAll(strings.ToUpper(color), "#", "")),
|
||||
},
|
||||
}
|
||||
}
|
||||
+1934
File diff suppressed because it is too large
Load Diff
+223
@@ -0,0 +1,223 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import "reflect"
|
||||
|
||||
// SetPageMargins provides a function to set worksheet page margins.
|
||||
func (f *File) SetPageMargins(sheet string, opts *PageLayoutMarginsOptions) error {
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if opts == nil {
|
||||
return err
|
||||
}
|
||||
preparePageMargins := func(ws *xlsxWorksheet) {
|
||||
if ws.PageMargins == nil {
|
||||
ws.PageMargins = new(xlsxPageMargins)
|
||||
}
|
||||
}
|
||||
preparePrintOptions := func(ws *xlsxWorksheet) {
|
||||
if ws.PrintOptions == nil {
|
||||
ws.PrintOptions = new(xlsxPrintOptions)
|
||||
}
|
||||
}
|
||||
s := reflect.ValueOf(opts).Elem()
|
||||
for i := 0; i < 6; i++ {
|
||||
if !s.Field(i).IsNil() {
|
||||
preparePageMargins(ws)
|
||||
name := s.Type().Field(i).Name
|
||||
reflect.ValueOf(ws.PageMargins).Elem().FieldByName(name).Set(s.Field(i).Elem())
|
||||
}
|
||||
}
|
||||
if opts.Horizontally != nil {
|
||||
preparePrintOptions(ws)
|
||||
ws.PrintOptions.HorizontalCentered = *opts.Horizontally
|
||||
}
|
||||
if opts.Vertically != nil {
|
||||
preparePrintOptions(ws)
|
||||
ws.PrintOptions.VerticalCentered = *opts.Vertically
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// GetPageMargins provides a function to get worksheet page margins.
|
||||
func (f *File) GetPageMargins(sheet string) (PageLayoutMarginsOptions, error) {
|
||||
opts := PageLayoutMarginsOptions{
|
||||
Bottom: float64Ptr(0.75),
|
||||
Footer: float64Ptr(0.3),
|
||||
Header: float64Ptr(0.3),
|
||||
Left: float64Ptr(0.7),
|
||||
Right: float64Ptr(0.7),
|
||||
Top: float64Ptr(0.75),
|
||||
}
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
if ws.PageMargins != nil {
|
||||
opts.Bottom = float64Ptr(ws.PageMargins.Bottom)
|
||||
opts.Footer = float64Ptr(ws.PageMargins.Footer)
|
||||
opts.Header = float64Ptr(ws.PageMargins.Header)
|
||||
opts.Left = float64Ptr(ws.PageMargins.Left)
|
||||
opts.Right = float64Ptr(ws.PageMargins.Right)
|
||||
opts.Top = float64Ptr(ws.PageMargins.Top)
|
||||
}
|
||||
if ws.PrintOptions != nil {
|
||||
opts.Horizontally = boolPtr(ws.PrintOptions.HorizontalCentered)
|
||||
opts.Vertically = boolPtr(ws.PrintOptions.VerticalCentered)
|
||||
}
|
||||
return opts, err
|
||||
}
|
||||
|
||||
// prepareSheetPr create sheetPr element which not exist.
|
||||
func (ws *xlsxWorksheet) prepareSheetPr() {
|
||||
if ws.SheetPr == nil {
|
||||
ws.SheetPr = new(xlsxSheetPr)
|
||||
}
|
||||
}
|
||||
|
||||
// setSheetOutlinePr set worksheet outline properties by given options.
|
||||
func (ws *xlsxWorksheet) setSheetOutlineProps(opts *SheetPropsOptions) {
|
||||
prepareOutlinePr := func(ws *xlsxWorksheet) {
|
||||
ws.prepareSheetPr()
|
||||
if ws.SheetPr.OutlinePr == nil {
|
||||
ws.SheetPr.OutlinePr = new(xlsxOutlinePr)
|
||||
}
|
||||
}
|
||||
if opts.OutlineSummaryBelow != nil {
|
||||
prepareOutlinePr(ws)
|
||||
ws.SheetPr.OutlinePr.SummaryBelow = opts.OutlineSummaryBelow
|
||||
}
|
||||
if opts.OutlineSummaryRight != nil {
|
||||
prepareOutlinePr(ws)
|
||||
ws.SheetPr.OutlinePr.SummaryRight = opts.OutlineSummaryRight
|
||||
}
|
||||
}
|
||||
|
||||
// setSheetProps set worksheet format properties by given options.
|
||||
func (ws *xlsxWorksheet) setSheetProps(opts *SheetPropsOptions) {
|
||||
preparePageSetUpPr := func(ws *xlsxWorksheet) {
|
||||
ws.prepareSheetPr()
|
||||
if ws.SheetPr.PageSetUpPr == nil {
|
||||
ws.SheetPr.PageSetUpPr = new(xlsxPageSetUpPr)
|
||||
}
|
||||
}
|
||||
prepareTabColor := func(ws *xlsxWorksheet) {
|
||||
ws.prepareSheetPr()
|
||||
if ws.SheetPr.TabColor == nil {
|
||||
ws.SheetPr.TabColor = new(xlsxTabColor)
|
||||
}
|
||||
}
|
||||
if opts.CodeName != nil {
|
||||
ws.prepareSheetPr()
|
||||
ws.SheetPr.CodeName = *opts.CodeName
|
||||
}
|
||||
if opts.EnableFormatConditionsCalculation != nil {
|
||||
ws.prepareSheetPr()
|
||||
ws.SheetPr.EnableFormatConditionsCalculation = opts.EnableFormatConditionsCalculation
|
||||
}
|
||||
if opts.Published != nil {
|
||||
ws.prepareSheetPr()
|
||||
ws.SheetPr.Published = opts.Published
|
||||
}
|
||||
if opts.AutoPageBreaks != nil {
|
||||
preparePageSetUpPr(ws)
|
||||
ws.SheetPr.PageSetUpPr.AutoPageBreaks = *opts.AutoPageBreaks
|
||||
}
|
||||
if opts.FitToPage != nil {
|
||||
preparePageSetUpPr(ws)
|
||||
ws.SheetPr.PageSetUpPr.FitToPage = *opts.FitToPage
|
||||
}
|
||||
ws.setSheetOutlineProps(opts)
|
||||
s := reflect.ValueOf(opts).Elem()
|
||||
for i := 5; i < 9; i++ {
|
||||
if !s.Field(i).IsNil() {
|
||||
prepareTabColor(ws)
|
||||
name := s.Type().Field(i).Name
|
||||
reflect.ValueOf(ws.SheetPr.TabColor).Elem().FieldByName(name[8:]).Set(s.Field(i).Elem())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetSheetProps provides a function to set worksheet properties.
|
||||
func (f *File) SetSheetProps(sheet string, opts *SheetPropsOptions) error {
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if opts == nil {
|
||||
return err
|
||||
}
|
||||
ws.setSheetProps(opts)
|
||||
if ws.SheetFormatPr == nil {
|
||||
ws.SheetFormatPr = &xlsxSheetFormatPr{DefaultRowHeight: defaultRowHeight}
|
||||
}
|
||||
s := reflect.ValueOf(opts).Elem()
|
||||
for i := 11; i < 18; i++ {
|
||||
if !s.Field(i).IsNil() {
|
||||
name := s.Type().Field(i).Name
|
||||
reflect.ValueOf(ws.SheetFormatPr).Elem().FieldByName(name).Set(s.Field(i).Elem())
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// GetSheetProps provides a function to get worksheet properties.
|
||||
func (f *File) GetSheetProps(sheet string) (SheetPropsOptions, error) {
|
||||
baseColWidth := uint8(8)
|
||||
opts := SheetPropsOptions{
|
||||
EnableFormatConditionsCalculation: boolPtr(true),
|
||||
Published: boolPtr(true),
|
||||
AutoPageBreaks: boolPtr(true),
|
||||
OutlineSummaryBelow: boolPtr(true),
|
||||
BaseColWidth: &baseColWidth,
|
||||
}
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
if ws.SheetPr != nil {
|
||||
opts.CodeName = stringPtr(ws.SheetPr.CodeName)
|
||||
if ws.SheetPr.EnableFormatConditionsCalculation != nil {
|
||||
opts.EnableFormatConditionsCalculation = ws.SheetPr.EnableFormatConditionsCalculation
|
||||
}
|
||||
if ws.SheetPr.Published != nil {
|
||||
opts.Published = ws.SheetPr.Published
|
||||
}
|
||||
if ws.SheetPr.PageSetUpPr != nil {
|
||||
opts.AutoPageBreaks = boolPtr(ws.SheetPr.PageSetUpPr.AutoPageBreaks)
|
||||
opts.FitToPage = boolPtr(ws.SheetPr.PageSetUpPr.FitToPage)
|
||||
}
|
||||
if ws.SheetPr.OutlinePr != nil {
|
||||
opts.OutlineSummaryBelow = ws.SheetPr.OutlinePr.SummaryBelow
|
||||
opts.OutlineSummaryRight = ws.SheetPr.OutlinePr.SummaryRight
|
||||
}
|
||||
if ws.SheetPr.TabColor != nil {
|
||||
opts.TabColorIndexed = intPtr(ws.SheetPr.TabColor.Indexed)
|
||||
opts.TabColorRGB = stringPtr(ws.SheetPr.TabColor.RGB)
|
||||
opts.TabColorTheme = intPtr(ws.SheetPr.TabColor.Theme)
|
||||
opts.TabColorTint = float64Ptr(ws.SheetPr.TabColor.Tint)
|
||||
}
|
||||
}
|
||||
if ws.SheetFormatPr != nil {
|
||||
opts.BaseColWidth = &ws.SheetFormatPr.BaseColWidth
|
||||
opts.DefaultColWidth = float64Ptr(ws.SheetFormatPr.DefaultColWidth)
|
||||
opts.DefaultRowHeight = float64Ptr(ws.SheetFormatPr.DefaultRowHeight)
|
||||
opts.CustomHeight = boolPtr(ws.SheetFormatPr.CustomHeight)
|
||||
opts.ZeroHeight = boolPtr(ws.SheetFormatPr.ZeroHeight)
|
||||
opts.ThickTop = boolPtr(ws.SheetFormatPr.ThickTop)
|
||||
opts.ThickBottom = boolPtr(ws.SheetFormatPr.ThickBottom)
|
||||
}
|
||||
return opts, err
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
// getSheetView returns the SheetView object
|
||||
func (f *File) getSheetView(sheet string, viewIndex int) (*xlsxSheetView, error) {
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ws.SheetViews == nil {
|
||||
ws.SheetViews = &xlsxSheetViews{
|
||||
SheetView: []xlsxSheetView{{WorkbookViewID: 0}},
|
||||
}
|
||||
}
|
||||
if viewIndex < 0 {
|
||||
if viewIndex < -len(ws.SheetViews.SheetView) {
|
||||
return nil, newViewIdxError(viewIndex)
|
||||
}
|
||||
viewIndex = len(ws.SheetViews.SheetView) + viewIndex
|
||||
} else if viewIndex >= len(ws.SheetViews.SheetView) {
|
||||
return nil, newViewIdxError(viewIndex)
|
||||
}
|
||||
|
||||
return &(ws.SheetViews.SheetView[viewIndex]), err
|
||||
}
|
||||
|
||||
// setSheetView set sheet view by given options.
|
||||
func (view *xlsxSheetView) setSheetView(opts *ViewOptions) {
|
||||
if opts.DefaultGridColor != nil {
|
||||
view.DefaultGridColor = opts.DefaultGridColor
|
||||
}
|
||||
if opts.RightToLeft != nil {
|
||||
view.RightToLeft = *opts.RightToLeft
|
||||
}
|
||||
if opts.ShowFormulas != nil {
|
||||
view.ShowFormulas = *opts.ShowFormulas
|
||||
}
|
||||
if opts.ShowGridLines != nil {
|
||||
view.ShowGridLines = opts.ShowGridLines
|
||||
}
|
||||
if opts.ShowRowColHeaders != nil {
|
||||
view.ShowRowColHeaders = opts.ShowRowColHeaders
|
||||
}
|
||||
if opts.ShowRuler != nil {
|
||||
view.ShowRuler = opts.ShowRuler
|
||||
}
|
||||
if opts.ShowZeros != nil {
|
||||
view.ShowZeros = opts.ShowZeros
|
||||
}
|
||||
if opts.TopLeftCell != nil {
|
||||
view.TopLeftCell = *opts.TopLeftCell
|
||||
}
|
||||
if opts.View != nil {
|
||||
if _, ok := map[string]interface{}{
|
||||
"normal": nil,
|
||||
"pageLayout": nil,
|
||||
"pageBreakPreview": nil,
|
||||
}[*opts.View]; ok {
|
||||
view.View = *opts.View
|
||||
}
|
||||
}
|
||||
if opts.ZoomScale != nil && *opts.ZoomScale >= 10 && *opts.ZoomScale <= 400 {
|
||||
view.ZoomScale = *opts.ZoomScale
|
||||
}
|
||||
}
|
||||
|
||||
// SetSheetView sets sheet view options. The viewIndex may be negative and if
|
||||
// so is counted backward (-1 is the last view).
|
||||
func (f *File) SetSheetView(sheet string, viewIndex int, opts *ViewOptions) error {
|
||||
view, err := f.getSheetView(sheet, viewIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if opts == nil {
|
||||
return err
|
||||
}
|
||||
view.setSheetView(opts)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSheetView gets the value of sheet view options. The viewIndex may be
|
||||
// negative and if so is counted backward (-1 is the last view).
|
||||
func (f *File) GetSheetView(sheet string, viewIndex int) (ViewOptions, error) {
|
||||
opts := ViewOptions{
|
||||
DefaultGridColor: boolPtr(true),
|
||||
ShowFormulas: boolPtr(true),
|
||||
ShowGridLines: boolPtr(true),
|
||||
ShowRowColHeaders: boolPtr(true),
|
||||
ShowRuler: boolPtr(true),
|
||||
ShowZeros: boolPtr(true),
|
||||
View: stringPtr("normal"),
|
||||
ZoomScale: float64Ptr(100),
|
||||
}
|
||||
view, err := f.getSheetView(sheet, viewIndex)
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
if view.DefaultGridColor != nil {
|
||||
opts.DefaultGridColor = view.DefaultGridColor
|
||||
}
|
||||
opts.RightToLeft = boolPtr(view.RightToLeft)
|
||||
opts.ShowFormulas = boolPtr(view.ShowFormulas)
|
||||
if view.ShowGridLines != nil {
|
||||
opts.ShowGridLines = view.ShowGridLines
|
||||
}
|
||||
if view.ShowRowColHeaders != nil {
|
||||
opts.ShowRowColHeaders = view.ShowRowColHeaders
|
||||
}
|
||||
if view.ShowRuler != nil {
|
||||
opts.ShowRuler = view.ShowRuler
|
||||
}
|
||||
if view.ShowZeros != nil {
|
||||
opts.ShowZeros = view.ShowZeros
|
||||
}
|
||||
opts.TopLeftCell = stringPtr(view.TopLeftCell)
|
||||
if view.View != "" {
|
||||
opts.View = stringPtr(view.View)
|
||||
}
|
||||
if view.ZoomScale >= 10 && view.ZoomScale <= 400 {
|
||||
opts.ZoomScale = float64Ptr(view.ZoomScale)
|
||||
}
|
||||
return opts, err
|
||||
}
|
||||
+537
@@ -0,0 +1,537 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// addSparklineGroupByStyle provides a function to create x14:sparklineGroups
|
||||
// element by given sparkline style ID.
|
||||
func (f *File) addSparklineGroupByStyle(ID int) *xlsxX14SparklineGroup {
|
||||
groups := []*xlsxX14SparklineGroup{
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{Theme: 4, Tint: -0.499984740745262},
|
||||
ColorNegative: &xlsxTabColor{Theme: 5},
|
||||
ColorMarkers: &xlsxTabColor{Theme: 4, Tint: -0.499984740745262},
|
||||
ColorFirst: &xlsxTabColor{Theme: 4, Tint: 0.39997558519241921},
|
||||
ColorLast: &xlsxTabColor{Theme: 4, Tint: 0.39997558519241921},
|
||||
ColorHigh: &xlsxTabColor{Theme: 4},
|
||||
ColorLow: &xlsxTabColor{Theme: 4},
|
||||
}, // 0
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{Theme: 4, Tint: -0.499984740745262},
|
||||
ColorNegative: &xlsxTabColor{Theme: 5},
|
||||
ColorMarkers: &xlsxTabColor{Theme: 4, Tint: -0.499984740745262},
|
||||
ColorFirst: &xlsxTabColor{Theme: 4, Tint: 0.39997558519241921},
|
||||
ColorLast: &xlsxTabColor{Theme: 4, Tint: 0.39997558519241921},
|
||||
ColorHigh: &xlsxTabColor{Theme: 4},
|
||||
ColorLow: &xlsxTabColor{Theme: 4},
|
||||
}, // 1
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{Theme: 5, Tint: -0.499984740745262},
|
||||
ColorNegative: &xlsxTabColor{Theme: 6},
|
||||
ColorMarkers: &xlsxTabColor{Theme: 5, Tint: -0.499984740745262},
|
||||
ColorFirst: &xlsxTabColor{Theme: 5, Tint: 0.39997558519241921},
|
||||
ColorLast: &xlsxTabColor{Theme: 5, Tint: 0.39997558519241921},
|
||||
ColorHigh: &xlsxTabColor{Theme: 5},
|
||||
ColorLow: &xlsxTabColor{Theme: 5},
|
||||
}, // 2
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{Theme: 6, Tint: -0.499984740745262},
|
||||
ColorNegative: &xlsxTabColor{Theme: 7},
|
||||
ColorMarkers: &xlsxTabColor{Theme: 6, Tint: -0.499984740745262},
|
||||
ColorFirst: &xlsxTabColor{Theme: 6, Tint: 0.39997558519241921},
|
||||
ColorLast: &xlsxTabColor{Theme: 6, Tint: 0.39997558519241921},
|
||||
ColorHigh: &xlsxTabColor{Theme: 6},
|
||||
ColorLow: &xlsxTabColor{Theme: 6},
|
||||
}, // 3
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{Theme: 7, Tint: -0.499984740745262},
|
||||
ColorNegative: &xlsxTabColor{Theme: 8},
|
||||
ColorMarkers: &xlsxTabColor{Theme: 7, Tint: -0.499984740745262},
|
||||
ColorFirst: &xlsxTabColor{Theme: 7, Tint: 0.39997558519241921},
|
||||
ColorLast: &xlsxTabColor{Theme: 7, Tint: 0.39997558519241921},
|
||||
ColorHigh: &xlsxTabColor{Theme: 7},
|
||||
ColorLow: &xlsxTabColor{Theme: 7},
|
||||
}, // 4
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{Theme: 8, Tint: -0.499984740745262},
|
||||
ColorNegative: &xlsxTabColor{Theme: 9},
|
||||
ColorMarkers: &xlsxTabColor{Theme: 8, Tint: -0.499984740745262},
|
||||
ColorFirst: &xlsxTabColor{Theme: 8, Tint: 0.39997558519241921},
|
||||
ColorLast: &xlsxTabColor{Theme: 8, Tint: 0.39997558519241921},
|
||||
ColorHigh: &xlsxTabColor{Theme: 8},
|
||||
ColorLow: &xlsxTabColor{Theme: 8},
|
||||
}, // 5
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{Theme: 9, Tint: -0.499984740745262},
|
||||
ColorNegative: &xlsxTabColor{Theme: 4},
|
||||
ColorMarkers: &xlsxTabColor{Theme: 9, Tint: -0.499984740745262},
|
||||
ColorFirst: &xlsxTabColor{Theme: 9, Tint: 0.39997558519241921},
|
||||
ColorLast: &xlsxTabColor{Theme: 9, Tint: 0.39997558519241921},
|
||||
ColorHigh: &xlsxTabColor{Theme: 9},
|
||||
ColorLow: &xlsxTabColor{Theme: 9},
|
||||
}, // 6
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893},
|
||||
ColorNegative: &xlsxTabColor{Theme: 5},
|
||||
ColorMarkers: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxTabColor{Theme: 5},
|
||||
ColorLow: &xlsxTabColor{Theme: 5},
|
||||
}, // 7
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893},
|
||||
ColorNegative: &xlsxTabColor{Theme: 6},
|
||||
ColorMarkers: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893},
|
||||
}, // 8
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893},
|
||||
ColorNegative: &xlsxTabColor{Theme: 7},
|
||||
ColorMarkers: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893},
|
||||
}, // 9
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893},
|
||||
ColorNegative: &xlsxTabColor{Theme: 8},
|
||||
ColorMarkers: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893},
|
||||
}, // 10
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893},
|
||||
ColorNegative: &xlsxTabColor{Theme: 9},
|
||||
ColorMarkers: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893},
|
||||
}, // 11
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893},
|
||||
ColorNegative: &xlsxTabColor{Theme: 4},
|
||||
ColorMarkers: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893},
|
||||
}, // 12
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{Theme: 4},
|
||||
ColorNegative: &xlsxTabColor{Theme: 5},
|
||||
ColorMarkers: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893},
|
||||
}, // 13
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{Theme: 5},
|
||||
ColorNegative: &xlsxTabColor{Theme: 6},
|
||||
ColorMarkers: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893},
|
||||
}, // 14
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{Theme: 6},
|
||||
ColorNegative: &xlsxTabColor{Theme: 7},
|
||||
ColorMarkers: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893},
|
||||
}, // 15
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{Theme: 7},
|
||||
ColorNegative: &xlsxTabColor{Theme: 8},
|
||||
ColorMarkers: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893},
|
||||
}, // 16
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{Theme: 8},
|
||||
ColorNegative: &xlsxTabColor{Theme: 9},
|
||||
ColorMarkers: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893},
|
||||
}, // 17
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{Theme: 9},
|
||||
ColorNegative: &xlsxTabColor{Theme: 4},
|
||||
ColorMarkers: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893},
|
||||
}, // 18
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{Theme: 4, Tint: 0.39997558519241921},
|
||||
ColorNegative: &xlsxTabColor{Theme: 0, Tint: -0.499984740745262},
|
||||
ColorMarkers: &xlsxTabColor{Theme: 4, Tint: 0.79998168889431442},
|
||||
ColorFirst: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxTabColor{Theme: 4, Tint: -0.499984740745262},
|
||||
ColorLow: &xlsxTabColor{Theme: 4, Tint: -0.499984740745262},
|
||||
}, // 19
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{Theme: 5, Tint: 0.39997558519241921},
|
||||
ColorNegative: &xlsxTabColor{Theme: 0, Tint: -0.499984740745262},
|
||||
ColorMarkers: &xlsxTabColor{Theme: 5, Tint: 0.79998168889431442},
|
||||
ColorFirst: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxTabColor{Theme: 5, Tint: -0.499984740745262},
|
||||
ColorLow: &xlsxTabColor{Theme: 5, Tint: -0.499984740745262},
|
||||
}, // 20
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{Theme: 6, Tint: 0.39997558519241921},
|
||||
ColorNegative: &xlsxTabColor{Theme: 0, Tint: -0.499984740745262},
|
||||
ColorMarkers: &xlsxTabColor{Theme: 6, Tint: 0.79998168889431442},
|
||||
ColorFirst: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxTabColor{Theme: 6, Tint: -0.499984740745262},
|
||||
ColorLow: &xlsxTabColor{Theme: 6, Tint: -0.499984740745262},
|
||||
}, // 21
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{Theme: 7, Tint: 0.39997558519241921},
|
||||
ColorNegative: &xlsxTabColor{Theme: 0, Tint: -0.499984740745262},
|
||||
ColorMarkers: &xlsxTabColor{Theme: 7, Tint: 0.79998168889431442},
|
||||
ColorFirst: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxTabColor{Theme: 7, Tint: -0.499984740745262},
|
||||
ColorLow: &xlsxTabColor{Theme: 7, Tint: -0.499984740745262},
|
||||
}, // 22
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{Theme: 8, Tint: 0.39997558519241921},
|
||||
ColorNegative: &xlsxTabColor{Theme: 0, Tint: -0.499984740745262},
|
||||
ColorMarkers: &xlsxTabColor{Theme: 8, Tint: 0.79998168889431442},
|
||||
ColorFirst: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxTabColor{Theme: 8, Tint: -0.499984740745262},
|
||||
ColorLow: &xlsxTabColor{Theme: 8, Tint: -0.499984740745262},
|
||||
}, // 23
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{Theme: 9, Tint: 0.39997558519241921},
|
||||
ColorNegative: &xlsxTabColor{Theme: 0, Tint: -0.499984740745262},
|
||||
ColorMarkers: &xlsxTabColor{Theme: 9, Tint: 0.79998168889431442},
|
||||
ColorFirst: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxTabColor{Theme: 9, Tint: -0.499984740745262},
|
||||
ColorLow: &xlsxTabColor{Theme: 9, Tint: -0.499984740745262},
|
||||
}, // 24
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{Theme: 1, Tint: 0.499984740745262},
|
||||
ColorNegative: &xlsxTabColor{Theme: 1, Tint: 0.249977111117893},
|
||||
ColorMarkers: &xlsxTabColor{Theme: 1, Tint: 0.249977111117893},
|
||||
ColorFirst: &xlsxTabColor{Theme: 1, Tint: 0.249977111117893},
|
||||
ColorLast: &xlsxTabColor{Theme: 1, Tint: 0.249977111117893},
|
||||
ColorHigh: &xlsxTabColor{Theme: 1, Tint: 0.249977111117893},
|
||||
ColorLow: &xlsxTabColor{Theme: 1, Tint: 0.249977111117893},
|
||||
}, // 25
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{Theme: 1, Tint: 0.34998626667073579},
|
||||
ColorNegative: &xlsxTabColor{Theme: 0, Tint: 0.249977111117893},
|
||||
ColorMarkers: &xlsxTabColor{Theme: 0, Tint: 0.249977111117893},
|
||||
ColorFirst: &xlsxTabColor{Theme: 0, Tint: 0.249977111117893},
|
||||
ColorLast: &xlsxTabColor{Theme: 0, Tint: 0.249977111117893},
|
||||
ColorHigh: &xlsxTabColor{Theme: 0, Tint: 0.249977111117893},
|
||||
ColorLow: &xlsxTabColor{Theme: 0, Tint: 0.249977111117893},
|
||||
}, // 26
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{RGB: "FF323232"},
|
||||
ColorNegative: &xlsxTabColor{RGB: "FFD00000"},
|
||||
ColorMarkers: &xlsxTabColor{RGB: "FFD00000"},
|
||||
ColorFirst: &xlsxTabColor{RGB: "FFD00000"},
|
||||
ColorLast: &xlsxTabColor{RGB: "FFD00000"},
|
||||
ColorHigh: &xlsxTabColor{RGB: "FFD00000"},
|
||||
ColorLow: &xlsxTabColor{RGB: "FFD00000"},
|
||||
}, // 27
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{RGB: "FF000000"},
|
||||
ColorNegative: &xlsxTabColor{RGB: "FF0070C0"},
|
||||
ColorMarkers: &xlsxTabColor{RGB: "FF0070C0"},
|
||||
ColorFirst: &xlsxTabColor{RGB: "FF0070C0"},
|
||||
ColorLast: &xlsxTabColor{RGB: "FF0070C0"},
|
||||
ColorHigh: &xlsxTabColor{RGB: "FF0070C0"},
|
||||
ColorLow: &xlsxTabColor{RGB: "FF0070C0"},
|
||||
}, // 28
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{RGB: "FF376092"},
|
||||
ColorNegative: &xlsxTabColor{RGB: "FFD00000"},
|
||||
ColorMarkers: &xlsxTabColor{RGB: "FFD00000"},
|
||||
ColorFirst: &xlsxTabColor{RGB: "FFD00000"},
|
||||
ColorLast: &xlsxTabColor{RGB: "FFD00000"},
|
||||
ColorHigh: &xlsxTabColor{RGB: "FFD00000"},
|
||||
ColorLow: &xlsxTabColor{RGB: "FFD00000"},
|
||||
}, // 29
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{RGB: "FF0070C0"},
|
||||
ColorNegative: &xlsxTabColor{RGB: "FF000000"},
|
||||
ColorMarkers: &xlsxTabColor{RGB: "FF000000"},
|
||||
ColorFirst: &xlsxTabColor{RGB: "FF000000"},
|
||||
ColorLast: &xlsxTabColor{RGB: "FF000000"},
|
||||
ColorHigh: &xlsxTabColor{RGB: "FF000000"},
|
||||
ColorLow: &xlsxTabColor{RGB: "FF000000"},
|
||||
}, // 30
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{RGB: "FF5F5F5F"},
|
||||
ColorNegative: &xlsxTabColor{RGB: "FFFFB620"},
|
||||
ColorMarkers: &xlsxTabColor{RGB: "FFD70077"},
|
||||
ColorFirst: &xlsxTabColor{RGB: "FF5687C2"},
|
||||
ColorLast: &xlsxTabColor{RGB: "FF359CEB"},
|
||||
ColorHigh: &xlsxTabColor{RGB: "FF56BE79"},
|
||||
ColorLow: &xlsxTabColor{RGB: "FFFF5055"},
|
||||
}, // 31
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{RGB: "FF5687C2"},
|
||||
ColorNegative: &xlsxTabColor{RGB: "FFFFB620"},
|
||||
ColorMarkers: &xlsxTabColor{RGB: "FFD70077"},
|
||||
ColorFirst: &xlsxTabColor{RGB: "FF777777"},
|
||||
ColorLast: &xlsxTabColor{RGB: "FF359CEB"},
|
||||
ColorHigh: &xlsxTabColor{RGB: "FF56BE79"},
|
||||
ColorLow: &xlsxTabColor{RGB: "FFFF5055"},
|
||||
}, // 32
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{RGB: "FFC6EFCE"},
|
||||
ColorNegative: &xlsxTabColor{RGB: "FFFFC7CE"},
|
||||
ColorMarkers: &xlsxTabColor{RGB: "FF8CADD6"},
|
||||
ColorFirst: &xlsxTabColor{RGB: "FFFFDC47"},
|
||||
ColorLast: &xlsxTabColor{RGB: "FFFFEB9C"},
|
||||
ColorHigh: &xlsxTabColor{RGB: "FF60D276"},
|
||||
ColorLow: &xlsxTabColor{RGB: "FFFF5367"},
|
||||
}, // 33
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{RGB: "FF00B050"},
|
||||
ColorNegative: &xlsxTabColor{RGB: "FFFF0000"},
|
||||
ColorMarkers: &xlsxTabColor{RGB: "FF0070C0"},
|
||||
ColorFirst: &xlsxTabColor{RGB: "FFFFC000"},
|
||||
ColorLast: &xlsxTabColor{RGB: "FFFFC000"},
|
||||
ColorHigh: &xlsxTabColor{RGB: "FF00B050"},
|
||||
ColorLow: &xlsxTabColor{RGB: "FFFF0000"},
|
||||
}, // 34
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{Theme: 3},
|
||||
ColorNegative: &xlsxTabColor{Theme: 9},
|
||||
ColorMarkers: &xlsxTabColor{Theme: 8},
|
||||
ColorFirst: &xlsxTabColor{Theme: 4},
|
||||
ColorLast: &xlsxTabColor{Theme: 5},
|
||||
ColorHigh: &xlsxTabColor{Theme: 6},
|
||||
ColorLow: &xlsxTabColor{Theme: 7},
|
||||
}, // 35
|
||||
{
|
||||
ColorSeries: &xlsxTabColor{Theme: 1},
|
||||
ColorNegative: &xlsxTabColor{Theme: 9},
|
||||
ColorMarkers: &xlsxTabColor{Theme: 8},
|
||||
ColorFirst: &xlsxTabColor{Theme: 4},
|
||||
ColorLast: &xlsxTabColor{Theme: 5},
|
||||
ColorHigh: &xlsxTabColor{Theme: 6},
|
||||
ColorLow: &xlsxTabColor{Theme: 7},
|
||||
}, // 36
|
||||
}
|
||||
return groups[ID]
|
||||
}
|
||||
|
||||
// AddSparkline provides a function to add sparklines to the worksheet by
|
||||
// given formatting options. Sparklines are small charts that fit in a single
|
||||
// cell and are used to show trends in data. Sparklines are a feature of Excel
|
||||
// 2010 and later only. You can write them to an XLSX file that can be read by
|
||||
// Excel 2007, but they won't be displayed. For example, add a grouped
|
||||
// sparkline. Changes are applied to all three:
|
||||
//
|
||||
// err := f.AddSparkline("Sheet1", &excelize.SparklineOptions{
|
||||
// Location: []string{"A1", "A2", "A3"},
|
||||
// Range: []string{"Sheet2!A1:J1", "Sheet2!A2:J2", "Sheet2!A3:J3"},
|
||||
// Markers: true,
|
||||
// })
|
||||
//
|
||||
// The following shows the formatting options of sparkline supported by excelize:
|
||||
//
|
||||
// Parameter | Description
|
||||
// -------------+--------------------------------------------
|
||||
// Location | Required, must have the same number with 'Range' parameter
|
||||
// Range | Required, must have the same number with 'Location' parameter
|
||||
// Type | Enumeration value: line, column, win_loss
|
||||
// Style | Value range: 0 - 35
|
||||
// Hight | Toggle sparkline high points
|
||||
// Low | Toggle sparkline low points
|
||||
// First | Toggle sparkline first points
|
||||
// Last | Toggle sparkline last points
|
||||
// Negative | Toggle sparkline negative points
|
||||
// Markers | Toggle sparkline markers
|
||||
// Axis | Used to specify if show horizontal axis
|
||||
// Reverse | Used to specify if enable plot data right-to-left
|
||||
// SeriesColor | An RGB Color is specified as RRGGBB
|
||||
func (f *File) AddSparkline(sheet string, opts *SparklineOptions) error {
|
||||
var (
|
||||
err error
|
||||
ws *xlsxWorksheet
|
||||
sparkType string
|
||||
sparkTypes map[string]string
|
||||
specifiedSparkTypes string
|
||||
ok bool
|
||||
group *xlsxX14SparklineGroup
|
||||
groups *xlsxX14SparklineGroups
|
||||
)
|
||||
|
||||
// parameter validation
|
||||
if ws, err = f.parseFormatAddSparklineSet(sheet, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
// Handle the sparkline type
|
||||
sparkType = "line"
|
||||
sparkTypes = map[string]string{"line": "line", "column": "column", "win_loss": "stacked"}
|
||||
if opts.Type != "" {
|
||||
if specifiedSparkTypes, ok = sparkTypes[opts.Type]; !ok {
|
||||
err = ErrSparklineType
|
||||
return err
|
||||
}
|
||||
sparkType = specifiedSparkTypes
|
||||
}
|
||||
group = f.addSparklineGroupByStyle(opts.Style)
|
||||
group.Type = sparkType
|
||||
group.ColorAxis = &xlsxColor{RGB: "FF000000"}
|
||||
group.DisplayEmptyCellsAs = "gap"
|
||||
group.High = opts.High
|
||||
group.Low = opts.Low
|
||||
group.First = opts.First
|
||||
group.Last = opts.Last
|
||||
group.Negative = opts.Negative
|
||||
group.DisplayXAxis = opts.Axis
|
||||
group.Markers = opts.Markers
|
||||
if opts.SeriesColor != "" {
|
||||
group.ColorSeries = &xlsxTabColor{
|
||||
RGB: getPaletteColor(opts.SeriesColor),
|
||||
}
|
||||
}
|
||||
if opts.Reverse {
|
||||
group.RightToLeft = opts.Reverse
|
||||
}
|
||||
f.addSparkline(opts, group)
|
||||
if err = f.appendSparkline(ws, group, groups); err != nil {
|
||||
return err
|
||||
}
|
||||
f.addSheetNameSpace(sheet, NameSpaceSpreadSheetX14)
|
||||
return err
|
||||
}
|
||||
|
||||
// parseFormatAddSparklineSet provides a function to validate sparkline
|
||||
// properties.
|
||||
func (f *File) parseFormatAddSparklineSet(sheet string, opts *SparklineOptions) (*xlsxWorksheet, error) {
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return ws, err
|
||||
}
|
||||
if opts == nil {
|
||||
return ws, ErrParameterRequired
|
||||
}
|
||||
if len(opts.Location) < 1 {
|
||||
return ws, ErrSparklineLocation
|
||||
}
|
||||
if len(opts.Range) < 1 {
|
||||
return ws, ErrSparklineRange
|
||||
}
|
||||
// The range and locations must match
|
||||
if len(opts.Location) != len(opts.Range) {
|
||||
return ws, ErrSparkline
|
||||
}
|
||||
if opts.Style < 0 || opts.Style > 35 {
|
||||
return ws, ErrSparklineStyle
|
||||
}
|
||||
if ws.ExtLst == nil {
|
||||
ws.ExtLst = &xlsxExtLst{}
|
||||
}
|
||||
return ws, err
|
||||
}
|
||||
|
||||
// addSparkline provides a function to create a sparkline in a sparkline group
|
||||
// by given properties.
|
||||
func (f *File) addSparkline(opts *SparklineOptions, group *xlsxX14SparklineGroup) {
|
||||
for idx, location := range opts.Location {
|
||||
group.Sparklines.Sparkline = append(group.Sparklines.Sparkline, &xlsxX14Sparkline{
|
||||
F: opts.Range[idx],
|
||||
Sqref: location,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// appendSparkline provides a function to append sparkline to sparkline
|
||||
// groups.
|
||||
func (f *File) appendSparkline(ws *xlsxWorksheet, group *xlsxX14SparklineGroup, groups *xlsxX14SparklineGroups) error {
|
||||
var (
|
||||
err error
|
||||
idx int
|
||||
appendMode bool
|
||||
decodeExtLst = new(decodeWorksheetExt)
|
||||
decodeSparklineGroups *decodeX14SparklineGroups
|
||||
ext *xlsxWorksheetExt
|
||||
sparklineGroupsBytes, sparklineGroupBytes, extLstBytes []byte
|
||||
)
|
||||
sparklineGroupBytes, _ = xml.Marshal(group)
|
||||
if ws.ExtLst != nil { // append mode ext
|
||||
if err = f.xmlNewDecoder(strings.NewReader("<extLst>" + ws.ExtLst.Ext + "</extLst>")).
|
||||
Decode(decodeExtLst); err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
for idx, ext = range decodeExtLst.Ext {
|
||||
if ext.URI == ExtURISparklineGroups {
|
||||
decodeSparklineGroups = new(decodeX14SparklineGroups)
|
||||
if err = f.xmlNewDecoder(strings.NewReader(ext.Content)).
|
||||
Decode(decodeSparklineGroups); err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
if groups == nil {
|
||||
groups = &xlsxX14SparklineGroups{}
|
||||
}
|
||||
groups.XMLNSXM = NameSpaceSpreadSheetExcel2006Main.Value
|
||||
groups.Content = decodeSparklineGroups.Content + string(sparklineGroupBytes)
|
||||
sparklineGroupsBytes, _ = xml.Marshal(groups)
|
||||
decodeExtLst.Ext[idx].Content = string(sparklineGroupsBytes)
|
||||
appendMode = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !appendMode {
|
||||
sparklineGroupsBytes, _ = xml.Marshal(&xlsxX14SparklineGroups{
|
||||
XMLNSXM: NameSpaceSpreadSheetExcel2006Main.Value,
|
||||
SparklineGroups: []*xlsxX14SparklineGroup{group},
|
||||
})
|
||||
decodeExtLst.Ext = append(decodeExtLst.Ext, &xlsxWorksheetExt{
|
||||
URI: ExtURISparklineGroups, Content: string(sparklineGroupsBytes),
|
||||
})
|
||||
}
|
||||
sort.Slice(decodeExtLst.Ext, func(i, j int) bool {
|
||||
return inStrSlice(extensionURIPriority, decodeExtLst.Ext[i].URI, false) <
|
||||
inStrSlice(extensionURIPriority, decodeExtLst.Ext[j].URI, false)
|
||||
})
|
||||
extLstBytes, err = xml.Marshal(decodeExtLst)
|
||||
ws.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), "<extLst>"), "</extLst>")}
|
||||
return err
|
||||
}
|
||||
+771
@@ -0,0 +1,771 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// StreamWriter defined the type of stream writer.
|
||||
type StreamWriter struct {
|
||||
file *File
|
||||
Sheet string
|
||||
SheetID int
|
||||
sheetWritten bool
|
||||
cols strings.Builder
|
||||
worksheet *xlsxWorksheet
|
||||
rawData bufferedWriter
|
||||
rows int
|
||||
mergeCellsCount int
|
||||
mergeCells strings.Builder
|
||||
tableParts string
|
||||
}
|
||||
|
||||
// NewStreamWriter return stream writer struct by given worksheet name for
|
||||
// generate new worksheet with large amounts of data. Note that after set
|
||||
// rows, you must call the 'Flush' method to end the streaming writing process
|
||||
// and ensure that the order of row numbers is ascending, the normal mode
|
||||
// functions and stream mode functions can't be work mixed to writing data on
|
||||
// the worksheets, you can't get cell value when in-memory chunks data over
|
||||
// 16MB. For example, set data for worksheet of size 102400 rows x 50 columns
|
||||
// with numbers and style:
|
||||
//
|
||||
// f := excelize.NewFile()
|
||||
// defer func() {
|
||||
// if err := f.Close(); err != nil {
|
||||
// fmt.Println(err)
|
||||
// }
|
||||
// }()
|
||||
// sw, err := f.NewStreamWriter("Sheet1")
|
||||
// if err != nil {
|
||||
// fmt.Println(err)
|
||||
// return
|
||||
// }
|
||||
// styleID, err := f.NewStyle(&excelize.Style{Font: &excelize.Font{Color: "777777"}})
|
||||
// if err != nil {
|
||||
// fmt.Println(err)
|
||||
// return
|
||||
// }
|
||||
// if err := sw.SetRow("A1",
|
||||
// []interface{}{
|
||||
// excelize.Cell{StyleID: styleID, Value: "Data"},
|
||||
// []excelize.RichTextRun{
|
||||
// {Text: "Rich ", Font: &excelize.Font{Color: "2354e8"}},
|
||||
// {Text: "Text", Font: &excelize.Font{Color: "e83723"}},
|
||||
// },
|
||||
// },
|
||||
// excelize.RowOpts{Height: 45, Hidden: false}); err != nil {
|
||||
// fmt.Println(err)
|
||||
// return
|
||||
// }
|
||||
// for rowID := 2; rowID <= 102400; rowID++ {
|
||||
// row := make([]interface{}, 50)
|
||||
// for colID := 0; colID < 50; colID++ {
|
||||
// row[colID] = rand.Intn(640000)
|
||||
// }
|
||||
// cell, err := excelize.CoordinatesToCellName(1, rowID)
|
||||
// if err != nil {
|
||||
// fmt.Println(err)
|
||||
// break
|
||||
// }
|
||||
// if err := sw.SetRow(cell, row); err != nil {
|
||||
// fmt.Println(err)
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// if err := sw.Flush(); err != nil {
|
||||
// fmt.Println(err)
|
||||
// return
|
||||
// }
|
||||
// if err := f.SaveAs("Book1.xlsx"); err != nil {
|
||||
// fmt.Println(err)
|
||||
// }
|
||||
//
|
||||
// Set cell value and cell formula for a worksheet with stream writer:
|
||||
//
|
||||
// err := sw.SetRow("A1", []interface{}{
|
||||
// excelize.Cell{Value: 1},
|
||||
// excelize.Cell{Value: 2},
|
||||
// excelize.Cell{Formula: "SUM(A1,B1)"}});
|
||||
//
|
||||
// Set cell value and rows style for a worksheet with stream writer:
|
||||
//
|
||||
// err := sw.SetRow("A1", []interface{}{
|
||||
// excelize.Cell{Value: 1}},
|
||||
// excelize.RowOpts{StyleID: styleID, Height: 20, Hidden: false});
|
||||
func (f *File) NewStreamWriter(sheet string) (*StreamWriter, error) {
|
||||
if err := checkSheetName(sheet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sheetID := f.getSheetID(sheet)
|
||||
if sheetID == -1 {
|
||||
return nil, newNoExistSheetError(sheet)
|
||||
}
|
||||
sw := &StreamWriter{
|
||||
file: f,
|
||||
Sheet: sheet,
|
||||
SheetID: sheetID,
|
||||
}
|
||||
var err error
|
||||
sw.worksheet, err = f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sheetXMLPath, _ := f.getSheetXMLPath(sheet)
|
||||
if f.streams == nil {
|
||||
f.streams = make(map[string]*StreamWriter)
|
||||
}
|
||||
f.streams[sheetXMLPath] = sw
|
||||
|
||||
_, _ = sw.rawData.WriteString(xml.Header + `<worksheet` + templateNamespaceIDMap)
|
||||
bulkAppendFields(&sw.rawData, sw.worksheet, 2, 3)
|
||||
return sw, err
|
||||
}
|
||||
|
||||
// AddTable creates an Excel table for the StreamWriter using the given
|
||||
// cell range and format set. For example, create a table of A1:D5:
|
||||
//
|
||||
// err := sw.AddTable(&excelize.Table{Range: "A1:D5"})
|
||||
//
|
||||
// Create a table of F2:H6 with format set:
|
||||
//
|
||||
// disable := false
|
||||
// err := sw.AddTable(&excelize.Table{
|
||||
// Range: "F2:H6",
|
||||
// Name: "table",
|
||||
// StyleName: "TableStyleMedium2",
|
||||
// ShowFirstColumn: true,
|
||||
// ShowLastColumn: true,
|
||||
// ShowRowStripes: &disable,
|
||||
// ShowColumnStripes: true,
|
||||
// })
|
||||
//
|
||||
// Note that the table must be at least two lines including the header. The
|
||||
// header cells must contain strings and must be unique.
|
||||
//
|
||||
// Currently, only one table is allowed for a StreamWriter. AddTable must be
|
||||
// called after the rows are written but before Flush.
|
||||
//
|
||||
// See File.AddTable for details on the table format.
|
||||
func (sw *StreamWriter) AddTable(table *Table) error {
|
||||
options, err := parseTableOptions(table)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
coordinates, err := rangeRefToCoordinates(options.Range)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = sortCoordinates(coordinates)
|
||||
|
||||
// Correct the minimum number of rows, the table at least two lines.
|
||||
if coordinates[1] == coordinates[3] {
|
||||
coordinates[3]++
|
||||
}
|
||||
|
||||
// Correct table reference range, such correct C1:B3 to B1:C3.
|
||||
ref, err := sw.file.coordinatesToRangeRef(coordinates)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create table columns using the first row
|
||||
tableHeaders, err := sw.getRowValues(coordinates[1], coordinates[0], coordinates[2])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tableColumn := make([]*xlsxTableColumn, len(tableHeaders))
|
||||
for i, name := range tableHeaders {
|
||||
tableColumn[i] = &xlsxTableColumn{
|
||||
ID: i + 1,
|
||||
Name: name,
|
||||
}
|
||||
}
|
||||
|
||||
tableID := sw.file.countTables() + 1
|
||||
|
||||
name := options.Name
|
||||
if name == "" {
|
||||
name = "Table" + strconv.Itoa(tableID)
|
||||
}
|
||||
|
||||
tbl := xlsxTable{
|
||||
XMLNS: NameSpaceSpreadSheet.Value,
|
||||
ID: tableID,
|
||||
Name: name,
|
||||
DisplayName: name,
|
||||
Ref: ref,
|
||||
AutoFilter: &xlsxAutoFilter{
|
||||
Ref: ref,
|
||||
},
|
||||
TableColumns: &xlsxTableColumns{
|
||||
Count: len(tableColumn),
|
||||
TableColumn: tableColumn,
|
||||
},
|
||||
TableStyleInfo: &xlsxTableStyleInfo{
|
||||
Name: options.StyleName,
|
||||
ShowFirstColumn: options.ShowFirstColumn,
|
||||
ShowLastColumn: options.ShowLastColumn,
|
||||
ShowRowStripes: *options.ShowRowStripes,
|
||||
ShowColumnStripes: options.ShowColumnStripes,
|
||||
},
|
||||
}
|
||||
|
||||
sheetRelationshipsTableXML := "../tables/table" + strconv.Itoa(tableID) + ".xml"
|
||||
tableXML := strings.ReplaceAll(sheetRelationshipsTableXML, "..", "xl")
|
||||
|
||||
// Add first table for given sheet
|
||||
sheetPath := sw.file.sheetMap[sw.Sheet]
|
||||
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetPath, "xl/worksheets/") + ".rels"
|
||||
rID := sw.file.addRels(sheetRels, SourceRelationshipTable, sheetRelationshipsTableXML, "")
|
||||
|
||||
sw.tableParts = fmt.Sprintf(`<tableParts count="1"><tablePart r:id="rId%d"></tablePart></tableParts>`, rID)
|
||||
|
||||
if err = sw.file.addContentTypePart(tableID, "table"); err != nil {
|
||||
return err
|
||||
}
|
||||
b, _ := xml.Marshal(tbl)
|
||||
sw.file.saveFileList(tableXML, b)
|
||||
return err
|
||||
}
|
||||
|
||||
// Extract values from a row in the StreamWriter.
|
||||
func (sw *StreamWriter) getRowValues(hRow, hCol, vCol int) (res []string, err error) {
|
||||
res = make([]string, vCol-hCol+1)
|
||||
|
||||
r, err := sw.rawData.Reader()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dec := sw.file.xmlNewDecoder(r)
|
||||
for {
|
||||
token, err := dec.Token()
|
||||
if err == io.EOF {
|
||||
return res, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
startElement, ok := getRowElement(token, hRow)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// decode cells
|
||||
var row xlsxRow
|
||||
if err := dec.DecodeElement(&row, &startElement); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, c := range row.C {
|
||||
col, _, err := CellNameToCoordinates(c.R)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if col < hCol || col > vCol {
|
||||
continue
|
||||
}
|
||||
res[col-hCol], _ = c.getValueFrom(sw.file, nil, false)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the token is an XLSX row with the matching row number.
|
||||
func getRowElement(token xml.Token, hRow int) (startElement xml.StartElement, ok bool) {
|
||||
startElement, ok = token.(xml.StartElement)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ok = startElement.Name.Local == "row"
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ok = false
|
||||
for _, attr := range startElement.Attr {
|
||||
if attr.Name.Local != "r" {
|
||||
continue
|
||||
}
|
||||
row, _ := strconv.Atoi(attr.Value)
|
||||
if row == hRow {
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Cell can be used directly in StreamWriter.SetRow to specify a style and
|
||||
// a value.
|
||||
type Cell struct {
|
||||
StyleID int
|
||||
Formula string
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
// RowOpts define the options for the set row, it can be used directly in
|
||||
// StreamWriter.SetRow to specify the style and properties of the row.
|
||||
type RowOpts struct {
|
||||
Height float64
|
||||
Hidden bool
|
||||
StyleID int
|
||||
OutlineLevel int
|
||||
}
|
||||
|
||||
// marshalAttrs prepare attributes of the row.
|
||||
func (r *RowOpts) marshalAttrs() (strings.Builder, error) {
|
||||
var (
|
||||
err error
|
||||
attrs strings.Builder
|
||||
)
|
||||
if r == nil {
|
||||
return attrs, err
|
||||
}
|
||||
if r.Height > MaxRowHeight {
|
||||
err = ErrMaxRowHeight
|
||||
return attrs, err
|
||||
}
|
||||
if r.OutlineLevel > 7 {
|
||||
err = ErrOutlineLevel
|
||||
return attrs, err
|
||||
}
|
||||
if r.StyleID > 0 {
|
||||
attrs.WriteString(` s="`)
|
||||
attrs.WriteString(strconv.Itoa(r.StyleID))
|
||||
attrs.WriteString(`" customFormat="1"`)
|
||||
}
|
||||
if r.Height > 0 {
|
||||
attrs.WriteString(` ht="`)
|
||||
attrs.WriteString(strconv.FormatFloat(r.Height, 'f', -1, 64))
|
||||
attrs.WriteString(`" customHeight="1"`)
|
||||
}
|
||||
if r.OutlineLevel > 0 {
|
||||
attrs.WriteString(` outlineLevel="`)
|
||||
attrs.WriteString(strconv.Itoa(r.OutlineLevel))
|
||||
attrs.WriteString(`"`)
|
||||
}
|
||||
if r.Hidden {
|
||||
attrs.WriteString(` hidden="1"`)
|
||||
}
|
||||
return attrs, err
|
||||
}
|
||||
|
||||
// parseRowOpts provides a function to parse the optional settings for
|
||||
// *StreamWriter.SetRow.
|
||||
func parseRowOpts(opts ...RowOpts) *RowOpts {
|
||||
options := &RowOpts{}
|
||||
for _, opt := range opts {
|
||||
options = &opt
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
// SetRow writes an array to stream rows by giving starting cell reference and a
|
||||
// pointer to an array of values. Note that you must call the 'Flush' function
|
||||
// to end the streaming writing process.
|
||||
//
|
||||
// As a special case, if Cell is used as a value, then the Cell.StyleID will be
|
||||
// applied to that cell.
|
||||
func (sw *StreamWriter) SetRow(cell string, values []interface{}, opts ...RowOpts) error {
|
||||
col, row, err := CellNameToCoordinates(cell)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if row <= sw.rows {
|
||||
return newStreamSetRowError(row)
|
||||
}
|
||||
sw.rows = row
|
||||
sw.writeSheetData()
|
||||
options := parseRowOpts(opts...)
|
||||
attrs, err := options.marshalAttrs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = sw.rawData.WriteString(`<row r="`)
|
||||
_, _ = sw.rawData.WriteString(strconv.Itoa(row))
|
||||
_, _ = sw.rawData.WriteString(`"`)
|
||||
_, _ = sw.rawData.WriteString(attrs.String())
|
||||
_, _ = sw.rawData.WriteString(`>`)
|
||||
for i, val := range values {
|
||||
if val == nil {
|
||||
continue
|
||||
}
|
||||
ref, err := CoordinatesToCellName(col+i, row)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c := xlsxC{R: ref, S: options.StyleID}
|
||||
if v, ok := val.(Cell); ok {
|
||||
c.S = v.StyleID
|
||||
val = v.Value
|
||||
setCellFormula(&c, v.Formula)
|
||||
} else if v, ok := val.(*Cell); ok && v != nil {
|
||||
c.S = v.StyleID
|
||||
val = v.Value
|
||||
setCellFormula(&c, v.Formula)
|
||||
}
|
||||
if err = sw.setCellValFunc(&c, val); err != nil {
|
||||
_, _ = sw.rawData.WriteString(`</row>`)
|
||||
return err
|
||||
}
|
||||
writeCell(&sw.rawData, c)
|
||||
}
|
||||
_, _ = sw.rawData.WriteString(`</row>`)
|
||||
return sw.rawData.Sync()
|
||||
}
|
||||
|
||||
// SetColWidth provides a function to set the width of a single column or
|
||||
// multiple columns for the StreamWriter. Note that you must call
|
||||
// the 'SetColWidth' function before the 'SetRow' function. For example set
|
||||
// the width column B:C as 20:
|
||||
//
|
||||
// err := sw.SetColWidth(2, 3, 20)
|
||||
func (sw *StreamWriter) SetColWidth(min, max int, width float64) error {
|
||||
if sw.sheetWritten {
|
||||
return ErrStreamSetColWidth
|
||||
}
|
||||
if min < MinColumns || min > MaxColumns || max < MinColumns || max > MaxColumns {
|
||||
return ErrColumnNumber
|
||||
}
|
||||
if width > MaxColumnWidth {
|
||||
return ErrColumnWidth
|
||||
}
|
||||
if min > max {
|
||||
min, max = max, min
|
||||
}
|
||||
|
||||
sw.cols.WriteString(`<col min="`)
|
||||
sw.cols.WriteString(strconv.Itoa(min))
|
||||
sw.cols.WriteString(`" max="`)
|
||||
sw.cols.WriteString(strconv.Itoa(max))
|
||||
sw.cols.WriteString(`" width="`)
|
||||
sw.cols.WriteString(strconv.FormatFloat(width, 'f', -1, 64))
|
||||
sw.cols.WriteString(`" customWidth="1"/>`)
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertPageBreak creates a page break to determine where the printed page ends
|
||||
// and where begins the next one by a given cell reference, the content before
|
||||
// the page break will be printed on one page and after the page break on
|
||||
// another.
|
||||
func (sw *StreamWriter) InsertPageBreak(cell string) error {
|
||||
return sw.worksheet.insertPageBreak(cell)
|
||||
}
|
||||
|
||||
// SetPanes provides a function to create and remove freeze panes and split
|
||||
// panes by giving panes options for the StreamWriter. Note that you must call
|
||||
// the 'SetPanes' function before the 'SetRow' function.
|
||||
func (sw *StreamWriter) SetPanes(panes *Panes) error {
|
||||
if sw.sheetWritten {
|
||||
return ErrStreamSetPanes
|
||||
}
|
||||
return sw.worksheet.setPanes(panes)
|
||||
}
|
||||
|
||||
// MergeCell provides a function to merge cells by a given range reference for
|
||||
// the StreamWriter. Don't create a merged cell that overlaps with another
|
||||
// existing merged cell.
|
||||
func (sw *StreamWriter) MergeCell(hCell, vCell string) error {
|
||||
_, err := cellRefsToCoordinates(hCell, vCell)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sw.mergeCellsCount++
|
||||
_, _ = sw.mergeCells.WriteString(`<mergeCell ref="`)
|
||||
_, _ = sw.mergeCells.WriteString(hCell)
|
||||
_, _ = sw.mergeCells.WriteString(`:`)
|
||||
_, _ = sw.mergeCells.WriteString(vCell)
|
||||
_, _ = sw.mergeCells.WriteString(`"/>`)
|
||||
return nil
|
||||
}
|
||||
|
||||
// setCellFormula provides a function to set formula of a cell.
|
||||
func setCellFormula(c *xlsxC, formula string) {
|
||||
if formula != "" {
|
||||
c.T, c.F = "str", &xlsxF{Content: formula}
|
||||
}
|
||||
}
|
||||
|
||||
// setCellTime provides a function to set number of a cell with a time.
|
||||
func (sw *StreamWriter) setCellTime(c *xlsxC, val time.Time) error {
|
||||
var date1904, isNum bool
|
||||
wb, err := sw.file.workbookReader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if wb != nil && wb.WorkbookPr != nil {
|
||||
date1904 = wb.WorkbookPr.Date1904
|
||||
}
|
||||
if isNum, err = c.setCellTime(val, date1904); err == nil && isNum && c.S == 0 {
|
||||
style, _ := sw.file.NewStyle(&Style{NumFmt: 22})
|
||||
c.S = style
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// setCellValFunc provides a function to set value of a cell.
|
||||
func (sw *StreamWriter) setCellValFunc(c *xlsxC, val interface{}) error {
|
||||
var err error
|
||||
switch val := val.(type) {
|
||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
|
||||
err = setCellIntFunc(c, val)
|
||||
case float32:
|
||||
c.T, c.V = setCellFloat(float64(val), -1, 32)
|
||||
case float64:
|
||||
c.T, c.V = setCellFloat(val, -1, 64)
|
||||
case string:
|
||||
c.setCellValue(val)
|
||||
case []byte:
|
||||
c.setCellValue(string(val))
|
||||
case time.Duration:
|
||||
c.T, c.V = setCellDuration(val)
|
||||
case time.Time:
|
||||
err = sw.setCellTime(c, val)
|
||||
case bool:
|
||||
c.T, c.V = setCellBool(val)
|
||||
case nil:
|
||||
return err
|
||||
case []RichTextRun:
|
||||
c.T, c.IS = "inlineStr", &xlsxSI{}
|
||||
c.IS.R, err = setRichText(val)
|
||||
default:
|
||||
c.setCellValue(fmt.Sprint(val))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// setCellIntFunc is a wrapper of SetCellInt.
|
||||
func setCellIntFunc(c *xlsxC, val interface{}) (err error) {
|
||||
switch val := val.(type) {
|
||||
case int:
|
||||
c.T, c.V = setCellInt(val)
|
||||
case int8:
|
||||
c.T, c.V = setCellInt(int(val))
|
||||
case int16:
|
||||
c.T, c.V = setCellInt(int(val))
|
||||
case int32:
|
||||
c.T, c.V = setCellInt(int(val))
|
||||
case int64:
|
||||
c.T, c.V = setCellInt(int(val))
|
||||
case uint:
|
||||
c.T, c.V = setCellInt(int(val))
|
||||
case uint8:
|
||||
c.T, c.V = setCellInt(int(val))
|
||||
case uint16:
|
||||
c.T, c.V = setCellInt(int(val))
|
||||
case uint32:
|
||||
c.T, c.V = setCellInt(int(val))
|
||||
case uint64:
|
||||
c.T, c.V = setCellInt(int(val))
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// writeCell constructs a cell XML and writes it to the buffer.
|
||||
func writeCell(buf *bufferedWriter, c xlsxC) {
|
||||
_, _ = buf.WriteString(`<c`)
|
||||
if c.XMLSpace.Value != "" {
|
||||
_, _ = buf.WriteString(` xml:`)
|
||||
_, _ = buf.WriteString(c.XMLSpace.Name.Local)
|
||||
_, _ = buf.WriteString(`="`)
|
||||
_, _ = buf.WriteString(c.XMLSpace.Value)
|
||||
_, _ = buf.WriteString(`"`)
|
||||
}
|
||||
_, _ = buf.WriteString(` r="`)
|
||||
_, _ = buf.WriteString(c.R)
|
||||
_, _ = buf.WriteString(`"`)
|
||||
if c.S != 0 {
|
||||
_, _ = buf.WriteString(` s="`)
|
||||
_, _ = buf.WriteString(strconv.Itoa(c.S))
|
||||
_, _ = buf.WriteString(`"`)
|
||||
}
|
||||
if c.T != "" {
|
||||
_, _ = buf.WriteString(` t="`)
|
||||
_, _ = buf.WriteString(c.T)
|
||||
_, _ = buf.WriteString(`"`)
|
||||
}
|
||||
_, _ = buf.WriteString(`>`)
|
||||
if c.F != nil {
|
||||
_, _ = buf.WriteString(`<f>`)
|
||||
_ = xml.EscapeText(buf, []byte(c.F.Content))
|
||||
_, _ = buf.WriteString(`</f>`)
|
||||
}
|
||||
if c.V != "" {
|
||||
_, _ = buf.WriteString(`<v>`)
|
||||
_ = xml.EscapeText(buf, []byte(c.V))
|
||||
_, _ = buf.WriteString(`</v>`)
|
||||
}
|
||||
if c.IS != nil {
|
||||
if len(c.IS.R) > 0 {
|
||||
is, _ := xml.Marshal(c.IS.R)
|
||||
_, _ = buf.WriteString(`<is>`)
|
||||
_, _ = buf.Write(is)
|
||||
_, _ = buf.WriteString(`</is>`)
|
||||
}
|
||||
if c.IS.T != nil {
|
||||
_, _ = buf.WriteString(`<is><t`)
|
||||
if c.IS.T.Space.Value != "" {
|
||||
_, _ = buf.WriteString(` xml:`)
|
||||
_, _ = buf.WriteString(c.IS.T.Space.Name.Local)
|
||||
_, _ = buf.WriteString(`="`)
|
||||
_, _ = buf.WriteString(c.IS.T.Space.Value)
|
||||
_, _ = buf.WriteString(`"`)
|
||||
}
|
||||
_, _ = buf.WriteString(`>`)
|
||||
_, _ = buf.Write([]byte(c.IS.T.Val))
|
||||
_, _ = buf.WriteString(`</t></is>`)
|
||||
}
|
||||
}
|
||||
_, _ = buf.WriteString(`</c>`)
|
||||
}
|
||||
|
||||
// writeSheetData prepares the element preceding sheetData and writes the
|
||||
// sheetData XML start element to the buffer.
|
||||
func (sw *StreamWriter) writeSheetData() {
|
||||
if !sw.sheetWritten {
|
||||
bulkAppendFields(&sw.rawData, sw.worksheet, 4, 5)
|
||||
if sw.cols.Len() > 0 {
|
||||
_, _ = sw.rawData.WriteString("<cols>")
|
||||
_, _ = sw.rawData.WriteString(sw.cols.String())
|
||||
_, _ = sw.rawData.WriteString("</cols>")
|
||||
}
|
||||
_, _ = sw.rawData.WriteString(`<sheetData>`)
|
||||
sw.sheetWritten = true
|
||||
}
|
||||
}
|
||||
|
||||
// Flush ending the streaming writing process.
|
||||
func (sw *StreamWriter) Flush() error {
|
||||
sw.writeSheetData()
|
||||
_, _ = sw.rawData.WriteString(`</sheetData>`)
|
||||
bulkAppendFields(&sw.rawData, sw.worksheet, 8, 15)
|
||||
mergeCells := strings.Builder{}
|
||||
if sw.mergeCellsCount > 0 {
|
||||
_, _ = mergeCells.WriteString(`<mergeCells count="`)
|
||||
_, _ = mergeCells.WriteString(strconv.Itoa(sw.mergeCellsCount))
|
||||
_, _ = mergeCells.WriteString(`">`)
|
||||
_, _ = mergeCells.WriteString(sw.mergeCells.String())
|
||||
_, _ = mergeCells.WriteString(`</mergeCells>`)
|
||||
}
|
||||
_, _ = sw.rawData.WriteString(mergeCells.String())
|
||||
bulkAppendFields(&sw.rawData, sw.worksheet, 17, 38)
|
||||
_, _ = sw.rawData.WriteString(sw.tableParts)
|
||||
bulkAppendFields(&sw.rawData, sw.worksheet, 40, 40)
|
||||
_, _ = sw.rawData.WriteString(`</worksheet>`)
|
||||
if err := sw.rawData.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sheetPath := sw.file.sheetMap[sw.Sheet]
|
||||
sw.file.Sheet.Delete(sheetPath)
|
||||
delete(sw.file.checked, sheetPath)
|
||||
sw.file.Pkg.Delete(sheetPath)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// bulkAppendFields bulk-appends fields in a worksheet by specified field
|
||||
// names order range.
|
||||
func bulkAppendFields(w io.Writer, ws *xlsxWorksheet, from, to int) {
|
||||
s := reflect.ValueOf(ws).Elem()
|
||||
enc := xml.NewEncoder(w)
|
||||
for i := 0; i < s.NumField(); i++ {
|
||||
if from <= i && i <= to {
|
||||
_ = enc.Encode(s.Field(i).Interface())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// bufferedWriter uses a temp file to store an extended buffer. Writes are
|
||||
// always made to an in-memory buffer, which will always succeed. The buffer
|
||||
// is written to the temp file with Sync, which may return an error.
|
||||
// Therefore, Sync should be periodically called and the error checked.
|
||||
type bufferedWriter struct {
|
||||
tmp *os.File
|
||||
buf bytes.Buffer
|
||||
}
|
||||
|
||||
// Write to the in-memory buffer. The error is always nil.
|
||||
func (bw *bufferedWriter) Write(p []byte) (n int, err error) {
|
||||
return bw.buf.Write(p)
|
||||
}
|
||||
|
||||
// WriteString write to the in-memory buffer. The error is always nil.
|
||||
func (bw *bufferedWriter) WriteString(p string) (n int, err error) {
|
||||
return bw.buf.WriteString(p)
|
||||
}
|
||||
|
||||
// Reader provides read-access to the underlying buffer/file.
|
||||
func (bw *bufferedWriter) Reader() (io.Reader, error) {
|
||||
if bw.tmp == nil {
|
||||
return bytes.NewReader(bw.buf.Bytes()), nil
|
||||
}
|
||||
if err := bw.Flush(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fi, err := bw.tmp.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// os.File.ReadAt does not affect the cursor position and is safe to use here
|
||||
return io.NewSectionReader(bw.tmp, 0, fi.Size()), nil
|
||||
}
|
||||
|
||||
// Sync will write the in-memory buffer to a temp file, if the in-memory
|
||||
// buffer has grown large enough. Any error will be returned.
|
||||
func (bw *bufferedWriter) Sync() (err error) {
|
||||
// Try to use local storage
|
||||
if bw.buf.Len() < StreamChunkSize {
|
||||
return nil
|
||||
}
|
||||
if bw.tmp == nil {
|
||||
bw.tmp, err = os.CreateTemp(os.TempDir(), "excelize-")
|
||||
if err != nil {
|
||||
// can not use local storage
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return bw.Flush()
|
||||
}
|
||||
|
||||
// Flush the entire in-memory buffer to the temp file, if a temp file is being
|
||||
// used.
|
||||
func (bw *bufferedWriter) Flush() error {
|
||||
if bw.tmp == nil {
|
||||
return nil
|
||||
}
|
||||
_, err := bw.buf.WriteTo(bw.tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bw.buf.Reset()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close the underlying temp file and reset the in-memory buffer.
|
||||
func (bw *bufferedWriter) Close() error {
|
||||
bw.buf.Reset()
|
||||
if bw.tmp == nil {
|
||||
return nil
|
||||
}
|
||||
defer os.Remove(bw.tmp.Name())
|
||||
return bw.tmp.Close()
|
||||
}
|
||||
+3822
File diff suppressed because it is too large
Load Diff
+547
@@ -0,0 +1,547 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// parseTableOptions provides a function to parse the format settings of the
|
||||
// table with default value.
|
||||
func parseTableOptions(opts *Table) (*Table, error) {
|
||||
var err error
|
||||
if opts == nil {
|
||||
return &Table{ShowRowStripes: boolPtr(true)}, err
|
||||
}
|
||||
if opts.ShowRowStripes == nil {
|
||||
opts.ShowRowStripes = boolPtr(true)
|
||||
}
|
||||
if err = checkTableName(opts.Name); err != nil {
|
||||
return opts, err
|
||||
}
|
||||
return opts, err
|
||||
}
|
||||
|
||||
// AddTable provides the method to add table in a worksheet by given worksheet
|
||||
// name, range reference and format set. For example, create a table of A1:D5
|
||||
// on Sheet1:
|
||||
//
|
||||
// err := f.AddTable("Sheet1", &excelize.Table{Range: "A1:D5"})
|
||||
//
|
||||
// Create a table of F2:H6 on Sheet2 with format set:
|
||||
//
|
||||
// disable := false
|
||||
// err := f.AddTable("Sheet2", &excelize.Table{
|
||||
// Range: "F2:H6",
|
||||
// Name: "table",
|
||||
// StyleName: "TableStyleMedium2",
|
||||
// ShowFirstColumn: true,
|
||||
// ShowLastColumn: true,
|
||||
// ShowRowStripes: &disable,
|
||||
// ShowColumnStripes: true,
|
||||
// })
|
||||
//
|
||||
// Note that the table must be at least two lines including the header. The
|
||||
// header cells must contain strings and must be unique, and must set the
|
||||
// header row data of the table before calling the AddTable function. Multiple
|
||||
// tables range reference that can't have an intersection.
|
||||
//
|
||||
// Name: The name of the table, in the same worksheet name of the table should
|
||||
// be unique, starts with a letter or underscore (_), doesn't include a
|
||||
// space or character, and should be no more than 255 characters
|
||||
//
|
||||
// StyleName: The built-in table style names
|
||||
//
|
||||
// TableStyleLight1 - TableStyleLight21
|
||||
// TableStyleMedium1 - TableStyleMedium28
|
||||
// TableStyleDark1 - TableStyleDark11
|
||||
func (f *File) AddTable(sheet string, table *Table) error {
|
||||
options, err := parseTableOptions(table)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Coordinate conversion, convert C1:B3 to 2,0,1,2.
|
||||
coordinates, err := rangeRefToCoordinates(options.Range)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Correct table reference range, such correct C1:B3 to B1:C3.
|
||||
_ = sortCoordinates(coordinates)
|
||||
tableID := f.countTables() + 1
|
||||
sheetRelationshipsTableXML := "../tables/table" + strconv.Itoa(tableID) + ".xml"
|
||||
tableXML := strings.ReplaceAll(sheetRelationshipsTableXML, "..", "xl")
|
||||
// Add first table for given sheet.
|
||||
sheetXMLPath, _ := f.getSheetXMLPath(sheet)
|
||||
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels"
|
||||
rID := f.addRels(sheetRels, SourceRelationshipTable, sheetRelationshipsTableXML, "")
|
||||
if err = f.addSheetTable(sheet, rID); err != nil {
|
||||
return err
|
||||
}
|
||||
f.addSheetNameSpace(sheet, SourceRelationship)
|
||||
if err = f.addTable(sheet, tableXML, coordinates[0], coordinates[1], coordinates[2], coordinates[3], tableID, options); err != nil {
|
||||
return err
|
||||
}
|
||||
return f.addContentTypePart(tableID, "table")
|
||||
}
|
||||
|
||||
// countTables provides a function to get table files count storage in the
|
||||
// folder xl/tables.
|
||||
func (f *File) countTables() int {
|
||||
count := 0
|
||||
f.Pkg.Range(func(k, v interface{}) bool {
|
||||
if strings.Contains(k.(string), "xl/tables/table") {
|
||||
count++
|
||||
}
|
||||
return true
|
||||
})
|
||||
return count
|
||||
}
|
||||
|
||||
// addSheetTable provides a function to add tablePart element to
|
||||
// xl/worksheets/sheet%d.xml by given worksheet name and relationship index.
|
||||
func (f *File) addSheetTable(sheet string, rID int) error {
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
table := &xlsxTablePart{
|
||||
RID: "rId" + strconv.Itoa(rID),
|
||||
}
|
||||
if ws.TableParts == nil {
|
||||
ws.TableParts = &xlsxTableParts{}
|
||||
}
|
||||
ws.TableParts.Count++
|
||||
ws.TableParts.TableParts = append(ws.TableParts.TableParts, table)
|
||||
return err
|
||||
}
|
||||
|
||||
// setTableHeader provides a function to set cells value in header row for the
|
||||
// table.
|
||||
func (f *File) setTableHeader(sheet string, showHeaderRow bool, x1, y1, x2 int) ([]*xlsxTableColumn, error) {
|
||||
var (
|
||||
tableColumns []*xlsxTableColumn
|
||||
idx int
|
||||
)
|
||||
for i := x1; i <= x2; i++ {
|
||||
idx++
|
||||
cell, err := CoordinatesToCellName(i, y1)
|
||||
if err != nil {
|
||||
return tableColumns, err
|
||||
}
|
||||
name, _ := f.GetCellValue(sheet, cell)
|
||||
if _, err := strconv.Atoi(name); err == nil {
|
||||
if showHeaderRow {
|
||||
_ = f.SetCellStr(sheet, cell, name)
|
||||
}
|
||||
}
|
||||
if name == "" {
|
||||
name = "Column" + strconv.Itoa(idx)
|
||||
if showHeaderRow {
|
||||
_ = f.SetCellStr(sheet, cell, name)
|
||||
}
|
||||
}
|
||||
tableColumns = append(tableColumns, &xlsxTableColumn{
|
||||
ID: idx,
|
||||
Name: name,
|
||||
})
|
||||
}
|
||||
return tableColumns, nil
|
||||
}
|
||||
|
||||
// checkSheetName check whether there are illegal characters in the table name.
|
||||
// Verify that the name:
|
||||
// 1. Starts with a letter or underscore (_)
|
||||
// 2. Doesn't include a space or character that isn't allowed
|
||||
func checkTableName(name string) error {
|
||||
if utf8.RuneCountInString(name) > MaxFieldLength {
|
||||
return ErrTableNameLength
|
||||
}
|
||||
for i, c := range name {
|
||||
if string(c) == "_" {
|
||||
continue
|
||||
}
|
||||
if unicode.IsLetter(c) {
|
||||
continue
|
||||
}
|
||||
if i > 0 && unicode.IsDigit(c) {
|
||||
continue
|
||||
}
|
||||
return newInvalidTableNameError(name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// addTable provides a function to add table by given worksheet name,
|
||||
// range reference and format set.
|
||||
func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, opts *Table) error {
|
||||
// Correct the minimum number of rows, the table at least two lines.
|
||||
if y1 == y2 {
|
||||
y2++
|
||||
}
|
||||
hideHeaderRow := opts != nil && opts.ShowHeaderRow != nil && !*opts.ShowHeaderRow
|
||||
if hideHeaderRow {
|
||||
y1++
|
||||
}
|
||||
// Correct table range reference, such correct C1:B3 to B1:C3.
|
||||
ref, err := f.coordinatesToRangeRef([]int{x1, y1, x2, y2})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tableColumns, _ := f.setTableHeader(sheet, !hideHeaderRow, x1, y1, x2)
|
||||
name := opts.Name
|
||||
if name == "" {
|
||||
name = "Table" + strconv.Itoa(i)
|
||||
}
|
||||
t := xlsxTable{
|
||||
XMLNS: NameSpaceSpreadSheet.Value,
|
||||
ID: i,
|
||||
Name: name,
|
||||
DisplayName: name,
|
||||
Ref: ref,
|
||||
AutoFilter: &xlsxAutoFilter{
|
||||
Ref: ref,
|
||||
},
|
||||
TableColumns: &xlsxTableColumns{
|
||||
Count: len(tableColumns),
|
||||
TableColumn: tableColumns,
|
||||
},
|
||||
TableStyleInfo: &xlsxTableStyleInfo{
|
||||
Name: opts.StyleName,
|
||||
ShowFirstColumn: opts.ShowFirstColumn,
|
||||
ShowLastColumn: opts.ShowLastColumn,
|
||||
ShowRowStripes: *opts.ShowRowStripes,
|
||||
ShowColumnStripes: opts.ShowColumnStripes,
|
||||
},
|
||||
}
|
||||
if hideHeaderRow {
|
||||
t.AutoFilter = nil
|
||||
t.HeaderRowCount = intPtr(0)
|
||||
}
|
||||
table, _ := xml.Marshal(t)
|
||||
f.saveFileList(tableXML, table)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AutoFilter provides the method to add auto filter in a worksheet by given
|
||||
// worksheet name, range reference and settings. An auto filter in Excel is a
|
||||
// way of filtering a 2D range of data based on some simple criteria. For
|
||||
// example applying an auto filter to a cell range A1:D4 in the Sheet1:
|
||||
//
|
||||
// err := f.AutoFilter("Sheet1", "A1:D4", []excelize.AutoFilterOptions{})
|
||||
//
|
||||
// Filter data in an auto filter:
|
||||
//
|
||||
// err := f.AutoFilter("Sheet1", "A1:D4", []excelize.AutoFilterOptions{
|
||||
// {Column: "B", Expression: "x != blanks"},
|
||||
// })
|
||||
//
|
||||
// Column defines the filter columns in an auto filter range based on simple
|
||||
// criteria
|
||||
//
|
||||
// It isn't sufficient to just specify the filter condition. You must also
|
||||
// hide any rows that don't match the filter condition. Rows are hidden using
|
||||
// the SetRowVisible function. Excelize can't filter rows automatically since
|
||||
// this isn't part of the file format.
|
||||
//
|
||||
// Setting a filter criteria for a column:
|
||||
//
|
||||
// Expression defines the conditions, the following operators are available
|
||||
// for setting the filter criteria:
|
||||
//
|
||||
// ==
|
||||
// !=
|
||||
// >
|
||||
// <
|
||||
// >=
|
||||
// <=
|
||||
// and
|
||||
// or
|
||||
//
|
||||
// An expression can comprise a single statement or two statements separated
|
||||
// by the 'and' and 'or' operators. For example:
|
||||
//
|
||||
// x < 2000
|
||||
// x > 2000
|
||||
// x == 2000
|
||||
// x > 2000 and x < 5000
|
||||
// x == 2000 or x == 5000
|
||||
//
|
||||
// Filtering of blank or non-blank data can be achieved by using a value of
|
||||
// Blanks or NonBlanks in the expression:
|
||||
//
|
||||
// x == Blanks
|
||||
// x == NonBlanks
|
||||
//
|
||||
// Excel also allows some simple string matching operations:
|
||||
//
|
||||
// x == b* // begins with b
|
||||
// x != b* // doesn't begin with b
|
||||
// x == *b // ends with b
|
||||
// x != *b // doesn't end with b
|
||||
// x == *b* // contains b
|
||||
// x != *b* // doesn't contains b
|
||||
//
|
||||
// You can also use '*' to match any character or number and '?' to match any
|
||||
// single character or number. No other regular expression quantifier is
|
||||
// supported by Excel's filters. Excel's regular expression characters can be
|
||||
// escaped using '~'.
|
||||
//
|
||||
// The placeholder variable x in the above examples can be replaced by any
|
||||
// simple string. The actual placeholder name is ignored internally so the
|
||||
// following are all equivalent:
|
||||
//
|
||||
// x < 2000
|
||||
// col < 2000
|
||||
// Price < 2000
|
||||
func (f *File) AutoFilter(sheet, rangeRef string, opts []AutoFilterOptions) error {
|
||||
coordinates, err := rangeRefToCoordinates(rangeRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = sortCoordinates(coordinates)
|
||||
// Correct reference range, such correct C1:B3 to B1:C3.
|
||||
ref, _ := f.coordinatesToRangeRef(coordinates, true)
|
||||
filterDB := "_xlnm._FilterDatabase"
|
||||
wb, err := f.workbookReader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sheetID, err := f.GetSheetIndex(sheet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filterRange := fmt.Sprintf("'%s'!%s", sheet, ref)
|
||||
d := xlsxDefinedName{
|
||||
Name: filterDB,
|
||||
Hidden: true,
|
||||
LocalSheetID: intPtr(sheetID),
|
||||
Data: filterRange,
|
||||
}
|
||||
if wb.DefinedNames == nil {
|
||||
wb.DefinedNames = &xlsxDefinedNames{
|
||||
DefinedName: []xlsxDefinedName{d},
|
||||
}
|
||||
} else {
|
||||
var definedNameExists bool
|
||||
for idx := range wb.DefinedNames.DefinedName {
|
||||
definedName := wb.DefinedNames.DefinedName[idx]
|
||||
if definedName.Name == filterDB && *definedName.LocalSheetID == sheetID && definedName.Hidden {
|
||||
wb.DefinedNames.DefinedName[idx].Data = filterRange
|
||||
definedNameExists = true
|
||||
}
|
||||
}
|
||||
if !definedNameExists {
|
||||
wb.DefinedNames.DefinedName = append(wb.DefinedNames.DefinedName, d)
|
||||
}
|
||||
}
|
||||
columns := coordinates[2] - coordinates[0]
|
||||
return f.autoFilter(sheet, ref, columns, coordinates[0], opts)
|
||||
}
|
||||
|
||||
// autoFilter provides a function to extract the tokens from the filter
|
||||
// expression. The tokens are mainly non-whitespace groups.
|
||||
func (f *File) autoFilter(sheet, ref string, columns, col int, opts []AutoFilterOptions) error {
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ws.SheetPr != nil {
|
||||
ws.SheetPr.FilterMode = true
|
||||
}
|
||||
ws.SheetPr = &xlsxSheetPr{FilterMode: true}
|
||||
filter := &xlsxAutoFilter{
|
||||
Ref: ref,
|
||||
}
|
||||
ws.AutoFilter = filter
|
||||
for _, opt := range opts {
|
||||
if opt.Column == "" || opt.Expression == "" {
|
||||
continue
|
||||
}
|
||||
fsCol, err := ColumnNameToNumber(opt.Column)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
offset := fsCol - col
|
||||
if offset < 0 || offset > columns {
|
||||
return fmt.Errorf("incorrect index of column '%s'", opt.Column)
|
||||
}
|
||||
fc := &xlsxFilterColumn{ColID: offset}
|
||||
re := regexp.MustCompile(`"(?:[^"]|"")*"|\S+`)
|
||||
token := re.FindAllString(opt.Expression, -1)
|
||||
if len(token) != 3 && len(token) != 7 {
|
||||
return fmt.Errorf("incorrect number of tokens in criteria '%s'", opt.Expression)
|
||||
}
|
||||
expressions, tokens, err := f.parseFilterExpression(opt.Expression, token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.writeAutoFilter(fc, expressions, tokens)
|
||||
filter.FilterColumn = append(filter.FilterColumn, fc)
|
||||
}
|
||||
ws.AutoFilter = filter
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeAutoFilter provides a function to check for single or double custom
|
||||
// filters as default filters and handle them accordingly.
|
||||
func (f *File) writeAutoFilter(fc *xlsxFilterColumn, exp []int, tokens []string) {
|
||||
if len(exp) == 1 && exp[0] == 2 {
|
||||
// Single equality.
|
||||
var filters []*xlsxFilter
|
||||
filters = append(filters, &xlsxFilter{Val: tokens[0]})
|
||||
fc.Filters = &xlsxFilters{Filter: filters}
|
||||
} else if len(exp) == 3 && exp[0] == 2 && exp[1] == 1 && exp[2] == 2 {
|
||||
// Double equality with "or" operator.
|
||||
var filters []*xlsxFilter
|
||||
for _, v := range tokens {
|
||||
filters = append(filters, &xlsxFilter{Val: v})
|
||||
}
|
||||
fc.Filters = &xlsxFilters{Filter: filters}
|
||||
} else {
|
||||
// Non default custom filter.
|
||||
expRel := map[int]int{0: 0, 1: 2}
|
||||
andRel := map[int]bool{0: true, 1: false}
|
||||
for k, v := range tokens {
|
||||
f.writeCustomFilter(fc, exp[expRel[k]], v)
|
||||
if k == 1 {
|
||||
fc.CustomFilters.And = andRel[exp[k]]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// writeCustomFilter provides a function to write the <customFilter> element.
|
||||
func (f *File) writeCustomFilter(fc *xlsxFilterColumn, operator int, val string) {
|
||||
operators := map[int]string{
|
||||
1: "lessThan",
|
||||
2: "equal",
|
||||
3: "lessThanOrEqual",
|
||||
4: "greaterThan",
|
||||
5: "notEqual",
|
||||
6: "greaterThanOrEqual",
|
||||
22: "equal",
|
||||
}
|
||||
customFilter := xlsxCustomFilter{
|
||||
Operator: operators[operator],
|
||||
Val: val,
|
||||
}
|
||||
if fc.CustomFilters != nil {
|
||||
fc.CustomFilters.CustomFilter = append(fc.CustomFilters.CustomFilter, &customFilter)
|
||||
} else {
|
||||
var customFilters []*xlsxCustomFilter
|
||||
customFilters = append(customFilters, &customFilter)
|
||||
fc.CustomFilters = &xlsxCustomFilters{CustomFilter: customFilters}
|
||||
}
|
||||
}
|
||||
|
||||
// parseFilterExpression provides a function to converts the tokens of a
|
||||
// possibly conditional expression into 1 or 2 sub expressions for further
|
||||
// parsing.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// ('x', '==', 2000) -> exp1
|
||||
// ('x', '>', 2000, 'and', 'x', '<', 5000) -> exp1 and exp2
|
||||
func (f *File) parseFilterExpression(expression string, tokens []string) ([]int, []string, error) {
|
||||
var expressions []int
|
||||
var t []string
|
||||
if len(tokens) == 7 {
|
||||
// The number of tokens will be either 3 (for 1 expression) or 7 (for 2
|
||||
// expressions).
|
||||
conditional := 0
|
||||
c := tokens[3]
|
||||
re, _ := regexp.Match(`(or|\|\|)`, []byte(c))
|
||||
if re {
|
||||
conditional = 1
|
||||
}
|
||||
expression1, token1, err := f.parseFilterTokens(expression, tokens[:3])
|
||||
if err != nil {
|
||||
return expressions, t, err
|
||||
}
|
||||
expression2, token2, err := f.parseFilterTokens(expression, tokens[4:7])
|
||||
if err != nil {
|
||||
return expressions, t, err
|
||||
}
|
||||
expressions = []int{expression1[0], conditional, expression2[0]}
|
||||
t = []string{token1, token2}
|
||||
} else {
|
||||
exp, token, err := f.parseFilterTokens(expression, tokens)
|
||||
if err != nil {
|
||||
return expressions, t, err
|
||||
}
|
||||
expressions = exp
|
||||
t = []string{token}
|
||||
}
|
||||
return expressions, t, nil
|
||||
}
|
||||
|
||||
// parseFilterTokens provides a function to parse the 3 tokens of a filter
|
||||
// expression and return the operator and token.
|
||||
func (f *File) parseFilterTokens(expression string, tokens []string) ([]int, string, error) {
|
||||
operators := map[string]int{
|
||||
"==": 2,
|
||||
"=": 2,
|
||||
"=~": 2,
|
||||
"eq": 2,
|
||||
"!=": 5,
|
||||
"!~": 5,
|
||||
"ne": 5,
|
||||
"<>": 5,
|
||||
"<": 1,
|
||||
"<=": 3,
|
||||
">": 4,
|
||||
">=": 6,
|
||||
}
|
||||
operator, ok := operators[strings.ToLower(tokens[1])]
|
||||
if !ok {
|
||||
// Convert the operator from a number to a descriptive string.
|
||||
return []int{}, "", fmt.Errorf("unknown operator: %s", tokens[1])
|
||||
}
|
||||
token := tokens[2]
|
||||
// Special handling for Blanks/NonBlanks.
|
||||
re, _ := regexp.Match("blanks|nonblanks", []byte(strings.ToLower(token)))
|
||||
if re {
|
||||
// Only allow Equals or NotEqual in this context.
|
||||
if operator != 2 && operator != 5 {
|
||||
return []int{operator}, token, fmt.Errorf("the operator '%s' in expression '%s' is not valid in relation to Blanks/NonBlanks'", tokens[1], expression)
|
||||
}
|
||||
token = strings.ToLower(token)
|
||||
// The operator should always be 2 (=) to flag a "simple" equality in
|
||||
// the binary record. Therefore we convert <> to =.
|
||||
if token == "blanks" {
|
||||
if operator == 5 {
|
||||
token = " "
|
||||
}
|
||||
} else {
|
||||
if operator == 5 {
|
||||
operator = 2
|
||||
token = "blanks"
|
||||
} else {
|
||||
operator = 5
|
||||
token = " "
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the string token contains an Excel match character then change the
|
||||
// operator type to indicate a non "simple" equality.
|
||||
re, _ = regexp.Match("[*?]", []byte(token))
|
||||
if operator == 2 && re {
|
||||
operator = 22
|
||||
}
|
||||
return []int{operator}, token, nil
|
||||
}
|
||||
+48
File diff suppressed because one or more lines are too long
+146
@@ -0,0 +1,146 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// vmlDrawing directly maps the root element in the file
|
||||
// xl/drawings/vmlDrawing%d.vml.
|
||||
type vmlDrawing struct {
|
||||
XMLName xml.Name `xml:"xml"`
|
||||
XMLNSv string `xml:"xmlns:v,attr"`
|
||||
XMLNSo string `xml:"xmlns:o,attr"`
|
||||
XMLNSx string `xml:"xmlns:x,attr"`
|
||||
XMLNSmv string `xml:"xmlns:mv,attr"`
|
||||
Shapelayout *xlsxShapelayout `xml:"o:shapelayout"`
|
||||
Shapetype *xlsxShapetype `xml:"v:shapetype"`
|
||||
Shape []xlsxShape `xml:"v:shape"`
|
||||
}
|
||||
|
||||
// xlsxShapelayout directly maps the shapelayout element. This element contains
|
||||
// child elements that store information used in the editing and layout of
|
||||
// shapes.
|
||||
type xlsxShapelayout struct {
|
||||
Ext string `xml:"v:ext,attr"`
|
||||
IDmap *xlsxIDmap `xml:"o:idmap"`
|
||||
}
|
||||
|
||||
// xlsxIDmap directly maps the idmap element.
|
||||
type xlsxIDmap struct {
|
||||
Ext string `xml:"v:ext,attr"`
|
||||
Data int `xml:"data,attr"`
|
||||
}
|
||||
|
||||
// xlsxShape directly maps the shape element.
|
||||
type xlsxShape struct {
|
||||
XMLName xml.Name `xml:"v:shape"`
|
||||
ID string `xml:"id,attr"`
|
||||
Type string `xml:"type,attr"`
|
||||
Style string `xml:"style,attr"`
|
||||
Fillcolor string `xml:"fillcolor,attr"`
|
||||
Insetmode string `xml:"urn:schemas-microsoft-com:office:office insetmode,attr,omitempty"`
|
||||
Strokecolor string `xml:"strokecolor,attr,omitempty"`
|
||||
Val string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
// xlsxShapetype directly maps the shapetype element.
|
||||
type xlsxShapetype struct {
|
||||
ID string `xml:"id,attr"`
|
||||
Coordsize string `xml:"coordsize,attr"`
|
||||
Spt int `xml:"o:spt,attr"`
|
||||
Path string `xml:"path,attr"`
|
||||
Stroke *xlsxStroke `xml:"v:stroke"`
|
||||
VPath *vPath `xml:"v:path"`
|
||||
}
|
||||
|
||||
// xlsxStroke directly maps the stroke element.
|
||||
type xlsxStroke struct {
|
||||
Joinstyle string `xml:"joinstyle,attr"`
|
||||
}
|
||||
|
||||
// vPath directly maps the v:path element.
|
||||
type vPath struct {
|
||||
Gradientshapeok string `xml:"gradientshapeok,attr,omitempty"`
|
||||
Connecttype string `xml:"o:connecttype,attr"`
|
||||
}
|
||||
|
||||
// vFill directly maps the v:fill element. This element must be defined within a
|
||||
// Shape element.
|
||||
type vFill struct {
|
||||
Angle int `xml:"angle,attr,omitempty"`
|
||||
Color2 string `xml:"color2,attr"`
|
||||
Type string `xml:"type,attr,omitempty"`
|
||||
Fill *oFill `xml:"o:fill"`
|
||||
}
|
||||
|
||||
// oFill directly maps the o:fill element.
|
||||
type oFill struct {
|
||||
Ext string `xml:"v:ext,attr"`
|
||||
Type string `xml:"type,attr,omitempty"`
|
||||
}
|
||||
|
||||
// vShadow directly maps the v:shadow element. This element must be defined
|
||||
// within a Shape element. In addition, the On attribute must be set to True.
|
||||
type vShadow struct {
|
||||
On string `xml:"on,attr"`
|
||||
Color string `xml:"color,attr,omitempty"`
|
||||
Obscured string `xml:"obscured,attr"`
|
||||
}
|
||||
|
||||
// vTextbox directly maps the v:textbox element. This element must be defined
|
||||
// within a Shape element.
|
||||
type vTextbox struct {
|
||||
Style string `xml:"style,attr"`
|
||||
Div *xlsxDiv `xml:"div"`
|
||||
}
|
||||
|
||||
// xlsxDiv directly maps the div element.
|
||||
type xlsxDiv struct {
|
||||
Style string `xml:"style,attr"`
|
||||
}
|
||||
|
||||
// xClientData (Attached Object Data) directly maps the x:ClientData element.
|
||||
// This element specifies data associated with objects attached to a
|
||||
// spreadsheet. While this element might contain any of the child elements
|
||||
// below, only certain combinations are meaningful. The ObjectType attribute
|
||||
// determines the kind of object the element represents and which subset of
|
||||
// child elements is appropriate. Relevant groups are identified for each child
|
||||
// element.
|
||||
type xClientData struct {
|
||||
ObjectType string `xml:"ObjectType,attr"`
|
||||
MoveWithCells string `xml:"x:MoveWithCells,omitempty"`
|
||||
SizeWithCells string `xml:"x:SizeWithCells,omitempty"`
|
||||
Anchor string `xml:"x:Anchor"`
|
||||
AutoFill string `xml:"x:AutoFill"`
|
||||
Row int `xml:"x:Row"`
|
||||
Column int `xml:"x:Column"`
|
||||
}
|
||||
|
||||
// decodeVmlDrawing defines the structure used to parse the file
|
||||
// xl/drawings/vmlDrawing%d.vml.
|
||||
type decodeVmlDrawing struct {
|
||||
Shape []decodeShape `xml:"urn:schemas-microsoft-com:vml shape"`
|
||||
}
|
||||
|
||||
// decodeShape defines the structure used to parse the particular shape element.
|
||||
type decodeShape struct {
|
||||
Val string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
// encodeShape defines the structure used to re-serialization shape element.
|
||||
type encodeShape struct {
|
||||
Fill *vFill `xml:"v:fill"`
|
||||
Shadow *vShadow `xml:"v:shadow"`
|
||||
Path *vPath `xml:"v:path"`
|
||||
Textbox *vTextbox `xml:"v:textbox"`
|
||||
ClientData *xClientData `xml:"x:ClientData"`
|
||||
}
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SetWorkbookProps provides a function to sets workbook properties.
|
||||
func (f *File) SetWorkbookProps(opts *WorkbookPropsOptions) error {
|
||||
wb, err := f.workbookReader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if wb.WorkbookPr == nil {
|
||||
wb.WorkbookPr = new(xlsxWorkbookPr)
|
||||
}
|
||||
if opts == nil {
|
||||
return nil
|
||||
}
|
||||
if opts.Date1904 != nil {
|
||||
wb.WorkbookPr.Date1904 = *opts.Date1904
|
||||
}
|
||||
if opts.FilterPrivacy != nil {
|
||||
wb.WorkbookPr.FilterPrivacy = *opts.FilterPrivacy
|
||||
}
|
||||
if opts.CodeName != nil {
|
||||
wb.WorkbookPr.CodeName = *opts.CodeName
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetWorkbookProps provides a function to gets workbook properties.
|
||||
func (f *File) GetWorkbookProps() (WorkbookPropsOptions, error) {
|
||||
var opts WorkbookPropsOptions
|
||||
wb, err := f.workbookReader()
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
if wb.WorkbookPr != nil {
|
||||
opts.Date1904 = boolPtr(wb.WorkbookPr.Date1904)
|
||||
opts.FilterPrivacy = boolPtr(wb.WorkbookPr.FilterPrivacy)
|
||||
opts.CodeName = stringPtr(wb.WorkbookPr.CodeName)
|
||||
}
|
||||
return opts, err
|
||||
}
|
||||
|
||||
// ProtectWorkbook provides a function to prevent other users from viewing
|
||||
// hidden worksheets, adding, moving, deleting, or hiding worksheets, and
|
||||
// renaming worksheets in a workbook. The optional field AlgorithmName
|
||||
// specified hash algorithm, support XOR, MD4, MD5, SHA-1, SHA2-56, SHA-384,
|
||||
// and SHA-512 currently, if no hash algorithm specified, will be using the XOR
|
||||
// algorithm as default. The generated workbook only works on Microsoft Office
|
||||
// 2007 and later. For example, protect workbook with protection settings:
|
||||
//
|
||||
// err := f.ProtectWorkbook(&excelize.WorkbookProtectionOptions{
|
||||
// Password: "password",
|
||||
// LockStructure: true,
|
||||
// })
|
||||
func (f *File) ProtectWorkbook(opts *WorkbookProtectionOptions) error {
|
||||
wb, err := f.workbookReader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if wb.WorkbookProtection == nil {
|
||||
wb.WorkbookProtection = new(xlsxWorkbookProtection)
|
||||
}
|
||||
if opts == nil {
|
||||
opts = &WorkbookProtectionOptions{}
|
||||
}
|
||||
wb.WorkbookProtection = &xlsxWorkbookProtection{
|
||||
LockStructure: opts.LockStructure,
|
||||
LockWindows: opts.LockWindows,
|
||||
}
|
||||
if opts.Password != "" {
|
||||
if opts.AlgorithmName == "" {
|
||||
opts.AlgorithmName = "SHA-512"
|
||||
}
|
||||
hashValue, saltValue, err := genISOPasswdHash(opts.Password, opts.AlgorithmName, "", int(workbookProtectionSpinCount))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wb.WorkbookProtection.WorkbookAlgorithmName = opts.AlgorithmName
|
||||
wb.WorkbookProtection.WorkbookSaltValue = saltValue
|
||||
wb.WorkbookProtection.WorkbookHashValue = hashValue
|
||||
wb.WorkbookProtection.WorkbookSpinCount = int(workbookProtectionSpinCount)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnprotectWorkbook provides a function to remove protection for workbook,
|
||||
// specified the optional password parameter to remove workbook protection with
|
||||
// password verification.
|
||||
func (f *File) UnprotectWorkbook(password ...string) error {
|
||||
wb, err := f.workbookReader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// password verification
|
||||
if len(password) > 0 {
|
||||
if wb.WorkbookProtection == nil {
|
||||
return ErrUnprotectWorkbook
|
||||
}
|
||||
if wb.WorkbookProtection.WorkbookAlgorithmName != "" {
|
||||
// check with given salt value
|
||||
hashValue, _, err := genISOPasswdHash(password[0], wb.WorkbookProtection.WorkbookAlgorithmName, wb.WorkbookProtection.WorkbookSaltValue, wb.WorkbookProtection.WorkbookSpinCount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if wb.WorkbookProtection.WorkbookHashValue != hashValue {
|
||||
return ErrUnprotectWorkbookPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
wb.WorkbookProtection = nil
|
||||
return err
|
||||
}
|
||||
|
||||
// setWorkbook update workbook property of the spreadsheet. Maximum 31
|
||||
// characters are allowed in sheet title.
|
||||
func (f *File) setWorkbook(name string, sheetID, rid int) {
|
||||
wb, _ := f.workbookReader()
|
||||
wb.Sheets.Sheet = append(wb.Sheets.Sheet, xlsxSheet{
|
||||
Name: name,
|
||||
SheetID: sheetID,
|
||||
ID: "rId" + strconv.Itoa(rid),
|
||||
})
|
||||
}
|
||||
|
||||
// getWorkbookPath provides a function to get the path of the workbook.xml in
|
||||
// the spreadsheet.
|
||||
func (f *File) getWorkbookPath() (path string) {
|
||||
if rels, _ := f.relsReader("_rels/.rels"); rels != nil {
|
||||
rels.Lock()
|
||||
defer rels.Unlock()
|
||||
for _, rel := range rels.Relationships {
|
||||
if rel.Type == SourceRelationshipOfficeDocument {
|
||||
path = strings.TrimPrefix(rel.Target, "/")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// getWorkbookRelsPath provides a function to get the path of the workbook.xml.rels
|
||||
// in the spreadsheet.
|
||||
func (f *File) getWorkbookRelsPath() (path string) {
|
||||
wbPath := f.getWorkbookPath()
|
||||
wbDir := filepath.Dir(wbPath)
|
||||
if wbDir == "." {
|
||||
path = "_rels/" + filepath.Base(wbPath) + ".rels"
|
||||
return
|
||||
}
|
||||
path = strings.TrimPrefix(filepath.Dir(wbPath)+"/_rels/"+filepath.Base(wbPath)+".rels", "/")
|
||||
return
|
||||
}
|
||||
|
||||
// workbookReader provides a function to get the pointer to the workbook.xml
|
||||
// structure after deserialization.
|
||||
func (f *File) workbookReader() (*xlsxWorkbook, error) {
|
||||
var err error
|
||||
if f.WorkBook == nil {
|
||||
wbPath := f.getWorkbookPath()
|
||||
f.WorkBook = new(xlsxWorkbook)
|
||||
if _, ok := f.xmlAttr[wbPath]; !ok {
|
||||
d := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(wbPath))))
|
||||
f.xmlAttr[wbPath] = append(f.xmlAttr[wbPath], getRootElement(d)...)
|
||||
f.addNameSpaces(wbPath, SourceRelationship)
|
||||
}
|
||||
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(wbPath)))).
|
||||
Decode(f.WorkBook); err != nil && err != io.EOF {
|
||||
return f.WorkBook, err
|
||||
}
|
||||
}
|
||||
return f.WorkBook, err
|
||||
}
|
||||
|
||||
// workBookWriter provides a function to save workbook.xml after serialize
|
||||
// structure.
|
||||
func (f *File) workBookWriter() {
|
||||
if f.WorkBook != nil {
|
||||
if f.WorkBook.DecodeAlternateContent != nil {
|
||||
f.WorkBook.AlternateContent = &xlsxAlternateContent{
|
||||
Content: f.WorkBook.DecodeAlternateContent.Content,
|
||||
XMLNSMC: SourceRelationshipCompatibility.Value,
|
||||
}
|
||||
}
|
||||
f.WorkBook.DecodeAlternateContent = nil
|
||||
output, _ := xml.Marshal(f.WorkBook)
|
||||
f.saveFileList(f.getWorkbookPath(), replaceRelationshipsBytes(f.replaceNameSpaceBytes(f.getWorkbookPath(), output)))
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// AppProperties directly maps the document application properties.
|
||||
type AppProperties struct {
|
||||
Application string
|
||||
ScaleCrop bool
|
||||
DocSecurity int
|
||||
Company string
|
||||
LinksUpToDate bool
|
||||
HyperlinksChanged bool
|
||||
AppVersion string
|
||||
}
|
||||
|
||||
// xlsxProperties specifies to an OOXML document properties such as the
|
||||
// template used, the number of pages and words, and the application name and
|
||||
// version.
|
||||
type xlsxProperties struct {
|
||||
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/officeDocument/2006/extended-properties Properties"`
|
||||
Vt string `xml:"xmlns:vt,attr"`
|
||||
Template string `xml:",omitempty"`
|
||||
Manager string `xml:",omitempty"`
|
||||
Company string `xml:",omitempty"`
|
||||
Pages int `xml:",omitempty"`
|
||||
Words int `xml:",omitempty"`
|
||||
Characters int `xml:",omitempty"`
|
||||
PresentationFormat string `xml:",omitempty"`
|
||||
Lines int `xml:",omitempty"`
|
||||
Paragraphs int `xml:",omitempty"`
|
||||
Slides int `xml:",omitempty"`
|
||||
Notes int `xml:",omitempty"`
|
||||
TotalTime int `xml:",omitempty"`
|
||||
HiddenSlides int `xml:",omitempty"`
|
||||
MMClips int `xml:",omitempty"`
|
||||
ScaleCrop bool `xml:",omitempty"`
|
||||
HeadingPairs *xlsxVectorVariant
|
||||
TitlesOfParts *xlsxVectorLpstr
|
||||
LinksUpToDate bool `xml:",omitempty"`
|
||||
CharactersWithSpaces int `xml:",omitempty"`
|
||||
SharedDoc bool `xml:",omitempty"`
|
||||
HyperlinkBase string `xml:",omitempty"`
|
||||
HLinks *xlsxVectorVariant
|
||||
HyperlinksChanged bool `xml:",omitempty"`
|
||||
DigSig *xlsxDigSig
|
||||
Application string `xml:",omitempty"`
|
||||
AppVersion string `xml:",omitempty"`
|
||||
DocSecurity int `xml:",omitempty"`
|
||||
}
|
||||
|
||||
// xlsxVectorVariant specifies the set of hyperlinks that were in this
|
||||
// document when last saved.
|
||||
type xlsxVectorVariant struct {
|
||||
Content string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
type xlsxVectorLpstr struct {
|
||||
Content string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
// xlsxDigSig contains the signature of a digitally signed document.
|
||||
type xlsxDigSig struct {
|
||||
Content string `xml:",innerxml"`
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// xlsxCalcChain directly maps the calcChain element. This element represents the root of the calculation chain.
|
||||
type xlsxCalcChain struct {
|
||||
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main calcChain"`
|
||||
C []xlsxCalcChainC `xml:"c"`
|
||||
}
|
||||
|
||||
// xlsxCalcChainC directly maps the c element.
|
||||
//
|
||||
// Attributes | Attributes
|
||||
// --------------------------+----------------------------------------------------------
|
||||
// a (Array) | A Boolean flag indicating whether the cell's formula
|
||||
// | is an array formula. True if this cell's formula is
|
||||
// | an array formula, false otherwise. If there is a
|
||||
// | conflict between this attribute and the t attribute
|
||||
// | of the f element (§18.3.1.40), the t attribute takes
|
||||
// | precedence. The possible values for this attribute
|
||||
// | are defined by the W3C XML Schema boolean datatype.
|
||||
// |
|
||||
// i (Sheet Id) | A sheet Id of a sheet the cell belongs to. If this is
|
||||
// | omitted, it is assumed to be the same as the i value
|
||||
// | of the previous cell.The possible values for this
|
||||
// | attribute are defined by the W3C XML Schema int datatype.
|
||||
// |
|
||||
// l (New Dependency Level) | A Boolean flag indicating that the cell's formula
|
||||
// | starts a new dependency level. True if the formula
|
||||
// | starts a new dependency level, false otherwise.
|
||||
// | Starting a new dependency level means that all
|
||||
// | concurrent calculations, and child calculations, shall
|
||||
// | be completed - and the cells have new values - before
|
||||
// | the calc chain can continue. In other words, this
|
||||
// | dependency level might depend on levels that came before
|
||||
// | it, and any later dependency levels might depend on
|
||||
// | this level; but not later dependency levels can have
|
||||
// | any calculations started until this dependency level
|
||||
// | completes.The possible values for this attribute are
|
||||
// | defined by the W3C XML Schema boolean datatype.
|
||||
// |
|
||||
// r (Cell Reference) | An A-1 style reference to a cell.The possible values
|
||||
// | for this attribute are defined by the ST_CellRef
|
||||
// | simple type (§18.18.7).
|
||||
// |
|
||||
// s (Child Chain) | A Boolean flag indicating whether the cell's formula
|
||||
// | is on a child chain. True if this cell is part of a
|
||||
// | child chain, false otherwise. If this is omitted, it
|
||||
// | is assumed to be the same as the s value of the
|
||||
// | previous cell .A child chain is a list of calculations
|
||||
// | that occur which depend on the parent to the chain.
|
||||
// | There shall not be cross dependencies between child
|
||||
// | chains. Child chains are not the same as dependency
|
||||
// | levels - a child chain and its parent are all on the
|
||||
// | same dependency level. Child chains are series of
|
||||
// | calculations that can be independently farmed out to
|
||||
// | other threads or processors.The possible values for
|
||||
// | this attribute is defined by the W3C XML Schema
|
||||
// | boolean datatype.
|
||||
// |
|
||||
// t (New Thread) | A Boolean flag indicating whether the cell's formula
|
||||
// | starts a new thread. True if the cell's formula starts
|
||||
// | a new thread, false otherwise.The possible values for
|
||||
// | this attribute is defined by the W3C XML Schema
|
||||
// | boolean datatype.
|
||||
type xlsxCalcChainC struct {
|
||||
R string `xml:"r,attr"`
|
||||
I int `xml:"i,attr"`
|
||||
L bool `xml:"l,attr,omitempty"`
|
||||
S bool `xml:"s,attr,omitempty"`
|
||||
T bool `xml:"t,attr,omitempty"`
|
||||
A bool `xml:"a,attr,omitempty"`
|
||||
}
|
||||
+611
@@ -0,0 +1,611 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// xlsxChartSpace directly maps the chartSpace element. The chart namespace in
|
||||
// DrawingML is for representing visualizations of numeric data with column
|
||||
// charts, pie charts, scatter charts, or other types of charts.
|
||||
type xlsxChartSpace struct {
|
||||
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/chart chartSpace"`
|
||||
XMLNSa string `xml:"xmlns:a,attr"`
|
||||
Date1904 *attrValBool `xml:"date1904"`
|
||||
Lang *attrValString `xml:"lang"`
|
||||
RoundedCorners *attrValBool `xml:"roundedCorners"`
|
||||
Chart cChart `xml:"chart"`
|
||||
SpPr *cSpPr `xml:"spPr"`
|
||||
TxPr *cTxPr `xml:"txPr"`
|
||||
PrintSettings *cPrintSettings `xml:"printSettings"`
|
||||
}
|
||||
|
||||
// cThicknessSpPr directly maps the element that specifies the thickness of
|
||||
// the walls or floor as a percentage of the largest dimension of the plot
|
||||
// volume and SpPr element.
|
||||
type cThicknessSpPr struct {
|
||||
Thickness *attrValInt `xml:"thickness"`
|
||||
SpPr *cSpPr `xml:"spPr"`
|
||||
}
|
||||
|
||||
// cChart (Chart) directly maps the chart element. This element specifies a
|
||||
// title.
|
||||
type cChart struct {
|
||||
Title *cTitle `xml:"title"`
|
||||
AutoTitleDeleted *cAutoTitleDeleted `xml:"autoTitleDeleted"`
|
||||
View3D *cView3D `xml:"view3D"`
|
||||
Floor *cThicknessSpPr `xml:"floor"`
|
||||
SideWall *cThicknessSpPr `xml:"sideWall"`
|
||||
BackWall *cThicknessSpPr `xml:"backWall"`
|
||||
PlotArea *cPlotArea `xml:"plotArea"`
|
||||
Legend *cLegend `xml:"legend"`
|
||||
PlotVisOnly *attrValBool `xml:"plotVisOnly"`
|
||||
DispBlanksAs *attrValString `xml:"dispBlanksAs"`
|
||||
ShowDLblsOverMax *attrValBool `xml:"showDLblsOverMax"`
|
||||
}
|
||||
|
||||
// cTitle (Title) directly maps the title element. This element specifies a
|
||||
// title.
|
||||
type cTitle struct {
|
||||
Tx cTx `xml:"tx,omitempty"`
|
||||
Layout string `xml:"layout,omitempty"`
|
||||
Overlay *attrValBool `xml:"overlay"`
|
||||
SpPr cSpPr `xml:"spPr,omitempty"`
|
||||
TxPr cTxPr `xml:"txPr,omitempty"`
|
||||
}
|
||||
|
||||
// cTx (Chart Text) directly maps the tx element. This element specifies text
|
||||
// to use on a chart, including rich text formatting.
|
||||
type cTx struct {
|
||||
StrRef *cStrRef `xml:"strRef"`
|
||||
Rich *cRich `xml:"rich,omitempty"`
|
||||
}
|
||||
|
||||
// cRich (Rich Text) directly maps the rich element. This element contains a
|
||||
// string with rich text formatting.
|
||||
type cRich struct {
|
||||
BodyPr aBodyPr `xml:"a:bodyPr,omitempty"`
|
||||
LstStyle string `xml:"a:lstStyle,omitempty"`
|
||||
P aP `xml:"a:p"`
|
||||
}
|
||||
|
||||
// aBodyPr (Body Properties) directly maps the a:bodyPr element. This element
|
||||
// defines the body properties for the text body within a shape.
|
||||
type aBodyPr struct {
|
||||
Anchor string `xml:"anchor,attr,omitempty"`
|
||||
AnchorCtr bool `xml:"anchorCtr,attr"`
|
||||
Rot int `xml:"rot,attr"`
|
||||
BIns float64 `xml:"bIns,attr,omitempty"`
|
||||
CompatLnSpc bool `xml:"compatLnSpc,attr,omitempty"`
|
||||
ForceAA bool `xml:"forceAA,attr,omitempty"`
|
||||
FromWordArt bool `xml:"fromWordArt,attr,omitempty"`
|
||||
HorzOverflow string `xml:"horzOverflow,attr,omitempty"`
|
||||
LIns float64 `xml:"lIns,attr,omitempty"`
|
||||
NumCol int `xml:"numCol,attr,omitempty"`
|
||||
RIns float64 `xml:"rIns,attr,omitempty"`
|
||||
RtlCol bool `xml:"rtlCol,attr,omitempty"`
|
||||
SpcCol int `xml:"spcCol,attr,omitempty"`
|
||||
SpcFirstLastPara bool `xml:"spcFirstLastPara,attr"`
|
||||
TIns float64 `xml:"tIns,attr,omitempty"`
|
||||
Upright bool `xml:"upright,attr,omitempty"`
|
||||
Vert string `xml:"vert,attr,omitempty"`
|
||||
VertOverflow string `xml:"vertOverflow,attr,omitempty"`
|
||||
Wrap string `xml:"wrap,attr,omitempty"`
|
||||
}
|
||||
|
||||
// aP (Paragraph) directly maps the a:p element. This element specifies a
|
||||
// paragraph of content in the document.
|
||||
type aP struct {
|
||||
PPr *aPPr `xml:"a:pPr"`
|
||||
R *aR `xml:"a:r"`
|
||||
EndParaRPr *aEndParaRPr `xml:"a:endParaRPr"`
|
||||
}
|
||||
|
||||
// aPPr (Paragraph Properties) directly maps the a:pPr element. This element
|
||||
// specifies a set of paragraph properties which shall be applied to the
|
||||
// contents of the parent paragraph after all style/numbering/table properties
|
||||
// have been applied to the text. These properties are defined as direct
|
||||
// formatting, since they are directly applied to the paragraph and supersede
|
||||
// any formatting from styles.
|
||||
type aPPr struct {
|
||||
DefRPr aRPr `xml:"a:defRPr"`
|
||||
}
|
||||
|
||||
// aSolidFill (Solid Fill) directly maps the solidFill element. This element
|
||||
// specifies a solid color fill. The shape is filled entirely with the specified
|
||||
// color.
|
||||
type aSolidFill struct {
|
||||
SchemeClr *aSchemeClr `xml:"a:schemeClr"`
|
||||
SrgbClr *attrValString `xml:"a:srgbClr"`
|
||||
}
|
||||
|
||||
// aSchemeClr (Scheme Color) directly maps the a:schemeClr element. This
|
||||
// element specifies a color bound to a user's theme. As with all elements which
|
||||
// define a color, it is possible to apply a list of color transforms to the
|
||||
// base color defined.
|
||||
type aSchemeClr struct {
|
||||
Val string `xml:"val,attr,omitempty"`
|
||||
LumMod *attrValInt `xml:"a:lumMod"`
|
||||
LumOff *attrValInt `xml:"a:lumOff"`
|
||||
}
|
||||
|
||||
// attrValInt directly maps the val element with integer data type as an
|
||||
// attribute.
|
||||
type attrValInt struct {
|
||||
Val *int `xml:"val,attr"`
|
||||
}
|
||||
|
||||
// attrValFloat directly maps the val element with float64 data type as an
|
||||
// attribute.
|
||||
type attrValFloat struct {
|
||||
Val *float64 `xml:"val,attr"`
|
||||
}
|
||||
|
||||
// attrValBool directly maps the val element with boolean data type as an
|
||||
// attribute.
|
||||
type attrValBool struct {
|
||||
Val *bool `xml:"val,attr"`
|
||||
}
|
||||
|
||||
// attrValString directly maps the val element with string data type as an
|
||||
// attribute.
|
||||
type attrValString struct {
|
||||
Val *string `xml:"val,attr"`
|
||||
}
|
||||
|
||||
// aCs directly maps the a:cs element.
|
||||
type aCs struct {
|
||||
Typeface string `xml:"typeface,attr"`
|
||||
}
|
||||
|
||||
// aEa directly maps the a:ea element.
|
||||
type aEa struct {
|
||||
Typeface string `xml:"typeface,attr"`
|
||||
}
|
||||
|
||||
type xlsxCTTextFont struct {
|
||||
Typeface string `xml:"typeface,attr"`
|
||||
Panose string `xml:"panose,attr,omitempty"`
|
||||
PitchFamily string `xml:"pitchFamily,attr,omitempty"`
|
||||
Charset string `xml:"Charset,attr,omitempty"`
|
||||
}
|
||||
|
||||
// aR directly maps the a:r element.
|
||||
type aR struct {
|
||||
RPr aRPr `xml:"a:rPr,omitempty"`
|
||||
T string `xml:"a:t,omitempty"`
|
||||
}
|
||||
|
||||
// aRPr (Run Properties) directly maps the rPr element. This element
|
||||
// specifies a set of run properties which shall be applied to the contents of
|
||||
// the parent run after all style formatting has been applied to the text. These
|
||||
// properties are defined as direct formatting, since they are directly applied
|
||||
// to the run and supersede any formatting from styles.
|
||||
type aRPr struct {
|
||||
AltLang string `xml:"altLang,attr,omitempty"`
|
||||
B bool `xml:"b,attr"`
|
||||
Baseline int `xml:"baseline,attr"`
|
||||
Bmk string `xml:"bmk,attr,omitempty"`
|
||||
Cap string `xml:"cap,attr,omitempty"`
|
||||
Dirty bool `xml:"dirty,attr,omitempty"`
|
||||
Err bool `xml:"err,attr,omitempty"`
|
||||
I bool `xml:"i,attr"`
|
||||
Kern int `xml:"kern,attr"`
|
||||
Kumimoji bool `xml:"kumimoji,attr,omitempty"`
|
||||
Lang string `xml:"lang,attr,omitempty"`
|
||||
NoProof bool `xml:"noProof,attr,omitempty"`
|
||||
NormalizeH bool `xml:"normalizeH,attr,omitempty"`
|
||||
SmtClean bool `xml:"smtClean,attr,omitempty"`
|
||||
SmtID uint64 `xml:"smtId,attr,omitempty"`
|
||||
Spc int `xml:"spc,attr"`
|
||||
Strike string `xml:"strike,attr,omitempty"`
|
||||
Sz float64 `xml:"sz,attr,omitempty"`
|
||||
U string `xml:"u,attr,omitempty"`
|
||||
SolidFill *aSolidFill `xml:"a:solidFill"`
|
||||
Latin *xlsxCTTextFont `xml:"a:latin"`
|
||||
Ea *aEa `xml:"a:ea"`
|
||||
Cs *aCs `xml:"a:cs"`
|
||||
}
|
||||
|
||||
// cSpPr (Shape Properties) directly maps the spPr element. This element
|
||||
// specifies the visual shape properties that can be applied to a shape. These
|
||||
// properties include the shape fill, outline, geometry, effects, and 3D
|
||||
// orientation.
|
||||
type cSpPr struct {
|
||||
NoFill *string `xml:"a:noFill"`
|
||||
SolidFill *aSolidFill `xml:"a:solidFill"`
|
||||
Ln *aLn `xml:"a:ln"`
|
||||
Sp3D *aSp3D `xml:"a:sp3d"`
|
||||
EffectLst *string `xml:"a:effectLst"`
|
||||
}
|
||||
|
||||
// aSp3D (3-D Shape Properties) directly maps the a:sp3d element. This element
|
||||
// defines the 3D properties associated with a particular shape in DrawingML.
|
||||
// The 3D properties which can be applied to a shape are top and bottom bevels,
|
||||
// a contour and an extrusion.
|
||||
type aSp3D struct {
|
||||
ContourW int `xml:"contourW,attr"`
|
||||
ContourClr *aContourClr `xml:"a:contourClr"`
|
||||
}
|
||||
|
||||
// aContourClr (Contour Color) directly maps the a:contourClr element. This
|
||||
// element defines the color for the contour on a shape. The contour of a shape
|
||||
// is a solid filled line which surrounds the outer edges of the shape.
|
||||
type aContourClr struct {
|
||||
SchemeClr *aSchemeClr `xml:"a:schemeClr"`
|
||||
}
|
||||
|
||||
// aLn (Outline) directly maps the a:ln element. This element specifies an
|
||||
// outline style that can be applied to a number of different objects such as
|
||||
// shapes and text. The line allows for the specifying of many different types
|
||||
// of outlines including even line dashes and bevels.
|
||||
type aLn struct {
|
||||
Algn string `xml:"algn,attr,omitempty"`
|
||||
Cap string `xml:"cap,attr,omitempty"`
|
||||
Cmpd string `xml:"cmpd,attr,omitempty"`
|
||||
W int `xml:"w,attr,omitempty"`
|
||||
NoFill string `xml:"a:noFill,omitempty"`
|
||||
Round string `xml:"a:round,omitempty"`
|
||||
SolidFill *aSolidFill `xml:"a:solidFill"`
|
||||
}
|
||||
|
||||
// cTxPr (Text Properties) directly maps the txPr element. This element
|
||||
// specifies text formatting. The lstStyle element is not supported.
|
||||
type cTxPr struct {
|
||||
BodyPr aBodyPr `xml:"a:bodyPr,omitempty"`
|
||||
LstStyle string `xml:"a:lstStyle,omitempty"`
|
||||
P aP `xml:"a:p,omitempty"`
|
||||
}
|
||||
|
||||
// aEndParaRPr (End Paragraph Run Properties) directly maps the a:endParaRPr
|
||||
// element. This element specifies the text run properties that are to be used
|
||||
// if another run is inserted after the last run specified. This effectively
|
||||
// saves the run property state so that it can be applied when the user enters
|
||||
// additional text. If this element is omitted, then the application can
|
||||
// determine which default properties to apply. It is recommended that this
|
||||
// element be specified at the end of the list of text runs within the paragraph
|
||||
// so that an orderly list is maintained.
|
||||
type aEndParaRPr struct {
|
||||
Lang string `xml:"lang,attr"`
|
||||
AltLang string `xml:"altLang,attr,omitempty"`
|
||||
Sz int `xml:"sz,attr,omitempty"`
|
||||
}
|
||||
|
||||
// cAutoTitleDeleted (Auto Title Is Deleted) directly maps the
|
||||
// autoTitleDeleted element. This element specifies the title shall not be
|
||||
// shown for this chart.
|
||||
type cAutoTitleDeleted struct {
|
||||
Val bool `xml:"val,attr"`
|
||||
}
|
||||
|
||||
// cView3D (View In 3D) directly maps the view3D element. This element
|
||||
// specifies the 3-D view of the chart.
|
||||
type cView3D struct {
|
||||
RotX *attrValInt `xml:"rotX"`
|
||||
RotY *attrValInt `xml:"rotY"`
|
||||
RAngAx *attrValInt `xml:"rAngAx"`
|
||||
DepthPercent *attrValInt `xml:"depthPercent"`
|
||||
Perspective *attrValInt `xml:"perspective"`
|
||||
ExtLst *xlsxExtLst `xml:"extLst"`
|
||||
}
|
||||
|
||||
// cPlotArea directly maps the plotArea element. This element specifies the
|
||||
// plot area of the chart.
|
||||
type cPlotArea struct {
|
||||
Layout *string `xml:"layout"`
|
||||
AreaChart *cCharts `xml:"areaChart"`
|
||||
Area3DChart *cCharts `xml:"area3DChart"`
|
||||
BarChart *cCharts `xml:"barChart"`
|
||||
Bar3DChart *cCharts `xml:"bar3DChart"`
|
||||
BubbleChart *cCharts `xml:"bubbleChart"`
|
||||
DoughnutChart *cCharts `xml:"doughnutChart"`
|
||||
LineChart *cCharts `xml:"lineChart"`
|
||||
Line3DChart *cCharts `xml:"line3DChart"`
|
||||
PieChart *cCharts `xml:"pieChart"`
|
||||
Pie3DChart *cCharts `xml:"pie3DChart"`
|
||||
OfPieChart *cCharts `xml:"ofPieChart"`
|
||||
RadarChart *cCharts `xml:"radarChart"`
|
||||
ScatterChart *cCharts `xml:"scatterChart"`
|
||||
Surface3DChart *cCharts `xml:"surface3DChart"`
|
||||
SurfaceChart *cCharts `xml:"surfaceChart"`
|
||||
CatAx []*cAxs `xml:"catAx"`
|
||||
ValAx []*cAxs `xml:"valAx"`
|
||||
SerAx []*cAxs `xml:"serAx"`
|
||||
SpPr *cSpPr `xml:"spPr"`
|
||||
}
|
||||
|
||||
// cCharts specifies the common element of the chart.
|
||||
type cCharts struct {
|
||||
BarDir *attrValString `xml:"barDir"`
|
||||
BubbleScale *attrValFloat `xml:"bubbleScale"`
|
||||
Grouping *attrValString `xml:"grouping"`
|
||||
RadarStyle *attrValString `xml:"radarStyle"`
|
||||
ScatterStyle *attrValString `xml:"scatterStyle"`
|
||||
OfPieType *attrValString `xml:"ofPieType"`
|
||||
VaryColors *attrValBool `xml:"varyColors"`
|
||||
Wireframe *attrValBool `xml:"wireframe"`
|
||||
Ser *[]cSer `xml:"ser"`
|
||||
SplitPos *attrValInt `xml:"splitPos"`
|
||||
SerLines *attrValString `xml:"serLines"`
|
||||
DLbls *cDLbls `xml:"dLbls"`
|
||||
Shape *attrValString `xml:"shape"`
|
||||
HoleSize *attrValInt `xml:"holeSize"`
|
||||
Smooth *attrValBool `xml:"smooth"`
|
||||
Overlap *attrValInt `xml:"overlap"`
|
||||
AxID []*attrValInt `xml:"axId"`
|
||||
}
|
||||
|
||||
// cAxs directly maps the catAx and valAx element.
|
||||
type cAxs struct {
|
||||
AxID *attrValInt `xml:"axId"`
|
||||
Scaling *cScaling `xml:"scaling"`
|
||||
Delete *attrValBool `xml:"delete"`
|
||||
AxPos *attrValString `xml:"axPos"`
|
||||
MajorGridlines *cChartLines `xml:"majorGridlines"`
|
||||
MinorGridlines *cChartLines `xml:"minorGridlines"`
|
||||
NumFmt *cNumFmt `xml:"numFmt"`
|
||||
MajorTickMark *attrValString `xml:"majorTickMark"`
|
||||
MinorTickMark *attrValString `xml:"minorTickMark"`
|
||||
TickLblPos *attrValString `xml:"tickLblPos"`
|
||||
SpPr *cSpPr `xml:"spPr"`
|
||||
TxPr *cTxPr `xml:"txPr"`
|
||||
CrossAx *attrValInt `xml:"crossAx"`
|
||||
Crosses *attrValString `xml:"crosses"`
|
||||
CrossBetween *attrValString `xml:"crossBetween"`
|
||||
MajorUnit *attrValFloat `xml:"majorUnit"`
|
||||
MinorUnit *attrValFloat `xml:"minorUnit"`
|
||||
Auto *attrValBool `xml:"auto"`
|
||||
LblAlgn *attrValString `xml:"lblAlgn"`
|
||||
LblOffset *attrValInt `xml:"lblOffset"`
|
||||
TickLblSkip *attrValInt `xml:"tickLblSkip"`
|
||||
TickMarkSkip *attrValInt `xml:"tickMarkSkip"`
|
||||
NoMultiLvlLbl *attrValBool `xml:"noMultiLvlLbl"`
|
||||
}
|
||||
|
||||
// cChartLines directly maps the chart lines content model.
|
||||
type cChartLines struct {
|
||||
SpPr *cSpPr `xml:"spPr"`
|
||||
}
|
||||
|
||||
// cScaling directly maps the scaling element. This element contains
|
||||
// additional axis settings.
|
||||
type cScaling struct {
|
||||
LogBase *attrValFloat `xml:"logBase"`
|
||||
Orientation *attrValString `xml:"orientation"`
|
||||
Max *attrValFloat `xml:"max"`
|
||||
Min *attrValFloat `xml:"min"`
|
||||
}
|
||||
|
||||
// cNumFmt (Numbering Format) directly maps the numFmt element. This element
|
||||
// specifies number formatting for the parent element.
|
||||
type cNumFmt struct {
|
||||
FormatCode string `xml:"formatCode,attr"`
|
||||
SourceLinked bool `xml:"sourceLinked,attr"`
|
||||
}
|
||||
|
||||
// cSer directly maps the ser element. This element specifies a series on a
|
||||
// chart.
|
||||
type cSer struct {
|
||||
IDx *attrValInt `xml:"idx"`
|
||||
Order *attrValInt `xml:"order"`
|
||||
Tx *cTx `xml:"tx"`
|
||||
SpPr *cSpPr `xml:"spPr"`
|
||||
DPt []*cDPt `xml:"dPt"`
|
||||
DLbls *cDLbls `xml:"dLbls"`
|
||||
Marker *cMarker `xml:"marker"`
|
||||
InvertIfNegative *attrValBool `xml:"invertIfNegative"`
|
||||
Cat *cCat `xml:"cat"`
|
||||
Val *cVal `xml:"val"`
|
||||
XVal *cCat `xml:"xVal"`
|
||||
YVal *cVal `xml:"yVal"`
|
||||
Smooth *attrValBool `xml:"smooth"`
|
||||
BubbleSize *cVal `xml:"bubbleSize"`
|
||||
Bubble3D *attrValBool `xml:"bubble3D"`
|
||||
}
|
||||
|
||||
// cMarker (Marker) directly maps the marker element. This element specifies a
|
||||
// data marker.
|
||||
type cMarker struct {
|
||||
Symbol *attrValString `xml:"symbol"`
|
||||
Size *attrValInt `xml:"size"`
|
||||
SpPr *cSpPr `xml:"spPr"`
|
||||
}
|
||||
|
||||
// cDPt (Data Point) directly maps the dPt element. This element specifies a
|
||||
// single data point.
|
||||
type cDPt struct {
|
||||
IDx *attrValInt `xml:"idx"`
|
||||
Bubble3D *attrValBool `xml:"bubble3D"`
|
||||
SpPr *cSpPr `xml:"spPr"`
|
||||
}
|
||||
|
||||
// cCat (Category Axis Data) directly maps the cat element. This element
|
||||
// specifies the data used for the category axis.
|
||||
type cCat struct {
|
||||
StrRef *cStrRef `xml:"strRef"`
|
||||
}
|
||||
|
||||
// cStrRef (String Reference) directly maps the strRef element. This element
|
||||
// specifies a reference to data for a single data label or title with a cache
|
||||
// of the last values used.
|
||||
type cStrRef struct {
|
||||
F string `xml:"f"`
|
||||
StrCache *cStrCache `xml:"strCache"`
|
||||
}
|
||||
|
||||
// cStrCache (String Cache) directly maps the strCache element. This element
|
||||
// specifies the last string data used for a chart.
|
||||
type cStrCache struct {
|
||||
Pt []*cPt `xml:"pt"`
|
||||
PtCount *attrValInt `xml:"ptCount"`
|
||||
}
|
||||
|
||||
// cPt directly maps the pt element. This element specifies data for a
|
||||
// particular data point.
|
||||
type cPt struct {
|
||||
IDx int `xml:"idx,attr"`
|
||||
V *string `xml:"v"`
|
||||
}
|
||||
|
||||
// cVal directly maps the val element. This element specifies the data values
|
||||
// which shall be used to define the location of data markers on a chart.
|
||||
type cVal struct {
|
||||
NumRef *cNumRef `xml:"numRef"`
|
||||
}
|
||||
|
||||
// cNumRef directly maps the numRef element. This element specifies a
|
||||
// reference to numeric data with a cache of the last values used.
|
||||
type cNumRef struct {
|
||||
F string `xml:"f"`
|
||||
NumCache *cNumCache `xml:"numCache"`
|
||||
}
|
||||
|
||||
// cNumCache directly maps the numCache element. This element specifies the
|
||||
// last data shown on the chart for a series.
|
||||
type cNumCache struct {
|
||||
FormatCode string `xml:"formatCode"`
|
||||
Pt []*cPt `xml:"pt"`
|
||||
PtCount *attrValInt `xml:"ptCount"`
|
||||
}
|
||||
|
||||
// cDLbls (Data Labels) directly maps the dLbls element. This element serves
|
||||
// as a root element that specifies the settings for the data labels for an
|
||||
// entire series or the entire chart. It contains child elements that specify
|
||||
// the specific formatting and positioning settings.
|
||||
type cDLbls struct {
|
||||
NumFmt *cNumFmt `xml:"numFmt"`
|
||||
ShowLegendKey *attrValBool `xml:"showLegendKey"`
|
||||
ShowVal *attrValBool `xml:"showVal"`
|
||||
ShowCatName *attrValBool `xml:"showCatName"`
|
||||
ShowSerName *attrValBool `xml:"showSerName"`
|
||||
ShowPercent *attrValBool `xml:"showPercent"`
|
||||
ShowBubbleSize *attrValBool `xml:"showBubbleSize"`
|
||||
ShowLeaderLines *attrValBool `xml:"showLeaderLines"`
|
||||
}
|
||||
|
||||
// cLegend (Legend) directly maps the legend element. This element specifies
|
||||
// the legend.
|
||||
type cLegend struct {
|
||||
Layout *string `xml:"layout"`
|
||||
LegendPos *attrValString `xml:"legendPos"`
|
||||
Overlay *attrValBool `xml:"overlay"`
|
||||
SpPr *cSpPr `xml:"spPr"`
|
||||
TxPr *cTxPr `xml:"txPr"`
|
||||
}
|
||||
|
||||
// cPrintSettings directly maps the printSettings element. This element
|
||||
// specifies the print settings for the chart.
|
||||
type cPrintSettings struct {
|
||||
HeaderFooter *string `xml:"headerFooter"`
|
||||
PageMargins *cPageMargins `xml:"pageMargins"`
|
||||
PageSetup *string `xml:"pageSetup"`
|
||||
}
|
||||
|
||||
// cPageMargins directly maps the pageMargins element. This element specifies
|
||||
// the page margins for a chart.
|
||||
type cPageMargins struct {
|
||||
B float64 `xml:"b,attr"`
|
||||
Footer float64 `xml:"footer,attr"`
|
||||
Header float64 `xml:"header,attr"`
|
||||
L float64 `xml:"l,attr"`
|
||||
R float64 `xml:"r,attr"`
|
||||
T float64 `xml:"t,attr"`
|
||||
}
|
||||
|
||||
// ChartNumFmt directly maps the number format settings of the chart.
|
||||
type ChartNumFmt struct {
|
||||
CustomNumFmt string
|
||||
SourceLinked bool
|
||||
}
|
||||
|
||||
// ChartAxis directly maps the format settings of the chart axis.
|
||||
type ChartAxis struct {
|
||||
None bool
|
||||
MajorGridLines bool
|
||||
MinorGridLines bool
|
||||
MajorUnit float64
|
||||
TickLabelSkip int
|
||||
ReverseOrder bool
|
||||
Maximum *float64
|
||||
Minimum *float64
|
||||
Font Font
|
||||
LogBase float64
|
||||
NumFmt ChartNumFmt
|
||||
}
|
||||
|
||||
// ChartDimension directly maps the dimension of the chart.
|
||||
type ChartDimension struct {
|
||||
Width uint
|
||||
Height uint
|
||||
}
|
||||
|
||||
// ChartPlotArea directly maps the format settings of the plot area.
|
||||
type ChartPlotArea struct {
|
||||
SecondPlotValues int
|
||||
ShowBubbleSize bool
|
||||
ShowCatName bool
|
||||
ShowLeaderLines bool
|
||||
ShowPercent bool
|
||||
ShowSerName bool
|
||||
ShowVal bool
|
||||
NumFmt ChartNumFmt
|
||||
}
|
||||
|
||||
// Chart directly maps the format settings of the chart.
|
||||
type Chart struct {
|
||||
Type ChartType
|
||||
Series []ChartSeries
|
||||
Format GraphicOptions
|
||||
Dimension ChartDimension
|
||||
Legend ChartLegend
|
||||
Title ChartTitle
|
||||
VaryColors *bool
|
||||
XAxis ChartAxis
|
||||
YAxis ChartAxis
|
||||
PlotArea ChartPlotArea
|
||||
ShowBlanksAs string
|
||||
HoleSize int
|
||||
order int
|
||||
}
|
||||
|
||||
// ChartLegend directly maps the format settings of the chart legend.
|
||||
type ChartLegend struct {
|
||||
Position string
|
||||
ShowLegendKey bool
|
||||
}
|
||||
|
||||
// ChartMarker directly maps the format settings of the chart marker.
|
||||
type ChartMarker struct {
|
||||
Symbol string
|
||||
Size int
|
||||
}
|
||||
|
||||
// ChartLine directly maps the format settings of the chart line.
|
||||
type ChartLine struct {
|
||||
Smooth bool
|
||||
Width float64
|
||||
}
|
||||
|
||||
// ChartSeries directly maps the format settings of the chart series.
|
||||
type ChartSeries struct {
|
||||
Name string
|
||||
Categories string
|
||||
Sizes string
|
||||
Values string
|
||||
Fill Fill
|
||||
Line ChartLine
|
||||
Marker ChartMarker
|
||||
}
|
||||
|
||||
// ChartTitle directly maps the format settings of the chart title.
|
||||
type ChartTitle struct {
|
||||
Name string
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// struct code generated by github.com/xuri/xgen
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// xlsxChartsheet directly maps the chartsheet element of Chartsheet Parts in
|
||||
// a SpreadsheetML document.
|
||||
type xlsxChartsheet struct {
|
||||
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main chartsheet"`
|
||||
SheetPr *xlsxChartsheetPr `xml:"sheetPr"`
|
||||
SheetViews *xlsxChartsheetViews `xml:"sheetViews"`
|
||||
SheetProtection *xlsxChartsheetProtection `xml:"sheetProtection"`
|
||||
CustomSheetViews *xlsxCustomChartsheetViews `xml:"customSheetViews"`
|
||||
PageMargins *xlsxPageMargins `xml:"pageMargins"`
|
||||
PageSetup *xlsxPageSetUp `xml:"pageSetup"`
|
||||
HeaderFooter *xlsxHeaderFooter `xml:"headerFooter"`
|
||||
Drawing *xlsxDrawing `xml:"drawing"`
|
||||
DrawingHF *xlsxDrawingHF `xml:"drawingHF"`
|
||||
Picture *xlsxPicture `xml:"picture"`
|
||||
WebPublishItems *xlsxInnerXML `xml:"webPublishItems"`
|
||||
ExtLst *xlsxExtLst `xml:"extLst"`
|
||||
}
|
||||
|
||||
// xlsxChartsheetPr specifies chart sheet properties.
|
||||
type xlsxChartsheetPr struct {
|
||||
XMLName xml.Name `xml:"sheetPr"`
|
||||
PublishedAttr bool `xml:"published,attr,omitempty"`
|
||||
CodeNameAttr string `xml:"codeName,attr,omitempty"`
|
||||
TabColor *xlsxTabColor `xml:"tabColor"`
|
||||
}
|
||||
|
||||
// xlsxChartsheetViews specifies chart sheet views.
|
||||
type xlsxChartsheetViews struct {
|
||||
XMLName xml.Name `xml:"sheetViews"`
|
||||
SheetView []*xlsxChartsheetView `xml:"sheetView"`
|
||||
ExtLst []*xlsxExtLst `xml:"extLst"`
|
||||
}
|
||||
|
||||
// xlsxChartsheetView defines custom view properties for chart sheets.
|
||||
type xlsxChartsheetView struct {
|
||||
XMLName xml.Name `xml:"sheetView"`
|
||||
TabSelectedAttr bool `xml:"tabSelected,attr,omitempty"`
|
||||
ZoomScaleAttr uint32 `xml:"zoomScale,attr,omitempty"`
|
||||
WorkbookViewIDAttr uint32 `xml:"workbookViewId,attr"`
|
||||
ZoomToFitAttr bool `xml:"zoomToFit,attr,omitempty"`
|
||||
ExtLst []*xlsxExtLst `xml:"extLst"`
|
||||
}
|
||||
|
||||
// xlsxChartsheetProtection collection expresses the chart sheet protection
|
||||
// options to enforce when the chart sheet is protected.
|
||||
type xlsxChartsheetProtection struct {
|
||||
XMLName xml.Name `xml:"sheetProtection"`
|
||||
AlgorithmNameAttr string `xml:"algorithmName,attr,omitempty"`
|
||||
HashValueAttr []byte `xml:"hashValue,attr,omitempty"`
|
||||
SaltValueAttr []byte `xml:"saltValue,attr,omitempty"`
|
||||
SpinCountAttr uint32 `xml:"spinCount,attr,omitempty"`
|
||||
ContentAttr bool `xml:"content,attr,omitempty"`
|
||||
ObjectsAttr bool `xml:"objects,attr,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxCustomChartsheetViews collection of custom Chart Sheet View
|
||||
// information.
|
||||
type xlsxCustomChartsheetViews struct {
|
||||
XMLName xml.Name `xml:"customSheetViews"`
|
||||
CustomSheetView []*xlsxCustomChartsheetView `xml:"customSheetView"`
|
||||
}
|
||||
|
||||
// xlsxCustomChartsheetView defines custom view properties for chart sheets.
|
||||
type xlsxCustomChartsheetView struct {
|
||||
XMLName xml.Name `xml:"customSheetView"`
|
||||
GUIDAttr string `xml:"guid,attr"`
|
||||
ScaleAttr uint32 `xml:"scale,attr,omitempty"`
|
||||
StateAttr string `xml:"state,attr,omitempty"`
|
||||
ZoomToFitAttr bool `xml:"zoomToFit,attr,omitempty"`
|
||||
PageMargins []*xlsxPageMargins `xml:"pageMargins"`
|
||||
PageSetup []*xlsxPageSetUp `xml:"pageSetup"`
|
||||
HeaderFooter []*xlsxHeaderFooter `xml:"headerFooter"`
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// xlsxComments directly maps the comments element from the namespace
|
||||
// http://schemas.openxmlformats.org/spreadsheetml/2006/main. A comment is a
|
||||
// rich text note that is attached to and associated with a cell, separate from
|
||||
// other cell content. Comment content is stored separate from the cell, and is
|
||||
// displayed in a drawing object (like a text box) that is separate from, but
|
||||
// associated with, a cell. Comments are used as reminders, such as noting how a
|
||||
// complex formula works, or to provide feedback to other users. Comments can
|
||||
// also be used to explain assumptions made in a formula or to call out
|
||||
// something special about the cell.
|
||||
type xlsxComments struct {
|
||||
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main comments"`
|
||||
Authors xlsxAuthor `xml:"authors"`
|
||||
CommentList xlsxCommentList `xml:"commentList"`
|
||||
}
|
||||
|
||||
// xlsxAuthor directly maps the author element. This element holds a string
|
||||
// representing the name of a single author of comments. Every comment shall
|
||||
// have an author. The maximum length of the author string is an implementation
|
||||
// detail, but a good guideline is 255 chars.
|
||||
type xlsxAuthor struct {
|
||||
Author []string `xml:"author"`
|
||||
}
|
||||
|
||||
// xlsxCommentList (List of Comments) directly maps the xlsxCommentList element.
|
||||
// This element is a container that holds a list of comments for the sheet.
|
||||
type xlsxCommentList struct {
|
||||
Comment []xlsxComment `xml:"comment"`
|
||||
}
|
||||
|
||||
// xlsxComment directly maps the comment element. This element represents a
|
||||
// single user entered comment. Each comment shall have an author and can
|
||||
// optionally contain richly formatted text.
|
||||
type xlsxComment struct {
|
||||
Ref string `xml:"ref,attr"`
|
||||
AuthorID int `xml:"authorId,attr"`
|
||||
Text xlsxText `xml:"text"`
|
||||
}
|
||||
|
||||
// xlsxText directly maps the text element. This element contains rich text
|
||||
// which represents the text of a comment. The maximum length for this text is a
|
||||
// spreadsheet application implementation detail. A recommended guideline is
|
||||
// 32767 chars.
|
||||
type xlsxText struct {
|
||||
T *string `xml:"t"`
|
||||
R []xlsxR `xml:"r"`
|
||||
RPh *xlsxPhoneticRun `xml:"rPh"`
|
||||
PhoneticPr *xlsxPhoneticPr `xml:"phoneticPr"`
|
||||
}
|
||||
|
||||
// xlsxPhoneticRun element represents a run of text which displays a phonetic
|
||||
// hint for this String Item (si). Phonetic hints are used to give information
|
||||
// about the pronunciation of an East Asian language. The hints are displayed
|
||||
// as text within the spreadsheet cells across the top portion of the cell.
|
||||
type xlsxPhoneticRun struct {
|
||||
Sb uint32 `xml:"sb,attr"`
|
||||
Eb uint32 `xml:"eb,attr"`
|
||||
T string `xml:"t"`
|
||||
}
|
||||
|
||||
// Comment directly maps the comment information.
|
||||
type Comment struct {
|
||||
Author string
|
||||
AuthorID int
|
||||
Cell string
|
||||
Text string
|
||||
Runs []RichTextRun
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// xlsxTypes directly maps the types' element of content types for relationship
|
||||
// parts, it takes a Multipurpose Internet Mail Extension (MIME) media type as a
|
||||
// value.
|
||||
type xlsxTypes struct {
|
||||
sync.Mutex
|
||||
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/content-types Types"`
|
||||
Defaults []xlsxDefault `xml:"Default"`
|
||||
Overrides []xlsxOverride `xml:"Override"`
|
||||
}
|
||||
|
||||
// xlsxOverride directly maps the override element in the namespace
|
||||
// http://schemas.openxmlformats.org/package/2006/content-types
|
||||
type xlsxOverride struct {
|
||||
PartName string `xml:",attr"`
|
||||
ContentType string `xml:",attr"`
|
||||
}
|
||||
|
||||
// xlsxDefault directly maps the default element in the namespace
|
||||
// http://schemas.openxmlformats.org/package/2006/content-types
|
||||
type xlsxDefault struct {
|
||||
Extension string `xml:",attr"`
|
||||
ContentType string `xml:",attr"`
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// DocProperties directly maps the document core properties.
|
||||
type DocProperties struct {
|
||||
Category string
|
||||
ContentStatus string
|
||||
Created string
|
||||
Creator string
|
||||
Description string
|
||||
Identifier string
|
||||
Keywords string
|
||||
LastModifiedBy string
|
||||
Modified string
|
||||
Revision string
|
||||
Subject string
|
||||
Title string
|
||||
Language string
|
||||
Version string
|
||||
}
|
||||
|
||||
// decodeDcTerms directly maps the DCMI metadata terms for the coreProperties.
|
||||
type decodeDcTerms struct {
|
||||
Text string `xml:",chardata"`
|
||||
Type string `xml:"http://www.w3.org/2001/XMLSchema-instance type,attr"`
|
||||
}
|
||||
|
||||
// decodeCoreProperties directly maps the root element for a part of this
|
||||
// content type shall coreProperties. In order to solve the problem that the
|
||||
// label structure is changed after serialization and deserialization, two
|
||||
// different structures are defined. decodeCoreProperties just for
|
||||
// deserialization.
|
||||
type decodeCoreProperties struct {
|
||||
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/metadata/core-properties coreProperties"`
|
||||
Title string `xml:"http://purl.org/dc/elements/1.1/ title,omitempty"`
|
||||
Subject string `xml:"http://purl.org/dc/elements/1.1/ subject,omitempty"`
|
||||
Creator string `xml:"http://purl.org/dc/elements/1.1/ creator"`
|
||||
Keywords string `xml:"keywords,omitempty"`
|
||||
Description string `xml:"http://purl.org/dc/elements/1.1/ description,omitempty"`
|
||||
LastModifiedBy string `xml:"lastModifiedBy"`
|
||||
Language string `xml:"http://purl.org/dc/elements/1.1/ language,omitempty"`
|
||||
Identifier string `xml:"http://purl.org/dc/elements/1.1/ identifier,omitempty"`
|
||||
Revision string `xml:"revision,omitempty"`
|
||||
Created *decodeDcTerms `xml:"http://purl.org/dc/terms/ created"`
|
||||
Modified *decodeDcTerms `xml:"http://purl.org/dc/terms/ modified"`
|
||||
ContentStatus string `xml:"contentStatus,omitempty"`
|
||||
Category string `xml:"category,omitempty"`
|
||||
Version string `xml:"version,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxDcTerms directly maps the DCMI metadata terms for the coreProperties.
|
||||
type xlsxDcTerms struct {
|
||||
Text string `xml:",chardata"`
|
||||
Type string `xml:"xsi:type,attr"`
|
||||
}
|
||||
|
||||
// xlsxCoreProperties directly maps the root element for a part of this
|
||||
// content type shall coreProperties.
|
||||
type xlsxCoreProperties struct {
|
||||
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/metadata/core-properties coreProperties"`
|
||||
Dc string `xml:"xmlns:dc,attr"`
|
||||
Dcterms string `xml:"xmlns:dcterms,attr"`
|
||||
Dcmitype string `xml:"xmlns:dcmitype,attr"`
|
||||
XSI string `xml:"xmlns:xsi,attr"`
|
||||
Title string `xml:"dc:title,omitempty"`
|
||||
Subject string `xml:"dc:subject,omitempty"`
|
||||
Creator string `xml:"dc:creator"`
|
||||
Keywords string `xml:"keywords,omitempty"`
|
||||
Description string `xml:"dc:description,omitempty"`
|
||||
LastModifiedBy string `xml:"lastModifiedBy"`
|
||||
Language string `xml:"dc:language,omitempty"`
|
||||
Identifier string `xml:"dc:identifier,omitempty"`
|
||||
Revision string `xml:"revision,omitempty"`
|
||||
Created *xlsxDcTerms `xml:"dcterms:created"`
|
||||
Modified *xlsxDcTerms `xml:"dcterms:modified"`
|
||||
ContentStatus string `xml:"contentStatus,omitempty"`
|
||||
Category string `xml:"category,omitempty"`
|
||||
Version string `xml:"version,omitempty"`
|
||||
}
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// decodeCellAnchor directly maps the oneCellAnchor (One Cell Anchor Shape
|
||||
// Size) and twoCellAnchor (Two Cell Anchor Shape Size). This element
|
||||
// specifies a two cell anchor placeholder for a group, a shape, or a drawing
|
||||
// element. It moves with cells and its extents are in EMU units.
|
||||
type decodeCellAnchor struct {
|
||||
EditAs string `xml:"editAs,attr,omitempty"`
|
||||
From *decodeFrom `xml:"from"`
|
||||
To *decodeTo `xml:"to"`
|
||||
Sp *decodeSp `xml:"sp"`
|
||||
ClientData *decodeClientData `xml:"clientData"`
|
||||
Content string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
// xdrSp (Shape) directly maps the sp element. This element specifies the
|
||||
// existence of a single shape. A shape can either be a preset or a custom
|
||||
// geometry, defined using the SpreadsheetDrawingML framework. In addition to
|
||||
// a geometry each shape can have both visual and non-visual properties
|
||||
// attached. Text and corresponding styling information can also be attached
|
||||
// to a shape. This shape is specified along with all other shapes within
|
||||
// either the shape tree or group shape elements.
|
||||
type decodeSp struct {
|
||||
NvSpPr *decodeNvSpPr `xml:"nvSpPr"`
|
||||
SpPr *decodeSpPr `xml:"spPr"`
|
||||
}
|
||||
|
||||
// decodeSp (Non-Visual Properties for a Shape) directly maps the nvSpPr
|
||||
// element. This element specifies all non-visual properties for a shape. This
|
||||
// element is a container for the non-visual identification properties, shape
|
||||
// properties and application properties that are to be associated with a
|
||||
// shape. This allows for additional information that does not affect the
|
||||
// appearance of the shape to be stored.
|
||||
type decodeNvSpPr struct {
|
||||
CNvPr *decodeCNvPr `xml:"cNvPr"`
|
||||
ExtLst *decodeExt `xml:"extLst"`
|
||||
CNvSpPr *decodeCNvSpPr `xml:"cNvSpPr"`
|
||||
}
|
||||
|
||||
// decodeCNvSpPr (Connection Non-Visual Shape Properties) directly maps the
|
||||
// cNvSpPr element. This element specifies the set of non-visual properties
|
||||
// for a connection shape. These properties specify all data about the
|
||||
// connection shape which do not affect its display within a spreadsheet.
|
||||
type decodeCNvSpPr struct {
|
||||
TxBox bool `xml:"txBox,attr"`
|
||||
}
|
||||
|
||||
// decodeWsDr directly maps the root element for a part of this content type
|
||||
// shall wsDr. In order to solve the problem that the label structure is
|
||||
// changed after serialization and deserialization, two different structures
|
||||
// are defined. decodeWsDr just for deserialization.
|
||||
type decodeWsDr struct {
|
||||
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing wsDr,omitempty"`
|
||||
A string `xml:"xmlns a,attr"`
|
||||
Xdr string `xml:"xmlns xdr,attr"`
|
||||
R string `xml:"xmlns r,attr"`
|
||||
AlternateContent []*xlsxInnerXML `xml:"http://schemas.openxmlformats.org/markup-compatibility/2006 AlternateContent"`
|
||||
OneCellAnchor []*decodeCellAnchor `xml:"oneCellAnchor,omitempty"`
|
||||
TwoCellAnchor []*decodeCellAnchor `xml:"twoCellAnchor,omitempty"`
|
||||
}
|
||||
|
||||
// decodeTwoCellAnchor directly maps the oneCellAnchor (One Cell Anchor Shape
|
||||
// Size) and twoCellAnchor (Two Cell Anchor Shape Size). This element
|
||||
// specifies a two cell anchor placeholder for a group, a shape, or a drawing
|
||||
// element. It moves with cells and its extents are in EMU units.
|
||||
type decodeTwoCellAnchor struct {
|
||||
From *decodeFrom `xml:"from"`
|
||||
To *decodeTo `xml:"to"`
|
||||
Pic *decodePic `xml:"pic"`
|
||||
ClientData *decodeClientData `xml:"clientData"`
|
||||
}
|
||||
|
||||
// decodeCNvPr directly maps the cNvPr (Non-Visual Drawing Properties). This
|
||||
// element specifies non-visual canvas properties. This allows for additional
|
||||
// information that does not affect the appearance of the picture to be
|
||||
// stored.
|
||||
type decodeCNvPr struct {
|
||||
ID int `xml:"id,attr"`
|
||||
Name string `xml:"name,attr"`
|
||||
Descr string `xml:"descr,attr"`
|
||||
Title string `xml:"title,attr,omitempty"`
|
||||
}
|
||||
|
||||
// decodePicLocks directly maps the picLocks (Picture Locks). This element
|
||||
// specifies all locking properties for a graphic frame. These properties
|
||||
// inform the generating application about specific properties that have been
|
||||
// previously locked and thus should not be changed.
|
||||
type decodePicLocks struct {
|
||||
NoAdjustHandles bool `xml:"noAdjustHandles,attr,omitempty"`
|
||||
NoChangeArrowheads bool `xml:"noChangeArrowheads,attr,omitempty"`
|
||||
NoChangeAspect bool `xml:"noChangeAspect,attr"`
|
||||
NoChangeShapeType bool `xml:"noChangeShapeType,attr,omitempty"`
|
||||
NoCrop bool `xml:"noCrop,attr,omitempty"`
|
||||
NoEditPoints bool `xml:"noEditPoints,attr,omitempty"`
|
||||
NoGrp bool `xml:"noGrp,attr,omitempty"`
|
||||
NoMove bool `xml:"noMove,attr,omitempty"`
|
||||
NoResize bool `xml:"noResize,attr,omitempty"`
|
||||
NoRot bool `xml:"noRot,attr,omitempty"`
|
||||
NoSelect bool `xml:"noSelect,attr,omitempty"`
|
||||
}
|
||||
|
||||
// decodeBlip element specifies the existence of an image (binary large image
|
||||
// or picture) and contains a reference to the image data.
|
||||
type decodeBlip struct {
|
||||
Embed string `xml:"embed,attr"`
|
||||
Cstate string `xml:"cstate,attr,omitempty"`
|
||||
R string `xml:"r,attr"`
|
||||
}
|
||||
|
||||
// decodeStretch directly maps the stretch element. This element specifies
|
||||
// that a BLIP should be stretched to fill the target rectangle. The other
|
||||
// option is a tile where a BLIP is tiled to fill the available area.
|
||||
type decodeStretch struct {
|
||||
FillRect string `xml:"fillRect"`
|
||||
}
|
||||
|
||||
// decodeOff directly maps the colOff and rowOff element. This element is used
|
||||
// to specify the column offset within a cell.
|
||||
type decodeOff struct {
|
||||
X int `xml:"x,attr"`
|
||||
Y int `xml:"y,attr"`
|
||||
}
|
||||
|
||||
// decodeExt directly maps the ext element.
|
||||
type decodeExt struct {
|
||||
Cx int `xml:"cx,attr"`
|
||||
Cy int `xml:"cy,attr"`
|
||||
}
|
||||
|
||||
// decodePrstGeom directly maps the prstGeom (Preset geometry). This element
|
||||
// specifies when a preset geometric shape should be used instead of a custom
|
||||
// geometric shape. The generating application should be able to render all
|
||||
// preset geometries enumerated in the ST_ShapeType list.
|
||||
type decodePrstGeom struct {
|
||||
Prst string `xml:"prst,attr"`
|
||||
}
|
||||
|
||||
// decodeXfrm directly maps the xfrm (2D Transform for Graphic Frame). This
|
||||
// element specifies the transform to be applied to the corresponding graphic
|
||||
// frame. This transformation is applied to the graphic frame just as it would
|
||||
// be for a shape or group shape.
|
||||
type decodeXfrm struct {
|
||||
Off decodeOff `xml:"off"`
|
||||
Ext decodeExt `xml:"ext"`
|
||||
}
|
||||
|
||||
// decodeCNvPicPr directly maps the cNvPicPr (Non-Visual Picture Drawing
|
||||
// Properties). This element specifies the non-visual properties for the picture
|
||||
// canvas. These properties are to be used by the generating application to
|
||||
// determine how certain properties are to be changed for the picture object in
|
||||
// question.
|
||||
type decodeCNvPicPr struct {
|
||||
PicLocks decodePicLocks `xml:"picLocks"`
|
||||
}
|
||||
|
||||
// directly maps the nvPicPr (Non-Visual Properties for a Picture). This
|
||||
// element specifies all non-visual properties for a picture. This element is
|
||||
// a container for the non-visual identification properties, shape properties
|
||||
// and application properties that are to be associated with a picture. This
|
||||
// allows for additional information that does not affect the appearance of
|
||||
// the picture to be stored.
|
||||
type decodeNvPicPr struct {
|
||||
CNvPr decodeCNvPr `xml:"cNvPr"`
|
||||
CNvPicPr decodeCNvPicPr `xml:"cNvPicPr"`
|
||||
}
|
||||
|
||||
// decodeBlipFill directly maps the blipFill (Picture Fill). This element
|
||||
// specifies the kind of picture fill that the picture object has. Because a
|
||||
// picture has a picture fill already by default, it is possible to have two
|
||||
// fills specified for a picture object.
|
||||
type decodeBlipFill struct {
|
||||
Blip decodeBlip `xml:"blip"`
|
||||
Stretch decodeStretch `xml:"stretch"`
|
||||
}
|
||||
|
||||
// decodeSpPr directly maps the spPr (Shape Properties). This element
|
||||
// specifies the visual shape properties that can be applied to a picture.
|
||||
// These are the same properties that are allowed to describe the visual
|
||||
// properties of a shape but are used here to describe the visual appearance
|
||||
// of a picture within a document.
|
||||
type decodeSpPr struct {
|
||||
Xfrm decodeXfrm `xml:"xfrm"`
|
||||
PrstGeom decodePrstGeom `xml:"prstGeom"`
|
||||
}
|
||||
|
||||
// decodePic elements encompass the definition of pictures within the
|
||||
// DrawingML framework. While pictures are in many ways very similar to shapes
|
||||
// they have specific properties that are unique in order to optimize for
|
||||
// picture- specific scenarios.
|
||||
type decodePic struct {
|
||||
NvPicPr decodeNvPicPr `xml:"nvPicPr"`
|
||||
BlipFill decodeBlipFill `xml:"blipFill"`
|
||||
SpPr decodeSpPr `xml:"spPr"`
|
||||
}
|
||||
|
||||
// decodeFrom specifies the starting anchor.
|
||||
type decodeFrom struct {
|
||||
Col int `xml:"col"`
|
||||
ColOff int `xml:"colOff"`
|
||||
Row int `xml:"row"`
|
||||
RowOff int `xml:"rowOff"`
|
||||
}
|
||||
|
||||
// decodeTo directly specifies the ending anchor.
|
||||
type decodeTo struct {
|
||||
Col int `xml:"col"`
|
||||
ColOff int `xml:"colOff"`
|
||||
Row int `xml:"row"`
|
||||
RowOff int `xml:"rowOff"`
|
||||
}
|
||||
|
||||
// decodeClientData directly maps the clientData element. An empty element
|
||||
// which specifies (via attributes) certain properties related to printing and
|
||||
// selection of the drawing object. The fLocksWithSheet attribute (either true
|
||||
// or false) determines whether to disable selection when the sheet is
|
||||
// protected, and fPrintsWithSheet attribute (either true or false) determines
|
||||
// whether the object is printed when the sheet is printed.
|
||||
type decodeClientData struct {
|
||||
FLocksWithSheet bool `xml:"fLocksWithSheet,attr"`
|
||||
FPrintsWithSheet bool `xml:"fPrintsWithSheet,attr"`
|
||||
}
|
||||
+630
@@ -0,0 +1,630 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Source relationship and namespace list, associated prefixes and schema in which it was
|
||||
// introduced.
|
||||
var (
|
||||
NameSpaceDocumentPropertiesVariantTypes = xml.Attr{Name: xml.Name{Local: "vt", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"}
|
||||
NameSpaceDrawing2016SVG = xml.Attr{Name: xml.Name{Local: "asvg", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2016/SVG/main"}
|
||||
NameSpaceDrawingML = xml.Attr{Name: xml.Name{Local: "a", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/drawingml/2006/main"}
|
||||
NameSpaceDrawingMLChart = xml.Attr{Name: xml.Name{Local: "c", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/drawingml/2006/chart"}
|
||||
NameSpaceDrawingMLSpreadSheet = xml.Attr{Name: xml.Name{Local: "xdr", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"}
|
||||
NameSpaceMacExcel2008Main = xml.Attr{Name: xml.Name{Local: "mx", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/mac/excel/2008/main"}
|
||||
NameSpaceSpreadSheet = xml.Attr{Name: xml.Name{Local: "xmlns"}, Value: "http://schemas.openxmlformats.org/spreadsheetml/2006/main"}
|
||||
NameSpaceSpreadSheetExcel2006Main = xml.Attr{Name: xml.Name{Local: "xne", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/excel/2006/main"}
|
||||
NameSpaceSpreadSheetX14 = xml.Attr{Name: xml.Name{Local: "x14", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"}
|
||||
NameSpaceSpreadSheetX15 = xml.Attr{Name: xml.Name{Local: "x15", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/spreadsheetml/2010/11/main"}
|
||||
SourceRelationship = xml.Attr{Name: xml.Name{Local: "r", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/officeDocument/2006/relationships"}
|
||||
SourceRelationshipChart20070802 = xml.Attr{Name: xml.Name{Local: "c14", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2007/8/2/chart"}
|
||||
SourceRelationshipChart2014 = xml.Attr{Name: xml.Name{Local: "c16", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2014/chart"}
|
||||
SourceRelationshipChart201506 = xml.Attr{Name: xml.Name{Local: "c16r2", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2015/06/chart"}
|
||||
SourceRelationshipCompatibility = xml.Attr{Name: xml.Name{Local: "mc", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/markup-compatibility/2006"}
|
||||
)
|
||||
|
||||
// Source relationship and namespace.
|
||||
const (
|
||||
ContentTypeAddinMacro = "application/vnd.ms-excel.addin.macroEnabled.main+xml"
|
||||
ContentTypeDrawing = "application/vnd.openxmlformats-officedocument.drawing+xml"
|
||||
ContentTypeDrawingML = "application/vnd.openxmlformats-officedocument.drawingml.chart+xml"
|
||||
ContentTypeMacro = "application/vnd.ms-excel.sheet.macroEnabled.main+xml"
|
||||
ContentTypeSheetML = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"
|
||||
ContentTypeSpreadSheetMLChartsheet = "application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml"
|
||||
ContentTypeSpreadSheetMLComments = "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"
|
||||
ContentTypeSpreadSheetMLPivotCacheDefinition = "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml"
|
||||
ContentTypeSpreadSheetMLPivotTable = "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml"
|
||||
ContentTypeSpreadSheetMLSharedStrings = "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"
|
||||
ContentTypeSpreadSheetMLTable = "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml"
|
||||
ContentTypeSpreadSheetMLWorksheet = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"
|
||||
ContentTypeTemplate = "application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml"
|
||||
ContentTypeTemplateMacro = "application/vnd.ms-excel.template.macroEnabled.main+xml"
|
||||
ContentTypeVBA = "application/vnd.ms-office.vbaProject"
|
||||
ContentTypeVML = "application/vnd.openxmlformats-officedocument.vmlDrawing"
|
||||
NameSpaceDrawingMLMain = "http://schemas.openxmlformats.org/drawingml/2006/main"
|
||||
NameSpaceDublinCore = "http://purl.org/dc/elements/1.1/"
|
||||
NameSpaceDublinCoreMetadataInitiative = "http://purl.org/dc/dcmitype/"
|
||||
NameSpaceDublinCoreTerms = "http://purl.org/dc/terms/"
|
||||
NameSpaceExtendedProperties = "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"
|
||||
NameSpaceXML = "http://www.w3.org/XML/1998/namespace"
|
||||
NameSpaceXMLSchemaInstance = "http://www.w3.org/2001/XMLSchema-instance"
|
||||
SourceRelationshipChart = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart"
|
||||
SourceRelationshipChartsheet = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartsheet"
|
||||
SourceRelationshipComments = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments"
|
||||
SourceRelationshipDialogsheet = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/dialogsheet"
|
||||
SourceRelationshipDrawingML = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing"
|
||||
SourceRelationshipDrawingVML = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing"
|
||||
SourceRelationshipExtendProperties = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties"
|
||||
SourceRelationshipHyperLink = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
|
||||
SourceRelationshipImage = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"
|
||||
SourceRelationshipOfficeDocument = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"
|
||||
SourceRelationshipPivotCache = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition"
|
||||
SourceRelationshipPivotTable = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable"
|
||||
SourceRelationshipSharedStrings = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"
|
||||
SourceRelationshipTable = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table"
|
||||
SourceRelationshipVBAProject = "http://schemas.microsoft.com/office/2006/relationships/vbaProject"
|
||||
SourceRelationshipWorkSheet = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"
|
||||
StrictNameSpaceDocumentPropertiesVariantTypes = "http://purl.oclc.org/ooxml/officeDocument/docPropsVTypes"
|
||||
StrictNameSpaceDrawingMLMain = "http://purl.oclc.org/ooxml/drawingml/main"
|
||||
StrictNameSpaceExtendedProperties = "http://purl.oclc.org/ooxml/officeDocument/extendedProperties"
|
||||
StrictNameSpaceSpreadSheet = "http://purl.oclc.org/ooxml/spreadsheetml/main"
|
||||
StrictSourceRelationship = "http://purl.oclc.org/ooxml/officeDocument/relationships"
|
||||
StrictSourceRelationshipChart = "http://purl.oclc.org/ooxml/officeDocument/relationships/chart"
|
||||
StrictSourceRelationshipComments = "http://purl.oclc.org/ooxml/officeDocument/relationships/comments"
|
||||
StrictSourceRelationshipExtendProperties = "http://purl.oclc.org/ooxml/officeDocument/relationships/extendedProperties"
|
||||
StrictSourceRelationshipImage = "http://purl.oclc.org/ooxml/officeDocument/relationships/image"
|
||||
StrictSourceRelationshipOfficeDocument = "http://purl.oclc.org/ooxml/officeDocument/relationships/officeDocument"
|
||||
// ExtURIConditionalFormattings is the extLst child element
|
||||
// ([ISO/IEC29500-1:2016] section 18.2.10) of the worksheet element
|
||||
// ([ISO/IEC29500-1:2016] section 18.3.1.99) is extended by the addition of
|
||||
// new child ext elements ([ISO/IEC29500-1:2016] section 18.2.7)
|
||||
ExtURIConditionalFormattingRuleID = "{B025F937-C7B1-47D3-B67F-A62EFF666E3E}"
|
||||
ExtURIConditionalFormattings = "{78C0D931-6437-407d-A8EE-F0AAD7539E65}"
|
||||
ExtURIDataValidations = "{CCE6A557-97BC-4B89-ADB6-D9C93CAAB3DF}"
|
||||
ExtURIDrawingBlip = "{28A0092B-C50C-407E-A947-70E740481C1C}"
|
||||
ExtURIIgnoredErrors = "{01252117-D84E-4E92-8308-4BE1C098FCBB}"
|
||||
ExtURIMacExcelMX = "{64002731-A6B0-56B0-2670-7721B7C09600}"
|
||||
ExtURIProtectedRanges = "{FC87AEE6-9EDD-4A0A-B7FB-166176984837}"
|
||||
ExtURISlicerCachesListX14 = "{BBE1A952-AA13-448e-AADC-164F8A28A991}"
|
||||
ExtURISlicerListX14 = "{A8765BA9-456A-4DAB-B4F3-ACF838C121DE}"
|
||||
ExtURISlicerListX15 = "{3A4CF648-6AED-40f4-86FF-DC5316D8AED3}"
|
||||
ExtURISparklineGroups = "{05C60535-1F16-4fd2-B633-F4F36F0B64E0}"
|
||||
ExtURISVG = "{96DAC541-7B7A-43D3-8B79-37D633B846F1}"
|
||||
ExtURITimelineRefs = "{7E03D99C-DC04-49d9-9315-930204A7B6E9}"
|
||||
ExtURIWebExtensions = "{F7C9EE02-42E1-4005-9D12-6889AFFD525C}"
|
||||
)
|
||||
|
||||
// extensionURIPriority is the priority of URI in the extension lists.
|
||||
var extensionURIPriority = []string{
|
||||
ExtURIConditionalFormattings,
|
||||
ExtURIDataValidations,
|
||||
ExtURISparklineGroups,
|
||||
ExtURISlicerListX14,
|
||||
ExtURIProtectedRanges,
|
||||
ExtURIIgnoredErrors,
|
||||
ExtURIWebExtensions,
|
||||
ExtURITimelineRefs,
|
||||
}
|
||||
|
||||
// Excel specifications and limits
|
||||
const (
|
||||
MaxCellStyles = 65430
|
||||
MaxColumns = 16384
|
||||
MaxColumnWidth = 255
|
||||
MaxFieldLength = 255
|
||||
MaxFilePathLength = 207
|
||||
MaxFontFamilyLength = 31
|
||||
MaxFontSize = 409
|
||||
MaxRowHeight = 409
|
||||
MaxSheetNameLength = 31
|
||||
MinColumns = 1
|
||||
MinFontSize = 1
|
||||
StreamChunkSize = 1 << 24
|
||||
TotalCellChars = 32767
|
||||
TotalRows = 1048576
|
||||
TotalSheetHyperlinks = 65529
|
||||
UnzipSizeLimit = 1000 << 24
|
||||
// pivotTableVersion should be greater than 3. One or more of the
|
||||
// PivotTables chosen are created in a version of Excel earlier than
|
||||
// Excel 2007 or in compatibility mode. Slicer can only be used with
|
||||
// PivotTables created in Excel 2007 or a newer version of Excel.
|
||||
pivotTableVersion = 3
|
||||
defaultPictureScale = 1.0
|
||||
defaultChartDimensionWidth = 480
|
||||
defaultChartDimensionHeight = 290
|
||||
defaultChartLegendPosition = "bottom"
|
||||
defaultChartShowBlanksAs = "gap"
|
||||
defaultShapeSize = 160
|
||||
defaultShapeLineWidth = 1
|
||||
)
|
||||
|
||||
// ColorMappingType is the type of color transformation.
|
||||
type ColorMappingType byte
|
||||
|
||||
// Color transformation types enumeration.
|
||||
const (
|
||||
ColorMappingTypeLight1 ColorMappingType = iota
|
||||
ColorMappingTypeDark1
|
||||
ColorMappingTypeLight2
|
||||
ColorMappingTypeDark2
|
||||
ColorMappingTypeAccent1
|
||||
ColorMappingTypeAccent2
|
||||
ColorMappingTypeAccent3
|
||||
ColorMappingTypeAccent4
|
||||
ColorMappingTypeAccent5
|
||||
ColorMappingTypeAccent6
|
||||
ColorMappingTypeHyperlink
|
||||
ColorMappingTypeFollowedHyperlink
|
||||
ColorMappingTypeUnset int = -1
|
||||
)
|
||||
|
||||
// IndexedColorMapping is the table of default mappings from indexed color value
|
||||
// to RGB value. Note that 0-7 are redundant of 8-15 to preserve backwards
|
||||
// compatibility. A legacy indexing scheme for colors that is still required
|
||||
// for some records, and for backwards compatibility with legacy formats. This
|
||||
// element contains a sequence of RGB color values that correspond to color
|
||||
// indexes (zero-based). When using the default indexed color palette, the
|
||||
// values are not written out, but instead are implied. When the color palette
|
||||
// has been modified from default, then the entire color palette is written
|
||||
// out.
|
||||
var IndexedColorMapping = []string{
|
||||
"000000", "FFFFFF", "FF0000", "00FF00", "0000FF", "FFFF00", "FF00FF", "00FFFF",
|
||||
"000000", "FFFFFF", "FF0000", "00FF00", "0000FF", "FFFF00", "FF00FF", "00FFFF",
|
||||
"800000", "008000", "000080", "808000", "800080", "008080", "C0C0C0", "808080",
|
||||
"9999FF", "993366", "FFFFCC", "CCFFFF", "660066", "FF8080", "0066CC", "CCCCFF",
|
||||
"000080", "FF00FF", "FFFF00", "00FFFF", "800080", "800000", "008080", "0000FF",
|
||||
"00CCFF", "CCFFFF", "CCFFCC", "FFFF99", "99CCFF", "FF99CC", "CC99FF", "FFCC99",
|
||||
"3366FF", "33CCCC", "99CC00", "FFCC00", "FF9900", "FF6600", "666699", "969696",
|
||||
"003366", "339966", "003300", "333300", "993300", "993366", "333399", "333333",
|
||||
"000000", "FFFFFF",
|
||||
}
|
||||
|
||||
// supportedImageTypes defined supported image types.
|
||||
var supportedImageTypes = map[string]string{
|
||||
".bmp": ".bmp", ".emf": ".emf", ".emz": ".emz", ".gif": ".gif",
|
||||
".jpeg": ".jpeg", ".jpg": ".jpeg", ".png": ".png", ".svg": ".svg",
|
||||
".tif": ".tiff", ".tiff": ".tiff", ".wmf": ".wmf", ".wmz": ".wmz",
|
||||
}
|
||||
|
||||
// supportedContentTypes defined supported file format types.
|
||||
var supportedContentTypes = map[string]string{
|
||||
".xlam": ContentTypeAddinMacro,
|
||||
".xlsm": ContentTypeMacro,
|
||||
".xlsx": ContentTypeSheetML,
|
||||
".xltm": ContentTypeTemplateMacro,
|
||||
".xltx": ContentTypeTemplate,
|
||||
}
|
||||
|
||||
// supportedUnderlineTypes defined supported underline types.
|
||||
var supportedUnderlineTypes = []string{"none", "single", "double"}
|
||||
|
||||
// supportedDrawingUnderlineTypes defined supported underline types in drawing
|
||||
// markup language.
|
||||
var supportedDrawingUnderlineTypes = []string{
|
||||
"none", "words", "sng", "dbl", "heavy", "dotted", "dottedHeavy", "dash", "dashHeavy", "dashLong", "dashLongHeavy", "dotDash", "dotDashHeavy", "dotDotDash", "dotDotDashHeavy", "wavy", "wavyHeavy",
|
||||
"wavyDbl",
|
||||
}
|
||||
|
||||
// xlsxCNvPr directly maps the cNvPr (Non-Visual Drawing Properties). This
|
||||
// element specifies non-visual canvas properties. This allows for additional
|
||||
// information that does not affect the appearance of the picture to be stored.
|
||||
type xlsxCNvPr struct {
|
||||
ID int `xml:"id,attr"`
|
||||
Name string `xml:"name,attr"`
|
||||
Descr string `xml:"descr,attr"`
|
||||
Title string `xml:"title,attr,omitempty"`
|
||||
HlinkClick *xlsxHlinkClick `xml:"a:hlinkClick"`
|
||||
}
|
||||
|
||||
// xlsxHlinkClick (Click Hyperlink) Specifies the on-click hyperlink
|
||||
// information to be applied to a run of text. When the hyperlink text is
|
||||
// clicked the link is fetched.
|
||||
type xlsxHlinkClick struct {
|
||||
R string `xml:"xmlns:r,attr,omitempty"`
|
||||
RID string `xml:"r:id,attr,omitempty"`
|
||||
InvalidURL string `xml:"invalidUrl,attr,omitempty"`
|
||||
Action string `xml:"action,attr,omitempty"`
|
||||
TgtFrame string `xml:"tgtFrame,attr,omitempty"`
|
||||
Tooltip string `xml:"tooltip,attr,omitempty"`
|
||||
History bool `xml:"history,attr,omitempty"`
|
||||
HighlightClick bool `xml:"highlightClick,attr,omitempty"`
|
||||
EndSnd bool `xml:"endSnd,attr,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxPicLocks directly maps the picLocks (Picture Locks). This element
|
||||
// specifies all locking properties for a graphic frame. These properties inform
|
||||
// the generating application about specific properties that have been
|
||||
// previously locked and thus should not be changed.
|
||||
type xlsxPicLocks struct {
|
||||
NoAdjustHandles bool `xml:"noAdjustHandles,attr,omitempty"`
|
||||
NoChangeArrowheads bool `xml:"noChangeArrowheads,attr,omitempty"`
|
||||
NoChangeAspect bool `xml:"noChangeAspect,attr"`
|
||||
NoChangeShapeType bool `xml:"noChangeShapeType,attr,omitempty"`
|
||||
NoCrop bool `xml:"noCrop,attr,omitempty"`
|
||||
NoEditPoints bool `xml:"noEditPoints,attr,omitempty"`
|
||||
NoGrp bool `xml:"noGrp,attr,omitempty"`
|
||||
NoMove bool `xml:"noMove,attr,omitempty"`
|
||||
NoResize bool `xml:"noResize,attr,omitempty"`
|
||||
NoRot bool `xml:"noRot,attr,omitempty"`
|
||||
NoSelect bool `xml:"noSelect,attr,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxBlip element specifies the existence of an image (binary large image or
|
||||
// picture) and contains a reference to the image data.
|
||||
type xlsxBlip struct {
|
||||
Embed string `xml:"r:embed,attr"`
|
||||
Cstate string `xml:"cstate,attr,omitempty"`
|
||||
R string `xml:"xmlns:r,attr"`
|
||||
ExtList *xlsxEGOfficeArtExtensionList `xml:"a:extLst"`
|
||||
}
|
||||
|
||||
// xlsxStretch directly maps the stretch element. This element specifies that a
|
||||
// BLIP should be stretched to fill the target rectangle. The other option is a
|
||||
// tile where a BLIP is tiled to fill the available area.
|
||||
type xlsxStretch struct {
|
||||
FillRect string `xml:"a:fillRect"`
|
||||
}
|
||||
|
||||
// xlsxOff directly maps the colOff and rowOff element. This element is used to
|
||||
// specify the column offset within a cell.
|
||||
type xlsxOff struct {
|
||||
X int `xml:"x,attr"`
|
||||
Y int `xml:"y,attr"`
|
||||
}
|
||||
|
||||
// xlsxExt directly maps the ext element.
|
||||
type xlsxExt struct {
|
||||
Cx int `xml:"cx,attr"`
|
||||
Cy int `xml:"cy,attr"`
|
||||
}
|
||||
|
||||
// xlsxPrstGeom directly maps the prstGeom (Preset geometry). This element
|
||||
// specifies when a preset geometric shape should be used instead of a custom
|
||||
// geometric shape. The generating application should be able to render all
|
||||
// preset geometries enumerated in the ST_ShapeType list.
|
||||
type xlsxPrstGeom struct {
|
||||
Prst string `xml:"prst,attr"`
|
||||
}
|
||||
|
||||
// xlsxXfrm directly maps the xfrm (2D Transform for Graphic Frame). This
|
||||
// element specifies the transform to be applied to the corresponding graphic
|
||||
// frame. This transformation is applied to the graphic frame just as it would
|
||||
// be for a shape or group shape.
|
||||
type xlsxXfrm struct {
|
||||
Off xlsxOff `xml:"a:off"`
|
||||
Ext xlsxExt `xml:"a:ext"`
|
||||
}
|
||||
|
||||
// xlsxCNvPicPr directly maps the cNvPicPr (Non-Visual Picture Drawing
|
||||
// Properties). This element specifies the non-visual properties for the picture
|
||||
// canvas. These properties are to be used by the generating application to
|
||||
// determine how certain properties are to be changed for the picture object in
|
||||
// question.
|
||||
type xlsxCNvPicPr struct {
|
||||
PicLocks xlsxPicLocks `xml:"a:picLocks"`
|
||||
}
|
||||
|
||||
// directly maps the nvPicPr (Non-Visual Properties for a Picture). This element
|
||||
// specifies all non-visual properties for a picture. This element is a
|
||||
// container for the non-visual identification properties, shape properties and
|
||||
// application properties that are to be associated with a picture. This allows
|
||||
// for additional information that does not affect the appearance of the picture
|
||||
// to be stored.
|
||||
type xlsxNvPicPr struct {
|
||||
CNvPr xlsxCNvPr `xml:"xdr:cNvPr"`
|
||||
CNvPicPr xlsxCNvPicPr `xml:"xdr:cNvPicPr"`
|
||||
}
|
||||
|
||||
// xlsxCTSVGBlip specifies a graphic element in Scalable Vector Graphics (SVG)
|
||||
// format.
|
||||
type xlsxCTSVGBlip struct {
|
||||
XMLNSaAVG string `xml:"xmlns:asvg,attr"`
|
||||
Embed string `xml:"r:embed,attr"`
|
||||
Link string `xml:"r:link,attr,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxCTOfficeArtExtension used for future extensibility and is seen elsewhere
|
||||
// throughout the drawing area.
|
||||
type xlsxCTOfficeArtExtension struct {
|
||||
XMLName xml.Name `xml:"a:ext"`
|
||||
URI string `xml:"uri,attr"`
|
||||
SVGBlip xlsxCTSVGBlip `xml:"asvg:svgBlip"`
|
||||
}
|
||||
|
||||
// xlsxEGOfficeArtExtensionList used for future extensibility and is seen
|
||||
// elsewhere throughout the drawing area.
|
||||
type xlsxEGOfficeArtExtensionList struct {
|
||||
Ext []xlsxCTOfficeArtExtension `xml:"ext"`
|
||||
}
|
||||
|
||||
// xlsxBlipFill directly maps the blipFill (Picture Fill). This element
|
||||
// specifies the kind of picture fill that the picture object has. Because a
|
||||
// picture has a picture fill already by default, it is possible to have two
|
||||
// fills specified for a picture object.
|
||||
type xlsxBlipFill struct {
|
||||
Blip xlsxBlip `xml:"a:blip"`
|
||||
Stretch xlsxStretch `xml:"a:stretch"`
|
||||
}
|
||||
|
||||
// xlsxLineProperties specifies the width of a line in EMUs. This simple type
|
||||
// has a minimum value of greater than or equal to 0. This simple type has a
|
||||
// maximum value of less than or equal to 20116800.
|
||||
type xlsxLineProperties struct {
|
||||
W int `xml:"w,attr,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxSpPr directly maps the spPr (Shape Properties). This element specifies
|
||||
// the visual shape properties that can be applied to a picture. These are the
|
||||
// same properties that are allowed to describe the visual properties of a shape
|
||||
// but are used here to describe the visual appearance of a picture within a
|
||||
// document.
|
||||
type xlsxSpPr struct {
|
||||
Xfrm xlsxXfrm `xml:"a:xfrm"`
|
||||
PrstGeom xlsxPrstGeom `xml:"a:prstGeom"`
|
||||
Ln xlsxLineProperties `xml:"a:ln"`
|
||||
}
|
||||
|
||||
// xlsxPic elements encompass the definition of pictures within the DrawingML
|
||||
// framework. While pictures are in many ways very similar to shapes they have
|
||||
// specific properties that are unique in order to optimize for picture-
|
||||
// specific scenarios.
|
||||
type xlsxPic struct {
|
||||
NvPicPr xlsxNvPicPr `xml:"xdr:nvPicPr"`
|
||||
BlipFill xlsxBlipFill `xml:"xdr:blipFill"`
|
||||
SpPr xlsxSpPr `xml:"xdr:spPr"`
|
||||
}
|
||||
|
||||
// xlsxFrom specifies the starting anchor.
|
||||
type xlsxFrom struct {
|
||||
Col int `xml:"xdr:col"`
|
||||
ColOff int `xml:"xdr:colOff"`
|
||||
Row int `xml:"xdr:row"`
|
||||
RowOff int `xml:"xdr:rowOff"`
|
||||
}
|
||||
|
||||
// xlsxTo directly specifies the ending anchor.
|
||||
type xlsxTo struct {
|
||||
Col int `xml:"xdr:col"`
|
||||
ColOff int `xml:"xdr:colOff"`
|
||||
Row int `xml:"xdr:row"`
|
||||
RowOff int `xml:"xdr:rowOff"`
|
||||
}
|
||||
|
||||
// xdrClientData directly maps the clientData element. An empty element which
|
||||
// specifies (via attributes) certain properties related to printing and
|
||||
// selection of the drawing object. The fLocksWithSheet attribute (either true
|
||||
// or false) determines whether to disable selection when the sheet is
|
||||
// protected, and fPrintsWithSheet attribute (either true or false) determines
|
||||
// whether the object is printed when the sheet is printed.
|
||||
type xdrClientData struct {
|
||||
FLocksWithSheet bool `xml:"fLocksWithSheet,attr"`
|
||||
FPrintsWithSheet bool `xml:"fPrintsWithSheet,attr"`
|
||||
}
|
||||
|
||||
// xdrCellAnchor directly maps the oneCellAnchor (One Cell Anchor Shape Size)
|
||||
// and twoCellAnchor (Two Cell Anchor Shape Size). This element specifies a two
|
||||
// cell anchor placeholder for a group, a shape, or a drawing element. It moves
|
||||
// with cells and its extents are in EMU units.
|
||||
type xdrCellAnchor struct {
|
||||
EditAs string `xml:"editAs,attr,omitempty"`
|
||||
Pos *xlsxPoint2D `xml:"xdr:pos"`
|
||||
From *xlsxFrom `xml:"xdr:from"`
|
||||
To *xlsxTo `xml:"xdr:to"`
|
||||
Ext *xlsxExt `xml:"xdr:ext"`
|
||||
Sp *xdrSp `xml:"xdr:sp"`
|
||||
Pic *xlsxPic `xml:"xdr:pic,omitempty"`
|
||||
GraphicFrame string `xml:",innerxml"`
|
||||
ClientData *xdrClientData `xml:"xdr:clientData"`
|
||||
}
|
||||
|
||||
// xlsxPoint2D describes the position of a drawing element within a spreadsheet.
|
||||
type xlsxPoint2D struct {
|
||||
XMLName xml.Name `xml:"xdr:pos"`
|
||||
X int `xml:"x,attr"`
|
||||
Y int `xml:"y,attr"`
|
||||
}
|
||||
|
||||
// xlsxWsDr directly maps the root element for a part of this content type shall
|
||||
// wsDr.
|
||||
type xlsxWsDr struct {
|
||||
sync.Mutex
|
||||
XMLName xml.Name `xml:"xdr:wsDr"`
|
||||
A string `xml:"xmlns:a,attr,omitempty"`
|
||||
Xdr string `xml:"xmlns:xdr,attr,omitempty"`
|
||||
R string `xml:"xmlns:r,attr,omitempty"`
|
||||
AlternateContent []*xlsxAlternateContent `xml:"mc:AlternateContent"`
|
||||
AbsoluteAnchor []*xdrCellAnchor `xml:"xdr:absoluteAnchor"`
|
||||
OneCellAnchor []*xdrCellAnchor `xml:"xdr:oneCellAnchor"`
|
||||
TwoCellAnchor []*xdrCellAnchor `xml:"xdr:twoCellAnchor"`
|
||||
}
|
||||
|
||||
// xlsxGraphicFrame (Graphic Frame) directly maps the xdr:graphicFrame element.
|
||||
// This element specifies the existence of a graphics frame. This frame contains
|
||||
// a graphic that was generated by an external source and needs a container in
|
||||
// which to be displayed on the slide surface.
|
||||
type xlsxGraphicFrame struct {
|
||||
XMLName xml.Name `xml:"xdr:graphicFrame"`
|
||||
Macro string `xml:"macro,attr"`
|
||||
NvGraphicFramePr xlsxNvGraphicFramePr `xml:"xdr:nvGraphicFramePr"`
|
||||
Xfrm xlsxXfrm `xml:"xdr:xfrm"`
|
||||
Graphic *xlsxGraphic `xml:"a:graphic"`
|
||||
}
|
||||
|
||||
// xlsxNvGraphicFramePr (Non-Visual Properties for a Graphic Frame) directly
|
||||
// maps the xdr:nvGraphicFramePr element. This element specifies all non-visual
|
||||
// properties for a graphic frame. This element is a container for the non-
|
||||
// visual identification properties, shape properties and application properties
|
||||
// that are to be associated with a graphic frame. This allows for additional
|
||||
// information that does not affect the appearance of the graphic frame to be
|
||||
// stored.
|
||||
type xlsxNvGraphicFramePr struct {
|
||||
CNvPr *xlsxCNvPr `xml:"xdr:cNvPr"`
|
||||
ChicNvGraphicFramePr string `xml:"xdr:cNvGraphicFramePr"`
|
||||
}
|
||||
|
||||
// xlsxGraphic (Graphic Object) directly maps the a:graphic element. This
|
||||
// element specifies the existence of a single graphic object. Document authors
|
||||
// should refer to this element when they wish to persist a graphical object of
|
||||
// some kind. The specification for this graphical object is provided entirely
|
||||
// by the document author and referenced within the graphicData child element.
|
||||
type xlsxGraphic struct {
|
||||
GraphicData *xlsxGraphicData `xml:"a:graphicData"`
|
||||
}
|
||||
|
||||
// xlsxGraphicData (Graphic Object Data) directly maps the a:graphicData
|
||||
// element. This element specifies the reference to a graphic object within the
|
||||
// document. This graphic object is provided entirely by the document authors
|
||||
// who choose to persist this data within the document.
|
||||
type xlsxGraphicData struct {
|
||||
URI string `xml:"uri,attr"`
|
||||
Chart *xlsxChart `xml:"c:chart,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxChart (Chart) directly maps the c:chart element.
|
||||
type xlsxChart struct {
|
||||
C string `xml:"xmlns:c,attr"`
|
||||
RID string `xml:"r:id,attr"`
|
||||
R string `xml:"xmlns:r,attr"`
|
||||
}
|
||||
|
||||
// xdrSp (Shape) directly maps the xdr:sp element. This element specifies the
|
||||
// existence of a single shape. A shape can either be a preset or a custom
|
||||
// geometry, defined using the SpreadsheetDrawingML framework. In addition to a
|
||||
// geometry each shape can have both visual and non-visual properties attached.
|
||||
// Text and corresponding styling information can also be attached to a shape.
|
||||
// This shape is specified along with all other shapes within either the shape
|
||||
// tree or group shape elements.
|
||||
type xdrSp struct {
|
||||
Macro string `xml:"macro,attr"`
|
||||
Textlink string `xml:"textlink,attr"`
|
||||
NvSpPr *xdrNvSpPr `xml:"xdr:nvSpPr"`
|
||||
SpPr *xlsxSpPr `xml:"xdr:spPr"`
|
||||
Style *xdrStyle `xml:"xdr:style"`
|
||||
TxBody *xdrTxBody `xml:"xdr:txBody"`
|
||||
}
|
||||
|
||||
// xdrNvSpPr (Non-Visual Properties for a Shape) directly maps the xdr:nvSpPr
|
||||
// element. This element specifies all non-visual properties for a shape. This
|
||||
// element is a container for the non-visual identification properties, shape
|
||||
// properties and application properties that are to be associated with a shape.
|
||||
// This allows for additional information that does not affect the appearance of
|
||||
// the shape to be stored.
|
||||
type xdrNvSpPr struct {
|
||||
CNvPr *xlsxCNvPr `xml:"xdr:cNvPr"`
|
||||
CNvSpPr *xdrCNvSpPr `xml:"xdr:cNvSpPr"`
|
||||
}
|
||||
|
||||
// xdrCNvSpPr (Connection Non-Visual Shape Properties) directly maps the
|
||||
// xdr:cNvSpPr element. This element specifies the set of non-visual properties
|
||||
// for a connection shape. These properties specify all data about the
|
||||
// connection shape which do not affect its display within a spreadsheet.
|
||||
type xdrCNvSpPr struct {
|
||||
TxBox bool `xml:"txBox,attr"`
|
||||
}
|
||||
|
||||
// xdrStyle (Shape Style) directly maps the xdr:style element. The element
|
||||
// specifies the style that is applied to a shape and the corresponding
|
||||
// references for each of the style components such as lines and fills.
|
||||
type xdrStyle struct {
|
||||
LnRef *aRef `xml:"a:lnRef"`
|
||||
FillRef *aRef `xml:"a:fillRef"`
|
||||
EffectRef *aRef `xml:"a:effectRef"`
|
||||
FontRef *aFontRef `xml:"a:fontRef"`
|
||||
}
|
||||
|
||||
// aRef directly maps the a:lnRef, a:fillRef and a:effectRef element.
|
||||
type aRef struct {
|
||||
Idx int `xml:"idx,attr"`
|
||||
ScrgbClr *aScrgbClr `xml:"a:scrgbClr"`
|
||||
SchemeClr *attrValString `xml:"a:schemeClr"`
|
||||
SrgbClr *attrValString `xml:"a:srgbClr"`
|
||||
}
|
||||
|
||||
// aScrgbClr (RGB Color Model - Percentage Variant) directly maps the a:scrgbClr
|
||||
// element. This element specifies a color using the red, green, blue RGB color
|
||||
// model. Each component, red, green, and blue is expressed as a percentage from
|
||||
// 0% to 100%. A linear gamma of 1.0 is assumed.
|
||||
type aScrgbClr struct {
|
||||
R float64 `xml:"r,attr"`
|
||||
G float64 `xml:"g,attr"`
|
||||
B float64 `xml:"b,attr"`
|
||||
}
|
||||
|
||||
// aFontRef (Font Reference) directly maps the a:fontRef element. This element
|
||||
// represents a reference to a themed font. When used it specifies which themed
|
||||
// font to use along with a choice of color.
|
||||
type aFontRef struct {
|
||||
Idx string `xml:"idx,attr"`
|
||||
SchemeClr *attrValString `xml:"a:schemeClr"`
|
||||
}
|
||||
|
||||
// xdrTxBody (Shape Text Body) directly maps the xdr:txBody element. This
|
||||
// element specifies the existence of text to be contained within the
|
||||
// corresponding shape. All visible text and visible text related properties are
|
||||
// contained within this element. There can be multiple paragraphs and within
|
||||
// paragraphs multiple runs of text.
|
||||
type xdrTxBody struct {
|
||||
BodyPr *aBodyPr `xml:"a:bodyPr"`
|
||||
P []*aP `xml:"a:p"`
|
||||
}
|
||||
|
||||
// Picture maps the format settings of the picture.
|
||||
type Picture struct {
|
||||
Extension string
|
||||
File []byte
|
||||
Format *GraphicOptions
|
||||
}
|
||||
|
||||
// GraphicOptions directly maps the format settings of the picture.
|
||||
type GraphicOptions struct {
|
||||
AltText string
|
||||
PrintObject *bool
|
||||
Locked *bool
|
||||
LockAspectRatio bool
|
||||
AutoFit bool
|
||||
OffsetX int
|
||||
OffsetY int
|
||||
ScaleX float64
|
||||
ScaleY float64
|
||||
Hyperlink string
|
||||
HyperlinkType string
|
||||
Positioning string
|
||||
}
|
||||
|
||||
// Shape directly maps the format settings of the shape.
|
||||
type Shape struct {
|
||||
Type string
|
||||
Macro string
|
||||
Width uint
|
||||
Height uint
|
||||
Format GraphicOptions
|
||||
Fill Fill
|
||||
Line ShapeLine
|
||||
Paragraph []RichTextRun
|
||||
}
|
||||
|
||||
// ShapeColor directly maps the color settings of the shape.
|
||||
type ShapeColor struct {
|
||||
Line string
|
||||
Fill string
|
||||
Effect string
|
||||
}
|
||||
|
||||
// ShapeLine directly maps the line settings of the shape.
|
||||
type ShapeLine struct {
|
||||
Color string
|
||||
Width *float64
|
||||
}
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// xlsxPivotCacheDefinition represents the pivotCacheDefinition part. This part
|
||||
// defines each field in the source data, including the name, the string
|
||||
// resources of the instance data (for shared items), and information about
|
||||
// the type of data that appears in the field.
|
||||
type xlsxPivotCacheDefinition struct {
|
||||
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main pivotCacheDefinition"`
|
||||
RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
|
||||
Invalid bool `xml:"invalid,attr,omitempty"`
|
||||
SaveData bool `xml:"saveData,attr"`
|
||||
RefreshOnLoad bool `xml:"refreshOnLoad,attr,omitempty"`
|
||||
OptimizeMemory bool `xml:"optimizeMemory,attr,omitempty"`
|
||||
EnableRefresh bool `xml:"enableRefresh,attr,omitempty"`
|
||||
RefreshedBy string `xml:"refreshedBy,attr,omitempty"`
|
||||
RefreshedDate float64 `xml:"refreshedDate,attr,omitempty"`
|
||||
RefreshedDateIso float64 `xml:"refreshedDateIso,attr,omitempty"`
|
||||
BackgroundQuery bool `xml:"backgroundQuery,attr"`
|
||||
MissingItemsLimit int `xml:"missingItemsLimit,attr,omitempty"`
|
||||
CreatedVersion int `xml:"createdVersion,attr,omitempty"`
|
||||
RefreshedVersion int `xml:"refreshedVersion,attr,omitempty"`
|
||||
MinRefreshableVersion int `xml:"minRefreshableVersion,attr,omitempty"`
|
||||
RecordCount int `xml:"recordCount,attr,omitempty"`
|
||||
UpgradeOnRefresh bool `xml:"upgradeOnRefresh,attr,omitempty"`
|
||||
TupleCacheAttr bool `xml:"tupleCache,attr,omitempty"`
|
||||
SupportSubquery bool `xml:"supportSubquery,attr,omitempty"`
|
||||
SupportAdvancedDrill bool `xml:"supportAdvancedDrill,attr,omitempty"`
|
||||
CacheSource *xlsxCacheSource `xml:"cacheSource"`
|
||||
CacheFields *xlsxCacheFields `xml:"cacheFields"`
|
||||
CacheHierarchies *xlsxCacheHierarchies `xml:"cacheHierarchies"`
|
||||
Kpis *xlsxKpis `xml:"kpis"`
|
||||
TupleCache *xlsxTupleCache `xml:"tupleCache"`
|
||||
CalculatedItems *xlsxCalculatedItems `xml:"calculatedItems"`
|
||||
CalculatedMembers *xlsxCalculatedMembers `xml:"calculatedMembers"`
|
||||
Dimensions *xlsxDimensions `xml:"dimensions"`
|
||||
MeasureGroups *xlsxMeasureGroups `xml:"measureGroups"`
|
||||
Maps *xlsxMaps `xml:"maps"`
|
||||
ExtLst *xlsxExtLst `xml:"extLst"`
|
||||
}
|
||||
|
||||
// xlsxCacheSource represents the description of data source whose data is
|
||||
// stored in the pivot cache. The data source refers to the underlying rows or
|
||||
// database records that provide the data for a PivotTable. You can create a
|
||||
// PivotTable report from a SpreadsheetML table, an external database
|
||||
// (including OLAP cubes), multiple SpreadsheetML worksheets, or another
|
||||
// PivotTable.
|
||||
type xlsxCacheSource struct {
|
||||
Type string `xml:"type,attr"`
|
||||
ConnectionID int `xml:"connectionId,attr,omitempty"`
|
||||
WorksheetSource *xlsxWorksheetSource `xml:"worksheetSource"`
|
||||
Consolidation *xlsxConsolidation `xml:"consolidation"`
|
||||
ExtLst *xlsxExtLst `xml:"extLst"`
|
||||
}
|
||||
|
||||
// xlsxWorksheetSource represents the location of the source of the data that
|
||||
// is stored in the cache.
|
||||
type xlsxWorksheetSource struct {
|
||||
RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
|
||||
Ref string `xml:"ref,attr,omitempty"`
|
||||
Name string `xml:"name,attr,omitempty"`
|
||||
Sheet string `xml:"sheet,attr,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxConsolidation represents the description of the PivotCache source using
|
||||
// multiple consolidation ranges. This element is used when the source of the
|
||||
// PivotTable is a collection of ranges in the workbook. The ranges are
|
||||
// specified in the rangeSets collection. The logic for how the application
|
||||
// consolidates the data in the ranges is application- defined.
|
||||
type xlsxConsolidation struct{}
|
||||
|
||||
// xlsxCacheFields represents the collection of field definitions in the
|
||||
// source data.
|
||||
type xlsxCacheFields struct {
|
||||
Count int `xml:"count,attr"`
|
||||
CacheField []*xlsxCacheField `xml:"cacheField"`
|
||||
}
|
||||
|
||||
// xlsxCacheField represent a single field in the PivotCache. This definition
|
||||
// contains information about the field, such as its source, data type, and
|
||||
// location within a level or hierarchy. The sharedItems element stores
|
||||
// additional information about the data in this field. If there are no shared
|
||||
// items, then values are stored directly in the pivotCacheRecords part.
|
||||
type xlsxCacheField struct {
|
||||
Name string `xml:"name,attr"`
|
||||
Caption string `xml:"caption,attr,omitempty"`
|
||||
PropertyName string `xml:"propertyName,attr,omitempty"`
|
||||
ServerField bool `xml:"serverField,attr,omitempty"`
|
||||
UniqueList bool `xml:"uniqueList,attr,omitempty"`
|
||||
NumFmtID int `xml:"numFmtId,attr"`
|
||||
Formula string `xml:"formula,attr,omitempty"`
|
||||
SQLType int `xml:"sqlType,attr,omitempty"`
|
||||
Hierarchy int `xml:"hierarchy,attr,omitempty"`
|
||||
Level int `xml:"level,attr,omitempty"`
|
||||
DatabaseField bool `xml:"databaseField,attr,omitempty"`
|
||||
MappingCount int `xml:"mappingCount,attr,omitempty"`
|
||||
MemberPropertyField bool `xml:"memberPropertyField,attr,omitempty"`
|
||||
SharedItems *xlsxSharedItems `xml:"sharedItems"`
|
||||
FieldGroup *xlsxFieldGroup `xml:"fieldGroup"`
|
||||
MpMap *xlsxX `xml:"mpMap"`
|
||||
ExtLst *xlsxExtLst `xml:"extLst"`
|
||||
}
|
||||
|
||||
// xlsxSharedItems represents the collection of unique items for a field in
|
||||
// the PivotCacheDefinition. The sharedItems complex type stores data type and
|
||||
// formatting information about the data in a field. Items in the
|
||||
// PivotCacheDefinition can be shared in order to reduce the redundancy of
|
||||
// those values that are referenced in multiple places across all the
|
||||
// PivotTable parts.
|
||||
type xlsxSharedItems struct {
|
||||
ContainsSemiMixedTypes bool `xml:"containsSemiMixedTypes,attr,omitempty"`
|
||||
ContainsNonDate bool `xml:"containsNonDate,attr,omitempty"`
|
||||
ContainsDate bool `xml:"containsDate,attr,omitempty"`
|
||||
ContainsString bool `xml:"containsString,attr,omitempty"`
|
||||
ContainsBlank bool `xml:"containsBlank,attr,omitempty"`
|
||||
ContainsMixedTypes bool `xml:"containsMixedTypes,attr,omitempty"`
|
||||
ContainsNumber bool `xml:"containsNumber,attr,omitempty"`
|
||||
ContainsInteger bool `xml:"containsInteger,attr,omitempty"`
|
||||
MinValue float64 `xml:"minValue,attr,omitempty"`
|
||||
MaxValue float64 `xml:"maxValue,attr,omitempty"`
|
||||
MinDate string `xml:"minDate,attr,omitempty"`
|
||||
MaxDate string `xml:"maxDate,attr,omitempty"`
|
||||
Count int `xml:"count,attr"`
|
||||
LongText bool `xml:"longText,attr,omitempty"`
|
||||
M *xlsxMissing `xml:"m"`
|
||||
N *xlsxNumber `xml:"n"`
|
||||
B *xlsxBoolean `xml:"b"`
|
||||
E *xlsxError `xml:"e"`
|
||||
S *xlsxString `xml:"s"`
|
||||
D *xlsxDateTime `xml:"d"`
|
||||
}
|
||||
|
||||
// xlsxMissing represents a value that was not specified.
|
||||
type xlsxMissing struct{}
|
||||
|
||||
// xlsxNumber represents a numeric value in the PivotTable.
|
||||
type xlsxNumber struct {
|
||||
V float64 `xml:"v,attr"`
|
||||
U bool `xml:"u,attr,omitempty"`
|
||||
F bool `xml:"f,attr,omitempty"`
|
||||
C string `xml:"c,attr,omitempty"`
|
||||
Cp int `xml:"cp,attr,omitempty"`
|
||||
In int `xml:"in,attr,omitempty"`
|
||||
Bc string `xml:"bc,attr,omitempty"`
|
||||
Fc string `xml:"fc,attr,omitempty"`
|
||||
I bool `xml:"i,attr,omitempty"`
|
||||
Un bool `xml:"un,attr,omitempty"`
|
||||
St bool `xml:"st,attr,omitempty"`
|
||||
B bool `xml:"b,attr,omitempty"`
|
||||
Tpls *xlsxTuples `xml:"tpls"`
|
||||
X *attrValInt `xml:"x"`
|
||||
}
|
||||
|
||||
// xlsxTuples represents members for the OLAP sheet data entry, also known as
|
||||
// a tuple.
|
||||
type xlsxTuples struct{}
|
||||
|
||||
// xlsxBoolean represents a boolean value for an item in the PivotTable.
|
||||
type xlsxBoolean struct{}
|
||||
|
||||
// xlsxError represents an error value. The use of this item indicates that an
|
||||
// error value is present in the PivotTable source. The error is recorded in
|
||||
// the value attribute.
|
||||
type xlsxError struct{}
|
||||
|
||||
// xlsxString represents a character value in a PivotTable.
|
||||
type xlsxString struct {
|
||||
V string `xml:"v,attr"`
|
||||
U bool `xml:"u,attr,omitempty"`
|
||||
F bool `xml:"f,attr,omitempty"`
|
||||
C string `xml:"c,attr,omitempty"`
|
||||
Cp int `xml:"cp,attr,omitempty"`
|
||||
In int `xml:"in,attr,omitempty"`
|
||||
Bc string `xml:"bc,attr,omitempty"`
|
||||
Fc string `xml:"fc,attr,omitempty"`
|
||||
I bool `xml:"i,attr,omitempty"`
|
||||
Un bool `xml:"un,attr,omitempty"`
|
||||
St bool `xml:"st,attr,omitempty"`
|
||||
B bool `xml:"b,attr,omitempty"`
|
||||
Tpls *xlsxTuples `xml:"tpls"`
|
||||
X *attrValInt `xml:"x"`
|
||||
}
|
||||
|
||||
// xlsxDateTime represents a date-time value in the PivotTable.
|
||||
type xlsxDateTime struct{}
|
||||
|
||||
// xlsxFieldGroup represents the collection of properties for a field group.
|
||||
type xlsxFieldGroup struct{}
|
||||
|
||||
// xlsxCacheHierarchies represents the collection of OLAP hierarchies in the
|
||||
// PivotCache.
|
||||
type xlsxCacheHierarchies struct{}
|
||||
|
||||
// xlsxKpis represents the collection of Key Performance Indicators (KPIs)
|
||||
// defined on the OLAP server and stored in the PivotCache.
|
||||
type xlsxKpis struct{}
|
||||
|
||||
// xlsxTupleCache represents the cache of OLAP sheet data members, or tuples.
|
||||
type xlsxTupleCache struct{}
|
||||
|
||||
// xlsxCalculatedItems represents the collection of calculated items.
|
||||
type xlsxCalculatedItems struct{}
|
||||
|
||||
// xlsxCalculatedMembers represents the collection of calculated members in an
|
||||
// OLAP PivotTable.
|
||||
type xlsxCalculatedMembers struct{}
|
||||
|
||||
// xlsxDimensions represents the collection of PivotTable OLAP dimensions.
|
||||
type xlsxDimensions struct{}
|
||||
|
||||
// xlsxMeasureGroups represents the collection of PivotTable OLAP measure
|
||||
// groups.
|
||||
type xlsxMeasureGroups struct{}
|
||||
|
||||
// xlsxMaps represents the PivotTable OLAP measure group - Dimension maps.
|
||||
type xlsxMaps struct{}
|
||||
+293
@@ -0,0 +1,293 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// xlsxPivotTableDefinition represents the PivotTable root element for
|
||||
// non-null PivotTables. There exists one pivotTableDefinition for each
|
||||
// PivotTableDefinition part
|
||||
type xlsxPivotTableDefinition struct {
|
||||
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main pivotTableDefinition"`
|
||||
Name string `xml:"name,attr"`
|
||||
CacheID int `xml:"cacheId,attr"`
|
||||
ApplyNumberFormats bool `xml:"applyNumberFormats,attr,omitempty"`
|
||||
ApplyBorderFormats bool `xml:"applyBorderFormats,attr,omitempty"`
|
||||
ApplyFontFormats bool `xml:"applyFontFormats,attr,omitempty"`
|
||||
ApplyPatternFormats bool `xml:"applyPatternFormats,attr,omitempty"`
|
||||
ApplyAlignmentFormats bool `xml:"applyAlignmentFormats,attr,omitempty"`
|
||||
ApplyWidthHeightFormats bool `xml:"applyWidthHeightFormats,attr,omitempty"`
|
||||
DataOnRows bool `xml:"dataOnRows,attr,omitempty"`
|
||||
DataPosition int `xml:"dataPosition,attr,omitempty"`
|
||||
DataCaption string `xml:"dataCaption,attr"`
|
||||
GrandTotalCaption string `xml:"grandTotalCaption,attr,omitempty"`
|
||||
ErrorCaption string `xml:"errorCaption,attr,omitempty"`
|
||||
ShowError *bool `xml:"showError,attr"`
|
||||
MissingCaption string `xml:"missingCaption,attr,omitempty"`
|
||||
ShowMissing bool `xml:"showMissing,attr,omitempty"`
|
||||
PageStyle string `xml:"pageStyle,attr,omitempty"`
|
||||
PivotTableStyle string `xml:"pivotTableStyle,attr,omitempty"`
|
||||
VacatedStyle string `xml:"vacatedStyle,attr,omitempty"`
|
||||
Tag string `xml:"tag,attr,omitempty"`
|
||||
UpdatedVersion int `xml:"updatedVersion,attr,omitempty"`
|
||||
MinRefreshableVersion int `xml:"minRefreshableVersion,attr,omitempty"`
|
||||
AsteriskTotals bool `xml:"asteriskTotals,attr,omitempty"`
|
||||
ShowItems bool `xml:"showItems,attr,omitempty"`
|
||||
EditData bool `xml:"editData,attr,omitempty"`
|
||||
DisableFieldList bool `xml:"disableFieldList,attr,omitempty"`
|
||||
ShowCalcMbrs bool `xml:"showCalcMbrs,attr,omitempty"`
|
||||
VisualTotals bool `xml:"visualTotals,attr,omitempty"`
|
||||
ShowMultipleLabel bool `xml:"showMultipleLabel,attr,omitempty"`
|
||||
ShowDataDropDown bool `xml:"showDataDropDown,attr,omitempty"`
|
||||
ShowDrill *bool `xml:"showDrill,attr"`
|
||||
PrintDrill bool `xml:"printDrill,attr,omitempty"`
|
||||
ShowMemberPropertyTips bool `xml:"showMemberPropertyTips,attr,omitempty"`
|
||||
ShowDataTips bool `xml:"showDataTips,attr,omitempty"`
|
||||
EnableWizard bool `xml:"enableWizard,attr,omitempty"`
|
||||
EnableDrill bool `xml:"enableDrill,attr,omitempty"`
|
||||
EnableFieldProperties bool `xml:"enableFieldProperties,attr,omitempty"`
|
||||
PreserveFormatting bool `xml:"preserveFormatting,attr,omitempty"`
|
||||
UseAutoFormatting *bool `xml:"useAutoFormatting,attr,omitempty"`
|
||||
PageWrap int `xml:"pageWrap,attr,omitempty"`
|
||||
PageOverThenDown *bool `xml:"pageOverThenDown,attr,omitempty"`
|
||||
SubtotalHiddenItems bool `xml:"subtotalHiddenItems,attr,omitempty"`
|
||||
RowGrandTotals *bool `xml:"rowGrandTotals,attr,omitempty"`
|
||||
ColGrandTotals *bool `xml:"colGrandTotals,attr,omitempty"`
|
||||
FieldPrintTitles bool `xml:"fieldPrintTitles,attr,omitempty"`
|
||||
ItemPrintTitles bool `xml:"itemPrintTitles,attr,omitempty"`
|
||||
MergeItem *bool `xml:"mergeItem,attr,omitempty"`
|
||||
ShowDropZones bool `xml:"showDropZones,attr,omitempty"`
|
||||
CreatedVersion int `xml:"createdVersion,attr,omitempty"`
|
||||
Indent int `xml:"indent,attr,omitempty"`
|
||||
ShowEmptyRow bool `xml:"showEmptyRow,attr,omitempty"`
|
||||
ShowEmptyCol bool `xml:"showEmptyCol,attr,omitempty"`
|
||||
ShowHeaders bool `xml:"showHeaders,attr,omitempty"`
|
||||
Compact *bool `xml:"compact,attr"`
|
||||
Outline *bool `xml:"outline,attr"`
|
||||
OutlineData bool `xml:"outlineData,attr,omitempty"`
|
||||
CompactData *bool `xml:"compactData,attr,omitempty"`
|
||||
Published bool `xml:"published,attr,omitempty"`
|
||||
GridDropZones bool `xml:"gridDropZones,attr,omitempty"`
|
||||
Immersive bool `xml:"immersive,attr,omitempty"`
|
||||
MultipleFieldFilters bool `xml:"multipleFieldFilters,attr,omitempty"`
|
||||
ChartFormat int `xml:"chartFormat,attr,omitempty"`
|
||||
RowHeaderCaption string `xml:"rowHeaderCaption,attr,omitempty"`
|
||||
ColHeaderCaption string `xml:"colHeaderCaption,attr,omitempty"`
|
||||
FieldListSortAscending bool `xml:"fieldListSortAscending,attr,omitempty"`
|
||||
MdxSubqueries bool `xml:"mdxSubqueries,attr,omitempty"`
|
||||
CustomListSort bool `xml:"customListSort,attr,omitempty"`
|
||||
Location *xlsxLocation `xml:"location"`
|
||||
PivotFields *xlsxPivotFields `xml:"pivotFields"`
|
||||
RowFields *xlsxRowFields `xml:"rowFields"`
|
||||
RowItems *xlsxRowItems `xml:"rowItems"`
|
||||
ColFields *xlsxColFields `xml:"colFields"`
|
||||
ColItems *xlsxColItems `xml:"colItems"`
|
||||
PageFields *xlsxPageFields `xml:"pageFields"`
|
||||
DataFields *xlsxDataFields `xml:"dataFields"`
|
||||
ConditionalFormats *xlsxConditionalFormats `xml:"conditionalFormats"`
|
||||
PivotTableStyleInfo *xlsxPivotTableStyleInfo `xml:"pivotTableStyleInfo"`
|
||||
}
|
||||
|
||||
// xlsxLocation represents location information for the PivotTable.
|
||||
type xlsxLocation struct {
|
||||
Ref string `xml:"ref,attr"`
|
||||
FirstHeaderRow int `xml:"firstHeaderRow,attr"`
|
||||
FirstDataRow int `xml:"firstDataRow,attr"`
|
||||
FirstDataCol int `xml:"firstDataCol,attr"`
|
||||
RowPageCount int `xml:"rowPageCount,attr,omitempty"`
|
||||
ColPageCount int `xml:"colPageCount,attr,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxPivotFields represents the collection of fields that appear on the
|
||||
// PivotTable.
|
||||
type xlsxPivotFields struct {
|
||||
Count int `xml:"count,attr"`
|
||||
PivotField []*xlsxPivotField `xml:"pivotField"`
|
||||
}
|
||||
|
||||
// xlsxPivotField represents a single field in the PivotTable. This element
|
||||
// contains information about the field, including the collection of items in
|
||||
// the field.
|
||||
type xlsxPivotField struct {
|
||||
Name string `xml:"name,attr,omitempty"`
|
||||
Axis string `xml:"axis,attr,omitempty"`
|
||||
DataField bool `xml:"dataField,attr,omitempty"`
|
||||
SubtotalCaption string `xml:"subtotalCaption,attr,omitempty"`
|
||||
ShowDropDowns bool `xml:"showDropDowns,attr,omitempty"`
|
||||
HiddenLevel bool `xml:"hiddenLevel,attr,omitempty"`
|
||||
UniqueMemberProperty string `xml:"uniqueMemberProperty,attr,omitempty"`
|
||||
Compact *bool `xml:"compact,attr"`
|
||||
AllDrilled bool `xml:"allDrilled,attr,omitempty"`
|
||||
NumFmtID string `xml:"numFmtId,attr,omitempty"`
|
||||
Outline *bool `xml:"outline,attr"`
|
||||
SubtotalTop bool `xml:"subtotalTop,attr,omitempty"`
|
||||
DragToRow bool `xml:"dragToRow,attr,omitempty"`
|
||||
DragToCol bool `xml:"dragToCol,attr,omitempty"`
|
||||
MultipleItemSelectionAllowed bool `xml:"multipleItemSelectionAllowed,attr,omitempty"`
|
||||
DragToPage bool `xml:"dragToPage,attr,omitempty"`
|
||||
DragToData bool `xml:"dragToData,attr,omitempty"`
|
||||
DragOff bool `xml:"dragOff,attr,omitempty"`
|
||||
ShowAll bool `xml:"showAll,attr"`
|
||||
InsertBlankRow bool `xml:"insertBlankRow,attr,omitempty"`
|
||||
ServerField bool `xml:"serverField,attr,omitempty"`
|
||||
InsertPageBreak bool `xml:"insertPageBreak,attr,omitempty"`
|
||||
AutoShow bool `xml:"autoShow,attr,omitempty"`
|
||||
TopAutoShow bool `xml:"topAutoShow,attr,omitempty"`
|
||||
HideNewItems bool `xml:"hideNewItems,attr,omitempty"`
|
||||
MeasureFilter bool `xml:"measureFilter,attr,omitempty"`
|
||||
IncludeNewItemsInFilter bool `xml:"includeNewItemsInFilter,attr,omitempty"`
|
||||
ItemPageCount int `xml:"itemPageCount,attr,omitempty"`
|
||||
SortType string `xml:"sortType,attr,omitempty"`
|
||||
DataSourceSort bool `xml:"dataSourceSort,attr,omitempty"`
|
||||
NonAutoSortDefault bool `xml:"nonAutoSortDefault,attr,omitempty"`
|
||||
RankBy int `xml:"rankBy,attr,omitempty"`
|
||||
DefaultSubtotal *bool `xml:"defaultSubtotal,attr,omitempty"`
|
||||
SumSubtotal bool `xml:"sumSubtotal,attr,omitempty"`
|
||||
CountASubtotal bool `xml:"countASubtotal,attr,omitempty"`
|
||||
AvgSubtotal bool `xml:"avgSubtotal,attr,omitempty"`
|
||||
MaxSubtotal bool `xml:"maxSubtotal,attr,omitempty"`
|
||||
MinSubtotal bool `xml:"minSubtotal,attr,omitempty"`
|
||||
ProductSubtotal bool `xml:"productSubtotal,attr,omitempty"`
|
||||
CountSubtotal bool `xml:"countSubtotal,attr,omitempty"`
|
||||
StdDevSubtotal bool `xml:"stdDevSubtotal,attr,omitempty"`
|
||||
StdDevPSubtotal bool `xml:"stdDevPSubtotal,attr,omitempty"`
|
||||
VarSubtotal bool `xml:"varSubtotal,attr,omitempty"`
|
||||
VarPSubtotal bool `xml:"varPSubtotal,attr,omitempty"`
|
||||
ShowPropCell bool `xml:"showPropCell,attr,omitempty"`
|
||||
ShowPropTip bool `xml:"showPropTip,attr,omitempty"`
|
||||
ShowPropAsCaption bool `xml:"showPropAsCaption,attr,omitempty"`
|
||||
DefaultAttributeDrillState bool `xml:"defaultAttributeDrillState,attr,omitempty"`
|
||||
Items *xlsxItems `xml:"items"`
|
||||
AutoSortScope *xlsxAutoSortScope `xml:"autoSortScope"`
|
||||
ExtLst *xlsxExtLst `xml:"extLst"`
|
||||
}
|
||||
|
||||
// xlsxItems represents the collection of items in a PivotTable field. The
|
||||
// items in the collection are ordered by index. Items represent the unique
|
||||
// entries from the field in the source data.
|
||||
type xlsxItems struct {
|
||||
Count int `xml:"count,attr"`
|
||||
Item []*xlsxItem `xml:"item"`
|
||||
}
|
||||
|
||||
// xlsxItem represents a single item in PivotTable field.
|
||||
type xlsxItem struct {
|
||||
N string `xml:"n,attr,omitempty"`
|
||||
T string `xml:"t,attr,omitempty"`
|
||||
H bool `xml:"h,attr,omitempty"`
|
||||
S bool `xml:"s,attr,omitempty"`
|
||||
SD bool `xml:"sd,attr,omitempty"`
|
||||
F bool `xml:"f,attr,omitempty"`
|
||||
M bool `xml:"m,attr,omitempty"`
|
||||
C bool `xml:"c,attr,omitempty"`
|
||||
X *int `xml:"x,attr,omitempty"`
|
||||
D bool `xml:"d,attr,omitempty"`
|
||||
E bool `xml:"e,attr,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxAutoSortScope represents the sorting scope for the PivotTable.
|
||||
type xlsxAutoSortScope struct{}
|
||||
|
||||
// xlsxRowFields represents the collection of row fields for the PivotTable.
|
||||
type xlsxRowFields struct {
|
||||
Count int `xml:"count,attr"`
|
||||
Field []*xlsxField `xml:"field"`
|
||||
}
|
||||
|
||||
// xlsxField represents a generic field that can appear either on the column
|
||||
// or the row region of the PivotTable. There areas many <x> elements as there
|
||||
// are item values in any particular column or row.
|
||||
type xlsxField struct {
|
||||
X int `xml:"x,attr"`
|
||||
}
|
||||
|
||||
// xlsxRowItems represents the collection of items in row axis of the
|
||||
// PivotTable.
|
||||
type xlsxRowItems struct {
|
||||
Count int `xml:"count,attr"`
|
||||
I []*xlsxI `xml:"i"`
|
||||
}
|
||||
|
||||
// xlsxI represents the collection of items in the row region of the
|
||||
// PivotTable.
|
||||
type xlsxI struct {
|
||||
X []*xlsxX `xml:"x"`
|
||||
}
|
||||
|
||||
// xlsxX represents an array of indexes to cached shared item values.
|
||||
type xlsxX struct{}
|
||||
|
||||
// xlsxColFields represents the collection of fields that are on the column
|
||||
// axis of the PivotTable.
|
||||
type xlsxColFields struct {
|
||||
Count int `xml:"count,attr"`
|
||||
Field []*xlsxField `xml:"field"`
|
||||
}
|
||||
|
||||
// xlsxColItems represents the collection of column items of the PivotTable.
|
||||
type xlsxColItems struct {
|
||||
Count int `xml:"count,attr"`
|
||||
I []*xlsxI `xml:"i"`
|
||||
}
|
||||
|
||||
// xlsxPageFields represents the collection of items in the page or report
|
||||
// filter region of the PivotTable.
|
||||
type xlsxPageFields struct {
|
||||
Count int `xml:"count,attr"`
|
||||
PageField []*xlsxPageField `xml:"pageField"`
|
||||
}
|
||||
|
||||
// xlsxPageField represents a field on the page or report filter of the
|
||||
// PivotTable.
|
||||
type xlsxPageField struct {
|
||||
Fld int `xml:"fld,attr"`
|
||||
Item int `xml:"item,attr,omitempty"`
|
||||
Hier int `xml:"hier,attr,omitempty"`
|
||||
Name string `xml:"name,attr,omitempty"`
|
||||
Cap string `xml:"cap,attr,omitempty"`
|
||||
ExtLst *xlsxExtLst `xml:"extLst"`
|
||||
}
|
||||
|
||||
// xlsxDataFields represents the collection of items in the data region of the
|
||||
// PivotTable.
|
||||
type xlsxDataFields struct {
|
||||
Count int `xml:"count,attr"`
|
||||
DataField []*xlsxDataField `xml:"dataField"`
|
||||
}
|
||||
|
||||
// xlsxDataField represents a field from a source list, table, or database
|
||||
// that contains data that is summarized in a PivotTable.
|
||||
type xlsxDataField struct {
|
||||
Name string `xml:"name,attr,omitempty"`
|
||||
Fld int `xml:"fld,attr"`
|
||||
Subtotal string `xml:"subtotal,attr,omitempty"`
|
||||
ShowDataAs string `xml:"showDataAs,attr,omitempty"`
|
||||
BaseField int `xml:"baseField,attr,omitempty"`
|
||||
BaseItem int64 `xml:"baseItem,attr,omitempty"`
|
||||
NumFmtID string `xml:"numFmtId,attr,omitempty"`
|
||||
ExtLst *xlsxExtLst `xml:"extLst"`
|
||||
}
|
||||
|
||||
// xlsxConditionalFormats represents the collection of conditional formats
|
||||
// applied to a PivotTable.
|
||||
type xlsxConditionalFormats struct{}
|
||||
|
||||
// xlsxPivotTableStyleInfo represent information on style applied to the
|
||||
// PivotTable.
|
||||
type xlsxPivotTableStyleInfo struct {
|
||||
Name string `xml:"name,attr"`
|
||||
ShowRowHeaders bool `xml:"showRowHeaders,attr"`
|
||||
ShowColHeaders bool `xml:"showColHeaders,attr"`
|
||||
ShowRowStripes bool `xml:"showRowStripes,attr,omitempty"`
|
||||
ShowColStripes bool `xml:"showColStripes,attr,omitempty"`
|
||||
ShowLastColumn bool `xml:"showLastColumn,attr,omitempty"`
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// xlsxSST directly maps the sst element from the namespace
|
||||
// http://schemas.openxmlformats.org/spreadsheetml/2006/main. String values may
|
||||
// be stored directly inside spreadsheet cell elements; however, storing the
|
||||
// same value inside multiple cell elements can result in very large worksheet
|
||||
// Parts, possibly resulting in performance degradation. The Shared String Table
|
||||
// is an indexed list of string values, shared across the workbook, which allows
|
||||
// implementations to store values only once.
|
||||
type xlsxSST struct {
|
||||
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main sst"`
|
||||
Count int `xml:"count,attr"`
|
||||
UniqueCount int `xml:"uniqueCount,attr"`
|
||||
SI []xlsxSI `xml:"si"`
|
||||
}
|
||||
|
||||
// xlsxSI (String Item) is the representation of an individual string in the
|
||||
// Shared String table. If the string is just a simple string with formatting
|
||||
// applied at the cell level, then the String Item (si) should contain a
|
||||
// single text element used to express the string. However, if the string in
|
||||
// the cell is more complex - i.e., has formatting applied at the character
|
||||
// level - then the string item shall consist of multiple rich text runs which
|
||||
// collectively are used to express the string.
|
||||
type xlsxSI struct {
|
||||
T *xlsxT `xml:"t,omitempty"`
|
||||
R []xlsxR `xml:"r"`
|
||||
RPh []*xlsxPhoneticRun `xml:"rPh"`
|
||||
PhoneticPr *xlsxPhoneticPr `xml:"phoneticPr"`
|
||||
}
|
||||
|
||||
// xlsxR represents a run of rich text. A rich text run is a region of text
|
||||
// that share a common set of properties, such as formatting properties. The
|
||||
// properties are defined in the rPr element, and the text displayed to the
|
||||
// user is defined in the Text (t) element.
|
||||
type xlsxR struct {
|
||||
XMLName xml.Name `xml:"r"`
|
||||
RPr *xlsxRPr `xml:"rPr"`
|
||||
T *xlsxT `xml:"t"`
|
||||
}
|
||||
|
||||
// xlsxT directly maps the t element in the run properties.
|
||||
type xlsxT struct {
|
||||
XMLName xml.Name `xml:"t"`
|
||||
Space xml.Attr `xml:"space,attr,omitempty"`
|
||||
Val string `xml:",chardata"`
|
||||
}
|
||||
|
||||
// xlsxRPr (Run Properties) specifies a set of run properties which shall be
|
||||
// applied to the contents of the parent run after all style formatting has been
|
||||
// applied to the text. These properties are defined as direct formatting, since
|
||||
// they are directly applied to the run and supersede any formatting from
|
||||
// styles.
|
||||
type xlsxRPr struct {
|
||||
RFont *attrValString `xml:"rFont"`
|
||||
Charset *attrValInt `xml:"charset"`
|
||||
Family *attrValInt `xml:"family"`
|
||||
B *string `xml:"b"`
|
||||
I *string `xml:"i"`
|
||||
Strike *string `xml:"strike"`
|
||||
Outline *string `xml:"outline"`
|
||||
Shadow *string `xml:"shadow"`
|
||||
Condense *string `xml:"condense"`
|
||||
Extend *string `xml:"extend"`
|
||||
Color *xlsxColor `xml:"color"`
|
||||
Sz *attrValFloat `xml:"sz"`
|
||||
U *attrValString `xml:"u"`
|
||||
VertAlign *attrValString `xml:"vertAlign"`
|
||||
Scheme *attrValString `xml:"scheme"`
|
||||
}
|
||||
|
||||
// RichTextRun directly maps the settings of the rich text run.
|
||||
type RichTextRun struct {
|
||||
Font *Font
|
||||
Text string
|
||||
}
|
||||
+376
@@ -0,0 +1,376 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// xlsxStyleSheet is the root element of the Styles part.
|
||||
type xlsxStyleSheet struct {
|
||||
sync.Mutex
|
||||
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main styleSheet"`
|
||||
NumFmts *xlsxNumFmts `xml:"numFmts"`
|
||||
Fonts *xlsxFonts `xml:"fonts"`
|
||||
Fills *xlsxFills `xml:"fills"`
|
||||
Borders *xlsxBorders `xml:"borders"`
|
||||
CellStyleXfs *xlsxCellStyleXfs `xml:"cellStyleXfs"`
|
||||
CellXfs *xlsxCellXfs `xml:"cellXfs"`
|
||||
CellStyles *xlsxCellStyles `xml:"cellStyles"`
|
||||
Dxfs *xlsxDxfs `xml:"dxfs"`
|
||||
TableStyles *xlsxTableStyles `xml:"tableStyles"`
|
||||
Colors *xlsxStyleColors `xml:"colors"`
|
||||
ExtLst *xlsxExtLst `xml:"extLst"`
|
||||
}
|
||||
|
||||
// xlsxAlignment formatting information pertaining to text alignment in cells.
|
||||
// There are a variety of choices for how text is aligned both horizontally and
|
||||
// vertically, as well as indentation settings, and so on.
|
||||
type xlsxAlignment struct {
|
||||
Horizontal string `xml:"horizontal,attr,omitempty"`
|
||||
Indent int `xml:"indent,attr,omitempty"`
|
||||
JustifyLastLine bool `xml:"justifyLastLine,attr,omitempty"`
|
||||
ReadingOrder uint64 `xml:"readingOrder,attr,omitempty"`
|
||||
RelativeIndent int `xml:"relativeIndent,attr,omitempty"`
|
||||
ShrinkToFit bool `xml:"shrinkToFit,attr,omitempty"`
|
||||
TextRotation int `xml:"textRotation,attr,omitempty"`
|
||||
Vertical string `xml:"vertical,attr,omitempty"`
|
||||
WrapText bool `xml:"wrapText,attr,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxProtection (Protection Properties) contains protection properties
|
||||
// associated with the cell. Each cell has protection properties that can be
|
||||
// set. The cell protection properties do not take effect unless the sheet has
|
||||
// been protected.
|
||||
type xlsxProtection struct {
|
||||
Hidden *bool `xml:"hidden,attr"`
|
||||
Locked *bool `xml:"locked,attr"`
|
||||
}
|
||||
|
||||
// xlsxLine expresses a single set of cell border.
|
||||
type xlsxLine struct {
|
||||
Style string `xml:"style,attr,omitempty"`
|
||||
Color *xlsxColor `xml:"color"`
|
||||
}
|
||||
|
||||
// xlsxColor is a common mapping used for both the fgColor and bgColor elements.
|
||||
// Foreground color of the cell fill pattern. Cell fill patterns operate with
|
||||
// two colors: a background color and a foreground color. These combine together
|
||||
// to make a patterned cell fill. Background color of the cell fill pattern.
|
||||
// Cell fill patterns operate with two colors: a background color and a
|
||||
// foreground color. These combine together to make a patterned cell fill.
|
||||
type xlsxColor struct {
|
||||
Auto bool `xml:"auto,attr,omitempty"`
|
||||
RGB string `xml:"rgb,attr,omitempty"`
|
||||
Indexed int `xml:"indexed,attr,omitempty"`
|
||||
Theme *int `xml:"theme,attr"`
|
||||
Tint float64 `xml:"tint,attr,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxFonts directly maps the font element. This element contains all font
|
||||
// definitions for this workbook.
|
||||
type xlsxFonts struct {
|
||||
Count int `xml:"count,attr"`
|
||||
Font []*xlsxFont `xml:"font"`
|
||||
}
|
||||
|
||||
// xlsxFont directly maps the font element. This element defines the
|
||||
// properties for one of the fonts used in this workbook.
|
||||
type xlsxFont struct {
|
||||
B *attrValBool `xml:"b"`
|
||||
I *attrValBool `xml:"i"`
|
||||
Strike *attrValBool `xml:"strike"`
|
||||
Outline *attrValBool `xml:"outline"`
|
||||
Shadow *attrValBool `xml:"shadow"`
|
||||
Condense *attrValBool `xml:"condense"`
|
||||
Extend *attrValBool `xml:"extend"`
|
||||
U *attrValString `xml:"u"`
|
||||
Sz *attrValFloat `xml:"sz"`
|
||||
Color *xlsxColor `xml:"color"`
|
||||
Name *attrValString `xml:"name"`
|
||||
Family *attrValInt `xml:"family"`
|
||||
Charset *attrValInt `xml:"charset"`
|
||||
Scheme *attrValString `xml:"scheme"`
|
||||
}
|
||||
|
||||
// xlsxFills directly maps the fills element. This element defines the cell
|
||||
// fills portion of the Styles part, consisting of a sequence of fill records. A
|
||||
// cell fill consists of a background color, foreground color, and pattern to be
|
||||
// applied across the cell.
|
||||
type xlsxFills struct {
|
||||
Count int `xml:"count,attr"`
|
||||
Fill []*xlsxFill `xml:"fill"`
|
||||
}
|
||||
|
||||
// xlsxFill directly maps the fill element. This element specifies fill
|
||||
// formatting.
|
||||
type xlsxFill struct {
|
||||
PatternFill *xlsxPatternFill `xml:"patternFill"`
|
||||
GradientFill *xlsxGradientFill `xml:"gradientFill"`
|
||||
}
|
||||
|
||||
// xlsxPatternFill is used to specify cell fill information for pattern and
|
||||
// solid color cell fills. For solid cell fills (no pattern), fgColor is used.
|
||||
// For cell fills with patterns specified, then the cell fill color is
|
||||
// specified by the bgColor element.
|
||||
type xlsxPatternFill struct {
|
||||
PatternType string `xml:"patternType,attr,omitempty"`
|
||||
FgColor *xlsxColor `xml:"fgColor"`
|
||||
BgColor *xlsxColor `xml:"bgColor"`
|
||||
}
|
||||
|
||||
// xlsxGradientFill defines a gradient-style cell fill. Gradient cell fills can
|
||||
// use one or two colors as the end points of color interpolation.
|
||||
type xlsxGradientFill struct {
|
||||
Bottom float64 `xml:"bottom,attr,omitempty"`
|
||||
Degree float64 `xml:"degree,attr,omitempty"`
|
||||
Left float64 `xml:"left,attr,omitempty"`
|
||||
Right float64 `xml:"right,attr,omitempty"`
|
||||
Top float64 `xml:"top,attr,omitempty"`
|
||||
Type string `xml:"type,attr,omitempty"`
|
||||
Stop []*xlsxGradientFillStop `xml:"stop"`
|
||||
}
|
||||
|
||||
// xlsxGradientFillStop directly maps the stop element.
|
||||
type xlsxGradientFillStop struct {
|
||||
Position float64 `xml:"position,attr"`
|
||||
Color xlsxColor `xml:"color,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxBorders directly maps the borders element. This element contains borders
|
||||
// formatting information, specifying all border definitions for all cells in
|
||||
// the workbook.
|
||||
type xlsxBorders struct {
|
||||
Count int `xml:"count,attr"`
|
||||
Border []*xlsxBorder `xml:"border"`
|
||||
}
|
||||
|
||||
// xlsxBorder directly maps the border element. Expresses a single set of cell
|
||||
// border formats (left, right, top, bottom, diagonal). Color is optional. When
|
||||
// missing, 'automatic' is implied.
|
||||
type xlsxBorder struct {
|
||||
DiagonalDown bool `xml:"diagonalDown,attr,omitempty"`
|
||||
DiagonalUp bool `xml:"diagonalUp,attr,omitempty"`
|
||||
Outline bool `xml:"outline,attr,omitempty"`
|
||||
Left xlsxLine `xml:"left,omitempty"`
|
||||
Right xlsxLine `xml:"right,omitempty"`
|
||||
Top xlsxLine `xml:"top,omitempty"`
|
||||
Bottom xlsxLine `xml:"bottom,omitempty"`
|
||||
Diagonal xlsxLine `xml:"diagonal,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxCellStyles directly maps the cellStyles element. This element contains
|
||||
// the named cell styles, consisting of a sequence of named style records. A
|
||||
// named cell style is a collection of direct or themed formatting (e.g., cell
|
||||
// border, cell fill, and font type/size/style) grouped together into a single
|
||||
// named style, and can be applied to a cell.
|
||||
type xlsxCellStyles struct {
|
||||
XMLName xml.Name `xml:"cellStyles"`
|
||||
Count int `xml:"count,attr"`
|
||||
CellStyle []*xlsxCellStyle `xml:"cellStyle"`
|
||||
}
|
||||
|
||||
// xlsxCellStyle directly maps the cellStyle element. This element represents
|
||||
// the name and related formatting records for a named cell style in this
|
||||
// workbook.
|
||||
type xlsxCellStyle struct {
|
||||
XMLName xml.Name `xml:"cellStyle"`
|
||||
Name string `xml:"name,attr"`
|
||||
XfID int `xml:"xfId,attr"`
|
||||
BuiltInID *int `xml:"builtinId,attr"`
|
||||
ILevel *int `xml:"iLevel,attr"`
|
||||
Hidden *bool `xml:"hidden,attr"`
|
||||
CustomBuiltIn *bool `xml:"customBuiltin,attr"`
|
||||
}
|
||||
|
||||
// xlsxCellStyleXfs directly maps the cellStyleXfs element. This element
|
||||
// contains the master formatting records (xf's) which define the formatting for
|
||||
// all named cell styles in this workbook. Master formatting records reference
|
||||
// individual elements of formatting (e.g., number format, font definitions,
|
||||
// cell fills, etc.) by specifying a zero-based index into those collections.
|
||||
// Master formatting records also specify whether to apply or ignore particular
|
||||
// aspects of formatting.
|
||||
type xlsxCellStyleXfs struct {
|
||||
Count int `xml:"count,attr"`
|
||||
Xf []xlsxXf `xml:"xf,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxXf directly maps the xf element. A single xf element describes all of the
|
||||
// formatting for a cell.
|
||||
type xlsxXf struct {
|
||||
NumFmtID *int `xml:"numFmtId,attr"`
|
||||
FontID *int `xml:"fontId,attr"`
|
||||
FillID *int `xml:"fillId,attr"`
|
||||
BorderID *int `xml:"borderId,attr"`
|
||||
XfID *int `xml:"xfId,attr"`
|
||||
QuotePrefix *bool `xml:"quotePrefix,attr"`
|
||||
PivotButton *bool `xml:"pivotButton,attr"`
|
||||
ApplyNumberFormat *bool `xml:"applyNumberFormat,attr"`
|
||||
ApplyFont *bool `xml:"applyFont,attr"`
|
||||
ApplyFill *bool `xml:"applyFill,attr"`
|
||||
ApplyBorder *bool `xml:"applyBorder,attr"`
|
||||
ApplyAlignment *bool `xml:"applyAlignment,attr"`
|
||||
ApplyProtection *bool `xml:"applyProtection,attr"`
|
||||
Alignment *xlsxAlignment `xml:"alignment"`
|
||||
Protection *xlsxProtection `xml:"protection"`
|
||||
}
|
||||
|
||||
// xlsxCellXfs directly maps the cellXfs element. This element contains the
|
||||
// master formatting records (xf) which define the formatting applied to cells
|
||||
// in this workbook. These records are the starting point for determining the
|
||||
// formatting for a cell. Cells in the Sheet Part reference the xf records by
|
||||
// zero-based index.
|
||||
type xlsxCellXfs struct {
|
||||
Count int `xml:"count,attr"`
|
||||
Xf []xlsxXf `xml:"xf,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxDxfs directly maps the dxfs element. This element contains the master
|
||||
// differential formatting records (dxf's) which define formatting for all non-
|
||||
// cell formatting in this workbook. Whereas xf records fully specify a
|
||||
// particular aspect of formatting (e.g., cell borders) by referencing those
|
||||
// formatting definitions elsewhere in the Styles part, dxf records specify
|
||||
// incremental (or differential) aspects of formatting directly inline within
|
||||
// the dxf element. The dxf formatting is to be applied on top of or in addition
|
||||
// to any formatting already present on the object using the dxf record.
|
||||
type xlsxDxfs struct {
|
||||
Count int `xml:"count,attr"`
|
||||
Dxfs []*xlsxDxf `xml:"dxf"`
|
||||
}
|
||||
|
||||
// xlsxDxf directly maps the dxf element. A single dxf record, expressing
|
||||
// incremental formatting to be applied.
|
||||
type xlsxDxf struct {
|
||||
Dxf string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
// dxf directly maps the dxf element.
|
||||
type dxf struct {
|
||||
Font *xlsxFont `xml:"font"`
|
||||
NumFmt *xlsxNumFmt `xml:"numFmt"`
|
||||
Fill *xlsxFill `xml:"fill"`
|
||||
Alignment *xlsxAlignment `xml:"alignment"`
|
||||
Border *xlsxBorder `xml:"border"`
|
||||
Protection *xlsxProtection `xml:"protection"`
|
||||
ExtLst *xlsxExt `xml:"extLst"`
|
||||
}
|
||||
|
||||
// xlsxTableStyles directly maps the tableStyles element. This element
|
||||
// represents a collection of Table style definitions for Table styles and
|
||||
// PivotTable styles used in this workbook. It consists of a sequence of
|
||||
// tableStyle records, each defining a single Table style.
|
||||
type xlsxTableStyles struct {
|
||||
Count int `xml:"count,attr"`
|
||||
DefaultPivotStyle string `xml:"defaultPivotStyle,attr"`
|
||||
DefaultTableStyle string `xml:"defaultTableStyle,attr"`
|
||||
TableStyles []*xlsxTableStyle `xml:"tableStyle"`
|
||||
}
|
||||
|
||||
// xlsxTableStyle directly maps the tableStyle element. This element represents
|
||||
// a single table style definition that indicates how a spreadsheet application
|
||||
// should format and display a table.
|
||||
type xlsxTableStyle struct {
|
||||
Name string `xml:"name,attr,omitempty"`
|
||||
Pivot int `xml:"pivot,attr"`
|
||||
Count int `xml:"count,attr,omitempty"`
|
||||
Table bool `xml:"table,attr,omitempty"`
|
||||
TableStyleElement string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
// xlsxNumFmts directly maps the numFmts element. This element defines the
|
||||
// number formats in this workbook, consisting of a sequence of numFmt records,
|
||||
// where each numFmt record defines a particular number format, indicating how
|
||||
// to format and render the numeric value of a cell.
|
||||
type xlsxNumFmts struct {
|
||||
Count int `xml:"count,attr"`
|
||||
NumFmt []*xlsxNumFmt `xml:"numFmt"`
|
||||
}
|
||||
|
||||
// xlsxNumFmt directly maps the numFmt element. This element specifies number
|
||||
// format properties which indicate how to format and render the numeric value
|
||||
// of a cell.
|
||||
type xlsxNumFmt struct {
|
||||
NumFmtID int `xml:"numFmtId,attr"`
|
||||
FormatCode string `xml:"formatCode,attr,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxStyleColors directly maps the colors element. Color information
|
||||
// associated with this stylesheet. This collection is written whenever the
|
||||
// legacy color palette has been modified (backwards compatibility settings) or
|
||||
// a custom color has been selected while using this workbook.
|
||||
type xlsxStyleColors struct {
|
||||
Color string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
// Alignment directly maps the alignment settings of the cells.
|
||||
type Alignment struct {
|
||||
Horizontal string
|
||||
Indent int
|
||||
JustifyLastLine bool
|
||||
ReadingOrder uint64
|
||||
RelativeIndent int
|
||||
ShrinkToFit bool
|
||||
TextRotation int
|
||||
Vertical string
|
||||
WrapText bool
|
||||
}
|
||||
|
||||
// Border directly maps the border settings of the cells.
|
||||
type Border struct {
|
||||
Type string
|
||||
Color string
|
||||
Style int
|
||||
}
|
||||
|
||||
// Font directly maps the font settings of the fonts.
|
||||
type Font struct {
|
||||
Bold bool
|
||||
Italic bool
|
||||
Underline string
|
||||
Family string
|
||||
Size float64
|
||||
Strike bool
|
||||
Color string
|
||||
ColorIndexed int
|
||||
ColorTheme *int
|
||||
ColorTint float64
|
||||
VertAlign string
|
||||
}
|
||||
|
||||
// Fill directly maps the fill settings of the cells.
|
||||
type Fill struct {
|
||||
Type string
|
||||
Pattern int
|
||||
Color []string
|
||||
Shading int
|
||||
}
|
||||
|
||||
// Protection directly maps the protection settings of the cells.
|
||||
type Protection struct {
|
||||
Hidden bool
|
||||
Locked bool
|
||||
}
|
||||
|
||||
// Style directly maps the style settings of the cells.
|
||||
type Style struct {
|
||||
Border []Border
|
||||
Fill Fill
|
||||
Font *Font
|
||||
Alignment *Alignment
|
||||
Protection *Protection
|
||||
NumFmt int
|
||||
DecimalPlaces int
|
||||
CustomNumFmt *string
|
||||
Lang string
|
||||
NegRed bool
|
||||
}
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// xlsxTable directly maps the table element. A table helps organize and provide
|
||||
// structure to list of information in a worksheet. Tables have clearly labeled
|
||||
// columns, rows, and data regions. Tables make it easier for users to sort,
|
||||
// analyze, format, manage, add, and delete information. This element is the
|
||||
// root element for a table that is not a single cell XML table.
|
||||
type xlsxTable struct {
|
||||
XMLName xml.Name `xml:"table"`
|
||||
XMLNS string `xml:"xmlns,attr"`
|
||||
DataCellStyle string `xml:"dataCellStyle,attr,omitempty"`
|
||||
DataDxfID int `xml:"dataDxfId,attr,omitempty"`
|
||||
DisplayName string `xml:"displayName,attr,omitempty"`
|
||||
HeaderRowBorderDxfID int `xml:"headerRowBorderDxfId,attr,omitempty"`
|
||||
HeaderRowCellStyle string `xml:"headerRowCellStyle,attr,omitempty"`
|
||||
HeaderRowCount *int `xml:"headerRowCount,attr"`
|
||||
HeaderRowDxfID int `xml:"headerRowDxfId,attr,omitempty"`
|
||||
ID int `xml:"id,attr"`
|
||||
InsertRow bool `xml:"insertRow,attr,omitempty"`
|
||||
InsertRowShift bool `xml:"insertRowShift,attr,omitempty"`
|
||||
Name string `xml:"name,attr"`
|
||||
Published bool `xml:"published,attr,omitempty"`
|
||||
Ref string `xml:"ref,attr"`
|
||||
TotalsRowCount int `xml:"totalsRowCount,attr,omitempty"`
|
||||
TotalsRowDxfID int `xml:"totalsRowDxfId,attr,omitempty"`
|
||||
TotalsRowShown bool `xml:"totalsRowShown,attr"`
|
||||
AutoFilter *xlsxAutoFilter `xml:"autoFilter"`
|
||||
TableColumns *xlsxTableColumns `xml:"tableColumns"`
|
||||
TableStyleInfo *xlsxTableStyleInfo `xml:"tableStyleInfo"`
|
||||
}
|
||||
|
||||
// xlsxAutoFilter temporarily hides rows based on a filter criteria, which is
|
||||
// applied column by column to a table of data in the worksheet. This collection
|
||||
// expresses AutoFilter settings.
|
||||
type xlsxAutoFilter struct {
|
||||
XMLName xml.Name `xml:"autoFilter"`
|
||||
Ref string `xml:"ref,attr"`
|
||||
FilterColumn []*xlsxFilterColumn `xml:"filterColumn"`
|
||||
}
|
||||
|
||||
// xlsxFilterColumn directly maps the filterColumn element. The filterColumn
|
||||
// collection identifies a particular column in the AutoFilter range and
|
||||
// specifies filter information that has been applied to this column. If a
|
||||
// column in the AutoFilter range has no criteria specified, then there is no
|
||||
// corresponding filterColumn collection expressed for that column.
|
||||
type xlsxFilterColumn struct {
|
||||
ColID int `xml:"colId,attr"`
|
||||
HiddenButton bool `xml:"hiddenButton,attr,omitempty"`
|
||||
ShowButton bool `xml:"showButton,attr,omitempty"`
|
||||
CustomFilters *xlsxCustomFilters `xml:"customFilters"`
|
||||
Filters *xlsxFilters `xml:"filters"`
|
||||
ColorFilter *xlsxColorFilter `xml:"colorFilter"`
|
||||
DynamicFilter *xlsxDynamicFilter `xml:"dynamicFilter"`
|
||||
IconFilter *xlsxIconFilter `xml:"iconFilter"`
|
||||
Top10 *xlsxTop10 `xml:"top10"`
|
||||
}
|
||||
|
||||
// xlsxCustomFilters directly maps the customFilters element. When there is more
|
||||
// than one custom filter criteria to apply (an 'and' or 'or' joining two
|
||||
// criteria), then this element groups the customFilter elements together.
|
||||
type xlsxCustomFilters struct {
|
||||
And bool `xml:"and,attr,omitempty"`
|
||||
CustomFilter []*xlsxCustomFilter `xml:"customFilter"`
|
||||
}
|
||||
|
||||
// xlsxCustomFilter directly maps the customFilter element. A custom AutoFilter
|
||||
// specifies an operator and a value. There can be at most two customFilters
|
||||
// specified, and in that case the parent element specifies whether the two
|
||||
// conditions are joined by 'and' or 'or'. For any cells whose values do not
|
||||
// meet the specified criteria, the corresponding rows shall be hidden from view
|
||||
// when the filter is applied.
|
||||
type xlsxCustomFilter struct {
|
||||
Operator string `xml:"operator,attr,omitempty"`
|
||||
Val string `xml:"val,attr,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxFilters directly maps the filters (Filter Criteria) element. When
|
||||
// multiple values are chosen to filter by, or when a group of date values are
|
||||
// chosen to filter by, this element groups those criteria together.
|
||||
type xlsxFilters struct {
|
||||
Blank bool `xml:"blank,attr,omitempty"`
|
||||
CalendarType string `xml:"calendarType,attr,omitempty"`
|
||||
Filter []*xlsxFilter `xml:"filter"`
|
||||
DateGroupItem []*xlsxDateGroupItem `xml:"dateGroupItem"`
|
||||
}
|
||||
|
||||
// xlsxFilter directly maps the filter element. This element expresses a filter
|
||||
// criteria value.
|
||||
type xlsxFilter struct {
|
||||
Val string `xml:"val,attr,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxColorFilter directly maps the colorFilter element. This element specifies
|
||||
// the color to filter by and whether to use the cell's fill or font color in
|
||||
// the filter criteria. If the cell's font or fill color does not match the
|
||||
// color specified in the criteria, the rows corresponding to those cells are
|
||||
// hidden from view.
|
||||
type xlsxColorFilter struct {
|
||||
CellColor bool `xml:"cellColor,attr"`
|
||||
DxfID int `xml:"dxfId,attr"`
|
||||
}
|
||||
|
||||
// xlsxDynamicFilter directly maps the dynamicFilter element. This collection
|
||||
// specifies dynamic filter criteria. These criteria are considered dynamic
|
||||
// because they can change, either with the data itself (e.g., "above average")
|
||||
// or with the current system date (e.g., show values for "today"). For any
|
||||
// cells whose values do not meet the specified criteria, the corresponding rows
|
||||
// shall be hidden from view when the filter is applied.
|
||||
type xlsxDynamicFilter struct {
|
||||
MaxValISO string `xml:"maxValIso,attr,omitempty"`
|
||||
Type string `xml:"type,attr,omitempty"`
|
||||
Val float64 `xml:"val,attr,omitempty"`
|
||||
ValISO string `xml:"valIso,attr,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxIconFilter directly maps the iconFilter element. This element specifies
|
||||
// the icon set and particular icon within that set to filter by. For any cells
|
||||
// whose icon does not match the specified criteria, the corresponding rows
|
||||
// shall be hidden from view when the filter is applied.
|
||||
type xlsxIconFilter struct {
|
||||
IconID int `xml:"iconId,attr"`
|
||||
IconSet string `xml:"iconSet,attr,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxTop10 directly maps the top10 element. This element specifies the top N
|
||||
// (percent or number of items) to filter by.
|
||||
type xlsxTop10 struct {
|
||||
FilterVal float64 `xml:"filterVal,attr,omitempty"`
|
||||
Percent bool `xml:"percent,attr,omitempty"`
|
||||
Top bool `xml:"top,attr"`
|
||||
Val float64 `xml:"val,attr,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxDateGroupItem directly maps the dateGroupItem element. This collection is
|
||||
// used to express a group of dates or times which are used in an AutoFilter
|
||||
// criteria. [Note: See parent element for an example. end note] Values are
|
||||
// always written in the calendar type of the first date encountered in the
|
||||
// filter range, so that all subsequent dates, even when formatted or
|
||||
// represented by other calendar types, can be correctly compared for the
|
||||
// purposes of filtering.
|
||||
type xlsxDateGroupItem struct {
|
||||
DateTimeGrouping string `xml:"dateTimeGrouping,attr,omitempty"`
|
||||
Day int `xml:"day,attr,omitempty"`
|
||||
Hour int `xml:"hour,attr,omitempty"`
|
||||
Minute int `xml:"minute,attr,omitempty"`
|
||||
Month int `xml:"month,attr,omitempty"`
|
||||
Second int `xml:"second,attr,omitempty"`
|
||||
Year int `xml:"year,attr,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxTableColumns directly maps the element representing the collection of all
|
||||
// table columns for this table.
|
||||
type xlsxTableColumns struct {
|
||||
Count int `xml:"count,attr"`
|
||||
TableColumn []*xlsxTableColumn `xml:"tableColumn"`
|
||||
}
|
||||
|
||||
// xlsxTableColumn directly maps the element representing a single column for
|
||||
// this table.
|
||||
type xlsxTableColumn struct {
|
||||
DataCellStyle string `xml:"dataCellStyle,attr,omitempty"`
|
||||
DataDxfID int `xml:"dataDxfId,attr,omitempty"`
|
||||
HeaderRowCellStyle string `xml:"headerRowCellStyle,attr,omitempty"`
|
||||
HeaderRowDxfID int `xml:"headerRowDxfId,attr,omitempty"`
|
||||
ID int `xml:"id,attr"`
|
||||
Name string `xml:"name,attr"`
|
||||
QueryTableFieldID int `xml:"queryTableFieldId,attr,omitempty"`
|
||||
TotalsRowCellStyle string `xml:"totalsRowCellStyle,attr,omitempty"`
|
||||
TotalsRowDxfID int `xml:"totalsRowDxfId,attr,omitempty"`
|
||||
TotalsRowFunction string `xml:"totalsRowFunction,attr,omitempty"`
|
||||
TotalsRowLabel string `xml:"totalsRowLabel,attr,omitempty"`
|
||||
UniqueName string `xml:"uniqueName,attr,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxTableStyleInfo directly maps the tableStyleInfo element. This element
|
||||
// describes which style is used to display this table, and specifies which
|
||||
// portions of the table have the style applied.
|
||||
type xlsxTableStyleInfo struct {
|
||||
Name string `xml:"name,attr,omitempty"`
|
||||
ShowFirstColumn bool `xml:"showFirstColumn,attr"`
|
||||
ShowLastColumn bool `xml:"showLastColumn,attr"`
|
||||
ShowRowStripes bool `xml:"showRowStripes,attr"`
|
||||
ShowColumnStripes bool `xml:"showColumnStripes,attr"`
|
||||
}
|
||||
|
||||
// Table directly maps the format settings of the table.
|
||||
type Table struct {
|
||||
Range string
|
||||
Name string
|
||||
StyleName string
|
||||
ShowColumnStripes bool
|
||||
ShowFirstColumn bool
|
||||
ShowHeaderRow *bool
|
||||
ShowLastColumn bool
|
||||
ShowRowStripes *bool
|
||||
}
|
||||
|
||||
// AutoFilterOptions directly maps the auto filter settings.
|
||||
type AutoFilterOptions struct {
|
||||
Column string
|
||||
Expression string
|
||||
}
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// xlsxTheme directly maps the theme element in the namespace
|
||||
// http://schemas.openxmlformats.org/drawingml/2006/main
|
||||
type xlsxTheme struct {
|
||||
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/main theme"`
|
||||
XMLNSa string `xml:"xmlns:a,attr"`
|
||||
XMLNSr string `xml:"xmlns:r,attr"`
|
||||
Name string `xml:"name,attr"`
|
||||
ThemeElements xlsxBaseStyles `xml:"themeElements"`
|
||||
ObjectDefaults xlsxObjectDefaults `xml:"objectDefaults"`
|
||||
ExtraClrSchemeLst xlsxExtraClrSchemeLst `xml:"extraClrSchemeLst"`
|
||||
CustClrLst *xlsxInnerXML `xml:"custClrLst"`
|
||||
ExtLst *xlsxExtLst `xml:"extLst"`
|
||||
}
|
||||
|
||||
// xlsxBaseStyles defines the theme elements for a theme, and is the workhorse
|
||||
// of the theme. The bulk of the shared theme information that is used by a
|
||||
// given document is defined here. Within this complex type is defined a color
|
||||
// scheme, a font scheme, and a style matrix (format scheme) that defines
|
||||
// different formatting options for different pieces of a document.
|
||||
type xlsxBaseStyles struct {
|
||||
ClrScheme xlsxColorScheme `xml:"clrScheme"`
|
||||
FontScheme xlsxFontScheme `xml:"fontScheme"`
|
||||
FmtScheme xlsxStyleMatrix `xml:"fmtScheme"`
|
||||
ExtLst *xlsxExtLst `xml:"extLst"`
|
||||
}
|
||||
|
||||
// xlsxCTColor holds the actual color values that are to be applied to a given
|
||||
// diagram and how those colors are to be applied.
|
||||
type xlsxCTColor struct {
|
||||
ScrgbClr *xlsxInnerXML `xml:"scrgbClr"`
|
||||
SrgbClr *attrValString `xml:"srgbClr"`
|
||||
HslClr *xlsxInnerXML `xml:"hslClr"`
|
||||
SysClr *xlsxSysClr `xml:"sysClr"`
|
||||
SchemeClr *xlsxInnerXML `xml:"schemeClr"`
|
||||
PrstClr *xlsxInnerXML `xml:"prstClr"`
|
||||
}
|
||||
|
||||
// xlsxColorScheme defines a set of colors for the theme. The set of colors
|
||||
// consists of twelve color slots that can each hold a color of choice.
|
||||
type xlsxColorScheme struct {
|
||||
Name string `xml:"name,attr"`
|
||||
Dk1 xlsxCTColor `xml:"dk1"`
|
||||
Lt1 xlsxCTColor `xml:"lt1"`
|
||||
Dk2 xlsxCTColor `xml:"dk2"`
|
||||
Lt2 xlsxCTColor `xml:"lt2"`
|
||||
Accent1 xlsxCTColor `xml:"accent1"`
|
||||
Accent2 xlsxCTColor `xml:"accent2"`
|
||||
Accent3 xlsxCTColor `xml:"accent3"`
|
||||
Accent4 xlsxCTColor `xml:"accent4"`
|
||||
Accent5 xlsxCTColor `xml:"accent5"`
|
||||
Accent6 xlsxCTColor `xml:"accent6"`
|
||||
Hlink xlsxCTColor `xml:"hlink"`
|
||||
FolHlink xlsxCTColor `xml:"folHlink"`
|
||||
ExtLst *xlsxExtLst `xml:"extLst"`
|
||||
}
|
||||
|
||||
// objectDefaults element allows for the definition of default shape, line,
|
||||
// and textbox formatting properties. An application can use this information
|
||||
// to format a shape (or text) initially on insertion into a document.
|
||||
type xlsxObjectDefaults struct {
|
||||
ObjectDefaults string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
// xlsxExtraClrSchemeLst element is a container for the list of extra color
|
||||
// schemes present in a document.
|
||||
type xlsxExtraClrSchemeLst struct {
|
||||
ExtraClrSchemeLst string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
// xlsxCTSupplementalFont defines an additional font that is used for language
|
||||
// specific fonts in themes. For example, one can specify a font that gets used
|
||||
// only within the Japanese language context.
|
||||
type xlsxCTSupplementalFont struct {
|
||||
Script string `xml:"script,attr"`
|
||||
Typeface string `xml:"typeface,attr"`
|
||||
}
|
||||
|
||||
// xlsxFontCollection defines a major and minor font which is used in the font
|
||||
// scheme. A font collection consists of a font definition for Latin, East
|
||||
// Asian, and complex script. On top of these three definitions, one can also
|
||||
// define a font for use in a specific language or languages.
|
||||
type xlsxFontCollection struct {
|
||||
Latin *xlsxCTTextFont `xml:"latin"`
|
||||
Ea *xlsxCTTextFont `xml:"ea"`
|
||||
Cs *xlsxCTTextFont `xml:"cs"`
|
||||
Font []xlsxCTSupplementalFont `xml:"font"`
|
||||
ExtLst *xlsxExtLst `xml:"extLst"`
|
||||
}
|
||||
|
||||
// xlsxFontScheme element defines the font scheme within the theme. The font
|
||||
// scheme consists of a pair of major and minor fonts for which to use in a
|
||||
// document. The major font corresponds well with the heading areas of a
|
||||
// document, and the minor font corresponds well with the normal text or
|
||||
// paragraph areas.
|
||||
type xlsxFontScheme struct {
|
||||
Name string `xml:"name,attr"`
|
||||
MajorFont xlsxFontCollection `xml:"majorFont"`
|
||||
MinorFont xlsxFontCollection `xml:"minorFont"`
|
||||
ExtLst *xlsxExtLst `xml:"extLst"`
|
||||
}
|
||||
|
||||
// xlsxStyleMatrix defines a set of formatting options, which can be referenced
|
||||
// by documents that apply a certain style to a given part of an object. For
|
||||
// example, in a given shape, say a rectangle, one can reference a themed line
|
||||
// style, themed effect, and themed fill that would be theme specific and
|
||||
// change when the theme is changed.
|
||||
type xlsxStyleMatrix struct {
|
||||
Name string `xml:"name,attr,omitempty"`
|
||||
FillStyleLst xlsxFillStyleLst `xml:"fillStyleLst"`
|
||||
LnStyleLst xlsxLnStyleLst `xml:"lnStyleLst"`
|
||||
EffectStyleLst xlsxEffectStyleLst `xml:"effectStyleLst"`
|
||||
BgFillStyleLst xlsxBgFillStyleLst `xml:"bgFillStyleLst"`
|
||||
}
|
||||
|
||||
// xlsxFillStyleLst element defines a set of three fill styles that are used
|
||||
// within a theme. The three fill styles are arranged in order from subtle to
|
||||
// moderate to intense.
|
||||
type xlsxFillStyleLst struct {
|
||||
FillStyleLst string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
// xlsxLnStyleLst element defines a list of three line styles for use within a
|
||||
// theme. The three line styles are arranged in order from subtle to moderate
|
||||
// to intense versions of lines. This list makes up part of the style matrix.
|
||||
type xlsxLnStyleLst struct {
|
||||
LnStyleLst string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
// xlsxEffectStyleLst element defines a set of three effect styles that create
|
||||
// the effect style list for a theme. The effect styles are arranged in order
|
||||
// of subtle to moderate to intense.
|
||||
type xlsxEffectStyleLst struct {
|
||||
EffectStyleLst string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
// xlsxBgFillStyleLst element defines a list of background fills that are
|
||||
// used within a theme. The background fills consist of three fills, arranged
|
||||
// in order from subtle to moderate to intense.
|
||||
type xlsxBgFillStyleLst struct {
|
||||
BgFillStyleLst string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
// xlsxSysClr element specifies a color bound to predefined operating system
|
||||
// elements.
|
||||
type xlsxSysClr struct {
|
||||
Val string `xml:"val,attr"`
|
||||
LastClr string `xml:"lastClr,attr"`
|
||||
}
|
||||
+341
@@ -0,0 +1,341 @@
|
||||
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.16 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// xlsxRelationships describe references from parts to other internal resources in the package or to external resources.
|
||||
type xlsxRelationships struct {
|
||||
sync.Mutex
|
||||
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/relationships Relationships"`
|
||||
Relationships []xlsxRelationship `xml:"Relationship"`
|
||||
}
|
||||
|
||||
// xlsxRelationship contains relations which maps id and XML.
|
||||
type xlsxRelationship struct {
|
||||
ID string `xml:"Id,attr"`
|
||||
Target string `xml:",attr"`
|
||||
Type string `xml:",attr"`
|
||||
TargetMode string `xml:",attr,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxWorkbook contains elements and attributes that encompass the data
|
||||
// content of the workbook. The workbook's child elements each have their own
|
||||
// subclause references.
|
||||
type xlsxWorkbook struct {
|
||||
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main workbook"`
|
||||
Conformance string `xml:"conformance,attr,omitempty"`
|
||||
FileVersion *xlsxFileVersion `xml:"fileVersion"`
|
||||
FileSharing *xlsxExtLst `xml:"fileSharing"`
|
||||
WorkbookPr *xlsxWorkbookPr `xml:"workbookPr"`
|
||||
AlternateContent *xlsxAlternateContent `xml:"mc:AlternateContent"`
|
||||
DecodeAlternateContent *xlsxInnerXML `xml:"http://schemas.openxmlformats.org/markup-compatibility/2006 AlternateContent"`
|
||||
WorkbookProtection *xlsxWorkbookProtection `xml:"workbookProtection"`
|
||||
BookViews *xlsxBookViews `xml:"bookViews"`
|
||||
Sheets xlsxSheets `xml:"sheets"`
|
||||
FunctionGroups *xlsxFunctionGroups `xml:"functionGroups"`
|
||||
ExternalReferences *xlsxExternalReferences `xml:"externalReferences"`
|
||||
DefinedNames *xlsxDefinedNames `xml:"definedNames"`
|
||||
CalcPr *xlsxCalcPr `xml:"calcPr"`
|
||||
OleSize *xlsxExtLst `xml:"oleSize"`
|
||||
CustomWorkbookViews *xlsxCustomWorkbookViews `xml:"customWorkbookViews"`
|
||||
PivotCaches *xlsxPivotCaches `xml:"pivotCaches"`
|
||||
SmartTagPr *xlsxExtLst `xml:"smartTagPr"`
|
||||
SmartTagTypes *xlsxExtLst `xml:"smartTagTypes"`
|
||||
WebPublishing *xlsxExtLst `xml:"webPublishing"`
|
||||
FileRecoveryPr *xlsxFileRecoveryPr `xml:"fileRecoveryPr"`
|
||||
WebPublishObjects *xlsxExtLst `xml:"webPublishObjects"`
|
||||
ExtLst *xlsxExtLst `xml:"extLst"`
|
||||
}
|
||||
|
||||
// xlsxFileRecoveryPr maps sheet recovery information. This element defines
|
||||
// properties that track the state of the workbook file, such as whether the
|
||||
// file was saved during a crash, or whether it should be opened in auto-recover
|
||||
// mode.
|
||||
type xlsxFileRecoveryPr struct {
|
||||
AutoRecover bool `xml:"autoRecover,attr,omitempty"`
|
||||
CrashSave bool `xml:"crashSave,attr,omitempty"`
|
||||
DataExtractLoad bool `xml:"dataExtractLoad,attr,omitempty"`
|
||||
RepairLoad bool `xml:"repairLoad,attr,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxWorkbookProtection directly maps the workbookProtection element. This
|
||||
// element specifies options for protecting data in the workbook. Applications
|
||||
// might use workbook protection to prevent anyone from accidentally changing,
|
||||
// moving, or deleting important data. This protection can be ignored by
|
||||
// applications which choose not to support this optional protection mechanism.
|
||||
// When a password is to be hashed and stored in this element, it shall be
|
||||
// hashed as defined below, starting from a UTF-16LE encoded string value. If
|
||||
// there is a leading BOM character (U+FEFF) in the encoded password it is
|
||||
// removed before hash calculation.
|
||||
type xlsxWorkbookProtection struct {
|
||||
LockRevision bool `xml:"lockRevision,attr,omitempty"`
|
||||
LockStructure bool `xml:"lockStructure,attr,omitempty"`
|
||||
LockWindows bool `xml:"lockWindows,attr,omitempty"`
|
||||
RevisionsAlgorithmName string `xml:"revisionsAlgorithmName,attr,omitempty"`
|
||||
RevisionsHashValue string `xml:"revisionsHashValue,attr,omitempty"`
|
||||
RevisionsSaltValue string `xml:"revisionsSaltValue,attr,omitempty"`
|
||||
RevisionsSpinCount int `xml:"revisionsSpinCount,attr,omitempty"`
|
||||
WorkbookAlgorithmName string `xml:"workbookAlgorithmName,attr,omitempty"`
|
||||
WorkbookHashValue string `xml:"workbookHashValue,attr,omitempty"`
|
||||
WorkbookSaltValue string `xml:"workbookSaltValue,attr,omitempty"`
|
||||
WorkbookSpinCount int `xml:"workbookSpinCount,attr,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxFileVersion directly maps the fileVersion element. This element defines
|
||||
// properties that track which version of the application accessed the data and
|
||||
// source code contained in the file.
|
||||
type xlsxFileVersion struct {
|
||||
AppName string `xml:"appName,attr,omitempty"`
|
||||
CodeName string `xml:"codeName,attr,omitempty"`
|
||||
LastEdited string `xml:"lastEdited,attr,omitempty"`
|
||||
LowestEdited string `xml:"lowestEdited,attr,omitempty"`
|
||||
RupBuild string `xml:"rupBuild,attr,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxWorkbookPr directly maps the workbookPr element from the namespace
|
||||
// http://schemas.openxmlformats.org/spreadsheetml/2006/main This element
|
||||
// defines a collection of workbook properties.
|
||||
type xlsxWorkbookPr struct {
|
||||
Date1904 bool `xml:"date1904,attr,omitempty"`
|
||||
ShowObjects string `xml:"showObjects,attr,omitempty"`
|
||||
ShowBorderUnselectedTables *bool `xml:"showBorderUnselectedTables,attr"`
|
||||
FilterPrivacy bool `xml:"filterPrivacy,attr,omitempty"`
|
||||
PromptedSolutions bool `xml:"promptedSolutions,attr,omitempty"`
|
||||
ShowInkAnnotation *bool `xml:"showInkAnnotation,attr"`
|
||||
BackupFile bool `xml:"backupFile,attr,omitempty"`
|
||||
SaveExternalLinkValues *bool `xml:"saveExternalLinkValues,attr"`
|
||||
UpdateLinks string `xml:"updateLinks,attr,omitempty"`
|
||||
CodeName string `xml:"codeName,attr,omitempty"`
|
||||
HidePivotFieldList bool `xml:"hidePivotFieldList,attr,omitempty"`
|
||||
ShowPivotChartFilter bool `xml:"showPivotChartFilter,attr,omitempty"`
|
||||
AllowRefreshQuery bool `xml:"allowRefreshQuery,attr,omitempty"`
|
||||
PublishItems bool `xml:"publishItems,attr,omitempty"`
|
||||
CheckCompatibility bool `xml:"checkCompatibility,attr,omitempty"`
|
||||
AutoCompressPictures *bool `xml:"autoCompressPictures,attr"`
|
||||
RefreshAllConnections bool `xml:"refreshAllConnections,attr,omitempty"`
|
||||
DefaultThemeVersion string `xml:"defaultThemeVersion,attr,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxBookViews directly maps the bookViews element. This element specifies the
|
||||
// collection of workbook views of the enclosing workbook. Each view can specify
|
||||
// a window position, filter options, and other configurations. There is no
|
||||
// limit on the number of workbook views that can be defined for a workbook.
|
||||
type xlsxBookViews struct {
|
||||
WorkBookView []xlsxWorkBookView `xml:"workbookView"`
|
||||
}
|
||||
|
||||
// xlsxWorkBookView directly maps the workbookView element from the namespace
|
||||
// http://schemas.openxmlformats.org/spreadsheetml/2006/main This element
|
||||
// specifies a single Workbook view.
|
||||
type xlsxWorkBookView struct {
|
||||
Visibility string `xml:"visibility,attr,omitempty"`
|
||||
Minimized bool `xml:"minimized,attr,omitempty"`
|
||||
ShowHorizontalScroll *bool `xml:"showHorizontalScroll,attr"`
|
||||
ShowVerticalScroll *bool `xml:"showVerticalScroll,attr"`
|
||||
ShowSheetTabs *bool `xml:"showSheetTabs,attr"`
|
||||
XWindow string `xml:"xWindow,attr,omitempty"`
|
||||
YWindow string `xml:"yWindow,attr,omitempty"`
|
||||
WindowWidth int `xml:"windowWidth,attr,omitempty"`
|
||||
WindowHeight int `xml:"windowHeight,attr,omitempty"`
|
||||
TabRatio int `xml:"tabRatio,attr,omitempty"`
|
||||
FirstSheet int `xml:"firstSheet,attr,omitempty"`
|
||||
ActiveTab int `xml:"activeTab,attr,omitempty"`
|
||||
AutoFilterDateGrouping *bool `xml:"autoFilterDateGrouping,attr"`
|
||||
}
|
||||
|
||||
// xlsxSheets directly maps the sheets element from the namespace
|
||||
// http://schemas.openxmlformats.org/spreadsheetml/2006/main.
|
||||
type xlsxSheets struct {
|
||||
Sheet []xlsxSheet `xml:"sheet"`
|
||||
}
|
||||
|
||||
// xlsxSheet defines a sheet in this workbook. Sheet data is stored in a
|
||||
// separate part.
|
||||
type xlsxSheet struct {
|
||||
Name string `xml:"name,attr,omitempty"`
|
||||
SheetID int `xml:"sheetId,attr,omitempty"`
|
||||
ID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr"`
|
||||
State string `xml:"state,attr,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxFunctionGroup represents a single function group.
|
||||
type xlsxFunctionGroup struct {
|
||||
Name string `xml:"name,attr"`
|
||||
}
|
||||
|
||||
// xlsxFunctionGroups defines the collection of function groups for the workbook.
|
||||
type xlsxFunctionGroups struct {
|
||||
BuiltInGroupCount *int `xml:"builtInGroupCount,attr"`
|
||||
FunctionGroup []xlsxFunctionGroup `xml:"functionGroup"`
|
||||
}
|
||||
|
||||
// xlsxExternalReferences directly maps the externalReferences element of the
|
||||
// external workbook references part.
|
||||
type xlsxExternalReferences struct {
|
||||
ExternalReference []xlsxExternalReference `xml:"externalReference"`
|
||||
}
|
||||
|
||||
// xlsxExternalReference directly maps the externalReference element of the
|
||||
// external workbook references part.
|
||||
type xlsxExternalReference struct {
|
||||
RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxPivotCaches element enumerates pivot cache definition parts used by pivot
|
||||
// tables and formulas in this workbook.
|
||||
type xlsxPivotCaches struct {
|
||||
PivotCache []xlsxPivotCache `xml:"pivotCache"`
|
||||
}
|
||||
|
||||
// xlsxPivotCache directly maps the pivotCache element.
|
||||
type xlsxPivotCache struct {
|
||||
CacheID int `xml:"cacheId,attr"`
|
||||
RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
|
||||
}
|
||||
|
||||
// extLst element provides a convention for extending spreadsheetML in
|
||||
// predefined locations. The locations shall be denoted with the extLst element,
|
||||
// and are called extension lists. Extension list locations within the markup
|
||||
// document are specified in the markup specification and can be used to store
|
||||
// extensions to the markup specification, whether those are future version
|
||||
// extensions of the markup specification or are private extensions implemented
|
||||
// independently from the markup specification. Markup within an extension might
|
||||
// not be understood by a consumer.
|
||||
type xlsxExtLst struct {
|
||||
Ext string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
// xlsxDefinedNames directly maps the definedNames element. This element defines
|
||||
// the collection of defined names for this workbook. Defined names are
|
||||
// descriptive names to represent cells, ranges of cells, formulas, or constant
|
||||
// values. Defined names can be used to represent a range on any worksheet.
|
||||
type xlsxDefinedNames struct {
|
||||
DefinedName []xlsxDefinedName `xml:"definedName"`
|
||||
}
|
||||
|
||||
// xlsxDefinedName directly maps the definedName element from the namespace
|
||||
// http://schemas.openxmlformats.org/spreadsheetml/2006/main This element
|
||||
// defines a defined name within this workbook. A defined name is descriptive
|
||||
// text that is used to represents a cell, range of cells, formula, or constant
|
||||
// value. For a descriptions of the attributes see https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.definedname
|
||||
type xlsxDefinedName struct {
|
||||
Comment string `xml:"comment,attr,omitempty"`
|
||||
CustomMenu string `xml:"customMenu,attr,omitempty"`
|
||||
Description string `xml:"description,attr,omitempty"`
|
||||
Function bool `xml:"function,attr,omitempty"`
|
||||
FunctionGroupID int `xml:"functionGroupId,attr,omitempty"`
|
||||
Help string `xml:"help,attr,omitempty"`
|
||||
Hidden bool `xml:"hidden,attr,omitempty"`
|
||||
LocalSheetID *int `xml:"localSheetId,attr"`
|
||||
Name string `xml:"name,attr,omitempty"`
|
||||
PublishToServer bool `xml:"publishToServer,attr,omitempty"`
|
||||
ShortcutKey string `xml:"shortcutKey,attr,omitempty"`
|
||||
StatusBar string `xml:"statusBar,attr,omitempty"`
|
||||
VbProcedure bool `xml:"vbProcedure,attr,omitempty"`
|
||||
WorkbookParameter bool `xml:"workbookParameter,attr,omitempty"`
|
||||
Xlm bool `xml:"xml,attr,omitempty"`
|
||||
Data string `xml:",chardata"`
|
||||
}
|
||||
|
||||
// xlsxCalcPr directly maps the calcPr element. This element defines the
|
||||
// collection of properties the application uses to record calculation status
|
||||
// and details. Calculation is the process of computing formulas and then
|
||||
// displaying the results as values in the cells that contain the formulas.
|
||||
type xlsxCalcPr struct {
|
||||
CalcCompleted bool `xml:"calcCompleted,attr,omitempty"`
|
||||
CalcID string `xml:"calcId,attr,omitempty"`
|
||||
CalcMode string `xml:"calcMode,attr,omitempty"`
|
||||
CalcOnSave bool `xml:"calcOnSave,attr,omitempty"`
|
||||
ConcurrentCalc *bool `xml:"concurrentCalc,attr"`
|
||||
ConcurrentManualCount int `xml:"concurrentManualCount,attr,omitempty"`
|
||||
ForceFullCalc bool `xml:"forceFullCalc,attr,omitempty"`
|
||||
FullCalcOnLoad bool `xml:"fullCalcOnLoad,attr,omitempty"`
|
||||
FullPrecision bool `xml:"fullPrecision,attr,omitempty"`
|
||||
Iterate bool `xml:"iterate,attr,omitempty"`
|
||||
IterateCount int `xml:"iterateCount,attr,omitempty"`
|
||||
IterateDelta float64 `xml:"iterateDelta,attr,omitempty"`
|
||||
RefMode string `xml:"refMode,attr,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxCustomWorkbookViews defines the collection of custom workbook views that
|
||||
// are defined for this workbook. A customWorkbookView is similar in concept to
|
||||
// a workbookView in that its attributes contain settings related to the way
|
||||
// that the workbook should be displayed on a screen by a spreadsheet
|
||||
// application.
|
||||
type xlsxCustomWorkbookViews struct {
|
||||
CustomWorkbookView []xlsxCustomWorkbookView `xml:"customWorkbookView"`
|
||||
}
|
||||
|
||||
// xlsxCustomWorkbookView directly maps the customWorkbookView element. This
|
||||
// element specifies a single custom workbook view. A custom workbook view
|
||||
// consists of a set of display and print settings that you can name and apply
|
||||
// to a workbook. You can create more than one custom workbook view of the same
|
||||
// workbook. Custom Workbook Views are not required in order to construct a
|
||||
// valid SpreadsheetML document, and are not necessary if the document is never
|
||||
// displayed by a spreadsheet application, or if the spreadsheet application has
|
||||
// a fixed display for workbooks. However, if a spreadsheet application chooses
|
||||
// to implement configurable display modes, the customWorkbookView element
|
||||
// should be used to persist the settings for those display modes.
|
||||
type xlsxCustomWorkbookView struct {
|
||||
ActiveSheetID *int `xml:"activeSheetId,attr"`
|
||||
AutoUpdate *bool `xml:"autoUpdate,attr"`
|
||||
ChangesSavedWin *bool `xml:"changesSavedWin,attr"`
|
||||
GUID *string `xml:"guid,attr"`
|
||||
IncludeHiddenRowCol *bool `xml:"includeHiddenRowCol,attr"`
|
||||
IncludePrintSettings *bool `xml:"includePrintSettings,attr"`
|
||||
Maximized *bool `xml:"maximized,attr"`
|
||||
MergeInterval int `xml:"mergeInterval,attr"`
|
||||
Minimized *bool `xml:"minimized,attr"`
|
||||
Name *string `xml:"name,attr"`
|
||||
OnlySync *bool `xml:"onlySync,attr"`
|
||||
PersonalView *bool `xml:"personalView,attr"`
|
||||
ShowComments *string `xml:"showComments,attr"`
|
||||
ShowFormulaBar *bool `xml:"showFormulaBar,attr"`
|
||||
ShowHorizontalScroll *bool `xml:"showHorizontalScroll,attr"`
|
||||
ShowObjects *string `xml:"showObjects,attr"`
|
||||
ShowSheetTabs *bool `xml:"showSheetTabs,attr"`
|
||||
ShowStatusbar *bool `xml:"showStatusbar,attr"`
|
||||
ShowVerticalScroll *bool `xml:"showVerticalScroll,attr"`
|
||||
TabRatio *int `xml:"tabRatio,attr"`
|
||||
WindowHeight *int `xml:"windowHeight,attr"`
|
||||
WindowWidth *int `xml:"windowWidth,attr"`
|
||||
XWindow *int `xml:"xWindow,attr"`
|
||||
YWindow *int `xml:"yWindow,attr"`
|
||||
}
|
||||
|
||||
// DefinedName directly maps the name for a cell or cell range on a
|
||||
// worksheet.
|
||||
type DefinedName struct {
|
||||
Name string
|
||||
Comment string
|
||||
RefersTo string
|
||||
Scope string
|
||||
}
|
||||
|
||||
// WorkbookPropsOptions directly maps the settings of workbook proprieties.
|
||||
type WorkbookPropsOptions struct {
|
||||
Date1904 *bool
|
||||
FilterPrivacy *bool
|
||||
CodeName *string
|
||||
}
|
||||
|
||||
// WorkbookProtectionOptions directly maps the settings of workbook protection.
|
||||
type WorkbookProtectionOptions struct {
|
||||
AlgorithmName string
|
||||
Password string
|
||||
LockStructure bool
|
||||
LockWindows bool
|
||||
}
|
||||
+1114
File diff suppressed because it is too large
Load Diff
+46
@@ -0,0 +1,46 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [xuri.me](https://xuri.me). The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at [https://www.contributor-covenant.org/version/2/0/code_of_conduct][version]
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[version]: https://www.contributor-covenant.org/version/2/0/code_of_conduct
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2022 Ri Xu All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of nfp nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
# PR Details
|
||||
|
||||
<!--- Provide a general summary of your changes in the Title above -->
|
||||
|
||||
## Description
|
||||
|
||||
<!--- Describe your changes in detail -->
|
||||
|
||||
## Related Issue
|
||||
|
||||
<!--- This project only accepts pull requests related to open issues -->
|
||||
<!--- If suggesting a new feature or change, please discuss it in an issue first -->
|
||||
<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
|
||||
<!--- Please link to the issue here: -->
|
||||
|
||||
## Motivation and Context
|
||||
|
||||
<!--- Why is this change required? What problem does it solve? -->
|
||||
|
||||
## How Has This Been Tested
|
||||
|
||||
<!--- Please describe in detail how you tested your changes. -->
|
||||
<!--- Include details of your testing environment, and the tests you ran to -->
|
||||
<!--- see how your change affects other areas of the code, etc. -->
|
||||
|
||||
## Types of changes
|
||||
|
||||
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
|
||||
|
||||
- [ ] Docs change / refactoring / dependency upgrade
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
||||
|
||||
## Checklist
|
||||
|
||||
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
|
||||
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
|
||||
|
||||
- [ ] My code follows the code style of this project.
|
||||
- [ ] My change requires a change to the documentation.
|
||||
- [ ] I have updated the documentation accordingly.
|
||||
- [ ] I have read the **CONTRIBUTING** document.
|
||||
- [ ] I have added tests to cover my changes.
|
||||
- [ ] All new and existing tests passed.
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
# NFP (Number Format Parser)
|
||||
|
||||
[](https://github.com/xuri/nfp/actions?workflow=Go)
|
||||
[](https://codecov.io/gh/xuri/nfp)
|
||||
[](https://goreportcard.com/report/github.com/xuri/nfp)
|
||||
[](https://pkg.go.dev/github.com/xuri/nfp)
|
||||
[](https://opensource.org/licenses/BSD-3-Clause)
|
||||
|
||||
Using NFP (Number Format Parser) you can get an Abstract Syntax Tree (AST) from Excel number format expression.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
go get github.com/xuri/nfp
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "github.com/xuri/nfp"
|
||||
|
||||
func main() {
|
||||
ps := nfp.NumberFormatParser()
|
||||
tokens := ps.Parse("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)")
|
||||
println(p.PrettyPrint())
|
||||
}
|
||||
```
|
||||
|
||||
Get AST
|
||||
|
||||
```text
|
||||
<Positive>
|
||||
<RepeatsChar>
|
||||
# <HashPlaceHolder>
|
||||
<ThousandsSeparator>
|
||||
## <HashPlaceHolder>
|
||||
0 <ZeroPlaceHolder>
|
||||
. <DecimalPoint>
|
||||
00 <ZeroPlaceHolder>
|
||||
<Negative>
|
||||
<RepeatsChar>
|
||||
( <Literal>
|
||||
# <HashPlaceHolder>
|
||||
, <ThousandsSeparator>
|
||||
## <HashPlaceHolder>
|
||||
0 <ZeroPlaceHolder>
|
||||
. <DecimalPoint>
|
||||
00 <ZeroPlaceHolder>
|
||||
) <Literal>
|
||||
<Zero>
|
||||
<RepeatsChar>
|
||||
- <Literal>
|
||||
?? <DigitalPlaceHolder>
|
||||
<Text>
|
||||
@ <TextPlaceHolder>
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Open a pull request to fix a bug, or open an issue to discuss a new feature or change.
|
||||
|
||||
## Licenses
|
||||
|
||||
This program is under the terms of the BSD 3-Clause License. See [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause).
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We will dive into any security-related issue as long as your nfp version is still supported by us. When reporting an issue, include as much information as possible, but no need to fill fancy forms or answer tedious questions. Just tell us what you found, how to reproduce it, and any concerns you have about it. We will respond as soon as possible and follow up with any missing information.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please e-mail us directly at `xuri.me@gmail.com` or use the security issue template on GitHub. In general, public disclosure is made after the issue has been fully identified and a patch is ready to be released. A security issue gets the highest priority assigned and a reply regarding the vulnerability is given within a typical 24 hours. Thank you!
|
||||
+800
@@ -0,0 +1,800 @@
|
||||
// Copyright 2022 The nfp Authors. All rights reserved. Use of this source code
|
||||
// is governed by a BSD-style license that can be found in the LICENSE file.
|
||||
//
|
||||
// This package NFP (Number Format Parser) produce syntax trees for number
|
||||
// format expression. Excel Number format controls options such the number of
|
||||
// decimal digits, the currency sign, commas to separate thousands, and
|
||||
// display of negative numbers. The number format of an index applies wherever
|
||||
// that index is used, including row or column headers of a table, or graph
|
||||
// axis that uses that index.
|
||||
//
|
||||
// Implementation with Go language by Ri Xu: https://xuri.me
|
||||
|
||||
package nfp
|
||||
|
||||
import "strings"
|
||||
|
||||
// Asterisk, At and other's constants are token definitions.
|
||||
const (
|
||||
// Character constants
|
||||
Asterisk = "*"
|
||||
At = "@"
|
||||
BackSlash = "\\"
|
||||
BlockDelimiter = ";"
|
||||
BracketClose = "]"
|
||||
BracketOpen = "["
|
||||
Colon = ":"
|
||||
Comma = ","
|
||||
Dash = "-"
|
||||
Dollar = "$"
|
||||
Dot = "."
|
||||
Hash = "#"
|
||||
ParenClose = ")"
|
||||
ParenOpen = "("
|
||||
Percent = "%"
|
||||
Plus = "+"
|
||||
Question = "?"
|
||||
QuoteDouble = "\""
|
||||
QuoteSingle = "'"
|
||||
Slash = "/"
|
||||
Underscore = "_"
|
||||
Whitespace = " "
|
||||
Zero = "0"
|
||||
// DatesTimesCodeChars defined dates and times control codes in upper case
|
||||
DatesTimesCodeChars = "EYMDHSG"
|
||||
// NumCodeChars defined numeric code character
|
||||
NumCodeChars = "0123456789"
|
||||
// Token section types
|
||||
TokenSectionNegative = "Negative"
|
||||
TokenSectionPositive = "Positive"
|
||||
TokenSectionText = "Text"
|
||||
TokenSectionZero = "Zero"
|
||||
// Token subtypes
|
||||
TokenSubTypeCurrencyString = "CurrencyString"
|
||||
TokenSubTypeLanguageInfo = "LanguageInfo"
|
||||
TokenTypeColor = "Color"
|
||||
// Token types
|
||||
TokenTypeCondition = "Condition"
|
||||
TokenTypeCurrencyLanguage = "CurrencyLanguage"
|
||||
TokenTypeDateTimes = "DateTimes"
|
||||
TokenTypeDecimalPoint = "DecimalPoint"
|
||||
TokenTypeDenominator = "Denominator"
|
||||
TokenTypeDigitalPlaceHolder = "DigitalPlaceHolder"
|
||||
TokenTypeElapsedDateTimes = "ElapsedDateTimes"
|
||||
TokenTypeExponential = "Exponential"
|
||||
TokenTypeFraction = "Fraction"
|
||||
TokenTypeGeneral = "General"
|
||||
TokenTypeHashPlaceHolder = "HashPlaceHolder"
|
||||
TokenTypeLiteral = "Literal"
|
||||
TokenTypeOperand = "Operand"
|
||||
TokenTypeOperator = "Operator"
|
||||
TokenTypePercent = "Percent"
|
||||
TokenTypeRepeatsChar = "RepeatsChar"
|
||||
TokenTypeSwitchArgument = "SwitchArgument"
|
||||
TokenTypeTextPlaceHolder = "TextPlaceHolder"
|
||||
TokenTypeThousandsSeparator = "ThousandsSeparator"
|
||||
TokenTypeUnknown = "Unknown"
|
||||
TokenTypeZeroPlaceHolder = "ZeroPlaceHolder"
|
||||
)
|
||||
|
||||
// ColorNames defined colors name used in for a section of the format, use the
|
||||
// name of one of the following eight colors in square brackets in the
|
||||
// section. The color code shall be the first item in the section.
|
||||
var ColorNames = []string{
|
||||
"black",
|
||||
"blue",
|
||||
"cyan",
|
||||
"green",
|
||||
"magenta",
|
||||
"red",
|
||||
"white",
|
||||
"yellow",
|
||||
}
|
||||
|
||||
// GeneralFormattingSwitchArguments defined switch-arguments apply to fields
|
||||
// whose field result is a numeric value. If the result type of the field is
|
||||
// not numeric, then these switches have no effect.
|
||||
var GeneralFormattingSwitchArguments = []string{
|
||||
"AIUEO",
|
||||
"ALPHABETIC",
|
||||
"alphabetic",
|
||||
"Arabic",
|
||||
"ARABICABJAD",
|
||||
"ARABICALPHA",
|
||||
"ArabicDash",
|
||||
"BAHTTEXT",
|
||||
"CardText",
|
||||
"CHINESENUM1",
|
||||
"CHINESENUM2",
|
||||
"CHINESENUM3",
|
||||
"CHOSUNG",
|
||||
"CIRCLENUM",
|
||||
"DBCHAR",
|
||||
"DBNUM1",
|
||||
"DBNUM2",
|
||||
"DBNUM3",
|
||||
"DBNUM4",
|
||||
"DollarText",
|
||||
"GANADA",
|
||||
"GB1",
|
||||
"GB2",
|
||||
"GB3",
|
||||
"GB4",
|
||||
"HEBREW1",
|
||||
"HEBREW2",
|
||||
"Hex",
|
||||
"HINDIARABIC",
|
||||
"HINDICARDTEXT",
|
||||
"HINDILETTER1",
|
||||
"HINDILETTER2",
|
||||
"IROHA",
|
||||
"KANJINUM1",
|
||||
"KANJINUM2",
|
||||
"KANJINUM3",
|
||||
"Ordinal",
|
||||
"OrdText",
|
||||
"Roman",
|
||||
"roman",
|
||||
"SBCHAR",
|
||||
"THAIARABIC",
|
||||
"THAICARDTEXT",
|
||||
"THAILETTER",
|
||||
"VIETCARDTEXT",
|
||||
"ZODIAC1",
|
||||
"ZODIAC2",
|
||||
"ZODIAC3",
|
||||
}
|
||||
|
||||
// AmPm defined the AM and PM with international considerations.
|
||||
var AmPm = []string{"AM/PM", "A/P", "上午/下午"}
|
||||
|
||||
// ConditionOperators defined the condition operators.
|
||||
var ConditionOperators = []string{"<", "<=", ">", ">=", "<>", "="}
|
||||
|
||||
// Part directly maps the sub part of the token.
|
||||
type Part struct {
|
||||
Token Token
|
||||
Value string
|
||||
}
|
||||
|
||||
// Token encapsulate a number format token.
|
||||
type Token struct {
|
||||
TValue string
|
||||
TType string
|
||||
Parts []Part
|
||||
}
|
||||
|
||||
// Section directly maps sections of the number format. Up to four sections of
|
||||
// format codes can be specified. The format codes, separated by semicolons,
|
||||
// define the formats for positive numbers, negative numbers, zero values, and
|
||||
// text, in that order. If only two sections are specified, the first is used
|
||||
// for positive numbers and zeros, and the second is used for negative
|
||||
// numbers. If only one section is specified, it is used for all numbers. To
|
||||
// skip a section, the ending semicolon for that section shall be written.
|
||||
type Section struct {
|
||||
Type string
|
||||
Items []Token
|
||||
}
|
||||
|
||||
// Tokens directly maps the ordered list of tokens.
|
||||
// Attributes:
|
||||
//
|
||||
// Index - Current position in the number format expression
|
||||
// SectionIndex - Current position in section
|
||||
// Sections - Ordered section of token sequences
|
||||
//
|
||||
type Tokens struct {
|
||||
Index int
|
||||
SectionIndex int
|
||||
Sections []Section
|
||||
}
|
||||
|
||||
// fTokens provides function to handle an ordered list of tokens.
|
||||
func fTokens() Tokens {
|
||||
return Tokens{
|
||||
Index: -1,
|
||||
}
|
||||
}
|
||||
|
||||
// fToken provides function to encapsulate a number format token.
|
||||
func fToken(value, tokenType string, parts []Part) Token {
|
||||
return Token{
|
||||
TValue: value,
|
||||
TType: tokenType,
|
||||
Parts: parts,
|
||||
}
|
||||
}
|
||||
|
||||
// add provides function to add a token to the end of the list.
|
||||
func (tk *Tokens) add(value, tokenType string, parts []Part) Token {
|
||||
token := fToken(value, tokenType, parts)
|
||||
tk.addRef(token)
|
||||
return token
|
||||
}
|
||||
|
||||
// addRef provides function to add a token to the end of the list.
|
||||
func (tk *Tokens) addRef(token Token) {
|
||||
if len(tk.Sections) <= tk.SectionIndex {
|
||||
sectionType := []string{TokenSectionPositive, TokenSectionNegative, TokenSectionZero, TokenSectionText}[tk.SectionIndex]
|
||||
for i := len(tk.Sections) - 1; i < tk.SectionIndex; i++ {
|
||||
tk.Sections = append(tk.Sections, Section{Type: sectionType})
|
||||
}
|
||||
}
|
||||
tk.Sections[tk.SectionIndex].Items = append(tk.Sections[tk.SectionIndex].Items, token)
|
||||
}
|
||||
|
||||
// reset provides function to reset the index to -1.
|
||||
func (tk *Tokens) reset() {
|
||||
tk.Index = -1
|
||||
}
|
||||
|
||||
// Parser inheritable container.
|
||||
type Parser struct {
|
||||
InBracket bool
|
||||
InString bool
|
||||
InPlaceholder bool
|
||||
NumFmt string
|
||||
Offset int
|
||||
Tokens Tokens
|
||||
Token Token
|
||||
}
|
||||
|
||||
// NumberFormatParser provides function to parse an Excel number format into a
|
||||
// stream of tokens.
|
||||
func NumberFormatParser() Parser {
|
||||
return Parser{}
|
||||
}
|
||||
|
||||
// EOF provides function to check whether end of tokens stack.
|
||||
func (ps *Parser) EOF() bool {
|
||||
return ps.Offset >= len([]rune(ps.NumFmt))
|
||||
}
|
||||
|
||||
// getTokens return a token stream (list).
|
||||
func (ps *Parser) getTokens() Tokens {
|
||||
ps.NumFmt = strings.TrimSpace(ps.NumFmt)
|
||||
// state-dependent character evaluation (order is important)
|
||||
for !ps.EOF() {
|
||||
if ps.InBracket {
|
||||
if ps.Token.TType == TokenTypeCurrencyLanguage {
|
||||
if ps.currentChar() != Dash && ps.currentChar() != BracketClose {
|
||||
ps.Token.Parts[1].Token.TValue += ps.currentChar()
|
||||
}
|
||||
if ps.currentChar() == Dash {
|
||||
ps.Token.Parts[0].Token.TValue, ps.Token.Parts[1].Token.TValue = ps.Token.Parts[1].Token.TValue, ps.Token.Parts[0].Token.TValue
|
||||
}
|
||||
}
|
||||
|
||||
if len(ps.Token.TValue) > 1 && inStrSlice(ConditionOperators, ps.Token.TValue[1:], true) != -1 {
|
||||
if ps.currentChar() == Dash || strings.ContainsAny(NumCodeChars, ps.currentChar()) {
|
||||
ps.Token.TType = TokenTypeCondition
|
||||
ps.Token.Parts = []Part{
|
||||
{Token: Token{TType: TokenTypeOperator, TValue: ps.Token.TValue[1:]}},
|
||||
{Token: Token{TType: TokenTypeOperand}},
|
||||
}
|
||||
ps.Token.TValue = ""
|
||||
ps.Token.TValue += ps.currentChar()
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if ps.currentChar() == BracketClose {
|
||||
ps.InBracket = false
|
||||
if ps.Token.TType == TokenTypeCondition && len(ps.Token.Parts) == 2 {
|
||||
ps.Token.Parts[1].Token.TValue = ps.Token.TValue
|
||||
ps.Tokens.add(ps.Token.Parts[0].Token.TValue+ps.Token.Parts[1].Token.TValue, ps.Token.TType, ps.Token.Parts)
|
||||
ps.Token = Token{}
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
ps.Token.TValue += ps.currentChar()
|
||||
if l := len(ps.Token.TValue); l > 2 {
|
||||
lit := ps.Token.TValue[1 : l-1]
|
||||
|
||||
if idx := inStrSlice(ColorNames, lit, false); idx != -1 {
|
||||
ps.Tokens.add(lit, TokenTypeColor, nil)
|
||||
ps.Token = Token{}
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
|
||||
if idx := inStrSlice(GeneralFormattingSwitchArguments, lit, false); idx != -1 {
|
||||
ps.Tokens.add(ps.Token.TValue, TokenTypeSwitchArgument, nil)
|
||||
ps.Token = Token{}
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
|
||||
if ps.Token.TType == TokenTypeCurrencyLanguage {
|
||||
if ps.Token.Parts[0].Token.TValue == "" {
|
||||
ps.Token.Parts = []Part{{Token: Token{TType: ps.Token.Parts[1].Token.TType, TValue: ps.Token.Parts[1].Token.TValue}}}
|
||||
}
|
||||
|
||||
ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts)
|
||||
ps.Token = Token{}
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
ps.Token.TType, ps.Token.TValue = TokenTypeUnknown, lit
|
||||
isDateTime := true
|
||||
for _, ch := range lit {
|
||||
if !strings.ContainsAny(DatesTimesCodeChars, strings.ToUpper(string(ch))) {
|
||||
isDateTime = false
|
||||
}
|
||||
}
|
||||
if isDateTime {
|
||||
ps.Token.TType = TokenTypeElapsedDateTimes
|
||||
}
|
||||
ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts)
|
||||
ps.Token = Token{}
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !ps.InBracket {
|
||||
if strings.ContainsAny(NumCodeChars, ps.currentChar()) {
|
||||
if ps.Token.TType == TokenTypeZeroPlaceHolder || ps.Token.TType == TokenTypeDenominator {
|
||||
ps.Token.TValue += ps.currentChar()
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
if ps.Token.TType == TokenTypeFraction {
|
||||
ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts)
|
||||
ps.Token = Token{TType: TokenTypeDenominator, TValue: ps.currentChar()}
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
if ps.Token.TType != "" {
|
||||
ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts)
|
||||
ps.Token = Token{}
|
||||
}
|
||||
ps.Token.TType = TokenTypeZeroPlaceHolder
|
||||
if ps.currentChar() != Zero {
|
||||
ps.Token.TType = TokenTypeLiteral
|
||||
}
|
||||
ps.Token.TValue += ps.currentChar()
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
|
||||
if ps.currentChar() == Hash {
|
||||
if ps.Token.TType != TokenTypeHashPlaceHolder && ps.Token.TType != "" {
|
||||
if ps.Token.TValue == Dot {
|
||||
ps.Token.TType = TokenTypeDecimalPoint
|
||||
}
|
||||
ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts)
|
||||
ps.Token = Token{}
|
||||
}
|
||||
ps.Token.TType = TokenTypeHashPlaceHolder
|
||||
ps.Token.TValue += ps.currentChar()
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
|
||||
if ps.currentChar() == Dot {
|
||||
if ps.Token.TType == TokenTypeZeroPlaceHolder || ps.Token.TType == TokenTypeHashPlaceHolder {
|
||||
ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts)
|
||||
ps.Tokens.add(ps.currentChar(), TokenTypeDecimalPoint, ps.Token.Parts)
|
||||
ps.Token = Token{}
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
if !ps.InString {
|
||||
if ps.Token.TType != "" && strings.ContainsAny(NumCodeChars, ps.nextChar()) {
|
||||
ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts)
|
||||
ps.Token = Token{}
|
||||
}
|
||||
ps.Token.TType = TokenTypeDecimalPoint
|
||||
}
|
||||
ps.Token.TValue += ps.currentChar()
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if strings.ContainsAny(Dollar+Dash+Plus+ParenOpen+ParenClose+Colon+Whitespace, ps.currentChar()) {
|
||||
if ps.InBracket {
|
||||
if len(ps.Token.Parts) == 0 {
|
||||
ps.Token.Parts = []Part{
|
||||
{Token: Token{TType: TokenSubTypeCurrencyString}},
|
||||
{Token: Token{TType: TokenSubTypeLanguageInfo}},
|
||||
}
|
||||
}
|
||||
ps.Token.TValue += ps.currentChar()
|
||||
ps.Token.TType = TokenTypeCurrencyLanguage
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
|
||||
if ps.Token.TType != TokenTypeLiteral && ps.Token.TType != TokenTypeDateTimes && ps.Token.TType != "" {
|
||||
ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts)
|
||||
ps.Token = Token{TType: TokenTypeLiteral, TValue: ps.currentChar()}
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
|
||||
if ps.Token.TValue != BackSlash && ps.Token.TType == "" && inStrSlice(AmPm, ps.Token.TValue, false) == -1 {
|
||||
ps.Token.TType = TokenTypeLiteral
|
||||
}
|
||||
|
||||
if ps.Token.TType == TokenTypeLiteral {
|
||||
ps.Token.TValue += ps.currentChar()
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if ps.currentChar() == Underscore {
|
||||
ps.Offset += 2
|
||||
continue
|
||||
}
|
||||
|
||||
if ps.currentChar() == Asterisk {
|
||||
ps.Tokens.add(ps.nextChar(), TokenTypeRepeatsChar, ps.Token.Parts)
|
||||
ps.Token = Token{}
|
||||
ps.Offset += 2
|
||||
continue
|
||||
}
|
||||
|
||||
if ps.currentChar() == BackSlash {
|
||||
if ps.Token.TValue != "" {
|
||||
ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts)
|
||||
ps.Token = Token{}
|
||||
}
|
||||
ps.Tokens.add(ps.nextChar(), TokenTypeLiteral, ps.Token.Parts)
|
||||
ps.Token = Token{}
|
||||
ps.Offset += 2
|
||||
continue
|
||||
}
|
||||
|
||||
if ps.currentChar() == Dash {
|
||||
if ps.Token.TType != "" {
|
||||
ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts)
|
||||
}
|
||||
ps.Token.TType = TokenTypeLiteral
|
||||
if ps.currentChar() != ps.nextChar() {
|
||||
ps.Tokens.add(ps.currentChar(), ps.Token.TType, ps.Token.Parts)
|
||||
}
|
||||
ps.Token = Token{}
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
|
||||
if ps.currentChar() == Comma {
|
||||
if ps.Token.TType == TokenTypeZeroPlaceHolder || ps.Token.TType == TokenTypeHashPlaceHolder {
|
||||
ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts)
|
||||
ps.Tokens.add(ps.currentChar(), TokenTypeThousandsSeparator, ps.Token.Parts)
|
||||
ps.Token = Token{}
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
if !ps.InString {
|
||||
if ps.Token.TType == TokenTypeLiteral {
|
||||
ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts)
|
||||
ps.Token = Token{TType: TokenTypeThousandsSeparator}
|
||||
}
|
||||
if ps.Token.TType == TokenTypeDateTimes {
|
||||
ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts)
|
||||
ps.Token = Token{TType: TokenTypeLiteral}
|
||||
}
|
||||
if ps.currentChar() != ps.nextChar() {
|
||||
if ps.Token.TType == "" {
|
||||
ps.Token.TType = TokenTypeLiteral
|
||||
}
|
||||
ps.Tokens.add(ps.currentChar(), ps.Token.TType, ps.Token.Parts)
|
||||
}
|
||||
ps.Token = Token{}
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
ps.Token.TType = TokenTypeLiteral
|
||||
ps.Token.TValue += ps.currentChar()
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
|
||||
if ps.currentChar() == Whitespace {
|
||||
if inStrSlice(AmPm, ps.Token.TValue, false) != -1 {
|
||||
ps.Token.TType = TokenTypeDateTimes
|
||||
ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts)
|
||||
ps.Token = Token{}
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
if ps.Token.TType != "" && ps.Token.TType != TokenTypeLiteral {
|
||||
ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts)
|
||||
}
|
||||
ps.Token.TType = TokenTypeLiteral
|
||||
ps.Tokens.add(ps.currentChar(), ps.Token.TType, ps.Token.Parts)
|
||||
ps.Token = Token{}
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
|
||||
if ps.currentChar() == Slash {
|
||||
if ps.Token.TType == TokenTypeDateTimes {
|
||||
ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts)
|
||||
ps.Tokens.add(ps.currentChar(), TokenTypeLiteral, ps.Token.Parts)
|
||||
ps.Token = Token{}
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
if ps.Token.TType == TokenTypeDigitalPlaceHolder {
|
||||
ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts)
|
||||
ps.Token = Token{TType: TokenTypeFraction, TValue: ps.currentChar()}
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if ps.currentChar() == Colon && ps.Token.TType == TokenTypeDateTimes {
|
||||
ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts)
|
||||
ps.Tokens.add(ps.currentChar(), TokenTypeLiteral, ps.Token.Parts)
|
||||
ps.Token = Token{}
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
|
||||
if ps.currentChar() == QuoteDouble {
|
||||
ps.Offset++
|
||||
if ps.InString && len(ps.Token.TValue) > 0 {
|
||||
ps.Tokens.add(ps.Token.TValue, TokenTypeLiteral, ps.Token.Parts)
|
||||
ps.Token = Token{}
|
||||
ps.InString = false
|
||||
continue
|
||||
}
|
||||
if ps.Token.TValue != "" {
|
||||
ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts)
|
||||
}
|
||||
ps.InString = true
|
||||
ps.Token = Token{TType: TokenTypeLiteral}
|
||||
continue
|
||||
}
|
||||
|
||||
if ps.currentChar() == At {
|
||||
if len(ps.Tokens.Sections) <= ps.Tokens.SectionIndex {
|
||||
ps.Tokens.Sections = append(ps.Tokens.Sections, Section{Type: TokenSectionText})
|
||||
}
|
||||
ps.Tokens.Sections[ps.Tokens.SectionIndex].Type = TokenSectionText
|
||||
if ps.Token.TType != "" && !ps.InBracket {
|
||||
ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts)
|
||||
}
|
||||
ps.Token = Token{TType: TokenTypeTextPlaceHolder, TValue: ps.currentChar()}
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
|
||||
if ps.currentChar() == BracketOpen {
|
||||
if ps.Token.TType != "" && !ps.InBracket {
|
||||
ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts)
|
||||
ps.Token = Token{}
|
||||
}
|
||||
ps.InBracket = true
|
||||
ps.Token.TValue += ps.currentChar()
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
|
||||
if ps.currentChar() == Question {
|
||||
if ps.Token.TType != "" && ps.Token.TType != TokenTypeDigitalPlaceHolder {
|
||||
ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts)
|
||||
ps.Token = Token{}
|
||||
}
|
||||
ps.Token.TType = TokenTypeDigitalPlaceHolder
|
||||
ps.Token.TValue += ps.currentChar()
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
|
||||
if ps.currentChar() == Percent {
|
||||
if ps.Token.TType != "" {
|
||||
ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts)
|
||||
ps.Token = Token{}
|
||||
}
|
||||
ps.Token.TType = TokenTypePercent
|
||||
ps.Token.TValue += ps.currentChar()
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
|
||||
if ps.currentChar() == BlockDelimiter {
|
||||
sectionTypes := []string{TokenSectionPositive, TokenSectionNegative, TokenSectionZero, TokenSectionText}
|
||||
if ps.Token.TType != "" {
|
||||
ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts)
|
||||
}
|
||||
if len(ps.Tokens.Sections) <= ps.Tokens.SectionIndex {
|
||||
ps.Tokens.Sections = append(ps.Tokens.Sections, Section{Type: sectionTypes[ps.Tokens.SectionIndex]})
|
||||
}
|
||||
ps.Tokens.SectionIndex++
|
||||
if ps.Tokens.SectionIndex > 3 {
|
||||
tokens := fTokens()
|
||||
tokens.reset()
|
||||
return Tokens{}
|
||||
}
|
||||
ps.Token = Token{}
|
||||
ps.Tokens.Sections = append(ps.Tokens.Sections, Section{Type: sectionTypes[ps.Tokens.SectionIndex]})
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.EqualFold("E+", ps.doubleChar()) {
|
||||
if ps.Token.TType != "" {
|
||||
ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts)
|
||||
ps.Token = Token{}
|
||||
}
|
||||
ps.Token.TType = TokenTypeExponential
|
||||
ps.Token.TValue += ps.doubleChar()
|
||||
ps.Offset += 2
|
||||
continue
|
||||
}
|
||||
|
||||
if ap, matched := ps.apPattern(); ap != -1 {
|
||||
ps.Tokens.add(matched, TokenTypeDateTimes, ps.Token.Parts)
|
||||
ps.Token = Token{}
|
||||
ps.Offset += len(matched)
|
||||
continue
|
||||
}
|
||||
|
||||
if general, matched := ps.generalPattern(); general != -1 {
|
||||
ps.Tokens.add(matched, TokenTypeGeneral, ps.Token.Parts)
|
||||
ps.Token = Token{}
|
||||
ps.Offset += len(matched)
|
||||
continue
|
||||
}
|
||||
|
||||
// token accumulation
|
||||
if !ps.InBracket && !ps.InString {
|
||||
if strings.ContainsAny(DatesTimesCodeChars, strings.ToUpper(ps.currentChar())) {
|
||||
if inStrSlice(AmPm, ps.Token.TValue, false) != -1 {
|
||||
ps.Token.TType = TokenTypeDateTimes
|
||||
ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts)
|
||||
ps.Token = Token{}
|
||||
}
|
||||
if ps.Token.TType == TokenTypeLiteral || ps.Token.TType == TokenTypeDateTimes && !strings.ContainsAny(ps.Token.TValue, ps.currentChar()) {
|
||||
ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts)
|
||||
ps.Token = Token{}
|
||||
}
|
||||
ps.Token.TType = TokenTypeDateTimes
|
||||
ps.Token.TValue += ps.currentChar()
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
if strings.ContainsAny(DatesTimesCodeChars, strings.ToUpper(ps.Token.TValue)) {
|
||||
ps.Tokens.add(ps.Token.TValue, TokenTypeDateTimes, ps.Token.Parts)
|
||||
ps.Token = Token{TType: TokenTypeLiteral, TValue: ps.currentChar()}
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
if strings.ContainsAny(DatesTimesCodeChars, strings.ToUpper(ps.nextChar())) {
|
||||
ps.Token.TValue += ps.currentChar()
|
||||
ps.Token.TType = TokenTypeLiteral
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
if ps.currentChar() == QuoteSingle {
|
||||
ps.Offset++
|
||||
continue
|
||||
}
|
||||
}
|
||||
ps.Token.TValue += ps.currentChar()
|
||||
ps.Offset++
|
||||
}
|
||||
|
||||
// dump remaining accumulation
|
||||
if len(ps.Token.TValue) > 0 {
|
||||
tokenType := TokenTypeLiteral
|
||||
if ps.Token.TType != "" {
|
||||
tokenType = ps.Token.TType
|
||||
}
|
||||
ps.Tokens.add(ps.Token.TValue, tokenType, nil)
|
||||
}
|
||||
|
||||
tokens := fTokens()
|
||||
tokens.reset()
|
||||
return ps.Tokens
|
||||
}
|
||||
|
||||
// Parse provides function to parse number format as a token stream (list).
|
||||
func (ps *Parser) Parse(numFmt string) []Section {
|
||||
ps.NumFmt = numFmt
|
||||
ps.Tokens = ps.getTokens()
|
||||
return ps.Tokens.Sections
|
||||
}
|
||||
|
||||
// doubleChar provides function to get two characters after the current
|
||||
// position.
|
||||
func (ps *Parser) doubleChar() string {
|
||||
if len([]rune(ps.NumFmt)) >= ps.Offset+2 {
|
||||
return string([]rune(ps.NumFmt)[ps.Offset : ps.Offset+2])
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// currentChar provides function to get the character of the current position.
|
||||
func (ps *Parser) currentChar() string {
|
||||
return string([]rune(ps.NumFmt)[ps.Offset])
|
||||
}
|
||||
|
||||
// nextChar provides function to get the next character of the current
|
||||
// position.
|
||||
func (ps *Parser) nextChar() string {
|
||||
if len([]rune(ps.NumFmt)) >= ps.Offset+2 {
|
||||
return string([]rune(ps.NumFmt)[ps.Offset+1 : ps.Offset+2])
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// apPattern infers whether the subsequent characters match the AM/PM pattern,
|
||||
// it will be returned matched index and result.
|
||||
func (ps *Parser) apPattern() (int, string) {
|
||||
for i, pattern := range AmPm {
|
||||
l := len(pattern)
|
||||
if len([]rune(ps.NumFmt)) >= ps.Offset+l {
|
||||
matched := string([]rune(ps.NumFmt)[ps.Offset : ps.Offset+l])
|
||||
if strings.EqualFold(matched, pattern) {
|
||||
return i, matched
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1, ""
|
||||
}
|
||||
|
||||
// generalPattern infers whether the subsequent characters match the
|
||||
// general pattern, it will be returned matched result and result.
|
||||
func (ps *Parser) generalPattern() (int, string) {
|
||||
l := len(TokenTypeGeneral)
|
||||
if len([]rune(ps.NumFmt)) >= ps.Offset+l {
|
||||
matched := string([]rune(ps.NumFmt)[ps.Offset : ps.Offset+l])
|
||||
if strings.EqualFold(matched, TokenTypeGeneral) {
|
||||
return 0, matched
|
||||
}
|
||||
}
|
||||
return -1, ""
|
||||
}
|
||||
|
||||
// inStrSlice provides a method to check if an element is present in an array,
|
||||
// and return the index of its location, otherwise return -1.
|
||||
func inStrSlice(a []string, x string, caseSensitive bool) int {
|
||||
for idx, n := range a {
|
||||
if !caseSensitive && strings.EqualFold(x, n) {
|
||||
return idx
|
||||
}
|
||||
if x == n {
|
||||
return idx
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// PrettyPrint provides function to pretty the parsed result with the indented
|
||||
// format.
|
||||
func (ps *Parser) PrettyPrint() string {
|
||||
indent, output := 0, ""
|
||||
for _, section := range ps.Tokens.Sections {
|
||||
output += "<" + section.Type + ">" + "\n"
|
||||
for _, item := range section.Items {
|
||||
indent++
|
||||
for i := 0; i < indent; i++ {
|
||||
output += "\t"
|
||||
}
|
||||
if len(item.Parts) == 0 {
|
||||
output += item.TValue + " <" + item.TType + ">" + "\n"
|
||||
} else {
|
||||
output += "<" + item.TType + ">" + "\n"
|
||||
}
|
||||
for _, part := range item.Parts {
|
||||
indent++
|
||||
for i := 0; i < indent; i++ {
|
||||
output += "\t"
|
||||
}
|
||||
output += part.Token.TValue + " <" + part.Token.TType + ">" + "\n"
|
||||
indent--
|
||||
}
|
||||
indent--
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package md4 implements the MD4 hash algorithm as defined in RFC 1320.
|
||||
//
|
||||
// Deprecated: MD4 is cryptographically broken and should should only be used
|
||||
// where compatibility with legacy systems, not security, is the goal. Instead,
|
||||
// use a secure hash like SHA-256 (from crypto/sha256).
|
||||
package md4 // import "golang.org/x/crypto/md4"
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"hash"
|
||||
)
|
||||
|
||||
func init() {
|
||||
crypto.RegisterHash(crypto.MD4, New)
|
||||
}
|
||||
|
||||
// The size of an MD4 checksum in bytes.
|
||||
const Size = 16
|
||||
|
||||
// The blocksize of MD4 in bytes.
|
||||
const BlockSize = 64
|
||||
|
||||
const (
|
||||
_Chunk = 64
|
||||
_Init0 = 0x67452301
|
||||
_Init1 = 0xEFCDAB89
|
||||
_Init2 = 0x98BADCFE
|
||||
_Init3 = 0x10325476
|
||||
)
|
||||
|
||||
// digest represents the partial evaluation of a checksum.
|
||||
type digest struct {
|
||||
s [4]uint32
|
||||
x [_Chunk]byte
|
||||
nx int
|
||||
len uint64
|
||||
}
|
||||
|
||||
func (d *digest) Reset() {
|
||||
d.s[0] = _Init0
|
||||
d.s[1] = _Init1
|
||||
d.s[2] = _Init2
|
||||
d.s[3] = _Init3
|
||||
d.nx = 0
|
||||
d.len = 0
|
||||
}
|
||||
|
||||
// New returns a new hash.Hash computing the MD4 checksum.
|
||||
func New() hash.Hash {
|
||||
d := new(digest)
|
||||
d.Reset()
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *digest) Size() int { return Size }
|
||||
|
||||
func (d *digest) BlockSize() int { return BlockSize }
|
||||
|
||||
func (d *digest) Write(p []byte) (nn int, err error) {
|
||||
nn = len(p)
|
||||
d.len += uint64(nn)
|
||||
if d.nx > 0 {
|
||||
n := len(p)
|
||||
if n > _Chunk-d.nx {
|
||||
n = _Chunk - d.nx
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
d.x[d.nx+i] = p[i]
|
||||
}
|
||||
d.nx += n
|
||||
if d.nx == _Chunk {
|
||||
_Block(d, d.x[0:])
|
||||
d.nx = 0
|
||||
}
|
||||
p = p[n:]
|
||||
}
|
||||
n := _Block(d, p)
|
||||
p = p[n:]
|
||||
if len(p) > 0 {
|
||||
d.nx = copy(d.x[:], p)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d0 *digest) Sum(in []byte) []byte {
|
||||
// Make a copy of d0, so that caller can keep writing and summing.
|
||||
d := new(digest)
|
||||
*d = *d0
|
||||
|
||||
// Padding. Add a 1 bit and 0 bits until 56 bytes mod 64.
|
||||
len := d.len
|
||||
var tmp [64]byte
|
||||
tmp[0] = 0x80
|
||||
if len%64 < 56 {
|
||||
d.Write(tmp[0 : 56-len%64])
|
||||
} else {
|
||||
d.Write(tmp[0 : 64+56-len%64])
|
||||
}
|
||||
|
||||
// Length in bits.
|
||||
len <<= 3
|
||||
for i := uint(0); i < 8; i++ {
|
||||
tmp[i] = byte(len >> (8 * i))
|
||||
}
|
||||
d.Write(tmp[0:8])
|
||||
|
||||
if d.nx != 0 {
|
||||
panic("d.nx != 0")
|
||||
}
|
||||
|
||||
for _, s := range d.s {
|
||||
in = append(in, byte(s>>0))
|
||||
in = append(in, byte(s>>8))
|
||||
in = append(in, byte(s>>16))
|
||||
in = append(in, byte(s>>24))
|
||||
}
|
||||
return in
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// MD4 block step.
|
||||
// In its own file so that a faster assembly or C version
|
||||
// can be substituted easily.
|
||||
|
||||
package md4
|
||||
|
||||
import "math/bits"
|
||||
|
||||
var shift1 = []int{3, 7, 11, 19}
|
||||
var shift2 = []int{3, 5, 9, 13}
|
||||
var shift3 = []int{3, 9, 11, 15}
|
||||
|
||||
var xIndex2 = []uint{0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15}
|
||||
var xIndex3 = []uint{0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15}
|
||||
|
||||
func _Block(dig *digest, p []byte) int {
|
||||
a := dig.s[0]
|
||||
b := dig.s[1]
|
||||
c := dig.s[2]
|
||||
d := dig.s[3]
|
||||
n := 0
|
||||
var X [16]uint32
|
||||
for len(p) >= _Chunk {
|
||||
aa, bb, cc, dd := a, b, c, d
|
||||
|
||||
j := 0
|
||||
for i := 0; i < 16; i++ {
|
||||
X[i] = uint32(p[j]) | uint32(p[j+1])<<8 | uint32(p[j+2])<<16 | uint32(p[j+3])<<24
|
||||
j += 4
|
||||
}
|
||||
|
||||
// If this needs to be made faster in the future,
|
||||
// the usual trick is to unroll each of these
|
||||
// loops by a factor of 4; that lets you replace
|
||||
// the shift[] lookups with constants and,
|
||||
// with suitable variable renaming in each
|
||||
// unrolled body, delete the a, b, c, d = d, a, b, c
|
||||
// (or you can let the optimizer do the renaming).
|
||||
//
|
||||
// The index variables are uint so that % by a power
|
||||
// of two can be optimized easily by a compiler.
|
||||
|
||||
// Round 1.
|
||||
for i := uint(0); i < 16; i++ {
|
||||
x := i
|
||||
s := shift1[i%4]
|
||||
f := ((c ^ d) & b) ^ d
|
||||
a += f + X[x]
|
||||
a = bits.RotateLeft32(a, s)
|
||||
a, b, c, d = d, a, b, c
|
||||
}
|
||||
|
||||
// Round 2.
|
||||
for i := uint(0); i < 16; i++ {
|
||||
x := xIndex2[i]
|
||||
s := shift2[i%4]
|
||||
g := (b & c) | (b & d) | (c & d)
|
||||
a += g + X[x] + 0x5a827999
|
||||
a = bits.RotateLeft32(a, s)
|
||||
a, b, c, d = d, a, b, c
|
||||
}
|
||||
|
||||
// Round 3.
|
||||
for i := uint(0); i < 16; i++ {
|
||||
x := xIndex3[i]
|
||||
s := shift3[i%4]
|
||||
h := b ^ c ^ d
|
||||
a += h + X[x] + 0x6ed9eba1
|
||||
a = bits.RotateLeft32(a, s)
|
||||
a, b, c, d = d, a, b, c
|
||||
}
|
||||
|
||||
a += aa
|
||||
b += bb
|
||||
c += cc
|
||||
d += dd
|
||||
|
||||
p = p[_Chunk:]
|
||||
n += _Chunk
|
||||
}
|
||||
|
||||
dig.s[0] = a
|
||||
dig.s[1] = b
|
||||
dig.s[2] = c
|
||||
dig.s[3] = d
|
||||
return n
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package ripemd160 implements the RIPEMD-160 hash algorithm.
|
||||
//
|
||||
// Deprecated: RIPEMD-160 is a legacy hash and should not be used for new
|
||||
// applications. Also, this package does not and will not provide an optimized
|
||||
// implementation. Instead, use a modern hash like SHA-256 (from crypto/sha256).
|
||||
package ripemd160 // import "golang.org/x/crypto/ripemd160"
|
||||
|
||||
// RIPEMD-160 is designed by Hans Dobbertin, Antoon Bosselaers, and Bart
|
||||
// Preneel with specifications available at:
|
||||
// http://homes.esat.kuleuven.be/~cosicart/pdf/AB-9601/AB-9601.pdf.
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"hash"
|
||||
)
|
||||
|
||||
func init() {
|
||||
crypto.RegisterHash(crypto.RIPEMD160, New)
|
||||
}
|
||||
|
||||
// The size of the checksum in bytes.
|
||||
const Size = 20
|
||||
|
||||
// The block size of the hash algorithm in bytes.
|
||||
const BlockSize = 64
|
||||
|
||||
const (
|
||||
_s0 = 0x67452301
|
||||
_s1 = 0xefcdab89
|
||||
_s2 = 0x98badcfe
|
||||
_s3 = 0x10325476
|
||||
_s4 = 0xc3d2e1f0
|
||||
)
|
||||
|
||||
// digest represents the partial evaluation of a checksum.
|
||||
type digest struct {
|
||||
s [5]uint32 // running context
|
||||
x [BlockSize]byte // temporary buffer
|
||||
nx int // index into x
|
||||
tc uint64 // total count of bytes processed
|
||||
}
|
||||
|
||||
func (d *digest) Reset() {
|
||||
d.s[0], d.s[1], d.s[2], d.s[3], d.s[4] = _s0, _s1, _s2, _s3, _s4
|
||||
d.nx = 0
|
||||
d.tc = 0
|
||||
}
|
||||
|
||||
// New returns a new hash.Hash computing the checksum.
|
||||
func New() hash.Hash {
|
||||
result := new(digest)
|
||||
result.Reset()
|
||||
return result
|
||||
}
|
||||
|
||||
func (d *digest) Size() int { return Size }
|
||||
|
||||
func (d *digest) BlockSize() int { return BlockSize }
|
||||
|
||||
func (d *digest) Write(p []byte) (nn int, err error) {
|
||||
nn = len(p)
|
||||
d.tc += uint64(nn)
|
||||
if d.nx > 0 {
|
||||
n := len(p)
|
||||
if n > BlockSize-d.nx {
|
||||
n = BlockSize - d.nx
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
d.x[d.nx+i] = p[i]
|
||||
}
|
||||
d.nx += n
|
||||
if d.nx == BlockSize {
|
||||
_Block(d, d.x[0:])
|
||||
d.nx = 0
|
||||
}
|
||||
p = p[n:]
|
||||
}
|
||||
n := _Block(d, p)
|
||||
p = p[n:]
|
||||
if len(p) > 0 {
|
||||
d.nx = copy(d.x[:], p)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d0 *digest) Sum(in []byte) []byte {
|
||||
// Make a copy of d0 so that caller can keep writing and summing.
|
||||
d := *d0
|
||||
|
||||
// Padding. Add a 1 bit and 0 bits until 56 bytes mod 64.
|
||||
tc := d.tc
|
||||
var tmp [64]byte
|
||||
tmp[0] = 0x80
|
||||
if tc%64 < 56 {
|
||||
d.Write(tmp[0 : 56-tc%64])
|
||||
} else {
|
||||
d.Write(tmp[0 : 64+56-tc%64])
|
||||
}
|
||||
|
||||
// Length in bits.
|
||||
tc <<= 3
|
||||
for i := uint(0); i < 8; i++ {
|
||||
tmp[i] = byte(tc >> (8 * i))
|
||||
}
|
||||
d.Write(tmp[0:8])
|
||||
|
||||
if d.nx != 0 {
|
||||
panic("d.nx != 0")
|
||||
}
|
||||
|
||||
var digest [Size]byte
|
||||
for i, s := range d.s {
|
||||
digest[i*4] = byte(s)
|
||||
digest[i*4+1] = byte(s >> 8)
|
||||
digest[i*4+2] = byte(s >> 16)
|
||||
digest[i*4+3] = byte(s >> 24)
|
||||
}
|
||||
|
||||
return append(in, digest[:]...)
|
||||
}
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// RIPEMD-160 block step.
|
||||
// In its own file so that a faster assembly or C version
|
||||
// can be substituted easily.
|
||||
|
||||
package ripemd160
|
||||
|
||||
import (
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
// work buffer indices and roll amounts for one line
|
||||
var _n = [80]uint{
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||
7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8,
|
||||
3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12,
|
||||
1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2,
|
||||
4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13,
|
||||
}
|
||||
|
||||
var _r = [80]uint{
|
||||
11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8,
|
||||
7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12,
|
||||
11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5,
|
||||
11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12,
|
||||
9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6,
|
||||
}
|
||||
|
||||
// same for the other parallel one
|
||||
var n_ = [80]uint{
|
||||
5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12,
|
||||
6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2,
|
||||
15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13,
|
||||
8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14,
|
||||
12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11,
|
||||
}
|
||||
|
||||
var r_ = [80]uint{
|
||||
8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6,
|
||||
9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11,
|
||||
9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5,
|
||||
15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8,
|
||||
8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11,
|
||||
}
|
||||
|
||||
func _Block(md *digest, p []byte) int {
|
||||
n := 0
|
||||
var x [16]uint32
|
||||
var alpha, beta uint32
|
||||
for len(p) >= BlockSize {
|
||||
a, b, c, d, e := md.s[0], md.s[1], md.s[2], md.s[3], md.s[4]
|
||||
aa, bb, cc, dd, ee := a, b, c, d, e
|
||||
j := 0
|
||||
for i := 0; i < 16; i++ {
|
||||
x[i] = uint32(p[j]) | uint32(p[j+1])<<8 | uint32(p[j+2])<<16 | uint32(p[j+3])<<24
|
||||
j += 4
|
||||
}
|
||||
|
||||
// round 1
|
||||
i := 0
|
||||
for i < 16 {
|
||||
alpha = a + (b ^ c ^ d) + x[_n[i]]
|
||||
s := int(_r[i])
|
||||
alpha = bits.RotateLeft32(alpha, s) + e
|
||||
beta = bits.RotateLeft32(c, 10)
|
||||
a, b, c, d, e = e, alpha, b, beta, d
|
||||
|
||||
// parallel line
|
||||
alpha = aa + (bb ^ (cc | ^dd)) + x[n_[i]] + 0x50a28be6
|
||||
s = int(r_[i])
|
||||
alpha = bits.RotateLeft32(alpha, s) + ee
|
||||
beta = bits.RotateLeft32(cc, 10)
|
||||
aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
// round 2
|
||||
for i < 32 {
|
||||
alpha = a + (b&c | ^b&d) + x[_n[i]] + 0x5a827999
|
||||
s := int(_r[i])
|
||||
alpha = bits.RotateLeft32(alpha, s) + e
|
||||
beta = bits.RotateLeft32(c, 10)
|
||||
a, b, c, d, e = e, alpha, b, beta, d
|
||||
|
||||
// parallel line
|
||||
alpha = aa + (bb&dd | cc&^dd) + x[n_[i]] + 0x5c4dd124
|
||||
s = int(r_[i])
|
||||
alpha = bits.RotateLeft32(alpha, s) + ee
|
||||
beta = bits.RotateLeft32(cc, 10)
|
||||
aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
// round 3
|
||||
for i < 48 {
|
||||
alpha = a + (b | ^c ^ d) + x[_n[i]] + 0x6ed9eba1
|
||||
s := int(_r[i])
|
||||
alpha = bits.RotateLeft32(alpha, s) + e
|
||||
beta = bits.RotateLeft32(c, 10)
|
||||
a, b, c, d, e = e, alpha, b, beta, d
|
||||
|
||||
// parallel line
|
||||
alpha = aa + (bb | ^cc ^ dd) + x[n_[i]] + 0x6d703ef3
|
||||
s = int(r_[i])
|
||||
alpha = bits.RotateLeft32(alpha, s) + ee
|
||||
beta = bits.RotateLeft32(cc, 10)
|
||||
aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
// round 4
|
||||
for i < 64 {
|
||||
alpha = a + (b&d | c&^d) + x[_n[i]] + 0x8f1bbcdc
|
||||
s := int(_r[i])
|
||||
alpha = bits.RotateLeft32(alpha, s) + e
|
||||
beta = bits.RotateLeft32(c, 10)
|
||||
a, b, c, d, e = e, alpha, b, beta, d
|
||||
|
||||
// parallel line
|
||||
alpha = aa + (bb&cc | ^bb&dd) + x[n_[i]] + 0x7a6d76e9
|
||||
s = int(r_[i])
|
||||
alpha = bits.RotateLeft32(alpha, s) + ee
|
||||
beta = bits.RotateLeft32(cc, 10)
|
||||
aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
// round 5
|
||||
for i < 80 {
|
||||
alpha = a + (b ^ (c | ^d)) + x[_n[i]] + 0xa953fd4e
|
||||
s := int(_r[i])
|
||||
alpha = bits.RotateLeft32(alpha, s) + e
|
||||
beta = bits.RotateLeft32(c, 10)
|
||||
a, b, c, d, e = e, alpha, b, beta, d
|
||||
|
||||
// parallel line
|
||||
alpha = aa + (bb ^ cc ^ dd) + x[n_[i]]
|
||||
s = int(r_[i])
|
||||
alpha = bits.RotateLeft32(alpha, s) + ee
|
||||
beta = bits.RotateLeft32(cc, 10)
|
||||
aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
// combine results
|
||||
dd += c + md.s[1]
|
||||
md.s[1] = md.s[2] + d + ee
|
||||
md.s[2] = md.s[3] + e + aa
|
||||
md.s[3] = md.s[4] + a + bb
|
||||
md.s[4] = md.s[0] + b + cc
|
||||
md.s[0] = dd
|
||||
|
||||
p = p[BlockSize:]
|
||||
n += BlockSize
|
||||
}
|
||||
return n
|
||||
}
|
||||
Vendored
+17
@@ -798,6 +798,12 @@ github.com/prometheus/procfs/internal/util
|
||||
# github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a
|
||||
## explicit
|
||||
github.com/rcrowley/go-metrics
|
||||
# github.com/richardlehane/mscfb v1.0.4
|
||||
## explicit
|
||||
github.com/richardlehane/mscfb
|
||||
# github.com/richardlehane/msoleps v1.0.3
|
||||
## explicit
|
||||
github.com/richardlehane/msoleps/types
|
||||
# github.com/rivo/uniseg v0.2.0
|
||||
## explicit; go 1.12
|
||||
github.com/rivo/uniseg
|
||||
@@ -938,6 +944,15 @@ github.com/willf/bitset
|
||||
# github.com/willf/bloom v2.0.3+incompatible
|
||||
## explicit
|
||||
github.com/willf/bloom
|
||||
# github.com/xuri/efp v0.0.0-20220603152613-6918739fd470
|
||||
## explicit; go 1.11
|
||||
github.com/xuri/efp
|
||||
# github.com/xuri/excelize/v2 v2.7.1
|
||||
## explicit; go 1.16
|
||||
github.com/xuri/excelize/v2
|
||||
# github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22
|
||||
## explicit; go 1.15
|
||||
github.com/xuri/nfp
|
||||
# github.com/yusufpapurcu/wmi v1.2.2
|
||||
## explicit; go 1.16
|
||||
github.com/yusufpapurcu/wmi
|
||||
@@ -1006,9 +1021,11 @@ golang.org/x/crypto/curve25519/internal/field
|
||||
golang.org/x/crypto/ed25519
|
||||
golang.org/x/crypto/internal/alias
|
||||
golang.org/x/crypto/internal/poly1305
|
||||
golang.org/x/crypto/md4
|
||||
golang.org/x/crypto/pbkdf2
|
||||
golang.org/x/crypto/pkcs12
|
||||
golang.org/x/crypto/pkcs12/internal/rc2
|
||||
golang.org/x/crypto/ripemd160
|
||||
golang.org/x/crypto/scrypt
|
||||
golang.org/x/crypto/sha3
|
||||
golang.org/x/crypto/ssh
|
||||
|
||||
+1
@@ -554,6 +554,7 @@ func requestInternal(client sClient, ctx context.Context, method THttpMethod, ur
|
||||
header.Set("X-Request-Id", ctxData.RequestId)
|
||||
}
|
||||
req, err := http.NewRequest(string(method), urlStr, body)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user