openehr 1.3.0 → 2.0.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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/README.rdoc +93 -12
  3. data/lib/openehr/am/archetype/constraint_model/primitive.rb +204 -6
  4. data/lib/openehr/am/archetype/constraint_model.rb +228 -4
  5. data/lib/openehr/am/archetype/ontology.rb +2 -2
  6. data/lib/openehr/am/archetype.rb +101 -2
  7. data/lib/openehr/am/openehr_profile/data_types/basic.rb +28 -0
  8. data/lib/openehr/am/openehr_profile/data_types/quantity.rb +88 -0
  9. data/lib/openehr/am/openehr_profile/data_types/text.rb +23 -0
  10. data/lib/openehr/am/template.rb +25 -3
  11. data/lib/openehr/aql/engine/binding.rb +13 -0
  12. data/lib/openehr/aql/engine/contains_resolver.rb +161 -0
  13. data/lib/openehr/aql/engine/dataset.rb +122 -0
  14. data/lib/openehr/aql/engine/path_evaluator.rb +137 -0
  15. data/lib/openehr/aql/engine/predicate_evaluator.rb +65 -0
  16. data/lib/openehr/aql/engine.rb +119 -0
  17. data/lib/openehr/aql/errors.rb +27 -0
  18. data/lib/openehr/aql/lexer.rb +295 -0
  19. data/lib/openehr/aql/model/containment.rb +45 -0
  20. data/lib/openehr/aql/model/from_clause.rb +35 -0
  21. data/lib/openehr/aql/model/function_call.rb +56 -0
  22. data/lib/openehr/aql/model/identified_path.rb +20 -0
  23. data/lib/openehr/aql/model/literal.rb +15 -0
  24. data/lib/openehr/aql/model/object_path.rb +34 -0
  25. data/lib/openehr/aql/model/order_by_and_limit.rb +50 -0
  26. data/lib/openehr/aql/model/predicate.rb +84 -0
  27. data/lib/openehr/aql/model/query.rb +24 -0
  28. data/lib/openehr/aql/model/select_clause.rb +29 -0
  29. data/lib/openehr/aql/model/where_clause.rb +105 -0
  30. data/lib/openehr/aql/model.rb +14 -0
  31. data/lib/openehr/aql/parser.rb +484 -0
  32. data/lib/openehr/aql/result_set.rb +54 -0
  33. data/lib/openehr/aql.rb +28 -0
  34. data/lib/openehr/assumed_library_types.rb +68 -17
  35. data/lib/openehr/parser/adl_grammar.tt +29 -13
  36. data/lib/openehr/parser/adl_parser.rb +18 -4
  37. data/lib/openehr/parser/archetype_validator.rb +126 -0
  38. data/lib/openehr/parser/exception.rb +13 -0
  39. data/lib/openehr/parser/opt_parser.rb +162 -9
  40. data/lib/openehr/parser.rb +2 -0
  41. data/lib/openehr/path.rb +195 -0
  42. data/lib/openehr/rm/common/archetyped.rb +116 -6
  43. data/lib/openehr/rm/common/change_control.rb +3 -8
  44. data/lib/openehr/rm/common/directory.rb +4 -0
  45. data/lib/openehr/rm/common/generic.rb +24 -10
  46. data/lib/openehr/rm/composition/content/entry.rb +24 -5
  47. data/lib/openehr/rm/composition/content/navigation.rb +2 -1
  48. data/lib/openehr/rm/composition.rb +19 -2
  49. data/lib/openehr/rm/data_structures/history.rb +3 -0
  50. data/lib/openehr/rm/data_structures/item_structure/representation.rb +13 -9
  51. data/lib/openehr/rm/data_structures/item_structure.rb +28 -15
  52. data/lib/openehr/rm/data_types/encapsulated.rb +23 -3
  53. data/lib/openehr/rm/data_types/quantity/date_time.rb +9 -1
  54. data/lib/openehr/rm/data_types/quantity.rb +66 -14
  55. data/lib/openehr/rm/data_types/text.rb +8 -0
  56. data/lib/openehr/rm/data_types/time_specification.rb +5 -4
  57. data/lib/openehr/rm/demographic.rb +4 -3
  58. data/lib/openehr/rm/ehr.rb +20 -1
  59. data/lib/openehr/rm/factory.rb +234 -6
  60. data/lib/openehr/rm/integration.rb +1 -0
  61. data/lib/openehr/rm/support/identification.rb +37 -8
  62. data/lib/openehr/rm/support/measurement.rb +32 -0
  63. data/lib/openehr/rm/type_name.rb +90 -0
  64. data/lib/openehr/rm.rb +3 -0
  65. data/lib/openehr/serializer/adl_serializer.rb +335 -0
  66. data/lib/openehr/serializer/base.rb +20 -0
  67. data/lib/openehr/serializer/opt_serializer.rb +49 -0
  68. data/lib/openehr/serializer/rm_json_serializer.rb +62 -0
  69. data/lib/openehr/serializer/xml_serializer.rb +260 -0
  70. data/lib/openehr/serializer.rb +5 -291
  71. data/lib/openehr/terminology_service.rb +44 -0
  72. data/lib/openehr/version.rb +1 -1
  73. data/lib/openehr.rb +5 -2
  74. metadata +35 -7
  75. data/lib/openehr/parser/validator.rb +0 -18
  76. data/lib/openehr/parser/xml_parser.rb +0 -13
  77. data/lib/openehr/rm/data_types/charset_extract.rb +0 -24
  78. data/lib/openehr/writer.rb +0 -12
