A step-by-step guide to building a simple Markdown to HTML converter using the declarative rules of the uCalc Transformer.
Product:
Class:
Warning
uCalc API Preview Release Notice:The uCalc engine has successfully transitioned to modern cross-platform environments.The next phase envolves some structural changes, performance optimizations, and API refinements.The API is subject to breaking changes prior to the stable release. Please evaluate the preview version thoroughly before production use.
This project will guide you through building a simple, functional Markdown to HTML converter. It's a perfect real-world example of how the declarative power of the uCalc.Transformer can solve complex text-structuring problems more safely and readably than traditional tools like Regular Expressions.
We'll create a transformer that can convert basic Markdown syntax into the corresponding HTML tags:
# A Header → <h1>A Header</h1>* An item → <li>An item</li>**bold** → <b>bold</b>*italic* → <i>italic</i>While you could attempt this with a series of Regex.Replace calls, you would quickly run into problems, especially with inline styles. A regex for italic text (\*(.*?)\*) would incorrectly match bold text (**text**), leading to broken tags. Handling this requires complex negative lookarounds that are difficult to write and maintain.
The uCalc Transformer avoids this by being token-aware and using a clear rule precedence system, making the solution both more robust and easier to understand.
First, we need a Transformer instance. We will also enable RewindOnChange for all rules. This is crucial because it tells the transformer to re-scan the text after a replacement. Without it, after converting a list item line like * text with **bold** into <li>text with **bold**</li>, the engine would not re-scan the new text to find and convert the **bold** part.
using uCalcSoftware;
var uc = new uCalc();
using (var t = new uCalc.Transformer()) {
t.DefaultRuleSet.RewindOnChange = true;
// ... rules go here ...
}
Next, we'll define our conversion logic using FromTo() rules. We'll start with block-level elements (headers and lists), which operate on whole lines.
using uCalcSoftware;
var uc = new uCalc();
// Headers: Match a '#' at the start of a line, capture the rest.
// The implicit newline at the end of the line will stop the {line} capture.
t.FromTo("#{@Whitespace}{line}", "<h1>{line}</h1>");
// List Items: Match a '*' at the start of a line.
t.FromTo("*{@Whitespace}{line}", "<li>{line}</li>");
Now for the inline elements. This is where rule order becomes critical.
using uCalcSoftware;
var uc = new uCalc();
// Rule for Italic text
t.FromTo("*{text}*", "<i>{text}</i>");
// Rule for Bold text
t.FromTo("**{text}**", "<b>{text}</b>");
In the code above, why did we define the rule for italics before the rule for bold? Because the Transformer checks rules in a LIFO (Last-In, First-Out) order. The last rule defined is checked first.
**{text}** is more specific than *{text}*.**bold** as * + *bold + *, resulting in <i>*bold</i>*—incorrect HTML!**...** first. If it matches, great. If not, it will then check for *...*.This is a fundamental concept for writing correct transformation logic.
The full example below combines these rules to process a sample Markdown document. The transformer will correctly handle both block-level tags and nested inline tags in a single pass.
<ul> TagsYou'll notice this simple converter creates <li> tags but doesn't wrap them in the required <ul>...</ul> container. This is a classic multi-pass transformation problem. To solve it, you could:
<li> tags.<li> tags and wraps them in <ul>...</ul>.This demonstrates how you can build sophisticated pipelines by chaining transformers or using the Pass system.
ID: 1404
using uCalcSoftware;
var uc = new uCalc();
using (var t = new uCalc.Transformer()) {
t.DefaultRuleSet.RewindOnChange = true;
// 2. Define Rules (General rules first, specific rules last for LIFO precedence)
// -- Inline rules --
// Italic is defined before Bold, giving Bold higher precedence.
t.FromTo("*{text}*", "{text}");
t.FromTo("**{text}**", "{text}");
// -- Block-level rules --
t.FromTo("#{@Whitespace}{line}", "{line}
");
t.FromTo("*{@Whitespace}{line}", " {line} ");
t.FromTo("{@nl}{@nl}", "{@nl}{@nl}"); // {@nl} = NewLine
t.FromTo("{@nl}{@nl}*{@Whitespace}", "{@nl}{@nl}* ");
// 3. Define the input Markdown text
var markdown = """
# Main Header
* First list item
* Second list item with **bold** text.
* Third list item with *italic* text.
Another paragraph with **bold** and *italic*.
""";
// 4. Run the transformation and print the result
Console.WriteLine(t.Transform(markdown));
}
<h1>Main Header</h1>
<ul>
<li>First list item</li>
<li>Second list item with <b>bold</b> text.</li>
<li>Third list item with <i>italic</i> text.</li>
</ul>
Another paragraph with <b>bold</b> and <i>italic</i>. using uCalcSoftware; var uc = new uCalc(); using (var t = new uCalc.Transformer()) { t.DefaultRuleSet.RewindOnChange = true; // 2. Define Rules (General rules first, specific rules last for LIFO precedence) // -- Inline rules -- // Italic is defined before Bold, giving Bold higher precedence. t.FromTo("*{text}*", "<i>{text}</i>"); t.FromTo("**{text}**", "<b>{text}</b>"); // -- Block-level rules -- t.FromTo("#{@Whitespace}{line}", "<h1>{line}</h1>"); t.FromTo("*{@Whitespace}{line}", "<li>{line}</li>"); t.FromTo("</li>{@nl}{@nl}", "</li>{@nl}</ul>{@nl}"); // {@nl} = NewLine t.FromTo("{@nl}{@nl}*{@Whitespace}", "{@nl}<ul>{@nl}* "); // 3. Define the input Markdown text var markdown = """ # Main Header * First list item * Second list item with **bold** text. * Third list item with *italic* text. Another paragraph with **bold** and *italic*. """; // 4. Run the transformation and print the result Console.WriteLine(t.Transform(markdown)); }
#include
#include "uCalc.h"
using namespace std;
using namespace uCalcSoftware;
int main() {
uCalc uc;
{
uCalc::Transformer t;
t.Owned(); // Causes t to be released when it goes out of scope
t.DefaultRuleSet().RewindOnChange(true);
// 2. Define Rules (General rules first, specific rules last for LIFO precedence)
// -- Inline rules --
// Italic is defined before Bold, giving Bold higher precedence.
t.FromTo("*{text}*", "{text}");
t.FromTo("**{text}**", "{text}");
// -- Block-level rules --
t.FromTo("#{@Whitespace}{line}", "{line}
");
t.FromTo("*{@Whitespace}{line}", "{line} ");
t.FromTo("{@nl}{@nl}", "{@nl}{@nl}"); // {@nl} = NewLine
t.FromTo("{@nl}{@nl}*{@Whitespace}", "{@nl}{@nl}* ");
// 3. Define the input Markdown text
auto markdown = R"(
# Main Header
* First list item
* Second list item with **bold** text.
* Third list item with *italic* text.
Another paragraph with **bold** and *italic*.
)";
// 4. Run the transformation and print the result
cout << t.Transform(markdown) << endl;
}
}
<h1>Main Header</h1>
<ul>
<li>First list item</li>
<li>Second list item with <b>bold</b> text.</li>
<li>Third list item with <i>italic</i> text.</li>
</ul>
Another paragraph with <b>bold</b> and <i>italic</i>. #include <iostream> #include "uCalc.h" using namespace std; using namespace uCalcSoftware; int main() { uCalc uc; { uCalc::Transformer t; t.Owned(); // Causes t to be released when it goes out of scope t.DefaultRuleSet().RewindOnChange(true); // 2. Define Rules (General rules first, specific rules last for LIFO precedence) // -- Inline rules -- // Italic is defined before Bold, giving Bold higher precedence. t.FromTo("*{text}*", "<i>{text}</i>"); t.FromTo("**{text}**", "<b>{text}</b>"); // -- Block-level rules -- t.FromTo("#{@Whitespace}{line}", "<h1>{line}</h1>"); t.FromTo("*{@Whitespace}{line}", "<li>{line}</li>"); t.FromTo("</li>{@nl}{@nl}", "</li>{@nl}</ul>{@nl}"); // {@nl} = NewLine t.FromTo("{@nl}{@nl}*{@Whitespace}", "{@nl}<ul>{@nl}* "); // 3. Define the input Markdown text auto markdown = R"( # Main Header * First list item * Second list item with **bold** text. * Third list item with *italic* text. Another paragraph with **bold** and *italic*. )"; // 4. Run the transformation and print the result cout << t.Transform(markdown) << endl; } }
Imports System
Imports uCalcSoftware
Public Module Program
Public Sub Main()
Dim uc As New uCalc()
Using t As New uCalc.Transformer()
t.DefaultRuleSet.RewindOnChange = true
'// 2. Define Rules (General rules first, specific rules last for LIFO precedence)
'// -- Inline rules --
'// Italic is defined before Bold, giving Bold higher precedence.
t.FromTo("*{text}*", "{text}")
t.FromTo("**{text}**", "{text}")
'// -- Block-level rules --
t.FromTo("#{@Whitespace}{line}", "{line}
")
t.FromTo("*{@Whitespace}{line}", " {line} ")
t.FromTo("{@nl}{@nl}", "{@nl}{@nl}") '// {@nl} = NewLine
t.FromTo("{@nl}{@nl}*{@Whitespace}", "{@nl}{@nl}* ")
'// 3. Define the input Markdown text
Dim markdown = "
# Main Header
* First list item
* Second list item with **bold** text.
* Third list item with *italic* text.
Another paragraph with **bold** and *italic*.
"
'// 4. Run the transformation and print the result
Console.WriteLine(t.Transform(markdown))
End Using
End Sub
End Module
<h1>Main Header</h1>
<ul>
<li>First list item</li>
<li>Second list item with <b>bold</b> text.</li>
<li>Third list item with <i>italic</i> text.</li>
</ul>
Another paragraph with <b>bold</b> and <i>italic</i>. Imports System Imports uCalcSoftware Public Module Program Public Sub Main() Dim uc As New uCalc() Using t As New uCalc.Transformer() t.DefaultRuleSet.RewindOnChange = true '// 2. Define Rules (General rules first, specific rules last for LIFO precedence) '// -- Inline rules -- '// Italic is defined before Bold, giving Bold higher precedence. t.FromTo("*{text}*", "<i>{text}</i>") t.FromTo("**{text}**", "<b>{text}</b>") '// -- Block-level rules -- t.FromTo("#{@Whitespace}{line}", "<h1>{line}</h1>") t.FromTo("*{@Whitespace}{line}", "<li>{line}</li>") t.FromTo("</li>{@nl}{@nl}", "</li>{@nl}</ul>{@nl}") '// {@nl} = NewLine t.FromTo("{@nl}{@nl}*{@Whitespace}", "{@nl}<ul>{@nl}* ") '// 3. Define the input Markdown text Dim markdown = " # Main Header * First list item * Second list item with **bold** text. * Third list item with *italic* text. Another paragraph with **bold** and *italic*. " '// 4. Run the transformation and print the result Console.WriteLine(t.Transform(markdown)) End Using End Sub End Module
This page last modified on: