coradoc-docx 0.1.0 → 0.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3c3d4bb0be28a10758044449cbd23105ebf3b954813322561ae3f338af085e4a
4
- data.tar.gz: 8fed48bf56249e71de9e8117f4345f25f6828558c3a1067ee37a786fa52e8506
3
+ metadata.gz: b2d3e5bd2f76d67000623cf3a2cb3889d5b49105a7420494f6f09622bac8a61b
4
+ data.tar.gz: 2c611f389f427ac79c73b5ce6e727e602b1d392f97f6090af837d2c6a5d05d9a
5
5
  SHA512:
6
- metadata.gz: b9ea07f47f7468155c3c3aa4e502284193c54609006ffb2768659fca3f0e77e8abd4edb7274bf07a6cf50f06c5922bd119d966f9ba5dfa1de419c65833619537
7
- data.tar.gz: f323b453647188ac9ffb716c82263e9bb13bce2ca24efdb6eead3562f01925f07769d39d6f369de37d14e1ef464ea47d07369bbbcf716fb467f5d22ab5c8eceb
6
+ metadata.gz: faff02134845b773db02e2e8e8f4a0f99e58548b013db9ad99212fe349f1e8be7088fc153465bb32b54c584c05030d24c8cc5a07dbf0e8365ab2cd3368ed5bc6
7
+ data.tar.gz: 308659e8aaee319757fb35fd19abf69296aebe00e8d31d4405b9d262454cd1e8e020aed702a6ab043664dd13e64670bf2758bcfda41ef7c80115d6c02d4f513b
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -82,57 +82,26 @@ module Coradoc
82
82
  private
83
83
 
84
84
  def transform_structural_element(element)
85
- case element.element_type
86
- when 'document'
87
- transform_document(element)
88
- when 'section'
89
- transform_section(element)
90
- else
91
- transform_section(element)
92
- end
85
+ transform_document(element)
93
86
  end
94
87
 
95
88
  def transform_document(element)
96
89
  doc = Uniword::Builder::DocumentBuilder.new
97
90
  doc.title(element.title) if element.title
98
91
 
92
+ FrontmatterCoreProperties.apply(doc, FrontmatterCoreProperties.extract(element))
93
+
99
94
  transform_children(element.children, doc)
100
95
 
101
96
  doc.build
102
97
  end
103
98
 
104
- def transform_section(element)
105
- paragraphs = []
106
-
107
- paragraphs << build_heading(element.title, level: element.heading_level) if element.title
108
-
109
- element.children&.each do |child|
110
- case child
111
- when Coradoc::CoreModel::StructuralElement
112
- paragraphs << transform_section(child)
113
- when Coradoc::CoreModel::AnnotationBlock
114
- paragraphs << transform_annotation_block(child)
115
- when Coradoc::CoreModel::Block
116
- paragraphs << build_ooxml_paragraph(child)
117
- when Coradoc::CoreModel::ListBlock
118
- paragraphs.concat(build_ooxml_list(child))
119
- when Coradoc::CoreModel::Table
120
- paragraphs << build_ooxml_table(child)
121
- when Coradoc::CoreModel::Image
122
- paragraphs << build_ooxml_image(child)
123
- end
124
- end
125
-
126
- paragraphs
127
- end
128
-
129
99
  def transform_block(block)
130
- case block.element_type
131
- when 'page_break'
100
+ semantic = block.resolve_semantic_type
101
+ case semantic
102
+ when :page_break
132
103
  build_page_break
133
- when 'paragraph', nil
134
- build_ooxml_paragraph(block)
135
- when 'comment'
104
+ when :comment
136
105
  nil
137
106
  else
138
107
  build_ooxml_paragraph(block)
@@ -206,8 +175,8 @@ module Coradoc
206
175
  end
207
176
 
208
177
  def add_block_to_builder(block, builder)
209
- case block.element_type
210
- when 'page_break'
178
+ case block.resolve_semantic_type
179
+ when :page_break
211
180
  builder.page_break
212
181
  else
213
182
  add_paragraph_to_builder(block, builder)
@@ -224,10 +193,12 @@ module Coradoc
224
193
  def add_paragraph_to_builder(block, builder)
225
194
  content = block.renderable_content
226
195
 
227
- if content.is_a?(Array) && content.any? { |c| !c.is_a?(String) }
196
+ if content.is_a?(Array) && content.any? { |c| !c.is_a?(Coradoc::CoreModel::TextContent) }
228
197
  builder.paragraph do |p|
