xls_function 0.1.0
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 +7 -0
- data/.github/workflows/publish_gem.yml +25 -0
- data/.github/workflows/rspec.yml +30 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/.rubocop.yml +41 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/LICENSE +201 -0
- data/README.md +48 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/xls_function/class_dictionary.rb +7 -0
- data/lib/xls_function/converter.rb +24 -0
- data/lib/xls_function/converters/date_converter.rb +30 -0
- data/lib/xls_function/converters/date_serial_converter.rb +17 -0
- data/lib/xls_function/converters/number_converter.rb +28 -0
- data/lib/xls_function/converters/time_converter.rb +158 -0
- data/lib/xls_function/converters/time_serial_converter.rb +97 -0
- data/lib/xls_function/default_logger.rb +7 -0
- data/lib/xls_function/error.rb +49 -0
- data/lib/xls_function/evaluators/arguments_definable.rb +39 -0
- data/lib/xls_function/evaluators/binary_operation_evaluator.rb +20 -0
- data/lib/xls_function/evaluators/binary_operations/add.rb +13 -0
- data/lib/xls_function/evaluators/binary_operations/concat.rb +13 -0
- data/lib/xls_function/evaluators/binary_operations/divide.rb +13 -0
- data/lib/xls_function/evaluators/binary_operations/equal.rb +13 -0
- data/lib/xls_function/evaluators/binary_operations/greater_than.rb +13 -0
- data/lib/xls_function/evaluators/binary_operations/greater_than_or_equal_to.rb +13 -0
- data/lib/xls_function/evaluators/binary_operations/less_than.rb +13 -0
- data/lib/xls_function/evaluators/binary_operations/less_than_or_equal_to.rb +13 -0
- data/lib/xls_function/evaluators/binary_operations/multiple.rb +13 -0
- data/lib/xls_function/evaluators/binary_operations/not_equal.rb +13 -0
- data/lib/xls_function/evaluators/binary_operations/power.rb +13 -0
- data/lib/xls_function/evaluators/binary_operations/subtract.rb +13 -0
- data/lib/xls_function/evaluators/class_dictionary.rb +30 -0
- data/lib/xls_function/evaluators/error_detector.rb +29 -0
- data/lib/xls_function/evaluators/evaluable.rb +37 -0
- data/lib/xls_function/evaluators/false_evaluator.rb +19 -0
- data/lib/xls_function/evaluators/function_evaluator.rb +133 -0
- data/lib/xls_function/evaluators/functions/and.rb +21 -0
- data/lib/xls_function/evaluators/functions/asc.rb +17 -0
- data/lib/xls_function/evaluators/functions/char.rb +19 -0
- data/lib/xls_function/evaluators/functions/clean.rb +22 -0
- data/lib/xls_function/evaluators/functions/code.rb +18 -0
- data/lib/xls_function/evaluators/functions/concat.rb +27 -0
- data/lib/xls_function/evaluators/functions/date.rb +57 -0
- data/lib/xls_function/evaluators/functions/date_value.rb +17 -0
- data/lib/xls_function/evaluators/functions/day.rb +15 -0
- data/lib/xls_function/evaluators/functions/dbcs.rb +18 -0
- data/lib/xls_function/evaluators/functions/e_date.rb +19 -0
- data/lib/xls_function/evaluators/functions/e_o_month.rb +20 -0
- data/lib/xls_function/evaluators/functions/exact.rb +16 -0
- data/lib/xls_function/evaluators/functions/find.rb +28 -0
- data/lib/xls_function/evaluators/functions/fixed.rb +46 -0
- data/lib/xls_function/evaluators/functions/hour.rb +15 -0
- data/lib/xls_function/evaluators/functions/if.rb +24 -0
- data/lib/xls_function/evaluators/functions/ifs.rb +20 -0
- data/lib/xls_function/evaluators/functions/int.rb +15 -0
- data/lib/xls_function/evaluators/functions/iserror.rb +20 -0
- data/lib/xls_function/evaluators/functions/isnumber.rb +15 -0
- data/lib/xls_function/evaluators/functions/lambda.rb +57 -0
- data/lib/xls_function/evaluators/functions/left.rb +16 -0
- data/lib/xls_function/evaluators/functions/len.rb +15 -0
- data/lib/xls_function/evaluators/functions/let.rb +24 -0
- data/lib/xls_function/evaluators/functions/lower.rb +15 -0
- data/lib/xls_function/evaluators/functions/mid.rb +23 -0
- data/lib/xls_function/evaluators/functions/minute.rb +15 -0
- data/lib/xls_function/evaluators/functions/month.rb +15 -0
- data/lib/xls_function/evaluators/functions/not.rb +15 -0
- data/lib/xls_function/evaluators/functions/now.rb +19 -0
- data/lib/xls_function/evaluators/functions/or.rb +21 -0
- data/lib/xls_function/evaluators/functions/power.rb +16 -0
- data/lib/xls_function/evaluators/functions/proper.rb +17 -0
- data/lib/xls_function/evaluators/functions/replace.rb +19 -0
- data/lib/xls_function/evaluators/functions/rept.rb +25 -0
- data/lib/xls_function/evaluators/functions/right.rb +16 -0
- data/lib/xls_function/evaluators/functions/round.rb +16 -0
- data/lib/xls_function/evaluators/functions/round_down.rb +16 -0
- data/lib/xls_function/evaluators/functions/round_up.rb +16 -0
- data/lib/xls_function/evaluators/functions/second.rb +15 -0
- data/lib/xls_function/evaluators/functions/sqrt.rb +17 -0
- data/lib/xls_function/evaluators/functions/substitute.rb +44 -0
- data/lib/xls_function/evaluators/functions/text.rb +17 -0
- data/lib/xls_function/evaluators/functions/time.rb +54 -0
- data/lib/xls_function/evaluators/functions/timevalue.rb +17 -0
- data/lib/xls_function/evaluators/functions/today.rb +19 -0
- data/lib/xls_function/evaluators/functions/trim.rb +16 -0
- data/lib/xls_function/evaluators/functions/trunc.rb +16 -0
- data/lib/xls_function/evaluators/functions/unichar.rb +19 -0
- data/lib/xls_function/evaluators/functions/unicode.rb +18 -0
- data/lib/xls_function/evaluators/functions/upper.rb +15 -0
- data/lib/xls_function/evaluators/functions/value.rb +26 -0
- data/lib/xls_function/evaluators/functions/year.rb +15 -0
- data/lib/xls_function/evaluators/number_evaluator.rb +18 -0
- data/lib/xls_function/evaluators/string_evaluator.rb +18 -0
- data/lib/xls_function/evaluators/true_evaluator.rb +19 -0
- data/lib/xls_function/evaluators/variant_evaluator.rb +24 -0
- data/lib/xls_function/extensions/array_extension.rb +22 -0
- data/lib/xls_function/extensions/big_decimal_extension.rb +15 -0
- data/lib/xls_function/extensions/date_extension.rb +11 -0
- data/lib/xls_function/extensions/hash_extension.rb +24 -0
- data/lib/xls_function/extensions/time_extension.rb +11 -0
- data/lib/xls_function/format_string/evaluators/elapsed_time_evaluator.rb +42 -0
- data/lib/xls_function/format_string/evaluators/number_evaluator.rb +243 -0
- data/lib/xls_function/format_string/evaluators/time_evaluator.rb +103 -0
- data/lib/xls_function/format_string/parse_rules/dates.rb +53 -0
- data/lib/xls_function/format_string/parse_rules/numbers.rb +31 -0
- data/lib/xls_function/format_string/parse_rules/texts.rb +25 -0
- data/lib/xls_function/format_string/parse_rules/times.rb +55 -0
- data/lib/xls_function/format_string/parser.rb +27 -0
- data/lib/xls_function/format_string/transform.rb +87 -0
- data/lib/xls_function/format_string/transform_rules/dates.rb +98 -0
- data/lib/xls_function/format_string/transform_rules/numbers.rb +59 -0
- data/lib/xls_function/format_string/transform_rules/texts.rb +25 -0
- data/lib/xls_function/format_string/transform_rules/times.rb +97 -0
- data/lib/xls_function/format_string.rb +24 -0
- data/lib/xls_function/i18n.rb +4 -0
- data/lib/xls_function/locales/en.yml +65 -0
- data/lib/xls_function/locales/ja.yml +65 -0
- data/lib/xls_function/parse_rules/binary_operation.rb +28 -0
- data/lib/xls_function/parse_rules/common.rb +34 -0
- data/lib/xls_function/parser.rb +12 -0
- data/lib/xls_function/transform.rb +58 -0
- data/lib/xls_function/transform_rules/binary_operation_transform.rb +16 -0
- data/lib/xls_function/transform_rules/boolean_transform.rb +12 -0
- data/lib/xls_function/transform_rules/function_call_transform.rb +16 -0
- data/lib/xls_function/transform_rules/number_transform.rb +11 -0
- data/lib/xls_function/transform_rules/string_transform.rb +17 -0
- data/lib/xls_function/transform_rules/variant_transform.rb +13 -0
- data/lib/xls_function/user_defined_function_factory.rb +59 -0
- data/lib/xls_function/version.rb +3 -0
- data/lib/xls_function.rb +82 -0
- data/xls_function.gemspec +30 -0
- metadata +221 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module XlsFunction
|
|
2
|
+
module Evaluators
|
|
3
|
+
class NumberEvaluator
|
|
4
|
+
include Evaluable
|
|
5
|
+
|
|
6
|
+
attr_reader :source
|
|
7
|
+
|
|
8
|
+
def initialize(source, context)
|
|
9
|
+
@source = source
|
|
10
|
+
@context = context
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def eval
|
|
14
|
+
source.to_s.to_d
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module XlsFunction
|
|
2
|
+
module Evaluators
|
|
3
|
+
class StringEvaluator
|
|
4
|
+
include Evaluable
|
|
5
|
+
|
|
6
|
+
attr_reader :source
|
|
7
|
+
|
|
8
|
+
def initialize(source, context)
|
|
9
|
+
@source = source
|
|
10
|
+
@context = context
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def eval
|
|
14
|
+
source.to_s
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module XlsFunction
|
|
2
|
+
module Evaluators
|
|
3
|
+
class TrueEvaluator
|
|
4
|
+
include Evaluable
|
|
5
|
+
|
|
6
|
+
# unused, but hold for future logging...
|
|
7
|
+
attr_reader :source
|
|
8
|
+
|
|
9
|
+
def initialize(source, context)
|
|
10
|
+
@source = source
|
|
11
|
+
@context = context
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def eval
|
|
15
|
+
true
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module XlsFunction
|
|
2
|
+
module Evaluators
|
|
3
|
+
class VariantEvaluator
|
|
4
|
+
include Evaluable
|
|
5
|
+
|
|
6
|
+
attr_reader :name
|
|
7
|
+
|
|
8
|
+
def initialize(name, context)
|
|
9
|
+
@name = name.to_s
|
|
10
|
+
@context = context
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def variant_context
|
|
14
|
+
context[:variants]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def eval
|
|
18
|
+
raise ::XlsFunction::Transform::VariantUndefinedError, "name: `#{name}` is undefined" unless variant_context.key?(name)
|
|
19
|
+
|
|
20
|
+
variant_context[name]
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module XlsFunction
|
|
2
|
+
module Extensions
|
|
3
|
+
module ArrayExtension
|
|
4
|
+
class << self
|
|
5
|
+
def max_depth(array, depth = 1)
|
|
6
|
+
array.inject(depth) do |current_depth, item|
|
|
7
|
+
next current_depth unless item.is_a?(Array)
|
|
8
|
+
|
|
9
|
+
child_depth = max_depth(item, depth + 1)
|
|
10
|
+
[current_depth, child_depth].max
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
refine Array do
|
|
16
|
+
def max_depth(depth = 1)
|
|
17
|
+
ArrayExtension.max_depth(self, depth)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module XlsFunction
|
|
2
|
+
module Extensions
|
|
3
|
+
module BigDecimalExtension
|
|
4
|
+
refine BigDecimal do
|
|
5
|
+
def to_date
|
|
6
|
+
XlsFunction::Converters::NumberConverter.decimal_to_date(self)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def to_time(now: ::XlsFunction::Converters::DateSerialConverter::ORIGIN)
|
|
10
|
+
XlsFunction::Converters::NumberConverter.decimal_to_time(self, now: now)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module XlsFunction
|
|
2
|
+
module Extensions
|
|
3
|
+
module HashExtension
|
|
4
|
+
class << self
|
|
5
|
+
# @param [Hash] source
|
|
6
|
+
# @param [Hash] other
|
|
7
|
+
def deep_merge(source, other, &block)
|
|
8
|
+
source.merge(other) do |key, oldval, newval|
|
|
9
|
+
next deep_merge(oldval, newval, &block) if oldval.is_a?(Hash) && newval.is_a?(Hash)
|
|
10
|
+
next block.call(key, oldval, newval) if block_given?
|
|
11
|
+
|
|
12
|
+
newval
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
refine Hash do
|
|
18
|
+
def deep_merge(other, &block)
|
|
19
|
+
HashExtension.deep_merge(self, other, &block)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module XlsFunction
|
|
2
|
+
module FormatString
|
|
3
|
+
module Evaluators
|
|
4
|
+
class ElapsedTimeEvaluator
|
|
5
|
+
attr_reader :cache, :half, :parse_proc
|
|
6
|
+
|
|
7
|
+
using ::XlsFunction::Extensions::DateExtension
|
|
8
|
+
using ::XlsFunction::Extensions::TimeExtension
|
|
9
|
+
|
|
10
|
+
def initialize(cache, half: true, &parse_proc)
|
|
11
|
+
@cache = cache
|
|
12
|
+
@half = half
|
|
13
|
+
@parse_proc = parse_proc
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call(input)
|
|
17
|
+
time = ::XlsFunction::Converters::TimeConverter.convert_with_cache(input, cache)
|
|
18
|
+
|
|
19
|
+
result = parse_proc.call(self, time).to_i.to_s
|
|
20
|
+
half ? result : result.rjust(2, '0')
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def hour(time)
|
|
24
|
+
elapsed_days = days_from_origin(time)
|
|
25
|
+
elapsed_days * 24 + time.hour
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def minute(time)
|
|
29
|
+
hour(time) * 60 + time.min
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def second(time)
|
|
33
|
+
minute(time) * 60 + time.sec
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def days_from_origin(time)
|
|
37
|
+
time.to_date.to_serial
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
module XlsFunction
|
|
2
|
+
module FormatString
|
|
3
|
+
module Evaluators
|
|
4
|
+
# @todo refactoring...
|
|
5
|
+
# rubocop:disable Metrics/AbcSize
|
|
6
|
+
class NumberEvaluator
|
|
7
|
+
attr_reader :parts
|
|
8
|
+
|
|
9
|
+
def initialize(parts)
|
|
10
|
+
@parts = parts
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call(input)
|
|
14
|
+
extract_parts!(input)
|
|
15
|
+
value = adjust_value(input)
|
|
16
|
+
format_value(value)
|
|
17
|
+
rescue ArgumentError
|
|
18
|
+
input
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def interger_parts
|
|
22
|
+
@interger_parts ||= PartsList.new
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def decimal_parts
|
|
26
|
+
@decimal_parts ||= PartsList.new
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def parts_length
|
|
30
|
+
@parts_length ||= parts.length
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def extract_parts!(input)
|
|
34
|
+
parts_target = interger_parts
|
|
35
|
+
|
|
36
|
+
comma_index = []
|
|
37
|
+
@parts.each_with_index do |expr, i|
|
|
38
|
+
if expr.respond_to?(:digits?)
|
|
39
|
+
parts_target.add_parts(expr, i, true)
|
|
40
|
+
next
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# evaluate except number placeholder
|
|
44
|
+
str = expr.call(input)
|
|
45
|
+
# switch stack when decimal point comes
|
|
46
|
+
parts_target = decimal_parts if str == '.'
|
|
47
|
+
# count comma
|
|
48
|
+
if str == ','
|
|
49
|
+
comma_index << i
|
|
50
|
+
# ignore
|
|
51
|
+
next
|
|
52
|
+
end
|
|
53
|
+
# last % is parcentage flag
|
|
54
|
+
@has_parcentage = true if str == '%' && (i == parts_length - 1)
|
|
55
|
+
|
|
56
|
+
parts_target.add_parts(str, i, false)
|
|
57
|
+
end.compact
|
|
58
|
+
check_comma(comma_index)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# ↓ check comma
|
|
62
|
+
# When % exists, check 2 or 1 chars before from %.
|
|
63
|
+
# If not, check last 2 or 1 chars.
|
|
64
|
+
def comma_index_pattern_thousands
|
|
65
|
+
offset = @has_parcentage ? 2 : 1
|
|
66
|
+
[parts_length - offset]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def comma_index_pattern_millions
|
|
70
|
+
offset = @has_parcentage ? 2 : 1
|
|
71
|
+
[parts_length - offset - 1, parts_length - offset]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def check_comma(comma_index)
|
|
75
|
+
return if comma_index.empty?
|
|
76
|
+
|
|
77
|
+
if (comma_index_pattern_millions - comma_index).empty?
|
|
78
|
+
@unit = :millions
|
|
79
|
+
@format = true unless (comma_index - comma_index_pattern_millions).empty?
|
|
80
|
+
elsif (comma_index_pattern_thousands - comma_index).empty?
|
|
81
|
+
@unit = :thousands
|
|
82
|
+
@format = true unless (comma_index - comma_index_pattern_thousands).empty?
|
|
83
|
+
else
|
|
84
|
+
@format = true
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def adjust_value(value)
|
|
89
|
+
decimal_value = value.to_s.to_d
|
|
90
|
+
|
|
91
|
+
case @unit
|
|
92
|
+
when :millions
|
|
93
|
+
decimal_value /= '1000000'.to_d
|
|
94
|
+
when :thousands
|
|
95
|
+
decimal_value /= '1000'.to_d
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
decimal_value *= '100'.to_d if @has_parcentage
|
|
99
|
+
|
|
100
|
+
decimal_value
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def format_value(value)
|
|
104
|
+
value_stack = ValueStack.new(value, decimal_parts.size, @format)
|
|
105
|
+
|
|
106
|
+
# format decimals
|
|
107
|
+
decimal_chars =
|
|
108
|
+
create_dec_pair(decimal_parts, value_stack).reverse
|
|
109
|
+
.then { |value_pairs| eval_pairs(value_pairs) }
|
|
110
|
+
.reverse
|
|
111
|
+
.join
|
|
112
|
+
|
|
113
|
+
# format integers
|
|
114
|
+
integer_chars =
|
|
115
|
+
create_int_pair(interger_parts, value_stack).reverse
|
|
116
|
+
.then { |value_pairs| eval_pairs(value_pairs, int_remain: value_stack.int_remain?) }
|
|
117
|
+
.join
|
|
118
|
+
|
|
119
|
+
"#{!value_stack.empty? ? value_stack.to_s : ''}#{integer_chars}#{decimal_chars}"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def create_dec_pair(parts_list, value_stack)
|
|
123
|
+
parts_list.parts_with_index.map do |part, index|
|
|
124
|
+
if parts_list.num?(index)
|
|
125
|
+
[value_stack.shift_dec, part]
|
|
126
|
+
else
|
|
127
|
+
[part, nil] # means already evaluated
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def create_int_pair(parts_list, value_stack)
|
|
133
|
+
parts_list.parts_with_index.reverse.map do |part, index|
|
|
134
|
+
if parts_list.num?(index)
|
|
135
|
+
[value_stack.pop_int, part]
|
|
136
|
+
else
|
|
137
|
+
[part, nil]
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def eval_pairs(value_eval_pairs, int_remain: false)
|
|
143
|
+
require_zero = false
|
|
144
|
+
value_eval_pairs.map do |value, eval|
|
|
145
|
+
next value unless eval
|
|
146
|
+
|
|
147
|
+
evaluated_value = eval.call(value)
|
|
148
|
+
if evaluated_value.strip != '' && evaluated_value != '0'
|
|
149
|
+
require_zero = true
|
|
150
|
+
next evaluated_value
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# `#` returns empty if zero but must return zero if higher place exists.
|
|
154
|
+
next '0' if evaluated_value == '' && (require_zero || int_remain)
|
|
155
|
+
|
|
156
|
+
evaluated_value
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
# rubocop:enable Metrics/AbcSize
|
|
161
|
+
|
|
162
|
+
class PartsList
|
|
163
|
+
attr_reader :parts_with_index
|
|
164
|
+
|
|
165
|
+
def initialize
|
|
166
|
+
@parts_with_index = []
|
|
167
|
+
@num_indexies = []
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def add_parts(part, index, is_num)
|
|
171
|
+
@parts_with_index << [part, index]
|
|
172
|
+
@num_indexies << index if is_num
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def num?(index)
|
|
176
|
+
@num_indexies.include?(index)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def size
|
|
180
|
+
parts_with_index.size
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# decomposes a number and takes from last.
|
|
185
|
+
class ValueStack
|
|
186
|
+
def initialize(source, dec_digit, format)
|
|
187
|
+
# reduce 1 because dec_digit include decimal point.
|
|
188
|
+
# call to_d because round(0) in ruby 3.0 returns integer.
|
|
189
|
+
value = dec_digit.positive? ? source.round(dec_digit - 1).to_d : source.round(0).to_d
|
|
190
|
+
str = value.to_s('F')
|
|
191
|
+
int, dec = str.split('.')
|
|
192
|
+
|
|
193
|
+
@ints = format ? int_split_with_comma(int) : int.split('')
|
|
194
|
+
@decs = dec_digit.positive? ? dec.split('') : []
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def int_split_with_comma(int_s)
|
|
198
|
+
result = []
|
|
199
|
+
count = 0
|
|
200
|
+
arr = int_s.split('')
|
|
201
|
+
length = arr.length
|
|
202
|
+
length.times do |i|
|
|
203
|
+
result.unshift(arr.pop)
|
|
204
|
+
count += 1
|
|
205
|
+
next if count < 3 || i == length - 1
|
|
206
|
+
|
|
207
|
+
result.unshift(',')
|
|
208
|
+
count = 0
|
|
209
|
+
end
|
|
210
|
+
result
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def pop_int
|
|
214
|
+
s = @ints.pop
|
|
215
|
+
return s unless @ints.last == ','
|
|
216
|
+
|
|
217
|
+
# return with comma if exists
|
|
218
|
+
@ints.pop + s
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def shift_dec
|
|
222
|
+
@decs.shift
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def empty?
|
|
226
|
+
@ints.empty? && @decs.empty?
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def int_remain?
|
|
230
|
+
@ints.filter { |x| x != '-' }
|
|
231
|
+
.any?
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def to_s
|
|
235
|
+
s = @ints.join
|
|
236
|
+
return s if @decs.empty? || @decs == ['0']
|
|
237
|
+
|
|
238
|
+
"#{s}.#{@decs.join}"
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
module XlsFunction
|
|
2
|
+
module FormatString
|
|
3
|
+
module Evaluators
|
|
4
|
+
class TimeEvaluator
|
|
5
|
+
attr_reader :source, :cache, :minute_mode, :flags, :after_effect
|
|
6
|
+
|
|
7
|
+
def initialize(source, cache, minute_mode: false, flags: {}, &after_effect)
|
|
8
|
+
@source = source.to_s
|
|
9
|
+
@cache = cache
|
|
10
|
+
@minute_mode = minute_mode
|
|
11
|
+
@flags = flags
|
|
12
|
+
@after_effect = after_effect
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def call(input)
|
|
16
|
+
time = ::XlsFunction::Converters::TimeConverter.convert_with_cache(input, cache)
|
|
17
|
+
format = convert_format
|
|
18
|
+
result = convert_to_string(time, format)
|
|
19
|
+
return result unless after_effect
|
|
20
|
+
|
|
21
|
+
after_effect.call(self, result)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def convert_format
|
|
25
|
+
XlsFunction::Converters::TimeConverter.convert_format(modified_source, ampm_mode: ampm_mode)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def ampm_mode
|
|
29
|
+
flags[:ampm]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def modified_source
|
|
33
|
+
minute_mode ? source.upcase : source.downcase
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def convert_to_string(time, format)
|
|
37
|
+
if format.start_with?('%J')
|
|
38
|
+
time.to_date.strftime(format)
|
|
39
|
+
else
|
|
40
|
+
time.strftime(format)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# ↓ after_effects: treat formats not covered by strftime
|
|
45
|
+
def month(value)
|
|
46
|
+
return value[1..] if source == 'm' && value.start_with?('0')
|
|
47
|
+
return value[0] if source == 'mmmmm'
|
|
48
|
+
|
|
49
|
+
value
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def day(value)
|
|
53
|
+
return value[1..] if source == 'd' && value.start_with?('0')
|
|
54
|
+
|
|
55
|
+
value
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def weekday(value)
|
|
59
|
+
return ::XlsFunction::Converters::TimeConverter.convert_weekday_jp(value.to_i) if source == 'aaa'
|
|
60
|
+
return "#{::XlsFunction::Converters::TimeConverter.convert_weekday_jp(value.to_i)}曜日" if source == 'aaaa'
|
|
61
|
+
|
|
62
|
+
value
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def wareki(value)
|
|
66
|
+
return '元' if gannen && value == '1'
|
|
67
|
+
return value.rjust(2, '0') if %w[ee r].include?(source)
|
|
68
|
+
|
|
69
|
+
value
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def gannen
|
|
73
|
+
flags[:gannen]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def gengo(value)
|
|
77
|
+
return ::XlsFunction::Converters::TimeConverter.convert_wareki_to_alphabet(value) if source == 'g'
|
|
78
|
+
return value[0] if source == 'gg'
|
|
79
|
+
|
|
80
|
+
value
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def hour(value)
|
|
84
|
+
return value[1..] if source == 'h' && value.length == 2 && value.start_with?('0')
|
|
85
|
+
|
|
86
|
+
value
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def minute(value)
|
|
90
|
+
return value[1..] if source == 'm' && value.length == 2 && value.start_with?('0')
|
|
91
|
+
|
|
92
|
+
value
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def second(value)
|
|
96
|
+
return value[1..] if source == 's' && value.length == 2 && value.start_with?('0')
|
|
97
|
+
|
|
98
|
+
value
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module XlsFunction
|
|
2
|
+
module FormatString
|
|
3
|
+
module ParseRules
|
|
4
|
+
module Dates
|
|
5
|
+
def self.included(klass)
|
|
6
|
+
klass.class_eval do
|
|
7
|
+
rule(:year_half) { str('yy').as(:year) }
|
|
8
|
+
rule(:year) { str('yyyy').as(:year) }
|
|
9
|
+
rule(:month_half) { str('m').as(:month) }
|
|
10
|
+
rule(:month) { str('mm').as(:month) }
|
|
11
|
+
rule(:month_3) { str('mmm').as(:month) }
|
|
12
|
+
rule(:month_4) { str('mmmm').as(:month) }
|
|
13
|
+
rule(:month_5) { str('mmmmm').as(:month) }
|
|
14
|
+
rule(:day_half) { str('d').as(:day) }
|
|
15
|
+
rule(:day) { str('dd').as(:day) }
|
|
16
|
+
rule(:weekday) { str('aaa').as(:weekday) }
|
|
17
|
+
rule(:weekday_4) { str('aaaa').as(:weekday) }
|
|
18
|
+
rule(:weekday_d) { str('ddd').as(:weekday) }
|
|
19
|
+
rule(:weekday_d_4) { str('dddd').as(:weekday) }
|
|
20
|
+
|
|
21
|
+
rule(:wareki_year_half) { str('e').as(:wareki) }
|
|
22
|
+
rule(:wareki_year) { (str('ee') | str('r')).as(:wareki) }
|
|
23
|
+
rule(:gengo) { str('g').as(:gengo) }
|
|
24
|
+
rule(:gengo_2) { (str('gg')).as(:gengo) }
|
|
25
|
+
rule(:gengo_3) { str('ggg').as(:gengo) }
|
|
26
|
+
|
|
27
|
+
rule(:gengo_wareki) { (str('gggee') | str('rr')).as(:gengo_wareki) }
|
|
28
|
+
rule(:gannen) { str('[$-ja-JP-x-gannen]').as(:gannen) }
|
|
29
|
+
|
|
30
|
+
rule(:date) do
|
|
31
|
+
(
|
|
32
|
+
year | year_half |
|
|
33
|
+
month_5 | month_4 | month_3 | month | month_half |
|
|
34
|
+
day | day_half |
|
|
35
|
+
weekday_d_4 | weekday_d | weekday_4 | weekday |
|
|
36
|
+
gengo_wareki |
|
|
37
|
+
wareki_year | wareki_year_half |
|
|
38
|
+
gengo_3 | gengo_2 | gengo |
|
|
39
|
+
gannen
|
|
40
|
+
).as(:date)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
rule(:dates) do
|
|
44
|
+
(
|
|
45
|
+
date >> (texts.maybe >> date).repeat
|
|
46
|
+
).as(:dates)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module XlsFunction
|
|
2
|
+
module FormatString
|
|
3
|
+
module ParseRules
|
|
4
|
+
module Numbers
|
|
5
|
+
def self.included(klass)
|
|
6
|
+
klass.class_eval do
|
|
7
|
+
rule(:digit_s) { str('#').as(:digit_s) }
|
|
8
|
+
rule(:digit_z) { str('0').as(:digit_z) }
|
|
9
|
+
rule(:digit_q) { str('?').as(:digit_q) }
|
|
10
|
+
|
|
11
|
+
rule(:number) { (digit_s | digit_z | digit_q).as(:number) }
|
|
12
|
+
|
|
13
|
+
rule(:suffixable) do
|
|
14
|
+
str(',').as(:string).repeat(1, 2) |
|
|
15
|
+
str('%').as(:string) |
|
|
16
|
+
str('.').as(:string)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
rule(:numbers) do
|
|
20
|
+
(
|
|
21
|
+
(
|
|
22
|
+
number >> (texts.maybe >> number).repeat
|
|
23
|
+
) >> suffixable.maybe
|
|
24
|
+
).as(:numbers)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module XlsFunction
|
|
2
|
+
module FormatString
|
|
3
|
+
module ParseRules
|
|
4
|
+
module Texts
|
|
5
|
+
def self.included(klass)
|
|
6
|
+
klass.class_eval do
|
|
7
|
+
rule(:space) { match('\s').repeat(1) }
|
|
8
|
+
|
|
9
|
+
rule(:default) { str('G/標準').as(:placeholder) }
|
|
10
|
+
rule(:placeholder) { str('@').as(:placeholder) }
|
|
11
|
+
|
|
12
|
+
rule(:escaped_string) { str('!') >> match('.').as(:string) }
|
|
13
|
+
|
|
14
|
+
rule(:string) do
|
|
15
|
+
(expr_sep | time | ampm | date | number).absent? >> any.as(:string)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
rule(:text) { (escaped_string | placeholder | default | string).as(:text) }
|
|
19
|
+
rule(:texts) { text.repeat(1).as(:texts) }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|