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