coradoc-docx 0.1.2 → 0.1.4
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/coradoc/docx/transform/from_core_model.rb +23 -5
- data/lib/coradoc/docx/transform/frontmatter_core_properties.rb +107 -0
- data/lib/coradoc/docx/transform/numbering_access.rb +62 -0
- data/lib/coradoc/docx/transform/rules/list_item_rule.rb +2 -1
- data/lib/coradoc/docx/transform/rules/math_rule.rb +7 -49
- data/lib/coradoc/docx/transform/rules/run_rule.rb +5 -3
- data/lib/coradoc/docx/transform/rules/simple_field_rule.rb +1 -1
- data/lib/coradoc/docx/transform/rules/table_rule.rb +14 -1
- data/lib/coradoc/docx/transform/style_resolver.rb +16 -13
- data/lib/coradoc/docx/transform/to_core_model.rb +7 -5
- data/lib/coradoc/docx/transform.rb +2 -0
- data/lib/coradoc/docx/version.rb +1 -1
- data/lib/coradoc/docx.rb +0 -1
- metadata +8 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 18f25e708f14692cd5a7b48a8636e34df3eee0b5ac3fa8a7f0904a5f56acbeaa
|
|
4
|
+
data.tar.gz: b15177104fe5b8b50635fb6856fa059856c0fb4abfea48efaf15a7bb0cad0a20
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9889857c0c723281bc2c45903c31898b8881d49ff431a7aef699882a74dd1243009f9d1cbd548bc9970f09131b34316743a90d00ee99e6ce9f7509dec7f3276a
|
|
7
|
+
data.tar.gz: b1a47163cdb94d6ecc07d0d0fb4ca1a36eb9539867620f5b7ad861aa7d789fedaaa6018d5731855938ba164b47ae35874d6fb29d028f67e6567221619a2cfdbd
|
|
@@ -89,6 +89,8 @@ module Coradoc
|
|
|
89
89
|
doc = Uniword::Builder::DocumentBuilder.new
|
|
90
90
|
doc.title(element.title) if element.title
|
|
91
91
|
|
|
92
|
+
FrontmatterCoreProperties.apply(doc, FrontmatterCoreProperties.extract(element))
|
|
93
|
+
|
|
92
94
|
transform_children(element.children, doc)
|
|
93
95
|
|
|
94
96
|
doc.build
|
|
@@ -232,6 +234,14 @@ module Coradoc
|
|
|
232
234
|
builder.table do |t|
|
|
233
235
|
table.rows.each do |row|
|
|
234
236
|
t.row do |r|
|
|
237
|
+
# TableRowBuilder#header_row is broken upstream (calls a
|
|
238
|
+
# nonexistent table_header= setter), so set the persisted
|
|
239
|
+
# w:tblHeader property on the row model directly
|
|
240
|
+
if row.header
|
|
241
|
+
r.model.properties = Uniword::Wordprocessingml::TableRowProperties.new(
|
|
242
|
+
tbl_header: Uniword::Wordprocessingml::ValInt.new(value: 1)
|
|
243
|
+
)
|
|
244
|
+
end
|
|
235
245
|
row.cells.each do |cell|
|
|
236
246
|
r.cell(text: cell.flat_text)
|
|
237
247
|
end
|
|
@@ -293,7 +303,9 @@ module Coradoc
|
|
|
293
303
|
|
|
294
304
|
props = Uniword::Wordprocessingml::RunProperties.new
|
|
295
305
|
|
|
296
|
-
|
|
306
|
+
# resolve_format_type covers typed subclasses (BoldElement etc.)
|
|
307
|
+
# that carry no explicit format_type attribute
|
|
308
|
+
case inline.resolve_format_type
|
|
297
309
|
when 'bold'
|
|
298
310
|
props.bold = Uniword::Properties::Bold.new
|
|
299
311
|
when 'italic'
|
|
@@ -353,6 +365,11 @@ module Coradoc
|
|
|
353
365
|
|
|
354
366
|
table.rows.each do |row|
|
|
355
367
|
tr = Uniword::Wordprocessingml::TableRow.new
|
|
368
|
+
if row.header
|
|
369
|
+
tr.properties = Uniword::Wordprocessingml::TableRowProperties.new(
|
|
370
|
+
tbl_header: Uniword::Wordprocessingml::ValInt.new(value: 1)
|
|
371
|
+
)
|
|
372
|
+
end
|
|
356
373
|
row.cells.each do |cell|
|
|
357
374
|
tc = Uniword::Wordprocessingml::TableCell.new
|
|
358
375
|
|
|
@@ -393,8 +410,7 @@ module Coradoc
|
|
|
393
410
|
def build_page_break
|
|
394
411
|
para = Uniword::Wordprocessingml::Paragraph.new
|
|
395
412
|
run = Uniword::Wordprocessingml::Run.new
|
|
396
|
-
run.break = Uniword::Wordprocessingml::Break.new
|
|
397
|
-
run.break.type = 'page'
|
|
413
|
+
run.break = Uniword::Wordprocessingml::Break.new(type: 'page')
|
|
398
414
|
para.runs << run
|
|
399
415
|
para
|
|
400
416
|
end
|
|
@@ -402,7 +418,9 @@ module Coradoc
|
|
|
402
418
|
def build_inline_text(inline)
|
|
403
419
|
text = inline.content.to_s
|
|
404
420
|
|
|
405
|
-
|
|
421
|
+
# resolve_format_type covers typed subclasses (BoldElement etc.)
|
|
422
|
+
# that carry no explicit format_type attribute
|
|
423
|
+
case inline.resolve_format_type
|
|
406
424
|
when 'bold' then Uniword::Builder.text(text, bold: true)
|
|
407
425
|
when 'italic' then Uniword::Builder.text(text, italic: true)
|
|
408
426
|
when 'underline' then Uniword::Builder.text(text, underline: true)
|
|
@@ -413,7 +431,7 @@ module Coradoc
|
|
|
413
431
|
run = Uniword::Wordprocessingml::Run.new(text: text)
|
|
414
432
|
props = Uniword::Wordprocessingml::RunProperties.new
|
|
415
433
|
va = Uniword::Properties::VerticalAlign.new
|
|
416
|
-
va.value = inline.
|
|
434
|
+
va.value = inline.resolve_format_type
|
|
417
435
|
props.vertical_align = va
|
|
418
436
|
run.properties = props
|
|
419
437
|
run
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Docx
|
|
5
|
+
module Transform
|
|
6
|
+
# Single source of truth for FrontmatterBlock -> OOXML core
|
|
7
|
+
# properties mapping. (MECE: DOCX-specific concerns live in the
|
|
8
|
+
# DOCX gem; CoreModel has no knowledge of OOXML.)
|
|
9
|
+
#
|
|
10
|
+
# Uniword's DocumentBuilder exposes setter methods (title, author,
|
|
11
|
+
# subject, keywords, description, date_field) that write the
|
|
12
|
+
# docProps/core.xml Dublin Core elements. This module reads
|
|
13
|
+
# FrontmatterBlock entries and invokes the right setters, so the
|
|
14
|
+
# mapping table lives in exactly one place.
|
|
15
|
+
#
|
|
16
|
+
# Mapping table (single source of truth):
|
|
17
|
+
#
|
|
18
|
+
# | Frontmatter key | OOXML element | Builder method |
|
|
19
|
+
# |-----------------------|----------------------|----------------|
|
|
20
|
+
# | title | dc:title | title |
|
|
21
|
+
# | author | dc:creator | author |
|
|
22
|
+
# | date | dc:date | created |
|
|
23
|
+
# | description, excerpt | dc:description | description |
|
|
24
|
+
# | subject | dc:subject | subject |
|
|
25
|
+
# | tags, categories | cp:keywords | keywords |
|
|
26
|
+
module FrontmatterCoreProperties
|
|
27
|
+
DESCRIPTION_KEYS = %w[description excerpt].freeze
|
|
28
|
+
KEYWORDS_KEYS = %w[tags categories].freeze
|
|
29
|
+
|
|
30
|
+
class << self
|
|
31
|
+
# Apply FrontmatterBlock data to a Uniword DocumentBuilder.
|
|
32
|
+
# Skips nil/empty values. Does not overwrite builder values
|
|
33
|
+
# that the caller has set explicitly (caller invokes this
|
|
34
|
+
# AFTER setting document.title, for example).
|
|
35
|
+
#
|
|
36
|
+
# @param builder [Uniword::Builder::DocumentBuilder]
|
|
37
|
+
# @param block [Coradoc::CoreModel::FrontmatterBlock, nil]
|
|
38
|
+
# @return [void]
|
|
39
|
+
def apply(builder, block)
|
|
40
|
+
return unless block.is_a?(Coradoc::CoreModel::FrontmatterBlock)
|
|
41
|
+
|
|
42
|
+
data = Coradoc::CoreModel::FrontmatterBlock::Codec.to_hash(block)
|
|
43
|
+
|
|
44
|
+
if (v = find_first_scalar(data, KEYWORDS_KEYS))
|
|
45
|
+
builder.keywords(v)
|
|
46
|
+
end
|
|
47
|
+
if (v = find_first_scalar(data, %w[author]))
|
|
48
|
+
builder.author(v)
|
|
49
|
+
end
|
|
50
|
+
if (v = find_first_scalar(data, %w[subject]))
|
|
51
|
+
builder.subject(v)
|
|
52
|
+
end
|
|
53
|
+
if (v = find_first_scalar(data, DESCRIPTION_KEYS))
|
|
54
|
+
builder.description(v)
|
|
55
|
+
end
|
|
56
|
+
if (v = find_first_scalar(data, %w[date]))
|
|
57
|
+
builder.created(v)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Locate the FrontmatterBlock in a DocumentElement's children,
|
|
62
|
+
# if any.
|
|
63
|
+
#
|
|
64
|
+
# @param document [Coradoc::CoreModel::DocumentElement]
|
|
65
|
+
# @return [Coradoc::CoreModel::FrontmatterBlock, nil]
|
|
66
|
+
def extract(document)
|
|
67
|
+
return nil unless document.is_a?(Coradoc::CoreModel::DocumentElement)
|
|
68
|
+
|
|
69
|
+
Array(document.children).find { |c| c.is_a?(Coradoc::CoreModel::FrontmatterBlock) }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def find_first_scalar(data, keys)
|
|
75
|
+
keys.each do |k|
|
|
76
|
+
value = data[k.to_s]
|
|
77
|
+
next if value.nil?
|
|
78
|
+
|
|
79
|
+
str = scalar_value(value)
|
|
80
|
+
return str if str && !str.empty?
|
|
81
|
+
end
|
|
82
|
+
nil
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def scalar_value(value)
|
|
86
|
+
return nil if value.nil?
|
|
87
|
+
|
|
88
|
+
case value
|
|
89
|
+
when String then value
|
|
90
|
+
when Integer, Float, TrueClass, FalseClass then value.to_s
|
|
91
|
+
when Date, Time, DateTime then value.iso8601
|
|
92
|
+
when Symbol then value.to_s
|
|
93
|
+
when Array then array_to_csv(value)
|
|
94
|
+
when Hash then nil
|
|
95
|
+
else value.to_s
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def array_to_csv(value)
|
|
100
|
+
strings = value.map { |i| scalar_value(i) }.compact
|
|
101
|
+
strings.any? ? strings.join(', ') : nil
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Docx
|
|
5
|
+
module Transform
|
|
6
|
+
# Shared access to a paragraph's numbering (list) attributes.
|
|
7
|
+
#
|
|
8
|
+
# uniword persists w:numPr as NESTED properties:
|
|
9
|
+
# paragraph.properties.numbering_properties.num_id.value
|
|
10
|
+
# paragraph.properties.numbering_properties.ilvl.value
|
|
11
|
+
# while the flat integers (properties.num_id / properties.ilvl) are
|
|
12
|
+
# only populated programmatically (builder/YAML API). Readers must
|
|
13
|
+
# consult the nested shape first and fall back to the flat one —
|
|
14
|
+
# documents deserialized from DOCX only ever carry the nested shape.
|
|
15
|
+
#
|
|
16
|
+
# lutaml-model also returns an UninitializedClass sentinel for
|
|
17
|
+
# attributes that were not deserialized from source, so every read
|
|
18
|
+
# is guarded by an Integer type check instead of truthiness.
|
|
19
|
+
module NumberingAccess
|
|
20
|
+
# Numbering definition ID (w:numId) for a paragraph.
|
|
21
|
+
#
|
|
22
|
+
# @param paragraph [Uniword::Wordprocessingml::Paragraph]
|
|
23
|
+
# @return [Integer, nil]
|
|
24
|
+
def paragraph_num_id(paragraph)
|
|
25
|
+
nested_numbering_value(paragraph, :num_id)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Numbering level (w:ilvl) for a paragraph.
|
|
29
|
+
#
|
|
30
|
+
# @param paragraph [Uniword::Wordprocessingml::Paragraph]
|
|
31
|
+
# @return [Integer, nil]
|
|
32
|
+
def paragraph_ilvl(paragraph)
|
|
33
|
+
nested_numbering_value(paragraph, :ilvl)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def nested_numbering_value(paragraph, attribute)
|
|
39
|
+
numbering = paragraph_properties(paragraph)&.numbering_properties
|
|
40
|
+
nested = numbering&.public_send(attribute)
|
|
41
|
+
value = wrapper_value(nested)
|
|
42
|
+
return value if value.is_a?(Integer)
|
|
43
|
+
|
|
44
|
+
# Fall back to the flat builder/YAML attribute
|
|
45
|
+
flat = paragraph_properties(paragraph)&.public_send(attribute)
|
|
46
|
+
flat.is_a?(Integer) ? flat : nil
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def paragraph_properties(paragraph)
|
|
50
|
+
paragraph.properties if paragraph.respond_to?(:properties)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Unwrap w:numId / w:ilvl element models to their value; sentinel
|
|
54
|
+
# objects (UninitializedClass) and nils fail the Integer check.
|
|
55
|
+
def wrapper_value(node)
|
|
56
|
+
value = node.respond_to?(:value) ? node.value : node
|
|
57
|
+
value.is_a?(Integer) ? value : nil
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -17,13 +17,14 @@ module Coradoc
|
|
|
17
17
|
# dispatches directly after checking style_resolver.list_item?.
|
|
18
18
|
class ListItemRule < Rule
|
|
19
19
|
include OrderedContent
|
|
20
|
+
include NumberingAccess
|
|
20
21
|
|
|
21
22
|
def matches?(_element)
|
|
22
23
|
false # Never auto-matched; orchestrator dispatches directly
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
def apply(paragraph, context)
|
|
26
|
-
ilvl = paragraph.
|
|
27
|
+
ilvl = paragraph_ilvl(paragraph).to_i
|
|
27
28
|
|
|
28
29
|
children = transform_paragraph_content(paragraph, context)
|
|
29
30
|
|
|
@@ -6,23 +6,20 @@ module Coradoc
|
|
|
6
6
|
module Rules
|
|
7
7
|
# Transforms OMML math elements to CoreModel.
|
|
8
8
|
#
|
|
9
|
-
# Display math (
|
|
10
|
-
# Inline math (
|
|
9
|
+
# Display math (MathEquation#block?) → CoreModel::Block (stem)
|
|
10
|
+
# Inline math (MathEquation#inline?) → CoreModel::InlineElement (stem)
|
|
11
11
|
#
|
|
12
|
-
#
|
|
13
|
-
# Falls back to raw XML string when Plurimath is not loaded.
|
|
12
|
+
# LaTeX conversion is provided natively by Uniword::MathEquation.
|
|
14
13
|
class MathRule < Rule
|
|
15
14
|
def matches?(element)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
element.is_a?(Uniword::Math::OMathPara) ||
|
|
19
|
-
element.is_a?(Uniword::Math::OMath)
|
|
15
|
+
defined?(Uniword::MathEquation) &&
|
|
16
|
+
element.is_a?(Uniword::MathEquation)
|
|
20
17
|
end
|
|
21
18
|
|
|
22
19
|
def apply(element, _context)
|
|
23
|
-
latex =
|
|
20
|
+
latex = element.to_latex.to_s
|
|
24
21
|
|
|
25
|
-
if
|
|
22
|
+
if element.block?
|
|
26
23
|
CoreModel::PassBlock.new(
|
|
27
24
|
delimiter_type: '++++',
|
|
28
25
|
language: 'latexmath',
|
|
@@ -35,45 +32,6 @@ module Coradoc
|
|
|
35
32
|
)
|
|
36
33
|
end
|
|
37
34
|
end
|
|
38
|
-
|
|
39
|
-
private
|
|
40
|
-
|
|
41
|
-
def display_math?(element)
|
|
42
|
-
defined?(Uniword::Math::OMathPara) &&
|
|
43
|
-
element.is_a?(Uniword::Math::OMathPara)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def omml_to_latex(element)
|
|
47
|
-
if defined?(Plurimath)
|
|
48
|
-
plurimath_to_latex(element)
|
|
49
|
-
else
|
|
50
|
-
# Fallback: serialize to XML string
|
|
51
|
-
element_respond_to_xml(element) || ''
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def plurimath_to_latex(element)
|
|
56
|
-
xml = element_to_xml(element)
|
|
57
|
-
return '' if xml.nil? || xml.empty?
|
|
58
|
-
|
|
59
|
-
begin
|
|
60
|
-
formula = Plurimath::OMML.parse(xml)
|
|
61
|
-
formula.to_latex
|
|
62
|
-
rescue StandardError
|
|
63
|
-
''
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def element_to_xml(element)
|
|
68
|
-
return '' unless element.is_a?(Uniword::Wordprocessingml::AlternateContent) ||
|
|
69
|
-
element.is_a?(Uniword::Wordprocessingml::OMath)
|
|
70
|
-
|
|
71
|
-
element.to_xml
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def element_respond_to_xml(element)
|
|
75
|
-
element_to_xml(element)
|
|
76
|
-
end
|
|
77
35
|
end
|
|
78
36
|
end
|
|
79
37
|
end
|
|
@@ -25,9 +25,11 @@ module Coradoc
|
|
|
25
25
|
def apply(run, context)
|
|
26
26
|
# Delegate non-text children (breaks, drawings, footnotes, etc.)
|
|
27
27
|
non_text = extract_non_text_children(run, context)
|
|
28
|
-
return non_text.first if non_text.any? && run.text.
|
|
28
|
+
return non_text.first if non_text.any? && Array(run.text).empty?
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
# w:r may carry several w:t children (uniword models text as
|
|
31
|
+
# a collection) — join them in document order
|
|
32
|
+
text = Array(run.text).map { |node| node&.content.to_s }.join
|
|
31
33
|
return '' if text.empty? && non_text.empty?
|
|
32
34
|
|
|
33
35
|
props = effective_props(run)
|
|
@@ -54,7 +56,7 @@ module Coradoc
|
|
|
54
56
|
def extract_non_text_children(run, context)
|
|
55
57
|
result = []
|
|
56
58
|
|
|
57
|
-
result
|
|
59
|
+
result.concat(Array(run.break).map { |brk| context.transform(brk) })
|
|
58
60
|
|
|
59
61
|
result << context.transform(run.footnote_reference) if run.footnote_reference
|
|
60
62
|
|
|
@@ -32,10 +32,23 @@ module Coradoc
|
|
|
32
32
|
def transform_row(row, context)
|
|
33
33
|
CoreModel::TableRow.new(
|
|
34
34
|
cells: row.cells.map { |c| transform_cell(c, context) },
|
|
35
|
-
header: row
|
|
35
|
+
header: row_header?(row)
|
|
36
36
|
)
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
+
# The persisted header flag lives in w:trPr/w:tblHeader
|
|
40
|
+
# (row.properties.tbl_header); TableRow#header? is a runtime-only
|
|
41
|
+
# accessor that deserialized documents never populate.
|
|
42
|
+
def row_header?(row)
|
|
43
|
+
tbl_header = row.properties&.tbl_header
|
|
44
|
+
return row.header? unless tbl_header.is_a?(Uniword::Wordprocessingml::ValInt)
|
|
45
|
+
|
|
46
|
+
# <w:tblHeader/> without w:val means "on" (ST_OnOff semantics)
|
|
47
|
+
return true if tbl_header.value.nil?
|
|
48
|
+
|
|
49
|
+
tbl_header.value.to_i.positive?
|
|
50
|
+
end
|
|
51
|
+
|
|
39
52
|
def transform_cell(cell, context)
|
|
40
53
|
inline_children = cell_paragraphs(cell).flat_map do |para|
|
|
41
54
|
extract_inline_from_paragraph(para, context)
|
|
@@ -15,6 +15,8 @@ module Coradoc
|
|
|
15
15
|
# The style map is built from the Uniword StylesConfiguration by walking
|
|
16
16
|
# all style definitions and their basedOn chains.
|
|
17
17
|
class StyleResolver
|
|
18
|
+
include NumberingAccess
|
|
19
|
+
|
|
18
20
|
HEADING_PATTERN = /^(heading|heading|h)\s*(\d+)$/i
|
|
19
21
|
QUOTE_PATTERN = /\bquote\b/i
|
|
20
22
|
CODE_PATTERN = /\b(code|source|listing)\b/i
|
|
@@ -53,14 +55,14 @@ module Coradoc
|
|
|
53
55
|
|
|
54
56
|
ol = paragraph.properties.outline_level
|
|
55
57
|
if ol
|
|
56
|
-
ol_level = ol.is_a?(Uniword::
|
|
58
|
+
ol_level = ol.is_a?(Uniword::Properties::OutlineLevel) ? ol.value.to_i : ol.to_i
|
|
57
59
|
return true if ol_level.positive?
|
|
58
60
|
end
|
|
59
61
|
|
|
60
62
|
style = find_style_for_paragraph(paragraph)
|
|
61
63
|
if style&.outline_level
|
|
62
64
|
ol_val = style.outline_level
|
|
63
|
-
ol_val = ol_val.is_a?(Uniword::
|
|
65
|
+
ol_val = ol_val.is_a?(Uniword::Properties::OutlineLevel) ? ol_val.value.to_i : ol_val.to_i
|
|
64
66
|
return true if ol_val.positive?
|
|
65
67
|
end
|
|
66
68
|
|
|
@@ -80,7 +82,7 @@ module Coradoc
|
|
|
80
82
|
# Check outline_level on paragraph properties
|
|
81
83
|
ol = paragraph.properties&.outline_level
|
|
82
84
|
if ol
|
|
83
|
-
level = ol.is_a?(Uniword::
|
|
85
|
+
level = ol.is_a?(Uniword::Properties::OutlineLevel) ? ol.value.to_i : ol.to_i
|
|
84
86
|
return level if level.positive?
|
|
85
87
|
end
|
|
86
88
|
|
|
@@ -93,8 +95,10 @@ module Coradoc
|
|
|
93
95
|
def list_item?(paragraph)
|
|
94
96
|
return false unless paragraph.properties
|
|
95
97
|
|
|
96
|
-
|
|
97
|
-
num_id
|
|
98
|
+
# w:numPr is persisted as nested numbering_properties; the flat
|
|
99
|
+
# num_id is only set by the builder/YAML API (see NumberingAccess)
|
|
100
|
+
num_id = paragraph_num_id(paragraph)
|
|
101
|
+
!num_id.nil? && num_id.positive?
|
|
98
102
|
end
|
|
99
103
|
|
|
100
104
|
# Check if paragraph has a specific role based on style name
|
|
@@ -133,10 +137,7 @@ module Coradoc
|
|
|
133
137
|
private
|
|
134
138
|
|
|
135
139
|
def resolve_style_name(paragraph)
|
|
136
|
-
|
|
137
|
-
return nil unless style_ref
|
|
138
|
-
|
|
139
|
-
value = style_ref.is_a?(Uniword::Wordprocessingml::PStyle) ? style_ref.val : style_ref.to_s
|
|
140
|
+
value = paragraph_style_value(paragraph)
|
|
140
141
|
return nil unless value
|
|
141
142
|
|
|
142
143
|
mapped = @style_map[value]
|
|
@@ -149,7 +150,7 @@ module Coradoc
|
|
|
149
150
|
style_ref = run.properties.style
|
|
150
151
|
return nil unless style_ref
|
|
151
152
|
|
|
152
|
-
value = style_ref.
|
|
153
|
+
value = style_ref.value.to_s
|
|
153
154
|
return nil unless value
|
|
154
155
|
|
|
155
156
|
mapped = @style_map[value]
|
|
@@ -168,10 +169,12 @@ module Coradoc
|
|
|
168
169
|
end
|
|
169
170
|
|
|
170
171
|
def style_id_from_paragraph(paragraph)
|
|
171
|
-
|
|
172
|
-
|
|
172
|
+
paragraph_style_value(paragraph)
|
|
173
|
+
end
|
|
173
174
|
|
|
174
|
-
|
|
175
|
+
# uniword models w:pStyle as a collection of StyleReference
|
|
176
|
+
def paragraph_style_value(paragraph)
|
|
177
|
+
Array(paragraph.properties&.style).filter_map(&:value).first.to_s
|
|
175
178
|
end
|
|
176
179
|
|
|
177
180
|
def build_style_map(config)
|
|
@@ -24,6 +24,8 @@ module Coradoc
|
|
|
24
24
|
# core = ToCoreModel.transform(doc)
|
|
25
25
|
# # => Coradoc::CoreModel::StructuralElement
|
|
26
26
|
class ToCoreModel
|
|
27
|
+
include NumberingAccess
|
|
28
|
+
|
|
27
29
|
class << self
|
|
28
30
|
def transform(document)
|
|
29
31
|
new.transform(document)
|
|
@@ -139,7 +141,7 @@ module Coradoc
|
|
|
139
141
|
|
|
140
142
|
def section_break_element(paragraph, context)
|
|
141
143
|
# First, transform the paragraph content if it has text
|
|
142
|
-
content = paragraph.runs&.map { |r| r.text
|
|
144
|
+
content = paragraph.runs&.map { |r| Array(r.text).map(&:to_s).join }&.join
|
|
143
145
|
if content && !content.strip.empty?
|
|
144
146
|
# Has content — transform normally (content comes before the break)
|
|
145
147
|
context.transform(paragraph)
|
|
@@ -154,7 +156,7 @@ module Coradoc
|
|
|
154
156
|
# Collect consecutive list items with the same numId into a ListBlock
|
|
155
157
|
def group_list(elements, start_index, context)
|
|
156
158
|
first = elements[start_index]
|
|
157
|
-
num_id = first.
|
|
159
|
+
num_id = paragraph_num_id(first).to_i
|
|
158
160
|
items = []
|
|
159
161
|
consumed = 0
|
|
160
162
|
|
|
@@ -163,7 +165,7 @@ module Coradoc
|
|
|
163
165
|
para = elements[idx]
|
|
164
166
|
break unless paragraph?(para)
|
|
165
167
|
break unless context.style_resolver.list_item?(para)
|
|
166
|
-
break unless para.
|
|
168
|
+
break unless paragraph_num_id(para).to_i == num_id
|
|
167
169
|
|
|
168
170
|
items << @list_item_rule.apply(para, context)
|
|
169
171
|
consumed += 1
|
|
@@ -242,7 +244,7 @@ module Coradoc
|
|
|
242
244
|
next unless context.style_resolver.heading_level(para) == 1
|
|
243
245
|
|
|
244
246
|
runs = para.runs || []
|
|
245
|
-
return runs.map { |r| r.text
|
|
247
|
+
return runs.map { |r| Array(r.text).map(&:to_s).join }.join
|
|
246
248
|
end
|
|
247
249
|
|
|
248
250
|
nil
|
|
@@ -312,7 +314,7 @@ module Coradoc
|
|
|
312
314
|
runs = para.runs || []
|
|
313
315
|
return nil unless runs
|
|
314
316
|
|
|
315
|
-
runs.map { |r| r.text
|
|
317
|
+
runs.map { |r| Array(r.text).map(&:to_s).join }.join
|
|
316
318
|
end
|
|
317
319
|
|
|
318
320
|
# Check if header/footer text is purely layout content
|
|
@@ -10,7 +10,9 @@ module Coradoc
|
|
|
10
10
|
autoload :FromCoreModel, 'coradoc/docx/transform/from_core_model'
|
|
11
11
|
autoload :StyleResolver, 'coradoc/docx/transform/style_resolver'
|
|
12
12
|
autoload :NumberingResolver, 'coradoc/docx/transform/numbering_resolver'
|
|
13
|
+
autoload :NumberingAccess, 'coradoc/docx/transform/numbering_access'
|
|
13
14
|
autoload :OrderedContent, 'coradoc/docx/transform/ordered_content'
|
|
15
|
+
autoload :FrontmatterCoreProperties, 'coradoc/docx/transform/frontmatter_core_properties'
|
|
14
16
|
|
|
15
17
|
# Element transform rules
|
|
16
18
|
module Rules
|
data/lib/coradoc/docx/version.rb
CHANGED
data/lib/coradoc/docx.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: coradoc-docx
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -41,16 +41,16 @@ dependencies:
|
|
|
41
41
|
name: uniword
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
43
43
|
requirements:
|
|
44
|
-
- - "
|
|
44
|
+
- - "~>"
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: '
|
|
46
|
+
version: '1.6'
|
|
47
47
|
type: :runtime
|
|
48
48
|
prerelease: false
|
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
50
|
requirements:
|
|
51
|
-
- - "
|
|
51
|
+
- - "~>"
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: '
|
|
53
|
+
version: '1.6'
|
|
54
54
|
- !ruby/object:Gem::Dependency
|
|
55
55
|
name: rake
|
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -108,6 +108,8 @@ files:
|
|
|
108
108
|
- lib/coradoc/docx/transform.rb
|
|
109
109
|
- lib/coradoc/docx/transform/context.rb
|
|
110
110
|
- lib/coradoc/docx/transform/from_core_model.rb
|
|
111
|
+
- lib/coradoc/docx/transform/frontmatter_core_properties.rb
|
|
112
|
+
- lib/coradoc/docx/transform/numbering_access.rb
|
|
111
113
|
- lib/coradoc/docx/transform/numbering_resolver.rb
|
|
112
114
|
- lib/coradoc/docx/transform/ordered_content.rb
|
|
113
115
|
- lib/coradoc/docx/transform/rule.rb
|
|
@@ -136,6 +138,7 @@ licenses:
|
|
|
136
138
|
metadata:
|
|
137
139
|
homepage_uri: https://github.com/lutaml/coradoc
|
|
138
140
|
source_code_uri: https://github.com/lutaml/coradoc
|
|
141
|
+
rubygems_mfa_required: 'true'
|
|
139
142
|
rdoc_options: []
|
|
140
143
|
require_paths:
|
|
141
144
|
- lib
|