[Rg 99] [PATCH] XSLT template support

Stephan Maka stephan at spaceboyz.net
Fri Sep 21 19:10:48 EDT 2007


Hi,

this is preliminary support for XSLT templates, along with specs and an
example. The extFunctions code is rather complicated and I could switch
to Facets' Functor pattern, if that dependency is allowed.

Please comment,
Stephan
-------------- next part --------------

New patches:

[Ramaze support for XSLT templates, example
stephan at spaceboyz.net**20070920011044] {
addfile ./examples/templates/template/external.xsl
hunk ./examples/templates/template/external.xsl 1
+<?xml version="1.0" encoding="utf-8"?>
+<xsl:stylesheet version="1.0"
+		xmlns="http://www.w3.org/1999/xhtml"
+		xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+		exclude-result-prefixes="xsl">
+
+<xsl:output method="xml"
+            version="1.0"
+            encoding="utf-8"
+            doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"
+            doctype-system="DTD/xhtml1-strict.dtd"
+            indent="yes"/>
+
+  <xsl:template match="/page">
+    <html>
+      <head>
+	<title>
+	  <xsl:value-of select="@title"/>
+	</title>
+      </head>
+      <body>
+	<xsl:apply-templates/>
+      </body>
+    </html>
+  </xsl:template>
+
+  <xsl:template match="heading">
+    <h1>
+      <xsl:apply-templates/>
+    </h1>
+  </xsl:template>
+
+  <xsl:template match="list">
+    <ul>
+      <xsl:apply-templates/>
+    </ul>
+  </xsl:template>
+
+  <xsl:template match="list/item">
+    <li>
+      <xsl:apply-templates/>
+    </li>
+  </xsl:template>
+
+  <xsl:template match="link">
+    <a href="{@href}">
+      <xsl:apply-templates/>
+    </a>
+  </xsl:template>
+
+  <xsl:template match="text">
+    <p>
+      <xsl:apply-templates/>
+    </p>
+  </xsl:template>
+
+</xsl:stylesheet>
addfile ./examples/templates/template_xslt.rb
hunk ./examples/templates/template_xslt.rb 1
+#          Copyright (c) 2006 Michael Fellinger m.fellinger at gmail.com
+# All files in this distribution are subject to the terms of the Ruby license.
+
+require 'ramaze'
+require 'ramaze/gestalt'
+
+include Ramaze
+
+class MainController < Controller
+  template_root __DIR__/:template
+  trait :engine => Template::XSLT
+
+  def index
+    redirect R(:external)
+  end
+
+  def external *args
+    r = lambda { |*a| R(*a) }
+
+    #options = {:place => :internal, :action => 'internal',
+    #  :args => args, :request => request, :this => self}
+    Ramaze::Gestalt.build do
+      page(:title=>"Template::XSLT") do
+        heading "The external Template for XSLT"
+        text "Here you can pass some stuff if you like, parameters are just passed like this:"
+        list do
+          item {
+            link(:href => r.call(@this, :external, :one)) { "external/one" }
+          }
+          item {
+            link(:href => r.call(@this, :external, :one, :two, :three)) { "external/one/two/three" }
+          }
+          item {
+            link(:href => r.call(@this, :external, :one, :foo => :bar)) { "external/one?foo=bar" }
+          }
+        end
+        text "The arguments you have passed to this action are:"
+        if args.empty?
+          text "none"
+        else
+          list {
+            args.each do |arg|
+              item arg
+            end
+          }
+        end
+      end
+    end
+  end
+end
+
+Ramaze.start
hunk ./lib/ramaze/template.rb 19
-    %w[ Amrita2 Erubis Haml Liquid Markaby Remarkably Sass ].each do |const|
+    %w[ Amrita2 Erubis Haml Liquid Markaby Remarkably Sass XSLT ].each do |const|
addfile ./lib/ramaze/template/xslt.rb
hunk ./lib/ramaze/template/xslt.rb 1
+require 'xml/libxml'
+require 'xml/xslt'
+
+module Ramaze
+
+  # Use the Gestalt helper to put your controller result
+  # into proper XML form
+  #
+  # TODO:
+  # * Error handling
+  # * Support for XML::XSLT::extFunction
+  # * Non-fatal failure when missing Ruby-XSLT
+  module Template
+    class XSLT < Template
+      ENGINES[self] = %w[ xsl ]
+
+      class << self
+
+        # Entry point for Action#render
+
+        def transform action
+          result, file = result_and_file(action)
+
+          xslt = XML::XSLT.new
+          xslt.xsl = action.template
+          xslt.xml = result
+          xslt.serve
+        end
+
+      end
+    end
+  end
+end
}

