lightningdb-happymapper 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,555 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+ require 'pp'
3
+ require 'uri'
4
+
5
+ class Address
6
+ include HappyMapper
7
+
8
+ tag 'address'
9
+ element :street, String
10
+ element :postcode, String
11
+ element :housenumber, String
12
+ element :city, String
13
+ element :country, String
14
+ end
15
+
16
+ class Feature
17
+ include HappyMapper
18
+ element :name, String, :tag => '.|.//text()'
19
+ end
20
+
21
+ class FeatureBullet
22
+ include HappyMapper
23
+
24
+ tag 'features_bullets'
25
+ has_many :features, Feature
26
+ element :bug, String
27
+ end
28
+
29
+ class Product
30
+ include HappyMapper
31
+
32
+ element :title, String
33
+ has_one :feature_bullets, FeatureBullet
34
+ has_one :address, Address
35
+ end
36
+
37
+ module FamilySearch
38
+ class Person
39
+ include HappyMapper
40
+
41
+ attribute :version, String
42
+ attribute :modified, Time
43
+ attribute :id, String
44
+ end
45
+
46
+ class Persons
47
+ include HappyMapper
48
+ has_many :person, Person
49
+ end
50
+
51
+ class FamilyTree
52
+ include HappyMapper
53
+
54
+ tag 'familytree'
55
+ attribute :version, String
56
+ attribute :status_message, String, :tag => 'statusMessage'
57
+ attribute :status_code, String, :tag => 'statusCode'
58
+ has_one :persons, Persons
59
+ end
60
+ end
61
+
62
+ module FedEx
63
+ class Address
64
+ include HappyMapper
65
+
66
+ tag 'Address'
67
+ namespace 'v2'
68
+ element :city, String, :tag => 'City'
69
+ element :state, String, :tag => 'StateOrProvinceCode'
70
+ element :zip, String, :tag => 'PostalCode'
71
+ element :countrycode, String, :tag => 'CountryCode'
72
+ element :residential, Boolean, :tag => 'Residential'
73
+ end
74
+
75
+ class Event
76
+ include HappyMapper
77
+
78
+ tag 'Events'
79
+ namespace 'v2'
80
+ element :timestamp, String, :tag => 'Timestamp'
81
+ element :eventtype, String, :tag => 'EventType'
82
+ element :eventdescription, String, :tag => 'EventDescription'
83
+ has_one :address, Address
84
+ end
85
+
86
+ class PackageWeight
87
+ include HappyMapper
88
+
89
+ tag 'PackageWeight'
90
+ namespace 'v2'
91
+ element :units, String, :tag => 'Units'
92
+ element :value, Integer, :tag => 'Value'
93
+ end
94
+
95
+ class TrackDetails
96
+ include HappyMapper
97
+
98
+ tag 'TrackDetails'
99
+ namespace 'v2'
100
+ element :tracking_number, String, :tag => 'TrackingNumber'
101
+ element :status_code, String, :tag => 'StatusCode'
102
+ element :status_desc, String, :tag => 'StatusDescription'
103
+ element :carrier_code, String, :tag => 'CarrierCode'
104
+ element :service_info, String, :tag => 'ServiceInfo'
105
+ has_one :weight, PackageWeight, :tag => 'PackageWeight'
106
+ element :est_delivery, String, :tag => 'EstimatedDeliveryTimestamp'
107
+ has_many :events, Event
108
+ end
109
+
110
+ class Notification
111
+ include HappyMapper
112
+
113
+ tag 'Notifications'
114
+ namespace 'v2'
115
+ element :severity, String, :tag => 'Severity'
116
+ element :source, String, :tag => 'Source'
117
+ element :code, Integer, :tag => 'Code'
118
+ element :message, String, :tag => 'Message'
119
+ element :localized_message, String, :tag => 'LocalizedMessage'
120
+ end
121
+
122
+ class TransactionDetail
123
+ include HappyMapper
124
+
125
+ tag 'TransactionDetail'
126
+ namespace 'v2'
127
+ element :cust_tran_id, String, :tag => 'CustomerTransactionId'
128
+ end
129
+
130
+ class TrackReply
131
+ include HappyMapper
132
+
133
+ tag 'TrackReply'
134
+ namespace 'v2'
135
+ element :highest_severity, String, :tag => 'HighestSeverity'
136
+ element :more_data, Boolean, :tag => 'MoreData'
137
+ has_many :notifications, Notification, :tag => 'Notifications'
138
+ has_many :trackdetails, TrackDetails, :tag => 'TrackDetails'
139
+ has_one :tran_detail, TransactionDetail, :tab => 'TransactionDetail'
140
+ end
141
+ end
142
+
143
+ class Place
144
+ include HappyMapper
145
+ element :name, String
146
+ end
147
+
148
+ class Radar
149
+ include HappyMapper
150
+ has_many :places, Place, :tag => :place
151
+ end
152
+
153
+ class Post
154
+ include HappyMapper
155
+
156
+ attribute :href, String
157
+ attribute :hash, String
158
+ attribute :description, String
159
+ attribute :tag, String
160
+ attribute :time, Time
161
+ attribute :others, Integer
162
+ attribute :extended, String
163
+ end
164
+
165
+ class User
166
+ include HappyMapper
167
+
168
+ element :id, Integer
169
+ element :name, String
170
+ element :screen_name, String
171
+ element :location, String
172
+ element :description, String
173
+ element :profile_image_url, String
174
+ element :url, String
175
+ element :protected, Boolean
176
+ element :followers_count, Integer
177
+ end
178
+
179
+ class Status
180
+ include HappyMapper
181
+
182
+ element :id, Integer
183
+ element :text, String
184
+ element :created_at, Time
185
+ element :source, String
186
+ element :truncated, Boolean
187
+ element :in_reply_to_status_id, Integer
188
+ element :in_reply_to_user_id, Integer
189
+ element :favorited, Boolean
190
+ element :non_existent, String, :tag => 'dummy', :namespace => 'fake'
191
+ has_one :user, User
192
+ end
193
+
194
+ class CurrentWeather
195
+ include HappyMapper
196
+
197
+ tag 'ob'
198
+ namespace 'aws'
199
+ element :temperature, Integer, :tag => 'temp'
200
+ element :feels_like, Integer, :tag => 'feels-like'
201
+ element :current_condition, String, :tag => 'current-condition', :attributes => {:icon => String}
202
+ end
203
+
204
+ # for type coercion
205
+ class ProductGroup < String; end
206
+
207
+ module PITA
208
+ class Item
209
+ include HappyMapper
210
+
211
+ tag 'Item' # if you put class in module you need tag
212
+ element :asin, String, :tag => 'ASIN'
213
+ element :detail_page_url, URI, :tag => 'DetailPageURL', :parser => :parse
214
+ element :manufacturer, String, :tag => 'Manufacturer', :deep => true
215
+ element :point, String, :tag => 'point', :namespace => 'georss'
216
+ element :product_group, ProductGroup, :tag => 'ProductGroup', :deep => true, :parser => :new, :raw => true
217
+ end
218
+
219
+ class Items
220
+ include HappyMapper
221
+
222
+ tag 'Items' # if you put class in module you need tag
223
+ element :total_results, Integer, :tag => 'TotalResults'
224
+ element :total_pages, Integer, :tag => 'TotalPages'
225
+ has_many :items, Item
226
+ end
227
+ end
228
+
229
+ module GitHub
230
+ class Commit
231
+ include HappyMapper
232
+
233
+ tag "commit"
234
+ element :url, String
235
+ element :tree, String
236
+ element :message, String
237
+ element :id, String
238
+ element :'committed-date', Date
239
+ end
240
+ end
241
+
242
+ module QuarterTest
243
+ class Quarter
244
+ include HappyMapper
245
+
246
+ element :start, String
247
+ end
248
+
249
+ class Details
250
+ include HappyMapper
251
+
252
+ element :round, Integer
253
+ element :quarter, Integer
254
+ end
255
+
256
+ class Game
257
+ include HappyMapper
258
+
259
+ # in an ideal world, the following elements would all be
260
+ # called 'quarter' with an attribute indicating which quarter
261
+ # it represented, but the refactoring that allows a single class
262
+ # to be used for all these differently named elements is the next
263
+ # best thing
264
+ has_one :details, QuarterTest::Details
265
+ has_one :q1, QuarterTest::Quarter, :tag => 'q1'
266
+ has_one :q2, QuarterTest::Quarter, :tag => 'q2'
267
+ has_one :q3, QuarterTest::Quarter, :tag => 'q3'
268
+ has_one :q4, QuarterTest::Quarter, :tag => 'q4'
269
+ end
270
+ end
271
+
272
+ describe HappyMapper do
273
+
274
+ describe "being included into another class" do
275
+ before do
276
+ Foo.instance_variable_set("@attributes", {})
277
+ Foo.instance_variable_set("@elements", {})
278
+ end
279
+ class Foo; include HappyMapper end
280
+
281
+ it "should set attributes to an array" do
282
+ Foo.attributes.should == []
283
+ end
284
+
285
+ it "should set @elements to a hash" do
286
+ Foo.elements.should == []
287
+ end
288
+
289
+ it "should allow adding an attribute" do
290
+ lambda {
291
+ Foo.attribute :name, String
292
+ }.should change(Foo, :attributes)
293
+ end
294
+
295
+ it "should allow adding an attribute containing a dash" do
296
+ lambda {
297
+ Foo.attribute :'bar-baz', String
298
+ }.should change(Foo, :attributes)
299
+ end
300
+
301
+ it "should be able to get all attributes in array" do
302
+ Foo.attribute :name, String
303
+ Foo.attributes.size.should == 1
304
+ end
305
+
306
+ it "should allow adding an element" do
307
+ lambda {
308
+ Foo.element :name, String
309
+ }.should change(Foo, :elements)
310
+ end
311
+
312
+ it "should allow adding an element containing a dash" do
313
+ lambda {
314
+ Foo.element :'bar-baz', String
315
+ }.should change(Foo, :elements)
316
+
317
+ end
318
+
319
+ it "should be able to get all elements in array" do
320
+ Foo.element(:name, String)
321
+ Foo.elements.size.should == 1
322
+ end
323
+
324
+ it "should allow has one association" do
325
+ Foo.has_one(:user, User)
326
+ element = Foo.elements.first
327
+ element.name.should == 'user'
328
+ element.type.should == User
329
+ element.options[:single] = true
330
+ end
331
+
332
+ it "should allow has many association" do
333
+ Foo.has_many(:users, User)
334
+ element = Foo.elements.first
335
+ element.name.should == 'users'
336
+ element.type.should == User
337
+ element.options[:single] = false
338
+ end
339
+
340
+ it "should default tag name to lowercase class" do
341
+ Foo.tag_name.should == 'foo'
342
+ end
343
+
344
+ it "should default tag name of class in modules to the last constant lowercase" do
345
+ module Bar; class Baz; include HappyMapper; end; end
346
+ Bar::Baz.tag_name.should == 'baz'
347
+ end
348
+
349
+ it "should allow setting tag name" do
350
+ Foo.tag('FooBar')
351
+ Foo.tag_name.should == 'FooBar'
352
+ end
353
+
354
+ it "should allow setting a namespace" do
355
+ Foo.namespace(namespace = "foo")
356
+ Foo.namespace.should == namespace
357
+ end
358
+
359
+ it "should provide #parse" do
360
+ Foo.should respond_to(:parse)
361
+ end
362
+ end
363
+
364
+ describe "#attributes" do
365
+ it "should only return attributes for the current class" do
366
+ Post.attributes.size.should == 7
367
+ Status.attributes.size.should == 0
368
+ end
369
+ end
370
+
371
+ describe "#elements" do
372
+ it "should only return elements for the current class" do
373
+ Post.elements.size.should == 0
374
+ Status.elements.size.should == 10
375
+ end
376
+ end
377
+
378
+ it "should parse xml attributes into ruby objects" do
379
+ posts = Post.parse(fixture_file('posts.xml'))
380
+ posts.size.should == 20
381
+ first = posts.first
382
+ first.href.should == 'http://roxml.rubyforge.org/'
383
+ first.hash.should == '19bba2ab667be03a19f67fb67dc56917'
384
+ first.description.should == 'ROXML - Ruby Object to XML Mapping Library'
385
+ first.tag.should == 'ruby xml gems mapping'
386
+ first.time.should == Time.utc(2008, 8, 9, 5, 24, 20)
387
+ first.others.should == 56
388
+ first.extended.should == 'ROXML is a Ruby library designed to make it easier for Ruby developers to work with XML. Using simple annotations, it enables Ruby classes to be custom-mapped to XML. ROXML takes care of the marshalling and unmarshalling of mapped attributes so that developers can focus on building first-class Ruby classes.'
389
+ end
390
+
391
+ it "should parse xml elements to ruby objcts" do
392
+ statuses = Status.parse(fixture_file('statuses.xml'))
393
+ statuses.size.should == 20
394
+ first = statuses.first
395
+ first.id.should == 882281424
396
+ first.created_at.should == Time.utc(2008, 8, 9, 5, 38, 12)
397
+ first.source.should == 'web'
398
+ first.truncated.should be_false
399
+ first.in_reply_to_status_id.should == 1234
400
+ first.in_reply_to_user_id.should == 12345
401
+ first.favorited.should be_false
402
+ first.user.id.should == 4243
403
+ first.user.name.should == 'John Nunemaker'
404
+ first.user.screen_name.should == 'jnunemaker'
405
+ first.user.location.should == 'Mishawaka, IN, US'
406
+ first.user.description.should == 'Loves his wife, ruby, notre dame football and iu basketball'
407
+ first.user.profile_image_url.should == 'http://s3.amazonaws.com/twitter_production/profile_images/53781608/Photo_75_normal.jpg'
408
+ first.user.url.should == 'http://addictedtonew.com'
409
+ first.user.protected.should be_false
410
+ first.user.followers_count.should == 486
411
+ end
412
+
413
+ it "should parse xml containing the desired element as root node" do
414
+ address = Address.parse(fixture_file('address.xml'), :single => true)
415
+ address.street.should == 'Milchstrasse'
416
+ address.postcode.should == '26131'
417
+ address.housenumber.should == '23'
418
+ address.city.should == 'Oldenburg'
419
+ address.country.should == 'Germany'
420
+ end
421
+
422
+ it "should parse xml with default namespace (amazon)" do
423
+ file_contents = fixture_file('pita.xml')
424
+ items = PITA::Items.parse(file_contents, :single => true)
425
+ items.total_results.should == 22
426
+ items.total_pages.should == 3
427
+ first = items.items[0]
428
+ second = items.items[1]
429
+ first.asin.should == '0321480791'
430
+ first.point.should == '38.5351715088 -121.7948684692'
431
+ first.detail_page_url.should be_a_kind_of(URI)
432
+ first.detail_page_url.to_s.should == 'http://www.amazon.com/gp/redirect.html%3FASIN=0321480791%26tag=ws%26lcode=xm2%26cID=2025%26ccmID=165953%26location=/o/ASIN/0321480791%253FSubscriptionId=dontbeaswoosh'
433
+ first.manufacturer.should == 'Addison-Wesley Professional'
434
+ first.product_group.should == '<ProductGroup>Book</ProductGroup>'
435
+ second.asin.should == '047022388X'
436
+ second.manufacturer.should == 'Wrox'
437
+ end
438
+
439
+ it "should parse xml that has attributes of elements" do
440
+ items = CurrentWeather.parse(fixture_file('current_weather.xml'))
441
+ first = items[0]
442
+ first.temperature.should == 51
443
+ first.feels_like.should == 51
444
+ first.current_condition.should == 'Sunny'
445
+ first.current_condition.icon.should == 'http://deskwx.weatherbug.com/images/Forecast/icons/cond007.gif'
446
+ end
447
+
448
+ it "should parse xml with nested elements" do
449
+ radars = Radar.parse(fixture_file('radar.xml'))
450
+ first = radars[0]
451
+ first.places.size.should == 1
452
+ first.places[0].name.should == 'Store'
453
+ second = radars[1]
454
+ second.places.size.should == 0
455
+ third = radars[2]
456
+ third.places.size.should == 2
457
+ third.places[0].name.should == 'Work'
458
+ third.places[1].name.should == 'Home'
459
+ end
460
+
461
+ it "should parse xml with element name different to class name" do
462
+ game = QuarterTest::Game.parse(fixture_file('quarters.xml'))
463
+ game.q1.start.should == '4:40:15 PM'
464
+ game.q2.start.should == '5:18:53 PM'
465
+ end
466
+
467
+ it "should parse xml that has elements with dashes" do
468
+ commit = GitHub::Commit.parse(fixture_file('commit.xml'))
469
+ commit.message.should == "move commands.rb and helpers.rb into commands/ dir"
470
+ commit.url.should == "http://github.com/defunkt/github-gem/commit/c26d4ce9807ecf57d3f9eefe19ae64e75bcaaa8b"
471
+ commit.id.should == "c26d4ce9807ecf57d3f9eefe19ae64e75bcaaa8b"
472
+ commit.committed_date.should == Date.parse("2008-03-02T16:45:41-08:00")
473
+ commit.tree.should == "28a1a1ca3e663d35ba8bf07d3f1781af71359b76"
474
+ end
475
+
476
+ it "should parse xml with no namespace" do
477
+ product = Product.parse(fixture_file('product_no_namespace.xml'), :single => true)
478
+ product.title.should == "A Title"
479
+ product.feature_bullets.bug.should == 'This is a bug'
480
+ product.feature_bullets.features.size.should == 2
481
+ product.feature_bullets.features[0].name.should == 'This is feature text 1'
482
+ product.feature_bullets.features[1].name.should == 'This is feature text 2'
483
+ end
484
+
485
+ it "should parse xml with default namespace" do
486
+ product = Product.parse(fixture_file('product_default_namespace.xml'), :single => true)
487
+ product.title.should == "A Title"
488
+ product.feature_bullets.bug.should == 'This is a bug'
489
+ product.feature_bullets.features.size.should == 2
490
+ product.feature_bullets.features[0].name.should == 'This is feature text 1'
491
+ product.feature_bullets.features[1].name.should == 'This is feature text 2'
492
+ end
493
+
494
+ it "should parse xml with single namespace" do
495
+ product = Product.parse(fixture_file('product_single_namespace.xml'), :single => true)
496
+ product.title.should == "A Title"
497
+ product.feature_bullets.bug.should == 'This is a bug'
498
+ product.feature_bullets.features.size.should == 2
499
+ product.feature_bullets.features[0].name.should == 'This is feature text 1'
500
+ product.feature_bullets.features[1].name.should == 'This is feature text 2'
501
+ end
502
+
503
+ it "should parse xml with multiple namespaces" do
504
+ track = FedEx::TrackReply.parse(fixture_file('multiple_namespaces.xml'))
505
+ track.highest_severity.should == 'SUCCESS'
506
+ track.more_data.should be_false
507
+ notification = track.notifications.first
508
+ notification.code.should == 0
509
+ notification.localized_message.should == 'Request was successfully processed.'
510
+ notification.message.should == 'Request was successfully processed.'
511
+ notification.severity.should == 'SUCCESS'
512
+ notification.source.should == 'trck'
513
+ detail = track.trackdetails.first
514
+ detail.carrier_code.should == 'FDXG'
515
+ detail.est_delivery.should == '2009-01-02T00:00:00'
516
+ detail.service_info.should == 'Ground-Package Returns Program-Domestic'
517
+ detail.status_code.should == 'OD'
518
+ detail.status_desc.should == 'On FedEx vehicle for delivery'
519
+ detail.tracking_number.should == '9611018034267800045212'
520
+ detail.weight.units.should == 'LB'
521
+ detail.weight.value.should == 2
522
+ events = detail.events
523
+ events.size.should == 10
524
+ first_event = events[0]
525
+ first_event.eventdescription.should == 'On FedEx vehicle for delivery'
526
+ first_event.eventtype.should == 'OD'
527
+ first_event.timestamp.should == '2009-01-02T06:00:00'
528
+ first_event.address.city.should == 'WICHITA'
529
+ first_event.address.countrycode.should == 'US'
530
+ first_event.address.residential.should be_false
531
+ first_event.address.state.should == 'KS'
532
+ first_event.address.zip.should == '67226'
533
+ last_event = events[-1]
534
+ last_event.eventdescription.should == 'In FedEx possession'
535
+ last_event.eventtype.should == 'IP'
536
+ last_event.timestamp.should == '2008-12-27T09:40:00'
537
+ last_event.address.city.should == 'LONGWOOD'
538
+ last_event.address.countrycode.should == 'US'
539
+ last_event.address.residential.should be_false
540
+ last_event.address.state.should == 'FL'
541
+ last_event.address.zip.should == '327506398'
542
+ track.tran_detail.cust_tran_id.should == '20090102-111321'
543
+ end
544
+
545
+ xit "should parse family search xml" do
546
+ tree = FamilySearch::FamilyTree.parse(fixture_file('family_tree.xml'))
547
+ tree.version.should == '1.0.20071213.942'
548
+ tree.status_message.should == 'OK'
549
+ tree.status_code.should == '200'
550
+ # tree.people.size.should == 1
551
+ # tree.people.first.version.should == '1199378491000'
552
+ # tree.people.first.modified.should == Time.utc(2008, 1, 3, 16, 41, 31) # 2008-01-03T09:41:31-07:00
553
+ # tree.people.first.id.should == 'KWQS-BBQ'
554
+ end
555
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --colour
@@ -0,0 +1,13 @@
1
+ begin
2
+ require 'spec'
3
+ rescue LoadError
4
+ require 'rubygems'
5
+ gem 'rspec'
6
+ require 'spec'
7
+ end
8
+
9
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'happymapper')
10
+
11
+ def fixture_file(filename)
12
+ File.read(File.dirname(__FILE__) + "/fixtures/#{filename}")
13
+ end
@@ -0,0 +1,47 @@
1
+ @media screen, projection {
2
+ /*
3
+ Copyright (c) 2007, Yahoo! Inc. All rights reserved.
4
+ Code licensed under the BSD License:
5
+ http://developer.yahoo.net/yui/license.txt
6
+ version: 2.2.0
7
+ */
8
+ body {font:13px arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;}table {font-size:inherit;font:100%;}select, input, textarea {font:99% arial,helvetica,clean,sans-serif;}pre, code {font:115% monospace;*font-size:100%;}body * {line-height:1.22em;}
9
+ body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}/*ol,ul {list-style:none;}*/caption,th {text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym {border:0;}
10
+ /* end of yahoo reset and fonts */
11
+
12
+ body {color:#333; background:#4b1a1a; line-height:1.3;}
13
+ p {margin:0 0 20px;}
14
+ a {color:#4b1a1a;}
15
+ a:hover {text-decoration:none;}
16
+ strong {font-weight:bold;}
17
+ em {font-style:italics;}
18
+ h1,h2,h3,h4,h5,h6 {font-weight:bold;}
19
+ h1 {font-size:197%; margin:30px 0; color:#4b1a1a;}
20
+ h2 {font-size:174%; margin:20px 0; color:#b8111a;}
21
+ h3 {font-size:152%; margin:10px 0;}
22
+ h4 {font-size:129%; margin:10px 0;}
23
+ pre {background:#eee; margin:0 0 20px; padding:20px; border:1px solid #ccc; font-size:100%; overflow:auto;}
24
+ code {font-size:100%; margin:0; padding:0;}
25
+ ul, ol {margin:10px 0 10px 25px;}
26
+ ol li {margin:0 0 10px;}
27
+
28
+
29
+
30
+
31
+
32
+ div#wrapper {background:#fff; width:560px; margin:0 auto; padding:20px; border:10px solid #bc8c46; border-width:0 10px;}
33
+ div#header {position:relative; border-bottom:1px dotted; margin:0 0 10px; padding:0 0 10px;}
34
+ div#header p {margin:0; padding:0;}
35
+ div#header h1 {margin:0; padding:0;}
36
+ ul#nav {position:absolute; top:0; right:0; list-style:none; margin:0; padding:0;}
37
+ ul#nav li {display:inline; padding:0 0 0 5px;}
38
+ ul#nav li a {}
39
+ div#content {}
40
+ div#footer {margin:40px 0 0; border-top:1px dotted; padding:10px 0 0;}
41
+
42
+
43
+
44
+
45
+
46
+
47
+ }