bestroi commited on
Commit
402e29c
·
verified ·
1 Parent(s): 14ca390

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +584 -0
app.py ADDED
@@ -0,0 +1,584 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import xml.etree.ElementTree as ET
3
+ import pandas as pd
4
+ from io import StringIO
5
+ import folium
6
+ from streamlit_folium import st_folium
7
+
8
+ # -------------------------------
9
+ # Authority Lists as XML Strings
10
+ # -------------------------------
11
+
12
+ materials_xml = """<?xml version="1.0" encoding="UTF-8"?>
13
+ <materials>
14
+ <material id="LAPIS">
15
+ <name>Lapis</name>
16
+ <name_en>Stone</name_en>
17
+ <description>Stone used as a durable medium for inscriptions and engravings.</description>
18
+ </material>
19
+ <material id="ARGENTUM">
20
+ <name>Argentum</name>
21
+ <name_en>Silver</name_en>
22
+ <description>Silver used in inscriptions, often for its lustrous appearance and value.</description>
23
+ </material>
24
+ <material id="PLUMBUM">
25
+ <name>Plumbum</name>
26
+ <name_en>Lead</name_en>
27
+ <description>Lead utilized in inscriptions, valued for its malleability and ease of engraving.</description>
28
+ </material>
29
+ <material id="OPUS_FIGLINAE">
30
+ <name>Opus Figlinae</name>
31
+ <name_en>Pottery</name_en>
32
+ <description>Pottery used as a medium for inscriptions, typically in the form of ceramic artifacts.</description>
33
+ </material>
34
+ </materials>
35
+ """
36
+
37
+ places_xml = """<?xml version="1.0" encoding="UTF-8"?>
38
+ <places>
39
+ <place id="VIZE">
40
+ <name>Vize</name>
41
+ <geonamesLink>https://www.geonames.org/738154/vize.html</geonamesLink>
42
+ <pleiadesLink>https://pleiades.stoa.org/places/511190</pleiadesLink>
43
+ <latitude>40.6545</latitude>
44
+ <longitude>28.4078</longitude>
45
+ <description>Ancient city located in modern-day Turkey.</description>
46
+ </place>
47
+ <place id="PHILIPPI">
48
+ <name>Philippi</name>
49
+ <geonamesLink>https://www.geonames.org/734652/filippoi-philippi.html</geonamesLink>
50
+ <pleiadesLink>https://pleiades.stoa.org/places/501482</pleiadesLink>
51
+ <latitude>40.5044</latitude>
52
+ <longitude>24.9722</longitude>
53
+ <description>Ancient city in Macedonia, founded by Philip II of Macedon.</description>
54
+ </place>
55
+ <place id="AUGUSTA_TRAIANA">
56
+ <name>Augusta Traiana</name>
57
+ <geonamesLink>https://www.geonames.org/maps/google_42.4333_25.65.html</geonamesLink>
58
+ <pleiadesLink>https://pleiades.stoa.org/places/216731</pleiadesLink>
59
+ <latitude>42.4259</latitude>
60
+ <longitude>25.6272</longitude>
61
+ <description>Ancient Roman city, present-day Stara Zagora in Bulgaria.</description>
62
+ </place>
63
+ <place id="DYRRACHIUM">
64
+ <name>Dyrrachium</name>
65
+ <geonamesLink>https://www.geonames.org/3185728/durres.html</geonamesLink>
66
+ <pleiadesLink>https://pleiades.stoa.org/places/481818</pleiadesLink>
67
+ <latitude>41.3231</latitude>
68
+ <longitude>19.4417</longitude>
69
+ <description>Ancient city on the Adriatic coast, present-day Durrës in Albania.</description>
70
+ </place>
71
+ <place id="ANTISARA">
72
+ <name>Antisara</name>
73
+ <geonamesLink>https://www.geonames.org/736079/akra-kalamitsa.html</geonamesLink>
74
+ <pleiadesLink>https://pleiades.stoa.org/places/501351</pleiadesLink>
75
+ <latitude>39.5000</latitude>
76
+ <longitude>20.0000</longitude>
77
+ <description>Ancient settlement, exact modern location TBD.</description>
78
+ </place>
79
+ <place id="MACEDONIA">
80
+ <name>Macedonia</name>
81
+ <geonamesLink>-</geonamesLink>
82
+ <pleiadesLink>-</pleiadesLink>
83
+ <latitude>40.0000</latitude>
84
+ <longitude>22.0000</longitude>
85
+ <description>Historical region in Southeast Europe, encompassing parts of modern Greece, North Macedonia, and Bulgaria.</description>
86
+ </place>
87
+ </places>
88
+ """
89
+
90
+ titles_xml = """<?xml version="1.0" encoding="UTF-8"?>
91
+ <emperorTitles>
92
+ <title id="IMPERATOR">
93
+ <name>Imperator</name>
94
+ <name_gr>Αυτοκράτορας</name_gr>
95
+ <abbreviation>Imp.</abbreviation>
96
+ <description>A title granted to a victorious general, later adopted as a formal title by Roman emperors.</description>
97
+ </title>
98
+ <title id="CAESAR">
99
+ <name>Caesar</name>
100
+ <name_gr>Καῖσαρ</name_gr>
101
+ <abbreviation>Caes.</abbreviation>
102
+ <description>A title used by Roman emperors, originally the family name of Julius Caesar.</description>
103
+ </title>
104
+ <title id="AUGUSTUS">
105
+ <name>Augustus</name>
106
+ <name_gr>-</name_gr>
107
+ <abbreviation>Aug.</abbreviation>
108
+ <description>The first Roman emperor's title, signifying revered or majestic status.</description>
109
+ </title>
110
+ </emperorTitles>
111
+ """
112
+
113
+ # -------------------------------
114
+ # Parse Authority Lists
115
+ # -------------------------------
116
+
117
+ def parse_materials(xml_string):
118
+ materials = {}
119
+ root = ET.fromstring(xml_string)
120
+ for material in root.findall('material'):
121
+ material_id = material.get('id')
122
+ materials[material_id] = {
123
+ 'Name': material.find('name').text,
124
+ 'Name_EN': material.find('name_en').text,
125
+ 'Description': material.find('description').text
126
+ }
127
+ return materials
128
+
129
+ def parse_places(xml_string):
130
+ places = {}
131
+ root = ET.fromstring(xml_string)
132
+ for place in root.findall('place'):
133
+ place_id = place.get('id')
134
+ places[place_id] = {
135
+ 'Name': place.find('name').text,
136
+ 'GeoNames Link': place.find('geonamesLink').text,
137
+ 'Pleiades Link': place.find('pleiadesLink').text,
138
+ 'Latitude': float(place.find('latitude').text),
139
+ 'Longitude': float(place.find('longitude').text),
140
+ 'Description': place.find('description').text
141
+ }
142
+ return places
143
+
144
+ def parse_titles(xml_string):
145
+ titles = {}
146
+ root = ET.fromstring(xml_string)
147
+ for title in root.findall('title'):
148
+ title_id = title.get('id')
149
+ titles[title_id] = {
150
+ 'Name': title.find('name').text,
151
+ 'Name_GR': title.find('name_gr').text,
152
+ 'Abbreviation': title.find('abbreviation').text,
153
+ 'Description': title.find('description').text
154
+ }
155
+ return titles
156
+
157
+ # Load authority data
158
+ materials_dict = parse_materials(materials_xml)
159
+ places_dict = parse_places(places_xml)
160
+ titles_dict = parse_titles(titles_xml)
161
+
162
+ # -------------------------------
163
+ # Function to Find Place ID by Name (Case-Insensitive)
164
+ # -------------------------------
165
+
166
+ def find_place_id_by_name(name):
167
+ """
168
+ Finds the place ID by matching the place name (case-insensitive).
169
+ Returns the place ID if found, else returns the original name.
170
+ """
171
+ for id_, place in places_dict.items():
172
+ if place['Name'].strip().lower() == name.strip().lower():
173
+ return id_
174
+ return name # Return the original name if no match is found
175
+
176
+ # -------------------------------
177
+ # Function to Parse Inscriptions
178
+ # -------------------------------
179
+
180
+ def parse_inscriptions(xml_content):
181
+ tree = ET.ElementTree(ET.fromstring(xml_content))
182
+ root = tree.getroot()
183
+ inscriptions = []
184
+ for inscription in root.findall('inscription'):
185
+ n = inscription.get('n')
186
+ publisher = inscription.find('Publisher').text if inscription.find('Publisher') is not None else "N/A"
187
+
188
+ # Handle Origin with or without 'ref' attribute
189
+ origin_elem = inscription.find('Origin')
190
+ if origin_elem is not None:
191
+ origin_ref = origin_elem.get('ref')
192
+ if origin_ref:
193
+ origin_id = origin_ref
194
+ else:
195
+ origin_text = origin_elem.text.strip() if origin_elem.text else ""
196
+ origin_id = find_place_id_by_name(origin_text)
197
+ else:
198
+ origin_id = "N/A"
199
+
200
+ origin = places_dict.get(origin_id, {}).get('Name', origin_id)
201
+ origin_geonames_link = places_dict.get(origin_id, {}).get('GeoNames Link', "#")
202
+ origin_pleiades_link = places_dict.get(origin_id, {}).get('Pleiades Link', "#")
203
+
204
+ # Handle Material with or without 'ref' attribute
205
+ material_elem = inscription.find('Material')
206
+ if material_elem is not None:
207
+ material_ref = material_elem.get('ref')
208
+ if material_ref:
209
+ material_id = material_ref
210
+ else:
211
+ material_text = material_elem.text.strip() if material_elem.text else ""
212
+ # Attempt to find material ID by matching the name_en
213
+ material_id = None
214
+ for id_, material in materials_dict.items():
215
+ if material['Name_EN'].strip().lower() == material_text.strip().lower():
216
+ material_id = id_
217
+ break
218
+ if not material_id:
219
+ material_id = material_text # Use the text if no match found
220
+ else:
221
+ material_id = "N/A"
222
+
223
+ material = materials_dict.get(material_id, {}).get('Name_EN', material_id)
224
+
225
+ language = inscription.find('Language').text if inscription.find('Language') is not None else "N/A"
226
+
227
+ text = "".join(inscription.find('Text').itertext()).strip() if inscription.find('Text') is not None else "N/A"
228
+
229
+ dating = inscription.find('Dating').text if inscription.find('Dating') is not None else "N/A"
230
+ images = inscription.find('Images').text if inscription.find('Images') is not None else "N/A"
231
+ encoder = inscription.find('Encoder').text if inscription.find('Encoder') is not None else "N/A"
232
+
233
+ category_terms = [term.text for term in inscription.findall('Category/term')]
234
+
235
+ inscriptions.append({
236
+ 'Number': n,
237
+ 'Publisher': publisher,
238
+ 'Origin_ID': origin_id,
239
+ 'Origin': origin,
240
+ 'GeoNames Link': origin_geonames_link,
241
+ 'Pleiades Link': origin_pleiades_link,
242
+ 'Material_ID': material_id,
243
+ 'Material': material,
244
+ 'Language': language,
245
+ 'Text': text,
246
+ 'Dating': dating,
247
+ 'Images': images,
248
+ 'Encoder': encoder,
249
+ 'Categories': ", ".join(category_terms)
250
+ })
251
+ return pd.DataFrame(inscriptions)
252
+
253
+ # -------------------------------
254
+ # Functions to Render Editions
255
+ # -------------------------------
256
+
257
+ def render_diplomatic(text_element):
258
+ lines = []
259
+ current_line = ""
260
+ for elem in text_element.iter():
261
+ if elem.tag == "lb":
262
+ if current_line:
263
+ lines.append(current_line.strip())
264
+ current_line = "" # Start a new line
265
+ line_number = elem.get("n", "")
266
+ current_line += f"{line_number} " if line_number else ""
267
+ elif elem.tag == "supplied":
268
+ # Process nested <expan> elements and concatenate abbreviations
269
+ supplied_content = ""
270
+ for sub_elem in elem.findall(".//expan"): # Nested <expan> elements
271
+ abbr_elem = sub_elem.find("abbr")
272
+ if abbr_elem is not None and abbr_elem.text:
273
+ supplied_content += abbr_elem.text.upper()
274
+ current_line += f"[{supplied_content}]"
275
+ elif elem.tag == "expan":
276
+ # Use only the abbreviation part
277
+ abbr_elem = elem.find("abbr")
278
+ if abbr_elem is not None and abbr_elem.text:
279
+ current_line += abbr_elem.text.upper()
280
+ elif elem.tag == "g" and elem.get("type") == "leaf":
281
+ current_line += " LEAF "
282
+ elif elem.tag == "title" and elem.get("type") == "emperor":
283
+ # Include title abbreviations
284
+ title_ref = elem.get('ref')
285
+ title_info = titles_dict.get(title_ref, {})
286
+ abbreviation = title_info.get('Abbreviation', '')
287
+ current_line += abbreviation
288
+ elif elem.text and elem.tag not in ["supplied", "expan", "g", "title"]:
289
+ current_line += elem.text.upper()
290
+ if current_line:
291
+ lines.append(current_line.strip()) # Append the last line
292
+ return "\n".join(lines)
293
+
294
+ def render_editor(text_element):
295
+ lines = []
296
+ current_line = ""
297
+ for elem in text_element.iter():
298
+ if elem.tag == "lb":
299
+ if current_line:
300
+ lines.append(current_line.strip())
301
+ current_line = "" # Start a new line
302
+ line_number = elem.get("n", "")
303
+ current_line += f"{line_number} " if line_number else ""
304
+ elif elem.tag == "supplied":
305
+ # Process nested <expan> elements with abbreviation and expansion
306
+ supplied_content = []
307
+ for sub_elem in elem.findall(".//expan"): # Nested <expan> elements
308
+ abbr_elem = sub_elem.find("abbr")
309
+ ex_elem = sub_elem.find("ex")
310
+ abbr = abbr_elem.text if abbr_elem is not None and abbr_elem.text else ""
311
+ ex = ex_elem.text if ex_elem is not None and ex_elem.text else ""
312
+ supplied_content.append(f"{abbr}({ex})")
313
+ current_line += " ".join(supplied_content)
314
+ elif elem.tag == "expan":
315
+ # Render abbreviation and expansion
316
+ abbr_elem = elem.find("abbr")
317
+ ex_elem = elem.find("ex")
318
+ abbr = abbr_elem.text if abbr_elem is not None and abbr_elem.text else ""
319
+ ex = ex_elem.text if ex_elem is not None and ex_elem.text else ""
320
+ current_line += f"{abbr}({ex})"
321
+ elif elem.tag == "g" and elem.get("type") == "leaf":
322
+ current_line += " ((leaf)) "
323
+ elif elem.tag == "title" and elem.get("type") == "emperor":
324
+ # Render title abbreviation and name
325
+ title_ref = elem.get('ref')
326
+ title_info = titles_dict.get(title_ref, {})
327
+ abbreviation = title_info.get('Abbreviation', '')
328
+ name_gr = title_info.get('Name_GR', '')
329
+ current_line += f"{abbreviation} {name_gr}"
330
+ elif elem.text and elem.tag not in ["supplied", "expan", "g", "title"]:
331
+ current_line += elem.text
332
+ if current_line:
333
+ lines.append(current_line.strip()) # Append the last line
334
+ return "\n".join(lines)
335
+
336
+ # -------------------------------
337
+ # Streamlit App Layout
338
+ # -------------------------------
339
+
340
+ st.set_page_config(page_title="Epigraphic XML Viewer", layout="wide")
341
+ st.title("Epigraphic XML Viewer: Diplomatic and Editor Editions")
342
+
343
+ # -------------------------------
344
+ # Sidebar - Project Information
345
+ # -------------------------------
346
+ with st.sidebar:
347
+ st.header("Project Information")
348
+ st.markdown("""
349
+ **Epigraphic Database Viewer** is a tool designed to visualize and analyze ancient inscriptions.
350
+
351
+ **Features**:
352
+ - Upload and view XML inscriptions data.
353
+ - Explore inscriptions in various formats.
354
+ - Visualize geographical origins on an interactive map.
355
+
356
+ **Authority Lists**:
357
+ - **Materials**: Details about materials used in inscriptions.
358
+ - **Places**: Geographical data and descriptions.
359
+ - **Emperor Titles**: Titles and abbreviations used in inscriptions.
360
+
361
+ **Developed by**: Your Name or Team
362
+ """)
363
+
364
+ # -------------------------------
365
+ # File uploader for Inscriptions XML
366
+ # -------------------------------
367
+ uploaded_file = st.file_uploader("Upload Inscriptions XML File", type=["xml"])
368
+
369
+ if uploaded_file:
370
+ st.success("File uploaded successfully!")
371
+ # Read uploaded XML content
372
+ inscriptions_content = uploaded_file.getvalue().decode("utf-8")
373
+ else:
374
+ st.info("No file uploaded. Using default sample XML data.")
375
+ # Default XML data (as provided by the user)
376
+ inscriptions_content = """<?xml version="1.0" encoding="UTF-8"?>
377
+ <!DOCTYPE epiData SYSTEM "epiData.dtd"> <!--<!DOCTYPE epiData SYSTEM "https://raw.githubusercontent.com/Bestroi150/EpiDataBase/refs/heads/main/epiData.dtd">-->
378
+
379
+ <epiData>
380
+ <inscription n="1">
381
+ <Publisher>EDCS</Publisher>
382
+ <Origin ref="VIZE">Vize</Origin>
383
+ <Origin-Geonames-Link>https://www.geonames.org/738154/vize.html</Origin-Geonames-Link>
384
+ <Origin-Pleiades-Link>https://pleiades.stoa.org/places/511190</Origin-Pleiades-Link>
385
+ <Institution ID="AE 1951, 00257"></Institution>
386
+ <Category>
387
+ <term>Augusti/Augustae</term>
388
+ <term>ordo senatorius</term>
389
+ <term>tituli sacri</term>
390
+ <term>tria nomina</term>
391
+ <term>viri</term>
392
+ </Category>
393
+ <Material ref="LAPIS">lapis</Material>
394
+ <Language>Greek</Language>
395
+ <Text>
396
+ <lb n="1"/>ἀγαθῇ τύχῃ
397
+ <lb n="2"/>ὑπὲρ τῆς τοῦ <title type="emperor" ref="IMPERATOR">Αὐτοκράτορος</title>
398
+ <lb n="3" break="no"/><expan><abbr>T</abbr><ex>ίτου</ex></expan> <expan>Αἰλ<ex>ίου</ex></expan> <persName type="emperor">Ἁδριανοῦ Ἀντωνείνου</persName> <title type="emperor">Καί
399
+ <lb n="4"/>σαρος</title><expan>Σεβ<ex>αστοῦ</ex></expan> Εὐσεβοῦς καὶ Οὐήρου Καίσαρ
400
+ <lb n="5"/>ος νείκης τε καὶ αἰωνίου διαμονῆς καὶ τοῦ
401
+ <lb n="6"/>σύμπαντος αὐτῶν οἴκου ἱερᾶς τε
402
+ <lb n="7"/>συνκλήτου καὶ δήμου Ῥωμαίων
403
+ <lb n="8" break="no"/>ἡγεμονεύοντος <place type="province">ἐπαρχείας Θρᾴκης</place>
404
+ <lb n="9"/><persName type="official"> <expan>Γ<ex>αΐου</ex></expan> Ἰουλίου <expan>Κομ<ex>μ</ex></expan>όδου</persName> <title type="official">πρεσβ<ex>ευτοῦ</ex></title> <expan>Σεβ<ex>αστοῦ</ex></expan>
405
+ <lb n="10"/>ἀντιστρατήγου ἡ <place type="city">πόλις Βιζυηνῶν</place>
406
+ <lb n="11"/>κατεσκεύασεν τοὺς πυργοὺς διὰ
407
+ <lb n="12" break="no"/>ἐπιμελητῶν Φίρμου Αυλουπορε
408
+ <lb n="13"/>ος καὶ Αυλουκενθου Δυτουκενθου
409
+ <lb n="14"/>καὶ Ραζδου Ὑακίνθου εὐτυχεῖτε
410
+ </Text>
411
+ <Dating>155 to 155</Dating>
412
+ <Images>https://db.edcs.eu/epigr/ae/ae1951/ae1951-74.pdf</Images>
413
+ <Encoder>Admin</Encoder>
414
+ </inscription>
415
+
416
+ <!-- Additional inscriptions can be added here -->
417
+
418
+ </epiData>
419
+ """
420
+
421
+ # -------------------------------
422
+ # Parse Inscriptions
423
+ # -------------------------------
424
+
425
+ try:
426
+ df = parse_inscriptions(inscriptions_content)
427
+ except ET.ParseError as e:
428
+ st.error(f"Error parsing XML: {e}")
429
+ st.stop()
430
+
431
+ # -------------------------------
432
+ # Tabs for Different Views
433
+ # -------------------------------
434
+ tabs = st.tabs(["Raw XML", "DataFrame", "Diplomatic Edition", "Editor Edition", "Visualization"])
435
+
436
+ # -------------------------------
437
+ # Raw XML Tab
438
+ # -------------------------------
439
+ with tabs[0]:
440
+ st.subheader("Raw XML Content")
441
+ st.text_area("XML Content", inscriptions_content, height=600)
442
+
443
+ # -------------------------------
444
+ # DataFrame Tab
445
+ # -------------------------------
446
+ with tabs[1]:
447
+ st.subheader("Inscriptions Data")
448
+ st.dataframe(df)
449
+
450
+ # -------------------------------
451
+ # Diplomatic Edition Tab
452
+ # -------------------------------
453
+ with tabs[2]:
454
+ st.subheader("Diplomatic Edition")
455
+ # Select Inscription
456
+ inscription_numbers = df['Number'].tolist()
457
+ selected_inscription_num = st.selectbox("Select Inscription Number", inscription_numbers)
458
+ selected_inscription = df[df['Number'] == selected_inscription_num].iloc[0]
459
+
460
+ # Parse the selected inscription's XML to get the Text element
461
+ tree = ET.ElementTree(ET.fromstring(inscriptions_content))
462
+ root = tree.getroot()
463
+ inscription_elem = root.find(f".//inscription[@n='{selected_inscription_num}']")
464
+ text_element = inscription_elem.find("Text") if inscription_elem is not None else None
465
+
466
+ if text_element is not None:
467
+ diplomatic_text = render_diplomatic(text_element)
468
+ st.text_area("Diplomatic Edition Text", diplomatic_text, height=600)
469
+ else:
470
+ st.warning("No text found for the selected inscription.")
471
+
472
+ # -------------------------------
473
+ # Editor Edition Tab
474
+ # -------------------------------
475
+ with tabs[3]:
476
+ st.subheader("Editor Edition")
477
+ # Select Inscription
478
+ inscription_numbers = df['Number'].tolist()
479
+ selected_inscription_num = st.selectbox("Select Inscription Number", inscription_numbers, key='editor_select')
480
+ selected_inscription = df[df['Number'] == selected_inscription_num].iloc[0]
481
+
482
+ # Parse the selected inscription's XML to get the Text element
483
+ tree = ET.ElementTree(ET.fromstring(inscriptions_content))
484
+ root = tree.getroot()
485
+ inscription_elem = root.find(f".//inscription[@n='{selected_inscription_num}']")
486
+ text_element = inscription_elem.find("Text") if inscription_elem is not None else None
487
+
488
+ if text_element is not None:
489
+ editor_text = render_editor(text_element)
490
+ st.text_area("Editor Edition Text", editor_text, height=600)
491
+ else:
492
+ st.warning("No text found for the selected inscription.")
493
+
494
+ # -------------------------------
495
+ # Visualization Tab
496
+ # -------------------------------
497
+ with tabs[4]:
498
+ st.subheader("Visualization")
499
+
500
+ # Extract categories
501
+ all_categories = set()
502
+ for categories in df['Categories']:
503
+ for cat in categories.split(", "):
504
+ all_categories.add(cat)
505
+
506
+ # Category filtering
507
+ selected_categories = st.multiselect("Filter by Category", sorted(all_categories))
508
+
509
+ if selected_categories:
510
+ filtered_df = df[df['Categories'].apply(lambda x: any(cat in x.split(", ") for cat in selected_categories))]
511
+ else:
512
+ filtered_df = df.copy()
513
+
514
+ # Merge with places to get coordinates
515
+ def get_coordinates(origin_id):
516
+ place = places_dict.get(origin_id, {})
517
+ return place.get('Latitude'), place.get('Longitude')
518
+
519
+ # Apply the function to get Latitude and Longitude
520
+ filtered_df['Latitude'], filtered_df['Longitude'] = zip(*filtered_df['Origin_ID'].apply(get_coordinates))
521
+
522
+ # Drop entries without coordinates
523
+ map_df = filtered_df.dropna(subset=['Latitude', 'Longitude'])
524
+
525
+ if not map_df.empty:
526
+ # Create a Folium map centered around the average coordinates
527
+ avg_lat = map_df['Latitude'].mean()
528
+ avg_lon = map_df['Longitude'].mean()
529
+ folium_map = folium.Map(location=[avg_lat, avg_lon], zoom_start=6)
530
+
531
+ # Add markers to the map
532
+ for _, row in map_df.iterrows():
533
+ popup_content = f"""
534
+ <b>Inscription Number:</b> {row['Number']}<br>
535
+ <b>Publisher:</b> {row['Publisher']}<br>
536
+ <b>Material:</b> {row['Material']}<br>
537
+ <b>Language:</b> {row['Language']}<br>
538
+ <b>Dating:</b> {row['Dating']}<br>
539
+ <b>Encoder:</b> {row['Encoder']}<br>
540
+ <b>Categories:</b> {row['Categories']}<br>
541
+ <b>Text:</b> {row['Text']}<br>
542
+ """
543
+ if row['Images'] and row['Images'] != "N/A":
544
+ popup_content += f'<a href="{row["Images"]}" target="_blank">View Images</a><br>'
545
+ folium.Marker(
546
+ location=[row['Latitude'], row['Longitude']],
547
+ popup=folium.Popup(popup_content, max_width=300),
548
+ tooltip=f"Inscription {row['Number']}"
549
+ ).add_to(folium_map)
550
+
551
+ # Display the Folium map using streamlit_folium
552
+ st_folium(folium_map, width=700, height=500)
553
+ else:
554
+ st.write("No inscriptions to display on the map based on the selected filters.")
555
+
556
+ st.dataframe(filtered_df)
557
+
558
+ # Detailed View
559
+ for _, row in filtered_df.iterrows():
560
+ with st.expander(f"Inscription {row['Number']}"):
561
+ st.markdown(f"**Publisher**: {row['Publisher']}")
562
+ st.markdown(f"**Origin**: {row['Origin']} ([GeoNames Link]({row['GeoNames Link']}), [Pleiades Link]({row['Pleiades Link']}))")
563
+ st.markdown(f"**Material**: {row['Material']} - {materials_dict.get(row['Material_ID'], {}).get('Description', '')}")
564
+ st.markdown(f"**Language**: {row['Language']}")
565
+ st.markdown(f"**Dating**: {row['Dating']}")
566
+ st.markdown(f"**Encoder**: {row['Encoder']}")
567
+ st.markdown(f"**Categories**: {row['Categories']}")
568
+ st.markdown(f"**Text**:\n\n{row['Text']}")
569
+ if row['Images'] and row['Images'] != "N/A":
570
+ st.markdown(f"[View Images]({row['Images']})")
571
+ # Display material description
572
+ material_desc = materials_dict.get(row['Material_ID'], {}).get('Description', "No description available.")
573
+ st.markdown(f"**Material Description**: {material_desc}")
574
+ # Display place description
575
+ place_desc = places_dict.get(row['Origin_ID'], {}).get('Description', "No description available.")
576
+ st.markdown(f"**Place Description**: {place_desc}")
577
+
578
+ # -------------------------------
579
+ # Footer
580
+ # -------------------------------
581
+ st.markdown("""
582
+ ---
583
+ **© 2024 InscriptaNETr**
584
+ """)