Tested in Excel 365 (16.8730.2046) 64-bit
Sometimes you have a set of files containing sheets with crosstabs that you need to further process in SQL server for example.
Because they are crosstabs, some information is summarized either as page title, table title or table info, see example below, click to see full size.
In this case you would need to add this summarized information as new columns. It can be easily done using formula if it’s only one file (see here how) but when you have hundreds, you could use some VBA code.
I used this file (no macros) to build the macros.
Jump to
Declarations
- Public TableHeaderDelimiter As String
Functions
GetRangeSize
- Public Function GetRangeSize(RangeToCheck As String)
- On Error GoTo err_return
- If RangeToCheck = "" Then
- GetRangeSize = 0
- Else
- GetRangeSize = Range(RangeToCheck).Count
- End If
- exit_function:
- Exit Function
- err_return:
- GetRangeSize = -1
- End Function
Helper sub
Main subs
A sub to get the parameters needed for the transformation sub that should be assigned to the Select button.
- Sub UserSettings()
- Dim a, b, c As String
- Dim rng As Range, strMsgBox As String
- Dim myWkb As Workbook
- Set a = Application
- Set myWkb = ActiveWorkbook
- Select Case ActiveCell.Address
- Case "$B$2" ' Folder containing the files*
- Cells(2, 2).Value = PickFileOrFolder(0)
- Case "$B$3" ' Destination folder
- Cells(3, 2).Value = PickFileOrFolder(0)
- Case "$B$4" ' Page Title Names
- b = "Select the range containing the Page Title Names. It should be a single column, one or more rows."
- c = a.InputBox(b, , , , , , , 0)
- If Range(GetAddressPart(c)).Columns.Count > 1 Then
- strMsgBox = "- It should be a single column, one or more rows."
- Cells(4, 2).ClearContents
- Else
- Cells(4, 2).Value = GetAddressPart(c)
- End If
- If Cells(4, 2).Value <> "" And Cells(5, 2).Value <> "" Then
- If Range(Cells(4, 2).Value).Count <> Range(Cells(5, 2).Value).Count Then
- strMsgBox = strMsgBox & vbCr & "- Ranges for values and names should be the same size."
- Cells(4, 2).ClearContents
- End If
- End If
- If strMsgBox <> "" Then
- strMsgBox = strMsgBox & vbCr & "Try again!"
- MsgBox strMsgBox
- End If
- Case "$B$5" ' Page Title Values
- b = "Select the range containing the Page Title Values. It should be a single column, one or more rows."
- c = a.InputBox(b, , , , , , , 0)
- If Range(GetAddressPart(c)).Columns.Count > 1 Then
- strMsgBox = "- It should be a single column, one or more rows."
- Cells(5, 2).ClearContents
- Else
- Cells(5, 2).Value = GetAddressPart(c)
- End If
- If Cells(4, 2).Value <> "" And Cells(5, 2).Value <> "" Then
- If Range(Cells(4, 2).Value).Count <> Range(Cells(5, 2).Value).Count Then
- strMsgBox = strMsgBox & vbCr & "- Ranges for values and names should be the same size."
- Cells(5, 2).ClearContents
- End If
- End If
- If strMsgBox <> "" Then
- strMsgBox = strMsgBox & vbCr & "Try again!"
- MsgBox strMsgBox
- End If
- Case "$B$6" ' Table Title
- b = "Select the range containing the Table Titles. It should be a single column, one or more rows."
- c = a.InputBox(b, , , , , , , 0)
- If Range(GetAddressPart(c)).Columns.Count > 1 Then
- MsgBox "It should be a single column, one or more rows! Try again."
- Cells(6, 2).ClearContents
- Else
- Cells(6, 2).Value = GetAddressPart(c)
- End If
- Case "$B$7" ' Table Header
- b = "Select the range containing the Table Header. It should be a single row."
- c = a.InputBox(b, , , , , , , 0)
- If Range(GetAddressPart(c)).Rows.Count > 1 Then
- MsgBox "It should be a single row! Try again."
- Cells(7, 2).ClearContents
- Else
- Cells(7, 2).Value = GetAddressPart(c)
- ' Table Header values
- TableHeaderDelimiter = FindDelimiter(Range(GetAddressPart(c)))
- Set rng = Workbooks(GetAddressPart(c, "w")).Sheets(GetAddressPart(c, "s")).Range(GetAddressPart(c, "r"))
- Cells(8, 2).Value = Join(a.Transpose(a.Transpose(rng)), TableHeaderDelimiter) & TableHeaderDelimiter
- End If
- Case "$B$9" ' Table Info
- b = "Select the range containing the Table Info. It should be a single cell."
- c = a.InputBox(b, , , , , , , 0)
- If Range(GetAddressPart(c)).Columns.Count > 1 Or Range(GetAddressPart(c)).Rows.Count > 1 Then
- MsgBox "It should be a single cell! Try again."
- Cells(9, 2).ClearContents
- Else
- Cells(9, 2).Value = GetAddressPart(c)
- End If
- Case Else
- End Select
- Cells(ActiveCell.Row + 1, 2).Select
- End Sub
The main sub that would process the files. The sub should be assigned to the Process Files button.
- ' need to add the following reference:
- ' Microsoft Scripting Runtime
- Dim fso As New FileSystemObject, folder As Variant, file As Variant
- Dim SourcePath As String, DestinationPath
- Dim myWkb As Workbook, extWkb As Workbook
- Dim SettingsArray(7), a, i, j, k, LastRow, LastCol
- Dim RngPage As Range, RngTitle As Range, RngInfo As Range, tempRng As Range
- Dim tempDelim As String, tempArr As Variant, arrPageName As Variant, arrPageValue As Variant
- Dim crtTableHeader As String, crtTableInfo As String, TtThDistance, TtTiDistance
- Dim PageTitleSize, TableTitleSize, TableInfoSize
- Dim PageTitleName0 As String, PageTitleValue0 As String, TableTitle0 As String, TableHeader0 As String
- Dim TableHeaderValue0 As String, TableInfo0 As String, TableInfoDelim0 As String, TableInfoCols0, TableInfoCols
- Dim SheetStatus As String, arrStatus(), arrRow
- Call ToggleExcelFunctionalities(False)
- Set a = Application
- '1) check if the provided info is ok
- ' check if the folder containing the files is specified
- If InStr(1, Cells(2, 2).Value, "\") > 0 Then
- SourcePath = Cells(2, 2).Value
- Else
- MsgBox "Select first the folder containing the files!"
- Exit Sub
- End If
- ' determine the destination folder
- If InStr(1, Cells(3, 2).Value, "\") > 0 Then
- DestinationPath = Cells(3, 2).Value
- Else
- If Len(Trim(Cells(3, 2).Value)) = 0 Then
- DestinationPath = SourcePath & "\" & "Processed"
- Else
- DestinationPath = SourcePath & "\" & Cells(3, 2).Value
- End If
- End If
- '2) get the params from the setting tab
- For i = 0 To UBound(SettingsArray)
- SettingsArray(i) = Cells(i + 4, 2).Value
- Next i
- PageTitleName0 = SettingsArray(0)
- PageTitleValue0 = SettingsArray(1)
- TableTitle0 = SettingsArray(2)
- TableHeader0 = SettingsArray(3)
- TableHeaderValue0 = SettingsArray(4)
- TableInfo0 = SettingsArray(5)
- TableInfoDelim0 = SettingsArray(6)
- TableInfoCols0 = SettingsArray(7)
- PageTitleSize = GetRangeSize(PageTitleName0)
- TableTitleSize = GetRangeSize(TableTitle0)
- If TableInfoCols0 = "" Then
- TableInfoCols = -1
- Else
- TableInfoCols = CInt(TableInfoCols0)
- End If
- ' check for table header and find the delimiter
- If TableHeaderValue0 <> "" Then
- TableHeaderDelimiter = Right(TableHeaderValue0, 1)
- Else
- MsgBox "Table header is missing. Please select it first!"
- Exit Sub
- End If
- ' get the distance between table title and table header
- If TableTitle0 <> "" And TableHeader0 <> "" Then
- TtThDistance = Range(TableHeader0).Row - Range(TableTitle0).Row
- Else
- TtThDistance = 0
- End If
- ' get the distance between table info and table header
- If TableInfo0 <> "" And TableHeader0 <> "" Then
- TtTiDistance = Range(TableHeader0).Row - Range(TableInfo0).Row
- Else
- TtTiDistance = 0
- End If
- 'prepare the summary array
- ReDim Preserve arrStatus(2, 0)
- '3) prepare the loop through files
- Set fso = CreateObject("Scripting.FileSystemObject")
- If fso.FolderExists(SourcePath) Then
- Set folder = fso.GetFolder(SourcePath)
- Else
- MsgBox "Source Folder doesn't exist!", vbCritical, "Warning"
- Exit Sub
- End If
- ' create the destination folder if missing
- If Not fso.FolderExists(DestinationPath) Then MkDir DestinationPath
- Set myWkb = ActiveWorkbook
- '4) loop through files
- For Each file In folder.Files
- ' Debug.Print file.Path, file.Name
- Workbooks.Open Filename:=file.Path
- Set extWkb = ActiveWorkbook
- '4a) loop through all sheets
- For i = 1 To Sheets.Count
- Sheets(i).Activate
- ' check if the format is right by checking the first table header
- crtTableHeader = Join(a.Transpose(a.Transpose(Range(TableHeader0).Value)), TableHeaderDelimiter) & TableHeaderDelimiter
- If crtTableHeader = TableHeaderValue0 Then ' if the same header, process the sheet
- LastRow = LastRowOrCol(0, 1, 10)
- LastCol = LastRowOrCol(1, 1, 20)
- ' store page title
- If PageTitleValue0 <> "" Then
- arrPageName = Range(PageTitleName0)
- arrPageValue = Range(PageTitleValue0)
- End If
- ' copy the table header on the first row
- Range(Cells(1, Range(TableHeader0).Column), _
- Cells(1, Range(TableHeader0).Column + Range(TableHeader0).Count - 1)).Value = _
- Range(TableHeader0).Value
- ' page title names as column names
- If PageTitleName0 <> "" Then _
- Range(Cells(1, LastCol + 1), Cells(1, LastCol + PageTitleSize)) = a.Transpose(arrPageName)
- ' find the table info paste size
- If TableInfoDelim0 <> "" And TableInfo0 <> "" Then
- tempArr = Split(Range(TableInfo0).Value, TableInfoDelim0, TableInfoCols)
- TableInfoSize = UBound(tempArr) + 1
- Else
- TableInfoSize = 1
- End If
- ' first table title range
- If TableTitle0 <> "" Then Set RngTitle = Range(TableTitle0)
- ' first table info range
- If TableInfo0 <> "" Then Set RngInfo = Range(TableInfo0)
- '4b) loop through all rows
- For j = 2 To LastRow
- '5a) page title values
- If PageTitleValue0 <> "" Then _
- Range(Cells(j, LastCol + 1), Cells(j, LastCol + PageTitleSize)) = a.Transpose(arrPageValue)
- '5b) table title
- If TableTitle0 <> "" Then
- ' check if the current row contains a new table title by verifying
- ' if table header is on the expected position
- Set tempRng = Range(Cells(j + TtThDistance, Range(TableHeader0).Column), _
- Cells(j + TtThDistance, Range(TableHeader0).Column + Range(TableHeader0).Count - 1))
- ' create the test header
- crtTableHeader = Join(a.Transpose(a.Transpose(tempRng.Value)), TableHeaderDelimiter) _
- & TableHeaderDelimiter
- ' if found a new header, current row is a table title
- If crtTableHeader = TableHeaderValue0 Then
- ' define the new table title range
- Set RngTitle = Range(Cells(j, Range(TableTitle0).Column), _
- Cells(j + TableTitleSize - 1, Range(TableTitle0).Column))
- End If
- Range(Cells(j, LastCol + PageTitleSize + 1), _
- Cells(j, LastCol + PageTitleSize + TableTitleSize - 1)).Value = _
- a.Transpose(RngTitle.Value)
- End If
- '5c) table info
- If TableInfo0 <> "" Then
- ' check if the current row contains a new table info by verifying
- ' if table info is on the expected position
- Set tempRng = Range(Cells(j + TtTiDistance, Range(TableHeader0).Column), _
- Cells(j + TtTiDistance, Range(TableHeader0).Column + Range(TableHeader0).Count - 1))
- ' create the test header
- crtTableHeader = Join(a.Transpose(a.Transpose(tempRng.Value)), TableHeaderDelimiter) _
- & TableHeaderDelimiter
- ' if found a new header, current row is a table info
- If crtTableHeader = TableHeaderValue0 Then
- ' define the new table info range
- Set RngInfo = Cells(j, Range(TableInfo0).Column)
- End If
- tempArr = Split(RngInfo.Value, TableInfoDelim0, TableInfoCols)
- Range(Cells(j, LastCol + PageTitleSize + TableTitleSize), _
- Cells(j, LastCol + PageTitleSize + TableTitleSize + TableInfoSize - 1)).Value = _
- tempArr
- End If
- ' add file and sheet name
- Cells(j, LastCol + PageTitleSize + TableTitleSize + TableInfoSize).Value = file.Path
- Cells(j, LastCol + PageTitleSize + TableTitleSize + TableInfoSize + 1).Value = Sheets(i).Name
- Next j
- Set RngTitle = Nothing
- Set RngInfo = Nothing
- SheetStatus = "Processed, format ok"
- Else
- SheetStatus = "Not processed, other format"
- End If
- Cells(1, LastCol + PageTitleSize + TableTitleSize + TableInfoSize).Value = "File"
- Cells(1, LastCol + PageTitleSize + TableTitleSize + TableInfoSize + 1).Value = "Sheet"
- ' add summary to array
- arrRow = UBound(arrStatus, 2) + 1
- ReDim Preserve arrStatus(2, arrRow)
- arrStatus(0, arrRow) = file.Path
- arrStatus(1, arrRow) = Sheets(i).Name
- arrStatus(2, arrRow) = SheetStatus
- Next i
- extWkb.Close SaveChanges:=True, Filename:=DestinationPath & "\" & file.Name
- Next
- ' write the summary to a sheet
- If SheetExists("Summary") Then Sheets("Summary").Delete
- Sheets.Add , Sheets(Sheets.Count)
- ActiveSheet.Name = "Summary"
- Range(Cells(1, 1), Cells(arrRow + 1, 3)).Value = a.Transpose(arrStatus)
- Range("A1:C1").Value = Split("File Sheet Status")
- MsgBox "Done!"
- Call ToggleExcelFunctionalities(True)
- End Sub