#-- # George Moschovitis # (c) 2004-2005 Navel, all rights reserved. # $Id: shaders.rb 202 2005-01-17 10:44:13Z gmosx $ #++ module N # A shader defines a transformation-pipeline that tansforms # the .xhtml files to actual ruby code ready for evaluation # (compilation) by the engine. # # Shaders are equivalend to the render-pipeline filters, ie they # share their folded-filter design. #-- # TODO: pipeline stage mixin to be reused in filters too. #++ class Shader # the next stage in the Shader pipeline. attr :next_stage def initialize(next_stage = nil) @next_stage = next_stage end # Process the text and optionally update the hash. # The hash is a short, unique representation of the input text, # typically used as a caching key. def process(hash, text) process_next(hash, text) end # Set the next stage of the pipeline. def << (next_stage = nil) @next_stage = next_stage return self end # Process the next stage of the pipeline. def process_next(hash, text) if @next_stage return @next_stage.process(hash, text) else return hash, text end end end # Convert the xhtml script to actual Ruby code, ready to be # evaluated. class RubyShader < Shader # Convert the xhtml script to actual Ruby code, ready to be # evaluated. # #-- # Investigate: # perhaps xl:href should be used to be XLink compatible? #++ def process(hash, text) # strip the xml header! (interracts with the following gsub!) text.gsub!(/<\?xml.*\?>/, "") # Statically include sub script files. # The target file is included at compile time. # # gmosx: must be xformed before the text.gsub!(/<\?include href="(.*?)"(.*)\?>/) { |match| # gmosx: xmm match matches the whole string. # match = overload_path($1) load_statically_included("#$root_dir/#$1") } # xform include instructions # must be transformed before the processinc instructions. # Useful to include fragments cached on disk # # gmosx, FIXME: NOT TESTED! test and add caching. # add load_statically_included fixes. text.gsub!(//) { |match| "" } # xform render/inject instructions # must be transformed before the processinc instructions. text.gsub!(//) { |match| "" } text.gsub!(//) { |match| "" } # remove elements. typically removed by xslt but lets # play it safe. text.gsub!(/<(\/)?root>/, '') # runtime ruby code. # xform the processing instructions, use /, "\n@out << %^") text.gsub!(/<\?r(\s?)/, "^\n") # xform alternative code tags (very useful in xsl stylesheets) text.gsub!(/<\/ruby>/, "\n@out << %^") text.gsub!(//, "^\n") # compile time ruby code. # Usefull for example to prevaluate localization. text.gsub!(/\#\[(.*?)\]/) { |match| eval($1) } text = "@out << %^" + text + "^" process_next(hash, text) end # Loads and statically includes a file. def load_statically_included(filename) Logger.debug "Statically including '#{filename}'" if $DBG text = File.read(filename) text.gsub!(/<\?xml.*\?>/, '') text.gsub!(/<\/?root(.*?)>/m, ' '); return text end end # Apply an XSL transformation to the script code. # There is no need to keep post xsl. I can reuse the same xsl # by calling transform again. class XSLTShader < Shader # The name attr :name # The xslt filename. attr :xsl_filename # The xslt transformer. attr :xslt # Last modified time of the xslt. attr :mtime def initialize(xsl_filename, next_stage = nil) # leave this require here. only inlcude the xslt # library if the project needs it. require "xml/xslt" @name = File.basename(xsl_filename, '.*') @xsl_filename = xsl_filename @xslt = XML::XSLT.new @next_stage = next_stage end # Transform the given text. def process(hash, text) parse_xsl() @xslt.xml = text hash += @name process_next(hash, xslt.serve) end private # Parse the xsl. def parse_xsl Logger.debug "Parsing xsl '#{@xsl_filename}'" if $DBG @mtime = File.mtime(@xsl_filename) @xslt.xsl = File.read(@xsl_filename) end end # Compress the inline xhtml. Does not touch the ruby code. class CompressShader < Shader # Compress the inline xhtml. Does not touch the ruby code. def process(hash, text) text.gsub!(/\@out \<\< \%\^(.*?)\^/m) do |match| c = $1.gsub(/^(\s*)/m, '').squeeze(" \t").tr("\n", '').tr("\t", ' ') "@out << %{#{c}}" end process_next(hash, text) end end end