├── .gitignore
├── .travis.yml
├── LICENSE.txt
├── Makefile
├── README.rst
├── example.py
├── kb
├── __init__.py
└── nl
│ ├── __init__.py
│ ├── api
│ ├── __init__.py
│ ├── oai.py
│ └── sru.py
│ ├── collections
│ └── __init__.py
│ └── helpers
│ └── __init__.py
├── setup.py
└── test
├── alto_to_text_test.py
├── oai_test.py
├── sru_test.py
└── test_data
└── anp_1937-10-01_1_alto.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | kb.egg-info
3 | __pycache__
4 | build
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - "3.7"
4 | # command to install dependencies
5 | install:
6 | - "pip3 install ."
7 | # command to run tests
8 | script: make test
9 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | all: install
2 |
3 | doc:
4 | rst2html README.rst > README.html
5 |
6 | test: install testENV testOAI testSRU testALTOtoTXT
7 |
8 | testENV:
9 | python3 setup.py install
10 |
11 | testOAI:
12 | python3 ./test/oai_test.py
13 |
14 | testSRU:
15 | python3 ./test/sru_test.py
16 |
17 | testALTOtoTXT:
18 | python3 ./test/alto_to_text_test.py
19 |
20 | clean:
21 | rm -rf build
22 | rm -rf dist
23 | rm -rf kb.egg-info
24 | find . -name \*.pyc -exec rm '{}' ';'
25 | find . -name "__pycache__" -exec rmdir {} \;
26 | rm README.html
27 |
28 | install:
29 | python3 setup.py install
30 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | .. image:: https://upload.wikimedia.org/wikipedia/commons/thumb/1/17/Logo_Koninklijke_Bibliotheek_wordmark.svg/120px-Logo_Koninklijke_Bibliotheek_wordmark.svg.png
2 | :alt: Koninklijke Bibliotheek Logo
3 | :align: right
4 | :scale: 50 %
5 | :width: 100 px
6 | :height: 100 px
7 |
8 |
9 | .. _API: https://en.wikipedia.org/wiki/Application_programming_interface
10 | .. _DataServices: http://www.kb.nl/bronnen-zoekwijzers/dataservices-en-apis
11 | .. _Delpher: http://www.delpher.nl/
12 | .. _KB: http://www.kb.nl/en
13 | .. _OAI-MPH: http://www.openarchives.org/pmh/
14 | .. _Python: http://python.org/
15 | .. _SRU: http://www.loc.gov/standards/sru/
16 | .. _Travis: https://travis-ci.org/KBNLresearch/KB-python-API
17 | .. _CC-BY-NC-ND: https://creativecommons.org/licenses/by-nc-nd/2.0/
18 | .. _Pypi: https://pypi.python.org/pypi/kb/
19 | .. _Downloads: https://pepy.tech/project/kb
20 |
21 | =====================================================================
22 | KB python API: Access to National Library of the Netherlands datasets
23 | =====================================================================
24 | .. image:: https://pepy.tech/badge/kb
25 | :alt: Downloads
26 | :align: left
27 | .. image:: https://img.shields.io/pypi/v/kb?logo=kb
28 | :alt: Pypi version
29 | :align: left
30 | Pypi_ version
31 |
32 | .. image:: https://api.travis-ci.org/KBNLresearch/KB-python-API.svg
33 | :alt: build status
34 | :align: left
35 | Travis_ build status
36 |
37 | KB-Python-API is a simple API_ for Python_, the API provides easy access to free and CC-BY-NC-ND_ datasets provided by the National Library of the Netherlands (KB_).
38 |
39 | It relies on the back-end infrastructure of the KB_ which consists of an SRU_ and OAI-MPH_ service. The KB Python API makes it easy to interact with historical data,
40 | for more information on the provided datasets and data-rights take a look at the DataServices_ page of the KB.
41 |
42 | For example usage have a look at the provided example.py file, or consult the /test directory.
43 |
44 | This package is also available from the pypi_ website.
45 | To do a quick install:
46 |
47 | .. code-block:: python
48 | pip install kb
49 |
50 |
51 | OAI example
52 | ===========
53 | .. code-block:: python
54 |
55 | >>> from kb.nl.api import oai
56 | >>> from kb.nl.helpers import alto_to_text
57 | >>> oai.list_sets()
58 | ['ANP', 'BYVANCK', 'DPO', 'SGD']
59 | >>> records = oai.list_records("ANP")
60 | >>> records.identifiers[:3]
61 | ['anp:1937:10:01:1', 'anp:1937:10:01:2', 'anp:1937:10:01:3']
62 | >>> len(oai.resumptiontoken)
63 | 42
64 | >>> record = oai.get(records.identifiers[0])
65 | >>> alto_record = record.alto
66 | >>> alto_to_text(alto_record[0]).split("\\n")[1][:27]
67 | u' RADIO 1 van 1 Ootober 1937'
68 | >> image_record = record.image
69 | >>> len(image_record)
70 | 721035
71 |
72 | SRU example
73 | ===========
74 | .. code-block:: python
75 |
76 | >>> from kb.nl.api import sru
77 | >>> from kb.nl.helpers import alto_to_text
78 | >>> response = sru.search("Beatrix AND Juliana AND Bernhard AND telegram", "ANP")
79 | >>> for record in response.records:
80 | ... print("Date: %s" % record.date)
81 | ... print("Abstract: %s" % record.abstract)
82 | ... print("Title: %s" % record.title)
83 |
--------------------------------------------------------------------------------
/example.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import os
4 | import sys
5 |
6 | from kb.nl.api import sru
7 | from kb.nl.api import oai
8 | from kb.nl.helpers import alto_to_text
9 |
10 | oai_handler = oai
11 | oai_handler.current_set = "ANP"
12 | oai_handler.set_key("")
13 |
14 | response = sru.search("karel AND reve AND hooftprijs", "ANP")
15 | print ("Number of records: %i" % response.sru.nr_of_records)
16 |
17 | record_nr = 0
18 |
19 | for record in response.records:
20 | record_nr += 1
21 | print("********~ Record number %i ~*********" % record_nr)
22 | print("Date: %s" % record.dates)
23 | print("RecordIdentifier: %s" % record.identifiers)
24 | print("Abstract: %s" % record.abstracts)
25 | print("Title: %s" % record.titles)
26 | oai_handler.DEBUG = True
27 | r = oai_handler.get(record.identifiers[0])
28 | for alto in r.alto:
29 | print("Fulltext: %s" % alto_to_text(alto))
30 |
--------------------------------------------------------------------------------
/kb/__init__.py:
--------------------------------------------------------------------------------
1 | __version__ = '0.1.5'
2 | __all__ = ['nl']
3 |
4 |
--------------------------------------------------------------------------------
/kb/nl/__init__.py:
--------------------------------------------------------------------------------
1 | __version__ = '0.1.5'
2 | __all__ = ['api', 'collections', 'helpers']
3 |
--------------------------------------------------------------------------------
/kb/nl/api/__init__.py:
--------------------------------------------------------------------------------
1 | __version__ = '0.1.5'
2 |
3 | __all__ = ['oai', 'sru']
4 | __author__ = 'WillemJan Faber -1]
176 | return ids
177 |
178 | @property
179 | def deleted_identifiers(self):
180 | """
181 | Return al list of deleted record Identifiers.
182 | """
183 | deleted = []
184 | for item in self.records_data[2]:
185 | if item.tag.endswith('record'):
186 | if item[0].tag.endswith('header') and \
187 | item[0].attrib.get('status') == 'deleted':
188 | deleted.append(item[0][0].text)
189 | return deleted
190 |
191 |
192 | class record():
193 |
194 | """
195 | Class for parsing XML output from OAI server,
196 | to more human readable form. This class
197 | is a generic record level object.
198 |
199 | A record has a bunch of properties, most of them
200 | are exposed, for example:
201 |
202 | >>> record.ocr # Returns the ocr for a record.
203 |
204 | >>> record.alto # Returns the alto files for a record.
205 |
206 | >>> record.image # Returns the image for a record.
207 | # Mostly .jpg's
208 | """
209 | record_data = False
210 | DEBUG = False
211 | current_set = False
212 |
213 | def __init__(self, record_data, current_set=False, debug=False):
214 | """
215 | :param record_data: XML object of the wanted record.
216 | :param debug: enable or disable debuging
217 | """
218 | self.record_data = record_data
219 | self.current_set = current_set
220 |
221 | if debug:
222 | self.DEBUG = debug
223 |
224 | @property
225 | def alto(self):
226 | """
227 | Return the ALTO(s) for the current record.
228 | For more information on ALTO see:
229 |
230 | https://en.wikipedia.org/wiki/ALTO_(XML)
231 |
232 | For parsing alto data back to text, use:
233 |
234 | >>> from kb.nl.helpers import alto_to_text
235 | >>> alto_to_text(alto)
236 | """
237 | if self.current_set == "BYVANCK":
238 | return False
239 |
240 | alto_url_list = []
241 | alto_url = False
242 |
243 | for item in self.record_data.iter():
244 | if item.attrib and \
245 | item.attrib.get('ref') and \
246 | item.attrib['ref'].lower().endswith(':alto'):
247 |
248 | alto_url = item.attrib['ref']
249 |
250 | if alto_url not in alto_url_list:
251 | alto_url_list.append(alto_url)
252 |
253 | if not alto_url:
254 | for item in self.record_data.iter():
255 | if item.attrib and \
256 | item.attrib.get('ref') and \
257 | item.attrib['ref'].lower().endswith('.xml'):
258 |
259 | alto_url = item.attrib['ref']
260 | alto_url_list.append(alto_url)
261 | break
262 |
263 | # The result is either one ALTO file,
264 | # or a bunch of them, if more then one,
265 | # fetch the results and return a list.
266 | if len(alto_url_list) == 0:
267 | return False
268 |
269 | elif len(alto_url_list) <= 1:
270 | alto_url = alto_url_list[0]
271 |
272 | if self.DEBUG:
273 | sys.stdout.write(alto_url)
274 |
275 | response = requests.get(alto_url)
276 |
277 | if not response.status_code == 200:
278 | raise Exception('Error while getting data from %s' % alto_url)
279 |
280 | return [response.text]
281 | else:
282 | alto_list = []
283 |
284 | for alto_url in alto_url_list:
285 | response = requests.get(alto_url)
286 |
287 | if self.DEBUG:
288 | sys.stdout.write(alto_url + " ")
289 |
290 | if not response.status_code == 200:
291 | raise Exception(
292 | 'Error while getting data from %s' % alto_url)
293 |
294 | alto_list.append(response.text)
295 |
296 | return alto_list
297 |
298 | @property
299 | def ocr(self):
300 | """
301 | Return the OCR for the current record.
302 |
303 | The OCR was produced with Abby-Finereader.
304 | """
305 | for item in self.record_data.iter():
306 | if item.attrib and \
307 | item.attrib.get('ref') and \
308 | item.attrib['ref'].lower().endswith(':ocr'):
309 | url = item.attrib['ref']
310 | if self.DEBUG:
311 | sys.stdout.write(url)
312 |
313 | response = requests.get(url)
314 | if not response.status_code == 200:
315 | return False
316 | return response.text
317 | return False
318 |
319 | @property
320 | def image(self):
321 | """
322 | Retrieve the image for the current record,
323 | and return the bits.
324 | """
325 | img_url = False
326 |
327 | if self.current_set == "BYVANCK":
328 | img_url = 'http://imageviewer.kb.nl/'
329 | img_url += 'ImagingService/imagingService?id='
330 | img_url += self.record_data[2][0][1][0][1].text.split('=')[1]
331 | else:
332 | for item in self.record_data.iter():
333 | if item.attrib and \
334 | item.attrib.get('ref') and \
335 | item.attrib.get('ref').startswith('http://') and \
336 | (item.attrib['ref'].endswith(':image') or
337 | item.attrib['ref'].endswith('.jpg')):
338 |
339 | img_url = item.attrib['ref']
340 | break
341 |
342 | if not img_url:
343 | return False
344 |
345 | if self.DEBUG:
346 | sys.stdout.write(img_url)
347 |
348 | response = requests.get(img_url)
349 |
350 | if not response.status_code == 200:
351 | raise Exception('Error while getting data from %s' % img_url)
352 |
353 | return response.content
354 |
355 | @property
356 | def title(self):
357 | """
358 | Get corresponding title.
359 | """
360 | for item in self.record_data.iter():
361 | if item.tag.endswith('title'):
362 | return item.text
363 | return False
364 |
365 | @property
366 | def annotation(self):
367 | """
368 | Get corresponding annotation.
369 | """
370 | for item in self.record_data.iter():
371 | if item.tag.endswith('annotation'):
372 | return item.text
373 | return False
374 |
375 | @property
376 | def date(self):
377 | """
378 | Get timeperiod of creation.
379 | """
380 | for item in self.record_data.iter():
381 | if item.tag.endswith('date'):
382 | return item.text
383 | return False
384 |
385 | @property
386 | def creator(self):
387 | """
388 | Get corresponding creator.
389 | """
390 | for item in self.record_data.iter():
391 | if item.tag.endswith('creator'):
392 | return item.text
393 | return False
394 |
395 | @property
396 | def contributor(self):
397 | """
398 | Get contributors to this record.
399 | """
400 | for item in self.record_data.iter():
401 | if item.tag.endswith('contributor'):
402 | return item.text
403 | return False
404 |
405 | @property
406 | def publisher(self):
407 | """
408 | Get publisher information for this record.
409 | """
410 | for item in self.record_data.iter():
411 | if item.tag.endswith('publisher'):
412 | return item.text
413 | return False
414 |
--------------------------------------------------------------------------------
/kb/nl/api/sru.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import requests
3 | import urllib
4 | import pprint
5 |
6 | try:
7 | from urllib import quote # Python 2.X
8 | except ImportError:
9 | from urllib.parse import quote # Python 3+
10 |
11 | from kb.nl.collections import SETS
12 | from kb.nl.helpers import etree
13 |
14 | SRU_BASEURL = 'https://jsru.kb.nl/sru/sru'
15 | SRU_BASEURL += '?version=1.2&maximumRecords=%i'
16 | SRU_BASEURL += '&operation=searchRetrieve'
17 | SRU_BASEURL += '&startRecord=%i'
18 | SRU_BASEURL += '&recordSchema=%s'
19 | SRU_BASEURL += '&x-collection=%s&query=%s'
20 |
21 |
22 | class response():
23 | def __init__(self, record_data, sru):
24 | self.record_data = record_data
25 | self.sru = sru
26 |
27 | @property
28 | def records(self):
29 | if self.sru.nr_of_records == 0:
30 | record_data = ""
31 | else:
32 | ns = {'zs': 'http://www.loc.gov/zing/srw/'}
33 | record_data = self.record_data.xpath("zs:records/zs:record",
34 | namespaces=ns)[0]
35 | return(record(record_data, self.sru))
36 |
37 | # TODO: distinguish by xsi:type
38 | @property
39 | def identifiers(self):
40 | baseurl = 'https://resolver.kb.nl/resolve?urn='
41 | result = [r.text.replace(baseurl, '') for r in self.record_data.iter() if
42 | r.tag.endswith('identifier') and r.text.find(':') > -1]
43 | return result
44 |
45 | @property
46 | def types(self):
47 | return [r.text for r in self.record_data.iter() if
48 | r.tag.endswith('type')]
49 |
50 | @property
51 | def languages(self):
52 | return [r.text for r in self.record_data.iter() if
53 | r.tag.endswith('language')]
54 |
55 | @property
56 | def dates(self):
57 | return [r.text for r in self.record_data.iter() if
58 | r.tag.endswith('date')]
59 |
60 | @property
61 | def extents(self):
62 | return [r.text for r in self.record_data.iter() if
63 | r.tag.endswith('extent')]
64 |
65 | @property
66 | def creators(self):
67 | return [r.text for r in self.record_data.iter() if
68 | r.tag.endswith('creator')]
69 |
70 | @property
71 | def contributors(self):
72 | return [r.text for r in self.record_data.iter() if
73 | r.tag.endswith('contributor')]
74 |
75 | # TODO: distinguish by xsi:type and xml:lang
76 | @property
77 | def subjects(self):
78 | return [r.text for r in self.record_data.iter() if
79 | r.tag.endswith('subject')]
80 |
81 | @property
82 | def abstracts(self):
83 | return [r.text for r in self.record_data.iter() if
84 | r.tag.endswith('abstract')]
85 |
86 | @property
87 | def titles(self):
88 | return [r.text for r in self.record_data.iter() if
89 | r.tag.endswith('title')]
90 |
91 | @property
92 | def publishers(self):
93 | return [r.text for r in self.record_data.iter() if
94 | r.tag.endswith('publisher')]
95 |
96 | # Following properties occur in GGC
97 |
98 | @property
99 | def annotations(self):
100 | return [r.text for r in self.record_data.iter() if
101 | r.tag.endswith('annotation')]
102 |
103 |
104 | class record():
105 | def __init__(self, record_data, sru):
106 | self.record_data = record_data
107 | self.sru = sru
108 |
109 | def __iter__(self):
110 | return self
111 |
112 | def __next__(self):
113 | if self.sru.nr_of_records == 0:
114 | raise StopIteration
115 | if self.sru.startrecord < self.sru.nr_of_records + 1:
116 | record_data = self.sru.run_query()
117 | self.sru.startrecord += 1
118 | return response(record_data, self.sru)
119 | else:
120 | raise StopIteration
121 |
122 | def next(self):
123 | return self.__next__()
124 |
125 |
126 | class sru():
127 | DEBUG = True
128 |
129 | collection = False
130 | maximumrecords = 50
131 | nr_of_records = 0
132 | query = ""
133 | recordschema = False
134 | sru_collections = SETS
135 | startrecord = 0
136 |
137 | def search(self, query, collection=False,
138 | startrecord=1, maximumrecords=1, recordschema=False):
139 |
140 | self.maximumrecords = maximumrecords
141 | self.query = quote(query)
142 | self.startrecord = startrecord
143 |
144 | if collection not in self.sru_collections:
145 | raise Exception('Unknown collection')
146 |
147 | self.collection = self.sru_collections[collection]['collection']
148 |
149 | if not self.collection:
150 | raise Exception('Error, no collection specified')
151 |
152 | if not recordschema:
153 | self.recordschema = self.sru_collections[collection]['recordschema']
154 | else:
155 | self.recordschema = recordschema
156 |
157 | record_data = self.run_query()
158 |
159 | nr_of_records = [i.text for i in record_data.iter() if
160 | i.tag.endswith('numberOfRecords')][0]
161 |
162 | self.nr_of_records = int(nr_of_records)
163 |
164 | if self.nr_of_records > 0:
165 | return response(record_data, self)
166 |
167 | return False
168 |
169 | def run_query(self):
170 | url = SRU_BASEURL % (self.maximumrecords, self.startrecord,
171 | self.recordschema, self.collection, self.query)
172 | if self.DEBUG:
173 | print("run_query: %s" % url)
174 |
175 | r = requests.get(url)
176 |
177 | if not r.status_code == 200:
178 | raise Exception('Error while getting data from %s' % url)
179 |
180 | record_data = etree.fromstring(r.content)
181 |
182 | return record_data
183 |
--------------------------------------------------------------------------------
/kb/nl/collections/__init__.py:
--------------------------------------------------------------------------------
1 | SETS = {'ANP': {'collection': 'ANP',
2 | 'description_en': 'Radio Bulletins ANP Press Agency',
3 | 'description_nl': 'ANP Radiobulletins Digitaal',
4 | 'metadataPrefix': 'didl',
5 | 'recordschema': 'dcx',
6 | 'setname': 'anp',
7 | 'time_period': [1937, 1989]},
8 | 'DPO': {'collection': 'DPO_boekdeel',
9 | 'description_en': 'Early Dutch Books Online',
10 | 'description_nl': 'Early Dutch Books Online',
11 | 'metadataPrefix': 'didl',
12 | 'recordschema': 'ddd',
13 | 'setname': 'DPO',
14 | 'time_period': [1781, 1800]},
15 | 'BYVANCK': {'collection': 'MISC',
16 | 'description_en': 'Medieval Illuminated Manuscripts',
17 | 'description_nl': 'Middeleeuwse Verluchte Handschriften',
18 | 'metadataPrefix': 'dcx',
19 | 'recordschema': 'dcx',
20 | 'setname': 'BYVANCK',
21 | 'time_period': [500, 1500],
22 | 'extra_query' : '(dcterms:isPartOf="ByvanckB" OR dcterms:isPartOf="BYVANCK")'},
23 | 'SGD': {'collection': 'SGD',
24 | 'description_en': 'States General Digital',
25 | 'description_nl': 'Staten-Generaal Digitaal',
26 | 'metadataPrefix': 'dcx',
27 | 'recordschema': 'dcx',
28 | 'setname': 'sgd:register',
29 | 'time_period': [1962, 1994]},
30 | 'GGC': {'collection': 'GGC',
31 | 'description_en': 'General Catalogue KB',
32 | 'description_nl': 'Algemene Catalogus KB',
33 | 'metadataPrefix': 'dcx',
34 | 'recordschema': 'dcx',
35 | 'setname': 'ggc',
36 | 'time_period': [1937, 2016]}
37 | }
38 |
--------------------------------------------------------------------------------
/kb/nl/helpers/__init__.py:
--------------------------------------------------------------------------------
1 | try:
2 | from lxml import etree
3 | except ImportError:
4 | try:
5 | # Python 2.5
6 | import xml.etree.cElementTree as etree
7 | except ImportError:
8 | try:
9 | # Python 2.5
10 | import xml.etree.ElementTree as etree
11 | except ImportError:
12 | try:
13 | # normal cElementTree install
14 | import cElementTree as etree
15 | except ImportError:
16 | try:
17 | # normal ElementTree install
18 | import elementtree.ElementTree as etree
19 | except ImportError:
20 | raise("Failed to import ElementTree from any known place")
21 | import codecs
22 |
23 | def alto_to_text(alto_data):
24 | ''' Grab the selected text blocks and write them to disk '''
25 | try:
26 | # some files have a BOM available which does not get stripped on windows
27 | if not alto_data[0] == '<':
28 | alto_data = alto_data[3:]
29 | alto_data = etree.fromstring(alto_data.encode('utf-8'))
30 |
31 | except etree.XMLSyntaxError as e:
32 | print ("error! %s" % alto_str[:40])
33 | return None
34 |
35 | alto_text = u""
36 | prev_was_hyp = False
37 |
38 | for item in alto_data.iter():
39 | if item.tag.endswith("String"):
40 | if prev_was_hyp:
41 | alto_text += item.get("CONTENT")
42 | prev_was_hyp = False
43 | else:
44 | alto_text += u" " + item.get("CONTENT")
45 |
46 | if item.tag.endswith("HYP"):
47 | prev_was_hyp = True
48 |
49 | if item.tag.endswith("TextBlock"):
50 | if len(alto_text) > 0:
51 | alto_text += u"\n"
52 |
53 | return alto_text
54 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 |
3 | setup(
4 | author="WillemJan Faber",
5 | author_email="WillemJan.faber@kb.nl",
6 | name='kb',
7 | url="https://github.com/KBNLresearch/KB-python-API",
8 | description='Access to National Library of the Netherlands datasets',
9 | version='0.1.9',
10 | packages=['kb.nl.api', 'kb.nl.helpers', 'kb', 'kb.nl', 'kb.nl.collections'],
11 | license='GNU General Public License',
12 | install_requires=['lxml>=2.3', 'requests'],
13 | classifiers=['Development Status :: 3 - Alpha',
14 | 'Intended Audience :: Developers',
15 | 'Intended Audience :: Science/Research',
16 | 'Programming Language :: Python :: 3',
17 | 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
18 | 'Natural Language :: Dutch',
19 | 'Topic :: Sociology :: History']
20 | )
21 |
--------------------------------------------------------------------------------
/test/alto_to_text_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | import os
5 | import sys
6 |
7 | sys.path.append('.')
8 | sys.path.append('..' + os.sep)
9 |
10 |
11 | def alto_to_text_test():
12 | """
13 | >>> import os
14 | >>> import codecs
15 | >>> from kb.nl.helpers import alto_to_text
16 | >>> path = os.path.dirname(os.path.abspath(__file__)) + os.sep
17 | >>> path += "test_data" + os.sep + "anp_1937-10-01_1_alto.xml"
18 | >>> fh = codecs.open(path, "r", encoding="utf-8")
19 | >>> data = fh.read()
20 | >>> fh.close()
21 | >>> alto_to_text(data).split('\\n')[2][:45]
22 | ' De Rijksradiocontroledienst heeft te UTRECHT'
23 | """
24 |
25 | if __name__ == "__main__":
26 | import doctest
27 | doctest.testmod()
28 |
--------------------------------------------------------------------------------
/test/oai_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import os
4 | import sys
5 |
6 | sys.path.append('..' + os.sep)
7 | sys.path.append('.' + os.sep)
8 |
9 | def oai_anp():
10 | """
11 | >>> from kb.nl.api import oai
12 | >>> from kb.nl.helpers import alto_to_text
13 | >>> oai.DEBUG=True
14 | >>> sorted(oai.list_sets())
15 | ['ANP', 'BYVANCK', 'DPO', 'GGC', 'SGD']
16 | >>> records = oai.list_records("ANP")
17 | http://services.kb.nl/mdo/oai?verb=ListRecords&metadataPrefix=didl&set=anp
18 | >>> records.identifiers[:3]
19 | ['anp:1937:10:01:1', 'anp:1937:10:01:2', 'anp:1937:10:01:3']
20 | >>> len(oai.resumptiontoken)
21 | 42
22 | >>> record = oai.get(records.identifiers[0])
23 | http://services.kb.nl/mdo/oai?verb=GetRecord&identifier=anp:anp:1937:10:01:1:mpeg21&metadataPrefix=didl
24 | >>> alto_records = record.alto
25 | http://resolver.kb.nl/resolve?urn=anp:1937:10:01:1:mpeg21:alto
26 | >>> len(alto_records[0])
27 | 90124
28 | >>> alto_to_text(alto_records[0]).split("\\n")[1][:27]
29 | ' RADIO 1 van 1 Ootober 1937'
30 | >>> image_record = record.image
31 | http://resolver.kb.nl/resolve?urn=anp:1937:10:01:1:mpeg21:image
32 | >>> len(image_record)
33 | 721035
34 | >>> record.title
35 | 'ANP Nieuwsbericht - 01-10-1937 - 1'
36 | >>> record.date
37 | '1937-10-01'
38 | """
39 |
40 | def oai_sgd():
41 | """
42 | >>> from kb.nl.api import oai
43 | >>> oai.DEBUG=True
44 | >>> records = oai.list_records("SGD")
45 | http://services.kb.nl/mdo/oai?verb=ListRecords&metadataPrefix=dcx&set=sgd:register
46 | >>> records.identifiers[:1]
47 | ['sgd:register:mpeg21:1967:0001424:L0000:lemma']
48 | >>> len(oai.resumptiontoken)
49 | 50
50 | >>> record = oai.get(records.identifiers[0])
51 | http://services.kb.nl/mdo/oai?verb=GetRecord&identifier=SGD:sgd:mpeg21:1967:0001424&metadataPrefix=didl
52 | >>> ocr_data = record.ocr # SGD does not have real alto files..
53 | http://resolver.kb.nl/resolve?urn=sgd:1967:0003969:ocr
54 | >>> len(ocr_data)
55 | 4777
56 | """
57 |
58 |
59 | def oai_dpo():
60 | """
61 | >>> from kb.nl.api import oai
62 | >>> from kb.nl.helpers import alto_to_text
63 | >>> oai.DEBUG=True
64 | >>> records = oai.list_records("DPO")
65 | http://services.kb.nl/mdo/oai?verb=ListRecords&metadataPrefix=didl&set=DPO
66 | >>> records.identifiers[:3]
67 | ['dpo:10221:mpeg21', 'dpo:10863:mpeg21', 'dpo:10864:mpeg21']
68 | >>> len(oai.resumptiontoken)
69 | 42
70 | >>> records = oai.list_records("DPO") # doctest: +ELLIPSIS
71 | http://services.kb.nl/mdo/oai?verb=ListRecords&metadataPrefix=didl&set=DPO&resumptionToken...
72 | >>> records.identifiers[:3]
73 | ['dpo:10012:mpeg21', 'dpo:10014:mpeg21', 'dpo:10015:mpeg21']
74 | >>> record = oai.get(records.identifiers[0])
75 | http://services.kb.nl/mdo/oai?verb=GetRecord&identifier=DPO:dpo:10012:mpeg21&metadataPrefix=didl
76 | >>> alto_records = record.alto # doctest: +ELLIPSIS
77 | http://resolver.kb.nl/resolve?urn=dpo:10012:mpeg21:0001:alto http://...
78 | >>> len(alto_records[0])
79 | 1739
80 | >>> record.title[:20]
81 | 'De minagting der her'
82 | >>> record.contributor
83 | False
84 | >>> record.publisher
85 | False
86 | """
87 |
88 |
89 | def oai_byvanck():
90 | """
91 | >>> from kb.nl.api import oai
92 | >>> oai.DEBUG=True
93 | >>> records = oai.list_records("BYVANCK")
94 | http://services.kb.nl/mdo/oai?verb=ListRecords&metadataPrefix=dcx&set=BYVANCK
95 | >>> records.deleted_identifiers[:2]
96 | ['BYVANCK:BYVANCK:5562', 'BYVANCK:BYVANCK:5567']
97 | >>> records.identifiers
98 | ['BYVANCK:3477']
99 | >>> record = oai.get(records.identifiers[0])
100 | http://services.kb.nl/mdo/oai?verb=GetRecord&identifier=ByvanckB:ByvanckB:3477&metadataPrefix=dcx
101 | >>> record.alto
102 | False
103 | >>> image = record.image
104 | http://imageviewer.kb.nl/ImagingService/imagingService?id=BYVANCKB:mimi_78d38:dl1_183r_min
105 | >>> len(image)
106 | 109800
107 | """
108 |
109 | if __name__ == "__main__":
110 | import doctest
111 | doctest.testmod()
112 |
--------------------------------------------------------------------------------
/test/sru_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | import os
5 | import sys
6 |
7 | sys.path.append('..' + os.sep)
8 | sys.path.append('.' + os.sep)
9 |
10 | def oai_anp():
11 | """
12 | >>> from kb.nl.api import sru
13 | >>> from kb.nl.helpers import alto_to_text
14 | >>> sru.DEBUG = True
15 | >>> response = sru.search("beatrix AND juliana AND Bernhard AND gelukwensen", "ANP")
16 | run_query: https://jsru.kb.nl/sru/sru?version=1.2&maximumRecords=1&operation=searchRetrieve&startRecord=1&recordSchema=dcx&x-collection=ANP&query=beatrix%20AND%20juliana%20AND%20Bernhard%20AND%20gelukwensen
17 | >>> for record in response.records:
18 | ... print(record.dates)
19 | run_query: https://jsru.kb.nl/sru/sru?version=1.2&maximumRecords=1&operation=searchRetrieve&startRecord=1&recordSchema=dcx&x-collection=ANP&query=beatrix%20AND%20juliana%20AND%20Bernhard%20AND%20gelukwensen
20 | ['1968/09/25 00:00:00']
21 | run_query: https://jsru.kb.nl/sru/sru?version=1.2&maximumRecords=1&operation=searchRetrieve&startRecord=2&recordSchema=dcx&x-collection=ANP&query=beatrix%20AND%20juliana%20AND%20Bernhard%20AND%20gelukwensen
22 | ['1967/04/28 00:00:00']
23 | run_query: https://jsru.kb.nl/sru/sru?version=1.2&maximumRecords=1&operation=searchRetrieve&startRecord=3&recordSchema=dcx&x-collection=ANP&query=beatrix%20AND%20juliana%20AND%20Bernhard%20AND%20gelukwensen
24 | ['1966/02/21 00:00:00']
25 | run_query: https://jsru.kb.nl/sru/sru?version=1.2&maximumRecords=1&operation=searchRetrieve&startRecord=4&recordSchema=dcx&x-collection=ANP&query=beatrix%20AND%20juliana%20AND%20Bernhard%20AND%20gelukwensen
26 | ['1966/03/10 00:00:00']
27 | run_query: https://jsru.kb.nl/sru/sru?version=1.2&maximumRecords=1&operation=searchRetrieve&startRecord=5&recordSchema=dcx&x-collection=ANP&query=beatrix%20AND%20juliana%20AND%20Bernhard%20AND%20gelukwensen
28 | ['1966/03/09 00:00:00']
29 | run_query: https://jsru.kb.nl/sru/sru?version=1.2&maximumRecords=1&operation=searchRetrieve&startRecord=6&recordSchema=dcx&x-collection=ANP&query=beatrix%20AND%20juliana%20AND%20Bernhard%20AND%20gelukwensen
30 | ['1969/10/11 00:00:00']
31 | run_query: https://jsru.kb.nl/sru/sru?version=1.2&maximumRecords=1&operation=searchRetrieve&startRecord=7&recordSchema=dcx&x-collection=ANP&query=beatrix%20AND%20juliana%20AND%20Bernhard%20AND%20gelukwensen
32 | ['1965/06/29 00:00:00']
33 | >>> sru.DEBUG = False
34 | >>> response = sru.search('"J.A. Deelder" AND "gevallen"', "GGC")
35 | >>> for i, record in enumerate(sorted(response.records)):
36 | ... print(record.titles, record.dates)
37 | ... break
38 | ['Zij die vielen zijn gevallen'] ['[ca. 1990]']
39 | """
40 |
41 |
42 | if __name__ == "__main__":
43 | import doctest
44 | doctest.testmod()
45 |
--------------------------------------------------------------------------------
/test/test_data/anp_1937-10-01_1_alto.xml:
--------------------------------------------------------------------------------
1 |
2 | pixel
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
507 |
508 |
509 |
510 |
511 |
512 |
513 |
514 |
515 |
516 |
517 |
518 |
519 |
520 |
521 |
522 |
523 |
524 |
525 |
526 |
527 |
528 |
529 |
530 |
531 |
532 |
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
542 |
543 |
544 |
545 |
546 |
547 |
548 |
549 |
550 |
551 |
552 |
553 |
554 |
555 |
556 |
557 |
558 |
559 |
560 |
561 |
562 |
563 |
564 |
565 |
566 |
567 |
568 |
569 |
570 |
571 |
572 |
573 |
574 |
575 |
576 |
577 |
578 |
579 |
580 |
581 |
582 |
583 |
584 |
585 |
586 |
587 |
588 |
589 |
590 |
591 |
592 |
593 |
594 |
595 |
596 |
597 |
598 |
599 |
600 |
601 |
602 |
603 |
604 |
605 |
606 |
607 |
608 |
609 |
610 |
611 |
612 |
613 |
614 |
615 |
616 |
617 |
618 |
619 |
620 |
621 |
622 |
623 |
624 |
625 |
626 |
627 |
628 |
629 |
630 |
631 |
632 |
633 |
634 |
635 |
636 |
637 |
638 |
639 |
640 |
641 |
642 |
643 |
644 |
645 |
646 |
647 |
648 |
649 |
650 |
651 |
652 |
653 |
654 |
655 |
656 |
657 |
658 |
659 |
660 |
661 |
662 |
663 |
664 |
665 |
666 |
667 |
668 |
669 |
670 |
671 |
672 |
673 |
674 |
675 |
676 |
677 |
678 |
679 |
680 |
681 |
682 |
683 |
684 |
685 |
686 |
687 |
688 |
689 |
690 |
691 |
692 |
693 |
694 |
695 |
696 |
697 |
698 |
699 |
700 |
701 |
702 |
703 |
704 |
705 |
706 |
707 |
708 |
709 |
710 |
711 |
712 |
713 |
714 |
715 |
716 |
717 |
718 |
719 |
720 |
721 |
722 |
723 |
724 |
725 |
726 |
727 |
728 |
729 |
730 |
731 |
732 |
733 |
734 |
735 |
736 |
737 |
738 |
739 |
740 |
741 |
742 |
743 |
744 |
745 |
746 |
747 |
748 |
749 |
750 |
751 |
752 |
753 |
754 |
755 |
756 |
757 |
758 |
759 |
760 |
761 |
762 |
763 |
764 |
765 |
766 |
767 |
768 |
769 |
770 |
771 |
772 |
773 |
774 |
775 |
776 |
777 |
778 |
779 |
780 |
781 |
782 |
783 |
784 |
785 |
786 |
787 |
788 |
789 |
790 |
791 |
792 |
793 |
794 |
795 |
796 |
797 |
798 |
799 |
800 |
801 |
802 |
803 |
804 |
805 |
806 |
807 |
808 |
809 |
810 |
811 |
812 |
813 |
814 |
815 |
816 |
817 |
818 |
819 |
820 |
821 |
822 |
823 |
824 |
825 |
826 |
827 |
828 |
829 |
830 |
831 |
832 |
833 |
834 |
835 |
836 |
837 |
838 |
839 |
840 |
841 |
842 |
843 |
844 |
845 |
846 |
847 |
848 |
849 |
850 |
851 |
852 |
853 |
854 |
855 |
856 |
857 |
858 |
859 |
860 |
861 |
862 |
863 |
864 |
865 |
866 |
867 |
868 |
869 |
870 |
871 |
872 |
873 |
874 |
875 |
876 |
877 |
878 |
879 |
880 |
881 |
882 |
883 |
884 |
885 |
886 |
887 |
888 |
889 |
890 |
891 |
892 |
893 |
894 |
895 |
896 |
897 |
898 |
899 |
900 |
901 |
902 |
903 |
904 |
905 |
906 |
907 |
908 |
909 |
910 |
911 |
912 |
913 |
914 |
915 |
916 |
917 |
918 |
919 |
920 |
921 |
922 |
923 |
924 |
925 |
926 |
927 |
928 |
929 |
930 |
931 |
932 |
933 |
934 |
935 |
936 |
937 |
938 |
939 |
940 |
941 |
942 |
943 |
944 |
945 |
946 |
947 |
948 |
949 |
950 |
951 |
952 |
953 |
954 |
955 |
956 |
957 |
958 |
959 |
960 |
961 |
962 |
963 |
964 |
965 |
966 |
967 |
968 |
969 |
970 |
971 |
972 |
973 |
974 |
975 |
976 |
977 |
978 |
979 |
980 |
981 |
982 |
983 |
984 |
985 |
986 |
987 |
988 |
989 |
990 |
991 |
992 |
993 |
994 |
995 |
996 |
997 |
998 |
999 |
1000 |
1001 |
1002 |
1003 |
1004 |
1005 |
1006 |
1007 |
1008 |
1009 |
1010 |
1011 |
1012 |
1013 |
1014 |
1015 |
1016 |
1017 |
1018 |
1019 |
1020 |
1021 |
1022 |
1023 |
1024 |
1025 |
1026 |
1027 |
1028 |
1029 |
1030 |
1031 |
1032 |
1033 |
1034 |
1035 |
1036 |
1037 |
1038 |
1039 |
1040 |
1041 |
1042 |
1043 |
1044 |
1045 |
1046 |
1047 |
1048 |
1049 |
1050 |
1051 |
1052 |
1053 |
1054 |
1055 |
1056 |
1057 |
1058 |
1059 |
1060 |
1061 |
1062 |
1063 |
1064 |
1065 |
1066 |
1067 |
1068 |
1069 |
1070 |
1071 |
1072 |
1073 |
1074 |
1075 |
1076 |
1077 |
1078 |
1079 |
1080 |
1081 |
1082 |
1083 |
1084 |
1085 |
1086 |
1087 |
1088 |
1089 |
1090 |
1091 |
1092 |
1093 |
1094 |
1095 |
1096 |
1097 |
1098 |
1099 |
1100 |
1101 |
1102 |
1103 |
1104 |
1105 |
1106 |
1107 |
1108 |
1109 |
1110 |
1111 |
1112 |
1113 |
1114 |
1115 |
1116 |
1117 |
1118 |
1119 |
1120 |
1121 |
1122 |
1123 |
1124 |
1125 |
1126 |
1127 |
1128 |
1129 |
1130 |
1131 |
1132 |
1133 |
1134 |
1135 |
1136 |
1137 |
1138 |
1139 |
1140 |
1141 |
1142 |
1143 |
1144 |
1145 |
1146 |
1147 |
1148 |
1149 |
1150 |
1151 |
1152 |
1153 |
1154 |
1155 |
1156 |
1157 |
1158 |
1159 |
1160 |
1161 |
1162 |
1163 |
1164 |
1165 |
1166 |
1167 |
1168 |
1169 |
1170 |
1171 |
1172 |
1173 |
1174 |
1175 |
1176 |
1177 |
1178 |
1179 |
1180 |
1181 |
1182 |
1183 |
1184 |
1185 |
1186 |
1187 |
1188 |
1189 |
1190 |
1191 |
1192 |
1193 |
1194 |
1195 |
1196 |
1197 |
1198 |
1199 |
1200 |
1201 |
1202 |
1203 |
1204 |
1205 |
1206 |
1207 |
1208 |
1209 |
1210 |
1211 |
1212 |
1213 |
1214 |
1215 |
1216 |
1217 |
1218 |
1219 |
1220 |
1221 |
1222 |
1223 |
1224 |
1225 |
1226 |
1227 |
1228 |
1229 |
1230 |
1231 |
1232 |
1233 |
1234 |
1235 |
1236 |
1237 |
1238 |
1239 |
1240 |
1241 |
1242 |
1243 |
1244 |
1245 |
1246 |
1247 |
1248 |
1249 |
1250 |
1251 |
1252 |
1253 |
1254 |
1255 |
1256 |
1257 |
1258 |
1259 |
1260 |
1261 |
1262 |
1263 |
1264 |
1265 |
1266 |
1267 |
1268 |
1269 |
1270 |
1271 |
1272 |
1273 |
1274 |
1275 |
1276 |
1277 |
1278 |
1279 |
1280 |
1281 |
1282 |
1283 |
1284 |
1285 |
1286 |
1287 |
1288 |
1289 |
1290 |
1291 |
1292 |
1293 |
1294 |
1295 |
1296 |
1297 |
1298 |
1299 |
1300 |
1301 |
1302 |
--------------------------------------------------------------------------------