write_xlsx 0.58.0 → 0.59.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.
Files changed (37) hide show
  1. data/README.rdoc +7 -1
  2. data/bin/extract_vba.rb +29 -0
  3. data/examples/add_vba_project.rb +36 -0
  4. data/examples/vbaProject.bin +0 -0
  5. data/lib/write_xlsx/chart.rb +22 -37
  6. data/lib/write_xlsx/package/conditional_format.rb +593 -0
  7. data/lib/write_xlsx/package/content_types.rb +17 -0
  8. data/lib/write_xlsx/package/packager.rb +26 -6
  9. data/lib/write_xlsx/package/relationships.rb +11 -1
  10. data/lib/write_xlsx/package/table.rb +284 -62
  11. data/lib/write_xlsx/utility.rb +179 -0
  12. data/lib/write_xlsx/version.rb +1 -1
  13. data/lib/write_xlsx/workbook.rb +14 -1
  14. data/lib/write_xlsx/worksheet.rb +667 -875
  15. data/test/package/table/test_table01.rb +1 -2
  16. data/test/package/table/test_table02.rb +1 -2
  17. data/test/package/table/test_table03.rb +1 -2
  18. data/test/package/table/test_table04.rb +1 -2
  19. data/test/package/table/test_table05.rb +1 -2
  20. data/test/package/table/test_table06.rb +1 -2
  21. data/test/package/table/test_table07.rb +1 -2
  22. data/test/package/table/test_table08.rb +1 -2
  23. data/test/package/table/test_table09.rb +1 -2
  24. data/test/package/table/test_table10.rb +1 -2
  25. data/test/package/table/test_table11.rb +1 -2
  26. data/test/package/table/test_table12.rb +1 -2
  27. data/test/package/table/test_write_auto_filter.rb +10 -3
  28. data/test/package/table/test_write_table_column.rb +9 -2
  29. data/test/package/table/test_write_table_style_info.rb +12 -11
  30. data/test/package/table/test_write_xml_declaration.rb +6 -1
  31. data/test/perl_output/add_vba_project.xlsm +0 -0
  32. data/test/regression/test_macro01.rb +29 -0
  33. data/test/regression/xlsx_files/macro01.xlsm +0 -0
  34. data/test/regression/xlsx_files/vbaProject01.bin +0 -0
  35. data/test/test_example_match.rb +22 -0
  36. data/test/vbaProject.bin +0 -0
  37. metadata +18 -3
@@ -5,6 +5,7 @@ require 'write_xlsx/format'
5
5
  require 'write_xlsx/drawing'
6
6
  require 'write_xlsx/compatibility'
7
7
  require 'write_xlsx/utility'
8
+ require 'write_xlsx/package/conditional_format'
8
9
  require 'tempfile'
9
10
 
10
11
  module Writexlsx
@@ -354,6 +355,7 @@ module Writexlsx
354
355
  attr_reader :writer, :set_rows, :col_formats # :nodoc:
355
356
  attr_accessor :vml_shape_id, :rel_count, :hlink_refs # :nodoc:
356
357
  attr_reader :comments_author # :nodoc:
358
+ attr_accessor :dxf_priority # :nodoc:
357
359
 
358
360
  def initialize(workbook, index, name) #:nodoc:
359
361
  @writer = Package::XMLWriterSimple.new
@@ -2901,133 +2903,6 @@ module Writexlsx
2901
2903
  write_formula(row, col, formula, format, value)
2902
2904
  end
2903
2905
 
2904
- #
2905
- # convert_date_time(date_time_string)
2906
- #
2907
- # The function takes a date and time in ISO8601 "yyyy-mm-ddThh:mm:ss.ss" format
2908
- # and converts it to a decimal number representing a valid Excel date.
2909
- #
2910
- # Dates and times in Excel are represented by real numbers. The integer part of
2911
- # the number stores the number of days since the epoch and the fractional part
2912
- # stores the percentage of the day in seconds. The epoch can be either 1900 or
2913
- # 1904.
2914
- #
2915
- # Parameter: Date and time string in one of the following formats:
2916
- # yyyy-mm-ddThh:mm:ss.ss # Standard
2917
- # yyyy-mm-ddT # Date only
2918
- # Thh:mm:ss.ss # Time only
2919
- #
2920
- # Returns:
2921
- # A decimal number representing a valid Excel date, or
2922
- # nil if the date is invalid.
2923
- #
2924
- def convert_date_time(date_time_string) #:nodoc:
2925
- date_time = date_time_string
2926
-
2927
- days = 0 # Number of days since epoch
2928
- seconds = 0 # Time expressed as fraction of 24h hours in seconds
2929
-
2930
- # Strip leading and trailing whitespace.
2931
- date_time.sub!(/^\s+/, '')
2932
- date_time.sub!(/\s+$/, '')
2933
-
2934
- # Check for invalid date char.
2935
- return nil if date_time =~ /[^0-9T:\-\.Z]/
2936
-
2937
- # Check for "T" after date or before time.
2938
- return nil unless date_time =~ /\dT|T\d/
2939
-
2940
- # Strip trailing Z in ISO8601 date.
2941
- date_time.sub!(/Z$/, '')
2942
-
2943
- # Split into date and time.
2944
- date, time = date_time.split(/T/)
2945
-
2946
- # We allow the time portion of the input DateTime to be optional.
2947
- if time
2948
- # Match hh:mm:ss.sss+ where the seconds are optional
2949
- if time =~ /^(\d\d):(\d\d)(:(\d\d(\.\d+)?))?/
2950
- hour = $1.to_i
2951
- min = $2.to_i
2952
- sec = $4.to_f || 0
2953
- else
2954
- return nil # Not a valid time format.
2955
- end
2956
-
2957
- # Some boundary checks
2958
- return nil if hour >= 24
2959
- return nil if min >= 60
2960
- return nil if sec >= 60
2961
-
2962
- # Excel expresses seconds as a fraction of the number in 24 hours.
2963
- seconds = (hour * 60* 60 + min * 60 + sec) / (24.0 * 60 * 60)
2964
- end
2965
-
2966
- # We allow the date portion of the input DateTime to be optional.
2967
- return seconds if date == ''
2968
-
2969
- # Match date as yyyy-mm-dd.
2970
- if date =~ /^(\d\d\d\d)-(\d\d)-(\d\d)$/
2971
- year = $1.to_i
2972
- month = $2.to_i
2973
- day = $3.to_i
2974
- else
2975
- return nil # Not a valid date format.
2976
- end
2977
-
2978
- # Set the epoch as 1900 or 1904. Defaults to 1900.
2979
- # Special cases for Excel.
2980
- unless date_1904?
2981
- return seconds if date == '1899-12-31' # Excel 1900 epoch
2982
- return seconds if date == '1900-01-00' # Excel 1900 epoch
2983
- return 60 + seconds if date == '1900-02-29' # Excel false leapday
2984
- end
2985
-
2986
-
2987
- # We calculate the date by calculating the number of days since the epoch
2988
- # and adjust for the number of leap days. We calculate the number of leap
2989
- # days by normalising the year in relation to the epoch. Thus the year 2000
2990
- # becomes 100 for 4 and 100 year leapdays and 400 for 400 year leapdays.
2991
- #
2992
- epoch = date_1904? ? 1904 : 1900
2993
- offset = date_1904? ? 4 : 0
2994
- norm = 300
2995
- range = year - epoch
2996
-
2997
- # Set month days and check for leap year.
2998
- mdays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
2999
- leap = 0
3000
- leap = 1 if year % 4 == 0 && year % 100 != 0 || year % 400 == 0
3001
- mdays[1] = 29 if leap != 0
3002
-
3003
- # Some boundary checks
3004
- return nil if year < epoch or year > 9999
3005
- return nil if month < 1 or month > 12
3006
- return nil if day < 1 or day > mdays[month - 1]
3007
-
3008
- # Accumulate the number of days since the epoch.
3009
- days = day # Add days for current month
3010
- (0 .. month-2).each do |m|
3011
- days += mdays[m] # Add days for past months
3012
- end
3013
- days += range * 365 # Add days for past years
3014
- days += ((range) / 4) # Add leapdays
3015
- days -= ((range + offset) /100) # Subtract 100 year leapdays
3016
- days += ((range + offset + norm)/400) # Add 400 year leapdays
3017
- days -= leap # Already counted above
3018
-
3019
- # Adjust for Excel erroneously treating 1900 as a leap year.
3020
- days += 1 if !date_1904? and days > 59
3021
-
3022
- date_time = sprintf("%0.10f", days + seconds)
3023
- date_time = date_time.sub(/\.?0+$/, '') if date_time =~ /\./
3024
- if date_time =~ /\./
3025
- date_time.to_f
3026
- else
3027
- date_time.to_i
3028
- end
3029
- end
3030
-
3031
2906
  #
3032
2907
  # :call-seq:
3033
2908
  # set_row(row [ , height, format, hidden, level, collapsed ] )
@@ -3222,6 +3097,9 @@ module Writexlsx
3222
3097
  #
3223
3098
  # This method handles the interface to Excel conditional formatting.
3224
3099
  #
3100
+ # This method contains a lot of parameters and is described in detail in
3101
+ # the section below.
3102
+ #
3225
3103
  # We allow the format to be called on one cell or a range of cells. The
3226
3104
  # hashref contains the formatting parameters and must be the last param:
3227
3105
  #
@@ -3240,170 +3118,604 @@ module Writexlsx
3240
3118
  # }
3241
3119
  # )
3242
3120
  #
3243
- # This method contains a lot of parameters and is described in detail in
3244
- # a separate section "CONDITIONAL FORMATTING IN EXCEL".
3121
+ # See also the conditional_format.rb program in the examples directory of
3122
+ # the distro.
3123
+ #
3124
+ # The conditional_formatting method is used to apply formatting based
3125
+ # on user defined criteria to an write_xlsx file.
3126
+ #
3127
+ # It can be applied to a single cell or a range of cells.
3128
+ # You can pass 3 parameters such as (row, col, {...})
3129
+ # or 5 parameters such as (first_row, first_col, last_row, last_col, {...}).
3130
+ # You can also use A1 style notation. For example:
3131
+ #
3132
+ # worksheet.conditional_formatting( 0, 0, {...} )
3133
+ # worksheet.conditional_formatting( 0, 0, 4, 1, {...} )
3134
+ #
3135
+ # # Which are the same as:
3136
+ #
3137
+ # worksheet.conditional_formatting( 'A1', {...} )
3138
+ # worksheet.conditional_formatting( 'A1:B5', {...} )
3139
+ #
3140
+ # Using A1 style notation is is also possible to specify
3141
+ # non-contiguous ranges, separated by a comma. For example:
3142
+ #
3143
+ # worksheet.conditional_formatting( 'A1:D5,A8:D12', {...} )
3144
+ # The last parameter in conditional_formatting must be a hash containing
3145
+ # the parameters that describe the type and style of the data validation.
3146
+ #
3147
+ # The main parameters are:
3148
+ #
3149
+ # :type
3150
+ # :format
3151
+ # :criteria
3152
+ # :value
3153
+ # :minimum
3154
+ # :maximum
3155
+ # Other, less commonly used parameters are:
3156
+ #
3157
+ # :min_type
3158
+ # :mid_type
3159
+ # :max_type
3160
+ # :min_value
3161
+ # :mid_value
3162
+ # :max_value
3163
+ # :min_color
3164
+ # :mid_color
3165
+ # :max_color
3166
+ # :bar_color
3167
+ # Additional parameters which are used for specific conditional format types
3168
+ # are shown in the relevant sections below.
3169
+ #
3170
+ # == :type
3171
+ #
3172
+ # This parameter is passed in a hash to conditional_formatting.
3173
+ #
3174
+ # The type parameter is used to set the type of conditional formatting
3175
+ # that you wish to apply. It is always required and it has no default value.
3176
+ # Allowable type values and their associated parameters are:
3177
+ #
3178
+ # Type Parameters
3179
+ # ====== ==========
3180
+ # 'cell' :criteria
3181
+ # :value
3182
+ # :minimum
3183
+ # :maximum
3184
+ #
3185
+ # 'date' :criteria
3186
+ # :value
3187
+ # :minimum
3188
+ # :maximum
3189
+ #
3190
+ # 'time_period' :criteria
3191
+ #
3192
+ # 'text' :criteria
3193
+ # :value
3194
+ #
3195
+ # 'average' :criteria
3196
+ #
3197
+ # 'duplicate' (none)
3198
+ #
3199
+ # 'unique' (none)
3200
+ #
3201
+ # 'top' :criteria
3202
+ # :value
3203
+ #
3204
+ # 'bottom' :criteria
3205
+ # :value
3206
+ #
3207
+ # 'blanks' (none)
3208
+ #
3209
+ # 'no_blanks' (none)
3210
+ #
3211
+ # 'errors' (none)
3212
+ #
3213
+ # 'no_errors' (none)
3214
+ #
3215
+ # '2_color_scale' (none)
3216
+ #
3217
+ # '3_color_scale' (none)
3218
+ #
3219
+ # 'data_bar' (none)
3220
+ #
3221
+ # 'formula' :criteria
3222
+ # All conditional formatting types have a format parameter, see below.
3223
+ # Other types and parameters such as icon sets will be added in time.
3224
+ #
3225
+ # == :type => 'cell'
3226
+ #
3227
+ # This is the most common conditional formatting type. It is used when
3228
+ # a format is applied to a cell based on a simple criterion. For example:
3229
+ #
3230
+ # worksheet.conditional_formatting( 'A1',
3231
+ # {
3232
+ # :type => 'cell',
3233
+ # :criteria => 'greater than',
3234
+ # :value => 5,
3235
+ # :format => red_format
3236
+ # }
3237
+ # )
3238
+ # Or, using the between criteria:
3239
+ #
3240
+ # worksheet.conditional_formatting( 'C1:C4',
3241
+ # {
3242
+ # :type => 'cell',
3243
+ # :criteria => 'between',
3244
+ # :minimum => 20,
3245
+ # :maximum => 30,
3246
+ # :format => green_format
3247
+ # }
3248
+ # )
3249
+ # == :criteria
3250
+ #
3251
+ # The criteria parameter is used to set the criteria by which the cell data
3252
+ # will be evaluated. It has no default value. The most common criteria
3253
+ # as applied to { type => 'cell' } are:
3254
+ #
3255
+ # 'between'
3256
+ # 'not between'
3257
+ # 'equal to' | '==' | '='
3258
+ # 'not equal to' | '!=' | '<>'
3259
+ # 'greater than' | '>'
3260
+ # 'less than' | '<'
3261
+ # 'greater than or equal to' | '>='
3262
+ # 'less than or equal to' | '<='
3263
+ # You can either use Excel's textual description strings,
3264
+ # in the first column above, or the more common symbolic alternatives.
3265
+ #
3266
+ # Additional criteria which are specific to other conditional format types
3267
+ # are shown in the relevant sections below.
3268
+ #
3269
+ # == :value
3270
+ #
3271
+ # The value is generally used along with the criteria parameter to set the
3272
+ # rule by which the cell data will be evaluated.
3273
+ #
3274
+ # :type => 'cell',
3275
+ # :criteria => '>',
3276
+ # :value => 5
3277
+ # :format => format
3278
+ # The value property can also be an cell reference.
3279
+ #
3280
+ # :type => 'cell',
3281
+ # :criteria => '>',
3282
+ # :value => '$C$1',
3283
+ # :format => format
3284
+ # == :format
3285
+ #
3286
+ # The format parameter is used to specify the format that will be applied
3287
+ # to the cell when the conditional formatting criterion is met.
3288
+ # The format is created using the add_format method in the same way as cell
3289
+ # formats:
3290
+ #
3291
+ # format = workbook.add_format( :bold => 1, :italic => 1 )
3292
+ #
3293
+ # worksheet.conditional_formatting( 'A1',
3294
+ # {
3295
+ # :type => 'cell',
3296
+ # :criteria => '>',
3297
+ # :value => 5
3298
+ # :format => format
3299
+ # }
3300
+ # )
3301
+ # The conditional format follows the same rules as in Excel:
3302
+ # it is superimposed over the existing cell format and not all font and
3303
+ # border properties can be modified. Font properties that can't be modified
3304
+ # are font name, font size, superscript and subscript.
3305
+ # The border property that cannot be modified is diagonal borders.
3306
+ #
3307
+ # Excel specifies some default formats to be used with conditional
3308
+ # formatting. You can replicate them using the following write_xlsx formats:
3309
+ #
3310
+ # # Light red fill with dark red text.
3311
+ #
3312
+ # format1 = workbook.add_format(
3313
+ # :bg_color => '#FFC7CE',
3314
+ # :color => '#9C0006'
3315
+ # )
3316
+ #
3317
+ # # Light yellow fill with dark yellow text.
3318
+ #
3319
+ # format2 = workbook.add_format(
3320
+ # :bg_color => '#FFEB9C',
3321
+ # :color => '#9C6500'
3322
+ # )
3323
+ #
3324
+ # # Green fill with dark green text.
3325
+ #
3326
+ # format3 = workbook.add_format(
3327
+ # :bg_color => '#C6EFCE',
3328
+ # :color => '#006100'
3329
+ # )
3330
+ # == :minimum
3331
+ #
3332
+ # The minimum parameter is used to set the lower limiting value when the
3333
+ # criteria is either 'between' or 'not between':
3334
+ #
3335
+ # :validate => 'integer',
3336
+ # :criteria => 'between',
3337
+ # :minimum => 1,
3338
+ # :maximum => 100
3339
+ # == :maximum
3340
+ #
3341
+ # The maximum parameter is used to set the upper limiting value when the
3342
+ # criteria is either 'between' or 'not between'. See the previous example.
3343
+ #
3344
+ # == :type => 'date'
3345
+ #
3346
+ # The date type is the same as the cell type and uses the same criteria
3347
+ # and values. However it allows the value, minimum and maximum properties
3348
+ # to be specified in the ISO8601 yyyy-mm-ddThh:mm:ss.sss date format which
3349
+ # is detailed in the write_date_time() method.
3350
+ #
3351
+ # worksheet.conditional_formatting( 'A1:A4',
3352
+ # {
3353
+ # :type => 'date',
3354
+ # :criteria => 'greater than',
3355
+ # :value => '2011-01-01T',
3356
+ # :format => format
3357
+ # }
3358
+ # )
3359
+ # == :type => 'time_period'
3360
+ #
3361
+ # The time_period type is used to specify Excel's "Dates Occurring" style
3362
+ # conditional format.
3363
+ #
3364
+ # worksheet.conditional_formatting( 'A1:A4',
3365
+ # {
3366
+ # :type => 'time_period',
3367
+ # :criteria => 'yesterday',
3368
+ # :format => format
3369
+ # }
3370
+ # )
3371
+ # The period is set in the criteria and can have one of the following
3372
+ # values:
3373
+ #
3374
+ # :criteria => 'yesterday',
3375
+ # :criteria => 'today',
3376
+ # :criteria => 'last 7 days',
3377
+ # :criteria => 'last week',
3378
+ # :criteria => 'this week',
3379
+ # :criteria => 'next week',
3380
+ # :criteria => 'last month',
3381
+ # :criteria => 'this month',
3382
+ # :criteria => 'next month'
3383
+ # == :type => 'text'
3384
+ #
3385
+ # The text type is used to specify Excel's "Specific Text" style conditional
3386
+ # format. It is used to do simple string matching using the criteria and
3387
+ # value parameters:
3388
+ #
3389
+ # worksheet.conditional_formatting( 'A1:A4',
3390
+ # {
3391
+ # :type => 'text',
3392
+ # :criteria => 'containing',
3393
+ # :value => 'foo',
3394
+ # :format => format
3395
+ # }
3396
+ # )
3397
+ # The criteria can have one of the following values:
3398
+ #
3399
+ # :criteria => 'containing',
3400
+ # :criteria => 'not containing',
3401
+ # :criteria => 'begins with',
3402
+ # :criteria => 'ends with'
3403
+ # The value parameter should be a string or single character.
3404
+ #
3405
+ # == :type => 'average'
3406
+ #
3407
+ # The average type is used to specify Excel's "Average" style conditional
3408
+ # format.
3409
+ #
3410
+ # worksheet.conditional_formatting( 'A1:A4',
3411
+ # {
3412
+ # :type => 'average',
3413
+ # :criteria => 'above',
3414
+ # :format => format
3415
+ # }
3416
+ # )
3417
+ # The type of average for the conditional format range is specified by the
3418
+ # criteria:
3419
+ #
3420
+ # :criteria => 'above',
3421
+ # :criteria => 'below',
3422
+ # :criteria => 'equal or above',
3423
+ # :criteria => 'equal or below',
3424
+ # :criteria => '1 std dev above',
3425
+ # :criteria => '1 std dev below',
3426
+ # :criteria => '2 std dev above',
3427
+ # :criteria => '2 std dev below',
3428
+ # :criteria => '3 std dev above',
3429
+ # :criteria => '3 std dev below'
3430
+ # == :type => 'duplicate'
3431
+ #
3432
+ # The duplicate type is used to highlight duplicate cells in a range:
3433
+ #
3434
+ # worksheet.conditional_formatting( 'A1:A4',
3435
+ # {
3436
+ # :type => 'duplicate',
3437
+ # :format => format
3438
+ # }
3439
+ # )
3440
+ # == :type => 'unique'
3441
+ #
3442
+ # The unique type is used to highlight unique cells in a range:
3443
+ #
3444
+ # worksheet.conditional_formatting( 'A1:A4',
3445
+ # {
3446
+ # :type => 'unique',
3447
+ # :format => format
3448
+ # }
3449
+ # )
3450
+ # == :type => 'top'
3451
+ #
3452
+ # The top type is used to specify the top n values by number or percentage
3453
+ # in a range:
3454
+ #
3455
+ # worksheet.conditional_formatting( 'A1:A4',
3456
+ # {
3457
+ # :type => 'top',
3458
+ # :value => 10,
3459
+ # :format => format
3460
+ # }
3461
+ # )
3462
+ # The criteria can be used to indicate that a percentage condition is
3463
+ # required:
3464
+ #
3465
+ # worksheet.conditional_formatting( 'A1:A4',
3466
+ # {
3467
+ # :type => 'top',
3468
+ # :value => 10,
3469
+ # :criteria => '%',
3470
+ # :format => format
3471
+ # }
3472
+ # )
3473
+ # == :type => 'bottom'
3474
+ #
3475
+ # The bottom type is used to specify the bottom n values by number or
3476
+ # percentage in a range.
3477
+ #
3478
+ # It takes the same parameters as top, see above.
3479
+ #
3480
+ # == :type => 'blanks'
3481
+ #
3482
+ # The blanks type is used to highlight blank cells in a range:
3483
+ #
3484
+ # worksheet.conditional_formatting( 'A1:A4',
3485
+ # {
3486
+ # :type => 'blanks',
3487
+ # :format => format
3488
+ # }
3489
+ # )
3490
+ # == :type => 'no_blanks'
3491
+ #
3492
+ # The no_blanks type is used to highlight non blank cells in a range:
3493
+ #
3494
+ # worksheet.conditional_formatting( 'A1:A4',
3495
+ # {
3496
+ # :type => 'no_blanks',
3497
+ # :format => format
3498
+ # }
3499
+ # )
3500
+ # == :type => 'errors'
3501
+ #
3502
+ # The errors type is used to highlight error cells in a range:
3503
+ #
3504
+ # worksheet.conditional_formatting( 'A1:A4',
3505
+ # {
3506
+ # :type => 'errors',
3507
+ # :format => format
3508
+ # }
3509
+ # )
3510
+ # == :type => 'no_errors'
3511
+ #
3512
+ # The no_errors type is used to highlight non error cells in a range:
3513
+ #
3514
+ # worksheet.conditional_formatting( 'A1:A4',
3515
+ # {
3516
+ # :type => 'no_errors',
3517
+ # :format => format
3518
+ # }
3519
+ # )
3520
+ # == :type => '2_color_scale'
3521
+ #
3522
+ # The 2_color_scale type is used to specify Excel's "2 Color Scale" style
3523
+ # conditional format.
3524
+ #
3525
+ # worksheet.conditional_formatting( 'A1:A12',
3526
+ # {
3527
+ # :type => '2_color_scale'
3528
+ # }
3529
+ # )
3530
+ # At the moment only the default colors and properties can be used. These
3531
+ # will be extended in time.
3532
+ #
3533
+ # == :type => '3_color_scale'
3534
+ #
3535
+ # The 3_color_scale type is used to specify Excel's "3 Color Scale" style
3536
+ # conditional format.
3537
+ #
3538
+ # worksheet.conditional_formatting( 'A1:A12',
3539
+ # {
3540
+ # :type => '3_color_scale'
3541
+ # }
3542
+ # )
3543
+ # At the moment only the default colors and properties can be used.
3544
+ # These will be extended in time.
3545
+ #
3546
+ # == :type => 'data_bar'
3547
+ #
3548
+ # The data_bar type is used to specify Excel's "Data Bar" style conditional
3549
+ # format.
3550
+ #
3551
+ # worksheet.conditional_formatting( 'A1:A12',
3552
+ # {
3553
+ # :type => 'data_bar',
3554
+ # }
3555
+ # )
3556
+ # At the moment only the default colors and properties can be used. These
3557
+ # will be extended in time.
3558
+ #
3559
+ # == :type => 'formula'
3560
+ #
3561
+ # The formula type is used to specify a conditional format based on
3562
+ # a user defined formula:
3563
+ #
3564
+ # worksheet.conditional_formatting( 'A1:A4',
3565
+ # {
3566
+ # :type => 'formula',
3567
+ # :criteria => '=$A$1 > 5',
3568
+ # :format => format
3569
+ # }
3570
+ # )
3571
+ # The formula is specified in the criteria.
3572
+ #
3573
+ # == :min_type, :mid_type, :max_type
3574
+ #
3575
+ # The min_type and max_type properties are available when the conditional
3576
+ # formatting type is 2_color_scale, 3_color_scale or data_bar. The mid_type
3577
+ # is available for 3_color_scale. The properties are used as follows:
3578
+ #
3579
+ # worksheet.conditional_formatting( 'A1:A12',
3580
+ # {
3581
+ # :type => '2_color_scale',
3582
+ # :min_type => 'percent',
3583
+ # :max_type => 'percent'
3584
+ # }
3585
+ # )
3586
+ # The available min/mid/max types are:
3587
+ #
3588
+ # 'num'
3589
+ # 'percent'
3590
+ # 'percentile'
3591
+ # 'formula'
3592
+ # == :min_value, :mid_value, :max_value
3593
+ #
3594
+ # The min_value and max_value properties are available when the conditional
3595
+ # formatting type is 2_color_scale, 3_color_scale or data_bar. The mid_value
3596
+ # is available for 3_color_scale. The properties are used as follows:
3597
+ #
3598
+ # worksheet.conditional_formatting( 'A1:A12',
3599
+ # {
3600
+ # :type => '2_color_scale',
3601
+ # :min_value => 10,
3602
+ # :max_value => 90
3603
+ # }
3604
+ # )
3605
+ # == :min_color, :mid_color, :max_color, :bar_color
3606
+ #
3607
+ # The min_color and max_color properties are available when the conditional
3608
+ # formatting type is 2_color_scale, 3_color_scale or data_bar. The mid_color
3609
+ # is available for 3_color_scale. The properties are used as follows:
3610
+ #
3611
+ # worksheet.conditional_formatting( 'A1:A12',
3612
+ # {
3613
+ # ;type => '2_color_scale',
3614
+ # :min_color => "#C5D9F1",
3615
+ # :max_color => "#538ED5"
3616
+ # }
3617
+ # )
3618
+ # The color can be specifies as an Excel::Writer::XLSX color index or,
3619
+ # more usefully, as a HTML style RGB hex number, as shown above.
3620
+ #
3621
+ # == Conditional Formatting Examples
3622
+ #
3623
+ # === Example 1. Highlight cells greater than an integer value.
3624
+ #
3625
+ # worksheet.conditional_formatting( 'A1:F10',
3626
+ # {
3627
+ # :type => 'cell',
3628
+ # :criteria => 'greater than',
3629
+ # :value => 5,
3630
+ # :format => format
3631
+ # }
3632
+ # )
3633
+ # === Example 2. Highlight cells greater than a value in a reference cell.
3634
+ #
3635
+ # worksheet.conditional_formatting( 'A1:F10',
3636
+ # {
3637
+ # :type => 'cell',
3638
+ # :criteria => 'greater than',
3639
+ # :value => '$H$1',
3640
+ # :format => format
3641
+ # }
3642
+ # )
3643
+ # === Example 3. Highlight cells greater than a certain date:
3644
+ #
3645
+ # worksheet.conditional_formatting( 'A1:F10',
3646
+ # {
3647
+ # :type => 'date',
3648
+ # :criteria => 'greater than',
3649
+ # :value => '2011-01-01T',
3650
+ # :format => format
3651
+ # }
3652
+ # )
3653
+ # === Example 4. Highlight cells with a date in the last seven days:
3654
+ #
3655
+ # worksheet.conditional_formatting( 'A1:F10',
3656
+ # {
3657
+ # :type => 'time_period',
3658
+ # :criteria => 'last 7 days',
3659
+ # :format => format
3660
+ # }
3661
+ # )
3662
+ # === Example 5. Highlight cells with strings starting with the letter b:
3663
+ #
3664
+ # worksheet.conditional_formatting( 'A1:F10',
3665
+ # {
3666
+ # :type => 'text',
3667
+ # :criteria => 'begins with',
3668
+ # :value => 'b',
3669
+ # :format => format
3670
+ # }
3671
+ # )
3672
+ # === Example 6. Highlight cells that are 1 std deviation above the average for the range:
3673
+ #
3674
+ # worksheet.conditional_formatting( 'A1:F10',
3675
+ # {
3676
+ # :type => 'average',
3677
+ # :format => format
3678
+ # }
3679
+ # )
3680
+ # === Example 7. Highlight duplicate cells in a range:
3681
+ #
3682
+ # worksheet.conditional_formatting( 'A1:F10',
3683
+ # {
3684
+ # :type => 'duplicate',
3685
+ # :format => format
3686
+ # }
3687
+ # )
3688
+ # === Example 8. Highlight unique cells in a range.
3689
+ #
3690
+ # worksheet.conditional_formatting( 'A1:F10',
3691
+ # {
3692
+ # :type => 'unique',
3693
+ # :format => format
3694
+ # }
3695
+ # )
3696
+ # === Example 9. Highlight the top 10 cells.
3245
3697
  #
