ddr-models 3.0.0.beta.3 → 3.0.0.beta.4

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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -3
  3. data/config/locales/ddr-models.en.yml +74 -0
  4. data/ddr-models.gemspec +3 -2
  5. data/lib/ddr/auth.rb +5 -2
  6. data/lib/ddr/auth/roles.rb +0 -2
  7. data/lib/ddr/auth/roles/role_set.rb +1 -0
  8. data/lib/ddr/derivatives/generators/png_generator.rb +1 -1
  9. data/lib/ddr/index.rb +6 -2
  10. data/lib/ddr/index/abstract_query_result.rb +1 -1
  11. data/lib/ddr/index/csv_options.rb +14 -0
  12. data/lib/ddr/index/csv_query_result.rb +39 -32
  13. data/lib/ddr/index/field.rb +11 -1
  14. data/lib/ddr/index/field_attribute.rb +22 -0
  15. data/lib/ddr/index/fields.rb +29 -20
  16. data/lib/ddr/index/filter.rb +81 -30
  17. data/lib/ddr/index/filters.rb +15 -10
  18. data/lib/ddr/index/query.rb +34 -13
  19. data/lib/ddr/index/query_builder.rb +150 -30
  20. data/lib/ddr/index/query_clause.rb +64 -19
  21. data/lib/ddr/index/query_params.rb +40 -0
  22. data/lib/ddr/index/solr_csv_options.rb +18 -0
  23. data/lib/ddr/index/sort_order.rb +28 -0
  24. data/lib/ddr/jobs.rb +5 -1
  25. data/lib/ddr/jobs/fits_file_characterization.rb +3 -41
  26. data/lib/ddr/jobs/fixity_check.rb +13 -0
  27. data/lib/ddr/jobs/job.rb +36 -0
  28. data/lib/ddr/jobs/queue.rb +27 -0
  29. data/lib/ddr/jobs/update_index.rb +13 -0
  30. data/lib/ddr/models.rb +20 -3
  31. data/lib/ddr/models/admin_set.rb +7 -3
  32. data/lib/ddr/models/contact.rb +19 -0
  33. data/lib/ddr/models/error.rb +3 -0
  34. data/lib/ddr/models/file_characterization.rb +37 -0
  35. data/lib/ddr/models/finding_aid.rb +35 -2
  36. data/lib/ddr/models/has_admin_metadata.rb +5 -1
  37. data/lib/ddr/models/has_content.rb +4 -2
  38. data/lib/ddr/models/indexing.rb +7 -0
  39. data/lib/ddr/models/licenses/license.rb +7 -3
  40. data/lib/ddr/models/solr_document.rb +2 -2
  41. data/lib/ddr/models/version.rb +1 -1
  42. data/lib/ddr/models/with_content_file.rb +37 -0
  43. data/lib/ddr/vocab/asset.rb +3 -0
  44. data/spec/derivatives/png_generator_spec.rb +8 -1
  45. data/spec/fixtures/arrow1rightred_e0.gif +0 -0
  46. data/spec/index/fields_spec.rb +197 -0
  47. data/spec/index/filter_spec.rb +155 -30
  48. data/spec/index/query_builder_spec.rb +116 -0
  49. data/spec/index/query_clause_spec.rb +58 -0
  50. data/spec/index/query_spec.rb +39 -10
  51. data/spec/jobs/fits_file_characterization_spec.rb +7 -43
  52. data/spec/jobs/fixity_check_spec.rb +22 -0
  53. data/spec/jobs/job_spec.rb +40 -0
  54. data/spec/jobs/update_index_spec.rb +22 -0
  55. data/spec/managers/derivatives_manager_spec.rb +15 -11
  56. data/spec/models/admin_set_spec.rb +28 -10
  57. data/spec/models/contact_spec.rb +42 -0
  58. data/spec/models/effective_license_spec.rb +17 -7
  59. data/spec/models/file_characterization_spec.rb +38 -0
  60. data/spec/models/finding_aid_spec.rb +31 -8
  61. data/spec/models/has_admin_metadata_spec.rb +8 -5
  62. data/spec/models/indexing_spec.rb +2 -0
  63. data/spec/models/license_spec.rb +31 -10
  64. data/spec/models/solr_document_spec.rb +23 -3
  65. data/spec/models/with_content_file_spec.rb +32 -0
  66. data/spec/spec_helper.rb +2 -0
  67. metadata +66 -28
  68. data/lib/ddr/contacts.rb +0 -25
  69. data/lib/ddr/index/query_value.rb +0 -18
  70. data/lib/ddr/models/access_controllable.rb +0 -12
  71. data/spec/auth/ldap_gateway_spec.rb +0 -9
  72. data/spec/contacts/contacts_spec.rb +0 -26
  73. data/spec/index/filters_spec.rb +0 -17
