pyeasyphd 0.0.2__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.

Potentially problematic release.


This version of pyeasyphd might be problematic. Click here for more details.

Files changed (80) hide show
  1. pyeasyphd/.python-version +1 -0
  2. pyeasyphd/Main.sublime-menu +43 -0
  3. pyeasyphd/__init__.py +0 -0
  4. pyeasyphd/bib/__init__.py +1 -0
  5. pyeasyphd/bib/bibtexbase/__init__.py +7 -0
  6. pyeasyphd/bib/bibtexbase/standardize/_base.py +36 -0
  7. pyeasyphd/bib/bibtexbase/standardize/default_data.py +97 -0
  8. pyeasyphd/bib/bibtexbase/standardize/do_on_bib.py +54 -0
  9. pyeasyphd/bib/bibtexbase/standardize/do_on_comment_block.py +38 -0
  10. pyeasyphd/bib/bibtexbase/standardize/do_on_entry_block.py +310 -0
  11. pyeasyphd/bib/bibtexbase/standardize/do_on_preamble_block.py +35 -0
  12. pyeasyphd/bib/bibtexbase/standardize/do_on_string_block.py +34 -0
  13. pyeasyphd/bib/bibtexbase/standardize_bib.py +75 -0
  14. pyeasyphd/bib/bibtexparser/__init__.py +47 -0
  15. pyeasyphd/bib/bibtexparser/bibtex_format.py +87 -0
  16. pyeasyphd/bib/bibtexparser/exceptions.py +64 -0
  17. pyeasyphd/bib/bibtexparser/library.py +207 -0
  18. pyeasyphd/bib/bibtexparser/middlewares/block/add.py +94 -0
  19. pyeasyphd/bib/bibtexparser/middlewares/block/authors.py +22 -0
  20. pyeasyphd/bib/bibtexparser/middlewares/block/doi_url.py +62 -0
  21. pyeasyphd/bib/bibtexparser/middlewares/block/entry_field_keys_normalize.py +47 -0
  22. pyeasyphd/bib/bibtexparser/middlewares/block/entry_field_keys_replace.py +31 -0
  23. pyeasyphd/bib/bibtexparser/middlewares/block/entry_field_values_normalize.py +222 -0
  24. pyeasyphd/bib/bibtexparser/middlewares/block/entry_fields_delete.py +34 -0
  25. pyeasyphd/bib/bibtexparser/middlewares/block/entry_fields_keep.py +33 -0
  26. pyeasyphd/bib/bibtexparser/middlewares/block/entry_fields_sort.py +70 -0
  27. pyeasyphd/bib/bibtexparser/middlewares/block/entry_types.py +15 -0
  28. pyeasyphd/bib/bibtexparser/middlewares/block/journal_booktitle.py +113 -0
  29. pyeasyphd/bib/bibtexparser/middlewares/block/month_year.py +34 -0
  30. pyeasyphd/bib/bibtexparser/middlewares/block/number_volume.py +21 -0
  31. pyeasyphd/bib/bibtexparser/middlewares/block/pages.py +28 -0
  32. pyeasyphd/bib/bibtexparser/middlewares/block/title.py +20 -0
  33. pyeasyphd/bib/bibtexparser/middlewares/library/generating_entrykeys.py +98 -0
  34. pyeasyphd/bib/bibtexparser/middlewares/library/keeping_blocks.py +29 -0
  35. pyeasyphd/bib/bibtexparser/middlewares/library/sorting_blocks.py +124 -0
  36. pyeasyphd/bib/bibtexparser/middlewares/middleware.py +222 -0
  37. pyeasyphd/bib/bibtexparser/middlewares/parsestack.py +13 -0
  38. pyeasyphd/bib/bibtexparser/middlewares/utils.py +226 -0
  39. pyeasyphd/bib/bibtexparser/middlewares_library_to_library.py +414 -0
  40. pyeasyphd/bib/bibtexparser/middlewares_library_to_str.py +42 -0
  41. pyeasyphd/bib/bibtexparser/middlewares_str_to_library.py +35 -0
  42. pyeasyphd/bib/bibtexparser/middlewares_str_to_str.py +29 -0
  43. pyeasyphd/bib/bibtexparser/model.py +481 -0
  44. pyeasyphd/bib/bibtexparser/splitter.py +151 -0
  45. pyeasyphd/bib/core/__init__.py +18 -0
  46. pyeasyphd/bib/core/convert_library_to_library.py +31 -0
  47. pyeasyphd/bib/core/convert_library_to_str.py +199 -0
  48. pyeasyphd/bib/core/convert_str_to_library.py +34 -0
  49. pyeasyphd/bib/core/convert_str_to_str.py +27 -0
  50. pyeasyphd/main/__init__.py +17 -0
  51. pyeasyphd/main/basic_input.py +149 -0
  52. pyeasyphd/main/pandoc_md_to.py +361 -0
  53. pyeasyphd/main/python_run_bib.py +73 -0
  54. pyeasyphd/main/python_run_md.py +235 -0
  55. pyeasyphd/main/python_run_tex.py +149 -0
  56. pyeasyphd/main/python_writers.py +212 -0
  57. pyeasyphd/pyeasyphd.py +72 -0
  58. pyeasyphd/pyeasyphd.sublime-settings +235 -0
  59. pyeasyphd/pyeasyphd.sublime-syntax +5 -0
  60. pyeasyphd/tools/__init__.py +30 -0
  61. pyeasyphd/tools/compare/compare_bibs.py +234 -0
  62. pyeasyphd/tools/experiments_base.py +203 -0
  63. pyeasyphd/tools/format_save_bibs.py +178 -0
  64. pyeasyphd/tools/generate/generate_from_bibs.py +447 -0
  65. pyeasyphd/tools/generate/generate_links.py +356 -0
  66. pyeasyphd/tools/py_run_bib_md_tex.py +378 -0
  67. pyeasyphd/tools/replace/replace.py +81 -0
  68. pyeasyphd/tools/search/data.py +318 -0
  69. pyeasyphd/tools/search/search_base.py +118 -0
  70. pyeasyphd/tools/search/search_core.py +326 -0
  71. pyeasyphd/tools/search/search_keywords.py +227 -0
  72. pyeasyphd/tools/search/search_writers.py +288 -0
  73. pyeasyphd/tools/search/utils.py +152 -0
  74. pyeasyphd/tools/spider/process_spider_bib.py +247 -0
  75. pyeasyphd/tools/spider/process_spider_url.py +74 -0
  76. pyeasyphd/tools/spider/process_spider_url_bib.py +62 -0
  77. pyeasyphd/utils/utils.py +62 -0
  78. pyeasyphd-0.0.2.dist-info/METADATA +27 -0
  79. pyeasyphd-0.0.2.dist-info/RECORD +80 -0
  80. pyeasyphd-0.0.2.dist-info/WHEEL +4 -0
