tcxread 0.1.5 → 0.2.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 +4 -4
- data/lib/tcxread.rb +155 -220
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 467aa1170c1d1e4187ad0bf62cdf274e3323427e1a17b373761d9e15344f3246
|
4
|
+
data.tar.gz: 3bb6690e5169d24ea3a18cbcea16af923a3d203a1f230364b5db2e68363ced11
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8fa40c28c9ead5bbdbdb3d3c7a79958f7145f6b53398771af1776be3e6d44c25d2e0e9ec8165026d4c54789dccad0550fd69e882e1471dec322feb79a3a3c059
|
7
|
+
data.tar.gz: ff9edd51b84495ce42b3db1ea53a807f23df2c97b264524f5f274a9b2b9a815efcedd6d0e7c2bc540268a4655a85c3cdd183ce1aa1f732edc22476bcbf53e157
|
data/lib/tcxread.rb
CHANGED
@@ -1,11 +1,49 @@
|
|
1
1
|
require "nokogiri"
|
2
2
|
|
3
|
+
# The `TCXRead` class processes and analyzes data from a TCX (Training Center XML) file.
|
4
|
+
# It extracts key metrics such as distance, time, calories, ascent/descent, altitude, heart rate,
|
5
|
+
# power (watts), cadence, and speed from the activities recorded in the TCX file.
|
6
|
+
#
|
7
|
+
# Reference (see also):
|
8
|
+
# I. Jr. Fister, L. Lukač, A. Rajšp, I. Fister, L. Pečnik, and D. Fister,
|
9
|
+
# "A minimalistic toolbox for extracting features from sport activity files,"
|
10
|
+
# 2021 IEEE 25th International Conference on Intelligent Engineering Systems (INES), 2021,
|
11
|
+
# pp. 121-126, doi: 10.1109/INES52918.2021.9512927.
|
3
12
|
class TCXRead
|
13
|
+
# @!attribute [r] total_distance_meters
|
14
|
+
# @return [Float] The total distance covered in meters.
|
15
|
+
# @!attribute [r] total_time_seconds
|
16
|
+
# @return [Float] The total time of activities in seconds.
|
17
|
+
# @!attribute [r] total_calories
|
18
|
+
# @return [Integer] The total calories burned.
|
19
|
+
# @!attribute [r] total_ascent
|
20
|
+
# @return [Float] The total ascent in meters.
|
21
|
+
# @!attribute [r] total_descent
|
22
|
+
# @return [Float] The total descent in meters.
|
23
|
+
# @!attribute [r] max_altitude
|
24
|
+
# @return [Float] The maximum altitude reached in meters.
|
25
|
+
# @!attribute [r] average_heart_rate
|
26
|
+
# @return [Float] The average heart rate in beats per minute.
|
27
|
+
# @!attribute [r] max_watts
|
28
|
+
# @return [String, Float] The maximum power output in watts, or 'NA' if unavailable.
|
29
|
+
# @!attribute [r] average_watts
|
30
|
+
# @return [String, Float] The average power output in watts, or 'NA' if unavailable.
|
31
|
+
# @!attribute [r] average_cadence_all
|
32
|
+
# @return [Float] The average cadence in RPM.
|
33
|
+
# @!attribute [r] average_cadence_biking
|
34
|
+
# @return [Float] The average cadence for the whole activity in RPM.
|
35
|
+
# @!attribute [r] average_speed_all
|
36
|
+
# @return [Float] The average speed for the whole activity in meters per second.
|
37
|
+
# @!attribute [r] average_speed_moving
|
38
|
+
# @return [Float] The average speed while moving in meters per second.
|
39
|
+
|
4
40
|
attr_reader :total_distance_meters, :total_time_seconds, :total_calories,
|
5
41
|
:total_ascent, :total_descent, :max_altitude, :average_heart_rate,
|
6
42
|
:max_watts, :average_watts, :average_cadence_all, :average_cadence_biking,
|
7
43
|
:average_speed_all, :average_speed_moving
|
8
44
|
|
45
|
+
# Initializes the TCXRead object and parses the TCX file.
|
46
|
+
# @param file_path [String] The file path of the TCX file to process.
|
9
47
|
def initialize(file_path)
|
10
48
|
@file_path = file_path
|
11
49
|
@doc = Nokogiri::XML(File.open(file_path))
|
@@ -28,276 +66,173 @@ class TCXRead
|
|
28
66
|
parse
|
29
67
|
end
|
30
68
|
|
69
|
+
# Parses the TCX file and computes metrics for all activities.
|
31
70
|
def parse
|
32
71
|
activities = parse_activities
|
33
|
-
if activities.
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
@average_cadence_biking = cadence_results[:average_cadence_biking]
|
43
|
-
speed_results = calculate_average_speed_from_activities(activities)
|
44
|
-
@average_speed_all = speed_results[:average_speed_all]
|
45
|
-
@average_speed_moving = speed_results[:average_speed_moving]
|
46
|
-
end
|
72
|
+
return if activities.empty?
|
73
|
+
|
74
|
+
@total_time_seconds = activities.sum { |activity| activity[:total_time_seconds] }
|
75
|
+
@total_distance_meters = activities.sum { |activity| activity[:total_distance_meters] }
|
76
|
+
@total_calories = activities.sum { |activity| activity[:total_calories] }
|
77
|
+
|
78
|
+
@total_ascent, @total_descent, @max_altitude = calculate_ascent_descent_and_max_altitude_from_activities(activities)
|
79
|
+
@average_heart_rate = calculate_average(:heart_rate, activities)
|
80
|
+
@max_watts, @average_watts = calculate_watts_from_activities(activities)
|
47
81
|
|
48
|
-
|
82
|
+
cadence_results = calculate_average_cadence_from_activities(activities)
|
83
|
+
@average_cadence_all = cadence_results[:average_cadence_all]
|
84
|
+
@average_cadence_biking = cadence_results[:average_cadence_biking]
|
85
|
+
|
86
|
+
speed_results = calculate_average_speed_from_activities(activities)
|
87
|
+
@average_speed_all = speed_results[:average_speed_all]
|
88
|
+
@average_speed_moving = speed_results[:average_speed_moving]
|
49
89
|
end
|
50
90
|
|
51
91
|
private
|
52
92
|
|
93
|
+
# Parses activities from the TCX file.
|
94
|
+
# @return [Array<Hash>] An array of parsed activity data.
|
53
95
|
def parse_activities
|
54
|
-
|
55
|
-
|
56
|
-
laps = parse_laps(activity)
|
57
|
-
total_time_seconds = laps.sum { |lap| lap[:total_time_seconds] }
|
58
|
-
total_distance_meters = laps.sum { |lap| lap[:distance_meters] }
|
59
|
-
total_calories = laps.sum { |lap| lap[:calories] }
|
60
|
-
total_ascent, total_descent, max_altitude = calculate_ascent_descent_and_max_altitude(laps)
|
61
|
-
average_heart_rate = calculate_average_heart_rate(laps)
|
96
|
+
@doc.xpath('//xmlns:Activities/xmlns:Activity').map do |activity|
|
97
|
+
laps = activity.xpath('xmlns:Lap').map { |lap| parse_lap(lap) }
|
62
98
|
|
63
|
-
|
99
|
+
{
|
64
100
|
sport: activity.attr('Sport'),
|
65
101
|
id: activity.xpath('xmlns:Id').text,
|
66
102
|
laps: laps,
|
67
|
-
total_time_seconds: total_time_seconds,
|
68
|
-
total_distance_meters:
|
69
|
-
total_calories:
|
70
|
-
total_ascent: total_ascent,
|
71
|
-
total_descent: total_descent,
|
72
|
-
max_altitude: max_altitude,
|
73
|
-
average_heart_rate:
|
103
|
+
total_time_seconds: laps.sum { |lap| lap[:total_time_seconds] },
|
104
|
+
total_distance_meters: laps.sum { |lap| lap[:distance_meters] },
|
105
|
+
total_calories: laps.sum { |lap| lap[:calories] },
|
106
|
+
total_ascent: laps.sum { |lap| lap[:total_ascent] },
|
107
|
+
total_descent: laps.sum { |lap| lap[:total_descent] },
|
108
|
+
max_altitude: laps.map { |lap| lap[:max_altitude] }.max,
|
109
|
+
average_heart_rate: calculate_average(:heart_rate, laps)
|
74
110
|
}
|
75
111
|
end
|
76
|
-
activities
|
77
112
|
end
|
78
113
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
total_time_seconds: lap.xpath('xmlns:TotalTimeSeconds').text.to_f,
|
85
|
-
distance_meters: lap.xpath('xmlns:DistanceMeters').text.to_f,
|
86
|
-
maximum_speed: lap.xpath('xmlns:MaximumSpeed').text.to_f,
|
87
|
-
calories: lap.xpath('xmlns:Calories').text.to_i,
|
88
|
-
average_heart_rate: lap.xpath('xmlns:AverageHeartRateBpm/xmlns:Value').text.to_i,
|
89
|
-
maximum_heart_rate: lap.xpath('xmlns:MaximumHeartRateBpm/xmlns:Value').text.to_i,
|
90
|
-
intensity: lap.xpath('xmlns:Intensity').text,
|
91
|
-
cadence: lap.xpath('xmlns:Cadence').text.to_i,
|
92
|
-
trigger_method: lap.xpath('xmlns:TriggerMethod').text,
|
93
|
-
tracks: parse_tracks(lap)
|
94
|
-
}
|
95
|
-
end
|
96
|
-
laps
|
97
|
-
end
|
114
|
+
# Parses a single lap from the TCX file.
|
115
|
+
# @param lap [Nokogiri::XML::Node] The lap node to parse.
|
116
|
+
# @return [Hash] The parsed lap data.
|
117
|
+
def parse_lap(lap)
|
118
|
+
trackpoints = lap.xpath('xmlns:Track/xmlns:Trackpoint').map { |tp| parse_trackpoint(tp) }
|
98
119
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
heart_rate: trackpoint.xpath('xmlns:HeartRateBpm/xmlns:Value').text.to_i,
|
110
|
-
cadence: trackpoint.xpath('xmlns:Cadence').text.to_i,
|
111
|
-
sensor_state: trackpoint.xpath('xmlns:SensorState').text,
|
112
|
-
watts: trackpoint.xpath('xmlns:Extensions/ns3:TPX/ns3:Watts').text.to_f,
|
113
|
-
speed: trackpoint.xpath('xmlns:Extensions/ns3:TPX/ns3:Speed').text.to_f
|
114
|
-
}
|
115
|
-
end
|
116
|
-
tracks << trackpoints
|
117
|
-
end
|
118
|
-
tracks
|
120
|
+
{
|
121
|
+
start_time: lap.attr('StartTime'),
|
122
|
+
total_time_seconds: lap.xpath('xmlns:TotalTimeSeconds').text.to_f,
|
123
|
+
distance_meters: lap.xpath('xmlns:DistanceMeters').text.to_f,
|
124
|
+
calories: lap.xpath('xmlns:Calories').text.to_i,
|
125
|
+
total_ascent: calculate_ascent(trackpoints),
|
126
|
+
total_descent: calculate_descent(trackpoints),
|
127
|
+
max_altitude: trackpoints.map { |tp| tp[:altitude_meters] }.max || 0,
|
128
|
+
trackpoints: trackpoints || []
|
129
|
+
}
|
119
130
|
end
|
120
131
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
132
|
+
# Parses a single trackpoint from the TCX file.
|
133
|
+
# @param trackpoint [Nokogiri::XML::Node] The trackpoint node to parse.
|
134
|
+
# @return [Hash] The parsed trackpoint data.
|
135
|
+
def parse_trackpoint(trackpoint)
|
125
136
|
{
|
126
|
-
|
127
|
-
|
137
|
+
time: trackpoint.xpath('xmlns:Time').text,
|
138
|
+
altitude_meters: trackpoint.xpath('xmlns:AltitudeMeters').text.to_f,
|
139
|
+
distance_meters: trackpoint.xpath('xmlns:DistanceMeters').text.to_f,
|
140
|
+
heart_rate: trackpoint.xpath('xmlns:HeartRateBpm/xmlns:Value').text.to_i,
|
141
|
+
cadence: trackpoint.xpath('xmlns:Cadence').text.to_i,
|
142
|
+
watts: trackpoint.xpath('xmlns:Extensions/ns3:TPX/ns3:Watts').text.to_f,
|
143
|
+
speed: trackpoint.xpath('xmlns:Extensions/ns3:TPX/ns3:Speed').text.to_f
|
128
144
|
}
|
129
145
|
end
|
130
146
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
147
|
+
# Calculates total ascent from an array of trackpoints.
|
148
|
+
# @param trackpoints [Array<Hash>] An array of trackpoint data.
|
149
|
+
# @return [Float] The total ascent in meters.
|
150
|
+
def calculate_ascent(trackpoints)
|
135
151
|
previous_altitude = nil
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
if previous_altitude
|
143
|
-
altitude_change = altitude - previous_altitude
|
144
|
-
if altitude_change > 0
|
145
|
-
total_ascent += altitude_change
|
146
|
-
elsif altitude_change < 0
|
147
|
-
total_descent += altitude_change.abs
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
previous_altitude = altitude
|
152
|
-
end
|
152
|
+
trackpoints.sum do |tp|
|
153
|
+
altitude = tp[:altitude_meters]
|
154
|
+
ascent = previous_altitude && altitude > previous_altitude ? altitude - previous_altitude : 0
|
155
|
+
previous_altitude = altitude
|
156
|
+
ascent
|
153
157
|
end
|
154
|
-
|
155
|
-
[total_ascent, total_descent, max_altitude]
|
156
158
|
end
|
157
159
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
160
|
+
# Calculates total descent from an array of trackpoints.
|
161
|
+
# @param trackpoints [Array<Hash>] An array of trackpoint data.
|
162
|
+
# @return [Float] The total descent in meters.
|
163
|
+
def calculate_descent(trackpoints)
|
164
|
+
previous_altitude = nil
|
165
|
+
trackpoints.sum do |tp|
|
166
|
+
altitude = tp[:altitude_meters]
|
167
|
+
descent = previous_altitude && altitude < previous_altitude ? previous_altitude - altitude : 0
|
168
|
+
previous_altitude = altitude
|
169
|
+
descent
|
167
170
|
end
|
168
|
-
|
169
|
-
[total_ascent, total_descent, max_altitude]
|
170
171
|
end
|
171
172
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
total_heart_rate += heart_rate
|
181
|
-
heart_rate_count += 1
|
182
|
-
end
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
heart_rate_count > 0 ? total_heart_rate.to_f / heart_rate_count : 0.0
|
173
|
+
# Calculates ascent, descent, and maximum altitude from activities.
|
174
|
+
# @param activities [Array<Hash>] An array of activity data.
|
175
|
+
# @return [Array<Float>] Total ascent, total descent, and maximum altitude.
|
176
|
+
def calculate_ascent_descent_and_max_altitude_from_activities(activities)
|
177
|
+
total_ascent = activities.sum { |activity| activity[:total_ascent] }
|
178
|
+
total_descent = activities.sum { |activity| activity[:total_descent] }
|
179
|
+
max_altitude = activities.map { |activity| activity[:max_altitude] }.max
|
180
|
+
[total_ascent, total_descent, max_altitude]
|
187
181
|
end
|
188
182
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
end
|
201
|
-
end
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
heart_rate_count > 0 ? total_heart_rate.to_f / heart_rate_count : 0.0
|
183
|
+
# Calculates the average value of a metric across laps or activities.
|
184
|
+
# @param metric [Symbol] The metric to average (e.g., :heart_rate).
|
185
|
+
# @param laps_or_activities [Array<Hash>] An array of lap or activity data.
|
186
|
+
# @return [Float] The average value of the metric.
|
187
|
+
def calculate_average(metric, laps_or_activities)
|
188
|
+
values = laps_or_activities.flat_map do |lap|
|
189
|
+
next [] unless lap[:trackpoints]
|
190
|
+
lap[:trackpoints].map { |tp| tp[metric] }
|
191
|
+
end.compact
|
192
|
+
|
193
|
+
values.any? ? values.sum.to_f / values.size : 0.0
|
206
194
|
end
|
207
195
|
|
196
|
+
# Calculates power metrics from activities.
|
197
|
+
# @param activities [Array<Hash>] An array of activity data.
|
198
|
+
# @return [Array<String, Float>] Maximum and average power output.
|
208
199
|
def calculate_watts_from_activities(activities)
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
activities.each do |activity|
|
214
|
-
activity[:laps].each do |lap|
|
215
|
-
lap[:tracks].flatten.each do |trackpoint|
|
216
|
-
watts = trackpoint[:watts]
|
217
|
-
if watts > 0
|
218
|
-
total_watts += watts
|
219
|
-
watts_count += 1
|
220
|
-
max_watts = watts if watts > max_watts
|
221
|
-
end
|
222
|
-
end
|
200
|
+
watts = activities.flat_map do |activity|
|
201
|
+
activity[:laps].flat_map do |lap|
|
202
|
+
lap[:trackpoints]&.map { |tp| tp[:watts] } || []
|
223
203
|
end
|
224
|
-
end
|
204
|
+
end.compact
|
225
205
|
|
226
|
-
if
|
227
|
-
|
228
|
-
|
206
|
+
if watts.any?
|
207
|
+
max_watts = watts.max
|
208
|
+
avg_watts = watts.sum.to_f / watts.size
|
229
209
|
else
|
230
|
-
average_watts = 'NA'
|
231
210
|
max_watts = 'NA'
|
211
|
+
avg_watts = 'NA'
|
232
212
|
end
|
233
213
|
|
234
|
-
[max_watts,
|
214
|
+
[max_watts, avg_watts]
|
235
215
|
end
|
236
216
|
|
217
|
+
# Calculates average cadence metrics from activities.
|
218
|
+
# @param activities [Array<Hash>] An array of activity data.
|
219
|
+
# @return [Hash] Average cadence metrics for all activities and biking only.
|
237
220
|
def calculate_average_cadence_from_activities(activities)
|
238
|
-
|
239
|
-
total_cadence_biking = 0
|
240
|
-
cadence_count_all = 0
|
241
|
-
cadence_count_biking = 0
|
242
|
-
|
243
|
-
activities.each do |activity|
|
244
|
-
activity[:laps].each do |lap|
|
245
|
-
lap[:tracks].flatten.each do |trackpoint|
|
246
|
-
cadence = trackpoint[:cadence]
|
247
|
-
total_cadence_all += cadence
|
248
|
-
cadence_count_all += 1
|
249
|
-
|
250
|
-
if cadence > 0
|
251
|
-
total_cadence_biking += cadence
|
252
|
-
cadence_count_biking += 1
|
253
|
-
end
|
254
|
-
end
|
255
|
-
end
|
256
|
-
end
|
257
|
-
|
258
|
-
average_cadence_all = cadence_count_all > 0 ? total_cadence_all.to_f / cadence_count_all : 0.0
|
259
|
-
average_cadence_biking = cadence_count_biking > 0 ? total_cadence_biking.to_f / cadence_count_biking : 0.0
|
260
|
-
|
221
|
+
cadences = activities.flat_map { |activity| activity[:laps].flat_map { |lap| lap[:trackpoints].map { |tp| tp[:cadence] } if lap[:trackpoints] } }.compact
|
261
222
|
{
|
262
|
-
average_cadence_all:
|
263
|
-
average_cadence_biking:
|
223
|
+
average_cadence_all: cadences.any? ? cadences.sum.to_f / cadences.size : 0.0,
|
224
|
+
average_cadence_biking: cadences.reject(&:zero?).any? ? cadences.reject(&:zero?).sum.to_f / cadences.reject(&:zero?).size : 0.0
|
264
225
|
}
|
265
226
|
end
|
266
227
|
|
267
|
-
# Calculates
|
268
|
-
#
|
269
|
-
# @
|
270
|
-
# @return [Hash] a hash containing average speed including zeros and average speed while moving.
|
228
|
+
# Calculates average speed metrics from activities.
|
229
|
+
# @param activities [Array<Hash>] An array of activity data.
|
230
|
+
# @return [Hash] Average speed metrics for the whole activity and moving activities only.
|
271
231
|
def calculate_average_speed_from_activities(activities)
|
272
|
-
|
273
|
-
total_speed_moving = 0
|
274
|
-
speed_count_all = 0
|
275
|
-
speed_count_moving = 0
|
276
|
-
|
277
|
-
activities.each do |activity|
|
278
|
-
activity[:laps].each do |lap|
|
279
|
-
lap[:tracks].flatten.each do |trackpoint|
|
280
|
-
speed = trackpoint[:speed]
|
281
|
-
|
282
|
-
if speed
|
283
|
-
total_speed_all += speed
|
284
|
-
speed_count_all += 1
|
285
|
-
|
286
|
-
if speed > 0
|
287
|
-
total_speed_moving += speed
|
288
|
-
speed_count_moving += 1
|
289
|
-
end
|
290
|
-
end
|
291
|
-
end
|
292
|
-
end
|
293
|
-
end
|
294
|
-
|
295
|
-
average_speed_all = speed_count_all > 0 ? total_speed_all.to_f / speed_count_all : 0.0
|
296
|
-
average_speed_moving = speed_count_moving > 0 ? total_speed_moving.to_f / speed_count_moving : 0.0
|
297
|
-
|
232
|
+
speeds = activities.flat_map { |activity| activity[:laps].flat_map { |lap| lap[:trackpoints].map { |tp| tp[:speed] } if lap[:trackpoints] } }.compact
|
298
233
|
{
|
299
|
-
average_speed_all:
|
300
|
-
average_speed_moving:
|
234
|
+
average_speed_all: speeds.any? ? speeds.sum.to_f / speeds.size : 0.0,
|
235
|
+
average_speed_moving: speeds.reject(&:zero?).any? ? speeds.reject(&:zero?).sum.to_f / speeds.reject(&:zero?).size : 0.0
|
301
236
|
}
|
302
237
|
end
|
303
238
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tcxread
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- firefly-cpp
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-12-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nokogiri
|
@@ -56,7 +56,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
56
56
|
- !ruby/object:Gem::Version
|
57
57
|
version: '0'
|
58
58
|
requirements: []
|
59
|
-
rubygems_version: 3.5.
|
59
|
+
rubygems_version: 3.5.22
|
60
60
|
signing_key:
|
61
61
|
specification_version: 4
|
62
62
|
summary: tcx reader/parser in Ruby
|