@@ -1,12 +1,16 @@
1
- require "ddr_aux/client"
1
+ require "active_resource"
2
2
 
3
3
  module Ddr::Models
4
- class AdminSet < DdrAux::Client::AdminSet
4
+ class AdminSet < ActiveResource::Base
5
+
6
+ self.site = ENV["DDR_AUX_API_URL"]
5
7
 
6
8
  def self.call(obj)
7
9
  if obj.admin_set
8
- find(code: obj.admin_set)
10
+ new get(:find, code: obj.admin_set)
9
11
  end
12
+ rescue ActiveResource::ResourceNotFound => e
13
+ raise Ddr::Models::NotFoundError, e
10
14
  end
11
15
 
12
16
  def to_s
@@ -0,0 +1,19 @@
1
+ require "active_resource"
2
+
3
+ module Ddr::Models
4
+ class Contact < ActiveResource::Base
5
+
6
+ self.site = ENV["DDR_AUX_API_URL"]
7
+
8
+ def self.call(slug)
9
+ new get(:find, slug: slug)
10
+ rescue ActiveResource::ResourceNotFound => e
11
+ raise Ddr::Models::NotFoundError, e
12
+ end
13
+
14
+ def to_s
15
+ name
16
+ end
17
+
18
+ end
19
+ end
@@ -11,5 +11,8 @@ module Ddr
11
11
 
12
12
  # Content model casting error
13
13
  class ContentModelError < Error; end
14
+
15
+ # A model instance was not found
16
+ class NotFoundError < Error; end
14
17
  end
15
18
  end
@@ -0,0 +1,37 @@
1
+ require "delegate"
2
+ require "shellwords"
3
+
4
+ module Ddr::Models
5
+ class FileCharacterization < SimpleDelegator
6
+
7
+ class FITSError < Error; end
8
+
9
+ def self.call(obj)
10
+ new(obj).call
11
+ end
12
+
13
+ def call
14
+ with_content_file do |path|
15
+ fits_output = run_fits(path)
16
+ reload
17
+ fits.content = fits_output
18
+ save!
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def run_fits(path)
25
+ output = `#{fits_command} -i #{Shellwords.escape(path)}`
26
+ unless $?.success?
27
+ raise FITSError, output
28
+ end
29
+ output
30
+ end
31
+
32
+ def fits_command
33
+ File.join(Ddr::Models.fits_home, 'fits.sh')
34
+ end
35
+
36
+ end
37
+ end
@@ -1,22 +1,55 @@
1
+ require "nokogiri"
2
+
1
3
  module Ddr::Models
2
4
  class FindingAid
3
5
 
4
6
  attr_reader :ead_id
5
7
 
8
+ EAD_XMLNS = "urn:isbn:1-931666-22-9"
9
+
6
10
  def initialize(ead_id)
7
11
  @ead_id = ead_id
8
12
  end
9
13
 
10
14
  def url
11
- @url ||= doc.css("eadid").attr("url").text
15
+ doc.css("eadid").attr("url").text
12
16
  end
13
17
 
18
+ # The finding aid title
14
19
  def title
15
- @title ||= doc.css("titleproper").children.first.text.strip
20
+ doc.css("titleproper").children.first.text.strip
21
+ end
22
+
23
+ def repository
24
+ collection.xpath('ead:did/ead:repository/ead:corpname', ead: EAD_XMLNS).text
25
+ end
26
+
27
+ def collection_date_span
28
+ collection.xpath('ead:did/ead:unitdate[@type="inclusive"]', ead: EAD_XMLNS).text
29
+ end
30
+
31
+ def collection_number
32
+ collection.xpath('ead:did/ead:unitid', ead: EAD_XMLNS).text
33
+ end
34
+
35
+ def collection_title
36
+ collection.xpath('ead:did/ead:unittitle', ead: EAD_XMLNS).text
37
+ end
38
+
39
+ def extent
40
+ collection.xpath('ead:did/ead:physdesc/ead:extent', ead: EAD_XMLNS).map(&:text).join("; ")
41
+ end
42
+
43
+ def abstract
44
+ collection.xpath('ead:did/ead:abstract', ead: EAD_XMLNS).text
16
45
  end
