I need to parse an XML file to Ruby objects.
Is there a tool to read attributes from XML like this
report.system_slots.items to return an array of item properties,
or report.system_slots.current_usage to return 'Available'?
Is it possible to do this with Nokogiri?
<page Title="System Slots" H1="Property" H2="Value" __type__="2">
<item Property="System Slot 1">
<item Property="Name" Value="PCI1"/>
<item Property="Type" Value="PCI"/>
<item Property="Data Bus Width" Value="32 bits"/>
<item Property="Current Usage" Value="Available"/>
<item Property="Characteristics">
<item Property="Vcc voltage supported" Value="3.3 V, 5.0 V"/>
<item Property="Shared" Value="No"/>
<item Property="PME Signal" Value="Yes"/>
<item Property="Support Hot Plug" Value="No"/>
<item Property="PCI slot supports SMBus signal" Value="Yes"/>
</item>
</item>
Look at Ox. It reads XML and returns a reasonable Ruby object facsimile of the XML.
require 'ox'
hash = {'foo' => { 'bar' => 'hello world'}}
puts Ox.dump(hash)
pp Ox.parse_obj(Ox.dump(hash))
Dumping that into IRB gives me:
require 'ox'
> hash = {'foo' => { 'bar' => 'hello world'}}
{
"foo" => {
"bar" => "hello world"
}
}
> puts Ox.dump(hash)
<h>
<s>foo</s>
<h>
<s>bar</s>
<s>hello world</s>
</h>
</h>
nil
> pp Ox.parse_obj(Ox.dump(hash))
{"foo"=>{"bar"=>"hello world"}}
{
"foo" => {
"bar" => "hello world"
}
}
That said, your XML sample is broken and won't work with OX. It WILL work with Nokogiri, though there are errors reported, which would hint that you wouldn't be able to parse the DOM correctly.
My question is, why do you want to convert the XML to an object? It is SO much easier to handle XML using a parser like Nokogiri. Using a fixed version of your XML:
require 'nokogiri'
xml = '
<xml>
<page Title="System Slots" H1="Property" H2="Value" __type__="2">
<item Property="System Slot 1"/>
<item Property="Name" Value="PCI1"/>
<item Property="Type" Value="PCI"/>
<item Property="Data Bus Width" Value="32 bits"/>
<item Property="Current Usage" Value="Available"/>
<item Property="Characteristics">
<item Property="Vcc voltage supported" Value="3.3 V, 5.0 V"/>
<item Property="Shared" Value="No"/>
<item Property="PME Signal" Value="Yes"/>
<item Property="Support Hot Plug" Value="No"/>
<item Property="PCI slot supports SMBus signal" Value="Yes"/>
</item>
</page>
</xml>'
doc = Nokogiri::XML(xml)
page = doc.at('page')
page['Title'] # => "System Slots"
page.at('item[#Property="Current Usage"]')['Value'] # => "Available"
item_properties = page.at('item[#Property="Characteristics"]')
item_properties.at('item[#Property="PCI slot supports SMBus signal"]')['Value'] # => "Yes"
Parsing a big XML document into memory can return a labyrinth of arrays and hashes that still have to be peeled apart to access the values you want. Using Nokogiri, you have CSS and XPath accessors which are easy to learn and read; I used CSS above but could easily have used XPath to accomplish the same things.
Related
I have create custom checkout Step between shipping and payment step . Now i want to show all the order details and customer fillup details.
Below is the files that i have created for the Custom checkout step
Below is the file (checkout_index_index.xml) i have created for new step in the checkout :
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceBlock name="checkout.root">
<arguments>
<argument name="jsLayout" xsi:type="array">
<item name="components" xsi:type="array">
<item name="checkout" xsi:type="array">
<item name="children" xsi:type="array">
<item name="steps" xsi:type="array">
<item name="children" xsi:type="array">
<!-- The new step you add -->
<item name="my-new-step" xsi:type="array">
<item name="component" xsi:type="string">Mycustom_Checkoutnew/js/view/my-step-view</item>
<!--To display step content before shipping step "sortOrder" value should be < 1-->
<!--To display step content between shipping step and payment step 1 < "sortOrder" < 2 -->
<!--To display step content after payment step "sortOrder" > 2 -->
<item name="sortOrder" xsi:type="string">1</item>
<item name="children" xsi:type="array">
<!--add here child component declaration for your step-->
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</argument>
</arguments>
</referenceBlock>
</body>
</page>
In the second file (my-step-view.js) there will be js code as follow :
define([
'ko',
'uiComponent',
'underscore',
'Magento_Checkout/js/model/step-navigator'
], function (ko, Component, _, stepNavigator) {
'use strict';
/**
* mystep - is the name of the component's .html template,
* <Vendor>_<Module> - is the name of your module directory.
*/
return Component.extend({
defaults: {
template: 'Mycustom_Checkoutnew/mystep'
},
// add here your logic to display step,
isVisible: ko.observable(true),
/**
* #returns {*}
*/
initialize: function () {
this._super();
// register your step
stepNavigator.registerStep(
// step code will be used as step content id in the component template
'shippingaddress',
// step alias
null,
// step title value
'Shipping',
// observable property with logic when display step or hide step
this.isVisible,
_.bind(this.navigate, this),
/**
* sort order value
* 'sort order value' < 10: step displays before shipping step;
* 10 < 'sort order value' < 20 : step displays between shipping and payment step
* 'sort order value' > 20 : step displays after payment step
*/
15
);
return this;
},
/**
* The navigate() method is responsible for navigation between checkout steps
* during checkout. You can add custom logic, for example some conditions
* for switching to your custom step
* When the user navigates to the custom step via url anchor or back button we_must show step manually here
*/
navigate: function () {
this.isVisible(true);
},
/**
* #returns void
*/
navigateToNextStep: function () {
stepNavigator.next();
}
});
});
Below is the html file (mystep.html) where i want to display all the data of orders and the customer
<li id="shippingaddress" data-bind="fadeVisible: isVisible">
<div class="step-title" data-bind="i18n: 'Shipping'" data-role="title"></div>
<div id="checkout-step-title"
class="step-content"
data-role="content">
<form data-bind="submit: navigateToNextStep" novalidate="novalidate">
<div class="actions-toolbar">
<div class="primary">
<button data-role="opc-continue" type="submit" class="button action continue primary">
<span><!-- ko i18n: 'Next'--><!-- /ko --></span>
</button>
</div>
</div>
</form>
</div>
</li>
In the new custom page i want show following things
1)Email 2)Contact 3 ) Address that sellected in previous step
4)Shipping method
If anyone can help me in this then please let me know
In frontend i want to show data as per image attached. go through that image
I'm currently making a custom UI-component for the adminhtml form which extends the default UI-select element. But the issue is that when I make a selection on this new UI-select all fields have their values cleared and if they are required the error appears.
I've been debugging for a while and have reached the conclusion that this issue appears down the line when the code gets to this.value(data) and also throws an exception.
Uncaught TypeError: Cannot read property 'click' of undefined
The element which is clicked in this case is the same element from the normal UI-select.
This is the component's XML through which it's added to the form.
And yes the options shouldn't be there but I couldn't figure out where to put it otherwise to make it work.
<field name="programs" class="Mirasvit\Affiliate\Component\Filters\Type\MultiplePrograms">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="label" xsi:type="string" translate="true">Programs</item>
<item name="componentType" xsi:type="string">field</item>
<item name="formElement" xsi:type="string">select</item>
<item name="elementTmpl" xsi:type="string">Mirasvit_Affiliate/form/elements/multiple-programs</item>
<item name="component" xsi:type="string">Mirasvit_Affiliate/js/component/multiple-programs</item>
<item name="filterOptions" xsi:type="boolean">true</item>
<item name="multiple" xsi:type="boolean">false</item>
<item name="options" xsi:type="object">Mirasvit\Affiliate\Ui\Account\Source\Program</item>
<item name="tableOptions" xsi:type="object">Mirasvit\Affiliate\Ui\Program\Source\ProgramsWithTiers</item>
<item name="tableValues" xsi:type="object">Mirasvit\Affiliate\Ui\Program\Source\AccountPrograms</item>
<item name="tableFields" xsi:type="array">
<item name="name" xsi:type="array">
<item name="label" xsi:type="string">Name</item>
<item name="type" xsi:type="string">text</item>
</item>
<item name="tier" xsi:type="array">
<item name="label" xsi:type="string">Tier</item>
<item name="type" xsi:type="string">select</item>
</item>
<item name="fixed" xsi:type="array">
<item name="label" xsi:type="string">Fixed tier</item>
<item name="type" xsi:type="string">checkbox</item>
</item>
</item>
</item>
</argument>
</field>
This is the component's js file
define([
'underscore',
'jquery',
'Magento_Ui/js/form/element/ui-select',
'uiRegistry',
], function (_, $, Abstract, registry) {
'use strict';
return Abstract.extend({
defaults: {
optionsCache: [],
options: [],
listVisible: false,
tableOptions: [],
tableValues: [],
tableFields: {},
currentSelected: [],
},
toggleOptionSelected: function (data) {
if (this.lastSelectable && data.hasOwnProperty(this.separator)) {
return this;
}
this.options(this.options.without(data));
/**
* THIS IS WHERE THE COMPONENT BREAKS
*/
this.value(data);
this.listVisible(false);
return this;
},
cleanHoveredElement: function () {
if (this.hoveredElement) {
$(this.hoveredElement)
.children(this.actionTargetSelector)
.removeClass(this.hoverClass);
this.hoveredElement = null;
}
return this;
},
});
});
And this is the component's php file
class MultiplePrograms extends Select
{
public function prepare() {
$config = $this->getData('config');
/** #var ArrayHelper $helper */
$helper = ObjectManager::getInstance()->get(ArrayHelper::class);
if (isset($config['tableOptions']) && $config['tableOptions'] instanceof ArrayInterface) {
$config['tableOptions'] = $config['tableOptions']->toOptionArray();
} else {
throw new \Exception('Missing tableOptions tag for multiple-options field');
}
if (isset($config['tableFields'])) {
$config['tableFields'] = $helper->nameToColumn($config['tableFields']);
} else {
throw new \Exception('Missing tableValues tag for multiple-options field');
}
if (isset($config['tableValues']) && $config['tableValues'] instanceof ArrayInterface) {
$config['tableValues'] = $config['tableValues']->toOptionArray();
}
if (isset($config['options']) && $config['options'] instanceof ArrayInterface) {
$config['options'] = $config['options']->toOptionArray();
}
$this->setData('config', (array)$config);
parent::prepare();
}
}
I want to be able to store information in the value of this component so that it can be passed to the controller later
I found out why this was happening.
The reason it was throwing that error is because this.value() contained all the form data and thus by setting this.value({anything}) to anything, would clear all of the fields stored and trigger the errors and the observers.
Given the following XML, I want to get the value "0123456" for Name="Cat":
xml.xpath '//Custom[Name="Cat"]'
Gives me the first custom, which is correct, but I only want the "Value" not the entire Custom node.
<body>
<Custom>
<count>1</count>
<Name>Cat</Name>
<Value>0123456</Value>
</Custom>
<Custom>
<count>2</count>
<Name>Dog</Name>
<Value>9876543</Value>
</Custom>
<body>
I only want the "Value" not the entire Custom node.
So just go on writing the path:
//Custom[Name="Cat"]/Value
I prefer to use CSS selectors over XPath, for readability, as usually CSS contains less visual noise:
require 'nokogiri'
doc = Nokogiri::HTML(<<EOT)
<body>
<Custom>
<count>1</count>
<Name>Cat</Name>
<Value>0123456</Value>
</Custom>
<Custom>
<count>2</count>
<Name>Dog</Name>
<Value>9876543</Value>
</Custom>
<body>
EOT
foo = doc.search('name:contains("Cat")').map{ |node|
node.next_element.text
}
foo # => ["0123456"]
This works because Nokogiri contains some of the jQuery CSS extensions, resulting in some useful additions.
To get the value element text you need to set the xpath as below:
doc = Nokogiri::HTML(<<EOT)
<body>
<Custom>
<count>1</count>
<Name>Cat</Name>
<Value>0123456</Value>
</Custom>
<Custom>
<count>2</count>
<Name>Dog</Name>
<Value>9876543</Value>
</Custom>
<body>
EOT
val=doc.xpath("//Custom[Name='Cat']/Value").text()
val => "0123456"
I tried to create a RSS feed with image for each article and eventually a banner for Feedly and a logo but I struggle to do that.
Here is the preview of what I get :
Here is my RSS Builder:
#encoding: UTF-8
xml.instruct! :xml, :version => "1.0"
xml.rss :version => '2.0', 'xmlns:atom' => 'http://www.w3.org/2005/Atom', 'xmlns:media' => 'http://search.yahoo.com/mrss/' do
xml.channel do
xml.title "My RSS feed"
xml.description "Super description"
xml.link "MY_URL"
xml.language "en"
xml.tag! 'atom:link', :rel => 'self', :type => 'application/rss+xml', :href => "MY_URL/feed"
for article in #articles
xml.item do
xml.title article.name
xml.pubDate article.created_at.to_s(:rfc822)
xml.link "MY_URL"
xml.guid "MY_URL"
xml.media(:content, :url => article.image.url(:medium))
xml.media(:thumbnail, :url => article.image.url(:thumbnail))
xml.description "<p>" + article.description + "</p>"
end
end
end
end
And here is the result:
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
<channel>
<title>My RSS feed</title>
<description>Super description</description>
<link>MY_URL</link>
<language>en</language>
<atom:link rel="self" type="application/rss+xml" href="MY_URL/feed"/>
<item>
<title>Dojo</title>
<pubDate>Mon, 23 Nov 2015 16:58:41 +0000</pubDate>
<link>MY_URL</link>
<guid>MY_URL</guid>
<media:content url="MY_URL"/>
<media:thumbnail url="MY_URL"/>
<description><p>Your security & privacy advisor.</p></description>
</item>
</channel>
</rss>
Thanks for your help with that!
I've checked out how it works in blog.feedly.com and here is example:
<item>
<title>POST_TITLE</title>
<link>URL_TO_POST</link>
<comments>URL_TO_COMMENTS</comments>
<pubDate>ISODate</pubDate>
<dc:creator>CDATA_AUTHOR_METATAG</dc:creator>
<category><![CDATA[All]]></category>
<guid isPermaLink="false">URL_WITHOUT_SLUG</guid>
<description>CDATA_DESCRIPTION</description>
<content:encoded>CDATA_POST_CONTENT</content:encoded>
<wfw:commentRss>URL_TO_COMMENTS_RSS</wfw:commentRss>
<slash:comments>NUMBER_OF_COMMENTS</slash:comments>
<media:content url="IMG_URL" medium="image">
<media:title type="html">Author</media:title>
</media:content>
<media:content url="IMG_URL" medium="image">
<media:title type="html">img_short_description</media:title>
</media:content>
</item>
Try to implement your feed items like I described above and if it will not work, check out item parent tags too. You need to do your rss feed just like at blog.feedly.com
My XML:
<content>
<item id="1">A</item>
<item id="2">B</item>
<item id="4">D</item>
</content>
I have loaded this using XML similar to:
XDocument xDoc = new XDocument(data.Value);
var items = from i in xDoc.Element("content").Elements("item")
select i;
I want to insert another element, to end up with something like:
<content>
<item id="1">A</item>
<item id="2">B</item>
<item id="3">C</item>
<item id="4">D</item>
</content>
How do I do this using Linq2Xml?
Try this:
xDoc.Element("content")
.Elements("item")
.Where(item => item.Attribute("id").Value == "2").FirstOrDefault()
.AddAfterSelf(new XElement("item", "C", new XAttribute("id", "3")));
Or, if you like XPath like I do:
xDoc.XPathSelectElement("content/item[#id = '2']")
.AddAfterSelf(new XElement("item", "C", new XAttribute("id", "3")));