lutaml-uml 0.1.0 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/macos.yml +38 -0
  3. data/.github/workflows/ubuntu.yml +40 -0
  4. data/.github/workflows/windows.yml +51 -0
  5. data/.gitignore +1 -0
  6. data/Gemfile +2 -1
  7. data/LUTAML.adoc +339 -0
  8. data/{README.md → README.adoc} +15 -16
  9. data/Rakefile +3 -1
  10. data/bin/console +1 -0
  11. data/bin/folder_yaml2lutaml.sh +6 -0
  12. data/bin/plantuml2lutaml +58 -0
  13. data/bin/yaml2lutaml +144 -0
  14. data/exe/lutaml-uml +4 -3
  15. data/lib/lutaml/layout/engine.rb +15 -0
  16. data/lib/lutaml/layout/graph_viz_engine.rb +19 -0
  17. data/lib/lutaml/uml.rb +4 -0
  18. data/lib/lutaml/uml/abstraction.rb +7 -5
  19. data/lib/lutaml/uml/activity.rb +7 -5
  20. data/lib/lutaml/uml/actor.rb +14 -12
  21. data/lib/lutaml/uml/association.rb +40 -14
  22. data/lib/lutaml/uml/behavior.rb +7 -5
  23. data/lib/lutaml/uml/class.rb +61 -16
  24. data/lib/lutaml/uml/classifier.rb +9 -6
  25. data/lib/lutaml/uml/connector.rb +16 -12
  26. data/lib/lutaml/uml/constraint.rb +8 -7
  27. data/lib/lutaml/uml/constructor_end.rb +11 -8
  28. data/lib/lutaml/uml/data_type.rb +12 -4
  29. data/lib/lutaml/uml/dependency.rb +16 -13
  30. data/lib/lutaml/uml/document.rb +71 -0
  31. data/lib/lutaml/uml/enum.rb +40 -0
  32. data/lib/lutaml/uml/event.rb +7 -5
  33. data/lib/lutaml/uml/final_state.rb +7 -5
  34. data/lib/lutaml/uml/formatter.rb +21 -0
  35. data/lib/lutaml/uml/formatter/base.rb +67 -0
  36. data/lib/lutaml/uml/formatter/graphviz.rb +334 -0
  37. data/lib/lutaml/uml/has_attributes.rb +14 -0
  38. data/lib/lutaml/uml/has_members.rb +30 -0
  39. data/lib/lutaml/uml/instance.rb +15 -10
  40. data/lib/lutaml/uml/interface/base.rb +28 -0
  41. data/lib/lutaml/uml/interface/command_line.rb +265 -0
  42. data/lib/lutaml/uml/lutaml_path/document_wrapper.rb +15 -0
  43. data/lib/lutaml/uml/model.rb +11 -8
  44. data/lib/lutaml/uml/node/base.rb +21 -0
  45. data/lib/lutaml/uml/node/class_node.rb +57 -0
  46. data/lib/lutaml/uml/node/class_relationship.rb +14 -0
  47. data/lib/lutaml/uml/node/document.rb +18 -0
  48. data/lib/lutaml/uml/node/field.rb +34 -0
  49. data/lib/lutaml/uml/node/has_name.rb +15 -0
  50. data/lib/lutaml/uml/node/has_type.rb +15 -0
  51. data/lib/lutaml/uml/node/method.rb +29 -0
  52. data/lib/lutaml/uml/node/method_argument.rb +16 -0
  53. data/lib/lutaml/uml/node/relationship.rb +28 -0
  54. data/lib/lutaml/uml/opaque_behavior.rb +7 -6
  55. data/lib/lutaml/uml/package.rb +16 -13
  56. data/lib/lutaml/uml/parsers/attribute.rb +70 -0
  57. data/lib/lutaml/uml/parsers/dsl.rb +399 -0
  58. data/lib/lutaml/uml/parsers/dsl_preprocessor.rb +44 -0
  59. data/lib/lutaml/uml/parsers/dsl_transform.rb +27 -0
  60. data/lib/lutaml/uml/parsers/yaml.rb +46 -0
  61. data/lib/lutaml/uml/port.rb +6 -4
  62. data/lib/lutaml/uml/primitive_type.rb +11 -3
  63. data/lib/lutaml/uml/property.rb +25 -15
  64. data/lib/lutaml/uml/pseudostate.rb +7 -6
  65. data/lib/lutaml/uml/realization.rb +7 -5
  66. data/lib/lutaml/uml/region.rb +7 -6
  67. data/lib/lutaml/uml/serializers/association.rb +58 -0
  68. data/lib/lutaml/uml/serializers/base.rb +16 -0
  69. data/lib/lutaml/uml/serializers/class.rb +29 -0
  70. data/lib/lutaml/uml/serializers/top_element_attribute.rb +14 -0
  71. data/lib/lutaml/uml/serializers/yaml_view.rb +18 -0
  72. data/lib/lutaml/uml/state.rb +8 -6
  73. data/lib/lutaml/uml/state_machine.rb +7 -5
  74. data/lib/lutaml/uml/top_element.rb +45 -35
  75. data/lib/lutaml/uml/top_element_attribute.rb +26 -0
  76. data/lib/lutaml/uml/transition.rb +8 -6
  77. data/lib/lutaml/uml/trigger.rb +8 -6
  78. data/lib/lutaml/uml/version.rb +3 -1
  79. data/lib/lutaml/uml/vertex.rb +7 -5
  80. data/lutaml-uml.gemspec +11 -3
  81. data/spec/fixtures/datamodel/models/AddressClassProfile.yml +90 -0
  82. data/spec/fixtures/datamodel/models/AddressComponentProfile.yml +63 -0
  83. data/spec/fixtures/datamodel/models/AddressComponentSpecification.yml +15 -0
  84. data/spec/fixtures/datamodel/models/AddressProfile.yml +36 -0
  85. data/spec/fixtures/datamodel/models/AttributeProfile.yml +32 -0
  86. data/spec/fixtures/datamodel/models/InterchangeAddressClassProfile.yml +79 -0
  87. data/spec/fixtures/datamodel/models/Localization copy.yml +23 -0
  88. data/spec/fixtures/datamodel/models/Localization.yml +23 -0
  89. data/spec/fixtures/datamodel/models/ProfileCompliantAddress.yml +36 -0
  90. data/spec/fixtures/datamodel/models/ProfileCompliantAddressComponent.yml +15 -0
  91. data/spec/fixtures/datamodel/models/Signature.yml +20 -0
  92. data/spec/fixtures/datamodel/models/SignatureBlankDefinition.yml +20 -0
  93. data/spec/fixtures/datamodel/models/TextDirectionCode copy.yml +16 -0
  94. data/spec/fixtures/datamodel/models/TextDirectionCode.yml +16 -0
  95. data/spec/fixtures/datamodel/models/Validity.yml +14 -0
  96. data/spec/fixtures/datamodel/models/iso19160-1/Address.yml +22 -0
  97. data/spec/fixtures/datamodel/models/iso19160-1/AddressComponent.yml +2 -0
  98. data/spec/fixtures/datamodel/style.uml.inc +37 -0
  99. data/spec/fixtures/datamodel/views/AddressClassProfile.yml +12 -0
  100. data/spec/fixtures/datamodel/views/AddressProfile.yml +3 -0
  101. data/spec/fixtures/datamodel/views/CommonModels.yml +9 -0
  102. data/spec/fixtures/datamodel/views/TopDown.yml +62 -0
  103. data/spec/fixtures/dsl/diagram.lutaml +3 -0
  104. data/spec/fixtures/dsl/diagram_attributes.lutaml +5 -0
  105. data/spec/fixtures/dsl/diagram_class_assocation.lutaml +29 -0
  106. data/spec/fixtures/dsl/diagram_class_fields.lutaml +19 -0
  107. data/spec/fixtures/dsl/diagram_comments.lutaml +28 -0
  108. data/spec/fixtures/dsl/diagram_concept_model.lutaml +132 -0
  109. data/spec/fixtures/dsl/diagram_data_types.lutaml +24 -0
  110. data/spec/fixtures/dsl/diagram_definitions.lutaml +20 -0
  111. data/spec/fixtures/dsl/diagram_includes.lutaml +6 -0
  112. data/spec/fixtures/dsl/diagram_multiply_classes.lutaml +7 -0
  113. data/spec/fixtures/dsl/shared.lutaml +3 -0
  114. data/spec/fixtures/dsl/shared1.lutaml +4 -0
  115. data/spec/fixtures/generated_dot/AddressClassProfile.dot +170 -0
  116. data/spec/fixtures/generated_dot/AddressProfile.dot +34 -0
  117. data/spec/lutaml/layout/graph_viz_engine_spec.rb +31 -0
  118. data/spec/lutaml/uml/formatter/graphviz_spec.rb +41 -0
  119. data/spec/lutaml/uml/parsers/dsl_spec.rb +276 -0
  120. data/spec/lutaml/uml/parsers/yaml_spec.rb +18 -0
  121. data/spec/lutaml/uml/serializers/yaml_view_spec.rb +20 -0
  122. data/spec/lutaml/uml_spec.rb +2 -4
  123. data/spec/spec_helper.rb +11 -0
  124. metadata +182 -17
