htmltoword 0.5.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a0deb2026e95be6bde1c5af01b676e231dea63c0
4
- data.tar.gz: 94971919c3ad4bfae9dadf00a1d810828f33140c
3
+ metadata.gz: 7034d51bc5f9b9251aec22334f9ee4427bc3d520
4
+ data.tar.gz: b5925e2169eca6f9de534788a2f62850da43ac4d
5
5
  SHA512:
6
- metadata.gz: 19f266330f3e4c953f108b01e960517e8b00c3937de7a1ad9f443f6a7bbca2abc94678a0c2926bb8905fe04708ec8665014edb73605e3b68826b098443176d7e
7
- data.tar.gz: 93d0cf96fc81fa058ba6eda905152352d6b3c9dfbef4473c03cbf900c512af00ab9d8244dfa271df77a2a2b6d649b38e76ad292e793c582e44a61b03a260fc8c
6
+ metadata.gz: c5ac9f6e7c6f26acbb06ee4c85a042cce12318802aabb08a836df3db136b408f6622b34366e0993ec4b8de7b2183b7f4e83c639c858342b5aa2586b264c2ed89
7
+ data.tar.gz: 6747b7abea40e7b43d52322ebe4283e4edc5f97c95487cb3de57e0f66bcfd209f379012572a88a96b4f95fe95651984eaeaf2b4cfb7b9410e648a80a0b7a3426
@@ -1,8 +1,7 @@
1
1
  # encoding: UTF-8
2
- # require 'action_controller'
3
- # require 'action_view'
4
2
  require 'nokogiri'
5
3
  require 'zip'
4
+ require 'open-uri'
6
5
  require_relative 'htmltoword/configuration'
7
6
 
8
7
  module Htmltoword
@@ -15,12 +14,16 @@ module Htmltoword
15
14
  @configuration ||= Configuration.new
16
15
  end
17
16
 
18
- alias :config :configuration
17
+ alias_method :config, :configuration
19
18
  end
20
19
  end
21
20
 
22
-
23
21
  require_relative 'htmltoword/version'
24
- require_relative 'htmltoword/htmltoword_helper'
22
+ require_relative 'htmltoword/helpers/templates_helper'
23
+ require_relative 'htmltoword/helpers/xslt_helper'
25
24
  require_relative 'htmltoword/document'
26
- require_relative 'htmltoword/action_controller'
25
+
26
+ if defined?(Rails)
27
+ require_relative 'htmltoword/renderer'
28
+ require_relative 'htmltoword/railtie'
29
+ end
@@ -1,26 +1,26 @@
1
1
  module Htmltoword
2
2
  class Document
3
- class << self
4
- include HtmltowordHelper
3
+ include XSLTHelper
5
4
 
5
+ class << self
6
+ include TemplatesHelper
6
7
  def create(content, template_name = nil, extras = false)
7
8
  template_name += extension if template_name && !template_name.end_with?(extension)
8
9
  document = new(template_file(template_name))
9
- document.replace_file(content, Document.doc_xml_file, extras)
10
+ document.replace_files(content, extras)
10
11
  document.generate
11
12
  end
12
13
 
13
14
  def create_and_save(content, file_path, template_name = nil, extras = false)
14
- File.open(file_path, "wb") do |out|
15
+ File.open(file_path, 'wb') do |out|
15
16
  out << create(content, template_name, extras)
16
17
  end
17
18
  end
18
19
 
19
- def create_with_content(template, content, set=nil, extras = false)
20
+ def create_with_content(template, content, extras = false)
20
21
  template += extension unless template.end_with?(extension)
21
- content = replace_values(content, set) if set
22
22
  document = new(template_file(template))
23
- document.replace_file(content, Document.doc_xml_file, extras)
23
+ document.replace_files(content, extras)
24
24
  document.generate
25
25
  end
26
26
 
@@ -40,22 +40,15 @@ module Htmltoword
40
40
  'word/_rels/document.xml.rels'
41
41
  end
42
42
 
43
- def numbering_xslt
44
- File.join(Htmltoword.config.default_xslt_path, 'numbering.xslt')
45
- end
46
-
47
- def relations_xslt
48
- File.join(Htmltoword.config.default_xslt_path, 'relations.xslt')
49
- end
50
-
51
- def xslt_template(extras = false)
52
- File.join(Htmltoword.config.default_xslt_path, (extras ? 'htmltoword.xslt' : 'base.xslt'))
43
+ def content_types_xml_file
44
+ '[Content_Types].xml'
53
45
  end
54
46
  end
55
47
 
56
48
  def initialize(template_path)
57
49
  @replaceable_files = {}
58
50
  @template_path = template_path
51
+ @image_files = []
59
52
  end
60
53
 
61
54
  #
@@ -67,35 +60,95 @@ module Htmltoword
67
60
  template_zip.each do |entry|
68
61
  out.put_next_entry entry.name
69
62
  if @replaceable_files[entry.name] && entry.name == Document.doc_xml_file
70
- source = entry.get_input_stream.read.sub(/(<w:body>)((.|\n)*?)(<w:sectPr)/, "\\1#{@replaceable_files[entry.name]}\\4")
63
+ source = entry.get_input_stream.read
64
+ # Change only the body of document. TODO: Improve this...
65
+ source = source.sub(/(<w:body>)((.|\n)*?)(<w:sectPr)/, "\\1#{@replaceable_files[entry.name]}\\4")
71
66
  out.write(source)
72
67
  elsif @replaceable_files[entry.name]
73
68
  out.write(@replaceable_files[entry.name])
69
+ elsif entry.name == Document.content_types_xml_file
70
+ raw_file = entry.get_input_stream.read
71
+ content_types = @image_files.empty? ? raw_file : inject_image_content_types(raw_file)
72
+
73
+ out.write(content_types)
74
74
  else
75
75
  out.write(template_zip.read(entry.name))
76
76
  end
77
77
  end
