biotite 1.0.0__cp311-cp311-macosx_11_0_arm64.whl → 1.1.0__cp311-cp311-macosx_11_0_arm64.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.
Potentially problematic release.
This version of biotite might be problematic. Click here for more details.
- biotite/application/dssp/app.py +13 -3
- biotite/application/localapp.py +34 -0
- biotite/application/muscle/app3.py +2 -15
- biotite/application/muscle/app5.py +2 -2
- biotite/application/util.py +1 -1
- biotite/application/viennarna/rnaplot.py +6 -2
- biotite/database/rcsb/query.py +6 -6
- biotite/database/uniprot/check.py +20 -15
- biotite/database/uniprot/download.py +1 -1
- biotite/database/uniprot/query.py +1 -1
- biotite/sequence/align/alignment.py +16 -3
- biotite/sequence/align/banded.cpython-311-darwin.so +0 -0
- biotite/sequence/align/banded.pyx +5 -5
- biotite/sequence/align/kmeralphabet.cpython-311-darwin.so +0 -0
- biotite/sequence/align/kmeralphabet.pyx +17 -0
- biotite/sequence/align/kmersimilarity.cpython-311-darwin.so +0 -0
- biotite/sequence/align/kmertable.cpython-311-darwin.so +0 -0
- biotite/sequence/align/kmertable.pyx +52 -42
- biotite/sequence/align/localgapped.cpython-311-darwin.so +0 -0
- biotite/sequence/align/localungapped.cpython-311-darwin.so +0 -0
- biotite/sequence/align/matrix.py +273 -55
- biotite/sequence/align/matrix_data/3Di.mat +24 -0
- biotite/sequence/align/matrix_data/PB.license +21 -0
- biotite/sequence/align/matrix_data/PB.mat +18 -0
- biotite/sequence/align/multiple.cpython-311-darwin.so +0 -0
- biotite/sequence/align/pairwise.cpython-311-darwin.so +0 -0
- biotite/sequence/align/permutation.cpython-311-darwin.so +0 -0
- biotite/sequence/align/selector.cpython-311-darwin.so +0 -0
- biotite/sequence/align/tracetable.cpython-311-darwin.so +0 -0
- biotite/sequence/alphabet.py +3 -0
- biotite/sequence/codec.cpython-311-darwin.so +0 -0
- biotite/sequence/graphics/color_schemes/3di_flower.json +48 -0
- biotite/sequence/graphics/color_schemes/pb_flower.json +2 -1
- biotite/sequence/graphics/colorschemes.py +44 -11
- biotite/sequence/phylo/nj.cpython-311-darwin.so +0 -0
- biotite/sequence/phylo/tree.cpython-311-darwin.so +0 -0
- biotite/sequence/phylo/upgma.cpython-311-darwin.so +0 -0
- biotite/sequence/profile.py +86 -4
- biotite/sequence/seqtypes.py +124 -3
- biotite/setup_ccd.py +197 -0
- biotite/structure/__init__.py +4 -3
- biotite/structure/alphabet/__init__.py +25 -0
- biotite/structure/alphabet/encoder.py +332 -0
- biotite/structure/alphabet/encoder_weights_3di.kerasify +0 -0
- biotite/structure/alphabet/i3d.py +110 -0
- biotite/structure/alphabet/layers.py +86 -0
- biotite/structure/alphabet/pb.license +21 -0
- biotite/structure/alphabet/pb.py +171 -0
- biotite/structure/alphabet/unkerasify.py +122 -0
- biotite/structure/atoms.py +156 -43
- biotite/structure/bonds.cpython-311-darwin.so +0 -0
- biotite/structure/bonds.pyx +72 -21
- biotite/structure/celllist.cpython-311-darwin.so +0 -0
- biotite/structure/charges.cpython-311-darwin.so +0 -0
- biotite/structure/filter.py +1 -1
- biotite/structure/geometry.py +60 -113
- biotite/structure/info/__init__.py +1 -0
- biotite/structure/info/atoms.py +13 -13
- biotite/structure/info/bonds.py +12 -6
- biotite/structure/info/ccd.py +125 -32
- biotite/structure/info/{ccd/components.bcif → components.bcif} +0 -0
- biotite/structure/info/groups.py +63 -17
- biotite/structure/info/masses.py +9 -6
- biotite/structure/info/misc.py +15 -21
- biotite/structure/info/standardize.py +3 -2
- biotite/structure/io/mol/sdf.py +41 -40
- biotite/structure/io/pdb/convert.py +2 -0
- biotite/structure/io/pdb/file.py +74 -3
- biotite/structure/io/pdb/hybrid36.cpython-311-darwin.so +0 -0
- biotite/structure/io/pdbqt/file.py +32 -32
- biotite/structure/io/pdbx/__init__.py +1 -0
- biotite/structure/io/pdbx/bcif.py +32 -8
- biotite/structure/io/pdbx/cif.py +148 -107
- biotite/structure/io/pdbx/component.py +9 -4
- biotite/structure/io/pdbx/compress.py +321 -0
- biotite/structure/io/pdbx/convert.py +227 -68
- biotite/structure/io/pdbx/encoding.cpython-311-darwin.so +0 -0
- biotite/structure/io/pdbx/encoding.pyx +98 -17
- biotite/structure/io/trajfile.py +16 -16
- biotite/structure/molecules.py +141 -141
- biotite/structure/sasa.cpython-311-darwin.so +0 -0
- biotite/structure/segments.py +1 -2
- biotite/structure/util.py +73 -1
- biotite/version.py +2 -2
- {biotite-1.0.0.dist-info → biotite-1.1.0.dist-info}/METADATA +4 -1
- {biotite-1.0.0.dist-info → biotite-1.1.0.dist-info}/RECORD +88 -78
- biotite/structure/info/ccd/README.rst +0 -8
- biotite/structure/info/ccd/amino_acids.txt +0 -1663
- biotite/structure/info/ccd/carbohydrates.txt +0 -1135
- biotite/structure/info/ccd/nucleotides.txt +0 -798
- {biotite-1.0.0.dist-info → biotite-1.1.0.dist-info}/WHEEL +0 -0
- {biotite-1.0.0.dist-info → biotite-1.1.0.dist-info}/licenses/LICENSE.rst +0 -0
biotite/structure/io/pdbx/cif.py
CHANGED
|
@@ -7,7 +7,6 @@ __author__ = "Patrick Kunzmann"
|
|
|
7
7
|
__all__ = ["CIFFile", "CIFBlock", "CIFCategory", "CIFColumn", "CIFData"]
|
|
8
8
|
|
|
9
9
|
import itertools
|
|
10
|
-
import re
|
|
11
10
|
from collections.abc import MutableMapping, Sequence
|
|
12
11
|
import numpy as np
|
|
13
12
|
from biotite.file import (
|
|
@@ -357,7 +356,7 @@ class CIFCategory(_Component, MutableMapping):
|
|
|
357
356
|
return CIFBlock
|
|
358
357
|
|
|
359
358
|
@staticmethod
|
|
360
|
-
def deserialize(text
|
|
359
|
+
def deserialize(text):
|
|
361
360
|
lines = [line.strip() for line in text.splitlines() if not _is_empty(line)]
|
|
362
361
|
|
|
363
362
|
if _is_loop_start(lines[0]):
|
|
@@ -370,9 +369,9 @@ class CIFCategory(_Component, MutableMapping):
|
|
|
370
369
|
if category_name is None:
|
|
371
370
|
raise DeserializationError("Failed to parse category name")
|
|
372
371
|
|
|
373
|
-
lines = _to_single(lines
|
|
372
|
+
lines = _to_single(lines)
|
|
374
373
|
if is_looped:
|
|
375
|
-
category_dict = CIFCategory._deserialize_looped(lines
|
|
374
|
+
category_dict = CIFCategory._deserialize_looped(lines)
|
|
376
375
|
else:
|
|
377
376
|
category_dict = CIFCategory._deserialize_single(lines)
|
|
378
377
|
return CIFCategory(category_dict, category_name)
|
|
@@ -416,6 +415,9 @@ class CIFCategory(_Component, MutableMapping):
|
|
|
416
415
|
raise ValueError("At least one column must remain")
|
|
417
416
|
del self._columns[key]
|
|
418
417
|
|
|
418
|
+
def __contains__(self, key):
|
|
419
|
+
return key in self._columns
|
|
420
|
+
|
|
419
421
|
def __iter__(self):
|
|
420
422
|
return iter(self._columns)
|
|
421
423
|
|
|
@@ -439,15 +441,32 @@ class CIFCategory(_Component, MutableMapping):
|
|
|
439
441
|
Process a category where each field has a single value.
|
|
440
442
|
"""
|
|
441
443
|
category_dict = {}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
444
|
+
line_i = 0
|
|
445
|
+
while line_i < len(lines):
|
|
446
|
+
line = lines[line_i]
|
|
447
|
+
parts = list(_split_one_line(line))
|
|
448
|
+
if len(parts) == 2:
|
|
449
|
+
# Standard case -> name and value in one line
|
|
450
|
+
name_part, value_part = parts
|
|
451
|
+
line_i += 1
|
|
452
|
+
elif len(parts) == 1:
|
|
453
|
+
# Value is a multiline value on the next line
|
|
454
|
+
name_part = parts[0]
|
|
455
|
+
parts = list(_split_one_line(lines[line_i + 1]))
|
|
456
|
+
if len(parts) == 1:
|
|
457
|
+
value_part = parts[0]
|
|
458
|
+
else:
|
|
459
|
+
raise DeserializationError(f"Failed to parse line '{line}'")
|
|
460
|
+
line_i += 2
|
|
461
|
+
elif len(parts) == 0:
|
|
462
|
+
raise DeserializationError("Empty line within category")
|
|
463
|
+
else:
|
|
464
|
+
raise DeserializationError(f"Failed to parse line '{line}'")
|
|
465
|
+
category_dict[name_part.split(".")[1]] = CIFColumn(value_part)
|
|
447
466
|
return category_dict
|
|
448
467
|
|
|
449
468
|
@staticmethod
|
|
450
|
-
def _deserialize_looped(lines
|
|
469
|
+
def _deserialize_looped(lines):
|
|
451
470
|
"""
|
|
452
471
|
Process a category where each field has multiple values
|
|
453
472
|
(category is a table).
|
|
@@ -468,26 +487,22 @@ class CIFCategory(_Component, MutableMapping):
|
|
|
468
487
|
data_lines = lines[i:]
|
|
469
488
|
# Rows may be split over multiple lines -> do not rely on
|
|
470
489
|
# row-line-alignment at all and simply cycle through columns
|
|
471
|
-
|
|
490
|
+
column_indices = itertools.cycle(range(len(column_names)))
|
|
472
491
|
for data_line in data_lines:
|
|
473
|
-
|
|
474
|
-
# use regex-based _split_one_line() to split
|
|
475
|
-
# Otherwise use much more faster whitespace split
|
|
476
|
-
# and quote removal if applicable.
|
|
477
|
-
if expect_whitespace:
|
|
478
|
-
values = _split_one_line(data_line)
|
|
479
|
-
else:
|
|
480
|
-
values = data_line.split()
|
|
481
|
-
for k in range(len(values)):
|
|
482
|
-
# Remove quotes
|
|
483
|
-
if (values[k][0] == '"' and values[k][-1] == '"') or (
|
|
484
|
-
values[k][0] == "'" and values[k][-1] == "'"
|
|
485
|
-
):
|
|
486
|
-
values[k] = values[k][1:-1]
|
|
492
|
+
values = _split_one_line(data_line)
|
|
487
493
|
for val in values:
|
|
488
|
-
|
|
494
|
+
column_index = next(column_indices)
|
|
495
|
+
column_name = column_names[column_index]
|
|
489
496
|
category_dict[column_name].append(val)
|
|
490
497
|
|
|
498
|
+
# Check if all columns have the same length
|
|
499
|
+
# Otherwise, this would indicate a parsing error or an invalid CIF file
|
|
500
|
+
column_index = next(column_indices)
|
|
501
|
+
if column_index != 0:
|
|
502
|
+
raise DeserializationError(
|
|
503
|
+
"Category contains columns with different lengths"
|
|
504
|
+
)
|
|
505
|
+
|
|
491
506
|
return category_dict
|
|
492
507
|
|
|
493
508
|
def _serialize_single(self):
|
|
@@ -496,7 +511,8 @@ class CIFCategory(_Component, MutableMapping):
|
|
|
496
511
|
# "+3" Because of three whitespace chars after longest key
|
|
497
512
|
req_len = max_len + 3
|
|
498
513
|
return [
|
|
499
|
-
|
|
514
|
+
# Remove potential terminal newlines from multiline values
|
|
515
|
+
(key.ljust(req_len) + _escape(column.as_item())).strip()
|
|
500
516
|
for key, column in zip(keys, self.values())
|
|
501
517
|
]
|
|
502
518
|
|
|
@@ -508,7 +524,7 @@ class CIFCategory(_Component, MutableMapping):
|
|
|
508
524
|
array = column.as_array(str)
|
|
509
525
|
# Quote before measuring the number of chars,
|
|
510
526
|
# as the quote characters modify the length
|
|
511
|
-
array = np.array([
|
|
527
|
+
array = np.array([_escape(element) for element in array])
|
|
512
528
|
column_arrays.append(array)
|
|
513
529
|
|
|
514
530
|
# Number of characters the longest string in the column needs
|
|
@@ -522,7 +538,8 @@ class CIFCategory(_Component, MutableMapping):
|
|
|
522
538
|
for j, array in enumerate(column_arrays):
|
|
523
539
|
value_lines[i] += array[i].ljust(column_n_chars[j])
|
|
524
540
|
# Remove trailing justification of last column
|
|
525
|
-
|
|
541
|
+
# and potential terminal newlines from multiline values
|
|
542
|
+
value_lines[i] = value_lines[i].strip()
|
|
526
543
|
|
|
527
544
|
return ["loop_"] + key_lines + value_lines
|
|
528
545
|
|
|
@@ -541,6 +558,17 @@ class CIFBlock(_Component, MutableMapping):
|
|
|
541
558
|
The keys are the category names and the values are the
|
|
542
559
|
:class:`CIFCategory` objects.
|
|
543
560
|
By default, an empty block is created.
|
|
561
|
+
name : str, optional
|
|
562
|
+
The name of the block.
|
|
563
|
+
This is only used for serialization and is automatically set,
|
|
564
|
+
when the :class:`CIFBlock` is added to a :class:`CIFFile`.
|
|
565
|
+
It only needs to be set manually, when the block is directly
|
|
566
|
+
serialized.
|
|
567
|
+
|
|
568
|
+
Attributes
|
|
569
|
+
----------
|
|
570
|
+
name : str
|
|
571
|
+
The name of the block.
|
|
544
572
|
|
|
545
573
|
Notes
|
|
546
574
|
-----
|
|
@@ -552,13 +580,15 @@ class CIFBlock(_Component, MutableMapping):
|
|
|
552
580
|
--------
|
|
553
581
|
|
|
554
582
|
>>> # Add category on creation
|
|
555
|
-
>>> block = CIFBlock({"foo": CIFCategory({"some_column": 1})})
|
|
583
|
+
>>> block = CIFBlock({"foo": CIFCategory({"some_column": 1})}, name="baz")
|
|
556
584
|
>>> # Add category later on
|
|
557
585
|
>>> block["bar"] = CIFCategory({"another_column": [2, 3]})
|
|
558
586
|
>>> # Access a column
|
|
559
587
|
>>> print(block["bar"]["another_column"].as_array())
|
|
560
588
|
['2' '3']
|
|
561
589
|
>>> print(block.serialize())
|
|
590
|
+
data_baz
|
|
591
|
+
#
|
|
562
592
|
_foo.some_column 1
|
|
563
593
|
#
|
|
564
594
|
loop_
|
|
@@ -568,11 +598,20 @@ class CIFBlock(_Component, MutableMapping):
|
|
|
568
598
|
#
|
|
569
599
|
"""
|
|
570
600
|
|
|
571
|
-
def __init__(self, categories=None):
|
|
601
|
+
def __init__(self, categories=None, name=None):
|
|
602
|
+
self._name = name
|
|
572
603
|
if categories is None:
|
|
573
604
|
categories = {}
|
|
574
605
|
self._categories = categories
|
|
575
606
|
|
|
607
|
+
@property
|
|
608
|
+
def name(self):
|
|
609
|
+
return self._name
|
|
610
|
+
|
|
611
|
+
@name.setter
|
|
612
|
+
def name(self, name):
|
|
613
|
+
self._name = name
|
|
614
|
+
|
|
576
615
|
@staticmethod
|
|
577
616
|
def subcomponent_class():
|
|
578
617
|
return CIFCategory
|
|
@@ -606,7 +645,10 @@ class CIFBlock(_Component, MutableMapping):
|
|
|
606
645
|
return CIFBlock(_create_element_dict(lines, category_names, category_starts))
|
|
607
646
|
|
|
608
647
|
def serialize(self):
|
|
609
|
-
|
|
648
|
+
if self._name is None:
|
|
649
|
+
raise SerializationError("Block name is required")
|
|
650
|
+
# The block starts with the black name line followed by a comment line
|
|
651
|
+
text_blocks = ["data_" + self._name + "\n#\n"]
|
|
610
652
|
for category_name, category in self._categories.items():
|
|
611
653
|
if isinstance(category, str):
|
|
612
654
|
# Category is already stored as lines
|
|
@@ -629,15 +671,7 @@ class CIFBlock(_Component, MutableMapping):
|
|
|
629
671
|
# Element is stored in serialized form
|
|
630
672
|
# -> must be deserialized first
|
|
631
673
|
try:
|
|
632
|
-
|
|
633
|
-
# Even if the values are quote protected,
|
|
634
|
-
# no whitespace is expected in escaped values
|
|
635
|
-
# Therefore slow regex-based _split_one_line() call is not necessary
|
|
636
|
-
if key == "atom_site":
|
|
637
|
-
expect_whitespace = False
|
|
638
|
-
else:
|
|
639
|
-
expect_whitespace = True
|
|
640
|
-
category = CIFCategory.deserialize(category, expect_whitespace)
|
|
674
|
+
category = CIFCategory.deserialize(category)
|
|
641
675
|
except Exception:
|
|
642
676
|
raise DeserializationError(f"Failed to deserialize category '{key}'")
|
|
643
677
|
# Update with deserialized object
|
|
@@ -655,6 +689,9 @@ class CIFBlock(_Component, MutableMapping):
|
|
|
655
689
|
def __delitem__(self, key):
|
|
656
690
|
del self._categories[key]
|
|
657
691
|
|
|
692
|
+
def __contains__(self, key):
|
|
693
|
+
return key in self._categories
|
|
694
|
+
|
|
658
695
|
def __iter__(self):
|
|
659
696
|
return iter(self._categories)
|
|
660
697
|
|
|
@@ -778,14 +815,12 @@ class CIFFile(_Component, File, MutableMapping):
|
|
|
778
815
|
def serialize(self):
|
|
779
816
|
text_blocks = []
|
|
780
817
|
for block_name, block in self._blocks.items():
|
|
781
|
-
text_blocks.append("data_" + block_name + "\n")
|
|
782
|
-
# A comment line is set after the block indicator
|
|
783
|
-
text_blocks.append("#\n")
|
|
784
818
|
if isinstance(block, str):
|
|
785
819
|
# Block is already stored as text
|
|
786
820
|
text_blocks.append(block)
|
|
787
821
|
else:
|
|
788
822
|
try:
|
|
823
|
+
block.name = block_name
|
|
789
824
|
text_blocks.append(block.serialize())
|
|
790
825
|
except Exception:
|
|
791
826
|
raise SerializationError(
|
|
@@ -856,11 +891,15 @@ class CIFFile(_Component, File, MutableMapping):
|
|
|
856
891
|
def __setitem__(self, key, block):
|
|
857
892
|
if not isinstance(block, CIFBlock):
|
|
858
893
|
raise TypeError(f"Expected 'CIFBlock', but got '{type(block).__name__}'")
|
|
894
|
+
block.name = key
|
|
859
895
|
self._blocks[key] = block
|
|
860
896
|
|
|
861
897
|
def __delitem__(self, key):
|
|
862
898
|
del self._blocks[key]
|
|
863
899
|
|
|
900
|
+
def __contains__(self, key):
|
|
901
|
+
return key in self._blocks
|
|
902
|
+
|
|
864
903
|
def __iter__(self):
|
|
865
904
|
return iter(self._blocks)
|
|
866
905
|
|
|
@@ -893,7 +932,7 @@ def _create_element_dict(lines, element_names, element_starts):
|
|
|
893
932
|
# Lazy deserialization
|
|
894
933
|
# -> keep as text for now and deserialize later if needed
|
|
895
934
|
return {
|
|
896
|
-
element_name: "\n".join(lines[element_starts[i] : element_starts[i + 1]])
|
|
935
|
+
element_name: "\n".join(lines[element_starts[i] : element_starts[i + 1]]) + "\n"
|
|
897
936
|
for i, element_name in enumerate(element_names)
|
|
898
937
|
}
|
|
899
938
|
|
|
@@ -927,52 +966,50 @@ def _is_loop_start(line):
|
|
|
927
966
|
return line.startswith("loop_")
|
|
928
967
|
|
|
929
968
|
|
|
930
|
-
def _to_single(lines
|
|
931
|
-
"""
|
|
969
|
+
def _to_single(lines):
|
|
970
|
+
r"""
|
|
932
971
|
Convert multiline values into singleline values
|
|
933
972
|
(in terms of 'lines' list elements).
|
|
934
|
-
Linebreaks are preserved.
|
|
973
|
+
Linebreaks are preserved as ``'\n'`` characters within a list element.
|
|
974
|
+
The initial ``';'`` character is also preserved, while the final ``';'`` character
|
|
975
|
+
is removed.
|
|
935
976
|
"""
|
|
936
|
-
processed_lines = [
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
multi_line_str += "\n" + lines[j]
|
|
947
|
-
j += 1
|
|
948
|
-
if is_looped:
|
|
949
|
-
# Create a line for the multiline string only
|
|
950
|
-
processed_lines[out_i] = f"'{multi_line_str}'"
|
|
951
|
-
out_i += 1
|
|
977
|
+
processed_lines = []
|
|
978
|
+
in_multi_line = False
|
|
979
|
+
mutli_line_value = []
|
|
980
|
+
for line in lines:
|
|
981
|
+
# Multiline value are enclosed by ';' at the start of the beginning and end line
|
|
982
|
+
if line[0] == ";":
|
|
983
|
+
if not in_multi_line:
|
|
984
|
+
# Start of multiline value
|
|
985
|
+
in_multi_line = True
|
|
986
|
+
mutli_line_value.append(line)
|
|
952
987
|
else:
|
|
953
|
-
#
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
processed_lines[out_i - 1] += " " + lines[in_i]
|
|
960
|
-
in_i += 1
|
|
961
|
-
|
|
988
|
+
# End of multiline value
|
|
989
|
+
in_multi_line = False
|
|
990
|
+
# The current line contains only the end character ';'
|
|
991
|
+
# Hence this line is not added to the processed lines
|
|
992
|
+
processed_lines.append("\n".join(mutli_line_value))
|
|
993
|
+
mutli_line_value = []
|
|
962
994
|
else:
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
return [line for line in processed_lines if line is not None]
|
|
995
|
+
if in_multi_line:
|
|
996
|
+
mutli_line_value.append(line)
|
|
997
|
+
else:
|
|
998
|
+
processed_lines.append(line)
|
|
999
|
+
return processed_lines
|
|
969
1000
|
|
|
970
1001
|
|
|
971
|
-
def
|
|
1002
|
+
def _escape(value):
|
|
972
1003
|
"""
|
|
973
|
-
|
|
1004
|
+
Escape special characters in a value to make it compatible with CIF.
|
|
974
1005
|
"""
|
|
975
|
-
if
|
|
1006
|
+
if "\n" in value:
|
|
1007
|
+
# A value with linebreaks must be represented as multiline value
|
|
1008
|
+
return _multiline(value)
|
|
1009
|
+
elif "'" in value and '"' in value:
|
|
1010
|
+
# If both quote types are present, you cannot use them for escaping
|
|
1011
|
+
return _multiline(value)
|
|
1012
|
+
elif len(value) == 0:
|
|
976
1013
|
return "''"
|
|
977
1014
|
elif value[0] == "_":
|
|
978
1015
|
return "'" + value + "'"
|
|
@@ -990,12 +1027,10 @@ def _quote(value):
|
|
|
990
1027
|
|
|
991
1028
|
def _multiline(value):
|
|
992
1029
|
"""
|
|
993
|
-
Convert a string
|
|
1030
|
+
Convert a string that may contain linebreaks into CIF-compatible
|
|
994
1031
|
multiline string.
|
|
995
1032
|
"""
|
|
996
|
-
|
|
997
|
-
return "\n;" + value + "\n;\n"
|
|
998
|
-
return value
|
|
1033
|
+
return "\n;" + value + "\n;\n"
|
|
999
1034
|
|
|
1000
1035
|
|
|
1001
1036
|
def _split_one_line(line):
|
|
@@ -1003,27 +1038,33 @@ def _split_one_line(line):
|
|
|
1003
1038
|
Split a line into its fields.
|
|
1004
1039
|
Supporting embedded quotes (' or "), like `'a dog's life'` to `a dog's life`
|
|
1005
1040
|
"""
|
|
1006
|
-
#
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1041
|
+
# Special case of multiline value, where the line starts with ';'
|
|
1042
|
+
if line[0] == ";":
|
|
1043
|
+
yield line[1:]
|
|
1044
|
+
elif "'" in line or '"' in line:
|
|
1045
|
+
# Quoted values in the line
|
|
1046
|
+
while line:
|
|
1047
|
+
# Strip leading whitespace(s)
|
|
1048
|
+
stripped_line = line.lstrip()
|
|
1049
|
+
# Split the line on whitespace
|
|
1050
|
+
word, _, line = stripped_line.partition(" ")
|
|
1051
|
+
# Handle the case where the word start with a quote
|
|
1052
|
+
if word.startswith(("'", '"')):
|
|
1053
|
+
# Set the separator to the quote found
|
|
1054
|
+
separator = word[0]
|
|
1055
|
+
# Handle the case of a quoted word without space
|
|
1056
|
+
if word.endswith(separator) and len(word) > 1:
|
|
1057
|
+
# Yield the word without the opening and closing quotes
|
|
1058
|
+
yield word[1:-1]
|
|
1059
|
+
continue
|
|
1060
|
+
# split the word on the separator
|
|
1061
|
+
word, _, line = stripped_line[1:].partition(separator)
|
|
1062
|
+
|
|
1063
|
+
yield word
|
|
1064
|
+
else:
|
|
1065
|
+
# No quoted values in the line -> simple whitespace split
|
|
1066
|
+
for line in line.split():
|
|
1067
|
+
yield line
|
|
1027
1068
|
|
|
1028
1069
|
|
|
1029
1070
|
def _arrayfy(data):
|
|
@@ -171,10 +171,10 @@ class _HierarchicalContainer(_Component, MutableMapping, metaclass=ABCMeta):
|
|
|
171
171
|
Parameters
|
|
172
172
|
----------
|
|
173
173
|
store_key_in: str, optional
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
174
|
+
If given, the key of each element is stored as value in the
|
|
175
|
+
serialized element.
|
|
176
|
+
This is basically the reverse operation of `take_key_from` in
|
|
177
|
+
:meth:`_deserialize_elements()`.
|
|
178
178
|
"""
|
|
179
179
|
serialized_elements = []
|
|
180
180
|
for key, element in self._elements.items():
|
|
@@ -223,6 +223,11 @@ class _HierarchicalContainer(_Component, MutableMapping, metaclass=ABCMeta):
|
|
|
223
223
|
def __delitem__(self, key):
|
|
224
224
|
del self._elements[key]
|
|
225
225
|
|
|
226
|
+
# Implement `__contains__()` explicitly,
|
|
227
|
+
# because the mixin method unnecessarily deserializes the value, if available
|
|
228
|
+
def __contains__(self, key):
|
|
229
|
+
return key in self._elements
|
|
230
|
+
|
|
226
231
|
def __iter__(self):
|
|
227
232
|
return iter(self._elements)
|
|
228
233
|
|