coradoc-html 1.1.7

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 (124) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/lib/coradoc/html/base.rb +157 -0
  4. data/lib/coradoc/html/config.rb +467 -0
  5. data/lib/coradoc/html/converter_base.rb +177 -0
  6. data/lib/coradoc/html/converters/admonition.rb +180 -0
  7. data/lib/coradoc/html/converters/attribute.rb +68 -0
  8. data/lib/coradoc/html/converters/attribute_reference.rb +60 -0
  9. data/lib/coradoc/html/converters/audio.rb +165 -0
  10. data/lib/coradoc/html/converters/base.rb +615 -0
  11. data/lib/coradoc/html/converters/bibliography.rb +82 -0
  12. data/lib/coradoc/html/converters/bibliography_entry.rb +108 -0
  13. data/lib/coradoc/html/converters/block_image.rb +72 -0
  14. data/lib/coradoc/html/converters/bold.rb +34 -0
  15. data/lib/coradoc/html/converters/break.rb +32 -0
  16. data/lib/coradoc/html/converters/comment_block.rb +42 -0
  17. data/lib/coradoc/html/converters/comment_line.rb +54 -0
  18. data/lib/coradoc/html/converters/cross_reference.rb +59 -0
  19. data/lib/coradoc/html/converters/document.rb +108 -0
  20. data/lib/coradoc/html/converters/example.rb +114 -0
  21. data/lib/coradoc/html/converters/highlight.rb +34 -0
  22. data/lib/coradoc/html/converters/include.rb +68 -0
  23. data/lib/coradoc/html/converters/inline_image.rb +41 -0
  24. data/lib/coradoc/html/converters/italic.rb +34 -0
  25. data/lib/coradoc/html/converters/line_break.rb +31 -0
  26. data/lib/coradoc/html/converters/link.rb +46 -0
  27. data/lib/coradoc/html/converters/list_item.rb +75 -0
  28. data/lib/coradoc/html/converters/listing.rb +99 -0
  29. data/lib/coradoc/html/converters/literal.rb +102 -0
  30. data/lib/coradoc/html/converters/monospace.rb +34 -0
  31. data/lib/coradoc/html/converters/open.rb +78 -0
  32. data/lib/coradoc/html/converters/ordered.rb +53 -0
  33. data/lib/coradoc/html/converters/paragraph.rb +46 -0
  34. data/lib/coradoc/html/converters/quote.rb +113 -0
  35. data/lib/coradoc/html/converters/reviewer_comment.rb +74 -0
  36. data/lib/coradoc/html/converters/reviewer_note.rb +134 -0
  37. data/lib/coradoc/html/converters/section.rb +90 -0
  38. data/lib/coradoc/html/converters/sidebar.rb +113 -0
  39. data/lib/coradoc/html/converters/source.rb +137 -0
  40. data/lib/coradoc/html/converters/source_code.rb +16 -0
  41. data/lib/coradoc/html/converters/span.rb +61 -0
  42. data/lib/coradoc/html/converters/strikethrough.rb +34 -0
  43. data/lib/coradoc/html/converters/subscript.rb +34 -0
  44. data/lib/coradoc/html/converters/superscript.rb +34 -0
  45. data/lib/coradoc/html/converters/table.rb +85 -0
  46. data/lib/coradoc/html/converters/table_cell.rb +203 -0
  47. data/lib/coradoc/html/converters/table_row.rb +45 -0
  48. data/lib/coradoc/html/converters/template_html_converter.rb +105 -0
  49. data/lib/coradoc/html/converters/term.rb +58 -0
  50. data/lib/coradoc/html/converters/text_element.rb +44 -0
  51. data/lib/coradoc/html/converters/underline.rb +34 -0
  52. data/lib/coradoc/html/converters/unordered.rb +47 -0
  53. data/lib/coradoc/html/converters/verse.rb +105 -0
  54. data/lib/coradoc/html/converters/video.rb +179 -0
  55. data/lib/coradoc/html/element_mapping.rb +210 -0
  56. data/lib/coradoc/html/entity.rb +137 -0
  57. data/lib/coradoc/html/input/cleaner.rb +163 -0
  58. data/lib/coradoc/html/input/config.rb +79 -0
  59. data/lib/coradoc/html/input/converters/a.rb +90 -0
  60. data/lib/coradoc/html/input/converters/aside.rb +23 -0
  61. data/lib/coradoc/html/input/converters/audio.rb +50 -0
  62. data/lib/coradoc/html/input/converters/base.rb +116 -0
  63. data/lib/coradoc/html/input/converters/blockquote.rb +25 -0
  64. data/lib/coradoc/html/input/converters/br.rb +19 -0
  65. data/lib/coradoc/html/input/converters/bypass.rb +83 -0
  66. data/lib/coradoc/html/input/converters/code.rb +25 -0
  67. data/lib/coradoc/html/input/converters/div.rb +25 -0
  68. data/lib/coradoc/html/input/converters/dl.rb +106 -0
  69. data/lib/coradoc/html/input/converters/drop.rb +28 -0
  70. data/lib/coradoc/html/input/converters/em.rb +23 -0
  71. data/lib/coradoc/html/input/converters/figure.rb +58 -0
  72. data/lib/coradoc/html/input/converters/h.rb +76 -0
  73. data/lib/coradoc/html/input/converters/head.rb +30 -0
  74. data/lib/coradoc/html/input/converters/hr.rb +20 -0
  75. data/lib/coradoc/html/input/converters/ignore.rb +22 -0
  76. data/lib/coradoc/html/input/converters/img.rb +110 -0
  77. data/lib/coradoc/html/input/converters/li.rb +35 -0
  78. data/lib/coradoc/html/input/converters/mark.rb +21 -0
  79. data/lib/coradoc/html/input/converters/markup.rb +107 -0
  80. data/lib/coradoc/html/input/converters/math.rb +46 -0
  81. data/lib/coradoc/html/input/converters/ol.rb +46 -0
  82. data/lib/coradoc/html/input/converters/p.rb +81 -0
  83. data/lib/coradoc/html/input/converters/pass_through.rb +19 -0
  84. data/lib/coradoc/html/input/converters/pre.rb +59 -0
  85. data/lib/coradoc/html/input/converters/q.rb +24 -0
  86. data/lib/coradoc/html/input/converters/strong.rb +22 -0
  87. data/lib/coradoc/html/input/converters/sub.rb +40 -0
  88. data/lib/coradoc/html/input/converters/sup.rb +40 -0
  89. data/lib/coradoc/html/input/converters/table.rb +64 -0
  90. data/lib/coradoc/html/input/converters/td.rb +70 -0
  91. data/lib/coradoc/html/input/converters/text.rb +67 -0
  92. data/lib/coradoc/html/input/converters/th.rb +20 -0
  93. data/lib/coradoc/html/input/converters/tr.rb +28 -0
  94. data/lib/coradoc/html/input/converters/video.rb +53 -0
  95. data/lib/coradoc/html/input/converters.rb +122 -0
  96. data/lib/coradoc/html/input/errors.rb +22 -0
  97. data/lib/coradoc/html/input/html_converter.rb +170 -0
  98. data/lib/coradoc/html/input/plugin.rb +169 -0
  99. data/lib/coradoc/html/input/plugins/plateau.rb +229 -0
  100. data/lib/coradoc/html/input/postprocessor.rb +31 -0
  101. data/lib/coradoc/html/input.rb +68 -0
  102. data/lib/coradoc/html/output.rb +95 -0
  103. data/lib/coradoc/html/renderer.rb +409 -0
  104. data/lib/coradoc/html/spa.rb +309 -0
  105. data/lib/coradoc/html/static.rb +293 -0
  106. data/lib/coradoc/html/template_config.rb +151 -0
  107. data/lib/coradoc/html/template_helpers.rb +58 -0
  108. data/lib/coradoc/html/template_locator.rb +114 -0
  109. data/lib/coradoc/html/theme/base.rb +231 -0
  110. data/lib/coradoc/html/theme/classic_renderer.rb +390 -0
  111. data/lib/coradoc/html/theme/modern/components/ui_components.rb +344 -0
  112. data/lib/coradoc/html/theme/modern/css_generator.rb +311 -0
  113. data/lib/coradoc/html/theme/modern/javascript_generator.rb +314 -0
  114. data/lib/coradoc/html/theme/modern/serializers/document_serializer.rb +382 -0
  115. data/lib/coradoc/html/theme/modern/tailwind_config_builder.rb +164 -0
  116. data/lib/coradoc/html/theme/modern/vue_template_generator.rb +374 -0
  117. data/lib/coradoc/html/theme/modern_renderer.rb +250 -0
  118. data/lib/coradoc/html/theme/registry.rb +153 -0
  119. data/lib/coradoc/html/theme.rb +13 -0
  120. data/lib/coradoc/html/transform/from_core_model.rb +32 -0
  121. data/lib/coradoc/html/transform/to_core_model.rb +39 -0
  122. data/lib/coradoc/html/version.rb +7 -0
  123. data/lib/coradoc/html.rb +255 -0
  124. metadata +264 -0
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Html
5
+ module Converters
6
+ # Converter for CoreModel::Block (source) to HTML <pre><code>
7
+ class Source < Base
8
+ # Convert CoreModel::Block (source) to HTML <pre><code>
9
+ def self.to_html(source, _options = {})
10
+ return '' unless source
11
+
12
+ # Build title if present
13
+ title_html = build_title(source)
14
+
15
+ # Build code attributes with language
16
+ code_attrs = build_code_attributes(source)
17
+
18
+ # Build pre attributes
19
+ pre_attrs = build_pre_attributes(source)
20
+
21
+ # Process source content
22
+ content = process_content(source.content)
23
+
24
+ # Combine into source block
25
+ source_html = ''
26
+ source_html += "#{title_html}\n" if title_html
27
+ source_html += %(<pre#{pre_attrs}><code#{code_attrs}>#{content}</code></pre>)
28
+
29
+ if title_html
30
+ %(<div class="source-block">\n#{source_html}\n</div>)
31
+ else
32
+ source_html
33
+ end
34
+ end
35
+
36
+ # Convert HTML <pre><code> to CoreModel::Block (source)
37
+ def self.to_coradoc(element, _options = {})
38
+ # Handle <div class="source-block"><pre><code>, <pre><code>, or <code>
39
+ code_elem = if element.name == 'div' && element['class']&.include?('source-block')
40
+ element.at_css('code')
41
+ elsif element.name == 'pre'
42
+ element.at_css('code')
43
+ elsif element.name == 'code'
44
+ element
45
+ else
46
+ return nil
47
+ end
48
+
49
+ return nil unless code_elem
50
+
51
+ # Extract title if in source-block wrapper
52
+ title = if element.name == 'div'
53
+ title_elem = element.at_css('.source-title')
54
+ title_elem&.text&.strip
55
+ end
56
+
57
+ # Extract language from class
58
+ language = extract_language(code_elem)
59
+
60
+ # Extract content
61
+ content = code_elem.text
62
+
63
+ # Extract ID if present
64
+ id = code_elem['id'] || element['id']
65
+
66
+ Coradoc::CoreModel::SourceBlock.new(
67
+ content: content,
68
+ title: title,
69
+ id: id,
70
+ language: language
71
+ )
72
+ end
73
+
74
+ def self.build_code_attributes(source)
75
+ attrs = []
76
+
77
+ # Add language class if present
78
+ lang = source.language
79
+
80
+ attrs << %( class="language-#{escape_attribute(lang)}") if lang && !lang.empty?
81
+
82
+ # Add ID if present
83
+ attrs << %( id="#{escape_attribute(source.id)}") if source.id
84
+
85
+ attrs.join
86
+ end
87
+
88
+ def self.build_pre_attributes(_source)
89
+ %( class="source")
90
+ end
91
+
92
+ def self.build_title(source)
93
+ return nil unless source.title
94
+
95
+ title_text = source.title.to_s
96
+ return nil if title_text.empty?
97
+
98
+ %(<div class="source-title">#{escape_html(title_text)}</div>)
99
+ end
100
+
101
+ def self.process_content(content)
102
+ return '' if content.nil?
103
+
104
+ # For source code, preserve the content exactly
105
+ if content.is_a?(String)
106
+ escape_html(content)
107
+ elsif content.is_a?(Array)
108
+ # Join array items with newlines
109
+ content.map { |line| escape_html(line.to_s) }.join("\n")
110
+ else
111
+ escape_html(content.to_s)
112
+ end
113
+ end
114
+
115
+ def self.extract_language(element)
116
+ return nil unless element['class']
117
+
118
+ # Extract language from class like "language-ruby", "lang-python", etc.
119
+ classes = element['class'].split
120
+ lang_class = classes.find { |c| c.start_with?('language-', 'lang-') }
121
+ return nil unless lang_class
122
+
123
+ lang_class.sub(/^(language-|lang-)/, '')
124
+ end
125
+ end
126
+
127
+ # Converter for SourceCode blocks
128
+ #
129
+ # SourceCode models use the `lines` attribute, while Source models use `content`.
130
+ # This converter inherits from Source and handles the lines attribute properly.
131
+ class SourceCode < Source
132
+ # The parent Source class already handles both content and lines attributes
133
+ # after our recent update, so we just need to inherit.
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Html
5
+ module Converters
6
+ autoload :Base, "#{__dir__}/base"
7
+ # Converter for SourceCode blocks
8
+ #
9
+ # SourceCode models use the `lines` attribute, while Source models use `content`.
10
+ class SourceCode < Source
11
+ # The parent Source class already handles both content and lines attributes
12
+ # after our recent update, so we just need to inherit.
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Html
5
+ module Converters
6
+ # Converter for generic Span inline element
7
+ class Span < Base
8
+ class << self
9
+ # Convert HTML <span> to CoreModel::InlineElement
10
+ # @param node [Nokogiri::XML::Node] HTML node
11
+ # @param state [Hash] Conversion state
12
+ # @return [Coradoc::CoreModel::InlineElement] Span inline element
13
+ def to_coradoc(node, state = {})
14
+ text = node.inner_text
15
+ attrs = extract_node_attributes(node)
16
+
17
+ # Collect all children for potential mixed content
18
+ children = node.children.flat_map do |child|
19
+ convert_node_to_core(child, state)
20
+ end.compact
21
+
22
+ span = Coradoc::CoreModel::InlineElement.new(
23
+ format_type: 'span',
24
+ content: text
25
+ )
26
+
27
+ # Set class from attributes as metadata
28
+ span.set_metadata(:class, attrs[:class]) if attrs[:class]
29
+
30
+ # If there are children (mixed content), use them
31
+ span.children = children if children.any?
32
+
33
+ span
34
+ end
35
+
36
+ # Convert CoreModel::InlineElement (span) to HTML <span>
37
+ # @param model [Coradoc::CoreModel::InlineElement] Span model
38
+ # @param state [Hash] Conversion state
39
+ # @return [String] HTML string
40
+ def to_html(model, state = {})
41
+ # Prefer children for mixed content, fall back to content
42
+ content = if model.children&.any?
43
+ model.children.map { |c| convert_content_to_html(c, state) }.join
44
+ else
45
+ escape_html(model.content || '')
46
+ end
47
+
48
+ # Build attributes from metadata
49
+ attrs = {}
50
+ if model.metadata && model.metadata[:class]
51
+ attrs[:class] =
52
+ model.metadata[:class]
53
+ end
54
+
55
+ build_element('span', content, attrs)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Html
5
+ module Converters
6
+ # Converter for Strikethrough inline element
7
+ class Strikethrough < Base
8
+ class << self
9
+ # Convert HTML <del>, <s>, or <strike> to CoreModel::InlineElement
10
+ # @param node [Nokogiri::XML::Node] HTML node
11
+ # @param state [Hash] Conversion state
12
+ # @return [Coradoc::CoreModel::InlineElement] Strikethrough inline element
13
+ def to_coradoc(node, state = {})
14
+ content = treat_children(node, state)
15
+ Coradoc::CoreModel::InlineElement.new(
16
+ format_type: 'strikethrough',
17
+ content: content
18
+ )
19
+ end
20
+
21
+ # Convert CoreModel::InlineElement (strikethrough) to HTML <del>
22
+ # @param model [Coradoc::CoreModel::InlineElement] Strikethrough model
23
+ # @param state [Hash] Conversion state
24
+ # @return [String] HTML string
25
+ def to_html(model, state = {})
26
+ content = convert_content_to_html(model.content, state)
27
+ attributes = extract_model_attributes(model)
28
+ build_element('del', content, attributes)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Html
5
+ module Converters
6
+ # Converter for Subscript inline element
7
+ class Subscript < Base
8
+ class << self
9
+ # Convert HTML <sub> to CoreModel::InlineElement
10
+ # @param node [Nokogiri::XML::Node] HTML node
11
+ # @param state [Hash] Conversion state
12
+ # @return [Coradoc::CoreModel::InlineElement] Subscript inline element
13
+ def to_coradoc(node, state = {})
14
+ content = treat_children(node, state)
15
+ Coradoc::CoreModel::InlineElement.new(
16
+ format_type: 'subscript',
17
+ content: content
18
+ )
19
+ end
20
+
21
+ # Convert CoreModel::InlineElement (subscript) to HTML <sub>
22
+ # @param model [Coradoc::CoreModel::InlineElement] Subscript model
23
+ # @param state [Hash] Conversion state
24
+ # @return [String] HTML string
25
+ def to_html(model, state = {})
26
+ content = convert_content_to_html(model.content, state)
27
+ attributes = extract_model_attributes(model)
28
+ build_element('sub', content, attributes)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Html
5
+ module Converters
6
+ # Converter for Superscript inline element
7
+ class Superscript < Base
8
+ class << self
9
+ # Convert HTML <sup> to CoreModel::InlineElement
10
+ # @param node [Nokogiri::XML::Node] HTML node
11
+ # @param state [Hash] Conversion state
12
+ # @return [Coradoc::CoreModel::InlineElement] Superscript inline element
13
+ def to_coradoc(node, state = {})
14
+ content = treat_children(node, state)
15
+ Coradoc::CoreModel::InlineElement.new(
16
+ format_type: 'superscript',
17
+ content: content
18
+ )
19
+ end
20
+
21
+ # Convert CoreModel::InlineElement (superscript) to HTML <sup>
22
+ # @param model [Coradoc::CoreModel::InlineElement] Superscript model
23
+ # @param state [Hash] Conversion state
24
+ # @return [String] HTML string
25
+ def to_html(model, state = {})
26
+ content = convert_content_to_html(model.content, state)
27
+ attributes = extract_model_attributes(model)
28
+ build_element('sup', content, attributes)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Html
5
+ module Converters
6
+ class Table < Base
7
+ # Convert CoreModel::Table to HTML <table>
8
+ def self.to_html(table, _options = {})
9
+ attrs = build_attributes(table)
10
+ caption = build_caption(table)
11
+
12
+ rows_html = table.rows.map do |row|
13
+ TableRow.to_html(row)
14
+ end.join("\n")
15
+
16
+ table_content = rows_html
17
+ table_content = "#{caption}\n#{table_content}" if caption
18
+
19
+ "<table#{attrs}>\n#{table_content}\n</table>"
20
+ end
21
+
22
+ # Convert HTML <table> to CoreModel::Table
23
+ def self.to_coradoc(element, _options = {})
24
+ return nil unless element.name == 'table'
25
+
26
+ rows = element.css('tr').map do |tr|
27
+ TableRow.to_coradoc(tr)
28
+ end.compact
29
+
30
+ # Extract caption if present
31
+ caption_elem = element.at_css('caption')
32
+ title = caption_elem&.text&.strip
33
+
34
+ # Extract table attributes
35
+ attrs = extract_table_attributes(element)
36
+
37
+ table = Coradoc::CoreModel::Table.new(
38
+ rows: rows,
39
+ title: title
40
+ )
41
+ table.id = attrs[:id] if attrs[:id]
42
+ table.frame = attrs[:frame] if attrs[:frame]
43
+
44
+ table
45
+ end
46
+
47
+ def self.build_attributes(table)
48
+ attrs = []
49
+
50
+ # Add ID if present
51
+ attrs << %( id="#{escape_attribute(table.id)}") if table.id
52
+
53
+ # CoreModel::Table with frame attribute
54
+ attrs << %( class="frame-#{escape_attribute(table.frame)}") if table.frame
55
+
56
+ attrs.join
57
+ end
58
+
59
+ def self.build_caption(table)
60
+ return nil unless table.title
61
+
62
+ caption_text = table.title.to_s
63
+ return nil if caption_text.empty?
64
+
65
+ "<caption>#{escape_html(caption_text)}</caption>"
66
+ end
67
+
68
+ def self.extract_table_attributes(element)
69
+ attrs = {}
70
+
71
+ # Extract id attribute
72
+ attrs[:id] = element['id'] if element['id']
73
+
74
+ # Extract frame attribute from class
75
+ if element['class']&.include?('frame-')
76
+ frame = element['class'][/frame-(\w+)/, 1]
77
+ attrs[:frame] = frame if frame
78
+ end
79
+
80
+ attrs
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,203 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Html
5
+ module Converters
6
+ class TableCell < Base
7
+ # Convert CoreModel::TableCell to HTML <td> or <th>
8
+ #
9
+ # @param cell [Coradoc::CoreModel::TableCell] Table cell model
10
+ # @param _options [Hash] Conversion options
11
+ # @return [String] HTML string
12
+ def self.to_html(cell, _options = {})
13
+ return '' unless cell
14
+
15
+ # Check if this is a header cell
16
+ is_header = cell.header == true
17
+ tag = is_header ? 'th' : 'td'
18
+
19
+ # Build cell attributes
20
+ attrs = build_attributes(cell)
21
+
22
+ # Process cell content
23
+ content = process_content(cell)
24
+
25
+ # Wrap content in style tags if needed
26
+ content = wrap_with_style(content, cell)
27
+
28
+ "<#{tag}#{attrs}>#{content}</#{tag}>"
29
+ end
30
+
31
+ # Convert HTML <td> or <th> to CoreModel::TableCell
32
+ def self.to_coradoc(element, _options = {})
33
+ return nil unless %w[td th].include?(element.name)
34
+
35
+ # Determine if this is a header cell
36
+ is_header = element.name == 'th'
37
+
38
+ # Extract content - could be text or nested elements
39
+ content = extract_content(element)
40
+
41
+ # Extract cell attributes
42
+ attrs = extract_cell_attributes(element)
43
+
44
+ Coradoc::CoreModel::TableCell.new(
45
+ content: content,
46
+ header: is_header,
47
+ **attrs.compact
48
+ )
49
+ end
50
+
51
+ # Build HTML attributes from CoreModel::TableCell
52
+ def self.build_attributes(cell)
53
+ attrs = []
54
+
55
+ attrs << %( id="#{escape_attribute(cell.id)}") if cell.id
56
+
57
+ attrs << %( colspan="#{cell.colspan}") if cell.colspan
58
+ attrs << %( rowspan="#{cell.rowspan}") if cell.rowspan
59
+
60
+ style_parts = []
61
+
62
+ style_parts << "text-align: #{escape_attribute(cell.alignment)}" if cell.alignment
63
+ style_parts << "vertical-align: #{escape_attribute(cell.vertical_alignment)}" if cell.vertical_alignment
64
+ style_parts << "background-color: #{escape_attribute(cell.bgcolor)}" if cell.bgcolor
65
+ style_parts << "color: #{escape_attribute(cell.color)}" if cell.color
66
+ style_parts << "width: #{escape_attribute(cell.width)}" if cell.width
67
+ style_parts << "height: #{escape_attribute(cell.height)}" if cell.height
68
+
69
+ # Add style attribute if we have any styles
70
+ attrs << %( style="#{style_parts.join('; ')}") if style_parts.any?
71
+
72
+ attrs.join
73
+ end
74
+
75
+ # Wrap content with style tags based on cell style
76
+ def self.wrap_with_style(content, cell)
77
+ return content unless cell.style
78
+
79
+ case cell.style.to_s.downcase
80
+ when 'strong', 's'
81
+ "<strong>#{content}</strong>"
82
+ when 'emphasis', 'e'
83
+ "<em>#{content}</em>"
84
+ when 'monospace', 'm'
85
+ "<code>#{content}</code>"
86
+ when 'literal', 'l'
87
+ # Literal content - preserve whitespace
88
+ "<pre>#{content}</pre>"
89
+ when 'verse', 'v'
90
+ # Verse content - preserve formatting
91
+ "<div class=\"verse\">#{content}</div>"
92
+ else
93
+ content
94
+ end
95
+ end
96
+
97
+ def self.process_content(cell)
98
+ return '' if cell.nil?
99
+
100
+ # Use renderable_content if available (prefers children over content)
101
+ content = if cell.renderable_content
102
+ cell.renderable_content
103
+ elsif cell.children.any?
104
+ cell.children
105
+ elsif cell.content
106
+ cell.content
107
+ else
108
+ cell
109
+ end
110
+
111
+ if content.is_a?(Array)
112
+ content.map { |item| convert_item(item) }.join
113
+ else
114
+ convert_item(content)
115
+ end
116
+ end
117
+
118
+ def self.convert_item(item)
119
+ case item
120
+ when String
121
+ escape_html(item)
122
+ else
123
+ # Use convert_content_to_html for CoreModel types
124
+ convert_content_to_html(item, {})
125
+ end
126
+ end
127
+
128
+ def self.extract_content(element)
129
+ # Extract content from the cell
130
+ children = element.children
131
+
132
+ if children.size == 1 && children.first.text?
133
+ # Simple text content
134
+ children.first.text
135
+ else
136
+ # Complex content with nested elements
137
+ children.map do |child|
138
+ if child.text?
139
+ child.text
140
+ else
141
+ # Convert HTML element to CoreModel
142
+ convert_node_to_core(child, {})
143
+ end
144
+ end.compact
145
+ end
146
+ end
147
+
148
+ def self.extract_cell_attributes(element)
149
+ attrs = {}
150
+
151
+ # Extract colspan
152
+ attrs[:colspan] = element['colspan'].to_i if element['colspan']
153
+
154
+ # Extract rowspan
155
+ attrs[:rowspan] = element['rowspan'].to_i if element['rowspan']
156
+
157
+ # Extract styles from style attribute
158
+ if element['style']
159
+ style = element['style']
160
+
161
+ # Horizontal alignment
162
+ if style.include?('text-align')
163
+ align = style[/text-align:\s*(\w+)/, 1]
164
+ attrs[:alignment] = align if align
165
+ end
166
+
167
+ # Vertical alignment
168
+ if style.include?('vertical-align')
169
+ valign = style[/vertical-align:\s*(\w+)/, 1]
170
+ attrs[:vertical_alignment] = valign if valign
171
+ end
172
+
173
+ # Background color
174
+ if style.include?('background-color')
175
+ bgcolor = style[/background-color:\s*([^;]+)/, 1]
176
+ attrs[:bgcolor] = bgcolor.strip if bgcolor
177
+ end
178
+
179
+ # Text color
180
+ if style.include?('color:')
181
+ color = style[/color:\s*([^;]+)/, 1]
182
+ attrs[:color] = color.strip if color
183
+ end
184
+
185
+ # Width
186
+ if style.include?('width:')
187
+ width = style[/width:\s*([^;]+)/, 1]
188
+ attrs[:width] = width.strip if width
189
+ end
190
+
191
+ # Height
192
+ if style.include?('height:')
193
+ height = style[/height:\s*([^;]+)/, 1]
194
+ attrs[:height] = height.strip if height
195
+ end
196
+ end
197
+
198
+ attrs
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Html
5
+ module Converters
6
+ class TableRow < Base
7
+ # Convert CoreModel::TableRow to HTML <tr>
8
+ def self.to_html(row, _options = {})
9
+ return '' unless row
10
+
11
+ # CoreModel::TableRow uses cells
12
+ cells = row.cells || []
13
+ columns_html = cells.map do |cell|
14
+ TableCell.to_html(cell)
15
+ end.join("\n")
16
+
17
+ attrs = build_attributes(row)
18
+
19
+ "<tr#{attrs}>\n#{columns_html}\n</tr>"
20
+ end
21
+
22
+ # Convert HTML <tr> to CoreModel::TableRow
23
+ def self.to_coradoc(element, _options = {})
24
+ return nil unless element.name == 'tr'
25
+
26
+ # Get all cells (both td and th)
27
+ cells = element.css('td, th').map do |cell_elem|
28
+ TableCell.to_coradoc(cell_elem)
29
+ end.compact
30
+
31
+ Coradoc::CoreModel::TableRow.new(cells: cells)
32
+ end
33
+
34
+ def self.build_attributes(row)
35
+ attrs = []
36
+
37
+ # Add ID if present
38
+ attrs << %( id="#{escape_attribute(row.id)}") if row.id
39
+
40
+ attrs.join
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end