78
+ unless @image_files.empty?
79
+ #stream the image files into the media folder using open-uri
80
+ @image_files.each do |hash|
81
+ out.put_next_entry("word/media/#{hash[:filename]}")
82
+ open(hash[:url], 'rb') do |f|
83
+ out.write(f.read)
84
+ end
85
+ end
86
+ end
78
87
  end
79
88
  buffer.string
80
89
  end
81
90
  end
82
91
 
83
- def replace_file(html, file_name = Document.doc_xml_file, extras = false)
84
- html = html.presence || '<body></body>'
92
+ def replace_files(html, extras = false)
93
+ html = '<body></body>' if html.nil? || html.empty?
85
94
  source = Nokogiri::HTML(html.gsub(/>\s+</, '><'))
86
- transform_and_replace(source, Document.numbering_xslt, Document.numbering_xml_file)
87
- transform_and_replace(source, Document.relations_xslt, Document.relations_xml_file)
88
- cleaned_source = Nokogiri::XSLT(File.open(File.join(Htmltoword.config.default_xslt_path, 'inline_elements.xslt'))).transform(source)
89
- transform_and_replace(cleaned_source, Document.xslt_template(extras), file_name, extras)
95
+ transform_and_replace(source, xslt_path('numbering'), Document.numbering_xml_file)
96
+ transform_and_replace(source, xslt_path('relations'), Document.relations_xml_file)
97
+ transform_doc_xml(source, extras)
98
+ local_images(source)
99
+ end
100
+
101
+ def transform_doc_xml(source, extras = false)
102
+ transformed_source = xslt(stylesheet_name: 'cleanup').transform(source)
103
+ transformed_source = xslt(stylesheet_name: 'inline_elements').transform(transformed_source)
104
+ transform_and_replace(transformed_source, document_xslt(extras), Document.doc_xml_file, extras)
90
105
  end
91
106
 
92
107
  private
93
108
 
94
- def transform_and_replace source, xslt, file, remove_ns = false
95
- xslt = Nokogiri::XSLT(File.open(xslt))
96
- content = xslt.apply_to(source)
97
- content.gsub!(/\s*xmlns:(\w+)="(.*?)\s*"/,'') if remove_ns
98
- @replaceable_files[file] = content
109
+ def transform_and_replace(source, stylesheet_path, file, remove_ns = false)
110
+ stylesheet = xslt(stylesheet_path: stylesheet_path)
111
+ content = stylesheet.apply_to(source)
112
+ content.gsub!(/\s*xmlns:(\w+)="(.*?)\s*"/, '') if remove_ns
113
+ @replaceable_files[file] = content
114
+ end
115
+
116
+ #generates an array of hashes with filename and full url
117
+ #for all images to be embeded in the word document
118
+ def local_images(source)
119
+ source.css('img').each_with_index do |image,i|
120
+ filename = image['data-filename'] ? image['data-filename'] : image['src'].split("/").last
121
+ ext = File.extname(filename).delete(".").downcase
122
+
123
+ @image_files << { filename: "image#{i+1}.#{ext}", url: image['src'], ext: ext }
124
+ end
125
+ end
126
+
127
+ #get extension from filename and clean to match content_types
128
+ def content_type_from_extension(ext)
129
+ ext == "jpg" ? "jpeg" : ext
130
+ end
131
+
132
+ #inject the required content_types into the [content_types].xml file...
133
+ def inject_image_content_types(source)
134
+ doc = Nokogiri::XML(source)
135
+
136
+ #get a list of all extensions currently in content_types file
137
+ existing_exts = doc.css("Default").map { |node| node.attribute("Extension").value }.compact
138
+
139
+ #get a list of extensions we need for our images
140
+ required_exts = @image_files.map{ |i| i[:ext] }
141
+
142
+ #workout which required extensions are missing from the content_types file
143
+ missing_exts = (required_exts - existing_exts).uniq
144
+
145
+ #inject missing extensions into document
146
+ missing_exts.each do |ext|
147
+ doc.at_css("Types").add_child( "<Default Extension='#{ext}' ContentType='image/#{content_type_from_extension(ext)}'/>")
148
+ end
149
+
150
+ #return the amended source to be saved into the zip
151
+ doc.to_s
99
152
  end
100
153
  end
101
154
  end
@@ -0,0 +1,9 @@
1
+ module Htmltoword
2
+ module TemplatesHelper
3
+ def template_file(template_file_name = nil)
4
+ default_path = File.join(::Htmltoword.config.default_templates_path, 'default.docx')
5
+ template_path = template_file_name.nil? ? '' : File.join(::Htmltoword.config.custom_templates_path, template_file_name)
6
+ File.exist?(template_path) ? template_path : default_path
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,17 @@
1
+ module Htmltoword
2
+ module XSLTHelper
3
+ def document_xslt(extras = false)
4
+ file_name = extras ? 'htmltoword' : 'base'
5
+ xslt_path(file_name)
6
+ end
7
+
8
+ def xslt_path(template_name)
9
+ File.join(Htmltoword.config.default_xslt_path, "#{template_name}.xslt")
10
+ end
11
+
12
+ def xslt(stylesheet_name: nil, stylesheet_path: nil)
13
+ return Nokogiri::XSLT(File.open(stylesheet_path)) if stylesheet_path
14
+ Nokogiri::XSLT(File.open(xslt_path(stylesheet_name)))
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,25 @@
1
+ module Htmltoword
2
+ class Railtie < ::Rails::Railtie
3
+ initializer 'htmltoword.setup' do
4
+ if defined?(Mime) and Mime[:docx].nil?
5
+ Mime::Type.register 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', :docx
6
+ end
7
+
8
+ ActionController::Renderers.add :docx do |file_name, options|
9
+ Htmltoword::Renderer.send_file(self, file_name, options)
10
+ end
11
+
12
+ if defined? ActionController::Responder
13
+ ActionController::Responder.class_eval do
14
+ def to_docx
15
+ if @default_response
16
+ @default_response.call(options)
17
+ else
18
+ controller.render({ docx: controller.action_name }.merge(options))
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,43 @@
1
+ module Htmltoword
2
+ class Renderer
3
+ class << self
4
+ def send_file(context, filename, options = {})
5
+ new(context, filename, options).send_file
6
+ end
7
+ end
8
+
9
+ def initialize(context, filename, options)
10
+ @word_template = options[:word_template].presence
11
+ @disposition = options.fetch(:disposition, 'attachment')
12
+ @use_extras = options.fetch(:extras, false)
13
+ @file_name = file_name(filename, options)
14
+ @context = context
15
+ define_template(filename, options)
16
+ @content = options[:content] || @context.render_to_string(options)
17
+ end
18
+
19
+ def send_file
20
+ document = Htmltoword::Document.create(@content, @word_template, @use_extras)
21
+ @context.send_data(document, filename: @file_name, type: Mime[:docx], disposition: @disposition)
22
+ end
23
+
24
+ private
25
+
26
+ def define_template(filename, options)
27
+ if options[:template] == @context.action_name
28
+ if filename =~ %r{^([^\/]+)/(.+)$}
29
+ options[:prefixes] ||= []
30
+ options[:prefixes].unshift $1
31
+ options[:template] = $2
32
+ else
33
+ options[:template] = filename
34
+ end
35
+ end
36
+ end
37
+
38
+ def file_name(filename, options)
39
+ name = options[:filename].presence || filename
40
+ name =~ /\.docx$/ ? name : "#{name}.docx"
41
+ end
42
+ end
43
+ end
@@ -1,3 +1,3 @@
1
1
  module Htmltoword
