xbrlware-ruby19 1.1.2.19

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.
@@ -0,0 +1,475 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # Author:: xbrlware@bitstat.com
4
+ #
5
+ # Copyright:: 2009, 2010 bitstat (http://www.bitstat.com). All Rights Reserved.
6
+ #
7
+ # License:: Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
16
+ # implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #
20
+ module Xbrlware
21
+
22
+ # Class to deal with valid, well-formatted XBRl instance file.
23
+ # This class provides methods to deal with instance file.
24
+ # Look at {delaing with instance page on xbrlware wiki}[http://code.google.com/p/xbrlware/wiki/InstanceTaxonomy] for more details.
25
+ class Instance
26
+
27
+ attr_reader :taxonomy
28
+
29
+ # Creates an Instance.
30
+ #
31
+ # instance_str:: XBRL Instance source. Tries to load and parse instance file from the instance_str. This can be XBRL instance file from the file system or XBRL content string.
32
+ #
33
+ # taxonomy_filepath::
34
+ # optional parameter, XBRL Taxonomy source. Tries to load and parse taxonomy file from path.
35
+ # If this param is specified, taxonomy file in the instance document will be ignored
36
+ #
37
+ # Expects instance source is well-formatted and valid
38
+ # Sometimes instance document contains large chunk of HTML content with new lines in-between,
39
+ # which will cause parsing error. Hence any exceptions during instance source parsing, will trigger re-parsing of
40
+ # the entire instnace file with new lines replaced.
41
+ # Look at {delaing with instance page on xbrlware wiki}[http://code.google.com/p/xbrlware/wiki/InstanceTaxonomy] for details.
42
+ def initialize(instance_str, taxonomy_filepath=nil)
43
+ m=Benchmark.measure do
44
+ begin
45
+ @file_name=instance_str unless instance_str =~ /<.*?>/m
46
+ @xbrl_content = XmlParser.xml_in(instance_str, {'ForceContent' => true})
47
+ rescue Exception
48
+ new_content=nil
49
+ if instance_str =~ /<.*?>/m
50
+ $LOG.warn "Supplied XBRL content is not well formed. Starting reparsing after removing new lines."
51
+ new_content=instance_str.gsub("\n", "")
52
+ else
53
+ $LOG.warn "File ["+instance_str+"] is not well formed. Starting reparsing after removing new lines."
54
+ new_content=File.open(instance_str).read.gsub("\n", "")
55
+ end
56
+ @xbrl_content = XmlParser.xml_in(new_content, {'ForceContent' => true}) unless new_content.nil?
57
+ end
58
+ end
59
+ bm("Parsing [" + instance_str + "] took", m)
60
+
61
+ # if taxonomy file is not supplied, get it from instance schema_ref
62
+ if taxonomy_filepath.nil?
63
+ taxonomy_file_location=File.dirname(instance_str)+File::Separator+schema_ref
64
+ taxonomy_filepath = taxonomy_file_location if File.exist?(taxonomy_file_location) && (not File.directory?(taxonomy_file_location))
65
+ end
66
+
67
+ @taxonomy=Xbrlware::Taxonomy.new(taxonomy_filepath, self)
68
+ @entity_details=Hash.new("UNKNOWN")
69
+ end
70
+
71
+ # Returns raw content of instance file in the form of Hash
72
+ def raw
73
+ @xbrl_content
74
+ end
75
+
76
+ # Returns schemaRef element of instance file. schemaRef holds path to taxonomy file for the instance file.
77
+ def schema_ref
78
+ base = @xbrl_content["schemaRef"][0]["xml:base"]
79
+ href = @xbrl_content["schemaRef"][0]["xlink:href"]
80
+ return base + href if base
81
+ href
82
+ end
83
+
84
+ # Takes optional context_id as string and dimensions as array
85
+ # Returns all contexts when context_id is nil
86
+ # Returns instance of Context object if context_id or dimensions is passed and matching context exist
87
+ # Returns nil if context_id or dimensions is passed and no matching context exist
88
+ def context(context_id=nil, dimensions=[])
89
+ all_contexts= context_by_id(context_id)
90
+
91
+ return all_contexts if dimensions.size==0
92
+
93
+ contexts=[]
94
+
95
+ all_contexts=[all_contexts] unless all_contexts.is_a?(Array)
96
+ all_contexts.each do |ctx|
97
+ next unless ctx.has_explicit_dimensions?(dimensions)
98
+ contexts << ctx
99
+ end
100
+
101
+ return contexts[0] unless context_id.nil?
102
+ contexts
103
+ end
104
+
105
+ # Returns contexts grouped by dimension as map. Map contains dimension as key and corresponding contexts as value
106
+ def ctx_groupby_dim
107
+ dim_group={}
108
+ all_contexts= context
109
+ all_contexts=[all_contexts] unless all_contexts.is_a?(Array)
110
+ all_contexts.each do |ctx|
111
+ ctx.explicit_dimensions.each do |dim|
112
+ dim_group[dim] = [] if dim_group[dim].nil?
113
+ dim_group[dim] << ctx
114
+ end
115
+ end
116
+ dim_group
117
+ end
118
+
119
+ # Takes optional dimensions as array
120
+ # Returns contexts group by domain as map. Map contains domain as key and corresponding contexts as value
121
+ def ctx_groupby_dom(dimensions=[])
122
+ dom_group={}
123
+ all_contexts= context(nil, dimensions)
124
+ all_contexts=[all_contexts] unless all_contexts.is_a?(Array)
125
+ all_contexts.each do |ctx|
126
+ ctx.explicit_domains(dimensions).each do |dom|
127
+ dom_group[dom] = [] if dom_group[dom].nil?
128
+ dom_group[dom] << ctx
129
+ end
130
+ end
131
+ dom_group
132
+ end
133
+
134
+ # Takes optional dimensions as array
135
+ # Returns contexts group by period as map. Map contains period as key and corresponding contexts as value
136
+ def ctx_groupby_period(dimensions=[])
137
+ period_group={}
138
+ all_contexts= context(nil, dimensions)
139
+ all_contexts=[all_contexts] unless all_contexts.is_a?(Array)
140
+ all_contexts.each do |ctx|
141
+ period_group[ctx.period] = [] if period_group[ctx.period].nil?
142
+ period_group[ctx.period] << ctx
143
+ end
144
+ period_group
145
+ end
146
+
147
+ # Prints dimension -> domain -> context relationship for all contexts in the console.
148
+ def ctx_groupby_dim_dom_print
149
+ group_dim=ctx_groupby_dim
150
+ group_dim.keys.each do |dimension|
151
+ puts " dimension :: " + dimension
152
+ group_dom=ctx_groupby_dom(dimension)
153
+ group_dom.keys.each do |domain|
154
+ puts " \t domain :: " + domain
155
+ group_dom[domain].each do |ctx|
156
+ puts " \t\t ctx :: " + ctx.id
157
+ end
158
+ end
159
+ end
160
+ end
161
+
162
+ def inspect
163
+ self.to_s
164
+ end
165
+
166
+ private
167
+ # Takes optional context_id
168
+ # Returns all contexts if context_id is nil
169
+ # Returns matching context as Context object if context_id is given and matching context found
170
+ # Returns nil if context_id is given and no matching context found
171
+ def context_by_id(context_id=nil)
172
+ if context_id.nil?
173
+ contexts=[]
174
+ @xbrl_content["context"].each {|c| contexts << to_ctx_obj(c) }
175
+ return contexts
176
+ end
177
+
178
+ ctx_content=nil
179
+ @xbrl_content["context"].each { |ctx| ctx_content=ctx if ctx["id"]==context_id}
180
+ $LOG.warn " unable to find context for id [" + context_id+"]" if ctx_content.nil?
181
+ return nil if ctx_content.nil?
182
+ return to_ctx_obj(ctx_content)
183
+
184
+ end
185
+
186
+ # Creates Context object from context content of XBRL instance file
187
+ def to_ctx_obj (ctx_content)
188
+ id=ctx_content["id"]
189
+
190
+ entity_content = ctx_content["entity"][0]
191
+ e = entity(entity_content)
192
+
193
+ period_content = ctx_content["period"][0]
194
+ p = period(period_content)
195
+
196
+ s = scenario(ctx_content)
197
+ _context = Context.new(id, e, p, s)
198
+ _context.ns=ctx_content["nspace"]
199
+ _context.nsp=ctx_content["nspace_prefix"]
200
+ return _context
201
+ end
202
+
203
+ # Returns map if period is duration. Map has key with name "start_date" and "end_date"
204
+ # Returns string if period is instant
205
+ # Returns -1 if period is forever
206
+ def period(period_content)
207
+ if period_content["startDate"] && period_content["endDate"]
208
+ return {"start_date" => Date.parse(period_content["startDate"][0]["content"]), "end_date" => Date.parse(period_content["endDate"][0]["content"])}
209
+ elsif period_content["instant"]
210
+ return Date.parse(period_content["instant"][0]["content"])
211
+ elsif period_content["forever"]
212
+ return Context::PERIOD_FOREVER
213
+ end
214
+ end
215
+
216
+ # Returns Entity object
217
+ def entity(entity_content)
218
+ entity_identifier = Identifier.new(entity_content["identifier"][0]["scheme"], entity_content["identifier"][0]["content"].strip!)
219
+
220
+ entity_segment_content=entity_content["segment"]
221
+ entity_segment=nil
222
+ unless entity_segment_content.nil?
223
+ entity_segment=entity_segment_content[0]
224
+ end
225
+
226
+ return Entity.new(entity_identifier, entity_segment)
227
+ end
228
+
229
+ # Returns scenario content
230
+ def scenario(ctx_content)
231
+ s=nil
232
+ unless ctx_content["scenario"].nil?
233
+ s = ctx_content["scenario"][0]
234
+ end
235
+ return s
236
+ end
237
+
238
+ public
239
+ # Takes optional unit_id
240
+ # Returns all units if unit_id is nil
241
+ # Returns matching unit as Unit object if unit_id is given and matching unit found
242
+ # Returns nil if unit_id is given and no matching unit found
243
+ def unit(unit_id=nil)
244
+ unit_content = @xbrl_content["unit"]
245
+ return nil if unit_content.nil?
246
+
247
+ units=[]
248
+
249
+ l = lambda {|measure_list| measures=[]; measure_list.each { |measure| measures << measure["content"]}; return measures}
250
+ unit_content.each do |unit|
251
+
252
+ next unless unit_id.nil? || unit["id"].to_s == unit_id
253
+
254
+ _unit=nil
255
+ unless unit["measure"].nil?
256
+ _unit = Unit.new(unit["id"], l.call(unit["measure"]))
257
+ else
258
+ divide_content = unit["divide"][0]
259
+
260
+ numerator = l.call(divide_content["unitNumerator"][0]["measure"])
261
+ denominator = l.call(divide_content["unitDenominator"][0]["measure"])
262
+
263
+ divide=Unit::Divide.new(numerator, denominator)
264
+ _unit = Unit.new(unit["id"], divide)
265
+ end
266
+ _unit.ns=unit["nspace"]
267
+ _unit.nsp=unit["nspace_prefix"]
268
+ units << _unit
269
+ end
270
+ return units[0] unless unit_id.nil?
271
+ units
272
+ end
273
+
274
+ private
275
+ def to_item_obj(item, name)
276
+ context, unit, precision, decimals, _footnotes=nil
277
+
278
+ context = context(item["contextRef"]) unless item["contextRef"].nil?
279
+ value = item["content"]
280
+
281
+ unit = unit(item["unitRef"]) unless item["unitRef"].nil?
282
+ precision = item["precision"] unless item["precision"].nil?
283
+ decimals = item["decimals"] unless item["decimals"].nil?
284
+
285
+ _footnotes = footnotes(item["id"]) unless item["id"].nil?
286
+ _item=Item.new(self, name, context, value, unit, precision, decimals, _footnotes)
287
+ _item.ns=item["nspace"]
288
+ _item.nsp=item["nspace_prefix"]
289
+ _item.def=@taxonomy.definition(name)
290
+ return _item
291
+ end
292
+
293
+ public
294
+ def item_all
295
+
296
+ return @item_all unless @item_all.nil?
297
+
298
+ all_items = @xbrl_content
299
+ return nil if all_items.nil?
300
+
301
+ @item_all=[]
302
+
303
+ all_items.each do |name, item_content|
304
+ next unless item_content.is_a?(Array)
305
+ next if item_content.size > 0 && item_content[0]["contextRef"].nil?
306
+ @item_all = @item_all + item(name)
307
+ end
308
+ @item_all
309
+ end
310
+
311
+ def item_all_map
312
+ items=item_all
313
+ return nil if items.nil?
314
+
315
+ items_hash={}
316
+
317
+ items.each do |item|
318
+ _name= item.name.upcase
319
+ items_hash[_name] = [] unless items_hash.include?(_name)
320
+ items_hash[_name] << item
321
+ end
322
+ items_hash
323
+ end
324
+
325
+ # Takes name and optional context_ref and unit_ref
326
+ # Returns array of Item for given name, context_ref and unit_ref
327
+ # Returns empty array if item is not found
328
+ def item(name, context_ref=nil, unit_ref=nil)
329
+
330
+ item_content = @xbrl_content[name]
331
+
332
+ return [] if item_content.nil?
333
+
334
+ items=[]
335
+
336
+ item_content.each do |item|
337
+
338
+ next unless context_ref.nil? || context_ref == item["contextRef"]
339
+ next unless unit_ref.nil? || unit_ref == item["unitRef"]
340
+
341
+ item = to_item_obj(item, name)
342
+ items << item
343
+ end
344
+ items
345
+ end
346
+
347
+ # Takes item name
348
+ # Returns array of contexts for given item name
349
+ # Returns empty array if no item with given name found
350
+ def context_for_item(item_name)
351
+ contexts=[]
352
+ items = item(item_name)
353
+ items.each {|item| contexts << item.context}
354
+ return contexts
355
+ end
356
+
357
+
358
+ # Takes item name and filter block
359
+ # Fetches item with name and invokes filter block with item context
360
+ # Returns matched items.
361
+ def item_ctx_filter(name, &context_filter_block)
362
+ items=item(name)
363
+ return items if context_filter_block.nil?
364
+ filtered_items=[]
365
+ items.each do |item|
366
+ filtered_items << item if yield(item.context)
367
+ end
368
+ filtered_items
369
+ end
370
+
371
+ public
372
+ # Takes optional item id and language
373
+ # Every item in XBRL instance file may contain optional id element.
374
+ # Footnotes is associated with id of the item. Footnotes may be in different languages.
375
+ # Returns Map with lang as key and corresponding footnotes in array as value if item_id is givien
376
+ # Returns Map with item_id as key and another Map as value.
377
+ # Second map has lang as key and corresponding footnotes in array as value if item_id is givien
378
+ # Returns nil if no match found for item_it or footnotes not exist
379
+ def footnotes (item_id=nil, lang=nil)
380
+ @item_footnote_map=nil
381
+ raise " lang can't be passed when item id is nil" if item_id.nil? && (not lang.nil?)
382
+ @item_footnote_map = compute_footnotes if @item_footnote_map.nil?
383
+ return nil if @item_footnote_map.nil?
384
+ return @item_footnote_map[item_id] if (not item_id.nil?) && lang.nil?
385
+ return @item_footnote_map[item_id][lang] unless item_id.nil? || lang.nil?
386
+ @item_footnote_map
387
+ end
388
+
389
+ def entity_details=(value)
390
+ @entity_details.merge!(value) if value.is_a?(Hash)
391
+ end
392
+
393
+ def entity_details
394
+ if @entity_details.size==0
395
+ begin
396
+ # Specific to US filing
397
+ e_name=item("EntityRegistrantName")[0]
398
+ e_ci_key=item("EntityCentralIndexKey")[0]
399
+ e_doc_type=item("DocumentType")[0]
400
+ e_doc_end_type=item("DocumentPeriodEndDate")[0]
401
+
402
+ fedate=item("CurrentFiscalYearEndDate")
403
+ e_fiscal_end_date=fedate[0] unless fedate.nil?
404
+
405
+ shares_outstanding = item("EntityCommonStockSharesOutstanding")
406
+ e_common_shares_outstanding=shares_outstanding[0] unless shares_outstanding.nil?
407
+
408
+ @entity_details["name"]=e_name.value unless e_name.nil?
409
+ @entity_details["ci_key"]=e_ci_key.value unless e_ci_key.nil?
410
+ @entity_details["doc_type"]=e_doc_type.value unless e_doc_type.nil?
411
+ @entity_details["doc_end_date"]=e_doc_end_type.value unless e_doc_end_type.nil?
412
+ @entity_details["fiscal_end_date"]=e_fiscal_end_date.value unless e_fiscal_end_date.nil?
413
+ @entity_details["common_shares_outstanding"]=e_common_shares_outstanding.value unless e_common_shares_outstanding.nil?
414
+
415
+ unless @file_name.nil?
416
+ file_name=File.basename(@file_name)
417
+ symbol=file_name.split("-")[0]
418
+ symbol.upcase!
419
+
420
+ @entity_details["symbol"]=symbol unless symbol.nil?
421
+ end
422
+ rescue Exception => e
423
+ @entity_details
424
+ end
425
+ end
426
+ @entity_details
427
+ end
428
+
429
+ private
430
+ def compute_footnotes()
431
+ return nil if @xbrl_content["footnoteLink"].nil?
432
+ item_map={}
433
+ @xbrl_content["footnoteLink"][0]["loc"].each do |loc|
434
+ item_map[loc["xlink:label"]]=[] if item_map[loc["xlink:label"]].nil?
435
+ item_map[loc["xlink:label"]] << loc["xlink:href"].split("#")[-1]
436
+ end unless @xbrl_content["footnoteLink"][0]["loc"].nil?
437
+
438
+ footnote_map = {}
439
+ @xbrl_content["footnoteLink"][0]["footnote"].each do |fn|
440
+ label=fn["xlink:label"]
441
+ lang=fn["xml:lang"]
442
+ content=fn["content"]
443
+
444
+ footnote_map[label]={} if footnote_map[label].nil?
445
+ footnote_map[label][lang]=content
446
+ end unless @xbrl_content["footnoteLink"][0]["footnote"].nil?
447
+
448
+ label_to_footnote_map = {}
449
+ @xbrl_content["footnoteLink"][0]["footnoteArc"].each do |fn_arc|
450
+ label_to_footnote_map[fn_arc["xlink:from"]] = [] if label_to_footnote_map[fn_arc["xlink:from"]].nil?
451
+ label_to_footnote_map[fn_arc["xlink:from"]] << fn_arc["xlink:to"]
452
+ end unless @xbrl_content["footnoteLink"][0]["footnoteArc"].nil?
453
+
454
+ return nil if label_to_footnote_map.size==0
455
+
456
+ item_footnote_map ={}
457
+ label_to_footnote_map.each do |item_label, fn_labels|
458
+ item_ids=item_map[item_label]
459
+ item_ids.each do |item_id|
460
+ item_footnote_map[item_id] = {} if item_footnote_map[item_id].nil?
461
+ map=item_footnote_map[item_id]
462
+ fn_labels.each do |fn_lab|
463
+ fn=footnote_map[fn_lab]
464
+ fn.each do |lang, content|
465
+ map[lang]=[] if map[lang].nil?
466
+ map[lang] << content
467
+ end
468
+ end unless fn_labels.nil?
469
+ end unless item_ids.nil?
470
+ end
471
+ item_footnote_map
472
+ end
473
+
474
+ end
475
+ end
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # Author:: xbrlware@bitstat.com
4
+ #
5
+ # Copyright:: 2009, 2010 bitstat (http://www.bitstat.com). All Rights Reserved.
6
+ #
7
+ # License:: Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
16
+ # implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #
20
+ module Xbrlware
21
+
22
+ # This class represents each item in the XBRL instance file.
23
+ # Taxonomy definition of a item can be retrieved using def or meta methods.
24
+ # Look at {delaing with instance page on xbrlware wiki}[http://code.google.com/p/xbrlware/wiki/InstanceTaxonomy] for more details.
25
+ class Item
26
+ include NSAware
27
+
28
+ attr_reader :name, :context, :unit, :precision, :decimals, :footnotes
29
+ attr_accessor :ins, :def
30
+
31
+ # Constructs item
32
+ # value is normalized based on precision and decimals passed as per XBRL specification
33
+ def initialize(instance, name, context, value, unit=nil, precision=nil, decimals=nil, footnotes=nil)
34
+ @ins=instance
35
+ @name=name
36
+ @context = context
37
+ @precision=precision
38
+ @decimals=decimals
39
+ @footnotes=footnotes
40
+ @value = ItemValue.new(value, precision, decimals).value
41
+ @unit = unit
42
+ end
43
+
44
+ def value
45
+ return yield(@value) if block_given?
46
+ @value
47
+ end
48
+
49
+ def is_value_numeric?
50
+ @value.to_i.to_s == @value || @value.to_f.to_s == @value
51
+ end
52
+
53
+ alias_method :meta, :def
54
+
55
+ def balance
56
+ _balance=meta["xbrli:balance"] unless meta.nil?
57
+ _balance.nil? ? "" : _balance
58
+ end
59
+
60
+ class ItemValue # :nodoc:
61
+
62
+ attr_reader :item_value
63
+
64
+ def initialize(item_value, precision=nil, decimals=nil)
65
+ @item_value=item_value
66
+ @precision=precision
67
+ @decimals=decimals
68
+ end
69
+
70
+ def value()
71
+ return precision() unless @precision.nil?
72
+ return decimals() unless @decimals.nil?
73
+ return @item_value
74
+ end
75
+
76
+ # returns BigDecimal float representation as String
77
+ def precision()
78
+ return @item_value if @precision=="INF"
79
+
80
+ precision_i=@precision.to_i
81
+ new_value=BigDecimal(@item_value)
82
+
83
+ is_value_integer = new_value==@item_value.to_i
84
+
85
+
86
+ return to_precision_from_integer(@item_value.to_i, precision_i) if is_value_integer
87
+
88
+ index_of_dot = new_value.abs.to_s("F").index(".")
89
+
90
+ # When mod value is greater than 1 and float number
91
+ if new_value.abs > 1
92
+ #Precision is less than number of digits before decimal
93
+ return to_precision_from_integer(new_value.to_i, precision_i) if precision_i <= index_of_dot
94
+
95
+ #Precision is greater than number of digits before decimal
96
+ return new_value.round(precision_i-index_of_dot).to_s("F")
97
+ else
98
+ new_value_s = new_value.abs.to_s("F")
99
+ no_of_zeroes = new_value_s.split(/[1-9].*/).join.length - new_value_s.index(".")-1
100
+ return new_value.round(no_of_zeroes + precision_i).to_s("F")
101
+ end
102
+ end
103
+
104
+ # returns BigDecimal float representation as String
105
+ def decimals
106
+ return @item_value if @decimals=="INF"
107
+ return BigDecimal(@item_value).round(@decimals.to_i).to_s("F")
108
+ end
109
+
110
+ private
111
+ def to_precision_from_integer(new_value, precision_i)
112
+ factor=10 **(new_value.abs.to_s.length - precision_i)
113
+ return to_big_decimal_float_str(((BigDecimal(new_value.to_s) / factor).to_i * factor).to_s)
114
+ end
115
+
116
+ def to_big_decimal_float_str(value)
117
+ return BigDecimal(value).to_s("F")
118
+ end
119
+
120
+ end
121
+ end
122
+
123
+ end