Coverage for C: \ Users \ peaco \ OneDrive \ Documents \ GitHub \ mt_metadata \ mt_metadata \ timeseries \ stationxml \ utils.py: 91%
106 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-10 00:11 -0800
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-10 00:11 -0800
1# -*- coding: utf-8 -*-
2"""
3Created on Tue Feb 16 10:33:27 2021
5:copyright:
6 Jared Peacock (jpeacock@usgs.gov)
8:license: MIT
10"""
11from loguru import logger
12from obspy.core.inventory import Comment
15# =============================================================================
16# Translate between metadata and inventory: mapping dictionaries
17# =============================================================================
18class BaseTranslator:
19 """
20 Base translator for StationXML <--> MT Metadata
22 """
24 def __init__(self):
25 self.logger = logger
26 self.xml_translator = {
27 "alternate_code": None,
28 "code": None,
29 "comments": None,
30 "data_availability": None,
31 "description": None,
32 "historical_code": None,
33 "identifiers": None,
34 "restricted_status": None,
35 "source_id": None,
36 }
38 self.mt_translator = self.flip_dict(self.xml_translator)
39 self.mt_comments_list = []
41 @staticmethod
42 def flip_dict(original_dict):
43 """
44 Flip keys and values of the dictionary
46 Need to take care of duplicate names and lists of names
48 :param original_dict: original dictionary
49 :type original_dict: dict
50 :return: reversed dictionary
51 :rtype: dictionary
53 """
54 flipped_dict = {}
56 for k, v in original_dict.items():
57 if v in [None, "special"]:
58 continue
59 if k in [None]:
60 continue
61 if isinstance(v, (list, tuple)):
62 # bit of a hack, needs to be more unique.
63 for value in v:
64 flipped_dict[value] = k
65 else:
66 flipped_dict[str(v)] = k
68 return flipped_dict
70 @staticmethod
71 def read_xml_comment(comment):
72 """
73 read stationxml comment
75 Assuming that separate comments are split by ':' and separated
76 by a comma.
78 """
79 if comment.subject is not None:
80 key = comment.subject.strip().replace(" ", "_").lower()
81 else:
82 key = "mt"
84 def parse(comment_string, filled=None, depth=0):
85 """
86 Recursively parse a comment string trying to adhere to the
87 original syntax of the comment. Expecting a dictionary type
88 string
90 'a: b, c:d' -> {'a': 'b', 'c':'d'}
92 but sometimes looks like
94 'a: b:c, d:e' -> {'a': 'b:c', 'd':'e'}
96 or
98 'a: b, b2, c: d:e' -> {'a': 'b:c', 'd':'e'}
100 """
101 if filled is None:
102 filled = {}
104 # Add recursion depth limit
105 if depth > 20: # Arbitrary limit to prevent stack overflow
106 return filled
108 if hasattr(comment_string, "value"):
109 comment_string = comment_string.value
110 if "author:" in comment_string and "comments:" in comment_string:
111 author, comments = [
112 s.strip()
113 for s in comment_string.split("author:", 1)[1].split("comments:", 1)
114 ]
116 if author.endswith(","):
117 author = author[:-1]
119 return {"author": author, "comments": comments}
121 else:
122 k, *other = comment_string.split(":", 1)
123 if other:
124 other = other[0]
125 key = k
126 if other.find(":") >= 0 and other.find(",") >= 0:
127 if other.find(":") < other.find(","):
128 if other.count(":") > 1:
129 value, *maybe = other.split(",", 1)
130 filled[key] = value.strip().replace(":", "--")
131 if maybe:
132 filled = parse(maybe[0].strip(), filled, depth + 1)
133 else:
134 filled[key] = other.replace(":", "--").strip()
135 else:
136 value, *maybe = other.split(",", 1)
137 filled[key] = value.strip()
138 if maybe:
139 filled = parse(maybe[0].strip(), filled, depth + 1)
140 elif other.find(":") > 0:
141 value, *maybe = other.split(":", 1)
142 filled[key] = value.strip()
143 else:
144 filled[key] = other.strip()
146 else:
147 filled[k] = None
148 return filled
150 # if the string is dictionary like, parse, otherwise skip
151 if ":" in comment.value:
152 value = parse(comment.value)
153 else:
154 value = comment.value
156 return key, value
158 @staticmethod
159 def read_xml_identifier(identifiers):
160 """
161 Read stationxml idenfier, which is a list of doi numbers, make
162 it into a string without the doi
164 :param doi: DESCRIPTION
165 :type doi: TYPE
166 :return: DESCRIPTION
167 :rtype: TYPE
169 """
170 return ", ".join(
171 [ii.strip().replace("DOI:", "https://doi.org/") for ii in identifiers]
172 )
174 def get_comment(self, comments, subject):
175 """
176 Get the correct comment from a list of comments
178 :param comments: list of :class:`obspy.core.inventory.Comments`
179 :type comments: list
180 :param subject: subject heading to get
181 :type subject: string
182 :return: the corresponding comment
183 :rtype: :class:`obspy.core.inventory.Comments`
185 """
187 for comment in comments:
188 if comment.subject == subject:
189 return comment
191 self.logger.info(f"Could not find {subject} in the given list of comments.")
192 return None
194 def make_mt_comments(self, mt_element, mt_key_base="mt"):
195 """
196 make comments from an MT element from self.mt_comments_list
198 :param mt_element: MT metadata element
199 """
200 comments = []
201 # add comments for MT specific information
202 try:
203 key_list = sorted(self.mt_comments_list)
204 except TypeError:
205 key_list = self.mt_comments_list
206 for key in key_list:
207 if isinstance(key, dict):
208 values = []
209 comment_key = list(key.keys())[0]
210 for part in key[comment_key]:
211 info = part.split(".")[-1]
212 value = mt_element.get_attr_from_name(part)
213 if hasattr(value, "value"):
214 value = value.value
215 if value:
216 values.append(f"{info}: {value}")
217 value = ", ".join(values)
218 comment = Comment(value, subject=f"{mt_key_base}.{comment_key}")
219 comments.append(comment)
220 else:
221 value = mt_element.get_attr_from_name(key)
222 if hasattr(value, "value"):
223 value = value.value
224 if value:
225 if isinstance(value, (list, tuple)):
226 value = ", ".join(value)
227 comment = Comment(value, subject=f"{mt_key_base}.{key}")
228 comments.append(comment)
229 return comments
231 def xml_to_mt(self, value):
232 pass
234 def mt_to_xml(self, value):
235 pass