2
- VERSION = "0.5.1"
2
+ VERSION = '0.7.0'
3
3
  end
@@ -19,7 +19,7 @@
19
19
  <xsl:include href="./functions.xslt"/>
20
20
  <xsl:include href="./tables.xslt"/>
21
21
  <xsl:include href="./links.xslt"/>
22
-
22
+ <xsl:include href="./images.xslt"/>
23
23
  <xsl:template match="/">
24
24
  <xsl:apply-templates />
25
25
  </xsl:template>
@@ -204,6 +204,12 @@
204
204
  <xsl:when test="self::a[starts-with(@href, 'http://') or starts-with(@href, 'https://')]">
205
205
  <xsl:call-template name="link" />
206
206
  </xsl:when>
207
+ <xsl:when test="self::img">
208
+ <xsl:comment>
209
+ This template adds images.
210
+ </xsl:comment>
211
+ <xsl:call-template name="image"/>
212
+ </xsl:when>
207
213
  <xsl:otherwise>
208
214
  <xsl:apply-templates />
209
215
  </xsl:otherwise>
@@ -277,12 +283,12 @@
277
283
  <xsl:template match="text()">
278
284
  <xsl:if test="string-length(.) > 0">
279
285
  <w:r>
280
- <xsl:if test="ancestor::i or ancestor::em">
286
+ <xsl:if test="ancestor::i">
281
287
  <w:rPr>
282
288
  <w:i />
283
289
  </w:rPr>
284
290
  </xsl:if>
285
- <xsl:if test="ancestor::b or ancestor::strong">
291
+ <xsl:if test="ancestor::b">
286
292
  <w:rPr>
287
293
  <w:b />
288
294
  </w:rPr>
@@ -292,6 +298,21 @@
292
298
  <w:u w:val="single"/>
293
299
  </w:rPr>
294
300
  </xsl:if>
301
+ <xsl:if test="ancestor::s">
302
+ <w:rPr>
303
+ <w:strike w:val="true"/>
304
+ </w:rPr>
305
+ </xsl:if>
306
+ <xsl:if test="ancestor::sub">
307
+ <w:rPr>
308
+ <w:vertAlign w:val="subscript"/>
309
+ </w:rPr>
310
+ </xsl:if>
311
+ <xsl:if test="ancestor::sup">
312
+ <w:rPr>
313
+ <w:vertAlign w:val="superscript"/>
314
+ </w:rPr>
315
+ </xsl:if>
295
316
  <w:t xml:space="preserve"><xsl:value-of select="."/></w:t>
296
317
  </w:r>
297
318
  </xsl:if>