@@ -80,12 +80,6 @@ module OpenEHR
80
80
  end
81
81
  end
82
82
 
83
- class TermMappingFactory
84
- def self.create(*param)
85
- OpenEHR::RM::DataTypes::Text::TermMapping.new(*param)
86
- end
87
- end
88
-
89
83
  class CodePhraseFactory
90
84
  def self.create(*param)
91
85
  OpenEHR::RM::DataTypes::Text::CodePhrase.new(*param)
@@ -128,6 +122,12 @@ module OpenEHR
128
122
  end
129
123
  end
130
124
 
125
+ class DvScaleFactory
126
+ def self.create(*param)
127
+ OpenEHR::RM::DataTypes::Quantity::DvScale.new(*param)
128
+ end
129
+ end
130
+
131
131
  class DvQuantifiedFactory
132
132
  def self.create(*param)
133
133
  DataTypes::Quantity::DvQuantified.new(*param)
@@ -296,6 +296,234 @@ module OpenEHR
296
296
  end
297
297
  end
298
298
 
299
+ class ElementFactory
300
+ def self.create(param)
301
+ DataStructures::ItemStructure::Representation::Element.new(param)
302
+ end
303
+ end
304
+
305
+ class ItemTreeFactory
306
+ def self.create(param)
307
+ DataStructures::ItemStructure::ItemTree.new(param)
308
+ end
309
+ end
310
+
311
+ class ItemListFactory
312
+ def self.create(param)
313
+ DataStructures::ItemStructure::ItemList.new(param)
314
+ end
315
+ end
316
+
317
+ class ItemSingleFactory
318
+ def self.create(param)
319
+ DataStructures::ItemStructure::ItemSingle.new(param)
320
+ end
321
+ end
322
+
323
+ class ItemTableFactory
324
+ def self.create(param)
325
+ DataStructures::ItemStructure::ItemTable.new(param)
326
+ end
327
+ end
328
+
329
+ class HistoryFactory
330
+ def self.create(param)
331
+ DataStructures::History::History.new(param)
332
+ end
333
+ end
334
+
335
+ class EventFactory
336
+ def self.create(param)
337
+ DataStructures::History::Event.new(param)
338
+ end
339
+ end
340
+
341
+ class PointEventFactory
342
+ def self.create(param)
343
+ DataStructures::History::PointEvent.new(param)
344
+ end
345
+ end
346
+
347
+ class IntervalEventFactory
348
+ def self.create(param)
349
+ DataStructures::History::IntervalEvent.new(param)
350
+ end
351
+ end
352
+
353
+ class EvaluationFactory
354
+ def self.create(param)
355
+ Composition::Content::Entry::Evaluation.new(param)
356
+ end
357
+ end
358
+
359
+ class InstructionFactory
360
+ def self.create(param)
361
+ Composition::Content::Entry::Instruction.new(param)
362
+ end
363
+ end
364
+
365
+ class ActionFactory
366
+ def self.create(param)
367
+ Composition::Content::Entry::Action.new(param)
368
+ end
369
+ end
370
+
371
+ class ActivityFactory
372
+ def self.create(param)
373
+ Composition::Content::Entry::Activity.new(param)
374
+ end
375
+ end
376
+
377
+ class AdminEntryFactory
378
+ def self.create(param)
379
+ Composition::Content::Entry::AdminEntry.new(param)
380
+ end
381
+ end
382
+
383
+ class InstructionDetailsFactory
384
+ def self.create(param)
385
+ Composition::Content::Entry::InstructionDetails.new(param)
386
+ end
387
+ end
388
+
389
+ class IsmTransitionFactory
390
+ def self.create(param)
391
+ Composition::Content::Entry::IsmTransition.new(param)
392
+ end
393
+ end
394
+
395
+ class GenericEntryFactory
396
+ def self.create(param)
397
+ Integration::GenericEntry.new(param)
398
+ end
399
+ end
400
+
401
+ class PartySelfFactory
402
+ def self.create(param)
403
+ Common::Generic::PartySelf.new(param)
404
+ end
405
+ end
406
+
407
+ class PartyRelatedFactory
408
+ def self.create(param)
409
+ Common::Generic::PartyRelated.new(param)
410
+ end
411
+ end
412
+
413
+ class ParticipationFactory
414
+ def self.create(param)
415
+ Common::Generic::Participation.new(param)
416
+ end
417
+ end
418
+
419
+ class LinkFactory
420
+ def self.create(param)
421
+ Common::Archetyped::Link.new(param)
422
+ end
423
+ end
424
+
425
+ class FeederAuditFactory
426
+ def self.create(param)
427
+ Common::Archetyped::FeederAudit.new(param)
428
+ end
429
+ end
430
+
431
+ class AuditDetailsFactory
432
+ def self.create(param)
433
+ Common::Generic::AuditDetails.new(param)
434
+ end
435
+ end
436
+
437
+ class AttestationFactory
438
+ def self.create(param)
439
+ Common::Generic::Attestation.new(param)
440
+ end
441
+ end
442
+
443
+ class ObjectRefFactory
444
+ def self.create(param)
445
+ Support::Identification::ObjectRef.new(param)
446
+ end
447
+ end
448
+
449
+ class LocatableRefFactory
450
+ def self.create(param)
451
+ Support::Identification::LocatableRef.new(param)
452
+ end
453
+ end
454
+
455
+ class ObjectVersionIdFactory
456
+ def self.create(param)
457
+ Support::Identification::ObjectVersionID.new(param)
458
+ end
459
+ end
460
+
461
+ class HierObjectIdFactory
462
+ def self.create(param)
463
+ Support::Identification::HierObjectID.new(param)
464
+ end
465
+ end
466
+
467
+ class UidBasedIdFactory
468
+ def self.create(param)
469
+ Support::Identification::UIDBasedID.new(param)
470
+ end
471
+ end
472
+
473
+ class AccessGroupRefFactory
474
+ def self.create(param)
475
+ Support::Identification::AccessGroupRef.new(param)
476
+ end
477
+ end
478
+
479
+ class PersonFactory
480
+ def self.create(param)
481
+ Demographic::Person.new(param)
482
+ end
483
+ end
484
+
485
+ class OrganisationFactory
486
+ def self.create(param)
487
+ Demographic::Organisation.new(param)
488
+ end
489
+ end
490
+
491
+ class RoleFactory
492
+ def self.create(param)
493
+ Demographic::Role.new(param)
494
+ end
495
+ end
496
+
497
+ class PartyIdentityFactory
498
+ def self.create(param)
499
+ Demographic::PartyIdentity.new(param)
500
+ end
501
+ end
502
+
503
+ class ContactFactory
504
+ def self.create(param)
505
+ Demographic::Contact.new(param)
506
+ end
507
+ end
508
+
509
+ class AddressFactory
510
+ def self.create(param)
511
+ Demographic::Address.new(param)
512
+ end
513
+ end
514
+
515
+ class CapabilityFactory
516
+ def self.create(param)
517
+ Demographic::Capability.new(param)
518
+ end
519
+ end
520
+
521
+ class PartyRelationshipFactory
522
+ def self.create(param)
523
+ Demographic::PartyRelationship.new(param)
524
+ end
525
+ end
526
+
299
527
  class CompositionFactory < Factory