[XSLT templates: add extFunctions capability
stephan at spaceboyz.net**20070921115648] {
adddir ./spec/ramaze/template/xslt
hunk ./lib/ramaze/template/xslt.rb 3
+require 'thread'
hunk ./lib/ramaze/template/xslt.rb 11
+  # * Complex extFunction return values
hunk ./lib/ramaze/template/xslt.rb 13
-  # * Support for XML::XSLT::extFunction
-  # * Non-fatal failure when missing Ruby-XSLT
+  # * Maybe prevent extFunction to be called by HTTP
hunk ./lib/ramaze/template/xslt.rb 18
+      XSLT_EXT_FUNCTIONS_LOCK = Mutex.new
+
hunk ./lib/ramaze/template/xslt.rb 25
+
+          if options = action.instance.ancestral_trait[:xslt_options] and
+              fun_xmlns = options[:fun_xmlns]
+            # If a controller uses extFunctions, lock the whole
+            # transform action with a Mutex to prevent mixing
+            # extFunction binding and callback of two controller
+            # instances with one fun_xmlns.
+            ext_functions_synchronize do
+              register_ext_functions action.instance, fun_xmlns
+              do_transform action
+            end
+
+          else
+            do_transform action
+          end
+
+        end
+            
+        private
+
+        def do_transform(action)
hunk ./lib/ramaze/template/xslt.rb 53
+
+        def ext_functions_synchronize &block
+          Inform.debug "Locking extFunctions Mutex #{XSLT_EXT_FUNCTIONS_LOCK.inspect}"
+          XSLT_EXT_FUNCTIONS_LOCK.synchronize &block
+        end
+
+        def register_ext_functions instance, fun_xmlns
+
+          instance.methods.each do |method|
+            if method =~ /^xslt_.+/
+              method_name = method[5..-1]
+              
+              proxy_instance = make_functor(method_name.intern) { |*a|
+                instance.send method.intern, *a
+              }
+              
+              XML::XSLT.extFunction method_name.gsub('_', '-'), fun_xmlns, proxy_instance
+            end
+          end
+
+        end
+
+        def make_functor(m, &block)
+          # Create anonymous class and instantiate;
+          # anonymous because only this one object with this
+          # particular custom method should call &block
+          o = Class.new.new
+
+          class << o
+            def create_method(name, &block)
+              self.class.send(:define_method, name, &block)
+            end
+          end
+
+          o.create_method(m, &block)
+          o
+        end
hunk ./spec/ramaze/gestalt.rb 9
+  # This is useful for any controller using Gestalt,
+  # should be made a MixIn somewhen.
addfile ./spec/ramaze/template/xslt.rb
hunk ./spec/ramaze/template/xslt.rb 1
+#          Copyright (c) 2006 Michael Fellinger m.fellinger at gmail.com
+# All files in this distribution are subject to the terms of the Ruby license.
+
+require 'spec/helper'
+
+testcase_requires 'xml/xslt'
+testcase_requires 'ramaze/gestalt'
+
+class TCTemplateXSLTController < Ramaze::Controller
+  template_root 'spec/ramaze/template/xslt/'
+  trait :engine       => Ramaze::Template::XSLT
+  trait :xslt_options => { :fun_xmlns => 'urn:test' }
+
+  def index
+    gestalt {
+      hi 'tobi'
+    }
+  end
+
+  def ruby_version
+    @version = RUBY_VERSION
+
+    gestalt {
+      document
+    }
+  end
+
+  def xslt_get_ruby_version
+    @version
+  end
+
+  private
+
+  def gestalt &block
+    Ramaze::Gestalt.new(&block).to_s
+  end
+
+end
+
+describe "XSLT" do
+  ramaze(:mapping => {'/' => TCTemplateXSLTController})
+
+  it "index" do
+    get('/').body.should == "hi tobi"
+  end
+
+  it "ruby_version through external functions" do
+    get('/ruby_version').body.should == RUBY_VERSION
+  end
+end
+
addfile ./spec/ramaze/template/xslt/index.xsl
hunk ./spec/ramaze/template/xslt/index.xsl 1
+<?xml version="1.0" encoding="utf-8"?>
+<xsl:stylesheet version="1.0"
+		xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+  <xsl:output method="text"
+              encoding="utf-8"/>
+
+  <xsl:template match="/*">
+    <xsl:value-of select="name(.)"/>
+    <xsl:text> </xsl:text>
+    <xsl:apply-templates/>
+  </xsl:template>
+
+</xsl:stylesheet>
addfile ./spec/ramaze/template/xslt/ruby_version.xsl
hunk ./spec/ramaze/template/xslt/ruby_version.xsl 1
+<?xml version="1.0" encoding="utf-8"?>
+<xsl:stylesheet version="1.0"
+		xmlns:test="urn:test"
+		xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+		exclude-result-prefixes="xsl">
+
+  <xsl:output method="text"
+              encoding="utf-8"/>
+
+  <xsl:template match="/document">
+    <ruby-version><xsl:value-of select="test:get-ruby-version()"/></ruby-version>
+  </xsl:template>
+
+</xsl:stylesheet>
}

