Home
Name Modified Size InfoDownloads / Week
README.txt 2019-07-13 28.9 kB
WBS_Generator.xlsm 2019-07-13 87.8 kB
Totals: 2 Items   116.7 kB 3
' This WBS Generator creates a work breakdown structure based on an Excel table.
' Created by Jerome Smith
' Published on https://sourceforge.net/projects/wbs-generator-good-plus-fast/
'
'Version 2.2 - July 13, 2019
    'thanks to Mathieu Bilodeau on https://www.projectmanagement.com/deliverables/500955/WBS-Generator
        'Fixed an error with conditional formatting and integer validation in Excel 365
    
    
'Version 2.1 - March 16, 2019
    'Updated sniff test to check for English as the default language before execution.

'Version 2.0 - August 5, 2018
    'Fixed misalignment issue when there are an odd number of WBS ID integers.
    'Fixed drawing lines issue when x.01 was not the first WBS ID in level-3 boxes.
    'Added option to draw connector lines or not.
    'Created vertical/horizontal option for last field
    'Added progress indicator
    'Created Option to display legend
    'Legend uses formulas to reflect real time updates in WBS Table headers.
    
'Version 1.1 - August 1, 2018
    'Updated code to use two decimal points for the WBS ID to avoid n.1 and n.10 confusion.
    
' Version 1.0 - July 28, 2018
    'First released

'-------------------------------------------------------------------------------
'-------------------------------------------------------------------------------
'----------------------------     WBS GENERATOR     ----------------------------
'-------------------------------------------------------------------------------
'-------------------------------------------------------------------------------

Option Base 1
Option Explicit

Dim wsWBS As Worksheet, wsData As Worksheet, wsOptions As Worksheet, wsFormats As Worksheet
Dim tbl As ListObject, colCount As Integer, WBSarray As Variant
Dim iRow As Long, x As Long, i As Integer, j As Integer
Dim cell As Range, WBScolumn As Range, rng As Range
Dim WBS_AddressIDsString As String, WBS_AddressIDsRng As Range
Dim rowWBSID As Integer, middleWBSid As Double
Dim iDuplicates As Integer, iBlanks As Integer, iInteger As Integer, cntWBSintegers As Integer
Dim heading() As String, formattedData() As String
Dim rowIncrement As Integer, colIncrement As Integer
Dim drawConnectors As Boolean, LineElement1_colLeft, LineElement1_colRight, LineElement1_rowBottom, LineElement1_colMiddle
Dim bgPattern As String, bgColor As String, bgTint As String, fontColor As String
Dim lastFieldDisplay As String

Public Sub GenerateWBS()
    Application.Calculation = xlCalculationManual
    Application.ScreenUpdating = False
    On Error GoTo 0
    Prepare
    Application.StatusBar = "Validation in progress..."
    SniffTest
    ResetWBS
    
    PullWBSfromData
    createConditionalFormats
    Cells.ShrinkToFit = True
    
    wsData.Activate
    Cells(1, 1).Select
    
    wsWBS.Activate
    If drawConnectors = True Then
        Application.StatusBar = "Drawing lines..."
        Call drawLines_EastWest
        Call drawLines_NorthSouth(xlEdgeRight, LineElement1_rowBottom + 1, LineElement1_colMiddle - 5)
    End If
    Cells(1, 1).Select
    Cells.ColumnWidth = wsOptions.Range("b6").Value
    Cells.EntireColumn.AutoFit
    ActiveWindow.DisplayHeadings = wsOptions.Range("b8").Value2
    ActiveWindow.DisplayGridlines = wsOptions.Range("b9").Value2
    
    If wsOptions.Range("b10").Value = True Then Call createLegend
    
SendToErrorHandler:
    Call CleanUp
End Sub

