VarWriter
Writes a single dictionary variable onto one or more linelist worksheets. On an HList layer the writer targets the main sheet, a printed companion and a CRF companion; on a VList layer it writes the main sheet alone. Each call to WriteVariable applies the label text, the number format, the formula, the dropdown choices, the data validation and the conditional formatting to the cell range it resolves. ValueOf reads one dictionary column of the variable in hand, and entries filed along the way leave through HasCheckings and CheckingValues.
ONE WRITER PER SHEET
The writer is built once per data entry sheet and used for every variable of
that sheet. SectionBuilder.Build owns it. What the class holds -- the
dictionary block, the column map, the hidden name store, the design format and
the translated messages -- covers a whole sheet, and the table anchor is set
on the first variable the writer is handed.
HOW A DICTIONARY VALUE IS READ
The dictionary data block and its header row are read once, as two worksheet
crossings, and every ValueOf after that answers from memory. A column added
to the dictionary after the map was built is picked up on the first miss, the
same way LLVariables.ResolveColumnIndex re-reads its header snapshot.
A variable name is matched without regard to case here. LLdictionary.Prepare
makes the names unique, so a name that differs only by case is a fault in the
sheet.
Depends on: LinelistSpecs, LLdictionary, LLVariables, LLSheets, LLFormat, TranslationObject, FormulaData, Formulas, DropdownLists, LLGeo, HiddenNames, Checking, BetterArray, Column index the first variable of a sheet carries when the dictionary is, numbered as LLdictionary.Prepare leaves it. It is the fallback for the table, anchor; the first variable the writer is handed is the primary rule., What an automatic list adds to the name of the variable it collects from, so, its table never lands on the one that variable already owns. An origin very, often has a choice list of its own, and the two used to share a single table:, the first rebuild of the automatic list then replaced the setup choices with, whatever had been typed in the column. EventLinelist carries the same suffix, and rebuilds the table under this name., Checking support, The milestone record of the sheet: one entry per variable written. It is, held apart from internalChecks because the two travel to different places., internalChecks carries the problems and reaches the __check worksheet;, this one reaches the run log's in-memory record and the text file alone.
Version: 1.1 (2026-07-31)
Instantiation
Create #
create
Create a new VarWriter for the given layer and dependencies
Signature:
Public Function Create(ByVal layer As Byte, _
ByVal specs As LinelistSpecs, _
ByVal wksh As Worksheet, _
Optional ByVal printWksh As Worksheet = Nothing, _
Optional ByVal crfWksh As Worksheet = Nothing, _
Optional ByVal dropdownObj As DropdownLists = Nothing, _
Optional ByVal customDropdownObj As DropdownLists = Nothing) As VarWriter
Factory method that instantiates and configures a VarWriter. The caller must supply a valid LinelistSpecs and target Worksheet at minimum. Optional parameters allow attaching printed and CRF companion worksheets (used only for HList layers) and dropdown list managers.
Parameters:
layer: Byte. VarWriterLayer enum value (HList=1, VList=2). Use Byte for macOS compatibility.specs: LinelistSpecs. The linelist specifications object providing dictionary, format, translations, etc.wksh: Worksheet. The target main worksheet.printWksh: Optional Worksheet. Printed companion worksheet (HList only). Defaults to Nothing.crfWksh: Optional Worksheet. CRF companion worksheet (HList only). Defaults to Nothing.dropdownObj: Optional DropdownLists. Dropdown lists manager. Defaults to Nothing.customDropdownObj: Optional DropdownLists. Custom dropdown lists manager (for choice_custom). Defaults to Nothing.
Returns: VarWriter. A fully initialised writer bound to the given layer and worksheets.
Throws:
- ProjectError.ObjectNotInitialized When specs or wksh is Nothing.
- ProjectError.InvalidArgument When layer names neither VarWriterLayer member.
Entry Point
WriteVariable #
write-variable
Write one variable to the target worksheet(s)
Signature:
Public Sub WriteVariable(ByVal varName As String)
Main public method called once per variable. It stores the variable name, resets the cached row and cell ranges, then dispatches to WriteMainSheet. For HList layers it additionally dispatches to WritePrintSheet and WriteCRFSheet for the companion worksheets. A variable the dictionary gives no column index is skipped and filed through Checking, because the cell it would be written to cannot be resolved.
Parameters:
varName: String. The variable name as it appears in the dictionary.
Checkings
HasMilestones #
has-milestones
Whether any variable of this sheet was written
Signature:
Public Property Get HasMilestones() As Boolean
Returns: Boolean. True once one milestone entry has been filed.
MilestoneValues #
milestone-values
The per-variable record of this sheet
Signature:
Public Property Get MilestoneValues() As Checking
The store travels up through SectionBuilder and LLDataEntry to the
build driver, which takes it into the run log record-only. It never
reaches the __check worksheet.
Returns: Checking. The milestone entries, or Nothing.
VariablesWritten #
variables-written
How many variables this writer put on the sheet
Signature:
Public Property Get VariablesWritten() As Long
Counts the variables that reached the worksheet. A variable the dictionary gives no column index is filed as a problem and left out of the count.
Returns: Long. The number of variables written.
Internal members (not exported)
Instance Sealing
Seal #
seal
Seal the instance against further setup writes
Signature:
Public Sub Seal()
GuardNotSealed #
guard-not-sealed
Guard a setup-only setter against post-seal writes
Signature:
Private Sub GuardNotSealed(ByVal propName As String)
Parameters:
propName: String. The property name for the error message.
Internal Setters
InternalSpecs #
internal-specs
Assign the linelist specifications object
Signature:
Public Property Set InternalSpecs(ByVal specs As LinelistSpecs)
Properties used by the factory only during instantiation.
InternalWksh #
internal-wksh
Assign the target main worksheet
Signature:
Public Property Set InternalWksh(ByVal wksh As Worksheet)
InternalLayer #
internal-layer
Assign the writer layer (HList or VList)
Signature:
Public Property Let InternalLayer(ByVal layer As Byte)
InternalPrintWksh #
internal-print-wksh
Assign the printed companion worksheet
Signature:
Public Property Set InternalPrintWksh(ByVal wksh As Worksheet)
InternalCRFWksh #
internal-crf-wksh
Assign the CRF companion worksheet
Signature:
Public Property Set InternalCRFWksh(ByVal wksh As Worksheet)
InternalDropdown #
internal-dropdown
Assign the dropdown lists manager
Signature:
Public Property Set InternalDropdown(ByVal dropdownObj As DropdownLists)
InternalCustomDropdown #
internal-custom-dropdown
Assign the custom dropdown lists manager
Signature:
Public Property Set InternalCustomDropdown(ByVal dropdownObj As DropdownLists)
Cached Accessors
Dictionary #
dictionary
Return the cached dictionary object
Signature:
Private Property Get Dictionary() As LLdictionary
Every collaborator the writer uses is resolved once and held. Each of these is
reached several times per variable, so a pass-through to this.specs on every
use was paid a few hundred times per sheet.
Returns: LLdictionary. The linelist dictionary.
Variables #
variables
Return the cached variables helper
Signature:
Private Property Get Variables() As LLVariables
Kept for Contains, which the two conditional formatting routines ask for a
variable that is not the one being written. Reading a column of the CURRENT
variable goes through ValueOf, which answers from the cached dictionary
block.
Returns: LLVariables. The variables helper object.
SheetsObject #
sheets-object
Return the cached sheets helper
Signature:
Private Property Get SheetsObject() As LLSheets
Provides VariableAddress and SheetInfo lookups used when building conditional-formatting formulas and resolving table anchors.
Returns: LLSheets. The sheets helper object.
DesignFormat #
design-format
Return the design format object from specs
Signature:
Private Property Get DesignFormat() As LLFormat
AddLabel, AddFormula, AddChoices, AddCRFLabel, AddCRFType and
AddCRFChoices each ask for it, so it is resolved once per writer.
Returns: LLFormat. The design format provider.
Trans #
trans
Return the translation object from specs
Signature:
Private Property Get Trans() As TranslationObject
Returns: TranslationObject. The translation provider.
FormulaDataObj #
formula-data-obj
Return the formula data object from specs
Signature:
Private Property Get FormulaDataObj() As FormulaData
Provides the lookup tables and metadata needed by the Formulas parser.
Returns: FormulaData. The formula data provider.
EnsureMessages #
ensure-messages
Read the three translated label messages once
Signature:
Private Sub EnsureMessages()
MSG_Calculated, MSG_Mandatory and MSG_CustomChoice are constant for a
whole build, and AddLabel used to fetch all three per variable and twice per
HList variable.
Dictionary Row Cache
EnsureDataMap #
ensure-data-map
Read the dictionary block and build the two lookups
Signature:
Private Sub EnsureDataMap()
The dictionary block and its header row are read once per writer. Every
ValueOf after that is an array read.
Two crossings: the data block and the header row. From them come a header to
column-offset map and a variable name to row-offset map. Both keep the first
entry when a name repeats, which is the rule LLVariables.HeaderColumn and
LLVariables.RowIndex already apply.
RebuildDataMap #
rebuild-data-map
Read the dictionary block again
Signature:
Private Sub RebuildDataMap()
Called on the first use and again whenever a column name misses, which is how a column added to the dictionary after the writer was built resolves.
DictionaryGrewAColumn #
dictionary-grew-a-column
Test whether the dictionary is wider than the map was built from
Signature:
Private Function DictionaryGrewAColumn() As Boolean
One property read against a whole block read. AddLabel asks for list auto
on every variable, and a dictionary that has not been through
LinelistSpecs.Prepare carries no such column, so a rebuild on every miss made
each variable pay for a full re-read of the dictionary and two lookup rebuilds
for an answer that was always empty.
Returns: Boolean. True when the header row is wider than it was.
ColumnOffset #
column-offset
Offset of a dictionary column inside a data row
Signature:
Private Function ColumnOffset(ByVal colName As String) As Long
Whole string, without regard to case, which is what LLVariables.HeaderColumn
answers. A Collection raises on a key it does not hold, and that is how a miss
is read.
Parameters:
colName: String. The dictionary column header.
Returns: Long. The 1-based offset, or 0 when the column is absent.
RowOffset #
row-offset
Row of one variable inside the dictionary data block
Signature:
Private Function RowOffset(ByVal varName As String) As Long
Parameters:
varName: String. The variable name.
Returns: Long. The 1-based row, or 0 when the variable is absent.
EnsureRowValues #
ensure-row-values
Hold the current variable's dictionary row in memory
Signature:
Private Sub EnsureRowValues()
BlockValues #
block-values
Read a Range as a two-dimensional 1-based array
Signature:
Private Function BlockValues(ByVal blockRng As Range) As Variant
Range.Value hands back a plain value for one cell and a two-dimensional
array otherwise. This flattens both cases so callers see one shape.
Parameters:
blockRng: Range. The block to read.
Returns: Variant. A 1-based two-dimensional array of cell values.
RowOfValues #
row-of-values
Read a one-row Range as a 1-based list of values
Signature:
Private Function RowOfValues(ByVal rowRng As Range) As Variant
Parameters:
rowRng: Range. A single-row block.
Returns: Variant. A 1-based one-dimensional array of cell values.
CellText #
cell-text
Read one cell value as text
Signature:
Private Function CellText(ByVal cellValue As Variant) As String
A dictionary cell can hold an error value or a Null, and CStr raises on
both. Every read of the cached block goes through here, the same way every
read in LLVariables goes through its own copy.
Parameters:
cellValue: Variant. The raw cell value.
Returns: String. The text, or an empty string when there is none.
Value Lookup
ValueOf #
value-of
Read a dictionary column value for the current variable
Signature:
Public Property Get ValueOf(ByVal colName As String) As String
The class's read hook, and the way every other method retrieves variable
metadata such as labels, types, formats and control information. An unknown
column and an unknown variable both answer an empty string, which is what
LLVariables.Value answers for the same two cases.
Parameters:
colName: String. The dictionary column name to look up.
Returns: String. The column value for the current variable.
NumberOf #
number-of
Read a dictionary column value as a whole number
Signature:
Private Function NumberOf(ByVal colName As String) As Long
column index and crf index were read with a bare CLng(ValueOf(...)) at
eight sites, guarded at three of them and unguarded at the rest. This is the
one rule: a value that is empty or is not a number answers 0, and every caller
tests for 0.
Parameters:
colName: String. The dictionary column name to look up.
Returns: Long. The stored number, or 0.
Cell Range Resolution
VarRange #
var-range
Resolve the main cell range for the current variable
Signature:
Private Property Get VarRange() As Range
Computes the target cell from the column index dictionary value and the layer-specific start position. For HList the cell is in a column at HLIST_START_ROW + 1; for VList it is in a row at VLIST_START_COL. The result is cached until WriteVariable resets it for the next variable.
Returns: Range. The main cell range, or Nothing when the index is missing.
VarPrintRange #
var-print-range
Resolve the printed companion cell range (HList only)
Signature:
Private Property Get VarPrintRange() As Range
Returns the cell on the printed companion worksheet that corresponds to the current variable. Uses the same column index as VarRange but targets the printWksh. Returns Nothing when no printed worksheet is attached.
Returns: Range. The printed companion cell range, or Nothing.
VarCRFRange #
var-crf-range
Resolve the CRF companion cell range (HList only)
Signature:
Private Property Get VarCRFRange() As Range
Returns the cell on the CRF companion worksheet at the row specified by the "crf index" dictionary value. Returns Nothing when no CRF worksheet is attached or when the crf index is zero or missing.
Returns: Range. The CRF companion cell range, or Nothing.
Sheet Dispatch
WriteMainSheet #
write-main-sheet
Dispatch all writing steps for the main worksheet
Signature:
Private Sub WriteMainSheet()
Orchestrates the sequence of writing operations on the main worksheet: label, formula, type formatting, choices, validation, and conditional formatting. The exact set of conditional formatting steps depends on the active layer (HList applies geo, standard, and unique formatting; VList applies standard formatting only).
WritePrintSheet #
write-print-sheet
Write variable label to the printed companion sheet (HList only)
Signature:
Private Sub WritePrintSheet()
Delegates to AddLabel with usePrinted=True so that the label is written to the printed companion worksheet instead of the main sheet. Exits silently when no printed worksheet is attached.
WriteCRFSheet #
write-crf-sheet
Write variable data to the CRF companion sheet (HList only)
Signature:
Private Sub WriteCRFSheet()
Resolves the CRF cell range and delegates to AddCRFLabel, AddCRFType, and AddCRFChoices. Exits silently when no CRF worksheet is attached or when the variable has no CRF index.
Label Writing
AddLabel #
add-label
Write variable label, sublabel, name, notes, and formatting
Signature:
Private Sub AddLabel(Optional ByVal usePrinted As Boolean = False)
Writes the main label, sublabel, variable name, notes, control info, and design formatting to the target cell range. For HList layers this includes header cells, named ranges for table anchors, comments for notes, hidden control rows, geo-specific formatting, editable labels, and column hiding. For VList layers the layout is row-based with adjacent cells for control info and label formatting.
Parameters:
usePrinted: Optional Boolean. When True, writes to the printed companion range instead of the main range. Defaults to False.
Type Formatting
AddType #
add-type
Apply Excel number format based on variable type and format
Signature:
Private Sub AddType()
Reads the variable type (text, date, decimal, integer) and optional
format string from the dictionary, then sets the NumberFormat property
on the main cell range. Supports built-in formats such as round, percentage,
euros, and dollars as well as custom format strings. A numeric variable whose
format reads text keeps the number format it already has: Excel refuses an
empty NumberFormat, and this method sits outside every suppression in
WriteMainSheet, so assigning one used to stop the sheet.
Formula Application
AddFormula #
add-formula
Parse and apply a formula to the variable cell
Signature:
Private Sub AddFormula()
Handles formula, case_when, and choice_formula control types. Parses the control details string through the Formulas class, validates the result, and writes the Excel formula to the cell.
A formula the parser rejects leaves the cell alone. The calculated-cell
styling and the lock both sit inside the same guard, so a cell that reads as
read-only is read-only, and the reason the parser gave is filed through
Checking. Before this the styling was applied whatever the parser answered,
while AddLabel had already unlocked the cell -- so the user saw a greyed
calculated cell they could type into and the value went into the export.
NeverPrinted #
never-printed
Whether a control has nothing to give on a printed sheet
Signature:
Private Function NeverPrinted(ByVal controlValue As String) As Boolean
A calculated column reads cells that nobody fills in on paper, and an
automatic list holds only values that have already been typed somewhere else.
Neither belongs on a register book, whatever the register book column says
about it, and the same four names decide the CRF: LLdictionary.AddVisibility
writes always hidden into crf status for them, and
ShowHide.IsLockedStatus holds them hidden on both layers once a linelist is
running. The three lists have to be changed together.
Parameters:
controlValue: String. Thecontrolcolumn of the variable.
Returns: Boolean. True when the column is hidden on a printed sheet.
Choice Setup
AddChoices #
add-choices
Set up dropdown validation for choice, list, and geo variables
Signature:
Private Sub AddChoices()
Handles choice_manual, choice_multiple, choice_custom, list_auto, and geo1 control types. For each type it populates a BetterArray of categories, registers them with the appropriate DropdownLists manager, and calls SetValidation to apply dropdown data validation to the cell. The choice_custom branch additionally creates a hyperlink from the label cell to the dropdown worksheet and adds a return link. The geo1 branch sets up admin1 through admin4 dropdown cascades.
The two dropdown managers are optional at creation, so a writer built without the one a control type needs files a checking and leaves the variable without its dropdown. The same holds for a geo variable in a linelist with no geography.
Data Validation
AddValidation #
add-validation
Add min/max data validation to the variable cell
Signature:
Private Sub AddValidation()
Reads min and max values from the dictionary, converts them to Excel validation formulas via the ExcelFormula helper, and applies whole-number, date, or decimal validation with the configured alert style. Both OS-local and standard formula variants are attempted, which works here because Validation.Add raises on a range that already carries one, so the first success sticks. Text variables are skipped.
Conditional Formatting
AddConditionalFormatting #
add-conditional-formatting
Add expression-based conditional formatting
Signature:
Private Sub AddConditionalFormatting()
Reads the formatting condition variable name from the dictionary, resolves its address, and adds an xlExpression format condition that highlights the cell when the condition variable equals 1. The interior colour, font colour, bold, and italic properties are copied from the formatting values cell in the dictionary.
AddUniqueConditionalFormatting #
add-unique-conditional-formatting
Add unique/duplicate conditional formatting (HList only)
Signature:
Private Sub AddUniqueConditionalFormatting()
When the dictionary marks a variable with unique=yes, adds a duplicate- values format condition that highlights duplicate entries with a red interior. This is typically used for patient IDs or other fields that must be unique across records.
AddGeoConditionalFormatting #
add-geo-conditional-formatting
Add geo pcode conditional formatting (HList only)
Signature:
Private Sub AddGeoConditionalFormatting()
For geo-type variables, adds a format condition that highlights the cell in orange when the corresponding pcode variable is empty but the geo variable itself is not. This alerts users that a geographic name was entered but the pcode lookup failed.
AddOneCondition #
add-one-condition
Give a cell one expression format condition
Signature:
Private Function AddOneCondition(ByVal cellRng As Range, _
ByVal osFormula As String, _
ByVal standardFormula As String) As Boolean
The OS-local formula is tried first and the standard one is used only when the first left the count where it was.
FormatConditions.Add appends whatever it is given, so the two calls this
replaces left two conditions on every cell that took both formulas, and only
the last of them was given a colour, SetFirstPriority and StopIfTrue. The
unstyled one then travelled down the ListObject column as rows were added and
into every exported copy. Validation.Add behaves the other way -- it raises
on a range that already carries one -- which is why AddValidation keeps the
two-call shape.
Parameters:
cellRng: Range. The cell to give the condition to.osFormula: String. The OS-local formula, tried first.standardFormula: String. The standard formula, tried only if the first did not take.
Returns: Boolean. True when the cell carries a new condition.
CRF Companion
AddCRFLabel #
add-crf-label
Write CRF label for the current variable
Signature:
Private Sub AddCRFLabel(ByVal crfRng As Range)
Writes the main label and sublabel to the CRF row, applies main-label and sublabel character formatting, and hides the row for formula-based or hidden variables. Also sets the table anchor named range on the first CRF row.
Parameters:
crfRng: Range. The target cell on the CRF companion worksheet.
AddCRFType #
add-crf-type
Apply CRF-specific type formatting
Signature:
Private Sub AddCRFType(ByVal crfRng As Range)
Maps the variable type and format to a CRF design scope constant (such as CRFText, CRFDate, CRFDecimal) and applies the corresponding visual format to the CRF cell. Skips choice_manual variables because their formatting is handled by AddCRFChoices instead.
Parameters:
crfRng: Range. The target cell on the CRF companion worksheet.
AddCRFChoices #
add-crf-choices
Write CRF choice headers and value cells
Signature:
Private Sub AddCRFChoices(ByVal crfRng As Range)
For choice_manual variables, retrieves the short-label categories from specs, writes optional column headers above the CRF row, and applies CRFChoiceValues formatting to each value cell. The headers are only written when the "crf choices" dictionary value is "yes".
Parameters:
crfRng: Range. The target cell on the CRF companion worksheet.
Formula Helpers
ScratchRange #
scratch-range
The cell a formula is tried on
Signature:
Private Property Get ScratchRange() As Range
FailedFormula and OSFormula both need a cell to write a formula into and
read back. It is A1 of the target worksheet, and both put back what they found.
WHY A1 AND NOT A CELL OUT OF THE WAY
A far cell looks safer and it is not: writing to the last column stretches the
worksheet's used range across all 16,384 columns, and from then on every
EntireColumn, every merge and every full-column read on that sheet works over
that range. A sheet build that took a minute did not finish in nine. The cost
is Excel's bookkeeping, so it does not show up in the code that pays it.
What was actually wrong with A1 was FailedFormula reaching it with .Clear,
which takes the formatting as well as the contents. Both helpers save the
formula and put it back now.
Returns: Range. The scratch cell.
FailedFormula #
failed-formula
Test if a formula fails when applied to a cell
Signature:
Private Function FailedFormula(ByVal frmName As String) As Boolean
Writes the formula to the scratch cell as a test. If the assignment raises an error the function returns True, indicating the formula is invalid. The cell contents are cleared after the test whatever the outcome, and Application.DisplayAlerts is suppressed for the duration.
Parameters:
frmName: String. The Excel formula expression to test (without leading equals sign).
Returns: Boolean. True if the formula raised an error, False otherwise.
ExcelFormula #
excel-formula
Convert a formula value to an Excel-compatible format for validation
Signature:
Private Function ExcelFormula(ByVal formVal As String, _
ByVal validationTypes As Long, _
Optional ByVal useOS As Boolean = False) As Variant
Parses the given formula string through the Formulas class, optionally
converts it to an OS-local formula via OSFormula, and then converts the
result to the type the validation asks for. A whole number takes CLng and a
decimal takes CDbl: CInt overflowed above 32,767, which is a legitimate
bound for a count or an ID, and CLng on a decimal turned a maximum of 0.5
into 0, so the validation rejected every value the dictionary meant to allow.
Returns Empty when the formula is invalid, fails the FailedFormula test, or
will not convert -- and the last of those is filed through Checking, because
the caller runs under a suppression and would otherwise drop the validation
with no message.
Parameters:
formVal: String. The raw formula value from the dictionary (e.g. a min or max expression).validationTypes: Long. The Excel validation type constant (xlValidateWholeNumber, xlValidateDate, or xlValidateDecimal).useOS: Optional Boolean. When True, converts the formula to OS-local format via OSFormula. Defaults to False.
Returns: Variant. The converted formula value, or Empty if invalid.
ConvertedBound #
converted-bound
Read a bound as the type the validation asks for
Signature:
Private Function ConvertedBound(ByVal boundText As String, _
ByVal validationTypes As Long) As Variant
Answers Empty when the text will not convert, which is how the caller knows to
hand the bound over as formula text instead. A whole number takes CLng and a
decimal takes the separator-independent read: CInt overflowed above 32,767,
which is a legitimate bound for a count or an ID, and CLng on a decimal
turned a maximum of 0.5 into 0, so the validation rejected every value the
dictionary meant to allow.
Parameters:
boundText: String. The parsed bound.validationTypes: Long. The Excel validation type constant.
Returns: Variant. The converted bound, or Empty.
AsNumber #
as-number
Read a bound as a number whatever decimal separator it carries
Signature:
Private Function AsNumber(ByVal numberText As String) As Double
CDbl reads the separator of the host, and a dictionary is authored on
whatever machine the user has. A bound typed as 0.5 on one and read back on
a comma machine is text CDbl refuses, and a bound of 0,5 is text a dot
machine refuses. The separator is taken from the host and both are mapped onto
it.
Val was the other candidate and it answers 0 for 0,5, which is the same
silent truncation this method exists to stop.
Parameters:
numberText: String. The bound as the dictionary carries it.
Returns: Double. The number.
OSFormula #
os-formula
Convert a formula to OS-specific local format
Signature:
Private Function OSFormula(ByVal formVal As String) As String
On macOS with VList layers, returns the formula as-is because FormulaLocal is unreliable. On Windows or for HList layers, temporarily writes the formula to the scratch cell, reads back FormulaLocal, and restores the previous cell content. This ensures validation formulas use the correct locale-aware separator characters.
The Mac carve-out covers VList alone and the HList branch calls FormulaLocal whatever the machine is. If FormulaLocal is unreliable on Mac for one layer it is unreliable for the other, and that is still open.
Parameters:
formVal: String. The Excel formula expression to convert.
Returns: String. The OS-local formula string.
Error Handling
ThrowError #
throw-error
Raise an error with the class name as source
Signature:
Private Sub ThrowError(ByVal errNumber As Long, ByVal errorMessage As String)
Raises the number it is given with the class name as the source. Every guard
in this class names a ProjectError member.
Parameters:
errNumber: Long. The ProjectError value to raise.errorMessage: String. The error description to include in the raised error.
Checkings
LogCheck #
log-check
File one entry against the current variable
Signature:
Private Sub LogCheck(ByVal label As String, Optional ByVal scope As Byte = checkingWarning)
What internalChecks files is a problem: a variable it could not place, a
formula the parser rejected, a validation bound that would not convert, a
dropdown manager it was not given. That store reaches the __check worksheet
through SectionBuilder and LLDataEntry.
THE MILESTONE STORE IS SEPARATE
milestoneChecks holds one entry per variable written, keyed by the variable
name, and it travels to the run log's in-memory record and the text file
alone. The two stores are kept apart because of what the worksheet costs:
CheckingOutput ends every row it writes with an EntireColumn.AutoFit, so
a 500-variable linelist put 500 autofits into the build when these entries
went to the worksheet. That is why an earlier per-variable entry filed into
internalChecks was removed; the record keeps the trace and the worksheet
keeps the problems.
KEYS ARE UNIQUE ACROSS A BUILD
Checking.Add raises on a duplicate key. Every entry filed here is keyed by
the worksheet and the variable, so a second subsection, a second sheet and a
second writer can all be merged into one Checking. The counter this replaces
started at 0 in every instance, so the second subsection of a sheet re-added
key "0" and stopped the build.
Collaborator entries are re-keyed on the way in for the same reason:
Formulas also keys with a counter from 0, and AddFormula builds a fresh
Formulas per variable.
Parameters:
label: String. The message for the report.scope: Optional Byte. Severity. Defaults to checkingWarning.
HarvestCollaboratorCheckings #
harvest-collaborator-checkings
Merge another object's entries into this writer's store
Signature:
Private Sub HarvestCollaboratorCheckings(ByVal collaboratorChecks As Checking)
The entries are re-keyed under this variable rather than appended, so two
collaborators that both key from 0 do not collide. The severity is read back
through the type label Checking prints, which is the only way it is exposed.
Parameters:
collaboratorChecks: Checking. The source store.
EnsureChecking #
ensure-checking
Build the internal store on first use
Signature:
Private Sub EnsureChecking()
LogMilestone #
log-milestone
File the milestone of the variable just written
Signature:
Private Sub LogMilestone()
Keyed by the variable name, which the dictionary keeps unique inside one
sheet, and the store is per sheet because the writer is. A name that
repeats all the same is numbered rather than raising, since
Checking.Add refuses a duplicate key and a milestone must never stop a
build.
EntryKeyRoot #
entry-key-root
The key an entry of the current variable starts from
Signature:
Private Function EntryKeyRoot() As String
Returns: String. Worksheet name, then the variable name.
UniqueKey #
unique-key
Answer a key the internal store does not hold yet
Signature:
Private Function UniqueKey(ByVal keyRoot As String) As String
A variable can file more than one entry -- a rejected formula and a validation bound that would not convert, for instance -- so the root is numbered when it is already taken.
Parameters:
keyRoot: String. The key to start from.
Returns: String. A key that is free.
UniqueKeyIn #
unique-key-in
Answer a key one given store does not hold yet
Signature:
Private Function UniqueKeyIn(ByVal store As Checking, ByVal keyRoot As String) As String
The writer keeps two stores -- the problems and the milestones -- and each needs its own free key.
Parameters:
store: Checking. The store the key has to be free in.keyRoot: String. The key to start from.
Returns: String. A key that is free.
ScopeOf #
scope-of
Read the severity of one entry of another checking
Signature:
Private Function ScopeOf(ByVal source As Checking, ByVal keyName As String) As Byte
Checking exposes a scope as the decorated type label it prints in the
report, so the byte is recovered from that text. Each of the five labels
carries its own word after the icon, and matching on the word keeps this free
of the icon code points.
Parameters:
source: Checking. The store holding the entry.keyName: String. The entry key.
Returns: Byte. A CheckingScope member.
Used in (20 file(s))
- CrossTable.cls
- LLdictionary.cls
- LLSheets.cls
- Formulas.cls
- HiddenNames.cls
- LLGeo.cls
- LLSpatial.cls
- EventLinelist.cls
- LLDataEntry.cls
- SectionBuilder.cls
- ShowHideLayout.cls
- EventsLinelistButtons.bas
- TestLLSheetsExtra.bas
- TestLLGeo.bas
- TestLLSpatial.bas
- TestEventLinelist.bas
- TestEventLinelistSheets.bas
- TestLLDataEntry.bas
- TestSectionBuilder.bas
- TestVarWriter.bas