Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Support custom xpath for happymapper items? #21

Merged
merged 1 commit into from

2 participants

@teleological

Thanks for happymapper! I put it to work parsing the Intrade API, but ran into an issue with an element named "msg" which included a member element that was also named "msg", but of a different type than the outer element. This pull request includes a solution to that issue, allowing users to override the default xpath-generation logic with an xpath literal. The problem with the Intrade API can be resolved by specifying option :xpath => './msg', which only includes direct children. I included fixtures with a sanitized example and specs which exercise the new option. I hope you find this useful.

@teleological teleological Support for explicit xpath option
Default xpath for non-root nodes matches all descendants. Sometimes,
you only want to match the immediate child. The ability to specify
xpath will cover this and other cases.
dbaaebc
@jnunemaker jnunemaker merged commit 2e95642 into jnunemaker:master
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 16, 2012
  1. @teleological

    Support for explicit xpath option

    teleological authored
    Default xpath for non-root nodes matches all descendants. Sometimes,
    you only want to match the immediate child. The ability to specify
    xpath will cover this and other cases.
This page is out of date. Refresh to see the latest.
View
8 lib/happymapper.rb
@@ -97,9 +97,11 @@ def parse(xml, options = {})
namespace = @namespace || (node.namespaces && node.namespaces.default)
namespace = "#{DEFAULT_NS}:#{namespace}" if namespace
- xpath = root ? '/' : './/'
- xpath += "#{DEFAULT_NS}:" if namespace
- xpath += tag_name
+ unless xpath = options[:xpath]
+ xpath = root ? '/' : './/'
+ xpath += "#{DEFAULT_NS}:" if namespace
+ xpath += tag_name
+ end
nodes = node.find(xpath, Array(namespace))
collection = nodes.collect do |n|
View
29 spec/fixtures/intrade.xml
@@ -0,0 +1,29 @@
+<tsResponse requestOp="getUserMessages" resultCode="0" timestamp="1329416249820"
+ timetaken="475">
+ <msg msgID="123456">
+ <msgID>123456</msgID>
+ <conID>789012</conID>
+ <symbol>2012.PIGS.FLY</symbol>
+ <readFlag>false</readFlag>
+ <type>T</type>
+ <msg>345678901</msg>
+ <price>13.0</price>
+ <quantity>100</quantity>
+ <side>B</side>
+ <timestamp>1329415852000</timestamp>
+ </msg>
+ <msg msgID="123460">
+ <msgID>123460</msgID>
+ <conID>789013</conID>
+ <symbol>2012.SNOWBALL.INFERNO.MELT</symbol>
+ <readFlag>false</readFlag>
+ <type>T</type>
+ <msg>345678933</msg>
+ <price>13.0</price>
+ <quantity>100</quantity>
+ <side>S</side>
+ <timestamp>1329415873000</timestamp>
+ </msg>
+ <faildesc>Ok</faildesc>
+ <sessionData>10R3M1p5um0010r5174m37c0n53C737uR401p151C1N931175300031U5m0073mp0r1Nc1010UN7u71480R3370010R3m49n44119u4U73N1M40M1n1MV3n14M9U15n057Ru03X3Rc174710Nu114Mc01480r15n151u74119U1P3x34C0mm000C0n539u470u154u731ruR30010r1Nr3PR3h3n03R171Nv01up7473v31173553C111Um0010R33ufU9147nu114P4R147Ur3XC3P73uR51n70CC43C47CuP104747n0nPR0103N75un71ncu1p49U10fF1C140353rUn7m011174N1M103571480Rum</sessionData>
+</tsResponse>
View
13 spec/happymapper_spec.rb
@@ -350,6 +350,19 @@ class Thing
# tree.people.first.id.should == 'KWQS-BBQ'
end
+ it "should support :xpath option" do
+ message_box = Intrade::Messages.parse(fixture_file('intrade.xml'))
+ message_box.timestamp.should == Time.at(1329416249)
+ message_box.error_message.should == "Ok"
+
+ # default xpath would default to './/msg', which would include
+ # nested nodes which are also named "msg", so xpath is
+ # explicitly supplied as option :xpath => './msg'
+ message_box.messages.should have(2).messages
+ message_box.messages[0].message_id.should == 123456
+ message_box.messages[1].message_id.should == 123460
+ end
+
describe 'nested elements with namespaces' do
module Namespaces
class Info
View
37 spec/support/models.rb
@@ -299,4 +299,39 @@ class Note
content :body
end
-end
+end
+
+module Intrade
+ class EpochMillis
+ def self.parse(millis); Time.at(millis.to_i / 1000); end
+ end
+
+ class Message
+ include HappyMapper
+
+ tag 'msg'
+ attribute :message_id, Integer, :tag => 'msgID'
+ element :contract_id, Integer, :tag => 'conID'
+ element :symbol, String, :tag => 'symbol'
+ element :is_read, Boolean, :tag => 'readFlag'
+ element :type, String, :tag => 'type'
+ element :text, String, :tag => 'msg'
+ element :price, Float, :tag => 'price'
+ element :quantity, Integer, :tag => 'quantity'
+ element :side, String, :tag => 'side'
+ element :timestamp, EpochMillis, :tag => 'timestamp', :parser => :parse
+ end
+
+ class Messages
+ include HappyMapper
+
+ tag 'tsResponse'
+ attribute :operation, String, :tag => 'requestOp'
+ attribute :timestamp, EpochMillis, :tag => 'timestamp', :parser => :parse
+ attribute :result_code, Integer, :tag => 'resultCode'
+ element :error_message, String, :tag => 'faildesc'
+ element :session_key, String, :tag => 'sessionData'
+ has_many :messages, Message, :tag => 'msg', :xpath => './msg'
+ end
+end
+
Something went wrong with that request. Please try again.