300
528
  class << self
301
529
  def create_from_json(json)
@@ -9,6 +9,7 @@ module OpenEHR
9
9
  module Integration
10
10
  class GenericEntry < OpenEHR::RM::Composition::Content::ContentItem
11
11
  attr_reader :data
12
+ path_attribute :data
12
13
 
13
14
  def initialize(args = { })
14
15
  super(args)
@@ -68,7 +68,7 @@ module OpenEHR
68
68
  end
69
69
 
70
70
  def value=(value)
71
- if /([a-zA-Z]\w+)-([a-zA-Z]\w+)-([a-zA-Z]\w+)\.([a-zA-Z]\w+)(-([a-zA-Z]\w+))?\.(v[1-9]\d*)/ =~ value
71
+ if /\A([a-zA-Z]\w+)-([a-zA-Z]\w+)-([a-zA-Z]\w+)\.([a-zA-Z]\w+)(-([a-zA-Z]\w+))?\.(v\d+)\z/ =~ value
72
72
  self.rm_originator = $1
73
73
  self.rm_name = $2
74
74
  self.rm_entity = $3
@@ -108,7 +108,7 @@ module OpenEHR
108
108
  if domain_concept.nil? or domain_concept.empty?
109
109
  raise ArgumentError, "domain concept not valid"