@@ -0,0 +1,70 @@
1
+ <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
2
+ xmlns:str="http://exslt.org/strings"
3
+ xmlns:func="http://exslt.org/functions"
4
+ xmlns:fn="http://www.w3.org/2005/xpath-functions"
5
+ version="1.0"
6
+ exclude-result-prefixes="java msxsl ext w o v WX aml w10"
7
+ extension-element-prefixes="func">
8
+ <xsl:output method="html" encoding="utf-8" omit-xml-declaration="yes" indent="yes"/>
9
+
10
+ <xsl:template match="/">
11
+ <xsl:apply-templates/>
12
+ </xsl:template>
13
+
14
+ <xsl:template match="head"/>
15
+
16
+ <!-- Elements not supported -->
17
+ <xsl:template match="applet"/>
18
+ <xsl:template match="area"/>
19
+ <xsl:template match="audio"/>
20
+ <xsl:template match="base"/>
21
+ <xsl:template match="basefont"/>
22
+ <xsl:template match="canvas"/>
23
+ <xsl:template match="command"/>
24
+ <xsl:template match="font"/>
25
+ <xsl:template match="iframe"/>
26
+ <xsl:template match="isindex"/>
27
+ <xsl:template match="map"/>
28
+ <xsl:template match="noframes"/>
29
+ <xsl:template match="noscript"/>
30
+ <xsl:template match="object"/>
31
+ <xsl:template match="param"/>
32
+ <xsl:template match="script"/>
33
+ <xsl:template match="source"/>
34
+ <xsl:template match="style"/>
35
+ <xsl:template match="video"/>
36
+
37
+ <!-- Elements currently being handled as normal text. Remove tags only -->
38
+ <xsl:template match="abbr"><xsl:apply-templates/></xsl:template>
39
+ <xsl:template match="acronym"><xsl:apply-templates/></xsl:template>
40
+ <xsl:template match="bdi"><xsl:apply-templates/></xsl:template>
41
+ <xsl:template match="bdo"><xsl:apply-templates/></xsl:template>
42
+ <xsl:template match="big"><xsl:apply-templates/></xsl:template>
43
+ <xsl:template match="code"><xsl:apply-templates/></xsl:template>
44
+ <xsl:template match="kbd"><xsl:apply-templates/></xsl:template>
45
+ <xsl:template match="samp"><xsl:apply-templates/></xsl:template>
46
+ <xsl:template match="small"><xsl:apply-templates/></xsl:template>
47
+ <xsl:template match="tt"><xsl:apply-templates/></xsl:template>
48
+ <xsl:template match="var"><xsl:apply-templates/></xsl:template>
49
+
50
+ <!-- Inline elements transformations -->
51
+ <xsl:template match="cite"><i><xsl:apply-templates/></i></xsl:template>
52
+ <xsl:template match="del"><s><xsl:apply-templates/></s></xsl:template>
53
+ <xsl:template match="dfn"><i><xsl:apply-templates/></i></xsl:template>
54
+ <xsl:template match="em"><i><xsl:apply-templates/></i></xsl:template>
55
+ <xsl:template match="ins"><u><xsl:apply-templates/></u></xsl:template>
56
+ <xsl:template match="mark"><span class="h" data-style="yellow"><xsl:apply-templates/></span></xsl:template>
57
+ <xsl:template match="q">"<xsl:apply-templates/>"</xsl:template>
58
+ <xsl:template match="strike"><s><xsl:apply-templates/></s></xsl:template>
59
+ <xsl:template match="strong"><b><xsl:apply-templates/></b></xsl:template>
60
+
61
+ <!-- Block elements transformations -->
62
+ <xsl:template match="section"><div class="{@class}" style="{@style}"><xsl:apply-templates/></div></xsl:template>
63
+ <xsl:template match="article"><div class="{@class}" style="{@style}"><xsl:apply-templates/></div></xsl:template>
64
+
65
+ <xsl:template match="@*|node()">
66
+ <xsl:copy>
67
+ <xsl:apply-templates select="@*|node()"/>
68
+ </xsl:copy>
69
+ </xsl:template>
70
+ </xsl:stylesheet>
@@ -31,4 +31,7 @@
31
31
  </xsl:choose>
32
32
  </func:result>
33
33
  </func:function>
34
+
35
+ <!-- template as function used to return the relationship id of the element (currently links or images) -->
36
+ <xsl:template name="relationship-id">rId<xsl:value-of select="count(preceding::a[starts-with(@href, 'http://') or starts-with(@href, 'https://')])+count(preceding::img)+8"/></xsl:template>
34
37
  </xsl:stylesheet>
