client_for_poslynx 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +22 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +3 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +75 -0
  8. data/Rakefile +7 -0
  9. data/bin/fake_pos_terminal +41 -0
  10. data/bin/poslynx_client_console +75 -0
  11. data/client_for_poslynx.gemspec +27 -0
  12. data/lib/client_for_poslynx.rb +13 -0
  13. data/lib/client_for_poslynx/data.rb +14 -0
  14. data/lib/client_for_poslynx/data/abstract_data.rb +202 -0
  15. data/lib/client_for_poslynx/data/properties_xml_parser.rb +27 -0
  16. data/lib/client_for_poslynx/data/requests.rb +21 -0
  17. data/lib/client_for_poslynx/data/requests/abstract_request.rb +32 -0
  18. data/lib/client_for_poslynx/data/requests/can_visit.rb +21 -0
  19. data/lib/client_for_poslynx/data/requests/credit_card_sale.rb +28 -0
  20. data/lib/client_for_poslynx/data/requests/debit_card_sale.rb +28 -0
  21. data/lib/client_for_poslynx/data/requests/pin_pad_display_message.rb +20 -0
  22. data/lib/client_for_poslynx/data/requests/pin_pad_display_specified_form.rb +21 -0
  23. data/lib/client_for_poslynx/data/requests/pin_pad_initialize.rb +18 -0
  24. data/lib/client_for_poslynx/data/responses.rb +19 -0
  25. data/lib/client_for_poslynx/data/responses/abstract_response.rb +23 -0
  26. data/lib/client_for_poslynx/data/responses/credit_card_sale.rb +33 -0
  27. data/lib/client_for_poslynx/data/responses/debit_card_sale.rb +35 -0
  28. data/lib/client_for_poslynx/data/responses/pin_pad_display_message.rb +18 -0
  29. data/lib/client_for_poslynx/data/responses/pin_pad_display_specified_form.rb +19 -0
  30. data/lib/client_for_poslynx/data/responses/pin_pad_initialize.rb +17 -0
  31. data/lib/client_for_poslynx/data/xml_document.rb +68 -0
  32. data/lib/client_for_poslynx/fake_pos_terminal.rb +27 -0
  33. data/lib/client_for_poslynx/fake_pos_terminal/console_user_interface.rb +227 -0
  34. data/lib/client_for_poslynx/fake_pos_terminal/format.rb +21 -0
  35. data/lib/client_for_poslynx/fake_pos_terminal/request_dispatcher.rb +36 -0
  36. data/lib/client_for_poslynx/fake_pos_terminal/request_handlers.rb +18 -0
  37. data/lib/client_for_poslynx/fake_pos_terminal/request_handlers/abstract_handler.rb +37 -0
  38. data/lib/client_for_poslynx/fake_pos_terminal/request_handlers/credit_card_sale.rb +37 -0
  39. data/lib/client_for_poslynx/fake_pos_terminal/request_handlers/debit_card_sale.rb +53 -0
  40. data/lib/client_for_poslynx/fake_pos_terminal/request_handlers/handles_card_sale.rb +89 -0
  41. data/lib/client_for_poslynx/fake_pos_terminal/request_handlers/pin_pad_display_message.rb +35 -0
  42. data/lib/client_for_poslynx/fake_pos_terminal/request_handlers/pin_pad_display_specified_form.rb +49 -0
  43. data/lib/client_for_poslynx/fake_pos_terminal/request_handlers/pin_pad_initialize.rb +19 -0
  44. data/lib/client_for_poslynx/fake_pos_terminal/result_assemblers.rb +12 -0
  45. data/lib/client_for_poslynx/fake_pos_terminal/result_assemblers/card_sale_receipt.rb +88 -0
  46. data/lib/client_for_poslynx/fake_pos_terminal/server.rb +65 -0
  47. data/lib/client_for_poslynx/has_client_console_support.rb +102 -0
  48. data/lib/client_for_poslynx/message_handling.rb +22 -0
  49. data/lib/client_for_poslynx/message_handling/data_extractor.rb +21 -0
  50. data/lib/client_for_poslynx/message_handling/stream_data_writer.rb +21 -0
  51. data/lib/client_for_poslynx/message_handling/xml_extractor.rb +38 -0
  52. data/lib/client_for_poslynx/version.rb +5 -0
  53. data/spec/client_for_poslynx/data/abstract_data_spec.rb +69 -0
  54. data/spec/client_for_poslynx/data/requests/abstract_request_spec.rb +72 -0
  55. data/spec/client_for_poslynx/data/requests/can_visit_spec.rb +43 -0
  56. data/spec/client_for_poslynx/data/requests/credit_card_sale_spec.rb +110 -0
  57. data/spec/client_for_poslynx/data/requests/debit_card_sale_spec.rb +107 -0
  58. data/spec/client_for_poslynx/data/requests/pin_pad_display_message_spec.rb +83 -0
  59. data/spec/client_for_poslynx/data/requests/pin_pad_display_specified_form_spec.rb +84 -0
  60. data/spec/client_for_poslynx/data/requests/pin_pad_initialize_spec.rb +71 -0
  61. data/spec/client_for_poslynx/data/responses/abstract_response_spec.rb +78 -0
  62. data/spec/client_for_poslynx/data/responses/credit_card_sale_spec.rb +137 -0
  63. data/spec/client_for_poslynx/data/responses/debit_card_sale_spec.rb +145 -0
  64. data/spec/client_for_poslynx/data/responses/pin_pad_display_message_spec.rb +68 -0
  65. data/spec/client_for_poslynx/data/responses/pin_pad_display_specified_form_spec.rb +72 -0
  66. data/spec/client_for_poslynx/data/responses/pin_pad_initialize_spec.rb +64 -0
  67. data/spec/client_for_poslynx/message_handling/data_extractor_spec.rb +28 -0
  68. data/spec/client_for_poslynx/message_handling/xml_extractor_spec.rb +44 -0
  69. data/spec/client_for_poslynx/message_handling_spec.rb +49 -0
  70. data/spec/client_for_poslynx_spec.rb +11 -0
  71. data/spec/spec_helper.rb +7 -0
  72. data/spec/support/shared_examples.rb +38 -0
  73. metadata +193 -0
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YjVkZWJhOTMxOTkyZjRjNmJmYmNhZDE2MTc2ZDNiOGExMjVkYzBjNw==
5
+ data.tar.gz: !binary |-
6
+ ZTIzMzQ2NmJkN2ZlMWUwZTVjNDM2YTZkNjlkNWRkOGVlODUyOWRhMg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ Yjk5YTJhNjcyZjQ4YzJjYjMyMTE3YzMwOTgyYzcwNGM2NTdlMWExYzhmMTc3
10
+ Yzk1NTNkZWQzZDIwYWE0ZDMwMTFlOGNjNGEyMDExZTQxMTU0MTRiYjViNDcx
11
+ N2ZmMDFlZTMzZGEzZGY5Y2UwNjA4OWQ3ODg0NDQ4NzY0YThiYjg=
12
+ data.tar.gz: !binary |-
13
+ MTZiZjhiMjRlOGEyNjhlNTg0ZmNlMmFjYWNmMjA1OGQ2ZTVkNWVjNDY3ZThm
14
+ ZTVjNzE4M2YzMjUyMzgyMGE5MmFkZDYyYmFhNmJkNzFjZGRlY2U2MDk2MTA1
15
+ NGQwOTlkMzg1MTYwNmNkZWM2NWVlMGVlM2U2NDA3NzRkYjhkYzQ=
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in client_for_poslynx.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Steve Jorgensen
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # ClientForPoslynx
2
+
3
+ A client library for Ruby applications to communicate with a
4
+ Precidia Technologies POSLynx (TM) payment appliance.
5
+
6
+ This library was not developed by or on behalf of Precidia
7
+ Technologies.
8
+
9
+ Features:
10
+
11
+ * Data models for requests and responses.
12
+ * Writing requests to output streams.
13
+ * Reading responses from input streams.
14
+ * A fake POSLynx appliance + PIN Pad script.
15
+ * A POSLynx client console script.
16
+
17
+ The best introduction to this gem is probably to play around with
18
+ with the POSLynx client console. Assuming you have a POSLynx
19
+ unit with IP address 192.168.1.123, listening on port 54321, with
20
+ a registered client MAC of 000000000000, an example poslynx
21
+ client session might look like...
22
+
23
+ bundle exec poslynx_client_console 192.168.1.123:54321
24
+ 1.9.3-p545 :001 > poslynx_client.client_mac_for_examples = '0' * 12
25
+ => "000000000000"
26
+ 1.9.3-p545 :002 > resp = poslynx_client.send_request( poslynx_client.example_pin_pad_display_message_request )
27
+ => #<ClientForPoslynx::Data::Responses::PinPadDisplayMessage:0x007fbf529a17f8 @result="SUCCESS", @result_text="Success", @error_code="0000", @button_response="2nd button", @source_data="<PLResponse><Command>PPDISPLAY</Command><Result>SUCCESS</Result><ResultText>Success</ResultText><ErrorCode>0000</ErrorCode><Response>2nd button</Response></PLResponse>\n">
28
+
29
+ This gem also provides a fake POS/terminal application that you
30
+ can run in a separate console window when you are working without
31
+ the necessary access to an actual POSLynx unit and PIN pad. To
32
+ start the fake POS/terminal listening on port 3010...
33
+
34
+ bundle exec fake_pos_terminal 3010
35
+
36
+ If you then want to start the client console to interact with the
37
+ fake POS/terminal instance on the same machine...
38
+
39
+ bundle exec poslynx_client_console :3010
40
+
41
+ ## Usage
42
+
43
+ The code in the
44
+ lib/client_for_poslynx/has_client_colsole_support.rb file
45
+ provides a good example of how to use the facilities that this
46
+ gem provides.
47
+
48
+ ## Known Limitations
49
+
50
+ * Only a subset of the possible messages and elements is supported.
51
+ __More will be added. Contributions are welcome and encouraged. :)__
52
+ * Performs serialization of requests and parsing of responses, but
53
+ does not ecapsulate actually making TCP connections and requests.
54
+
55
+ ## Installation
56
+
57
+ Add this line to your application's Gemfile:
58
+
59
+ gem 'client_for_poslynx'
60
+
61
+ And then execute:
62
+
63
+ $ bundle
64
+
65
+ Or install it yourself as:
66
+
67
+ $ gem install client_for_poslynx
68
+
69
+ ## Contributing
70
+
71
+ 1. Fork it ( https://github.com/[my-github-username]/client_for_poslynx/fork )
72
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
73
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
74
+ 4. Push to the branch (`git push origin my-new-feature`)
75
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ option_args = []
4
+ value_args = []
5
+
6
+ $*.each do |arg|
7
+ list = arg =~ /^--/ ? option_args : value_args
8
+ list << arg
9
+ end
10
+
11
+ # Drop command line arguments so 1st argument won't be treated as
12
+ # file name for input.
13
+ $*.replace []
14
+
15
+ # The only option we car about is --help, so we either show usage
16
+ # for --help or because an unrecognized option was given.
17
+ show_usage = option_args.length > 0
18
+
19
+ # Should have exactly 1 value argument for the port number.
20
+ show_usage ||= value_args.length != 1
21
+
22
+ # Port number should consist of 1 or more digits
23
+ show_usage ||= value_args.first !~ /\A\d+\z/
24
+
25
+ if show_usage
26
+ puts
27
+ puts "Usage: client_for_poslynx <port-number>"
28
+ puts
29
+ puts "Runs a fake POS terminal that behaves like a POSLynx unit with"
30
+ puts "an attached PIN pad. It accepts connections from a client on"
31
+ puts "the specified TCP port number, and presents a text terminal"
32
+ puts "user interface that acts similarly to a PIN pad."
33
+ puts
34
+ puts "This allows you to manually test or demonstrate your client"
35
+ puts "software without having access to actual POSLynx hardware."
36
+ puts
37
+ else
38
+ port_number = (value_args.first || 0).to_i
39
+ require 'client_for_poslynx/fake_pos_terminal'
40
+ ClientForPoslynx::FakePosTerminal.start port_number
41
+ end
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ option_args = []
4
+ value_args = []
5
+
6
+ $*.each do |arg|
7
+ list = arg =~ /^--/ ? option_args : value_args
8
+ list << arg
9
+ end
10
+
11
+ # Drop command line arguments so 1st argument won't be treated as
12
+ # file name for input.
13
+ $*.replace []
14
+
15
+ # The only option we car about is --help, so we either show usage
16
+ # for --help or because an unrecognized option was given.
17
+ show_usage = option_args.length > 0
18
+
19
+ # Should have exactly 1 value argument for the address + port number.
20
+ show_usage ||= value_args.length != 1
21
+
22
+ # Port number should consist of an optional IP address, followed by
23
+ # colon, followed by a port number
24
+ show_usage ||= value_args.first !~ /\A([^\s]*)?:(\d+)\z/
25
+ host = $1
26
+ port = $2
27
+
28
+ bin_path = File.dirname(__FILE__)
29
+ gem_path = File.dirname( bin_path )
30
+ support_path = File.join( gem_path, 'lib', 'client_for_poslynx', 'has_client_console_support.rb' )
31
+
32
+ if show_usage
33
+
34
+ puts <<-EOS
35
+
36
+ Usage: poslynx_client_console [<host>]:<port-number> Host is the
37
+ domain name or IP address of the POSLynx or fake POS terminal
38
+ host. If this is not supplied, then 127.0.0.1 is assumed.
39
+
40
+ Opens an interactive Ruby shell with a globally acccessible
41
+ poslynx_client method that returns an object with conveniences
42
+ for making requests to a POSLynx or to your fake
43
+ POSLynx+terminal host.
44
+
45
+ For example, you might execute the following commands from
46
+ within the console...
47
+
48
+ poslynx_client.client_mac_for_examples = '123456789012'
49
+ resp = poslynx_client.send_request( poslynx_client.example_credit_card_sale_request )
50
+
51
+ To see what else the poslynx_client object provides, see the
52
+ source code
53
+ in #{support_path}
54
+
55
+ EOS
56
+
57
+ else
58
+
59
+ require 'client_for_poslynx/has_client_console_support'
60
+ require 'irb'
61
+
62
+ host = '127.0.0.1' if host.nil? || host.length == 0
63
+ port = port.to_i
64
+
65
+ class Object
66
+ include ClientForPoslynx::HasClientConsoleSupport
67
+ end
68
+
69
+ config = poslynx_client.config
70
+ config.host = host
71
+ config.port = port
72
+
73
+ IRB.start
74
+
75
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'client_for_poslynx/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "client_for_poslynx"
9
+ spec.version = ClientForPoslynx::VERSION
10
+ spec.authors = ["Steve Jorgensen"]
11
+ spec.email = ["stevej@stevej.name"]
12
+ spec.summary = "A TCP client for Precidia's POSLynx™ devices"
13
+ spec.description = spec.summary
14
+ spec.homepage = ""
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0")
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency 'nokogiri', "~> 1.6.2"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.6"
25
+ spec.add_development_dependency "rake"
26
+ spec.add_development_dependency "rspec"
27
+ end
@@ -0,0 +1,13 @@
1
+ # coding: utf-8
2
+
3
+ require "client_for_poslynx/version"
4
+ require "client_for_poslynx/data"
5
+ require "client_for_poslynx/message_handling"
6
+
7
+ module ClientForPoslynx
8
+
9
+ class Error < StandardError ; end
10
+ class InvalidXmlError < Error ; end
11
+ class InvalidXmlContentError < Error ; end
12
+
13
+ end
@@ -0,0 +1,14 @@
1
+ # coding: utf-8
2
+
3
+ require_relative "data/properties_xml_parser"
4
+ require_relative "data/requests"
5
+ require_relative "data/responses"
6
+ require_relative "data/xml_document"
7
+
8
+ module ClientForPoslynx
9
+
10
+ module Data
11
+
12
+ end
13
+
14
+ end
@@ -0,0 +1,202 @@
1
+ # coding: utf-8
2
+
3
+ require 'nokogiri'
4
+
5
+ module ClientForPoslynx
6
+ module Data
7
+
8
+ class AbstractData
9
+
10
+ class << self
11
+
12
+ alias blank_new new
13
+
14
+ def new
15
+ blank_new
16
+ end
17
+
18
+ def xml_parse(source_xml)
19
+ doc = XmlDocument.new( source_xml )
20
+ concrete_data_classes = descendants.
21
+ reject{ |d| d.name =~ /\bAbstract[A-Z]\w*$/ }.
22
+ sort_by{ |d| -d.ancestors.length }
23
+ data_class = concrete_data_classes.detect{ |dc|
24
+ dc.root_element_name == doc.root_name &&
25
+ dc.fits_properties?( doc.property_element_contents )
26
+ }
27
+ data_class.xml_deserialize(source_xml)
28
+ end
29
+
30
+ def xml_deserialize(xml)
31
+ doc = XmlDocument.new(xml)
32
+ raise InvalidXmlContentError, "#{root_element_name} root element not found" unless doc.root_name == root_element_name
33
+ instance = load_from_properties( doc.property_element_contents )
34
+ instance.source_data = doc.source_xml
35
+ instance
36
+ end
37
+
38
+ def load_from_properties(property_contents)
39
+ verify_defining_properties property_contents
40
+ variable_property_contents = select_variable_property_contents(property_contents)
41
+ instance = blank_new
42
+ populate_instance_from_properties instance, variable_property_contents
43
+ instance
44
+ end
45
+
46
+ def fits_properties?(property_contents)
47
+ unmatched = unmatched_defining_properties( property_contents )
48
+ unmatched.empty?
49
+ end
50
+
51
+ def defining_element_mappings
52
+ @defining_element_mappings ||=
53
+ begin
54
+ self == AbstractData ?
55
+ [] :
56
+ superclass.defining_element_mappings + []
57
+ end
58
+ end
59
+
60
+ def attr_element_mappings
61
+ @attr_element_mappings ||=
62
+ begin
63
+ self == AbstractData ?
64
+ [] :
65
+ superclass.attr_element_mappings + []
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def inherited(descendant)
72
+ descendants << descendant
73
+ end
74
+
75
+ def descendants
76
+ @@descendants ||= []
77
+ end
78
+
79
+ def defining_element_value(options)
80
+ attribute = options.fetch( :attribute )
81
+ element = options.fetch( :element )
82
+ value = options.fetch( :value )
83
+ define_singleton_method(attribute) { value }
84
+ defining_element_mappings << { attribute: attribute, element: element }
85
+ end
86
+
87
+ def attr_element_mapping(options)
88
+ type = options[:type]
89
+ unless type.nil?
90
+ raise ArgumentError, "The :type option must be a symbol, but a #{type.class} was given." unless Symbol === type
91
+ raise ArgumentError, "#{type.inspect} is not a valid :type option. Must be :array when given." unless type == :array
92
+ end
93
+ attribute = options.fetch( :attribute )
94
+ element = options.fetch( :element )
95
+ attr_accessor attribute
96
+ attr_element_mappings << options
97
+ end
98
+
99
+ def verify_defining_properties(property_contents)
100
+ unmatched = unmatched_defining_properties( property_contents )
101
+ return if unmatched.empty?
102
+ message = unmatched.map{ |mapping|
103
+ attribute, el_name = mapping.values_at(:attribute, :element)
104
+ defining_value = public_send(attribute)
105
+ "#{el_name} child element with \"#{defining_value}\" value not found."
106
+ }.join( ' ' )
107
+ raise InvalidXmlContentError, message
108
+ end
109
+
110
+ def unmatched_defining_properties(property_contents)
111
+ unmatched = []
112
+ defining_element_mappings.each do |mapping|
113
+ attribute, el_name = mapping.values_at(:attribute, :element)
114
+ defining_value = public_send(attribute)
115
+ unmatched << mapping unless property_contents[el_name] == defining_value
116
+ end
117
+ unmatched
118
+ end
119
+
120
+ def select_variable_property_contents(property_contents)
121
+ defining_element_names = defining_element_mappings.map{ |mapping| mapping[:element] }
122
+ property_contents.reject{ |name, content| defining_element_names.include?(name) }
123
+ end
124
+
125
+ def populate_instance_from_properties instance, variable_property_contents
126
+ variable_property_contents.each do |name, content|
127
+ mapping = attr_element_mappings.detect{ |mapping| mapping[:element] == name }
128
+ next unless mapping
129
+ value = if mapping[:numbered_lines]
130
+ template = mapping[:numbered_lines]
131
+ [].tap{ |lines|
132
+ line_num = 1
133
+ while ( content.has_key?(key = template % line_num) )
134
+ lines << content[key]
135
+ line_num += 1
136
+ end
137
+ }
138
+ elsif mapping[:type] == :array
139
+ content.split('|')
140
+ else
141
+ content
142
+ end
143
+ attribute = mapping[:attribute]
144
+ instance.public_send "#{attribute}=", value
145
+ end
146
+ end
147
+
148
+ end
149
+
150
+ attr_accessor :source_data
151
+
152
+ def xml_serialize
153
+ doc = Nokogiri::XML::Document.new
154
+ root = doc.create_element(self.class.root_element_name)
155
+ self.class.defining_element_mappings.each do |mapping|
156
+ content = self.class.public_send( mapping[:attribute] )
157
+ next unless content
158
+ element = doc.create_element( mapping[:element], nil, nil, content )
159
+ root.add_child element
160
+ end
161
+ self.class.attr_element_mappings.each do |mapping|
162
+ content = public_send( mapping[:attribute] )
163
+ next unless content
164
+ element = if mapping[:numbered_lines]
165
+ build_numbered_lines_xml_node( doc, mapping[:element], mapping[:numbered_lines], content )
166
+ elsif mapping[:type] == :array
167
+ build_vertical_bar_separated_list_node( doc, mapping[:element], content )
168
+ else
169
+ build_text_element_node( doc, mapping[:element], content )
170
+ end
171
+ root.add_child element
172
+ end
173
+ doc.root = root
174
+ doc.serialize(:save_with => Nokogiri::XML::Node::SaveOptions::AS_XML | Nokogiri::XML::Node::SaveOptions::NO_DECLARATION)
175
+ end
176
+
177
+ private
178
+
179
+ def build_numbered_lines_xml_node(doc, element_name, line_template, content)
180
+ element = doc.create_element( element_name )
181
+ [content].flatten.each_with_index do |line_text, idx|
182
+ line_num = idx + 1
183
+ element_name = line_template % line_num
184
+ line_el = doc.create_element( element_name, nil, nil, line_text )
185
+ element.add_child line_el
186
+ end
187
+ element
188
+ end
189
+
190
+ def build_vertical_bar_separated_list_node(doc, element_name, content)
191
+ text = [content].flatten * '|'
192
+ doc.create_element( element_name, nil, nil, text )
193
+ end
194
+
195
+ def build_text_element_node(doc, element_name, content)
196
+ doc.create_element( element_name, nil, nil, "#{content}" )
197
+ end
198
+
199
+ end
200
+
201
+ end
202
+ end