chemdataextractor-lite 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.
- chemdataextractor_lite-1.0.0/LICENSE +50 -0
- chemdataextractor_lite-1.0.0/MANIFEST.in +5 -0
- chemdataextractor_lite-1.0.0/PKG-INFO +72 -0
- chemdataextractor_lite-1.0.0/README.md +56 -0
- chemdataextractor_lite-1.0.0/chemdataextractor/__init__.py +13 -0
- chemdataextractor_lite-1.0.0/chemdataextractor/data/README.md +3 -0
- chemdataextractor_lite-1.0.0/chemdataextractor/data/punkt_chem-1.0.pickle +0 -0
- chemdataextractor_lite-1.0.0/chemdataextractor/doc/__init__.py +17 -0
- chemdataextractor_lite-1.0.0/chemdataextractor/doc/document.py +168 -0
- chemdataextractor_lite-1.0.0/chemdataextractor/doc/element.py +59 -0
- chemdataextractor_lite-1.0.0/chemdataextractor/doc/figure.py +14 -0
- chemdataextractor_lite-1.0.0/chemdataextractor/doc/meta.py +30 -0
- chemdataextractor_lite-1.0.0/chemdataextractor/doc/table.py +36 -0
- chemdataextractor_lite-1.0.0/chemdataextractor/doc/text.py +224 -0
- chemdataextractor_lite-1.0.0/chemdataextractor/errors.py +9 -0
- chemdataextractor_lite-1.0.0/chemdataextractor/nlp/__init__.py +8 -0
- chemdataextractor_lite-1.0.0/chemdataextractor/nlp/tokenize.py +1021 -0
- chemdataextractor_lite-1.0.0/chemdataextractor/reader/__init__.py +46 -0
- chemdataextractor_lite-1.0.0/chemdataextractor/reader/_helpers.py +67 -0
- chemdataextractor_lite-1.0.0/chemdataextractor/reader/acs.py +42 -0
- chemdataextractor_lite-1.0.0/chemdataextractor/reader/base.py +40 -0
- chemdataextractor_lite-1.0.0/chemdataextractor/reader/cssp.py +45 -0
- chemdataextractor_lite-1.0.0/chemdataextractor/reader/elsevier.py +247 -0
- chemdataextractor_lite-1.0.0/chemdataextractor/reader/markup.py +359 -0
- chemdataextractor_lite-1.0.0/chemdataextractor/reader/nlm.py +63 -0
- chemdataextractor_lite-1.0.0/chemdataextractor/reader/pdf.py +145 -0
- chemdataextractor_lite-1.0.0/chemdataextractor/reader/plaintext.py +35 -0
- chemdataextractor_lite-1.0.0/chemdataextractor/reader/rsc.py +93 -0
- chemdataextractor_lite-1.0.0/chemdataextractor/reader/springer.py +122 -0
- chemdataextractor_lite-1.0.0/chemdataextractor/reader/springer_jats.py +88 -0
- chemdataextractor_lite-1.0.0/chemdataextractor/reader/uspto.py +159 -0
- chemdataextractor_lite-1.0.0/chemdataextractor/scrape/__init__.py +15 -0
- chemdataextractor_lite-1.0.0/chemdataextractor/scrape/clean.py +218 -0
- chemdataextractor_lite-1.0.0/chemdataextractor/scrape/csstranslator.py +120 -0
- chemdataextractor_lite-1.0.0/chemdataextractor/text/__init__.py +375 -0
- chemdataextractor_lite-1.0.0/chemdataextractor_lite.egg-info/PKG-INFO +72 -0
- chemdataextractor_lite-1.0.0/chemdataextractor_lite.egg-info/SOURCES.txt +49 -0
- chemdataextractor_lite-1.0.0/chemdataextractor_lite.egg-info/dependency_links.txt +1 -0
- chemdataextractor_lite-1.0.0/chemdataextractor_lite.egg-info/requires.txt +7 -0
- chemdataextractor_lite-1.0.0/chemdataextractor_lite.egg-info/top_level.txt +1 -0
- chemdataextractor_lite-1.0.0/pyproject.toml +29 -0
- chemdataextractor_lite-1.0.0/setup.cfg +4 -0
- chemdataextractor_lite-1.0.0/tests/data/acs/acs.jmedchem.6b00723.html +6179 -0
- chemdataextractor_lite-1.0.0/tests/data/elsevier/j.jnoncrysol.2017.07.006.xml +2836 -0
- chemdataextractor_lite-1.0.0/tests/data/elsevier/j.jnoncrysol.2018.02.024.xml +6035 -0
- chemdataextractor_lite-1.0.0/tests/data/rsc/10.1039_C6OB02074G.html +392 -0
- chemdataextractor_lite-1.0.0/tests/data/rsc/searchresults.html +3007 -0
- chemdataextractor_lite-1.0.0/tests/data/rsc/test_paper.html +452 -0
- chemdataextractor_lite-1.0.0/tests/data/springer/spr_test1.xml +17 -0
- chemdataextractor_lite-1.0.0/tests/data/uspto/US06840965B2.xml +2135 -0
- chemdataextractor_lite-1.0.0/tests/test_lite.py +159 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
ChemDataExtractor v2 is released under the MIT license.
|
|
2
|
+
===========================================================
|
|
3
|
+
|
|
4
|
+
The MIT License
|
|
5
|
+
|
|
6
|
+
Copyright 2020 Juraj Mavračić, Callum J. Court, Taketomo Isazawa, Jacqueline M. Cole and contributors
|
|
7
|
+
|
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
of this software and associated documentation files (the “Software”), to deal
|
|
10
|
+
in the Software without restriction, including without limitation the rights
|
|
11
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
furnished to do so, subject to the following conditions:
|
|
14
|
+
|
|
15
|
+
The above copyright notice and this permission notice shall be included in
|
|
16
|
+
all copies or substantial portions of the Software.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
|
|
19
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
21
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
22
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
|
23
|
+
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
ChemDataExtractor License
|
|
28
|
+
===========================================================
|
|
29
|
+
|
|
30
|
+
The MIT License
|
|
31
|
+
|
|
32
|
+
Copyright 2017 Matt Swain and contributors
|
|
33
|
+
|
|
34
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
35
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
36
|
+
in the Software without restriction, including without limitation the rights
|
|
37
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
38
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
39
|
+
furnished to do so, subject to the following conditions:
|
|
40
|
+
|
|
41
|
+
The above copyright notice and this permission notice shall be included in
|
|
42
|
+
all copies or substantial portions of the Software.
|
|
43
|
+
|
|
44
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
45
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
46
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
47
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
48
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
49
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
50
|
+
THE SOFTWARE.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: chemdataextractor-lite
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Lightweight scientific document loaders for HTML, XML, PDF, and plain text.
|
|
5
|
+
License: MIT
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: cssselect>=1.0.1
|
|
10
|
+
Requires-Dist: lxml>=4.9
|
|
11
|
+
Requires-Dist: nltk>=3.8
|
|
12
|
+
Requires-Dist: pdfminer.six>=20220524
|
|
13
|
+
Provides-Extra: test
|
|
14
|
+
Requires-Dist: pytest>=8; extra == "test"
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# ChemDataExtractor Lite
|
|
18
|
+
|
|
19
|
+
ChemDataExtractor Lite converts scientific HTML, XML, PDF, and plain-text files into a consistent `Document` object. It keeps document structure, metadata, tables, figures, references, sentence segmentation, and chemistry-aware word segmentation without the tagging and extraction stack from ChemDataExtractor 2.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install -e .
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The chemistry-trained sentence model is included in the package, so document loading and segmentation work offline after installation.
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from chemdataextractor import Document
|
|
33
|
+
|
|
34
|
+
document = Document.from_file("article.xml")
|
|
35
|
+
|
|
36
|
+
for element in document:
|
|
37
|
+
if hasattr(element, "text"):
|
|
38
|
+
print(type(element).__name__, element.text)
|
|
39
|
+
|
|
40
|
+
for sentence in document.sentences:
|
|
41
|
+
print(sentence.text)
|
|
42
|
+
print(sentence.raw_tokens)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`Document.from_string` accepts `bytes` or Unicode text. Both loading methods accept an optional `readers` list when explicit format handling is needed.
|
|
46
|
+
|
|
47
|
+
## Supported readers
|
|
48
|
+
|
|
49
|
+
- Generic HTML and XML
|
|
50
|
+
- ACS, RSC, CSSP, and Springer HTML
|
|
51
|
+
- Elsevier, NLM/JATS, Springer JATS, and USPTO XML
|
|
52
|
+
- PDF through `pdfminer.six`
|
|
53
|
+
- Plain text
|
|
54
|
+
|
|
55
|
+
Publisher-specific readers run before generic fallbacks. Readers preserve titles, headings, paragraphs, citations, footnotes, metadata, figure captions and links, table captions, and raw table cells when the source exposes them.
|
|
56
|
+
|
|
57
|
+
## Document model
|
|
58
|
+
|
|
59
|
+
The public import namespace remains `chemdataextractor`. Structural classes are available from `chemdataextractor.doc`, and readers from `chemdataextractor.reader`.
|
|
60
|
+
|
|
61
|
+
Extraction APIs from ChemDataExtractor 2 are intentionally absent: models, parsers, records, POS/NER tagging, chemical mention detection, relation extraction, scraping clients, and model downloads are outside this package.
|
|
62
|
+
|
|
63
|
+
## Development
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
python -m pip install -e .
|
|
67
|
+
python -m pip install pytest build
|
|
68
|
+
python -m pytest
|
|
69
|
+
python -m build
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The project is released under the MIT license. The bundled `punkt_chem-1.0.pickle` sentence model is the chemistry Punkt model distributed by the ChemDataExtractor project.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# ChemDataExtractor Lite
|
|
2
|
+
|
|
3
|
+
ChemDataExtractor Lite converts scientific HTML, XML, PDF, and plain-text files into a consistent `Document` object. It keeps document structure, metadata, tables, figures, references, sentence segmentation, and chemistry-aware word segmentation without the tagging and extraction stack from ChemDataExtractor 2.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install -e .
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The chemistry-trained sentence model is included in the package, so document loading and segmentation work offline after installation.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from chemdataextractor import Document
|
|
17
|
+
|
|
18
|
+
document = Document.from_file("article.xml")
|
|
19
|
+
|
|
20
|
+
for element in document:
|
|
21
|
+
if hasattr(element, "text"):
|
|
22
|
+
print(type(element).__name__, element.text)
|
|
23
|
+
|
|
24
|
+
for sentence in document.sentences:
|
|
25
|
+
print(sentence.text)
|
|
26
|
+
print(sentence.raw_tokens)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`Document.from_string` accepts `bytes` or Unicode text. Both loading methods accept an optional `readers` list when explicit format handling is needed.
|
|
30
|
+
|
|
31
|
+
## Supported readers
|
|
32
|
+
|
|
33
|
+
- Generic HTML and XML
|
|
34
|
+
- ACS, RSC, CSSP, and Springer HTML
|
|
35
|
+
- Elsevier, NLM/JATS, Springer JATS, and USPTO XML
|
|
36
|
+
- PDF through `pdfminer.six`
|
|
37
|
+
- Plain text
|
|
38
|
+
|
|
39
|
+
Publisher-specific readers run before generic fallbacks. Readers preserve titles, headings, paragraphs, citations, footnotes, metadata, figure captions and links, table captions, and raw table cells when the source exposes them.
|
|
40
|
+
|
|
41
|
+
## Document model
|
|
42
|
+
|
|
43
|
+
The public import namespace remains `chemdataextractor`. Structural classes are available from `chemdataextractor.doc`, and readers from `chemdataextractor.reader`.
|
|
44
|
+
|
|
45
|
+
Extraction APIs from ChemDataExtractor 2 are intentionally absent: models, parsers, records, POS/NER tagging, chemical mention detection, relation extraction, scraping clients, and model downloads are outside this package.
|
|
46
|
+
|
|
47
|
+
## Development
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
python -m pip install -e .
|
|
51
|
+
python -m pip install pytest build
|
|
52
|
+
python -m pytest
|
|
53
|
+
python -m build
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The project is released under the MIT license. The bundled `punkt_chem-1.0.pickle` sentence model is the chemistry Punkt model distributed by the ChemDataExtractor project.
|
|
@@ -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"]
|
|
@@ -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
|