poml 0.0.1 → 0.0.3
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 +4 -4
- data/lib/poml/components/base.rb +45 -7
- data/lib/poml/components/data.rb +310 -12
- data/lib/poml/components/examples.rb +159 -13
- data/lib/poml/components/formatting.rb +148 -0
- data/lib/poml/components/media.rb +34 -0
- data/lib/poml/components/meta.rb +248 -0
- data/lib/poml/components/template.rb +334 -0
- data/lib/poml/components/utilities.rb +580 -0
- data/lib/poml/components.rb +93 -2
- data/lib/poml/context.rb +41 -2
- data/lib/poml/parser.rb +128 -15
- data/lib/poml/renderer.rb +26 -7
- data/lib/poml/template_engine.rb +101 -4
- data/lib/poml/version.rb +1 -1
- data/lib/poml.rb +67 -1
- data/{README.md → readme.md} +9 -1
- metadata +8 -4
- data/examples/_generate_expects.py +0 -35
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 57f8a3904a43267d79cbc6fb76465dd51c05da5923e58635304242eb90b04b96
|
4
|
+
data.tar.gz: 591512861013d2ae3d302b342b4678696a4d3691c98a26a2d9cdcd042ec70422
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ba9aeb9b1e06de010b5daa9489d847dc41f5f5d73a9a02e70d81432e997b1c3a3a32116cf9d85ee2f4c70755024201272e62951381cf6363be59dc807c6c55b0
|
7
|
+
data.tar.gz: 2f763c41a44b095f64fbe75cc64f58af266b50c8d966e0d44e6c382945a21fdde9abdd83a49b17ab57608c90a8481fb3066eca6dbc3633533e16269af1693f61
|
data/lib/poml/components/base.rb
CHANGED
@@ -16,7 +16,8 @@ module Poml
|
|
16
16
|
|
17
17
|
def apply_stylesheet
|
18
18
|
# Apply stylesheet rules to the element
|
19
|
-
|
19
|
+
stylesheet = @context.respond_to?(:stylesheet) ? @context.stylesheet : {}
|
20
|
+
style_rules = stylesheet[@element.tag_name.to_s] || {}
|
20
21
|
style_rules.each do |attr, value|
|
21
22
|
@element.attributes[attr] ||= value
|
22
23
|
end
|
@@ -24,7 +25,7 @@ module Poml
|
|
24
25
|
# Apply class-based styles
|
25
26
|
class_name = @element.attributes['classname'] || @element.attributes['className']
|
26
27
|
if class_name
|
27
|
-
class_rules =
|
28
|
+
class_rules = stylesheet[".#{class_name}"] || {}
|
28
29
|
class_rules.each do |attr, value|
|
29
30
|
@element.attributes[attr] ||= value
|
30
31
|
end
|
@@ -32,7 +33,11 @@ module Poml
|
|
32
33
|
end
|
33
34
|
|
34
35
|
def xml_mode?
|
35
|
-
@context.determine_syntax
|
36
|
+
if @context.respond_to?(:determine_syntax)
|
37
|
+
@context.determine_syntax(@element) == 'xml'
|
38
|
+
else
|
39
|
+
false
|
40
|
+
end
|
36
41
|
end
|
37
42
|
|
38
43
|
def render_as_xml(tag_name, content = nil, attributes = {})
|
@@ -81,13 +86,17 @@ module Poml
|
|
81
86
|
rendered_children.each_with_index do |child_content, index|
|
82
87
|
result << child_content
|
83
88
|
|
84
|
-
# Add spacing if current element is text and next element is a component
|
89
|
+
# Add spacing if current element is text and next element is a block-level component
|
85
90
|
if index < rendered_children.length - 1
|
86
91
|
current_element = @element.children[index]
|
87
92
|
next_element = @element.children[index + 1]
|
88
93
|
|
94
|
+
# Only add spacing for block-level components, not inline components
|
89
95
|
if current_element.text? && next_element.component?
|
90
|
-
|
96
|
+
next_component_class = Components::COMPONENT_MAPPING[next_element.tag_name]
|
97
|
+
if next_component_class && !is_inline_component?(next_component_class)
|
98
|
+
result << "\n\n"
|
99
|
+
end
|
91
100
|
end
|
92
101
|
end
|
93
102
|
end
|
@@ -102,8 +111,9 @@ module Poml
|
|
102
111
|
component_name = self.class.name.split('::').last.gsub('Component', '').downcase
|
103
112
|
|
104
113
|
# Check for text transformation in stylesheet - first try component-specific, then "cp" (for captioned paragraph inheritance)
|
105
|
-
|
106
|
-
|
114
|
+
stylesheet = @context.respond_to?(:stylesheet) ? @context.stylesheet : {}
|
115
|
+
transform = stylesheet.dig(component_name, 'captionTextTransform') ||
|
116
|
+
stylesheet.dig('cp', 'captionTextTransform')
|
107
117
|
|
108
118
|
case transform
|
109
119
|
when 'upper'
|
@@ -116,6 +126,34 @@ module Poml
|
|
116
126
|
text
|
117
127
|
end
|
118
128
|
end
|
129
|
+
|
130
|
+
def inline_component?
|
131
|
+
# Override this in inline components
|
132
|
+
false
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.inline_component_classes
|
136
|
+
# List of component classes that should be treated as inline
|
137
|
+
@inline_component_classes ||= []
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.register_inline_component(component_class)
|
141
|
+
inline_component_classes << component_class
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def is_inline_component?(component_class)
|
147
|
+
# Check if the component class is registered as inline
|
148
|
+
# For now, we'll use a simple list of known inline formatting components
|
149
|
+
inline_component_names = %w[
|
150
|
+
BoldComponent ItalicComponent UnderlineComponent StrikethroughComponent
|
151
|
+
CodeComponent InlineComponent
|
152
|
+
]
|
153
|
+
|
154
|
+
component_class_name = component_class.name.split('::').last
|
155
|
+
inline_component_names.include?(component_class_name)
|
156
|
+
end
|
119
157
|
end
|
120
158
|
|
121
159
|
# Component registry and factory
|
data/lib/poml/components/data.rb
CHANGED
@@ -12,16 +12,19 @@ module Poml
|
|
12
12
|
columns_attr = get_attribute('columns')
|
13
13
|
parser = get_attribute('parser', 'auto')
|
14
14
|
syntax = get_attribute('syntax')
|
15
|
-
selected_columns =
|
16
|
-
selected_records =
|
17
|
-
max_records =
|
18
|
-
max_columns =
|
15
|
+
selected_columns = parse_array_attribute('selectedColumns')
|
16
|
+
selected_records = parse_array_attribute('selectedRecords')
|
17
|
+
max_records = parse_integer_attribute('maxRecords')
|
18
|
+
max_columns = parse_integer_attribute('maxColumns')
|
19
19
|
|
20
20
|
# Load data from source or use provided records
|
21
21
|
data = if src
|
22
22
|
load_table_data(src, parser)
|
23
23
|
elsif records_attr
|
24
24
|
parse_records_attribute(records_attr)
|
25
|
+
elsif @element.children.any? { |child| child.tag_name == :tr }
|
26
|
+
# Handle HTML-style table markup
|
27
|
+
parse_html_table_children
|
25
28
|
else
|
26
29
|
{ records: [], columns: [] }
|
27
30
|
end
|
@@ -133,7 +136,12 @@ module Poml
|
|
133
136
|
def parse_records_attribute(records_attr)
|
134
137
|
# Handle string records (JSON) or already parsed arrays
|
135
138
|
records = if records_attr.is_a?(String)
|
136
|
-
|
139
|
+
begin
|
140
|
+
JSON.parse(records_attr)
|
141
|
+
rescue JSON::ParserError => e
|
142
|
+
# Return empty records on parse error
|
143
|
+
return { records: [], columns: [] }
|
144
|
+
end
|
137
145
|
else
|
138
146
|
records_attr
|
139
147
|
end
|
@@ -147,6 +155,71 @@ module Poml
|
|
147
155
|
{ records: records.is_a?(Array) ? records : [records], columns: columns }
|
148
156
|
end
|
149
157
|
|
158
|
+
def parse_array_attribute(attr_name)
|
159
|
+
attr_value = get_attribute(attr_name)
|
160
|
+
return nil unless attr_value
|
161
|
+
|
162
|
+
if attr_value.is_a?(String)
|
163
|
+
begin
|
164
|
+
parsed = JSON.parse(attr_value)
|
165
|
+
parsed.is_a?(Array) ? parsed : nil
|
166
|
+
rescue JSON::ParserError
|
167
|
+
# Try to handle as slice notation
|
168
|
+
attr_value.include?(':') ? attr_value : nil
|
169
|
+
end
|
170
|
+
elsif attr_value.is_a?(Array)
|
171
|
+
attr_value
|
172
|
+
else
|
173
|
+
nil
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def parse_integer_attribute(attr_name)
|
178
|
+
attr_value = get_attribute(attr_name)
|
179
|
+
return nil unless attr_value
|
180
|
+
|
181
|
+
if attr_value.is_a?(String)
|
182
|
+
attr_value.to_i
|
183
|
+
elsif attr_value.is_a?(Integer)
|
184
|
+
attr_value
|
185
|
+
else
|
186
|
+
nil
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def parse_html_table_children
|
191
|
+
records = []
|
192
|
+
columns = []
|
193
|
+
|
194
|
+
# Extract rows from tr children
|
195
|
+
@element.children.each do |child|
|
196
|
+
next unless child.tag_name == :tr
|
197
|
+
|
198
|
+
row_data = {}
|
199
|
+
child.children.each_with_index do |cell, index|
|
200
|
+
next unless cell.tag_name == :td || cell.tag_name == :th
|
201
|
+
|
202
|
+
# Get cell content (render children to get text)
|
203
|
+
cell_content = cell.children.map do |cell_child|
|
204
|
+
Components.render_element(cell_child, @context)
|
205
|
+
end.join('').strip
|
206
|
+
|
207
|
+
# Use cell content as content, index as key for now
|
208
|
+
column_key = "col_#{index}"
|
209
|
+
row_data[column_key] = cell_content
|
210
|
+
|
211
|
+
# Track columns
|
212
|
+
unless columns.any? { |col| col[:field] == column_key }
|
213
|
+
columns << { field: column_key, header: "Column #{index + 1}" }
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
records << row_data unless row_data.empty?
|
218
|
+
end
|
219
|
+
|
220
|
+
{ records: records, columns: columns }
|
221
|
+
end
|
222
|
+
|
150
223
|
def apply_selection(data, selected_columns, selected_records, max_records, max_columns)
|
151
224
|
records = data[:records]
|
152
225
|
columns = data[:columns]
|
@@ -183,13 +256,15 @@ module Poml
|
|
183
256
|
end
|
184
257
|
end
|
185
258
|
|
186
|
-
# Apply max records
|
187
|
-
if max_records && records.length > max_records
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
259
|
+
# Apply max records - simple truncation with ellipsis row
|
260
|
+
if max_records && max_records > 0 && records.length > max_records
|
261
|
+
truncated_records = records[0...max_records]
|
262
|
+
# Add ellipsis row if we truncated
|
263
|
+
if records.length > max_records && columns
|
264
|
+
ellipsis_record = columns.each_with_object({}) { |col, record| record[col[:field]] = '...' }
|
265
|
+
truncated_records << ellipsis_record
|
266
|
+
end
|
267
|
+
records = truncated_records
|
193
268
|
end
|
194
269
|
|
195
270
|
# Apply max columns
|
@@ -327,6 +402,229 @@ module Poml
|
|
327
402
|
end
|
328
403
|
end
|
329
404
|
|
405
|
+
# Object component for displaying structured data
|
406
|
+
class ObjectComponent < Component
|
407
|
+
require 'json'
|
408
|
+
require 'yaml'
|
409
|
+
|
410
|
+
def render
|
411
|
+
apply_stylesheet
|
412
|
+
|
413
|
+
data = get_attribute('data')
|
414
|
+
syntax = get_attribute('syntax', 'json')
|
415
|
+
|
416
|
+
return '' unless data
|
417
|
+
|
418
|
+
if xml_mode?
|
419
|
+
render_as_xml('obj', serialize_data(data, syntax))
|
420
|
+
else
|
421
|
+
serialize_data(data, syntax)
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
private
|
426
|
+
|
427
|
+
def serialize_data(data, syntax)
|
428
|
+
case syntax.downcase
|
429
|
+
when 'json'
|
430
|
+
JSON.pretty_generate(data)
|
431
|
+
when 'yaml', 'yml'
|
432
|
+
YAML.dump(data)
|
433
|
+
when 'xml'
|
434
|
+
# Simple XML serialization for basic data structures
|
435
|
+
serialize_to_xml(data)
|
436
|
+
else
|
437
|
+
data.to_s
|
438
|
+
end
|
439
|
+
rescue => e
|
440
|
+
"[Error serializing data: #{e.message}]"
|
441
|
+
end
|
442
|
+
|
443
|
+
def serialize_to_xml(data, root_name = 'data', indent = 0)
|
444
|
+
spaces = ' ' * indent
|
445
|
+
|
446
|
+
case data
|
447
|
+
when Hash
|
448
|
+
result = ["#{spaces}<#{root_name}>"]
|
449
|
+
data.each do |key, value|
|
450
|
+
result << serialize_to_xml(value, key, indent + 1)
|
451
|
+
end
|
452
|
+
result << "#{spaces}</#{root_name}>"
|
453
|
+
result.join("\n")
|
454
|
+
when Array
|
455
|
+
result = ["#{spaces}<#{root_name}>"]
|
456
|
+
data.each_with_index do |item, index|
|
457
|
+
result << serialize_to_xml(item, "item#{index}", indent + 1)
|
458
|
+
end
|
459
|
+
result << "#{spaces}</#{root_name}>"
|
460
|
+
result.join("\n")
|
461
|
+
else
|
462
|
+
"#{spaces}<#{root_name}>#{escape_xml(data)}</#{root_name}>"
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
def escape_xml(text)
|
467
|
+
text.to_s.gsub('&', '&').gsub('<', '<').gsub('>', '>')
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
# Webpage component for displaying web content
|
472
|
+
class WebpageComponent < Component
|
473
|
+
def render
|
474
|
+
apply_stylesheet
|
475
|
+
|
476
|
+
url = get_attribute('url')
|
477
|
+
src = get_attribute('src')
|
478
|
+
buffer = get_attribute('buffer')
|
479
|
+
base64 = get_attribute('base64')
|
480
|
+
extract_text = get_attribute('extractText', false)
|
481
|
+
selector = get_attribute('selector', 'body')
|
482
|
+
syntax = get_attribute('syntax', 'text')
|
483
|
+
|
484
|
+
content = if url
|
485
|
+
fetch_webpage_content(url, selector, extract_text)
|
486
|
+
elsif src
|
487
|
+
read_html_file(src, selector, extract_text)
|
488
|
+
elsif buffer
|
489
|
+
process_html_content(buffer, selector, extract_text)
|
490
|
+
elsif base64
|
491
|
+
require 'base64'
|
492
|
+
decoded = Base64.decode64(base64)
|
493
|
+
process_html_content(decoded, selector, extract_text)
|
494
|
+
else
|
495
|
+
'[Webpage: no source specified]'
|
496
|
+
end
|
497
|
+
|
498
|
+
if xml_mode?
|
499
|
+
render_as_xml('webpage', content)
|
500
|
+
else
|
501
|
+
content
|
502
|
+
end
|
503
|
+
end
|
504
|
+
|
505
|
+
private
|
506
|
+
|
507
|
+
def fetch_webpage_content(url, selector, extract_text)
|
508
|
+
begin
|
509
|
+
require 'net/http'
|
510
|
+
require 'uri'
|
511
|
+
|
512
|
+
uri = URI.parse(url)
|
513
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
514
|
+
http.use_ssl = true if uri.scheme == 'https'
|
515
|
+
http.read_timeout = 10
|
516
|
+
|
517
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
518
|
+
request['User-Agent'] = 'POML/1.0'
|
519
|
+
|
520
|
+
response = http.request(request)
|
521
|
+
|
522
|
+
if response.code == '200'
|
523
|
+
process_html_content(response.body, selector, extract_text)
|
524
|
+
else
|
525
|
+
"[Webpage: HTTP #{response.code} error fetching #{url}]"
|
526
|
+
end
|
527
|
+
rescue => e
|
528
|
+
"[Webpage: Error fetching #{url}: #{e.message}]"
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
532
|
+
def read_html_file(file_path, selector, extract_text)
|
533
|
+
begin
|
534
|
+
# Resolve relative paths
|
535
|
+
full_path = if file_path.start_with?('/')
|
536
|
+
file_path
|
537
|
+
else
|
538
|
+
base_path = @context.source_path ? File.dirname(@context.source_path) : Dir.pwd
|
539
|
+
File.join(base_path, file_path)
|
540
|
+
end
|
541
|
+
|
542
|
+
unless File.exist?(full_path)
|
543
|
+
return "[Webpage: File not found: #{file_path}]"
|
544
|
+
end
|
545
|
+
|
546
|
+
html_content = File.read(full_path)
|
547
|
+
process_html_content(html_content, selector, extract_text)
|
548
|
+
rescue => e
|
549
|
+
"[Webpage: Error reading file #{file_path}: #{e.message}]"
|
550
|
+
end
|
551
|
+
end
|
552
|
+
|
553
|
+
def process_html_content(html_content, selector, extract_text)
|
554
|
+
begin
|
555
|
+
require 'nokogiri'
|
556
|
+
|
557
|
+
doc = Nokogiri::HTML(html_content)
|
558
|
+
|
559
|
+
# Apply selector if specified
|
560
|
+
if selector && selector != 'body'
|
561
|
+
selected = doc.css(selector).first
|
562
|
+
return "[Webpage: Selector '#{selector}' not found]" unless selected
|
563
|
+
doc = selected
|
564
|
+
end
|
565
|
+
|
566
|
+
if extract_text
|
567
|
+
# Extract plain text
|
568
|
+
doc.text.strip.gsub(/\s+/, ' ')
|
569
|
+
else
|
570
|
+
# Convert HTML to structured POML-like format
|
571
|
+
convert_html_to_poml(doc)
|
572
|
+
end
|
573
|
+
rescue LoadError
|
574
|
+
# Nokogiri not available, do simple text extraction
|
575
|
+
if extract_text
|
576
|
+
html_content.gsub(/<[^>]*>/, ' ').gsub(/\s+/, ' ').strip
|
577
|
+
else
|
578
|
+
html_content
|
579
|
+
end
|
580
|
+
rescue => e
|
581
|
+
"[Webpage: Error processing HTML: #{e.message}]"
|
582
|
+
end
|
583
|
+
end
|
584
|
+
|
585
|
+
def convert_html_to_poml(element)
|
586
|
+
case element.name.downcase
|
587
|
+
when 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'
|
588
|
+
level = element.name[1].to_i
|
589
|
+
"#{('#' * level)} #{element.text.strip}\n\n"
|
590
|
+
when 'p'
|
591
|
+
"#{element.text.strip}\n\n"
|
592
|
+
when 'ul', 'ol'
|
593
|
+
items = element.css('li').map { |li| "- #{li.text.strip}" }
|
594
|
+
"#{items.join("\n")}\n\n"
|
595
|
+
when 'strong', 'b'
|
596
|
+
"**#{element.text.strip}**"
|
597
|
+
when 'em', 'i'
|
598
|
+
"*#{element.text.strip}*"
|
599
|
+
when 'code'
|
600
|
+
"`#{element.text.strip}`"
|
601
|
+
when 'pre'
|
602
|
+
"```\n#{element.text.strip}\n```\n\n"
|
603
|
+
when 'blockquote'
|
604
|
+
lines = element.text.strip.split("\n")
|
605
|
+
quoted = lines.map { |line| "> #{line}" }
|
606
|
+
"#{quoted.join("\n")}\n\n"
|
607
|
+
when 'a'
|
608
|
+
href = element['href']
|
609
|
+
text = element.text.strip
|
610
|
+
href ? "[#{text}](#{href})" : text
|
611
|
+
when 'img'
|
612
|
+
alt = element['alt'] || 'Image'
|
613
|
+
src = element['src']
|
614
|
+
src ? "" : "[#{alt}]"
|
615
|
+
else
|
616
|
+
# For other elements, process children recursively
|
617
|
+
if element.children.any?
|
618
|
+
element.children.map { |child|
|
619
|
+
child.text? ? child.text : convert_html_to_poml(child)
|
620
|
+
}.join('')
|
621
|
+
else
|
622
|
+
element.text.strip
|
623
|
+
end
|
624
|
+
end
|
625
|
+
end
|
626
|
+
end
|
627
|
+
|
330
628
|
# Image component
|
331
629
|
class ImageComponent < Component
|
332
630
|
def render
|
@@ -5,7 +5,38 @@ module Poml
|
|
5
5
|
apply_stylesheet
|
6
6
|
|
7
7
|
content = @element.content.empty? ? render_children : @element.content
|
8
|
-
|
8
|
+
caption = get_attribute('caption', 'Example')
|
9
|
+
caption_style = get_attribute('captionStyle', 'hidden')
|
10
|
+
chat = get_attribute('chat', nil)
|
11
|
+
|
12
|
+
if xml_mode?
|
13
|
+
render_as_xml('example', content)
|
14
|
+
else
|
15
|
+
# Determine if chat format should be used
|
16
|
+
if chat.nil?
|
17
|
+
# Auto-detect: use chat format for markup syntaxes by default
|
18
|
+
use_chat = @context.determine_syntax(@element) != 'xml'
|
19
|
+
else
|
20
|
+
use_chat = chat
|
21
|
+
end
|
22
|
+
|
23
|
+
if use_chat && caption_style == 'hidden'
|
24
|
+
content
|
25
|
+
else
|
26
|
+
case caption_style
|
27
|
+
when 'header'
|
28
|
+
"## #{caption}\n\n#{content}\n\n"
|
29
|
+
when 'bold'
|
30
|
+
"**#{caption}:** #{content}\n\n"
|
31
|
+
when 'plain'
|
32
|
+
"#{caption}: #{content}\n\n"
|
33
|
+
when 'hidden'
|
34
|
+
"#{content}\n\n"
|
35
|
+
else
|
36
|
+
"#{content}\n\n"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
9
40
|
end
|
10
41
|
end
|
11
42
|
|
@@ -15,7 +46,26 @@ module Poml
|
|
15
46
|
apply_stylesheet
|
16
47
|
|
17
48
|
content = @element.content.empty? ? render_children : @element.content
|
18
|
-
|
49
|
+
caption = get_attribute('caption', 'Input')
|
50
|
+
caption_style = get_attribute('captionStyle', 'hidden')
|
51
|
+
speaker = get_attribute('speaker', 'human')
|
52
|
+
|
53
|
+
if xml_mode?
|
54
|
+
render_as_xml('input', content, { speaker: speaker })
|
55
|
+
else
|
56
|
+
case caption_style
|
57
|
+
when 'header'
|
58
|
+
"## #{caption}\n\n#{content}\n\n"
|
59
|
+
when 'bold'
|
60
|
+
"**#{caption}:** #{content}\n\n"
|
61
|
+
when 'plain'
|
62
|
+
"#{caption}: #{content}\n\n"
|
63
|
+
when 'hidden'
|
64
|
+
"#{content}\n\n"
|
65
|
+
else
|
66
|
+
"#{content}\n\n"
|
67
|
+
end
|
68
|
+
end
|
19
69
|
end
|
20
70
|
end
|
21
71
|
|
@@ -25,7 +75,71 @@ module Poml
|
|
25
75
|
apply_stylesheet
|
26
76
|
|
27
77
|
content = @element.content.empty? ? render_children : @element.content
|
28
|
-
|
78
|
+
caption = get_attribute('caption', 'Output')
|
79
|
+
caption_style = get_attribute('captionStyle', 'hidden')
|
80
|
+
speaker = get_attribute('speaker', 'ai')
|
81
|
+
|
82
|
+
if xml_mode?
|
83
|
+
render_as_xml('output', content, { speaker: speaker })
|
84
|
+
else
|
85
|
+
case caption_style
|
86
|
+
when 'header'
|
87
|
+
"## #{caption}\n\n#{content}\n\n"
|
88
|
+
when 'bold'
|
89
|
+
"**#{caption}:** #{content}\n\n"
|
90
|
+
when 'plain'
|
91
|
+
"#{caption}: #{content}\n\n"
|
92
|
+
when 'hidden'
|
93
|
+
"#{content}\n\n"
|
94
|
+
else
|
95
|
+
"#{content}\n\n"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Example set component for managing multiple examples
|
102
|
+
class ExampleSetComponent < Component
|
103
|
+
def render
|
104
|
+
apply_stylesheet
|
105
|
+
|
106
|
+
caption = get_attribute('caption', 'Examples')
|
107
|
+
caption_style = get_attribute('captionStyle', 'header')
|
108
|
+
chat = get_attribute('chat', true)
|
109
|
+
introducer = get_attribute('introducer', '')
|
110
|
+
|
111
|
+
content = @context.with_chat_context(chat) { render_children }
|
112
|
+
|
113
|
+
if xml_mode?
|
114
|
+
render_as_xml('examples', content)
|
115
|
+
else
|
116
|
+
result = []
|
117
|
+
|
118
|
+
case caption_style
|
119
|
+
when 'header'
|
120
|
+
result << "# #{caption}"
|
121
|
+
when 'bold'
|
122
|
+
result << "**#{caption}:**"
|
123
|
+
when 'plain'
|
124
|
+
result << "#{caption}:"
|
125
|
+
when 'hidden'
|
126
|
+
# No caption
|
127
|
+
else
|
128
|
+
result << "# #{caption}"
|
129
|
+
end
|
130
|
+
|
131
|
+
result << "" unless result.empty? # Add blank line after caption
|
132
|
+
|
133
|
+
unless introducer.empty?
|
134
|
+
result << introducer
|
135
|
+
result << ""
|
136
|
+
end
|
137
|
+
|
138
|
+
result << content
|
139
|
+
result << ""
|
140
|
+
|
141
|
+
result.join("\n")
|
142
|
+
end
|
29
143
|
end
|
30
144
|
end
|
31
145
|
|
@@ -38,17 +152,49 @@ module Poml
|
|
38
152
|
caption_style = get_attribute('captionStyle', 'header')
|
39
153
|
content = @element.content.empty? ? render_children : @element.content
|
40
154
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
155
|
+
if xml_mode?
|
156
|
+
render_as_xml('outputFormat', content)
|
157
|
+
else
|
158
|
+
case caption_style
|
159
|
+
when 'header'
|
160
|
+
"# #{caption}\n\n#{content}\n\n"
|
161
|
+
when 'bold'
|
162
|
+
"**#{caption}:** #{content}\n\n"
|
163
|
+
when 'plain'
|
164
|
+
"#{caption}: #{content}\n\n"
|
165
|
+
when 'hidden'
|
166
|
+
"#{content}\n\n"
|
167
|
+
else
|
168
|
+
"# #{caption}\n\n#{content}\n\n"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# Introducer component
|
175
|
+
class IntroducerComponent < Component
|
176
|
+
def render
|
177
|
+
apply_stylesheet
|
178
|
+
|
179
|
+
content = @element.content.empty? ? render_children : @element.content
|
180
|
+
caption = get_attribute('caption', 'Introducer')
|
181
|
+
caption_style = get_attribute('captionStyle', 'hidden')
|
182
|
+
|
183
|
+
if xml_mode?
|
184
|
+
render_as_xml('introducer', content)
|
50
185
|
else
|
51
|
-
|
186
|
+
case caption_style
|
187
|
+
when 'header'
|
188
|
+
"# #{caption}\n\n#{content}\n\n"
|
189
|
+
when 'bold'
|
190
|
+
"**#{caption}:** #{content}\n\n"
|
191
|
+
when 'plain'
|
192
|
+
"#{caption}: #{content}\n\n"
|
193
|
+
when 'hidden'
|
194
|
+
"#{content}\n\n"
|
195
|
+
else
|
196
|
+
"#{content}\n\n"
|
197
|
+
end
|
52
198
|
end
|
53
199
|
end
|
54
200
|
end
|