unhappymapper 0.4.0

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.
Files changed (39) hide show
  1. data/README.md +479 -0
  2. data/TODO +0 -0
  3. data/lib/happymapper/attribute.rb +3 -0
  4. data/lib/happymapper/element.rb +3 -0
  5. data/lib/happymapper/item.rb +250 -0
  6. data/lib/happymapper/text_node.rb +3 -0
  7. data/lib/happymapper.rb +574 -0
  8. data/spec/fixtures/address.xml +8 -0
  9. data/spec/fixtures/ambigous_items.xml +22 -0
  10. data/spec/fixtures/analytics.xml +61 -0
  11. data/spec/fixtures/analytics_profile.xml +127 -0
  12. data/spec/fixtures/commit.xml +52 -0
  13. data/spec/fixtures/current_weather.xml +89 -0
  14. data/spec/fixtures/dictionary.xml +20 -0
  15. data/spec/fixtures/family_tree.xml +21 -0
  16. data/spec/fixtures/inagy.xml +86 -0
  17. data/spec/fixtures/lastfm.xml +355 -0
  18. data/spec/fixtures/multiple_namespaces.xml +170 -0
  19. data/spec/fixtures/multiple_primitives.xml +5 -0
  20. data/spec/fixtures/pita.xml +133 -0
  21. data/spec/fixtures/posts.xml +23 -0
  22. data/spec/fixtures/product_default_namespace.xml +17 -0
  23. data/spec/fixtures/product_no_namespace.xml +10 -0
  24. data/spec/fixtures/product_single_namespace.xml +10 -0
  25. data/spec/fixtures/quarters.xml +19 -0
  26. data/spec/fixtures/radar.xml +21 -0
  27. data/spec/fixtures/statuses.xml +422 -0
  28. data/spec/fixtures/subclass_namespace.xml +50 -0
  29. data/spec/happymapper_attribute_spec.rb +21 -0
  30. data/spec/happymapper_element_spec.rb +21 -0
  31. data/spec/happymapper_item_spec.rb +115 -0
  32. data/spec/happymapper_spec.rb +941 -0
  33. data/spec/happymapper_text_node_spec.rb +21 -0
  34. data/spec/happymapper_to_xml_namespaces_spec.rb +196 -0
  35. data/spec/happymapper_to_xml_spec.rb +196 -0
  36. data/spec/ignay_spec.rb +95 -0
  37. data/spec/spec_helper.rb +7 -0
  38. data/spec/xpath_spec.rb +88 -0
  39. metadata +150 -0
