LLExport

Manages the export specification sheet, the table where each row defines one export of a linelist. Create binds the worksheet through a DataSheet. NumberOfExports counts the rows, IsActive says whether an export is switched on, ActiveExportNumbers lists the ones that are, and ColumnValue reads any column of one row. ManageRows, InsertRows, DeleteRows, AddRows and RemoveRows work on the rows, Sort orders the table, Translate rewrites the two translated columns, and ImportSpecs and ExportSpecs move the sheet between workbooks. SyncDictionaryExports keeps the export columns of the dictionary in step with this table.

THE FILE NAME OF AN EXPORT

ExportFileName builds the name one export is saved under. The template comes from the "file name" column, and its chunks are replaced from the dictionary. A version suffix read from Passwords and a timestamp are added at the end, and the whole name is cleaned of the characters a file name cannot carry. An export asked for with exportAll is named from the workbook instead. An empty template falls back to a default one.

WHAT THE INSTANCE HOLDS

The specification block and its header row are read once and answered from memory. The class drops that copy whenever it writes to the table, and ResetCaches drops it for a caller that wrote to the sheet itself.

WHAT IS REPORTED

Entries filed while a member runs leave through HasCheckings and CheckingValues.

Depends on: BetterArray, Checking, CustomTable, DataSheet, LLdictionary, LLVariables, Passwords, TranslationObject

Internal members (not exported)

Factory

Create #

Create a unified export handler

Signature:

Public Function Create(ByVal exportSheet As Worksheet, _
                       Optional ByVal startRow As Long = 1, _
                       Optional ByVal startColumn As Long = 1) As LLExport

Creates a linelist export handler over the specification sheet provided by the caller. The implementation caches the underlying datasheet, worksheet, and sanitisation rules so subsequent calls can resolve filenames and table rows without repeatedly scanning the workbook.

Parameters:

Returns: A fully initialised LLExport instance.

Throws:


InternalData #

SetInternalData

Signature:

Public Property Set InternalData(ByVal exportData As DataSheet)

Parameters:


InternalWorksheet #

SetInternalWorksheet

Signature:

Public Property Set InternalWorksheet(ByVal exportSheet As Worksheet)

Parameters:


Seal #

Prevent further changes to creation-only setters

Signature:

Public Sub Seal()

GuardNotSealed #

Guard creation-only setters after sealing

Signature:

Private Sub GuardNotSealed(ByVal propName As String)

Properties

Data #

Underlying export datasheet

Signature:

Public Property Get Data() As DataSheet

Returns: Datasheet abstraction wrapping the export specification table.


NumberOfExports #

Count the configured export rows

Signature:

Public Property Get NumberOfExports() As Long

Counts rows within the specification table excluding headers. The count is read from the table itself, so it always agrees with what the sheet holds.

Returns: Total number of export definitions currently present.


HarvestCollaboratorCheckings #

harvest-collaborator-checkings

Whether informational checkings were captured

Signature:

Private Sub HarvestCollaboratorCheckings(ByVal collaboratorChecks As Checking)

DataSheet and CustomTable each keep a store of their own, and nobody read either of them before this line.

Parameters:


MergeCollaboratorCheckings #

merge-collaborator-checkings

Take the collaborators' entries before the report is read

Signature:

Private Sub MergeCollaboratorCheckings()

The data sheet lives as long as this object does; the table adapter is the last one ExportTable built. Both are folded in when the report is asked for, and the flag keeps the fold to once because the caller reads HasCheckings and then CheckingValues.


CheckingValues #

Collected informational diagnostics

Signature:

Public Property Get CheckingValues() As Checking

Returns: Checking object containing log entries, if any.


Row Management

ManageRows #

Align export rows and dictionary columns

Signature:

Public Sub ManageRows(Optional ByVal del As Boolean = False, _
                      Optional ByVal dict As LLdictionary, _
                      Optional ByVal rowCount As Long = 2)

Handle the addition or removal of export rows while keeping the dictionary columns aligned with the actual identifiers present in the worksheet. The logic operates on identifiers, so arbitrary deletions leave unrelated export columns in place.

Parameters:


InsertRows #

Insert export rows and keep dictionary columns aligned

Signature:

Public Sub InsertRows(ByVal targetCell As Range, _
                      Optional ByVal dict As LLdictionary, _
                      Optional ByVal insertShift As Boolean = False)

Parameters:


DeleteRows #

Delete export rows intersecting the supplied selection

Signature:

Public Sub DeleteRows(ByVal targetCell As Range, _
                      Optional ByVal dict As LLdictionary, _
                      Optional ByVal forceShift As Boolean = False)

Parameters:


AddRows #

Append export definition rows

Signature:

Public Sub AddRows(Optional ByVal dict As LLdictionary)

Parameters:


RemoveRows #

Remove empty export rows

Signature:

Public Sub RemoveRows(Optional ByVal dict As LLdictionary, _
                      Optional ByVal rowCount As Long = 2)

Parameters:


SyncDictionaryExports #

Synchronise dictionary export columns

Signature:

Public Sub SyncDictionaryExports(Optional ByVal dict As LLdictionary)

Parameters:


Sort #

Sort export definitions and normalize identifiers

Signature:

Public Sub Sort(Optional ByVal dict As LLdictionary)

Parameters:


Row Coordination Helpers

ApplyIncludeDefaults #

Write the default identifier flag into the rows that have none

Signature:

Private Sub ApplyIncludeDefaults(ByVal tableObj As CustomTable, _
                                 ByVal startIndex As Long, _
                                 ByVal endIndex As Long)

Parameters:


ResolveSelectionRowIndex #

Turn a worksheet selection into a row index inside the table body

Signature:

Private Function ResolveSelectionRowIndex(ByVal tableObj As CustomTable, _
                                          ByVal targetCell As Range) As Long

Parameters:

Returns: Long. The 1-based row index, clamped to one past the last row.


AssignExportNumbers #

Give the freshly inserted rows the next free export numbers

Signature:

Private Function AssignExportNumbers(ByVal tableObj As CustomTable, _
                                     ByVal startIndex As Long, _
                                     ByVal insertCount As Long) As BetterArray

The numbers in use are read once and kept in memory, so the routine answers every later question about them without going back to the worksheet.

Parameters:

Returns: BetterArray. Every export number in use once the rows are numbered.


SequentiallyRenameExports #

Renumber every export row from 1 and follow up in the dictionary

Signature:

Private Sub SequentiallyRenameExports(ByVal tableObj As CustomTable, _
                                      Optional ByVal dict As LLdictionary)

Parameters:


RenameDictionaryExports #

Move the dictionary export columns onto the new sequence

Signature:

