PLAIN-TEXT BASIC, RUN AGAINST DOCUMENTS THAT CANNOT STORE MACROS

Documents that can't store macros,run with plain-text Basic. Without Office,drive your documents.

No Microsoft Office required. The document's format stays as-is; only the contents are processed by machine. The source is plain text, so diffs are readable.

The tool and this page were both written by AI, then rendered and checked by AI. The evidence is in Visual QA and What is not verified.

Amount.bas plain text, kept in git
u = oSheet.getCellByPosition(2, i).getValue()
q = oSheet.getCellByPosition(3, i).getValue()
oSheet.getCellByPosition(4, i).setValue(u * q)
total = total + u * q
basrun apply
quote.xlsx no macros inside
1,28040 51,200
340120 40,800
95500 47,500
8,6006 51,600
72080 57,600
Total 248,700
Where it gets stuckTHE CONSTRAINT

For example, a situation like this.

“Fill in the amounts and total on this quote by end of day.
It arrives in the same format every month, so from next month on too.”

  • What you're handed is an .xlsx. Not a single byte of macro inside.
  • The machine in front of you has no Microsoft Office.
  • You can't change their workflow. The format must be returned as-is.
  • The same task comes again next month. A one-off by hand won't end it.

This request doesn't come together cleanly when the file you're handed is an .xlsx, because an .xlsx cannot store macros by design (macros live in .xlsm). It's not about whether you can write VBA; it's that there's nowhere to put what you wrote. And all three ways out mean giving something up.

Change the format

Convert to .xlsm. That changes their workflow, and often isn't accepted.

Write the values directly

Fill in just the results. Can't re-run, no formulas left. If the numbers change, redo it.

Give up

Back to doing it by hand. Time grows with the sheet count and the months. Next month, the same.

None of the three is a technical limit. The reason it can't be done is written in the spec, so that's just where people tend to stop. What's stuck is the storage side, not the execution side.

The limit is storage, not executionSTORAGE, NOT EXECUTION

It just can't be stored; that doesn't mean it can't run. Inject the source into a Basic library at runtime, and the document is processed while staying an ordinary .xlsx.

Plain-text source src/Amount.bas git diff readable, truth lives here apply inject Basic library MyLib runtime only, gone when done process Document book.xlsx no source embedded pull · rescue macros trapped in a document back to plain text
Plain-text source src/Amount.bas git diff readable, truth here apply inject Basic library MyLib runtime only, gone when done process Document book.xlsx no source embedded pull · rescue trapped macros to plain text
# The source stays plain text, never embedded in the document
python basrun.py apply book.xlsx src MyLib Amount.FillAmounts --backup

# You can also rescue macros trapped in a document back to plain text
python basrun.py pull rescued Standard --book legacy.ods

The long way around turns out to be the better shape.

Embedded macros
Source readability

buried in the document's XML

Review

effectively impossible

Target formats

only formats that can hold macros

Where truth lives

the document

basrun
Source readability

git diff is readable

Review

ordinary code review

Target formats

.xlsx .pptx .docx alike

Where truth lives

the source (library is a runtime cache)

A monthly sheet and chart where basrun filled in the gross margins

Another .xlsx: basrun filled the gross margins and % format; the chart and conditional formatting were set at generation. No Excel used.

Build it, then lookRENDER, THEN LOOK

A string being correct and the result being readable are two different things. Code that prints “it worked” prints it even when it didn't. So we render the result and confirm it with our eyes before calling it done.

Values alone can be read by dumping to CSV. But the moment it becomes CSV, merged cells, column widths, and sheet structure are lost. The coordinates survive as numbers, but how it looks does not. A chart has no textual form at all, so there's no way to confirm by re-reading whether it was drawn.

Readable as textOnly visible when rendered
Cellsvalues, formulas, coordinateshow a merge looks, text cut by column width
Formattingthe rules you setthe color those rules actually produced
Chartsthe definition (XML)whether it shows up at all

I hit this for real. A chart's shape had no size written on it; the chart existed but its area was zero, meaning it was invisible. Ask, and it correctly answers “1 chart.” A method that checks for existence will never find this. I misread the cause three times running, and finally pinned it on the tool's limits: “this renderer doesn't support charts.” Wrong all three times. Only by rendering could I doubt my own output.