17
46
 
18
47
  private
19
48
 
49
+ def collection
50
+ doc.xpath('//ead:archdesc[@level="collection"]', ead: EAD_XMLNS)
51
+ end
52
+
20
53
  # @raise [OpenURI::HTTPError] if 404, etc.
21
54
  def doc
22
55
  @doc ||= Nokogiri::XML(open(ead_xml_url))
@@ -14,6 +14,10 @@ module Ddr::Models
14
14
  predicate: Ddr::Vocab::Asset.adminSet,
15
15
  multiple: false
16
16
 
17
+ property :aspace_id,
18
+ predicate: Ddr::Vocab::Asset.archivesSpaceId,
19
+ multiple: false
20
+
17
21
  property :depositor,
18
22
  predicate: RDF::Vocab::MARCRelators.dpt,
19
23
  multiple: false
@@ -98,7 +102,7 @@ module Ddr::Models
98
102
  end
99
103
 
100
104
  def research_help
101
- Ddr::Contacts.get(research_help_contact) if research_help_contact
105
+ Ddr::Models::Contact.call(research_help_contact) if research_help_contact
102
106
  end
103
107
 
104
108
  def effective_license
@@ -1,5 +1,3 @@
1
- require 'openssl'
2
-
3
1
  module Ddr
4
2
  module Models
5
3
  module HasContent
@@ -107,6 +105,10 @@ module Ddr
107
105
  !extractedText.empty?
108
106
  end
109
107
 
108
+ def with_content_file(&block)
109
+ WithContentFile.new(self, &block)
110
+ end
111
+
110
112
  protected
111
113
 
112
114
  def update_derivatives
@@ -4,6 +4,10 @@ module Ddr
4
4
 
5
5
  include Ddr::Index::Fields
6
6
 
7
+ def self.const_missing(name)
8
+ Ddr::Index::Fields.const_missing(name)
9
+ end
10
+
7
11
  def to_solr(solr_doc=Hash.new, opts={})
8
12
  solr_doc = super(solr_doc, opts)
9
13
  solr_doc.merge index_fields
@@ -13,8 +17,10 @@ module Ddr
13
17
  fields = {
14
18
  ACCESS_ROLE => roles.to_json,
15
19
  ADMIN_SET => admin_set,
20
+ ASPACE_ID => aspace_id,
16
21
  ATTACHED_FILES => attached_files_profile.to_json,
17
22
  BOX_NUMBER_FACET => desc_metadata_values('box_number'),
23
+ CONTRIBUTOR_FACET => desc_metadata_values('contributor'),
18
24
  CREATOR_FACET => descMetadata.creator,
19
25
  DATE_FACET => descMetadata.date,
20
26
  DATE_SORT => date_sort,
@@ -34,6 +40,7 @@ module Ddr
34
40
  RESOURCE_ROLE => roles.in_resource_scope.agents,
35
41
  SERIES_FACET => desc_metadata_values('series'),
36
42
  SPATIAL_FACET => desc_metadata_values('spatial'),
43
+ SUBJECT_FACET => desc_metadata_values('subject'),
37
44
  TITLE => title_display,
38
45
  TYPE_FACET => descMetadata.type,
39
46
  WORKFLOW_STATE => workflow_state,
@@ -1,17 +1,21 @@
1
- require "ddr_aux/client"
1
+ require "active_resource"
2
2
 
3
3
  module Ddr::Models
4
- class License < DdrAux::Client::License
4
+ class License < ActiveResource::Base
5
5
  extend Deprecation
6
6
 
7
+ self.site = ENV["DDR_AUX_API_URL"]
8
+
7
9
  attr_accessor :object_id
8
10
 
9
11
  def self.call(obj)
10
12
  if obj.license
11
- license = find(url: obj.license)
13
+ license = new get(:find, url: obj.license)
12
14
  license.object_id = obj.id
13
15
  license
14
16
  end
17
+ rescue ActiveResource::ResourceNotFound => e
18
+ raise Ddr::Models::NotFoundError, e
15
19
  end
16
20
 
17
21
  def pid
@@ -90,7 +90,7 @@ module Ddr::Models
90
90
  end
91
91
 
92
92
  def has_datastream?(dsID)
93
- datastreams[dsID].present?
93
+ datastreams.key?(dsID) && datastreams[dsID]["size"].present?
94
94
  end
95
95
 
96
96
  def has_admin_policy?
@@ -200,7 +200,7 @@ module Ddr::Models
200
200
 
201
201
  def research_help
202
202
  research_help_contact = self[Ddr::Index::Fields::RESEARCH_HELP_CONTACT] || inherited_research_help_contact
203
- Ddr::Contacts.get(research_help_contact) if research_help_contact
203
+ Ddr::Models::Contact.call(research_help_contact) if research_help_contact
204
204
  end
205
205
 
206
206
  def parent_uri
@@ -1,5 +1,5 @@
1
1
  module Ddr
2
2
  module Models
3
- VERSION = "3.0.0.beta.3"
3
+ VERSION = "3.0.0.beta.4"
4
4
  end
5
5
  end
@@ -0,0 +1,37 @@
1
+ require "tempfile"
2
+ require "delegate"
3
+
4
+ module Ddr::Models
5
+ class WithContentFile < SimpleDelegator
6
+
7
+ def initialize(obj, &block)
8
+ super(obj)
9
+ with_temp_file &block
10
+ end
11
+
12
+ # Yields path of tempfile containing content to block
13
+ # @yield [String] the path to the tempfile containing content
14
+ def with_temp_file
15
+ filename = original_filename || content.default_file_name
16
+ basename = [ File.basename(filename, ".*"), File.extname(filename) ]
17
+ infile = Tempfile.open(basename, Ddr::Models.tempdir, encoding: 'ascii-8bit')
18
+ begin
19
+ infile.write(content.content)
20
+ infile.close
21
+ verify_checksum!(infile)
22
+ yield infile.path
23
+ ensure
24
+ infile.close unless infile.closed?
25
+ File.unlink(infile)
26
+ end
27
+ end
28
+
29
+ def verify_checksum!(file)
30
+ digest = Ddr::Utils.digest(File.read(file), content.checksum.algorithm)
31
+ if digest != content.checksum.value
32
+ raise ChecksumInvalid, "The checksum of the downloaded file does not match the stored checksum of the content."
33
+ end
34
+ end
35
+
36
+ end
37
+ end
@@ -18,6 +18,9 @@
18
18
  property "eadId",
19
19
  label: "EAD ID"
20
20
 
21
+ property "archivesSpaceId",
22
+ label: "ArchivesSpace Identifier"
23
+
21
24
  property "multiresImageFilePath",
22
25
  label: "Multires Image File Path"
23
26
 
@@ -16,7 +16,14 @@ module Ddr::Derivatives
16
16
  expect(File.size(output_path)).to be > 0
17
17
  end
18
18
  end
19
+ context "animated gif source" do
20
+ let(:source_path) { File.join(Ddr::Models::Engine.root, "spec", "fixtures", "arrow1rightred_e0.gif") }
21
+ it "should generate a non-empty file" do
22
+ generator.generate(source_path, output_path)
23
+ expect(File.size(output_path)).to be > 0
24
+ end
25
+ end
19
26
  end
20
27
 
21
28
  end
22
- end
29
+ end
Binary file
@@ -0,0 +1,197 @@
1
+ module Ddr::Index
2
+ RSpec.describe Fields do
3
+
4
+ describe "module methods" do
5
+ specify {
6
+ expect(Fields.techmd).to contain_exactly(
7
+ Fields::TECHMD_COLOR_SPACE,
8
+ Fields::TECHMD_CREATING_APPLICATION,
9
+ Fields::TECHMD_CREATION_TIME,
10
+ Fields::TECHMD_FILE_SIZE,
11
+ Fields::TECHMD_FITS_VERSION,
12
+ Fields::TECHMD_FITS_DATETIME,
13
+ Fields::TECHMD_FORMAT_LABEL,
14
+ Fields::TECHMD_FORMAT_VERSION,
15
+ Fields::TECHMD_IMAGE_HEIGHT,
16
+ Fields::TECHMD_IMAGE_WIDTH,
17
+ Fields::TECHMD_MEDIA_TYPE,
18
+ Fields::TECHMD_MODIFICATION_TIME,
19
+ Fields::TECHMD_PRONOM_IDENTIFIER,
20
+ Fields::TECHMD_VALID,
21
+ Fields::TECHMD_WELL_FORMED
22
+ )
23
+ }
24
+ end
25
+
26
+ describe "constants" do
27
+ describe "ID" do
28
+ subject { Fields::ID }
29
+ its(:label) { is_expected.to eq "Fedora PID" }
30
+ its(:heading) { is_expected.to eq "pid" }
31
+ end
32
+
33
+ describe "PID" do
34
+ subject { Fields::PID }
35
+ its(:label) { is_expected.to eq "Fedora PID" }
36
+ its(:heading) { is_expected.to eq "pid" }
37
+ end
38
+
39
+ describe "ACCESS_ROLE" do
40
+ subject { Fields::ACCESS_ROLE }
41
+ its(:label) { is_expected.to eq "Access Role" }
42
+ its(:heading) { is_expected.to eq "access_role" }
43
+ end
44
+
45
+ describe "ACTIVE_FEDORA_MODEL" do
46
+ subject { Fields::ACTIVE_FEDORA_MODEL }
47
+ its(:label) { is_expected.to eq "Model" }
48
+ its(:heading) { is_expected.to eq "model" }
49
+ end
50
+
51
+ describe "ADMIN_SET" do
52
+ subject { Fields::ADMIN_SET }
53
+ its(:label) { is_expected.to eq "Admin Set" }
54
+ its(:heading) { is_expected.to eq "admin_set" }
55
+ end
56
+
57
+ describe "ASPACE_ID" do
58
+ subject { Fields::ASPACE_ID }
59
+ its(:label) { is_expected.to eq "ArchivesSpace ID" }
60
+ its(:heading) { is_expected.to eq "aspace_id" }
61
+ end
62
+
63
+ describe "DOI" do
64
+ subject { Fields::DOI }
65
+ its(:label) { is_expected.to eq "DOI" }
66
+ its(:heading) { is_expected.to eq "doi" }
67
+ end
68
+
69
+ describe "EAD_ID" do
70
+ subject { Fields::EAD_ID }
71
+ its(:label) { is_expected.to eq "EAD ID" }
72
+ its(:heading) { is_expected.to eq "ead_id" }
73
+ end
74
+
75
+ describe "LOCAL_ID" do
76
+ subject { Fields::LOCAL_ID }
77
+ its(:label) { is_expected.to eq "Local ID" }
78
+ its(:heading) { is_expected.to eq "local_id" }
79
+ end
80
+
81
+ describe "OBJECT_CREATE_DATE" do
82
+ subject { Fields::OBJECT_CREATE_DATE }
83
+ its(:label) { is_expected.to eq "Creation Date" }
84
+ its(:heading) { is_expected.to eq "creation_date" }
85
+ end
86
+
87
+ describe "OBJECT_MODIFIED_DATE" do
88
+ subject { Fields::OBJECT_MODIFIED_DATE }
89
+ its(:label) { is_expected.to eq "Modification Date" }
90
+ its(:heading) { is_expected.to eq "modification_date" }
91
+ end
92
+
93
+ describe "PERMANENT_ID" do
94
+ subject { Fields::PERMANENT_ID }
95
+ its(:label) { is_expected.to eq "Permanent ID" }
96
+ its(:heading) { is_expected.to eq "permanent_id" }
97
+ end
98
+
99
+ describe "PERMANENT_URL" do
100
+ subject { Fields::PERMANENT_URL }
101
+ its(:label) { is_expected.to eq "Permanent URL" }
102
+ its(:heading) { is_expected.to eq "permanent_url" }
103
+ end
104
+
105
+ describe "TECHMD_COLOR_SPACE" do
106
+ subject { Fields::TECHMD_COLOR_SPACE }
107
+ its(:label) { is_expected.to eq "Color Space" }
108
+ its(:heading) { is_expected.to eq "color_space" }
109
+ end
110
+
111
+ describe "TECHMD_CREATING_APPLICATION" do
112
+ subject { Fields::TECHMD_CREATING_APPLICATION }
113
+ its(:label) { is_expected.to eq "Creating Application" }
114
+ its(:heading) { is_expected.to eq "creating_application" }
115
+ end
116
+
117
+ describe "TECHMD_CREATION_TIME" do
118
+ subject { Fields::TECHMD_CREATION_TIME }
119
+ its(:label) { is_expected.to eq "Creation Time" }
120
+ its(:heading) { is_expected.to eq "creation_time" }
121
+ end
122
+
123
+ describe "TECHMD_FILE_SIZE" do
124
+ subject { Fields::TECHMD_FILE_SIZE }
125
+ its(:label) { is_expected.to eq "File Size" }
126
+ its(:heading) { is_expected.to eq "file_size" }
127
+ end
128
+
129
+ describe "TECHMD_FITS_VERSION" do
130
+ subject { Fields::TECHMD_FITS_VERSION }
131
+ its(:label) { is_expected.to eq "FITS Version" }
132
+ its(:heading) { is_expected.to eq "fits_version" }
133
+ end
134
+
135
+ describe "TECHMD_FITS_DATETIME" do
136
+ subject { Fields::TECHMD_FITS_DATETIME }
137
+ its(:label) { is_expected.to eq "FITS Run At" }
138
+ its(:heading) { is_expected.to eq "fits_datetime" }
139
+ end
140
+
141
+ describe "TECHMD_FORMAT_LABEL" do
142
+ subject { Fields::TECHMD_FORMAT_LABEL }
143
+ its(:label) { is_expected.to eq "Format Label" }
144
+ its(:heading) { is_expected.to eq "format_label" }
145
+ end
146
+
147
+ describe "TECHMD_FORMAT_VERSION" do
148
+ subject { Fields::TECHMD_FORMAT_VERSION }
149
+ its(:label) { is_expected.to eq "Format Version" }
150
+ its(:heading) { is_expected.to eq "format_version" }
151
+ end
152
+
153
+ describe "TECHMD_IMAGE_HEIGHT" do
154
+ subject { Fields::TECHMD_IMAGE_HEIGHT }
155
+ its(:label) { is_expected.to eq "Image Height" }
156
+ its(:heading) { is_expected.to eq "image_height" }
157
+ end
158
+
159
+ describe "TECHMD_IMAGE_WIDTH" do
160
+ subject { Fields::TECHMD_IMAGE_WIDTH }
161
+ its(:label) { is_expected.to eq "Image Width" }
162
+ its(:heading) { is_expected.to eq "image_width" }
163
+ end
164
+
165
+ describe "TECHMD_MEDIA_TYPE" do
166
+ subject { Fields::TECHMD_MEDIA_TYPE }
167
+ its(:label) { is_expected.to eq "Media Type" }
168
+ its(:heading) { is_expected.to eq "media_type" }
169
+ end
170
+
171
+ describe "TECHMD_MODIFICATION_TIME" do
172
+ subject { Fields::TECHMD_MODIFICATION_TIME }
173
+ its(:label) { is_expected.to eq "Modification Time" }
174
+ its(:heading) { is_expected.to eq "modification_time" }
175
+ end
176
+
177
+ describe "TECHMD_PRONOM_IDENTIFIER" do
178
+ subject { Fields::TECHMD_PRONOM_IDENTIFIER }
179
+ its(:label) { is_expected.to eq "PRONOM Unique ID" }
180
+ its(:heading) { is_expected.to eq "pronom_uid" }
181
+ end
182
+
183
+ describe "TECHMD_VALID" do
184
+ subject { Fields::TECHMD_VALID }
185
+ its(:label) { is_expected.to eq "Valid" }
186
+ its(:heading) { is_expected.to eq "valid" }
187
+ end
188
+
189
+ describe "TECHMD_WELL_FORMED" do
190
+ subject { Fields::TECHMD_WELL_FORMED }
191
+ its(:label) { is_expected.to eq "Well-formed" }
192
+ its(:heading) { is_expected.to eq "well_formed" }
193
+ end
194
+ end
195
+
196
+ end
197
+ end