229
198
  content.each do |child|
230
199
  case child
200
+ when Coradoc::CoreModel::TextContent
201
+ p << child.text
231
202
  when String
232
203
  p << child
233
204
  when Coradoc::CoreModel::InlineElement
@@ -242,7 +213,7 @@ module Coradoc
242
213
  end
243
214
 
244
215
  def add_list_to_builder(list_block, builder)
245
- list_method = list_block.marker_type == 'numbered' ? :numbered_list : :bullet_list
216
+ list_method = list_block.marker_type == 'ordered' ? :numbered_list : :bullet_list
246
217
 
247
218
  if list_method == :numbered_list
248
219
  builder.numbered_list do |list|
@@ -300,9 +271,9 @@ module Coradoc
300
271
  if content.is_a?(Array)
301
272
  content.each do |child|
302
273
  case child
303
- when String
274
+ when Coradoc::CoreModel::TextContent
304
275
  run = Uniword::Wordprocessingml::Run.new
305
- run.text = Uniword::Wordprocessingml::Text.new(content: child)
276
+ run.text = Uniword::Wordprocessingml::Text.new(content: child.text)
306
277
  para.runs << run
307
278
  when Coradoc::CoreModel::InlineElement
308
279
  para.runs << build_ooxml_run(child)
@@ -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 = block.data || {}
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
@@ -112,13 +112,13 @@ module Coradoc
112
112
 
113
113
  def marker_type_for(fmt)
114
114
  case fmt
115
- when 'decimal' then 'numbered'
115
+ when 'decimal' then 'ordered'
116
116
  when 'lowerLetter' then 'lower_alpha'
117
117
  when 'upperLetter' then 'upper_alpha'
118
118
  when 'lowerRoman' then 'lower_roman'
119
119
  when 'upperRoman' then 'upper_roman'
120
- when 'bullet' then 'asterisk'
121
- else 'numbered'
120
+ when 'bullet' then 'unordered'
121
+ else 'ordered'
122
122
  end
123
123
  end
124
124
  end
@@ -21,12 +21,24 @@ module Coradoc
21
21
  order = paragraph.is_a?(Uniword::Wordprocessingml::Paragraph) ? paragraph.element_order : nil
22
22
 
23
23
  if order && !order.empty?
24
- transform_ordered(paragraph, order, context)
24
+ wrap_text_children(transform_ordered(paragraph, order, context))
25
25
  else
26
- transform_sequential(paragraph, context)
26
+ wrap_text_children(transform_sequential(paragraph, context))
27
27
  end
28
28
  end
29
29
 
30
+ # Wrap raw strings in TextContent so all children are Base instances.
31
+ #
32
+ # @param items [Array] mixed Strings and CoreModel::Base
33
+ # @return [Array<CoreModel::Base>]
34
+ def wrap_text_children(items)
35
+ items.map do |item|
36
+ next nil if item.nil?
37
+
38
+ item.is_a?(String) ? CoreModel::TextContent.new(text: item) : item
39
+ end.compact
40
+ end
41
+
30
42
  # Flatten children array to plain text string.
31
43
  #
32
44
  # @param children [Array] mixed content (Strings, InlineElements, Blocks)
@@ -34,7 +46,7 @@ module Coradoc
34
46
  def extract_plain_text(children)
35
47
  children.map do |c|
36
48
  case c
37
- when String then c
49
+ when CoreModel::TextContent then c.text
38
50
  when CoreModel::InlineElement then c.content.to_s
39
51
  when CoreModel::Block then c.content.to_s
40
52
  else c.to_s
@@ -16,7 +16,7 @@ module Coradoc
16
16
 
17
17
  def apply(brk, _context)
18
18
  if brk.type == 'page'
19
- Coradoc::CoreModel::Block.new(element_type: 'page_break')
19
+ Coradoc::CoreModel::Block.new(block_semantic_type: 'page_break')
20
20
  else