110
110
  end
111
- if /([a-zA-Z]\w+)(-([a-zA-Z]\w))?/ =~ domain_concept
111
+ if /\A([a-zA-Z]\w+)(-([a-zA-Z]\w+))?\z/ =~ domain_concept
112
112
  self.concept_name = $1
113
113
  self.specialisation = $3
114
114
  else
@@ -249,6 +249,8 @@ module OpenEHR
249
249
  self.objectid = UID.new(:value => $1)
250
250
  self.creating_system_id = UID.new(:value => $2)
251
251
  self.version_tree_id = VersionTreeID.new(:value => $3)
252
+ @root = @oid
253
+ @extension = @creating_system_id.value + '::' + @version_tree_id.value
252
254
  else
253
255
  raise ArgumentError, 'invalid format'
254
256
  end
@@ -297,12 +299,14 @@ module OpenEHR
297
299
  end
298
300
 
299
301
  def path=(path)
300
- raise ArgumentError if path.nil? or path.empty?
302
+ raise ArgumentError if !path.nil? and path.empty?
301
303
  @path = path
302
304
  end
303
305
 
304
306
  def as_uri
305
- 'ehr://' + @id.value + '/' + @path
307
+ uri = 'ehr://' + @id.value
308
+ uri += '/' + @path unless @path.nil?
309
+ uri
306
310
  end
307
311
  end
308
312
 
@@ -395,16 +399,41 @@ module OpenEHR
395
399
  end
396
400
 
397
401
  class UUID < UID
