Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

optimize XML serialization #55

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions lib/xlsxtream/core_extension.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
class Object
def to_xslx_value(cid, _, sst)
to_s.to_xslx_value(cid, false, sst)
end
end

class Numeric
def to_xslx_value(cid, _, _)
%Q{<c r="#{cid}" t="n"><v>#{self}</v></c>}
end
end

class TrueClass
def to_xslx_value(cid, _, _)
%Q{<c r="#{cid}" t="b"><v>1</v></c>}
end
end

class FalseClass
def to_xslx_value(cid, _, _)
%Q{<c r="#{cid}" t="b"><v>0</v></c>}
end
end

class Time
def to_xslx_value(cid, _, _)
# Local dates are stored as UTC by truncating the offset:
# 1970-01-01 00:00:00 +0200 => 1970-01-01 00:00:00 UTC
# This is done because SpreadsheetML is not timezone aware.
oa_date = (to_f + utc_offset) / 86400 + 25569

%Q{<c r="#{cid}" s="#{Xlsxtream::Row::TIME_STYLE}"><v>#{oa_date}</v></c>}
end
end

class DateTime
def to_xslx_value(cid, _, _)
_, jd, df, sf, of = marshal_dump
oa_date = jd - 2415019 + (df + of + sf / 1e9) / 86400

%Q{<c r="#{cid}" s="#{Xlsxtream::Row::TIME_STYLE}"><v>#{oa_date}</v></c>}
end
end

class Date
if RUBY_ENGINE == 'ruby'
def to_xslx_value(cid, _, _)
oa_date = (jd - 2415019).to_f
%Q{<c r="#{cid}" s="#{Xlsxtream::Row::DATE_STYLE}"><v>#{oa_date}</v></c>}
end
else
def to_xslx_value(cid, _, _)
oa_date = jd - 2415019 + (hour * 3600 + sec + sec_fraction.to_f) / 86400
%Q{<c r="#{cid}" s="#{Xlsxtream::Row::DATE_STYLE}"><v>#{oa_date}</v></c>}
end
end
end

class String
def to_xslx_value(cid, auto_format, sst)
if empty?
""
elsif auto_format
Xlsxtream::Row.auto_format(self).to_xslx_value(cid, false, sst)
else
if encoding != Xlsxtream::Row::ENCODING
value = encode(Xlsxtream::Row::ENCODING)
else
value = self
end

if sst
%Q{<c r="#{cid}" t="s"><v>#{sst[value]}</v></c>}
else
%Q{<c r="#{cid}" t="inlineStr"><is><t>#{Xlsxtream::XML.escape_value(value)}</t></is></c>}
end
end
end
end
91 changes: 20 additions & 71 deletions lib/xlsxtream/row.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# frozen_string_literal: true
require "date"
require "xlsxtream/core_extension"
require "xlsxtream/xml"

module Xlsxtream
Expand Down Expand Up @@ -31,85 +32,33 @@ def to_xml
xml = String.new(%Q{<row r="#{@rownum}">})

@row.each do |value|
cid = "#{column}#{@rownum}"
column.next!

if @auto_format && value.is_a?(String)
value = auto_format(value)
end

case value
when Numeric
xml << %Q{<c r="#{cid}" t="n"><v>#{value}</v></c>}
when TrueClass, FalseClass
xml << %Q{<c r="#{cid}" t="b"><v>#{value ? 1 : 0}</v></c>}
when Time
xml << %Q{<c r="#{cid}" s="#{TIME_STYLE}"><v>#{time_to_oa_date(value)}</v></c>}
when DateTime
xml << %Q{<c r="#{cid}" s="#{TIME_STYLE}"><v>#{datetime_to_oa_date(value)}</v></c>}
when Date
xml << %Q{<c r="#{cid}" s="#{DATE_STYLE}"><v>#{date_to_oa_date(value)}</v></c>}
else
value = value.to_s

unless value.empty? # no xml output for for empty strings
value = value.encode(ENCODING) if value.encoding != ENCODING

if @sst
xml << %Q{<c r="#{cid}" t="s"><v>#{@sst[value]}</v></c>}
else
xml << %Q{<c r="#{cid}" t="inlineStr"><is><t>#{XML.escape_value(value)}</t></is></c>}
end
end
unless value.nil?
xml << value.to_xslx_value("#{column}#{@rownum}", @auto_format, @sst)
end
column.next!
end

xml << '</row>'
end

private

# Detects and casts numbers, date, time in text
def auto_format(value)
case value
when TRUE_STRING
true
when FALSE_STRING
false
when NUMBER_PATTERN
value.include?('.') ? value.to_f : value.to_i
when DATE_PATTERN
Date.parse(value) rescue value
when TIME_PATTERN
DateTime.parse(value) rescue value
else
value
end
end

# Converts Time instance to OLE Automation Date
def time_to_oa_date(time)
# Local dates are stored as UTC by truncating the offset:
# 1970-01-01 00:00:00 +0200 => 1970-01-01 00:00:00 UTC
# This is done because SpreadsheetML is not timezone aware.
(time.to_f + time.utc_offset) / 86400 + 25569
end

# Converts DateTime instance to OLE Automation Date
if RUBY_ENGINE == 'ruby'
def datetime_to_oa_date(date)
_, jd, df, sf, of = date.marshal_dump
jd - 2415019 + (df + of + sf / 1e9) / 86400
end
else
def datetime_to_oa_date(date)
date.jd - 2415019 + (date.hour * 3600 + date.sec + date.sec_fraction.to_f) / 86400
class << self
def auto_format(value)
case value
when TRUE_STRING
true
when FALSE_STRING
false
when NUMBER_PATTERN
value.include?('.') ? value.to_f : value.to_i
when DATE_PATTERN
Date.parse(value) rescue value
when TIME_PATTERN
DateTime.parse(value) rescue value
else
value
end
end
end

# Converts Date instance to OLE Automation Date
def date_to_oa_date(date)
(date.jd - 2415019).to_f
end
end
end