xml-document-generatorCarl Sassenrath (Rebol) and Douglas Crockford (Json) don’t like XML because it’s too verbose. Still XML is now a standard and not being able to parse and create XML documents would be very annoying. Rebol has an internal build-tag function but it is far from enough to easily cope with a complex XML document. Happily there are Rebol Gurus who have built some XML libraries for Rebol.

My favorite one is ML library by Andrew Martin. It is a DSL for creating XML document with a less verbose and easier syntax than XML. There is an interesting article here which gives some examples of its capability.

To test the ML library, copy and paste this source code or download them from Rebol.org.


Push: func [
    "Inserts a value into a series and returns the series head."
    Stack [series! port! bitset!]   "Series at point to insert."
    Value [any-type!] /Only "The value to insert."
    ][
    head either Only [
        insert/only Stack :Value
        ][
        insert Stack :Value
        ]
    ]

Pop: function [
    "Returns the first value in a series and removes it from the series."
    Stack [series! port! bitset!]   "Series at point to pop from."
    ][
    Value
    ][
    Value: pick Stack 1
    remove Stack
    :Value
    ]
Build-Tag: function [
    "Generates a tag from a composed block."
    Values [block!] "Block of parens to evaluate and other data."
    ] [
    Tag Value_Rule XML? Name Attribute Value
    ] [
    Tag: make string! 7 * length? Values
    Value_Rule: [
        set Value issue! (Value: mold Value)
        | set value file! (Value: replace/all copy Value #" " "%20")
        | set Value any-type!
        ]
    XML?: false
    parse compose Values [
        [
            set Name ['?xml (XML?: true) | word! | url! | string!] (append Tag Name)
            any [
                set Attribute [word! | url! | string!] Value_Rule (
                    repend Tag [#" " Attribute {="} Value {"}]
                    )
                | Value_Rule (repend Tag [#" " Value])
                ]
            end (if XML? [append Tag #"?"])
            ]
        | [set Name refinement! to end (Tag: mold Name)]
        ]
    to tag! Tag
    ]

make object! [
	Stack: make block! 10
	push Stack ""
	set 'ML function [
		{ML generates HTML, XHTML, XML, WML and SVG markup
		from Rebol words, paths, tags and blocks.}
		Dialect [block!]
		] [String Values_Rule Values Value Tag NameSpace] [
		String: copy ""
		Values_Rule: [
			; Caution! The 'none word below is replaced in the 'parse rule below!
			none [
				set Value any-type! (
					Tag: next next Tag
					insert Tag Value
					Value: none
					)
				]
			; Caution! The 'opt word below is replaced in the 'parse rule below!
			opt [
				set Value [
					decimal! | file! | block! | string! | char!
					| money! | time! | issue! | tuple! | date!
					| email! | pair! | logic! | integer! | url!
					]
				]
			]
		Values: make block! 10
		parse Dialect [
			any [
				[
					set Tag tag! (
						Values_Rule/1: 0	; Replace 'none word in 'Values_Rule above.
						Values_Rule/3: either any [	; Replace 'opt word...
							#"/" = last Tag	; empty tag.
							#"?" = first Tag	; XML tag.
							#"!" = first Tag	; DOCTYPE tag.
							] [0] [1]
						)
					| set Tag [path! | word!] (
						Tag: to-block get 'Tag
						; Replace 'none word in 'Values_Rule above.
						Values_Rule/1: -1 + length? Tag
						Values_Rule/3: 'opt	; Replace 'opt word...
						)
					] (Value: none) Values_Rule (
					Tag: head Tag
					repend String either none? Value [
						if not tag? Tag [
							Tag: Build-Tag Tag
							]
						if all [
							#"/"  last Tag
							#"?"  first Tag
							#"!"  first Tag
							] [
							append Tag " /"
							]
						[Tag newline]
						] [
						[
							either all [block? Value empty? String] [newline] [""]
							either tag? Tag [Tag] [
								Build-Tag head change Tag join first Stack first Tag
								]
							either block? Value [ML Value] [Value]
							to-tag join #"/" first either tag? Tag [to-block Tag] [Tag]
							]
						]
					Values_Rule/1: none
					)
				| set NameSpace set-word! set Value block! (
					push Stack probe mold :NameSpace
					insert tail String ML Value
					pop Stack
					)
				| none!	; Ignore 'none values.
				| set Value any-type! (append String Value)
				]
			end
			]
		String
		]
	]

In less than 200 lines of code, ML can build this powerfull Domain Specific Language. To test it, let’s first create a simple “Hello World” XHTML document (see video demo at the end if you prefer):

In Rebol Console, first type


ml [

Validate and then type on second line:


html [

Remark that Rebol automatically indents your input like this


>> ML [
[    html [

Now copy and paste the head section in Rebol Console:


head [
title "Hello World"
]

This should output


>> ML [
[    html [
[        head [
[            title "Hello World"
[            ]
[

Now copy and paste the body section:


body [
h1 "Hello World"
]

to get this output:


>> ML [
[    html [
[        head [
[            title "Hello World"
[            ]
[        body [
[            h1 "Hello World"
[            ]
[        ]
[

Then validate and finish by just closing the final bracket like this:


]

You would then get the html output:


>> ML [
[    html [
[        head [
[            title "Hello World"
[            ]
[        body [
[            h1 "Hello World"
[            ]
[        ]
[    ]
== {

Hello World

}

Now let’s create a real-world xml document for creating this Visual Studio Snippet Code:


<pre><code>
<CodeSnippets
    xmlns="http://schemas.microsoft.com/VisualStudio/2008/CodeSnippet">
    <CodeSnippet Format="1.0.0">
        <Header>
            <Title>
                My Snippet
            </Title>
        </Header>
        <Snippet>
            <References>
                <Reference>
                    <Assembly>System.Windows.Forms.dll</Assembly>
                </Reference>
            </References>
            <Code Language="VB">
                <![CDATA[MessageBox.Show("Hello World")]]>
            </Code>
        </Snippet>
    </CodeSnippet>
</CodeSnippets>

</code></pre>

As you can see the CDATA section doesn’t fit the usual ML syntax. Fortunately the Rebol Compose/deep function can do the trick (Copy and paste the code below into Rebol Console):


write clipboard:// ML compose/deep [
  CodeSnippets/xmlns "http://schemas.microsoft.com/VisualStudio/2008/CodeSnippet" [
    CodeSnippet/Format "1.0.0" [
      Header [
        Title "My Snippet"
      ]
      Snippet [
        References [
          Reference [
            Assembly "System.Windows.Forms.dll"
          ]
        ]
        Code/Language "VB" [
        ({<![CDATA[MessageBox.Show("Hello World")]]>})
        ]
      ]
    ]
  ]
]

This should ouput this in Rebol Console:


>> write clipboard:// ML compose/deep [
[      CodeSnippets/xmlns "http://schemas.microsoft.com/VisualStudio/2008/
CodeSnippet" [
[            CodeSnippet/Format "1.0.0" [
[                  Header [
[                        Title "My Snippet"
[                      ]
[                  Snippet [
[                        References [
[                              Reference [
[                                    Assembly "System.Windows.Forms.dll"
[                                  ]
[                            ]
[                        Code/Language "VB" [
[                            ({<![CDATA[MessageBox.Show("Hello World")]]>}
)
[                            ]
[                      ]
[                ]
[          ]
[    ]
>>

Pasting the clipboard into Notepad should give this:


<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2008/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header><Title>My Snippet</Title></Header><Snippet>
<References>
<Reference><Assembly>System.Windows.Forms.dll</Assembly></Reference></References><Code Language="VB"><![CDATA[MessageBox.Show("Hello World")]]></Code></Snippet></CodeSnippet></CodeSnippets>

The wrapping is not very nice so you may want to reformat it with this online tool (could some Rebol Guru craft one ;) ):


<?xml version="1.0"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2008/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Title>My Snippet</Title>
    </Header>
    <Snippet>
      <References>
        <Reference>
          <Assembly>System.Windows.Forms.dll</Assembly>
        </Reference>
      </References>
      <Code Language="VB">
        <![CDATA[MessageBox.Show("Hello World")]]>
      </Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

Bookmark and Share

Recent Articles