@@ -0,0 +1,148 @@
1
+ <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
2
+ xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
3
+ xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
4
+ xmlns:o="urn:schemas-microsoft-com:office:office"
5
+ xmlns:v="urn:schemas-microsoft-com:vml"
6
+ xmlns:WX="http://schemas.microsoft.com/office/word/2003/auxHint"
7
+ xmlns:aml="http://schemas.microsoft.com/aml/2001/core"
8
+ xmlns:w10="urn:schemas-microsoft-com:office:word"
9
+ xmlns:pkg="http://schemas.microsoft.com/office/2006/xmlPackage"
10
+ xmlns:msxsl="urn:schemas-microsoft-com:xslt"
11
+ xmlns:ext="http://www.xmllab.net/wordml2html/ext"
12
+ xmlns:java="http://xml.apache.org/xalan/java"
13
+ xmlns:str="http://exslt.org/strings"
14
+ xmlns:func="http://exslt.org/functions"
15
+ xmlns:fn="http://www.w3.org/2005/xpath-functions"
16
+ version="1.0"
17
+ exclude-result-prefixes="java msxsl ext w o v WX aml w10"
18
+ extension-element-prefixes="func">
19
+
20
+ <!-- template as function used to return the file extension of an image. -->
21
+ <xsl:template name="image-extension">
22
+ <xsl:param name="data-filename" select="." />
23
+ <xsl:param name="source" select="." />
24
+ <xsl:variable name="filename">
25
+ <xsl:call-template name="image-name">
26
+ <xsl:with-param name="source" select="$source"/>
27
+ <xsl:with-param name="data-filename" select="$data-filename"/>
28
+ </xsl:call-template>
29
+ </xsl:variable>
30
+ <xsl:value-of select="substring-after($filename,'.')"/>
31
+ </xsl:template>
32
+
33
+ <!-- template as function used to return the name of an image. -->
34
+ <xsl:template name="image-name">
35
+ <xsl:param name="data-filename" select="." />
36
+ <xsl:param name="source" select="." />
37
+ <xsl:choose>
38
+ <xsl:when test="string-length($data-filename) > 0">
39
+ <xsl:value-of select="$data-filename"/>
40
+ </xsl:when>
41
+ <xsl:otherwise>
42
+ <xsl:call-template name="extract-filename-from-path">
43
+ <xsl:with-param name="path" select="$source"/>
44
+ </xsl:call-template>
45
+ </xsl:otherwise>
46
+ </xsl:choose>
47
+ </xsl:template>
48
+
49
+ <!-- template as function used to extract the filename from the image source. Can't use tokenize or other functions which return a fragment tree as xpath can't process them. -->
50
+ <xsl:template name="extract-filename-from-path">
51
+ <xsl:param name="path" select="." />
52
+ <xsl:choose>
53
+ <xsl:when test="not(contains($path, '/'))">
54
+ <xsl:value-of select="$path" />
55
+ </xsl:when>
56
+ <xsl:otherwise>
57
+ <xsl:call-template name="extract-filename-from-path">
58
+ <xsl:with-param name="path" select="substring-after($path, '/')" />
59
+ </xsl:call-template>
60
+ </xsl:otherwise>
61
+ </xsl:choose>
62
+ </xsl:template>
63
+
64
+ <!--
65
+ Returns the unqualified dimension from a length specification copied from:
66
+ http://docbook.sourceforge.net/release/xsl/1.76.1/doc/lib/length-magnitude.html
67
+ -->
68
+
69
+ <xsl:template name="length-magnitude">
70
+ <xsl:param name="length" select="'0pt'"></xsl:param>
71
+
72
+ <xsl:choose>
73
+ <xsl:when test="string-length($length) = 0"></xsl:when>
74
+ <xsl:when test="substring($length,1,1) = '0' or substring($length,1,1) = '1' or substring($length,1,1) = '2' or substring($length,1,1) = '3' or substring($length,1,1) = '4' or substring($length,1,1) = '5' or substring($length,1,1) = '6' or substring($length,1,1) = '7' or substring($length,1,1) = '8' or substring($length,1,1) = '9' or substring($length,1,1) = '.'">
75
+ <xsl:value-of select="substring($length,1,1)"></xsl:value-of>
76
+ <xsl:call-template name="length-magnitude">
77
+ <xsl:with-param name="length" select="substring($length,2)"></xsl:with-param>
78
+ </xsl:call-template>
79
+ </xsl:when>
80
+ </xsl:choose>
81
+ </xsl:template>
82
+
83
+
84
+
85
+
86
+ <!-- Convert em and pixel sizes to inches. Inspired by from:
87
+ http://docbook.sourceforge.net/release/xsl/1.76.1/doc/lib/length-in-points.html
88
+ EMU info from: https://startbigthinksmall.wordpress.com/2010/01/04/points-inches-and-emus-measuring-units-in-office-open-xml/
89
+ -->
90
+ <xsl:template name="length-in-emus">
91
+ <xsl:param name="length" select="'0px'"/>
92
+ <xsl:param name="em.size" select="6.02250006023"/>
93
+ <xsl:param name="pixels.per.inch" select="90"/>
94
+ <xsl:variable name="emus.per.inch" select="914400"/>
95
+
96
+ <xsl:variable name="magnitude">
97
+ <xsl:call-template name="length-magnitude">
98
+ <xsl:with-param name="length" select="$length"/>
99
+ </xsl:call-template>
100
+ </xsl:variable>
101
+
102
+ <xsl:variable name="units">
103
+ <xsl:value-of select="substring($length, string-length($magnitude)+1)"/>
104
+ </xsl:variable>
105
+
106
+ <xsl:variable name="inches">
107
+ <xsl:choose>
108
+ <xsl:when test="$units = 'px'">
109
+ <xsl:value-of select="$magnitude div $pixels.per.inch"/>
110
+ </xsl:when>
111
+ <xsl:when test="$units = 'em'">
112
+ <xsl:value-of select="$magnitude div $em.size"/>
113
+ </xsl:when>
114
+ <xsl:otherwise>
115
+ <xsl:message>
116
+ <xsl:text>Unrecognized unit of measure: </xsl:text>
117
+ <xsl:value-of select="$units"></xsl:value-of>
118
+ <xsl:text>.</xsl:text>
119
+ </xsl:message>
120
+ </xsl:otherwise>
121
+ </xsl:choose>
122
+ </xsl:variable>
123
+
124
+ <xsl:value-of select="$inches * $emus.per.inch"/>
125
+ </xsl:template>
126
+
127
+ <!-- template as function used to get the width or height of an image in points. -->
128
+ <xsl:template name="image-dimention">
129
+ <xsl:param name="style" />
130
+ <xsl:param name="data-value" />
131
+ <xsl:param name="type" />
132
+
133
+ <xsl:variable name="size">
134
+ <xsl:choose>
135
+ <xsl:when test="string-length($data-value) > 0">
136
+ <xsl:value-of select="$data-value" />
137
+ </xsl:when>
138
+ <xsl:when test="contains($style, concat($type,':'))">
139
+ <xsl:value-of select="translate(str:tokenize(substring-after($style, concat($type,':')), ';')[1],'&#x20;&#x9;&#xD;&#xA;','')" />
140
+ </xsl:when>
141
+ <xsl:otherwise>1</xsl:otherwise>
142
+ </xsl:choose>
143
+ </xsl:variable>
144
+ <xsl:call-template name="length-in-emus">
145
+ <xsl:with-param name="length" select="$size"/>
146
+ </xsl:call-template>
147
+ </xsl:template>
148
+ </xsl:stylesheet>
@@ -0,0 +1,129 @@
1
+ <xsl:stylesheet xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas"
2
+ xmlns:mo="http://schemas.microsoft.com/office/mac/office/2008/main"
3
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
4
+ xmlns:mv="urn:schemas-microsoft-com:mac:vml"
5
+ xmlns:o="urn:schemas-microsoft-com:office:office"
6
+ xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
7
+ xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"
8
+ xmlns:v="urn:schemas-microsoft-com:vml"
9
+ xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing"
10
+ xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
11
+ xmlns:w10="urn:schemas-microsoft-com:office:word"
12
+ xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
13
+ xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml"
14
+ xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml"
15
+ xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup"
16
+ xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk"
17
+ xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml"
18
+ xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape"
19
+ xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
20
+ xmlns:a14="http://schemas.microsoft.com/office/drawing/2010/main"
21
+ xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture"
22
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
23
+ xmlns:WX="http://schemas.microsoft.com/office/word/2003/auxHint"
24
+ xmlns:aml="http://schemas.microsoft.com/aml/2001/core"
25
+ xmlns:pkg="http://schemas.microsoft.com/office/2006/xmlPackage"
26
+ xmlns:msxsl="urn:schemas-microsoft-com:xslt"
27
+ xmlns:ext="http://www.xmllab.net/wordml2html/ext"
28
+ xmlns:java="http://xml.apache.org/xalan/java"
29
+ xmlns:str="http://exslt.org/strings"
30
+ xmlns:func="http://exslt.org/functions"
31
+ xmlns:fn="http://www.w3.org/2005/xpath-functions"
32
+ mc:Ignorable="w14 w15 wp14 a14"
33
+ version="1.0"
34
+ exclude-result-prefixes="java msxsl ext w o v WX aml w10"
35
+ extension-element-prefixes="func">
36
+
37
+ <xsl:include href="./image_functions.xslt"/>
38
+
39
+ <xsl:template match="img|body/img" name="image">
40
+ <w:drawing>
41
+ <wp:inline distT="0" distB="0" distL="0" distR="0">
42
+ <wp:extent>
43
+ <xsl:call-template name="image-dimention-attributes"/>
44
+ </wp:extent>
45
+ <wp:effectExtent l="0" t="0" r="0" b="0"/>
46
+ <wp:docPr>
47
+ <xsl:attribute name="id"><xsl:value-of select="count(preceding::img)+1" /></xsl:attribute>
48
+ <xsl:attribute name="name">Picture <xsl:value-of select="count(preceding::img)+1" /></xsl:attribute>
49
+ </wp:docPr>
50
+ <wp:cNvGraphicFramePr>
51
+ <a:graphicFrameLocks noChangeAspect="1"/>
52
+ </wp:cNvGraphicFramePr>
53
+ <a:graphic>
54
+ <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
55
+ <pic:pic>
56
+ <pic:nvPicPr>
57
+ <pic:cNvPr>
58
+ <xsl:attribute name="id"><xsl:value-of select="count(preceding::img)+1" /></xsl:attribute>
59
+ <xsl:attribute name="title"><xsl:value-of select="@alt" /></xsl:attribute>
60
+ <xsl:attribute name="name"><xsl:call-template name="image-name">
61
+ <xsl:with-param name="source" select="@src"/>
62
+ <xsl:with-param name="data-filename" select="@data-filename"/>
63
+ </xsl:call-template></xsl:attribute>
64
+ </pic:cNvPr>
65
+ <pic:cNvPicPr/>
66
+ </pic:nvPicPr>
67
+ <pic:blipFill>
68
+ <a:blip>
69
+ <xsl:attribute name="r:embed"><xsl:call-template name="relationship-id"/></xsl:attribute>
70
+ <a:extLst>
71
+ <a:ext uri="{{28A0092B-C50C-407E-A947-70E740481C1C}}">
72
+ <a14:useLocalDpi val="0"/>
73
+ </a:ext>
74
+ </a:extLst>
75
+ </a:blip>
76
+ <a:stretch>
77
+ <a:fillRect/>
78
+ </a:stretch>
79
+ </pic:blipFill>
80
+ <pic:spPr>
81
+ <a:xfrm>
82
+ <a:off x="0" y="0"/>
83
+ <a:ext>
84
+ <xsl:call-template name="image-dimention-attributes"/>
85
+ </a:ext>
86
+ </a:xfrm>
87
+ <a:prstGeom prst="rect">
88
+ <a:avLst/>
89
+ </a:prstGeom>
90
+ </pic:spPr>
91
+ </pic:pic>
92
+ </a:graphicData>
93
+ </a:graphic>
94
+ </wp:inline>
95
+ </w:drawing>
96
+ </xsl:template>
97
+
98
+ <!--
99
+ A style specifiying the width and height is required to render an image correctly in word.
100
+ Styles can be provided either via data attributes:
101
+ img src="pathtosomeimage" data-width="200px" data-height="250px"
102
+
103
+ or via the html style attribute
104
+ img src="pathtosomeimage" style="width:150px;height:200px"
105
+
106
+ If both a style and data attributes are provided then data attributes take president.
107
+ e.g.
108
+ img src="pathtosomeimage" data-width="200em" style="width:150px;height:200px"
109
+ becomes: width:200em;height:200px
110
+
111
+ Note: All sizes must be in pixles or em
112
+ -->
113
+ <xsl:template name="image-dimention-attributes">
114
+ <xsl:attribute name="cx">
115
+ <xsl:call-template name="image-dimention">
116
+ <xsl:with-param name="style" select="@style" />
117
+ <xsl:with-param name="data-value" select="@data-width" />
118
+ <xsl:with-param name="type" select="'width'" />
119
+ </xsl:call-template>
120
+ </xsl:attribute>
121
+ <xsl:attribute name="cy">
122
+ <xsl:call-template name="image-dimention">
123
+ <xsl:with-param name="style" select="@style" />
124
+ <xsl:with-param name="data-value" select="@data-height" />
125
+ <xsl:with-param name="type" select="'height'" />
126
+ </xsl:call-template>
127
+ </xsl:attribute>
128
+ </xsl:template>
129
+ </xsl:stylesheet>
@@ -11,29 +11,30 @@
11
11
  </xsl:template>