Private Sub Prepare()
    Set wsData = sheetData
    Set wsWBS = sheetWBS
    Set wsOptions = sheetOptions
    Set wsFormats = sheetFormats
    Set tbl = wsData.ListObjects("WBS_Table")
    getBodyBGandFontColor
    wsData.Activate
    rowWBSID = Cells(Rows.Count, 1).End(xlUp).row
    Set WBScolumn = wsData.Range(Cells(2, 1), Cells(rowWBSID, 1))
    Cells.FormatConditions.Delete
    
  'does the data table start in cell A1?
    If Left(tbl.HeaderRowRange.Address(0, 0), 2) <> "A1" Then
        MsgBox "The table on the " & wsData.Name & " sheet doesn't start in cell A1. Delete any rows above or columns to the left of the data table and try agan.", _
            vbCritical, "Set Data Table to Cell A1"
        CleanUp
    End If
    
    With tbl.Sort
       .SortFields.Clear
       .SortFields.Add Key:=Range("a1"), SortOn:=xlSortOnValues, Order:=xlAscending
       .Header = xlYes
       .Apply
    End With
    
    WBSarray = tbl.DataBodyRange
    colCount = tbl.ListColumns.Count
    drawConnectors = wsOptions.Range("b11").Value
    lastFieldDisplay = wsOptions.Range("b7").Value
    
    With WBScolumn.Interior 'clear shading
        .Pattern = xlNone
        .TintAndShade = 0
    End With
    
    highlightAllBlanksInDataTable
    findMiddlePlotForLevel1
    getDataFormats
End Sub
Private Sub ResetWBS()
    wsWBS.Activate
    Cells.Clear
    Cells.ColumnWidth = 2.25
    ActiveWindow.Zoom = 100
    Cells(1, 1).Select
    ActiveSheet.Shapes.SelectAll
    Selection.Delete
End Sub

Private Function getBodyBGandFontColor()
    bgPattern = wsOptions.Range("b5").Interior.PatternColorIndex
    bgColor = wsOptions.Range("b5").Interior.Color
    bgTint = wsOptions.Range("b5").Interior.TintAndShade
    fontColor = wsOptions.Range("b5").Font.Color
End Function

Private Sub PullWBSfromData()
    
    Dim colLevel2 As Double, rowLevel2 As Double
    Dim colLevel3 As Double, rowLevel3 As Double
    Dim IDpackage As Double, IDdeliverable As Double
    Dim firstLevelThreeBox As Boolean
    
    wsWBS.Activate
    colLevel2 = 1
    colLevel3 = 2
    For x = LBound(WBSarray) To UBound(WBSarray)
    
      'This is element level 3... WBS ID is not an integer
        If WBSarray(x, 1) <> Int(WBSarray(x, 1)) Then
            Call PlotWBS(rowLevel3, colLevel3, Format(WBSarray(x, 1), "0.00"))
            Call createDetailsBox(rowLevel3, colLevel3, 3)
            
            If drawConnectors = True Then
                If firstLevelThreeBox = True Then
                    Call drawLines_NorthSouth(xlDiagonalDown, rowLevel3, colLevel3)
                    firstLevelThreeBox = False
                Else
                    Call drawLines_NorthSouth(xlEdgeRight, rowLevel3, colLevel3)
                End If
            End If
            
            rowLevel3 = Cells(Rows.Count, colLevel3 + 1).End(xlUp).row + 2 'start next box below last row in this column
            IDdeliverable = WBSarray(x, 1)
        
      'This is element level 2... WBS ID is an integer greater than 0
        ElseIf WBSarray(x, 1) > 0 And val(WBSarray(x, 1)) = Int(WBSarray(x, 1)) Then
            Call PlotWBS(rowLevel2, colLevel2, Format(WBSarray(x, 1), "0.00"))
            Call createDetailsBox(rowLevel2, colLevel2, 2)
            If drawConnectors = True Then Call drawLines_NorthSouth(xlEdgeRight, rowLevel2, colLevel2)
            rowLevel3 = Cells(Rows.Count, colLevel2 + 1).End(xlUp).row + 2  'start next box below last row in this column
            colLevel2 = colLevel2 + 13
            IDpackage = WBSarray(x, 1)
            firstLevelThreeBox = True
            
      'This is element level 1... WBS ID is 0
        ElseIf WBSarray(x, 1) = 0 Then
            Call PlotWBS(1, middleWBSid, Format(WBSarray(x, 1), "0.00"))
            Call createDetailsBox(1, middleWBSid, 1)
            rowLevel2 = Cells(Rows.Count, middleWBSid + 1).End(xlUp).row + 3
            LineElement1_rowBottom = rowLevel2 - 2
            LineElement1_colMiddle = middleWBSid + 5
        End If
            
      'If ready for next Package, update deliverable column
        If IDpackage > IDdeliverable And IDpackage > 1 Then colLevel3 = colLevel3 + 13
        Application.StatusBar = x & " of " & UBound(WBSarray) & " boxes created..."
    Next x
    
