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.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/lib/coradoc/html/base.rb +157 -0
- data/lib/coradoc/html/config.rb +467 -0
- data/lib/coradoc/html/converter_base.rb +177 -0
- data/lib/coradoc/html/converters/admonition.rb +180 -0
- data/lib/coradoc/html/converters/attribute.rb +68 -0
- data/lib/coradoc/html/converters/attribute_reference.rb +60 -0
- data/lib/coradoc/html/converters/audio.rb +165 -0
- data/lib/coradoc/html/converters/base.rb +615 -0
- data/lib/coradoc/html/converters/bibliography.rb +82 -0
- data/lib/coradoc/html/converters/bibliography_entry.rb +108 -0
- data/lib/coradoc/html/converters/block_image.rb +72 -0
- data/lib/coradoc/html/converters/bold.rb +34 -0
- data/lib/coradoc/html/converters/break.rb +32 -0
- data/lib/coradoc/html/converters/comment_block.rb +42 -0
- data/lib/coradoc/html/converters/comment_line.rb +54 -0
- data/lib/coradoc/html/converters/cross_reference.rb +59 -0
- data/lib/coradoc/html/converters/document.rb +108 -0
- data/lib/coradoc/html/converters/example.rb +114 -0
- data/lib/coradoc/html/converters/highlight.rb +34 -0
- data/lib/coradoc/html/converters/include.rb +68 -0
- data/lib/coradoc/html/converters/inline_image.rb +41 -0
- data/lib/coradoc/html/converters/italic.rb +34 -0
- data/lib/coradoc/html/converters/line_break.rb +31 -0
- data/lib/coradoc/html/converters/link.rb +46 -0
- data/lib/coradoc/html/converters/list_item.rb +75 -0
- data/lib/coradoc/html/converters/listing.rb +99 -0
- data/lib/coradoc/html/converters/literal.rb +102 -0
- data/lib/coradoc/html/converters/monospace.rb +34 -0
- data/lib/coradoc/html/converters/open.rb +78 -0
- data/lib/coradoc/html/converters/ordered.rb +53 -0
- data/lib/coradoc/html/converters/paragraph.rb +46 -0
- data/lib/coradoc/html/converters/quote.rb +113 -0
- data/lib/coradoc/html/converters/reviewer_comment.rb +74 -0
- data/lib/coradoc/html/converters/reviewer_note.rb +134 -0
- data/lib/coradoc/html/converters/section.rb +90 -0
- data/lib/coradoc/html/converters/sidebar.rb +113 -0
- data/lib/coradoc/html/converters/source.rb +137 -0
- data/lib/coradoc/html/converters/source_code.rb +16 -0
- data/lib/coradoc/html/converters/span.rb +61 -0
- data/lib/coradoc/html/converters/strikethrough.rb +34 -0
- data/lib/coradoc/html/converters/subscript.rb +34 -0
- data/lib/coradoc/html/converters/superscript.rb +34 -0
- data/lib/coradoc/html/converters/table.rb +85 -0
- data/lib/coradoc/html/converters/table_cell.rb +203 -0
- data/lib/coradoc/html/converters/table_row.rb +45 -0
- data/lib/coradoc/html/converters/template_html_converter.rb +105 -0
- data/lib/coradoc/html/converters/term.rb +58 -0
- data/lib/coradoc/html/converters/text_element.rb +44 -0
- data/lib/coradoc/html/converters/underline.rb +34 -0
- data/lib/coradoc/html/converters/unordered.rb +47 -0
- data/lib/coradoc/html/converters/verse.rb +105 -0
- data/lib/coradoc/html/converters/video.rb +179 -0
- data/lib/coradoc/html/element_mapping.rb +210 -0
- data/lib/coradoc/html/entity.rb +137 -0
- data/lib/coradoc/html/input/cleaner.rb +163 -0
- data/lib/coradoc/html/input/config.rb +79 -0
- data/lib/coradoc/html/input/converters/a.rb +90 -0
- data/lib/coradoc/html/input/converters/aside.rb +23 -0
- data/lib/coradoc/html/input/converters/audio.rb +50 -0
- data/lib/coradoc/html/input/converters/base.rb +116 -0
- data/lib/coradoc/html/input/converters/blockquote.rb +25 -0
- data/lib/coradoc/html/input/converters/br.rb +19 -0
- data/lib/coradoc/html/input/converters/bypass.rb +83 -0
- data/lib/coradoc/html/input/converters/code.rb +25 -0
- data/lib/coradoc/html/input/converters/div.rb +25 -0
- data/lib/coradoc/html/input/converters/dl.rb +106 -0
- data/lib/coradoc/html/input/converters/drop.rb +28 -0
- data/lib/coradoc/html/input/converters/em.rb +23 -0
- data/lib/coradoc/html/input/converters/figure.rb +58 -0
- data/lib/coradoc/html/input/converters/h.rb +76 -0
- data/lib/coradoc/html/input/converters/head.rb +30 -0
- data/lib/coradoc/html/input/converters/hr.rb +20 -0
- data/lib/coradoc/html/input/converters/ignore.rb +22 -0
- data/lib/coradoc/html/input/converters/img.rb +110 -0
- data/lib/coradoc/html/input/converters/li.rb +35 -0
- data/lib/coradoc/html/input/converters/mark.rb +21 -0
- data/lib/coradoc/html/input/converters/markup.rb +107 -0
- data/lib/coradoc/html/input/converters/math.rb +46 -0
- data/lib/coradoc/html/input/converters/ol.rb +46 -0
- data/lib/coradoc/html/input/converters/p.rb +81 -0
- data/lib/coradoc/html/input/converters/pass_through.rb +19 -0
- data/lib/coradoc/html/input/converters/pre.rb +59 -0
- data/lib/coradoc/html/input/converters/q.rb +24 -0
- data/lib/coradoc/html/input/converters/strong.rb +22 -0
- data/lib/coradoc/html/input/converters/sub.rb +40 -0
- data/lib/coradoc/html/input/converters/sup.rb +40 -0
- data/lib/coradoc/html/input/converters/table.rb +64 -0
- data/lib/coradoc/html/input/converters/td.rb +70 -0
- data/lib/coradoc/html/input/converters/text.rb +67 -0
- data/lib/coradoc/html/input/converters/th.rb +20 -0
- data/lib/coradoc/html/input/converters/tr.rb +28 -0
- data/lib/coradoc/html/input/converters/video.rb +53 -0
- data/lib/coradoc/html/input/converters.rb +122 -0
- data/lib/coradoc/html/input/errors.rb +22 -0
- data/lib/coradoc/html/input/html_converter.rb +170 -0
- data/lib/coradoc/html/input/plugin.rb +169 -0
- data/lib/coradoc/html/input/plugins/plateau.rb +229 -0
- data/lib/coradoc/html/input/postprocessor.rb +31 -0
- data/lib/coradoc/html/input.rb +68 -0
- data/lib/coradoc/html/output.rb +95 -0
- data/lib/coradoc/html/renderer.rb +409 -0
- data/lib/coradoc/html/spa.rb +309 -0
- data/lib/coradoc/html/static.rb +293 -0
- data/lib/coradoc/html/template_config.rb +151 -0
- data/lib/coradoc/html/template_helpers.rb +58 -0
- data/lib/coradoc/html/template_locator.rb +114 -0
- data/lib/coradoc/html/theme/base.rb +231 -0
- data/lib/coradoc/html/theme/classic_renderer.rb +390 -0
- data/lib/coradoc/html/theme/modern/components/ui_components.rb +344 -0
- data/lib/coradoc/html/theme/modern/css_generator.rb +311 -0
- data/lib/coradoc/html/theme/modern/javascript_generator.rb +314 -0
- data/lib/coradoc/html/theme/modern/serializers/document_serializer.rb +382 -0
- data/lib/coradoc/html/theme/modern/tailwind_config_builder.rb +164 -0
- data/lib/coradoc/html/theme/modern/vue_template_generator.rb +374 -0
- data/lib/coradoc/html/theme/modern_renderer.rb +250 -0
- data/lib/coradoc/html/theme/registry.rb +153 -0
- data/lib/coradoc/html/theme.rb +13 -0
- data/lib/coradoc/html/transform/from_core_model.rb +32 -0
- data/lib/coradoc/html/transform/to_core_model.rb +39 -0
- data/lib/coradoc/html/version.rb +7 -0
- data/lib/coradoc/html.rb +255 -0
- 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
|