ooxml

Check-in [5b32057030]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:merged from trunk
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | tcltest
Files: files | file ages | folders
SHA3-256: 5b32057030ec193714ab10b4dbfdd3595004ee4ed13bc20b49f7c37579bfc0fb
User & Date: alex 2019-11-16 10:40:00
Context
2019-11-18
18:28
test bechmark check-in: 9d252baeda user: alex tags: tcltest
2019-11-16
10:42
merged from tcltest check-in: b7dd6e7b67 user: alex tags: trunk
10:40
merged from trunk check-in: 5b32057030 user: alex tags: tcltest
10:37
merged from namespaces check-in: 6931dd69d4 user: alex tags: trunk
2019-11-07
21:12
change ../text() nodeValue to string() check-in: 99033f7a53 user: alex tags: tcltest
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to ooxml.tcl.

176
177
178
179
180
181
182

183
184
185
186
187
188
189
...
327
328
329
330
331
332
333


















334
335
336
337
338
339
340
...
760
761
762
763
764
765
766


767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
...
815
816
817
818
819
820
821


822
823
824
825
826
827
828
...
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
...
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
...
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
...
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
...
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
....
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
....
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
....
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
....
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
....
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
....
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
....
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
....
1355
1356
1357
1358
1359
1360
1361

1362
1363
1364
1365
1366
1367
1368
....
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406





























1407
1408
1409
1410
1411
1412
1413
1414
....
2457
2458
2459
2460
2461
2462
2463


2464
2465
2466
2467
2468
2469
2470
....
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
....
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
....
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
....
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
....
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
....
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
....
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
....
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189

3190
3191
3192
3193


3194
3195
3196
3197
3198
3199
3200
....
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
  variable initNodeCmds
  variable predefNumFmts
  variable predefColors
  variable predefColorsName
  variable predefColorsARBG
  variable predefBorderLineStyles
  variable predefPatternType


  set defaults(path) {.}

  set defaults(numFmts,start) 166
  set defaults(cols,width) 10.83203125

  # predefined formats
................................................................................
    mediumDashDotDot
    mediumDashDotDot
    none
    slantDashDot
    thick
    thin
  }



















  # ar - Arabic - العربية 
  msgcat::mcset ar LANGUAGE \u0627\u0644\u0639\u0631\u0628\u064a\u0629
  msgcat::mcset ar Book \u0627\u0644\u0643\u062a\u0627\u0628
  msgcat::mcset ar Worksheets "\u0623\u0648\u0631\u0627\u0642 \u0627\u0644\u0639\u0645\u0644"
  msgcat::mcset ar Sheet \u0627\u0644\u0648\u0631\u0642\u0629
  # cs - Czech - čeština, český jazyk
................................................................................


#
# ooxml::xl_sheets
#

