DataFlex wrappers for Markdown, Handlebars, PDF page counting, and case-insensitive replace.
ThreeParams.dll is a native library that exposes four families of helper
functions to DataFlex. Each is fronted by a small .pkg wrapper that handles
buffer sizing, UTF-8 conversion, and error reporting so you can call simple, idiomatic
DataFlex functions.
| Package | Public function(s) | Purpose |
|---|---|---|
Markdown.pkg | MarkdownToHTML, WrappedMarkdownToHTML, BlankLinesToHTML | Convert Markdown text to HTML. |
HandleBars.pkg | RenderHandleBars | Render a Handlebars template against a JSON String. |
PdfPageCount.pkg | GetPdfPageCount | Return the number of pages in a PDF file. |
CIReplace.pkg | CIReplace, CIReplaces | Case-insensitive string replacement (first / all). |
Place threeparams.dll in the workspace Programs folder, place the
packages in the workspace AppHTML folder, then Use the packages you
need:
Use Markdown.pkg
Use HandleBars.pkg
Use PdfPageCount.pkg
Use CIReplace.pkg
Converts a Markdown string into an HTML fragment (no surrounding <html> wrapper).
String sHtml
Move (MarkdownToHTML("# Invoice\n\nThank you for your **order**.")) to sHtml
// sHtml -> "<h1>Invoice</h1><p>Thank you for your <strong>order</strong>.</p>"
Before conversion the text is passed through BlankLinesToHTML (below), so the
custom {N blank lines} token is honoured. On failure the function returns the
literal string <h1>Error Displaying Text. Contact your Administrator</h1>.
Same conversion as above, but the result is embedded in a complete, styled HTML document
(fonts, table styling, hover rows, etc.). Use it when you want a ready-to-display page rather
than a fragment — e.g. loading into a cWebView or writing to a file.
String sPage
Move (WrappedMarkdownToHTML(sReportMarkdown)) to sPage
// sPage is a full <html>…</html> document with embedded CSS
Helper used internally by the converters. It replaces tokens of the form
{N blank lines} (where N is 2–20) with a spacer
<div> of N em height. Useful for forcing vertical whitespace
that plain Markdown cannot express.
Move (BlankLinesToHTML("Above{3 blank lines}Below")) to sText
// -> "Above<div style='margin-bottom:3em'></div>Below"
oMarkdownParser object with
helpers (ParseMarkdownRow, ParseMarkdownTable,
IsMarkdownSeparator) for pulling structured data out of a Markdown table into
DataFlex arrays — handy when you need the values, not just the rendered HTML.
Renders a Handlebars template, substituting values from a JSON data object. Returns the rendered text, or an empty string on error.
| Parameter | Meaning |
|---|---|
sTemplate | The Handlebars template source. |
sJSON | A JSON string supplying the template's data context. |
isStopError | If False, a failure raises a UserError. If True, the call fails silently and you inspect gsLastHandleBarsError yourself. |
String sTemplate sJSON sOut
Move "Dear {{customer.name}},\n\nYour balance is {{balance}}." to sTemplate
Move '{"customer":{"name":"Ada Lovelace"},"balance":"$42.00"}' to sJSON
Move (RenderHandleBars(sTemplate, sJSON, False)) to sOut
// sOut -> "Dear Ada Lovelace,\n\nYour balance is $42.00."
Move "Items:\n{{#each items}}- {{this.name}} ({{this.qty}})\n{{/each}}" to sTemplate
Move '{"items":[{"name":"Widget","qty":3},{"name":"Gadget","qty":1}]}' to sJSON
Move (RenderHandleBars(sTemplate, sJSON, False)) to sOut
// Items:
// - Widget (3)
// - Gadget (1)
WrappedMarkdownToHTML to produce a finished, data-driven HTML document.
Returns the number of pages in a PDF file, or a negative error code.
| Parameter | Meaning |
|---|---|
sFilename | Full path to the PDF file. |
isStopError | If False, errors raise a UserError. If True, errors are silent — check the negative return value and gsLastPdfPageCountError. |
Integer iPages
Move (GetPdfPageCount("C:\docs\contract.pdf", True)) to iPages
If (iPages < 0) Begin
Showln "Could not read PDF: " gsLastPdfPageCountError
End
Else Begin
Showln "The document has " iPages " page(s)."
End
| Code | Meaning |
|---|---|
| -1 | Invalid filename (null, not valid UTF-8, or file does not exist). |
| -2 | Not a valid PDF (missing PDF header or unparseable). |
Two wrappers mirror the parameter order of DataFlex's built-in Replace /
Replaces functions (Find, Source, With),
but match without regard to letter case.
String s
Move (CIReplace("hello", "HELLO world, hello again", "Hi")) to s
// First match only -> "Hi world, hello again"
Move (CIReplaces("hello", "HELLO world, hello again", "Hi")) to s
// All matches -> "Hi world, Hi again"
sWith exactly as supplied; only the matching is
case-insensitive.
Each package keeps the last error message in a global string and exposes a
…ErrorToText translator. When a function takes an isStopError
flag, pass True to suppress the automatic UserError and handle
problems yourself.
| Area | Last-error global | Translator function |
|---|---|---|
| Handlebars | gsLastHandleBarsError | HandleBarsErrorToText |
| PDF page count | gsLastPdfPageCountError | PdfPageCountErrorToText |
| CI Replace | gsLastCIReplaceError | CIReplaceErrorToText |
| Code | Meaning |
|---|---|
| -1 | Null input pointer. |
| -2 | Invalid UTF-8 in input string. |
| -3 | Negative or invalid buffer length. |
| -4 | JSON parse error. |
| -5 | Handlebars template compile error. |
| -6 | Template render error. |
| -7 | Rendered output not valid UTF-8. |
| Code | Meaning |
|---|---|
| -1 | A required pointer was null. |
| -2 | Source was not valid UTF-8. |
| -3 | Find was not valid UTF-8. |
| -4 | Replacement was not valid UTF-8. |
The real power comes from combining the functions. This example builds a customer invoice in three stages:
Note that the template is itself Markdown — headings, bold, a table, and the
{2 blank lines} spacer extension — with Handlebars tags woven through it.
# Invoice {{invoiceNo}}
**Billed to:** {{customer.name}}
**Date:** {{date}}
| Item | Qty | Price |
|------|----:|------:|
{{#each lineItems}}| {{this.name}} | {{this.qty}} | {{this.price}} |
{{/each}}
{{#if isPaid}}
*Paid in full — thank you!*
{{else}}
**Balance due: {{total}}**
{{/if}}
{2 blank lines}
> Questions? Reply to this email.
{
"invoiceNo": "INV-1042",
"date": "2026-06-06",
"customer": { "name": "Ada Lovelace" },
"lineItems": [
{ "name": "Widget", "qty": 3, "price": "$1.00" },
{ "name": "Gadget", "qty": 1, "price": "$4.00" }
],
"isPaid": false,
"total": "$7.00"
}
Use HandleBars.pkg
Use Markdown.pkg
Procedure BuildInvoice
String sTemplate sJSON sMarkdown sHtml
// --- 1. Load the Handlebars/Markdown template and the JSON data ---
Move (ReadFileToString("invoice.hbs.md")) to sTemplate // your own loader
Move (ReadFileToString("invoice.json")) to sJSON
// --- 2. Handlebars: merge data into Markdown ---
Move (RenderHandleBars(sTemplate, sJSON, True)) to sMarkdown
If (gsLastHandleBarsError <> "") Begin
Showln "Render failed: " gsLastHandleBarsError
Procedure_Return
End
// --- 3. Markdown -> a complete, styled HTML document ---
Move (WrappedMarkdownToHTML(sMarkdown)) to sHtml
// sHtml is now a full <html>…</html> page, ready to
// display in a cWebView, email, or write to disk.
Send WriteStringToFile sHtml "invoice.html" // your own writer
End_Procedure
After step 2, sMarkdown holds fully-merged Markdown:
# Invoice INV-1042
**Billed to:** Ada Lovelace
**Date:** 2026-06-06
| Item | Qty | Price |
|------|----:|------:|
| Widget | 3 | $1.00 |
| Gadget | 1 | $4.00 |
**Balance due: $7.00**
{2 blank lines}
> Questions? Reply to this email.
Step 3 then renders that to HTML — the table becomes a styled
<table>, **Balance due** becomes bold, the
{2 blank lines} token becomes a 2 em spacer, and the whole thing is wrapped
in the document shell with embedded CSS.
.md file means business users can tweak wording and layout without touching
DataFlex code or HTML.
Markdown is a lightweight plain-text format that converts cleanly to HTML.
# Heading 1
## Heading 2
### Heading 3
| Markdown | Result |
|---|---|
*italic* or _italic_ | italic |
**bold** | bold |
~~strikethrough~~ | |
`inline code` | inline code |
- Bullet item
- Another bullet
- Nested bullet
1. First
2. Second
3. Third
[Link text](https://example.com)

> This is a quoted line.
> It can span multiple lines.
```
Multiple lines
of preformatted code
```
| Name | Qty | Price |
|---------|----:|------:|
| Widget | 3 | $1.00 |
| Gadget | 1 | $4.00 |
Use colons in the separator row to set alignment: :--- left, :--: centre, ---: right. Escape a literal pipe inside a cell as \|.
---
Specific to these wrappers — insert {N blank lines} (with N
from 2 to 20) to add N em of vertical space:
Section one.
{4 blank lines}
Section two appears well below.
Handlebars is a logic-light templating language. A template is text containing
{{expressions}} that are replaced with values from a JSON data context.
Hello {{name}}!
With data {"name":"World"} → Hello World!
{{customer.address.city}}
| Syntax | Behaviour |
|---|---|
{{value}} | HTML-escaped output (safe by default). |
{{{value}}} | Raw, unescaped output — use only with trusted content. |
{{#if isPaid}}
Paid in full. Thank you!
{{else}}
Balance outstanding: {{balance}}
{{/if}}
{{#unless inStock}}
Currently out of stock.
{{/unless}}
{{#each lineItems}}
{{this.description}} — {{this.amount}}
{{/each}}
Inside an {{#each}} block, {{this}} is the current element,
{{@index}} is its zero-based position, and {{@first}} /
{{@last}} flag the ends of the list.
#with{{#with customer}}
{{name}} of {{address.city}}
{{/with}}
{{! This note will not appear in the output }}
RenderHandleBars must be valid JSON; a parse
error returns code -4. Watch quoting when building JSON inside
DataFlex string literals — single-quoted DataFlex strings let you embed the
double quotes JSON requires.
Need a hand? If you want help using these functions or run into a problem, contact our support team at support@threeparams.com and we'll be glad to assist.