Wapp

Html Generator
Login

Html Generator

(1) By Olaf Ritter von Ruppert (oruppert) on 2025-02-06 09:54:32 [source]

Hello all,

I have a small html generator that I would like to share
with you.  It consists of one global variable and one
function.   The function

   html tag_name attributes...

appends the html for the given tag and attributes to
the html global variable.  There are 3 special attributes:

  - text: appends escaped text to the body of the html
          element.
  - html: appends unescaped text to the body of the
          html element.
  - body: executes body script before appending the
          closing tag.

The html output is somewhat formatted and html entities
are currently not escaped.

I will clean it up and document it if someone is
interested.


Here and example:

  html html {
    html body {
      html hr
      html h1 text "Hello, World!" style {
        color: green
      }
    }
  }

Generates:

  <html>
  <body>
  <hr>
  <h1 style="color: green">Hello, World!</h1>
  </body>
  </html>



And here is the source code:


set html ""
proc html {tag args} {
	global html

	# Normalize Implicit Script
	if {[llength $args] % 2 == 1} {
		set args [linsert $args end-1 body]
	}

	# Generate Attributes String
	set attributes {}
	foreach {name value} $args {
		switch $name {
			{html} {}
			{text} {}
			{body} {}
			{default} {
				# Collapse Whitespace
				set value [regsub -all {\s+} $value { }]
				# Escape Quotes
				set value [string map {\" &quot;} $value]
				# Trim Whitespace
				set value [string trim $value]
				# Skip Empty Attributes
				if {$value == {}} {
					continue
				}
				# Append Attribute
				set attributes [format {%s %s="%s"} $attributes $name $value]
			}
		}
	}

	# Append Opening Tag
	switch $tag {
		{} {}
		{default} {
			# Maybe Append Newline
			switch [string index $html end] {
				{} {}
				\n {}
				default {
					append html \n
				}
			}
			# Append the actual Tag
			append html <$tag$attributes>
		}
	}
	# Process Special Attributes
	foreach {name value} $args {
		switch $name {
			{html} { append html $value }
			{text} { append html [string map {< &lt;} $value] }
			{body} { uplevel 1 $value }
		}
	}
	# Append Closing Tag
	switch $tag {
		{} {}
		{area} {}
		{base} {}
		{br} {}
		{col} {}
		{embed} {}
		{hr} {}
		{img} {}
		{input} {}
		{link} {}
		{meta} {}
		{param} {}
		{source} {}
		{track} {}
		{wbr} {}
		{default} {
			append html </$tag>\n
		}
	}
}

(2) By ufko.org on 2025-02-06 14:25:38 in reply to 1 [link] [source]

Hey Olaf.

Very nice! I really like how you've created this simple DSL for generating HTML. It's a great way to speed up the process and simplify writing HTML code. I'll definitely give it a try. Thanks for sharing!

(3) By Olaf Ritter von Ruppert (oruppert) on 2025-02-25 19:31:50 in reply to 2 [link] [source]

Thank you for your kind words!
I have written a few prototypes
and it seems to work fine.

I like that it allows me to
use inline styles and
decompose my application into
small components, e.g.

proc render-panel {name body} {
	html div style {
		display: flex;
		flex-direction: column;
		gap: 2rem;
		padding: 2rem;
		border-radius: 1rem;
		background-color: #fafaf9;
	} {
		html strong text $name
		uplevel 1 $body
	}
}

Here a little trick is used:
I do not like to write 0.25rem
or 0.5rem all the time, so I set
the font-size of html to 25% and
and the font-size of body to 400%.
so 1rem is actually 0.25em.

If you want to try it, here is a Version
that writes to the wapp .reply string:


proc html {tag args} {
	global wapp

	# Normalize Implicit Script
	if {[llength $args] % 2 == 1} {
		set args [linsert $args end-1 body]
	}

	# Generate Attributes String
	set attributes {}
	foreach {name value} $args {
		switch $name {
			{html} {}
			{text} {}
			{body} {}
			{default} {
				# Collapse Whitespace
				set value [regsub -all {\s+} $value { }]
				# Escape Quotes
				set value [string map {\" &quot;} $value]
				# Trim Whitespace
				set value [string trim $value]
				# Skip Empty Attributes
				if {$value == {}} {
					continue
				}
				# Append Attribute
				set attributes [format {%s %s="%s"} $attributes $name $value]
			}
		}
	}

	# Append Opening Tag
	switch $tag {
		{} {}
		{default} {
			# Maybe Append Newline
			switch [string index [dict get $wapp .reply] end] {
				{} {}
				\n {}
				default {
					dict append wapp .reply \n
				}
			}
			# Append the actual Tag
			dict append wapp .reply <$tag$attributes>
		}
	}
	# Process Special Attributes
	foreach {name value} $args {
		switch $name {
			{html} { dict append wapp .reply $value }
			{text} { dict append wapp .reply [string map {< &lt;} $value] }
			{body} { uplevel 1 $value }
		}
	}
	# Append Closing Tag
	switch $tag {
		{} {}
		{area} {}
		{base} {}
		{br} {}
		{col} {}
		{embed} {}
		{hr} {}
		{img} {}
		{input} {}
		{link} {}
		{meta} {}
		{param} {}
		{source} {}
		{track} {}
		{wbr} {}
		{default} {
			dict append wapp .reply </$tag>\n
		}
	}
}

(4.3) By ufko.org on 2025-02-26 05:23:43 edited from 4.2 in reply to 3 [link] [source]

Olaf,

I hope you don’t mind, but I renamed your procedure to proc olaf in my code—I just had to! 😄 Your approach is really elegant, and I appreciate how simple yet powerful it is.

By the way, have you considered publishing your source code on Tcl wiki? I think it deserves more attention, and others in the Tcl community might find it useful or even contribute improvements.

You can create new page here: https://wiki.tcl-lang.org/_new

Thanks again for sharing your work!

To make proc olaf more compact, without calling set olaf "" outside of the procedure:

Note:

  1. Now olaf is something like static variable in other languages.
  2. I was thinking, it might be a good idea to rename the global variable (single purpose in this context) to something a bit more unique, like ___olaf (with multiple underscores) or something similarly unconventional, to avoid accidental clash.
proc olaf {tag args} {
	global olaf
	# init only if variable olaf doesn't exist
        if {![info exists olaf]} { set olaf "" }

	# Normalize Implicit Script
	if {[llength $args] % 2 == 1} {
		set args [linsert $args end-1 body]
	}

	# Generate Attributes String
	set attributes {}
	foreach {name value} $args {
		switch $name {
			{olaf} {}
			{text} {}
			{body} {}
			{default} {
				# Collapse Whitespace
				set value [regsub -all {\s+} $value { }]
				# Escape Quotes
				set value [string map {\" &quot;} $value]
				# Trim Whitespace
				set value [string trim $value]
				# Skip Empty Attributes
				if {$value == {}} {
					continue
				}
				# Append Attribute
				set attributes [format {%s %s="%s"} $attributes $name $value]
			}
		}
	}

	# Append Opening Tag
	switch $tag {
		{} {}
		{default} {
			# Maybe Append Newline
			switch [string index $olaf end] {
				{} {}
				\n {}
				default {
					append olaf \n
				}
			}
			# Append the actual Tag
			append olaf <$tag$attributes>
		}
	}
	# Process Special Attributes
	foreach {name value} $args {
		switch $name {
			{olaf} { append olaf $value }
			{text} { append olaf [string map {< &lt;} $value] }
			{body} { uplevel 1 $value }
		}
	}
	# Append Closing Tag
	switch $tag {
		{} {}
		{area} {}
		{base} {}
		{br} {}
		{col} {}
		{embed} {}
		{hr} {}
		{img} {}
		{input} {}
		{link} {}
		{meta} {}
		{param} {}
		{source} {}
		{track} {}
		{wbr} {}
		{default} {
			append olaf </$tag>\n
		}
	}
}

(5) By Olaf Ritter von Ruppert (oruppert) on 2025-03-07 16:55:04 in reply to 4.3 [link] [source]

Thank you for your kind words.  I apologize
for my late reply, I did not forget you.
It's just that my queue is full.

You can call the proc olaf if you want to.
But I prefer the name html.  I created a
repository on github:

  https://github.com/oruppert/html.tcl

I added some of your changes there and
will add your name in the comments if
that is fine with you.

Thank you for your link to the tcl wiki.
I will write a page in the next week
or so.  Would you want me to include
some tricks I learned writing dumb old
webpages?  Sometimes I have the feeling
people have forgotten how to write
html based web applications, like this
forum: Simple, fast, useful.

Take care, Olaf

(6) By ufko.org on 2025-03-08 11:02:21 in reply to 5 [link] [source]

I'm fine with mentions. Already follow your project on GitHub :) Vanilla HTML's my thing.