prosereflect 0.1.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 (75) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/rake.yml +15 -0
  3. data/.github/workflows/release.yml +25 -0
  4. data/.gitignore +12 -0
  5. data/.rubocop.yml +1 -0
  6. data/.rubocop_todo.yml +228 -0
  7. data/CODE_OF_CONDUCT.md +132 -0
  8. data/Gemfile +10 -0
  9. data/README.adoc +268 -0
  10. data/Rakefile +12 -0
  11. data/debug_loading.rb +34 -0
  12. data/lib/prosereflect/document.rb +63 -0
  13. data/lib/prosereflect/hard_break.rb +22 -0
  14. data/lib/prosereflect/node.rb +77 -0
  15. data/lib/prosereflect/paragraph.rb +50 -0
  16. data/lib/prosereflect/parser.rb +56 -0
  17. data/lib/prosereflect/table.rb +64 -0
  18. data/lib/prosereflect/table_cell.rb +37 -0
  19. data/lib/prosereflect/table_row.rb +32 -0
  20. data/lib/prosereflect/text.rb +37 -0
  21. data/lib/prosereflect/version.rb +5 -0
  22. data/lib/prosereflect.rb +17 -0
  23. data/prosereflect.gemspec +33 -0
  24. data/sig/prosemirror.rbs +4 -0
  25. data/spec/fixtures/ituob-1000/ituob-1000-DP.json +366 -0
  26. data/spec/fixtures/ituob-1000/ituob-1000-DP.yaml +182 -0
  27. data/spec/fixtures/ituob-1000/ituob-1000-E118_IIN.json +381 -0
  28. data/spec/fixtures/ituob-1000/ituob-1000-E118_IIN.yaml +182 -0
  29. data/spec/fixtures/ituob-1000/ituob-1000-E164_ACN.json +203 -0
  30. data/spec/fixtures/ituob-1000/ituob-1000-E164_ACN.yaml +98 -0
  31. data/spec/fixtures/ituob-1000/ituob-1000-E212_MNC.json +202 -0
  32. data/spec/fixtures/ituob-1000/ituob-1000-E212_MNC.yaml +101 -0
  33. data/spec/fixtures/ituob-1000/ituob-1000-F32_TDI.json +6948 -0
  34. data/spec/fixtures/ituob-1000/ituob-1000-F32_TDI.yaml +3519 -0
  35. data/spec/fixtures/ituob-1000/ituob-1000-M1400_ICC.json +529 -0
  36. data/spec/fixtures/ituob-1000/ituob-1000-M1400_ICC.yaml +263 -0
  37. data/spec/fixtures/ituob-1000/ituob-1000-NNP.json +288 -0
  38. data/spec/fixtures/ituob-1000/ituob-1000-NNP.yaml +152 -0
  39. data/spec/fixtures/ituob-1000/ituob-1000-Q708_ISPC.json +1534 -0
  40. data/spec/fixtures/ituob-1000/ituob-1000-Q708_ISPC.yaml +789 -0
  41. data/spec/fixtures/ituob-1000/ituob-1000-Q708_SANC.json +252 -0
  42. data/spec/fixtures/ituob-1000/ituob-1000-Q708_SANC.yaml +123 -0
  43. data/spec/fixtures/ituob-1000/ituob-1000-R_SP_LM.V.json +428 -0
  44. data/spec/fixtures/ituob-1000/ituob-1000-R_SP_LM.V.yaml +208 -0
  45. data/spec/fixtures/ituob-1000/ituob-1000-T35_NA.json +621 -0
  46. data/spec/fixtures/ituob-1000/ituob-1000-T35_NA.yaml +317 -0
  47. data/spec/fixtures/ituob-1001/ituob-1001-DP.json +532 -0
  48. data/spec/fixtures/ituob-1001/ituob-1001-DP.yaml +266 -0
  49. data/spec/fixtures/ituob-1001/ituob-1001-E118_IIN.json +1093 -0
  50. data/spec/fixtures/ituob-1001/ituob-1001-E118_IIN.yaml +519 -0
  51. data/spec/fixtures/ituob-1001/ituob-1001-E164_ACN.json +449 -0
  52. data/spec/fixtures/ituob-1001/ituob-1001-E164_ACN.yaml +214 -0
  53. data/spec/fixtures/ituob-1001/ituob-1001-E164_CC.json +271 -0
  54. data/spec/fixtures/ituob-1001/ituob-1001-E164_CC.yaml +136 -0
  55. data/spec/fixtures/ituob-1001/ituob-1001-E212_MNC.json +199 -0
  56. data/spec/fixtures/ituob-1001/ituob-1001-E212_MNC.yaml +99 -0
  57. data/spec/fixtures/ituob-1001/ituob-1001-NNP.json +288 -0
  58. data/spec/fixtures/ituob-1001/ituob-1001-NNP.yaml +152 -0
  59. data/spec/fixtures/ituob-1001/ituob-1001-Q708_ISPC.json +960 -0
  60. data/spec/fixtures/ituob-1001/ituob-1001-Q708_ISPC.yaml +487 -0
  61. data/spec/prosereflect/document_spec.rb +149 -0
  62. data/spec/prosereflect/hard_break_spec.rb +51 -0
  63. data/spec/prosereflect/node_spec.rb +256 -0
  64. data/spec/prosereflect/paragraph_spec.rb +152 -0
  65. data/spec/prosereflect/parser_spec.rb +129 -0
  66. data/spec/prosereflect/table_cell_spec.rb +114 -0
  67. data/spec/prosereflect/table_row_spec.rb +77 -0
  68. data/spec/prosereflect/table_spec.rb +144 -0
  69. data/spec/prosereflect/text_spec.rb +116 -0
  70. data/spec/prosereflect/version_spec.rb +11 -0
  71. data/spec/prosereflect_spec.rb +57 -0
  72. data/spec/spec_helper.rb +21 -0
  73. data/spec/support/matchers.rb +131 -0
  74. data/spec/support/shared_examples.rb +220 -0
  75. metadata +133 -0
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require 'rubocop/rake_task'
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
data/debug_loading.rb ADDED
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ puts 'Debug script to check class loading'
5
+
6
+ begin
7
+ puts '1. Adding lib to path'
8
+ $LOAD_PATH.unshift File.expand_path('lib', __dir__)
9
+ puts "Load path: #{$LOAD_PATH.inspect}"
10
+
11
+ puts "\n2. Requiring prosereflect"
12
+ require 'prosereflect'
13
+ puts 'Successfully required prosereflect'
14
+
15
+ puts "\n3. Checking Prosereflect module"
16
+ puts "Prosereflect defined? #{!defined?(Prosereflect).nil?}"
17
+ puts "Prosereflect is a #{Prosereflect.class}"
18
+
19
+ puts "\n4. Checking individual classes"
20
+ classes = %w[Node Document Paragraph Text HardBreak Table TableRow TableCell Parser]
21
+ classes.each do |klass|
22
+ full_class = "Prosereflect::#{klass}"
23
+ is_defined = begin
24
+ !defined?(Object.const_get(full_class)).nil?
25
+ rescue StandardError
26
+ false
27
+ end
28
+ puts "#{full_class} defined? #{is_defined}"
29
+ puts " #{full_class} is a #{Object.const_get(full_class).class}" if is_defined
30
+ end
31
+ rescue StandardError => e
32
+ puts "ERROR: #{e.class}: #{e.message}"
33
+ puts e.backtrace
34
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'node'
4
+
5
+ module Prosereflect
6
+ # Document class represents a ProseMirror document.
7
+ class Document < Node
8
+ def initialize(data = {})
9
+ super(data)
10
+ @type = 'doc'
11
+ end
12
+
13
+ def tables
14
+ find_children('table')
15
+ end
16
+
17
+ def paragraphs
18
+ find_children('paragraph')
19
+ end
20
+
21
+ def first_paragraph
22
+ find_first('paragraph')
23
+ end
24
+
25
+ def first_table
26
+ find_first('table')
27
+ end
28
+
29
+ # Create a new empty document
30
+ def self.create(attrs = nil)
31
+ doc = new({ 'type' => 'doc', 'content' => [] })
32
+ doc.instance_variable_set(:@attrs, attrs) if attrs
33
+ doc
34
+ end
35
+
36
+ # Add a paragraph with text to the document
37
+ def add_paragraph(text = nil, attrs = nil)
38
+ paragraph = Paragraph.create(attrs)
39
+
40
+ paragraph.add_text(text) if text
41
+
42
+ add_child(paragraph)
43
+ paragraph
44
+ end
45
+
46
+ # Add a table to the document
47
+ def add_table(attrs = nil)
48
+ table = Table.create(attrs)
49
+ add_child(table)
50
+ table
51
+ end
52
+
53
+ # Convert document to JSON
54
+ def to_json(*_args)
55
+ JSON.generate(to_h)
56
+ end
57
+
58
+ # Convert document to YAML
59
+ def to_yaml
60
+ to_h.to_yaml
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'node'
4
+
5
+ module Prosereflect
6
+ class HardBreak < Node
7
+ def text_content
8
+ "\n"
9
+ end
10
+
11
+ def text_content_with_breaks
12
+ "\n"
13
+ end
14
+
15
+ # Create a new hard break
16
+ def self.create(marks = nil)
17
+ node = new({ 'type' => 'hard_break' })
18
+ node.instance_variable_set(:@marks, marks) if marks
19
+ node
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prosereflect
4
+ class Node
5
+ attr_reader :type, :attrs, :marks
6
+ attr_accessor :content
7
+
8
+ def initialize(data = {})
9
+ @type = data['type']
10
+ @attrs = data['attrs']
11
+ @content = parse_content(data['content'])
12
+ @marks = data['marks']
13
+ end
14
+
15
+ def parse_content(content_data)
16
+ return [] unless content_data
17
+
18
+ content_data.map { |item| Parser.parse(item) }
19
+ end
20
+
21
+ # Create a serializable hash representation of this node
22
+ def to_h
23
+ result = { 'type' => @type }
24
+ result['attrs'] = @attrs if @attrs
25
+ result['marks'] = @marks if @marks
26
+ result['content'] = @content.map(&:to_h) unless @content.empty?
27
+ result
28
+ end
29
+
30
+ # Add a child node to this node's content
31
+ def add_child(node)
32
+ @content ||= []
33
+ @content << node
34
+ node
35
+ end
36
+
37
+ def find_first(node_type)
38
+ return self if type == node_type
39
+
40
+ content.each do |child|
41
+ result = child.find_first(node_type)
42
+ return result if result
43
+ end
44
+ nil
45
+ end
46
+
47
+ # Create a node of the specified type with optional attributes
48
+ def self.create(node_type, attrs = nil)
49
+ node = new({ 'type' => node_type })
50
+ node.instance_variable_set(:@attrs, attrs) if attrs
51
+ node.instance_variable_set(:@content, [])
52
+ node
53
+ end
54
+
55
+ def find_all(node_type)
56
+ results = []
57
+ results << self if type == node_type
58
+ content.each do |child|
59
+ results.concat(child.find_all(node_type))
60
+ end
61
+ results
62
+ end
63
+
64
+ def find_children(node_type)
65
+ content.select { |child| child.type == node_type }
66
+ end
67
+
68
+ def text_content
69
+ content.map(&:text_content).join
70
+ end
71
+
72
+ # Hard breaks should add a newline in text content
73
+ def text_content_with_breaks
74
+ content.map(&:text_content_with_breaks).join
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'node'
4
+ require_relative 'text'
5
+ require_relative 'hard_break'
6
+
7
+ module Prosereflect
8
+ class Paragraph < Node
9
+ def text_nodes
10
+ content.select { |node| node.type == 'text' }
11
+ end
12
+
13
+ def text_content
14
+ result = ''
15
+ content.each do |node|
16
+ result += if node.type == 'text'
17
+ node.text_content
18
+ elsif node.type == 'hard_break'
19
+ "\n"
20
+ else
21
+ node.text_content
22
+ end
23
+ end
24
+ result
25
+ end
26
+
27
+ # Create a new paragraph
28
+ def self.create(attrs = nil)
29
+ para = new({ 'type' => 'paragraph', 'content' => [] })
30
+ para.instance_variable_set(:@attrs, attrs) if attrs
31
+ para
32
+ end
33
+
34
+ # Add text to the paragraph
35
+ def add_text(text, marks = nil)
36
+ return if text.nil? || text.empty?
37
+
38
+ text_node = Text.create(text, marks)
39
+ add_child(text_node)
40
+ text_node
41
+ end
42
+
43
+ # Add a hard break to the paragraph
44
+ def add_hard_break(marks = nil)
45
+ hard_break = HardBreak.create(marks)
46
+ add_child(hard_break)
47
+ hard_break
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'node'
4
+ require_relative 'text'
5
+ require_relative 'paragraph'
6
+ require_relative 'table'
7
+ require_relative 'table_row'
8
+ require_relative 'table_cell'
9
+ require_relative 'hard_break'
10
+ require_relative 'document'
11
+
12
+ module Prosereflect
13
+ class Parser
14
+ def self.parse(data)
15
+ return nil unless data.is_a?(Hash)
16
+
17
+ parse_node(data)
18
+ end
19
+
20
+ def self.parse_node(data)
21
+ return nil unless data.is_a?(Hash)
22
+
23
+ case data['type']
24
+ when 'text'
25
+ Text.new(data)
26
+ when 'paragraph'
27
+ Paragraph.new(data)
28
+ when 'table'
29
+ Table.new(data)
30
+ when 'table_row'
31
+ TableRow.new(data)
32
+ when 'table_cell'
33
+ TableCell.new(data)
34
+ when 'hard_break'
35
+ HardBreak.new(data)
36
+ when 'doc', 'document'
37
+ Document.new(data)
38
+ else
39
+ Node.new(data)
40
+ end
41
+ end
42
+
43
+ def self.parse_document(data)
44
+ raise ArgumentError, 'Input must be a hash' if data.nil?
45
+ raise ArgumentError, 'Input must be a hash' unless data.is_a?(Hash)
46
+
47
+ if data['content']
48
+ Document.new(data)
49
+ elsif data['contents'] && data['contents']['en'] && data['contents']['en']['content']
50
+ Document.new({ 'content' => data['contents']['en']['content'] })
51
+ else
52
+ Document.new
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'node'
4
+ require_relative 'table_row'
5
+
6
+ module Prosereflect
7
+ class Table < Node
8
+ def rows
9
+ content.select { |node| node.type == 'table_row' }
10
+ end
11
+
12
+ def header_row
13
+ rows.first
14
+ end
15
+
16
+ def data_rows
17
+ rows[1..] || []
18
+ end
19
+
20
+ # Get cell at specific position (skips header)
21
+ def cell_at(row_index, col_index)
22
+ return nil if row_index.negative? || col_index.negative?
23
+
24
+ data_row = data_rows[row_index]
25
+ return nil unless data_row
26
+
27
+ data_row.cells[col_index]
28
+ end
29
+
30
+ # Create a new table
31
+ def self.create(attrs = nil)
32
+ table = new({ 'type' => 'table', 'content' => [] })
33
+ table.instance_variable_set(:@attrs, attrs) if attrs
34
+ table
35
+ end
36
+
37
+ # Add a header row to the table
38
+ def add_header(header_cells)
39
+ row = TableRow.create
40
+ header_cells.each do |cell_content|
41
+ row.add_cell(cell_content)
42
+ end
43
+ add_child(row)
44
+ row
45
+ end
46
+
47
+ # Add a data row to the table
48
+ def add_row(cell_contents = [])
49
+ row = TableRow.create
50
+ cell_contents.each do |cell_content|
51
+ row.add_cell(cell_content)
52
+ end
53
+ add_child(row)
54
+ row
55
+ end
56
+
57
+ # Add multiple rows at once
58
+ def add_rows(rows_data)
59
+ rows_data.each do |row_data|
60
+ add_row(row_data)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'node'
4
+ require_relative 'paragraph'
5
+
6
+ module Prosereflect
7
+ class TableCell < Node
8
+ def paragraphs
9
+ content.select { |node| node.type == 'paragraph' }
10
+ end
11
+
12
+ def text_content
13
+ paragraphs.map(&:text_content).join("\n")
14
+ end
15
+
16
+ def lines
17
+ text_content.split("\n").map(&:strip).reject(&:empty?)
18
+ end
19
+
20
+ # Create a new table cell
21
+ def self.create(attrs = nil)
22
+ cell = new({ 'type' => 'table_cell', 'content' => [] })
23
+ cell.instance_variable_set(:@attrs, attrs) if attrs
24
+ cell
25
+ end
26
+
27
+ # Add a paragraph to the cell
28
+ def add_paragraph(text = nil)
29
+ paragraph = Paragraph.create
30
+
31
+ paragraph.add_text(text) if text
32
+
33
+ add_child(paragraph)
34
+ paragraph
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'node'
4
+ require_relative 'table_cell'
5
+
6
+ module Prosereflect
7
+ class TableRow < Node
8
+ def cells
9
+ content.select { |node| node.type == 'table_cell' }
10
+ end
11
+
12
+ # Create a new table row
13
+ def self.create(attrs = nil)
14
+ row = new({ 'type' => 'table_row', 'content' => [] })
15
+ row.instance_variable_set(:@attrs, attrs) if attrs
16
+ row
17
+ end
18
+
19
+ # Add a cell to the row
20
+ def add_cell(content_text = nil)
21
+ cell = TableCell.create
22
+
23
+ if content_text
24
+ paragraph = cell.add_paragraph
25
+ paragraph.add_text(content_text)
26
+ end
27
+
28
+ add_child(cell)
29
+ cell
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'node'
4
+
5
+ module Prosereflect
6
+ class Text < Node
7
+ attr_reader :text
8
+ attr_accessor :marks
9
+
10
+ def initialize(data = {})
11
+ super
12
+ @text = data['text'] || ''
13
+ end
14
+
15
+ def text_content
16
+ @text || ''
17
+ end
18
+
19
+ def text_content_with_breaks
20
+ @text || ''
21
+ end
22
+
23
+ # Create a new text node
24
+ def self.create(text, marks = nil)
25
+ node = new({ 'type' => 'text', 'text' => text })
26
+ node.instance_variable_set(:@marks, marks) if marks
27
+ node
28
+ end
29
+
30
+ # Convert to hash representation
31
+ def to_h
32
+ result = super
33
+ result['text'] = @text if @text
34
+ result
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prosereflect
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'prosereflect/version'
4
+ require_relative 'prosereflect/node'
5
+ require_relative 'prosereflect/text'
6
+ require_relative 'prosereflect/paragraph'
7
+ require_relative 'prosereflect/hard_break'
8
+ require_relative 'prosereflect/table'
9
+ require_relative 'prosereflect/table_row'
10
+ require_relative 'prosereflect/table_cell'
11
+ require_relative 'prosereflect/document'
12
+ require_relative 'prosereflect/parser'
13
+
14
+ module Prosereflect
15
+ class Error < StandardError; end
16
+ # Your code goes here...
17
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/prosereflect/version'
4
+
5
+ all_files_in_git = Dir.chdir(File.expand_path(__dir__)) do
6
+ `git ls-files -z`.split("\x0")
7
+ end
8
+
9
+ Gem::Specification.new do |spec|
10
+ spec.name = 'prosereflect'
11
+ spec.version = Prosereflect::VERSION
12
+ spec.authors = ['Ribose']
13
+ spec.email = ['open.source@ribose.com']
14
+
15
+ spec.summary = 'Ruby model accessor for prosereflect document trees.'
16
+ spec.homepage = 'https://github.com/metanorma/prosereflect'
17
+ spec.license = 'BSD-2-Clause'
18
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.6.0')
19
+
20
+ spec.metadata['homepage_uri'] = spec.homepage
21
+ spec.metadata['source_code_uri'] = spec.homepage
22
+ spec.metadata['bug_tracker_uri'] = "#{spec.homepage}/issues"
23
+
24
+ # Specify which files should be added to the gem when it is released.
25
+ spec.files = all_files_in_git
26
+ .reject { |f| f.match(%r{\A(?:test|features|bin|\.)/}) }
27
+
28
+ spec.bindir = 'exe'
29
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ['lib']
31
+
32
+ spec.add_dependency 'lutaml-model', '~> 0.7'
33
+ end
@@ -0,0 +1,4 @@
1
+ module Prosereflect
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end