proc ::ooxml::xl_sheets { file } {


  package require vfs::zip

  set sheets {}

  set mnt [vfs::zip::Mount $file xlsx]

  set rels 0
  if {![catch {open xlsx/xl/_rels/workbook.xml.rels r} fd]} {
    fconfigure $fd -encoding utf-8
    if {![catch {dom parse [read $fd]} rdoc]} {
      set rels 1
      set relsroot [$rdoc documentElement]
      $rdoc selectNodesNamespaces [list X [$relsroot namespaceURI]]
    }
    close $fd
  }

  if {![catch {open xlsx/xl/workbook.xml r} fd]} {
    fconfigure $fd -encoding utf-8
    if {![catch {dom parse [read $fd]} doc]} {
      set root [$doc documentElement]
      $doc selectNodesNamespaces [list X [$root namespaceURI]]
      set idx -1
      foreach node [$root selectNodes /X:workbook/X:sheets/X:sheet] {
	if {[$node hasAttribute sheetId] && [$node hasAttribute name]} {
	  set sheetId [$node @sheetId]
	  set name [$node @name]
	  set rid [$node @r:id]
	  foreach node [$relsroot selectNodes {/X:Relationships/X:Relationship[@Id=$rid]}] {
	    if {[$node hasAttribute Target]} {
	      lappend sheets [incr idx] [list sheetId $sheetId name $name rId $rid]
	    }
	  }
	}
      }
      $doc delete
................................................................................


#
# ooxml::xl_read
#

proc ::ooxml::xl_read { file args } {


  variable predefNumFmts

  package require vfs::zip

  array set cellXfs {}
  array set numFmts [array get predefNumFmts]
  array set sharedStrings {}
................................................................................

  set rels 0
  if {![catch {open xlsx/xl/_rels/workbook.xml.rels r} fd]} {
    fconfigure $fd -encoding utf-8
    if {![catch {dom parse [read $fd]} rdoc]} {
      set rels 1
      set relsroot [$rdoc documentElement]
      $rdoc selectNodesNamespaces [list X [$relsroot namespaceURI]]
    }
    close $fd
  }

  if {![catch {open xlsx/xl/workbook.xml r} fd]} {
    fconfigure $fd -encoding utf-8
    if {![catch {dom parse [read $fd]} doc]} {
      set root [$doc documentElement]
      $doc selectNodesNamespaces [list X [$root namespaceURI]]
      set idx -1
      foreach node [$root selectNodes /X:workbook/X:sheets/X:sheet] {
	if {[$node hasAttribute sheetId] && [$node hasAttribute name]} {
	  set sheetId [$node @sheetId]
	  set name [$node @name]
	  set rid [$node @r:id]
	  foreach node [$relsroot selectNodes {/X:Relationships/X:Relationship[@Id=$rid]}] {
	    if {[$node hasAttribute Target]} {
	      lappend sheets [incr idx] $sheetId $name $rid [$node @Target]
	    }
	  }
	}
      }
      $doc delete
................................................................................
    $rdoc delete
  }

  if {![catch {open xlsx/xl/sharedStrings.xml r} fd]} {
    fconfigure $fd -encoding utf-8
    if {![catch {dom parse [read $fd]} doc]} {
      set root [$doc documentElement]
      $doc selectNodesNamespaces [list X [$root namespaceURI]]
      set idx -1
      foreach shared [$root selectNodes /X:sst/X:si] {
	incr idx
	foreach node [$shared selectNodes X:t/text()] {
	  append sharedStrings($idx) [$node nodeValue]
	}
	foreach node [$shared selectNodes */X:t/text()] {
	  append sharedStrings($idx) [$node nodeValue]
	}
      }
      $doc delete
    }
    close $fd
  }


  if {![catch {open xlsx/xl/styles.xml r} fd]} {
    fconfigure $fd -encoding utf-8
    if {![catch {dom parse [read $fd]} doc]} {
      set root [$doc documentElement]
      $doc selectNodesNamespaces [list X [$root namespaceURI]]
      set idx -1
      foreach node [$root selectNodes /X:styleSheet/X:numFmts/X:numFmt] {
        incr idx
	if {[$node hasAttribute numFmtId] && [$node hasAttribute formatCode]} {
	  set numFmtId [$node @numFmtId]
	  set formatCode [$node @formatCode]
	  set datetime 0
	  foreach tag {*y* *m* *d* *h* *s*} {
	    if {[string match -nocase $tag [string map {Black {} Blue {} Cyan {} Green {} Magenta {} Red {} White {} Yellow {}} $formatCode]]} {
................................................................................
	      break
	    }
	  }
	  set numFmts($numFmtId) [list dt $datetime fmt $formatCode]
	}
      }
      set idx -1
      foreach node [$root selectNodes /X:styleSheet/X:cellXfs/X:xf] {
        incr idx
	if {[$node hasAttribute numFmtId]} {
	  set numFmtId [$node @numFmtId]
	  if {[$node hasAttribute applyNumberFormat]} {
	    set applyNumberFormat [$node @applyNumberFormat]
	  } else {
	    set applyNumberFormat 0
................................................................................
      ### READING KNOWN FORMATS AND STYLES ###

      set wb(s,@) {}

      array unset a *
      set a(max) 0
      set wb(s,numFmtsIds) {}
      foreach node [$root selectNodes /X:styleSheet/X:numFmts/X:numFmt] {
        if {[$node hasAttribute numFmtId] && [$node hasAttribute formatCode]} {
	  set wb(s,numFmts,[set idx [$node @numFmtId]]) [$node @formatCode]
	  lappend wb(s,numFmtsIds) $idx
	  if {$idx > $a(max)} {
	    set a(max) $idx
	  }
	}
................................................................................
        set a(max) $::ooxml::defaults(numFmts,start)
      }
      lappend wb(s,@) numFmtId [incr a(max)]


      set idx -1
      array unset a *
      foreach node [$root selectNodes /X:styleSheet/X:fonts/X:font] {
	incr idx
	array set a {name {} family {} size {} color {} scheme {} bold 0 italic 0 underline 0 color {}}
	foreach node1 [$node childNodes] {
	  switch -- [$node1 nodeName] {
	    b {
	      set a(bold) 1
	    }
................................................................................
	set wb(s,fonts,$idx) [array get a]
      }
      lappend wb(s,@) fonts [incr idx]


      set idx -1
      array unset a *
      foreach node [$root selectNodes /X:styleSheet/X:fills/X:fill] {
	incr idx
	array set a {patterntype {} fgcolor {} bgcolor {}}
	foreach node1 [$node childNodes] {
	  switch -- [$node1 nodeName] {
	    patternFill {
	      if {[$node1 hasAttribute patternType]} {
		set a(patterntype) [$node1 @patternType]
................................................................................
	set wb(s,fills,$idx) [array get a]
      }
      lappend wb(s,@) fills [incr idx]


      set idx -1
      unset -nocomplain d
      foreach node [$root selectNodes /X:styleSheet/X:borders/X:border] {
	incr idx
	set d {left {style {} color {}} right {style {} color {}} top {style {} color {}} bottom {style {} color {}} diagonal {style {} color {} direction {}}}
	foreach node1 [$node childNodes] {
	  if {[$node1 hasAttribute style]} {
	    set style [$node1 @style]
	  } else {
	    set style {}
................................................................................
	set wb(s,borders,$idx) $d
      }
      lappend wb(s,@) borders [incr idx]


      set idx -1
      array unset a *
      foreach node [$root selectNodes /X:styleSheet/X:cellXfs/X:xf] {
	incr idx
	array set a {numfmt 0 font 0 fill 0 border 0 xf 0 horizontal {} vertical {} rotate {} wrap {}}
        if {[$node hasAttribute numFmtId]} {
	  set a(numfmt) [$node @numFmtId]
	}
        if {[$node hasAttribute fontId]} {
	  set a(font) [$node @fontId]
................................................................................
    set wb($sheet,max_row) -1
    set wb($sheet,max_column) -1

    if {![catch {open [file join xlsx/xl $target] r} fd]} {
      fconfigure $fd -encoding utf-8
      if {![catch {dom parse [read $fd]} doc]} {
	set root [$doc documentElement]
        $doc selectNodesNamespaces [list X [$root namespaceURI]]
	set idx -1
	foreach col [$root selectNodes /X:worksheet/X:cols/X:col] {
	  incr idx
	  foreach item {min max width style bestFit customWidth} {
	    if {[$col hasAttribute $item]} {
	      switch -- $item {
	        min - max {
		  lappend wb($sheet,col,$idx) [string tolower $item] [expr {[$col @$item] - 1}]
		}
................................................................................
	    } else {
	      lappend wb($sheet,col,$idx) [string tolower $item] 0
	    }
	  }
	  lappend wb($sheet,col,$idx) string 0 nozero 0 calcfit 0
	}
	set wb($sheet,cols) [incr idx]
	foreach cell [$root selectNodes /X:worksheet/X:sheetData/X:row/X:c] {
	  if {[$cell hasAttribute t]} {
	    set type [$cell @t]
	  } else {
	    set type n
	  }
	  set value {}
	  set datetime {}
	  switch -- $type {
	    n - b - d - str {
	      # number (default), boolean, iso-date, formula string
	      if {[set node [$cell selectNodes X:v/text()]] ne {}} {
		set value [$node nodeValue]
		if {$type eq {n} && [$cell hasAttribute s] && [string is double -strict $value]} {
		  set idx [$cell @s]
		  if {[dict exists $cellXfs($idx) nfi]} {
		    set numFmtId [dict get $cellXfs($idx) nfi]
		    if {[info exists numFmts($numFmtId)] && [dict exists $numFmts($numFmtId) dt] && [dict get $numFmts($numFmtId) dt]} {
		      set datetime $value
................................................................................
		} 
	      } else {
		if {![$cell hasAttribute s]} continue
	      }
	    }
	    s {
	      # shared string
	      if {[set node [$cell selectNodes X:v/text()]] ne {}} {
		set index [$node nodeValue]
		if {[info exists sharedStrings($index)]} {
		  set value $sharedStrings($index)
		}
	      } else {
		if {![$cell hasAttribute s]} continue
	      }
	    }
	    inlineStr {
	      # inline string
	      if {[set string [$cell selectNodes X:is]] ne {}} {
		foreach node [$string selectNodes X:t/text()] {
		  append value [$node nodeValue]
		}
		foreach node [$string selectNodes */X:t/text()] {
		  append value [$node nodeValue]
		}
	      } else {
		if {![$cell hasAttribute s]} continue
	      }
	    }
	    e {
................................................................................
		set wb($sheet,t,[StringToRowColumn [$cell @r]]) [$cell @t]
	      }
	    }
	    set wb($sheet,v,[StringToRowColumn [$cell @r]]) $value
	    if {!$opts(valuesonly) && $datetime ne {}} {
	      set wb($sheet,d,[StringToRowColumn [$cell @r]]) $datetime
	    }
	    if {!$opts(valuesonly) && [set node [$cell selectNodes X:f/text()]] ne {}} {
	      set wb($sheet,f,[StringToRowColumn [$cell @r]]) [$node nodeValue]
	    }
	  }
	}
	if {!$opts(valuesonly)} {
	  foreach row [$root selectNodes /X:worksheet/X:sheetData/X:row] {
	    if {[$row hasAttribute r] && [$row hasAttribute ht] && [$row hasAttribute customHeight] && [$row @customHeight] == 1} {
	      dict set wb($sheet,rowheight) [expr {[$row @r] - 1}] [$row @ht]
	    }
	  }
	}
	if {!$opts(valuesonly)} {
	  foreach freeze [$root selectNodes /X:worksheet/X:sheetViews/X:sheetView/X:pane] {
	    if {[$freeze hasAttribute topLeftCell] && [$freeze hasAttribute state] && [$freeze @state] eq {frozen}} {
	      set wb($sheet,freeze) [$freeze @topLeftCell]
	    }
	  }
	}
	if {!$opts(valuesonly)} {
	  foreach filter [$root selectNodes /X:worksheet/X:autoFilter] {
	    if {[$filter hasAttribute ref]} {
	      lappend wb($sheet,filter) [$filter @ref]
	    }
	  }
	}
	if {!$opts(valuesonly)} {
	  foreach merge [$root selectNodes /X:worksheet/X:mergeCells/X:mergeCell] {
	    if {[$merge hasAttribute ref]} {
	      lappend wb($sheet,merge) [$merge @ref]
	    }
	  }
	}
	$doc delete
      }
................................................................................
#
# ooxml::InitNodeCommands
#


proc ooxml::InitNodeCommands {} {
  variable initNodeCmds


  if {[info exists initNodeCmds] && $initNodeCmds} return

  set elementNodes {
    AppVersion Application
    Company
    Default DocSecurity
................................................................................
    name numFmt numFmts
    pageMargins pane patternFill
    right row
    scheme sheet sheetData sheetFormatPr sheetView sheetViews sheets si sz
    t tableStyles top
    u
    v
    vt:i4 vt:lpstr vt:lpstrvt:lpstr vt:variant vt:vector
    workbookPr workbookView
    xf
  }

  namespace eval ::ooxml "dom createNodeCmd textNode Text; namespace export Text"

  foreach tag $elementNodes {





























    namespace eval ::ooxml "dom createNodeCmd -tagName $tag elementNode Tag_$tag; namespace export Tag_$tag"
  }
  
  set initNodeCmds 1
}


#
................................................................................
    my variable fonts
    my variable numFmts
    my variable styles
    my variable fills
    my variable borders
    my variable cols



    array set opts {
      holdcontainerdirectory 0
    }

    set len [llength $args]
    set idx 0
    for {set idx 0} {$idx < $len} {incr idx} {
................................................................................
	dict set cells($n) v $pos
      }
    }
    unset -nocomplain n v
    array unset lookup
    
    # _rels/.rels
    set doc [dom createDocument Relationships]
    set root [$doc documentElement]

    set rId 0

    $root setAttribute xmlns http://schemas.openxmlformats.org/package/2006/relationships

    $root appendFromScript {
      Tag_Relationship Id rId1 Type http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument Target xl/workbook.xml {}
      Tag_Relationship Id rId2 Type http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties Target docProps/app.xml {}
      Tag_Relationship Id rId3 Type http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties Target docProps/core.xml {}
    }
    ::ooxml::Dom2zip $zf $root "_rels/.rels" cd count
    $doc delete

    # [Content_Types].xml
    set doc [dom createDocument Types]
    set root [$doc documentElement]


    $root setAttribute xmlns http://schemas.openxmlformats.org/package/2006/content-types

    $root appendFromScript {
      Tag_Default Extension xml ContentType application/xml {}
      Tag_Default Extension rels ContentType application/vnd.openxmlformats-package.relationships+xml {}
      Tag_Override PartName /xl/workbook.xml ContentType application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml {}
      Tag_Override PartName /xl/worksheets/sheet1.xml ContentType application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml {}
      for {set ws 1} {$ws <= $obj(sheets)} {incr ws} {
	Tag_Override PartName /xl/theme/theme${ws}.xml ContentType application/vnd.openxmlformats-officedocument.theme+xml {}
................................................................................
      Tag_Override PartName /docProps/core.xml ContentType application/vnd.openxmlformats-package.core-properties+xml {}
      Tag_Override PartName /docProps/app.xml ContentType application/vnd.openxmlformats-officedocument.extended-properties+xml {}
    }
    ::ooxml::Dom2zip $zf $root "\[Content_Types\].xml" cd count
    $doc delete

    # docProps/app.xml
    set doc [set obj(doc,) [dom createDocument Properties]]
    set root [$doc documentElement]

    $root setAttribute xmlns http://schemas.openxmlformats.org/officeDocument/2006/extended-properties
    $root setAttribute xmlns:vt http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes

    $root appendFromScript {
      Tag_Application { Text $obj(application) }
      Tag_DocSecurity { Text 0 }
      Tag_ScaleCrop { Text false }
      Tag_HeadingPairs {
	Tag_vt:vector size 2 baseType variant {
................................................................................
      Tag_HyperlinksChanged { Text false }
      Tag_AppVersion { Text 1.0 }
    }
    ::ooxml::Dom2zip $zf $root "docProps/app.xml" cd count
    $doc delete

    # docProps/core.xml
    set doc [dom createDocument cp:coreProperties]
    set root [$doc documentElement]

    $root setAttribute xmlns:cp http://schemas.openxmlformats.org/package/2006/metadata/core-properties
    $root setAttribute xmlns:dc http://purl.org/dc/elements/1.1/
    $root setAttribute xmlns:dcterms http://purl.org/dc/terms/
    $root setAttribute xmlns:dcmitype http://purl.org/dc/dcmitype/
    $root setAttribute xmlns:xsi http://www.w3.org/2001/XMLSchema-instance

    $root appendFromScript {
      Tag_dc:creator { Text $obj(creator) }
      Tag_cp:lastModifiedBy { Text $obj(lastModifiedBy) }
      Tag_dcterms:created xsi:type dcterms:W3CDTF { Text $obj(created) }
      Tag_dcterms:modified xsi:type dcterms:W3CDTF { Text $obj(modified) }
    }
    ::ooxml::Dom2zip $zf $root "docProps/core.xml" cd count
    $doc delete

    # xl/_rels/workbook.xml.rels
    set doc [dom createDocument Relationships]
    set root [$doc documentElement]

    $root setAttribute xmlns http://schemas.openxmlformats.org/package/2006/relationships

    $root appendFromScript {
      for {set ws 1} {$ws <= $obj(sheets)} {incr ws} {
	Tag_Relationship Id rId$ws Type http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet Target worksheets/sheet${ws}.xml {}
      }
      set rId [incr ws -1]
      Tag_Relationship Id rId[incr rId] Type http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme Target theme/theme1.xml {}
      Tag_Relationship Id rId[incr rId] Type http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles Target styles.xml {}
................................................................................
    }
    ::ooxml::Dom2zip $zf $root "xl/_rels/workbook.xml.rels" cd count
    $doc delete


    # xl/sharedStrings.xml
    if {$obj(sharedStrings) > 0} {
      set doc [dom createDocument sst]
      set root [$doc documentElement]

      $root setAttribute xmlns http://schemas.openxmlformats.org/spreadsheetml/2006/main
      $root setAttribute count [llength $sharedStrings]
      $root setAttribute uniqueCount [llength $sharedStrings]

      $root appendFromScript {
	foreach string $sharedStrings {
	  Tag_si {
	    Tag_t { Text $string }
................................................................................
      ::ooxml::Dom2zip $zf $root "xl/sharedStrings.xml" cd count
      $doc delete
    }


    # xl/calcChain.xml
    if {$obj(calcChain)} {
      set doc [dom createDocument calcChain]
      set root [$doc documentElement]

      $root setAttribute xmlns http://schemas.openxmlformats.org/spreadsheetml/2006/main

      $root appendFromScript {
	Tag_c r C1 i 3 l 1 {}
	Tag_c r A3 i 2 {}
      }
      ::ooxml::Dom2zip $zf $root "xl/calcChain.xml" cd count
      $doc delete
    }


    # xl/styles.xml
    set doc [dom createDocument styleSheet]
    set root [$doc documentElement]

    $root setAttribute xmlns http://schemas.openxmlformats.org/spreadsheetml/2006/main
    $root setAttribute xmlns:mc http://schemas.openxmlformats.org/markup-compatibility/2006
    $root setAttribute xmlns:x14ac http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac
    $root setAttribute mc:Ignorable x14ac

    $root appendFromScript {
      if {$obj(numFmts) > $::ooxml::defaults(numFmts,start)} {
	Tag_numFmts count [llength [array names numFmts]] {
	  foreach idx [lsort -integer [array names numFmts]] {
	    Tag_numFmt numFmtId $idx formatCode $numFmts($idx) {}
................................................................................
      Tag_tableStyles count 0 {}
    }
    ::ooxml::Dom2zip $zf $root "xl/styles.xml" cd count
    $doc delete


    # xl/theme/theme1.xml
    set doc [dom createDocument a:theme]
    set root [$doc documentElement]

    $root setAttribute xmlns:a http://schemas.openxmlformats.org/drawingml/2006/main
    $root setAttribute name Office-Design

    $root appendFromScript {
      Tag_a:themeElements {
	Tag_a:clrScheme name Office {
	  Tag_a:dk1 {
	    Tag_a:sysClr val windowText lastClr 000000 {}
................................................................................
      Tag_a:extraClrSchemeLst {}
    }
    ::ooxml::Dom2zip $zf $root "xl/theme/theme1.xml" cd count
    $doc delete


    # xl/workbook.xml
    set doc [dom createDocument workbook]
    set root [$doc documentElement]

    $root setAttribute xmlns http://schemas.openxmlformats.org/spreadsheetml/2006/main
    $root setAttribute xmlns:r http://schemas.openxmlformats.org/officeDocument/2006/relationships

    $root appendFromScript {
      Tag_fileVersion appName xl lastEdited 5 lowestEdited 5 rupBuild 5000 {}
      Tag_workbookPr showInkAnnotation 0 autoCompressPictures 0 {}
      Tag_bookViews {
	Tag_workbookView activeTab 1 {}
      }
................................................................................
    ::ooxml::Dom2zip $zf $root "xl/workbook.xml" cd count
    $doc delete


    # xl/worksheets/sheet1.xml SHEET

    for {set ws 1} {$ws <= $obj(sheets)} {incr ws} {
      set doc [dom createDocument worksheet]
      set root [$doc documentElement]
      $root setAttribute xmlns http://schemas.openxmlformats.org/spreadsheetml/2006/main

      $root setAttribute xmlns:r http://schemas.openxmlformats.org/officeDocument/2006/relationships
      $root setAttribute xmlns:mc http://schemas.openxmlformats.org/markup-compatibility/2006
      $root setAttribute xmlns:x14ac http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac
      $root setAttribute mc:Ignorable x14ac



      $root appendFromScript {
	Tag_dimension ref [::ooxml::RowColumnToString $obj(dminrow,$ws),$obj(dmincol,$ws)]:[::ooxml::RowColumnToString $obj(dmaxrow,$ws),$obj(dmaxcol,$ws)] {}
	Tag_sheetViews {
	  Tag_sheetView workbookViewId 0 {
	    if {$obj(freeze,$ws) ne {}} {
	      lassign [split [::ooxml::StringToRowColumn $obj(freeze,$ws)] ,] row col
................................................................................
	      Tag_mergeCell ref $item {}
	    }
	  }
	}
	Tag_pageMargins left 0.75 right 0.75 top 1 bottom 1 header 0.5 footer 0.5 {}
      }

      if {[set colsNode [$root selectNodes /worksheet/cols]] ne {}} {
	if {[info exists obj($ws,cols)] && $obj($ws,cols) > 0} {
	  $colsNode appendFromScript {
	    foreach idx [lsort -dictionary [array names cols $ws,*]] {
	      set attr {}
	      lappend attr min [expr {[dict get $cols($idx) min] + 1}] max [expr {[dict get $cols($idx) max] + 1}]
	      if {[dict get $cols($idx) width] ne {}} {
		lappend attr width [dict get $cols($idx) width]







>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
>












|








|

|



|
|







 







>
>







 







|








|

|



|
|







 







|

|

|


|













|

|







 







|







 







|







 







|







 







|







 







|







 







|







 







|

|







 







|










|







 







|










|
|


|







 







|





|






|






|






|







 







>







 







|







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|







 







>
>







 







|




<
<









|


<
<
<







 







|


|
<







 







|


|
|
|
|
<











|


<
<







 







|


<







 







|


<
<










|


|
|
<







 







|


<







 







|


|
<







 







|

<
>
|
|
|

>
>







 







|







176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
...
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
...
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
...
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
...
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
...
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
...
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
...
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
....
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
....
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
....
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
....
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
....
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
....
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
....
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
....
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
....
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
....
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
....
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
....
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579


2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591



2592
2593
2594
2595
2596
2597
2598
....
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617

2618
2619
2620
2621
2622
2623
2624
....
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658

2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672


2673
2674
2675
2676
2677
2678
2679
....
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695

2696
2697
2698
2699
2700
2701
2702
....
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717


2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732

2733
2734
2735
2736
2737
2738
2739
....
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867

2868
2869
2870
2871
2872
2873
2874
....
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199

3200
3201
3202
3203
3204
3205
3206
....
3220
3221
3222
3223
3224
3225
3226
3227
3228

3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
....
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
  variable initNodeCmds
  variable predefNumFmts
  variable predefColors
  variable predefColorsName
  variable predefColorsARBG
  variable predefBorderLineStyles
  variable predefPatternType
  variable xmlns

  set defaults(path) {.}

  set defaults(numFmts,start) 166
  set defaults(cols,width) 10.83203125

  # predefined formats
................................................................................
    mediumDashDotDot
    mediumDashDotDot
    none
    slantDashDot
    thick
    thin
  }

  array set xmlns {
    M http://schemas.openxmlformats.org/spreadsheetml/2006/main
    CT http://schemas.openxmlformats.org/package/2006/content-types
    EP http://schemas.openxmlformats.org/officeDocument/2006/extended-properties
    PR http://schemas.openxmlformats.org/package/2006/relationships
    a http://schemas.openxmlformats.org/drawingml/2006/main
    cp http://schemas.openxmlformats.org/package/2006/metadata/core-properties
    dc http://purl.org/dc/elements/1.1/
    dcmitype http://purl.org/dc/dcmitype/
    dcterms http://purl.org/dc/terms/
    mc http://schemas.openxmlformats.org/markup-compatibility/2006
    r http://schemas.openxmlformats.org/officeDocument/2006/relationships
    vt http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes
    x14ac http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac
    x16r2 http://schemas.microsoft.com/office/spreadsheetml/2015/02/main
    xsi http://www.w3.org/2001/XMLSchema-instance
  }

  # ar - Arabic - العربية 
  msgcat::mcset ar LANGUAGE \u0627\u0644\u0639\u0631\u0628\u064a\u0629
  msgcat::mcset ar Book \u0627\u0644\u0643\u062a\u0627\u0628
  msgcat::mcset ar Worksheets "\u0623\u0648\u0631\u0627\u0642 \u0627\u0644\u0639\u0645\u0644"
  msgcat::mcset ar Sheet \u0627\u0644\u0648\u0631\u0642\u0629
  # cs - Czech - čeština, český jazyk
................................................................................


#
# ooxml::xl_sheets
#

proc ::ooxml::xl_sheets { file } {
  variable xmlns

  package require vfs::zip

  set sheets {}

  set mnt [vfs::zip::Mount $file xlsx]

  set rels 0
  if {![catch {open xlsx/xl/_rels/workbook.xml.rels r} fd]} {
    fconfigure $fd -encoding utf-8
    if {![catch {dom parse [read $fd]} rdoc]} {
      set rels 1
      set relsroot [$rdoc documentElement]
      $rdoc selectNodesNamespaces [list PR $xmlns(PR)]
    }
    close $fd
  }

  if {![catch {open xlsx/xl/workbook.xml r} fd]} {
    fconfigure $fd -encoding utf-8
    if {![catch {dom parse [read $fd]} doc]} {
      set root [$doc documentElement]
      $doc selectNodesNamespaces [list M $xmlns(M) r $xmlns(r)]
      set idx -1
      foreach node [$root selectNodes /M:workbook/M:sheets/M:sheet] {
	if {[$node hasAttribute sheetId] && [$node hasAttribute name]} {
	  set sheetId [$node @sheetId]
	  set name [$node @name]
	  set rid [$node getAttributeNS $xmlns(r) id]
	  foreach node [$relsroot selectNodes {/PR:Relationships/PR:Relationship[@Id=$rid]}] {
	    if {[$node hasAttribute Target]} {
	      lappend sheets [incr idx] [list sheetId $sheetId name $name rId $rid]
	    }
	  }
	}
      }
      $doc delete
................................................................................


#
# ooxml::xl_read
#

proc ::ooxml::xl_read { file args } {
  variable xmlns

  variable predefNumFmts

  package require vfs::zip

  array set cellXfs {}
  array set numFmts [array get predefNumFmts]
  array set sharedStrings {}
................................................................................

  set rels 0
  if {![catch {open xlsx/xl/_rels/workbook.xml.rels r} fd]} {
    fconfigure $fd -encoding utf-8
    if {![catch {dom parse [read $fd]} rdoc]} {
      set rels 1
      set relsroot [$rdoc documentElement]
      $rdoc selectNodesNamespaces [list PR $xmlns(PR)]
    }
    close $fd
  }

  if {![catch {open xlsx/xl/workbook.xml r} fd]} {
    fconfigure $fd -encoding utf-8
    if {![catch {dom parse [read $fd]} doc]} {
      set root [$doc documentElement]
      $doc selectNodesNamespaces [list M $xmlns(M) r $xmlns(r)]
      set idx -1
      foreach node [$root selectNodes /M:workbook/M:sheets/M:sheet] {
	if {[$node hasAttribute sheetId] && [$node hasAttribute name]} {
	  set sheetId [$node @sheetId]
	  set name [$node @name]
	  set rid [$node getAttributeNS $xmlns(r) id]
	  foreach node [$relsroot selectNodes {/PR:Relationships/PR:Relationship[@Id=$rid]}] {
	    if {[$node hasAttribute Target]} {
	      lappend sheets [incr idx] $sheetId $name $rid [$node @Target]
	    }
	  }
	}
      }
      $doc delete
................................................................................
    $rdoc delete
  }

  if {![catch {open xlsx/xl/sharedStrings.xml r} fd]} {
    fconfigure $fd -encoding utf-8
    if {![catch {dom parse [read $fd]} doc]} {
      set root [$doc documentElement]
      $doc selectNodesNamespaces [list M $xmlns(M)]
      set idx -1
      foreach shared [$root selectNodes /M:sst/M:si] {
	incr idx
	foreach node [$shared selectNodes M:t/text()] {
	  append sharedStrings($idx) [$node nodeValue]
	}
	foreach node [$shared selectNodes */M:t/text()] {
	  append sharedStrings($idx) [$node nodeValue]
	}
      }
      $doc delete
    }
    close $fd
  }


  if {![catch {open xlsx/xl/styles.xml r} fd]} {
    fconfigure $fd -encoding utf-8
    if {![catch {dom parse [read $fd]} doc]} {
      set root [$doc documentElement]
      $doc selectNodesNamespaces [list M $xmlns(M) mc $xmlns(mc) x14ac $xmlns(x14ac) x16r2 $xmlns(x16r2)]
      set idx -1
      foreach node [$root selectNodes /M:styleSheet/M:numFmts/M:numFmt] {
        incr idx
	if {[$node hasAttribute numFmtId] && [$node hasAttribute formatCode]} {
	  set numFmtId [$node @numFmtId]
	  set formatCode [$node @formatCode]
	  set datetime 0
	  foreach tag {*y* *m* *d* *h* *s*} {
	    if {[string match -nocase $tag [string map {Black {} Blue {} Cyan {} Green {} Magenta {} Red {} White {} Yellow {}} $formatCode]]} {
................................................................................
	      break
	    }
	  }
	  set numFmts($numFmtId) [list dt $datetime fmt $formatCode]
	}
      }
      set idx -1
      foreach node [$root selectNodes /M:styleSheet/M:cellXfs/M:xf] {
        incr idx
	if {[$node hasAttribute numFmtId]} {
	  set numFmtId [$node @numFmtId]
	  if {[$node hasAttribute applyNumberFormat]} {
	    set applyNumberFormat [$node @applyNumberFormat]
	  } else {
	    set applyNumberFormat 0
................................................................................
      ### READING KNOWN FORMATS AND STYLES ###

      set wb(s,@) {}

      array unset a *
      set a(max) 0
      set wb(s,numFmtsIds) {}
      foreach node [$root selectNodes /M:styleSheet/M:numFmts/M:numFmt] {
        if {[$node hasAttribute numFmtId] && [$node hasAttribute formatCode]} {
	  set wb(s,numFmts,[set idx [$node @numFmtId]]) [$node @formatCode]
	  lappend wb(s,numFmtsIds) $idx
	  if {$idx > $a(max)} {
	    set a(max) $idx
	  }
	}
................................................................................
        set a(max) $::ooxml::defaults(numFmts,start)
      }
      lappend wb(s,@) numFmtId [incr a(max)]


      set idx -1
      array unset a *
      foreach node [$root selectNodes /M:styleSheet/M:fonts/M:font] {
	incr idx
	array set a {name {} family {} size {} color {} scheme {} bold 0 italic 0 underline 0 color {}}
	foreach node1 [$node childNodes] {
	  switch -- [$node1 nodeName] {
	    b {
	      set a(bold) 1
	    }
................................................................................
	set wb(s,fonts,$idx) [array get a]
      }
      lappend wb(s,@) fonts [incr idx]


      set idx -1
      array unset a *
      foreach node [$root selectNodes /M:styleSheet/M:fills/M:fill] {
	incr idx
	array set a {patterntype {} fgcolor {} bgcolor {}}
	foreach node1 [$node childNodes] {
	  switch -- [$node1 nodeName] {
	    patternFill {
	      if {[$node1 hasAttribute patternType]} {
		set a(patterntype) [$node1 @patternType]
................................................................................
	set wb(s,fills,$idx) [array get a]
      }
      lappend wb(s,@) fills [incr idx]


      set idx -1
      unset -nocomplain d
      foreach node [$root selectNodes /M:styleSheet/M:borders/M:border] {
	incr idx
	set d {left {style {} color {}} right {style {} color {}} top {style {} color {}} bottom {style {} color {}} diagonal {style {} color {} direction {}}}
	foreach node1 [$node childNodes] {
	  if {[$node1 hasAttribute style]} {
	    set style [$node1 @style]
	  } else {
	    set style {}
................................................................................
	set wb(s,borders,$idx) $d
      }
      lappend wb(s,@) borders [incr idx]


      set idx -1
      array unset a *
      foreach node [$root selectNodes /M:styleSheet/M:cellXfs/M:xf] {
	incr idx
	array set a {numfmt 0 font 0 fill 0 border 0 xf 0 horizontal {} vertical {} rotate {} wrap {}}
        if {[$node hasAttribute numFmtId]} {
	  set a(numfmt) [$node @numFmtId]
	}
        if {[$node hasAttribute fontId]} {
	  set a(font) [$node @fontId]
................................................................................
    set wb($sheet,max_row) -1
    set wb($sheet,max_column) -1

    if {![catch {open [file join xlsx/xl $target] r} fd]} {
      fconfigure $fd -encoding utf-8
      if {![catch {dom parse [read $fd]} doc]} {
	set root [$doc documentElement]
        $doc selectNodesNamespaces [list M $xmlns(M) r $xmlns(r) mc $xmlns(mc) x14ac $xmlns(x14ac)]
	set idx -1
	foreach col [$root selectNodes /M:worksheet/M:cols/M:col] {
	  incr idx
	  foreach item {min max width style bestFit customWidth} {
	    if {[$col hasAttribute $item]} {
	      switch -- $item {
	        min - max {
		  lappend wb($sheet,col,$idx) [string tolower $item] [expr {[$col @$item] - 1}]
		}
................................................................................
	    } else {
	      lappend wb($sheet,col,$idx) [string tolower $item] 0
	    }
	  }
	  lappend wb($sheet,col,$idx) string 0 nozero 0 calcfit 0
	}
	set wb($sheet,cols) [incr idx]
	foreach cell [$root selectNodes /M:worksheet/M:sheetData/M:row/M:c] {
	  if {[$cell hasAttribute t]} {
	    set type [$cell @t]
	  } else {
	    set type n
	  }
	  set value {}
	  set datetime {}
	  switch -- $type {
	    n - b - d - str {
	      # number (default), boolean, iso-date, formula string
	      if {[set node [$cell selectNodes M:v/text()]] ne {}} {
		set value [$node nodeValue]
		if {$type eq {n} && [$cell hasAttribute s] && [string is double -strict $value]} {
		  set idx [$cell @s]
		  if {[dict exists $cellXfs($idx) nfi]} {
		    set numFmtId [dict get $cellXfs($idx) nfi]
		    if {[info exists numFmts($numFmtId)] && [dict exists $numFmts($numFmtId) dt] && [dict get $numFmts($numFmtId) dt]} {
		      set datetime $value
................................................................................
		} 
	      } else {
		if {![$cell hasAttribute s]} continue
	      }
	    }
	    s {
	      # shared string
	      if {[set node [$cell selectNodes M:v/text()]] ne {}} {
		set index [$node nodeValue]
		if {[info exists sharedStrings($index)]} {
		  set value $sharedStrings($index)
		}
	      } else {
		if {![$cell hasAttribute s]} continue
	      }
	    }
	    inlineStr {
	      # inline string
	      if {[set string [$cell selectNodes M:is]] ne {}} {
		foreach node [$string selectNodes M:t/text()] {
		  append value [$node nodeValue]
		}
		foreach node [$string selectNodes */M:t/text()] {
		  append value [$node nodeValue]
		}
	      } else {
		if {![$cell hasAttribute s]} continue
	      }
	    }
	    e {
................................................................................
		set wb($sheet,t,[StringToRowColumn [$cell @r]]) [$cell @t]
	      }
	    }
	    set wb($sheet,v,[StringToRowColumn [$cell @r]]) $value
	    if {!$opts(valuesonly) && $datetime ne {}} {
	      set wb($sheet,d,[StringToRowColumn [$cell @r]]) $datetime
	    }
	    if {!$opts(valuesonly) && [set node [$cell selectNodes M:f/text()]] ne {}} {
	      set wb($sheet,f,[StringToRowColumn [$cell @r]]) [$node nodeValue]
	    }
	  }
	}
	if {!$opts(valuesonly)} {
	  foreach row [$root selectNodes /M:worksheet/M:sheetData/M:row] {
	    if {[$row hasAttribute r] && [$row hasAttribute ht] && [$row hasAttribute customHeight] && [$row @customHeight] == 1} {
	      dict set wb($sheet,rowheight) [expr {[$row @r] - 1}] [$row @ht]
	    }
	  }
	}
	if {!$opts(valuesonly)} {
	  foreach freeze [$root selectNodes /M:worksheet/M:sheetViews/M:sheetView/M:pane] {
	    if {[$freeze hasAttribute topLeftCell] && [$freeze hasAttribute state] && [$freeze @state] eq {frozen}} {
	      set wb($sheet,freeze) [$freeze @topLeftCell]
	    }
	  }
	}
	if {!$opts(valuesonly)} {
	  foreach filter [$root selectNodes /M:worksheet/M:autoFilter] {
	    if {[$filter hasAttribute ref]} {
	      lappend wb($sheet,filter) [$filter @ref]
	    }
	  }
	}
	if {!$opts(valuesonly)} {
	  foreach merge [$root selectNodes /M:worksheet/M:mergeCells/M:mergeCell] {
	    if {[$merge hasAttribute ref]} {
	      lappend wb($sheet,merge) [$merge @ref]
	    }
	  }
	}
	$doc delete
      }
................................................................................
#
# ooxml::InitNodeCommands
#


proc ooxml::InitNodeCommands {} {
  variable initNodeCmds
  variable xmlns

  if {[info exists initNodeCmds] && $initNodeCmds} return

  set elementNodes {
    AppVersion Application
    Company
    Default DocSecurity
................................................................................
    name numFmt numFmts
    pageMargins pane patternFill
    right row
    scheme sheet sheetData sheetFormatPr sheetView sheetViews sheets si sz
    t tableStyles top
    u
    v
    vt:i4 vt:lpstr vt:variant vt:vector
    workbookPr workbookView
    xf
  }

  namespace eval ::ooxml "dom createNodeCmd textNode Text; namespace export Text"

  foreach tag $elementNodes {
    switch -glob -- $tag {
      a:* {
	set ns $xmlns(a)
      }
      cp:* {
	set ns $xmlns(cp)
      }
      dc:* {
	set ns $xmlns(dc)
      }
      dcterms:* {
	set ns $xmlns(dcterms)
      }
      vt:* {
	set ns $xmlns(vt)
      }
      AppVersion - Application - Company - DocSecurity - HeadingPairs - HyperlinksChanged - LinksUpToDate - ScaleCrop - SharedDoc - TitlesOfParts {
	set ns $xmlns(EP)
      }
      Default - Override {
	set ns $xmlns(CT)
      }
      Relationship {
	set ns $xmlns(PR)
      }
      default {
	set ns $xmlns(M)
      }
    }
    namespace eval ::ooxml "dom createNodeCmd -tagName $tag -namespace $ns elementNode Tag_$tag; namespace export Tag_$tag"
  }
  
  set initNodeCmds 1
}


#
................................................................................
    my variable fonts
    my variable numFmts
    my variable styles
    my variable fills
    my variable borders
    my variable cols

    upvar #0 ::ooxml::xmlns xmlns

    array set opts {
      holdcontainerdirectory 0
    }

    set len [llength $args]
    set idx 0
    for {set idx 0} {$idx < $len} {incr idx} {
................................................................................
	dict set cells($n) v $pos
      }
    }
    unset -nocomplain n v
    array unset lookup
    
    # _rels/.rels
    set doc [dom createDocumentNS $xmlns(PR) Relationships]
    set root [$doc documentElement]

    set rId 0



    $root appendFromScript {
      Tag_Relationship Id rId1 Type http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument Target xl/workbook.xml {}
      Tag_Relationship Id rId2 Type http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties Target docProps/app.xml {}
      Tag_Relationship Id rId3 Type http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties Target docProps/core.xml {}
    }
    ::ooxml::Dom2zip $zf $root "_rels/.rels" cd count
    $doc delete

    # [Content_Types].xml
    set doc [dom createDocumentNS $xmlns(CT) Types]
    set root [$doc documentElement]




    $root appendFromScript {
      Tag_Default Extension xml ContentType application/xml {}
      Tag_Default Extension rels ContentType application/vnd.openxmlformats-package.relationships+xml {}
      Tag_Override PartName /xl/workbook.xml ContentType application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml {}
      Tag_Override PartName /xl/worksheets/sheet1.xml ContentType application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml {}
      for {set ws 1} {$ws <= $obj(sheets)} {incr ws} {
	Tag_Override PartName /xl/theme/theme${ws}.xml ContentType application/vnd.openxmlformats-officedocument.theme+xml {}
................................................................................
      Tag_Override PartName /docProps/core.xml ContentType application/vnd.openxmlformats-package.core-properties+xml {}
      Tag_Override PartName /docProps/app.xml ContentType application/vnd.openxmlformats-officedocument.extended-properties+xml {}
    }
    ::ooxml::Dom2zip $zf $root "\[Content_Types\].xml" cd count
    $doc delete

    # docProps/app.xml
    set doc [set obj(doc,) [dom createDocumentNS $xmlns(EP) Properties]]
    set root [$doc documentElement]

    $root setAttributeNS {} xmlns:vt $xmlns(vt)


    $root appendFromScript {
      Tag_Application { Text $obj(application) }
      Tag_DocSecurity { Text 0 }
      Tag_ScaleCrop { Text false }
      Tag_HeadingPairs {
	Tag_vt:vector size 2 baseType variant {
................................................................................
      Tag_HyperlinksChanged { Text false }
      Tag_AppVersion { Text 1.0 }
    }
    ::ooxml::Dom2zip $zf $root "docProps/app.xml" cd count
    $doc delete

    # docProps/core.xml
    set doc [dom createDocumentNS $xmlns(cp) cp:coreProperties]
    set root [$doc documentElement]

    $root setAttributeNS {} xmlns:dc $xmlns(dc)
    $root setAttributeNS {} xmlns:dcterms $xmlns(dcterms)
    $root setAttributeNS {} xmlns:dcmitype $xmlns(dcmitype)
    $root setAttributeNS {} xmlns:xsi $xmlns(xsi)


    $root appendFromScript {
      Tag_dc:creator { Text $obj(creator) }
      Tag_cp:lastModifiedBy { Text $obj(lastModifiedBy) }
      Tag_dcterms:created xsi:type dcterms:W3CDTF { Text $obj(created) }
      Tag_dcterms:modified xsi:type dcterms:W3CDTF { Text $obj(modified) }
    }
    ::ooxml::Dom2zip $zf $root "docProps/core.xml" cd count
    $doc delete

    # xl/_rels/workbook.xml.rels
    set doc [dom createDocumentNS $xmlns(PR) Relationships]
    set root [$doc documentElement]



    $root appendFromScript {
      for {set ws 1} {$ws <= $obj(sheets)} {incr ws} {
	Tag_Relationship Id rId$ws Type http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet Target worksheets/sheet${ws}.xml {}
      }
      set rId [incr ws -1]
      Tag_Relationship Id rId[incr rId] Type http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme Target theme/theme1.xml {}
      Tag_Relationship Id rId[incr rId] Type http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles Target styles.xml {}
................................................................................
    }
    ::ooxml::Dom2zip $zf $root "xl/_rels/workbook.xml.rels" cd count
    $doc delete


    # xl/sharedStrings.xml
    if {$obj(sharedStrings) > 0} {
      set doc [dom createDocumentNS $xmlns(M) sst]
      set root [$doc documentElement]


      $root setAttribute count [llength $sharedStrings]
      $root setAttribute uniqueCount [llength $sharedStrings]

      $root appendFromScript {
	foreach string $sharedStrings {
	  Tag_si {
	    Tag_t { Text $string }
................................................................................
      ::ooxml::Dom2zip $zf $root "xl/sharedStrings.xml" cd count
      $doc delete
    }


    # xl/calcChain.xml
    if {$obj(calcChain)} {
      set doc [dom createDocumentNS $xmlns(M) calcChain]
      set root [$doc documentElement]



      $root appendFromScript {
	Tag_c r C1 i 3 l 1 {}
	Tag_c r A3 i 2 {}
      }
      ::ooxml::Dom2zip $zf $root "xl/calcChain.xml" cd count
      $doc delete
    }


    # xl/styles.xml
    set doc [dom createDocumentNS $xmlns(M) styleSheet]
    set root [$doc documentElement]

    $root setAttributeNS {} xmlns:mc $xmlns(mc)
    $root setAttributeNS {} xmlns:x14ac $xmlns(x14ac)

    $root setAttribute mc:Ignorable x14ac

    $root appendFromScript {
      if {$obj(numFmts) > $::ooxml::defaults(numFmts,start)} {
	Tag_numFmts count [llength [array names numFmts]] {
	  foreach idx [lsort -integer [array names numFmts]] {
	    Tag_numFmt numFmtId $idx formatCode $numFmts($idx) {}
................................................................................
      Tag_tableStyles count 0 {}
    }
    ::ooxml::Dom2zip $zf $root "xl/styles.xml" cd count
    $doc delete


    # xl/theme/theme1.xml
    set doc [dom createDocumentNS $xmlns(a) a:theme]
    set root [$doc documentElement]


    $root setAttribute name Office-Design

    $root appendFromScript {
      Tag_a:themeElements {
	Tag_a:clrScheme name Office {
	  Tag_a:dk1 {
	    Tag_a:sysClr val windowText lastClr 000000 {}
................................................................................
      Tag_a:extraClrSchemeLst {}
    }
    ::ooxml::Dom2zip $zf $root "xl/theme/theme1.xml" cd count
    $doc delete


    # xl/workbook.xml
    set doc [dom createDocumentNS $xmlns(M) workbook]
    set root [$doc documentElement]

    $root setAttributeNS {} xmlns:r $xmlns(r)


    $root appendFromScript {
      Tag_fileVersion appName xl lastEdited 5 lowestEdited 5 rupBuild 5000 {}
      Tag_workbookPr showInkAnnotation 0 autoCompressPictures 0 {}
      Tag_bookViews {
	Tag_workbookView activeTab 1 {}
      }
................................................................................
    ::ooxml::Dom2zip $zf $root "xl/workbook.xml" cd count
    $doc delete


    # xl/worksheets/sheet1.xml SHEET

    for {set ws 1} {$ws <= $obj(sheets)} {incr ws} {
      set doc [dom createDocumentNS $xmlns(M) worksheet]
      set root [$doc documentElement]


      $root setAttributeNS {} xmlns:r $xmlns(r)
      $root setAttributeNS {} xmlns:mc $xmlns(mc)
      $root setAttributeNS {} xmlns:x14ac $xmlns(x14ac)
      $root setAttribute mc:Ignorable x14ac

      $doc selectNodesNamespaces [list M $xmlns(M) r $xmlns(r) mc $xmlns(mc) ac $xmlns(x14ac)]

      $root appendFromScript {
	Tag_dimension ref [::ooxml::RowColumnToString $obj(dminrow,$ws),$obj(dmincol,$ws)]:[::ooxml::RowColumnToString $obj(dmaxrow,$ws),$obj(dmaxcol,$ws)] {}
	Tag_sheetViews {
	  Tag_sheetView workbookViewId 0 {
	    if {$obj(freeze,$ws) ne {}} {
	      lassign [split [::ooxml::StringToRowColumn $obj(freeze,$ws)] ,] row col
................................................................................
	      Tag_mergeCell ref $item {}
	    }
	  }
	}
	Tag_pageMargins left 0.75 right 0.75 top 1 bottom 1 header 0.5 footer 0.5 {}
      }

      if {[set colsNode [$root selectNodes /M:worksheet/M:cols]] ne {}} {
	if {[info exists obj($ws,cols)] && $obj($ws,cols) > 0} {
	  $colsNode appendFromScript {
	    foreach idx [lsort -dictionary [array names cols $ws,*]] {
	      set attr {}
	      lappend attr min [expr {[dict get $cols($idx) min] + 1}] max [expr {[dict get $cols($idx) max] + 1}]
	      if {[dict get $cols($idx) width] ne {}} {
		lappend attr width [dict get $cols($idx) width]