basrun, and this page too, were written by AI.
And the flaw above was found by that same AI, rendering and looking.

  • AI can read images. But with no path to turn a document into an image, there's nothing to read in the first place. A library returns the specification of values and formatting, not the result of rendering.
  • So I built a path: to PDF via soffice, then to an image, then re-read. The two images in this section, and the monthly sheet above, were all looked at once before being posted.
  • This page itself was made the same way. Rendered at different widths, checking heading wraps, missing pieces of diagrams, and contrast between ground and text.
A work instruction where basrun swapped the revision number

.docx: basrun swapped the revision number and appended the lot count

A generated 3-D pie chart, rendered

.xlsx: 3-D chart and texture. The domain you can't confirm without rendering

A title wraps to two lines

Perfectly correct as a string. A flaw re-reading will never surface.

Dark text on a dark panel

Mapped the design-system colors by name and dropped the role of the panel it sat on.

The bottom third is blank

Designed by width and never used the vertical dimension. Numerically a correct layout.

Getting startedGETTING STARTED

All you need is LibreOffice. No Microsoft Office required. For Python, use the one bundled with LibreOffice (plain Python has no uno module).

01

Write a Sub

The source is a plain .bas. Never embedded in the document, so it lives in git as-is.

02

Apply it to a document

Injected into a library at runtime, processed, and saved. The format stays .xlsx.

03

Render and look

Not the “it worked” message; we confirm the result with our eyes before calling it done.

' ★ Receive the document as an argument. Don't rely on ThisComponent
Sub FillAmounts(oDoc As Object)
    Dim oSheet As Object
    oSheet = oDoc.Sheets.getByIndex(0)
    ...
End Sub

ThisComponent won't work. A document opened hidden with --headless is not the “current component.” It looks like it succeeded, but nothing happens: in practice it printed “loaded / ran / saved” and not one cell had changed.

# Sync, apply, and save (--backup keeps the original)
python basrun.py apply book.xlsx src MyLib Amount.FillAmounts --backup

The LibreOffice it launches is isolated in a dedicated profile, so it doesn't disturb any LibreOffice you have open on screen. To stop it, it ends only the instance it connected to, not via taskkill. Override with BASRUN_OFFICE / BASRUN_PROFILE / BASRUN_PORT.

What is not verifiedWHAT IS NOT VERIFIED

I write down what I've checked and what I haven't, separately. Answering when asked, and writing it up front, do not mean the same thing.

Not verified

  • Only run on Windows. Path resolution for Linux / macOS is written, but unverified.
  • Huge sheets (tens of thousands of rows: performance, memory) untested. Merged cells and multiple sheets were checked, but scale is a separate matter.
  • Rendering uses LibreOffice. It differs from Excel / PowerPoint rendering. In practice, a 3-D pie chart's label position differed between the two.
  • The bundled sync tool was published in 2017 with no updates since. It's confirmed to work on current LibreOffice, but future compatibility isn't guaranteed.

Verified

  • Works with merged cells and multiple sheets. Existing merges survive a processing round-trip, a second sheet can be targeted for writing, and new merges can be made, confirmed not just by values but by rendering and looking.
  • 22 tests (16 unit / 6 integration). 22/22 individually and end-to-end; one flaw only surfaced running end-to-end, and it's fixed. On machines without LibreOffice, the integration ones are skipped.
  • Before and after a run, the user's default profile is unchanged, byte for byte.
  • After processing it's an ordinary .xlsx with no vbaProject.bin.
  • Results are confirmed by re-reading with a different implementation, not the one used to generate them.
  • This page too was rendered at 1280 / 390 / 360 and checked. Ground-to-text contrast was measured by reading pixels (8.2 : 1 at worst), and outbound network requests were zero.
Built onBUILT ON
obasync Sync is delegated to it. By imacat / Apache-2.0, bundled unmodified open-design Palette and depth. Apache-2.0. Only the data is used; nothing resident is added

Anything with no reason to rewrite isn't rewritten. The time goes into what only becomes possible once they're combined. The --book option, which opens a document and rescues the macros inside it to plain text, was in neither tool on its own.