3246
- # See also the conditional_format.rb program in the examples directory of the distro
3698
+ # worksheet.conditional_formatting( 'A1:F10',
3699
+ # {
3700
+ # :type => 'top',
3701
+ # :value => 10,
3702
+ # :format => format
3703
+ # }
3704
+ # )
3705
+ # === Example 10. Highlight blank cells.
3706
+ #
3707
+ # worksheet.conditional_formatting( 'A1:F10',
3708
+ # {
3709
+ # :type => 'blanks',
3710
+ # :format => format
3711
+ # }
3712
+ # )
3713
+ # See also the conditional_format.rb example program in EXAMPLES.
3247
3714
  #
3248
3715
  def conditional_formatting(*args)
3249
- # Check for a cell reference in A1 notation and substitute row and column
3250
- if args[0] =~ /^\D/
3251
- # Check for a user defined multiple range like B3:K6,B8:K11.
3252
- user_range = args[0].gsub(/\s*,\s*/, ' ').gsub(/\$/, '') if args[0] =~ /,/
3253
- end
3254
- row1, col1, row2, col2, param = row_col_notation(args)
3255
- if row2.respond_to?(:keys)
3256
- param = row2
3257
- row2, col2 = row1, col1
3258
- end
3259
- raise WriteXLSXInsufficientArgumentError if [row1, col1, row2, col2, param].include?(nil)
3260
-
3261
- # Check that row and col are valid without storing the values.
3262
- check_dimensions(row1, col1)
3263
- check_dimensions(row2, col2)
3264
- check_conditional_formatting_parameters(param)
3265
-
3266
- # Swap last row/col for first row/col as necessary
3267
- row1, row2 = row2, row1 if row1 > row2
3268
- col1, col2 = col2, col1 if col1 > col2
3269
-
3270
- # If the first and last cell are the same write a single cell.
3271
- if row1 == row2 && col1 == col2
3272
- range = xl_rowcol_to_cell(row1, col1)
3273
- start_cell = range
3274
- else
3275
- range = xl_range(row1, row2, col1, col2)
3276
- start_cell = xl_rowcol_to_cell(row1, col1)
3277
- end
3278
-
3279
- # Override with user defined multiple range if provided.
3280
- range = user_range if user_range
3281
-
3282
- param[:format] = param[:format].get_dxf_index if param[:format]
3283
- param[:priority] = @dxf_priority
3284
- @dxf_priority += 1
3285
-
3286
- # Special handling of text criteria.
3287
- if param[:type] == 'text'
3288
- case param[:criteria]
3289
- when 'containsText'
3290
- param[:type] = 'containsText';
3291
- param[:formula] = %Q!NOT(ISERROR(SEARCH("#{param[:value]}",#{start_cell})))!
3292
- when 'notContains'
3293
- param[:type] = 'notContainsText';
3294
- param[:formula] = %Q!ISERROR(SEARCH("#{param[:value]}",#{start_cell}))!
3295
- when 'beginsWith'
3296
- param[:type] = 'beginsWith'
3297
- param[:formula] =
3298
- %Q!LEFT(#{start_cell},#{param[:value].size})="#{param[:value]}"!
3299
- when 'endsWith'
3300
- param[:type] = 'endsWith'
3301
- param[:formula] =
3302
- %Q!RIGHT(#{start_cell},#{param[:value].size})="#{param[:value]}"!
3303
- else
3304
- raise "Invalid text criteria '#{param[:criteria]} in conditional_formatting()"
3305
- end
3306
- end
3307
-
3308
- # Special handling of time time_period criteria.
3309
- if param[:type] == 'timePeriod'
3310
- case param[:criteria]
3311
- when 'yesterday'
3312
- param[:formula] = "FLOOR(#{start_cell},1)=TODAY()-1"
3313
- when 'today'
3314
- param[:formula] = "FLOOR(#{start_cell},1)=TODAY()"
3315
- when 'tomorrow'
3316
- param[:formula] = "FLOOR(#{start_cell},1)=TODAY()+1"
3317
- when 'last7Days'
3318
- param[:formula] =
3319
- "AND(TODAY()-FLOOR(#{start_cell},1)<=6,FLOOR(#{start_cell},1)<=TODAY())"
3320
- when 'lastWeek'
3321
- param[:formula] =
3322
- "AND(TODAY()-ROUNDDOWN(#{start_cell},0)>=(WEEKDAY(TODAY())),TODAY()-ROUNDDOWN(#{start_cell},0)<(WEEKDAY(TODAY())+7))"
3323
- when 'thisWeek'
3324
- param[:formula] =
3325
- "AND(TODAY()-ROUNDDOWN(#{start_cell},0)<=WEEKDAY(TODAY())-1,ROUNDDOWN(#{start_cell},0)-TODAY()<=7-WEEKDAY(TODAY()))"
3326
- when 'nextWeek'
3327
- param[:formula] =
3328
- "AND(ROUNDDOWN(#{start_cell},0)-TODAY()>(7-WEEKDAY(TODAY())),ROUNDDOWN(#{start_cell},0)-TODAY()<(15-WEEKDAY(TODAY())))"
3329
- when 'lastMonth'
3330
- param[:formula] =
3331
- "AND(MONTH(#{start_cell})=MONTH(TODAY())-1,OR(YEAR(#{start_cell})=YEAR(TODAY()),AND(MONTH(#{start_cell})=1,YEAR(A1)=YEAR(TODAY())-1)))"
3332
- when 'thisMonth'
3333
- param[:formula] =
3334
- "AND(MONTH(#{start_cell})=MONTH(TODAY()),YEAR(#{start_cell})=YEAR(TODAY()))"
3335
- when 'nextMonth'
3336
- param[:formula] =
3337
- "AND(MONTH(#{start_cell})=MONTH(TODAY())+1,OR(YEAR(#{start_cell})=YEAR(TODAY()),AND(MONTH(#{start_cell})=12,YEAR(#{start_cell})=YEAR(TODAY())+1)))"
3338
- else
3339
- raise "Invalid time_period criteria '#{param[:criteria]}' in conditional_formatting()"
3340
- end
3341
- end
3342
-
3343
- # Special handling of blanks/error types.
3344
- case param[:type]
3345
- when 'containsBlanks'
3346
- param[:formula] = "LEN(TRIM(#{start_cell}))=0"
3347
- when 'notContainsBlanks'
3348
- param[:formula] = "LEN(TRIM(#{start_cell}))>0"
3349
- when 'containsErrors'
3350
- param[:formula] = "ISERROR(#{start_cell})"
3351
- when 'notContainsErrors'
3352
- param[:formula] = "NOT(ISERROR(#{start_cell}))"
3353
- when '2_color_scale'
3354
- param[:type] = 'colorScale'
3355
-
3356
- # Color scales don't use any additional formatting.
3357
- param[:format] = nil
3358
-
3359
- # Turn off 3 color parameters.
3360
- param[:mid_type] = nil
3361
- param[:mid_color] = nil
3362
-
3363
- param[:min_type] ||= 'min'
3364
- param[:max_type] ||= 'max'
3365
- param[:min_value] ||= 0
3366
- param[:max_value] ||= 0
3367
- param[:min_color] ||= '#FF7128'
3368
- param[:max_color] ||= '#FFEF9C'
3369
-
3370
- param[:max_color] = get_palette_color( param[:max_color] )
3371
- param[:min_color] = get_palette_color( param[:min_color] )
3372
- when '3_color_scale'
3373
- param[:type] = 'colorScale'
3374
-
3375
- # Color scales don't use any additional formatting.
3376
- param[:format] = nil
3377
-
3378
- param[:min_type] ||= 'min'
3379
- param[:mid_type] ||= 'percentile'
3380
- param[:max_type] ||= 'max'
3381
- param[:min_value] ||= 0
3382
- param[:mid_value] ||= 50
3383
- param[:max_value] ||= 0
3384
- param[:min_color] ||= '#F8696B'
3385
- param[:mid_color] ||= '#FFEB84'
3386
- param[:max_color] ||= '#63BE7B'
3387
-
3388
- param[:max_color] = get_palette_color(param[:max_color])
3389
- param[:mid_color] = get_palette_color(param[:mid_color])
3390
- param[:min_color] = get_palette_color(param[:min_color])
3391
- when 'dataBar'
3392
- # Color scales don't use any additional formatting.
3393
- param[:format] = nil
3394
-
3395
- param[:min_type] ||= 'min'
3396
- param[:max_type] ||= 'max'
3397
- param[:min_value] ||= 0
3398
- param[:max_value] ||= 0
3399
- param[:bar_color] ||= '#638EC6'
3400
-
3401
- param[:bar_color] = get_palette_color(param[:bar_color])
3402
- end
3403
-
3404
- # Store the validation information until we close the worksheet.
3405
- @cond_formats[range] ||= []
3406
- @cond_formats[range] << param
3716
+ cond_format = Package::ConditionalFormat.factory(self, *args)
3717
+ @cond_formats[cond_format.range] ||= []
3718
+ @cond_formats[cond_format.range] << cond_format
3407
3719
  end
3408
3720
 
3409
3721
  #
@@ -3420,218 +3732,37 @@ module Writexlsx
3420
3732
  # See also the tables.rb program in the examples directory of the distro
3421
3733
  #
3422
3734
  def add_table(*args)
3423
- col_formats = []
3424
- =begin
3425
- # We would need to order the write statements very carefully within this
3426
- # function to support optimisation mode. Disable add_table() when it is
3427
- # on for now.
3428
- if @optimization
3429
- carp "add_table() isn't supported when set_optimization() is on"
3430
- return -1
3431
- end
3432
- =end
3433
- # Check for a cell reference in A1 notation and substitute row and column
3434
- row1, col1, row2, col2, param = row_col_notation(args)
3435
-
3436
- # Check for a valid number of args.
3437
- raise "Not enough parameters to add_table()" if [row1, col1, row2, col2].include?(nil)
3438
-
3439
- # Check that row and col are valid without storing the values.
3440
- check_dimensions_and_update_max_min_values(row1, col1, 1, 1)
3441
- check_dimensions_and_update_max_min_values(row2, col2, 1, 1)
3442
-
3443
- # The final hashref contains the validation parameters.
3444
- param ||= {}
3445
-
3446
- check_parameter(param, valid_table_parameter, 'add_table')
3447
-
3448
3735
  # Table count is a member of Workbook, global to all Worksheet.
3449
3736
  @workbook.table_count += 1
3450
- table = {}
3451
- table[:_columns] = []
3452
- table[:id] = @workbook.table_count
3453
-
3454
- # Turn on Excel's defaults.
3455
- param[:banded_rows] ||= 1
3456
- param[:header_row] ||= 1
3457
- param[:autofilter] ||= 1
3458
-
3459
- # Set the table options.
3460
- table[:_show_first_col] = ptrue?(param[:first_column]) ? 1 : 0
3461
- table[:_show_last_col] = ptrue?(param[:last_column]) ? 1 : 0
3462
- table[:_show_row_stripes] = ptrue?(param[:banded_rows]) ? 1 : 0
3463
- table[:_show_col_stripes] = ptrue?(param[:banded_columns]) ? 1 : 0
3464
- table[:_header_row_count] = ptrue?(param[:header_row]) ? 1 : 0
3465
- table[:_totals_row_shown] = ptrue?(param[:total_row]) ? 1 : 0
3466
-
3467
- # Set the table name.
3468
- if param[:name]
3469
- table[:_name] = param[:name]
3470
- else
3471
- # Set a default name.
3472
- table[:_name] = "Table#{table[:id]}"
3473
- end
3474
-
3475
- # Set the table style.
3476
- if param[:style]
3477
- table[:_style] = param[:style]
3478
- # Remove whitespace from style name.
3479
- table[:_style].gsub!(/\s/, '')
3480
- else
3481
- table[:_style] = "TableStyleMedium9"
3482
- end
3483
-
3484
- # Swap last row/col for first row/col as necessary.
3485
- row1, row2 = row2, row1 if row1 > row2
3486
- col1, col2 = col2, col1 if col1 > col2
3737
+ table = Package::Table.new(self, @workbook.table_count, *args)
3487
3738
 
3488
- # Set the data range rows (without the header and footer).
3489
- first_data_row = row1
3490
- last_data_row = row2
3491
- first_data_row += 1 if param[:header_row] != 0
3492
- last_data_row -= 1 if param[:total_row]
3493
-
3494
- # Set the table and autofilter ranges.
3495
- table[:_range] = xl_range(row1, row2, col1, col2)
3496
- table[:_a_range] = xl_range(row1, last_data_row, col1, col2)
3497
-
3498
- # If the header row if off the default is to turn autofilter off.
3499
- param[:autofilter] = 0 if param[:header_row] == 0
3500
-
3501
- # Set the autofilter range.
3502
- if param[:autofilter] && param[:autofilter] != 0
3503
- table[:_autofilter] = table[:_a_range]
3504
- end
3505
-
3506
- # Add the table columns.
3507
- col_id = 1
3508
- (col1..col2).each do |col_num|
3509
- # Set up the default column data.
3510
- col_data = {
3511
- :_id => col_id,
3512
- :_name => "Column#{col_id}",
3513
- :_total_string => '',
3514
- :_total_function => '',
3515
- :_formula => '',
3516
- :_format => nil
3517
- }
3518
-
3519
- # Overwrite the defaults with any use defined values.
3520
- if param[:columns]
3521
- # Check if there are user defined values for this column.
3522
- if user_data = param[:columns][col_id - 1]
3523
- # Map user defined values to internal values.
3524
- if user_data[:header] && !user_data[:header].empty?
3525
- col_data[:_name] = user_data[:header]
3526
- end
3527
- # Handle the column formula.
3528
- if user_data[:formula]
3529
- formula = user_data[:formula]
3530
- # Remove the leading = from formula.
3531
- formula.sub!(/^=/, '')
3532
- # Covert Excel 2010 "@" ref to 2007 "#This Row".
3533
- formula.gsub!(/@/,'[#This Row],')
3534
-
3535
- col_data[:_formula] = formula
3536
-
3537
- (first_data_row..last_data_row).each do |row|
3538
- write_formula(row, col_num, formula, user_data[:format])
3539
- end
3540
- end
3541
-
3542
- # Handle the function for the total row.
3543
- if user_data[:total_function]
3544
- function = user_data[:total_function]
3545
-
3546
- # Massage the function name.
3547
- function = function.downcase
3548
- function.gsub!(/_/, '')
3549
- function.gsub!(/\s/,'')
3550
-
3551
- function = 'countNums' if function == 'countnums'
3552
- function = 'stdDev' if function == 'stddev'
3553
-
3554
- col_data[:_total_function] = function
3555
-
3556
- formula = table_function_to_formula(function, col_data[:_name])
3557
- write_formula(row2, col_num, formula, user_data[:format])
3558
- elsif user_data[:total_string]
3559
- # Total label only (not a function).
3560
- total_string = user_data[:total_string]
3561
- col_data[:_total_string] = total_string
3562
-
3563
- write_string(row2, col_num, total_string, user_data[:format])
3564
- end
3565
-
3566
- # Get the dxf format index.
3567
- if user_data[:format]
3568
- col_data[:_format] = user_data[:format].get_dxf_index
3569
- end
3570
-
3571
- # Store the column format for writing the cell data.
3572
- # It doesn't matter if it is undefined.
3573
- col_formats[col_id - 1] = user_data[:format]
3574
- end
3575
- end
3576
-
3577
- # Store the column data.
3578
- table[:_columns] << col_data
3579
-
3580
- # Write the column headers to the worksheet.
3581
- if param[:header_row] != 0
3582
- write_string(row1, col_num, col_data[:_name])
3583
- end
3584
-
3585
- col_id += 1
3586
- end # Table columns.
3587
-
3588
- # Write the cell data if supplied.
3589
- if data = param[:data]
3590
-
3591
- i = 0 # For indexing the row data.
3592
- (first_data_row..last_data_row).each do |row|
3593
- next unless data[i]
3594
-
3595
- j = 0 # For indexing the col data.
3596
- (col1..col2).each do |col|
3597
- token = data[i][j]
3598
- write(row, col, token, col_formats[j]) if token
3599
- j += 1
3600
- end
3601
- i += 1
3602
- end
3603
- end
3604
-
3605
- # Store the table data.
3739
+ @external_table_links << ['/table', "../tables/table#{table.id}.xml"]
3606
3740
  @tables << table
3607
-
3608
- # Store the link used for the rels file.
3609
- @external_table_links << ['/table', "../tables/table#{table[:id]}.xml"]
3610
-
3611
- return table
3741
+ table
3612
3742
  end
3613
3743
 
3614
- # List of valid input parameters.
3615
- def valid_table_parameter
3616
- [
3617
- :autofilter,
3618
- :banded_columns,
3619
- :banded_rows,
3620
- :columns,
3621
- :data,
3622
- :first_column,
3623
- :header_row,
3624
- :last_column,
3625
- :name,
3626
- :style,
3627
- :total_row
3628
- ]
3629
- end
3630
- private :valid_table_parameter
3631
-
3632
3744
  #
3633
3745
  # Add sparklines to the worksheet.
3634
3746
  #
3747
+ # The add_sparkline worksheet method is used to add sparklines to a cell or a range of cells.
3748
+ #
3749
+ # worksheet.add_sparkline(
3750
+ # {
3751
+ # :location => 'F2',
3752
+ # :range => 'Sheet1!A2:E2',
3753
+ # :type => 'column',
3754
+ # :style => 12
3755
+ # }
3756
+ # )
3757
+ #
3758
+ # See also the sparklines1.rb and sparklines2.rb example programs in the examples directory of the distro.
3759
+ #
3760
+ # Note: Sparklines are a feature of Excel 2010+ only.
3761
+ # You can write them to an XLSX file that can be read by Excel 2007 but they won't be displayed.
3762
+ #
3763
+ # Sparklines are a feature of Excel 2010+ which allows you to add small charts to worksheet cells.
3764
+ # These are useful for showing visual trends in data in a compact format.
3765
+ #
3635
3766
  def add_sparkline(param)
3636
3767
  sparkline = {}
3637
3768
 
@@ -4840,29 +4971,31 @@ module Writexlsx
4840
4971
  @writer.data_element('f', formula, attributes)
4841
4972
  end
4842
4973
 
4843
- private
4974
+ def date_1904? #:nodoc:
4975
+ @workbook.date_1904?
4976
+ end
4844
4977
 
4845
4978
  #
4846
- # Convert a table total function to a worksheet formula.
4979
+ # Convert from an Excel internal colour index to a XML style #RRGGBB index
4980
+ # based on the default or user defined values in the Workbook palette.
4847
4981
  #
4848
- def table_function_to_formula(function, col_name)
4849
- subtotals = {
4850
- :average => 101,
4851
- :countNums => 102,
4852
- :count => 103,
4853
- :max => 104,
4854
- :min => 105,
4855
- :stdDev => 107,
4856
- :sum => 109,
4857
- :var => 110
4858
- }
4859
-
4860
- unless func_num = subtotals[function.to_sym]
4861
- raise "Unsupported function '#{function}' in add_table()"
4982
+ def get_palette_color(index) #:nodoc:
4983
+ if index =~ /^#([0-9A-F]{6})$/i
4984
+ return "FF#{$~[1]}"
4862
4985
  end
4863
- "SUBTOTAL(#{func_num},[#{col_name}])"
4986
+
4987
+ # Adjust the colour index.
4988
+ index -= 8
4989
+
4990
+ # Palette is passed in from the Workbook class.
4991
+ rgb = @workbook.palette[index]
4992
+
4993
+ # TODO Add the alpha part to the RGB.
4994
+ sprintf("FF%02X%02X%02X", *rgb[0, 3])
4864
4995
  end
4865
4996
 
4997
+ private
4998
+
4866
4999
  def check_for_valid_input_params(param)
4867
5000
  check_parameter(param, valid_validation_parameter, 'data_validation')
4868
5001
 
@@ -4999,139 +5132,6 @@ module Writexlsx
4999
5132
  [fragments, length]
5000
5133
  end
5001
5134
 
5002
- def check_conditional_formatting_parameters(param) # :nodoc:
5003
- # Check for valid input parameters.
5004
- unless (param.keys.uniq - valid_parameter_for_conditional_formatting).empty? &&
5005
- param.has_key?(:type) &&
5006
- valid_type_for_conditional_formatting.has_key?(param[:type].downcase)
5007
- raise WriteXLSXOptionParameterError, "Invalid type : #{param[:type]}"
5008
- end
5009
-
5010
- param[:direction] = 'bottom' if param[:type] == 'bottom'
5011
- param[:type] = valid_type_for_conditional_formatting[param[:type].downcase]
5012
-
5013
- # Check for valid criteria types.
5014
- if param.has_key?(:criteria) && valid_criteria_type_for_conditional_formatting.has_key?(param[:criteria].downcase)
5015
- param[:criteria] = valid_criteria_type_for_conditional_formatting[param[:criteria].downcase]
5016
- end
5017
-
5018
- # Convert date/times value if required.
5019
- if %w[date time cellIs].include?(param[:type])
5020
- param[:type] = 'cellIs'
5021
-
5022
- param[:value] = convert_date_time_if_required(param[:value])
5023
- param[:minimum] = convert_date_time_if_required(param[:minimum])
5024
- param[:maximum] = convert_date_time_if_required(param[:maximum])
5025
- end
5026
-
5027
- # 'Between' and 'Not between' criteria require 2 values.
5028
- if param[:criteria] == 'between' || param[:criteria] == 'notBetween'
5029
- unless param.has_key?(:minimum) || param.has_key?(:maximum)
5030
- raise WriteXLSXOptionParameterError, "Invalid criteria : #{param[:criteria]}"
5031
- end
5032
- else
5033
- param[:minimum] = nil
5034
- param[:maximum] = nil
5035
- end
5036
-
5037
- # Convert date/times value if required.
5038
- if param[:type] == 'date' || param[:type] == 'time'
5039
- unless convert_date_time_value(param, :value) || convert_date_time_value(param, :maximum)
5040
- raise WriteXLSXOptionParameterError
5041
- end
5042
- end
5043
- end
5044
-
5045
- def convert_date_time_if_required(val)
5046
- if val =~ /T/
5047
- date_time = convert_date_time(val)
5048
- raise "Invalid date/time value '#{val}' in conditional_formatting()" unless date_time
5049
- date_time
5050
- else
5051
- val
5052
- end
5053
- end
5054
-
5055
- # List of valid input parameters for conditional_formatting.
5056
- def valid_parameter_for_conditional_formatting
5057
- [
5058
- :type,
5059
- :format,
5060
- :criteria,
5061
- :value,
5062
- :minimum,
5063
- :maximum,
5064
- :min_type,
5065
- :mid_type,
5066
- :max_type,
5067
- :min_value,
5068
- :mid_value,
5069
- :max_value,
5070
- :min_color,
5071
- :mid_color,
5072
- :max_color,
5073
- :bar_color
5074
- ]
5075
- end
5076
-
5077
- # List of valid validation types for conditional_formatting.
5078
- def valid_type_for_conditional_formatting
5079
- {
5080
- 'cell' => 'cellIs',
5081
- 'date' => 'date',
5082
- 'time' => 'time',
5083
- 'average' => 'aboveAverage',
5084
- 'duplicate' => 'duplicateValues',
5085
- 'unique' => 'uniqueValues',
5086
- 'top' => 'top10',
5087
- 'bottom' => 'top10',
5088
- 'text' => 'text',
5089
- 'time_period' => 'timePeriod',
5090
- 'blanks' => 'containsBlanks',
5091
- 'no_blanks' => 'notContainsBlanks',
5092
- 'errors' => 'containsErrors',
5093
- 'no_errors' => 'notContainsErrors',
5094
- '2_color_scale' => '2_color_scale',
5095
- '3_color_scale' => '3_color_scale',
5096
- 'data_bar' => 'dataBar',
5097
- 'formula' => 'expression'
5098
- }
5099
- end
5100
-
5101
- # List of valid criteria types for conditional_formatting.
5102
- def valid_criteria_type_for_conditional_formatting
5103
- {
5104
- 'between' => 'between',
5105
- 'not between' => 'notBetween',
5106
- 'equal to' => 'equal',
5107
- '=' => 'equal',
5108
- '==' => 'equal',
5109
- 'not equal to' => 'notEqual',
5110
- '!=' => 'notEqual',
5111
- '<>' => 'notEqual',
5112
- 'greater than' => 'greaterThan',
5113
- '>' => 'greaterThan',
5114
- 'less than' => 'lessThan',
5115
- '<' => 'lessThan',
5116
- 'greater than or equal to' => 'greaterThanOrEqual',
5117
- '>=' => 'greaterThanOrEqual',
5118
- 'less than or equal to' => 'lessThanOrEqual',
5119
- '<=' => 'lessThanOrEqual',
5120
- 'containing' => 'containsText',
5121
- 'not containing' => 'notContains',
5122
- 'begins with' => 'beginsWith',
5123
- 'ends with' => 'endsWith',
5124
- 'yesterday' => 'yesterday',
5125
- 'today' => 'today',
5126
- 'last 7 days' => 'last7Days',
5127
- 'last week' => 'lastWeek',
5128
- 'this week' => 'thisWeek',
5129
- 'next week' => 'nextWeek',
5130
- 'last month' => 'lastMonth',
5131
- 'this month' => 'thisMonth',
5132
- 'next month' => 'nextMonth'
5133
- }
5134
- end
5135
5135
  # Pad out the rest of the area with formatted blank cells.
5136
5136
  def write_formatted_blank_to_area(row_first, row_last, col_first, col_last, format)
5137
5137
  (row_first .. row_last).each do |row|
@@ -5301,25 +5301,6 @@ module Writexlsx
5301
5301
  [operator, token]
5302
5302
  end
5303
5303
 
5304
- #
5305
- # Convert from an Excel internal colour index to a XML style #RRGGBB index
5306
- # based on the default or user defined values in the Workbook palette.
5307
- #
5308
- def get_palette_color(index) #:nodoc:
5309
- if index =~ /^#([0-9A-F]{6})$/i
5310
- return "FF#{$~[1]}"
5311
- end
5312
-
5313
- # Adjust the colour index.
5314
- index -= 8
5315
-
5316
- # Palette is passed in from the Workbook class.
5317
- rgb = @workbook.palette[index]
5318
-
5319
- # TODO Add the alpha part to the RGB.
5320
- sprintf("FF%02X%02X%02X", *rgb[0, 3])
5321
- end
5322
-
5323
5304
  #
5324
5305
  # This is an internal method that is used to filter elements of the array of
5325
5306
  # pagebreaks used in the _store_hbreak() and _store_vbreak() methods. It:
@@ -6764,15 +6745,6 @@ module Writexlsx
6764
6745
  writer.empty_tag('vertAlign', attributes)
6765
6746
  end
6766
6747
 
6767
- #
6768
- # Write the <color> element.
6769
- #
6770
- def write_color(writer, name, value) #:nodoc:
6771
- attributes = [name, value]
6772
-
6773
- writer.empty_tag('color', attributes)
6774
- end
6775
-
6776
6748
  #
6777
6749
  # Write the <tableParts> element.
6778
6750
  #
@@ -7313,36 +7285,30 @@ module Writexlsx
7313
7285
  # maxAxisType="custom"
7314
7286
  # rightToLeft="1">
7315
7287
  #
7316
- def write_sparkline_group(opts) # :nodoc:
7317
- empty = opts[:_empty]
7318
- user_max = 0
7319
- user_min = 0
7320
- a = []
7288
+ def write_sparkline_group(sparkline) # :nodoc:
7289
+ @writer.start_tag(
7290
+ 'x14:sparklineGroup',
7291
+ attributes_from_sparkline(sparkline)
7292
+ )
7293
+ end
7321
7294
 
7322
- if opts[:_max]
7323
- if opts[:_max] == 'group'
7324
- opts[:_cust_max] = 'group'
7325
- else
7326
- a << 'manualMax' << opts[:_max]
7327
- opts[:_cust_max] = 'custom'
7328
- end
7329
- end
7295
+ def attributes_from_sparkline(opts) # :nodoc:
7296
+ opts[:_cust_max] = cust_max_min(opts[:_max]) if opts[:_max]
7297
+ opts[:_cust_min] = cust_max_min(opts[:_min]) if opts[:_min]
7330
7298
 
7331
- if opts[:_min]
7332
- if opts[:_min] == 'group'
7333
- opts[:_cust_min] = 'group'
7334
- else
7335
- a << 'manualMin' << opts[:_min]
7336
- opts[:_cust_min] = 'custom'
7337
- end
7338
- end
7299
+ opts[:_cust_max] = cust_max_min(opts[:_max]) if opts[:_max]
7300
+ opts[:_cust_min] = cust_max_min(opts[:_min]) if opts[:_min]
7301
+
7302
+ a = []
7303
+ a << 'manualMax' << opts[:_max] if opts[:_max] && opts[:_max] != 'group'
7304
+ a << 'manualMin' << opts[:_min] if opts[:_min] && opts[:_min] != 'group'
7339
7305
 
7340
7306
  # Ignore the default type attribute (line).
7341
7307
  a << 'type' << opts[:_type] if opts[:_type] != 'line'
7342
7308
 
7343
7309
  a << 'lineWeight' << opts[:_weight] if opts[:_weight]
7344
7310
  a << 'dateAxis' << 1 if opts[:_date_axis]
7345
- a << 'displayEmptyCellsAs' << empty if ptrue?(empty)
7311
+ a << 'displayEmptyCellsAs' << opts[:_empty] if ptrue?(opts[:_empty])
7346
7312
 
7347
7313
  a << 'markers' << 1 if opts[:_markers]
7348
7314
  a << 'high' << 1 if opts[:_high]
@@ -7355,8 +7321,11 @@ module Writexlsx
7355
7321
  a << 'minAxisType' << opts[:_cust_min] if opts[:_cust_min]
7356
7322
  a << 'maxAxisType' << opts[:_cust_max] if opts[:_cust_max]
7357
7323
  a << 'rightToLeft' << 1 if opts[:_reverse]
7324
+ a
7325
+ end
7358
7326
 
7359
- @writer.start_tag('x14:sparklineGroup', a)
7327
+ def cust_max_min(max_min) # :nodoc:
7328
+ max_min == 'group' ? 'group' : 'custom'
7360
7329
  end
7361
7330
 
7362
7331
  #
@@ -7514,153 +7483,23 @@ module Writexlsx
7514
7483
  @writer.data_element('formula2', formula)
7515
7484
  end
7516
7485
 
7517
- # in Perl module : _write_formula()
7518
- #
7519
- def write_formula_tag(data) #:nodoc:
7520
- data = data.sub(/^=/, '') if data.respond_to?(:sub)
7521
- @writer.data_element('formula', data)
7522
- end
7523
-
7524
- #
7525
- # Write the <colorScale> element.
7526
- #
7527
- def write_color_scale(param)
7528
- @writer.tag_elements('colorScale') do
7529
- write_cfvo(param[:min_type], param[:min_value])
7530
- write_cfvo(param[:mid_type], param[:mid_value]) if param[:mid_type]
7531
- write_cfvo(param[:max_type], param[:max_value])
7532
- write_color(@writer, 'rgb', param[:min_color])
7533
- write_color(@writer, 'rgb', param[:mid_color]) if param[:mid_color]
7534
- write_color(@writer, 'rgb', param[:max_color])
7535
- end
7536
- end
7537
-
7538
- #
7539
- # Write the <dataBar> element.
7540
- #
7541
- def write_data_bar(param)
7542
- @writer.tag_elements('dataBar') do
7543
- write_cfvo(param[:min_type], param[:min_value])
7544
- write_cfvo(param[:max_type], param[:max_value])
7545
-
7546
- write_color(@writer, 'rgb', param[:bar_color])
7547
- end
7548
- end
7549
-
7550
- #
7551
- # Write the <cfvo> element.
7552
- #
7553
- def write_cfvo(type, val)
7554
- attributes = [
7555
- 'type', type,
7556
- 'val', val
7557
- ]
7558
-
7559
- @writer.empty_tag('cfvo', attributes)
7560
- end
7561
-
7562
7486
  #
7563
7487
  # Write the Worksheet conditional formats.
7564
7488
  #
7565
- def write_conditional_formats #:nodoc:
7566
- ranges = @cond_formats.keys.sort
7567
- return if ranges.empty?
7568
-
7569
- ranges.each { |range| write_conditional_formatting(range, @cond_formats[range]) }
7489
+ def write_conditional_formats #:nodoc:
7490
+ @cond_formats.keys.sort.each do |range|
7491
+ write_conditional_formatting(range, @cond_formats[range])
7492
+ end
7570
7493
  end
7571
7494
 
7572
7495
  #
7573
7496
  # Write the <conditionalFormatting> element.
7574
7497
  #
7575
- # The conditional_formatting() method is used to add formatting
7576
- # to a cell or range of cells based on user defined criteria.
7577
- #
7578
- # worksheet.conditional_formatting('A1:J10',
7579
- # {
7580
- # :type => 'cell',
7581
- # :criteria => '>=',
7582
- # :value => 50,
7583
- # :format => format1
7584
- # }
7585
- # )
7586
- # This method contains a lot of parameters and is described
7587
- # in detail in a separate section "CONDITIONAL FORMATTING IN EXCEL".
7588
- #
7589
- # See also the conditional_format.rb program in the examples directory
7590
- # of the distro
7591
- #
7592
- def write_conditional_formatting(range, params) #:nodoc:
7498
+ def write_conditional_formatting(range, cond_formats) #:nodoc:
7593
7499
  attributes = ['sqref', range]
7594
7500
 
7595
7501
  @writer.tag_elements('conditionalFormatting', attributes) do
7596
- params.each { |param| write_cf_rule(param) }
7597
- end
7598
- end
7599
-
7600
- #
7601
- # Write the <cfRule> element.
7602
- #
7603
- def write_cf_rule(param) #:nodoc:
7604
- attributes = ['type' , param[:type]]
7605
-
7606
- if param[:format]
7607
- attributes << 'dxfId' << param[:format]
7608
- end
7609
- attributes << 'priority' << param[:priority]
7610
-
7611
- case param[:type]
7612
- when 'cellIs'
7613
- attributes << 'operator' << param[:criteria]
7614
- @writer.tag_elements('cfRule', attributes) do
7615
- if param[:minimum] && param[:maximum]
7616
- write_formula_tag(param[:minimum])
7617
- write_formula_tag(param[:maximum])
7618
- else
7619
- write_formula_tag(param[:value])
7620
- end
7621
- end
7622
- when 'aboveAverage'
7623
- attributes << 'aboveAverage' << 0 if param[:criteria] =~ /below/
7624
- attributes << 'equalAverage' << 1 if param[:criteria] =~ /equal/
7625
- if param[:criteria] =~ /([123]) std dev/
7626
- attributes << 'stdDev' << $~[1]
7627
- end
7628
- @writer.empty_tag('cfRule', attributes)
7629
- when 'top10'
7630
- attributes << 'percent' << 1 if param[:criteria] == '%'
7631
- attributes << 'bottom' << 1 if param[:direction]
7632
- rank = param[:value] || 10
7633
- attributes << 'rank' << rank
7634
- @writer.empty_tag('cfRule', attributes)
7635
- when 'duplicateValues', 'uniqueValues'
7636
- @writer.empty_tag('cfRule', attributes)
7637
- when 'containsText', 'notContainsText', 'beginsWith', 'endsWith'
7638
- attributes << 'operator' << param[:criteria]
7639
- attributes << 'text' << param[:value]
7640
- @writer.tag_elements('cfRule', attributes) do
7641
- write_formula_tag(param[:formula])
7642
- end
7643
- when 'timePeriod'
7644
- attributes << 'timePeriod' << param[:criteria]
7645
- @writer.tag_elements('cfRule', attributes) do
7646
- write_formula_tag(param[:formula])
7647
- end
7648
- when 'containsBlanks', 'notContainsBlanks', 'containsErrors', 'notContainsErrors'
7649
- @writer.tag_elements('cfRule', attributes) do
7650
- write_formula_tag(param[:formula])
7651
- end
7652
- when 'colorScale'
7653
- @writer.tag_elements('cfRule', attributes) do
7654
- write_color_scale(param)
7655
- end
7656
- when 'dataBar'
7657
- @writer.tag_elements('cfRule', attributes) do
7658
- write_data_bar(param)
7659
- end
7660
- when 'expression'
7661
- @writer.tag_elements('cfRule', attributes) do
7662
- write_formula_tag(param[:criteria])
7663
- end
7502
+ cond_formats.each { |cond_format| cond_format.write_cf_rule }
7664
7503
  end
7665
7504
  end
7666
7505
 
@@ -7674,54 +7513,11 @@ module Writexlsx
7674
7513
  end
7675
7514
  end
7676
7515
 
7677
- # Check for a cell reference in A1 notation and substitute row and column
7678
- def row_col_notation(args) # :nodoc:
7679
- if args[0] =~ /^\D/
7680
- substitute_cellref(*args)
7681
- else
7682
- args
7683
- end
7684
- end
7685
-
7686
- #
7687
- # Check that row and col are valid and store max and min values for use in
7688
- # other methods/elements.
7689
- #
7690
- # The ignore_row/ignore_col flags is used to indicate that we wish to
7691
- # perform the dimension check without storing the value.
7692
- #
7693
- # The ignore flags are use by set_row() and data_validate.
7694
- #
7695
- def check_dimensions_and_update_max_min_values(row, col, ignore_row = 0, ignore_col = 0) #:nodoc:
7696
- check_dimensions(row, col)
7697
- store_row_max_min_values(row) if ignore_row == 0
7698
- store_col_max_min_values(col) if ignore_col == 0
7699
-
7700
- 0
7701
- end
7702
-
7703
- def check_dimensions(row, col)
7704
- if !row || row >= ROW_MAX || !col || col >= COL_MAX
7705
- raise WriteXLSXDimensionError
7706
- end
7707
- 0
7708
- end
7709
-
7710
7516
  def store_row_col_max_min_values(row, col)
7711
7517
  store_row_max_min_values(row)
7712
7518
  store_col_max_min_values(col)
7713
7519
  end
7714
7520
 
7715
- def store_row_max_min_values(row)
7716
- @dim_rowmin = row if !@dim_rowmin || (row < @dim_rowmin)
7717
- @dim_rowmax = row if !@dim_rowmax || (row > @dim_rowmax)
7718
- end
7719
-
7720
- def store_col_max_min_values(col)
7721
- @dim_colmin = col if !@dim_colmin || (col < @dim_colmin)
7722
- @dim_colmax = col if !@dim_colmax || (col > @dim_colmax)
7723
- end
7724
-
7725
7521
  #
7726
7522
  # Calculate the "spans" attribute of the <row> tag. This is an XLSX
7727
7523
  # optimisation and isn't strictly required. However, it makes comparing
@@ -7885,10 +7681,6 @@ module Writexlsx
7885
7681
  !!@autofilter_ref
7886
7682
  end
7887
7683
 
7888
- def date_1904? #:nodoc:
7889
- @workbook.date_1904?
7890
- end
7891
-
7892
7684
  def print_options_changed? #:nodoc:
7893
7685
  !!@print_options_changed
7894
7686
  end