[XSLT template spec: two more examples
stephan at spaceboyz.net**20070921130029] {
hunk ./spec/ramaze/template/xslt.rb 8
+testcase_requires 'rexml/document'
hunk ./spec/ramaze/template/xslt.rb 33
+  def products
+    gestalt {
+      order {
+        first
+        items
+      }
+    }
+  end
+
+  def xslt_get_products
+    REXML::Document.new \
+    gestalt {
+      list {
+        %w[Onion Bacon].each { |product|
+          item product
+        }
+      }
+    }
+  end
+
+  def concat_words
+    gestalt {
+      document
+    }
+  end
+
+  def xslt_concat(*args)
+    args.to_s
+  end
+
hunk ./spec/ramaze/template/xslt.rb 81
+
+  it "external functions returning XML data" do
+    get('/products').body.
+      gsub(/<\?.+\?>/, '').strip.
+      should == '<result><first>Onion</first><item>Onion</item><item>Bacon</item></result>'
+  end
+
+  it "parameters" do
+    get('/concat_words').body.should == 'oneonetwoonetwothree'
+  end
addfile ./spec/ramaze/template/xslt/concat_words.xsl
hunk ./spec/ramaze/template/xslt/concat_words.xsl 1
+<?xml version="1.0" encoding="utf-8"?>
+<xsl:stylesheet version="1.0"
+		xmlns:test="urn:test"
+		xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+		exclude-result-prefixes="test xsl">
+
+  <xsl:output method="text"
+              encoding="utf-8"/>
+
+  <xsl:template match="/document">
+    <xsl:value-of select="test:concat('one')"/>
+    <xsl:value-of select="test:concat('one', 'two')"/>
+    <xsl:value-of select="test:concat('one', 'two', 'three')"/>
+  </xsl:template>
+
+</xsl:stylesheet>
addfile ./spec/ramaze/template/xslt/products.xsl
hunk ./spec/ramaze/template/xslt/products.xsl 1
+<?xml version="1.0" encoding="utf-8"?>
+<xsl:stylesheet version="1.0"
+		xmlns:test="urn:test"
+		xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+		exclude-result-prefixes="test xsl">
+
+  <xsl:output method="xml"
+              encoding="utf-8"/>
+
+  <xsl:template match="order">
+    <result>
+      <xsl:apply-templates/>
+    </result>
+  </xsl:template>
+
+  <xsl:template match="first">
+    <first>
+      <xsl:value-of select="test:get-products()/item[1]"/>
+    </first>
+  </xsl:template>
+
+  <xsl:template match="items">
+    <xsl:apply-templates select="test:get-products()" mode="list"/>
+  </xsl:template>
+
+  <xsl:template match="item" mode="list">
+    <item>
+      <xsl:apply-templates mode="list"/>
+    </item>
+  </xsl:template>
+
+</xsl:stylesheet>
hunk ./spec/ramaze/template/xslt/ruby_version.xsl 5
-		exclude-result-prefixes="xsl">
+		exclude-result-prefixes="test xsl">
}

[XSLT template example: add Content-Type
stephan at spaceboyz.net**20070921225525] {
hunk ./examples/templates/template_xslt.rb 2
+#           Copyright (c) 2007  Stephan Maka  stephan at spaceboyz.net
hunk ./examples/templates/template_xslt.rb 20
+    response['Content-Type'] = 'application/xhtml+xml'
hunk ./lib/ramaze/template/xslt.rb 1
+#          Copyright (c) 2007  Stephan Maka  stephan at spaceboyz.net
+# All files in this distribution are subject to the terms of the Ruby license.
+
hunk ./lib/ramaze/template/xslt.rb 14
-  # * Complex extFunction return values
hunk ./lib/ramaze/template/xslt.rb 57
-          Inform.debug "Locking extFunctions Mutex #{XSLT_EXT_FUNCTIONS_LOCK.inspect}"
hunk ./lib/ramaze/template/xslt.rb 76
+        # Can be replaced by Ruby Facets' Functor pattern
}

