xbrlware-ruby19 1.1.2.19

Sign up to get free protection for your applications and to get access to all the features.
@@ -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