Setting an attribute in a Nokogiri::XML::NodeSet with css

时光毁灭记忆、已成空白 提交于 2019-12-10 12:13:45

问题


I was trying to set an attribute for a specific element in an xml file and I was having success using

doc.css('Object').attr("Id").value = timestamp

This was fine until the situation where 'Object' doesn't exist causing an exception in the program and quitting. To avoid that I wanted to use Nodeset as it'll just be empty instead.

doc.css('Object').each do |element|
    element.attr("Id").value = timestamp
end

However this returns with the error that value= is an undefined method. It's probably something simple but I'm new to Ruby and CSS so any help would be great.


回答1:


The problem has little to do with CSS, since Nokogiri uses CSS selectors as an alternative to using XPath selectors, and both are only used to provide a path to a node, or nodes.

It looks like you're overthinking this, and making it harder than it needs to be. Here's what I'd do:

require 'nokogiri'

doc = Nokogiri::XML(<<EOT)
<xml>
  <foo>
    <Object>bar</Object>
  </foo>
</xml>
EOT

doc.at('Object')["Id"] = Time.now.to_s

Looking at doc at this point shows:

puts doc.to_xml
# >> <?xml version="1.0"?>
# >> <xml>
# >>   <foo>
# >>     <Object Id="2014-01-28 19:13:32 -0700">bar</Object>
# >>   </foo>
# >> </xml>

It's really important to understand the difference between at, at_css and at_xpath, which return the first matching Node, and search, css and xpath, which return NodeSets. A NodeSet is akin to an array containing Nodes. When you know that, your statement:

doc.css('Object').attr("Id").value = timestamp

won't make much sense, especially since that's not how the attr method is defined:

attr(key, value = nil, &blk) 

You'd need to use:

doc.css('Object').attr("Id", value)

which would assign value to all Id attributes for every <Object> node in the document.

But, again, that's not the right choice, instead you should use at or at_css to return the single node.

This was fine until the situation where 'Object' doesn't exist

If no <Object> node exists, then it gets more interesting, and you have to determine what to do. You can insert it, or, you can simply move along and do nothing.

To see if a node exists is simple:

object_node = doc.at('Object')
if object_node
  object_node['Id'] = Time.now.to_s
else
  # ... insert it
end

To insert a node involves locating the place you want to insert it, then add it:

doc.at('foo').add_child("<Object Id='#{ Time.now }'>baz</Object>")
puts doc.to_xml
# >> <?xml version="1.0"?>
# >> <xml>
# >>   <foo>
# >>     <Object>bar</Object>
# >>   <Object Id="2014-01-28 19:39:38 -0700">baz</Object></foo>
# >> </xml>

I didn't try to make the XML output pretty, which isn't important in XML, it merely needs to be syntactically correct.

Also note that it's possible to insert a node, or nodes, by defining them as a string of XML. Nokogiri will parse it into the appropriate XML and graft it in where you said. You could also go the long route by define a NodeSet or Node, then inserting it, but, in general, that makes uglier code and causes you to do a lot more work, which results in less readable code for those who follow in your footsteps maintaining the source.



来源:https://stackoverflow.com/questions/21419808/setting-an-attribute-in-a-nokogirixmlnodeset-with-css

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!