chemdataextractor-lite 1.0.0__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.
- chemdataextractor/__init__.py +13 -0
- chemdataextractor/data/README.md +3 -0
- chemdataextractor/data/punkt_chem-1.0.pickle +0 -0
- chemdataextractor/doc/__init__.py +17 -0
- chemdataextractor/doc/document.py +168 -0
- chemdataextractor/doc/element.py +59 -0
- chemdataextractor/doc/figure.py +14 -0
- chemdataextractor/doc/meta.py +30 -0
- chemdataextractor/doc/table.py +36 -0
- chemdataextractor/doc/text.py +224 -0
- chemdataextractor/errors.py +9 -0
- chemdataextractor/nlp/__init__.py +8 -0
- chemdataextractor/nlp/tokenize.py +1021 -0
- chemdataextractor/reader/__init__.py +46 -0
- chemdataextractor/reader/_helpers.py +67 -0
- chemdataextractor/reader/acs.py +42 -0
- chemdataextractor/reader/base.py +40 -0
- chemdataextractor/reader/cssp.py +45 -0
- chemdataextractor/reader/elsevier.py +247 -0
- chemdataextractor/reader/markup.py +359 -0
- chemdataextractor/reader/nlm.py +63 -0
- chemdataextractor/reader/pdf.py +145 -0
- chemdataextractor/reader/plaintext.py +35 -0
- chemdataextractor/reader/rsc.py +93 -0
- chemdataextractor/reader/springer.py +122 -0
- chemdataextractor/reader/springer_jats.py +88 -0
- chemdataextractor/reader/uspto.py +159 -0
- chemdataextractor/scrape/__init__.py +15 -0
- chemdataextractor/scrape/clean.py +218 -0
- chemdataextractor/scrape/csstranslator.py +120 -0
- chemdataextractor/text/__init__.py +375 -0
- chemdataextractor_lite-1.0.0.dist-info/METADATA +72 -0
- chemdataextractor_lite-1.0.0.dist-info/RECORD +36 -0
- chemdataextractor_lite-1.0.0.dist-info/WHEEL +5 -0
- chemdataextractor_lite-1.0.0.dist-info/licenses/LICENSE +50 -0
- chemdataextractor_lite-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Lightweight scientific document loading."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from .doc.document import Document
|
|
6
|
+
|
|
7
|
+
__title__ = "ChemDataExtractor Lite"
|
|
8
|
+
__version__ = "1.0.0"
|
|
9
|
+
__license__ = "MIT"
|
|
10
|
+
|
|
11
|
+
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
|
12
|
+
|
|
13
|
+
__all__ = ["Document"]
|
|
Binary file
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Structural document model."""
|
|
2
|
+
|
|
3
|
+
from .document import Document
|
|
4
|
+
from .element import BaseElement, CaptionedElement
|
|
5
|
+
from .figure import Figure
|
|
6
|
+
from .meta import MetaData
|
|
7
|
+
from .table import Table
|
|
8
|
+
from .text import (
|
|
9
|
+
Caption, Cell, Citation, Footnote, Heading, Paragraph, RichToken, Sentence,
|
|
10
|
+
Span, Text, Title, Token,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"BaseElement", "Caption", "CaptionedElement", "Cell", "Citation", "Document",
|
|
15
|
+
"Figure", "Footnote", "Heading", "MetaData", "Paragraph", "RichToken",
|
|
16
|
+
"Sentence", "Span", "Table", "Text", "Title", "Token",
|
|
17
|
+
]
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""Document container and reader dispatch."""
|
|
2
|
+
|
|
3
|
+
import collections
|
|
4
|
+
import io
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from ..errors import ReaderError
|
|
10
|
+
from ..text import get_encoding
|
|
11
|
+
from .element import CaptionedElement
|
|
12
|
+
from .figure import Figure
|
|
13
|
+
from .meta import MetaData
|
|
14
|
+
from .table import Table
|
|
15
|
+
from .text import Caption, Cell, Citation, Footnote, Heading, Paragraph, Sentence, Title
|
|
16
|
+
|
|
17
|
+
log = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Document(collections.abc.Sequence):
|
|
21
|
+
"""A format-neutral sequence of structural document elements."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, *elements):
|
|
24
|
+
self._elements = []
|
|
25
|
+
for element in elements:
|
|
26
|
+
if isinstance(element, str):
|
|
27
|
+
element = Paragraph(element)
|
|
28
|
+
elif isinstance(element, bytes):
|
|
29
|
+
encoding = get_encoding(element)
|
|
30
|
+
log.warning("Guessed bytestring encoding as %s", encoding)
|
|
31
|
+
element = Paragraph(element.decode(encoding))
|
|
32
|
+
element.document = self
|
|
33
|
+
self._elements.append(element)
|
|
34
|
+
|
|
35
|
+
def __repr__(self):
|
|
36
|
+
return f"<Document: {len(self)} elements>"
|
|
37
|
+
|
|
38
|
+
__str__ = __repr__
|
|
39
|
+
|
|
40
|
+
def __getitem__(self, index):
|
|
41
|
+
return self.elements[index]
|
|
42
|
+
|
|
43
|
+
def __len__(self):
|
|
44
|
+
return len(self.elements)
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def elements(self):
|
|
48
|
+
return self._elements
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def from_file(cls, file, fname=None, readers=None):
|
|
52
|
+
if isinstance(file, (str, bytes, Path)):
|
|
53
|
+
path = Path(file)
|
|
54
|
+
with path.open("rb") as stream:
|
|
55
|
+
return cls.from_string(stream.read(), fname=fname or str(path), readers=readers)
|
|
56
|
+
if fname is None and hasattr(file, "name"):
|
|
57
|
+
fname = file.name
|
|
58
|
+
return cls.from_string(file.read(), fname=fname, readers=readers)
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def from_string(cls, content, fname=None, readers=None):
|
|
62
|
+
if isinstance(content, str):
|
|
63
|
+
content = content.encode("utf-8")
|
|
64
|
+
if not isinstance(content, bytes):
|
|
65
|
+
raise TypeError("content must be bytes or str")
|
|
66
|
+
if readers is None:
|
|
67
|
+
from ..reader import DEFAULT_READERS
|
|
68
|
+
readers = DEFAULT_READERS
|
|
69
|
+
errors = []
|
|
70
|
+
for reader in readers:
|
|
71
|
+
try:
|
|
72
|
+
if not reader.detect(content, fname=fname):
|
|
73
|
+
continue
|
|
74
|
+
document = reader.readstring(content)
|
|
75
|
+
log.debug("Parsed document with %s", reader.__class__.__name__)
|
|
76
|
+
return document
|
|
77
|
+
except ReaderError as error:
|
|
78
|
+
errors.append(error)
|
|
79
|
+
message = "Unable to read document"
|
|
80
|
+
if errors:
|
|
81
|
+
message += f": {errors[-1]}"
|
|
82
|
+
raise ReaderError(message)
|
|
83
|
+
|
|
84
|
+
def get_element_with_id(self, id):
|
|
85
|
+
pending = list(self.elements)
|
|
86
|
+
while pending:
|
|
87
|
+
element = pending.pop(0)
|
|
88
|
+
if element.id == id:
|
|
89
|
+
return element
|
|
90
|
+
if element.elements:
|
|
91
|
+
pending.extend(element.elements)
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
def _elements_of_type(self, element_type):
|
|
95
|
+
return [element for element in self.elements if isinstance(element, element_type)]
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def figures(self):
|
|
99
|
+
return self._elements_of_type(Figure)
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def tables(self):
|
|
103
|
+
return self._elements_of_type(Table)
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def citations(self):
|
|
107
|
+
return self._elements_of_type(Citation)
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def footnotes(self):
|
|
111
|
+
return self._elements_of_type(Footnote)
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def titles(self):
|
|
115
|
+
return self._elements_of_type(Title)
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def headings(self):
|
|
119
|
+
return self._elements_of_type(Heading)
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def paragraphs(self):
|
|
123
|
+
return self._elements_of_type(Paragraph)
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def captions(self):
|
|
127
|
+
return self._elements_of_type(Caption)
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def captioned_elements(self):
|
|
131
|
+
return self._elements_of_type(CaptionedElement)
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def metadata(self):
|
|
135
|
+
metadata = self._elements_of_type(MetaData)
|
|
136
|
+
return metadata[0] if metadata else None
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def sentences(self):
|
|
140
|
+
pending = list(self.elements)
|
|
141
|
+
sentences = []
|
|
142
|
+
while pending:
|
|
143
|
+
element = pending.pop(0)
|
|
144
|
+
if isinstance(element, Sentence) and not isinstance(element, Cell):
|
|
145
|
+
sentences.append(element)
|
|
146
|
+
elif element.elements:
|
|
147
|
+
pending[0:0] = element.elements
|
|
148
|
+
return sentences
|
|
149
|
+
|
|
150
|
+
def heading_for_sentence(self, sentence):
|
|
151
|
+
current_heading = None
|
|
152
|
+
for element in self.elements:
|
|
153
|
+
if isinstance(element, Heading):
|
|
154
|
+
current_heading = element
|
|
155
|
+
continue
|
|
156
|
+
if sentence in (element.elements or []):
|
|
157
|
+
return current_heading
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
def serialize(self):
|
|
161
|
+
return {"type": "document", "elements": [element.serialize() for element in self.elements]}
|
|
162
|
+
|
|
163
|
+
def to_json(self, *args, **kwargs):
|
|
164
|
+
return json.dumps(self.serialize(), *args, **kwargs)
|
|
165
|
+
|
|
166
|
+
def _repr_html_(self):
|
|
167
|
+
body = "\n".join(element._repr_html_() for element in self.elements if hasattr(element, "_repr_html_"))
|
|
168
|
+
return f'<div class="cde-document">\n{body}\n</div>'
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Base classes for document elements."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from abc import ABCMeta, abstractmethod
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BaseElement(metaclass=ABCMeta):
|
|
8
|
+
def __init__(self, document=None, references=None, id=None, **kwargs):
|
|
9
|
+
self._document = document
|
|
10
|
+
self.id = id
|
|
11
|
+
self.references = list(references or [])
|
|
12
|
+
|
|
13
|
+
def __repr__(self):
|
|
14
|
+
return f"<{self.__class__.__name__}>"
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def document(self):
|
|
18
|
+
return self._document
|
|
19
|
+
|
|
20
|
+
@document.setter
|
|
21
|
+
def document(self, document):
|
|
22
|
+
self._document = document
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def elements(self):
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def serialize(self):
|
|
30
|
+
raise NotImplementedError
|
|
31
|
+
|
|
32
|
+
def to_json(self, *args, **kwargs):
|
|
33
|
+
return json.dumps(self.serialize(), *args, **kwargs)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class CaptionedElement(BaseElement):
|
|
37
|
+
def __init__(self, caption, label=None, **kwargs):
|
|
38
|
+
self.caption = caption
|
|
39
|
+
self.label = label
|
|
40
|
+
super().__init__(**kwargs)
|
|
41
|
+
self.caption.document = self.document
|
|
42
|
+
|
|
43
|
+
def __str__(self):
|
|
44
|
+
return self.caption.text
|
|
45
|
+
|
|
46
|
+
@BaseElement.document.setter
|
|
47
|
+
def document(self, document):
|
|
48
|
+
self._document = document
|
|
49
|
+
self.caption.document = document
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def elements(self):
|
|
53
|
+
return [self.caption]
|
|
54
|
+
|
|
55
|
+
def serialize(self):
|
|
56
|
+
data = {"type": self.__class__.__name__, "caption": self.caption.serialize()}
|
|
57
|
+
if self.label is not None:
|
|
58
|
+
data["label"] = self.label
|
|
59
|
+
return data
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Figure document element."""
|
|
2
|
+
|
|
3
|
+
from .element import CaptionedElement
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Figure(CaptionedElement):
|
|
7
|
+
def __init__(self, caption, label=None, links=None, **kwargs):
|
|
8
|
+
super().__init__(caption=caption, label=label, **kwargs)
|
|
9
|
+
self.links = list(links or [])
|
|
10
|
+
|
|
11
|
+
def serialize(self):
|
|
12
|
+
data = super().serialize()
|
|
13
|
+
data["links"] = self.links
|
|
14
|
+
return data
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Bibliographic metadata document element."""
|
|
2
|
+
|
|
3
|
+
from .element import BaseElement
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MetaData(BaseElement):
|
|
7
|
+
FIELDS = (
|
|
8
|
+
"title", "authors", "publisher", "journal", "volume", "issue",
|
|
9
|
+
"firstpage", "lastpage", "doi", "date", "language", "pdf_url", "html_url",
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
def __init__(self, data, **kwargs):
|
|
13
|
+
super().__init__(**kwargs)
|
|
14
|
+
normalized = {key.lstrip("_"): value for key, value in data.items()}
|
|
15
|
+
self._data = {key: normalized.get(key) for key in self.FIELDS}
|
|
16
|
+
|
|
17
|
+
def __repr__(self):
|
|
18
|
+
return repr({key: value for key, value in self.data.items() if value})
|
|
19
|
+
|
|
20
|
+
def __getattr__(self, name):
|
|
21
|
+
if name in self.FIELDS:
|
|
22
|
+
return self._data[name]
|
|
23
|
+
raise AttributeError(name)
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def data(self):
|
|
27
|
+
return dict(self._data)
|
|
28
|
+
|
|
29
|
+
def serialize(self):
|
|
30
|
+
return {"MetaData": {key: value for key, value in self._data.items() if value}}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Structural table document element."""
|
|
2
|
+
|
|
3
|
+
from .element import CaptionedElement
|
|
4
|
+
from .text import Cell
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Table(CaptionedElement):
|
|
8
|
+
def __init__(self, caption, label=None, table_data=None, footnotes=None, **kwargs):
|
|
9
|
+
super().__init__(caption=caption, label=label, **kwargs)
|
|
10
|
+
self.table_data = [
|
|
11
|
+
[cell if isinstance(cell, Cell) else Cell(str(cell)) for cell in row]
|
|
12
|
+
for row in (table_data or [])
|
|
13
|
+
]
|
|
14
|
+
self.footnotes = list(footnotes or [])
|
|
15
|
+
self.document = self.document
|
|
16
|
+
|
|
17
|
+
@CaptionedElement.document.setter
|
|
18
|
+
def document(self, document):
|
|
19
|
+
self._document = document
|
|
20
|
+
self.caption.document = document
|
|
21
|
+
for row in getattr(self, "table_data", []):
|
|
22
|
+
for cell in row:
|
|
23
|
+
cell.document = document
|
|
24
|
+
for footnote in getattr(self, "footnotes", []):
|
|
25
|
+
footnote.document = document
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def elements(self):
|
|
29
|
+
return [cell for row in self.table_data for cell in row] + [self.caption] + self.footnotes
|
|
30
|
+
|
|
31
|
+
def serialize(self):
|
|
32
|
+
data = super().serialize()
|
|
33
|
+
data["rows"] = [[cell.text for cell in row] for row in self.table_data]
|
|
34
|
+
if self.footnotes:
|
|
35
|
+
data["footnotes"] = [footnote.serialize() for footnote in self.footnotes]
|
|
36
|
+
return data
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"""Text document elements with sentence and word segmentation."""
|
|
2
|
+
|
|
3
|
+
import collections
|
|
4
|
+
import unicodedata
|
|
5
|
+
|
|
6
|
+
from ..nlp.tokenize import ChemSentenceTokenizer, ChemWordTokenizer
|
|
7
|
+
from .element import BaseElement
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Span:
|
|
11
|
+
def __init__(self, text, start, end):
|
|
12
|
+
self.text = text
|
|
13
|
+
self.start = start
|
|
14
|
+
self.end = end
|
|
15
|
+
|
|
16
|
+
def __repr__(self):
|
|
17
|
+
return f"{self.__class__.__name__}({self.text!r}, {self.start!r}, {self.end!r})"
|
|
18
|
+
|
|
19
|
+
def __str__(self):
|
|
20
|
+
return self.text
|
|
21
|
+
|
|
22
|
+
def __eq__(self, other):
|
|
23
|
+
return (
|
|
24
|
+
isinstance(other, self.__class__)
|
|
25
|
+
and self.text == other.text
|
|
26
|
+
and self.start == other.start
|
|
27
|
+
and self.end == other.end
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
def __hash__(self):
|
|
31
|
+
return hash((self.text, self.start, self.end))
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def length(self):
|
|
35
|
+
return self.end - self.start
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Token(Span):
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Kept as an import alias for loader clients that used the old token class name.
|
|
43
|
+
RichToken = Token
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class BaseText(BaseElement):
|
|
47
|
+
def __init__(self, text, **kwargs):
|
|
48
|
+
if not isinstance(text, str):
|
|
49
|
+
raise TypeError("Text must be a unicode string")
|
|
50
|
+
super().__init__(**kwargs)
|
|
51
|
+
self._text = text
|
|
52
|
+
|
|
53
|
+
def __repr__(self):
|
|
54
|
+
return (
|
|
55
|
+
f"{self.__class__.__name__}(id={self.id!r}, "
|
|
56
|
+
f"references={self.references!r}, text={self.text!r})"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
def __str__(self):
|
|
60
|
+
return self.text
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def text(self):
|
|
64
|
+
return self._text
|
|
65
|
+
|
|
66
|
+
def serialize(self):
|
|
67
|
+
data = {"type": self.__class__.__name__, "content": self.text}
|
|
68
|
+
if self.id is not None:
|
|
69
|
+
data["id"] = self.id
|
|
70
|
+
if self.references:
|
|
71
|
+
data["references"] = self.references
|
|
72
|
+
return data
|
|
73
|
+
|
|
74
|
+
def _repr_html_(self):
|
|
75
|
+
return self.text
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class Text(collections.abc.Sequence, BaseText):
|
|
79
|
+
sentence_tokenizer = ChemSentenceTokenizer()
|
|
80
|
+
word_tokenizer = ChemWordTokenizer()
|
|
81
|
+
|
|
82
|
+
def __init__(self, text, sentence_tokenizer=None, word_tokenizer=None, **kwargs):
|
|
83
|
+
super().__init__(text, **kwargs)
|
|
84
|
+
self.sentence_tokenizer = sentence_tokenizer or self.sentence_tokenizer
|
|
85
|
+
self.word_tokenizer = word_tokenizer or self.word_tokenizer
|
|
86
|
+
self._sentences = None
|
|
87
|
+
|
|
88
|
+
def __getitem__(self, index):
|
|
89
|
+
return self.sentences[index]
|
|
90
|
+
|
|
91
|
+
def __len__(self):
|
|
92
|
+
return len(self.sentences)
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def sentences(self):
|
|
96
|
+
if self._sentences is None:
|
|
97
|
+
spans = self.sentence_tokenizer.span_tokenize(self.text)
|
|
98
|
+
self._sentences = [
|
|
99
|
+
Sentence(
|
|
100
|
+
self.text[start:end],
|
|
101
|
+
start=start,
|
|
102
|
+
end=end,
|
|
103
|
+
word_tokenizer=self.word_tokenizer,
|
|
104
|
+
document=self.document,
|
|
105
|
+
)
|
|
106
|
+
for start, end in spans
|
|
107
|
+
]
|
|
108
|
+
return self._sentences
|
|
109
|
+
|
|
110
|
+
@BaseElement.document.setter
|
|
111
|
+
def document(self, document):
|
|
112
|
+
self._document = document
|
|
113
|
+
if self._sentences is not None:
|
|
114
|
+
for sentence in self._sentences:
|
|
115
|
+
sentence.document = document
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def elements(self):
|
|
119
|
+
return self.sentences
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def raw_sentences(self):
|
|
123
|
+
return [sentence.text for sentence in self.sentences]
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def tokens(self):
|
|
127
|
+
return [sentence.tokens for sentence in self.sentences]
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def raw_tokens(self):
|
|
131
|
+
return [sentence.raw_tokens for sentence in self.sentences]
|
|
132
|
+
|
|
133
|
+
def __add__(self, other):
|
|
134
|
+
if type(self) is type(other):
|
|
135
|
+
return self.__class__(
|
|
136
|
+
self.text + other.text,
|
|
137
|
+
id=self.id or other.id,
|
|
138
|
+
references=self.references + other.references,
|
|
139
|
+
sentence_tokenizer=self.sentence_tokenizer,
|
|
140
|
+
word_tokenizer=self.word_tokenizer,
|
|
141
|
+
)
|
|
142
|
+
return NotImplemented
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class Title(Text):
|
|
146
|
+
def _repr_html_(self):
|
|
147
|
+
return f'<h1 class="cde-title">{self.text}</h1>'
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class Heading(Text):
|
|
151
|
+
def _repr_html_(self):
|
|
152
|
+
return f'<h2 class="cde-heading">{self.text}</h2>'
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class Paragraph(Text):
|
|
156
|
+
def _repr_html_(self):
|
|
157
|
+
return f'<p class="cde-paragraph">{self.text}</p>'
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class Footnote(Text):
|
|
161
|
+
pass
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class Citation(Text):
|
|
165
|
+
pass
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class Caption(Text):
|
|
169
|
+
pass
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class Sentence(collections.abc.Sequence, BaseText):
|
|
173
|
+
word_tokenizer = ChemWordTokenizer()
|
|
174
|
+
|
|
175
|
+
def __init__(self, text, start=0, end=None, word_tokenizer=None, **kwargs):
|
|
176
|
+
super().__init__(text, **kwargs)
|
|
177
|
+
self.start = start
|
|
178
|
+
self.end = len(text) if end is None else end
|
|
179
|
+
self.word_tokenizer = word_tokenizer or self.word_tokenizer
|
|
180
|
+
self._tokens = None
|
|
181
|
+
|
|
182
|
+
def __getitem__(self, index):
|
|
183
|
+
return self.tokens[index]
|
|
184
|
+
|
|
185
|
+
def __len__(self):
|
|
186
|
+
return len(self.tokens)
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def tokens(self):
|
|
190
|
+
if self._tokens is None:
|
|
191
|
+
spans = self.word_tokenizer.span_tokenize(self.text)
|
|
192
|
+
self._tokens = [
|
|
193
|
+
Token(
|
|
194
|
+
"".join(ch for ch in self.text[left:right] if unicodedata.category(ch)[0] != "C"),
|
|
195
|
+
left + self.start,
|
|
196
|
+
right + self.start,
|
|
197
|
+
)
|
|
198
|
+
for left, right in spans
|
|
199
|
+
]
|
|
200
|
+
return self._tokens
|
|
201
|
+
|
|
202
|
+
@property
|
|
203
|
+
def raw_tokens(self):
|
|
204
|
+
return [token.text for token in self.tokens]
|
|
205
|
+
|
|
206
|
+
@property
|
|
207
|
+
def elements(self):
|
|
208
|
+
return None
|
|
209
|
+
|
|
210
|
+
def __add__(self, other):
|
|
211
|
+
if type(self) is type(other):
|
|
212
|
+
return self.__class__(
|
|
213
|
+
self.text + other.text,
|
|
214
|
+
start=self.start,
|
|
215
|
+
end=self.start + len(self.text + other.text),
|
|
216
|
+
id=self.id or other.id,
|
|
217
|
+
references=self.references + other.references,
|
|
218
|
+
word_tokenizer=self.word_tokenizer,
|
|
219
|
+
)
|
|
220
|
+
return NotImplemented
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class Cell(Sentence):
|
|
224
|
+
pass
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Sentence and word segmentation utilities."""
|
|
2
|
+
|
|
3
|
+
from .tokenize import ChemSentenceTokenizer, ChemWordTokenizer, FineWordTokenizer, SentenceTokenizer, WordTokenizer
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"ChemSentenceTokenizer", "ChemWordTokenizer", "FineWordTokenizer",
|
|
7
|
+
"SentenceTokenizer", "WordTokenizer",
|
|
8
|
+
]
|