Private Sub RenameDictionaryExports(ByVal dict As LLdictionary, _
                                    ByRef previousNames() As String, _

The rename runs in two passes through temporary names so an old "Export 3" can become "Export 1" while another column still carries that name.

Parameters:


NextAvailableExportNumber #

Pick the lowest export number that is still free

Signature:

Private Function NextAvailableExportNumber(ByVal existing As BetterArray) As Long

Parameters:

Returns: Long. The next free number.


HighestNumber #

Largest value in a list of export numbers

Signature:

Private Function HighestNumber(ByVal numbers As BetterArray) As Long

Parameters:

Returns: Long. The largest value, or zero for an empty list.


NewNumberList #

Build an empty 1-based list of export numbers

Signature:

Private Function NewNumberList() As BetterArray

Returns: BetterArray. An empty list ready to be pushed into.


TemporaryExportColumn #

Placeholder header used while export columns swap names

Signature:

Private Function TemporaryExportColumn(ByVal orderIndex As Long) As String

Parameters:

Returns: String. The placeholder header.


NormalizedExportHeader #

Turn an export identifier into the dictionary column header

Signature:

Private Function NormalizedExportHeader(ByVal headerText As String) As String

Parameters:

Returns: String. The matching column header, empty when the text carries no number.


EnsureExportIdentifiers #

Give every export row a unique identifier

Signature:

Private Function EnsureExportIdentifiers(ByVal tableObj As CustomTable) As BetterArray

The identifier column is read as one block, settled in memory and written back in one go. A row whose number repeats an earlier one, or whose text carries no number, takes the next free number.

Parameters:

Returns: BetterArray. The export numbers in worksheet order.


CollectExportNumbers #

Read the export numbers the specification table holds

Signature:

Private Function CollectExportNumbers(ByVal tableObj As CustomTable) As BetterArray

The identifier column is read as one block and parsed in memory.

Parameters:

Returns: BetterArray. The export numbers in worksheet order, Nothing when there are none.


EnumerateDictionaryExportNumbers #

Read the export numbers the dictionary columns carry

Signature:

Private Function EnumerateDictionaryExportNumbers(ByVal dict As LLdictionary) As BetterArray

The dictionary header row is read as one block, so a wide dictionary costs one worksheet crossing instead of one per column.

Parameters:

Returns: BetterArray. The export numbers found, Nothing when there are none.


ContainsNumber #

Whether a list of export numbers holds a value

Signature:

Private Function ContainsNumber(ByVal numbers As BetterArray, ByVal target As Long) As Boolean

Parameters:

Returns: Boolean. True when the value is in the list.


ParseExportIdentifier #

Read the number out of an export identifier

Signature:

Private Function ParseExportIdentifier(ByVal rawValue As Variant) As Long

Anything that starts with "export" and ends in digits gives that number. "export" on its own, and text such as "exported", give zero: the conversion runs under On Error Resume Next so a bad tail reads as "no number".

Parameters:

Returns: Long. The export number, or zero.


SyncDictionaryExportsCore #

Make the dictionary export columns match the export rows

Signature:

Private Sub SyncDictionaryExportsCore(ByVal exportNumbers As BetterArray, ByVal dict As LLdictionary)

The dictionary header is read once and the list of export columns is kept in memory as columns are added, so a sync of n exports costs one header read. The dictionary is then told how many export columns it carries.

Parameters:


EnsureDictionaryExportColumn #

Add one export column to the dictionary in the right place

Signature:

Private Sub EnsureDictionaryExportColumn(ByVal dict As LLdictionary, _
                                         ByVal exportIndex As Long, _
                                         ByVal existing As BetterArray)

The new column goes right after the highest numbered export column below it, and after "unique" when there is none. The list of numbers already placed is passed in, so the dictionary header is read once per sync.

Parameters:


ExportColumnName #

Dictionary column header for an export number

Signature:

Private Function ExportColumnName(ByVal exportIndex As Long) As String

The header is capitalised here while LLdictionary lower-cases what it is given and searches with matchCase False. Both sides have to keep that true.

Parameters:

Returns: String. The column header.


SpecManagement

ImportSpecs #

Import export specifications from another worksheet

Signature:

Public Sub ImportSpecs(ByVal fromWksh As Worksheet, _
                       ByVal fromStartRow As Long, _
                       ByVal fromStartColumn As Long)

A setup written before this class filled the export number column holds a bare "1" where the rest of the code reads "Export 1", and ParseExportIdentifier answers 0 for it. The column is rewritten here, at the one moment an old file enters, so no later routine meets a row it cannot read.

Parameters:

Throws:


TagImportedExportNumbers #

Write "Export 1" over an imported bare "1"

Signature:

Private Sub TagImportedExportNumbers()

The column is read once, settled in memory and written back once, and only when a cell actually changed. A cell already carrying the word keeps its number and comes back in the one spelling ExportColumnName writes. A cell holding neither a number nor the word is left exactly as it arrived: EnsureExportIdentifiers is what gives a row with no number one, and it runs when a row is added or deleted.

This reads the whole imported block through the DataSheet rather than the table, so a row that landed below the ListObject is converted with the rest.


BareExportNumber #

Read an export number written as a plain number

Signature:

Private Function BareExportNumber(ByVal rawValue As Variant) As Long

ParseExportIdentifier wants the word in front of the number, which is what this class writes. An older setup holds the number alone, and this is the one place that reads it.

Parameters:

Returns: Long. The export number, or 0 when the cell holds something else.


ExportSpecs #

Export specifications into another workbook

Signature:

Public Sub ExportSpecs(ByVal toWkb As Workbook, _
                       Optional ByVal Hide As Long = xlSheetHidden)

Parameters:

Throws:


FileName

ExportFileName #

Build the filename for a specific export definition

Signature:

Public Function ExportFileName(ByVal exportNumber As Long, _
                               ByVal dict As LLdictionary, _
                               ByVal pass As Passwords, _
                               Optional ByVal exportAll As Boolean = False) As String

Parameters:

Returns: Sanitised filename incorporating dictionary-driven chunks and metadata.

Throws:


Export Count

CountExportRows #

How many rows the specification table holds

Signature:

Private Function CountExportRows() As Long

Returns: Long. The row count, zero when there is no data store.


Utilities

IsActive #

Determine whether an export is marked active

Signature:

Public Function IsActive(ByVal exportNumber As Long) As Boolean

Parameters:

Returns: True when the status column contains "active" (case-insensitive).


ColumnValue #

Retrieve a column value for a given export row

Signature:

Public Function ColumnValue(ByVal exportNumber As Long, ByVal columnName As String) As String

Parameters:

Returns: Column value as string (empty string when not found).


ActiveExportNumbers #

Enumerate export numbers marked as active

Signature:

Public Function ActiveExportNumbers() As BetterArray

Returns: BetterArray containing active export numbers using 1-based numbering.


Translate #

Translate the current LLExport

Signature:

Public Sub Translate(ByVal trads As TranslationObject)

A setup workbook that lacks one of the two translated columns is logged and the other column is still translated.

Parameters:

Throws:


Private Helpers

EnsureDataReady #

Ensure the datasheet state is initialised

Signature:

Private Sub EnsureDataReady()

EnsureWorksheetReady #

Ensure the worksheet reference is initialised

Signature:

Private Sub EnsureWorksheetReady()

ResolveDictionary #

Keep the dictionary the caller passed, or reuse the cached one

Signature:

Private Sub ResolveDictionary(Optional ByVal dict As LLdictionary, _
                              Optional ByVal required As Boolean = True)

A dictionary passed in becomes the cached one. When nothing is passed the cached one stands. This is the single place the class picks its dictionary.

Parameters:

Throws:


EnsureVariablesReady #

Lazily instantiate dictionary-backed variable helper

Signature:

Private Sub EnsureVariablesReady(Optional ByVal dict As LLdictionary)

Parameters:


InternalDictionary #

Dictionary the class is currently working with

Signature:

Private Property Get InternalDictionary() As LLdictionary

Returns: LLdictionary. The cached dictionary.


DictionaryIsAvailable #

Whether a dictionary is cached

Signature:

Private Function DictionaryIsAvailable() As Boolean

Returns: Boolean. True when the class holds one.


ValidateExportNumber #

Validate export number bounds

Signature:

Private Sub ValidateExportNumber(ByVal exportNumber As Long)

Parameters:


ValidatePasswords #

Validate password provider dependency

Signature:

Private Sub ValidatePasswords(ByVal pass As Passwords)

A Nothing here used to be refused outright. It has one honest source: a linelist older than the protection matrix, whose __pass sheet carries no T_ProtectedSheets, so Passwords.Create refuses to build over it. An export walk reading such a file has no passwords object and still has every right to a file name -- the export-all name is composed without one, and the numbered name loses only its version suffix.

Nothing is guarded at the two places that read a value, so the absence travels no further than the suffix it removes.

Parameters:


ExportTable #

Retrieve export table wrapper

Signature:

Private Function ExportTable() As CustomTable

VBA evaluates both sides of Or, so the two guards below are written as two separate tests: reading Count off Nothing would raise on the very case the first test is there to catch.

Returns: CustomTable. The wrapper over the first table on the export sheet.


Specification Reads

LoadSpecification #

Read the whole specification table once

Signature:

Private Sub LoadSpecification()

The header row and the data body are each read in one crossing and kept. Every later read of a cell is answered from those two arrays, so an export that reads eight columns costs two crossings instead of eight searches. The copy is dropped by DropSpecification whenever the class writes to the table, and by ResetCaches for a caller that wrote to the sheet itself.


DropSpecification #

Forget the copy of the specification table

Signature:

Private Sub DropSpecification()

SpecColumnIndex #

Position of a column in the specification table

Signature:

Private Function SpecColumnIndex(ByVal columnName As String) As Long

Whole string, case-insensitive, leftmost wins. That is the same rule the worksheet search used before, applied to the header row held in memory.

Parameters:

Returns: Long. The 1-based position, zero when the header is missing.


SpecRowCount #

How many rows the copy of the specification table holds

Signature:

Private Function SpecRowCount() As Long

Returns: Long. The row count, zero when nothing is loaded.


ReadValue #

Read a column value for one export row

Signature:

Private Function ReadValue(ByVal columnName As String, _
                           ByVal exportNumber As Long, _
                           Optional ByVal required As Boolean = False) As String

Parameters:

Returns: String. The cell value, empty when it cannot be read and required is False.

Throws:


SpecColumnRange #

Resolve one specification column as a worksheet Range

Signature:

Private Function SpecColumnRange(ByVal columnName As String) As Range

Translate writes back into the cells, so it needs the Range itself rather than the values held in memory.

Parameters:

Returns: Range. The column body, Nothing when the header is missing.


RowValues #

Read a one-row Range as a 1-based list of values

Signature:

Private Function RowValues(ByVal rowRange As Range) As Variant

Parameters:

Returns: Variant. A 1-based one-dimensional array.


ColumnValues #

Read a one-column Range as a 1-based list of values

Signature:

Private Function ColumnValues(ByVal columnRange As Range) As Variant

Parameters:

Returns: Variant. A 1-based one-dimensional array.


BlockValues #

Read a Range as a 1-based two-dimensional array

Signature:

Private Function BlockValues(ByVal blockRange As Range) As Variant

Range.Value hands back a plain value for one cell and a two-dimensional array otherwise. This gives callers one shape.

Parameters:

Returns: Variant. A 1-based two-dimensional array.


ResetCaches #

Reset cached references and diagnostics

Signature:

Public Sub ResetCaches()

Callers that write to the export sheet themselves call this so the next read goes back to the worksheet.


ComposeTemplatePath #

Build the filename path from template chunks

Signature:

Private Function ComposeTemplatePath(ByVal template As String) As String

Parameters:

Returns: String. The resolved chunks joined by a double underscore.


ResolveTemplateChunk #

Resolve a single template chunk into a safe string

Signature:

Private Function ResolveTemplateChunk(ByVal rawChunk As String, _
                                      ByVal hostWorkbook As Workbook) As String

Parameters:

Returns: String. The resolved and sanitised value.


IsLiteralTemplateChunk #

Determine whether a chunk represents a literal string

Signature:

Private Function IsLiteralTemplateChunk(ByVal chunk As String) As Boolean

Parameters:

Returns: Boolean. True when the chunk is wrapped in quotes.


StripLiteralChunk #

Remove wrapping quotes from literal chunk values

Signature:

Private Function StripLiteralChunk(ByVal chunk As String) As String

Parameters:

Returns: String. The chunk without its wrapping quotes.


ResolveVariableChunk #

Resolve dictionary-driven chunk value

Signature:

Private Function ResolveVariableChunk(ByVal chunk As String, _
                                      ByVal hostWorkbook As Workbook) As String

Parameters:

Returns: String. The sanitised value, empty when the chunk cannot be resolved.


TryGetWorksheet #

Safely retrieve a worksheet by name

Signature:

Private Function TryGetWorksheet(ByVal hostWorkbook As Workbook, ByVal sheetName As String) As Worksheet

Parameters:

Returns: Worksheet. Nothing when there is no such sheet.


TryGetNamedRange #

Safely retrieve a named range for a chunk

Signature:

Private Function TryGetNamedRange(ByVal hostWorkbook As Workbook, _
                                  ByVal chunk As String, _
                                  ByVal targetSheet As Worksheet) As Range

Parameters:

Returns: Range. Nothing when there is no such name.


SanitizeChunk #

Sanitise individual chunk values for filenames

Signature:

Private Function SanitizeChunk(ByVal chunk As String) As String

Parameters:

Returns: String. The cleaned value, "chunk" when nothing is left.


ReplaceInvalidCharacters #

Replace characters unsupported in filenames

Signature:

Private Function ReplaceInvalidCharacters(ByVal value As String) As String

Each pass works on the result of the one before it, so every character in the list is replaced. The value goes straight into a SaveAs path.

Parameters:

Returns: String. The text with every listed character turned into an underscore.


EnsureSanitizeList #

Initialise invalid filename character list

Signature:

Private Sub EnsureSanitizeList()

BuildVersionSuffix #

Compose version/public key suffix for filenames

Signature:

Private Function BuildVersionSuffix(ByVal pass As Passwords) As String

Parameters:

Returns: String. The suffix to append to the file name.


BuildTimestamp #

Compose timestamp fragment for filenames

Signature:

Private Function BuildTimestamp() As String

Returns: String. The current date and time in the file name format.


DefaultTemplate #

Provide fallback filename template

Signature:

Private Function DefaultTemplate(ByVal exportNumber As Long) As String

Parameters:

Returns: String. The fallback template.


WorkbookBaseName #

Remove Excel extensions from workbook name

Signature:

Private Function WorkbookBaseName(ByVal hostWorkbook As Workbook) As String

Parameters:

Returns: String. The lower-cased name without its extension.


LogInfo #

Append informational diagnostics to the Checking log

Signature:

Private Sub LogInfo(ByVal label As String, Optional ByVal scope As Byte = checkingInfo)

Parameters:


Error Handling

ThrowError #

Raise a ProjectError coded runtime error

Signature:

Private Sub ThrowError(ByVal errNumb As Long, ByVal errorMessage As String)

Parameters:


Used in (16 file(s))