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,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Html
|
|
5
|
+
module Converters
|
|
6
|
+
# Converter for CoreModel::Block (example) to HTML <div class="example">
|
|
7
|
+
class Example < Base
|
|
8
|
+
# Convert CoreModel::Block (example) to HTML <div class="example">
|
|
9
|
+
def self.to_html(example, _options = {})
|
|
10
|
+
return '' unless example
|
|
11
|
+
|
|
12
|
+
# Build div attributes
|
|
13
|
+
attrs = build_attributes(example)
|
|
14
|
+
|
|
15
|
+
# Build title if present
|
|
16
|
+
title_html = build_title(example)
|
|
17
|
+
|
|
18
|
+
# Process example content
|
|
19
|
+
content = process_content(example.content)
|
|
20
|
+
|
|
21
|
+
# Combine title and content
|
|
22
|
+
example_html = ''
|
|
23
|
+
example_html += "#{title_html}\n" if title_html
|
|
24
|
+
example_html += content
|
|
25
|
+
|
|
26
|
+
%(<div#{attrs}>\n#{example_html}\n</div>)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Convert HTML <div class="example"> to CoreModel::Block (example)
|
|
30
|
+
def self.to_coradoc(element, _options = {})
|
|
31
|
+
return nil unless element.name == 'div'
|
|
32
|
+
return nil unless element['class']&.include?('example')
|
|
33
|
+
|
|
34
|
+
# Extract title if present
|
|
35
|
+
title_elem = element.at_css('.example-title')
|
|
36
|
+
title = title_elem&.text&.strip
|
|
37
|
+
|
|
38
|
+
# Extract content - all children except title
|
|
39
|
+
content_nodes = if title_elem
|
|
40
|
+
element.children.reject { |node| node == title_elem }
|
|
41
|
+
else
|
|
42
|
+
element.children
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
content = extract_content(content_nodes)
|
|
46
|
+
|
|
47
|
+
# Extract ID if present
|
|
48
|
+
id = element['id']
|
|
49
|
+
|
|
50
|
+
Coradoc::CoreModel::ExampleBlock.new(
|
|
51
|
+
content: content,
|
|
52
|
+
title: title,
|
|
53
|
+
id: id
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.build_attributes(example)
|
|
58
|
+
attrs = [%( class="example")]
|
|
59
|
+
|
|
60
|
+
# Add ID if present
|
|
61
|
+
attrs << %( id="#{escape_attribute(example.id)}") if example.id
|
|
62
|
+
|
|
63
|
+
attrs.join
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.build_title(example)
|
|
67
|
+
return nil unless example.title
|
|
68
|
+
|
|
69
|
+
title_text = example.title.to_s
|
|
70
|
+
return nil if title_text.empty?
|
|
71
|
+
|
|
72
|
+
%(<div class="example-title">#{escape_html(title_text)}</div>)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def self.process_content(content)
|
|
76
|
+
return '' if content.nil?
|
|
77
|
+
|
|
78
|
+
if content.is_a?(Array)
|
|
79
|
+
content.map { |item| convert_item(item) }.join("\n")
|
|
80
|
+
elsif content.is_a?(String)
|
|
81
|
+
"<p>#{escape_html(content)}</p>"
|
|
82
|
+
else
|
|
83
|
+
convert_item(content)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def self.convert_item(item)
|
|
88
|
+
case item
|
|
89
|
+
when String
|
|
90
|
+
"<p>#{escape_html(item)}</p>"
|
|
91
|
+
else
|
|
92
|
+
convert_content_to_html(item)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def self.extract_content(nodes)
|
|
97
|
+
# Extract and convert content nodes
|
|
98
|
+
nodes.map do |node|
|
|
99
|
+
if node.text? && !node.text.strip.empty?
|
|
100
|
+
node.text.strip
|
|
101
|
+
elsif node.element?
|
|
102
|
+
case node.name
|
|
103
|
+
when 'p'
|
|
104
|
+
Paragraph.to_coradoc(node)
|
|
105
|
+
else
|
|
106
|
+
node.text.strip
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end.compact
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Html
|
|
5
|
+
module Converters
|
|
6
|
+
# Converter for Highlight inline element
|
|
7
|
+
class Highlight < Base
|
|
8
|
+
class << self
|
|
9
|
+
# Convert HTML <mark> to CoreModel::InlineElement
|
|
10
|
+
# @param node [Nokogiri::XML::Node] HTML node
|
|
11
|
+
# @param state [Hash] Conversion state
|
|
12
|
+
# @return [Coradoc::CoreModel::InlineElement] Highlight inline element
|
|
13
|
+
def to_coradoc(node, state = {})
|
|
14
|
+
content = treat_children(node, state)
|
|
15
|
+
Coradoc::CoreModel::InlineElement.new(
|
|
16
|
+
format_type: 'highlight',
|
|
17
|
+
content: content
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Convert CoreModel::InlineElement (highlight) to HTML <mark>
|
|
22
|
+
# @param model [Coradoc::CoreModel::InlineElement] Highlight 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('mark', content, attributes)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Html
|
|
5
|
+
module Converters
|
|
6
|
+
# Converter for include directives
|
|
7
|
+
class Include < Base
|
|
8
|
+
# Convert CoreModel::Block (include) to HTML comment with include directive
|
|
9
|
+
# Note: HTML doesn't have native include support, so we use a comment
|
|
10
|
+
def self.to_html(include_directive, _options = {})
|
|
11
|
+
return '' unless include_directive
|
|
12
|
+
|
|
13
|
+
# Get include path from metadata
|
|
14
|
+
path = include_directive.metadata&.dig(:path) || ''
|
|
15
|
+
path = escape_html(path)
|
|
16
|
+
|
|
17
|
+
# Build include directive as comment
|
|
18
|
+
comment_text = "include::#{path}[]"
|
|
19
|
+
|
|
20
|
+
# Add attributes if present
|
|
21
|
+
attrs = include_directive.metadata&.dig(:attributes) || {}
|
|
22
|
+
if attrs && !attrs.empty?
|
|
23
|
+
attrs_str = attrs.map { |k, v| "#{k}=#{v}" }.join(',')
|
|
24
|
+
comment_text = "include::#{path}[#{attrs_str}]" unless attrs_str.empty?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
"<!-- #{comment_text} -->"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Convert HTML comment with include directive to CoreModel::Block (include)
|
|
31
|
+
def self.to_coradoc(element, _options = {})
|
|
32
|
+
return nil unless element.comment?
|
|
33
|
+
|
|
34
|
+
# Check if comment contains include directive
|
|
35
|
+
text = element.text.to_s.strip
|
|
36
|
+
return nil unless text.match?(/^include::/)
|
|
37
|
+
|
|
38
|
+
# Parse include directive
|
|
39
|
+
# Format: include::path[attributes]
|
|
40
|
+
return unless text =~ /^include::([^\[]+)(\[([^\]]*)\])?/
|
|
41
|
+
|
|
42
|
+
path = ::Regexp.last_match(1).strip
|
|
43
|
+
attrs_str = ::Regexp.last_match(3)
|
|
44
|
+
|
|
45
|
+
attrs = {}
|
|
46
|
+
if attrs_str && !attrs_str.empty?
|
|
47
|
+
# Parse attributes (simplified - doesn't handle complex cases)
|
|
48
|
+
attrs_str.split(',').each do |attr|
|
|
49
|
+
if attr.include?('=')
|
|
50
|
+
k, v = attr.split('=', 2)
|
|
51
|
+
attrs[k.strip.to_sym] = v.strip
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
Coradoc::CoreModel::Block.new(
|
|
57
|
+
element_type: 'include',
|
|
58
|
+
content: path,
|
|
59
|
+
metadata: {
|
|
60
|
+
path: path,
|
|
61
|
+
attributes: attrs
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Html
|
|
5
|
+
module Converters
|
|
6
|
+
# Converter for CoreModel::Image (inline image)
|
|
7
|
+
class InlineImage < Base
|
|
8
|
+
def self.to_html(model, _state = {})
|
|
9
|
+
attrs = {}
|
|
10
|
+
# Remove leading colons from src (source format syntax artifact)
|
|
11
|
+
src = model.src
|
|
12
|
+
src = src.sub(/^:+/, '') if src
|
|
13
|
+
attrs[:src] = src if src
|
|
14
|
+
attrs[:id] = model.id if model.id
|
|
15
|
+
attrs[:alt] = model.alt || ''
|
|
16
|
+
|
|
17
|
+
# Extract additional attributes
|
|
18
|
+
attrs[:width] = model.width if model.width
|
|
19
|
+
attrs[:height] = model.height if model.height
|
|
20
|
+
|
|
21
|
+
build_element('img', nil, attrs)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.to_coradoc(node, _state = {})
|
|
25
|
+
return nil unless node.name == 'img'
|
|
26
|
+
|
|
27
|
+
attrs = extract_attributes(node)
|
|
28
|
+
|
|
29
|
+
Coradoc::CoreModel::Image.new(
|
|
30
|
+
src: attrs[:src],
|
|
31
|
+
id: attrs[:id],
|
|
32
|
+
alt: attrs[:alt],
|
|
33
|
+
width: attrs[:width],
|
|
34
|
+
height: attrs[:height],
|
|
35
|
+
inline: true
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Html
|
|
5
|
+
module Converters
|
|
6
|
+
# Converter for Italic inline element
|
|
7
|
+
class Italic < Base
|
|
8
|
+
class << self
|
|
9
|
+
# Convert HTML <em> or <i> to CoreModel::InlineElement
|
|
10
|
+
# @param node [Nokogiri::XML::Node] HTML node
|
|
11
|
+
# @param state [Hash] Conversion state
|
|
12
|
+
# @return [Coradoc::CoreModel::InlineElement] Italic inline element
|
|
13
|
+
def to_coradoc(node, state = {})
|
|
14
|
+
content = treat_children(node, state)
|
|
15
|
+
Coradoc::CoreModel::InlineElement.new(
|
|
16
|
+
format_type: 'italic',
|
|
17
|
+
content: content
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Convert CoreModel::InlineElement (italic) to HTML <em>
|
|
22
|
+
# @param model [Coradoc::CoreModel::InlineElement] Italic 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('em', content, attributes)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Html
|
|
5
|
+
module Converters
|
|
6
|
+
# Converter for LineBreak inline element
|
|
7
|
+
class LineBreak < Base
|
|
8
|
+
class << self
|
|
9
|
+
# Convert HTML <br> to CoreModel::InlineElement (break)
|
|
10
|
+
# @param node [Nokogiri::XML::Node] HTML node
|
|
11
|
+
# @param state [Hash] Conversion state
|
|
12
|
+
# @return [Coradoc::CoreModel::InlineElement] LineBreak inline element
|
|
13
|
+
def to_coradoc(_node, _state = {})
|
|
14
|
+
Coradoc::CoreModel::InlineElement.new(
|
|
15
|
+
format_type: 'break',
|
|
16
|
+
metadata: { break_type: 'line' }
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Convert CoreModel::InlineElement (break with line type) to HTML <br>
|
|
21
|
+
# @param model [Coradoc::CoreModel::InlineElement] LineBreak model
|
|
22
|
+
# @param state [Hash] Conversion state
|
|
23
|
+
# @return [String] HTML string
|
|
24
|
+
def to_html(_model, _state = {})
|
|
25
|
+
build_element('br')
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Html
|
|
5
|
+
module Converters
|
|
6
|
+
class Link < Base
|
|
7
|
+
def self.to_html(model, _state = {})
|
|
8
|
+
# Handle CoreModel::InlineElement with format_type "link"
|
|
9
|
+
attrs = {}
|
|
10
|
+
attrs[:href] = model.target if model.target
|
|
11
|
+
attrs[:id] = model.id if model.id
|
|
12
|
+
|
|
13
|
+
# Get title from metadata
|
|
14
|
+
if model.metadata && model.metadata[:title]
|
|
15
|
+
attrs[:title] =
|
|
16
|
+
model.metadata[:title]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Determine link text - use content or target
|
|
20
|
+
text = model.content || model.target || ''
|
|
21
|
+
build_element('a', text, attrs)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.to_coradoc(node, _state = {})
|
|
25
|
+
return nil unless node.name == 'a'
|
|
26
|
+
|
|
27
|
+
attrs = extract_attributes(node)
|
|
28
|
+
href = attrs[:href] || ''
|
|
29
|
+
title = attrs[:title]
|
|
30
|
+
text = node.text
|
|
31
|
+
|
|
32
|
+
# If text equals href, don't set text (empty link text)
|
|
33
|
+
text = nil if text == href
|
|
34
|
+
|
|
35
|
+
Coradoc::CoreModel::InlineElement.new(
|
|
36
|
+
format_type: 'link',
|
|
37
|
+
target: href,
|
|
38
|
+
content: text,
|
|
39
|
+
metadata: { title: title }.compact,
|
|
40
|
+
id: attrs[:id]
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Html
|
|
5
|
+
module Converters
|
|
6
|
+
# Converter for ListItem
|
|
7
|
+
class ListItem < Base
|
|
8
|
+
class << self
|
|
9
|
+
# Convert HTML <li> to CoreModel::ListItem
|
|
10
|
+
# @param node [Nokogiri::XML::Node] HTML node
|
|
11
|
+
# @param state [Hash] Conversion state
|
|
12
|
+
# @return [Coradoc::CoreModel::ListItem] ListItem model
|
|
13
|
+
def to_coradoc(node, state = {})
|
|
14
|
+
attrs = extract_node_attributes(node)
|
|
15
|
+
|
|
16
|
+
item = Coradoc::CoreModel::ListItem.new
|
|
17
|
+
item.id = attrs[:id] if attrs[:id]
|
|
18
|
+
|
|
19
|
+
# Extract content and nested lists
|
|
20
|
+
content_nodes = []
|
|
21
|
+
nested_list = nil
|
|
22
|
+
|
|
23
|
+
node.children.each do |child|
|
|
24
|
+
case child.name
|
|
25
|
+
when 'ul', 'ol'
|
|
26
|
+
# This is a nested list
|
|
27
|
+
nested_list = convert_node_to_core(child, state)
|
|
28
|
+
else
|
|
29
|
+
content_nodes << child unless child.text.strip.empty? && child.name == 'text'
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Convert content nodes - collect as mixed content array
|
|
34
|
+
if content_nodes.any?
|
|
35
|
+
content = content_nodes.flat_map { |n| convert_node_to_core(n, state) }.compact
|
|
36
|
+
# Store as children for mixed content
|
|
37
|
+
item.children = content if content.any?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
item.nested = nested_list if nested_list
|
|
41
|
+
|
|
42
|
+
item
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Convert CoreModel::ListItem to HTML <li>
|
|
46
|
+
# @param model [Coradoc::CoreModel::ListItem] ListItem model
|
|
47
|
+
# @param state [Hash] Conversion state
|
|
48
|
+
# @return [String] HTML string
|
|
49
|
+
def to_html(model, state = {})
|
|
50
|
+
parts = []
|
|
51
|
+
|
|
52
|
+
# Convert main content - check children first (mixed content), then content
|
|
53
|
+
content_to_render = model.children&.any? ? model.children : model.content
|
|
54
|
+
parts << convert_content_to_html(content_to_render, state) if content_to_render
|
|
55
|
+
|
|
56
|
+
# Convert attached content
|
|
57
|
+
if model.attached && !model.attached.empty?
|
|
58
|
+
model.attached.each do |attached_item|
|
|
59
|
+
parts << convert_content_to_html(attached_item, state)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Convert nested list
|
|
64
|
+
parts << convert_content_to_html(model.nested, state) if model.nested
|
|
65
|
+
|
|
66
|
+
attrs = {}
|
|
67
|
+
attrs[:id] = model.id if model.id
|
|
68
|
+
|
|
69
|
+
build_element('li', parts.join("\n"), attrs)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Html
|
|
5
|
+
module Converters
|
|
6
|
+
# Converter for CoreModel::Block (listing) to HTML <pre>
|
|
7
|
+
class Listing < Base
|
|
8
|
+
# Convert CoreModel::Block (listing) to HTML <pre>
|
|
9
|
+
def self.to_html(listing, _options = {})
|
|
10
|
+
return '' unless listing
|
|
11
|
+
|
|
12
|
+
# Build pre attributes
|
|
13
|
+
attrs = build_attributes(listing)
|
|
14
|
+
|
|
15
|
+
# Build title if present
|
|
16
|
+
title_html = build_title(listing)
|
|
17
|
+
|
|
18
|
+
# Process listing content - preserve formatting
|
|
19
|
+
content = process_content(listing.content)
|
|
20
|
+
|
|
21
|
+
# Combine title and content
|
|
22
|
+
listing_html = ''
|
|
23
|
+
listing_html += "#{title_html}\n" if title_html
|
|
24
|
+
listing_html += %(<pre#{attrs}>#{content}</pre>)
|
|
25
|
+
|
|
26
|
+
if title_html
|
|
27
|
+
%(<div class="listing-block">\n#{listing_html}\n</div>)
|
|
28
|
+
else
|
|
29
|
+
listing_html
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Convert HTML <pre> to CoreModel::Block (listing)
|
|
34
|
+
def self.to_coradoc(element, _options = {})
|
|
35
|
+
# Handle both <pre> and <div class="listing-block"><pre>
|
|
36
|
+
pre_elem = if element.name == 'div' && element['class']&.include?('listing-block')
|
|
37
|
+
element.at_css('pre')
|
|
38
|
+
elsif element.name == 'pre'
|
|
39
|
+
element
|
|
40
|
+
else
|
|
41
|
+
return nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
return nil unless pre_elem
|
|
45
|
+
|
|
46
|
+
# Extract title if in listing-block wrapper
|
|
47
|
+
title = if element.name == 'div'
|
|
48
|
+
title_elem = element.at_css('.listing-title')
|
|
49
|
+
title_elem&.text&.strip
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Extract content
|
|
53
|
+
content = pre_elem.text
|
|
54
|
+
|
|
55
|
+
# Extract ID if present
|
|
56
|
+
id = pre_elem['id'] || element['id']
|
|
57
|
+
|
|
58
|
+
Coradoc::CoreModel::SourceBlock.new(
|
|
59
|
+
content: content,
|
|
60
|
+
title: title,
|
|
61
|
+
id: id
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def self.build_attributes(listing)
|
|
66
|
+
attrs = [%( class="listing")]
|
|
67
|
+
|
|
68
|
+
# Add ID if present
|
|
69
|
+
attrs << %( id="#{escape_attribute(listing.id)}") if listing.id
|
|
70
|
+
|
|
71
|
+
attrs.join
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def self.build_title(listing)
|
|
75
|
+
return nil unless listing.title
|
|
76
|
+
|
|
77
|
+
title_text = listing.title.to_s
|
|
78
|
+
return nil if title_text.empty?
|
|
79
|
+
|
|
80
|
+
%(<div class="listing-title">#{escape_html(title_text)}</div>)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def self.process_content(content)
|
|
84
|
+
return '' if content.nil?
|
|
85
|
+
|
|
86
|
+
# For listing, preserve the content as-is
|
|
87
|
+
if content.is_a?(String)
|
|
88
|
+
escape_html(content)
|
|
89
|
+
elsif content.is_a?(Array)
|
|
90
|
+
# Join array items with newlines
|
|
91
|
+
content.map { |line| escape_html(line.to_s) }.join("\n")
|
|
92
|
+
else
|
|
93
|
+
escape_html(content.to_s)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Html
|
|
5
|
+
module Converters
|
|
6
|
+
# Converter for CoreModel::Block (literal) to HTML <pre>
|
|
7
|
+
class Literal < Base
|
|
8
|
+
# Convert CoreModel::Block (literal) to HTML <pre>
|
|
9
|
+
def self.to_html(literal, _options = {})
|
|
10
|
+
return '' unless literal
|
|
11
|
+
|
|
12
|
+
# Build pre attributes
|
|
13
|
+
attrs = build_attributes(literal)
|
|
14
|
+
|
|
15
|
+
# Build title if present
|
|
16
|
+
title_html = build_title(literal)
|
|
17
|
+
|
|
18
|
+
# Process literal content - preserve exact formatting
|
|
19
|
+
content = process_content(literal.content)
|
|
20
|
+
|
|
21
|
+
# Combine title and content
|
|
22
|
+
literal_html = ''
|
|
23
|
+
literal_html += "#{title_html}\n" if title_html
|
|
24
|
+
literal_html += %(<pre#{attrs}>#{content}</pre>)
|
|
25
|
+
|
|
26
|
+
if title_html
|
|
27
|
+
%(<div class="literal-block">\n#{literal_html}\n</div>)
|
|
28
|
+
else
|
|
29
|
+
literal_html
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Convert HTML <pre> to CoreModel::Block (literal)
|
|
34
|
+
def self.to_coradoc(element, _options = {})
|
|
35
|
+
# Handle both <pre> and <div class="literal-block"><pre>
|
|
36
|
+
pre_elem = if element.name == 'div' && element['class']&.include?('literal-block')
|
|
37
|
+
element.at_css('pre')
|
|
38
|
+
elsif element.name == 'pre'
|
|
39
|
+
# Only convert if it's a literal (no code class)
|
|
40
|
+
return nil if element['class']&.include?('code')
|
|
41
|
+
|
|
42
|
+
element
|
|
43
|
+
else
|
|
44
|
+
return nil
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
return nil unless pre_elem
|
|
48
|
+
|
|
49
|
+
# Extract title if in literal-block wrapper
|
|
50
|
+
title = if element.name == 'div'
|
|
51
|
+
title_elem = element.at_css('.literal-title')
|
|
52
|
+
title_elem&.text&.strip
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Extract content
|
|
56
|
+
content = pre_elem.text
|
|
57
|
+
|
|
58
|
+
# Extract ID if present
|
|
59
|
+
id = pre_elem['id'] || element['id']
|
|
60
|
+
|
|
61
|
+
Coradoc::CoreModel::LiteralBlock.new(
|
|
62
|
+
content: content,
|
|
63
|
+
title: title,
|
|
64
|
+
id: id
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def self.build_attributes(literal)
|
|
69
|
+
attrs = [%( class="literal")]
|
|
70
|
+
|
|
71
|
+
# Add ID if present
|
|
72
|
+
attrs << %( id="#{escape_attribute(literal.id)}") if literal.id
|
|
73
|
+
|
|
74
|
+
attrs.join
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def self.build_title(literal)
|
|
78
|
+
return nil unless literal.title
|
|
79
|
+
|
|
80
|
+
title_text = literal.title.to_s
|
|
81
|
+
return nil if title_text.empty?
|
|
82
|
+
|
|
83
|
+
%(<div class="literal-title">#{escape_html(title_text)}</div>)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def self.process_content(content)
|
|
87
|
+
return '' if content.nil?
|
|
88
|
+
|
|
89
|
+
# For literal, preserve the content exactly as-is
|
|
90
|
+
if content.is_a?(String)
|
|
91
|
+
escape_html(content)
|
|
92
|
+
elsif content.is_a?(Array)
|
|
93
|
+
# Join array items with newlines
|
|
94
|
+
content.map { |line| escape_html(line.to_s) }.join("\n")
|
|
95
|
+
else
|
|
96
|
+
escape_html(content.to_s)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Html
|
|
5
|
+
module Converters
|
|
6
|
+
# Converter for Monospace inline element
|
|
7
|
+
class Monospace < Base
|
|
8
|
+
class << self
|
|
9
|
+
# Convert HTML <code> to CoreModel::InlineElement
|
|
10
|
+
# @param node [Nokogiri::XML::Node] HTML node
|
|
11
|
+
# @param state [Hash] Conversion state
|
|
12
|
+
# @return [Coradoc::CoreModel::InlineElement] Monospace inline element
|
|
13
|
+
def to_coradoc(node, state = {})
|
|
14
|
+
content = treat_children(node, state)
|
|
15
|
+
Coradoc::CoreModel::InlineElement.new(
|
|
16
|
+
format_type: 'monospace',
|
|
17
|
+
content: content
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Convert CoreModel::InlineElement (monospace) to HTML <code>
|
|
22
|
+
# @param model [Coradoc::CoreModel::InlineElement] Monospace 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('code', content, attributes)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|