402
+ FORMAT = /\A[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\z/
398
403
 
404
+ def value=(value)
405
+ unless value.is_a?(String) && value =~ FORMAT
406
+ raise ArgumentError, 'invalid UUID form'
407
+ end
408
+ super
409
+ end
399
410
  end
400
411
 
401
- class InternetID <UID
402
-
412
+ class InternetID < UID
413
+ # RFC 1034 domain-name form: dot-separated labels, each
414
+ # alphanumeric (with internal hyphens allowed).
415
+ FORMAT = /\A[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+\z/
416
+
417
+ def value=(value)
418
+ unless value.is_a?(String) && value =~ FORMAT
419
+ raise ArgumentError, 'invalid internet id form'
420
+ end
421
+ super
422
+ end
403
423
  end
404
424
 
405
- class IsoOID <UID
425
+ class IsoOID < UID
426
+ # ISO/IEC 8824 OID form: a dot-separated sequence of
427
+ # non-negative integers, e.g. "1.2.840.113619.2.1".
428
+ FORMAT = /\A\d+(\.\d+)+\z/
406
429
 
407
- end
430
+ def value=(value)
431
+ unless value.is_a?(String) && value =~ FORMAT
432
+ raise ArgumentError, 'invalid ISO OID form'
433
+ end
434
+ super
435
+ end
436
+ end
408
437
  end # of Identification
409
438
  end # of Support
410
439
  end # of RM
@@ -2,7 +2,39 @@ module OpenEHR
2
2
  module RM
3
3
  module Support
4
4
  module Measurement
5
+ # A minimal UCUM-lite syntactic checker, not a full UCUM
6
+ # parser/conversion table: it validates that a units string is
7
+ # *shaped* like a UCUM unit (allowed characters, balanced
8
+ # brackets) and treats "equivalent" as exact string equality of
9
+ # two syntactically-valid units strings. It does not perform
10
+ # unit conversion (e.g. "kg" and "1000g" are not recognized as
11
+ # equivalent).
5
12
  class MeasurementService
13
+ UCUM_LITE_PATTERN = /\A[A-Za-z0-9%\/.*\[\]{}'_-]+\z/
14
+
15
+ def self.is_valid_units_string?(units)
16
+ return false unless units.is_a?(String) && !units.empty?
17
+ return false unless units =~ UCUM_LITE_PATTERN
18
+
19
+ balanced_brackets?(units, '[', ']') && balanced_brackets?(units, '{', '}')
20
+ end
21
+
22
+ def self.units_equivalent?(units1, units2)
23
+ return false unless is_valid_units_string?(units1) && is_valid_units_string?(units2)
24
+
25
+ units1 == units2
26
+ end
27
+
28
+ def self.balanced_brackets?(str, open, close)
29
+ depth = 0
30
+ str.each_char do |c|
31
+ depth += 1 if c == open
32
+ depth -= 1 if c == close
33
+ return false if depth.negative?
34
+ end
35
+ depth.zero?
36
+ end
37
+ private_class_method :balanced_brackets?
6
38
  end
7
39
  module ExternalEnvironmentAccess
8
40
  def eea_terminology_svc
@@ -0,0 +1,90 @@
1
+ require 'set'
2
+ require 'active_support/core_ext/string/inflections'
3
+
4
+ module OpenEHR
5
+ module RM
6
+ module TypeName
7
+ module_function
8
+
9
+ def type_name_of(class_or_instance)
10
+ class_of(class_or_instance).name.split('::').last.underscore.upcase
11
+ end
12
+
13
+ def class_for(type_name)
14
+ registry[normalize(type_name)]
15
+ end
16
+
17
+ def subtype_of?(class_or_instance, type_name)
18
+ expected = normalize(type_name)
19
+ ancestor = class_of(class_or_instance)
20
+ while ancestor
21
+ return true if ancestor.name && type_name_of(ancestor) == expected
22
+
23
+ ancestor = ancestor.superclass
24
+ end
25
+ false
26
+ end
27
+
28
+ # Accepts a spec type name in any of its forms seen across this
29
+ # codebase - UPPER_SNAKE ('DV_QUANTITY', from the ADL/OPT
30
+ # parsers), CamelCase ('DvQuantity', hardcoded by some
31
+ # CDomainType constructors) or lower_snake - and normalises to
32
+ # the UPPER_SNAKE form used as the registry key.
33
+ def normalize(type_name)
34
+ type_name.to_s.underscore.upcase
35
+ end
36
+ private_class_method :normalize
37
+
38
+ def class_of(class_or_instance)
39
+ class_or_instance.is_a?(Module) ? class_or_instance : class_or_instance.class
40
+ end
41
+ private_class_method :class_of
42
+
43
+ def registry
44
+ @registry ||= build_registry
45
+ end
46
+ private_class_method :registry
47
+
48
+ # Skips Factory (OpenEHR::RM::Factory and the *Factory helper
49
+ # classes in factory.rb) - those are builders, not RM types, and
50
+ # would otherwise shadow nothing but add registry noise.
51
+ def build_registry
52
+ classes = {}
53
+ visit(OpenEHR::RM, Set.new.compare_by_identity) do |klass|
54
+ next if klass.name.end_with?('Factory')
55
+
56
+ classes[type_name_of(klass)] ||= klass
57
+ end
58
+ classes
59
+ end
60
+ private_class_method :build_registry
61
+
62
+ def visit(mod, seen, &block)
63
+ mod.constants(false).each do |const_name|
64
+ const = mod.const_get(const_name)
65
+ next unless const.is_a?(Module)
66
+ next if seen.include?(const)
67
+
68
+ seen << const
69
+ yield const if const.is_a?(Class)
70
+ visit(const, seen, &block)
71
+ end
72
+ end
73
+ private_class_method :visit
74
+ end
75
+
76
+ class << self
77
+ def type_name_of(class_or_instance)
78
+ TypeName.type_name_of(class_or_instance)
79
+ end
80
+
81
+ def class_for(type_name)
82
+ TypeName.class_for(type_name)
83
+ end
84
+
85
+ def subtype_of?(class_or_instance, type_name)
86
+ TypeName.subtype_of?(class_or_instance, type_name)
87
+ end
88
+ end
89
+ end
90
+ end
data/lib/openehr/rm.rb CHANGED
@@ -44,3 +44,6 @@ require_relative 'rm/integration'
44
44
 
45
45
  #factory and builder
46
46
  require_relative 'rm/factory'
47
+
48
+ #RM type name <-> class lookup
49
+ require_relative 'rm/type_name'