dynarex 1.8.27

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 (6) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/lib/dynarex.rb +1558 -0
  5. metadata +251 -0
  6. metadata.gz.sig +0 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d331676a235d9cb8422a3855be3803bbf3203ac0f87238efe44c7adf2d541dc4
4
+ data.tar.gz: 306dfe9c3667fdf27db9e9ac7d410ef1fe25148fe79dd278543109264823112c
5
+ SHA512:
6
+ metadata.gz: 3577e9e6067e4fef09f1c31654212a5ce47c3af65dafd9e1e10d887c190ff4ed253ae8859980ea3cfc273a57664da7aa6d58bfbb6b94f996c5efd2fb17a208f0
7
+ data.tar.gz: 81d4c396ba22264bc517117a6cfc3011a185c26e733f63f25ea42e0b28dc9177ea2788b5bd01282c8bff7e0556a8f9b6f2094b11c326eedad81b5357d65f5571
Binary file
Binary file
@@ -0,0 +1,1558 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # file: dynarex.rb
4
+
5
+ require 'open-uri'
6
+ require 'dynarex-import'
7
+ #require 'line-tree'
8
+ #require 'rexle'
9
+ require 'rexle-builder'
10
+ require 'rexslt'
11
+ require 'dynarex-xslt'
12
+ require 'recordx'
13
+ require 'rxraw-lineparser'
14
+ require 'yaml'
15
+ require 'rowx'
16
+ require 'ostruct'
17
+ require 'table-formatter'
18
+ #require 'rxfhelper'
19
+ require 'kvx'
20
+ require 'json'
21
+
22
+
23
+ module RegGem
24
+
25
+ def self.register()
26
+ '
27
+ hkey_gems
28
+ doctype
29
+ dynarex
30
+ require dynarex
31
+ class Dynarex
32
+ media_type dynarex
33
+ '
34
+ end
35
+ end
36
+
37
+
38
+ class DynarexException < Exception
39
+ end
40
+
41
+ class DynarexRecordset < Array
42
+
43
+ def initialize(a, caller=nil)
44
+ super(a)
45
+ @caller = caller
46
+ end
47
+
48
+ def reject!()
49
+
50
+ a = self.to_a.clone
51
+ a2 = super
52
+ return nil unless a2
53
+ a3 = a - a2
54
+
55
+ @caller.delete a3.map(&:id)
56
+ self
57
+ end
58
+
59
+ def sum(field)
60
+ self.inject(0) {|r, x| r + x[field.to_sym][/\d+(\.\d+)?$/].to_f }
61
+ end
62
+
63
+ def index(val)
64
+ self.map(&:to_h).index val.to_h
65
+ end
66
+
67
+ def index_by_id(id)
68
+ self.map(&:id).index id
69
+ end
70
+
71
+ end
72
+
73
+
74
+ class Dynarex
75
+ include RXFHelperModule
76
+ using ColouredText
77
+
78
+ attr_accessor :format_mask, :delimiter, :xslt_schema, :schema, :linked,
79
+ :order, :type, :limit, :xslt, :json_out, :unique
80
+
81
+
82
+ #Create a new dynarex document from 1 of the following options:
83
+ #* a local file path
84
+ #* a URL
85
+ #* a schema string
86
+ # Dynarex.new 'contacts[title,description]/contact(name,age,dob)'
87
+ #* an XML string
88
+ # Dynarex.new '<contacts><summary><schema>contacts/contact(name,age,dob)</schema></summary><records/></contacts>'
89
+
90
+ def initialize(rawx=nil, username: nil, password: nil, schema: nil,
91
+ default_key: nil, json_out: true, debug: false,
92
+ delimiter: ' # ', autosave: false, order: 'ascending',
93
+ unique: false, filepath: nil)
94
+
95
+
96
+ puts 'inside Dynarex::initialize' if debug
97
+ @username, @password, @schema, @default_key, @json_out, @debug = \
98
+ username, password, schema, default_key, json_out, debug
99
+ @autosave, @unique = autosave, unique
100
+ @local_filepath = filepath
101
+
102
+ puts ('@debug: ' + @debug.inspect).debug if debug
103
+ @delimiter = delimiter
104
+ @spaces_delimited = false
105
+ @order = order
106
+ @limit = nil
107
+ @records, @flat_records = [], []
108
+ rawx ||= schema if schema
109
+
110
+ if rawx then
111
+
112
+ return import(rawx) if rawx =~ /\.txt$/
113
+ openx(rawx.clone)
114
+
115
+ end
116
+
117
+ self.order = @order unless @order.to_sym == :ascending
118
+
119
+ end
120
+
121
+ def add(x)
122
+ @doc.root.add x
123
+ @dirty_flag = true
124
+ self
125
+ end
126
+
127
+ def all()
128
+
129
+ refresh! if @dirty_flag
130
+ a = @doc.root.xpath("records/*").map {|x| recordx_to_record x}
131
+ DynarexRecordset.new(a, self)
132
+
133
+ end
134
+
135
+ def clone()
136
+ Dynarex.new(self.to_xml)
137
+ end
138
+
139
+ def default_key()
140
+ self.summary[:default_key]
141
+ end
142
+
143
+ def delimiter=(separator)
144
+
145
+ if separator == :spaces then
146
+ @spaces_delimited = true
147
+ separator = ' # '
148
+ end
149
+
150
+ @delimiter = separator
151
+
152
+ if separator.length > 0 then
153
+ @summary[:delimiter] = separator
154
+ else
155
+ @summary.delete :delimiter
156
+ end
157
+
158
+ @format_mask = @format_mask.to_s.gsub(/\s/, separator)
159
+ @summary[:format_mask] = @format_mask
160
+ end
161
+
162
+ def doc
163
+ (load_records; rebuild_doc) if @dirty_flag == true
164
+ @doc
165
+ end
166
+
167
+ # XML import
168
+ #
169
+ def foreign_import(options={})
170
+ o = {xml: '', schema: ''}.merge(options)
171
+ h = {xml: o[:xml], schema: @schema, foreign_schema: o[:schema]}
172
+ buffer = DynarexImport.new(h).to_xml
173
+
174
+ openx(buffer)
175
+ self
176
+ end
177
+
178
+ def fields
179
+ @fields
180
+ end
181
+
182
+ def first
183
+ r = @doc.root.element("records/*")
184
+ r ? recordx_to_record(r) : nil
185
+ end
186
+
187
+ def format_mask=(s)
188
+ @format_mask = s
189
+ @summary[:format_mask] = @format_mask
190
+ end
191
+
192
+ def insert(raw_params)
193
+ record = make_record(raw_params)
194
+ @doc.root.element('records/*').insert_before record
195
+ @dirty_flag = true
196
+ end
197
+
198
+ def inspect()
199
+ "<object #%s>" % [self.object_id]
200
+ end
201
+
202
+ def linked=(bool)
203
+ @linked = bool == 'true'
204
+ end
205
+
206
+ def order=(value)
207
+
208
+ self.summary.merge!({order: value.to_s})
209
+
210
+ @order = value.to_s
211
+ end
212
+
213
+ def recordx_type()
214
+ @summary[:recordx_type]
215
+ end
216
+
217
+ def schema=(s)
218
+ openx s
219
+ end
220
+
221
+ def type=(v)
222
+ @order = 'descending' if v == 'feed'
223
+ @type = v
224
+ @summary[:type] = v
225
+ end
226
+
227
+ # Returns the hash representation of the document summary.
228
+ #
229
+ def summary
230
+ @summary
231
+ end
232
+
233
+ # Return a Hash (which can be edited) containing all records.
234
+ #
235
+ def records
236
+
237
+ load_records if @dirty_flag == true
238
+
239
+ if block_given? then
240
+ yield(@records)
241
+ rebuild_doc
242
+ @dirty_flag = true
243
+ else
244
+ @records
245
+ end
246
+
247
+ end
248
+
249
+ # Returns a ready-only snapshot of records as a simple Hash.
250
+ #
251
+ def flat_records(select: nil)
252
+
253
+ fields = select
254
+
255
+ load_records if @dirty_flag == true
256
+
257
+ if fields then
258
+
259
+ case fields.class.to_s.downcase.to_sym
260
+ when :string
261
+ field = fields.to_sym
262
+ @flat_records.map {|row| {field => row[field]}}
263
+ when :symbol
264
+ field = fields.to_sym
265
+ @flat_records.map {|row| {field => row[field]} }
266
+ when :array
267
+ @flat_records.map {|row| fields.inject({})\
268
+ {|r,x| r.merge(x.to_sym => row[x.to_sym])}}
269
+ end
270
+
271
+ else
272
+ @flat_records
273
+ end
274
+
275
+ end
276
+
277
+ alias to_a flat_records
278
+
279
+ # Returns an array snapshot of OpenStruct records
280
+ #
281
+ def ro_records
282
+ flat_records.map {|record| OpenStruct.new record }
283
+ end
284
+
285
+ def rm(force: false)
286
+
287
+ if force or all.empty? then
288
+ FileX.rm @local_filepath if @local_filepath
289
+ 'file ' + @local_filepath + ' deleted'
290
+ else
291
+ 'unable to rm file: document not empty'
292
+ end
293
+
294
+ end
295
+
296
+
297
+ def to_doc
298
+ self.clone().doc
299
+ end
300
+
301
+ # Typically uses the 1st field as a key and the remaining fields as the value
302
+ #
303
+ def to_h()
304
+
305
+ key = self.default_key.to_sym
306
+ fields = self.fields() - [key]
307
+ puts 'fields: ' + fields.inspect if @debug
308
+
309
+ flat_records.inject({}) do |r, h|
310
+
311
+ puts 'h: ' + h.inspect if @debug
312
+
313
+ value = if h.length == 2 then
314
+ h[fields[0]].to_s
315
+ else
316
+ fields.map {|x| h[x]}
317
+ end
318
+
319
+ r.merge(h[key] => value)
320
+ end
321
+
322
+ end
323
+
324
+ def to_html(domain: '')
325
+
326
+ h = {username: @username, password: @password}
327
+ xsl_buffer = RXFHelper.read(domain + @xslt, h).first
328
+ Rexslt.new(xsl_buffer, self.to_doc).to_s
329
+
330
+ end
331
+
332
+
333
+ # to_json: pretty is set to true because although the file size is larger,
334
+ # the file can be load slightly quicker
335
+
336
+ def to_json(pretty: true)
337
+
338
+ records = self.to_a
339
+ summary = self.summary.to_h
340
+
341
+ root_name = schema()[/^\w+/]
342
+ record_name = schema()[/(?<=\/)[^\(]+/]
343
+
344
+ h = {
345
+ root_name.to_sym =>
346
+ {
347
+ summary: @summary,
348
+ records: @records.map {|_, h| {record_name.to_sym => h} }
349
+ }
350
+ }
351
+
352
+ pretty ? JSON.pretty_generate(h) : h.to_json
353
+
354
+ end
355
+
356
+ def to_s(header: true, delimiter: @delimiter)
357
+
358
+ xsl_buffer =<<EOF
359
+ <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
360
+ <xsl:output encoding="UTF-8"
361
+ method="text"
362
+ indent="no"
363
+ omit-xml-declaration="yes"/>
364
+
365
+ <xsl:template match="*">
366
+ <xsl:for-each select="records/*">[!regex_values]<xsl:text>
367
+ </xsl:text>
368
+ </xsl:for-each>
369
+ </xsl:template>
370
+ </xsl:stylesheet>
371
+ EOF
372
+
373
+
374
+ raw_summary_fields = self.summary[:schema][/^\w+\[([^\]]+)\]/,1]
375
+ sumry = ''
376
+
377
+ if raw_summary_fields then
378
+ summary_fields = raw_summary_fields.split(',') # .map(&:to_sym)
379
+ sumry = summary_fields.map {|x| x.strip!; x + ': ' + \
380
+ self.summary[x.to_sym].to_s}.join("\n") + "\n\n"
381
+ end
382
+
383
+ if @raw_header then
384
+ declaration = @raw_header
385
+ else
386
+
387
+ smry_fields = %i(schema)
388
+ smry_fields << :order if self.summary[:order] == 'descending'
389
+
390
+ if delimiter.length > 0 then
391
+ smry_fields << :delimiter
392
+ else
393
+ smry_fields << :format_mask unless self.summary[:rawdoc_type] == 'rowx'
394
+ end
395
+ s = smry_fields.map {|x| "%s=\"%s\"" % \
396
+ [x, self.send(x).gsub('"', '\"') ]}.join ' '
397
+
398
+ declaration = %Q(<?dynarex %s?>\n) % s
399
+ end
400
+
401
+ docheader = declaration + sumry
402
+
403
+ if self.summary[:rawdoc_type] == 'rowx' then
404
+ a = self.fields.map do |field|
405
+ "<xsl:if test=\"%s != ''\">
406
+ <xsl:text>\n</xsl:text>%s:<xsl:text> </xsl:text><xsl:value-of select='%s'/>
407
+ </xsl:if>" % ([field]*3)
408
+ end
409
+
410
+ puts ('a: ' + a.inspect).debug if @debug
411
+
412
+ xslt_format = a.join
413
+
414
+ xsl_buffer.sub!(/\[!regex_values\]/, xslt_format)
415
+
416
+ if @debug then
417
+ File.write '/tmp/foo.xsl', xsl_buffer
418
+ File.write '/tmp/foo.xml', @doc.xml
419
+ puts 'xsl_buffer: ' + xsl_buffer.inspect
420
+ end
421
+
422
+ out = Rexslt.new(xsl_buffer, @doc).to_s
423
+
424
+ docheader + "\n--+\n" + out
425
+ elsif self.summary[:rawdoc_type] == 'sectionx' then
426
+
427
+ a = (self.fields - [:uid, 'uid']).map do |field|
428
+ "<xsl:if test=\"%s != ''\">
429
+ <xsl:text>\n</xsl:text><xsl:value-of select='%s'/>
430
+ </xsl:if>" % ([field]*2)
431
+ end
432
+
433
+ xslt_format = a.join
434
+
435
+ xsl_buffer.sub!(/\[!regex_values\]/, xslt_format)
436
+ puts 'xsl_buffer: ' + xsl_buffer.inspect if @debug
437
+
438
+ out = Rexslt.new(xsl_buffer, @doc).to_s
439
+
440
+ header ? docheader + "--#\n" + out : out
441
+
442
+ elsif self.delimiter.length > 0 then
443
+ puts 'dinddd'
444
+ tfo = TableFormatter.new border: false, wrap: false, \
445
+ divider: self.delimiter
446
+ tfo.source = self.to_a.map{|x| x.values}
447
+ docheader + tfo.display.strip
448
+
449
+ else
450
+
451
+ format_mask = self.format_mask
452
+ format_mask.gsub!(/\[[^!\]]+\]/) {|x| x[1] }
453
+
454
+ s1, s2 = '<xsl:text>', '</xsl:text>'
455
+ xslt_format = s1 + format_mask\
456
+ .gsub(/(?:\[!(\w+)\])/, s2 + '<xsl:value-of select="\1"/>' + s1) + s2
457
+
458
+ xsl_buffer.sub!(/\[!regex_values\]/, xslt_format)
459
+
460
+ puts 'xsl_buffer: ' + xsl_buffer if @debug
461
+ out = Rexslt.new(xsl_buffer, @doc).to_s
462
+
463
+ header ? docheader + "\n" + out : out
464
+ end
465
+
466
+ end
467
+
468
+ def to_table(fields: nil, markdown: false, innermarkdown: false, heading: true)
469
+
470
+ tfo = TableFormatter.new markdown: markdown, innermarkdown: innermarkdown
471
+ tfo.source = self.to_a.map {|h| fields ? fields.map {|x| h[x]} : h.values }
472
+
473
+ if heading then
474
+ raw_headings = self.summary[:headings]
475
+ fields = raw_headings.split(self.delimiter) if raw_headings and fields.nil?
476
+ tfo.labels = (fields ? fields : self.fields.map{|x| x.to_s.capitalize })
477
+ end
478
+
479
+ tfo
480
+
481
+ end
482
+
483
+ def to_xml(opt={})
484
+ opt = {pretty: true} if opt == :pretty
485
+ display_xml(opt)
486
+ end
487
+
488
+ # Save the document to a file.
489
+
490
+ def save(filepath=@local_filepath, options={})
491
+
492
+ if @debug then
493
+ puts 'inside Dynarex::save'
494
+ puts 'filepath: ' + filepath.inspect
495
+
496
+ end
497
+
498
+ return unless filepath
499
+
500
+ opt = {pretty: true}.merge options
501
+
502
+ @local_filepath = filepath || 'dx.xml'
503
+ xml = display_xml(opt)
504
+ buffer = block_given? ? yield(xml) : xml
505
+
506
+ if @debug then
507
+ puts 'before write; filepath: ' + filepath.inspect
508
+ puts 'buffer: ' + buffer.inspect
509
+ end
510
+
511
+ FileX.write filepath, buffer
512
+ FileX.write(filepath.sub(/\.xml$/,'.json'), self.to_json) if @json_out
513
+ end
514
+
515
+ #Parses 1 or more lines of text to create or update existing records.
516
+
517
+ def parse(x=nil)
518
+
519
+ @dirty_flag = true
520
+
521
+ if x.is_a? Array then
522
+
523
+ unless @schema then
524
+ cols = x.first.keys.map {|c| c == 'id' ? 'uid' : c}
525
+ self.schema = "items/item(%s)" % cols.join(', ')
526
+ end
527
+
528
+ x.each {|record| self.create record }
529
+ return self
530
+
531
+ end
532
+ raw_buffer, type = RXFHelper.read(x, auto: false)
533
+
534
+ if raw_buffer.is_a? String then
535
+
536
+ buffer = block_given? ? yield(raw_buffer) : raw_buffer.clone
537
+ string_parse buffer
538
+
539
+ else
540
+ foreign_import x
541
+ end
542
+
543
+ end
544
+
545
+
546
+ alias import parse
547
+
548
+ #Create a record from a hash containing the field name, and the field value.
549
+ # dynarex = Dynarex.new 'contacts/contact(name,age,dob)'
550
+ # dynarex.create name: Bob, age: 52
551
+
552
+ def create(obj, id: nil, custom_attributes: {})
553
+
554
+ puts 'inside create' if @debug
555
+ raise 'Dynarex#create(): input error: no arg provided' unless obj
556
+
557
+ case obj.class.to_s.downcase.to_sym
558
+ when :hash
559
+ hash_create obj, id, attr: custom_attributes
560
+ when :string
561
+ create_from_line obj, id, attr: custom_attributes
562
+ when :recordx
563
+ hash_create obj.to_h, id || obj.id, attr: custom_attributes
564
+ else
565
+ hash_create obj.to_h, id, attr: custom_attributes
566
+ end
567
+
568
+ @dirty_flag = true
569
+
570
+ puts 'before save ' + @autosave.inspect if @debug
571
+ save() if @autosave
572
+
573
+ self
574
+ end
575
+
576
+ #Create a record from a string, given the dynarex document contains a format mask.
577
+ # dynarex = Dynarex.new 'contacts/contact(name,age,dob)'
578
+ # dynarex.create_from_line 'Tracy 37 15-Jun-1972'
579
+
580
+ def create_from_line(line, id=nil, attr: '')
581
+ t = @format_mask.to_s.gsub(/\[!(\w+)\]/, '(.*)').sub(/\[/,'\[')\
582
+ .sub(/\]/,'\]')
583
+ line.match(/#{t}/).captures
584
+
585
+ a = line.match(/#{t}/).captures
586
+ h = Hash[@fields.zip(a)]
587
+ create h
588
+ self
589
+ end
590
+
591
+ def default_key=(id)
592
+ @default_key = id.to_sym
593
+ @summary[:default_key] = id
594
+ @fields << id.to_sym
595
+ end
596
+
597
+
598
+ #Updates a record from an id and a hash containing field name and field value.
599
+ # dynarex.update 4, name: Jeff, age: 38
600
+
601
+ def update(id, obj)
602
+
603
+ params = if obj.is_a? Hash then
604
+ obj
605
+ elsif obj.is_a? RecordX
606
+ obj.to_h
607
+ end
608
+
609
+ fields = capture_fields(params)
610
+
611
+ # for each field update each record field
612
+ record = @doc.root.element("records/#{@record_name}[@id='#{id.to_s}']")
613
+
614
+ fields.each do |k,v|
615
+ puts "updating ... %s = '%s'" % [k,v] if @debug
616
+ record.element(k.to_s).text = v if v
617
+ end
618
+
619
+ record.add_attribute(last_modified: Time.now.to_s)
620
+
621
+ @dirty_flag = true
622
+
623
+ save() if @autosave
624
+
625
+ self
626
+
627
+ end
628
+
629
+
630
+ #Delete a record.
631
+ # dyarex.delete 3 # deletes record with id 3
632
+
633
+ def delete(x)
634
+
635
+ return x.each {|id| self.delete id} if x.is_a? Array
636
+
637
+ if x.to_i.to_s == x.to_s and x[/[0-9]/] then
638
+ @doc.root.delete("records/*[@id='#{x}']")
639
+ else
640
+ @doc.delete x
641
+ end
642
+
643
+ @dirty_flag = true
644
+ save() if @autosave
645
+
646
+ self
647
+ end
648
+
649
+ def element(x)
650
+ @doc.root.element x
651
+ end
652
+
653
+ def sort_by!(field=nil, &element_blk)
654
+
655
+ blk = field ? ->(x){ x.text(field.to_s).to_s} : element_blk
656
+ r = sort_records_by! &blk
657
+ @dirty_flag = true
658
+ r
659
+
660
+ end
661
+
662
+
663
+ def record(id)
664
+ e = @doc.root.element("records/*[@id='#{id}']")
665
+ recordx_to_record e if e
666
+ end
667
+
668
+ alias find record
669
+ alias find_by_id record
670
+
671
+ def record_exists?(id)
672
+ !@doc.root.element("records/*[@id='#{id}']").nil?
673
+ end
674
+
675
+ def refresh()
676
+ @dirty_flag = true
677
+ end
678
+
679
+ def refresh!()
680
+ (load_records; rebuild_doc) if @dirty_flag == true
681
+ end
682
+
683
+ # used internally by to_rss()
684
+ #
685
+ def rss_xslt(opt={})
686
+
687
+ h = {limit: 11}.merge(opt)
688
+ doc = Rexle.new(self.to_xslt)
689
+ e = doc.element('//xsl:apply-templates[2]')
690
+
691
+ e2 = doc.root.element('xsl:template[3]')
692
+ item = e2.element('item')
693
+ new_item = item.deep_clone
694
+ item.delete
695
+
696
+ pubdate = @xslt_schema[/pubDate:/]
697
+ xslif = Rexle.new("<xsl:if test='position() &lt; #{h[:limit]}'/>").root
698
+
699
+ if pubdate.nil? then
700
+ pubdate = Rexle.new("<pubDate><xsl:value-of select='pubDate'>" + \
701
+ "</xsl:value-of></pubDate>").root
702
+ new_item.add pubdate
703
+ end
704
+
705
+ xslif.add new_item
706
+ e2.add xslif.root
707
+ xslt = doc.xml
708
+
709
+ xslt
710
+
711
+ end
712
+
713
+ def filter(&blk)
714
+
715
+ dx = Dynarex.new @schema
716
+ self.all.select(&blk).each {|x| dx.create x}
717
+ dx
718
+
719
+ end
720
+
721
+ def to_xslt(opt={})
722
+
723
+ h = {limit: -1}.merge(opt)
724
+ @xslt_schema = @xslt_schema || self.summary[:xslt_schema]
725
+ raise 'to_xslt(): xslt_schema nil' unless @xslt_schema
726
+
727
+ xslt = DynarexXSLT.new(schema: @schema, xslt_schema: @xslt_schema ).to_xslt
728
+
729
+ return xslt
730
+ end
731
+
732
+ def to_rss(opt={}, xslt=nil)
733
+
734
+ puts 'inside to_rss'.info if @debug
735
+
736
+ unless xslt then
737
+
738
+ h = {limit: 11}.merge(opt)
739
+ doc = Rexle.new(self.to_xslt)
740
+ e = doc.element('//xsl:apply-templates[2]')
741
+
742
+ e2 = doc.root.element('xsl:template[3]')
743
+ item = e2.element('item')
744
+ new_item = item.deep_clone
745
+ item.delete
746
+
747
+ pubdate = @xslt_schema[/pubDate:/]
748
+ xslif = Rexle.new("<xsl:if test='position() &lt; #{h[:limit]}'/>").root
749
+
750
+
751
+ if pubdate.nil? then
752
+ pubdate2 = Rexle.new("<pubDate><xsl:value-of select='pubDate'></xsl:value-of></pubDate>").root
753
+ new_item.add pubdate2
754
+ end
755
+
756
+ xslif.add new_item
757
+ e2.add xslif
758
+ xslt = doc.xml
759
+
760
+ xslt
761
+ end
762
+
763
+ doc = Rexle.new(self.to_xml)
764
+
765
+ puts ('pubdate: ' + pubdate.inspect).debug if @debug
766
+
767
+ if pubdate.nil? then
768
+ doc.root.xpath('records/*').each do |x|
769
+ raw_dt = DateTime.parse x.attributes[:created]
770
+ dt = raw_dt.strftime("%a, %d %b %Y %H:%M:%S %z")
771
+ x.add Rexle::Element.new('pubDate').add_text dt.to_s
772
+ end
773
+ end
774
+
775
+ puts ('doc: ' + doc.root.xml) if @debug
776
+ File.write '/tmp/blog.xml', doc.root.xml
777
+ puts ('xslt:' + xslt.inspect) if @debug
778
+ File.write '/tmp/blog.xslt', xslt
779
+
780
+ out = Rexslt.new(xslt, doc).to_s(declaration: false)
781
+
782
+ #Rexle.new("<rss version='2.0'>%s</rss>" % xml).xml(pretty: true)
783
+
784
+ doc = Rexle.new("<rss version='2.0'>%s</rss>" % out.to_s)
785
+ yield( doc ) if block_given?
786
+ xml = doc.xml(pretty: true)
787
+ xml
788
+ end
789
+
790
+ def unique=(bool)
791
+ self.summary.merge!({unique: bool})
792
+ @dirty_flag = true
793
+ @unique = bool
794
+ end
795
+
796
+ def xpath(x)
797
+ @doc.root.xpath x
798
+ end
799
+
800
+ def xslt=(value)
801
+
802
+ self.summary.merge!({xslt: value})
803
+ @dirty_flag = true
804
+ @xslt = value
805
+ end
806
+
807
+ private
808
+
809
+ def add_id(a)
810
+ @default_key = :uid
811
+ @summary[:default_key] = 'uid'
812
+ @fields << :uid
813
+ a.each.with_index{|x,i| x << (i+1).to_s}
814
+ end
815
+
816
+ def create_find(fields)
817
+
818
+ methods = fields.map do |field|
819
+ "def find_by_#{field}(value) findx_by('#{field}', value) end\n" + \
820
+ "def find_all_by_#{field}(value) findx_all_by(\"#{field}\", value) end"
821
+ end
822
+ self.instance_eval(methods.join("\n"))
823
+ end
824
+
825
+ def findx_by(field, value)
826
+ #@logger.debug "field: #{field.inspect}, value: #{value.inspect}"
827
+ (load_records; rebuild_doc) if @dirty_flag == true
828
+ r = @doc.root.element("records/*[#{field}=\"#{value}\"]")
829
+ r ? recordx_to_record(r) : nil
830
+ end
831
+
832
+ def findx_all_by(field, value)
833
+ @doc.root.xpath("records/*[#{field}=\"#{value}\"]")\
834
+ .map {|x| recordx_to_record x}
835
+ end
836
+
837
+ def recordx_to_record(recordx)
838
+
839
+ h = recordx.attributes
840
+
841
+ records = recordx.xpath("*").map {|x| x.text ? x.text.unescape.to_s : '' }
842
+ hash = @fields.zip(records).to_h
843
+ RecordX.new(hash, self, h[:id], h[:created], h[:last_modified])
844
+
845
+ end
846
+
847
+ def hash_create(raw_params={}, id=nil, attr: {})
848
+
849
+ puts 'inside hash_create' if @debug
850
+ record = make_record(raw_params, id, attr: attr)
851
+ puts 'record: ' + record.inspect if @debug
852
+ method_name = @order == 'ascending' ? :add : :prepend
853
+ @doc.root.element('records').method(method_name).call record
854
+
855
+ end
856
+
857
+ def capture_fields(params)
858
+ fields = Hash[@fields.map {|x| [x,nil]}]
859
+ fields.keys.each {|key| fields[key] = params[key.to_sym] if params.has_key? key.to_sym}
860
+ fields
861
+ end
862
+
863
+ def display_xml(options={})
864
+ #@logger.debug 'inside display_xml'
865
+ opt = {unescape_html: false}.merge options
866
+
867
+ state = :external
868
+ #@logger.debug 'before diry'
869
+ if @dirty_flag == true then
870
+ load_records
871
+ state = :internal
872
+ end
873
+ #@logger.debug 'before rebuilt'
874
+ doc = rebuild_doc(state)
875
+ #@logger.debug 'after rebuild_doc'
876
+
877
+ if opt[:unescape_html] == true then
878
+ doc.content(opt)
879
+ else
880
+ doc.xml(opt)
881
+ end
882
+ end
883
+
884
+ def make_record(raw_params, id=nil, attr: {})
885
+
886
+ id = (@doc.root.xpath('max(records/*/attribute::id)') || '0').succ unless id
887
+ raw_params.merge!(uid: id) if @default_key.to_sym == :uid
888
+ params = Hash[raw_params.keys.map(&:to_sym).zip(raw_params.values)]
889
+
890
+ fields = capture_fields(params)
891
+ record = Rexle::Element.new @record_name
892
+
893
+ fields.each do |k,v|
894
+ element = Rexle::Element.new(k.to_s)
895
+ element.root.text = v.to_s.gsub('<','&lt;').gsub('>','&gt;') if v
896
+ record.add element if record
897
+ end
898
+
899
+ attributes = {id: id.to_s, created: Time.now.to_s, last_modified: nil}\
900
+ .merge attr
901
+ attributes.each {|k,v| record.add_attribute(k, v)}
902
+
903
+ record
904
+ end
905
+
906
+ alias refresh_doc display_xml
907
+
908
+ def parse_links(raw_lines)
909
+
910
+ raw_lines.map do |line|
911
+
912
+ buffer = RXFHelper.read(line.chomp, auto: false).first
913
+
914
+ doc = Rexle.new buffer
915
+
916
+ if doc.root.name == 'kvx' then
917
+
918
+ kvx = Kvx.new doc
919
+ h = kvx.to_h[:body]
920
+ @fields.inject([]){|r,x| r << h[x]}
921
+
922
+ end
923
+
924
+ end
925
+
926
+ end
927
+
928
+ def rebuild_doc(state=:internal)
929
+
930
+ puts 'inside rebuild_doc'.info if @debug
931
+
932
+ reserved_keywords = (
933
+ Object.public_methods | \
934
+ Kernel.public_methods | \
935
+ public_methods + [:method_missing]
936
+ )
937
+
938
+ xml = RexleBuilder.new
939
+
940
+ a = xml.send @root_name do
941
+
942
+ xml.summary do
943
+
944
+ @summary.each do |key,value|
945
+
946
+ v = value.to_s.gsub('>','&gt;')\
947
+ .gsub('<','&lt;')\
948
+ .gsub(/(&\s|&[a-zA-Z\.]+;?)/) {|x| x[-1] == ';' ? x \
949
+ : x.sub('&','&amp;')}
950
+
951
+ xml.send key, v
952
+
953
+ end
954
+ end
955
+
956
+ records = @records.to_a
957
+
958
+ if records then
959
+
960
+ #jr160315records.reverse! if @order == 'descending' and state == :external
961
+
962
+ xml.records do
963
+
964
+ records.each do |k, item|
965
+
966
+ attributes = {}
967
+
968
+ item.keys.each do |key|
969
+ attributes[key] = item[key] || '' unless key == :body
970
+ end
971
+
972
+ if @record_name.nil? then
973
+ raise DynarexException, 'record_name can\'t be nil. Check the schema'
974
+ end
975
+
976
+ xml.send(@record_name, attributes) do
977
+ item[:body].each do |name,value|
978
+
979
+ if reserved_keywords.include? name then
980
+ name = ('._' + name.to_s).to_sym
981
+ end
982
+
983
+ val = value.send(value.is_a?(String) ? :to_s : :to_yaml)
984
+ xml.send(name, val.gsub('>','&gt;')\
985
+ .gsub('<','&lt;')\
986
+ .gsub(/(&\s|&[a-zA-Z\.]+;?)/) do |x|
987
+ x[-1] == ';' ? x : x.sub('&','&amp;')
988
+ end
989
+ )
990
+ end
991
+ end
992
+ end
993
+
994
+ end
995
+ else
996
+ xml.records
997
+ end # end of if @records
998
+ end
999
+
1000
+ doc = Rexle.new(a)
1001
+
1002
+ puts ('@xslt: ' + @xslt.inspect).debug if @debug
1003
+
1004
+ if @xslt then
1005
+ doc.instructions = [['xml-stylesheet',
1006
+ "title='XSL_formatting' type='text/xsl' href='#{@xslt}'"]]
1007
+ end
1008
+
1009
+ return doc if state != :internal
1010
+ @doc = doc
1011
+ end
1012
+
1013
+ def string_parse(buffer)
1014
+
1015
+ if @spaces_delimited then
1016
+ buffer = buffer.lines.map{|x| x.gsub(/\s{2,}/,' # ')}.join
1017
+ end
1018
+
1019
+ buffer.gsub!("\r",'')
1020
+ buffer.gsub!(/\n-{4,}\n/,"\n\n")
1021
+ buffer.gsub!(/---\n/m, "--- ")
1022
+
1023
+ buffer.gsub!(/.>/) {|x| x[0] != '?' ? x.sub(/>/,'&gt;') : x }
1024
+ buffer.gsub!(/<./) {|x| x[1] != '?' ? x.sub(/</,'&lt;') : x }
1025
+
1026
+ @raw_header = buffer[/<\?dynarex[^>]+>/]
1027
+
1028
+ if buffer[/<\?/] then
1029
+
1030
+ raw_stylesheet = buffer.slice!(/<\?xml-stylesheet[^>]+>/)
1031
+ @xslt = raw_stylesheet[/href=["']([^"']+)/,1] if raw_stylesheet
1032
+ @raw_header = buffer.slice!(/<\?dynarex[^>]+>/) + "\n"
1033
+
1034
+ header = @raw_header[/<?dynarex (.*)?>/,1]
1035
+
1036
+ r1 = /([\w\-]+\s*\=\s*'[^']*)'/
1037
+ r2 = /([\w\-]+\s*\=\s*"[^"]*)"/
1038
+
1039
+ r = header.scan(/#{r1}|#{r2}/).map(&:compact).flatten
1040
+
1041
+ r.each do |x|
1042
+
1043
+ attr, val = x.split(/\s*=\s*["']/,2)
1044
+ name = (attr + '=').to_sym
1045
+
1046
+ if self.public_methods.include? name then
1047
+ self.method(name).call(unescape val)
1048
+ else
1049
+ puts "Dynarex: warning: method %s doesn't exist." % [name.inspect]
1050
+ end
1051
+ end
1052
+
1053
+ end
1054
+
1055
+ # if records already exist find the max id
1056
+ i = @doc.root.xpath('max(records/*/attribute::id)').to_i
1057
+
1058
+ raw_summary = schema[/\[([^\]]+)/,1]
1059
+
1060
+ raw_lines = buffer.lines.to_a
1061
+
1062
+ if raw_summary then
1063
+
1064
+ a_summary = raw_summary.split(',').map(&:strip)
1065
+
1066
+ @summary ||= {}
1067
+ raw_lines.shift while raw_lines.first.strip.empty?
1068
+
1069
+ # fetch any summary lines
1070
+ while not raw_lines.empty? and \
1071
+ raw_lines.first[/#{a_summary.join('|')}:\s+\S+/] do
1072
+
1073
+ label, val = raw_lines.shift.chomp.match(/(\w+):\s*([^$]+)$/).captures
1074
+ @summary[label.to_sym] = val
1075
+ end
1076
+
1077
+ self.xslt = @summary[:xslt] || @summary[:xsl] if @summary[:xslt]\
1078
+ or @summary[:xsl]
1079
+ end
1080
+
1081
+ @summary[:recordx_type] = 'dynarex'
1082
+ @summary[:schema] = @schema
1083
+ @summary[:format_mask] = @format_mask
1084
+ @summary[:unique] = @unique if @unique
1085
+
1086
+ raw_lines.shift while raw_lines.first.strip.empty?
1087
+
1088
+ lines = case raw_lines.first.rstrip
1089
+
1090
+ when '---'
1091
+
1092
+ yaml = YAML.load raw_lines.join("\n")
1093
+
1094
+ yamlize = lambda {|x| (x.is_a? Array) ? x.to_yaml : x}
1095
+
1096
+ yprocs = {
1097
+ Hash: lambda {|y|
1098
+ y.map do |k,v|
1099
+ procs = {Hash: proc {|x| x.values}, Array: proc {v}}
1100
+ values = procs[v.class.to_s.to_sym].call(v).map(&yamlize)
1101
+ [k, *values]
1102
+ end
1103
+ },
1104
+ Array: lambda {|y| y.map {|x2| x2.map(&yamlize)} }
1105
+ }
1106
+
1107
+ yprocs[yaml.class.to_s.to_sym].call yaml
1108
+
1109
+ when '--+'
1110
+
1111
+ rowx(raw_lines)
1112
+
1113
+ when '--#'
1114
+
1115
+ self.summary[:rawdoc_type] = 'sectionx'
1116
+ raw_lines.shift
1117
+
1118
+ raw_lines.join.lstrip.split(/(?=^#[^#])/).map {|x| [x.rstrip]}
1119
+
1120
+ else
1121
+
1122
+ raw_lines = raw_lines.join("\n").gsub(/^(\s*#[^\n]+|\n)/,'').lines.to_a
1123
+
1124
+ if @linked then
1125
+
1126
+ parse_links(raw_lines)
1127
+
1128
+ else
1129
+ a2 = raw_lines.map.with_index do |x,i|
1130
+
1131
+ next if x[/^\s+$|\n\s*#/]
1132
+
1133
+ begin
1134
+
1135
+ field_names, field_values = RXRawLineParser.new(@format_mask).parse(x)
1136
+ rescue
1137
+ raise "input file parser error at line " + (i + 1).to_s + ' --> ' + x
1138
+ end
1139
+ field_values
1140
+ end
1141
+
1142
+ a2.compact!
1143
+ a3 = a2.compact.map(&:first)
1144
+
1145
+ if a3 != a3.uniq then
1146
+
1147
+ if @unique then
1148
+ raise DynarexException, "Duplicate id found"
1149
+ else
1150
+ add_id(a2)
1151
+ end
1152
+
1153
+ end
1154
+
1155
+ a2
1156
+ end
1157
+
1158
+ end
1159
+
1160
+ a = lines.map.with_index do |x,i|
1161
+
1162
+ created = Time.now.to_s
1163
+
1164
+ h = Hash[
1165
+ @fields.zip(
1166
+ x.map do |t|
1167
+
1168
+ t.to_s[/^---(?:\s|\n)/] ? YAML.load(t[/^---(?:\s|\n)(.*)/,1]) : unescape(t.to_s)
1169
+ end
1170
+ )
1171
+ ]
1172
+ h[@fields.last] = checked[i].to_s if @type == 'checklist'
1173
+ [h[@default_key], {id: '', created: created, last_modified: '', body: h}]
1174
+ end
1175
+
1176
+ h2 = Hash[a]
1177
+
1178
+ #replace the existing records hash
1179
+ h = @records
1180
+ i = 0
1181
+ h2.each do |key,item|
1182
+ if h.has_key? key then
1183
+
1184
+ # overwrite the previous item and change the timestamps
1185
+ h[key][:last_modified] = item[:created]
1186
+ item[:body].each do |k,v|
1187
+ h[key][:body][k.to_sym] = v
1188
+ end
1189
+ else
1190
+ item[:id] = (@order == 'descending' ? (h2.count) - i : i+ 1).to_s
1191
+ i += 1
1192
+ h[key] = item.clone
1193
+ end
1194
+ end
1195
+
1196
+ h.each {|key, item| h.delete(key) if not h2.has_key? key}
1197
+
1198
+ @flat_records = @records.values.map{|x| x[:body]}
1199
+
1200
+ rebuild_doc
1201
+ self
1202
+ end
1203
+
1204
+ def sort_records_by!(&element_blk)
1205
+
1206
+ refresh_doc
1207
+ a = @doc.root.xpath('records/*').sort_by &element_blk
1208
+ @doc.root.delete('records')
1209
+
1210
+ records = Rexle::Element.new 'records'
1211
+
1212
+ a.each {|record| records.add record}
1213
+
1214
+ @doc.root.add records
1215
+
1216
+ load_records if @dirty_flag
1217
+ self
1218
+ end
1219
+
1220
+ def unescape(s)
1221
+ s.gsub('&lt;', '<').gsub('&gt;','>')
1222
+ end
1223
+
1224
+ def dynarex_new(s, default_key: nil)
1225
+
1226
+ @schema = schema = s
1227
+ @default_key = default_key if default_key
1228
+
1229
+ ptrn = %r((\w+)\[?([^\]]+)?\]?\/(\w+)\(([^\)]+)\))
1230
+
1231
+ if s.match(ptrn) then
1232
+
1233
+ @root_name, raw_summary, record_name, raw_fields = s.match(ptrn).captures
1234
+ summary, fields = [raw_summary || '',raw_fields].map {|x| x.split(/,/).map &:strip}
1235
+
1236
+ if fields.include? 'id' then
1237
+ raise 'Dynarex#dynarex_new: schema field id is a reserved keyword'
1238
+ end
1239
+
1240
+ create_find fields
1241
+
1242
+ reserved = %w(require parent)
1243
+ raise 'reserved keyword' if (fields & reserved).any?
1244
+
1245
+ else
1246
+ ptrn = %r((\w+)\[?([^\]]+)?\]?)
1247
+ @root_name, raw_summary = s.match(ptrn).captures
1248
+ summary = raw_summary.split(/,/).map &:strip
1249
+
1250
+ end
1251
+
1252
+ format_mask = fields ? fields.map {|x| "[!%s]" % x}.join(' ') : ''
1253
+
1254
+ @summary = Hash[summary.zip([''] * summary.length).flatten.each_slice(2)\
1255
+ .map{|x1,x2| [x1.to_sym,x2]}]
1256
+ @summary.merge!({recordx_type: 'dynarex', format_mask: format_mask, schema: s})
1257
+ @records = {}
1258
+ @flat_records = {}
1259
+
1260
+ rebuild_doc
1261
+
1262
+ end
1263
+
1264
+ def attach_record_methods()
1265
+ create_find @fields
1266
+ end
1267
+
1268
+ def openx(s)
1269
+ #@logger.debug 'inside openx'
1270
+ if s[/</] then # xml
1271
+ #@logger.debug 'regular string'
1272
+ #@logger.debug 's: ' + s.inspect
1273
+ buffer = s
1274
+
1275
+ elsif s[/[\[\(]/] # schema
1276
+
1277
+ dynarex_new(s)
1278
+
1279
+ elsif s[/^https?:\/\//] then # url
1280
+ buffer, type = RXFHelper.read s, {username: @username,
1281
+ password: @password, auto: false}
1282
+ elsif s[/^dfs?:\/\//] then
1283
+
1284
+ @local_filepath = s
1285
+
1286
+ if FileX.exists? s then
1287
+ buffer = FileX.read s
1288
+ elsif @schema
1289
+ dynarex_new @schema, default_key: @default_key
1290
+ end
1291
+
1292
+ else # local file
1293
+
1294
+ @local_filepath = s
1295
+
1296
+ if File.exists? s then
1297
+ buffer = File.read s
1298
+ elsif @schema
1299
+ dynarex_new @schema, default_key: @default_key
1300
+ else
1301
+ raise DynarexException, 'file not found: ' + s
1302
+ end
1303
+ end
1304
+ #@logger.debug 'buffer: ' + buffer[0..120]
1305
+
1306
+ return import(buffer) if buffer =~ /^<\?dynarex\b/
1307
+
1308
+ if buffer then
1309
+
1310
+ raw_stylesheet = buffer.slice!(/<\?xml-stylesheet[^>]+>/)
1311
+ @xslt = raw_stylesheet[/href=["']([^"']+)/,1] if raw_stylesheet
1312
+
1313
+ @doc = Rexle.new(buffer) unless @doc
1314
+ #@logger.debug 'openx/@doc : ' + @doc.xml.inspect
1315
+ end
1316
+
1317
+ return if @doc.root.nil?
1318
+ e = @doc.root.element('summary')
1319
+
1320
+ @schema = e.text('schema')
1321
+ @root_name = @doc.root.name
1322
+ @summary = summary_to_h
1323
+
1324
+ summary_methods = (@summary.keys - self.public_methods)
1325
+
1326
+ summary_methods.each do |x|
1327
+
1328
+ instance_eval "
1329
+
1330
+ def #{x.to_sym}()
1331
+ @summary[:#{x}]
1332
+ end
1333
+
1334
+ def #{x.to_s}=(v)
1335
+ @summary[:#{x}] = v
1336
+ @doc.root.element('summary/#{x.to_s}').text = v
1337
+ end
1338
+ "
1339
+ end
1340
+
1341
+ @order = @summary[:order] if @summary.has_key? :order
1342
+
1343
+
1344
+ @default_key ||= e.text('default_key')
1345
+ @format_mask = e.text('format_mask')
1346
+ @xslt = e.text('xslt')
1347
+
1348
+ @fields = @schema[/([^(]+)\)$/,1].split(/\s*,\s*/).map(&:to_sym)
1349
+
1350
+ @fields << @default_key if @default_key and not @default_key.empty? and \
1351
+ !@fields.include? @default_key.to_sym
1352
+
1353
+ if @schema and @schema.match(/(\w+)\(([^\)]+)/) then
1354
+ @record_name, raw_fields = @schema.match(/(\w+)\(([^\)]+)/).captures
1355
+ @fields = raw_fields.split(',').map{|x| x.strip.to_sym} unless @fields
1356
+ end
1357
+
1358
+ if @fields then
1359
+
1360
+ @default_key = @fields[0] unless @default_key
1361
+ # load the record query handler methods
1362
+ attach_record_methods
1363
+ else
1364
+
1365
+ #jr080912 @default_key = @doc.root.xpath('records/*/*').first.name
1366
+ @default_key = @doc.root.element('records/./.[1]').name
1367
+ end
1368
+
1369
+ @summary[:default_key] = @default_key.to_s
1370
+
1371
+ if @doc.root.xpath('records/*').length > 0 then
1372
+ @record_name = @doc.root.element('records/*[1]').name
1373
+ #jr240913 load_records
1374
+ @dirty_flag = true
1375
+ end
1376
+
1377
+ end
1378
+
1379
+ def load_records
1380
+
1381
+ puts 'inside load_records'.info if @debug
1382
+
1383
+ @dirty_flag = false
1384
+
1385
+ if @summary[:order] then
1386
+ orderfield = @summary[:order][/(\w+)\s+(?:ascending|descending)/,1]
1387
+ sort_records_by! {|x| x.element(orderfield).text } if orderfield
1388
+ end
1389
+
1390
+ @records = records_to_h
1391
+
1392
+ @records.instance_eval do
1393
+ def delete_item(i)
1394
+ self.delete self.keys[i]
1395
+ end
1396
+ end
1397
+
1398
+ #Returns a ready-only snapshot of records as a simple Hash.
1399
+ @flat_records = @records.values.map{|x| x[:body]}
1400
+
1401
+ end
1402
+
1403
+
1404
+ def display()
1405
+ puts @doc.to_s
1406
+ end
1407
+
1408
+ def records_to_h(order=:ascending)
1409
+
1410
+ i = @doc.root.xpath('max(records/*/attribute::id)') || 0
1411
+ records = @doc.root.xpath('records/*')
1412
+ #@logger.debug 'records: ' + records.inspect
1413
+ records = records.take @limit if @limit
1414
+
1415
+ recs = records #jr160315 (order == :descending ? records.reverse : records)
1416
+ a = recs.inject({}) do |result,row|
1417
+
1418
+ created = Time.now.to_s
1419
+ last_modified = ''
1420
+
1421
+ if row.attributes[:id] then
1422
+ id = row.attributes[:id]
1423
+ else
1424
+ i += 1; id = i.to_s
1425
+ end
1426
+
1427
+ body = (@fields - ['uid']).inject({}) do |r,field|
1428
+
1429
+ node = row.element field.to_s
1430
+
1431
+ if node then
1432
+ text = node.text ? node.text.unescape : ''
1433
+
1434
+ r.merge node.name.to_sym => (text[/^---(?:\s|\n)/] ?
1435
+ YAML.load(text[/^---(?:\s|\n)(.*)/,1]) : text)
1436
+ else
1437
+ r
1438
+ end
1439
+ end
1440
+
1441
+ body[:uid] = id if @default_key == 'uid'
1442
+
1443
+ attributes = row.attributes
1444
+ result.merge body[@default_key.to_sym] => attributes.merge({id: id, body: body})
1445
+ end
1446
+
1447
+ puts 'records_to_h a: ' + a.inspect if @debug
1448
+ #@logger.debug 'a: ' + a.inspect
1449
+ a
1450
+
1451
+ end
1452
+
1453
+ def rowx(raw_lines)
1454
+
1455
+ self.summary[:rawdoc_type] = 'rowx'
1456
+ raw_lines.shift
1457
+
1458
+ a3 = raw_lines.join.strip.split(/\n\n(?=\w+:)/)
1459
+
1460
+ # get the fields
1461
+ a4 = a3.map{|x| x.scan(/^\w+(?=:)/)}.flatten(1).uniq
1462
+
1463
+ abbrv_fields = a4.all? {|x| x.length == 1}
1464
+
1465
+ a5 = a3.map do |xlines|
1466
+
1467
+ puts 'xlines: ' + xlines.inspect if @debug
1468
+
1469
+ missing_fields = a4 - xlines.scan(/^\w+(?=:)/)
1470
+
1471
+ r = xlines.split(/\n(\w+:.*)/m)
1472
+ puts 'r: ' + r.inspect if @debug
1473
+
1474
+ missing_fields.map!{|x| x + ":"}
1475
+ key = (abbrv_fields ? @fields[0].to_s[0] : @fields.first.to_s) + ':'
1476
+
1477
+ if missing_fields.include? key
1478
+ r.unshift key
1479
+ missing_fields.delete key
1480
+ end
1481
+
1482
+ r += missing_fields
1483
+ r.join("\n")
1484
+
1485
+ end
1486
+ puts 'a5: ' + a5.inspect if @debug
1487
+
1488
+ xml = RowX.new(a5.join("\n").strip, level: 0).to_xml
1489
+ puts 'xml: ' + xml.inspect if @debug
1490
+
1491
+ a2 = Rexle.new(xml).root.xpath('item').inject([]) do |r,x|
1492
+
1493
+ r << @fields.map do |field|
1494
+ x.text(abbrv_fields ? field.to_s.chr : field.to_s )
1495
+ end
1496
+
1497
+ end
1498
+
1499
+ a2.compact!
1500
+
1501
+ # if there is no field value for the first field then
1502
+ # the default_key is invalid. The default_key is changed to an ID.
1503
+ if a2.detect {|x| x.first == ''} then
1504
+ add_id(a2)
1505
+ else
1506
+
1507
+ a3 = a2.map(&:first)
1508
+ add_id(a2) if a3 != a3.uniq
1509
+
1510
+ end
1511
+
1512
+ a2
1513
+
1514
+ end
1515
+
1516
+ def sort_records
1517
+ xsl =<<XSL
1518
+ <?xml version="1.0" encoding="UTF-8"?>
1519
+ <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
1520
+
1521
+ <xsl:template match="*">
1522
+ <xsl:element name="{name()}"><xsl:text>
1523
+ </xsl:text>
1524
+ <xsl:copy-of select="summary"/><xsl:text>
1525
+ </xsl:text>
1526
+ <xsl:apply-templates select="records"/>
1527
+ </xsl:element>
1528
+ </xsl:template>
1529
+ <xsl:template match="records">
1530
+ <records><xsl:text>
1531
+ </xsl:text>
1532
+ <xsl:for-each select="child::*">
1533
+ <xsl:sort order="descending"/>
1534
+ <xsl:text> </xsl:text><xsl:copy-of select="."/><xsl:text>
1535
+ </xsl:text>
1536
+ </xsl:for-each>
1537
+ </records><xsl:text>
1538
+ </xsl:text>
1539
+ </xsl:template>
1540
+
1541
+ </xsl:stylesheet>
1542
+ XSL
1543
+
1544
+ @doc = Rexle.new(Rexslt.new(xsl, self.to_xml).to_s)
1545
+ @dirty_flag = true
1546
+ end
1547
+
1548
+ def summary_to_h
1549
+
1550
+ h = {recordx_type: 'dynarex'}
1551
+
1552
+ @doc.root.xpath('summary/*').inject(h) do |r,node|
1553
+ r.merge node.name.to_s.to_sym =>
1554
+ node.text ? node.text.unescape : node.text.to_s
1555
+ end
1556
+ end
1557
+
1558
+ end