#
# Copyright (C) 2007 Mobio Networks, Inc.
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see .
#
require 'builder/xmlmarkup'
require 'rmobio/rxml/base_transformer'
require 'singleton'
module Rmobio
module Rxml
=begin rdoc
Provide a transformer to translate rxml document to xforms markup for Mobio client.
It subclasses BaseTransformer class and overwirtes the tag methods to produce the
proper xforms markup.
To get xforms transformer, pass the client type 'xf' to TransformerFactory get_transformer method:
require 'rmobio/rxml/transformer_factory'
@xml = TransformerFactory.get_transformer('xf')
or call the instance method to get the instance:
require 'rmobio/rxml/xforms_transformer'
@xml = XformsTransformer.instance
Here is an example rxml view that uses the transformer methods to output xforms document:
img = {:alt=>"Rails", :xstyle=>'height="5ex" width="100%"'}
@xml.doctimgype(xml) do |x|
@xml.body(x, 'mobio') do|body|
@xml.image(body, "img1", 'http://localhost:3000/images/rails.png',img)
@xml.softBr(body)
@xml.text(body, 'My test app')
end
end
The above code generates the following xforms for Mobio runner:
http://localhost:3000/images/rails.png
My test app
A rails icon and some text will be displayed on Mobio runner when you load the rxml page.
=end
class XformsTransformer < BaseTransformer
include Singleton
#Generate standdard xforms document header. The model output goes to
#instance variable @model_buffer and view output goes to instance variable
#@view_buffer. Any tag methods in the call block will be directed to either
#view or model buffer (depending on the tag method) and combined to produce
#the final document.
def doctype(xml)
xml << ''
@model_buffer = ''
@model_buffer << "\n"
yield xml
@model_buffer << "\n"
xml << @model_buffer << @view_buffer
xml << ""
@view_buffer = ""
@model_buffer = ""
end
#Generate xthml head and body tag. The title argument is not used for xforms
#client. The style is optional. If style is specified,
#it will be added to the xforms style tag with src attribute:
#
#In rxml:
# @xml.body(x, 'mobio', 'default_style.xml')
#
#generates the following xforms markup:
#
#
#Please note, by default when client sees the src attribute, it is expecting
#the response content to be sent as 'application/xml' not 'application/mform' (for
#complete xforms document). For the styles to work, you will need to handle
#the url so it sends back the xml content with http content-type header set
#to 'application/xml'.
#
#An easier way to include external styles is to output the styles using
#builder xml (not transformer @xml) variable:
#
#==== Examples
# @xml.body(x, 'mobio') do |body|
# xml << render(:partial=> 'default_style.xml')
#The content in the style xml (note, it will be _default_style.xml) will
#then be embeded in the final document.
def body(doc, title="", style=nil)
# xforms style is external style
@view_buffer << '' if style
@view_buffer << "\n"
yield doc
@view_buffer << ""
end
#Produce a text string. The text displayed can be the _txt_ argument if there's
#no :id and :xpath options or it can be the data from model instance specified
#in :id and :xpath.
#
#==== Options
#
#* :xstyle -- specifies xforms style attributes like height, width, style,\
#etc. as string.
#* :id -- specifies the instance id of string. \
#If :id is specified but nso :xpath, the method creates an instance data in \
#@model_buffer with a tag name "text" that contains the _txt_ string.\
#If :id and :xpath are both specified, the method doesn't create any model instance,\
#instead, it assumes there's an instance with the id and xpath already defined\
#and the text widget displays the data in that xpath.
#* :xpath -- specifies the xpath of the text string in the instance model.
#
#==== Examples
#* Text widget displays the string of _txt_ argument without creating an instance:
# @xml.text(body, 'My test', :xstyle=>'style="white"')
#
#generates the following xforms:
# My test
#The outupt is a one line text.
#* Text widget creates a model instance and displays the string from the new instance:
# @xml.text(body, 'My test',
# :id=>'txt1',
# :xstyle=>'style="white"')
#generates the following xforms:
#In model:
# My test
#In view:
#
#* Text widget displays string from a pre defined instance:
# @xml.text(body, 'My test', :id=>'txt1', :xpath=>'images/id')
#generates the following xforms:
#
def text(doc, txt="", options={})
# If there's id but no xpath, we will create a default instance data for
# the textoutput.
if options[:id] and options[:xpath].nil?
@model_buffer << "\n" << '' << txt << "\n"
end
if options[:xstyle]
@view_buffer << "\n"
else
@view_buffer << ">" << txt << ""
end
end
#Produce a textoutput widget (multi line text box).
#The text area is read only for xforms client.
#
#==== Options
#
#* :xstyle -- specifies xforms style attributes like height, width, style, \
#etc. as string.
#* :id -- specifies the instance id of string.
#If :id is specified but no :xpath, the method creates an instance data in
#@model_buffer with a tag name "text" that contains the _txt_ string.
#If :id and :xpath are both specified, the method doesn't create any model instance,
#instead, it assumes there's an instance with the id and xpath already defined
#and the textoutput widget displays the data in that xpath.
#* :xpath -- specifies the xpath of the text string in the instance model.
#
#==== Examples
# @xml.textarea(body, 'My test', {:xstyle=>'style="white" height="2ex"'})
#
#generates the following xforms:
# My test
#The output is a 5 character high text box.
#
#==== Examples
#* Text widget displays the string of _txt_ argument without creating an instance:
# @xml.textarea(body, 'My test box', :xstyle=>'height="3ex"')
#
#generates a 3 line text box:
# My test box
#* Text widget creates a model instance and displays the string from the new instance:
# @xml.textarea(body, 'My test box',
# :id=>'txt1', :xstyle=>'height="3ex"')
#generates the following xforms:
#In model:
# My test
#In view:
#
#* Text widget displays string from a pre defined instance:
# @xml.textarea(body, 'My test', :id=>'txt1', :xpath=>'images/id',
# :xstyle=>'height="5ex" width="50%"')
#generates the following xforms:
#
#
def textarea(doc, txt="", options={})
# If there's id but no xpath, we will create a default instance data for
# the textoutput.
if options[:id] and options[:xpath].nil?
@model_buffer << "\n" << '' << txt << "\n"
end
@view_buffer << "\n"
else
@view_buffer << ">" << txt << ""
end
end # textarea
#
#Create user input field.
#1. id: The instance id that will be associated with this input field. An instance data \
#will be created automatically in the model if options[:xpath] is not specified. \
#The instance is created with only one tag: 'txt'.
#2. value: initial value that will be displayed when ui is loaded
#3. type: not used for xforms client
#==== Options
#* :xstyle -- specifies xforms style attributes as a string
#* :xpath -- specifies the xpath of the input data in the model
#==== Examples
#* Create a simple input box:
# @xml.input(body, "name", "john", "text")
#generates the following xforms:
#
#In Model section of the form:
#
# john
#
#In View section of the form:
#
#Notice the instance data is created automatically because the rxml tag
#didn't specify the :xpath argument. Mobio runner will display an input box with default
#value "john" in the box when the page is launched. User can edit the
#string in the box and the value will be assinged to the model instance('name')/txt.
#
#* The next example shows how to create an input tag with predefined instance data:
#Note, create the options hash before the beginning of the document)
# options = {
# :style=> 'style="white"',
# :xstyle=>'maxlength="10" style="white"',
# :xpath=>"images/id"}
# @xml.doctype(xml) do |x|
# @xml.body(x, 'mobio', 'default_style.xml') do|body|
# @xml.input(body, "img", "", "text", options)
# end
# end
#
#The above rxml generates only the view part in the xforms:
# ' << value << "\n"
end
@view_buffer << "\n"
else
@view_buffer << options[:xpath] << "\"/>"
end
end
#Implement xforms in view buffer and tag in model buffer
#If argument 'create_instance' is set to true, it will create an instance
#based on the attributes (see options). Otherwise, it assumes an instance is
#already defined in the model. The submit_tag can not act along, it has to be
#associated with a tag with UI (like link tag, button or menu item where
#user can click on).
#
#1. id: the name of the model instance that contains the submission defintion
#2. create_instance: true or false, create submission model instance or not.
#If set to true, a new model instance will be created.
#==== Options
#To create submission model, the following attributes should be specified in options:
#* :action -- specifies the url for the action
#* :req_id -- specifies the instance id to hold the submission data
#* :replace_id -- if specified, the submission replace attribute is\
#set to "instance"; otherwise, the submission replace attribute is set to "all". \
#In case of "all", the client is expecting a complete xforms document in \
#response. If it's "instance", only the request_id instance data is updated \
#with the response data. Note the response header should be set to \
#"application/xml" instead of "application/xforms" for "instance" replace.\
#See Mobio client user manual for defails.\
#* :event -- specifies event type to trigger the action. \
#If not defined, "DOMActivate" will be used.
#==== Examples
#* First example demonstrates a submit tag (wrapped in link widget) that \
#creates a submit action and submission instance. When user clicks on the link, \
#the response will replace only the instance data in "results". The UI \
#doesn't change.
#
#Create two instances for the submission to hold the request and replace data:
# (Request data)
# @xml.instance_tag(body, "name") do |y|
# @xml.fname
# end
#
# (Instance to hold the response data)
# @xml.instance_tag(body, "results")
#
#Use link to provide a UI for user to activate the submission, specify true for
#create_instance argument so a new submission instance data can be created:
#
# @xml.link(body, "http://m.twitter.com", "m.twitter.com") do |link|
# @xml.submit_tag(link, "login", true,
# :action=>'login', :req_id=>'name', :replace_id=>'results')
# end
#generates the following xforms:
#
#In the model:
#
#
#In the view:
#
#
#* In this example, we will create a submit tag that creates just the submit button\
# and use an existing submission instance to invoke the action. The response \
# is a complete xforms document which replaces the whole UI:
#
# @xml.link(body, "http://m.twitter.com", "m.twitter.com") do |link|
# @xml.submit_tag(link, "login", false,
# :action=>'login', :req_id=>'name', :replace_id=>'results')
# end
#
#generates only a view:
#
#The submission instance with id "login" must exist for the button to work.
#
def submit_tag(doc, id, create_instance, options={}, &block)
# The model
if create_instance
@model_buffer << "\n'
end
# The view
@view_buffer << "\n"
if block
yield doc
end
@view_buffer << ""
end
#Not supported for xforms client. Just pass the block. Xforsm client should
#use instance_tag, submit_tag for form submission.
def form(doc, id, action, method)
yield doc
end
#There's no link widget in Mobio client so use a button widget to provide
#the same feature for html link. If href is provided and there's no call block,
# will be used to invoke the 'href' url. Otherwise, the call block
#will be executed and template can provide specifal action using the action tag.
#or view_tag.
#Default event type is ev:event="DOMActivate" for the button.
#1. href: the http get url when user press the button
#2. txt: label displayed on the button
#==== Options
##* :xstyle -- specifies xforms style attributes like height, width, style, \
#etc. as string.
#
#==== Examples
#* Simple link:
# @xml.link(body, "http://m.twitter.com", "Twitter")
#generate the following xforms:
# Twitter
#
#
#* Customize action:
# @xml.link(body, "http://m.twitter.com", "Twitter") do |link|
# @xml.view_tag(link, '')
# end
#generates the following xforms:
# Twitter
#
#
#
def link(doc, href, txt="", options={}, &block)
@view_buffer << "\n' << txt << ''
# If no action provided, use to load the url
if block
yield doc
else
@view_buffer << ''
end
@view_buffer << ''
end
#Output arbitray stuff in the model buffer. A workaround for any special xforms
#tags that are not supported yet in the transformer.
def model_tag(doc, txt)
@model_buffer << txt
end
#Output arbitray stuff in the view buffer. A workaround for any special xforms
#tags that are not supported yet in the transformer.
def view_tag(doc, txt)
@view_buffer << txt
end
#Line break
def softBr(doc)
@view_buffer << ''
end
#Create image widget. There are two types of image control: icon and image.
#See Mobio client user manual for details.
#1. id: name of the widget
#2. src: the url of the image
#3. options: all other attributes
#==== Options
#* :xstyle -- specifies xforms style attributes like height, width, style, \
#etc. as string.
#* :widget -- specifies the widget to use, "icon" or "image". Default\
#is .
#
#==== Examples (
#* Create a simple image control\
#Note, define the style options outside the doctype block):
# img = {:alt=>"Rails", :xstyle=>'height="5ex" width="100%"'}
# @xml.doctype(xml) do |x|
# @xml.body(x, 'mobio', 'default_style.xml') do|body|
# @xml.image(body, "img1", 'http://localhost:3000/images/rails.png',img)
# end
# end
#generates the following xforms:
# http://localhost:3000/images/rails.png
#The image will be scaled to 5 character high on the screen when the page is loaded.
#* This example creates an icon widget which takes the image url from an \
#instance variable:
# @xml.image(body, "img1", 'http://localhost:3000/images/rails.png',
# {:xstyle=>'height="3ex" width="20em"',
# :xpath=>"images/url",
# :widget=>"icon"})
#generates the following xforms:
#
#The icon takes the url from an instance named "img1" with xpath /images/url.
#The instance data has to be defined for the image to be displayed.
def image(doc, id, src, options={})
widget = "image"
widget = options[:widget] if options[:widget]
@view_buffer << "\n"
else
@view_buffer << src << "\">"
end
else
@view_buffer << '>' << src
end
@view_buffer << ""
end
#Create an instance in the model
#1. id: the name of the instance that will be created
#==== Examples
#* Create a blank instance:
# @xml.instance_tag(body, "foo")
#generates the following xforms in the model:
#
#
#* Combine instance_tag with any xml tag to create a useful instance model:
# @xml.instance_tag(body, "name") do |y|
# @xml.fname
# @xml.lname("Doe")
# @xml.info("Basic", :zip=>"95014", :phone=>"415-1111111")
# end
#generates the instance data in the model:
#
#
#
# Doe
# Basic
#
#
def instance_tag(doc, id, &block)
@model_buffer << "\n"
if block
yield doc
end
@model_buffer << ''
end
#Create grid control, similar to table in html
#1. style: html styles
#2. xstyle: xforms styles
def table(doc, style="", xstyle="")
@view_buffer << "\n"
yield doc
@view_buffer << ''
end
def table_row(doc, style="", xstyle="")
@view_buffer << "\n"
yield doc
@view_buffer << ''
end
def table_cell(doc, style="", xstyle="")
@view_buffer << ""
yield doc
@view_buffer << ''
end
#Override builder method_missing method to send output to model buffer so we
#can create arbitrary xml tags in the model. Typically combine with instance_tag
#to create model instance that can be used in any of the action tag (link, submit_tag, etc.)
#
#====Examples
# @xml.instance_tag(body, "user") do |y|
# @xml.fname
# @xml.lname("Doe")
# @xml.info("Basic", :zip=>"95014", :phone=>"415-1111111")
# end
#generates an instance data in the model:
#
#
#
# Doe
# Basic
#
#
def method_missing(sym, *options, &block)
text = nil
attrs = nil
sym = "#{sym}:#{options.shift}" if options.first.kind_of?(Symbol)
options.each do |arg|
case arg
when Hash
attrs ||= {}
attrs.merge!(arg)
else
text ||= ''
text << arg.to_s
end
end
if block
unless text.nil?
raise ArgumentError, "XmlMarkup cannot mix a text argument with a block"
end
_indent
_start_tag(sym, attrs)
_newline
_nested_structures(block)
_indent
_end_tag(sym)
_newline
elsif text.nil?
_indent
_start_tag(sym, attrs, true)
_newline
else
_indent
_start_tag(sym, attrs)
text! text
_end_tag(sym)
_newline
end
end
private
def _text(text)
@model_buffer << text
end
# Start an XML tag. If end_too is true, then the start
# tag is also the end tag (e.g.
def _start_tag(sym, attrs, end_too=false)
@model_buffer << "<#{sym}"
_insert_attributes(attrs)
@model_buffer << "/" if end_too
@model_buffer << ">"
end
# Insert an ending tag.
def _end_tag(sym)
@model_buffer << "#{sym}>"
end
# Insert the attributes (given in the hash).
def _insert_attributes(attrs, order=[])
return if attrs.nil?
order.each do |k|
v = attrs[k]
@model_buffer << %{ #{k}="#{_attr_value(v)}"} if v # " WART
end
attrs.each do |k, v|
@model_buffer << %{ #{k}="#{_attr_value(v)}"} unless order.member?(k) # " WART
end
end
# End builder methods
end
end
end