@@ -0,0 +1,447 @@
1
+ import os
2
+ import re
3
+ from typing import Any, Dict, List, Union
4
+
5
+ from pyadvtools import IterateSortDict, standard_path, write_list
6
+
7
+ from ...bib.bibtexparser import Entry, Library
8
+ from ...main import PandocMdTo, PythonRunBib, PythonWriters
9
+ from ...utils.utils import html_head, html_style, html_tail, textarea_header, textarea_tail
10
+ from ..experiments_base import generate_standard_publisher_abbr_options_dict
11
+
12
+
13
+ def preparation(
14
+ path_storage: str,
15
+ path_output: str,
16
+ output_basename: str,
17
+ pub_type: str,
18
+ issue_or_month_flag: Union[str, List[str]] = "current_issue",
19
+ year_flag: Union[str, List[str]] = "current_year",
20
+ options: Dict[str, Any] = {},
21
+ ):
22
+ """Prepare paths and flags for data generation.
23
+
24
+ Examples
25
+ --------
26
+ | | current_issue | last_issue | current_month | last_month | given_month | given_months | all_months |
27
+ |--------------|---------------|------------|---------------|------------|-------------|--------------|------------|
28
+ | current_year | | | | | "2" | ["1", "3"] | |
29
+ | given_year | "2020" | | | | | | |
30
+ | given_years | ["2025"] | | | | | | |
31
+ | all_years | | | | | | | |
32
+
33
+ Returns
34
+ -------
35
+ Tuple[str, str, bool]
36
+ Returns (path_root, path_output, combine_flag)
37
+ """
38
+ # default settings
39
+ path_storage = standard_path(path_storage)
40
+ path_output = standard_path(path_output)
41
+
42
+ # "absolute_path" or "relative_path"
43
+ absolute_or_relative_path = options.get("absolute_or_relative_path", "absolute_path")
44
+
45
+ # Create path components
46
+ yy = "-".join(year_flag) if isinstance(year_flag, List) else year_flag
47
+ im = "-".join(issue_or_month_flag) if isinstance(issue_or_month_flag, List) else issue_or_month_flag
48
+
49
+ if options.get("early_access", False):
50
+ base_path = f"{output_basename}/{pub_type.title()}_Early_Access/{yy}_{im}"
51
+ path_output = os.path.join(path_output + "_Early_Access", f"{yy}_{im}")
52
+ else:
53
+ base_path = f"{output_basename}/{pub_type.title()}/{yy}_{im}"
54
+ path_output = os.path.join(path_output, f"{yy}_{im}")
55
+
56
+ path_root = base_path if absolute_or_relative_path == "absolute_path" else ""
57
+
58
+ # Determine combine flag
59
+ b = options.get("early_access", False) and (year_flag != "all_years")
60
+ c = year_flag == "current_year"
61
+ c = c and (not isinstance(issue_or_month_flag, list)) and (issue_or_month_flag != "all_months")
62
+ combine_flag = b or c
63
+
64
+ return path_root, path_output, combine_flag
65
+
66
+
67
+ def generate_from_bibs_and_write(
68
+ path_storage: str,
69
+ path_output: str,
70
+ output_basename: str,
71
+ pub_type: str,
72
+ generate_or_combine: str,
73
+ year_flag: Union[str, List[str]] = "current_year",
74
+ issue_or_month_flag: Union[str, List[str]] = "current_issue",
75
+ options: Dict[str, Any] = {},
76
+ ) -> None:
77
+ """Generate or combine data from bibliographies.
78
+
79
+ Parameters
80
+ ----------
81
+ path_storage : str
82
+ Path to storage directory
83
+ path_output : str
84
+ Path to output directory
85
+ generate_or_combine : str
86
+ Either "generate_data" or "combine_data"
87
+ year_flag : Union[str, List[str]], optional
88
+ Flag for year selection, by default "current_year"
89
+ issue_or_month_flag : Union[str, List[str]], optional
90
+ Flag for issue/month selection, by default "current_issue"
91
+ options : Dict[str, Any], optional
92
+ Additional options, by default {}
93
+ """
94
+ path_root, path_output, combine_flag = preparation(
95
+ path_storage, path_output, output_basename, pub_type, issue_or_month_flag, year_flag, options
96
+ )
97
+
98
+ if generate_or_combine == "generate_data":
99
+ publisher_abbr_dict = generate_standard_publisher_abbr_options_dict(path_storage, options)
100
+ for publisher in publisher_abbr_dict:
101
+ pp = os.path.join(path_output, publisher.lower())
102
+
103
+ publisher_html_body = []
104
+ for abbr in publisher_abbr_dict[publisher]:
105
+ print(f"*** Processing {publisher.upper()}: {abbr} ***")
106
+ new_options = publisher_abbr_dict[publisher][abbr]
107
+
108
+ # Get bibliography path
109
+ path_abbr = os.path.join(path_storage, f"{publisher.lower()}/{abbr}")
110
+ if isinstance(year_flag, str) and year_flag.isdigit():
111
+ for root, _, files in os.walk(path_abbr, topdown=True):
112
+ files = [f for f in files if f.endswith(".bib")]
113
+ if files := [f for f in files if re.search(f"_{year_flag}.bib", f)]:
114
+ path_abbr = os.path.join(root, files[0])
115
+
116
+ # Generate and process library
117
+ library = generate_given_library(path_abbr, issue_or_month_flag, year_flag, new_options)
118
+
119
+ # Generate md, tex, pdf, html
120
+ html_body = generate_md_tex_pdf_html(abbr, library, pp, new_options)
121
+ if combine_flag and html_body:
122
+ publisher_html_body.extend(html_body + ["\n"])
123
+
124
+ # Combine for publisher
125
+ if publisher_html_body:
126
+ html_content = _html_content(publisher_html_body[:-1], publisher)
127
+ write_list(html_content, f"{publisher}_all.html", "w", pp, False)
128
+
129
+ elif generate_or_combine == "combine_data":
130
+ # Compulsory
131
+ options["include_abbr_list"] = []
132
+ options["exclude_abbr_list"] = []
133
+ publisher_abbr_dict = generate_standard_publisher_abbr_options_dict(path_storage, options)
134
+ for publisher in publisher_abbr_dict:
135
+ print(f"*** Combining papers for {publisher.upper()} ***")
136
+ pp = os.path.join(path_output, publisher.lower())
137
+ absolute_path = f"{path_root}/{publisher}" if len(path_root) > 0 else ""
138
+
139
+ link = [f"# {publisher.upper()}\n\n"]
140
+ for abbr in publisher_abbr_dict[publisher]:
141
+ if os.path.exists(os.path.join(pp, abbr, f"{abbr}.html")):
142
+ link.append(f"- [{abbr}]({absolute_path}/{abbr}/{abbr}.html)\n")
143
+
144
+ if combine_flag:
145
+ link.insert(1, f"- [All Journals]({absolute_path}/{publisher}_all.html)\n")
146
+
147
+ # Process combined content
148
+ if len(link) > 1:
149
+ write_list(link, f"{publisher}_link.md", "w", pp, False)
150
+ PandocMdTo({}).pandoc_md_to_html(pp, pp, f"{publisher}_link.md", f"{publisher}_link.html", True)
151
+
152
+ # Clean up
153
+ for name in ["_link"]:
154
+ if os.path.exists(file := os.path.join(pp, f"{publisher}{name}.md")):
155
+ os.remove(file)
156
+ return None
157
+
158
+
159
+ def generate_given_library(
160
+ original_data: Union[List[str], str, Library],
161
+ issue_or_month_flag: Union[str, List[str]],
162
+ year_flag: Union[str, List[str]] = "current_year",
163
+ options: Dict[str, Any] = {},
164
+ ) -> Library:
165
+ """Generate a Library object from input data with given filters.
166
+
167
+ Parameters
168
+ ----------
169
+ original_data : Union[List[str], str, Library]
170
+ Input bibliography data
171
+ issue_or_month_flag : Union[str, List[str]]
172
+ Flag for issue/month selection
173
+ year_flag : Union[str, List[str]], optional
174
+ Flag for year selection, by default "current_year"
175
+ options : Dict[str, Any], optional
176
+ Additional options, by default {}
177
+
178
+ Returns
179
+ -------
180
+ Library
181
+ Processed library object
182
+ """
183
+ _options = {}
184
+ # convert_str_to_library
185
+ _options["is_standardize_bib"] = False # default is True
186
+ # middlewares_str_to_library.py
187
+ _options["is_display_implicit_comments"] = False # default is True
188
+
189
+ # convert_library_to_library.py
190
+ _options["choose_abbr_zotero_save"] = "save" # default is "save"
191
+ # middlewares_library_to_library.py
192
+ _options["generate_entry_cite_keys"] = False # default is False
193
+ _options["function_common_again"] = False # default is True
194
+ _options["function_common_again_abbr"] = False # default is True
195
+ _options["function_common_again_zotero"] = False # default is True
196
+ _options["function_common_again_save"] = False # default is True
197
+
198
+ # convert_library_to_str.py
199
+ # middlewares_library_to_str.py
200
+ _options["is_sort_entry_fields"] = True # compulsory
201
+ _options["is_sort_blocks"] = True # compulsory
202
+ _options["sort_entries_by_field_keys_reverse"] = True # compulsory
203
+
204
+ # convert_str_to_str.py
205
+ _options["default_additional_field_list"] = []
206
+ # middlewares_str_to_str.py
207
+ _options["substitute_in_bib"] = False # default is True
208
+
209
+ _options.update(options)
210
+ _python_bib = PythonRunBib(_options)
211
+
212
+ # Generate nested entries dictionary
213
+ entry_type_year_volume_number_month_entry_dict = _python_bib.parse_to_nested_entries_dict(original_data)
214
+ old_dict = entry_type_year_volume_number_month_entry_dict
215
+
216
+ # Filter by year_flag
217
+ new_dict = {}
218
+ for entry_type in old_dict:
219
+ years = [year for year in old_dict[entry_type]]
220
+
221
+ # Update years
222
+ if isinstance(year_flag, List):
223
+ years = sorted(list(set(years).intersection(set(year_flag))))
224
+ elif year_flag.lower().strip() == "all_years":
225
+ years = years
226
+ elif year_flag.lower().strip() == "current_year":
227
+ years = years[:1]
228
+ else:
229
+ if year_flag not in years:
230
+ continue
231
+ else:
232
+ years = [year_flag]
233
+
234
+ for year in years:
235
+ new_dict.setdefault(entry_type, {}).update({year: old_dict[entry_type][year]})
236
+
237
+ # Filter by issue/month flag
238
+ if issue_or_month_flag in ["current_issue", "last_issue"]:
239
+ return obtain_issue_flag_library(new_dict, issue_or_month_flag)
240
+
241
+ return obtain_month_flag_library(new_dict, issue_or_month_flag)
242
+
243
+
244
+ def obtain_issue_flag_library(
245
+ old_dict: Dict[str, Dict[str, Dict[str, Dict[str, Dict[str, List[Entry]]]]]], issue_flag: str = "current_issue"
246
+ ) -> Library:
247
+ """Filter library by issue flag."""
248
+ old_dict = IterateSortDict(True).dict_update(old_dict)
249
+
250
+ entries = []
251
+ for entry_type in old_dict:
252
+ for year in old_dict[entry_type]:
253
+ temp_dict = old_dict[entry_type][year]
254
+
255
+ # Article entries
256
+ if entry_type.lower() == "article":
257
+ volumes, numbers, months = [], [], []
258
+ for volume in (volumes := [volume for volume in temp_dict]):
259
+ for number in (numbers := [number for number in temp_dict[volume]]):
260
+ months = [month for month in temp_dict[volume][number]]
261
+ break
262
+ break
263
+
264
+ if issue_flag == "current_issue": # current volume, current issue, and current month
265
+ entries.extend(temp_dict[volumes[0]][numbers[0]][months[0]])
266
+
267
+ elif issue_flag == "last_issue":
268
+ # Logic for getting previous issue
269
+ if len(months) == 1:
270
+ if len(numbers) == 1:
271
+ if len(volumes) == 1:
272
+ entries.extend(temp_dict[volumes[0]][numbers[0]][months[0]])
273
+ else:
274
+ numbers = [number for number in temp_dict[volumes[1]]]
275
+ months = [month for month in temp_dict[volumes[1]][numbers[0]]]
276
+ entries.extend(temp_dict[volumes[1]][numbers[0]][months[0]])
277
+ else:
278
+ months = [month for month in temp_dict[volumes[0]][numbers[1]]]
279
+ entries.extend(temp_dict[volumes[0]][numbers[1]][months[0]])
280
+ else:
281
+ entries.extend(temp_dict[volumes[0]][numbers[0]][months[1]])
282
+
283
+ else:
284
+ print(f"Unknown issue flag: {issue_flag}.")
285
+
286
+ else:
287
+ # Non-article entries
288
+ for volume in temp_dict:
289
+ for number in temp_dict[volume]:
290
+ for month in temp_dict[volume][number]:
291
+ entries.extend(temp_dict[volume][number][month])
292
+
293
+ return Library(entries)
294
+
295
+
296
+ def obtain_month_flag_library(
297
+ old_dict: Dict[str, Dict[str, Dict[str, Dict[str, Dict[str, List[Entry]]]]]],
298
+ month_flag: Union[str, List[str]] = "current_month",
299
+ ) -> Library:
300
+ """Filter library by month flag."""
301
+ new_dict = {}
302
+ for entry_type in old_dict:
303
+ for year in old_dict[entry_type]:
304
+
305
+ for volume in old_dict[entry_type][year]:
306
+ for number in old_dict[entry_type][year][volume]:
307
+ for month in old_dict[entry_type][year][volume][number]:
308
+ new_dict.setdefault(entry_type, {}).setdefault(year, {}).setdefault(month, {}).setdefault(
309
+ volume, {}
310
+ ).setdefault(number, []).extend(old_dict[entry_type][year][volume][number][month])
311
+
312
+ # Sort
313
+ old_dict = IterateSortDict(True).dict_update(new_dict)
314
+
315
+ entries = []
316
+ for entry_type in old_dict:
317
+ for year in old_dict[entry_type]:
318
+ temp_dict = old_dict[entry_type][year]
319
+ default_months = [month for month in temp_dict]
320
+
321
+ # Update month
322
+ new_months = []
323
+ if month_flag == "current_month": # current_month
324
+ new_months = [default_months[0]]
325
+ elif month_flag == "last_month": # last_month
326
+ new_months = [default_months[1]] if len(default_months) > 1 else []
327
+ elif month_flag == "all_months": # all months
328
+ new_months = default_months
329
+ else:
330
+ if isinstance(month_flag, str): # given month
331
+ if month_flag in default_months:
332
+ new_months = [month_flag]
333
+ else:
334
+ for month in month_flag: # given months
335
+ if month in default_months:
336
+ new_months.append(month)
337
+
338
+ for month in new_months:
339
+ for volume in temp_dict[month]:
340
+ for number in temp_dict[month][volume]:
341
+ entries.extend(temp_dict[month][volume][number])
342
+
343
+ return Library(entries)
344
+
345
+
346
+ def generate_md_tex_pdf_html(
347
+ abbr_standard: str,
348
+ original_bib_data: Union[List[str], str, Library],
349
+ path_output: str,
350
+ options: Dict[str, Any] = {},
351
+ ) -> List[str]:
352
+ """Generate markdown and LaTeX from bibliography data."""
353
+ options_: dict = {
354
+ # convert_str_to_library
355
+ "is_standardize_bib": False,
356
+ # middlewares_str_to_library.py
357
+ "is_display_implicit_comments": False,
358
+ #
359
+ # convert_library_to_library.py
360
+ # middlewares_library_to_library.py
361
+ "function_common_again": False,
362
+ "function_common_again_abbr": False,
363
+ "function_common_again_zotero": False,
364
+ "function_common_again_save": False,
365
+ "abbr_index_article_for_abbr": 2,
366
+ "abbr_index_inproceedings_for_abbr": 2,
367
+ #
368
+ # convert_library_to_str.py
369
+ "empty_entry_cite_keys": True,
370
+ # middlewares_library_to_str.py
371
+ "is_sort_entry_fields": True,
372
+ "is_sort_blocks": True,
373
+ "sort_entries_by_field_keys_reverse": True,
374
+ }
375
+ options_.update(options)
376
+
377
+ # Process bibliography data
378
+ _python_bib = PythonRunBib(options_)
379
+ _, zotero_library, _ = _python_bib.parse_to_multi_standard_library(original_bib_data)
380
+
381
+ _python_writer = PythonWriters(options_)
382
+
383
+ # Generate HTML content body
384
+ html_body = []
385
+ for entry in zotero_library.entries:
386
+ html_body.append(_format_entry(entry, abbr_standard, _python_writer.write_to_str([entry])))
387
+
388
+ if len(html_body) > 0:
389
+ html_body = (
390
+ [f'<h2 id="{abbr_standard.lower()}">{abbr_standard} - {len(zotero_library.entries)}</h2>\n', "<ul>\n"]
391
+ + html_body
392
+ + ["</ul>\n"]
393
+ )
394
+
395
+ html_content = _html_content(html_body, abbr_standard)
396
+
397
+ # Write output file
398
+ write_list(html_content, f"{abbr_standard}.html", "w", os.path.join(path_output, abbr_standard), False)
399
+
400
+ return html_body
401
+
402
+
403
+ def _html_content(html_body, abbr_standard):
404
+ html_content = [html_head.format(abbr_standard), html_style, "\n"]
405
+ html_content.extend(html_body)
406
+ html_content.extend([html_tail])
407
+ return html_content
408
+
409
+
410
+ def _format_entry(entry, abbr, data_list):
411
+ """Format a single bibliography entry into HTML."""
412
+ number = entry["number"] if "number" in entry else ""
413
+ pages = entry["pages"] if "pages" in entry else ""
414
+ title = entry["title"] if "title" in entry else ""
415
+ year = entry["year"] if "year" in entry else ""
416
+ volume = entry["volume"] if "volume" in entry else ""
417
+
418
+ url = ""
419
+ if "doi" in entry:
420
+ doi = entry["doi"]
421
+ url = doi if doi.startswith("http") else f"https://doi.org/{doi}"
422
+ elif "url" in entry:
423
+ url = entry["url"]
424
+
425
+ line = _format_entry_apa(title, year, volume, number, pages, url, abbr)
426
+
427
+ line = f"<li><details>\n<summary>\n{line.strip()}\n</summary>\n"
428
+
429
+ return line + textarea_header + "".join(data_list).rstrip() + textarea_tail + "\n</details></li>\n"
430
+
431
+
432
+ def _format_entry_apa(title, year, volume, number, pages, url, abbr):
433
+ line = f"({year}). {title}. <em>{abbr}</em>"
434
+ if volume:
435
+ line += f", <em>{volume}</em>"
436
+ if number:
437
+ line += f"({number})"
438
+
439
+ if pages:
440
+ line += f", {pages}"
441
+
442
+ line += "."
443
+
444
+ if url:
445
+ line += f" (<a href='{url}'>www</a>)"
446
+
447
+ return line