Context:

[Slight 'beautifcation' for specwrapper
Michael Fellinger <m.fellinger at gmail.com>**20070921081739] 
[SourceReload#reload_glob is now SourceReload.trait[:reload_glob] for ultimate control.
Michael Fellinger <m.fellinger at gmail.com>**20070921080937] 
[This improves logging a bit by introducing the :dev tag and so lowering the overall output in default mode.
Michael Fellinger <m.fellinger at gmail.com>**20070921064236] 
[alias Ramaze.contrib to Ramaze::Contrib.load
Aman Gupta <ramaze at tmm1.net>**20070919153655] 
[Gestalt: allow text in arguments, properly escape this text and attributes
stephan at spaceboyz.net**20070920153337] 
[Add $0 to files being sourcereloaded.
Michael Fellinger <m.fellinger at gmail.com>**20070919062417] 
[Implement Global.ignore and spec it.
Michael Fellinger <m.fellinger at gmail.com>**20070919062143] 
[Add Global.cache_alternative so you can specify a different cache-class to use for only certain caches. For example: Ramaze::Global.cache_alternative[:sessions] = Ramaze::MemcachedCache
Michael Fellinger <m.fellinger at gmail.com>**20070918104659] 
[Small improvment to the sequel/fill contrib
Michael Fellinger <m.fellinger at gmail.com>**20070918054112] 
[Add contrib/sequel/fill
Michael Fellinger <m.fellinger at gmail.com>**20070918053330] 
[alias redirect_referer to redirect_referrer in RedirectHelper
Michael Fellinger <m.fellinger at gmail.com>**20070918045439] 
[Allow custom /helper directory in apps, will be searched before ramazes helpers.
Michael Fellinger <m.fellinger at gmail.com>**20070918014843] 
[Add the wikore example and fix spec for wiktacular a little.
Michael Fellinger <m.fellinger at gmail.com>**20070917114503] 
[Improve output of spec wrapper a bit.
Michael Fellinger <m.fellinger at gmail.com>**20070917114458] 
[Adding path for OSX to tool/tidy and improve readability of the spec for it a bit.
Michael Fellinger <m.fellinger at gmail.com>**20070912125255] 
[Fixing dependence on the debug.rb implementation of ruby, since this may vary between different versions/implementations, use gestalt.rb instead, the oldest and most stable file we have.
Michael Fellinger <m.fellinger at gmail.com>**20070912090809] 
[Small beautification/speedup for the mocked http
Michael Fellinger <m.fellinger at gmail.com>**20070911084731] 
[Clean up the controller/resolve part a bit, implement raise_no_filter which throws a NoFilter error now and fix a minor bug that would result in a faulty response if an element of Cache.resolved was no valid action. Added docs for all methods in Controller.
Michael Fellinger <m.fellinger at gmail.com>**20070911162955] 
[Restructuring of how contribs are handled, introducing the Ramaze::Contrib namespace, adding Global.contribs so we can add a unified shutdown in future, fixing routing so it won't try to match an already resolved route again and thereby avoiding recursion.
Michael Fellinger <m.fellinger at gmail.com>**20070911144843] 
[Adding snippet for Array#put_within/put_before/put_after plus specs. docs missing.
Michael Fellinger <m.fellinger at gmail.com>**20070911054158] 
[Updated spec for route
Aman Gupta <ramaze at tmm1.net>**20070910063242] 
[This introduces the first contrib for routes, slight restructuring of Controller::resolve to allow filtering based on Controller::FILTER like we know it from Dispatcher. Added dictionary.rb from facets to allow sorted but hash-like routes-adding. Spec for routes added as small showcase.
Michael Fellinger <m.fellinger at gmail.com>**20070910044521] 
[Add basic Ramaze::contrib as future helping instance for contributed things.
Michael Fellinger <m.fellinger at gmail.com>**20070910044506] 
[make snippets/struct/values_at behaviour compatible with standard ruby (orig. by riffraff)
Michael Fellinger <m.fellinger at gmail.com>**20070907083216] 
[Fix for directory-listing, always sort files/dirs shown
Michael Fellinger <m.fellinger at gmail.com>**20070907083158] 
[TAG 0.1.4
Michael Fellinger <m.fellinger at gmail.com>**20070906135219] 
Patch bundle hash:
057156156e12aba1ef2d9329f3cc99a57d736775
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 189 bytes
Desc: not available
Url : http://rubyforge.org/pipermail/ramaze-general/attachments/20070922/28660443/attachment.bin 


More information about the Ramaze-general mailing list