tulit 0.0.1__py3-none-any.whl
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.
- tulit/__init__.py +4 -0
- tulit/download/__init__.py +23 -0
- tulit/download/cellar.py +163 -0
- tulit/download/download.py +85 -0
- tulit/download/legilux.py +39 -0
- tulit/download/normattiva.py +97 -0
- tulit/main.py +64 -0
- tulit/parsers/__init__.py +3 -0
- tulit/parsers/akomantoso.py +729 -0
- tulit/parsers/assets/akomantoso30.xsd +8062 -0
- tulit/parsers/assets/formex4.xsd +5737 -0
- tulit/parsers/assets/import/xml.xsd +117 -0
- tulit/parsers/assets/xml.xsd +117 -0
- tulit/parsers/formex.py +280 -0
- tulit/parsers/html.py +275 -0
- tulit/parsers/parser.py +85 -0
- tulit/sparql.py +118 -0
- tulit-0.0.1.dist-info/LICENSE +287 -0
- tulit-0.0.1.dist-info/METADATA +107 -0
- tulit-0.0.1.dist-info/RECORD +21 -0
- tulit-0.0.1.dist-info/WHEEL +4 -0
tulit/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module provides functionality for [describe the main purpose of the module].
|
|
3
|
+
|
|
4
|
+
Classes:
|
|
5
|
+
[ClassName]: [Brief description of the class]
|
|
6
|
+
|
|
7
|
+
Functions:
|
|
8
|
+
[function_name_1]: [Brief description of the function]
|
|
9
|
+
[function_name_2]: [Brief description of the function]
|
|
10
|
+
...
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
[Provide a brief example of how to use the module]
|
|
14
|
+
|
|
15
|
+
Notes:
|
|
16
|
+
[Any additional notes or important information about the module]
|
|
17
|
+
|
|
18
|
+
Author:
|
|
19
|
+
[Your Name]
|
|
20
|
+
|
|
21
|
+
Date:
|
|
22
|
+
[Date of creation or last modification]
|
|
23
|
+
"""
|
tulit/download/cellar.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
import json
|
|
5
|
+
from tulit.download.download import DocumentDownloader
|
|
6
|
+
|
|
7
|
+
class CellarDownloader(DocumentDownloader):
|
|
8
|
+
|
|
9
|
+
def __init__(self, download_dir, log_dir):
|
|
10
|
+
super().__init__(download_dir, log_dir)
|
|
11
|
+
self.endpoint = 'http://publications.europa.eu/resource/cellar/'
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def fetch_content(self, url) -> requests.Response:
|
|
15
|
+
"""
|
|
16
|
+
Send a GET request to download a file
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
url : str
|
|
21
|
+
The URL to send the request to.
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
requests.Response
|
|
26
|
+
The response from the server.
|
|
27
|
+
|
|
28
|
+
Notes
|
|
29
|
+
-----
|
|
30
|
+
The request is sent with the following headers:
|
|
31
|
+
- Accept: application/zip;mtype=fmx4, application/xml;mtype=fmx4, application/xhtml+xml, text/html, text/html;type=simplified, application/msword, text/plain, application/xml;notice=object
|
|
32
|
+
- Accept-Language: eng
|
|
33
|
+
- Content-Type: application/x-www-form-urlencoded
|
|
34
|
+
- Host: publications.europa.eu
|
|
35
|
+
|
|
36
|
+
Raises
|
|
37
|
+
------
|
|
38
|
+
requests.RequestException
|
|
39
|
+
If there is an error sending the request.
|
|
40
|
+
|
|
41
|
+
See Also
|
|
42
|
+
--------
|
|
43
|
+
requests : The underlying library used for making HTTP requests.
|
|
44
|
+
|
|
45
|
+
"""
|
|
46
|
+
try:
|
|
47
|
+
headers = {
|
|
48
|
+
'Accept': "*, application/zip, application/zip;mtype=fmx4, application/xml;mtype=fmx4, application/xhtml+xml, text/html, text/html;type=simplified, application/msword, text/plain, application/xml, application/xml;notice=object",
|
|
49
|
+
'Accept-Language': "eng",
|
|
50
|
+
'Content-Type': "application/x-www-form-urlencoded",
|
|
51
|
+
'Host': "publications.europa.eu"
|
|
52
|
+
}
|
|
53
|
+
response = requests.request("GET", url, headers=headers)
|
|
54
|
+
response.raise_for_status()
|
|
55
|
+
return response
|
|
56
|
+
except requests.RequestException as e:
|
|
57
|
+
logging.error(f"Error sending GET request: {e}")
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
def build_request_url(self, params):
|
|
61
|
+
"""
|
|
62
|
+
Build the request URL based on the source and parameters.
|
|
63
|
+
"""
|
|
64
|
+
url = f"{self.endpoint}{params['cellar']}"
|
|
65
|
+
|
|
66
|
+
return url
|
|
67
|
+
|
|
68
|
+
def get_cellar_ids_from_json_results(self, results, format):
|
|
69
|
+
"""
|
|
70
|
+
Extract CELLAR ids from a JSON dictionary.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
cellar_results : dict
|
|
75
|
+
A dictionary containing the response of the CELLAR SPARQL query
|
|
76
|
+
|
|
77
|
+
Returns
|
|
78
|
+
-------
|
|
79
|
+
list
|
|
80
|
+
A list of CELLAR ids.
|
|
81
|
+
|
|
82
|
+
Notes
|
|
83
|
+
-----
|
|
84
|
+
The function assumes that the JSON dictionary has the following structure:
|
|
85
|
+
- The dictionary contains a key "results" that maps to another dictionary.
|
|
86
|
+
- The inner dictionary contains a key "bindings" that maps to a list of dictionaries.
|
|
87
|
+
- Each dictionary in the list contains a key "cellarURIs" that maps to a dictionary.
|
|
88
|
+
- The innermost dictionary contains a key "value" that maps to a string representing the CELLAR URI.
|
|
89
|
+
|
|
90
|
+
The function extracts the CELLAR id by splitting the CELLAR URI at "cellar/" and taking the second part.
|
|
91
|
+
|
|
92
|
+
Examples
|
|
93
|
+
--------
|
|
94
|
+
>>> cellar_results = {
|
|
95
|
+
... "results": {
|
|
96
|
+
... "bindings": [
|
|
97
|
+
... {"cellarURIs": {"value": "https://example.com/cellar/some_id"}},
|
|
98
|
+
... {"cellarURIs": {"value": "https://example.com/cellar/another_id"}}
|
|
99
|
+
... ]
|
|
100
|
+
... }
|
|
101
|
+
... }
|
|
102
|
+
>>> cellar_ids = get_cellar_ids_from_json_results(cellar_results)
|
|
103
|
+
>>> print(cellar_ids)
|
|
104
|
+
['some_id', 'another_id']
|
|
105
|
+
"""
|
|
106
|
+
cellar_ids = []
|
|
107
|
+
results_list = results["results"]["bindings"]
|
|
108
|
+
for i, file in enumerate(results_list):
|
|
109
|
+
if file['format']['value'] == format:
|
|
110
|
+
cellar_ids.append(file['cellarURIs']["value"].split("cellar/")[1])
|
|
111
|
+
|
|
112
|
+
return cellar_ids
|
|
113
|
+
|
|
114
|
+
def download(self, results, format=None):
|
|
115
|
+
"""
|
|
116
|
+
Sends a REST query to the specified source APIs and downloads the documents
|
|
117
|
+
corresponding to the given results.
|
|
118
|
+
|
|
119
|
+
Parameters
|
|
120
|
+
----------
|
|
121
|
+
results : dict
|
|
122
|
+
A dictionary containing the JSON results from the APIs.
|
|
123
|
+
format : str, optional
|
|
124
|
+
The format of the documents to download.
|
|
125
|
+
|
|
126
|
+
Returns
|
|
127
|
+
-------
|
|
128
|
+
list
|
|
129
|
+
A list of paths to the downloaded documents.
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
cellar_ids = self.get_cellar_ids_from_json_results(results, format=format)
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
document_paths = []
|
|
136
|
+
|
|
137
|
+
for id in cellar_ids:
|
|
138
|
+
# Build the request URL
|
|
139
|
+
url = self.build_request_url(params={'cellar': id})
|
|
140
|
+
|
|
141
|
+
# Send the GET request
|
|
142
|
+
response = self.fetch_content(url)
|
|
143
|
+
# Handle the response
|
|
144
|
+
file_path = self.handle_response(response=response, filename=id)
|
|
145
|
+
# Append the file path to the list
|
|
146
|
+
document_paths.append(file_path)
|
|
147
|
+
|
|
148
|
+
return document_paths
|
|
149
|
+
|
|
150
|
+
except Exception as e:
|
|
151
|
+
logging.error(f"Error processing range: {e}")
|
|
152
|
+
|
|
153
|
+
return document_paths
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
# Example usage
|
|
157
|
+
if __name__ == "__main__":
|
|
158
|
+
downloader = CellarDownloader(download_dir='./tests/data/formex', log_dir='./tests/logs')
|
|
159
|
+
with open('./tests/metadata/query_results/query_results.json', 'r') as f:
|
|
160
|
+
results = json.loads(f.read())
|
|
161
|
+
documents = downloader.download(results, format='fmx4')
|
|
162
|
+
print(documents)
|
|
163
|
+
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import io
|
|
3
|
+
import logging
|
|
4
|
+
import zipfile
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
class DocumentDownloader:
|
|
8
|
+
"""
|
|
9
|
+
A generic document downloader class.
|
|
10
|
+
"""
|
|
11
|
+
def __init__(self, download_dir, log_dir):
|
|
12
|
+
self.download_dir = download_dir
|
|
13
|
+
self.log_dir = log_dir
|
|
14
|
+
self._ensure_directories()
|
|
15
|
+
|
|
16
|
+
def _ensure_directories(self):
|
|
17
|
+
if not os.path.exists(self.download_dir):
|
|
18
|
+
os.makedirs(self.download_dir)
|
|
19
|
+
if not os.path.exists(self.log_dir):
|
|
20
|
+
os.makedirs(self.log_dir)
|
|
21
|
+
|
|
22
|
+
def handle_response(self, response, filename):
|
|
23
|
+
"""
|
|
24
|
+
Handle a server response by saving or extracting its content.
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
response : requests.Response
|
|
29
|
+
The HTTP response object.
|
|
30
|
+
folder_path : str
|
|
31
|
+
Directory where the file will be saved.
|
|
32
|
+
cid : str
|
|
33
|
+
CELLAR ID of the document.
|
|
34
|
+
|
|
35
|
+
Returns
|
|
36
|
+
-------
|
|
37
|
+
str or None
|
|
38
|
+
Path to the saved file or None if the response couldn't be processed.
|
|
39
|
+
"""
|
|
40
|
+
content_type = response.headers.get('Content-Type', '')
|
|
41
|
+
|
|
42
|
+
# The return file is usually either a zip file, or a file with the name DOC_* inside a folder named as the cellar_id
|
|
43
|
+
target_path = os.path.join(self.download_dir, filename)
|
|
44
|
+
os.makedirs(os.path.dirname(target_path), exist_ok=True)
|
|
45
|
+
|
|
46
|
+
if 'zip' in content_type:
|
|
47
|
+
self.extract_zip(response, target_path)
|
|
48
|
+
return target_path
|
|
49
|
+
else:
|
|
50
|
+
extension = self.get_extension_from_content_type(content_type)
|
|
51
|
+
if not extension:
|
|
52
|
+
logging.warning(f"Unknown content type for ID {filename}: {content_type}")
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
file_path = f"{target_path}.{extension}"
|
|
56
|
+
file_path = os.path.normpath(file_path)
|
|
57
|
+
|
|
58
|
+
with open(file_path, mode='wb+') as f:
|
|
59
|
+
f.write(response.content)
|
|
60
|
+
|
|
61
|
+
return file_path
|
|
62
|
+
|
|
63
|
+
def get_extension_from_content_type(self, content_type):
|
|
64
|
+
"""Map Content-Type to a file extension."""
|
|
65
|
+
content_type_mapping = {
|
|
66
|
+
'text/html': 'html',
|
|
67
|
+
'application/json': 'json',
|
|
68
|
+
'application/xml': 'xml',
|
|
69
|
+
'text/plain': 'txt',
|
|
70
|
+
'application/zip': 'zip',
|
|
71
|
+
'text/xml': 'xml',
|
|
72
|
+
'application/xhtml+xml': 'xhtml',
|
|
73
|
+
}
|
|
74
|
+
for ext, mapped_ext in content_type_mapping.items():
|
|
75
|
+
if ext in content_type:
|
|
76
|
+
return mapped_ext
|
|
77
|
+
|
|
78
|
+
# Function to download a zip file and extract it
|
|
79
|
+
def extract_zip(self, response: requests.Response, folder_path: str):
|
|
80
|
+
try:
|
|
81
|
+
z = zipfile.ZipFile(io.BytesIO(response.content))
|
|
82
|
+
z.extractall(folder_path)
|
|
83
|
+
except Exception as e:
|
|
84
|
+
logging.error(f"Error downloading zip: {e}")
|
|
85
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from tulit.download.download import DocumentDownloader
|
|
3
|
+
|
|
4
|
+
class LegiluxDownloader(DocumentDownloader):
|
|
5
|
+
def __init__(self, download_dir, log_dir):
|
|
6
|
+
super().__init__(download_dir, log_dir)
|
|
7
|
+
#self.endpoint = "https://legilux.public.lu/eli/etat/leg/loi"
|
|
8
|
+
|
|
9
|
+
def build_request_url(self, eli) -> str:
|
|
10
|
+
"""
|
|
11
|
+
Build the request URL based on the source and parameters.
|
|
12
|
+
"""
|
|
13
|
+
url = eli
|
|
14
|
+
return url
|
|
15
|
+
|
|
16
|
+
def fetch_content(self, url):
|
|
17
|
+
"""
|
|
18
|
+
Fetch the content of the document.
|
|
19
|
+
"""
|
|
20
|
+
headers = {"Accept": "application/xml"}
|
|
21
|
+
response = requests.get(url, headers=headers)
|
|
22
|
+
return response
|
|
23
|
+
|
|
24
|
+
def download(self, eli):
|
|
25
|
+
file_paths = []
|
|
26
|
+
url = self.build_request_url(eli)
|
|
27
|
+
response = self.fetch_content(url)
|
|
28
|
+
filename = eli.split('loi/')[1].replace('/', '_')
|
|
29
|
+
if response.status_code == 200:
|
|
30
|
+
file_paths.append(self.handle_response(response, filename=filename))
|
|
31
|
+
print(f"Document downloaded successfully and saved to {file_paths}")
|
|
32
|
+
return file_paths
|
|
33
|
+
else:
|
|
34
|
+
print(f"Failed to download document. Status code: {response.status_code}")
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
if __name__ == "__main__":
|
|
38
|
+
downloader = LegiluxDownloader(download_dir='./tests/data/legilux', log_dir='./tests/metadata/logs')
|
|
39
|
+
downloader.download(eli='http://data.legilux.public.lu/eli/etat/leg/loi/2006/07/31/n2/jo')
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from tulit.download.download import DocumentDownloader
|
|
2
|
+
import requests
|
|
3
|
+
import logging
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class NormattivaDownloader(DocumentDownloader):
|
|
8
|
+
def __init__(self, download_dir, log_dir):
|
|
9
|
+
super().__init__(download_dir, log_dir)
|
|
10
|
+
self.endpoint = "https://www.normattiva.it/do/atto/caricaAKN"
|
|
11
|
+
|
|
12
|
+
def build_request_url(self, params=None) -> str:
|
|
13
|
+
"""
|
|
14
|
+
Build the request URL based on the source and parameters.
|
|
15
|
+
"""
|
|
16
|
+
uri = f"https://www.normattiva.it/eli/id/{params['date']}//{params['codiceRedaz']}/CONSOLIDATED"
|
|
17
|
+
# In case we want to use the NIR:URI instead of ELI
|
|
18
|
+
#uri = f"https://www.normattiva.it/uri-res/N2Ls?urn:nir:stato:decreto.legislativo:{params['date']};{params['number']}"
|
|
19
|
+
url = f"{self.endpoint}?dataGU={params['dataGU']}&codiceRedaz={params['codiceRedaz']}&dataVigenza={params['dataVigenza']}"
|
|
20
|
+
|
|
21
|
+
return uri, url
|
|
22
|
+
|
|
23
|
+
def fetch_content(self, uri, url) -> requests.Response:
|
|
24
|
+
"""
|
|
25
|
+
Send a GET request to download a file
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
url : str
|
|
30
|
+
The URL to send the request to.
|
|
31
|
+
|
|
32
|
+
Returns
|
|
33
|
+
-------
|
|
34
|
+
requests.Response
|
|
35
|
+
The response from the server.
|
|
36
|
+
|
|
37
|
+
Raises
|
|
38
|
+
------
|
|
39
|
+
requests.RequestException
|
|
40
|
+
If there is an error sending the request.
|
|
41
|
+
"""
|
|
42
|
+
try:
|
|
43
|
+
|
|
44
|
+
# Make a GET request to the URI to get the cookies
|
|
45
|
+
cookies_response = requests.get(uri)
|
|
46
|
+
cookies_response.raise_for_status()
|
|
47
|
+
cookies = cookies_response.cookies
|
|
48
|
+
|
|
49
|
+
headers = {
|
|
50
|
+
'Accept': "text/xml",
|
|
51
|
+
'Accept-Encoding': "gzip, deflate, br, zstd",
|
|
52
|
+
'Accept-Language': "en-US,en;q=0.9",
|
|
53
|
+
|
|
54
|
+
}
|
|
55
|
+
response = requests.get(url, headers=headers, cookies=cookies)
|
|
56
|
+
response.raise_for_status()
|
|
57
|
+
return response
|
|
58
|
+
except requests.RequestException as e:
|
|
59
|
+
logging.error(f"Error sending GET request: {e}")
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
def download(self, dataGU, codiceRedaz, dataVigenza = datetime.today().strftime('%Y%m%d')):
|
|
63
|
+
document_paths = []
|
|
64
|
+
|
|
65
|
+
# Convert the dataGU to a datetime object
|
|
66
|
+
dataGU = datetime.strptime(dataGU, '%Y%m%d')
|
|
67
|
+
|
|
68
|
+
params = {
|
|
69
|
+
# dataGU as a string in the format YYYYMMDD
|
|
70
|
+
'dataGU': dataGU.strftime('%Y%m%d'),
|
|
71
|
+
'codiceRedaz': codiceRedaz,
|
|
72
|
+
'dataVigenza': dataVigenza,
|
|
73
|
+
# dataGU as a string in the format YYYY/MM/DD
|
|
74
|
+
'date': dataGU.strftime('%Y/%m/%d')
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
uri, url = self.build_request_url(params)
|
|
78
|
+
response = self.fetch_content(uri, url)
|
|
79
|
+
|
|
80
|
+
# If the response in HTML, raise an error saying that the date or codiceRedaz is wrong
|
|
81
|
+
if 'text/html' in response.headers.get('Content-Type', ''):
|
|
82
|
+
logging.error(f"Error downloading document: there is not an XML file with the following parameters: {params}")
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
file_path = self.handle_response(response=response, filename=f"{params['dataGU']}_{params['codiceRedaz']}_VIGENZA_{params['dataVigenza']}")
|
|
86
|
+
document_paths.append(file_path)
|
|
87
|
+
return document_paths
|
|
88
|
+
|
|
89
|
+
# Example usage
|
|
90
|
+
if __name__ == "__main__":
|
|
91
|
+
|
|
92
|
+
downloader = NormattivaDownloader(download_dir='./tests/data/akn/italy', log_dir='./tests/logs')
|
|
93
|
+
#documents = downloader.download(dataGU='19410716', codiceRedaz='041U0633')
|
|
94
|
+
documents = downloader.download(dataGU='19410716', codiceRedaz='041U0633', dataVigenza='20211231')
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
print(documents)
|
tulit/main.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
from tulit.download.download import download_documents
|
|
5
|
+
from sparql import send_sparql_query
|
|
6
|
+
from parsers.html import HTMLParser
|
|
7
|
+
from parsers.formex import Formex4Parser
|
|
8
|
+
|
|
9
|
+
def main():
|
|
10
|
+
"""
|
|
11
|
+
Main function to execute SPARQL query and download documents
|
|
12
|
+
"""
|
|
13
|
+
# Configure logging
|
|
14
|
+
logging.basicConfig(level=logging.INFO)
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
|
|
19
|
+
# Send SPARQL query
|
|
20
|
+
logger.info("Executing SPARQL query")
|
|
21
|
+
results = send_sparql_query('./tests/metadata/queries/formex_query.rq', celex='32008R1137')
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Save query results to JSON
|
|
25
|
+
results_file = './tests/metadata/query_results/query_results.json'
|
|
26
|
+
with open(results_file, "w") as f:
|
|
27
|
+
json.dump(results, f, indent=4)
|
|
28
|
+
logger.info(f"Results dumped in {results_file}")
|
|
29
|
+
|
|
30
|
+
# Load query results
|
|
31
|
+
with open('./tests/metadata/query_results/query_results.json', 'r') as f:
|
|
32
|
+
results = json.loads(f.read())
|
|
33
|
+
|
|
34
|
+
# Download documents
|
|
35
|
+
logger.info("Downloading documents")
|
|
36
|
+
downloaded_document_paths = download_documents(
|
|
37
|
+
results,
|
|
38
|
+
'./tests/data/formex',
|
|
39
|
+
log_dir='./tests/logs',
|
|
40
|
+
format='fmx4'
|
|
41
|
+
)
|
|
42
|
+
logger.info(f'{len(downloaded_document_paths)} documents downloaded in {downloaded_document_paths}')
|
|
43
|
+
|
|
44
|
+
# Extract the directory path (removing what's after the last '/')
|
|
45
|
+
|
|
46
|
+
# List the contents of the first directory
|
|
47
|
+
first_path = downloaded_document_paths[0]
|
|
48
|
+
first_item = os.listdir(first_path)[0]
|
|
49
|
+
file_path = os.path.join(*first_path.split('/'), first_item)
|
|
50
|
+
|
|
51
|
+
print(f'Parsing {file_path}')
|
|
52
|
+
# Sort the contents alphabetically and get the first item
|
|
53
|
+
|
|
54
|
+
parser = Formex4Parser()
|
|
55
|
+
parser.parse(file_path)
|
|
56
|
+
print(parser.articles)
|
|
57
|
+
#print(document_tree)
|
|
58
|
+
|
|
59
|
+
except Exception as e:
|
|
60
|
+
logger.error(f"An error occurred: {e}")
|
|
61
|
+
raise
|
|
62
|
+
|
|
63
|
+
if __name__ == "__main__":
|
|
64
|
+
main()
|