End Sub
Private Function findMiddlePlotForLevel1()
    For x = LBound(WBSarray) To UBound(WBSarray)
        If WBSarray(x, 1) > 0 And val(WBSarray(x, 1)) = Int(WBSarray(x, 1)) Then
            cntWBSintegers = cntWBSintegers + 1
        End If
    Next x
    LineElement1_colLeft = 6
    LineElement1_colRight = cntWBSintegers * 13 - 8
    middleWBSid = Int(cntWBSintegers * 13 / 2 - 5)
    
End Function

Private Sub PlotWBS(row As Double, col As Double, val As Double)
    With wsWBS.Cells(row, col)
        .Value = val
        .NumberFormat = "0.00"
        .Borders.LineStyle = xlContinuous
        .Font.Bold = True
    End With
End Sub

Private Sub getDataFormats()
    'remember, Option Base 1 is set
    Dim i As Integer, xData As Variant
    wsData.Activate

    i = 0
    For Each xData In wsData.Rows(1).Cells
        If xData.Value = "" Then Exit For
        i = i + 1
        ReDim Preserve heading(i) As String
        heading(i) = xData.Value
    Next xData
    
    i = 0
    For Each xData In wsData.Rows(2).Cells
        If xData.Value = "" Then Exit For
        i = i + 1
        ReDim Preserve formattedData(i) As String
        formattedData(i) = xData.NumberFormat
    Next xData
End Sub

Private Sub createDetailsBox(row As Double, col As Double, level As Integer)
    Dim x As Integer
    Dim mergeArray() As Range
    wsWBS.Activate
    
    Set WBS_AddressIDsRng = wsWBS.Cells(row, col)
    WBS_AddressIDsString = WBS_AddressIDsRng.Address
    
    'Enter formulas and set define cells to merge
    rowIncrement = 0
    colIncrement = 1
    
    For x = 2 To colCount
      ReDim Preserve mergeArray(x) As Range
      wsWBS.Cells(row + rowIncrement, col + colIncrement).Value = "=VLOOKUP(" & WBS_AddressIDsString & ",WBS_Table," & x & ",FALSE)"
      
      'second to last detail
        If x = colCount - 1 And lastFieldDisplay = "Vertical" Then
            If colIncrement = 1 Then
                colIncrement = 9
                Set mergeArray(x) = wsWBS.Range(Cells(row + rowIncrement, col + 1), Cells(row + rowIncrement, col + 8))
            ElseIf colIncrement = 5 Then
                colIncrement = 9
                Set mergeArray(x) = wsWBS.Range(Cells(row + rowIncrement, col + 5), Cells(row + rowIncrement, col + 8))
            End If
 
      'last detail
        ElseIf x = colCount And lastFieldDisplay = "Vertical" Then
            Set mergeArray(x) = wsWBS.Range(Cells(row + rowIncrement, col + colIncrement), Cells(row, col + colIncrement))
        ElseIf x = colCount And lastFieldDisplay = "Horizontal" Then
            If colIncrement = 1 Then
                colIncrement = 9
                Set mergeArray(x) = wsWBS.Range(Cells(row + rowIncrement, col + 1), Cells(row + rowIncrement, col + 8))
            ElseIf colIncrement = 5 Then
                colIncrement = 9
                Set mergeArray(x) = wsWBS.Range(Cells(row + rowIncrement, col + 5), Cells(row + rowIncrement, col + 8))
            End If
            
      'details below title, left side
        ElseIf rowIncrement > 0 And colIncrement = 1 Then
            colIncrement = 5
            Set mergeArray(x) = wsWBS.Range(Cells(row + rowIncrement, col + 1), Cells(row + rowIncrement, col + 4))
      
      'details below title, right side
        ElseIf rowIncrement > 0 And colIncrement = 5 Then
            Set mergeArray(x) = wsWBS.Range(Cells(row + rowIncrement, col + 5), Cells(row + rowIncrement, col + 8))
            rowIncrement = rowIncrement + 1
            colIncrement = 1
      
      'title, merged across
        ElseIf rowIncrement = 0 And colIncrement = 1 Then
            Set mergeArray(x) = wsWBS.Range(Cells(row, col + 1), Cells(row, col + 8))
            rowIncrement = rowIncrement + 1
    
        Else
            Debug.Print "What is the else?"
        End If
        
        mergeArray(x).Select
        
        'Format cells
        Select Case x
        
            Case 2                              'title of details box
                wsOptions.Range("b" & level + 1).Copy
                Selection.PasteSpecial Paste:=xlPasteFormats
                
            Case colCount                       'last detail
                If lastFieldDisplay = "Vertical" Then
                    With Selection
                        .NumberFormat = formattedData(x)
                        .Orientation = 90
                        .Borders.LineStyle = xlContinuous
                    End With
                End If
                
            Case Else                           'all other details, format as source
                On Error Resume Next
                With Selection
                    .NumberFormat = formattedData(x)
                    .Interior.PatternColorIndex = bgPattern
                    .Interior.Color = bgColor
                    .Interior.TintAndShade = bgTint
                    .Font.Color = fontColor
                End With
                On Error GoTo 0
        End Select
        
        'merge cells
        With Selection
            .HorizontalAlignment = xlLeft
            .VerticalAlignment = xlBottom
            .WrapText = False
            .ShrinkToFit = True
            .ReadingOrder = xlContext
            .MergeCells = True
            .Borders.LineStyle = xlContinuous
        End With
        
    Next
    Erase mergeArray
