data_active 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +2 -1
- data/data_active.gemspec +8 -6
- data/features/step_definitions/step_helper.rb +5 -4
- data/features/step_definitions/sync_books_with_xml.rb +11 -7
- data/features/support/book_generator.rb +29 -0
- data/features/support/factories/book.rb +6 -0
- data/features/support/factories/book_price.rb +8 -0
- data/features/support/factories/chapter.rb +7 -0
- data/features/support/factories/page.rb +7 -0
- data/features/support/fixtures/fresh/pages.yml +10 -10
- data/features/sync_database_with_xml.feature +2 -2
- data/features/sync_one_to_many.feature +0 -6
- data/features/sync_one_to_one.feature +1 -1
- data/lib/data_active.rb +8 -282
- data/lib/data_active/attribute.rb +12 -0
- data/lib/data_active/entity.rb +151 -0
- data/lib/data_active/parser.rb +159 -0
- data/lib/data_active/sax_document.rb +29 -0
- data/lib/data_active/version.rb +1 -1
- data/spec/entity_spec.rb +11 -0
- data/spec/fixtures/xml/ms_xml/books_fresh.xml +173 -0
- data/spec/fixtures/xml/rails_xml/books_fresh.xml +173 -0
- data/spec/parser_spec.rb +285 -0
- data/spec/sax_document_spec.rb +3 -0
- data/spec/spec_helper.rb +39 -0
- data/test_apps/book_store_rails_31x/config/application.rb +0 -32
- data/test_apps/book_store_rails_32x/.rspec +1 -0
- data/test_apps/book_store_rails_32x/.rvmrc +1 -1
- data/test_apps/book_store_rails_32x/Gemfile +12 -4
- data/test_apps/book_store_rails_32x/config/application.rb +0 -40
- metadata +63 -5
- data/features/step_definitions/web_steps.rb +0 -211
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module DataActive
|
4
|
+
class Entity
|
5
|
+
attr_reader :attributes
|
6
|
+
attr_reader :klass
|
7
|
+
attr_reader :associations
|
8
|
+
attr_reader :tag_name
|
9
|
+
attr_accessor :belongs_to
|
10
|
+
attr_accessor :record
|
11
|
+
attr_accessor :excluded
|
12
|
+
|
13
|
+
def initialize(tag_name, options = [], excluded = false)
|
14
|
+
@tag_name = tag_name
|
15
|
+
@options = options
|
16
|
+
@attributes = {}
|
17
|
+
@associations = {}
|
18
|
+
@excluded = excluded
|
19
|
+
|
20
|
+
unless @excluded
|
21
|
+
begin
|
22
|
+
@klass = Kernel.const_get(@tag_name.camelize)
|
23
|
+
raise "Class '#{@tag_name.camelize}' is not inherit ActiveRecord" unless @klass.ancestors.include? ActiveRecord::Base
|
24
|
+
@associations = Hash[@klass.reflect_on_all_associations.map { |a| [a.plural_name.to_sym, a] }]
|
25
|
+
@record = @klass.new
|
26
|
+
rescue
|
27
|
+
@excluded = true
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
def options_include? (context, options)
|
34
|
+
((@options & options).count.eql? options.count and context.eql? :all) ||
|
35
|
+
((@options & options).count > 0 and context.eql? :any)
|
36
|
+
end
|
37
|
+
|
38
|
+
def has_attribute?(name)
|
39
|
+
if @excluded
|
40
|
+
false
|
41
|
+
else
|
42
|
+
@record.attributes.include? name
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def has_association_with?(name)
|
47
|
+
@associations.has_key? name.pluralize.to_sym
|
48
|
+
end
|
49
|
+
|
50
|
+
def commit
|
51
|
+
id = nil
|
52
|
+
existing_record = find_existing
|
53
|
+
@attributes.delete(@klass.primary_key.to_s)
|
54
|
+
|
55
|
+
if existing_record.nil? and options_include? :any, [:create, :sync]
|
56
|
+
id = save
|
57
|
+
elsif existing_record.present? and options_include? :any, [:update, :sync]
|
58
|
+
@record = existing_record
|
59
|
+
id = save
|
60
|
+
elsif options_include? :any, [:destroy, :sync]
|
61
|
+
id = @record.attributes[@klass.primary_key.to_s]
|
62
|
+
end
|
63
|
+
|
64
|
+
id
|
65
|
+
end
|
66
|
+
|
67
|
+
def update_associations
|
68
|
+
if belongs_to.present? and not belongs_to.excluded
|
69
|
+
association = belongs_to.klass.reflect_on_all_associations.select { |a| a.plural_name == @klass.name.underscore.pluralize }[0]
|
70
|
+
if belongs_to.record.new_record?
|
71
|
+
existing = belongs_to.find_existing
|
72
|
+
belongs_to.record = existing if existing.present?
|
73
|
+
end
|
74
|
+
|
75
|
+
if belongs_to.record.new_record?
|
76
|
+
case association.macro
|
77
|
+
when :has_many, :has_many_and_blongs_to
|
78
|
+
belongs_to.record.__send__(association.name) << @record
|
79
|
+
|
80
|
+
when :has_one
|
81
|
+
belongs_to.record.__send__("#{association.name}=", @record)
|
82
|
+
|
83
|
+
else
|
84
|
+
raise "unsupported association #{association.macro} for #{association.name} on #{@klass.name}"
|
85
|
+
|
86
|
+
end
|
87
|
+
else
|
88
|
+
foreign_key = foreign_key_from(association)
|
89
|
+
@record.__send__("#{foreign_key}=", belongs_to.record.__send__(belongs_to.klass.primary_key.to_sym))
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def foreign_key_from(association)
|
95
|
+
if ActiveRecord::Reflection::AssociationReflection.method_defined? :foreign_key
|
96
|
+
# Support for Rails 3.1 and later
|
97
|
+
foreign_key = association.foreign_key
|
98
|
+
elsif ActiveRecord::Reflection::AssociationReflection.method_defined? :primary_key_name
|
99
|
+
# Support for Rails earlier than 3.1
|
100
|
+
foreign_key = association.primary_key_name
|
101
|
+
else
|
102
|
+
raise 'Unsupported version of ActiveRecord. Unable to identify the foreign key.'
|
103
|
+
end
|
104
|
+
foreign_key
|
105
|
+
end
|
106
|
+
|
107
|
+
def find_existing
|
108
|
+
existing_record = nil
|
109
|
+
|
110
|
+
begin
|
111
|
+
existing_record = @klass.find(@attributes[@klass.primary_key.to_s].content) if @attributes[@klass.primary_key.to_s].present?
|
112
|
+
rescue
|
113
|
+
existing_record = nil
|
114
|
+
end
|
115
|
+
|
116
|
+
existing_record
|
117
|
+
end
|
118
|
+
|
119
|
+
def save
|
120
|
+
commit_attributes
|
121
|
+
update_associations
|
122
|
+
|
123
|
+
if will_violate_association?
|
124
|
+
@record.save!
|
125
|
+
@record.attributes[@klass.primary_key.to_s]
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def will_violate_association?
|
130
|
+
ok = true
|
131
|
+
if belongs_to.present? and belongs_to.associations.count > 0
|
132
|
+
if belongs_to.associations[@tag_name.pluralize.to_sym].macro == :has_one
|
133
|
+
existing = belongs_to.record.__send__(@tag_name)
|
134
|
+
if existing.present?
|
135
|
+
if @record.__send__(@klass.primary_key.to_s) != existing.__send__(@klass.primary_key.to_s)
|
136
|
+
ok = false
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
ok
|
143
|
+
end
|
144
|
+
|
145
|
+
def commit_attributes
|
146
|
+
@attributes.each do |key, a|
|
147
|
+
@record.__send__("#{a.name}=", a.content)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
module DataActive
|
2
|
+
class Parser
|
3
|
+
attr_accessor :options
|
4
|
+
attr_reader :stack
|
5
|
+
attr_reader :first_element_name
|
6
|
+
attr_reader :root_klass
|
7
|
+
|
8
|
+
def initialize(first_element_name, options = [])
|
9
|
+
@options = options
|
10
|
+
@stack = []
|
11
|
+
@first_element_name = first_element_name
|
12
|
+
@started_parsing = false
|
13
|
+
@processed_entities = {}
|
14
|
+
@root_klass = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def options_include? (context, options)
|
18
|
+
((@options & options).count.eql? options.count and context.eql? :all) ||
|
19
|
+
((@options & options).count > 0 and context.eql? :any)
|
20
|
+
end
|
21
|
+
|
22
|
+
def begin(name)
|
23
|
+
if not @started_parsing and @first_element_name == name
|
24
|
+
@started_parsing = true
|
25
|
+
begin
|
26
|
+
@root_klass = Kernel.const_get(name.camelize)
|
27
|
+
rescue
|
28
|
+
raise "Unable to find class for '#{name}'"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
process_element(name)
|
33
|
+
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
def process_element(name)
|
38
|
+
if @stack.last.nil?
|
39
|
+
@stack << DataActive::Entity.new(name, @options, !@started_parsing)
|
40
|
+
elsif @stack.last.class.name == 'DataActive::Entity'
|
41
|
+
process_entity_child(name)
|
42
|
+
elsif @stack.last.class.name == 'DataActive::Attribute' and not @stack.last.known
|
43
|
+
@stack << DataActive::Attribute.new(name, false)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def process_entity_child(name)
|
48
|
+
parent = @stack.last
|
49
|
+
if parent.has_association_with? name or parent.tag_name == name.pluralize
|
50
|
+
create_child_entity(name)
|
51
|
+
elsif parent.has_attribute? name
|
52
|
+
@stack << DataActive::Attribute.new(name)
|
53
|
+
elsif klass_exists? name
|
54
|
+
create_child_entity(name)
|
55
|
+
elsif parent.excluded
|
56
|
+
@stack << DataActive::Attribute.new(name, false)
|
57
|
+
else
|
58
|
+
raise "Unknown element '#{name}' for #{parent.klass.name}" if @options.include? :strict
|
59
|
+
@stack << DataActive::Attribute.new(name, false)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def klass_exists? name
|
64
|
+
is_klass = true
|
65
|
+
|
66
|
+
begin
|
67
|
+
klass = Kernel.const_get(name.camelize)
|
68
|
+
fail unless klass.ancestors.include? ActiveRecord::Base
|
69
|
+
rescue
|
70
|
+
is_klass = false
|
71
|
+
end
|
72
|
+
|
73
|
+
is_klass
|
74
|
+
end
|
75
|
+
|
76
|
+
def create_child_entity(name)
|
77
|
+
entity = DataActive::Entity.new(name, @options, !@started_parsing)
|
78
|
+
|
79
|
+
if @stack.last.tag_name == name.pluralize
|
80
|
+
entity.belongs_to = @stack.last.belongs_to
|
81
|
+
else
|
82
|
+
entity.belongs_to = @stack.last
|
83
|
+
end
|
84
|
+
|
85
|
+
@stack << entity
|
86
|
+
end
|
87
|
+
|
88
|
+
def content(value)
|
89
|
+
if @stack.last.class.name == 'DataActive::Entity'
|
90
|
+
raise "'#{@stack.last.tag_name}' contains text '#{value}'" if value.strip.length > 0
|
91
|
+
else
|
92
|
+
@stack.last.content = @stack.last.content.nil? ? value : @stack.last.content + value
|
93
|
+
end
|
94
|
+
|
95
|
+
self
|
96
|
+
end
|
97
|
+
|
98
|
+
def end(name)
|
99
|
+
case @stack.last.class.name
|
100
|
+
when 'DataActive::Entity'
|
101
|
+
end_entity(name)
|
102
|
+
|
103
|
+
when 'DataActive::Attribute'
|
104
|
+
end_attribute(name)
|
105
|
+
|
106
|
+
else
|
107
|
+
raise "Unhandled class '#{@stack.last.class.name}'"
|
108
|
+
end
|
109
|
+
|
110
|
+
self
|
111
|
+
end
|
112
|
+
|
113
|
+
def end_attribute(name)
|
114
|
+
raise "Mismatched closing tag '#{name}' when opening tag was #{@stack.last.name}" unless @stack.last.name.eql? name
|
115
|
+
attribute = @stack.pop()
|
116
|
+
@stack.last.attributes[attribute.name] = attribute if attribute.known
|
117
|
+
end
|
118
|
+
|
119
|
+
def end_entity(name)
|
120
|
+
raise "Mismatched closing tag '#{name}' when opening tag was #{@stack.last.tag_name}" unless @stack.last.tag_name.eql? name
|
121
|
+
entity = @stack.pop()
|
122
|
+
store_processed_entity_id(entity.klass, entity.commit()) if not entity.excluded
|
123
|
+
end
|
124
|
+
|
125
|
+
def destroy(klass = self.root_klass)
|
126
|
+
@destroyed ||= []
|
127
|
+
if options_include? :any, [:destroy, :sync]
|
128
|
+
klass.reflect_on_all_associations.each do |a|
|
129
|
+
if [:has_many, :has_many_and_belongs_to, :has_one].include? a.macro and @destroyed.exclude? a.klass.name
|
130
|
+
@destroyed << a.klass.name
|
131
|
+
destroy(a.klass)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
destroy_records(klass)
|
136
|
+
@destroyed << klass.name
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def destroy_records(klass)
|
141
|
+
ids = @processed_entities[klass.name.to_sym]
|
142
|
+
ids.nil? ? klass.destroy_all : klass.destroy_all([klass.primary_key.to_s + ' not in (?)', ids.collect])
|
143
|
+
end
|
144
|
+
|
145
|
+
def store_processed_entity_id(klass, id)
|
146
|
+
return if id.nil?
|
147
|
+
|
148
|
+
if @processed_entities[klass.name.to_sym].present?
|
149
|
+
@processed_entities[klass.name.to_sym] << id
|
150
|
+
else
|
151
|
+
@processed_entities[klass.name.to_sym] = [id]
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def has_started_parsing?
|
156
|
+
@started_parsing
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
module DataActive
|
4
|
+
class SaxDocument < ::Nokogiri::XML::SAX::Document
|
5
|
+
def initialize(first_element_name, options = {})
|
6
|
+
@parser = DataActive::Parser.new(first_element_name, options)
|
7
|
+
end
|
8
|
+
|
9
|
+
def start_element(name, attr)
|
10
|
+
@parser.begin(name)
|
11
|
+
end
|
12
|
+
|
13
|
+
def end_element(name)
|
14
|
+
@parser.end(name)
|
15
|
+
end
|
16
|
+
|
17
|
+
def cdata_block(value)
|
18
|
+
@parser.content(value)
|
19
|
+
end
|
20
|
+
|
21
|
+
def characters(value)
|
22
|
+
@parser.content(value)
|
23
|
+
end
|
24
|
+
|
25
|
+
def end_document
|
26
|
+
@parser.destroy
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/data_active/version.rb
CHANGED
data/spec/entity_spec.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe DataActive::Entity do
|
3
|
+
context 'context' do
|
4
|
+
let(:book_entity) { DataActive::Entity.new('book') }
|
5
|
+
it ('should have a class of Book') { book_entity.klass.name == 'Book' }
|
6
|
+
it ('should have an association with chapters') { book_entity.has_association_with? 'chapter' }
|
7
|
+
it ('should have an association with book_price') { book_entity.has_association_with? 'book_price' }
|
8
|
+
it ('should have an attribute named id') { book_entity.has_attribute? 'id' }
|
9
|
+
it ('should have an attribute named title') { book_entity.has_attribute? 'title' }
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<books type="array">
|
3
|
+
<book>
|
4
|
+
<id type="integer">1</id>
|
5
|
+
<name>Book 1</name>
|
6
|
+
<book_price>
|
7
|
+
<id type="integer">1</id>
|
8
|
+
<sell>50.00</sell>
|
9
|
+
<educational>35.00</educational>
|
10
|
+
<cost>20.00</cost>
|
11
|
+
</book_price>
|
12
|
+
<chapters type="array">
|
13
|
+
<chapter>
|
14
|
+
<id type="integer">1</id>
|
15
|
+
<title>Chapter 1</title>
|
16
|
+
<introduction>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.</introduction>
|
17
|
+
<pages type="array">
|
18
|
+
<page>
|
19
|
+
<id type="integer">1</id>
|
20
|
+
<content>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.</content>
|
21
|
+
<number>1</number>
|
22
|
+
</page>
|
23
|
+
<page>
|
24
|
+
<id type="integer">2</id>
|
25
|
+
<content>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.</content>
|
26
|
+
<number>2</number>
|
27
|
+
</page>
|
28
|
+
<page>
|
29
|
+
<id type="integer">3</id>
|
30
|
+
<content>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.</content>
|
31
|
+
<number>3</number>
|
32
|
+
</page>
|
33
|
+
<page>
|
34
|
+
<id type="integer">4</id>
|
35
|
+
<content>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.</content>
|
36
|
+
<number>4</number>
|
37
|
+
</page>
|
38
|
+
</pages>
|
39
|
+
</chapter>
|
40
|
+
<chapter>
|
41
|
+
<id type="integer">2</id>
|
42
|
+
<title>Chapter 2</title>
|
43
|
+
<introduction>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.</introduction>
|
44
|
+
<pages type="array">
|
45
|
+
<page>
|
46
|
+
<id type="integer">5</id>
|
47
|
+
<content>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.</content>
|
48
|
+
<number>1</number>
|
49
|
+
</page>
|
50
|
+
<page>
|
51
|
+
<id type="integer">6</id>
|
52
|
+
<content>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.</content>
|
53
|
+
<number>2</number>
|
54
|
+
</page>
|
55
|
+
<page>
|
56
|
+
<id type="integer">7</id>
|
57
|
+
<content>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.</content>
|
58
|
+
<number>3</number>
|
59
|
+
</page>
|
60
|
+
<page>
|
61
|
+
<id type="integer">8</id>
|
62
|
+
<content>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.</content>
|
63
|
+
<number>4</number>
|
64
|
+
</page>
|
65
|
+
</pages>
|
66
|
+
</chapter>
|
67
|
+
<chapter>
|
68
|
+
<id type="integer">3</id>
|
69
|
+
<title>Chapter 3</title>
|
70
|
+
<introduction>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.</introduction>
|
71
|
+
<pages type="array">
|
72
|
+
<page>
|
73
|
+
<id type="integer">9</id>
|
74
|
+
<content>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.</content>
|
75
|
+
<number>1</number>
|
76
|
+
</page>
|
77
|
+
<page>
|
78
|
+
<id type="integer">10</id>
|
79
|
+
<content>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.</content>
|
80
|
+
<number>2</number>
|
81
|
+
</page>
|
82
|
+
<page>
|
83
|
+
<id type="integer">11</id>
|
84
|
+
<content>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.</content>
|
85
|
+
<number>3</number>
|
86
|
+
</page>
|
87
|
+
</pages>
|
88
|
+
</chapter>
|
89
|
+
</chapters>
|
90
|
+
</book>
|
91
|
+
<book>
|
92
|
+
<id type="integer">2</id>
|
93
|
+
<name>Book 1</name>
|
94
|
+
<chapters type="array">
|
95
|
+
<chapter>
|
96
|
+
<id type="integer">4</id>
|
97
|
+
<title>Chapter 1</title>
|
98
|
+
<introduction>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.</introduction>
|
99
|
+
<pages type="array">
|
100
|
+
<page>
|
101
|
+
<id type="integer">12</id>
|
102
|
+
<content>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.</content>
|
103
|
+
<number>1</number>
|
104
|
+
</page>
|
105
|
+
<page>
|
106
|
+
<id type="integer">13</id>
|
107
|
+
<content>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.</content>
|
108
|
+
<number>2</number>
|
109
|
+
</page>
|
110
|
+
<page>
|
111
|
+
<id type="integer">14</id>
|
112
|
+
<content>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.</content>
|
113
|
+
<number>3</number>
|
114
|
+
</page>
|
115
|
+
<page>
|
116
|
+
<id type="integer">15</id>
|
117
|
+
<content>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.</content>
|
118
|
+
<number>4</number>
|
119
|
+
</page>
|
120
|
+
</pages>
|
121
|
+
</chapter>
|
122
|
+
<chapter>
|
123
|
+
<id type="integer">5</id>
|
124
|
+
<title>Chapter 2</title>
|
125
|
+
<introduction>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.</introduction>
|
126
|
+
<pages type="array">
|
127
|
+
<page>
|
128
|
+
<id type="integer">16</id>
|
129
|
+
<content>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.</content>
|
130
|
+
<number>1</number>
|
131
|
+
</page>
|
132
|
+
<page>
|
133
|
+
<id type="integer">17</id>
|
134
|
+
<content>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.</content>
|
135
|
+
<number>2</number>
|
136
|
+
</page>
|
137
|
+
<page>
|
138
|
+
<id type="integer">18</id>
|
139
|
+
<content>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.</content>
|
140
|
+
<number>3</number>
|
141
|
+
</page>
|
142
|
+
<page>
|
143
|
+
<id type="integer">19</id>
|
144
|
+
<content>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.</content>
|
145
|
+
<number>4</number>
|
146
|
+
</page>
|
147
|
+
</pages>
|
148
|
+
</chapter>
|
149
|
+
<chapter>
|
150
|
+
<id type="integer">6</id>
|
151
|
+
<title>Chapter 3</title>
|
152
|
+
<introduction>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.</introduction>
|
153
|
+
<pages type="array">
|
154
|
+
<page>
|
155
|
+
<id type="integer">20</id>
|
156
|
+
<content>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.</content>
|
157
|
+
<number>1</number>
|
158
|
+
</page>
|
159
|
+
<page>
|
160
|
+
<id type="integer">21</id>
|
161
|
+
<content>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.</content>
|
162
|
+
<number>2</number>
|
163
|
+
</page>
|
164
|
+
<page>
|
165
|
+
<id type="integer">22</id>
|
166
|
+
<content>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.</content>
|
167
|
+
<number>3</number>
|
168
|
+
</page>
|
169
|
+
</pages>
|
170
|
+
</chapter>
|
171
|
+
</chapters>
|
172
|
+
</book>
|
173
|
+
</books>
|