temporal-normalization-spacy 1.0.0__tar.gz
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.
- temporal_normalization_spacy-1.0.0/LICENSE +21 -0
- temporal_normalization_spacy-1.0.0/MANIFEST.in +6 -0
- temporal_normalization_spacy-1.0.0/PKG-INFO +251 -0
- temporal_normalization_spacy-1.0.0/README.md +227 -0
- temporal_normalization_spacy-1.0.0/setup.cfg +4 -0
- temporal_normalization_spacy-1.0.0/setup.py +27 -0
- temporal_normalization_spacy-1.0.0/temporal_normalization/__init__.py +5 -0
- temporal_normalization_spacy-1.0.0/temporal_normalization/commons/__init__.py +3 -0
- temporal_normalization_spacy-1.0.0/temporal_normalization/commons/print_utils.py +98 -0
- temporal_normalization_spacy-1.0.0/temporal_normalization/commons/temporal_models.py +105 -0
- temporal_normalization_spacy-1.0.0/temporal_normalization/commons/temporal_types.py +10 -0
- temporal_normalization_spacy-1.0.0/temporal_normalization/index.py +88 -0
- temporal_normalization_spacy-1.0.0/temporal_normalization/libs/temporal-normalization-1.6.jar +0 -0
- temporal_normalization_spacy-1.0.0/temporal_normalization/process/__init__.py +1 -0
- temporal_normalization_spacy-1.0.0/temporal_normalization/process/java_process.py +87 -0
- temporal_normalization_spacy-1.0.0/temporal_normalization_spacy.egg-info/PKG-INFO +251 -0
- temporal_normalization_spacy-1.0.0/temporal_normalization_spacy.egg-info/SOURCES.txt +18 -0
- temporal_normalization_spacy-1.0.0/temporal_normalization_spacy.egg-info/dependency_links.txt +1 -0
- temporal_normalization_spacy-1.0.0/temporal_normalization_spacy.egg-info/requires.txt +3 -0
- temporal_normalization_spacy-1.0.0/temporal_normalization_spacy.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Ilie
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: temporal_normalization_spacy
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A spaCy plugin for identifying and parsing historical data in Romanian texts
|
|
5
|
+
Home-page: https://github.com/iliedorobat/timespan-normalization-spacy
|
|
6
|
+
Author: Ilie Cristian Dorobat
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.6
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: spacy>=3.0
|
|
14
|
+
Requires-Dist: py4j
|
|
15
|
+
Requires-Dist: langdetect
|
|
16
|
+
Dynamic: author
|
|
17
|
+
Dynamic: classifier
|
|
18
|
+
Dynamic: description
|
|
19
|
+
Dynamic: description-content-type
|
|
20
|
+
Dynamic: home-page
|
|
21
|
+
Dynamic: requires-dist
|
|
22
|
+
Dynamic: requires-python
|
|
23
|
+
Dynamic: summary
|
|
24
|
+
|
|
25
|
+
# Temporal Expressions Normalization spaCy (TeNs)
|
|
26
|
+
|
|
27
|
+
<b>Temporal Expressions Normalization spaCy (TeNs)</b> is a powerful pipeline component for spaCy that seamlessly
|
|
28
|
+
identifies and parses date entities in text. It leverages the <b>[Temporal Normalization Framework](
|
|
29
|
+
https://github.com/iliedorobat/timespan-normalization)</b> to recognize a wide variety of date formats using an
|
|
30
|
+
extensive set of regular expressions (RegEx), ensuring robust and adaptable date extraction across diverse
|
|
31
|
+
textual sources.
|
|
32
|
+
|
|
33
|
+
Unlike conventional solutions that primarily focus on well-structured date formats, TeNs excels in handling
|
|
34
|
+
real-world text by <b>identifying</b> not only standard date representations but also <b>abbreviated, informal, or even
|
|
35
|
+
misspelled temporal expressions.</b> This makes it particularly effective for processing noisy or unstructured data,
|
|
36
|
+
such as historical records, user-generated content, and scanned documents with OCR inaccuracies.
|
|
37
|
+
|
|
38
|
+
Moreover, TeNs is designed to <b>integrate seamlessly into existing NLP pipelines,</b> allowing for enhanced temporal
|
|
39
|
+
information processing in tasks such as event extraction, timeline construction, and knowledge graph population.
|
|
40
|
+
By providing a flexible and accurate approach to temporal data normalization, it significantly improves the
|
|
41
|
+
quality and reliability of date-related information extracted from text.
|
|
42
|
+
|
|
43
|
+
<b>Table I.</b> Types of temporal expressions which can be processed
|
|
44
|
+
<table>
|
|
45
|
+
<tr>
|
|
46
|
+
<th>Type of Temporal Expressions</th>
|
|
47
|
+
<th>Examples of Temporal Expressions*</th>
|
|
48
|
+
</tr>
|
|
49
|
+
<tr>
|
|
50
|
+
<td>dates</td>
|
|
51
|
+
<td>
|
|
52
|
+
YMD: 1881-08-31; 1857 mai 10; etc.<br/>
|
|
53
|
+
DMY: 09.11.1518; 1 noiembrie 1624; etc.<br/>
|
|
54
|
+
MY: ianuarie 632; etc.
|
|
55
|
+
</td>
|
|
56
|
+
</tr>
|
|
57
|
+
<tr>
|
|
58
|
+
<td>timespans</td>
|
|
59
|
+
<td>
|
|
60
|
+
centuries: s:; sc; se.; sec; sec.; secol; secolele; secolul; sex.<br/>
|
|
61
|
+
millenniums: mil; mil.; mileniul; mileniului; mileniile
|
|
62
|
+
</td>
|
|
63
|
+
</tr>
|
|
64
|
+
<tr>
|
|
65
|
+
<td>years</td>
|
|
66
|
+
<td>77; 78; 1652; [1873]; aproximativ 1834; cca. 1420; etc.</td>
|
|
67
|
+
</tr>
|
|
68
|
+
</table>
|
|
69
|
+
* The values are mentioned in the reference language – Romanian language
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
## Getting started
|
|
74
|
+
To integrate TeNs into spaCy pipelines you need the following:
|
|
75
|
+
|
|
76
|
+
### Prerequisites
|
|
77
|
+
- Python 3.x
|
|
78
|
+
- JRE 11+
|
|
79
|
+
- spaCy 3.x
|
|
80
|
+
- py4j 0.10.9.9
|
|
81
|
+
- langdetect 1.0.9
|
|
82
|
+
|
|
83
|
+
1. Install python packages:
|
|
84
|
+
```bash
|
|
85
|
+
pip install -r requirements.txt
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
2. Install [JDK 11](https://www.oracle.com/nl/java/technologies/javase/jdk11-archive-downloads.html) or [OpenJDK 11](https://openjdk.org/install/) (or newer versions)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
### Install
|
|
92
|
+
```bash
|
|
93
|
+
pip install temporal-spacy
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Supported languages
|
|
97
|
+
- [Romanian](https://universaldependencies.org/tagset-conversion/ro-multext-uposf.html)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
## Usage
|
|
102
|
+
***
|
|
103
|
+
|
|
104
|
+
### Importing Modules & Defining Constants
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
import subprocess
|
|
108
|
+
|
|
109
|
+
import spacy
|
|
110
|
+
|
|
111
|
+
from temporal_normalization.commons.print_utils import console
|
|
112
|
+
from temporal_normalization.index import create_normalized_component, TemporalNormalization ## noqa: F401
|
|
113
|
+
|
|
114
|
+
LANG = "ro"
|
|
115
|
+
MODEL = "ro_core_news_sm"
|
|
116
|
+
TEXT_RO = ("Sec al II-lea a.ch. a fost o perioadă de mari schimbări. "
|
|
117
|
+
"În secolul XX, tehnologia a avansat semnificativ. "
|
|
118
|
+
"Sec. 21 este adesea asociat cu globalizarea rapidă.")
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Adding the Component to spaCy Pipeline
|
|
122
|
+
```python
|
|
123
|
+
# Display a warning if the language of the text is not Romanian.
|
|
124
|
+
console.lang_warning(TEXT_RO, target_lang=LANG)
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
# Load the spaCy model if it has already been downloaded
|
|
128
|
+
nlp = spacy.load(MODEL)
|
|
129
|
+
except OSError:
|
|
130
|
+
console.warning(f'Started downloading {MODEL}...')
|
|
131
|
+
# Download the Romanian model if it wasn't already downloaded
|
|
132
|
+
subprocess.run(["python", "-m", "spacy", "download", MODEL])
|
|
133
|
+
# Load the spaCy model
|
|
134
|
+
nlp = spacy.load(MODEL)
|
|
135
|
+
|
|
136
|
+
# Add "temporal_normalization" component to the spaCy pipeline
|
|
137
|
+
nlp.add_pipe("temporal_normalization", last=True)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Processing Text with the Pipeline
|
|
141
|
+
```python
|
|
142
|
+
doc = nlp(TEXT_RO)
|
|
143
|
+
|
|
144
|
+
# Display NLP-specific linguistic annotations
|
|
145
|
+
console.tokens_table(doc)
|
|
146
|
+
print()
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Accessing the Parsed Temporal Expressions
|
|
150
|
+
```python
|
|
151
|
+
# Display information about the identified and normalized dates in the text.
|
|
152
|
+
for entity in doc.ents:
|
|
153
|
+
for edge in entity._.normalized.edges:
|
|
154
|
+
print(edge.serialize())
|
|
155
|
+
print()
|
|
156
|
+
|
|
157
|
+
print("Periods:")
|
|
158
|
+
for period in entity._.normalized.periods:
|
|
159
|
+
print(period.serialize("\t"))
|
|
160
|
+
print()
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Result
|
|
164
|
+
#### First Sentence
|
|
165
|
+
```text
|
|
166
|
+
Start time:
|
|
167
|
+
Matched value: Sec al II-lea a.ch.
|
|
168
|
+
Matched Type: century
|
|
169
|
+
Normalized label: 2nd century BC
|
|
170
|
+
DBpedia uri: https://dbpedia.org/page/2nd_century_BC
|
|
171
|
+
End time:
|
|
172
|
+
Matched value: Sec al II-lea a.ch.
|
|
173
|
+
Matched Type: century
|
|
174
|
+
Normalized label: 2nd century BC
|
|
175
|
+
DBpedia uri: https://dbpedia.org/page/2nd_century_BC
|
|
176
|
+
|
|
177
|
+
Periods:
|
|
178
|
+
Matched value: Sec al II-lea a.ch.
|
|
179
|
+
Matched Type: century
|
|
180
|
+
Normalized label: 1st millennium BC
|
|
181
|
+
DBpedia uri: https://dbpedia.org/page/1st_millennium_BC
|
|
182
|
+
|
|
183
|
+
Matched value: Sec al II-lea a.ch.
|
|
184
|
+
Matched Type: century
|
|
185
|
+
Normalized label: 2nd century BC
|
|
186
|
+
DBpedia uri: https://dbpedia.org/page/2nd_century_BC
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
#### Second Sentence
|
|
190
|
+
```text
|
|
191
|
+
Start time:
|
|
192
|
+
Matched value: secolul XX
|
|
193
|
+
Matched Type: century
|
|
194
|
+
Normalized label: 20th century
|
|
195
|
+
DBpedia uri: https://dbpedia.org/page/20th_century
|
|
196
|
+
End time:
|
|
197
|
+
Matched value: secolul XX
|
|
198
|
+
Matched Type: century
|
|
199
|
+
Normalized label: 20th century
|
|
200
|
+
DBpedia uri: https://dbpedia.org/page/20th_century
|
|
201
|
+
|
|
202
|
+
Periods:
|
|
203
|
+
Matched value: secolul XX
|
|
204
|
+
Matched Type: century
|
|
205
|
+
Normalized label: 2nd millennium
|
|
206
|
+
DBpedia uri: https://dbpedia.org/page/2nd_millennium
|
|
207
|
+
|
|
208
|
+
Matched value: secolul XX
|
|
209
|
+
Matched Type: century
|
|
210
|
+
Normalized label: 20th century
|
|
211
|
+
DBpedia uri: https://dbpedia.org/page/20th_century
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
#### Third Sentence
|
|
215
|
+
```text
|
|
216
|
+
Start time:
|
|
217
|
+
Matched value: Sec. 21
|
|
218
|
+
Matched Type: century
|
|
219
|
+
Normalized label: 21st century
|
|
220
|
+
DBpedia uri: https://dbpedia.org/page/21st_century
|
|
221
|
+
End time:
|
|
222
|
+
Matched value: Sec. 21
|
|
223
|
+
Matched Type: century
|
|
224
|
+
Normalized label: 21st century
|
|
225
|
+
DBpedia uri: https://dbpedia.org/page/21st_century
|
|
226
|
+
|
|
227
|
+
Periods:
|
|
228
|
+
Matched value: Sec. 21
|
|
229
|
+
Matched Type: century
|
|
230
|
+
Normalized label: 3rd millennium
|
|
231
|
+
DBpedia uri: https://dbpedia.org/page/3rd_millennium
|
|
232
|
+
|
|
233
|
+
Matched value: Sec. 21
|
|
234
|
+
Matched Type: century
|
|
235
|
+
Normalized label: 21st century
|
|
236
|
+
DBpedia uri: https://dbpedia.org/page/21st_century
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
## Publications
|
|
242
|
+
ECAI 2021: [The Power of Regular Expressions in Recognizing Dates and Epochs (2021)](https://ieeexplore.ieee.org/document/9515139)
|
|
243
|
+
```
|
|
244
|
+
@inproceedings{9515139,
|
|
245
|
+
author={Dorobăț, Ilie Cristian and Posea, Vlad},
|
|
246
|
+
booktitle={2021 13th International Conference on Electronics, Computers and Artificial Intelligence (ECAI)},
|
|
247
|
+
title={The Power of Regular Expressions in Recognizing Dates and Epochs},
|
|
248
|
+
year={2021},
|
|
249
|
+
pages={1-3},
|
|
250
|
+
doi={10.1109/ECAI52376.2021.9515139}}
|
|
251
|
+
```
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# Temporal Expressions Normalization spaCy (TeNs)
|
|
2
|
+
|
|
3
|
+
<b>Temporal Expressions Normalization spaCy (TeNs)</b> is a powerful pipeline component for spaCy that seamlessly
|
|
4
|
+
identifies and parses date entities in text. It leverages the <b>[Temporal Normalization Framework](
|
|
5
|
+
https://github.com/iliedorobat/timespan-normalization)</b> to recognize a wide variety of date formats using an
|
|
6
|
+
extensive set of regular expressions (RegEx), ensuring robust and adaptable date extraction across diverse
|
|
7
|
+
textual sources.
|
|
8
|
+
|
|
9
|
+
Unlike conventional solutions that primarily focus on well-structured date formats, TeNs excels in handling
|
|
10
|
+
real-world text by <b>identifying</b> not only standard date representations but also <b>abbreviated, informal, or even
|
|
11
|
+
misspelled temporal expressions.</b> This makes it particularly effective for processing noisy or unstructured data,
|
|
12
|
+
such as historical records, user-generated content, and scanned documents with OCR inaccuracies.
|
|
13
|
+
|
|
14
|
+
Moreover, TeNs is designed to <b>integrate seamlessly into existing NLP pipelines,</b> allowing for enhanced temporal
|
|
15
|
+
information processing in tasks such as event extraction, timeline construction, and knowledge graph population.
|
|
16
|
+
By providing a flexible and accurate approach to temporal data normalization, it significantly improves the
|
|
17
|
+
quality and reliability of date-related information extracted from text.
|
|
18
|
+
|
|
19
|
+
<b>Table I.</b> Types of temporal expressions which can be processed
|
|
20
|
+
<table>
|
|
21
|
+
<tr>
|
|
22
|
+
<th>Type of Temporal Expressions</th>
|
|
23
|
+
<th>Examples of Temporal Expressions*</th>
|
|
24
|
+
</tr>
|
|
25
|
+
<tr>
|
|
26
|
+
<td>dates</td>
|
|
27
|
+
<td>
|
|
28
|
+
YMD: 1881-08-31; 1857 mai 10; etc.<br/>
|
|
29
|
+
DMY: 09.11.1518; 1 noiembrie 1624; etc.<br/>
|
|
30
|
+
MY: ianuarie 632; etc.
|
|
31
|
+
</td>
|
|
32
|
+
</tr>
|
|
33
|
+
<tr>
|
|
34
|
+
<td>timespans</td>
|
|
35
|
+
<td>
|
|
36
|
+
centuries: s:; sc; se.; sec; sec.; secol; secolele; secolul; sex.<br/>
|
|
37
|
+
millenniums: mil; mil.; mileniul; mileniului; mileniile
|
|
38
|
+
</td>
|
|
39
|
+
</tr>
|
|
40
|
+
<tr>
|
|
41
|
+
<td>years</td>
|
|
42
|
+
<td>77; 78; 1652; [1873]; aproximativ 1834; cca. 1420; etc.</td>
|
|
43
|
+
</tr>
|
|
44
|
+
</table>
|
|
45
|
+
* The values are mentioned in the reference language – Romanian language
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
## Getting started
|
|
50
|
+
To integrate TeNs into spaCy pipelines you need the following:
|
|
51
|
+
|
|
52
|
+
### Prerequisites
|
|
53
|
+
- Python 3.x
|
|
54
|
+
- JRE 11+
|
|
55
|
+
- spaCy 3.x
|
|
56
|
+
- py4j 0.10.9.9
|
|
57
|
+
- langdetect 1.0.9
|
|
58
|
+
|
|
59
|
+
1. Install python packages:
|
|
60
|
+
```bash
|
|
61
|
+
pip install -r requirements.txt
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
2. Install [JDK 11](https://www.oracle.com/nl/java/technologies/javase/jdk11-archive-downloads.html) or [OpenJDK 11](https://openjdk.org/install/) (or newer versions)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
### Install
|
|
68
|
+
```bash
|
|
69
|
+
pip install temporal-spacy
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Supported languages
|
|
73
|
+
- [Romanian](https://universaldependencies.org/tagset-conversion/ro-multext-uposf.html)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
## Usage
|
|
78
|
+
***
|
|
79
|
+
|
|
80
|
+
### Importing Modules & Defining Constants
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
import subprocess
|
|
84
|
+
|
|
85
|
+
import spacy
|
|
86
|
+
|
|
87
|
+
from temporal_normalization.commons.print_utils import console
|
|
88
|
+
from temporal_normalization.index import create_normalized_component, TemporalNormalization ## noqa: F401
|
|
89
|
+
|
|
90
|
+
LANG = "ro"
|
|
91
|
+
MODEL = "ro_core_news_sm"
|
|
92
|
+
TEXT_RO = ("Sec al II-lea a.ch. a fost o perioadă de mari schimbări. "
|
|
93
|
+
"În secolul XX, tehnologia a avansat semnificativ. "
|
|
94
|
+
"Sec. 21 este adesea asociat cu globalizarea rapidă.")
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Adding the Component to spaCy Pipeline
|
|
98
|
+
```python
|
|
99
|
+
# Display a warning if the language of the text is not Romanian.
|
|
100
|
+
console.lang_warning(TEXT_RO, target_lang=LANG)
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
# Load the spaCy model if it has already been downloaded
|
|
104
|
+
nlp = spacy.load(MODEL)
|
|
105
|
+
except OSError:
|
|
106
|
+
console.warning(f'Started downloading {MODEL}...')
|
|
107
|
+
# Download the Romanian model if it wasn't already downloaded
|
|
108
|
+
subprocess.run(["python", "-m", "spacy", "download", MODEL])
|
|
109
|
+
# Load the spaCy model
|
|
110
|
+
nlp = spacy.load(MODEL)
|
|
111
|
+
|
|
112
|
+
# Add "temporal_normalization" component to the spaCy pipeline
|
|
113
|
+
nlp.add_pipe("temporal_normalization", last=True)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Processing Text with the Pipeline
|
|
117
|
+
```python
|
|
118
|
+
doc = nlp(TEXT_RO)
|
|
119
|
+
|
|
120
|
+
# Display NLP-specific linguistic annotations
|
|
121
|
+
console.tokens_table(doc)
|
|
122
|
+
print()
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Accessing the Parsed Temporal Expressions
|
|
126
|
+
```python
|
|
127
|
+
# Display information about the identified and normalized dates in the text.
|
|
128
|
+
for entity in doc.ents:
|
|
129
|
+
for edge in entity._.normalized.edges:
|
|
130
|
+
print(edge.serialize())
|
|
131
|
+
print()
|
|
132
|
+
|
|
133
|
+
print("Periods:")
|
|
134
|
+
for period in entity._.normalized.periods:
|
|
135
|
+
print(period.serialize("\t"))
|
|
136
|
+
print()
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Result
|
|
140
|
+
#### First Sentence
|
|
141
|
+
```text
|
|
142
|
+
Start time:
|
|
143
|
+
Matched value: Sec al II-lea a.ch.
|
|
144
|
+
Matched Type: century
|
|
145
|
+
Normalized label: 2nd century BC
|
|
146
|
+
DBpedia uri: https://dbpedia.org/page/2nd_century_BC
|
|
147
|
+
End time:
|
|
148
|
+
Matched value: Sec al II-lea a.ch.
|
|
149
|
+
Matched Type: century
|
|
150
|
+
Normalized label: 2nd century BC
|
|
151
|
+
DBpedia uri: https://dbpedia.org/page/2nd_century_BC
|
|
152
|
+
|
|
153
|
+
Periods:
|
|
154
|
+
Matched value: Sec al II-lea a.ch.
|
|
155
|
+
Matched Type: century
|
|
156
|
+
Normalized label: 1st millennium BC
|
|
157
|
+
DBpedia uri: https://dbpedia.org/page/1st_millennium_BC
|
|
158
|
+
|
|
159
|
+
Matched value: Sec al II-lea a.ch.
|
|
160
|
+
Matched Type: century
|
|
161
|
+
Normalized label: 2nd century BC
|
|
162
|
+
DBpedia uri: https://dbpedia.org/page/2nd_century_BC
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
#### Second Sentence
|
|
166
|
+
```text
|
|
167
|
+
Start time:
|
|
168
|
+
Matched value: secolul XX
|
|
169
|
+
Matched Type: century
|
|
170
|
+
Normalized label: 20th century
|
|
171
|
+
DBpedia uri: https://dbpedia.org/page/20th_century
|
|
172
|
+
End time:
|
|
173
|
+
Matched value: secolul XX
|
|
174
|
+
Matched Type: century
|
|
175
|
+
Normalized label: 20th century
|
|
176
|
+
DBpedia uri: https://dbpedia.org/page/20th_century
|
|
177
|
+
|
|
178
|
+
Periods:
|
|
179
|
+
Matched value: secolul XX
|
|
180
|
+
Matched Type: century
|
|
181
|
+
Normalized label: 2nd millennium
|
|
182
|
+
DBpedia uri: https://dbpedia.org/page/2nd_millennium
|
|
183
|
+
|
|
184
|
+
Matched value: secolul XX
|
|
185
|
+
Matched Type: century
|
|
186
|
+
Normalized label: 20th century
|
|
187
|
+
DBpedia uri: https://dbpedia.org/page/20th_century
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
#### Third Sentence
|
|
191
|
+
```text
|
|
192
|
+
Start time:
|
|
193
|
+
Matched value: Sec. 21
|
|
194
|
+
Matched Type: century
|
|
195
|
+
Normalized label: 21st century
|
|
196
|
+
DBpedia uri: https://dbpedia.org/page/21st_century
|
|
197
|
+
End time:
|
|
198
|
+
Matched value: Sec. 21
|
|
199
|
+
Matched Type: century
|
|
200
|
+
Normalized label: 21st century
|
|
201
|
+
DBpedia uri: https://dbpedia.org/page/21st_century
|
|
202
|
+
|
|
203
|
+
Periods:
|
|
204
|
+
Matched value: Sec. 21
|
|
205
|
+
Matched Type: century
|
|
206
|
+
Normalized label: 3rd millennium
|
|
207
|
+
DBpedia uri: https://dbpedia.org/page/3rd_millennium
|
|
208
|
+
|
|
209
|
+
Matched value: Sec. 21
|
|
210
|
+
Matched Type: century
|
|
211
|
+
Normalized label: 21st century
|
|
212
|
+
DBpedia uri: https://dbpedia.org/page/21st_century
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
## Publications
|
|
218
|
+
ECAI 2021: [The Power of Regular Expressions in Recognizing Dates and Epochs (2021)](https://ieeexplore.ieee.org/document/9515139)
|
|
219
|
+
```
|
|
220
|
+
@inproceedings{9515139,
|
|
221
|
+
author={Dorobăț, Ilie Cristian and Posea, Vlad},
|
|
222
|
+
booktitle={2021 13th International Conference on Electronics, Computers and Artificial Intelligence (ECAI)},
|
|
223
|
+
title={The Power of Regular Expressions in Recognizing Dates and Epochs},
|
|
224
|
+
year={2021},
|
|
225
|
+
pages={1-3},
|
|
226
|
+
doi={10.1109/ECAI52376.2021.9515139}}
|
|
227
|
+
```
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="temporal_normalization_spacy",
|
|
5
|
+
version="1.0.0",
|
|
6
|
+
author="Ilie Cristian Dorobat",
|
|
7
|
+
description="A spaCy plugin for identifying and parsing historical data in Romanian texts",
|
|
8
|
+
long_description=open("README.md").read(),
|
|
9
|
+
long_description_content_type="text/markdown",
|
|
10
|
+
url="https://github.com/iliedorobat/timespan-normalization-spacy",
|
|
11
|
+
packages=find_packages(),
|
|
12
|
+
include_package_data=True,
|
|
13
|
+
package_data={
|
|
14
|
+
"temporal_normalization.libs": ["temporal-normalization-1.6.jar"],
|
|
15
|
+
},
|
|
16
|
+
install_requires=[
|
|
17
|
+
"spacy>=3.0",
|
|
18
|
+
"py4j",
|
|
19
|
+
"langdetect"
|
|
20
|
+
],
|
|
21
|
+
classifiers=[
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"License :: OSI Approved :: MIT License",
|
|
24
|
+
"Operating System :: OS Independent",
|
|
25
|
+
],
|
|
26
|
+
python_requires=">=3.6",
|
|
27
|
+
)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
from langdetect import detect
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# https://godoc.org/github.com/whitedevops/colors
|
|
7
|
+
class COLORS:
|
|
8
|
+
BLUE = "\033[34m"
|
|
9
|
+
CYAN = "\033[36m"
|
|
10
|
+
LIGHT_CYAN = "\033[96m"
|
|
11
|
+
LIGHT_YELLOW = "\033[93m"
|
|
12
|
+
LIGHT_RED = "\033[91m"
|
|
13
|
+
RED = "\033[31m"
|
|
14
|
+
RESET_ALL = '\033[0m'
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# https://stackoverflow.com/questions/287871/how-to-print-colored-text-in-python#answer-287944
|
|
18
|
+
class console:
|
|
19
|
+
@staticmethod
|
|
20
|
+
def display(message: str, color: str, alert: str, show_time: bool = True):
|
|
21
|
+
if show_time:
|
|
22
|
+
print(f'{color}{datetime.now().replace(microsecond=0)} {alert}: {message}{COLORS.RESET_ALL}')
|
|
23
|
+
else:
|
|
24
|
+
print(f'{color}{message}{COLORS.RESET_ALL}')
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
def debug(message: str, show_time: bool = True):
|
|
28
|
+
console.display(message, COLORS.CYAN, "DEBUG", show_time)
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def log(message: str, show_time: bool = True):
|
|
32
|
+
console.display(message, COLORS.BLUE, "LOG", show_time)
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def info(message: str, show_time: bool = True):
|
|
36
|
+
console.display(message, COLORS.LIGHT_CYAN, "INFO", show_time)
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def warning(message: str, show_time: bool = True):
|
|
40
|
+
console.display(message, COLORS.LIGHT_YELLOW, "WARNING", show_time)
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
def error(message: str, show_time: bool = True):
|
|
44
|
+
console.display(message, COLORS.RED, "ERROR", show_time)
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def lang_warning(query: str, target_lang: str):
|
|
48
|
+
if detect(query) != target_lang:
|
|
49
|
+
console.warning(
|
|
50
|
+
f'Detected language: "{detect(query)}" but required: "{target_lang}"'
|
|
51
|
+
)
|
|
52
|
+
@staticmethod
|
|
53
|
+
def tokens_table(document):
|
|
54
|
+
console.info(
|
|
55
|
+
f'-------------------------------------------------------------------------------------------------------'
|
|
56
|
+
f'\n{"Token":{15}}|{"Lemma":{15}}|{"POS":{10}}|{"TAG":{10}}|'
|
|
57
|
+
f'{"DEP":{10}}|{"shape_":{15}}|{"is_alpha":{10}}',
|
|
58
|
+
show_time=False
|
|
59
|
+
)
|
|
60
|
+
console.info(
|
|
61
|
+
'-------------------------------------------------------------------------------------------------------',
|
|
62
|
+
show_time=False
|
|
63
|
+
)
|
|
64
|
+
for token in document:
|
|
65
|
+
console.info(
|
|
66
|
+
f'{token.text:{15}}|{token.lemma_:{15}}|{token.pos_:{10}}|{token.tag_:{10}}|{token.dep_:{10}}|'
|
|
67
|
+
f'{token.shape_:{15}}|{token.is_alpha:{10}}',
|
|
68
|
+
show_time=False
|
|
69
|
+
)
|
|
70
|
+
console.info(
|
|
71
|
+
'-------------------------------------------------------------------------------------------------------',
|
|
72
|
+
show_time=False
|
|
73
|
+
)
|
|
74
|
+
console.info(f'sentence: {document}', show_time=False)
|
|
75
|
+
|
|
76
|
+
@staticmethod
|
|
77
|
+
def deps_list(document):
|
|
78
|
+
separator = ''
|
|
79
|
+
for i in range(130):
|
|
80
|
+
separator += '-'
|
|
81
|
+
|
|
82
|
+
console.info(
|
|
83
|
+
f'{separator}\n'
|
|
84
|
+
f' '
|
|
85
|
+
f'{"Token":{15}}|{"Governor":{10}}|{"Root Node":{10}}|'
|
|
86
|
+
f'{"Lefts":{20}}|{"Rights":{20}}|'
|
|
87
|
+
f'{"Lemma":{15}}|{"POS":{10}}|{"TAG":{10}}|{"DEP":{10}}'
|
|
88
|
+
)
|
|
89
|
+
console.info(separator)
|
|
90
|
+
for token in document:
|
|
91
|
+
console.info(
|
|
92
|
+
f' '
|
|
93
|
+
f'{token.text:{15}}|{token.head.text:{10}}|{str(token == token.head):{10}}|'
|
|
94
|
+
f'{str(list(token.lefts)):{20}}|{str(list(token.rights)):{20}}|'
|
|
95
|
+
f'{token.lemma_:{15}}|{token.pos_:{10}}|{token.tag_:{10}}|{token.dep_:{10}}'
|
|
96
|
+
)
|
|
97
|
+
console.info(separator)
|
|
98
|
+
console.info(f'sentence: {document}')
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from py4j.java_gateway import JavaObject
|
|
3
|
+
|
|
4
|
+
from temporal_normalization.commons.temporal_types import TemporalType
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TemporalExpression:
|
|
8
|
+
"""
|
|
9
|
+
A model representing a temporal expression, extracted and processed from a Java object.
|
|
10
|
+
|
|
11
|
+
Attributes:
|
|
12
|
+
is_valid (bool): A flag that specifies whether the text processed through timespan-normalization library is a temporal expression.
|
|
13
|
+
initial (str or None): The original temporal expression before processing.
|
|
14
|
+
edges (list[EdgeModel]): A list of temporal intervals represented as edges.
|
|
15
|
+
periods (list[DBpediaModel]): A list of normalized DBpedia entities extracted from the expression.
|
|
16
|
+
matches (list[str]): A unique list of matched values found in the normalized entities.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, java_object: JavaObject):
|
|
20
|
+
serialize = java_object.serialize()
|
|
21
|
+
json_obj = json.loads(serialize)
|
|
22
|
+
|
|
23
|
+
self.is_valid = TemporalExpression.is_valid_json(json_obj)
|
|
24
|
+
self.initial: str | None = json_obj["initial"] if self.is_valid else None
|
|
25
|
+
self.edges: list[EdgeModel] = [
|
|
26
|
+
EdgeModel(item) for item in json_obj["edges"]
|
|
27
|
+
] if self.is_valid else []
|
|
28
|
+
self.periods: list[DBpediaModel] = [
|
|
29
|
+
DBpediaModel(item) for item in json_obj["periods"]
|
|
30
|
+
] if self.is_valid else []
|
|
31
|
+
self.matches: list[str] = list(
|
|
32
|
+
set(
|
|
33
|
+
[item.matched_value for item in self.periods]
|
|
34
|
+
)
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
def __str__(self):
|
|
38
|
+
if self.initial is None:
|
|
39
|
+
return "TemporalExpression(None)"
|
|
40
|
+
|
|
41
|
+
return f"TemporalExpression({self.initial})"
|
|
42
|
+
|
|
43
|
+
def __repr__(self):
|
|
44
|
+
return self.initial
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def is_valid_json(json_obj) -> bool:
|
|
48
|
+
return ("initial" in json_obj
|
|
49
|
+
and "edges" in json_obj
|
|
50
|
+
and "periods" in json_obj)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class DBpediaModel:
|
|
54
|
+
"""
|
|
55
|
+
A model representing an entity from DBpedia, storing key attributes related to the entity.
|
|
56
|
+
|
|
57
|
+
Attributes:
|
|
58
|
+
uri (str): The unique identifier (URI) of the DBpedia entity.
|
|
59
|
+
label (str): A human-readable name for the entity.
|
|
60
|
+
matched_value (str): The original matched value from the input data.
|
|
61
|
+
matched_type (TemporalType or None): The temporal type of the entity, if applicable.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def __init__(self, data: dict):
|
|
65
|
+
self.uri: str = data["uri"] if "uri" in data else None
|
|
66
|
+
self.label: str = data["label"] if "label" in data else None
|
|
67
|
+
self.matched_value: str = data["matchedValue"] if "matchedValue" in data else None
|
|
68
|
+
try:
|
|
69
|
+
self.matched_type: TemporalType = TemporalType(data["matchedType"]) if "matchedType" in data else None
|
|
70
|
+
except ValueError:
|
|
71
|
+
self.matched_type = None
|
|
72
|
+
|
|
73
|
+
def __repr__(self):
|
|
74
|
+
return f"DBpediaModel(label={self.label}, matched_value={self.matched_value})"
|
|
75
|
+
|
|
76
|
+
def serialize(self, indent: str = ""):
|
|
77
|
+
return (f"{indent}Matched value: {self.matched_value}\n"
|
|
78
|
+
f"{indent}Matched Type: {self.matched_type.value if self.matched_type else None}\n"
|
|
79
|
+
f"{indent}Normalized label: {self.label}\n"
|
|
80
|
+
f"{indent}DBpedia uri: {self.uri}")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class EdgeModel:
|
|
84
|
+
"""
|
|
85
|
+
A model representing time interval represented as DBpedia entities.
|
|
86
|
+
This edge represents the starting and ending points of a time period.
|
|
87
|
+
|
|
88
|
+
Attributes:
|
|
89
|
+
start (DBpediaModel): The starting entity of the time period.
|
|
90
|
+
end (DBpediaModel): The ending entity of the time period.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
def __init__(self, data: dict):
|
|
94
|
+
self.start: DBpediaModel = DBpediaModel(data["start"]) if "start" in data else None
|
|
95
|
+
self.end: DBpediaModel = DBpediaModel(data["end"]) if "end" in data else None
|
|
96
|
+
|
|
97
|
+
def __repr__(self):
|
|
98
|
+
return f"EdgeModel(start={self.start}, end={self.end})"
|
|
99
|
+
|
|
100
|
+
def serialize(self, indent: str = ""):
|
|
101
|
+
start = self.start.serialize("\t")
|
|
102
|
+
end = self.end.serialize("\t")
|
|
103
|
+
|
|
104
|
+
return (f"{indent}Start time:\n{start}\n"
|
|
105
|
+
f"{indent}End time:\n{end}")
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import unicodedata
|
|
4
|
+
|
|
5
|
+
from spacy import Language
|
|
6
|
+
from spacy.tokens import Doc, Span
|
|
7
|
+
|
|
8
|
+
from temporal_normalization.commons.temporal_models import TemporalExpression
|
|
9
|
+
from temporal_normalization.process.java_process import start_process
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
@Language.factory("temporal_normalization")
|
|
13
|
+
def create_normalized_component(nlp, name):
|
|
14
|
+
return TemporalNormalization(nlp, name)
|
|
15
|
+
except AttributeError:
|
|
16
|
+
# spaCy 2.x
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TemporalNormalization:
|
|
21
|
+
__FIELD = "normalized"
|
|
22
|
+
|
|
23
|
+
def __init__(self, nlp: Language, name: str):
|
|
24
|
+
Span.set_extension("normalized", default=None, force=True)
|
|
25
|
+
self.nlp = nlp
|
|
26
|
+
|
|
27
|
+
def __call__(self, doc: Doc) -> Doc:
|
|
28
|
+
jar_path = os.path.join(
|
|
29
|
+
os.path.dirname(__file__),
|
|
30
|
+
'libs/temporal-normalization-1.6.jar'
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
expressions: list[TemporalExpression] = []
|
|
34
|
+
start_process(doc, expressions, jar_path)
|
|
35
|
+
str_matches: list[str] = _prepare_str_patterns(expressions)
|
|
36
|
+
|
|
37
|
+
_retokenize(doc, str_matches, expressions)
|
|
38
|
+
|
|
39
|
+
return doc
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _prepare_str_patterns(expressions: list[TemporalExpression]) -> list[str]:
|
|
43
|
+
matches: list[str] = []
|
|
44
|
+
|
|
45
|
+
for expression in expressions:
|
|
46
|
+
for match in expression.matches:
|
|
47
|
+
matches.append(match)
|
|
48
|
+
|
|
49
|
+
return matches
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _retokenize(doc: Doc, str_matches: list[str], expressions: list[TemporalExpression]) -> None:
|
|
53
|
+
regex_matches: list[str] = [fr"{item}" for item in str_matches]
|
|
54
|
+
pattern = f"({'|'.join(regex_matches)})"
|
|
55
|
+
matches = list(re.finditer(pattern, remove_accents(doc.text), re.IGNORECASE)) if len(regex_matches) > 0 else []
|
|
56
|
+
|
|
57
|
+
with doc.retokenize() as retokenizer:
|
|
58
|
+
for match in matches:
|
|
59
|
+
start_char, end_char = match.start(), match.end()
|
|
60
|
+
start_token, end_token = None, None
|
|
61
|
+
|
|
62
|
+
for token in doc:
|
|
63
|
+
if token.idx == start_char:
|
|
64
|
+
start_token = token.i
|
|
65
|
+
if token.idx + len(token.text) == end_char:
|
|
66
|
+
end_token = token.i
|
|
67
|
+
|
|
68
|
+
if start_token is not None and end_token is not None:
|
|
69
|
+
entity = Span(doc, start_token, end_token + 1, label="DATETIME")
|
|
70
|
+
expression = next((item for item in expressions if remove_accents(entity.text) in item.matches), None)
|
|
71
|
+
|
|
72
|
+
if expression:
|
|
73
|
+
entity._.set("normalized", expression)
|
|
74
|
+
|
|
75
|
+
retokenizer.merge(entity)
|
|
76
|
+
else:
|
|
77
|
+
print(f"Warning: Could not find tokens for match '{match.group()}' at {start_char}-{end_char}")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def remove_accents(input_str):
|
|
81
|
+
# Normalize the input string to NFD form (decomposed)
|
|
82
|
+
nfkd_form = unicodedata.normalize('NFD', input_str)
|
|
83
|
+
# Filter out characters that are combining accents (category 'Mn' stands for Non-spacing Mark)
|
|
84
|
+
return ''.join([c for c in nfkd_form if unicodedata.category(c) != 'Mn'])
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
if __name__ == "__main__":
|
|
88
|
+
pass
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .java_process import *
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import shutil
|
|
3
|
+
import subprocess
|
|
4
|
+
|
|
5
|
+
from py4j.java_gateway import JavaGateway
|
|
6
|
+
from py4j.protocol import Py4JNetworkError
|
|
7
|
+
from spacy.tokens import Doc
|
|
8
|
+
|
|
9
|
+
from temporal_normalization.commons.print_utils import console
|
|
10
|
+
from temporal_normalization.commons.temporal_models import TemporalExpression
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def start_process(doc: Doc, expressions: list[TemporalExpression], jar_path):
|
|
14
|
+
check_java_version()
|
|
15
|
+
|
|
16
|
+
java_process = subprocess.Popen(
|
|
17
|
+
["java", "-jar", jar_path],
|
|
18
|
+
stdout=subprocess.PIPE,
|
|
19
|
+
stderr=subprocess.PIPE,
|
|
20
|
+
text=True
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
for line in java_process.stdout:
|
|
24
|
+
if "Gateway Server Started..." in line:
|
|
25
|
+
print(line.strip())
|
|
26
|
+
break
|
|
27
|
+
|
|
28
|
+
gateway = gateway_conn(doc, expressions)
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
# Proper way to shut down Py4J
|
|
32
|
+
gateway.shutdown()
|
|
33
|
+
print("Python connection closed.")
|
|
34
|
+
except Py4JNetworkError:
|
|
35
|
+
print("Java process already shut down.")
|
|
36
|
+
|
|
37
|
+
# Terminate Java process
|
|
38
|
+
java_process.terminate()
|
|
39
|
+
print("Java server is shutting down...")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def gateway_conn(doc: Doc, expressions: list[TemporalExpression]) -> JavaGateway:
|
|
43
|
+
"""Connect to the running Py4J Gateway"""
|
|
44
|
+
|
|
45
|
+
gateway = JavaGateway()
|
|
46
|
+
print("Python connection established.")
|
|
47
|
+
|
|
48
|
+
if isinstance(doc, Doc):
|
|
49
|
+
java_object = gateway.jvm.ro.webdata.normalization.timespan.ro.TimeExpression(doc.text, False, "\n")
|
|
50
|
+
time_expression = TemporalExpression(java_object)
|
|
51
|
+
|
|
52
|
+
if time_expression.is_valid:
|
|
53
|
+
expressions.append(time_expression)
|
|
54
|
+
|
|
55
|
+
return gateway
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def check_java_version():
|
|
59
|
+
min_version = 11
|
|
60
|
+
java_path = shutil.which("java")
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
if java_path:
|
|
64
|
+
# Run the command to check the Java version
|
|
65
|
+
result = subprocess.run([java_path, "-version"], capture_output=True, text=True)
|
|
66
|
+
|
|
67
|
+
# Print the version information (Java version is printed to stderr)
|
|
68
|
+
if result.returncode == 0:
|
|
69
|
+
version_output = result.stderr
|
|
70
|
+
match = re.search(r'version "(\d+\.\d+)', version_output)
|
|
71
|
+
|
|
72
|
+
if match:
|
|
73
|
+
crr_version = float(match.group(1))
|
|
74
|
+
if crr_version < min_version:
|
|
75
|
+
console.error(f"Java {crr_version} is installed, but version {min_version} is required.")
|
|
76
|
+
else:
|
|
77
|
+
console.error("Could not extract Java version.")
|
|
78
|
+
else:
|
|
79
|
+
console.error("Error occurred while checking the version.")
|
|
80
|
+
else:
|
|
81
|
+
console.error("Java not found.")
|
|
82
|
+
except Exception as e:
|
|
83
|
+
console.error(e.__str__())
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
if __name__ == "__main__":
|
|
87
|
+
pass
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: temporal_normalization_spacy
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A spaCy plugin for identifying and parsing historical data in Romanian texts
|
|
5
|
+
Home-page: https://github.com/iliedorobat/timespan-normalization-spacy
|
|
6
|
+
Author: Ilie Cristian Dorobat
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.6
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: spacy>=3.0
|
|
14
|
+
Requires-Dist: py4j
|
|
15
|
+
Requires-Dist: langdetect
|
|
16
|
+
Dynamic: author
|
|
17
|
+
Dynamic: classifier
|
|
18
|
+
Dynamic: description
|
|
19
|
+
Dynamic: description-content-type
|
|
20
|
+
Dynamic: home-page
|
|
21
|
+
Dynamic: requires-dist
|
|
22
|
+
Dynamic: requires-python
|
|
23
|
+
Dynamic: summary
|
|
24
|
+
|
|
25
|
+
# Temporal Expressions Normalization spaCy (TeNs)
|
|
26
|
+
|
|
27
|
+
<b>Temporal Expressions Normalization spaCy (TeNs)</b> is a powerful pipeline component for spaCy that seamlessly
|
|
28
|
+
identifies and parses date entities in text. It leverages the <b>[Temporal Normalization Framework](
|
|
29
|
+
https://github.com/iliedorobat/timespan-normalization)</b> to recognize a wide variety of date formats using an
|
|
30
|
+
extensive set of regular expressions (RegEx), ensuring robust and adaptable date extraction across diverse
|
|
31
|
+
textual sources.
|
|
32
|
+
|
|
33
|
+
Unlike conventional solutions that primarily focus on well-structured date formats, TeNs excels in handling
|
|
34
|
+
real-world text by <b>identifying</b> not only standard date representations but also <b>abbreviated, informal, or even
|
|
35
|
+
misspelled temporal expressions.</b> This makes it particularly effective for processing noisy or unstructured data,
|
|
36
|
+
such as historical records, user-generated content, and scanned documents with OCR inaccuracies.
|
|
37
|
+
|
|
38
|
+
Moreover, TeNs is designed to <b>integrate seamlessly into existing NLP pipelines,</b> allowing for enhanced temporal
|
|
39
|
+
information processing in tasks such as event extraction, timeline construction, and knowledge graph population.
|
|
40
|
+
By providing a flexible and accurate approach to temporal data normalization, it significantly improves the
|
|
41
|
+
quality and reliability of date-related information extracted from text.
|
|
42
|
+
|
|
43
|
+
<b>Table I.</b> Types of temporal expressions which can be processed
|
|
44
|
+
<table>
|
|
45
|
+
<tr>
|
|
46
|
+
<th>Type of Temporal Expressions</th>
|
|
47
|
+
<th>Examples of Temporal Expressions*</th>
|
|
48
|
+
</tr>
|
|
49
|
+
<tr>
|
|
50
|
+
<td>dates</td>
|
|
51
|
+
<td>
|
|
52
|
+
YMD: 1881-08-31; 1857 mai 10; etc.<br/>
|
|
53
|
+
DMY: 09.11.1518; 1 noiembrie 1624; etc.<br/>
|
|
54
|
+
MY: ianuarie 632; etc.
|
|
55
|
+
</td>
|
|
56
|
+
</tr>
|
|
57
|
+
<tr>
|
|
58
|
+
<td>timespans</td>
|
|
59
|
+
<td>
|
|
60
|
+
centuries: s:; sc; se.; sec; sec.; secol; secolele; secolul; sex.<br/>
|
|
61
|
+
millenniums: mil; mil.; mileniul; mileniului; mileniile
|
|
62
|
+
</td>
|
|
63
|
+
</tr>
|
|
64
|
+
<tr>
|
|
65
|
+
<td>years</td>
|
|
66
|
+
<td>77; 78; 1652; [1873]; aproximativ 1834; cca. 1420; etc.</td>
|
|
67
|
+
</tr>
|
|
68
|
+
</table>
|
|
69
|
+
* The values are mentioned in the reference language – Romanian language
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
## Getting started
|
|
74
|
+
To integrate TeNs into spaCy pipelines you need the following:
|
|
75
|
+
|
|
76
|
+
### Prerequisites
|
|
77
|
+
- Python 3.x
|
|
78
|
+
- JRE 11+
|
|
79
|
+
- spaCy 3.x
|
|
80
|
+
- py4j 0.10.9.9
|
|
81
|
+
- langdetect 1.0.9
|
|
82
|
+
|
|
83
|
+
1. Install python packages:
|
|
84
|
+
```bash
|
|
85
|
+
pip install -r requirements.txt
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
2. Install [JDK 11](https://www.oracle.com/nl/java/technologies/javase/jdk11-archive-downloads.html) or [OpenJDK 11](https://openjdk.org/install/) (or newer versions)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
### Install
|
|
92
|
+
```bash
|
|
93
|
+
pip install temporal-spacy
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Supported languages
|
|
97
|
+
- [Romanian](https://universaldependencies.org/tagset-conversion/ro-multext-uposf.html)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
## Usage
|
|
102
|
+
***
|
|
103
|
+
|
|
104
|
+
### Importing Modules & Defining Constants
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
import subprocess
|
|
108
|
+
|
|
109
|
+
import spacy
|
|
110
|
+
|
|
111
|
+
from temporal_normalization.commons.print_utils import console
|
|
112
|
+
from temporal_normalization.index import create_normalized_component, TemporalNormalization ## noqa: F401
|
|
113
|
+
|
|
114
|
+
LANG = "ro"
|
|
115
|
+
MODEL = "ro_core_news_sm"
|
|
116
|
+
TEXT_RO = ("Sec al II-lea a.ch. a fost o perioadă de mari schimbări. "
|
|
117
|
+
"În secolul XX, tehnologia a avansat semnificativ. "
|
|
118
|
+
"Sec. 21 este adesea asociat cu globalizarea rapidă.")
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Adding the Component to spaCy Pipeline
|
|
122
|
+
```python
|
|
123
|
+
# Display a warning if the language of the text is not Romanian.
|
|
124
|
+
console.lang_warning(TEXT_RO, target_lang=LANG)
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
# Load the spaCy model if it has already been downloaded
|
|
128
|
+
nlp = spacy.load(MODEL)
|
|
129
|
+
except OSError:
|
|
130
|
+
console.warning(f'Started downloading {MODEL}...')
|
|
131
|
+
# Download the Romanian model if it wasn't already downloaded
|
|
132
|
+
subprocess.run(["python", "-m", "spacy", "download", MODEL])
|
|
133
|
+
# Load the spaCy model
|
|
134
|
+
nlp = spacy.load(MODEL)
|
|
135
|
+
|
|
136
|
+
# Add "temporal_normalization" component to the spaCy pipeline
|
|
137
|
+
nlp.add_pipe("temporal_normalization", last=True)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Processing Text with the Pipeline
|
|
141
|
+
```python
|
|
142
|
+
doc = nlp(TEXT_RO)
|
|
143
|
+
|
|
144
|
+
# Display NLP-specific linguistic annotations
|
|
145
|
+
console.tokens_table(doc)
|
|
146
|
+
print()
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Accessing the Parsed Temporal Expressions
|
|
150
|
+
```python
|
|
151
|
+
# Display information about the identified and normalized dates in the text.
|
|
152
|
+
for entity in doc.ents:
|
|
153
|
+
for edge in entity._.normalized.edges:
|
|
154
|
+
print(edge.serialize())
|
|
155
|
+
print()
|
|
156
|
+
|
|
157
|
+
print("Periods:")
|
|
158
|
+
for period in entity._.normalized.periods:
|
|
159
|
+
print(period.serialize("\t"))
|
|
160
|
+
print()
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Result
|
|
164
|
+
#### First Sentence
|
|
165
|
+
```text
|
|
166
|
+
Start time:
|
|
167
|
+
Matched value: Sec al II-lea a.ch.
|
|
168
|
+
Matched Type: century
|
|
169
|
+
Normalized label: 2nd century BC
|
|
170
|
+
DBpedia uri: https://dbpedia.org/page/2nd_century_BC
|
|
171
|
+
End time:
|
|
172
|
+
Matched value: Sec al II-lea a.ch.
|
|
173
|
+
Matched Type: century
|
|
174
|
+
Normalized label: 2nd century BC
|
|
175
|
+
DBpedia uri: https://dbpedia.org/page/2nd_century_BC
|
|
176
|
+
|
|
177
|
+
Periods:
|
|
178
|
+
Matched value: Sec al II-lea a.ch.
|
|
179
|
+
Matched Type: century
|
|
180
|
+
Normalized label: 1st millennium BC
|
|
181
|
+
DBpedia uri: https://dbpedia.org/page/1st_millennium_BC
|
|
182
|
+
|
|
183
|
+
Matched value: Sec al II-lea a.ch.
|
|
184
|
+
Matched Type: century
|
|
185
|
+
Normalized label: 2nd century BC
|
|
186
|
+
DBpedia uri: https://dbpedia.org/page/2nd_century_BC
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
#### Second Sentence
|
|
190
|
+
```text
|
|
191
|
+
Start time:
|
|
192
|
+
Matched value: secolul XX
|
|
193
|
+
Matched Type: century
|
|
194
|
+
Normalized label: 20th century
|
|
195
|
+
DBpedia uri: https://dbpedia.org/page/20th_century
|
|
196
|
+
End time:
|
|
197
|
+
Matched value: secolul XX
|
|
198
|
+
Matched Type: century
|
|
199
|
+
Normalized label: 20th century
|
|
200
|
+
DBpedia uri: https://dbpedia.org/page/20th_century
|
|
201
|
+
|
|
202
|
+
Periods:
|
|
203
|
+
Matched value: secolul XX
|
|
204
|
+
Matched Type: century
|
|
205
|
+
Normalized label: 2nd millennium
|
|
206
|
+
DBpedia uri: https://dbpedia.org/page/2nd_millennium
|
|
207
|
+
|
|
208
|
+
Matched value: secolul XX
|
|
209
|
+
Matched Type: century
|
|
210
|
+
Normalized label: 20th century
|
|
211
|
+
DBpedia uri: https://dbpedia.org/page/20th_century
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
#### Third Sentence
|
|
215
|
+
```text
|
|
216
|
+
Start time:
|
|
217
|
+
Matched value: Sec. 21
|
|
218
|
+
Matched Type: century
|
|
219
|
+
Normalized label: 21st century
|
|
220
|
+
DBpedia uri: https://dbpedia.org/page/21st_century
|
|
221
|
+
End time:
|
|
222
|
+
Matched value: Sec. 21
|
|
223
|
+
Matched Type: century
|
|
224
|
+
Normalized label: 21st century
|
|
225
|
+
DBpedia uri: https://dbpedia.org/page/21st_century
|
|
226
|
+
|
|
227
|
+
Periods:
|
|
228
|
+
Matched value: Sec. 21
|
|
229
|
+
Matched Type: century
|
|
230
|
+
Normalized label: 3rd millennium
|
|
231
|
+
DBpedia uri: https://dbpedia.org/page/3rd_millennium
|
|
232
|
+
|
|
233
|
+
Matched value: Sec. 21
|
|
234
|
+
Matched Type: century
|
|
235
|
+
Normalized label: 21st century
|
|
236
|
+
DBpedia uri: https://dbpedia.org/page/21st_century
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
## Publications
|
|
242
|
+
ECAI 2021: [The Power of Regular Expressions in Recognizing Dates and Epochs (2021)](https://ieeexplore.ieee.org/document/9515139)
|
|
243
|
+
```
|
|
244
|
+
@inproceedings{9515139,
|
|
245
|
+
author={Dorobăț, Ilie Cristian and Posea, Vlad},
|
|
246
|
+
booktitle={2021 13th International Conference on Electronics, Computers and Artificial Intelligence (ECAI)},
|
|
247
|
+
title={The Power of Regular Expressions in Recognizing Dates and Epochs},
|
|
248
|
+
year={2021},
|
|
249
|
+
pages={1-3},
|
|
250
|
+
doi={10.1109/ECAI52376.2021.9515139}}
|
|
251
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
setup.py
|
|
5
|
+
temporal_normalization/__init__.py
|
|
6
|
+
temporal_normalization/index.py
|
|
7
|
+
temporal_normalization/commons/__init__.py
|
|
8
|
+
temporal_normalization/commons/print_utils.py
|
|
9
|
+
temporal_normalization/commons/temporal_models.py
|
|
10
|
+
temporal_normalization/commons/temporal_types.py
|
|
11
|
+
temporal_normalization/libs/temporal-normalization-1.6.jar
|
|
12
|
+
temporal_normalization/process/__init__.py
|
|
13
|
+
temporal_normalization/process/java_process.py
|
|
14
|
+
temporal_normalization_spacy.egg-info/PKG-INFO
|
|
15
|
+
temporal_normalization_spacy.egg-info/SOURCES.txt
|
|
16
|
+
temporal_normalization_spacy.egg-info/dependency_links.txt
|
|
17
|
+
temporal_normalization_spacy.egg-info/requires.txt
|
|
18
|
+
temporal_normalization_spacy.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
temporal_normalization
|