End Sub

Private Sub createConditionalFormats()
    Dim iInteger As Integer
    Dim tblFormats_Conditional As ListObject
    Set tblFormats_Conditional = sheetFormats.ListObjects("OptionsConditionalFormat_Table")
    
    On Error Resume Next
    For iInteger = 2 To Range("OptionsConditionalFormat_Table").Rows.Count + 1
        Set rng = wsFormats.Range("a" & iInteger)
        wsWBS.Cells.FormatConditions.Add Type:=xlCellValue, Operator:=xlEqual, Formula1:=rng.Value
        wsWBS.Cells.FormatConditions(wsWBS.Cells.FormatConditions.Count).SetFirstPriority
        With wsWBS.Cells.FormatConditions(1).Font
            .Color = rng.Font.Color
            .ColorIndex = rng.Font.ColorIndex
            .FontStyle = rng.Font.FontStyle
            .Strikethrough = rng.Font.Strikethrough
            .Underline = rng.Font.Underline
            .ThemeColor = rng.Font.ThemeColor
            .TintAndShade = rng.Font.TintAndShade
        End With
        With wsWBS.Cells.FormatConditions(1).Interior
            .PatternColorIndex = rng.Interior.PatternColorIndex
            .Color = rng.Interior.Color
            .TintAndShade = rng.Interior.TintAndShade
        End With
        wsWBS.Cells.FormatConditions(1).StopIfTrue = False
    Next iInteger
    On Error GoTo 0
End Sub

Private Sub drawLines_EastWest()
    With Range(wsWBS.Cells(LineElement1_rowBottom, LineElement1_colLeft), _
               wsWBS.Cells(LineElement1_rowBottom, LineElement1_colRight)).Borders(xlEdgeBottom)
        .LineStyle = xlContinuous
        .ColorIndex = 0
        .TintAndShade = 0
        .Weight = xlThin
    End With
End Sub
Private Function drawLines_NorthSouth(lineType As String, row As Double, col As Double)
    With wsWBS.Cells(row - 1, col + 4).Borders(lineType)
        .LineStyle = xlContinuous
        .ColorIndex = 0
        .TintAndShade = 0
        .Weight = xlThin
    End With
End Function