12
12
 
13
13
  <!-- get first inline element of a sequence or text having block element siblings... -->
14
- <xsl:template match="node()[self::a|self::b|self::em|self::i|self::small|self::span|self::strong|self::u|self::text()][parent::div|parent::li|parent::td]">
14
+ <xsl:template match="node()[self::a|self::b|self::i|self::s|self::span|self::sub|self::sup|self::u|self::text()][parent::div|parent::li|parent::td]">
15
15
  <div>
16
16
  <xsl:attribute name="class"><xsl:value-of select="../@class"/></xsl:attribute>
17
17
  <xsl:attribute name="style"><xsl:value-of select="../@style"/></xsl:attribute>
18
18
  <xsl:call-template name="inlineElement"/>
19
19
  </div>
20
- <xsl:apply-templates select="following-sibling::node()[not((self::a|self::b|self::em|self::i|self::small|self::span|self::strong|self::u|self::text())[parent::div|parent::li|parent::td])][1]"/>
20
+ <xsl:apply-templates select="following-sibling::node()[not((self::a|self::b|self::i|self::s|self::span|self::sub|self::sup|self::u|self::text())[parent::div|parent::li|parent::td])][1]"/>
21
21
  </xsl:template>
22
22
 
23
23
  <!-- get following inline elements... -->
