yahalome's original implementation (version 1.x) edit
Code
This is the package itself:package provide excel 1.1 namespace eval excel:: { variable workbooks 0 variable workbooksArray variable workSheets variable workSheetsArray variable styles variable columnDefault variable data variable rowCounter variable columnsIndex array set columnsIndex [list A 1 B 2 C 3 D 4 E 5 F 6 G 7 H 8 I 9 J 10 K 11 L 12 M 13 N 14 O 15 P 16 Q 17 R 18 S 19 T 20 U 21 V 22 W 23 X 24 Y 25 Z 26 AA 27 AB 28 AC 29 AD 30 AE 31] } proc excel::createWorkbook {} { # # @comment create a workbook pointer # @result pointer to created workbook # incr excel::workbooks set workbookName workbook$excel::workbooks set excel::workbooksArray($workbookName) 1 return $workbookName } proc excel::createWorkSheet {workbook name} { # # @comment create a worksheet pointer # @argument workbook pointer to a workbook # @argument name name of the worksheet # @result pointer to a worksheet # variable data if {[info exists excel::workbooksArray($workbook)]} { if {![info exists ::excel::workSheets($workbook)]} { set excel::workSheets($workbook) 1 } else { incr excel::workSheets($workbook) } set workSheetName workSheet[string range ${workbook} 8 end].$excel::workSheets($workbook) set data(workSheet,$::excel::workSheets($workbook),name) $name set data(workSheet,$::excel::workSheets($workbook)) $workSheetName set data(workSheet,$workSheetName) 1 set excel::rowCounter($workSheetName) 0 return $workSheetName } else { error "$workbook is not a valid workbook" } } proc excel::createStyle {workbook args} { # # @comment create an excel style # @argument workbook pointer to a workbook # @argument args argument list # @result style pointer # variable data if {[info exists excel::styles($workbook)]} { incr excel::styles($workbook) } else { set excel::styles($workbook) 2 } set styleName s$excel::styles($workbook) foreach {name value} $args { # check that name is valid if {[lsearch "-font -fontcolor -background -bold" $name]==-1} { error "style option $name option is not supported" } set data($workbook,styles,$styleName,$name) $value } return $styleName } proc excel::setColumnType {workSheet columnIndex type} { # # @comment define a column type # @argument workSheet pointer to a workSheet # @argument columnIndex index of column # @argument type of column # @result column type is changed # variable data _checkSpreadSheet $workSheet set data($workSheet,row,$columnIndex,type) [string totitle $type] } proc excel::_checkSpreadSheet {workSheet} { variable data if {![info exists data(workSheet,$workSheet)]} { error "$workSheet is not a valid workSheet" } } proc excel::addRow {workSheet columnsDataList} { # # @comment add row to excel worksheet # @argument workSheet pointer to a workSheet # @argument args list of variables # @result row id # variable data set i 0 incr excel::rowCounter($workSheet) set data($workSheet,$excel::rowCounter($workSheet),length) [llength $columnsDataList] foreach arg $columnsDataList { incr i if {[llength $arg]>1} { if {[lsearch [list String Number] [lindex $arg 1]]!=-1} { if {[llength $arg]>2} { set data($workSheet,$excel::rowCounter($workSheet),$i,style) [lindex $arg end] } set data($workSheet,$excel::rowCounter($workSheet),$i,type) [string totitle [lindex $arg end-1]] set value [lindex $arg 0] } else { set value $arg } } else { set value $arg } set data($workSheet,$excel::rowCounter($workSheet),$i,data) $value } return row$excel::rowCounter($workSheet) } proc excel::asXml {workbook} { # # @comment returns excel workbook as xml # @argument workbook pointer to a workbook # @result workbook xml representation # variable data variable rowCounter set xml "<?xml version='1.0'?>\ <?mso-application progid='Excel.Sheet'?>\ <Workbook xmlns='urn:schemas-microsoft-com:office:spreadsheet'\ xmlns:o='urn:schemas-microsoft-com:office:office'\ xmlns:x='urn:schemas-microsoft-com:office:excel'\ xmlns:ss='urn:schemas-microsoft-com:office:spreadsheet'\ xmlns:html='http://www.w3.org/TR/REC-html40'>\ <DocumentProperties xmlns='urn:schemas-microsoft-com:office:office'>\ <Author>Ashrait</Author>\ <Created>[clock format [clock seconds] -format {%Y-%m-%dT%H:%M:%SZ}]</Created>\ <Company>Xor Technologies</Company>\ </DocumentProperties>\ <Styles>\ <Style ss:ID='Default' ss:Name='Normal'>\ <Alignment ss:Vertical='Bottom'/>\ <Font x:CharSet='177'/>\ </Style>\ <Style ss:ID='s21'>\ <Alignment ss:Horizontal='Center' ss:Vertical='Bottom'/>\ <Font x:Family='Swiss' ss:Color='#000080' ss:Bold='1'/>\ <Interior ss:Color='#99CCFF' ss:Pattern='Solid'/>\ </Style>\ <Style ss:ID='s22'>\ <Alignment ss:Vertical='Bottom'/>\ <Borders>\ <Border ss:Position='Top' ss:LineStyle='Double' ss:Weight='3'/>\ </Borders>\ <Font x:CharSet='177' x:Family='Swiss' ss:Bold='1'/>\ </Style>" if {[info exists excel::styles($workbook)]} { for {set d 2} {$d<=$excel::styles($workbook)} {incr d} { set styleName s$d append xml "<Style ss:ID='$styleName'><Alignment ss:Vertical='Bottom'/>" if {[info exists data($workbook,styles,$styleName,-font)] || [info exists data($workbook,styles,$styleName,-fontcolor)]} { append xml "<Font x:CharSet='177'" if {[info exists data($workbook,styles,$styleName,-font)]} { append xml " ss:FontName='$data($workbook,styles,$styleName,-font)'" } if {[info exists data($workbook,styles,$styleName,-fontcolor)]} { append xml " ss:Color='$data($workbook,styles,$styleName,-fontcolor)'" } if {[info exists data($workbook,styles,$styleName,-bold)]} { append xml " ss:Bold='1'" } append xml "/>" } if {[info exists data($workbook,styles,$styleName,-background)]} { append xml "<Interior ss:Color='$data($workbook,styles,$styleName,-background)' ss:Pattern='Solid'/>" } append xml "</Style>" } } append xml "</Styles>" for {set d 1} {$d<=$excel::workSheets($workbook)} {incr d} { append xml "<Worksheet ss:Name='$excel::data(workSheet,$d,name)'>\ <Table x:FullColumns='1' x:FullRows='1'>" set workSheet $excel::data(workSheet,$d) for {set i 1} {$i<=$excel::rowCounter($workSheet)} {incr i} { append xml "<Row>" for {set j 1} {$j<=$data($workSheet,$i,length)} {incr j} { set dataValue $data($workSheet,$i,$j,data) if {[string index $dataValue 0]=="="} { append xml "<Cell ss:Formula='$dataValue'" set dataValue "" set numeric 1 } else { if {[string is double -strict $dataValue]} { set numeric 1 } else { set numeric 0 } append xml "<Cell" } if {[info exists data($workSheet,$i,$j,type)]} { set type $data($workSheet,$i,$j,type) } else { if {[info exists data($workSheet,row,$j,type)]} { set type $data($workSheet,row,$j,type) } elseif {$numeric} { set type "Number" } else { set type "String" } } if {[info exists data($workSheet,$i,$j,style)]} { append xml " ss:StyleID='$data($workSheet,$i,$j,style)'>" } else { append xml ">" } append xml "<Data ss:Type='$type'>$dataValue</Data></Cell>" } append xml "</Row>" } append xml "</Table></Worksheet>" } append xml "</Workbook>" } proc excel::deleteWorkbook {workbook} { # # @comment delete a workbook pointer # @argument workbook pointer to a workbook # @result undecoded string # variable data for {set d 1} {$d<=$excel::workSheets($workbook)} {incr d} { array unset data $d set workSheet $excel::data(workSheet,$d) for {set i 1} {$i<=$excel::rowCounter($workSheet)} {incr i} { array unset data $workSheet* } unset $excel::rowCounter($workSheet) } } proc excel::addTitle {workSheet columnsDataList} { # # @comment delete a workbook pointer # @argument workbook pointer to a workbook # @result undecoded string # foreach arg $columnsDataList { lappend newArgs [list $arg String s21] } addRow $workSheet $newArgs } proc excel::addTotal {workSheet columnsDataList} { # # @comment delete a workbook pointer # @argument workbook pointer to a workbook # @result undecoded string # foreach arg $columnsDataList { lappend newArgs [list $arg String s22] } addRow $workSheet $newArgs } proc excel::setCell {workSheet row column value} { # # @comment delete a workbook pointer # @argument workbook pointer to a workbook # @result undecoded string # variable data set data($workSheet,$row,$excel::columnsIndex($column),data) $value } proc excel::getCurrentRow {workSheet} { # # @comment delete a workbook pointer # @argument workbook pointer to a workbook # @result undecoded string # return $excel::rowCounter($workSheet) }
Example
# create workbook set book [excel::createWorkbook] # create worksheets set worksheet [excel::createWorkSheet $book "test"] set worksheet2 [excel::createWorkSheet $book "hello"] # define default row types excel::setColumnType $worksheet2 1 number excel::setColumnType $worksheet2 2 number excel::setColumnType $worksheet2 3 number # create style set style [excel::createStyle $book -font Arial -background black -fontcolor red] # add simple row excel::addRow $worksheet [list 2 1 2 3] # add row with different style excel::addRow $worksheet [list 2 1 2 [list 4 number $style]] # add row with formula excel::addRow $worksheet [list 1 2 {=sum(rc[-2]+rc[-1])} ] # get the excel as xml set xml [excel::asXml $book]
Discussion
escargo 2005-07-13: After correcting a couple of typographical errors and reordering the code so I could read it with wish-reaper, I added a few lines to produce an output file.set fd [open "excel[clock seconds].xml" "w"] puts $fd $xml close $fd[yahalom] 2008-08-05: updated to latest version
[AElfwine] 2008-09-15:
[leprechau] 2008-10-27This package has some severe problems dealing with multiple simultaneous workbooks/worksheets. Consider the following simple scenerio:
set book [excel::createWorkbook] set book2 [excel::createWorkbook] set ws [excel::createWorkSheet $book "hello there"] set ws2 [excel::createWorkSheet $book2 testing] excel::addRow $ws [list 1 2 3 4 5 6] excel::addRow $ws2 [list 4 5 6 7 8 9] set fid [open book1.xml w] puts $fid [excel::asXml $book]; close $fid set fid [open book2.xml w] puts $fid [excel::asXml $book2]; close $fid excel::deleteWorkbook $book excel::deleteWorkbook $book2Firstly, both book1.xml and book2.xml will contain the row '4 5 6 7 8 9' and the first row is lost completely. Secondly, it will throw error when you try to delete the second book with a nonexistent rowConter element. In addition to that, the information in the 'data' array is not cleaned completely from either book. I am going to give this package a re-write to allow for multiple simultaneous books and worksheets and will post the results here.
[mjjensen] 2012-09-12 12:49:55:Just used this in anger - here are my patches ...
--- excel.tcl-dist 2012-08-26 04:02:59.806443173 +1000 +++ excel.tcl 2012-09-08 11:31:47.011915254 +1000 @@ -63,7 +63,7 @@ set styleName s$excel::styles($workbook) foreach {name value} $args { # check that name is valid - if {[lsearch "-font -fontcolor -background -bold" $name]==-1} { + if {[lsearch "-font -fontcolor -background -bold -numfmt" $name]==-1} { error "style option $name option is not supported" } set data($workbook,styles,$styleName,$name) $value @@ -105,7 +105,7 @@ foreach arg $columnsDataList { incr i if {[llength $arg]>1} { - if {[lsearch [list String Number] [lindex $arg 1]]!=-1} { + if {[lsearch [list String Number DateTime] [lindex $arg 1]]!=-1} { if {[llength $arg]>2} { set data($workSheet,$excel::rowCounter($workSheet),$i,style) [lindex $arg end] @@ -122,6 +122,30 @@ } return row$excel::rowCounter($workSheet) } + +proc excel::addRowLists {workSheet columnsDataList} { +# +# @comment add row to excel worksheet +# @argument workSheet pointer to a workSheet +# @argument args list of variables +# @result row id +# + variable data + set i 0 + incr excel::rowCounter($workSheet) + set data($workSheet,$excel::rowCounter($workSheet),length) [llength $columnsDataList] + foreach arg $columnsDataList { + incr i + set data($workSheet,$excel::rowCounter($workSheet),$i,data) [lindex $arg 0] + if {[llength $arg] > 1} { + set data($workSheet,$excel::rowCounter($workSheet),$i,type) [string totitle [lindex $arg 1]] + if {[llength $arg] > 2} { + set data($workSheet,$excel::rowCounter($workSheet),$i,style) [lindex $arg 2] + } + } + } + return row$excel::rowCounter($workSheet) +} proc excel::asXml {workbook} { # # @comment returns excel workbook as xml @@ -162,7 +186,12 @@ if {[info exists excel::styles($workbook)]} { for {set d 2} {$d<=$excel::styles($workbook)} {incr d} { set styleName s$d - append xml "<Style ss:ID='$styleName'><Alignment ss:Vertical='Bottom'/>" + append xml "<Style ss:ID='$styleName'>" + if {[info exists data($workbook,styles,$styleName,-numfmt)]} { + set numfmt [regsub -all {\-} $data($workbook,styles,$styleName,-numfmt) {\-}] + append xml "<NumberFormat ss:Format='$numfmt'/>" + } + append xml "<Alignment ss:Vertical='Bottom'/>" if {[info exists data($workbook,styles,$styleName,-font)] || [info exists data($workbook,styles,$styleName,-fontcolor)]} { append xml "<Font x:CharSet='177'" if {[info exists data($workbook,styles,$styleName,-font)]} { @@ -207,8 +236,8 @@ if {[info exists data($workSheet,$i,$j,type)]} { set type $data($workSheet,$i,$j,type) } else { - if {[info exists data($workSheet,row,$j,type)]} { - set type $data($workSheet,row,$j,type) + if {[info exists data($workSheet,row,$i,type)]} { + set type $data($workSheet,row,$i,type) } elseif {$numeric} { set type "Number" } else { @@ -220,6 +249,9 @@ } else { append xml ">" } + if {$type == "Datetime"} { + set type "DateTime" + } append xml "<Data ss:Type='$type'>$dataValue</Data></Cell>" } append xml "</Row>" @@ -236,12 +268,14 @@ # variable data for {set d 1} {$d<=$excel::workSheets($workbook)} {incr d} { - array unset data $d - set workSheet $excel::data(workSheet,$d) + # array unset data $d + foreach {n v} [array get data $d] { unset data($n) } + set workSheet $excel::data(workSheet,$d) for {set i 1} {$i<=$excel::rowCounter($workSheet)} {incr i} { - array unset data $workSheet* + # array unset data $workSheet* + foreach {n v} [array get data $workSheet*] { unset data($n) } } - unset $excel::rowCounter($workSheet) + unset excel::rowCounter($workSheet) } } proc excel::addTitle {workSheet columnsDataList} { @@ -253,7 +287,7 @@ foreach arg $columnsDataList { lappend newArgs [list $arg String s21] } - addRow $workSheet $newArgs + addRowLists $workSheet $newArgs } proc excel::addTotal {workSheet columnsDataList} { # @@ -264,7 +298,7 @@ foreach arg $columnsDataList { lappend newArgs [list $arg String s22] } - addRow $workSheet $newArgs + addRowLists $workSheet $newArgs } proc excel::setCell {workSheet row column value} { #
KimmellC's rewrite (version 2.x) edit
KimmellC - 2017-11-30 00:42:31I rewrote this and added several more features:There is one issue if there is a large amount of data inserted it crawls. I think that it is due to using an array for data structure, but am not sure.Code
package provide Tcl2ExXML 2.0 ## ♦ DESCRIPTION: CREATE NAMESPACE VARIABLES ## • VARIABLE: WBCounter - <INT> COUNT OF ACTIVE WORKBOOKS ## • ARRAY/VARIABLE: WorkBooksArray :KEY = (WB<INT> ) :VALUE = <STRING> WORKBOOK NAME ## • ARRAY/VARIABLE: RowCounter :KEY = (WB<INT>.WS<INT> ) :VALUE = <INT> COUNT OF ACTIVE ROWS ON GIVEN WORKBOOK ## • ARRAY/VARIABLE: ColCounter :KEY = (WB<INT>.WS<INT> ) :VALUE = <INT> COUNT OF ACTIVE COLUMN ON GIVEN WORKBOOK ## • ARRAY/VARIABLE: StylesCounter :KEY = (WB<INT>.WS<INT> ) :VALUE = <INT> COUNT OF STYLES ON GIVEN WORKBOOK ## • ARRAY/VARIABLE: StylesArray :KEY = (WB<INT>.S<INT>.<SO_1><SO_2><So_n> ) :VALUE = ## • ARRAY/VARIABLE: ColStyle :KEY = (WB<INT>.WS<INT>.CIXD<INT>.<ATTRIBUTES>_n ) :VALUE = <INT> STYLES FOR GIVEN COLUMN ON GIVEN WORKSHEET ## • ARRAY/VARIABLE: RowStyle :KEY = (WB<INT>.WS<INT>.RIDX<INT> ) :VALUE = <INT> STYLES FOR GIVEN ROW ON GIVEN WORKSHEET ## • ARRAY/VARIABLE: WorkSheets :KEY = (WB<INT>.WS<INT> ) :VALUE = <INT> COUNT OF ACTIVE WORKSHEETS PER WORKBOOK ## • ARRAY/VARIABLE: WorkSheetsArray :KEY = (WB<INT>.WS<INT> ) :VALUE = <STRING> WORKSHEET NAME ## • VARIABLE: ColumnDefault - <INT> DEFAULT MAPPING OF COLUMN COUNT (A 1, B 2, C 3,.....) ## • LIST: ColumnIndex - COLUMN INDEX MAPPING (A 1, B 2, C 3,.....) ## • ARRAY/VARIABLE: Data :KEY = (WB<INT>.WS<INT>RN<INT>CN<INT> ) :VALUE = CELL DATA. variable WBCounter variable WorkBooksArray variable RowCounter variable ColCounter variable StylesCounter variable ColStyle variable RowStyle variable WorkSheets variable WorkSheetsArray variable StylesArray variable ColumnDefault 500 variable ColumnIndex variable Data } ## <== END OF namespace eval ## ♦ DESCRIPTION: CREATE A COLUMN MAPPING INDEX ## ◘ RETURN: LIST, INDEX FOR COLUMN NAMES TO CORRESPONDING LETTER proc Tcl2ExXML::_CreateColIdx {{ColIxdWdth 500}} { variable ColumnIndex; set alphabetList [list A B C D E F G H I J K L M N O P Q R S T U V W X Y Z]; set ColIxdWdth 500; set idxCounter 1; set CLI0 -1; set LetterIdxInt 0; set LetterIdxStng {[lindex $alphabetList $CLI0]${letter}} set IncrementCmd "incr CLI0;" for {set i 1} {$i < [expr {[expr {$ColIxdWdth/26}]+2}]} {incr i} { if {![expr {$idxCounter%703}]} { incr LetterIdxInt; set LetterIdxStng "\[lindex \$alphabetList \$CLI0\]\[lindex \$alphabetList \$CLI$LetterIdxInt\]\${letter}" set CLI0 0; set CLI$LetterIdxInt 0; set IncrementCmd "incr CLI$LetterIdxInt;" } foreach letter $alphabetList { lappend ColumnIndex [list [subst ${LetterIdxStng}] $idxCounter] incr idxCounter; if {$ColIxdWdth < $idxCounter} { return; } } eval $IncrementCmd; } unset -nocomplain -- alphabetList idxCounter CLI0 LetterIdxInt IncrementCmd } ## <== END OF Tcl2ExXML::_CreateColIdx ## ♦ DESCRIPTION: CREATE A NEW WORKBOOK; ## • INPUT: WORKBOOK NAME; ## ◘ RETURN: POINTER TO THE WOORKBOOK WB<INT>; proc Tcl2ExXML::CreateWorkbook {WBName} { incr Tcl2ExXML::WBCounter set workbookId WB$Tcl2ExXML::WBCounter set Tcl2ExXML::WorkBooksArray($workbookId) $WBName return $workbookId } ## <== END OF Tcl2ExXML::CreateWorkbook ## ♦ DESCRIPTION: DELETE A WORKBOOK; ## • INPUT: WORKBOOK ID TO DELETE; ## ◘ RETURN: 0 ON SUCCESS 1 ON ERROR; proc Tcl2ExXML::DeleteWorkbook {workbookId} { variable WorkBooksArray variable WBCounter variable WorkSheets variable WorkSheetsArray variable RowCounter variable ColCounter variable StylesCounter variable StylesArray variable ColStyle variable RowStyle variable Data if {[array exists WorkBooksArray($workbookId)]} { incr WBCounter -1 array unset WorkBooksArray ${workbookId}* array unset WorkSheetsArray ${workbookId}* array unset WorkSheets ${workbookId}* array unset RowCounter ${workbookId}* array unset ColCounter ${workbookId}* array unset StylesCounter ${workbookId}* array unset StylesArray ${workbookId}* array unset ColStyle ${workbookId}* array unset RowStyle ${workbookId}* array unset Data ${workbookId}* return 0 } else { return 1 } } ## ♦ CREATED: FRIDAY, 04/28/17 11:44 AM; ## ♦ CREATOR: CHAD A KIMMELL; ## ♦ DESCRIPTION: INTERNAL PROCEDURE TO VERIFY WORKBOOK EXIST; ## • PARAMETERS: WORKBOOK ID; ## ◘ RETURN: ERROR IF WORKBOOK DOES NOT EXIST; NULL OTHERWISE; proc Tcl2ExXML::_WorkBookExist {workbookId} { variable WorkBooksArray return [info exists WorkBooksArray($workbookId)]; } ## <== END OF Tcl2ExXML::_WorkBookExist ## ♦ CREATED: FRIDAY, 04/28/17 11:44 AM; ## ♦ CREATOR: CHAD A KIMMELL; ## ♦ DESCRIPTION: INTERNAL PROCEDURE TO VERIFY WORKSHEET EXIST; ## • INPUT: WORKBOOK ID; ## ◘ RETURN: BOOL proc Tcl2ExXML::_WorkSheetExist {workSheet} { variable WorkSheetsArray return [info exists WorkSheetsArray($workSheet)]; } ## <== END OF Tcl2ExXML::_WorkSheetExist ## ♦ CREATED: FRIDAY, 04/28/17 11:44 AM; ## ♦ CREATOR: CHAD A KIMMELL; ## ♦ DESCRIPTION: INTERNAL PROCEDURE TO VERIFY STYLE EXIST; ## • INPUT: WORKBOOK ID; ## ◘ RETURN: BOOL proc Tcl2ExXML::_StyleExist {styleName} { variable StylesCounter lassign [split $styleName .] WorkSheet Style if {[info exist StylesCounter($WorkSheet)]} { set StyleInt [string map -nocase {s ""} $Style] if {[string is integer -strict $StyleInt]} { if {$StyleInt <= $StylesCounter($WorkSheet)} { return 1; } else { error "The provided style does not exist: $StyleInt" } } else { error "Invalid style provided while validating if style exist: Value $styleName" } } else { error "No styles found in Worksheet: $WorkSheet" } } ## <== END OF Tcl2ExXML::_StyleExist ## ♦ CREATED: FRIDAY, 04/28/17 11:44 AM; ## ♦ CREATOR: CHAD A KIMMELL; ## ♦ DESCRIPTION: INTERNAL PROCEDURE TO OBTAIN THE COLUMN NUMBER GIVEN A LETTER SEQ ## • INPUT: COLUMN LETTER SEQUENCE ## ◘ RETURN: INT, ABORTS ON ERROR. proc Tcl2ExXML::_ColIdx {ColLetSeq} { variable ColumnIndex; set idx [lsearch -nocase -index 0 $Tcl2ExXML::ColumnIndex $ColLetSeq]; if {$idx == -1} { error "Letter Sequence $ColLetSeq is not defined. Expand the Sequencing with command Tcl2ExXML::CreateColIdx <MAX-idx>" } return [lindex $ColumnIndex $idx 1] } ## <== END OF Tcl2ExXML::_ColIdx ## ♦ CREATED: FRIDAY, 04/28/17 11:44 AM; ## ♦ CREATOR: CHAD A KIMMELL; ## ♦ DESCRIPTION: CREATE A WORKSHEET FOR A WOOKBOOK, WORKBOOK MUST EXIST, ABORT ON ERROR; ## • INPUT: WORKBOOKID, NAME OF WORKSEET; ## ◘ RETURN: WORKSHEET ID, ABORT ON ERROR; proc Tcl2ExXML::CreateWorkSheet {workbookId WSName} { variable WorkSheets variable WorkSheetsArray variable RowCounter variable ColCounter #VERIFY THAT WE HAVE A VALID WORKBOOK if {![_WorkBookExist $workbookId]} { error "WorkBook $workbookId does not exist. Workbook must be valid."; } #INCREMENT THE COUNT IF WORKSHEETS EXIST IN THE WORKBOOK; if {![info exists WorkSheets($workbookId)]} { #IF THIS IS THE FIRST WS FOR THE WB INITIALIZE THE ELEMENT; set WorkSheets($workbookId) 1 } else { #NOW THAT WE KNOW THE WORKBOOK IS VALID, LET'S VERIFY THAT THE WORKBOOK NAME DOES NOT ALEADY EXIST- #GET THE KEYS FOR THE WORKSHEET ARRAY; #WORKSHEET NAME CAN NOT BE LONGER THAN 31 CHARS if {[string length $WSName] >= 32} { set WSName [string range 0 28]... puts stderr "WorkSheet Name Length greater than 31 characters.\nWorkseet Name truncated to: $WSName" } foreach {Key Name} [array get WorkSheetsArray ${workbookId}*] { if {$Name == $WSName} { error "Workbook $workbookId already contans a WorkSheet with the name of $WSName." } } incr WorkSheets($workbookId) } set WSID ${workbookId}.WS$Tcl2ExXML::WorkSheets($workbookId) set WorkSheetsArray($WSID) $WSName set RowCounter($WSID) 0 set ColCounter($WSID) 0 return $WSID } ## <== END OF Tcl2ExXML::CreateWorkSheet ## ♦ CREATED: FRIDAY, 04/28/17 11:44 AM; ## ♦ CREATOR: CHAD A KIMMELL; ## ♦ DESCRIPTION: CREATE A STYLE FOR A WORKBOOK, WORKBOOK MUST EXIST, ABORTS OTHERWISE; ## • INPUT: WORKBOOKID, STYLE OPTIONS ; ## ◘ RETURN: WORKSHEET ID, ABORT ON ERROR; proc Tcl2ExXML::CreateStyle {workbook args} { variable StylesArray variable StylesCounter #VERIFY THAT THE WORKBOOK EXIST; if {[Tcl2ExXML::_WorkBookExist $workbook]} { #VERIFY THAT WE HAVE AT LEAST ONE NAMED PAIR OF VALUES TO SET; if {[llength $args] < 2} { error "Minimum of one style option is required." } #VERIFY IF ANY STYLES EXIST; INCR IF DOES, CREATE IF NOT; if {[info exists Tcl2ExXML::StylesCounter($workbook)]} { incr StylesCounter($workbook) } else { set StylesCounter($workbook) 2 } set styleName ${workbook}.S$Tcl2ExXML::StylesCounter($workbook) foreach {name value} $args { #VERIFY THAT WE HAVE BEEN GIVEN PROPER SWITCHES; if {[lsearch -nocase "-algnmt -rotate -vertxt -wrap -mergh -mergv -font -border -interior -datafmt" $name] == -1} { error "Style option $name option is not supported" } switch -nocase -- $name { -algnmt { foreach {algnmtOpt algnmtvalue} $value { #VERIFY THE PROPER CHILD VALUES WERE INCLUDED. if {[lsearch "-v -h" $algnmtOpt] == -1} { error "Style option $algnmtOpt is not supported for object class '-algnmt'" } #VALIDATE THAT WE HAVE BEEN GIVEN PROPER VALUES ONCE AGAIN, IF WE HAVE CREATE THE ITEM IN THE DATA ARRAY; switch $algnmtOpt { -v { if {[lsearch -nocase "automatic top center bottom" $algnmtvalue] == -1} { error "Invlaid option provided for object class '-algnmt' inherited class '-v'. Value: $algnmtvalue\n Should be one of: 'Top, Center, Bottom'" } set StylesArray(${styleName}.${name}.${algnmtOpt}) [string tolower $algnmtvalue]; } -h { if {[lsearch -nocase "automatic left center right" $algnmtvalue] == -1} { error "Invlaid option provided for object class '-algnmt' inherited class '-h'. Value: $algnmtvalue\n Should be one of: 'Right, Center, Left'" } set StylesArray(${styleName}.${name}.${algnmtOpt}) [string tolower $algnmtvalue]; } } } } -rotate { #VERT TEXT AND ROTATE ARE MUTUALLY EXCLUSIVE, CHECK THAT THE OTHER DOES NOT EXIST; if {[info exist StylesArray(${styleName}.-vertxt)]} { error "\"Vertical Text\" and \"Rotate Text\" are mutually exclusive and can not be used at the same time."; } #TEST IF THE VALUE GIVEN IS A PROPER INTAGER; set testInt $value if {[catch {incr testInt} err]} { error "Invalid option for object class '-rotate': $err" } #NOW WE NEED TO VALIDATE THAT THE VALUE IS BETWEEN -90 AND 90 if {$value < -90 || $value > 90} { error "Invalid integer provided for class '-rotate'. Value MUST be between -90 and 90." } set StylesArray(${styleName}.${name}) $value } -vertxt { #VERT TEXT AND ROTATE ARE MUTUALLY EXCLUSIVE, CHECK THAT THE OTHER DOES NOT EXIST; if {[info exist StylesArray(${styleName}.-rotate)]} { error "\"Vertical Text\" and \"Rotate Text\" are mutually exclusive and can not be used at the same time."; } #VERIFY THAT WE HAVE BEEN GIVEN A BOOL VALUE; if {[string is boolean -strict $value] == 0} { error "Expected boolean value for object class '-vertxt'. Got $value instead." } if {[string tolower $value] in {1 true yes on}} { set StylesArray(${styleName}.${name}) 1 } else { set StylesArray(${styleName}.${name}) 0 } } -wrap { #VERIFY THAT WE HAVE BEEN GIVEN A BOOL VALUE; if {[string is boolean -strict $value] == 0} { error "Expected boolean value for object class '-wrap'. Got $value instead." } if {[string tolower $value] in {1 true yes on}} { set StylesArray(${styleName}.${name}) 1 } else { set StylesArray(${styleName}.${name}) 0 } } -mergh { #MERGE CELLS TO THE RIGHT - #TEST IF THE VALUE GIVEN IS A PROPER INTAGER; set testInt $value if {[catch {incr testInt} err]} { error "Invalid option for object class '-mergh': $err" } set StylesArray(${styleName}.${name}) $value } -mergv { #MERGE CELLS DOWN FROM CURRENT - #TEST IF THE VALUE GIVEN IS A PROPER INTAGER; set testInt $value if {[catch {incr testInt} err]} { error "Invalid option for object class '-mergv': $err" } set StylesArray(${styleName}.${name}) $value } -font { foreach {fontOpt fontValue} $value { #VERIFY THEH PROPER CHILD VALUES WERE INCLUDED. if {[lsearch {-nm -ff -sz -fc -b -i -u} $fontOpt] == -1 } { error "Style option '$fontOpt' is not supported for object class '-font'" } switch $fontOpt { -nm { #TODO: FIND A WAY TO VALIDATE PROPER FONT NAMES set StylesArray(${styleName}.${name}.${fontOpt}) [string tolower $fontValue]; } -ff { #TODO: FIND A WAY TO VALIDATE THE FAMILY ASSOCIATED TO THE FONT. set StylesArray(${styleName}.${name}.${fontOpt}) [string tolower $fontValue]; } -sz { set testInt $fontValue if {[catch {incr testInt} err]} { error "Invalid option for object class '-font' inherited class '-sz': $err" } set StylesArray(${styleName}.${name}.${fontOpt}) $fontValue } -fc { if {[string is xdigit -strict [string map {"#" ""} $fontValue]] == 0} { error "Expected hexadecimal value for object class '-font' inherited class '-fc'. Got $fontValue instead." } set StylesArray(${styleName}.${name}.${fontOpt}) $fontValue } -b { if {[string is boolean -strict $fontValue] == 0} { error "Expected boolean value for object class '-font' inherited class '-b'. Got $fontValue instead." } if {[lsearch -nocase "0 false no off" $fontValue] == 0} { set StylesArray(${styleName}.${name}.$fontOpt) 0 } else { set StylesArray(${styleName}.${name}.${fontOpt}) 1 } } -u { if {[string is boolean -strict $fontValue] == 0} { error "Expected boolean value for object class '-font' inherited class '-u'. Got $fontValue instead." } if {[lsearch -nocase "0 false no off" $fontValue] == -1} { set StylesArray(${styleName}.${name}.${fontOpt}) 0 } else { set StylesArray(${styleName}.${name}.$fontOpt) 1 } } -i { if {[string is boolean -strict $fontValue] == 0} { error "Expected boolean value for object class '-font' inherited class '-i'. Got $fontValue instead." } if {[lsearch -nocase "0 false no off" $fontValue] == -1} { set StylesArray(${styleName}.${name}.${fontOpt}) 0 } else { set StylesArray(${styleName}.${name}.${fontOpt}) 1 } } } } } -border { set ParamFound 0; for {set count 0} {$count <[llength $value]} {incr count;} { set opt [lindex $value $count]; if {[regexp {^-[A-Za-z]{1,2}} $opt]} { if {$opt == "-bp"} { set BdrPos [lindex $value [expr {$count+1}]]; set BdrSid [llength $BdrPos]; #VERIFY IF WE WERE GIVEN A LIST, IF WE HAVE VALIDATE EACH OF THE ITEMS; if {$BdrSid > 1} { if {$BdrSid > 4} { error "'$BdrPos' count exceeds the maxumum sides." } #VERIFY THAT WE HAVE BEEN GIVEN THE PROPER VALUES. foreach side $BdrPos { if {[lsearch -nocase "right left top bottom" $side] == -1} { error "Invalid value provided for object class '-border' subclass '-bp'. Got $side instead." } } } else { if {[lsearch -nocase "right left top bottom" $BdrPos] == -1} { error "Invalid value provided for object class '-border' subclass '-bp'. Got $BdrPos instead." } } #CHECK TO SEE IF WE ARE GIVEN ADDITIONAL STYLE PARAMS, IF WE ARE NOT WE WILL USE THE DEFAULT; set adjParam [lindex $value [expr {$count+2}]]; switch $adjParam { -ls { #WE HAVE FOUND AN ADDITIONAL PARAM, GET IT'S VALUE, VERIFY IT IS CORRECT AND SET THE OBJECT; set LineStyle [string tolower [lindex $value [expr {$count+3}]]]; if {[lsearch "continuous dot dashdot dashdotdot dash double slantdashdot" $LineStyle] == -1} { error "Invalid linetype given for '-border' subcommand '-ls'; Value $LineStyle" } set ParamFound 1; } -lw { set LineWeight [lindex $value [expr {$count+3}]]; set IntTest $LineWeight; if {[catch {incr $IntTest} err]} { error "Expected an integer for object class '-border' inherited class '-wt'. Got $LineWeight instead." } set ParamFound 3; } -lc { set LineColor [lindex $value [expr {$count+3}]]; if {[string is xdigit -strict [string map {"#" ""} $LineColor]] == 0} { error "Expected hexadecimal value for object class '-border' inherited class '-lc'. Got $LineColor instead." } set ParamFound 6; } } #IF WE FOUND ONE PARAMETER, LET'S SEE IF WE CAN FIND THE OTHER? AGAIN IF WE DON'T FIND IT WE WILL USE A DEFAULT; set nextAdjParam [lindex $value [expr {$count+4}]]; switch $nextAdjParam { -ls { #WE HAVE FOUND AN ADDITIONAL PARAM, GET IT'S VALUE, VERIFY IT IS CORRECT AND SET THE OBJECT; set LineStyle [string tolower [lindex $value [expr {$count+5}]]]; if {[lsearch "continuous dot dashdot dashdotdot dash double slantdashdot" $LineStyle] == -1} { error "Invalid linetype given for '-border' subcommand '-ls'; Value $LineStyle" } set ParamFound [expr {$ParamFound+1}]; } -lw { set LineWeight [lindex $value [expr {$count+5}]]; set IntTest $LineWeight; if {[catch {incr $IntTest} err]} { error "Expected an integer for object class '-border' inherited class '-wt'. Got $LineWeight instead."; } set ParamFound [expr {$ParamFound+3}]; } -lc { set LineColor [lindex $value [expr {$count+5}]]; if {[string is xdigit -strict [string map {"#" ""} $LineColor]] == 0} { error "Expected hexadecimal value for object class '-border' inherited class '-lc'. Got $LineColor instead." } set ParamFound [expr {$ParamFound+6}]; } } #IF WE FOUND A SECOND PARAMETER, LET'S SEE IF WE CAN FIND THE OTHER? AGAIN IF WE DON'T FIND IT WE WILL USE A DEFAULT; set nextnextAdjParam [lindex $value [expr {$count+6}]]; switch $nextnextAdjParam { -ls { #WE HAVE FOUND AN ADDITIONAL PARAM, GET IT'S VALUE, VERIFY IT IS CORRECT AND SET THE OBJECT; set LineStyle [string tolower [lindex $value [expr {$count+7}]]]; if {[lsearch "continuous dot dashdot dashdotdot dash double slantdashdot" $LineStyle] == -1} { error "Invalid linetype given for '-border' subcommand '-ls'; Value $LineStyle" } set ParamFound [expr {$ParamFound+1}]; } -lw { set LineWeight [lindex $value [expr {$count+7}]]; set IntTest $LineWeight; if {[catch {incr $IntTest} err]} { error "Expected an integer for object class '-border' inherited class '-wt'. Got $LineWeight instead."; } set ParamFound [expr {$ParamFound+3}]; } -lc { set LineColor [lindex $value [expr {$count+7}]]; if {[string is xdigit -strict [string map {"#" ""} $LineColor]] == 0} { error "Expected hexadecimal value for object class '-border' inherited class '-lc'. Got $LineColor instead." } set ParamFound [expr {$ParamFound+6}]; } } #NOW WE **SHOULD** HAVE ALL OF THE INFORMATION THAT WE NEED TO GENERATE THE OBJECT NOW; for {set t 0} {$t < $BdrSid} {incr t} { switch $ParamFound { 0 { set StylesArray(${styleName}.${name}.[lindex $BdrPos $t]) "continuous 1"; } 1 { set StylesArray(${styleName}.${name}.[lindex $BdrPos $t]) "[string tolower $LineStyle] 1"; } 3 { set StylesArray(${styleName}.${name}.[lindex $BdrPos $t]) "continuous $LineWeight"; } 4 { set StylesArray(${styleName}.${name}.[lindex $BdrPos $t]) "[string tolower $LineStyle] [string tolower $LineWeight]"; } 6 { set StylesArray(${styleName}.${name}.[lindex $BdrPos $t]) "continuous 1 $LineColor"; } 7 { set StylesArray(${styleName}.${name}.[lindex $BdrPos $t]) "[string tolower $LineStyle] 1 $LineColor"; } 9 { set StylesArray(${styleName}.${name}.[lindex $BdrPos $t]) "continuous $LineWeight $LineColor"; } 10 { set StylesArray(${styleName}.${name}.[lindex $BdrPos $t]) "[string tolower $LineStyle] $LineWeight $LineColor"; } } } } } } } -interior { foreach {interiorOpt interiorValue} $value { if {[lsearch "-ic -ip -ipc" $interiorOpt] ==-1} { error "Style option $interiorOpt option is not supported for class '-interior'"; } switch $interiorValue { -ic { if {[string is xdigit -strict [string map {"#" ""} $interiorValue]] == 0} { error "Expected hexadecimal value for object class '-interior' inherited class '-ic'. Got $interiorValue instead."; } set StylesArray(${styleName}.${name}.${interiorOpt}) $interiorValue } -ip { if {[lsearch -nocase "thindiagcross thinhorzcross thindiagstripe thinreversediagstripe thinvertstripe thinhorzstripe thickdiagcross diagcross diagstripe reversediagstripe vertstripe horzstripe gray0625 gray125 gray75 gray50 gray25" $interiorValue ==-1]} { error "Invalid interior Pattern given for class '-interior' subclass '-ip'; Value $interiorValue"; } set StylesArray(${styleName}.${name}.${interiorOpt} $interiorValue; } -ipc { set StylesArray(${styleName}.${name}.${interiorOpt}) $interiorValue; } } } } -datafmt { if {[lsearch -nocase "sdate mdate ldate ymd t24 t12 curr nbr pct 0" $value] == -1} { error "Unsuported data format given for -datafmt argument; Value $value"; } switch $value { sdate { set StylesArray(${styleName}.${name}) {m/d/yy;@} } mdate { set StylesArray(${styleName}.${name}) {[ENG][$-409]d\-mmm\-yy;@} } ldate { set StylesArray(${styleName}.${name}) {[ENG][$-409]mmmm\ d\,\ yyyy;@} } ymd { set StylesArray(${styleName}.${name}) {yyyy\-mm\-dd} } t24 { set StylesArray(${styleName}.${name}) {h:mm;@} } t12 { set StylesArray(${styleName}.${name}) {[$-409]h:mm\ AM/PM;@} } curr { set StylesArray(${styleName}.${name}) {"$"#,##0.00} } nbr { set StylesArray(${styleName}.${name}) {Number} } pct { set StylesArray(${styleName}.${name}) {Percent} } str { set StylesArray(${styleName}.${name}) {String} } 0 { set StylesArray(${styleName}.${name}) {0} } } } } } return $styleName } else { error "Workbook: $workbook not valid. Please indicate a valid workbook."; } } ## <== END OF Tcl2ExXML::createStyle ## ♦ CREATED: FRIDAY, 04/28/17 11:44 AM; ## ♦ CREATOR: CHAD A KIMMELL; ## ♦ DESCRIPTION: CREATE COLUMN ATTABUTES, WORKBOOK MUST EXIST, ABORTS OTHERWISE; ## • INPUT: WORKSHEETID, STYLE OPTIONS ; ## ◘ RETURN: NULL STRING, ABORT ON ERROR; proc Tcl2ExXML::setColumnAttributes {workSheet columnIndex args} { variable ColStyle; #VALIDATE THAT THE WORKBOOK/WORKSHEET EXIST; if {[_WorkSheetExist $workSheet]} { #WORKSHEET IS VALID, LET'S CHECK IF THE COLUMN IS VALID AS WELL - #CHECK IF WE WERE GIVEN A COLUMN NUMBER OR A NAME; if {[regexp {\d{1,3}} $columnIndex]} { set ColIdxNbr $columnIndex; } elseif {[regexp -nocase {[a-z]{1,3}} $columnIndex]} { set ColIdxNbr [_ColIdx $columnIndex]; } else { error "Unable to determine Column index for given value: $columnIndex"; } #VERIFY THAT WE HAVE AT LEAST ONE NAMED PAIR OF VALUES TO SET; if {[llength $args] < 2} { error "Minimum of one column attribute option is required." } foreach {name value} $args { switch -nocase -- $name { -w { #SET COLUMN WIDTH - #VERIFY THAT WE HAVE BEEN GIVEN AN INTEGER; set testInt $value if {[catch {incr testInt} err]} { error "Invalid option for Column Attributes object class '-w': $err"; } set ColStyle(${workSheet}.${ColIdxNbr}.${name}) $value } -afw { #AUTO FIT WIDTH, ONLY BOOL TRUE WILL BE EVALUATED; if {[string is boolean -strict $value] == 0} { error "Expected boolean value for object class '-afw'. Got $value instead." } if {[lsearch -nocase "1 true yes on" $value] != -1} { set ColStyle(${workSheet}.${ColIdxNbr}.${name}) 1 } } -h { #INDICATE IF THE COLUMN SHOULD BE HIDDEN; if {[string is boolean -strict $value] == 0} { error "Expected boolean value for object class '-h'. Got $value instead." } if {[lsearch -nocase "1 true yes on" $value] != -1} { set ColStyle(${workSheet}.${ColIdxNbr}.${name}) 1 } } -s { #VERIFY FIRST THAT THE STYLE ARRAY EXIST FOR THE GIVEN WORKSHEET; if {[_StyleExist $value]} { set ColStyle(${workSheet}.${ColIdxNbr}.${name}) $value } } } } } else { error "Worksheet: $workSheet not valid!"; } }; ## <====END OF Tcl2ExXML::setColumnAttributes ## ♦ CREATED: FRIDAY, 04/28/17 11:44 AM; ## ♦ CREATOR: CHAD A KIMMELL; ## ♦ DESCRIPTION: ADD ROW TO WORKSHEET, WORKSHEET MUST EXIST, ABORTS OTHERWISE; ## • INPUT: WORKSHEETID - ## • INPUT: ?ROW NUMBER (IF ROWS HAVE BEEN SKIPPED)? - ## • INPUT: LIST OF DATA, EACH LIST ITEM REPRESENTS THE NEXT ADJACENT COLUMN, IF DATA STARTS WITH '-c' - ## FOLLOWING LIST ITEM REPRESENTS THE COLUMN TO START THE DATA TO BE INSERTED INTO VALUE CAN BE AN INT OR COLUMN NAME; ## ◘ RETURN: INT , ABORT ON ERROR; proc Tcl2ExXML::addRow {workSheet args} { variable RowCounter variable ColCounter variable ColStyle variable Data #SET INTERNAL VARIABLE; set CN 1; #ENSURE THAT THE WORKBOOK EXIST, ABORT IF IT DOES NOT; if {[Tcl2ExXML::_WorkSheetExist $workSheet]} { #CHECK IF A ROW HAS BEEN SPECIFIED; if {[lindex $args 0] == "-r"} { #A ROW HAS BEEN SPECIFIED, VALIDATE THAT IT IS A PROPER INTEGER; set RN [lindex $args 1]; set testInt [lindex $args 1]; if {[catch {incr testInt} err]} { error "Invalid Row Number provided: $err"; } #DO SOME CHECKING TO ENSURE NO DATA COLLISIONS OCCUR - #IS THIS ROW GREATER THAN PREVIOUS ROWS? IF IT IS WE CAN SKIP DATA VALIDATIONS; if {$RN < $RowCounter($workSheet)} { #THIS ROW IS NOT A "NEW" ROW, WE NEED TO VALIDATE NO DATA COLLISIONS OCCUR; for {set i 2} {$i < [llength $args]} {incr i} { #IF A PARTICUALR COL IS SPECIFIED; if {[lindex $args $i] == "-c"} { #CHECK THAT THE COLUMN IS VALID, MUST BE EITHER AN INTEGER OR COLUMN NAME; if {[regexp {^\d{1,3}} [lindex $args [expr {$i+1}]] CN ]} { #COL GIVEN IN INT FORM; incr i 2; } elseif {[regexp -nocase {^[a-z]{1,3}} [lindex $args [expr {$i+1}]]]} { #COL NAME GIVEN, GET INTIGER VALUE FOR THE COL; set CN [_ColIdx [lindex $args [expr {$i+1}]]] incr i 2; } else { #UNABLE TO DETERMINE COLUMN, THOW AN ERROR; error "Invalid column specified, must be an integer (1-999) or column name."; } } #CHECK IF A STYLE IS SPECIFIED; if {[lindex $args $i] == "-s"} { #VALIDATE THAT THE STYLE EXIST; if {[_StyleExist [lindex $args [expr {$i+1}]]]} { set Data($workSheet.$RN.$CN.-s) [lindex $args [expr {$i+1}]]; incr i 2; } } #VERIFY THAT DATA DOES NOT ALREADY EXIST FOR THE GIVEN INDEX; if {[info exist Data($workSheet.$RN.$CN)]} { error "Data collision ROW: $RN & COL: $CN]"; } #DATA DOES NOT EXIST, WRITE THE INFORMATION TO THE ARRAY; set Data($workSheet.$RN.$CN) [lindex $args $i]; #CHECK IF THIS ROW HAS EXPANDED THE ACTIVE COLUMN BOUNDRY; if {$CN > $ColCounter($workSheet)} { #IT DOES, WRITE THE NEW VALUE TO THE ARRAY; set ColCounter($workSheet) $CN } incr CN; } } else { #THIS IS A NEW ROW, WE DO NOT NEED TO VALIDATE set RowCounter($workSheet) $RN; for {set i 0} {$i < [llength $args]} {incr i} { #CHECK IF A PARTICUALR COL IS SPECIFIED; if {[lindex $args $i] == "-c"} { #CHECK THAT THE COLUMN IS VALID, MUST BE EITHER AN INTEGER OR COLUMN NAME; if {[regexp {^\d{1,3}} [lindex $args [expr {$i+1}]] CN ]} { #COL GIVEN IN INT FORM; incr i 2; } elseif {[regexp -nocase {^[a-z]{1,3}} [lindex $args [expr {$i+1}]]]} { #COL NAME GIVEN, GET INTIGER VALUE FOR THE COL; set CN [Tcl2ExXML::_ColIdx [lindex $args [expr {$i+1}]]] incr i 2; } else { #UNABLE TO DETERMINE COLUMN, THOW AN ERROR; error ""; } } #CHECK IF A STYLE IS SPECIFIED; if {[lindex $args $i] == "-s"} { #VALIDATE THAT THE STYLE EXIST; if {[_StyleExist [lindex $args [expr {$i+1}]]]} { set Data($workSheet.$RN.$CN.-s) [lindex $args [expr {$i+1}]]; incr i 2; } } #WRITE THE DATA TO THE ARRAY; set Data($workSheet.$RN.$CN) [lindex $args $i]; #CHECK IF THIS ROW HAS EXPANDED THE ACTIVE COLUMN BOUNDRY; if {$CN > $ColCounter($workSheet)} { #IT DOES, WRITE THE NEW VALUE; set ColCounter($workSheet) $CN } incr CN; } } } else { #NO ROW SPECIFIED, SET THE VALUE TO THE NEXT ROW IN THE SHEET; incr RowCounter($workSheet); set RN $RowCounter($workSheet); for {set i 0} {$i < [llength $args]} {incr i} { #IF A PARTICUALR COL IS SPECIFIED; if {[lindex $args $i] == "-c"} { #CHECK THAT THE COLUMN IS VALID, MUST BE EITHER AN INTEGER OR COLUMN NAME; if {[regexp {^(\d{1,3})} [lindex $args [expr {$i+1}]]]} { #COL GIVEN IN INT FORM; set CN [lindex $args [expr {$i+1}]]; incr i 2; } elseif {[regexp -nocase {^([a-z]{1,3})} [lindex $args [expr {$i+1}]]]} { #COL NAME GIVEN, GET INTIGER VALUE FOR THE COL; set CN [_ColIdx [lindex $args [expr {$i+1}]]] incr i 2; } else { #UNABLE TO DETERMINE COLUMN, THOW AN ERROR; error "Unable to determine the column provided."; } } #CHECK IF A STYLE IS SPECIFIED; if {[lindex $args $i] == "-s"} { #VALIDATE THAT THE STYLE EXIST; if {[_StyleExist [lindex $args [expr {$i+1}]]]} { set Tcl2ExXML::Data($workSheet.$RN.$CN.-s) [lindex $args [expr {$i+1}]]; incr i 2; } } #WRITE THE DATA TO THE ARRAY; set Data($workSheet.$RN.$CN) [lindex $args $i]; #CHECK IF THIS ROW HAS EXPANDED THE ACTIVE COLUMN BOUNDRY; if {$CN > $ColCounter($workSheet)} { #IT DOES, WRITE THE NEW VALUE; set Tcl2ExXML::ColCounter($workSheet) $CN } incr CN; } } return $RowCounter($workSheet); } else { error "WorkSheet $workSheet does not exist." } } ## <====END OF Tcl2ExXML::addRow ## ♦ CREATED: FRIDAY, 04/28/17 11:44 AM; ## ♦ CREATOR: CHAD A KIMMELL; ## ♦ DESCRIPTION: RETURN ALL DATA IN EXCEL XML 2003 WORKBOOK FORMAT ## • INPUT: WORKBOKID ## ◘ RETURN: XML STRING, ABORTS ON ERROR. proc Tcl2ExXML::outputXML {workbook} { variable Data variable ColStyle variable ColCounter variable WorkSheets variable ColCounter variable RowCounter variable StylesArray variable StylesCounter variable WorkSheetsArray #GET TCL ENVIRNMENTAL VARIABLES upvar #0 env(USERNAME) User upvar #0 env(USERDOMAIN) Company if {[_WorkBookExist $workbook]} { #START THE XML STRING; set xml "<?xml version=\"1.0\"?>\n" append xml " <?mso-application progid=\"Excel.Sheet\"?>\n" append xml " <Workbook xmlns=\"urn:schemas-microsoft-com:office:spreadsheet\"\n" append xml " xmlns:o=\"urn:schemas-microsoft-com:office:office\"\n" append xml " xmlns:x=\"urn:schemas-microsoft-com:office:excel\"\n" append xml " xmlns:ss=\"urn:schemas-microsoft-com:office:spreadsheet\"\n" append xml " xmlns:html=\"http://www.w3.org/TR/REC-html40\">\n" append xml " <DocumentProperties xmlns=\"urn:schemas-microsoft-com:office:office\">\n" append xml " <Author>$User</Author>\n" append xml " <Created>[clock format [clock seconds] -format {%Y-%m-%dT%H:%M:%S}]</Created>\n" append xml " <Company>$Company</Company>\n" append xml " <Version>15.00</Version>\n" append xml " </DocumentProperties>\n" append xml " <OfficeDocumentSettings xmlns=\"urn:schemas-microsoft-com:office:office\">\n" append xml " <AllowPNG/>\n" append xml " </OfficeDocumentSettings>\n" append xml " <ExcelWorkbook xmlns=\"urn:schemas-microsoft-com:office:excel\">\n" append xml " <WindowHeight>100</WindowHeight>\n" append xml " <WindowWidth>100</WindowWidth>\n" append xml " <WindowTopX>30</WindowTopX>\n" append xml " <WindowTopY>30</WindowTopY>\n" append xml " <ActiveSheet>1</ActiveSheet>\n" append xml " <ProtectStructure>False</ProtectStructure>\n" append xml " <ProtectWindows>False</ProtectWindows>\n" append xml " </ExcelWorkbook>\n" append xml " <Styles>\n" append xml " <Style ss:ID=\"Default\" ss:Name=\"Normal\">\n" append xml " <Alignment ss:Horizontal=\"Center\" ss:Vertical=\"Bottom\"/>\n" append xml " <Borders/>\n" append xml " <Font ss:FontName=\"Calibri\" x:Family=\"Swiss\" ss:Size=\"11\" ss:Color=\"#000000\"/>\n" append xml " <Interior/>\n" append xml " <NumberFormat/>\n" append xml " <Protection/>\n" append xml " </Style>\n" #DETERMINE IF THERE ARE ANY STYLES THAT EXSIT FOR THE WORKBOOK; if {[info exists StylesCounter($workbook)]} { #STYLE(S) HAVE BEEN FOUND, NOW WE NEED TO GENERATE THE APPRORIATE XML TAGS; for {set WbSc 2} {$WbSc <= $StylesCounter($workbook)} {incr WbSc} { set WbSn $workbook.S$WbSc set StyleName S$WbSc #CHECK THAT THE STYLES ARRAY EXIST FIRST; if {[array exists StylesArray]} { #BEGIN GENERATING THE DYNAMIC STYLE CONTENT; append xml " <Style ss:ID=\"$StyleName\">\n" #IF ANY OF THE ALIGNMENT ELEMENTS ARE INDICATED GENERATE THE TAG - #UNABLE TO GET KEY LIST DUE TO THE VARIETY OF OPTIONS, CHECK FOR EACH INDIVIDUALLY; if {[info exist StylesArray($WbSn.-algnmt.-v)] || \ [info exist StylesArray($WbSn.-algnmt.-h)] || \ [info exist StylesArray($WbSn.-rotate)] || \ [info exist StylesArray($WbSn.-vertxt)] || \ [info exist StylesArray($WbSn.-wrap)] } { append xml " <Alignment " #APPEND EACH OF THE VALUES THAT EXIST IN THE ARRAY; if {[info exist StylesArray($WbSn.-algnmt.-v)]} { append xml "ss:Vertical=\"[string totitle $StylesArray($WbSn.-algnmt.-v)]\"" } if {[info exist StylesArray($WbSn.-algnmt.-h)]} { append xml " ss:Horizontal=\"[string totitle $StylesArray($WbSn.-algnmt.-h)]\"" } #IF THE ROTATE ELEMENT IS INDICATED GENERATE THE TAG; if {[info exist StylesArray($WbSn.-rotate)]} { append xml " ss:Rotate=\"[string totitle $StylesArray($WbSn.-rotate)]\"" } #IF THE VERTICALTEXT ELEMENT IS INDICATED GENERATE THE TAG; if {[info exist StylesArray($WbSn.-vertxt)]} { append xml " ss:VerticalText=\"[string totitle $StylesArray($WbSn.-vertxt)]\"" } #IF THE WRAP TEXT ELEMENT IS INDICATED GENERATE THE TAG; if {[info exist StylesArray($WbSn.-wrap)]} { append xml " ss:WrapText=\"[string totitle $StylesArray($WbSn.-wrap)]\"" } append xml "/>\n" } #GET A LIST OF THE FONT KEYS, GENERATE THE APPROPRIATE ELEMNETS, IF ANY EXIST; set FontAttibuteList [array names StylesArray "$WbSn.-font.*"]; if {[llength $FontAttibuteList]} { append xml " <Font" foreach Param $FontAttibuteList { switch $Param [list \ "${WbSn}.-font.-nm" { append xml " ss:FontName=\"[string totitle $StylesArray($WbSn.-font.-nm)]\""} \ "${WbSn}.-font.-ff" { append xml " x:Family=\"[string totitle $StylesArray($WbSn.-font.-ff)]\"" } \ "${WbSn}.-font.-sz" { append xml " ss:Size=\"$StylesArray($WbSn.-font.-sz)\"" } \ "${WbSn}.-font.-fc" { append xml " ss:Color=\"[string toupper $StylesArray($WbSn.-font.-fc)]\"" } \ "${WbSn}.-font.-b" { if {$StylesArray($WbSn.-font.-b)} { append xml " ss:Bold=\"1\"" }} \ "${WbSn}.-font.-u" { if {$StylesArray($WbSn.-font.-u)} { append xml " ss:Underline=\"1\"" }} \ "${WbSn}.-font.-i" { if {$StylesArray($WbSn.-font.-i)} { append xml " ss:Italic=\"1\"" }} \ ] } append xml "/>\n" } #GET ANY OF THE BORDER ATTRIBUTES, IF THERE ARE ANY AND GENERATE THE TAGS; set BorderAttibuteList [array names StylesArray "$WbSn.-border.*"]; if {[llength $BorderAttibuteList]} { append xml " <Borders>\n" foreach Param $BorderAttibuteList { set BdrPos [lindex [split $Param .] 3] set BdrPrams $StylesArray($Param) if {[llength $BdrPrams] == 2} { append xml " <Border ss:Position=\"[string totitle $BdrPos]\" ss:LineStyle=\"[string totitle [lindex $BdrPrams 0]]\" ss:Weight=\"[lindex $BdrPrams 1]\"/>\n" } else { append xml " <Border ss:Position=\"[string totitle $BdrPos]\" ss:LineStyle=\"[string totitle [lindex $BdrPrams 0]]\" ss:Weight=\"[lindex $BdrPrams 1]\" ss:Color=\"[lindex $BdrPrams 2]\"/>\n" } } append xml " </Borders>\n" } set InteriorAttibuteList [array names StylesArray "$WbSn.-interior.*"]; if {[llength $InteriorAttibuteList]} { append xml " <Interior" foreach Param $BorderAttibuteList { switch $Param [list \ "${WbSn}.-interior.-ic" { append xml " ss:Color=\"$StylesArray($Param)\""} \ "${WbSn}.-interior.-ip" { append xml " ss:Pattern=\"$StylesArray($Param)\""} \ "${WbSn}.-interior.-ipc" { append xml " ss:PatternColor=\"$StylesArray($Param)\""} \ ] } append xml "/>\n" } #GET THE NUMBER FORMAT IF THERE IS ONE DEFINED; if {[info exist StylesArray(${WbSn}.-datafmt)]} { append xml " <NumberFormat ss:Format=\"$StylesArray(${WbSn}.-datafmt)\"/>\n" } } append xml " </Style>\n" } } append xml " </Styles>\n" #BEGIN TO ADD THE DATA FOR EACH WORKSHEET IN THE WORKBOOK; for {set WS 1} {$WS <= $WorkSheets($workbook)} {incr WS} { append xml " <Worksheet ss:Name=\"$WorkSheetsArray(${workbook}.WS${WS})\">\n"; #SET THE WORKSHEET PARAMETERS; append xml " <Table ss:ExpandedColumnCount=\"$ColCounter(${workbook}.WS${WS})\" ss:ExpandedRowCount=\"$RowCounter(${workbook}.WS${WS})\" x:FullColumns=\"1\" x:FullRows=\"1\" ss:DefaultColumnWidth=\"25\" ss:DefaultRowHeight=\"15\">\n"; #VERIFY IF THERE ARE ANY COLUMN ATTRIBUTES IN THE ARRAY THAT WE NEED TO SET; set ColumnStyleKeys [array names ColStyle ${workbook}.WS${WS}*]; if {[llength $ColumnStyleKeys]} { set ColString "" #COLUMN STYLES EXIST, PROCESS THEM; foreach Key $ColumnStyleKeys { set cidx [lindex [split $Key .] 2]; switch $Key [list \ "${workbook}.WS${WS}.$cidx.-w" { append ColString " ss:Width=\"$ColStyle($Key)\"" } \ "${workbook}.WS${WS}.$cidx.-afw" { append ColString " ss:AutoFitWidth=\"$ColStyle($Key)\""} \ "${workbook}.WS${WS}.$cidx.-h" { append ColString " ss:Hidden=\"$ColStyle($Key)\"" } \ ] } if {$ColString != ""} { append xml " <Column $ColString />\n" } } set SkippedRowFlag 0; set SkippedColFlag 0; for {set R 1} {$R <= $RowCounter($workbook.WS${WS})} {incr R} { #CHECK IF DATA EXIST IN THIS ROW; set RowCount [array names Data ${workbook}.WS${WS}.$R.*]; if {[llength $RowCount] > 0} { #IF ROWS HAVE BEEN SKIPPED INDICATE THE ROW INDEX; if {$SkippedRowFlag} { append xml " <Row ss:Index=\"$R\" ss:AutoFitHeight=\"0\" ss:Height=\"15\">\n" #RESET THE FLAG; set SkippedRowFlag 0; } else { append xml " <Row ss:AutoFitHeight=\"0\" ss:Height=\"15\">\n" } #COLUMN LOOP; set DataTypeFlag 0; for {set C 1} {$C <= $ColCounter($workbook.WS${WS})} {incr C} { #CHECK IF DATA EXIST FOR THE ROW & COL IDX; if {[info exist Data(${workbook}.WS${WS}.$R.$C)]} { set DataValue $Data(${workbook}.WS${WS}.$R.$C) #CHECK THAT THERE IS ACTUALLY DATA AND IT IS NOT JUST BLANK, #IF IT IS BLANK (ONLY HAS A SPACE) IT CAN BE SKIPPED; if {[string map {" " ""} $DataValue] eq ""} { set SkippedColFlag 1; continue; } #IF A COLUMN HAS BEEN SKIPPED INDICATE IT ON THE COL INDEX; if {$SkippedColFlag} { append xml " <Cell ss:Index=\"$C\"" set SkippedColFlag 0; } else { append xml " <Cell" } #APPLY THE STYLE TO THE CELL IF THERE IS ONE DEFINED; if {[info exist Data(${workbook}.WS${WS}.$R.$C.-s)]} { append xml " ss:StyleID=\"[lindex [split $Data(${workbook}.WS${WS}.$R.$C.-s) .] 1]\"" } elseif {[info exist ColStyle(${workbook}.WS${WS}.$C.-s)]} { #IF THERE IS NO CELL STYLE APPLY THE COLUMN STYLE IF ONE EXIST; append xml " ss:StyleID=\"[lindex [split $ColStyle(${workbook}.WS${WS}.$C.-s) .] 1]\"" } elseif {0} { #if there is a row style. } append xml ">" #DETERMINE THE FORMAT OF THE DATA POSSIBLE VALUES ARE: DateTime Number String switch -regexp -matchvar DataList -- $DataValue { ^(0?[1-9]|[12][0-9]|3[01])[-[:space:]\\/\.](0?[1-9]|1[012])[-[:space:]\\/\.]((?:19|20)?[0-9]{2})$ { #DMY lassign $DataList Full Day Month Year if {[string length $Year] == 2} { if {$Year <= 20} { #ASSUME THAT WE ARE REFERENCING THE 21ST CENTURY; set Year "20$Year" } else { #ASSUME THAT THE USER IS REFERENCING THE 20TH CENTURY; set Year "19$Year" } } set NomalizedDate [clock scan $Month/$Day/$Year -format {%m/%d/%Y}] append xml "<Data ss:Type=\"DateTime\">[clock format $NomalizedDate -format {%Y-%m-%dT00:00:00.000}]</Data></Cell>\n" } ^(0?[1-9]|1[012])[-[:space:]\\/\.](0?[1-9]|[12][0-9]|3[01])[-[:space:]\\/\.]((?:19|20)?[0-9]{2})$ { #MDY puts $DataList lassign $DataList Full Month Day Year if {[string length $Year] == 2} { if {$Year <= 20} { #ASSUME THAT WE ARE REFERENCING THE 21ST CENTURY; set Year "20$Year" } else { #ASSUME THAT THE USER IS REFERENCING THE 20TH CENTURY; set Year "19$Year" } } set NomalizedDate [clock scan $Month/$Day/$Year -format {%m/%d/%Y}] append xml "<Data ss:Type=\"DateTime\">[clock format $NomalizedDate -format {%Y-%m-%dT00:00:00.000}]</Data></Cell>\n" } ^((?:19|20)[0-9]{2})[-[:space:]\\/\.](0?[1-9]|1[012])[-[:space:]\\/\.](0?[1-9]|[12][0-9]|3[01])$ { #YMD lassign $DataList Full Year Month Day if {[string length $Year] == 2} { if {$Year <= 20} { #ASSUME THAT WE ARE REFERENCING THE 21ST CENTURY; set Year "20$Year" } else { #ASSUME THAT THE USER IS REFERENCING THE 20TH CENTURY; set Year "19$Year" } } set NomalizedDate [clock scan $Month/$Day/$Year -format {%m/%d/%Y}] append xml "<Data ss:Type=\"DateTime\">[clock format $NomalizedDate -format {%Y-%m-%dT00:00:00.000}]</Data></Cell>\n" } ^[-]?[0-9]*\.?[0-9]+$ { append xml "<Data ss:Type=\"Number\">$DataValue</Data></Cell>\n"; } default { append xml "<Data ss:Type=\"String\">[string map {\" " ' ' < < > > & &} $DataValue]</Data></Cell>\n"; } } #END THE SWITCH STATEMENT FOR STING TYPE; } else { #NO DATA EXIST IN THIS COLUMN, SKIP IT AND SET FLAG; set SkippedColFlag 1; set PrevNumber $C } } #END COLUMN LOOP; append xml " </Row>\n" } else { set SkippedRowFlag 1 } } #END ROW LOOP; append xml " </Table>\n" append xml " </Worksheet>\n" } #END WORKSHEET LOOP; append xml " <WorksheetOptions xmlns=\"urn:schemas-microsoft-com:office:excel\">\n" append xml " <PageSetup>\n" append xml " <Header x:Margin=\"0.3\"/>\n" append xml " <Footer x:Margin=\"0.3\"/>\n" append xml " <PageMargins x:Bottom=\"0.75\" x:Left=\"0.7\" x:Right=\"0.7\" x:Top=\"0.75\"/>\n" append xml " </PageSetup>\n" append xml " <Unsynced/>\n" append xml " <Print>\n" append xml " <ValidPrinterInfo/>\n" append xml " <HorizontalResolution>600</HorizontalResolution>\n" append xml " <VerticalResolution>600</VerticalResolution>\n" append xml " </Print>\n" append xml " <Selected/>\n" append xml " <Panes>\n" append xml " <Pane>\n" append xml " <Number>3</Number>\n" append xml " <ActiveRow>1</ActiveRow>\n" append xml " <ActiveCol>1</ActiveCol>\n" append xml " </Pane>\n" append xml " </Panes>\n" append xml " <ProtectObjects>False</ProtectObjects>\n" append xml " <ProtectScenarios>False</ProtectScenarios>\n" append xml " </WorksheetOptions>\n" append xml "</Workbook>\n" } else { error "At least one Worksheet must exist in the workbook" } } #INITILIZE THE DEFAULT VALUES FOR COLUMN INDEX; Tcl2ExXML::_CreateColIdx