Private Sub createLegend()
    Prepare
    Dim wsLegend As Worksheet
    Dim row As Integer, col As Integer
    Dim lastRow As Long, lastCol As Long
    Set wsLegend = sheetLegend
    wsLegend.Activate
    Cells.Clear
    
  'create ID and first detail
    wsLegend.Range("a1").Value = "ID"
    wsLegend.Range("b1").Value = "=VLOOKUP(""WBS ID"",WBS_Table[#Headers], 2, false)"
    '=VLOOKUP("WBS ID",WBS_Table[#Headers],2,FALSE)
    wsOptions.Range("b2").Copy
    wsLegend.Range("b1").PasteSpecial Paste:=xlPasteFormats
    wsLegend.Range("a1").Borders.LineStyle = xlContinuous
    
  'created remaining details, except for the last one
    row = 2
    col = 2
    For i = 3 To colCount - 1
    wsLegend.Cells(row, col).Value = "=VLOOKUP(""WBS ID"",WBS_Table[#Headers]," & i & ", false)"
        If col = 2 Then 'column B
            col = 3
        Else            'column C
            col = 2
            row = row + 1
        End If
    Next i
        
  'create last detail, determine if to merge vertical or not
    If lastFieldDisplay = "Horizontal" Then
        wsLegend.Cells(row, col).Value = "=VLOOKUP(""WBS ID"",WBS_Table[#Headers]," & i & ", false)"
        wsLegend.Range("b1:c1").Merge
        
    ElseIf lastFieldDisplay = "Vertical" Then
        wsLegend.Cells(2, 4).Value = "=VLOOKUP(""WBS ID"",WBS_Table[#Headers]," & i & ", false)"
        lastRow = wsLegend.UsedRange.Rows.Count
        wsLegend.Range("d2:d" & lastRow).Select
        With Selection
            .HorizontalAlignment = xlCenter
            .VerticalAlignment = xlBottom
            .WrapText = False
            .Orientation = 90
            .AddIndent = False
            .IndentLevel = 0
            .ShrinkToFit = True
            .ReadingOrder = xlContext
            .MergeCells = True
        End With
        wsLegend.Range("b1:d1").Merge
    End If
    
  'draw borders
    Cells.EntireColumn.AutoFit
    Cells.ShrinkToFit = True
    lastRow = wsLegend.UsedRange.Rows.Count
    lastCol = wsLegend.UsedRange.Columns.Count
    wsLegend.Range(Cells(1, 2), Cells(lastRow, lastCol)).Borders.LineStyle = xlContinuous
    
  'Merge detail if it's on the last row by itself
    If wsLegend.Range("c" & lastRow).Value = "" Then
        wsLegend.Range("b" & lastRow, "c" & lastRow).Merge
    End If
    
  'add instructions
    wsLegend.Range("f1").Value = "<--"
    wsLegend.Range("f2").Value = "This legend shows up on the WBS sheet as a linked image."
    wsLegend.Range("f3").Value = "Changing the WBS Table headers will be reflected here."
    wsLegend.Range("f4").Value = "The image you see here will appear on the linked image."
    wsLegend.Range("f5").Value = "To enable/disable the legend display, go to the WBS Options page."
    wsLegend.Columns("F:F").ColumnWidth = 60
    
  'copy/paste linked image of Legend to WBS sheet
    wsLegend.Range(Cells(1, 1), Cells(lastRow, lastCol)).Copy
    wsWBS.Select
    Range("b2").Select
    ActiveSheet.Pictures.Paste(Link:=True).Select
    Selection.ShapeRange.ScaleWidth 0.8, msoFalse, msoScaleFromTopLeft
    Selection.ShapeRange.ScaleHeight 0.8, msoFalse, msoScaleFromTopLeft
End Sub

'-------------------------------------------------------------------------------
'-------------------------------------------------------------------------------
'----------------------------       SNIFF TEST      ----------------------------
'-------------------------------------------------------------------------------
'-------------------------------------------------------------------------------

