hermod 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.travis.yml +3 -0
- data/Gemfile +10 -0
- data/LICENSE +202 -0
- data/README.md +496 -0
- data/Rakefile +10 -0
- data/hermod.gemspec +32 -0
- data/lib/hermod/sanitisation.rb +26 -0
- data/lib/hermod/version.rb +3 -0
- data/lib/hermod/xml_node.rb +55 -0
- data/lib/hermod/xml_section.rb +108 -0
- data/lib/hermod/xml_section_builder.rb +223 -0
- data/lib/hermod.rb +5 -0
- data/spec/hermod/xml_section_builder/date_node_spec.rb +27 -0
- data/spec/hermod/xml_section_builder/monetary_node_spec.rb +50 -0
- data/spec/hermod/xml_section_builder/parent_node_spec.rb +28 -0
- data/spec/hermod/xml_section_builder/string_node_spec.rb +70 -0
- data/spec/hermod/xml_section_builder/yes_no_node_spec.rb +36 -0
- data/spec/hermod/xml_section_builder/yes_node_spec.rb +36 -0
- data/spec/hermod/xml_section_spec.rb +95 -0
- data/spec/hermod_spec.rb +7 -0
- data/spec/minitest_helper.rb +74 -0
- metadata +193 -0
data/README.md
ADDED
@@ -0,0 +1,496 @@
|
|
1
|
+
# Hermod
|
2
|
+
|
3
|
+
This gem makes it easier to talk to HMRC through the [Government Gateway][1] by
|
4
|
+
providing a DSL you can use to create Ruby classes to build the XML required in
|
5
|
+
a form that meets HMRC's specification.
|
6
|
+
|
7
|
+
It ensures that nodes appear in the correct order with the correct formatting
|
8
|
+
and allows you to preprocess values and apply validations at submission time.
|
9
|
+
|
10
|
+
[1]: http://www.hmrc.gov.uk/schemas/GatewayDocumentSubmissionProtocol_V3.1.pdf "HMRC's specification"
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
Add this line to your application's Gemfile:
|
15
|
+
|
16
|
+
gem 'hermod'
|
17
|
+
|
18
|
+
And then execute:
|
19
|
+
|
20
|
+
$ bundle
|
21
|
+
|
22
|
+
Or install it yourself as:
|
23
|
+
|
24
|
+
$ gem install hermod
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
This gem allows you to describe classes that represent a section of XML that
|
29
|
+
will be sent to HMRC. This description includes type, validation, and format
|
30
|
+
information as well as any runtime mutations that should be applied to inputs
|
31
|
+
you provide.
|
32
|
+
|
33
|
+
### Supported Types
|
34
|
+
|
35
|
+
The following types of XML node are supported:
|
36
|
+
|
37
|
+
* Strings
|
38
|
+
* Integers
|
39
|
+
* Dates
|
40
|
+
* Yes/No
|
41
|
+
* Yes only
|
42
|
+
* Monetary values
|
43
|
+
* Parent XML
|
44
|
+
|
45
|
+
#### Global Options
|
46
|
+
|
47
|
+
There are some options that can be passed to all or some of the different node
|
48
|
+
types.
|
49
|
+
|
50
|
+
**XML Name**
|
51
|
+
|
52
|
+
By default the name used for the XML node is generated by converting the node
|
53
|
+
name from `snake_case` to `TitleCase`. For example, the `date_of_birth` node in
|
54
|
+
the example above would become `DateOfBirth`. By providing an `xml_name` you
|
55
|
+
can override this, thus changing it to `BirthDate`
|
56
|
+
|
57
|
+
*Building an XmlSection*
|
58
|
+
```ruby
|
59
|
+
Example = Hermod::XmlSection.build do |builder|
|
60
|
+
builder.string_node :ni_number, xml_name: "NINumber"
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
*Using that XmlSection*
|
65
|
+
```ruby
|
66
|
+
Example.new do |example|
|
67
|
+
example.ni_number "AB123456C"
|
68
|
+
end
|
69
|
+
```
|
70
|
+
|
71
|
+
*The Resulting XML*
|
72
|
+
```xml
|
73
|
+
<Example>
|
74
|
+
<NINumber>AB123456C</NINumber>
|
75
|
+
</Example>
|
76
|
+
```
|
77
|
+
|
78
|
+
**Attributes**
|
79
|
+
|
80
|
+
Any node can have attributes which are defined by passing a `Hash` of symbol,
|
81
|
+
string pairs. The symbol is used to refer to the attribute when setting the
|
82
|
+
value of the node and the string is the form that will be sent to HMRC.
|
83
|
+
|
84
|
+
*Building an XmlSection*
|
85
|
+
```ruby
|
86
|
+
Example = Hermod::XmlSection.build do |builder|
|
87
|
+
builder.string_node :tax_code, attributes: {week_1_month_1: "WeekOneMonthOne"}
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
91
|
+
*Using that XmlSection*
|
92
|
+
```ruby
|
93
|
+
Example.new do |example|
|
94
|
+
example.tax_code "1000L", week_1_month_1: true
|
95
|
+
end
|
96
|
+
```
|
97
|
+
|
98
|
+
*The Resulting XML*
|
99
|
+
```xml
|
100
|
+
<Example>
|
101
|
+
<TaxCode WeekOneMonthOne="yes">1000L</TaxCode>
|
102
|
+
</Example>
|
103
|
+
```
|
104
|
+
|
105
|
+
**Optional**
|
106
|
+
|
107
|
+
Not all nodes allow this but for those that do (String, Date and Monetary nodes)
|
108
|
+
if a node is marked as optional then any blank values (like nil or an empty
|
109
|
+
string) will be ignored.
|
110
|
+
|
111
|
+
*Building an XmlSection*
|
112
|
+
```ruby
|
113
|
+
Example = Hermod::XmlSection.build do |builder|
|
114
|
+
builder.string_node :middle_name, optional: true
|
115
|
+
end
|
116
|
+
```
|
117
|
+
|
118
|
+
*Using that XmlSection*
|
119
|
+
```ruby
|
120
|
+
Example.new do |example|
|
121
|
+
example.middle_name nil
|
122
|
+
end
|
123
|
+
```
|
124
|
+
|
125
|
+
*No XML will be produced*
|
126
|
+
|
127
|
+
#### String Nodes
|
128
|
+
|
129
|
+
String nodes handle a wide variety of cases and can take regular expressions
|
130
|
+
and lists of values to restrict the provided values. If they are marked as
|
131
|
+
optional then the node will be excluded if the value given is blank
|
132
|
+
(`nil` or the empty string).
|
133
|
+
|
134
|
+
**Regular Expressions**
|
135
|
+
|
136
|
+
The `matches` option allows you to provide a regular expression that is used to
|
137
|
+
validate the input. If you try to pass a value that doesn't match the expression
|
138
|
+
a `Hermod::InvalidInputError` will be raised.
|
139
|
+
|
140
|
+
*Building an XmlSection*
|
141
|
+
```ruby
|
142
|
+
Example = Hermod::XmlSection.build do |builder|
|
143
|
+
builder.string_node :ni_number, matches: /\A[A-Z]{2}[0-9]{6}[A-D ]\z/
|
144
|
+
end
|
145
|
+
```
|
146
|
+
|
147
|
+
*Using that XmlSection*
|
148
|
+
```ruby
|
149
|
+
Example.new do |example|
|
150
|
+
example.ni_number "I can't remember it"
|
151
|
+
end
|
152
|
+
```
|
153
|
+
|
154
|
+
*A `Hermod::InvalidInputError` will be raised*
|
155
|
+
|
156
|
+
|
157
|
+
**Allowable Values**
|
158
|
+
|
159
|
+
The `allowable_values` lets you specify a list of string that are allowed for
|
160
|
+
this node. Passing a value not in this list will raise
|
161
|
+
a `Hermod::InvalidInputError`.
|
162
|
+
|
163
|
+
*Building an XmlSection*
|
164
|
+
```ruby
|
165
|
+
Example = Hermod::XmlSection.build do |builder|
|
166
|
+
builder.string_node :mood, allowable_values: %w(Happy Sad Hangry)
|
167
|
+
end
|
168
|
+
```
|
169
|
+
|
170
|
+
*Using that XmlSection*
|
171
|
+
```ruby
|
172
|
+
Example.new do |example|
|
173
|
+
example.gender "Wrathful"
|
174
|
+
end
|
175
|
+
```
|
176
|
+
|
177
|
+
*A `Hermod::InvalidInputError` will be raised*
|
178
|
+
|
179
|
+
**Input Mutator**
|
180
|
+
|
181
|
+
The `input_mutator` option allows you to provide a lambda that is provided with
|
182
|
+
two arguments, the value assigned to the node and the `Hash` of attributes (if
|
183
|
+
any). This can be used to change either or both of these and the lambda must
|
184
|
+
return both the value and the attributes as an array (`[value, attributes]`)
|
185
|
+
after they've been modified.
|
186
|
+
|
187
|
+
*Building an XmlSection*
|
188
|
+
```ruby
|
189
|
+
Example = Hermod::XmlSection.build do |builder|
|
190
|
+
builder.string_node :ni_number, xml_name: "NINO", optional: true, matches: /\A[A-Z]{2}[0-9]{6}[A-D ]\z/,
|
191
|
+
input_mutator: (lambda { |value, attrs| [value.delete(' ').upcase, attrs] })
|
192
|
+
end
|
193
|
+
```
|
194
|
+
|
195
|
+
*Using that XmlSection*
|
196
|
+
```ruby
|
197
|
+
Example.new do |example|
|
198
|
+
example.ni_number "AB 12 34 56 C"
|
199
|
+
end
|
200
|
+
```
|
201
|
+
|
202
|
+
*The Resulting XML*
|
203
|
+
```xml
|
204
|
+
<Example>
|
205
|
+
<NiNumber>AB123456C</NiNumber>
|
206
|
+
</Example>
|
207
|
+
```
|
208
|
+
|
209
|
+
#### Integer Nodes
|
210
|
+
|
211
|
+
Integer nodes let you provide a whole number that won't be formatted as
|
212
|
+
a monetary value.
|
213
|
+
|
214
|
+
**Range**
|
215
|
+
|
216
|
+
You can specify a `range` option as a hash with a `min` and `max` value. If you
|
217
|
+
provide a value outwith the range (inclusive) then
|
218
|
+
a `Hermod::InvalidInputError` exception will be raised.
|
219
|
+
|
220
|
+
*Building an XmlSection*
|
221
|
+
```ruby
|
222
|
+
Example = Hermod::XmlSection.build do |builder|
|
223
|
+
builder.integer_node :day_of_the_week, range: {min: 1, max: 7}
|
224
|
+
end
|
225
|
+
```
|
226
|
+
|
227
|
+
*Using that XmlSection*
|
228
|
+
```ruby
|
229
|
+
Example.new do |example|
|
230
|
+
example.day_of_the_week 8
|
231
|
+
end
|
232
|
+
```
|
233
|
+
|
234
|
+
*A `Hermod::InvalidInputError` will be raised*
|
235
|
+
|
236
|
+
#### Date Nodes
|
237
|
+
|
238
|
+
Date nodes let you send through a date to HMRC. It will be converted to the
|
239
|
+
given date format which you can specify as a format string in the `formats`
|
240
|
+
option passed to the `Hermod::XmlSection.build` call. Anything that responds to
|
241
|
+
`strftime` can be passed to the node. Anything else will cause an
|
242
|
+
`Hermod::InvalidInputError` exception to be raised.
|
243
|
+
|
244
|
+
*Building an XmlSection*
|
245
|
+
```ruby
|
246
|
+
Example = Hermod::XmlSection.build(formats: {date: "%Y-%m-%d"}) do |builder|
|
247
|
+
builder.date_node :date_of_birth
|
248
|
+
end
|
249
|
+
```
|
250
|
+
|
251
|
+
*Using that XmlSection*
|
252
|
+
```ruby
|
253
|
+
Example.new do |example|
|
254
|
+
example.date_of_birth Date.new(1988, 8, 13)
|
255
|
+
end
|
256
|
+
```
|
257
|
+
|
258
|
+
*The Resulting XML*
|
259
|
+
```xml
|
260
|
+
<Example>
|
261
|
+
<DateOfBirth>1988-08-13</DateOfBirth>
|
262
|
+
</Example>
|
263
|
+
```
|
264
|
+
|
265
|
+
#### Yes Nodes
|
266
|
+
|
267
|
+
Yes nodes allow you to send a boolean value to HMRC provided that value is
|
268
|
+
true. Nothing will be sent if the value is false. This pattern is commonly used
|
269
|
+
by HMRC for optional boolean nodes. They're known as "yes nodes" because HMRC
|
270
|
+
use "yes" and "no" in place of true and false in their XML.
|
271
|
+
|
272
|
+
*Building an XmlSection*
|
273
|
+
```ruby
|
274
|
+
Example = Hermod::XmlSection.build do |builder|
|
275
|
+
builder.yes_node :verily
|
276
|
+
builder.yes_node :nae
|
277
|
+
end
|
278
|
+
```
|
279
|
+
|
280
|
+
*Using that XmlSection*
|
281
|
+
```ruby
|
282
|
+
Example.new do |example|
|
283
|
+
example.verily true
|
284
|
+
example.nae false
|
285
|
+
end
|
286
|
+
```
|
287
|
+
|
288
|
+
*The Resulting XML*
|
289
|
+
```xml
|
290
|
+
<Example>
|
291
|
+
<Verily>yes</Verily>
|
292
|
+
</Example>
|
293
|
+
```
|
294
|
+
|
295
|
+
#### Yes/No Nodes
|
296
|
+
|
297
|
+
This works in a similar fashion to the yes nodes described above but if a false
|
298
|
+
value is provided a "no" will be sent instead of the node being excluded.
|
299
|
+
|
300
|
+
*Building an XmlSection*
|
301
|
+
```ruby
|
302
|
+
Example = Hermod::XmlSection.build do |builder|
|
303
|
+
builder.yes_no_node :verily
|
304
|
+
builder.yes_no_node :nae
|
305
|
+
end
|
306
|
+
```
|
307
|
+
|
308
|
+
*Using that XmlSection*
|
309
|
+
```ruby
|
310
|
+
Example.new do |example|
|
311
|
+
example.verily true
|
312
|
+
example.nae false
|
313
|
+
end
|
314
|
+
```
|
315
|
+
|
316
|
+
*The Resulting XML*
|
317
|
+
```xml
|
318
|
+
<Example>
|
319
|
+
<Verily>yes</Verily>
|
320
|
+
<Nae>no</Nae>
|
321
|
+
</Example>
|
322
|
+
```
|
323
|
+
|
324
|
+
#### Monetary Nodes
|
325
|
+
|
326
|
+
Monetary nodes let you send through monetary values to HMRC. They will be
|
327
|
+
converted to the given monetary format which you can specify as a format string
|
328
|
+
in the `formats` option passed to the `Hermod::XmlSection.build` call. Values
|
329
|
+
passed to monetary nodes should be BigDecimal objects.
|
330
|
+
|
331
|
+
**Negative**
|
332
|
+
|
333
|
+
By default negative numbers are allowed. If you need to prevent them you can
|
334
|
+
set the `negative` option to false.
|
335
|
+
|
336
|
+
*Building an XmlSection*
|
337
|
+
```ruby
|
338
|
+
Example = Hermod::XmlSection.build(formats: {money: "%.2f"}) do |builder|
|
339
|
+
builder.monetary_node :taxable_pay, negative: false
|
340
|
+
end
|
341
|
+
```
|
342
|
+
|
343
|
+
*Using that XmlSection*
|
344
|
+
```ruby
|
345
|
+
Example.new do |example|
|
346
|
+
example.taxable_pay BigDecimal.new("-300")
|
347
|
+
end
|
348
|
+
```
|
349
|
+
|
350
|
+
*A `Hermod::InvalidInputError` will be raised*
|
351
|
+
|
352
|
+
**Whole Units**
|
353
|
+
|
354
|
+
Sometimes HMRC require that you send through a value as a whole unit. If this
|
355
|
+
is the case you can set the `whole_units` option to true and if an invalid
|
356
|
+
value is passed a `Hermod::InvalidInputError` exception will be raised.
|
357
|
+
|
358
|
+
*Building an XmlSection*
|
359
|
+
```ruby
|
360
|
+
Example = Hermod::XmlSection.build(formats: {money: "%.2f"}) do |builder|
|
361
|
+
builder.monetary_node :lower_earnings_limit, whole_units: true
|
362
|
+
end
|
363
|
+
```
|
364
|
+
|
365
|
+
*Using that XmlSection*
|
366
|
+
```ruby
|
367
|
+
Example.new do |example|
|
368
|
+
example.lower_earnings_limit BigDecimal.new("153.49")
|
369
|
+
end
|
370
|
+
```
|
371
|
+
|
372
|
+
*A `Hermod::InvalidInputError` will be raised*
|
373
|
+
|
374
|
+
**Optional**
|
375
|
+
|
376
|
+
For monetary nodes the `optional` option will also prevent zero values from
|
377
|
+
being submitted.
|
378
|
+
|
379
|
+
*Building an XmlSection*
|
380
|
+
```ruby
|
381
|
+
Example = Hermod::XmlSection.build(formats: {money: "%.2f"}) do |builder|
|
382
|
+
builder.monetary_node :taxable_pay
|
383
|
+
builder.monetary_node :tax, optional: true
|
384
|
+
end
|
385
|
+
```
|
386
|
+
|
387
|
+
*Using that XmlSection*
|
388
|
+
```ruby
|
389
|
+
Example.new do |example|
|
390
|
+
example.taxable_pay BigDecimal.new("1000")
|
391
|
+
example.tax BigDecimal.new("0")
|
392
|
+
end
|
393
|
+
```
|
394
|
+
|
395
|
+
*The Resulting XML*
|
396
|
+
```xml
|
397
|
+
<Example>
|
398
|
+
<TaxablePay>1000.00</TaxablePay>
|
399
|
+
</Example>
|
400
|
+
```
|
401
|
+
|
402
|
+
#### Parent Nodes
|
403
|
+
|
404
|
+
Parent nodes are the way you specify that the contents of this node is another
|
405
|
+
`XmlSection`. The `xml_name` is ignored (whether you supply it or rely on the
|
406
|
+
default) so the given `symbolic_name` is just the name of the method you call
|
407
|
+
to add content. Instead the node name is picked up from the class name of the
|
408
|
+
XmlSection you add as a child.
|
409
|
+
|
410
|
+
*Building an XmlSection*
|
411
|
+
```ruby
|
412
|
+
Example = Hermod::XmlSection.build do |builder|
|
413
|
+
builder.parent_node :inner
|
414
|
+
end
|
415
|
+
|
416
|
+
Inside = Hermod::XmlSection.build do |builder|
|
417
|
+
builder.string_node :text"
|
418
|
+
end
|
419
|
+
```
|
420
|
+
|
421
|
+
*Using that XmlSection*
|
422
|
+
```ruby
|
423
|
+
Example.new do |example|
|
424
|
+
example.inner(Inside.new do |inside|
|
425
|
+
inside.text "Hello, World"
|
426
|
+
end)
|
427
|
+
end
|
428
|
+
```
|
429
|
+
|
430
|
+
*The Resulting XML*
|
431
|
+
```xml
|
432
|
+
<Example>
|
433
|
+
<Inside>
|
434
|
+
<Text>Hello, World</Text>
|
435
|
+
</Inside>
|
436
|
+
</Example>
|
437
|
+
```
|
438
|
+
|
439
|
+
### Full Example
|
440
|
+
|
441
|
+
This is all explained in more detail below but a reasonably complex XML section
|
442
|
+
may be described as follows.
|
443
|
+
|
444
|
+
```ruby
|
445
|
+
Details = Hermod::XmlSection.build(xml_name: "EmployeeDetails", formats: Payroll::RTI::FORMATS) do |builder|
|
446
|
+
builder.string_node :ni_number, xml_name: "NINO", optional: true, matches: /\A[A-Z]{2}[0-9]{6}[A-D ]\z/,
|
447
|
+
input_mutator: (lambda do |value, attrs|
|
448
|
+
[value.delete(' ').upcase, attrs]
|
449
|
+
end)
|
450
|
+
builder.parent_node :name
|
451
|
+
builder.parent_node :address
|
452
|
+
builder.date_node :date_of_birth, xml_name: "BirthDate", optional: true
|
453
|
+
builder.string_node :gender, allowable_values: %w(Male Female),
|
454
|
+
input_mutator: (lambda do |input, attrs|
|
455
|
+
[input == AppConstants::MALE ? Payroll::RTI::MALE : Payroll::RTI::FEMALE, attrs]
|
456
|
+
end)
|
457
|
+
end
|
458
|
+
```
|
459
|
+
|
460
|
+
This creates a class that can be used like so.
|
461
|
+
|
462
|
+
```ruby
|
463
|
+
xml = Payroll::RTI::Employee::Details.new do |details|
|
464
|
+
details.name(Payroll::RTI::Name.new do |name|
|
465
|
+
name.title employee.title
|
466
|
+
employee.forenames.each do |forename|
|
467
|
+
name.forename forename
|
468
|
+
end
|
469
|
+
name.surname employee.last_name
|
470
|
+
end)
|
471
|
+
details.gender employee.gender
|
472
|
+
|
473
|
+
details.address(Address.new do |address|
|
474
|
+
employee.address_lines.each do |line|
|
475
|
+
address.line line
|
476
|
+
end
|
477
|
+
address.postcode profile.postcode
|
478
|
+
end)
|
479
|
+
|
480
|
+
details.ni_number employee.ni_number
|
481
|
+
details.date_of_birth employee.date_of_birth
|
482
|
+
end)
|
483
|
+
```
|
484
|
+
|
485
|
+
Nodes are defined in the builder in the order they will be sent to HMRC. They
|
486
|
+
can then be called in any order when using the class. Calling the same method
|
487
|
+
multiple times will add multiple instances of that node and they will be output
|
488
|
+
in the order the calls were made in.
|
489
|
+
|
490
|
+
## Contributing
|
491
|
+
|
492
|
+
1. Fork it ( https://github.com/fac/hermod/fork )
|
493
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
494
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
495
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
496
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/hermod.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'hermod/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "hermod"
|
8
|
+
spec.version = Hermod::VERSION
|
9
|
+
spec.authors = ["Harry Mills"]
|
10
|
+
spec.email = ["harry@freeagent.com"]
|
11
|
+
spec.summary = %q{A Ruby library for talking to the HMRC Government Gateway.}
|
12
|
+
spec.description = %q{A Ruby library for talking to the HMRC Government Gateway.
|
13
|
+
This provides a builder for creating classes that can generate the XML needed complete with type information and
|
14
|
+
runtime validation.}
|
15
|
+
spec.homepage = ""
|
16
|
+
spec.license = "Apache License, Version 2.0"
|
17
|
+
|
18
|
+
spec.files = `git ls-files -z`.split("\x0")
|
19
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
20
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.required_ruby_version = ">= 2"
|
24
|
+
|
25
|
+
spec.add_runtime_dependency "libxml-ruby", "~> 2.7", ">= 2.7.0"
|
26
|
+
spec.add_runtime_dependency "activesupport", "~> 4.1", ">= 4.1.4"
|
27
|
+
|
28
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
29
|
+
spec.add_development_dependency "rake", "~> 10.3", ">= 10.3.2"
|
30
|
+
spec.add_development_dependency "minitest", "~> 5.3", ">= 5.3.5"
|
31
|
+
spec.add_development_dependency "nokogiri", "~> 1.6", ">= 1.6.2.1"
|
32
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Hermod
|
2
|
+
module Sanitisation
|
3
|
+
# TODO: replace this module with something better
|
4
|
+
# Any replacement should make it possible for both yes only attributes and
|
5
|
+
# yes/no attributes to work correctly.
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
# Private: alters attributes so a true becomes "yes", a no isn't sent and
|
10
|
+
# anything else gets turned into a String.
|
11
|
+
#
|
12
|
+
# value - the non-sanitised value
|
13
|
+
#
|
14
|
+
# Returns the sanitised value of the attribute ready for sending to HMRC.
|
15
|
+
def sanitise_attribute(value)
|
16
|
+
case value
|
17
|
+
when true
|
18
|
+
XmlSectionBuilder::YES
|
19
|
+
when false
|
20
|
+
nil # Attributes aren't included if they're false
|
21
|
+
else
|
22
|
+
value.to_s
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'hermod/sanitisation'
|
2
|
+
|
3
|
+
module Hermod
|
4
|
+
# A representation of an XML node with content and attributes.
|
5
|
+
class XmlNode
|
6
|
+
include Sanitisation
|
7
|
+
|
8
|
+
attr_reader :name, :value, :attributes
|
9
|
+
|
10
|
+
# Internal: creates a XmlNode. This is used by the XmlSectionBuilder's node
|
11
|
+
# building methods and should not be called manually.
|
12
|
+
#
|
13
|
+
# name - the name of the node as it appears in the XML
|
14
|
+
# value - the node contents as a string.
|
15
|
+
# attributes - a Hash of attributes as Symbol -> value pairs. The symbol
|
16
|
+
# must be in the list of attributes allowed for the node as
|
17
|
+
# set in the builder.
|
18
|
+
def initialize(name, value, attributes={})
|
19
|
+
@name = name
|
20
|
+
@value = value
|
21
|
+
@attributes = attributes
|
22
|
+
end
|
23
|
+
|
24
|
+
# Internal: turns the XmlNode into an XML::Node including any attributes
|
25
|
+
# without any sanitisation (currently - this may change in a future
|
26
|
+
# version).
|
27
|
+
#
|
28
|
+
# Returns an XML::Node built from the XmlNode object.
|
29
|
+
def to_xml
|
30
|
+
if value.respond_to? :to_xml
|
31
|
+
value.to_xml
|
32
|
+
else
|
33
|
+
XML::Node.new(@name, @value).tap do |node|
|
34
|
+
@attributes.each do |attribute_name, attribute_value|
|
35
|
+
node[attribute_name] = attribute_value if attribute_value.present?
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Internal: replaces symbol attributes with strings looked up in the provided
|
42
|
+
# hash
|
43
|
+
#
|
44
|
+
# lookup_hash - the hash to use to convert symbols to strings HMRC recognise
|
45
|
+
#
|
46
|
+
# Returns self so it can be used in a call chain (This may change in
|
47
|
+
# future)
|
48
|
+
def rename_attributes(lookup_hash)
|
49
|
+
attributes.keys.each do |attribute|
|
50
|
+
attributes[lookup_hash.fetch(attribute)] = sanitise_attribute(attributes.delete(attribute))
|
51
|
+
end
|
52
|
+
self
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|