BigDaddyO Posted July 20, 2017 Share Posted July 20, 2017 I know there are some Excel .xml UDF's out there already but since I'm using this for frickin huge reports I didn't want to have to call a function for each cell I needed to populate so I wrote a function that would take a 2D array which contains another 2D array in each row for each worksheet. Currently I'm using this to pull defect reports from HP ALM for 102 Projects. The below example has been modified to look more like a Server Patching report since I know a lot of you do that sort of stuff. My Largest report right now has 103 worksheets, with between 100 and 10,000 rows in each worksheet and is about 4MB in size after being saved as .xlsx Excel would randomly crash when building these reports through direct interaction especially when performing the formatting but using the .xml code below it builds the files extremely quick and most importantly, with no crashing. The sample below creates a single excel workbook with 59 worksheets. An overview worksheet that has hyperlinks to the other 58 worksheets. This is what I now use as my standard report template. expandcollapse popup#include <Excel.au3> ;This is only used when we want to convert the .xml to .xlsx $aAllWorksheets = _Build_Sample_Array() ;This builds an array populated with data so we can write it out to the .xml file ;Save the spreadsheet as .xml only ;_GenerateXML($aAllWorksheets, 6, "C:\TEMP\SpreadsheetXMLformat.xml", False) ;_GenerateXML("Array containing everything", "max number of columns needed in the entire worksheet", "The target .xml file") ;Save the array data to .xlsx and remove the temp .xml file _GenerateXML($aAllWorksheets, 6, "C:\TEMP\SpreadsheetFromXML.xlsx") ;_GenerateXML("Array containing everything", "max number of columns needed in the entire worksheet", "The target .xlsx file") ;----------------------------------------------------------------------------------------------------------------------------------------------------- ;Creates a .xml file using FileWrite and does not require Excel at all if leaving as .xml. ;$aWorksheets is a 2D array where the last array item stores another 2D array for the individual worksheets. ;Required 2D Array setup for $aWorksheets ; [0][#] will store the column width for the Overview worksheet columns ; [1][#] will store the Headers for the Overview worksheet Columns ; [2+][#] will store the individual worksheets ; [2+][last array item] This will be used to store another array which contains all data for the worksheet ; ; Worksheet 2D Array $aWorksheetData ; [0][#] will store the column width for the worksheet ; [1][#] will store the column headers A1 will be used as a hyperlink back to the overview worksheet ; [2+][#] Remaining items will store the data used to populate the worksheet ; ; $iMaxColumns, This needs to be the maximum Column used in any of the worksheets so you will need to count as you're building the 2D arrays ; ; $sOutputFile: If Convert to XLSX = True, then this OutputFile must end with .xlsx. ; If the Convert to XLSX = False, then this output file must end with .xml ; ; $ConvertToXLSX: If True, then there will be a temp .xml file created with the array data and will then be opened with Excel and saved as .xlsx ; If False, then it will create just the .xml file, warning these can be huge. ;----------------------------------------------------------------------------------------------------------------------------------------------------- Func _GenerateXML($aWorksheets, $iMaxColumns, $sOutputFile, $ConvertToXLSX = True) ConsoleWrite("Creating Report" & @CRLF) ;Create the main Workbook, this will need to be customized if you want to use other font, or Styles ; Easiest way to get new styles is to create in Excel, save as XML Spreadsheet 2003 (*.xml) then open it up in Scite to pull out the new style, "look for Style child items too" ;Below are the Current styles that can be used ; ;Default: If no style specified then this will be used ; Align Right, Bottom ; No cell borders ; Times New Romain, size 12 ; Black font on white background ; ;s16: Used for the Hyperlinks ; Font Times New Romain, size 12 ; Blue text that is Underlined ; ;s17: Format the cell as a Number ; ;s18: Used for Header text ; Font Times New Romain, Size 12 ; Black Font, Bold ; Set background color to blue ; Set cells as Text formatting ; ;s19: This is a child item of s16 "not sure why it's always stuck down at bottom" ; Used to apply Hyperlink to items in the Header row with background color ; ;s20: This is another child item of s16, used to show hyperlinks on normal cells ;Create the $sWorksheets string that will contain the entire .xml that will be written to file. This top section identifies it as Excel XML, and sets up the Styles that can be used. $sWorksheets = '<?xml version="1.0"?>' & @CRLF & _ '<?mso-application progid="Excel.Sheet"?>' & @CRLF & _ '<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" ' & @CRLF & _ 'xmlns:o="urn:schemas-microsoft-com:office:office" ' & @CRLF & _ 'xmlns:x="urn:schemas-microsoft-com:office:excel" ' & @CRLF & _ 'xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" ' & @CRLF & _ 'xmlns:html="http://www.w3.org/TR/REC-html40"> ' & @CRLF & _ '<Styles> ' & @CRLF & _ '<Style ss:ID="Default" ss:Name="Normal"> ' & @CRLF & _ '<Alignment ss:Vertical="Bottom"/> ' & @CRLF & _ '<Borders/> ' & @CRLF & _ '<Font ss:FontName="Times New Roman" x:Family="Swiss" ss:Size="12" ' & @CRLF & _ 'ss:Color="#000000"/> ' & @CRLF & _ '<Interior/> ' & @CRLF & _ '<NumberFormat/> ' & @CRLF & _ '<Protection/> ' & @CRLF & _ '</Style> ' & @CRLF & _ '<Style ss:ID="s16" ss:Name="Hyperlink"> ' & @CRLF & _ '<Font ss:FontName="Times New Roman" x:Family="Swiss" ss:Size="12" ' & @CRLF & _ 'ss:Color="#0563C1" ss:Underline="Single"/> ' & @CRLF & _ '</Style> ' & @CRLF & _ '<Style ss:ID="s17"> ' & @CRLF & _ '<NumberFormat ss:Format="@"/> ' & @CRLF & _ '</Style> ' & @CRLF & _ '<Style ss:ID="s18"> ' & @CRLF & _ '<Font ss:FontName="Times New Roman" x:Family="Roman" ss:Size="12" ' & @CRLF & _ 'ss:Color="#000000" ss:Bold="1"/> ' & @CRLF & _ '<Interior ss:Color="#BDD7EE" ss:Pattern="Solid"/> ' & @CRLF & _ '<NumberFormat ss:Format="@"/> ' & @CRLF & _ '</Style> ' & @CRLF & _ '<Style ss:ID="s19" ss:Parent="s16"> ' & @CRLF & _ '<Interior ss:Color="#BDD7EE" ss:Pattern="Solid"/> ' & @CRLF & _ '<NumberFormat ss:Format="@"/> ' & @CRLF & _ '</Style> ' & @CRLF & _ '<Style ss:ID="s20" ss:Parent="s16"> ' & @CRLF & _ '<NumberFormat ss:Format="@"/> ' & @CRLF & _ '</Style> ' & @CRLF & _ '</Styles> ' & @CRLF ;Create the Overview Worksheet which will have hyperlinks to all of the other ones ; ss:Name= the name of the worksheet tab ; _FilterDatabase: Add the Dropdown filters to the used columns ; ss:ExpandedColumnCount: Seems to need the max column count from All worksheets in the workbook which is passed to the function in $iMaxColumns ; ExpandedRowCount = the total rows being used for this worksheet $sWorksheets &= '<Worksheet ss:Name="Overview">' & @CRLF & _ '<Names>' & @CRLF & _ '<NamedRange ss:Name="_FilterDatabase" ss:RefersTo="=''Overview''!R1C1:R2C' & $iMaxColumns & '" ss:Hidden="1"/>' & @CRLF & _ '</Names>' & @CRLF & _ '<Table ss:ExpandedColumnCount="' & $iMaxColumns & '" ss:ExpandedRowCount="' & UBound($aWorksheets) & '" x:FullColumns="1" x:FullRows="1" ss:DefaultColumnWidth="54" ss:DefaultRowHeight="15.75">' & @CRLF ;Need to format the width of the columns depending on the [0][#] data For $c = 0 to UBound($aWorksheets, 2) - 2 ;The last item in the array is the Worksheets array so we don't want to use that as a column in the overview worksheet $sWorksheets &= '<Column ss:AutoFitWidth="0" ss:Width="' & $aWorksheets[0][$c] & '"/>' & @CRLF Next ;We now need to add each of the Servers as a row in the Overview worksheet For $i = 1 to UBound($aWorksheets) - 1 If $aWorksheets[$i][0] = "" Then ContinueLoop ;If the Domain is not populated, then we shouldn't add the rest since this is most likely an empty row at the end $iLastArray = UBound($aWorksheets, 2) - 1 ;Identify the last array item for each row which should contain individual worksheet array data If $i = 1 Then ;This is the header row $sWorksheets &= '<Row ss:StyleID="s18">' & @CRLF ;make the Header row as blue background and bold using Style 18 For $c = 0 to UBound($aWorksheets, 2) - 1 If $aWorksheets[$i][$c] <> "" Then $sWorksheets &= '<Cell><Data ss:Type="String">' & $aWorksheets[$i][$c] & '</Data></Cell>' & @CRLF Next $sWorksheets &= '</Row>' & @CRLF Else $sWorksheets &= '<Row>' & @CRLF ;Write the value in Column A If $aWorksheets[$i][0] <> "" Then $sWorksheets &= '<Cell><Data ss:Type="String">' & $aWorksheets[$i][0] & '</Data></Cell>' & @CRLF ;If this row has Array Data then add a hyperlink in Column B to the Worksheet which will be created If IsArray($aWorksheets[$i][$iLastArray]) Then If $aWorksheets[$i][1] <> "" Then $sWorksheets &= '<Cell ss:StyleID="s20" ss:HRef="#''' & $aWorksheets[$i][1] & '''!A1" x:HRefScreenTip="' & $aWorksheets[$i][1] & '"><Data ss:Type="String">' & $aWorksheets[$i][1] & '</Data></Cell>' & @CRLF Else If $aWorksheets[$i][1] <> "" Then $sWorksheets &= '<Cell><Data ss:Type="String">' & $aWorksheets[$i][1] & '</Data></Cell>' & @CRLF EndIf ;Only write out Column C if there is enough items in the main array to contain it If ($iLastArray > 2) and ($aWorksheets[$i][2] >= 0) Then If StringIsInt($aWorksheets[$i][2]) Then ;Figure out what type of formatting this cell requires $sType = "Number" Else $sType = "String" EndIf $sWorksheets &= '<Cell><Data ss:Type="' & $sType & '">' & $aWorksheets[$i][2] & '</Data></Cell>' & @CRLF EndIf ;Only write out Column D if there is enough items in the main array to contain it If ($iLastArray > 3) and ($aWorksheets[$i][3] >= 0) Then If StringIsInt($aWorksheets[$i][3]) Then $sType = "Number" Else $sType = "String" EndIf $sWorksheets &= '<Cell><Data ss:Type="' & $sType & '">' & $aWorksheets[$i][3] & '</Data></Cell>' & @CRLF EndIf ;Only write out Column E if there is enough items in the main array to contain it If ($iLastArray > 4) and ($aWorksheets[$i][4] >= 0) Then If StringIsInt($aWorksheets[$i][4]) Then $sType = "Number" Else $sType = "String" EndIf $sWorksheets &= '<Cell><Data ss:Type="' & $sType & '">' & $aWorksheets[$i][4] & '</Data></Cell>' & @CRLF EndIf ;Only write out Column F if there is enough items in the main array to contain it If ($iLastArray > 5) and ($aWorksheets[$i][5] >= 0) Then If StringIsInt($aWorksheets[$i][5]) Then $sType = "Number" Else $sType = "String" EndIf $sWorksheets &= '<Cell><Data ss:Type="' & $sType & '">' & $aWorksheets[$i][5] & '</Data></Cell>' & @CRLF EndIf $sWorksheets &= '</Row>' & @CRLF EndIf Next ;Write out the footer info for the first Overview worksheet $sWorksheets &= '</Table>' & @CRLF & _ '<WorksheetOptions xmlns="urn:schemas-microsoft-com:office:excel">' & @CRLF & _ '<PageSetup>' & @CRLF & _ '<Header x:Margin="0.3"/>' & @CRLF & _ '<Footer x:Margin="0.3"/>' & @CRLF & _ '<PageMargins x:Bottom="0.75" x:Left="0.7" x:Right="0.7" x:Top="0.75"/>' & @CRLF & _ '</PageSetup>' & @CRLF & _ '<Selected/>' & @CRLF & _ '<FreezePanes/>' & @CRLF & _ '<FrozenNoSplit/>' & @CRLF & _ '<SplitHorizontal>1</SplitHorizontal>' & @CRLF & _ ;Split the top row so it's stationary when scrolling down '<TopRowBottomPane>1</TopRowBottomPane>' & @CRLF & _ '<ActivePane>2</ActivePane>' & @CRLF & _ '<Panes>' & @CRLF & _ '<Pane>' & @CRLF & _ '<Number>3</Number>' & @CRLF & _ '</Pane>' & @CRLF & _ '<Pane>' & @CRLF & _ '<Number>2</Number>' & @CRLF & _ '<ActiveRow>0</ActiveRow>' & @CRLF & _ '</Pane>' & @CRLF & _ '</Panes>' & @CRLF & _ '<ProtectObjects>False</ProtectObjects>' & @CRLF & _ '<ProtectScenarios>False</ProtectScenarios>' & @CRLF & _ '</WorksheetOptions>' & @CRLF & _ '</Worksheet>' & @CRLF ;Create all of the remaining worksheets For $i = 1 to UBound($aWorksheets) - 1 ;Skip 0 since that will be the Overview worksheet If IsArray($aWorksheets[$i][$iLastArray]) Then ;Only continue building this worksheet if the last item in main Array has data $aWorksheetData = $aWorksheets[$i][$iLastArray] ;Pull out the worksheets array data from the main Array ;Name the worksheet using the value from Column B in the Overview worksheet ;Set the dropdown filter on the used columns of this worksheet ;Again, set the Expanded Column Count to the max used on all worksheets ;Set the row count to the total that will be used in this worksheet $sWorksheets &= '<Worksheet ss:Name="' & $aWorksheets[$i][1] & '">' & @CRLF & _ '<Names>' & @CRLF & _ '<NamedRange ss:Name="_FilterDatabase" ss:RefersTo="=''' & $aWorksheets[$i][1] & '''!R1C1:R2C' & $iMaxColumns & '" ss:Hidden="1"/>' & @CRLF & _ '</Names>' & @CRLF & _ '<Table ss:ExpandedColumnCount="' & $iMaxColumns & '" ss:ExpandedRowCount="' & UBound($aWorksheetData) & '" x:FullColumns="1" x:FullRows="1" ss:StyleID="s17" ss:DefaultColumnWidth="54" ss:DefaultRowHeight="15.75">' & @CRLF ;Format the width of the columns depending on the [0][#] data For $c = 0 to UBound($aWorksheetData, 2) - 1 $sWorksheets &= '<Column ss:AutoFitWidth="0" ss:Width="' & $aWorksheetData[0][$c] & '"/>' & @CRLF Next For $r = 1 to UBound($aWorksheetData) - 1 If $r = 1 Then ;If this is the first row, then it's Header data so use differently $sWorksheets &= '<Row ss:StyleID="s18">' & @CRLF For $c = 0 to UBound($aWorksheetData, 2) - 1 If $r = 1 and $c = 0 Then ;If this is the first Cell in the First Column "A1" then give it a hyperlink back to the overview worksheet if $aWorksheetData[$r][$c] <> "" Then $sWorksheets &= '<Cell ss:StyleID="s19" ss:HRef="#''Overview''!B' & $i & '" x:HRefScreenTip="Return to Overview"><Data ss:Type="String">' & $aWorksheetData[$r][$c] & '</Data><NamedCell ss:Name="_FilterDatabase"/></Cell>' & @CRLF Else if $aWorksheetData[$r][$c] <> "" Then $sWorksheets &= '<Cell><Data ss:Type="String">' & $aWorksheetData[$r][$c] & '</Data></Cell>' & @CRLF EndIf Next $sWorksheets &= '</Row>' & @CRLF Else $sWorksheets &= '<Row>' & @CRLF ;Create a new row For $c = 0 to UBound($aWorksheetData, 2) - 1 ;Populate each column in the new row with data $sWorksheets &= '<Cell><Data ss:Type="String">' & $aWorksheetData[$r][$c] & '</Data></Cell>' & @CRLF Next $sWorksheets &= '</Row>' & @CRLF EndIf Next ;All data should be in the worksheet now so add the footer data to it $sWorksheets &= '</Table>' & @CRLF & _ '<WorksheetOptions xmlns="urn:schemas-microsoft-com:office:excel">' & @CRLF & _ '<PageSetup>' & @CRLF & _ '<Header x:Margin="0.3"/>' & @CRLF & _ '<Footer x:Margin="0.3"/>' & @CRLF & _ '<PageMargins x:Bottom="0.75" x:Left="0.7" x:Right="0.7" x:Top="0.75"/>' & @CRLF & _ '</PageSetup>' & @CRLF & _ '<Print>' & @CRLF & _ '<ValidPrinterInfo/>' & @CRLF & _ '<HorizontalResolution>600</HorizontalResolution>' & @CRLF & _ '<VerticalResolution>600</VerticalResolution>' & @CRLF & _ '</Print>' & @CRLF & _ '<Zoom>90</Zoom>' & @CRLF & _ '<FreezePanes/>' & @CRLF & _ '<SplitHorizontal>1</SplitHorizontal>' & @CRLF & _ '<TopRowBottomPane>1</TopRowBottomPane>' & @CRLF & _ '<ActivePane>2</ActivePane>' & @CRLF & _ '<Panes>' & @CRLF & _ '<Pane>' & @CRLF & _ '<Number>3</Number>' & @CRLF & _ '</Pane>' & @CRLF & _ '<Pane>' & @CRLF & _ '<Number>2</Number>' & @CRLF & _ '<ActiveRow>0</ActiveRow>' & @CRLF & _ '</Pane>' & @CRLF & _ '</Panes>' & @CRLF & _ '<ProtectObjects>False</ProtectObjects>' & @CRLF & _ '<ProtectScenarios>False</ProtectScenarios>' & @CRLF & _ '</WorksheetOptions>' & @CRLF & _ '<AutoFilter x:Range="R1C1:R2C' & UBound($aWorksheetData, 2) & '" xmlns="urn:schemas-microsoft-com:office:excel">' & @CRLF & _ '</AutoFilter>' & @CRLF & _ '</Worksheet>' & @CRLF EndIf Next $sWorksheets &= @CRLF & '</Workbook>' ;Add the closing for the entire xml workbook ;Create the temp .xml file that will get opened, then converted to the final .xlsx If $ConvertToXLSX = True Then ;See if we should convert this .xml into .xlsx $sTmpFile = "C:\TEMP\" & @UserName & "_" & @YDAY & @HOUR & @MIN & @SEC & @MSEC & ".xml" If FileWrite($sTmpFile, $sWorksheets) Then ;Write the full string to the .xml temp file $oXL = _Excel_Open(False, False, False, True, True) ;Create the Excel Com object $oWorkbook = _Excel_BookOpen($oXL, $sTmpFile, True) ;Open the huge .xml file If _Excel_BookSaveAs($oWorkbook, $sOutputFile, $xlOpenXMLWorkbook, True) Then ;Save the .xml file as .xlsx so it will be compressed into the new format FileDelete($sTmpFile) ;If the Save As was successful, Delete the Temp .mlfile Else MsgBox(0, "Error", "Unable to convert temp file to .xlsx, temp file remains " & $sTmpFile) EndIf _Excel_BookClose($oWorkbook, False) ;Close the newly created .xlsx file _Excel_Close($oXL, False) ;Disconnect from the Excel COM object MsgBox(0, "Finished", "Output report saved as: " & $sOutputFile) Else MsgBox(0, "Error", "Unable to save the temp .xml file as " & $sTmpFile) EndIf Else ;Will just leave as .xml so save as the specified file name since it should be .xml If FileWrite($sOutputFile, $sWorksheets) Then ;Write the full string to the .xml file MsgBox(0, "Finished", "Output report saved as: " & $sOutputFile) Else MsgBox(0, "Error", "Unable to save the XML spreadsheet as: " & $sOutputFile) EndIf EndIf EndFunc Func _Build_Sample_Array() ;Build an array we will randomly pull values from to build sample server names Local $aValues[62] $w = 0 For $x = 48 to 57 $aValues[$w] = Chr($x) $w += 1 Next For $y = 65 to 90 $aValues[$w] = Chr($y) $w += 1 Next For $y = 97 to 122 $aValues[$w] = Chr($y) $w += 1 Next ;Build the array that will store everything for this report, 0-4 is the Overview worksheet. 5 will store the arrays for the other 58 worksheets Local $aAllWorksheets[60][6] $aAllWorksheets[0][0] = 110 ;Set the Column sizes for the Overview worksheet $aAllWorksheets[0][1] = 120 $aAllWorksheets[0][2] = 180 $aAllWorksheets[0][3] = 110 $aAllWorksheets[0][4] = 90 $aAllWorksheets[1][0] = "Domain" ;Set the header rows for the Overview worksheet $aAllWorksheets[1][1] = "Server" $aAllWorksheets[1][2] = "Total Applications to patch" $aAllWorksheets[1][3] = "Successfully Patched" $aAllWorksheets[1][4] = "Patched Failed" ;Loop used to build the arrays which will contain the data for the individual worksheets For $i = 2 to UBound($aAllWorksheets) - 1 $iFailed = 0 $iPassed = 0 Local $aDBvalues[200][5] ;Make the 2D array that will hold everything $aDBvalues[0][0] = 60 ;[0][#] is used to store the column widths $aDBvalues[0][1] = 120 $aDBvalues[0][2] = 200 $aDBvalues[0][3] = 220 $aDBvalues[0][4] = 160 $aDBvalues[1][0] = "Domain" ;[1][#] is used to store the Header values $aDBvalues[1][1] = "Server" $aDBvalues[1][2] = "Application Name" $aDBvalues[1][3] = "Patch Required" $aDBvalues[1][4] = "Patch Results" $sDomain = "Prod_ADS" $sServerName = "Prd-" & $aValues[Random(0, 61)] & $aValues[Random(0, 61)] & $aValues[Random(0, 61)] & $aValues[Random(0, 61)] & $aValues[Random(0, 61)] & $aValues[Random(0, 61)] & "-" & $i - 2 For $a = 2 to 199 $aDBvalues[$a][0] = $sDomain $aDBvalues[$a][1] = $sServerName $aDBvalues[$a][2] = "App_NameHere" & $a * $i $aDBvalues[$a][3] = "Yes" If IsInt($a / 2) Then $aDBvalues[$a][4] = "Error " & $a - 1 $iFailed += 1 Else $aDBvalues[$a][4] = "Passed" $iPassed += 1 EndIf Next $aAllWorksheets[$i][0] = $sDomain $aAllWorksheets[$i][1] = $sServerName $aAllWorksheets[$i][2] = UBound($aDBvalues) - 2 ;Populate the Total applications to patch $aAllWorksheets[$i][3] = $iPassed ;Populate the Overview worksheet Successfully Patched $aAllWorksheets[$i][4] = $iFailed ;Populate the Overview worksheet Failed to Patch $aAllWorksheets[$i][5] = $aDBvalues ;Save the array that contains the servers detailed worksheet data Next Return $aAllWorksheets EndFunc dmob 1 Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now