Private Sub SniffTest()
    'is Excel in English?
    Dim lng As Long
    lng = Application.LanguageSettings.LanguageID(msoLanguageIDUI)
        Select Case lng
            'English, see https://docs.microsoft.com/en-us/previous-versions/office/developer/office-2007/bb213877(v=office.12)
            Case 1033, 2057
                'do nothing
            
            'not English
            Case Else
                MsgBox "Your default language for Excel is not English, which may or may not result in errors. Proceed at your own risk." & vbNewLine & vbNewLine & _
                  "Please contact me if you 'd like to localize this macro for your language." & vbNewLine & _
                  "For help changing your default language, search the internet for ""excel change default language"" for instructions.", _
                    vbInformation, "Known Issue - Language Settings"
        End Select

    'is WBS ID the first column in the data table
    If tbl.ListColumns(1) <> "WBS ID" Then
        MsgBox "The first column on the " & wsData.Name & " sheet's table must be titled ""WBS ID"".", vbCritical, "Invalid Column"
        wsData.Activate
        Cells(1, 1).Select
        Call CleanUp
    
    'does the wBS ID column only have numbers?
    ElseIf WBS_OnlyNumbers = False Then
        MsgBox "The WBS ID column on the " & wsData.Name & " sheet can only have numbers in the ""0.00"" format." & vbNewLine & vbNewLine & _
        "Replace """ & Left(cell.Value, 15) & """ in cell " & cell.Address(False, False) & " with a valid number.", vbCritical, "Invalid WBS ID"
        wsData.Activate
        Cells(1, 1).Select
        Call CleanUp
        
    'are there duplicates in the WBS column of the data sheet?
    ElseIf FindDuplicatesWBS = True Then
        MsgBox "Remove the " & iDuplicates & " duplicate(s) in the WBS ID column of the " & wsData.Name & " sheet and try again." & _
            vbNewLine & vbNewLine & "Verify you're not mixing up 1.1 and 1.01 and 1.10 (Microsoft Project will mix this up, for example).", _
            vbCritical, "Duplicate WBS IDs"
        wsData.Activate
        Cells(1, 1).Select
        Call CleanUp
        
    'are there any blanks in the WBS column of the data sheet?
    ElseIf findBlanksWBS = True Then
        MsgBox "Remove the " & iBlanks & " blanks in the WBS ID column of the " & wsData.Name & " sheet and try again.", vbCritical, "Blanks in WBS ID Column"
        wsData.Activate
        Cells(1, 1).Select
        Call CleanUp
    
    'does the 0.0 WBS ID exist, to show the project level?
    ElseIf WBS_ZeroExists = False Then
        MsgBox "Add the 0.0 WBS ID to the table in the " & wsData.Name & " sheet to show the project level status.", vbCritical, "Missing Project WBS ID"
        wsData.Activate
        Cells(1, 1).Select
        Call CleanUp
    
    'do decimals have supporting intergers, i.e., does 1.1 have 1.0?
    ElseIf doDecimalsHaveSupportingIntegers = False And iInteger <> 0 Then
        MsgBox "Include WBS ID """ & iInteger & ".00"" in the " & wsData.Name & " sheet to maintain the hierarchical relationships of the WBS.", vbCritical, "Missing Level 2 WBS ID"
        wsData.Activate
        Cells(1, 1).Select
        Call CleanUp
        
    ElseIf doNumbersExistBetweenZeroAndOne = True Then
        MsgBox "Remove all WBS ID's between 0 and 1, i.e., 0.xx."
        wsData.Activate
        Cells(1, 1).Select
        Call CleanUp
    
    'are there blanks in row 2 of the data table?
    ElseIf doBlanksExist_DataTable_FirstDataRow = True Then
        MsgBox "There are blanks in row 2 of the " & wsData.Name & " sheet." & vbNewLine & vbNewLine & _
            "Fill in all blanks and format the cells how they'll appear on the " & wsWBS.Name & " sheet.", _
            vbCritical, "Data Table Has Blanks"
        wsData.Activate
        Cells(1, 1).Select
        Call CleanUp
    End If
    
    'CHECK THE OPTIONS PAGE
    'Valid number for Column Width
    If wsOptions.Range("b6").Value < 1 Or wsOptions.Range("b6").Value > 10 Then
        wsOptions.Activate
        wsOptions.Range("b6").Select
        MsgBox "Enter a value between 1 and 10 in cell B6 of the " & wsOptions.Name & " sheet and try again.", _
            vbCritical, "Invalid Entry"
        Call CleanUp
    End If
    
    'Vertical or Horizontal value for Make last column…
    If wsOptions.Range("b7").Value = "Horizontal" Or wsOptions.Range("b7").Value = "Vertical" Then
        'do nothing
    Else
        wsOptions.Activate
        wsOptions.Range("b7").Select
        MsgBox "Enter Horizontal or Vertical in cell B7 of the " & wsOptions.Name & " sheet and try again.", _
            vbCritical, "Invalid Entry"
        Call CleanUp
    End If
    
    'True or false value for Display Excel headings
    If wsOptions.Range("b8").Value = True Or wsOptions.Range("b8").Value = False Then
        'do nothing
        
    Else
        wsOptions.Activate
        wsOptions.Range("b8").Select
        MsgBox "Enter TRUE or FALSE in cell B8 of the " & wsOptions.Name & " sheet and try again.", _
            vbCritical, "Invalid Entry"
        Call CleanUp
    End If
    
    'true or false value for Display Lines
    If wsOptions.Range("b9").Value = True Or wsOptions.Range("B9").Value = False Then
        'do nothing
    Else
        wsOptions.Activate
        wsOptions.Range("b9").Select
        MsgBox "Enter TRUE or FALSE in cell B9 of the " & wsOptions.Name & " sheet and try again.", _
            vbCritical, "Invalid Entry"
        Call CleanUp
    End If
    