@@ -1,28 +1,27 @@
1
1
  # Lutaml::Uml
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/lutaml/uml`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ Lutaml is a language for specifying UML class diagrams and a tool for converting it into various different formats.
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
5
+ ## Install
6
6
 
7
- ## Installation
7
+ ### Bundler: `gem "lutaml-uml"`
8
8
 
9
- Add this line to your application's Gemfile:
9
+ ### RubyGems: `gem install lutaml-uml`
10
10
 
11
- ```ruby
12
- gem 'lutaml-uml'
13
- ```
11
+ ## Executable
14
12
 
15
- And then execute:
13
+ [source,sh]
14
+ --
15
+ # Convert example.lutaml to example.png
16
+ $ lutaml-uml --type png --output . example.lutaml
16
17
 
17
- $ bundle install
18
+ # Display detailed help message
19
+ $ lutaml-uml --help
20
+ --
18
21
 
19
- Or install it yourself as:
22
+ ## Language
20
23
 
21
- $ gem install lutaml-uml
22
-
23
- ## Usage
24
-
25
- TODO: Write usage instructions here
24
+ See link:LUTAML.adoc[LUTAML.adoc]
26
25
 
27
26
  ## Development
28
27
 
@@ -37,4 +36,4 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERN
37
36
 
38
37
  ## Code of Conduct
39
38
 
40
- Everyone interacting in the Lutaml::Uml project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/lutaml-uml/blob/master/CODE_OF_CONDUCT.md).
39
+ Everyone interacting in the Lutaml::Uml project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/lutaml-uml/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bundler/gem_tasks"
2
4
  require "rspec/core/rake_task"
3
5
 
4
6
  RSpec::Core::RakeTask.new(:spec)
5
7
 
6
- task :default => :spec
8
+ task default: :spec
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require "bundler/setup"
4
5
  require "lutaml/uml"
@@ -0,0 +1,6 @@
1
+ script_full_path=$(dirname "$0")
2
+
3
+ for i in $1/*.yml
4
+ do
5
+ $script_full_path/yaml2lutaml $i > "$1/$(basename -s .yml $i).lutaml"
6
+ done
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # Script to convert plantuml files into LutaML syntax
6
+ # Usage: bin/plantuml2lutaml /path/to/plantuml.wsd
7
+
8
+ file_path = ARGV[0]
9
+ FILE_NAME = File.basename(file_path, ".wsd")
10
+ wsd_file = File.new(ARGV[0])
11
+
12
+ def sync_puts(line, level = 0)
13
+ $stdout.puts("#{''.rjust(level)}#{line}")
14
+ $stdout.flush
15
+ end
16
+
17
+ SKIPPED_LINES_REGEXP = /^(@startuml|'\*{7}|note|@enduml|\!|'\/)/
18
+ COMMENT_START = /\/'/
19
+ COMMENT_END = /'\//
20
+ ASSOCIATION_MAPPINGS = {
21
+ /-\|>/ => ",inheritance",
22
+ /<\|-/ => "inheritance,",
23
+ /->/ => ",direct",
24
+ /<-/ => "direct,",
25
+ }.freeze
26
+
27
+ in_comment_block = false
28
+
29
+ def transform_line(line)
30
+ return sync_puts(line, 2) if ASSOCIATION_MAPPINGS.keys.none? { |key| line =~ key }
31
+
32
+ owner_type, member_type = ASSOCIATION_MAPPINGS.detect { |(key, _value)| line =~ key }.last.split(",")
33
+ blocks = line.split(" ")
34
+ owner = blocks.first
35
+ member = blocks.last
36
+ sync_puts("association {", 2)
37
+ sync_puts("owner #{owner}", 4)
38
+ sync_puts("member #{member}", 4)
39
+ sync_puts("owner_type #{owner_type}", 4) if !owner_type.to_s.empty?
40
+ sync_puts("member_type #{member_type}", 4) if !member_type.to_s.empty?
41
+ sync_puts("}", 2)
42
+ end
43
+
44
+ sync_puts("diagram #{FILE_NAME} {")
45
+ wsd_file.readlines.each do |line|
46
+ if line.match?(COMMENT_START)
47
+ in_comment_block = true
48
+ end
49
+
50
+ if line.match?(COMMENT_END)
51
+ in_comment_block = false
52
+ end
53
+
54
+ next if in_comment_block || line =~ SKIPPED_LINES_REGEXP
55
+
56
+ transform_line(line)
57
+ end
58
+ sync_puts("}")
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # Script to convert datamodel yaml files into LutaML syntax
6
+ # Usage: bin/yaml2lutaml /path/to/datamodel/view/file.yml
7
+
8
+ require "yaml"
9
+
10
+ view_yaml = YAML.safe_load(File.read(ARGV[0]))
11
+ models_path = File.expand_path("../../models", ARGV[0])
12
+
13
+ def sync_puts(line, level = 0)
14
+ $stdout.puts("#{''.rjust(level)}#{line}")
15
+ $stdout.flush
16
+ end
17
+
18
+ encountered_relations = Hash.new { |h, key| h[key] = [] }
19
+ # relations:
20
+ # - target: AttributeProfile
21
+ # relationship:
22
+ # source:
23
+ # type: aggregation
24
+ # attribute:
25
+ # addressClassProfile:
26
+ # target:
27
+ # type: direct
28
+ # attribute:
29
+ # attributeProfile:
30
+ # cardinality:
31
+ # min: 0
32
+ # max: '*'
33
+ def process_association(owner, values, encountered_relations)
34
+ target_name = values["target"]
35
+ return if encountered_relations[owner].include?(target_name)
36
+
37
+ encountered_relations[owner].push(target_name)
38
+ sync_puts("association {", 2)
39
+
40
+ relationship_block = values["relationship"] || {}
41
+
42
+ if relationship_block["source"] && relationship_block["source"]["type"]
43
+ source = relationship_block["source"]
44
+ sync_puts("owner_type #{source['type']}", 4)
45
+ if source["attribute"]
46
+ source_attribute_name = source["attribute"].keys.first
47
+ owner += "##{source_attribute_name}"
48
+ if source["attribute"][source_attribute_name] && source["attribute"][source_attribute_name]["cardinality"]
49
+ cardinality = source["attribute"][source_attribute_name]["cardinality"]
50
+ owner += " [#{cardinality['min']}..#{cardinality['max']}]"
51
+ end
52
+ end
53
+ end
54
+ sync_puts("owner #{owner}", 4)
55
+
56
+ member = target_name
57
+ if relationship_block["target"]
58
+ target = relationship_block["target"]
59
+ type = target["type"] || "direct"
60
+ sync_puts("member_type #{type}", 4)
61
+ if target["attribute"]
62
+ target_attribute_name = target["attribute"].keys.first
63
+ member += "##{target_attribute_name}"
64
+ if target["attribute"][target_attribute_name] && target["attribute"][target_attribute_name]["cardinality"]
65
+ cardinality = target["attribute"][target_attribute_name]["cardinality"]
66
+ member += " [#{cardinality['min']}..#{cardinality['max']}]"
67
+ end
68
+ end
69
+ else
70
+ sync_puts("member_type direct", 4)
71
+ end
72
+ sync_puts("member #{member}", 4)
73
+
74
+ sync_puts("}", 2)
75
+ end
76
+ sync_puts("diagram #{File.basename(ARGV[0], 'yml')[0..-2]} {")
77
+ sync_puts("title '#{view_yaml['title']}'", 2)
78
+ sync_puts("caption '#{view_yaml['caption']}'", 2)
79
+
80
+ # Class associations notations
81
+ view_yaml["relations"]&.each do |values|
82
+ process_association(values["source"], values, encountered_relations)
83
+ end
84
+
85
+ view_yaml["imports"].keys.each do |entry|
86
+ import = YAML.safe_load(File.read(File.join(models_path, "#{entry}.yml")))
87
+ import_name = import["name"] || File.basename(entry)
88
+ # Class notation
89
+ sync_puts("#{import['modelType']} #{import_name} {", 2)
90
+ if import["definition"]
91
+ definition = <<~TEXT
92
+ definition
93
+ #{import['definition']}
94
+ end definition
95
+ TEXT
96
+ sync_puts(definition, 4)
97
+ end
98
+ import["values"]&.each_pair do |key, values|
99
+ result_string = key
100
+ if values["definition"]
101
+ result_string += <<~TEXT
102
+ {
103
+ definition
104
+ #{values['definition']}
105
+ end definition
106
+ }
107
+ TEXT
108
+ end
109
+ sync_puts(result_string, 4)
110
+ end
111
+ import["attributes"]&.each_pair do |key, values|
112
+ definition = values["definition"]
113
+ cardinality = if values["cardinality"]
114
+ cardinality_val = values["cardinality"]
115
+ "[#{cardinality_val['min']}..#{cardinality_val['max']}]"
116
+ else
117
+ ""
118
+ end
119
+ result_string = "+#{key}"
120
+ if values["type"]
121
+ result_string += ": #{values['type']}"
122
+ end
123
+ if cardinality
124
+ result_string += " #{cardinality}"
125
+ end
126
+ if definition
127
+ result_string += <<~TEXT
128
+ {
129
+ definition
130
+ #{definition}
131
+ end definition
132
+ }
133
+ TEXT
134
+ end
135
+ sync_puts(result_string, 4)
136
+ end
137
+ sync_puts("}", 2)
138
+
139
+ # Associations notations
140
+ import["relations"]&.each do |values|
141
+ process_association(import_name, values, encountered_relations)
142
+ end
143
+ end
144
+ sync_puts("}")
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  # encoding: UTF-8
3
+ # frozen_string_literal: true
3
4
 
4
5
  # resolve bin path, ignoring symlinks
5
6
  require "pathname"
@@ -15,6 +16,6 @@ class Gem::Specification
15
16
  def this; self; end
16
17
  end
17
18
 
18
- # start up the CLI
19
- require "reeper"
20
- Reeper::Cli.start(ARGV)
19
+ require "lutaml/uml/interface/command_line"
20
+
21
+ Lutaml::Uml::Interface::CommandLine.run
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ class Engine
5
+ attr_accessor :input
6
+
7
+ def initialize(input:)
8
+ @input = input
9
+ end
10
+
11
+ def render(_type)
12
+ raise ArgumentError, "Implement render method"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ruby-graphviz"
4
+ require "lutaml/layout/engine"
5
+
6
+ module Lutaml
7
+ module Layout
8
+ class GraphVizEngine < Engine
9
+ def render(type)
10
+ Open3.popen3("dot -T#{type}") do |stdin, stdout, _stderr, _wait|
11
+ stdin.puts(input)
12
+ stdin.close
13
+ # unless (err = stderr.read).empty? then raise err end
14
+ stdout.read
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,4 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "lutaml/uml/version"
4
+ require "lutaml/uml/interface/command_line"
5
+ require "lutaml/uml/lutaml_path/document_wrapper"
2
6
 
3
7
  module Lutaml
4
8
  module Uml
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ##
2
4
  ## Behaviour metamodel
3
5
  ##
4
- module Lutaml::Uml
5
-
6
- class Abstraction < Dependency
7
- end
8
-
6
+ module Lutaml
7
+ module Uml
8
+ class Abstraction < Dependency
9
+ end
10
+ end
9
11
  end
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ##
2
4
  ## Behaviour metamodel
3
5
  ##
4
- module Lutaml::Uml
5
-
6
- class Activity < Behavior
6
+ module Lutaml
7
+ module Uml
8
+ class Activity < Behavior
9
+ end
10
+ end
7
11
  end
8
-
9
- end
@@ -1,17 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ##
2
4
  ## Behaviour metamodel
3
5
  ##
4
6
 
5
- module Lutaml::Uml
6
-
7
- class Actor < Classifier
8
- def initialize
9
- @name = nil
10
- @xmi_id = nil
11
- @stereotype = []
12
- @generalization = []
13
- @namespace = nil
14
- end
7
+ module Lutaml
8
+ module Uml
9
+ class Actor < Classifier
10
+ def initialize
11
+ @name = nil
12
+ @xmi_id = nil
13
+ @stereotype = []
14
+ @generalization = []
15
+ @namespace = nil
16
+ end
17
+ end
18
+ end
15
19
  end
16
-
17
- end
@@ -1,15 +1,41 @@
1
- module Lutaml::Uml
2
-
3
- class Association < TopElement
4
- attr_accessor :owned_end, :member_end
5
- def initialize
6
- @name = nil
7
- @xmi_id = nil
8
- @xmi_uuid = nil
9
- @owned_end = []
10
- @member_end = []
11
- @namespace = nil
12
- end
13
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Uml
5
+ class Association < TopElement
6
+ include HasMembers
7
+
8
+ attr_accessor :owner_end,
9
+ :owner_end_attribute_name,
10
+ :owner_end_cardinality,
11
+ :owner_end_type,
12
+ :member_end,
13
+ :member_end_attribute_name,
14
+ :member_end_cardinality,
15
+ :member_end_type,
16
+ :static,
17
+ :action
14
18
 
15
- end
19
+ # TODO: move to Parslet::Transform
20
+ def members=(value)
21
+ value.group_by { |member| member.keys.first }
22
+ .each do |(type, group)|
23
+ if %w[owner_end member_end].include?(type)
24
+ group.each do |member|
25
+ member.each_pair do |key, member_value|
26
+ public_send("#{associtaion_type(key)}=", member_value)
27
+ end
28
+ end
29
+ next
30
+ end
31
+ attribute_value = group.map(&:values).flatten
32
+ if attribute_value.length == 1 && !attribute_value.first.is_a?(Hash)
33
+ next public_send("#{associtaion_type(type)}=", attribute_value.first)
34
+ end
35
+
36
+ public_send("#{associtaion_type(type)}=", attribute_value)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end