fias_reader 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0487bfee5053fa367df10d23d433effe5c1cbf9b
4
+ data.tar.gz: c221923b51e134a479fad411d742111196bfbc30
5
+ SHA512:
6
+ metadata.gz: 1c760dc5c617ac874ba55a2e0e4d64307dbb1eb6afd73ee2c3fd1046eb3abf8b504a098eed0fb8dccf9666a917d5fd44479bc15cbdfbd9a735d29de8b126bcf3
7
+ data.tar.gz: c1dcd52754c78df2c6f1c7aa7674831a16cff3b3d84afe21faff3bd47d315b38cef3ddd52b12830bbedb00f0f1ff6b49aee74bf44ffb8782541feba32e5743c0
@@ -0,0 +1,11 @@
1
+ .bundle/
2
+ .yardoc
3
+ Gemfile.lock
4
+ *.gem
5
+ /_yardoc/
6
+ /coverage/
7
+ /doc/
8
+ /pkg/
9
+ /spec/reports/
10
+ /tmp/
11
+ /spec/db/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.4
4
+ before_install: gem install bundler -v 1.11.2
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'dm-types',
4
+ git: 'git://github.com/julienma/dm-types.git',
5
+ branch: 'gem-v1.2.2-with-frozen-nilclass-fix'
6
+
7
+ gemspec
@@ -0,0 +1,65 @@
1
+ # FiasReader
2
+
3
+ Библиотека для работы с XML базой ФИАС. Библиотека позволяет провести обход по всем активным записям домов.
4
+
5
+ ## Installation
6
+
7
+ Добваить строку в Gemfile:
8
+
9
+ ```ruby
10
+ gem 'fias_reader', path: '/path_to_dir/fias_reader'
11
+ ```
12
+
13
+ И выполнить после:
14
+
15
+ ```
16
+ $ bundle
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ Перед запуском лучше исполнить rake для построения индекса:
22
+
23
+ ```
24
+ rake fias_reader:build DB_PATH=/path/to/dir
25
+ ```
26
+
27
+ если запускать без него то индекс построится в процессе выполнения.
28
+
29
+ Для просотого обхода по всем записям домов нужно вызвать итератор следующим способом:
30
+
31
+ ``` ruby
32
+ FiasReader.open('/path/to/unpacked/fais/xml/dir/').each do |row|
33
+ # somecode here
34
+ end
35
+ ```
36
+
37
+ Итератор передает аргумент типа `FiasReader::Reader::Row`, который имеет методы:
38
+ * `postal_code` - почтовый индекс
39
+ * `house_number` - номер дома
40
+ * `house_building_number` - номер корпуса, опционально
41
+ * `house_structure_number` - номер строения, опционально
42
+ * `levels` - хеш всех уровни частей адреса
43
+ * Части адреса возращаются либо масивом из двух объектов(сокращенное название типа части адреса, название части адреса). В случае если часть адреса не указана возвращаем nil.
44
+ * `street` - улица
45
+ * `settlement` - населенный пункт
46
+ * `city_district` - внутригородская территория
47
+ * `city` - город
48
+ * `district` - район
49
+ * `autonomy` - автономный округ
50
+ * `state` - регион
51
+
52
+
53
+ После выполнения работы нужно удалить индекс:
54
+
55
+ ```
56
+ rake fias_reader:clear
57
+ ```
58
+
59
+ ## Development
60
+
61
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
62
+
63
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
64
+
65
+ ## Contributing
@@ -0,0 +1,9 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ spec = Gem::Specification.find_by_name 'fias'
6
+ load "#{spec.gem_dir}/tasks/download.rake"
7
+ load "#{spec.gem_dir}/tasks/db.rake"
8
+
9
+ task default: :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'fias_reader'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'fias_reader/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'fias_reader'
8
+ spec.version = FiasReader::VERSION
9
+ spec.authors = ["Андрей Большов", "uno4ki"]
10
+ spec.email = ['asnow.dev@gmail.ru', 'i.have@no.mail']
11
+
12
+ spec.summary = 'FIAS reader'
13
+ spec.description = 'FIAS reader'
14
+ spec.homepage = 'http://www.thefurnish.ru/'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = 'exe'
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ['lib']
21
+
22
+ spec.add_dependency 'ox', '~> 2.4'
23
+ spec.add_dependency 'activesupport', '~> 4.2'
24
+ spec.add_dependency 'data_mapper', '~>1.2'
25
+ spec.add_dependency 'dm-sqlite-adapter', '~>1.2'
26
+ spec.add_dependency 'sqlite3', '~>1.3'
27
+
28
+ spec.add_development_dependency 'bundler', '~> 1.11'
29
+ spec.add_development_dependency 'rake', '~> 10.0'
30
+ spec.add_development_dependency 'rspec', '~> 3.0'
31
+ spec.add_development_dependency 'pry'
32
+ spec.add_development_dependency 'ruby-prof'
33
+ spec.add_development_dependency 'pry-byebug'
34
+ spec.add_development_dependency 'pry-stack_explorer'
35
+ end
@@ -0,0 +1,41 @@
1
+ require 'fias_reader/version'
2
+
3
+ require 'date'
4
+ require 'sqlite3'
5
+ require 'ox'
6
+ require 'data_mapper'
7
+ require 'dm-migrations'
8
+ require 'dm-types'
9
+ begin
10
+ require 'pry'
11
+ rescue
12
+ nil
13
+ end
14
+ require 'active_support/all'
15
+ require 'fias_reader/parse_logic/abstract'
16
+ require 'fias_reader/parse_logic/attributes'
17
+ require 'fias_reader/parse_logic/attributes_select'
18
+ require 'fias_reader/parse_logic/depth'
19
+ require 'fias_reader/parse_logic/logger'
20
+ require 'fias_reader/parse_logic/rows'
21
+ require 'fias_reader/parse_logic/stub'
22
+ require 'fias_reader/parse_logic'
23
+ require 'fias_reader/parser'
24
+ require 'fias_reader/scope'
25
+ require 'fias_reader/cache'
26
+ require 'fias_reader/cache/guid'
27
+ require 'fias_reader/cache/address_part/level_adapter'
28
+ require 'fias_reader/cache/address_part'
29
+ require 'fias_reader/table'
30
+ require 'fias_reader/table/house'
31
+ require 'fias_reader/table/address_part'
32
+
33
+ require 'fias_reader/reader/row'
34
+ require 'fias_reader/reader'
35
+
36
+ module FiasReader
37
+ # Создаем инстанс ФИАС базы. Параметр путь до файлов базы
38
+ def self.open(path)
39
+ FiasReader::Reader.new path
40
+ end
41
+ end
@@ -0,0 +1,38 @@
1
+ module FiasReader
2
+ module Cache
3
+ FILE = '/tmp/fias_reader_cache.db'.freeze
4
+
5
+ def init(reader)
6
+ reindex = true unless File.exist?(FILE)
7
+
8
+ DataMapper.setup(:default, "sqlite://#{FILE}")
9
+ begin
10
+ DataMapper.auto_upgrade!
11
+ rescue
12
+ nil
13
+ end
14
+ adapter = DataMapper.repository(:default).adapter
15
+ adapter.execute('PRAGMA journal_mode=OFF; PRAGMA synchronous=OFF; PRAGMA locking_mode = EXCLUSIVE; PRAGMA count_changes = OFF; PRAGMA cache_size=500000; PRAGMA temp_store = MEMORY; PRAGMA auto_vacuum = NONE;')
16
+ index_build(reader) if reindex
17
+ end
18
+
19
+ def clear
20
+ File.delete(FILE)
21
+ end
22
+
23
+ def index_build(reader)
24
+ adapter = DataMapper.repository(:default).adapter
25
+ adapter.execute('BEGIN;')
26
+ puts "Индексируем части адресов..."
27
+ FiasReader::Cache::AddressPart.index reader
28
+ puts "Индексируем уровни адресов..."
29
+ adapter.execute('CREATE INDEX `aolevel_index` ON `fias_reader_cache_address_parts` (`aolevel` );')
30
+ puts "Индексируем структуру частеи адресов..."
31
+ FiasReader::Cache::AddressPart.index_levels
32
+ puts "Индексирование закончено..."
33
+ adapter.execute('COMMIT;')
34
+ end
35
+
36
+ module_function :init, :clear, :index_build
37
+ end
38
+ end
@@ -0,0 +1,118 @@
1
+ module FiasReader
2
+ module Cache
3
+ class AddressPart
4
+ include DataMapper::Resource
5
+ ZERO_STR = '0'.freeze
6
+
7
+ # property :AOGUID, String
8
+ property :id0, Integer, key: true
9
+ property :id1, Integer, key: true
10
+ property :id2, Integer, key: true
11
+ property :id3, Integer, key: true
12
+ property :SHORTNAME, String, length: 10
13
+ property :OFFNAME, String, length: 120
14
+ property :levels_filled, Boolean
15
+ property :shortname_l1, String, length: 10
16
+ property :offname_l1, String, length: 120
17
+ property :shortname_l2, String, length: 10
18
+ property :offname_l2, String, length: 120
19
+ property :shortname_l3, String, length: 10
20
+ property :offname_l3, String, length: 120
21
+ property :shortname_l4, String, length: 10
22
+ property :offname_l4, String, length: 120
23
+ property :shortname_l5, String, length: 10
24
+ property :offname_l5, String, length: 120
25
+ property :shortname_l6, String, length: 10
26
+ property :offname_l6, String, length: 120
27
+ property :shortname_l7, String, length: 10
28
+ property :offname_l7, String, length: 120
29
+ property :shortname_l8, String, length: 10
30
+ property :offname_l8, String, length: 120
31
+ property :shortname_l9, String, length: 10
32
+ property :offname_l9, String, length: 120
33
+ property :shortname_l65, String, length: 10
34
+ property :offname_l65, String, length: 120
35
+ property :shortname_l75, String, length: 10
36
+ property :offname_l75, String, length: 120
37
+ property :shortname_l90, String, length: 10
38
+ property :offname_l90, String, length: 120
39
+ property :shortname_l91, String, length: 10
40
+ property :offname_l91, String, length: 120
41
+ property :AOLEVEL, Integer
42
+ # property :PARENTGUID, String
43
+ property :parent0, Integer
44
+ property :parent1, Integer
45
+ property :parent2, Integer
46
+ property :parent3, Integer
47
+
48
+ require 'ruby-prof'
49
+
50
+ def self.index(reader)
51
+ attrs = properties.map(&:name)
52
+ FiasReader::Table::AddressPart.new(reader).query.all do |row|
53
+ next if row[:LIVESTATUS] == ZERO_STR
54
+ begin
55
+ params = FiasReader::Cache::Guid.new(row[:AOGUID]).to_attr(:id)
56
+ params.merge! FiasReader::Cache::Guid.new(row[:PARENTGUID]).to_attr(:parent)
57
+ params[:SHORTNAME] = row[:SHORTNAME]
58
+ params[:OFFNAME] = row[:OFFNAME]
59
+ params[:AOLEVEL] = row[:AOLEVEL]
60
+ create params
61
+ rescue DataObjects::IntegrityError => e
62
+ end
63
+ end
64
+ end
65
+
66
+ def self.index_levels
67
+ puts "Части 4..."
68
+ index_levels_batch '4'
69
+ puts "Части 6..."
70
+ index_levels_batch '6'
71
+ puts "Части 7..."
72
+ index_levels_batch '7'
73
+ end
74
+
75
+ def self.index_levels_batch(level)
76
+ start = -1
77
+ i = 0
78
+
79
+ while start != i
80
+ start = i
81
+ all(offset: i, limit: 1000, AOLEVEL: level).each do |item|
82
+ i += 1
83
+ puts Time.new if (i % 10_000).zero?
84
+ item.parent_string
85
+ end
86
+ end
87
+ end
88
+
89
+ def levels
90
+ @levels ||= FiasReader::Cache::AddressPart::LevelAdapter.new(self)
91
+ end
92
+
93
+ def parent_string
94
+ return levels if levels_filled
95
+
96
+ if parent0.nil?
97
+ levels.reset_own
98
+ else
99
+ # some bug with parent DataObjects::SyntaxError, so using first
100
+ parent_item = FiasReader::Cache::AddressPart.first(id0: parent0, id1: parent1, id2: parent2, id3: parent3)
101
+
102
+ if parent_item
103
+ levels.copy(parent_item.parent_string)
104
+ else
105
+ levels.reset_own
106
+ end
107
+ save
108
+ end
109
+
110
+ levels
111
+ end
112
+
113
+ belongs_to :parent, 'AddressPart',
114
+ parent_key: [:id0, :id1, :id2, :id3],
115
+ child_key: [:parent0, :parent1, :parent2, :parent3]
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,137 @@
1
+ module FiasReader
2
+ module Cache
3
+ class AddressPart
4
+ class LevelAdapter
5
+ module AddressObjectLevel
6
+ STREET = '7'.freeze
7
+ SETTLEMENT = '6'.freeze
8
+ CITY_DISTRICT = '5'.freeze
9
+ CITY = '4'.freeze
10
+ STATE_DISTRICT = '3'.freeze
11
+ AUTONOMY = '2'.freeze
12
+ STATE = '1'.freeze
13
+ # 90 – уровень дополнительных территорий
14
+ # 91 – уровень подчиненных дополнительным территориям объектов
15
+ end
16
+
17
+ attr_reader :item
18
+
19
+ def initialize(item)
20
+ @item = item
21
+ end
22
+
23
+ def copy(other)
24
+ @item.shortname_l1 = other.item.shortname_l1
25
+ @item.offname_l1 = other.item.offname_l1
26
+ @item.shortname_l2 = other.item.shortname_l2
27
+ @item.offname_l2 = other.item.offname_l2
28
+ @item.shortname_l3 = other.item.shortname_l3
29
+ @item.offname_l3 = other.item.offname_l3
30
+ @item.shortname_l4 = other.item.shortname_l4
31
+ @item.offname_l4 = other.item.offname_l4
32
+ @item.shortname_l5 = other.item.shortname_l5
33
+ @item.offname_l5 = other.item.offname_l5
34
+ @item.shortname_l6 = other.item.shortname_l6
35
+ @item.offname_l6 = other.item.offname_l6
36
+ @item.shortname_l7 = other.item.shortname_l7
37
+ @item.offname_l7 = other.item.offname_l7
38
+ @item.shortname_l8 = other.item.shortname_l8
39
+ @item.offname_l8 = other.item.offname_l8
40
+ @item.shortname_l9 = other.item.shortname_l9
41
+ @item.offname_l9 = other.item.offname_l9
42
+ @item.shortname_l65 = other.item.shortname_l65
43
+ @item.offname_l65 = other.item.offname_l65
44
+ @item.shortname_l75 = other.item.shortname_l75
45
+ @item.offname_l75 = other.item.offname_l75
46
+ @item.shortname_l90 = other.item.shortname_l90
47
+ @item.offname_l90 = other.item.offname_l90
48
+ @item.shortname_l91 = other.item.shortname_l91
49
+ @item.offname_l91 = other.item.offname_l91
50
+ reset_own
51
+ end
52
+
53
+ def reset_own
54
+ case @item[:AOLEVEL]
55
+ when 1
56
+ @item.shortname_l1 = @item[:SHORTNAME]
57
+ @item.offname_l1 = @item[:OFFNAME]
58
+ when 2
59
+ @item.shortname_l2 = @item[:SHORTNAME]
60
+ @item.offname_l2 = @item[:OFFNAME]
61
+ when 3
62
+ @item.shortname_l3 = @item[:SHORTNAME]
63
+ @item.offname_l3 = @item[:OFFNAME]
64
+ when 4
65
+ @item.shortname_l4 = @item[:SHORTNAME]
66
+ @item.offname_l4 = @item[:OFFNAME]
67
+ when 5
68
+ @item.shortname_l5 = @item[:SHORTNAME]
69
+ @item.offname_l5 = @item[:OFFNAME]
70
+ when 6
71
+ @item.shortname_l6 = @item[:SHORTNAME]
72
+ @item.offname_l6 = @item[:OFFNAME]
73
+ when 7
74
+ @item.shortname_l7 = @item[:SHORTNAME]
75
+ @item.offname_l7 = @item[:OFFNAME]
76
+ when 8
77
+ @item.shortname_l8 = @item[:SHORTNAME]
78
+ @item.offname_l8 = @item[:OFFNAME]
79
+ when 9
80
+ @item.shortname_l9 = @item[:SHORTNAME]
81
+ @item.offname_l9 = @item[:OFFNAME]
82
+ when 65
83
+ @item.shortname_l65 = @item[:SHORTNAME]
84
+ @item.offname_l65 = @item[:OFFNAME]
85
+ when 75
86
+ @item.shortname_l75 = @item[:SHORTNAME]
87
+ @item.offname_l75 = @item[:OFFNAME]
88
+ when 90
89
+ @item.shortname_l90 = @item[:SHORTNAME]
90
+ @item.offname_l90 = @item[:OFFNAME]
91
+ when 91
92
+ @item.shortname_l91 = @item[:SHORTNAME]
93
+ @item.offname_l91 = @item[:OFFNAME]
94
+ else
95
+ raise 'errors'
96
+ end
97
+ @item.levels_filled = true
98
+ end
99
+
100
+ # Возвращает улицу
101
+ def street
102
+ [@item.shortname_l7, @item.offname_l7]
103
+ end
104
+
105
+ # Возвращает деревню, поселок
106
+ def settlement
107
+ [@item.shortname_l6, @item.offname_l6]
108
+ end
109
+
110
+ # Возвращает внутригородской территории
111
+ def city_district
112
+ [@item.shortname_l5, @item.offname_l5]
113
+ end
114
+
115
+ # Возвращает город
116
+ def city
117
+ [@item.shortname_l4, @item.offname_l4]
118
+ end
119
+
120
+ # Возвращает район
121
+ def district
122
+ [@item.shortname_l3, @item.offname_l3]
123
+ end
124
+
125
+ # Возвращает автономный округ
126
+ def autonomy
127
+ [@item.shortname_l2, @item.offname_l2]
128
+ end
129
+
130
+ # Возвращает область
131
+ def state
132
+ [@item.shortname_l1, @item.offname_l1]
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,70 @@
1
+ module FiasReader
2
+ module Cache
3
+ class Guid
4
+ DASH_STR = '-'.freeze
5
+ EMPTY_STR = ''.freeze
6
+ GUID_PARTS = [
7
+ 0..8,
8
+ 8..16,
9
+ 16..24,
10
+ 24..32
11
+ ].freeze
12
+
13
+ def initialize(*args)
14
+ if args.size == 1
15
+ guid = args[0]
16
+ case guid
17
+ when Hash
18
+ init_from_hash guid
19
+ when Array
20
+ init_from_array guid
21
+ else
22
+ init_from_guid guid
23
+ end
24
+
25
+ elsif args.size == 2
26
+ init_from_hash_slice args[0], args[1]
27
+ elsif args.size > 2
28
+ init_from_array args
29
+ end
30
+ end
31
+
32
+ def to_attr(name)
33
+ @parts.each_with_index.each_with_object({}) do |(part, index), result|
34
+ result["#{name}#{index}"] = part
35
+ end
36
+ end
37
+
38
+ GUID_FORMAT_STR = '%08x'.freeze
39
+ def to_guid
40
+ @parts.each_with_object('') { |i, str| str << format(GUID_FORMAT_STR, i) }
41
+ end
42
+
43
+ def parts
44
+ @parts ||= []
45
+ end
46
+
47
+ attr_writer :parts
48
+
49
+ def init_from_array(val)
50
+ self.parts = val
51
+ end
52
+
53
+ def init_from_guid(guid)
54
+ (self.parts = []) && return unless guid
55
+ guid.tr!(DASH_STR, EMPTY_STR)
56
+ self.parts = GUID_PARTS.map { |range| guid[range].to_i(16) }
57
+ end
58
+
59
+ def init_from_hash(hash)
60
+ hash.each { |key, val| parts[key[-1].to_i] = val }
61
+ end
62
+
63
+ def init_from_hash_slice(hash, mask)
64
+ self.parts = hash.keys.select { |key| key.to_s =~ /^#{mask}\d/ }.each_with_object([]) do |key, obj|
65
+ obj[key[-1].to_i] = hash[key]
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,48 @@
1
+ module FiasReader
2
+ module ParseLogic
3
+ extend ActiveSupport::Concern
4
+ attr_accessor :element_name, :attr_name, :attr_value, :text
5
+
6
+ def self.build(logics)
7
+ Class.new do
8
+ include FiasReader::ParseLogic::Stub
9
+
10
+ logics.reverse.each do |logic|
11
+ include logic
12
+ end
13
+
14
+ include FiasReader::ParseLogic
15
+ end
16
+ end
17
+
18
+ included do
19
+ # define_callbacks :initialize, :start_element, :end_element, :attr, :text
20
+
21
+ def initialize(options)
22
+ @options = options.dup.freeze
23
+ super
24
+ end
25
+ end
26
+
27
+ def start_element(name)
28
+ @element_name = name
29
+ super
30
+ end
31
+
32
+ def end_element(name)
33
+ @element_name = name
34
+ super
35
+ end
36
+
37
+ def attr(name, value)
38
+ @attr_name = name
39
+ @attr_value = value
40
+ super
41
+ end
42
+
43
+ def text(value)
44
+ @text = value
45
+ super
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,6 @@
1
+ module FiasReader
2
+ module ParseLogic
3
+ module Abstract
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,12 @@
1
+ module FiasReader
2
+ module ParseLogic
3
+ module Attributes
4
+ include FiasReader::ParseLogic::Abstract
5
+
6
+ def attr(name, value)
7
+ @row[name] = value if @row
8
+ super
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module FiasReader
2
+ module ParseLogic
3
+ module AttributesSelect
4
+ include FiasReader::ParseLogic::Abstract
5
+
6
+ def attr(name, value)
7
+ @row[name] = value if @row && @options[:select].include?(name)
8
+ super
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,22 @@
1
+ module FiasReader
2
+ module ParseLogic
3
+ module Depth
4
+ include FiasReader::ParseLogic::Abstract
5
+
6
+ def initialize(options)
7
+ @depth = 0
8
+ super
9
+ end
10
+
11
+ def start_element(name)
12
+ @depth += 1
13
+ super
14
+ end
15
+
16
+ def end_element(name)
17
+ @depth -= 1
18
+ super
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,27 @@
1
+ module FiasReader
2
+ module ParseLogic
3
+ module Logger
4
+ include FiasReader::ParseLogic::Abstract
5
+
6
+ def start_element(name)
7
+ puts "start: #{name}"
8
+ super
9
+ end
10
+
11
+ def end_element(name)
12
+ puts "end: #{name}"
13
+ super
14
+ end
15
+
16
+ def attr(name, value)
17
+ puts " #{name} => #{value}"
18
+ super
19
+ end
20
+
21
+ def text(value)
22
+ puts "text #{value}"
23
+ super
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,28 @@
1
+ module FiasReader
2
+ module ParseLogic
3
+ module Rows
4
+ include FiasReader::ParseLogic::Abstract
5
+
6
+ def initialize(options)
7
+ @rows_enabled = true if @options[:rows].respond_to? :call
8
+ super
9
+ end
10
+
11
+ def start_element(name)
12
+ if @rows_enabled && @depth == 2
13
+ @row = {}
14
+ @skip = false
15
+ end
16
+ super
17
+ end
18
+
19
+ def end_element(name)
20
+ if @rows_enabled && @depth == 1
21
+ @options[:rows].call(@row) if @row
22
+ @row = nil
23
+ end
24
+ super
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,22 @@
1
+ module FiasReader
2
+ module ParseLogic
3
+ module Stub
4
+ include FiasReader::ParseLogic::Abstract
5
+
6
+ def initialize(options)
7
+ end
8
+
9
+ def start_element(name)
10
+ end
11
+
12
+ def end_element(name)
13
+ end
14
+
15
+ def attr(name, value)
16
+ end
17
+
18
+ def text(value)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,10 @@
1
+ module FiasReader
2
+ class Parser < ::Ox::Sax
3
+ attr_reader :logics
4
+ delegate :start_element, :end_element, :attr, :text, to: :logics
5
+
6
+ def initialize(logics, options)
7
+ @logics = ParseLogic.build(logics).new options
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,35 @@
1
+ module FiasReader
2
+ # Инерфейс для работы XML с базой ФИАС
3
+ class Reader
4
+ attr_reader :path
5
+ def initialize(path)
6
+ @path = path
7
+ end
8
+
9
+ # Итерируем по всем активным записям домов, принимает блок в качестве аргумента
10
+ # В вызываемы блок передается объект типа FiasReader::Reader::Row.
11
+ XML_DATE_SEPARATOR = '-'.freeze
12
+ EMPTY_STRING = '0'.freeze
13
+ TODAY = Date.today.strftime('%Y0%m0%d').to_i
14
+ def each
15
+ Cache.init self
16
+
17
+ Table::House.new(self).query.all do |row|
18
+ next unless active?(row)
19
+ row = FiasReader::Reader::Row.new row
20
+ yield row if row.address_object
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def active?(row)
27
+ date_to_int(row[:ENDDATE]) >= TODAY && date_to_int(row[:STARTDATE]) < TODAY
28
+ end
29
+
30
+ def date_to_int(date)
31
+ date.tr!(XML_DATE_SEPARATOR, EMPTY_STRING)
32
+ date.to_i
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,80 @@
1
+ module FiasReader
2
+ class Reader
3
+ # Представление строки итерироемой Reader'ом
4
+ class Row
5
+ def initialize(house_row)
6
+ @house_row = house_row
7
+ end
8
+
9
+ # Возвращает объект части адреса к которой прикреплен дом(объект улицы)
10
+ def address_object
11
+ return @address_object unless @address_object.nil?
12
+ @address_object = FiasReader::Cache::AddressPart.first(address_object_id.to_attr(:id)) || false
13
+ end
14
+
15
+ def address_object_id
16
+ FiasReader::Cache::Guid.new(@house_row[:AOGUID])
17
+ end
18
+
19
+ # Возвращает хеш уровней частей адреса к которой прикреплен дом(объект улицы)
20
+ def levels
21
+ address_object.parent_string
22
+ end
23
+
24
+ # Возвращает номер дома
25
+ def house_number
26
+ @house_row[:HOUSENUM]
27
+ end
28
+
29
+ # Возвращает номер корпуса
30
+ def house_building_number
31
+ @house_row[:BUILDNUM]
32
+ end
33
+
34
+ # Возвращает номер строения
35
+ def house_structure_number
36
+ @house_row[:STRUCNUM]
37
+ end
38
+
39
+ # Возвращает улицу
40
+ def street
41
+ levels.street
42
+ end
43
+
44
+ # Возвращает почтовый индекс
45
+ def postal_code
46
+ @house_row[:POSTALCODE]
47
+ end
48
+
49
+ # Возвращает деревню, поселок
50
+ def settlement
51
+ levels.settlement
52
+ end
53
+
54
+ # Возвращает внутригородской территории
55
+ def city_district
56
+ levels.city_district
57
+ end
58
+
59
+ # Возвращает город
60
+ def city
61
+ levels.city
62
+ end
63
+
64
+ # Возвращает район
65
+ def district
66
+ levels.district
67
+ end
68
+
69
+ # Возвращает автономный округ
70
+ def autonomy
71
+ levels.autonomy
72
+ end
73
+
74
+ # Возвращает область
75
+ def state
76
+ levels.state
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,48 @@
1
+ module FiasReader
2
+ # Инерфейс для работы XML с базой ФИАС
3
+ class Scope
4
+ attr_reader :options, :logics
5
+
6
+ def initialize(table)
7
+ @table = table
8
+ @options = {}
9
+ @logics = @table.class::LOGICS.dup
10
+ end
11
+
12
+ def all
13
+ @options[:rows] = -> (row) { yield row }
14
+ parse!
15
+ end
16
+
17
+ def first
18
+ result = nil
19
+ catch :fias_parse_stop do
20
+ @options[:rows] = -> (row) do
21
+ result = row
22
+ throw :fias_parse_stop
23
+ end
24
+ parse!
25
+ end
26
+ result
27
+ end
28
+
29
+ def select(*args)
30
+ index = @logics.index(FiasReader::ParseLogic::Attributes)
31
+ @logics[index] = FiasReader::ParseLogic::AttributesSelect if index
32
+ @options[:select] = args
33
+ self
34
+ end
35
+
36
+ def parse!(parser = default_parser)
37
+ Ox.sax_parse parser, @table.file, symbolize: true
38
+ end
39
+
40
+ def default_parser
41
+ FiasReader::Parser.new(logics, @options)
42
+ end
43
+
44
+ # def logics
45
+ # @logics #+ [ParseLogic::Logger]
46
+ # end
47
+ end
48
+ end
@@ -0,0 +1,28 @@
1
+ module FiasReader
2
+ # Инерфейс для работы XML с базой ФИАС
3
+ class Table
4
+ LOGICS = [
5
+ ParseLogic::Depth,
6
+ ParseLogic::Rows,
7
+ ParseLogic::Attributes
8
+ ].freeze
9
+ def initialize(reader)
10
+ @reader = reader
11
+ end
12
+
13
+ def query
14
+ FiasReader::Scope.new(self)
15
+ end
16
+
17
+ def file
18
+ File.new(file_path, 'r')
19
+ end
20
+
21
+ def file_path
22
+ Dir[
23
+ "#{@reader.path}/#{self.class::File}_*.XML",
24
+ "#{@reader.path}/#{self.class::File}_*.xml"
25
+ ].first
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,7 @@
1
+ module FiasReader
2
+ class Table
3
+ class AddressPart < FiasReader::Table
4
+ File = 'AS_ADDROBJ'.freeze
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module FiasReader
2
+ class Table
3
+ class House < FiasReader::Table
4
+ File = 'AS_HOUSE'.freeze
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ module FiasReader
2
+ VERSION = '0.1.0'.freeze
3
+ end
@@ -0,0 +1,11 @@
1
+ namespace :fias_reader do
2
+ task :build do
3
+ raise "Вы должны указать путь к XML базе ФИАС в переменной окружения DB_PATH" unless ENV[:DB_PATH]
4
+ raise "Не верно указан путь к XML базе ФИАС в переменной окружения DB_PATH" unless File.exist? ENV[:DB_PATH]
5
+ Cache.init FiasReader.open(ENV[:DB_PATH])
6
+ end
7
+
8
+ task :clear do
9
+ Cache.clear
10
+ end
11
+ end
metadata ADDED
@@ -0,0 +1,245 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fias_reader
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Андрей Большов
8
+ - uno4ki
9
+ autorequire:
10
+ bindir: exe
11
+ cert_chain: []
12
+ date: 2016-06-27 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: ox
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '2.4'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '2.4'
28
+ - !ruby/object:Gem::Dependency
29
+ name: activesupport
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '4.2'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '4.2'
42
+ - !ruby/object:Gem::Dependency
43
+ name: data_mapper
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '1.2'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '1.2'
56
+ - !ruby/object:Gem::Dependency
57
+ name: dm-sqlite-adapter
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '1.2'
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '1.2'
70
+ - !ruby/object:Gem::Dependency
71
+ name: sqlite3
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '1.3'
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '1.3'
84
+ - !ruby/object:Gem::Dependency
85
+ name: bundler
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: '1.11'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '1.11'
98
+ - !ruby/object:Gem::Dependency
99
+ name: rake
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - "~>"
103
+ - !ruby/object:Gem::Version
104
+ version: '10.0'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - "~>"
110
+ - !ruby/object:Gem::Version
111
+ version: '10.0'
112
+ - !ruby/object:Gem::Dependency
113
+ name: rspec
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - "~>"
117
+ - !ruby/object:Gem::Version
118
+ version: '3.0'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - "~>"
124
+ - !ruby/object:Gem::Version
125
+ version: '3.0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: pry
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ type: :development
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ - !ruby/object:Gem::Dependency
141
+ name: ruby-prof
142
+ requirement: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ type: :development
148
+ prerelease: false
149
+ version_requirements: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ - !ruby/object:Gem::Dependency
155
+ name: pry-byebug
156
+ requirement: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ type: :development
162
+ prerelease: false
163
+ version_requirements: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - ">="
166
+ - !ruby/object:Gem::Version
167
+ version: '0'
168
+ - !ruby/object:Gem::Dependency
169
+ name: pry-stack_explorer
170
+ requirement: !ruby/object:Gem::Requirement
171
+ requirements:
172
+ - - ">="
173
+ - !ruby/object:Gem::Version
174
+ version: '0'
175
+ type: :development
176
+ prerelease: false
177
+ version_requirements: !ruby/object:Gem::Requirement
178
+ requirements:
179
+ - - ">="
180
+ - !ruby/object:Gem::Version
181
+ version: '0'
182
+ description: FIAS reader
183
+ email:
184
+ - asnow.dev@gmail.ru
185
+ - i.have@no.mail
186
+ executables: []
187
+ extensions: []
188
+ extra_rdoc_files: []
189
+ files:
190
+ - ".gitignore"
191
+ - ".rspec"
192
+ - ".travis.yml"
193
+ - Gemfile
194
+ - README.md
195
+ - Rakefile
196
+ - bin/console
197
+ - bin/setup
198
+ - fias_reader.gemspec
199
+ - lib/fias_reader.rb
200
+ - lib/fias_reader/cache.rb
201
+ - lib/fias_reader/cache/address_part.rb
202
+ - lib/fias_reader/cache/address_part/level_adapter.rb
203
+ - lib/fias_reader/cache/guid.rb
204
+ - lib/fias_reader/parse_logic.rb
205
+ - lib/fias_reader/parse_logic/abstract.rb
206
+ - lib/fias_reader/parse_logic/attributes.rb
207
+ - lib/fias_reader/parse_logic/attributes_select.rb
208
+ - lib/fias_reader/parse_logic/depth.rb
209
+ - lib/fias_reader/parse_logic/logger.rb
210
+ - lib/fias_reader/parse_logic/rows.rb
211
+ - lib/fias_reader/parse_logic/stub.rb
212
+ - lib/fias_reader/parser.rb
213
+ - lib/fias_reader/reader.rb
214
+ - lib/fias_reader/reader/row.rb
215
+ - lib/fias_reader/scope.rb
216
+ - lib/fias_reader/table.rb
217
+ - lib/fias_reader/table/address_part.rb
218
+ - lib/fias_reader/table/house.rb
219
+ - lib/fias_reader/version.rb
220
+ - lib/tasks/build.rake
221
+ homepage: http://www.thefurnish.ru/
222
+ licenses:
223
+ - MIT
224
+ metadata: {}
225
+ post_install_message:
226
+ rdoc_options: []
227
+ require_paths:
228
+ - lib
229
+ required_ruby_version: !ruby/object:Gem::Requirement
230
+ requirements:
231
+ - - ">="
232
+ - !ruby/object:Gem::Version
233
+ version: '0'
234
+ required_rubygems_version: !ruby/object:Gem::Requirement
235
+ requirements:
236
+ - - ">="
237
+ - !ruby/object:Gem::Version
238
+ version: '0'
239
+ requirements: []
240
+ rubyforge_project:
241
+ rubygems_version: 2.6.6
242
+ signing_key:
243
+ specification_version: 4
244
+ summary: FIAS reader
245
+ test_files: []