lib/xml/mixup.rb in xml-mixup-0.1.6 vs lib/xml/mixup.rb in xml-mixup-0.1.8

- old
+ new

@@ -302,13 +302,17 @@ # now go over the attributes and set any missing namespaces to nil at.keys.each do |k| p, _ = /^(?:([^:]+):)?(.+)$/.match(k).captures ns[p] ||= nil end + # also do the tag prefix but only if there is a local name ns[prefix] ||= nil if local + # unconditionally remove ns['xml'], we never want it in there + ns.delete 'xml' + # pseudo is a stand-in for non-parent adjacent nodes pseudo = nodes[:pseudo] || nodes[:parent] # now get the final namespace mapping ns.keys.each do |k| @@ -322,11 +326,11 @@ ns.delete(nil) end # there should be no nil namespace declarations now if ns.has_value? nil - raise 'INTERNAL ERROR: nil namespace declaration' + raise "INTERNAL ERROR: nil namespace declaration: #{ns}" end # generate the node node = element name, doc: doc, ns: ns, attr: at, args: args @@ -371,34 +375,41 @@ # @param prefix [Hash] the contents of the root node's +prefix=+ # and +xmlns:*+ attributes. # # @param vocab [#to_s] the contents of the root node's +vocab=+. # - # @param lang [#to_s] the contents of +lang=+ and when applicable, +xml:lang+. + # @param lang [#to_s] the contents of +lang=+ and when applicable, + # +xml:lang+. # # @param title [#to_s, #to_a, Hash] the contents of the +<title>+ # tag. When given as an array-like object, all elements after the # first one will be flattened to a single string and inserted into # the +property=+ attribute. When given as a {Hash}, it will be # coerced into a snippet of spec that produces the appropriate tag. # - # @param link [#to_a, Hash] A spec describing one or more +<link/>+ elements. + # @param link [#to_a, Hash] A spec describing one or more +<link/>+ + # elements. # - # @param meta [#to_a, Hash] A spec describing one or more +<meta/>+ elements. + # @param meta [#to_a, Hash] A spec describing one or more +<meta/>+ + # elements. # # @param style [#to_a, Hash] A spec describing one or more # +<style/>+ elements. # # @param script [#to_a, Hash] A spec describing one or more # +<script/>+ elements. # + # @param extra [#to_a, Hash] A spec describing any extra elements + # inside +<head>+ that aren't in the previous categories. + # # @param attr [Hash] A spec containing attributes for the +<body>+. # # @param content [Hash, Array, Nokogiri::XML::Node, ...] A spec which # will be attached underneath the +<body>+. # - # @param head [Hash] A spec which overrides the entire +<head>+. + # @param head [Hash, Array] A Hash spec which overrides the entire + # +<head>+ element, or otherwise an array of its children. # # @param body [Hash] A spec which overrides the entire +<body>+. # # @param transform [#to_s] An optional XSLT transform. # @@ -416,83 +427,131 @@ # # @return [Nokogiri::XML::Node] the last node generated, in document order. def xhtml_stub doc: nil, base: nil, ns: {}, prefix: {}, vocab: nil, lang: nil, title: nil, link: [], meta: [], style: [], script: [], - head: {}, body: {}, attr: {}, content: [], + extra: [], head: {}, body: {}, attr: {}, content: [], transform: nil, dtd: true, xmlns: true, args: [] spec = [] # add xslt stylesheet if transform spec << (transform.is_a?(Hash) ? transform : - { nil => ['#pi', 'xml-stylesheet'], - type: 'text/xsl', href: transform.to_s }) + { '#pi' => 'xml-stylesheet', type: 'text/xsl', href: transform }) end # add doctype declaration if dtd ps = dtd.respond_to?(:to_a) ? dtd.to_a : [] spec << { nil => %w{#dtd html} + ps } end + # normalize title + + if title.nil? or (title.respond_to?(:empty?) and title.empty?) + title = { '#title' => '' } # add an empty string for closing tag + elsif title.is_a? Hash + # nothing + elsif title.respond_to? :to_a + title = title.to_a + text = title.shift + props = title + title = { '#title' => text } + title[:property] = props unless props.empty? + else + title = { '#title' => title } + end + + # normalize base + + if base and not base.is_a? Hash + base = { nil => :base, href: base } + end + + # TODO normalize link, meta, style, script elements + # construct document tree head ||= {} - if head.respond_to?(:empty) and head.empty? - head[nil] = [:head, title, base, link, meta, style, script] + if head.is_a? Hash and head.empty? + head[nil] = [:head, title, base, link, meta, style, script, extra] + elsif head.is_a? Array + # children of unmarked head element + head = { nil => [:head] + head } end body ||= {} - if body.respond_to?(:empty?) and body.empty? + if body.is_a? Hash and body.empty? body[nil] = [:body, content] + body = attr.merge(body) if attr and attr.is_a?(Hash) end - root = { nil => [:html, [head, body]] } + root = { nil => [:html, head, body] } root[:vocab] = vocab if vocab root[:lang] = lang if lang # deal with namespaces if xmlns root['xmlns'] = 'http://www.w3.org/1999/xhtml' # namespaced language attribute root['xml:lang'] = lang if lang + + if !prefix and xmlns.is_a? Hash + x = prefix.transform_keys { |k| "xmlns:#{k}" } + root = x.merge(root) + end end # deal with prefixes distinct from namespaces - if prefix + prefix ||= {} + if prefix.respond_to? :to_h + prefix = prefix.to_h + unless prefix.empty? + # this will get automatically smushed into the right shape + root[:prefix] = prefix + + if xmlns + x = prefix.transform_keys { |k| "xmlns:#{k}" } + root = x.merge(root) + end + end end # add the document structure to the spec spec << root # as usual this will return the last innermost node - markup spec: spec, doc: doc + markup spec: spec, doc: doc, args: args end private def element tag, doc: nil, ns: {}, attr: {}, args: [] raise 'Document node must be present' unless doc - prefix = local = nil + pfx = nil if tag.respond_to? :to_a - prefix, local = tag - tag = tag[0..1].join ':' + pfx = tag[0] + tag = tag.to_a[0..1].join ':' + elsif m = tag.match(/([^:]+):/) + pfx = m[1] end + elem = doc.create_element tag.to_s - ns.sort.each do |p, u| - elem.add_namespace((p.nil? ? p : p.to_s), u.to_s) + elem.default_namespace = ns[pfx] if ns[pfx] + + ns.keys.sort { |a, b| a.to_s <=> b.to_s }.each do |p| + elem.add_namespace((p.nil? ? p : p.to_s), ns[p].to_s) end attr.sort.each do |k, v| elem[k.to_s] = flatten(v, args) end elem end - ATOMS = [String, Symbol, Numeric, NilClass, FalseClass, TrueClass] + ATOMS = [String, Symbol, Numeric, NilClass, FalseClass, TrueClass].freeze # yo dawg def flatten obj, args # early bailout for most likely condition