Decorate XML

This simple program copies decorates one xml file with another. Two files are input. One is the decoration for the other. It looks for lines in the decoration that contain a string like [decorated body goes here]. When it finds one, that string is replaced by the body of the other file, the decorated file. Such a sting is called a directive. Below is the source.

# Decorate the input.
# Usage: ruby -s decoratexml.rb -with=decore.xhtml [input]
# The decore is copied to the output.
# Strings in the decore like [decorated tag goes here]
# are replaced with the tagged section of the input.
# Strings in the decore like [date goes here]
# are replaced with the current date in yyyy-mm-dd form.
# Strings in the decore like [global var goes here]
# are replaced with the value of global variable $var.
# e.g. ruby -s decoratexml.rb -var=value ...
# Strings in the decore like [env var goes here]
# are replaced with the value of environment variable $var.
# Strings in the decore like [system command goes here]
# are replaced with the stdout of running "command".
# Such strings are called "directives".
# Directives cannot span lines. i.e. they must be fully in one line.
# There can be multiple directives in one line.
# Directives cannot nest or overlap.

require 'rexml/document'

decore = nil
decore = $with if $with
source = STDIN
source = File.open( ARGV[0] ) if 0 < ARGV.length

if ! decore
  puts "usage: ruby -s decoratexml.rb -with=decore.xhtml [input]"
  exit
end
if true == decore
  puts "usage: ruby -s decoratexml.rb -with=decore.xhtml [input]"
  exit
end

$xml = REXML::Document.new(source)

#global_variables.each { |g| puts g }

#wanted = "$with"
#puts eval wanted

def replacement(theLine)
  if ( m = /\[decorated ([^\] ]*) goes here\]/.match theLine )
    selector = m[1]
    if ( m2 = /([^.#]+)\.(.+)/.match selector )
      tag = m2[1]
      tagclass = m2[2]
      ea = $xml.elements.to_a( "//#{tag}[@class='#{tagclass}']" )
      section = ea.first
    elsif ( m2 = /([^.#]+)#(.+)/.match selector )
      tag = m2[1]
      tagname = m2[2]
      ea = $xml.elements.to_a( "//#{tag}[@name='#{tagname}']" )
      if ea.size < 1
        ea = $xml.elements.to_a( "//#{tag}[@id='#{tagname}']" )
      end
      section = ea.first
    else
      section = $xml.elements["//#{selector}"]
    end
    if section
      return m.pre_match +
           section.text +
           section.elements.each { |theElement| theElement.to_s }.join( "\n" ) +
           m.post_match
    else
      return theLine
    end
  elsif ( m = /\[date goes here\]/.match theLine )
    return m.pre_match + Time.now.strftime( "%Y-%m-%d" ) + m.post_match
  elsif ( m = /\[global ([^ ]*) goes here\]/.match theLine )
    g = eval "$" + m[1]
    return theLine unless g
    return m.pre_match + g + m.post_match if g
  elsif ( m = /\[env ([^ ]+) goes here\]/.match theLine )
    e = ENV[m[1]]
    return theLine unless e
    return m.pre_match + e + m.post_match if e
  elsif ( m = /\[system ([^\[\]]+) goes here\]/.match theLine )
    # This bit is problematic. We cannot just return a string.
    # So in this case there is the side effect
    # of the print and the system function writing to stdout.
#   return m.pre_match + %x{#{m[1]}} + m.post_match
    # The prefix (before the match is printed directly (without a newline).
    print m.pre_match
    # Then the stdout from the system command goes directly to stdout.
    system m[1]
    # Only the suffix (after the match) is returned.
    return m.post_match
  else
    return theLine
  end
  return "ERROR"
end

File.open( decore ).each { |theLine|
  ourLine = theLine
  newLine = replacement(ourLine)
  while newLine != ourLine
    ourLine = newLine
    newLine = replacement(ourLine)
  end
  puts newLine
}

Look at the Makefile in this directory to see one way it can be used. We export some shell variables that are used within decoratexml.html.uc. This way all the file names appear in the Makefile and none are hidden in decoratexml.html.uc. Ubercat.rb is then applied to create decoratexml.html. At last decore.xhtml is applied by decoratexml.rb to create decoratexml.xhtml.

Notice that all pages, those being decorated and those used as decoration as well as the result, should be readily readable and understandable when viewed in a browser.

Notice also that decoration can be nested. c.f. SiteMesh (2013-01-08: Not sure what I meant by this. Maybe that one could generate a template from another one since unreplaced directives should remain in the result. Hence, one could replace some directives with a first run and then others with a second run. One run being more widely usable and one more specialized. Nesting directives like [decorated [global ourTag goes here] goes here] does work, but not the system directive.)

The tag in the syntax [decorated tag goes here] is like a CSS selector. So I'm going to call it a "selector". There are only a few forms it can take:

Other Insertions

Future

Helpful might be:

Maybe the terms should be changed:

undecorated
The raw file that will be decorated.
decore
The file that will be wrapped around the undecorated file.
decorated
The result

So the undecorated + the decore -> the decorated. This will allow us to refer in the decore to both the undecorated and the decorated. e.g. we might want the file name of the decorated in the footer. Hence there might be both "[undecorated body goes here]" and "[decorated filename goes here]".

Perhaps a syntax like [undecorated attr of selector goes here] would be useful? Or other CSS like selector syntax like .class, tag subtag, etc.?

We could add some other stuff to make it a bit like a poor man's XSLT. Note that we want to continue the idea that every page stands on it's own, and can be reasonably viewed and understood in a web browser.

Maybe strip any mock up division (<div class="mockup">). This would allow web designers to put in mock text to see how a decore might look.