# frozen_string_literal: truemoduleNokogirimoduleXML#### Nokogiri builder can be used for building XML and HTML documents.## == Synopsis:## builder = Nokogiri::XML::Builder.new do |xml|# xml.root {# xml.products {# xml.widget {# xml.id_ "10"# xml.name "Awesome widget"# }# }# }# end# puts builder.to_xml## Will output:## <?xml version="1.0"?># <root># <products># <widget># <id>10</id># <name>Awesome widget</name># </widget># </products># </root>### === Builder scope## The builder allows two forms. When the builder is supplied with a block# that has a parameter, the outside scope is maintained. This means you# can access variables that are outside your builder. If you don't need# outside scope, you can use the builder without the "xml" prefix like# this:## builder = Nokogiri::XML::Builder.new do# root {# products {# widget {# id_ "10"# name "Awesome widget"# }# }# }# end## == Special Tags## The builder works by taking advantage of method_missing. Unfortunately# some methods are defined in ruby that are difficult or dangerous to# remove. You may want to create tags with the name "type", "class", and# "id" for example. In that case, you can use an underscore to# disambiguate your tag name from the method call.## Here is an example of using the underscore to disambiguate tag names from# ruby methods:## @objects = [Object.new, Object.new, Object.new]## builder = Nokogiri::XML::Builder.new do |xml|# xml.root {# xml.objects {# @objects.each do |o|# xml.object {# xml.type_ o.type# xml.class_ o.class.name# xml.id_ o.id# }# end# }# }# end# puts builder.to_xml## The underscore may be used with any tag name, and the last underscore# will just be removed. This code will output the following XML:## <?xml version="1.0"?># <root># <objects># <object># <type>Object</type># <class>Object</class># <id>48390</id># </object># <object># <type>Object</type># <class>Object</class># <id>48380</id># </object># <object># <type>Object</type># <class>Object</class># <id>48370</id># </object># </objects># </root>## == Tag Attributes## Tag attributes may be supplied as method arguments. Here is our# previous example, but using attributes rather than tags:## @objects = [Object.new, Object.new, Object.new]## builder = Nokogiri::XML::Builder.new do |xml|# xml.root {# xml.objects {# @objects.each do |o|# xml.object(:type => o.type, :class => o.class, :id => o.id)# end# }# }# end# puts builder.to_xml## === Tag Attribute Short Cuts## A couple attribute short cuts are available when building tags. The# short cuts are available by special method calls when building a tag.## This example builds an "object" tag with the class attribute "classy"# and the id of "thing":## builder = Nokogiri::XML::Builder.new do |xml|# xml.root {# xml.objects {# xml.object.classy.thing!# }# }# end# puts builder.to_xml## Which will output:## <?xml version="1.0"?># <root># <objects># <object class="classy" id="thing"/># </objects># </root>## All other options are still supported with this syntax, including# blocks and extra tag attributes.## == Namespaces## Namespaces are added similarly to attributes. Nokogiri::XML::Builder# assumes that when an attribute starts with "xmlns", it is meant to be# a namespace:## builder = Nokogiri::XML::Builder.new { |xml|# xml.root('xmlns' => 'default', 'xmlns:foo' => 'bar') do# xml.tenderlove# end# }# puts builder.to_xml## Will output XML like this:## <?xml version="1.0"?># <root xmlns:foo="bar" xmlns="default"># <tenderlove/># </root>## === Referencing declared namespaces## Tags that reference non-default namespaces (i.e. a tag "foo:bar") can be# built by using the Nokogiri::XML::Builder#[] method.## For example:## builder = Nokogiri::XML::Builder.new do |xml|# xml.root('xmlns:foo' => 'bar') {# xml.objects {# xml['foo'].object.classy.thing!# }# }# end# puts builder.to_xml## Will output this XML:## <?xml version="1.0"?># <root xmlns:foo="bar"># <objects># <foo:object class="classy" id="thing"/># </objects># </root>## Note the "foo:object" tag.## === Namespace inheritance## In the Builder context, children will inherit their parent's namespace. This is the same# behavior as if the underlying {XML::Document} set +namespace_inheritance+ to +true+:## result = Nokogiri::XML::Builder.new do |xml|# xml["soapenv"].Envelope("xmlns:soapenv" => "http://schemas.xmlsoap.org/soap/envelope/") do# xml.Header# end# end# result.doc.to_xml# # => <?xml version="1.0" encoding="utf-8"?># # <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"># # <soapenv:Header/># # </soapenv:Envelope>## Users may turn this behavior off by passing a keyword argument +namespace_inheritance:false+# to the initializer:## result = Nokogiri::XML::Builder.new(namespace_inheritance: false) do |xml|# xml["soapenv"].Envelope("xmlns:soapenv" => "http://schemas.xmlsoap.org/soap/envelope/") do# xml.Header# xml["soapenv"].Body # users may explicitly opt into the namespace# end# end# result.doc.to_xml# # => <?xml version="1.0" encoding="utf-8"?># # <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"># # <Header/># # <soapenv:Body/># # </soapenv:Envelope>## For more information on namespace inheritance, please see {XML::Document#namespace_inheritance}### == Document Types## To create a document type (DTD), use the Builder#doc method to get# the current context document. Then call Node#create_internal_subset to# create the DTD node.## For example, this Ruby:## builder = Nokogiri::XML::Builder.new do |xml|# xml.doc.create_internal_subset(# 'html',# "-//W3C//DTD HTML 4.01 Transitional//EN",# "http://www.w3.org/TR/html4/loose.dtd"# )# xml.root do# xml.foo# end# end## puts builder.to_xml## Will output this xml:## <?xml version="1.0"?># <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"># <root># <foo/># </root>#classBuilderincludeNokogiri::ClassResolverDEFAULT_DOCUMENT_OPTIONS={namespace_inheritance: true}# The current Document object being builtattr_accessor:doc# The parent of the current node being builtattr_accessor:parent# A context object for use when the block has no argumentsattr_accessor:contextattr_accessor:arity# :nodoc:#### Create a builder with an existing root object. This is for use when# you have an existing document that you would like to augment with# builder methods. The builder context created will start with the# given +root+ node.## For example:## doc = Nokogiri::XML(File.read('somedoc.xml'))# Nokogiri::XML::Builder.with(doc.at_css('some_tag')) do |xml|# # ... Use normal builder methods here ...# xml.awesome # add the "awesome" tag below "some_tag"# end#defself.with(root,&block)new({},root,&block)end#### Create a new Builder object. +options+ are sent to the top level# Document that is being built.## Building a document with a particular encoding for example:## Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|# ...# enddefinitialize(options={},root=nil,&block)ifroot@doc=root.document@parent=rootelse@parent=@doc=related_class("Document").newend@context=nil@arity=nil@ns=niloptions=DEFAULT_DOCUMENT_OPTIONS.merge(options)options.eachdo|k,v|@doc.send(:"#{k}=",v)endreturnunlessblock@arity=block.arityif@arity<=0@context=eval("self",block.binding)instance_eval(&block)elseyieldselfend@parent=@docend#### Create a Text Node with content of +string+deftext(string)insert(@doc.create_text_node(string))end#### Create a CDATA Node with content of +string+defcdata(string)insert(doc.create_cdata(string))end#### Create a Comment Node with content of +string+defcomment(string)insert(doc.create_comment(string))end#### Build a tag that is associated with namespace +ns+. Raises an# ArgumentError if +ns+ has not been defined higher in the tree.def[](ns)if@parent!=@doc@ns=@parent.namespace_definitions.find{|x|x.prefix==ns.to_s}endreturnselfif@ns@parent.ancestors.eachdo|a|nextifa==doc@ns=a.namespace_definitions.find{|x|x.prefix==ns.to_s}returnselfif@nsend@ns={pending: ns.to_s}selfend#### Convert this Builder object to XMLdefto_xml(*args)ifNokogiri.jruby?options=args.first.is_a?(Hash)?args.shift:{}unlessoptions[:save_with]options[:save_with]=Node::SaveOptions::AS_BUILDERendargs.insert(0,options)end@doc.to_xml(*args)end#### Append the given raw XML +string+ to the documentdef<<(string)@doc.fragment(string).children.each{|x|insert(x)}enddefmethod_missing(method,*args,&block)# :nodoc:if@context&.respond_to?(method)@context.send(method,*args,&block)elsenode=@doc.create_element(method.to_s.sub(/[_!]$/,""),*args)do|n|# Set up the namespaceif@ns.is_a?(Nokogiri::XML::Namespace)n.namespace=@ns@ns=nilendendif@ns.is_a?(Hash)node.namespace=node.namespace_definitions.find{|x|x.prefix==@ns[:pending]}ifnode.namespace.nil?raiseArgumentError,"Namespace #{@ns[:pending]} has not been defined"end@ns=nilendinsert(node,&block)endendprivate#### Insert +node+ as a child of the current Nodedefinsert(node,&block)node=@parent.add_child(node)ifblockbeginold_parent=@parent@parent=node@arity||=block.arityif@arity<=0instance_eval(&block)elseyield(self)endensure@parent=old_parentendendNodeBuilder.new(node,self)endclassNodeBuilder# :nodoc:definitialize(node,doc_builder)@node=node@doc_builder=doc_builderenddef[]=(k,v)@node[k]=venddef[](k)@node[k]enddefmethod_missing(method,*args,&block)opts=args.last.is_a?(Hash)?args.pop:{}casemethod.to_swhen/^(.*)!$/@node["id"]=Regexp.last_match(1)@node.content=args.firstifargs.firstwhen/^(.*)=/@node[Regexp.last_match(1)]=args.firstelse@node["class"]=((@node["class"]||"").split(/\s/)+[method.to_s]).join(" ")@node.content=args.firstifargs.firstend# Assign any extra optionsopts.eachdo|k,v|@node[k.to_s]=((@node[k.to_s]||"").split(/\s/)+[v]).join(" ")endifblockold_parent=@doc_builder.parent@doc_builder.parent=@nodevalue=@doc_builder.instance_eval(&block)@doc_builder.parent=old_parentreturnvalueendselfendendendendend