End Sub
Private Function doNumbersExistBetweenZeroAndOne()
    For Each cell In WBScolumn
        If cell.Value > 0 And cell.Value < 1 Then
            doNumbersExistBetweenZeroAndOne = True
            cell.Interior.ColorIndex = 3
            Exit Function
        End If
    Next cell
End Function
Private Function doBlanksExist_DataTable_FirstDataRow() As Boolean
    Dim i As Integer
    i = 1
    For i = 1 To tbl.ListColumns.Count
        If IsEmpty(tbl.DataBodyRange.Cells(1, i)) = True Then
            doBlanksExist_DataTable_FirstDataRow = True
        End If
    Next i
End Function

Private Function doDecimalsHaveSupportingIntegers() As Boolean
    For Each cell In WBScolumn
        If Right(Format(cell, "0.00"), 2) > 0 Then 'number to right of decimal > 0
            If findText(Int(cell.Value)) = False Then
                doDecimalsHaveSupportingIntegers = False
                iInteger = Int(cell.Value)
                cell.Interior.ColorIndex = 3
                Exit Function
            End If
        End If
    Next cell
End Function

Private Function findText(searchFor As String) As Boolean
    Set rng = Cells.Find(What:=searchFor, After:=ActiveCell, LookIn:=xlFormulas, LookAt _
            :=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:= _
            False, SearchFormat:=False)
    If rng Is Nothing Then
        findText = False
    Else
        findText = True
    End If
    Set rng = Nothing
End Function

Private Function FindDuplicatesWBS() As Boolean
    wsData.Activate
    For Each cell In WBScolumn
        If WorksheetFunction.CountIf(WBScolumn, cell.Value) > 1 Then
            cell.Interior.ColorIndex = 3
            iDuplicates = iDuplicates + 1
            FindDuplicatesWBS = True
        End If
    Next
    iDuplicates = iDuplicates / 2
End Function

Private Function findBlanksWBS() As Boolean
    wsData.Activate
    For Each cell In WBScolumn
        If Trim(cell.Value) = "" Then
            cell.Interior.ColorIndex = 3
            iBlanks = iBlanks + 1
        End If
    Next cell
    If iBlanks > 0 Then findBlanksWBS = True
End Function

Private Function WBS_ZeroExists() As Boolean
    wsData.Activate
    Dim zeroDotZero As Long
    zeroDotZero = 0#
    For Each cell In WBScolumn
        If cell.Value = 0 Then WBS_ZeroExists = True
        Exit Function
    Next cell
End Function
Private Function WBS_OnlyNumbers() As Boolean
    For Each cell In WBScolumn
        If IsNumeric(cell) = False Then
            WBS_OnlyNumbers = False
            Exit Function
        Else
            WBS_OnlyNumbers = True
        End If
    Next cell
End Function

Private Sub highlightAllBlanksInDataTable()
    Range(tbl).Select
    Selection.FormatConditions.Add Type:=xlExpression, Formula1:= _
        "=LEN(TRIM(A2))=0"
    Selection.FormatConditions(Selection.FormatConditions.Count).SetFirstPriority
    With Selection.FormatConditions(1).Interior
        .PatternColorIndex = xlAutomatic
        .Color = 65535
        .TintAndShade = 0
    End With
    Selection.FormatConditions(1).StopIfTrue = False
End Sub

'-------------------------------------------------------------------------------
'-------------------------------------------------------------------------------
'----------------------------       CLEAN UP        ----------------------------
'-------------------------------------------------------------------------------
'-------------------------------------------------------------------------------

Private Sub CleanUp(Optional par1 As String, Optional par2 As String)
    On Error Resume Next
    Set tbl = Nothing
    Set wsWBS = Nothing
    Set wsData = Nothing
    Set wsOptions = Nothing
    Set WBScolumn = Nothing
    Set WBS_AddressIDsRng = Nothing
    Set rng = Nothing
    Application.StatusBar = False
    Application.Calculation = xlCalculationAutomatic
    Application.ScreenUpdating = True

    Select Case Err.Number
      Case 0 'do nothing
      Case Else
        MsgBox Err.Number & " - " & Err.Description & vbNewLine & vbNewLine & "Please report errors to https://sourceforge.net/u/jerome-smith01/profile/", _
        vbCritical, "Unknown Error"
    End Select
    End
End Sub
Source: README.txt, updated 2019-07-13