@@ -0,0 +1,574 @@
1
+ require 'nokogiri'
2
+ require 'date'
3
+ require 'time'
4
+
5
+ class Boolean; end
6
+ class XmlContent; end
7
+
8
+ module HappyMapper
9
+
10
+ DEFAULT_NS = "happymapper"
11
+
12
+ def self.included(base)
13
+ base.instance_variable_set("@attributes", {})
14
+ base.instance_variable_set("@elements", {})
15
+ base.instance_variable_set("@registered_namespaces", {})
16
+
17
+ base.extend ClassMethods
18
+ end
19
+
20
+ module ClassMethods
21
+
22
+ #
23
+ # The xml has the following attributes defined.
24
+ #
25
+ # @example
26
+ #
27
+ # "<country code='de'>Germany</country>"
28
+ #
29
+ # # definition of the 'code' attribute within the class
30
+ # attribute :code, String
31
+ #
32
+ # @param [Symbol] name the name of the accessor that is created
33
+ # @param [String,Class] type the class name of the name of the class whcih
34
+ # the object will be converted upon parsing
35
+ # @param [Hash] options additional parameters to send to the relationship
36
+ #
37
+ def attribute(name, type, options={})
38
+ attribute = Attribute.new(name, type, options)
39
+ @attributes[to_s] ||= []
40
+ @attributes[to_s] << attribute
41
+ attr_accessor attribute.method_name.intern
42
+ end
43
+
44
+ #
45
+ # The elements defined through {#attribute}.
46
+ #
47
+ # @return [Array<Attribute>] a list of the attributes defined for this class;
48
+ # an empty array is returned when there have been no attributes defined.
49
+ #
50
+ def attributes
51
+ @attributes[to_s] || []
52
+ end
53
+
54
+ #
55
+ # Register a namespace that is used to persist the object namespace back to
56
+ # XML.
57
+ #
58
+ # @example
59
+ #
60
+ # register_namespace 'prefix', 'http://www.unicornland.com/prefix'
61
+ #
62
+ # # the output will contain the namespace defined
63
+ #
64
+ # "<outputXML xmlns:prefix="http://www.unicornland.com/prefix">
65
+ # ...
66
+ # </outputXML>"
67
+ #
68
+ # @param [String] namespace the xml prefix
69
+ # @param [String] ns url for the xml namespace
70
+ #
71
+ def register_namespace(namespace, ns)
72
+ @registered_namespaces.merge!({namespace => ns})
73
+ end
74
+
75
+ #
76
+ # An element defined in the XML that is parsed.
77
+ #
78
+ # @example
79
+ #
80
+ # "<address location='home'>
81
+ # <city>Oldenburg</city>
82
+ # </address>"
83
+ #
84
+ # # definition of the 'city' element within the class
85
+ #
86
+ # element :city, String
87
+ #
88
+ # @param [Symbol] name the name of the accessor that is created
89
+ # @param [String,Class] type the class name of the name of the class whcih
90
+ # the object will be converted upon parsing
91
+ # @param [Hash] options additional parameters to send to the relationship
92
+ #
93
+ def element(name, type, options={})
94
+ element = Element.new(name, type, options)
95
+ @elements[to_s] ||= []
96
+ @elements[to_s] << element
97
+ attr_accessor element.method_name.intern
98
+ end
99
+
100
+ #
101
+ # The elements defined through {#element}, {#has_one}, and {#has_many}.
102
+ #
103
+ # @return [Array<Element>] a list of the elements contained defined for this
104
+ # class; an empty array is returned when there have been no elements
105
+ # defined.
106
+ #
107
+ def elements
108
+ @elements[to_s] || []
109
+ end
110
+
111
+ #
112
+ # The value stored in the text node of the current element.
113
+ #
114
+ # @example
115
+ #
116
+ # "<firstName>Michael Jackson</firstName>"
117
+ #
118
+ # # definition of the 'firstName' text node within the class
119
+ #
120
+ # text_node :first_name, String
121
+ #
122
+ # @param [Symbol] name the name of the accessor that is created
123
+ # @param [String,Class] type the class name of the name of the class whcih
124
+ # the object will be converted upon parsing
125
+ # @param [Hash] options additional parameters to send to the relationship
126
+ #
127
+ def text_node(name, type, options={})
128
+ @text_node = TextNode.new(name, type, options)
129
+ attr_accessor @text_node.method_name.intern
130
+ end
131
+
132
+ #
133
+ # Sets the object to have xml content, this will assign the XML contents
134
+ # that are parsed to the attribute accessor xml_content. The object will
135
+ # respond to the method #xml_content and will return the XML data that
136
+ # it has parsed.
137
+ #
138
+ def has_xml_content
139
+ attr_accessor :xml_content
140
+ end
141
+
142
+ #
143
+ # The object has one of these elements in the XML. If there are multiple,
144
+ # the last one will be set to this value.
145
+ #
146
+ # @param [Symbol] name the name of the accessor that is created
147
+ # @param [String,Class] type the class name of the name of the class whcih
148
+ # the object will be converted upon parsing
149
+ # @param [Hash] options additional parameters to send to the relationship
150
+ #
151
+ # @see #element
152
+ #
153
+ def has_one(name, type, options={})
154
+ element name, type, {:single => true}.merge(options)
155
+ end
156
+
157
+ #
158
+ # The object has many of these elements in the XML.
159
+ #
160
+ # @param [Symbol] name the name of accessor that is created
161
+ # @param [String,Class] type the class name or the name of the class which
162
+ # the object will be converted upon parsing.
163
+ # @param [Hash] options additional parameters to send to the relationship
164
+ #
165
+ # @see #element
166
+ #
167
+ def has_many(name, type, options={})
168
+ element name, type, {:single => false}.merge(options)
169
+ end
170
+
171
+ #
172
+ # Specify a namespace if a node and all its children are all namespaced
173
+ # elements. This is simpler than passing the :namespace option to each
174
+ # defined element.
175
+ #
176
+ # @param [String] namespace the namespace to set as default for the class
177
+ # element.
178
+ #
179
+ def namespace(namespace = nil)
180
+ @namespace = namespace if namespace
181
+ @namespace
182
+ end
183
+
184
+ #
185
+ # @param [String] new_tag_name the name for the tag
186
+ #
187
+ def tag(new_tag_name)
188
+ @tag_name = new_tag_name.to_s unless new_tag_name.nil? || new_tag_name.to_s.empty?
189
+ end
190
+
191
+ #
192
+ # The name of the tag
193
+ #
194
+ # @return [String] the name of the tag as a string, downcased
195
+ #
196
+ def tag_name
197
+ @tag_name ||= to_s.split('::')[-1].downcase
198
+ end
199
+
200
+ #
201
+ # @param [Nokogiri::XML::Node,Nokogiri:XML::Document,String] xml the XML
202
+ # contents to convert into Object.
203
+ # @param [Hash] options additional information for parsing. :single => true
204
+ # if requesting a single object, otherwise it defaults to retuning an
205
+ # array of multiple items. :xpath information where to start the parsing
206
+ # :namespace is the namespace to use for additional information.
207
+ #
208
+ def parse(xml, options = {})
209
+
210
+ # create a local copy of the objects namespace value for this parse execution
211
+ namespace = @namespace
212
+
213
+ # If the XML specified is an Node then we have what we need.
214
+ if xml.is_a?(Nokogiri::XML::Node)
215
+ node = xml
216
+ else
217
+
218
+ # If xml is an XML document select the root node of the document
219
+ if xml.is_a?(Nokogiri::XML::Document)
220
+ node = xml.root
221
+ else
222
+
223
+ # Attempt to parse the xml value with Nokogiri XML as a document
224
+ # and select the root element
225
+
226
+ xml = Nokogiri::XML(xml)
227
+ node = xml.root
228
+ end
229
+
230
+ # if the node name is equal to the tag name then the we are parsing the
231
+ # root element and that is important to record so that we can apply
232
+ # the correct xpath on the elements of this document.
233
+
234
+ root = node.name == tag_name
235
+ end
236
+
237
+ # if any namespaces have been provied then we should capture those and then
238
+ # merge them with any namespaces found on the xml node and merge all that
239
+ # with any namespaces that have been registered on the object
240
+
241
+ namespaces = options[:namespaces] || {}
242
+ namespaces = namespaces.merge(xml.collect_namespaces) if xml.respond_to?(:collect_namespaces)
243
+ namespaces = namespaces.merge(@registered_namespaces)
244
+
245
+ # if a namespace has been provided then set the current namespace to it
246
+ # or set the default namespace to the one defined under 'xmlns'
247
+ # or set the default namespace to the namespace that matches 'happymapper's
248
+
249
+ if options[:namespace]
250
+ namespace = options[:namespace]
251
+ elsif namespaces.has_key?("xmlns")
252
+ namespace ||= DEFAULT_NS
253
+ namespaces[namespace] = namespaces.delete("xmlns")
254
+ elsif namespaces.has_key?(DEFAULT_NS)
255
+ namespace ||= DEFAULT_NS
256
+ end
257
+
258
+ # from the options grab any nodes present and if none are present then
259
+ # perform the following to find the nodes for the given class
260
+
261
+ nodes = options.fetch(:nodes) do
262
+
263
+ # when at the root use the xpath '/' otherwise use a more gready './/'
264
+ # unless an xpath has been specified, which should overwrite default
265
+ # and finally attach the current namespace if one has been defined
266
+ #
267
+
268
+ xpath = (root ? '/' : './/')
269
+ xpath = options[:xpath].to_s.sub(/([^\/])$/, '\1/') if options[:xpath]
270
+ xpath += "#{namespace}:" if namespace
271
+
272
+ nodes = []
273
+
274
+ # when finding nodes, do it in this order:
275
+ # 1. specified tag
276
+ # 2. name of element
277
+ # 3. tag_name (derived from class name by default)
278
+
279
+
280
+ [options[:tag], options[:name], tag_name].compact.each do |xpath_ext|
281
+ begin
282
+ nodes = node.xpath(xpath + xpath_ext.to_s, namespaces)
283
+ rescue
284
+ break
285
+ end
286
+ break if nodes && !nodes.empty?
287
+ end
288
+
289
+ nodes
290
+ end
291
+
292
+
293
+ collection = nodes.collect do |n|
294
+ obj = new
295
+
296
+ attributes.each do |attr|
297
+ obj.send("#{attr.method_name}=",
298
+ attr.from_xml_node(n, namespace, namespaces))
299
+ end
300
+
301
+ elements.each do |elem|
302
+ obj.send("#{elem.method_name}=",
303
+ elem.from_xml_node(n, namespace, namespaces))
304
+ end
305
+
306
+ obj.send("#{@text_node.method_name}=",
307
+ @text_node.from_xml_node(n, namespace, namespaces)) if @text_node
308
+
309
+ if obj.respond_to?('xml_value=')
310
+ n.namespaces.each {|name,path| n[name] = path }
311
+ obj.xml_value = n.to_xml
312
+ obj.class.class_eval { define_method(:to_xml) { @xml_value } }
313
+ end
314
+
315
+ if obj.respond_to?('xml_content=')
316
+ n = n.children if n.respond_to?(:children)
317
+ obj.xml_content = n.to_xml
318
+ end
319
+
320
+ obj
321
+ end
322
+
323
+ # per http://libxml.rubyforge.org/rdoc/classes/LibXML/XML/Document.html#M000354
324
+ nodes = nil
325
+
326
+
327
+ if options[:single] || root
328
+ collection.first
329
+ else
330
+ collection
331
+ end
332
+ end
333
+
334
+ end
335
+
336
+ #
337
+ # Create an xml representation of the specified class based on defined
338
+ # HappyMapper elements and attributes. The method is defined in a way
339
+ # that it can be called recursively by classes that are also HappyMapper
340
+ # classes, allowg for the composition of classes.
341
+ #
342
+ # @param [Nokogiri::XML::Builder] builder an instance of the XML builder which
343
+ # is being used when called recursively.
344
+ # @param [String] default_namespace the name of the namespace which is the
345
+ # default for the xml being produced; this is specified by the element
346
+ # declaration when calling #to_xml recursively.
347
+ #
348
+ # @return [String,Nokogiri::XML::Builder] return XML representation of the
349
+ # HappyMapper object; when called recursively this is going to return
350
+ # and Nokogiri::XML::Builder object.
351
+ #
352
+ def to_xml(builder = nil,default_namespace = nil)
353
+
354
+ #
355
+ # If to_xml has been called without a passed in builder instance that
356
+ # means we are going to return xml output. When it has been called with
357
+ # a builder instance that means we most likely being called recursively
358
+ # and will return the end product as a builder instance.
359
+ #
360
+ unless builder
361
+ write_out_to_xml = true
362
+ builder = Nokogiri::XML::Builder.new
363
+ end
364
+
365
+ #
366
+ # Find the attributes for the class and collect them into an array
367
+ # that will be placed into a Hash structure
368
+ #
369
+ attributes = self.class.attributes.collect do |attribute|
370
+
371
+ #
372
+ # If an attribute is marked as read_only then we want to ignore the attribute
373
+ # when it comes to saving the xml document; so we wiill not go into any of
374
+ # the below process
375
+ #
376
+ unless attribute.options[:read_only]
377
+
378
+ value = send(attribute.method_name)
379
+
380
+ #
381
+ # If the attribute defines an on_save lambda/proc or value that maps to
382
+ # a method that the class has defined, then call it with the value as a
383
+ # parameter.
384
+ #
385
+ if on_save_action = attribute.options[:on_save]
386
+ if on_save_action.is_a?(Proc)
387
+ value = on_save_action.call(value)
388
+ elsif respond_to?(on_save_action)
389
+ value = send(on_save_action,value)
390
+ end
391
+ end
392
+
393
+ #
394
+ # Attributes that have a nil value should be ignored unless they explicitly
395
+ # state that they should be expressed in the output.
396
+ #
397
+ if value || attribute.options[:state_when_nil]
398
+ attribute_namespace = attribute.options[:namespace] || default_namespace
399
+ [ "#{attribute_namespace ? "#{attribute_namespace}:" : ""}#{attribute.tag}", value ]
400
+ else
401
+ []
402
+ end
403
+
404
+ else
405
+ []
406
+ end
407
+
408
+ end.flatten
409
+
410
+ attributes = Hash[ *attributes ]
411
+
412
+ #
413
+ # Create a tag in the builder that matches the class's tag name and append
414
+ # any attributes to the element that were defined above.
415
+ #
416
+ builder.send(self.class.tag_name,attributes) do |xml|
417
+
418
+ #
419
+ # Add all the registered namespaces to the root element.
420
+ # When this is called recurisvely by composed classes the namespaces
421
+ # are still added to the root element
422
+ #
423
+ # However, we do not want to add the namespace if the namespace is 'xmlns'
424
+ # which means that it is the default namesapce of the code.
425
+ #
426
+ if self.class.instance_variable_get('@registered_namespaces') && builder.doc.root
427
+ self.class.instance_variable_get('@registered_namespaces').each_pair do |name,href|
428
+ name = nil if name == "xmlns"
429
+ builder.doc.root.add_namespace(name,href)
430
+ end
431
+ end
432
+
433
+ #
434
+ # If the object we are persisting has a namespace declaration we will want
435
+ # to use that namespace or we will use the default namespace.
436
+ # When neither are specifed we are simply using whatever is default to the
437
+ # builder
438
+ #
439
+ if self.class.respond_to?(:namespace) && self.class.namespace
440
+ xml.parent.namespace = builder.doc.root.namespace_definitions.find { |x| x.prefix == self.class.namespace }
441
+ elsif default_namespace
442
+ xml.parent.namespace = builder.doc.root.namespace_definitions.find { |x| x.prefix == default_namespace }
443
+ end
444
+
445
+
446
+ #
447
+ # When a text_node has been defined we add the resulting value
448
+ # the output xml
449
+ #
450
+ if text_node = self.class.instance_variable_get('@text_node')
451
+
452
+ unless text_node.options[:read_only]
453
+ text_accessor = text_node.tag || text_node.name
454
+ value = send(text_accessor)
455
+
456
+ if on_save_action = text_node.options[:on_save]
457
+ if on_save_action.is_a?(Proc)
458
+ value = on_save_action.call(value)
459
+ elsif respond_to?(on_save_action)
460
+ value = send(on_save_action,value)
461
+ end
462
+ end
463
+
464
+ builder.text(value)
465
+ end
466
+
467
+ end
468
+
469
+ #
470
+ # for every define element (i.e. has_one, has_many, element) we are
471
+ # going to persist each one
472
+ #
473
+ self.class.elements.each do |element|
474
+
475
+ #
476
+ # If an element is marked as read only do not consider at all when
477
+ # saving to XML.
478
+ #
479
+ unless element.options[:read_only]
480
+
481
+ tag = element.tag || element.name
482
+
483
+ #
484
+ # The value to store is the result of the method call to the element,
485
+ # by default this is simply utilizing the attr_accessor defined. However,
486
+ # this allows for this method to be overridden
487
+ #
488
+ value = send(element.name)
489
+
490
+ #
491
+ # If the element defines an on_save lambda/proc then we will call that
492
+ # operation on the specified value. This allows for operations to be
493
+ # performed to convert the value to a specific value to be saved to the xml.
494
+ #
495
+ if on_save_action = element.options[:on_save]
496
+ if on_save_action.is_a?(Proc)
497
+ value = on_save_action.call(value)
498
+ elsif respond_to?(on_save_action)
499
+ value = send(on_save_action,value)
500
+ end
501
+ end
502
+
503
+ #
504
+ # Normally a nil value would be ignored, however if specified then
505
+ # an empty element will be written to the xml
506
+ #
507
+ if value.nil? && element.options[:single] && element.options[:state_when_nil]
508
+ xml.send(tag,"")
509
+ end
510
+
511
+ #
512
+ # To allow for us to treat both groups of items and singular items
513
+ # equally we wrap the value and treat it as an array.
514
+ #
515
+ if value.nil?
516
+ values = []
517
+ elsif value.respond_to?(:to_ary) && !element.options[:single]
518
+ values = value.to_ary
519
+ else
520
+ values = [value]
521
+ end
522
+
523
+ values.each do |item|
524
+
525
+ if item.is_a?(HappyMapper)
526
+
527
+ #
528
+ # Other items are convertable to xml through the xml builder
529
+ # process should have their contents retrieved and attached
530
+ # to the builder structure
531
+ #
532
+ item.to_xml(xml,element.options[:namespace])
533
+
534
+ elsif item
535
+
536
+ item_namespace = element.options[:namespace] || default_namespace
537
+
538
+ #
539
+ # When a value exists we should append the value for the tag
540
+ #
541
+ if item_namespace
542
+ xml[item_namespace].send(tag,item.to_s)
543
+ else
544
+ xml.send(tag,item.to_s)
545
+ end
546
+
547
+ else
548
+
549
+ #
550
+ # Normally a nil value would be ignored, however if specified then
551
+ # an empty element will be written to the xml
552
+ #
553
+ xml.send(tag,"") if element.options[:state_when_nil]
554
+
555
+ end
556
+
557
+ end
558
+
559
+ end
560
+ end
561
+
562
+ end
563
+
564
+ write_out_to_xml ? builder.to_xml : builder
565
+
566
+ end
567
+
568
+
569
+ end
570
+
571
+ require 'happymapper/item'
572
+ require 'happymapper/attribute'
573
+ require 'happymapper/element'
574
+ require 'happymapper/text_node'
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <address>
3
+ <street>Milchstrasse</street>
4
+ <housenumber>23</housenumber>
5
+ <postcode>26131</postcode>
6
+ <city>Oldenburg</city>
7
+ <country code="de">Germany</country>
8
+ </address>
@@ -0,0 +1,22 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <ambigous>
3
+ <my-items>
4
+ <item>
5
+ <name>My first item</name>
6
+ <item><name>My first internal item</name></item>
7
+ </item>
8
+ <item>
9
+ <name>My second item</name>
10
+ <item><name>My second internal item</name></item>
11
+ </item>
12
+ <item>
13
+ <name>My third item</name>
14
+ <item><name>My third internal item</name></item>
15
+ </item>
16
+ </my-items>
17
+ <others-items>
18
+ <item>
19
+ <name>Other item</name>
20
+ </item>
21
+ <others-items>
22
+ </ambigous>
@@ -0,0 +1,61 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <feed xmlns="http://www.w3.org/2005/Atom"
3
+ xmlns:openSearch="http://a9.com/-/spec/opensearchrss/1.0/"
4
+ xmlns:dxp="http://schemas.google.com/analytics/2009">
5
+ <id>http://www.google.com/analytics/feeds/accounts/nunemaker@gmail.com</id>
6
+ <updated>2009-04-22T23:21:23.000-07:00</updated>
7
+ <title type="text">Profile list for nunemaker@gmail.com</title>
8
+ <link rel="self" type="application/atom+xml"
9
+ href="http://www.google.com/analytics/feeds/accounts/default"/>
10
+ <author>
11
+ <name>Google Analytics</name>
12
+ </author>
13
+ <generator version="1.0">Google Analytics</generator>
14
+ <openSearch:totalResults>4</openSearch:totalResults>
15
+ <openSearch:startIndex>1</openSearch:startIndex>
16
+ <openSearch:itemsPerPage>4</openSearch:itemsPerPage>
17
+ <entry>
18
+ <id>http://www.google.com/analytics/feeds/accounts/ga:47912</id>
19
+ <updated>2008-11-24T12:11:32.000-08:00</updated>
20
+ <title type="text">addictedtonew.com</title>
21
+ <link rel="alternate" type="text/html" href="http://www.google.com/analytics"/>
22
+ <dxp:tableId>ga:47912</dxp:tableId>
23
+ <dxp:property name="ga:accountId" value="85301"/>
24
+ <dxp:property name="ga:accountName" value="addictedtonew.com"/>
25
+ <dxp:property name="ga:profileId" value="47912"/>
26
+ <dxp:property name="ga:webPropertyId" value="UA-85301-1"/>
27
+ </entry>
28
+ <entry>
29
+ <id>http://www.google.com/analytics/feeds/accounts/ga:1897579</id>
30
+ <updated>2008-11-24T12:11:32.000-08:00</updated>
31
+ <title type="text">railstips.org</title>
32
+ <link rel="alternate" type="text/html" href="http://www.google.com/analytics"/>
33
+ <dxp:tableId>ga:1897579</dxp:tableId>
34
+ <dxp:property name="ga:accountId" value="85301"/>
35
+ <dxp:property name="ga:accountName" value="addictedtonew.com"/>
36
+ <dxp:property name="ga:profileId" value="1897579"/>
37
+ <dxp:property name="ga:webPropertyId" value="UA-85301-7"/>
38
+ </entry>
39
+ <entry>
40
+ <id>http://www.google.com/analytics/feeds/accounts/ga:17132454</id>
41
+ <updated>2009-04-22T23:21:23.000-07:00</updated>
42
+ <title type="text">johnnunemaker.com</title>
43
+ <link rel="alternate" type="text/html" href="http://www.google.com/analytics"/>
44
+ <dxp:tableId>ga:17132454</dxp:tableId>
45
+ <dxp:property name="ga:accountId" value="85301"/>
46
+ <dxp:property name="ga:accountName" value="addictedtonew.com"/>
47
+ <dxp:property name="ga:profileId" value="17132454"/>
48
+ <dxp:property name="ga:webPropertyId" value="UA-85301-25"/>
49
+ </entry>
50
+ <entry>
51
+ <id>http://www.google.com/analytics/feeds/accounts/ga:17132478</id>
52
+ <updated>2009-04-22T23:21:23.000-07:00</updated>
53
+ <title type="text">harmonyapp.com</title>
54
+ <link rel="alternate" type="text/html" href="http://www.google.com/analytics"/>
55
+ <dxp:tableId>ga:17132478</dxp:tableId>
56
+ <dxp:property name="ga:accountId" value="85301"/>
57
+ <dxp:property name="ga:accountName" value="addictedtonew.com"/>
58
+ <dxp:property name="ga:profileId" value="17132478"/>
59
+ <dxp:property name="ga:webPropertyId" value="UA-85301-26"/>
60
+ </entry>
61
+ </feed>