21
21
  Coradoc::CoreModel::InlineElement.new(
22
22
  format_type: 'hard_line_break'
@@ -25,8 +25,7 @@ module Coradoc
25
25
  title = extract_title(paragraph, context)
26
26
  id = extract_bookmark_id(paragraph)
27
27
 
28
- CoreModel::StructuralElement.new(
29
- element_type: 'section',
28
+ CoreModel::SectionElement.new(
30
29
  level: level,
31
30
  title: title,
32
31
  id: id
@@ -23,8 +23,7 @@ module Coradoc
23
23
  latex = omml_to_latex(element)
24
24
 
25
25
  if display_math?(element)
26
- CoreModel::Block.new(
27
- element_type: 'block',
26
+ CoreModel::PassBlock.new(
28
27
  delimiter_type: '++++',
29
28
  language: 'latexmath',
30
29
  content: latex
@@ -32,7 +32,7 @@ module Coradoc
32
32
  id = extract_bookmark_id(paragraph)
33
33
 
34
34
  block = CoreModel::Block.new(
35
- element_type: block_type,
35
+ block_semantic_type: block_type,
36
36
  content: extract_plain_text(children)
37
37
  )
38
38
  block.children = children
@@ -94,7 +94,12 @@ module Coradoc
94
94
  return false unless paragraph.properties
95
95
 
96
96
  num_id = paragraph.properties.num_id
97
- num_id.to_i.positive?
97
+ # Uniword (via lutaml-model) returns an UninitializedClass
98
+ # sentinel for attributes that weren't deserialized from source.
99
+ # Treat anything that isn't a real Integer as "no list id".
100
+ return false unless num_id.is_a?(Integer)
101
+
102
+ num_id.positive?
98
103
  end
99
104
 
100
105
  # Check if paragraph has a specific role based on style name
@@ -56,8 +56,7 @@ module Coradoc
56
56
  children.shift
57
57
  end
58
58
 
59
- doc = Coradoc::CoreModel::StructuralElement.new(
60
- element_type: 'document',
59
+ doc = Coradoc::CoreModel::DocumentElement.new(
61
60
  title: doc_title,
62
61
  children: children
63
62
  )
@@ -147,7 +146,7 @@ module Coradoc
147
146
  else
148
147
  # Standalone section break → thematic break
149
148
  Coradoc::CoreModel::Block.new(
150
- element_type: 'thematic_break'
149
+ block_semantic_type: 'thematic_break'
151
150
  )
152
151
  end
153
152
  end
@@ -11,6 +11,7 @@ module Coradoc
11
11
  autoload :StyleResolver, 'coradoc/docx/transform/style_resolver'
12
12
  autoload :NumberingResolver, 'coradoc/docx/transform/numbering_resolver'
13
13
  autoload :OrderedContent, 'coradoc/docx/transform/ordered_content'
14
+ autoload :FrontmatterCoreProperties, 'coradoc/docx/transform/frontmatter_core_properties'
14
15
 
15
16
  # Element transform rules
16
17
  module Rules
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Coradoc
4
4
  module Docx
5
- VERSION = '0.1.0'
5
+ VERSION = '0.1.3'
6
6
  end
7
7
  end
data/lib/coradoc/docx.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'coradoc'
4
- require 'coradoc/core_model'
5
4
  require 'uniword'
6
5
 
7
6
  # Coradoc::Docx provides DOCX (OOXML) format support for Coradoc.
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.0
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -103,10 +103,12 @@ extensions: []
103
103
  extra_rdoc_files: []
104
104
  files:
105
105
  - README.adoc
106
+ - Rakefile
106
107
  - lib/coradoc/docx.rb
107
108
  - lib/coradoc/docx/transform.rb
108
109
  - lib/coradoc/docx/transform/context.rb
109
110
  - lib/coradoc/docx/transform/from_core_model.rb
111
+ - lib/coradoc/docx/transform/frontmatter_core_properties.rb
110
112
  - lib/coradoc/docx/transform/numbering_resolver.rb
111
113
  - lib/coradoc/docx/transform/ordered_content.rb
112
114
  - lib/coradoc/docx/transform/rule.rb
@@ -135,6 +137,7 @@ licenses:
135
137
  metadata:
136
138
  homepage_uri: https://github.com/lutaml/coradoc
137
139
  source_code_uri: https://github.com/lutaml/coradoc
140
+ rubygems_mfa_required: 'true'
138
141
  rdoc_options: []
139
142
  require_paths:
140
143
  - lib
@@ -142,7 +145,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
142
145
  requirements:
143
146
  - - ">="
144
147
  - !ruby/object:Gem::Version
145
- version: 3.1.0
148
+ version: 3.3.0
146
149
  required_rubygems_version: !ruby/object:Gem::Requirement
147
150
  requirements:
148
151
  - - ">="