24
- <xsl:template match="a[preceding-sibling::node()[1][self::a|self::b|self::em|self::i|self::small|self::span|self::strong|self::u|self::text()]]
25
- |b[preceding-sibling::node()[1][self::a|self::b|self::em|self::i|self::small|self::span|self::strong|self::u|self::text()]]
26
- |em[preceding-sibling::node()[1][self::a|self::b|self::em|self::i|self::small|self::span|self::strong|self::u|self::text()]]
27
- |i[preceding-sibling::node()[1][self::a|self::b|self::em|self::i|self::small|self::span|self::strong|self::u|self::text()]]
28
- |small[preceding-sibling::node()[1][self::a|self::b|self::em|self::i|self::small|self::span|self::strong|self::u|self::text()]]
29
- |span[preceding-sibling::node()[1][self::a|self::b|self::em|self::i|self::small|self::span|self::strong|self::u|self::text()]]
30
- |strong[preceding-sibling::node()[1][self::a|self::b|self::em|self::i|self::small|self::span|self::strong|self::u|self::text()]]
31
- |u[preceding-sibling::node()[1][self::a|self::b|self::em|self::i|self::small|self::span|self::strong|self::u|self::text()]]
32
- |text()[preceding-sibling::node()[1][self::a|self::b|self::em|self::i|self::small|self::span|self::strong|self::u|self::text()]]"
24
+ <xsl:template match="
25
+ a[preceding-sibling::node()[1][self::a|self::b|self::i|self::s|self::span|self::sub|self::sup|self::u|self::text()]]
26
+ |b[preceding-sibling::node()[1][self::a|self::b|self::i|self::s|self::span|self::sub|self::sup|self::u|self::text()]]
27
+ |i[preceding-sibling::node()[1][self::a|self::b|self::i|self::s|self::span|self::sub|self::sup|self::u|self::text()]]
28
+ |s[preceding-sibling::node()[1][self::a|self::b|self::i|self::s|self::span|self::sub|self::sup|self::u|self::text()]]
29
+ |span[preceding-sibling::node()[1][self::a|self::b|self::i|self::s|self::span|self::sub|self::sup|self::u|self::text()]]
30
+ |sub[preceding-sibling::node()[1][self::a|self::b|self::i|self::s|self::span|self::sub|self::sup|self::u|self::text()]]
31
+ |sup[preceding-sibling::node()[1][self::a|self::b|self::i|self::s|self::span|self::sub|self::sup|self::u|self::text()]]
32
+ |u[preceding-sibling::node()[1][self::a|self::b|self::i|self::s|self::span|self::sub|self::sup|self::u|self::text()]]
33
+ |text()[preceding-sibling::node()[1][self::a|self::b|self::i|self::s|self::span|self::sub|self::sup|self::u|self::text()]]"
33
34
  name="inlineElement">
34
35
  <xsl:copy>
35
36
  <xsl:apply-templates select="@*|node()[1]"/>
36
37
  </xsl:copy>
37
- <xsl:apply-templates select="following-sibling::node()[1][self::a|self::b|self::em|self::i|self::small|self::span|self::strong|self::u|self::text()]"/>
38
+ <xsl:apply-templates select="following-sibling::node()[1][self::a|self::b|self::i|self::s|self::span|self::sub|self::sup|self::u|self::text()]"/>
38
39
  </xsl:template>
39
40
  </xsl:stylesheet>
@@ -17,9 +17,10 @@
17
17
  exclude-result-prefixes="java msxsl ext w o v WX aml w10"
18
18
  extension-element-prefixes="func">
19
19
 
20
+
20
21
  <xsl:template match="a[starts-with(@href, 'http://') or starts-with(@href, 'https://')]" name="link">
21
22
  <w:hyperlink>
22
- <xsl:attribute name="r:id">rId<xsl:value-of select="count(preceding::a[starts-with(@href, 'http://') or starts-with(@href, 'https://')]) + 8" /></xsl:attribute>
23
+ <xsl:attribute name="r:id"><xsl:call-template name="relationship-id"/></xsl:attribute>
23
24
  <w:r>
24
25
  <w:rPr>
25
26
  <w:rStyle w:val="Hyperlink"/>
@@ -3,13 +3,45 @@
3
3
  version="1.0">
4
4
  <xsl:output method="xml" encoding="utf-8" omit-xml-declaration="yes" indent="yes" />
5
5
 
6
+ <xsl:include href="./image_functions.xslt"/>
7
+ <xsl:include href="./functions.xslt"/>
8
+
6
9
  <xsl:template match="a[starts-with(@href, 'http://') or starts-with(@href, 'https://')]" priority="1">
7
10
  <Relationship Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" Target="{@href}" TargetMode="External">
