class Wunderbar::XmlMarkup
Attributes
_width[RW]
Public Class Methods
dump(content, args={})
click to toggle source
convenience method for taking an XML node or string and formatting it
# File lib/wunderbar/builder.rb, line 120 def self.dump(content, args={}) markup = self.new(args) if Nokogiri::XML::Document === content and content.root.name == 'html' markup.declare! :DOCTYPE, :html end unless Nokogiri::XML::Node === content if defined? Nokogiri::HTML5.fragment content = Nokogiri::HTML5.fragment(content.to_s) else content = Nokogiri::HTML.fragment(content.to_s) end end markup[content] markup.target! end
new(args={})
click to toggle source
# File lib/wunderbar/builder.rb, line 139 def initialize(args={}) @_scope = args.delete(:scope) @_indent = args.delete(:indent) || Wunderbar.option[:indent] @_width = args.delete(:width) || Wunderbar.option[:width] @_pdf = false @doc = Node.new(nil) @node = @doc @indentation_enabled = true @spaced = false end
Public Instance Methods
<<(data)
click to toggle source
insert verbatim
# File lib/wunderbar/builder.rb, line 317 def <<(data) if defined? Nokogiri if not String === data or data.include? '<' or data.include? '&' # https://github.com/google/gumbo-parser/issues/266 data = Nokogiri::HTML::fragment(data.to_s).to_xml # fix CDATA in most cases (notably scripts) data.gsub!(/<!\[CDATA\[(.*?)\]\]>/m) do if $1.include? '<' or $1.include? '&' "//<![CDATA[\n#{$1}\n//]]>" else $1 end end # fix CDATA for style elements data.gsub!(/<style([^>])*>\/\/<!\[CDATA\[\n(.*?)\s+\/\/\]\]>/m) do if $2.include? '<' or $2.include? '&' "<style#{$1}>/*<![CDATA[*/\n#{$2.gsub("\n\Z",'')}\n/*]]>*/" else $1 end end end end if String === data @node.children << data else @node.add_child data end end
[](*children)
click to toggle source
# File lib/wunderbar/builder.rb, line 350 def [](*children) if children.length == 1 if children.first.respond_to? :root children = [children.first.root] elsif defined? Nokogiri::XML::DocumentFragment and Nokogiri::XML::DocumentFragment === children.first then children = children.first.children end end # remove leading and trailing space if not children.empty? children.shift if children.first.text? and children.first.text.strip.empty? end if not children.empty? children.pop if children.last.text? and children.last.text.strip.empty? end children.map do |child| if child.text? or child.cdata? text = child.text if not @indentation_enabled text! text elsif text.strip.empty? text! "" if text.count("\n")>1 else indented_text! text end elsif child.comment? comment! child.text.sub(/\A /,'').sub(/ \Z/, '') elsif HtmlMarkup.flatten? child.children # disable indentation on the entire element compact! { tag!(child) {self[*child.children]} } elsif child.children.empty? and HtmlMarkup::VOID.include? child.name tag!(child) elsif child.children.all?(&:text?) and child.text tag!(child, @indentation_enabled ? child.text.strip : child.text) elsif child.children.any?(&:cdata?) and child.text =~ /[<&]/ self << child elsif child.name == 'pre' compact! { tag!(child) {self[*child.children]} } elsif child.name == 'head' head = tag!(child) {self[*child.children]} html = @doc.children.last if html.name == :html head.parent.children.pop html.children.unshift head head.parent = html end head elsif not Nokogiri::XML::DTD === child tag!(child) {self[*child.children]} end end end
clear!()
click to toggle source
# File lib/wunderbar/builder.rb, line 206 def clear! @doc.children.clear @node = @doc end
comment!(text)
click to toggle source
# File lib/wunderbar/builder.rb, line 187 def comment! text comment = CommentNode.new(text) @node.children << comment comment end
compact!(&block)
click to toggle source
# File lib/wunderbar/builder.rb, line 211 def compact!(&block) begin indentation_enabled, @indentation_enabled = @indentation_enabled, false block.call ensure @indentation_enabled = indentation_enabled end end
declare!(*args)
click to toggle source
# File lib/wunderbar/builder.rb, line 181 def declare! *args doctype = DocTypeNode.new(*args) @node.children << doctype doctype end
indented_text!(text)
click to toggle source
# File lib/wunderbar/builder.rb, line 193 def indented_text!(text) return if text.length == 0 and not @spaced text = IndentedTextNode.new(text) text.extend SpacedNode if @spaced @node.children << text @spaced = false text end
method_missing(method, *args, &block)
click to toggle source
forward to Wunderbar
or @_scope
Calls superclass method
# File lib/wunderbar/builder.rb, line 153 def method_missing(method, *args, &block) if Wunderbar.respond_to? method Wunderbar.send method, *args, &block elsif @_scope and @_scope.respond_to? method @_scope.send method, *args, &block else super end end
methods()
click to toggle source
Calls superclass method
# File lib/wunderbar/builder.rb, line 163 def methods result = super + Wunderbar.methods result += @_scope.methods if @_scope result.uniq end
pdf=(value)
click to toggle source
# File lib/wunderbar/builder.rb, line 291 def pdf=(value) @_pdf = value end
pdf?()
click to toggle source
# File lib/wunderbar/builder.rb, line 295 def pdf? @_pdf end
render(container, timeout: nil, &block)
click to toggle source
# File lib/wunderbar/render.rb, line 33 def render(container, timeout: nil, &block) csspath = Wunderbar::Node.parse_css_selector(container) root = @node.root # find the scripts and target on the page scripts = root.search('script') target = root.at(container) # compute base base = root.at('base') base = base && base.attrs[:href] base ||= @_scope.env['REQUEST_URI'][/.*\//] _base = @_scope.env['HTTP_X_WUNDERBAR_BASE'] base = base[_base.length..-1] if _base and base.start_with? _base if base == '..' or base.end_with? '/..' base = (Pathname.new(@_scope.env['REQUEST_URI']) + '../' + base).to_s end script = @_scope.env['SCRIPT_NAME'] base = base[script.length..-1] if script and base.start_with? script base = base[1..-1] if base.start_with? '/' # compute client side container element = "document.querySelector(#{container.inspect})" if csspath.length == 1 and csspath[0].length == 1 value = csspath[0].values.first case csspath[0].keys.first when :id element = "document.getElementById(#{value.inspect})" when :class value = value.join(' ') element = "document.getElementsByClassName(#{value.inspect})[0]" when :name element = "document.getElementsByTagName(#{value.inspect})[0]" end end # build client and server scripts options = Wunderbar::Render::RUBY2JS_OPTIONS.merge(scope: @_scope, strict: false) common = Ruby2JS.convert(block, options) server = Wunderbar::Render.server(common) client = Wunderbar::Render.client(common, element, target) # extract content of scripts scripts.map! do |script| result = nil next if Wunderbar::ClientScriptNode === script if script.attrs[:src] src = script.attrs[:src] src = File.join(base, src) if not base.empty? src = src.sub(/\?.*$/, '') # strip queries (typically mtimes) name = File.expand_path(src, @_scope.settings.public_folder) if File.exist? name result = File.read(name) else file = File.expand_path(src+'.rb', @_scope.settings.views) result = Wunderbar::Asset.convert(file) end else result = Ruby2JS.convert(script.block, binding: script.binding) end result end builder = Wunderbar::HtmlMarkup.new({}) setup = [] requires = {} browserify = false Wunderbar::Asset.scripts.each do |script| next unless script.options[:render] setup += script.options[:render] if Array === script.options[:render] requires.merge! script.options[:require] if script.options[:require] browserify = true if script.options[:browserify] if script.contents scripts.unshift script.contents elsif script.path if script.path.start_with? '/' path = ENV['DOCUMENT_ROOT'] + script.path else path = File.expand_path(script.path, Wunderbar::Asset.root) end setup << File.read(script.options[:server] || path) end end # add timeout, if requested # # useful if the rendering itself is inherently synchronous, but may make # use of a library routines that set up event handlers that will never fire. # if timeout server += ";\nsetTimeout(() => {process.exit()}, #{timeout})" end # concatenate and execute scripts on server if browserify setup += requires.map {|key, value| "const #{key}=module.exports.#{key} || require(#{value.inspect})" } end scripts.unshift *setup.uniq html = Wunderbar::Render.eval(scripts, server) # insert results into target nodes = Wunderbar::Render.extract(builder._ { html }) begin if nodes.length == 1 nodes.each {|node| node.parent = target} target.children += nodes else span = Wunderbar::Node.new('span') nodes.each {|node| node.parent = span} span.children += nodes target.children << span end rescue => e span = Wunderbar::Node.new('span', style: 'background-color:#ff0; margin: 1em 0; padding: 1em; ' + 'border: 4px solid red; border-radius: 1em') span.children << Wunderbar::Node.new('pre', e.to_s) span.children << Wunderbar::Node.new('pre', e.backtrace.join("\n")) span.children << Wunderbar::Node.new('pre', html) span.children << Wunderbar::Node.new('pre', nodes.inspect) span.children.each {|node| node.parent = span} target.children << span end # add client side script tag! 'script', Wunderbar::ClientScriptNode, client end
respond_to?(method)
click to toggle source
Calls superclass method
# File lib/wunderbar/builder.rb, line 169 def respond_to?(method) respond true if Wunderbar.respond_to? method respond true if @_scope and @_scope.respond_to? method super end
spaced!()
click to toggle source
# File lib/wunderbar/builder.rb, line 220 def spaced! @spaced = true end
system(*args)
click to toggle source
execute a system command, echoing stdin, stdout, and stderr
Calls superclass method
Wunderbar::BuilderClass#system
# File lib/wunderbar/builder.rb, line 300 def system(*args) opts = {} opts = args.pop if Hash === args.last tag = opts[:tag] || 'pre' output_class = opts[:class] || {} output_class[:stdin] ||= '_stdin' output_class[:stdout] ||= '_stdout' output_class[:stderr] ||= '_stderr' output_class[:hilite] ||= '_stdout _hilite' super(*args, opts) do |kind, line| tag! tag, line, class: output_class[kind] end end
tag!(sym, *args, &block)
click to toggle source
avoid method_missing
overhead for the most common case
# File lib/wunderbar/builder.rb, line 225 def tag!(sym, *args, &block) current_node = @node if sym.respond_to? :children node = sym attributes = node.attributes if node.attribute_nodes.any?(&:namespace) attributes = Hash[node.attribute_nodes.map { |attr| name = attr.name name = "#{attr.namespace.prefix}:#{name}" if attr.namespace [name, attr.value] }] end attributes.merge!(node.namespaces) if node.namespaces args.push attributes if node.namespace and node.namespace.prefix sym = "#{node.namespace.prefix}:#{node.name}" else sym = node.name end unless Class === args.first args.unshift PreformattedNode if sym == 'pre' args.unshift ScriptNode if sym == 'script' args.unshift StyleNode if sym == 'style' end end children = nil if block and block.arity !=0 if args.first and args.first.respond_to? :each children = args.shift end end if Class === args.first and args.first < Node node = args.shift.new sym, *args else node = Node.new sym, *args end node.extend CompactNode unless @indentation_enabled if @spaced node.extend SpacedNode @spaced = false end node.text = args.shift if String === args.first @node.add_child node @node = node if block if children children.each {|child| block.call(child)} else block.call(self) end @node.children << nil if @node.children.empty? end node ensure @node = current_node end
target!()
click to toggle source
# File lib/wunderbar/builder.rb, line 202 def target! "#{@doc.serialize(indent: ' ' * @_indent, width: @_width).join("\n")}\n" end
text!(text)
click to toggle source
# File lib/wunderbar/builder.rb, line 175 def text! text text = TextNode.new(text) @node.children << text text end