8
- <xsl:attribute name="Id">rId<xsl:value-of select="count(preceding::a[starts-with(@href, 'http://') or starts-with(@href, 'https://')]) + 8"/></xsl:attribute>
11
+ <xsl:attribute name="Id"><xsl:call-template name="relationship-id"/></xsl:attribute>
12
+ </Relationship>
13
+ </xsl:template>
14
+
15
+ <!--
16
+ Images can either be embedded in the document or referenced via the external url.
17
+ By default images are embedded and a copy will be stored in the final zip folder.
18
+ If you want your images to be referenced externally (don't store the image files) then add data-external="true" to your image tag(s).
19
+ If you are embedding them images need a filename. You can either define one by passing the name and extension to data-filename or rely on the defaul which is the last part of the source url.
20
+ **Word files can be corrupted if an extension isn't present so if you are relying on the source url ensure that is has an extension.**
21
+ -->
22
+ <xsl:template match="img" priority="0">
23
+ <Relationship Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image">
24
+ <xsl:choose>
25
+ <xsl:when test="@data-external = 'true'">
26
+ <xsl:attribute name="Target"><xsl:value-of select="@src"/></xsl:attribute>
27
+ <xsl:attribute name="TargetMode">External</xsl:attribute>
28
+ </xsl:when>
29
+
30
+ <xsl:otherwise>
31
+ <xsl:attribute name="Target">media/image<xsl:value-of select="count(preceding::img)+1"/>.<xsl:call-template name="image-extension">
32
+ <xsl:with-param name="source" select="@src"/>
33
+ <xsl:with-param name="data-filename" select="@data-filename"/>
34
+ </xsl:call-template>
35
+ </xsl:attribute>
36
+ </xsl:otherwise>
37
+ </xsl:choose>
38
+
39
+ <xsl:attribute name="Id"><xsl:call-template name="relationship-id"/></xsl:attribute>
9
40
  </Relationship>
10
41
  </xsl:template>
11
42
 
12
43
  <xsl:template match="/">
44
+ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
13
45
  <Relationships>
14
46
  <Relationship Id="rId3" Type="http://schemas.microsoft.com/office/2007/relationships/stylesWithEffects" Target="stylesWithEffects.xml"/>
15
47
  <Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings" Target="settings.xml"/>
@@ -152,6 +152,9 @@
152
152
  <xsl:variable name="cell-bg" select="str:tokenize(substring-after(@class, 'ms-fill-'), ' ')[1]"/>
153
153
  <w:shd w:val="clear" w:color="auto" w:fill="{$cell-bg}" />
154
154
  </xsl:if>
155
+ <xsl:if test="@colspan &gt; 1">
156
+ <w:gridSpan w:val="{@colspan}"/>
157
+ </xsl:if>
155
158
  </w:tcPr>
156
159
  </xsl:template>
157
160
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: htmltoword
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nicholas Frandsen, Cristina Matonte
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-11-24 00:00:00.000000000 Z
11
+ date: 2017-11-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -134,16 +134,21 @@ files:
134
134
  - Rakefile
135
135
  - bin/htmltoword
136
136
  - lib/htmltoword.rb
137
- - lib/htmltoword/action_controller.rb
138
137
  - lib/htmltoword/configuration.rb
139
138
  - lib/htmltoword/document.rb
140
- - lib/htmltoword/htmltoword_helper.rb
139
+ - lib/htmltoword/helpers/templates_helper.rb
140
+ - lib/htmltoword/helpers/xslt_helper.rb
141
+ - lib/htmltoword/railtie.rb
142
+ - lib/htmltoword/renderer.rb
141
143
  - lib/htmltoword/templates/default.docx
142
144
  - lib/htmltoword/version.rb
143
145
  - lib/htmltoword/xslt/base.xslt
146
+ - lib/htmltoword/xslt/cleanup.xslt
144
147
  - lib/htmltoword/xslt/extras.xslt
145
148
  - lib/htmltoword/xslt/functions.xslt
146
149
  - lib/htmltoword/xslt/htmltoword.xslt
150
+ - lib/htmltoword/xslt/image_functions.xslt
151
+ - lib/htmltoword/xslt/images.xslt
147
152
  - lib/htmltoword/xslt/inline_elements.xslt
148
153
  - lib/htmltoword/xslt/links.xslt
149
154
  - lib/htmltoword/xslt/numbering.xslt
@@ -170,7 +175,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
170
175
  version: '0'
171
176
  requirements: []
172
177
  rubyforge_project:
173
- rubygems_version: 2.4.8
178
+ rubygems_version: 2.6.14
174
179
  signing_key:
175
180
  specification_version: 4
176
181
  summary: This simple gem allows you to create MS Word docx documents from simple html
@@ -1,54 +0,0 @@
1
- require 'rack/test'
2
- require 'action_controller'
3
-
4
- unless defined? Mime::DOCX
5
- Mime::Type.register 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', :docx
6
- end
7
-
8
- ActionController::Renderers.add :docx do |filename, options|
9
- formats << :docx unless formats.include?(:docx) || Rails.version < '3.2'
10
-
11
- # This is ugly and should be solved with regular file utils
12
- if options[:template] == action_name
13
- if filename =~ %r{^([^\/]+)/(.+)$}
14
- options[:prefixes] ||= []
15
- options[:prefixes].unshift $1
16
- options[:template] = $2
17
- else
18
- options[:template] = filename
19
- end
20
- end
21
-
22
- # disposition / filename
23
- disposition = options.delete(:disposition) || 'attachment'
24
- if file_name = options.delete(:filename)
25
- file_name += '.docx' unless file_name =~ /\.docx$/
26
- else
27
- file_name = "#{filename.gsub(/^.*\//, '')}.docx"
28
- end
29
-
30
- # other properties
31
- word_template = options.delete(:word_template) || nil
32
- extras = options.delete(:extras) || false
33
- # content will come from property content unless not specified
34
- # then it will look for a template.
35
- content = options.delete(:content) || render_to_string(options)
36
-
37
- document = Htmltoword::Document.create(content, word_template, extras)
38
- send_data document, filename: file_name, type: Mime::DOCX, disposition: disposition
39
- end
40
-
41
- if defined? ActionController::Responder
42
- module ActionController
43
- # For respond_with default
44
- class Responder
45
- def to_docx
46
- if @default_response
47
- @default_response.call(options)
48
- else
49
- controller.render({ docx: controller.action_name }.merge(options))
50
- end
51
- end
52
- end
53
- end
54
- end
@@ -1,35 +0,0 @@
1
- module Htmltoword
2
- module HtmltowordHelper
3
-
4
- def template_file template_file_name=nil
5
- default_path = File.join(::Htmltoword.config.default_templates_path, 'default.docx')
6
- template_path = template_file_name.present? ? File.join(::Htmltoword.config.custom_templates_path, template_file_name) : ''
7
- File.exist?(template_path) ? template_path : default_path
8
- end
9
-
10
- def replace_values content, set
11
- doc = Nokogiri::HTML(content)
12
- set.each_pair do |key, value|
13
- fields = (doc/"//span[@data-id='#{key}']")
14
- fields.each do |f|
15
- date_format = f.attr("date-format") || "long"
16
- data_transform = f.attr("data-transform")
17
- if value.is_a? Hash
18
- view = ActionView::Base.new(ActionController::Base.view_paths, {})
19
- final_value = view.render 'partials/answer_table', answer: value
20
- fragment = doc.root.parse(final_value).first
21
- new_node = doc.root.add_child(fragment)
22
- f.parent.replace new_node
23
- elsif value.is_a? Time
24
- f.content = I18n.l(value.to_date, format: date_format.to_sym)
25
- elsif data_transform == 'capitalized'
26
- f.content = value.mb_chars.capitalize rescue value
27
- else
28
- f.content = value
29
- end
30
- end
31
- end
32
- doc.to_s
33
- end
34
- end
35
- end