Repository: elifesciences/elife-tools (language: python)

Issue:
Code linting of main modules
Re issue https://github.com/elifesciences/issues/issues/6714

The code had recently been linted using the `black` tool, and this also includes some reductions in long lines, variable names, unused variables, and consistent return values.

There's a couple code changes which should have minimal impact to backwards compatibility but it may affect some situations,

`utils.py` `subject_slug()` default stop words argument is no longer a list, so when passing in `None` the stop words may be different than expected. To get the same functionality as passing `None`, instead pass in an empty listi `[]`

`xmlio.py` `output()` the argument name `type` was changed to `output_type` (to avoid using a reserved word), and this should only be a problem if a function call to it passed in a named argument, and if so, change the name of this in your function call

Outside of the one test case in `test_utils.py` that was changed to use the new `subject_slug()` argument, the test cases remain the same, lead to confidence the changes did not disrupt the previously defined test scenarios.




Discussion / hints:

Fix patch:
diff --git a/elifetools/__init__.py b/elifetools/__init__.py
index 49e0fc1e..777f190d 100644
--- a/elifetools/__init__.py
+++ b/elifetools/__init__.py
@@ -1,1 +1,1 @@
-__version__ = "0.7.0"
+__version__ = "0.8.0"
diff --git a/elifetools/file_utils/__init__.py b/elifetools/file_utils/__init__.py
index 74528683..2791a795 100644
--- a/elifetools/file_utils/__init__.py
+++ b/elifetools/file_utils/__init__.py
@@ -12,6 +12,7 @@ def json_expected_folder(filename):
 def json_expected_file(filename, function_name):
     if json_expected_folder(filename):
         return os.path.join(json_expected_folder(filename), function_name + ".json")
+    return None
 
 
 def sample_xml(filename):
@@ -51,9 +52,9 @@ def read_fixture(folder_name, filename):
 
 
 __all__ = [
-    json_expected_file,
-    json_expected_folder,
-    sample_xml,
-    fixture_folder,
-    read_fixture,
+    "json_expected_file",
+    "json_expected_folder",
+    "sample_xml",
+    "fixture_folder",
+    "read_fixture",
 ]
diff --git a/elifetools/json_rewrite.py b/elifetools/json_rewrite.py
index e5ea557a..8edc58a2 100644
--- a/elifetools/json_rewrite.py
+++ b/elifetools/json_rewrite.py
@@ -135,7 +135,10 @@ def elife_references_rewrite_json():
                 ),
                 (
                     "articleTitle",
-                    "WHO Expert Committee on Malaria [meeting held in Geneva from 19 to 30 October 1970]: fifteenth report",
+                    (
+                        "WHO Expert Committee on Malaria [meeting held in Geneva from 19 to 30 "
+                        "October 1970]: fifteenth report"
+                    ),
                 ),
                 (
                     "publisher",
@@ -170,7 +173,10 @@ def elife_references_rewrite_json():
         "bib25": {
             "conference": {
                 "name": [
-                    "Seventeenth Meeting of the RBM Partnership Monitoring and Evaluation Reference Group (MERG)"
+                    (
+                        "Seventeenth Meeting of the RBM Partnership Monitoring and Evaluation "
+                        "Reference Group (MERG)"
+                    )
                 ]
             }
         }
@@ -617,18 +623,19 @@ def elife_references_rewrite_json():
     # Now turn the authors data into the json
     for author_row in references_authors:
         ref_json = OrderedDict()
-        doi, id, author_type, authors = author_row
-        # if id not in ref_json:
-        ref_json[id] = OrderedDict()
-        ref_json[id][author_type] = []
+        doi, author_id, author_type, authors = author_row
+        # if author_id not in ref_json:
+        ref_json[author_id] = OrderedDict()
+        ref_json[author_id][author_type] = []
         for ref_author in authors:
             if "collab" in ref_author:
                 author_json = elifetools.utils_html.references_author_collab(ref_author)
             else:
                 author_json = elifetools.utils.references_author_person(ref_author)
             if author_json:
-                ref_json[id][author_type].append(author_json)
-        # Add to json array, and do not verwrite existing rule of a specific bib id (if present)
+                ref_json[author_id][author_type].append(author_json)
+        # Add to json array, and do not rewrite existing rule of a
+        # specific bib author_id (if present)
         if doi not in references_rewrite_json:
             references_rewrite_json[doi] = ref_json
         else:
@@ -637,8 +644,8 @@ def elife_references_rewrite_json():
                     references_rewrite_json[doi][key] = value
                 else:
                     # Append dict items
-                    for k, v in value.items():
-                        references_rewrite_json[doi][key][k] = v
+                    for child_key, child_value in value.items():
+                        references_rewrite_json[doi][key][child_key] = child_value
 
     return references_rewrite_json
 
@@ -830,209 +837,104 @@ def rewrite_elife_authors_json(json_content, doi):
                     ] = "MP: Managing Executive Editor, <i>eLife</i>"
 
     # Remainder of competing interests rewrites
+    no_competing_interests_msg = (
+        "The authors declare that no competing interests exist."
+    )
+
     elife_author_competing_interests = {}
-    elife_author_competing_interests[
-        "10.7554/eLife.00133"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.00190"
-    ] = "The authors declare that no competing interests exist."
+    elife_author_competing_interests["10.7554/eLife.00133"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.00190"] = no_competing_interests_msg
     elife_author_competing_interests[
         "10.7554/eLife.00230"
     ] = "The authors have declared that no competing interests exist"
-    elife_author_competing_interests[
-        "10.7554/eLife.00288"
-    ] = "The authors declare that no competing interests exist."
+    elife_author_competing_interests["10.7554/eLife.00288"] = no_competing_interests_msg
     elife_author_competing_interests[
         "10.7554/eLife.00352"
     ] = "The author declares that no competing interest exist"
-    elife_author_competing_interests[
-        "10.7554/eLife.00362"
-    ] = "The authors declare that no competing interests exist."
+    elife_author_competing_interests["10.7554/eLife.00362"] = no_competing_interests_msg
     elife_author_competing_interests[
         "10.7554/eLife.00475"
     ] = "The remaining authors have no competing interests to declare."
     elife_author_competing_interests[
         "10.7554/eLife.00592"
     ] = "The other authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.00633"
-    ] = "The authors declare that no competing interests exist."
+    elife_author_competing_interests["10.7554/eLife.00633"] = no_competing_interests_msg
     elife_author_competing_interests[
         "10.7554/eLife.02725"
     ] = "The other authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.02935"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.04126"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.04878"
-    ] = "The authors declare that no competing interests exist."
+    elife_author_competing_interests["10.7554/eLife.02935"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.04126"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.04878"] = no_competing_interests_msg
     elife_author_competing_interests[
         "10.7554/eLife.05322"
     ] = "The other authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.06011"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.06416"
-    ] = "The authors declare that no competing interests exist."
+    elife_author_competing_interests["10.7554/eLife.06011"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.06416"] = no_competing_interests_msg
     elife_author_competing_interests[
         "10.7554/eLife.07383"
     ] = "The other authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.08421"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.08494"
-    ] = "The authors declare that no competing interests exist."
+    elife_author_competing_interests["10.7554/eLife.08421"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.08494"] = no_competing_interests_msg
     elife_author_competing_interests[
         "10.7554/eLife.08648"
     ] = "The other authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.08924"
-    ] = "The authors declare that no competing interests exist."
+    elife_author_competing_interests["10.7554/eLife.08924"] = no_competing_interests_msg
     elife_author_competing_interests[
         "10.7554/eLife.09083"
     ] = "The other authors declare that no competing interests exists."
-    elife_author_competing_interests[
-        "10.7554/eLife.09102"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.09460"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.09591"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.09600"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.10113"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.10230"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.10453"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.10635"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.11407"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.11473"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.11750"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.12217"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.12620"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.12724"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.13023"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.13732"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.14116"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.14258"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.14694"
-    ] = "The authors declare that no competing interests exist."
+    elife_author_competing_interests["10.7554/eLife.09102"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.09460"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.09591"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.09600"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.10113"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.10230"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.10453"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.10635"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.11407"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.11473"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.11750"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.12217"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.12620"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.12724"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.13023"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.13732"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.14116"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.14258"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.14694"] = no_competing_interests_msg
     elife_author_competing_interests[
         "10.7554/eLife.15085"
     ] = "The other authors declare that no competing interests exist."
     elife_author_competing_interests[
         "10.7554/eLife.15312"
     ] = "The other authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.16011"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.16940"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.17023"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.17092"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.17218"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.17267"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.17523"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.17556"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.17769"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.17834"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.18101"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.18515"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.18544"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.18648"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.19071"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.19334"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.19510"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.20183"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.20242"
-    ] = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.20375"
-    ] = "The authors declare that no competing interests exist."
+    elife_author_competing_interests["10.7554/eLife.16011"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.16940"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.17023"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.17092"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.17218"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.17267"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.17523"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.17556"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.17769"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.17834"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.18101"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.18515"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.18544"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.18648"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.19071"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.19334"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.19510"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.20183"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.20242"] = no_competing_interests_msg
+    elife_author_competing_interests["10.7554/eLife.20375"] = no_competing_interests_msg
     elife_author_competing_interests[
         "10.7554/eLife.20797"
     ] = "The other authors declare that no competing interests exist."
-    msg = "The authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.21454"
-    ] = "The authors declare that no competing interests exist."
+    elife_author_competing_interests["10.7554/eLife.21454"] = no_competing_interests_msg
     elife_author_competing_interests[
         "10.7554/eLife.21491"
     ] = "The other authors declare that no competing interests exist."
-    elife_author_competing_interests[
-        "10.7554/eLife.22187"
-    ] = "The authors declare that no competing interests exist."
+    elife_author_competing_interests["10.7554/eLife.22187"] = no_competing_interests_msg
 
     if article_doi in elife_author_competing_interests:
         for i, ref in enumerate(json_content):
@@ -1066,11 +968,16 @@ def rewrite_elife_datasets_json(json_content, doi):
     elife_dataset_dates.append(("10.7554/eLife.02935", "used", "dataro2", u"2014"))
     elife_dataset_dates.append(("10.7554/eLife.03583", "used", "dataro5", u"2013"))
     if doi in map(lambda dataset: dataset[0], elife_dataset_dates):
-        for (match_doi, used_or_generated, id, dataset_date) in elife_dataset_dates:
+        for (
+            match_doi,
+            used_or_generated,
+            dataset_id,
+            dataset_date,
+        ) in elife_dataset_dates:
             if doi == match_doi:
                 if json_content.get(used_or_generated):
                     for dataset in json_content[used_or_generated]:
-                        if dataset.get("id") and dataset["id"] == id:
+                        if dataset.get("id") and dataset["id"] == dataset_id:
                             if not dataset.get("date"):
                                 dataset["date"] = dataset_date
 
@@ -1289,9 +1196,10 @@ def rewrite_elife_datasets_json(json_content, doi):
                     if not dataset.get("date"):
                         dataset["date"] = u"2016"
                     if not dataset.get("title"):
-                        dataset[
-                            "title"
-                        ] = u"An effector of the Irish potato famine pathogen antagonizes a host autophagy cargo receptor"
+                        dataset["title"] = (
+                            u"An effector of the Irish potato famine pathogen antagonizes a "
+                            u"host autophagy cargo receptor"
+                        )
                     if not dataset.get("authors"):
                         dataset["authors"] = datasets_authors_for_10856
                     if (
@@ -1305,9 +1213,10 @@ def rewrite_elife_datasets_json(json_content, doi):
                     if not dataset.get("date"):
                         dataset["date"] = u"2015"
                     if not dataset.get("title"):
-                        dataset[
-                            "title"
-                        ] = u"An effector of the Irish potato famine pathogen antagonizes a host autophagy cargo receptor"
+                        dataset["title"] = (
+                            u"An effector of the Irish potato famine pathogen antagonizes a "
+                            u"host autophagy cargo receptor"
+                        )
                     if not dataset.get("authors"):
                         dataset["authors"] = datasets_authors_for_10856
                     if (
@@ -1326,9 +1235,10 @@ def rewrite_elife_datasets_json(json_content, doi):
             for dataset in json_content["generated"]:
                 if dataset.get("id") and dataset["id"] == "dataro1":
                     if not dataset.get("title"):
-                        dataset[
-                            "title"
-                        ] = u"Oct4 ChIP-Seq at G1 and G2/M phase of cell cycle in mouse embryonic stem cells"
+                        dataset["title"] = (
+                            u"Oct4 ChIP-Seq at G1 and G2/M phase of cell cycle in "
+                            u"mouse embryonic stem cells"
+                        )
 
     if doi == "10.7554/eLife.10921":
         if json_content.get("generated"):
@@ -1587,7 +1497,13 @@ def rewrite_elife_decision_letter_json(json_content, doi):
             json_content["description"] = [
                 {
                     "type": "paragraph",
-                    "text": "In the interests of transparency, eLife includes the editorial decision letter and accompanying author responses. A lightly edited version of the letter sent to the authors after peer review is shown, indicating the most substantive concerns; minor comments are not usually included.",
+                    "text": (
+                        "In the interests of transparency, eLife includes the editorial "
+                        "decision letter and accompanying author responses. A lightly edited "
+                        "version of the letter sent to the authors after peer review is shown, "
+                        "indicating the most substantive concerns; minor comments are not "
+                        "usually included."
+                    ),
                 }
             ]
 
diff --git a/elifetools/parseJATS.py b/elifetools/parseJATS.py
index df313c97..b0b79bb9 100644
--- a/elifetools/parseJATS.py
+++ b/elifetools/parseJATS.py
@@ -1,5 +1,6 @@
 from collections import OrderedDict
 import copy
+import re
 
 from bs4 import BeautifulSoup
 from slugify import slugify
@@ -7,7 +8,6 @@
 import elifetools.rawJATS as raw_parser
 import elifetools.json_rewrite
 from elifetools import utils
-from elifetools.utils import *
 from elifetools.utils_html import xml_to_html, references_author_collab
 
 
@@ -34,23 +34,23 @@ def parse_xml(xml):
 
 
 def parse_document(filelocation):
-    with open(filelocation, "rb") as fp:
-        return parse_xml(fp)
+    with open(filelocation, "rb") as open_file:
+        return parse_xml(open_file)
 
 
 def title(soup):
-    return node_text(raw_parser.article_title(soup))
+    return utils.node_text(raw_parser.article_title(soup))
 
 
 def full_title(soup):
     # The title including italic tags, etc.
-    return node_contents_str(raw_parser.article_title(soup))
+    return utils.node_contents_str(raw_parser.article_title(soup))
 
 
 def title_short(soup):
     "'title' truncated to 20 chars"
     # TODO: 20 is arbitrary,
-    return title(soup)[:20]
+    return str(title(soup))[:20]
 
 
 def title_slug(soup):
@@ -65,7 +65,9 @@ def title_prefix(soup):
     for d_channel in display_channel(soup):
         if d_channel.lower() in display_channel_match_list:
             if raw_parser.sub_display_channel(soup):
-                prefix = node_text(first(raw_parser.sub_display_channel(soup)))
+                prefix = utils.node_text(
+                    utils.first(raw_parser.sub_display_channel(soup))
+                )
     return prefix
 
 
@@ -80,28 +82,28 @@ def title_prefix_json(soup):
 
 def doi(soup):
     # the first non-nil value returned by the raw parser
-    return doi_uri_to_doi(node_text(raw_parser.doi(soup)))
+    return utils.doi_uri_to_doi(utils.node_text(raw_parser.doi(soup)))
 
 
 def publisher_id(soup):
     # aka the article_id, specified by the publisher
-    return node_text(raw_parser.publisher_id(soup))
+    return utils.node_text(raw_parser.publisher_id(soup))
 
 
 def journal_id(soup):
-    return node_text(raw_parser.journal_id(soup))
+    return utils.node_text(raw_parser.journal_id(soup))
 
 
 def journal_title(soup):
-    return node_text(raw_parser.journal_title(soup))
+    return utils.node_text(raw_parser.journal_title(soup))
 
 
 def journal_issn(soup, pub_format=None, pub_type=None):
-    return node_text(raw_parser.journal_issn(soup, pub_format, pub_type))
+    return utils.node_text(raw_parser.journal_issn(soup, pub_format, pub_type))
 
 
 def publisher(soup):
-    return node_text(raw_parser.publisher(soup))
+    return utils.node_text(raw_parser.publisher(soup))
 
 
 def article_type(soup):
@@ -110,37 +112,41 @@ def article_type(soup):
 
 
 def volume(soup):
-    return node_text(first(raw_parser.volume(soup)))
+    return utils.node_text(utils.first(raw_parser.volume(soup)))
 
 
 def issue(soup):
-    return node_text(first(raw_parser.issue(raw_parser.article_meta(soup))))
+    return utils.node_text(utils.first(raw_parser.issue(raw_parser.article_meta(soup))))
 
 
 def fpage(soup):
-    return node_text(first(raw_parser.fpage(raw_parser.article_meta(soup))))
+    return utils.node_text(utils.first(raw_parser.fpage(raw_parser.article_meta(soup))))
 
 
 def lpage(soup):
-    return node_text(first(raw_parser.lpage(raw_parser.article_meta(soup))))
+    return utils.node_text(utils.first(raw_parser.lpage(raw_parser.article_meta(soup))))
 
 
 def elocation_id(soup):
-    return node_text(first(raw_parser.elocation_id(raw_parser.article_meta(soup))))
+    return utils.node_text(
+        utils.first(raw_parser.elocation_id(raw_parser.article_meta(soup)))
+    )
 
 
 def research_organism(soup):
     "Find the research-organism from the set of kwd-group tags"
     if not raw_parser.research_organism_keywords(soup):
         return []
-    return list(map(node_text, raw_parser.research_organism_keywords(soup)))
+    return list(map(utils.node_text, raw_parser.research_organism_keywords(soup)))
 
 
 def full_research_organism(soup):
     "research-organism list including inline tags, such as italic"
     if not raw_parser.research_organism_keywords(soup):
         return []
-    return list(map(node_contents_str, raw_parser.research_organism_keywords(soup)))
+    return list(
+        map(utils.node_contents_str, raw_parser.research_organism_keywords(soup))
+    )
 
 
 def keywords(soup):
@@ -150,20 +156,22 @@ def keywords(soup):
     """
     if not raw_parser.author_keywords(soup):
         return []
-    return list(map(node_text, raw_parser.author_keywords(soup)))
+    return list(map(utils.node_text, raw_parser.author_keywords(soup)))
 
 
 def full_keywords(soup):
     "author keywords list including inline tags, such as italic"
     if not raw_parser.author_keywords(soup):
         return []
-    return list(map(node_contents_str, raw_parser.author_keywords(soup)))
+    return list(map(utils.node_contents_str, raw_parser.author_keywords(soup)))
 
 
 def full_keyword_groups(soup):
     groups = {}
     for group_tag in raw_parser.keyword_group(soup):
-        group = list(map(node_contents_str, extract_nodes(group_tag, "kwd")))
+        group = list(
+            map(utils.node_contents_str, utils.extract_nodes(group_tag, "kwd"))
+        )
         group = list(map(lambda s: s.strip(), group))
         if "kwd-group-type" in group_tag.attrs:
             groups[group_tag["kwd-group-type"].strip()] = group
@@ -175,36 +183,41 @@ def full_custom_meta(soup, meta_name=None):
 
 
 def impact_statement(soup):
-    tag = first(full_custom_meta(soup, "Author impact statement"))
+    tag = utils.first(full_custom_meta(soup, "Author impact statement"))
     if tag is not None:
-        return node_contents_str(first(extract_nodes(tag, "meta-value")))
+        return utils.node_contents_str(
+            utils.first(utils.extract_nodes(tag, "meta-value"))
+        )
     return ""
 
 
 def version_history(soup, html_flag=True):
     "extract the article version history details"
-    convert = lambda xml_string: xml_to_html(html_flag, xml_string)
+
+    def convert(xml_string):
+        return xml_to_html(html_flag, xml_string)
+
     version_history = []
     related_object_tags = raw_parser.related_object(raw_parser.article_meta(soup))
     for tag in related_object_tags:
         article_version = OrderedDict()
-        date_tag = first(raw_parser.date(tag))
+        date_tag = utils.first(raw_parser.date(tag))
 
         if not date_tag:
             continue
 
-        copy_attribute(date_tag.attrs, "date-type", article_version, "version")
+        utils.copy_attribute(date_tag.attrs, "date-type", article_version, "version")
         (day, month, year) = ymd(date_tag)
         article_version["day"] = day
         article_version["month"] = month
         article_version["year"] = year
-        article_version["date"] = date_struct_nn(year, month, day)
+        article_version["date"] = utils.date_struct_nn(year, month, day)
 
-        copy_attribute(tag.attrs, "xlink:href", article_version, "xlink_href")
-        set_if_value(
+        utils.copy_attribute(tag.attrs, "xlink:href", article_version, "xlink_href")
+        utils.set_if_value(
             article_version,
             "comment",
-            convert(node_contents_str(first(raw_parser.comment(tag)))),
+            convert(utils.node_contents_str(utils.first(raw_parser.comment(tag)))),
         )
         version_history.append(article_version)
     return version_history
@@ -225,9 +238,9 @@ def clinical_trials(soup):
             "source-id-type",
             "source-type",
         ]:
-            copy_attribute(tag.attrs, attribute, clinical_trial)
+            utils.copy_attribute(tag.attrs, attribute, clinical_trial)
         clinical_trial["text"] = tag.text
-        copy_attribute(tag.attrs, "xlink:href", clinical_trial, "xlink_href")
+        utils.copy_attribute(tag.attrs, "xlink:href", clinical_trial, "xlink_href")
         clinical_trials.append(clinical_trial)
     return clinical_trials
 
@@ -252,9 +265,9 @@ def article_id_list(soup):
     id_list = []
     for article_id_tag in raw_parser.article_id(soup):
         id_details = OrderedDict()
-        set_if_value(id_details, "type", article_id_tag.get("pub-id-type"))
-        set_if_value(id_details, "value", article_id_tag.text)
-        set_if_value(
+        utils.set_if_value(id_details, "type", article_id_tag.get("pub-id-type"))
+        utils.set_if_value(id_details, "value", article_id_tag.text)
+        utils.set_if_value(
             id_details, "assigning-authority", article_id_tag.get("assigning-authority")
         )
         id_list.append(id_details)
@@ -263,51 +276,57 @@ def article_id_list(soup):
 
 def pub_history(soup):
     events = []
-    pub_history = first(raw_parser.pub_history(soup))
+    pub_history = utils.first(raw_parser.pub_history(soup))
     if pub_history:
         event_tags = raw_parser.event(pub_history)
         for event_tag in event_tags:
             event = OrderedDict()
 
-            date_tag = first(raw_parser.date(event_tag))
+            date_tag = utils.first(raw_parser.date(event_tag))
 
             # set event_type from the event tag, otherwise from the date tag
-            set_if_value(event, "event_type", event_tag.get("event-type"))
+            utils.set_if_value(event, "event_type", event_tag.get("event-type"))
             if not event.get("event_type") and date_tag:
-                set_if_value(event, "event_type", date_tag.get("date-type"))
+                utils.set_if_value(event, "event_type", date_tag.get("date-type"))
 
-            event_desc_tag = first(raw_parser.event_desc(event_tag))
+            event_desc_tag = utils.first(raw_parser.event_desc(event_tag))
             if event_desc_tag:
-                event_desc = node_contents_str(event_desc_tag).rstrip()
-                set_if_value(event, "event_desc", event_desc)
-                set_if_value(event, "event_desc_html", xml_to_html(True, event_desc))
+                event_desc = utils.node_contents_str(event_desc_tag).rstrip()
+                utils.set_if_value(event, "event_desc", event_desc)
+                utils.set_if_value(
+                    event, "event_desc_html", xml_to_html(True, event_desc)
+                )
             # look for uri in a self-uri tag, otherwise look for an ext-link tag
-            self_uri_tag = first(raw_parser.self_uri(event_tag))
+            self_uri_tag = utils.first(raw_parser.self_uri(event_tag))
             if self_uri_tag:
-                set_if_value(event, "uri", self_uri_tag.get("xlink:href"))
+                utils.set_if_value(event, "uri", self_uri_tag.get("xlink:href"))
             else:
-                uri_tag = first(raw_parser.ext_link(event_tag, "uri"))
+                uri_tag = utils.first(raw_parser.ext_link(event_tag, "uri"))
                 if uri_tag:
-                    set_if_value(event, "uri", uri_tag.get("xlink:href"))
-                    set_if_value(event, "uri_text", node_contents_str(uri_tag))
+                    utils.set_if_value(event, "uri", uri_tag.get("xlink:href"))
+                    utils.set_if_value(
+                        event, "uri_text", utils.node_contents_str(uri_tag)
+                    )
             if article_id_list(event_tag):
                 event["id_list"] = article_id_list(event_tag)
-            
+
             if date_tag:
                 (day, month, year) = ymd(date_tag)
                 event["day"] = day
                 event["month"] = month
                 event["year"] = year
-                event["date"] = date_struct_nn(year, month, day)
-                set_if_value(event, "iso-8601-date", date_tag.get("iso-8601-date"))
+                event["date"] = utils.date_struct_nn(year, month, day)
+                utils.set_if_value(
+                    event, "iso-8601-date", date_tag.get("iso-8601-date")
+                )
                 (day, month, year) = ymd(date_tag)
             events.append(event)
     return events
 
 
-@strippen
+@utils.strippen
 def acknowledgements(soup):
-    return node_text(raw_parser.acknowledgements(soup))
+    return utils.node_text(raw_parser.acknowledgements(soup))
 
 
 # DEPRECATED: use `acknowledgements`. avoid unnecessary abbreviations
@@ -315,32 +334,32 @@ def ack(soup):
     return acknowledgements(soup)
 
 
-@nullify
-@strippen
+@utils.nullify
+@utils.strippen
 def conflict(soup):
-    result = [node_text(tag) for tag in raw_parser.conflict(soup)]
+    result = [utils.node_text(tag) for tag in raw_parser.conflict(soup)]
     return result
 
 
 def copyright_statement(soup):
     permissions_tag = raw_parser.article_permissions(soup)
     if permissions_tag:
-        return node_text(raw_parser.copyright_statement(permissions_tag))
+        return utils.node_text(raw_parser.copyright_statement(permissions_tag))
     return None
 
 
-@inten
+@utils.inten
 def copyright_year(soup):
     permissions_tag = raw_parser.article_permissions(soup)
     if permissions_tag:
-        return node_text(raw_parser.copyright_year(permissions_tag))
+        return utils.node_text(raw_parser.copyright_year(permissions_tag))
     return None
 
 
 def copyright_holder(soup):
     permissions_tag = raw_parser.article_permissions(soup)
     if permissions_tag:
-        return node_text(raw_parser.copyright_holder(permissions_tag))
+        return utils.node_text(raw_parser.copyright_holder(permissions_tag))
     return None
 
 
@@ -349,7 +368,7 @@ def copyright_holder_json(soup):
     holder = None
     permissions_tag = raw_parser.article_permissions(soup)
     if permissions_tag:
-        holder = node_text(raw_parser.copyright_holder(permissions_tag))
+        holder = utils.node_text(raw_parser.copyright_holder(permissions_tag))
     if holder is not None and holder.endswith("et al"):
         holder = holder + "."
     return holder
@@ -358,14 +377,16 @@ def copyright_holder_json(soup):
 def license(soup):
     permissions_tag = raw_parser.article_permissions(soup)
     if permissions_tag:
-        return node_text(first(raw_parser.licence_p(permissions_tag)))
+        return utils.node_text(utils.first(raw_parser.licence_p(permissions_tag)))
     return None
 
 
 def full_license(soup):
     permissions_tag = raw_parser.article_permissions(soup)
     if permissions_tag:
-        return node_contents_str(first(raw_parser.licence_p(permissions_tag)))
+        return utils.node_contents_str(
+            utils.first(raw_parser.licence_p(permissions_tag))
+        )
     return None
 
 
@@ -381,11 +402,11 @@ def license_json(soup):
 
 
 def funding_statement(soup):
-    return node_text(raw_parser.funding_statement(soup))
+    return utils.node_text(raw_parser.funding_statement(soup))
 
 
 def full_funding_statement(soup):
-    return node_contents_str(raw_parser.funding_statement(soup))
+    return utils.node_contents_str(raw_parser.funding_statement(soup))
 
 
 #
@@ -399,12 +420,12 @@ def full_funding_statement(soup):
 
 def ref_text(tag):
     # ref - human readable full reference text
-    ref_text = node_text(tag)
-    ref_text = strip_strings(ref_text)
+    ref_text = utils.node_text(tag)
+    ref_text = utils.strip_strings(ref_text)
     # Remove excess space
     ref_text = " ".join(ref_text.split())
     # Fix punctuation spaces and extra space
-    ref_text = strip_punctuation_space(strip_strings(ref_text))
+    ref_text = utils.strip_punctuation_space(utils.strip_strings(ref_text))
     return ref_text
 
 
@@ -416,7 +437,7 @@ def subject_area(soup):
 
     tags = raw_parser.subject_area(soup)
     for tag in tags:
-        subject_area.append(node_text(tag))
+        subject_area.append(utils.node_text(tag))
 
     return subject_area
 
@@ -440,7 +461,7 @@ def display_channel(soup):
 
     tags = raw_parser.display_channel(soup)
     for tag in tags:
-        display_channel.append(node_text(tag))
+        display_channel.append(utils.node_text(tag))
 
     return display_channel
 
@@ -453,7 +474,7 @@ def category(soup):
 
     tags = raw_parser.category(soup)
     for tag in tags:
-        category.append(node_text(tag))
+        category.append(utils.node_text(tag))
 
     return category
 
@@ -462,9 +483,9 @@ def ymd(soup):
     """
     Get the year, month and day from child tags
     """
-    day = node_text(raw_parser.day(soup))
-    month = node_text(raw_parser.month(soup))
-    year = node_text(raw_parser.year(soup))
+    day = utils.node_text(raw_parser.day(soup))
+    month = utils.node_text(raw_parser.month(soup))
+    year = utils.node_text(raw_parser.year(soup))
     return (day, month, year)
 
 
@@ -474,13 +495,13 @@ def pub_date(soup):
     pub_date_date, pub_date_day, pub_date_month, pub_date_year, pub_date_timestamp
     Default date_type is pub
     """
-    pub_date = first(raw_parser.pub_date(soup, date_type="pub"))
+    pub_date = utils.first(raw_parser.pub_date(soup, date_type="pub"))
     if pub_date is None:
-        pub_date = first(raw_parser.pub_date(soup, date_type="publication"))
+        pub_date = utils.first(raw_parser.pub_date(soup, date_type="publication"))
     if pub_date is None:
         return None
     (day, month, year) = ymd(pub_date)
-    return date_struct(year, month, day)
+    return utils.date_struct(year, month, day)
 
 
 def pub_dates(soup):
@@ -491,16 +512,16 @@ def pub_dates(soup):
     tags = raw_parser.pub_date(soup)
     for tag in tags:
         pub_date = OrderedDict()
-        copy_attribute(tag.attrs, "publication-format", pub_date)
-        copy_attribute(tag.attrs, "date-type", pub_date)
-        copy_attribute(tag.attrs, "pub-type", pub_date)
+        utils.copy_attribute(tag.attrs, "publication-format", pub_date)
+        utils.copy_attribute(tag.attrs, "date-type", pub_date)
+        utils.copy_attribute(tag.attrs, "pub-type", pub_date)
         for tag_attr in ["date-type", "pub-type"]:
             if tag_attr in tag.attrs:
                 (day, month, year) = ymd(tag)
                 pub_date["day"] = day
                 pub_date["month"] = month
                 pub_date["year"] = year
-                pub_date["date"] = date_struct_nn(year, month, day)
+                pub_date["date"] = utils.date_struct_nn(year, month, day)
         pub_dates.append(pub_date)
     return pub_dates
 
@@ -510,135 +531,135 @@ def history_date(soup, date_type=None):
     Find a date in the history tag for the specific date_type
     typical date_type values: received, accepted
     """
-    if date_type == None:
+    if date_type is None:
         return None
 
     history_date = raw_parser.history_date(soup, date_type)
     if history_date is None:
         return None
     (day, month, year) = ymd(history_date)
-    return date_struct(year, month, day)
+    return utils.date_struct(year, month, day)
 
 
 def pub_date_date(soup):
     """
     Find the published date in human readable form
     """
-    return date_text(pub_date(soup))
+    return utils.date_text(pub_date(soup))
 
 
 def pub_date_day(soup):
     """
     Find the published date day
     """
-    return day_text(pub_date(soup))
+    return utils.day_text(pub_date(soup))
 
 
 def pub_date_month(soup):
     """
     Find the published date month
     """
-    return month_text(pub_date(soup))
+    return utils.month_text(pub_date(soup))
 
 
 def pub_date_year(soup):
     """
     Find the published date year
     """
-    return year_text(pub_date(soup))
+    return utils.year_text(pub_date(soup))
 
 
 def pub_date_timestamp(soup):
     """
     Find the published date timestamp, in UTC time
     """
-    return date_timestamp(pub_date(soup))
+    return utils.date_timestamp(pub_date(soup))
 
 
 def received_date_date(soup):
     """
     Find the received date in human readable form
     """
-    return date_text(history_date(soup, date_type="received"))
+    return utils.date_text(history_date(soup, date_type="received"))
 
 
 def received_date_day(soup):
     """
     Find the received date day
     """
-    return day_text(history_date(soup, date_type="received"))
+    return utils.day_text(history_date(soup, date_type="received"))
 
 
 def received_date_month(soup):
     """
     Find the received date month
     """
-    return month_text(history_date(soup, date_type="received"))
+    return utils.month_text(history_date(soup, date_type="received"))
 
 
 def received_date_year(soup):
     """
     Find the received date year
     """
-    return year_text(history_date(soup, date_type="received"))
+    return utils.year_text(history_date(soup, date_type="received"))
 
 
 def received_date_timestamp(soup):
     """
     Find the received date timestamp, in UTC time
     """
-    return date_timestamp(history_date(soup, date_type="received"))
+    return utils.date_timestamp(history_date(soup, date_type="received"))
 
 
 def accepted_date_date(soup):
     """
     Find the accepted date in human readable form
     """
-    return date_text(history_date(soup, date_type="accepted"))
+    return utils.date_text(history_date(soup, date_type="accepted"))
 
 
 def accepted_date_day(soup):
     """
     Find the accepted date day
     """
-    return day_text(history_date(soup, date_type="accepted"))
+    return utils.day_text(history_date(soup, date_type="accepted"))
 
 
 def accepted_date_month(soup):
     """
     Find the accepted date month
     """
-    return month_text(history_date(soup, date_type="accepted"))
+    return utils.month_text(history_date(soup, date_type="accepted"))
 
 
 def accepted_date_year(soup):
     """
     Find the accepted date year
     """
-    return year_text(history_date(soup, date_type="accepted"))
+    return utils.year_text(history_date(soup, date_type="accepted"))
 
 
 def accepted_date_timestamp(soup):
     """
     Find the accepted date timestamp, in UTC time
     """
-    return date_timestamp(history_date(soup, date_type="accepted"))
+    return utils.date_timestamp(history_date(soup, date_type="accepted"))
 
 
 def collection_year(soup):
     """
     Pub date of type collection will hold a year element for VOR articles
     """
-    pub_date = first(raw_parser.pub_date(soup, pub_type="collection"))
+    pub_date = utils.first(raw_parser.pub_date(soup, pub_type="collection"))
     if not pub_date:
-        pub_date = first(raw_parser.pub_date(soup, date_type="collection"))
+        pub_date = utils.first(raw_parser.pub_date(soup, date_type="collection"))
     if not pub_date:
         return None
 
     year = None
     year_tag = raw_parser.year(pub_date)
     if year_tag:
-        year = int(node_text(year_tag))
+        year = int(utils.node_text(year_tag))
 
     return year
 
@@ -649,8 +670,8 @@ def is_poa(soup):
     """
     if collection_year(soup) is None:
         return True
-    else:
-        return False
+
+    return False
 
 
 def abstracts(soup):
@@ -668,25 +689,27 @@ def abstracts(soup):
         abstract["abstract_type"] = tag.get("abstract-type")
         title_tag = raw_parser.title(tag)
         if title_tag:
-            abstract["title"] = node_text(title_tag)
+            abstract["title"] = utils.node_text(title_tag)
 
         abstract["content"] = None
         if raw_parser.paragraph(tag):
             abstract["content"] = ""
             abstract["full_content"] = ""
 
-            good_paragraphs = remove_doi_paragraph(raw_parser.paragraph(tag))
+            good_paragraphs = utils.remove_doi_paragraph(raw_parser.paragraph(tag))
 
             # Plain text content
             glue = ""
             for p_tag in good_paragraphs:
-                abstract["content"] += glue + node_text(p_tag)
+                abstract["content"] += glue + utils.node_text(p_tag)
                 glue = " "
 
             # Content including markup tags
             # When more than one paragraph, wrap each in a <p> tag
             for p_tag in good_paragraphs:
-                abstract["full_content"] += "<p>" + node_contents_str(p_tag) + "</p>"
+                abstract["full_content"] += (
+                    "<p>" + utils.node_contents_str(p_tag) + "</p>"
+                )
 
         abstracts.append(abstract)
 
@@ -697,23 +720,23 @@ def abstract(soup):
     abstract = None
     abstract_list = abstracts(soup)
     if abstract_list:
-        abstract = first(
+        abstract = utils.first(
             list(filter(lambda tag: tag.get("abstract_type") is None, abstract_list))
         )
     if abstract:
         return abstract.get("content")
-    else:
-        return None
+
+    return None
 
 
 def abstract_xml(soup, strip_doi_paragraphs=True, abstract_type=None):
-    original_abstract_tag = first(raw_parser.abstract(soup, abstract_type))
+    original_abstract_tag = utils.first(raw_parser.abstract(soup, abstract_type))
     if not original_abstract_tag:
         return None
     abstract_tag = copy.copy(original_abstract_tag)
     if strip_doi_paragraphs and raw_parser.paragraph(abstract_tag):
         for p_tag in raw_parser.paragraph(abstract_tag):
-            if paragraph_is_only_doi(p_tag) or starts_with_doi(p_tag):
+            if utils.paragraph_is_only_doi(p_tag) or utils.starts_with_doi(p_tag):
                 p_tag.decompose()
     # add in common JATS XML namespaces which are getting lost
     for namespace in XML_NAMESPACES:
@@ -733,20 +756,20 @@ def full_abstract(soup):
     abstract = None
     abstract_list = abstracts(soup)
     if abstract_list:
-        abstract = first(
+        abstract = utils.first(
             list(filter(lambda tag: tag.get("abstract_type") is None, abstract_list))
         )
     if abstract:
         return abstract.get("full_content")
-    else:
-        return None
+
+    return None
 
 
 def digest(soup):
     abstract = None
     abstract_list = abstracts(soup)
     if abstract_list:
-        abstract = first(
+        abstract = utils.first(
             list(
                 filter(
                     lambda tag: tag.get("abstract_type") == "executive-summary",
@@ -756,8 +779,8 @@ def digest(soup):
         )
     if abstract:
         return abstract.get("content")
-    else:
-        return None
+
+    return None
 
 
 def full_digest(soup):
@@ -767,7 +790,7 @@ def full_digest(soup):
     abstract = None
     abstract_list = abstracts(soup)
     if abstract_list:
-        abstract = first(
+        abstract = utils.first(
             list(
                 filter(
                     lambda tag: tag.get("abstract_type") == "executive-summary",
@@ -777,8 +800,8 @@ def full_digest(soup):
         )
     if abstract:
         return abstract["full_content"]
-    else:
-        return None
+
+    return None
 
 
 def related_article(soup):
@@ -801,21 +824,23 @@ def sub_articles(soup):
     sub_article_tags = raw_parser.sub_article(soup)
     for tag in sub_article_tags:
         sub_article = OrderedDict()
-        set_if_value(sub_article, "doi", sub_article_doi(tag))
-        set_if_value(sub_article, "article_type", tag.get("article-type"))
-        set_if_value(sub_article, "id", tag.get("id"))
-        set_if_value(sub_article, "article_title", title(tag))
+        utils.set_if_value(sub_article, "doi", sub_article_doi(tag))
+        utils.set_if_value(sub_article, "article_type", tag.get("article-type"))
+        utils.set_if_value(sub_article, "id", tag.get("id"))
+        utils.set_if_value(sub_article, "article_title", title(tag))
         if all_contributors(tag):
             sub_article["contributors"] = all_contributors(tag)
         # find parent article tag and set attributes
         for parent in tag.parents:
             if parent.name == "article":
-                set_if_value(sub_article, "parent_doi", doi(parent))
-                set_if_value(
+                utils.set_if_value(sub_article, "parent_doi", doi(parent))
+                utils.set_if_value(
                     sub_article, "parent_article_type", parent.get("article-type")
                 )
-                set_if_value(sub_article, "parent_article_title", title(parent))
-                set_if_value(sub_article, "parent_license_url", license_url(parent))
+                utils.set_if_value(sub_article, "parent_article_title", title(parent))
+                utils.set_if_value(
+                    sub_article, "parent_license_url", license_url(parent)
+                )
                 break
         # append
         article_list.append(sub_article)
@@ -833,26 +858,26 @@ def name(nom):
 
     def preferred_name(nom):
         suffix = None
-        return author_preferred_name(
+        return utils.author_preferred_name(
             nom.surname.text, nom.find("given-names").text, suffix
         )
 
-    def do(mc):
+    def do_format(mc_tag):
         return {
             "journal": {
-                "name": mc.source.text,
-                "volume": mc.volume.text,
-                "fpage": mc.fpage.text,
-                "lpage": node_text(mc.lpage),
+                "name": mc_tag.source.text,
+                "volume": mc_tag.volume.text,
+                "fpage": mc_tag.fpage.text,
+                "lpage": utils.node_text(mc_tag.lpage),
             },
             "article": {
-                "title": mc.find("article-title").text,
-                "doi": mc.find("pub-id", attrs={"pub-id-type": "doi"}).text,
+                "title": mc_tag.find("article-title").text,
+                "doi": mc_tag.find("pub-id", attrs={"pub-id-type": "doi"}).text,
                 "pub-date": list(map(int, ymd(soup)[::-1])),
                 "authors": list(
                     map(
                         name,
-                        mc.find(
+                        mc_tag.find(
                             "person-group", attrs={"person-group-type": "author"}
                         ).contents,
                     )
@@ -861,7 +886,7 @@ def do(mc):
                     list(
                         map(
                             preferred_name,
-                            mc.find(
+                            mc_tag.find(
                                 "person-group", attrs={"person-group-type": "author"}
                             ).contents,
                         )
@@ -870,7 +895,7 @@ def do(mc):
             },
         }
 
-    return list(map(do, mc_tags))
+    return list(map(do_format, mc_tags))
 
 
 def component_doi(soup):
@@ -888,7 +913,7 @@ def component_doi(soup):
 
     for tag in object_id_tags:
         component_object = {}
-        component_object["doi"] = doi_uri_to_doi(tag.text)
+        component_object["doi"] = utils.doi_uri_to_doi(tag.text)
         component_object["position"] = position
 
         # Try to find the type of component
@@ -908,21 +933,21 @@ def tag_details_sibling_ordinal(tag):
 
     if (tag.name == "fig" and "specific-use" not in tag.attrs) or tag.name == "media":
         # Fig that is not a child figure / figure supplement
-        if first_parent(tag, "sub-article"):
+        if utils.first_parent(tag, "sub-article"):
             # Sub-article sibling ordinal numbers work differently
-            sibling_ordinal = tag_subarticle_sibling_ordinal(tag)
-        elif first_parent(tag, "app"):
-            sibling_ordinal = tag_appendix_sibling_ordinal(tag)
+            sibling_ordinal = utils.tag_subarticle_sibling_ordinal(tag)
+        elif utils.first_parent(tag, "app"):
+            sibling_ordinal = utils.tag_appendix_sibling_ordinal(tag)
         elif tag.name == "media":
             # Media video or non-video are different numbering
-            sibling_ordinal = tag_media_sibling_ordinal(tag)
+            sibling_ordinal = utils.tag_media_sibling_ordinal(tag)
         else:
-            sibling_ordinal = tag_fig_ordinal(tag)
+            sibling_ordinal = utils.tag_fig_ordinal(tag)
     elif tag.name == "supplementary-material":
-        sibling_ordinal = tag_supplementary_material_sibling_ordinal(tag)
+        sibling_ordinal = utils.tag_supplementary_material_sibling_ordinal(tag)
     else:
         # Default
-        sibling_ordinal = tag_sibling_ordinal(tag)
+        sibling_ordinal = utils.tag_sibling_ordinal(tag)
 
     return sibling_ordinal
 
@@ -940,16 +965,18 @@ def tag_details_asset(tag):
         asset = "app"
     elif tag.name == "supplementary-material":
         # Default is supp
-        asset = supp_asset(tag)
+        asset = utils.supp_asset(tag)
     elif tag.name == "sub-article":
         if (
-            node_text(raw_parser.article_title(tag))
-            and node_text(raw_parser.article_title(tag)).lower() == "decision letter"
+            utils.node_text(raw_parser.article_title(tag))
+            and utils.node_text(raw_parser.article_title(tag)).lower()
+            == "decision letter"
         ):
             asset = "dec"
         elif (
-            node_text(raw_parser.article_title(tag))
-            and node_text(raw_parser.article_title(tag)).lower() == "author response"
+            utils.node_text(raw_parser.article_title(tag))
+            and utils.node_text(raw_parser.article_title(tag)).lower()
+            == "author response"
         ):
             asset = "resp"
 
@@ -963,7 +990,7 @@ def tag_details(tag, nodenames):
     details = {}
 
     details["type"] = tag.name
-    details["ordinal"] = tag_ordinal(tag)
+    details["ordinal"] = utils.tag_ordinal(tag)
 
     # Ordinal value
     if tag_details_sibling_ordinal(tag):
@@ -973,7 +1000,7 @@ def tag_details(tag, nodenames):
     if tag_details_asset(tag):
         details["asset"] = tag_details_asset(tag)
 
-    object_id_tag = first(raw_parser.object_id(tag, pub_id_type="doi"))
+    object_id_tag = utils.first(raw_parser.object_id(tag, pub_id_type="doi"))
     if object_id_tag:
         details["component_doi"] = extract_component_doi(tag, nodenames)
 
@@ -994,10 +1021,10 @@ def media(soup):
     for tag in media_tags:
         media_item = {}
 
-        copy_attribute(tag.attrs, "mime-subtype", media_item)
-        copy_attribute(tag.attrs, "mimetype", media_item)
-        copy_attribute(tag.attrs, "xlink:href", media_item, "xlink_href")
-        copy_attribute(tag.attrs, "content-type", media_item)
+        utils.copy_attribute(tag.attrs, "mime-subtype", media_item)
+        utils.copy_attribute(tag.attrs, "mimetype", media_item)
+        utils.copy_attribute(tag.attrs, "xlink:href", media_item, "xlink_href")
+        utils.copy_attribute(tag.attrs, "content-type", media_item)
 
         nodenames = [
             "sub-article",
@@ -1008,67 +1035,73 @@ def media(soup):
         ]
 
         details = tag_details(tag, nodenames)
-        copy_attribute(details, "component_doi", media_item)
-        copy_attribute(details, "type", media_item)
-        copy_attribute(details, "sibling_ordinal", media_item)
+        utils.copy_attribute(details, "component_doi", media_item)
+        utils.copy_attribute(details, "type", media_item)
+        utils.copy_attribute(details, "sibling_ordinal", media_item)
 
         # Try to get the component DOI of the parent tag
-        parent_tag = first_parent(tag, nodenames)
+        parent_tag = utils.first_parent(tag, nodenames)
         if parent_tag:
-            acting_parent_tag = component_acting_parent_tag(parent_tag, tag)
+            acting_parent_tag = utils.component_acting_parent_tag(parent_tag, tag)
             if acting_parent_tag:
                 details = tag_details(acting_parent_tag, nodenames)
-                copy_attribute(details, "type", media_item, "parent_type")
-                copy_attribute(details, "ordinal", media_item, "parent_ordinal")
-                copy_attribute(details, "asset", media_item, "parent_asset")
-                copy_attribute(
+                utils.copy_attribute(details, "type", media_item, "parent_type")
+                utils.copy_attribute(details, "ordinal", media_item, "parent_ordinal")
+                utils.copy_attribute(details, "asset", media_item, "parent_asset")
+                utils.copy_attribute(
                     details, "sibling_ordinal", media_item, "parent_sibling_ordinal"
                 )
-                copy_attribute(
+                utils.copy_attribute(
                     details, "component_doi", media_item, "parent_component_doi"
                 )
 
             # Try to get the parent parent
-            p_parent_tag = first_parent(parent_tag, nodenames)
+            p_parent_tag = utils.first_parent(parent_tag, nodenames)
             if p_parent_tag:
-                acting_p_parent_tag = component_acting_parent_tag(
+                acting_p_parent_tag = utils.component_acting_parent_tag(
                     p_parent_tag, parent_tag
                 )
                 if acting_p_parent_tag:
                     details = tag_details(acting_p_parent_tag, nodenames)
-                    copy_attribute(details, "type", media_item, "p_parent_type")
-                    copy_attribute(details, "ordinal", media_item, "p_parent_ordinal")
-                    copy_attribute(details, "asset", media_item, "p_parent_asset")
-                    copy_attribute(
+                    utils.copy_attribute(details, "type", media_item, "p_parent_type")
+                    utils.copy_attribute(
+                        details, "ordinal", media_item, "p_parent_ordinal"
+                    )
+                    utils.copy_attribute(details, "asset", media_item, "p_parent_asset")
+                    utils.copy_attribute(
                         details,
                         "sibling_ordinal",
                         media_item,
                         "p_parent_sibling_ordinal",
                     )
-                    copy_attribute(
+                    utils.copy_attribute(
                         details, "component_doi", media_item, "p_parent_component_doi"
                     )
 
                 # Try to get the parent parent parent
-                p_p_parent_tag = first_parent(p_parent_tag, nodenames)
+                p_p_parent_tag = utils.first_parent(p_parent_tag, nodenames)
                 if p_p_parent_tag:
-                    acting_p_p_parent_tag = component_acting_parent_tag(
+                    acting_p_p_parent_tag = utils.component_acting_parent_tag(
                         p_p_parent_tag, p_parent_tag
                     )
                     if acting_p_p_parent_tag:
                         details = tag_details(acting_p_p_parent_tag, nodenames)
-                        copy_attribute(details, "type", media_item, "p_p_parent_type")
-                        copy_attribute(
+                        utils.copy_attribute(
+                            details, "type", media_item, "p_p_parent_type"
+                        )
+                        utils.copy_attribute(
                             details, "ordinal", media_item, "p_p_parent_ordinal"
                         )
-                        copy_attribute(details, "asset", media_item, "p_p_parent_asset")
-                        copy_attribute(
+                        utils.copy_attribute(
+                            details, "asset", media_item, "p_p_parent_asset"
+                        )
+                        utils.copy_attribute(
                             details,
                             "sibling_ordinal",
                             media_item,
                             "p_p_parent_sibling_ordinal",
                         )
-                        copy_attribute(
+                        utils.copy_attribute(
                             details,
                             "component_doi",
                             media_item,
@@ -1078,7 +1111,7 @@ def media(soup):
         # Increment the position
         media_item["position"] = position
         # Ordinal should be the same as position in this case but set it anyway
-        media_item["ordinal"] = tag_ordinal(tag)
+        media_item["ordinal"] = utils.tag_ordinal(tag)
 
         media.append(media_item)
 
@@ -1102,52 +1135,56 @@ def graphics(soup):
     for tag in graphic_tags:
         graphic_item = {}
 
-        copy_attribute(tag.attrs, "xlink:href", graphic_item, "xlink_href")
+        utils.copy_attribute(tag.attrs, "xlink:href", graphic_item, "xlink_href")
 
         # Get the tag type
         nodenames = ["sub-article", "fig-group", "fig", "app"]
         details = tag_details(tag, nodenames)
-        copy_attribute(details, "type", graphic_item)
+        utils.copy_attribute(details, "type", graphic_item)
 
-        parent_tag = first_parent(tag, nodenames)
+        parent_tag = utils.first_parent(tag, nodenames)
         if parent_tag:
             details = tag_details(parent_tag, nodenames)
-            copy_attribute(details, "type", graphic_item, "parent_type")
-            copy_attribute(details, "ordinal", graphic_item, "parent_ordinal")
-            copy_attribute(details, "asset", graphic_item, "parent_asset")
-            copy_attribute(
+            utils.copy_attribute(details, "type", graphic_item, "parent_type")
+            utils.copy_attribute(details, "ordinal", graphic_item, "parent_ordinal")
+            utils.copy_attribute(details, "asset", graphic_item, "parent_asset")
+            utils.copy_attribute(
                 details, "sibling_ordinal", graphic_item, "parent_sibling_ordinal"
             )
-            copy_attribute(
+            utils.copy_attribute(
                 details, "component_doi", graphic_item, "parent_component_doi"
             )
 
             # Try to get the parent parent - special for looking at fig tags
             #  use component_acting_parent_tag
-            p_parent_tag = first_parent(parent_tag, nodenames)
+            p_parent_tag = utils.first_parent(parent_tag, nodenames)
             if p_parent_tag:
-                acting_p_parent_tag = component_acting_parent_tag(
+                acting_p_parent_tag = utils.component_acting_parent_tag(
                     p_parent_tag, parent_tag
                 )
                 if acting_p_parent_tag:
                     details = tag_details(acting_p_parent_tag, nodenames)
-                    copy_attribute(details, "type", graphic_item, "p_parent_type")
-                    copy_attribute(details, "ordinal", graphic_item, "p_parent_ordinal")
-                    copy_attribute(details, "asset", graphic_item, "p_parent_asset")
-                    copy_attribute(
+                    utils.copy_attribute(details, "type", graphic_item, "p_parent_type")
+                    utils.copy_attribute(
+                        details, "ordinal", graphic_item, "p_parent_ordinal"
+                    )
+                    utils.copy_attribute(
+                        details, "asset", graphic_item, "p_parent_asset"
+                    )
+                    utils.copy_attribute(
                         details,
                         "sibling_ordinal",
                         graphic_item,
                         "p_parent_sibling_ordinal",
                     )
-                    copy_attribute(
+                    utils.copy_attribute(
                         details, "component_doi", graphic_item, "p_parent_component_doi"
                     )
 
         # Increment the position
         graphic_item["position"] = position
         # Ordinal should be the same as position in this case but set it anyway
-        graphic_item["ordinal"] = tag_ordinal(tag)
+        graphic_item["ordinal"] = utils.tag_ordinal(tag)
 
         graphics.append(graphic_item)
 
@@ -1169,17 +1206,17 @@ def inline_graphics(soup):
     for tag in inline_graphic_tags:
         item = {}
 
-        copy_attribute(tag.attrs, "xlink:href", item, "xlink_href")
+        utils.copy_attribute(tag.attrs, "xlink:href", item, "xlink_href")
 
         # Get the tag type
         nodenames = ["sub-article"]
         details = tag_details(tag, nodenames)
-        copy_attribute(details, "type", item)
+        utils.copy_attribute(details, "type", item)
 
         # Increment the position
         item["position"] = position
         # Ordinal should be the same as position in this case but set it anyway
-        item["ordinal"] = tag_ordinal(tag)
+        item["ordinal"] = utils.tag_ordinal(tag)
 
         inline_graphics.append(item)
 
@@ -1197,18 +1234,18 @@ def self_uri(soup):
     for tag in self_uri_tags:
         item = {}
 
-        copy_attribute(tag.attrs, "xlink:href", item, "xlink_href")
-        copy_attribute(tag.attrs, "content-type", item)
+        utils.copy_attribute(tag.attrs, "xlink:href", item, "xlink_href")
+        utils.copy_attribute(tag.attrs, "content-type", item)
 
         # Get the tag type
         nodenames = ["sub-article"]
         details = tag_details(tag, nodenames)
-        copy_attribute(details, "type", item)
+        utils.copy_attribute(details, "type", item)
 
         # Increment the position
         item["position"] = position
         # Ordinal should be the same as position in this case but set it anyway
-        item["ordinal"] = tag_ordinal(tag)
+        item["ordinal"] = utils.tag_ordinal(tag)
 
         self_uri.append(item)
 
@@ -1228,24 +1265,24 @@ def supplementary_material(soup):
     for tag in supplementary_material_tags:
         item = {}
 
-        copy_attribute(tag.attrs, "id", item)
+        utils.copy_attribute(tag.attrs, "id", item)
 
         # Get the tag type
         nodenames = ["supplementary-material"]
         details = tag_details(tag, nodenames)
-        copy_attribute(details, "type", item)
-        copy_attribute(details, "asset", item)
-        copy_attribute(details, "component_doi", item)
-        copy_attribute(details, "sibling_ordinal", item)
+        utils.copy_attribute(details, "type", item)
+        utils.copy_attribute(details, "asset", item)
+        utils.copy_attribute(details, "component_doi", item)
+        utils.copy_attribute(details, "sibling_ordinal", item)
 
         if raw_parser.label(tag):
-            item["label"] = node_text(raw_parser.label(tag))
-            item["full_label"] = node_contents_str(raw_parser.label(tag))
+            item["label"] = utils.node_text(raw_parser.label(tag))
+            item["full_label"] = utils.node_contents_str(raw_parser.label(tag))
 
         # Increment the position
         item["position"] = position
         # Ordinal should be the same as position in this case but set it anyway
-        item["ordinal"] = tag_ordinal(tag)
+        item["ordinal"] = utils.tag_ordinal(tag)
 
         supplementary_material.append(item)
 
@@ -1265,7 +1302,7 @@ def contrib_email(contrib_tag):
     only return the value if it is not inside an aff tag
     """
     email = []
-    for email_tag in extract_nodes(contrib_tag, "email"):
+    for email_tag in utils.extract_nodes(contrib_tag, "email"):
         if email_tag.parent.name != "aff":
             email.append(email_tag.text)
     return email if len(email) > 0 else None
@@ -1277,7 +1314,7 @@ def contrib_phone(contrib_tag):
     """
     phone = None
     if raw_parser.phone(contrib_tag):
-        phone = first(raw_parser.phone(contrib_tag)).text
+        phone = utils.first(raw_parser.phone(contrib_tag)).text
     return phone
 
 
@@ -1311,7 +1348,7 @@ def contrib_xref(contrib_tag, ref_type):
 
 def format_contrib_refs(contrib_tag, soup):
     contrib_refs = {}
-    ref_tags = extract_nodes(contrib_tag, "xref")
+    ref_tags = utils.extract_nodes(contrib_tag, "xref")
     ref_type_aff_count = 0
     for ref_tag in ref_tags:
         if "ref-type" in ref_tag.attrs and "rid" in ref_tag.attrs:
@@ -1323,7 +1360,7 @@ def format_contrib_refs(contrib_tag, soup):
                 add_to_list_dictionary(contrib_refs, "affiliation", rid)
             if ref_type == "corresp":
                 # Check for email or phone type
-                corresp_tag = firstnn(soup.find_all(id=rid))
+                corresp_tag = utils.firstnn(soup.find_all(id=rid))
                 if contrib_phone(corresp_tag):
                     add_to_list_dictionary(contrib_refs, "phone", rid)
                 elif contrib_email(corresp_tag):
@@ -1357,53 +1394,57 @@ def format_contributor(
     target_tags_fn=None,
 ):
     contributor = {}
-    copy_attribute(contrib_tag.attrs, "contrib-type", contributor, "type")
+    utils.copy_attribute(contrib_tag.attrs, "contrib-type", contributor, "type")
     # Set contrib type if passed via params
     if not contributor.get("type") and contrib_type:
         contributor["type"] = contrib_type
-    copy_attribute(contrib_tag.attrs, "equal-contrib", contributor)
-    copy_attribute(contrib_tag.attrs, "corresp", contributor)
-    copy_attribute(contrib_tag.attrs, "deceased", contributor)
-    copy_attribute(contrib_tag.attrs, "id", contributor)
-    contrib_id_tag = first(raw_parser.contrib_id(contrib_tag))
+    utils.copy_attribute(contrib_tag.attrs, "equal-contrib", contributor)
+    utils.copy_attribute(contrib_tag.attrs, "corresp", contributor)
+    utils.copy_attribute(contrib_tag.attrs, "deceased", contributor)
+    utils.copy_attribute(contrib_tag.attrs, "id", contributor)
+    contrib_id_tag = utils.first(raw_parser.contrib_id(contrib_tag))
     if contrib_id_tag and "contrib-id-type" in contrib_id_tag.attrs:
         if contrib_id_tag["contrib-id-type"] == "group-author-key":
-            contributor["group-author-key"] = node_contents_str(contrib_id_tag)
+            contributor["group-author-key"] = utils.node_contents_str(contrib_id_tag)
     # Set group-author-key if passed via params
     if not contributor.get("group-author-key") and group_author_key:
         contributor["group-author-key"] = group_author_key
     if raw_parser.collab(contrib_tag):
-        collab_tag = first(raw_parser.collab(contrib_tag))
+        collab_tag = utils.first(raw_parser.collab(contrib_tag))
         if collab_tag:
             # Clean up if there are tags inside the collab tag
             tag_copy = copy.copy(collab_tag)
-            tag_copy = remove_tag_from_tag(tag_copy, "contrib-group")
-            contributor["collab"] = node_contents_str(tag_copy).rstrip()
+            tag_copy = utils.remove_tag_from_tag(tag_copy, "contrib-group")
+            contributor["collab"] = utils.node_contents_str(tag_copy).rstrip()
 
     # Check if it is not a group author
     if not is_author_group_author(contrib_tag):
         if contrib_id_tag and "contrib-id-type" in contrib_id_tag.attrs:
             if contrib_id_tag["contrib-id-type"] == "orcid":
-                contributor["orcid"] = node_contents_str(contrib_id_tag)
-        set_if_value(contributor, "role", first_node_str_contents(contrib_tag, "role"))
+                contributor["orcid"] = utils.node_contents_str(contrib_id_tag)
+        utils.set_if_value(
+            contributor, "role", utils.first_node_str_contents(contrib_tag, "role")
+        )
         if raw_parser.bio(contrib_tag):
             biography_content = body_block_content_render(
-                firstnn(raw_parser.bio(contrib_tag))
+                utils.firstnn(raw_parser.bio(contrib_tag))
             )
             if len(biography_content) > 0:
                 contributor["bio"] = biography_content[0].get("content")
-        set_if_value(contributor, "email", contrib_email(contrib_tag))
-        set_if_value(contributor, "phone", contrib_phone(contrib_tag))
-        set_if_value(
-            contributor, "surname", first_node_str_contents(contrib_tag, "surname")
+        utils.set_if_value(contributor, "email", contrib_email(contrib_tag))
+        utils.set_if_value(contributor, "phone", contrib_phone(contrib_tag))
+        utils.set_if_value(
+            contributor,
+            "surname",
+            utils.first_node_str_contents(contrib_tag, "surname"),
         )
-        set_if_value(
+        utils.set_if_value(
             contributor,
             "given-names",
-            first_node_str_contents(contrib_tag, "given-names"),
+            utils.first_node_str_contents(contrib_tag, "given-names"),
         )
-        set_if_value(
-            contributor, "suffix", first_node_str_contents(contrib_tag, "suffix")
+        utils.set_if_value(
+            contributor, "suffix", utils.first_node_str_contents(contrib_tag, "suffix")
         )
         # Get the sub-group value from the parent role tag if it is inside a group
         if (
@@ -1412,19 +1453,19 @@ def format_contributor(
             and contrib_tag.parent.parent.parent
             and is_author_group_author(contrib_tag.parent.parent.parent)
         ):
-            set_if_value(
+            utils.set_if_value(
                 contributor,
                 "sub-group",
-                first_node_str_contents(contrib_tag.parent, "role"),
+                utils.first_node_str_contents(contrib_tag.parent, "role"),
             )
     elif contributor.get("corresp") and not contributor.get("email"):
         # For corresponding group authors, look for an email address anywhere in the group
-        set_if_value(contributor, "email", contrib_email(contrib_tag))
+        utils.set_if_value(contributor, "email", contrib_email(contrib_tag))
 
     # on-behalf-of
     if contrib_tag.name == "on-behalf-of":
         contributor["type"] = "on-behalf-of"
-        contributor["on-behalf-of"] = node_contents_str(contrib_tag)
+        contributor["on-behalf-of"] = utils.node_contents_str(contrib_tag)
 
     contrib_refs, ref_type_aff_count = format_contrib_refs(contrib_tag, soup)
     if len(contrib_refs) > 0:
@@ -1432,7 +1473,7 @@ def format_contributor(
 
     if detail == "brief" or ref_type_aff_count == 0:
         # Brief format only allows one aff and it must be within the contrib tag
-        aff_tag = firstnn(contrib_inline_aff(contrib_tag))
+        aff_tag = utils.firstnn(contrib_inline_aff(contrib_tag))
         if aff_tag:
             contributor["affiliations"] = []
             contrib_affs = {}
@@ -1451,7 +1492,7 @@ def format_contributor(
                         aff_attribute in aff_detail
                         and aff_detail[aff_attribute] is not None
                     ):
-                        copy_attribute(aff_detail, aff_attribute, contrib_affs)
+                        utils.copy_attribute(aff_detail, aff_attribute, contrib_affs)
                 if len(contrib_affs) > 0:
                     contributor["affiliations"].append(contrib_affs)
 
@@ -1480,7 +1521,9 @@ def format_contributor(
             rid = aff_tag.get("rid")
             if rid:
                 # Look for the matching aff tag by rid
-                aff_node = first(extract_nodes(soup, "aff", attr="id", value=rid))
+                aff_node = utils.first(
+                    utils.extract_nodes(soup, "aff", attr="id", value=rid)
+                )
             else:
                 # Aff tag inside contrib tag
                 aff_node = aff_tag
@@ -1501,7 +1544,7 @@ def format_contributor(
                         aff_attribute in aff_detail
                         and aff_detail[aff_attribute] is not None
                     ):
-                        copy_attribute(aff_detail, aff_attribute, contrib_affs)
+                        utils.copy_attribute(aff_detail, aff_attribute, contrib_affs)
                 contributor["affiliations"].append(contrib_affs)
 
         # Add xref linked correspondence author notes if applicable
@@ -1514,10 +1557,10 @@ def format_contributor(
             for cor in corresp_tags:
                 # Find the matching tag
                 rid = cor["rid"]
-                corresp_node = first(
+                corresp_node = utils.first(
                     list(filter(lambda tag: tag.get("id") == rid, target_tags_corresp))
                 )
-                author_notes = node_text(corresp_node)
+                author_notes = utils.node_text(corresp_node)
                 if author_notes:
                     contributor["notes-corresp"].append(author_notes)
         # Add xref linked footnotes if applicable
@@ -1527,13 +1570,13 @@ def format_contributor(
                 contributor["notes-fn"] = []
             if not target_tags_fn:
                 target_tags_fn = raw_parser.fn(soup)
-            for fn in fn_tags:
+            for fn_tag in fn_tags:
                 # Find the matching tag
-                rid = fn["rid"]
-                fn_node = first(
+                rid = fn_tag["rid"]
+                fn_node = utils.first(
                     list(filter(lambda tag: tag.get("id") == rid, target_tags_fn))
                 )
-                fn_text = node_text(fn_node)
+                fn_text = utils.node_text(fn_node)
                 if fn_text:
                     contributor["notes-fn"].append(fn_text)
 
@@ -1561,9 +1604,7 @@ def all_contributors(soup, detail="brief"):
 def is_author_non_byline(tag, contrib_type="author non-byline"):
     if tag and tag.get("contrib-type") and tag.get("contrib-type") == contrib_type:
         return True
-    elif (
-        tag and tag.parent and tag.parent.parent and tag.parent.parent.name == "collab"
-    ):
+    if tag and tag.parent and tag.parent.parent and tag.parent.parent.name == "collab":
         return True
     return False
 
@@ -1663,37 +1704,45 @@ def format_aff(aff_tag):
     if not aff_tag:
         return None, {}
     values = {
-        "dept": node_contents_str(
-            first(extract_nodes(aff_tag, "institution", "content-type", "dept"))
+        "dept": utils.node_contents_str(
+            utils.first(
+                utils.extract_nodes(aff_tag, "institution", "content-type", "dept")
+            )
         ),
-        "institution": node_contents_str(
-            first(
+        "institution": utils.node_contents_str(
+            utils.first(
                 list(
                     filter(
                         lambda n: "content-type" not in n.attrs,
-                        extract_nodes(aff_tag, "institution"),
+                        utils.extract_nodes(aff_tag, "institution"),
                     )
                 )
             )
         ),
-        "city": node_contents_str(
-            first(extract_nodes(aff_tag, "named-content", "content-type", "city"))
+        "city": utils.node_contents_str(
+            utils.first(
+                utils.extract_nodes(aff_tag, "named-content", "content-type", "city")
+            )
+        ),
+        "country": utils.node_contents_str(
+            utils.first(utils.extract_nodes(aff_tag, "country"))
+        ),
+        "email": utils.node_contents_str(
+            utils.first(utils.extract_nodes(aff_tag, "email"))
         ),
-        "country": node_contents_str(first(extract_nodes(aff_tag, "country"))),
-        "email": node_contents_str(first(extract_nodes(aff_tag, "email"))),
     }
     # Remove keys with None value
-    values = prune_dict_of_none_values(values)
+    values = utils.prune_dict_of_none_values(values)
     # If all values are none then extract as text
     if not values:
         # extract the text ignoring the label tag
-        aff_tag = remove_tag_from_tag(aff_tag, "label")
-        values = {"text": node_text(aff_tag).strip()}
+        aff_tag = utils.remove_tag_from_tag(aff_tag, "label")
+        values = {"text": utils.node_text(aff_tag).strip()}
 
     if "id" in aff_tag.attrs:
         return aff_tag["id"], values
-    else:
-        return None, values
+
+    return None, values
 
 
 def full_affiliation(soup):
@@ -1702,8 +1751,8 @@ def full_affiliation(soup):
     affs = []
     for tag in aff_tags:
         aff = {}
-        (id, aff_details) = format_aff(tag)
-        aff[id] = aff_details
+        (aff_id, aff_details) = format_aff(tag)
+        aff[aff_id] = aff_details
         affs.append(aff)
     return affs
 
@@ -1727,98 +1776,122 @@ def refs(soup):
         ref["ref"] = ref_text(tag)
 
         # ref_id
-        copy_attribute(tag.attrs, "id", ref)
+        utils.copy_attribute(tag.attrs, "id", ref)
 
         # article_title
         if raw_parser.article_title(tag):
-            ref["article_title"] = node_text(raw_parser.article_title(tag))
-            ref["full_article_title"] = node_contents_str(raw_parser.article_title(tag))
+            ref["article_title"] = utils.node_text(raw_parser.article_title(tag))
+            ref["full_article_title"] = utils.node_contents_str(
+                raw_parser.article_title(tag)
+            )
 
         if raw_parser.pub_id(tag, "pmid"):
-            ref["pmid"] = node_contents_str(first(raw_parser.pub_id(tag, "pmid")))
+            ref["pmid"] = utils.node_contents_str(
+                utils.first(raw_parser.pub_id(tag, "pmid"))
+            )
 
         if raw_parser.pub_id(tag, "isbn"):
-            ref["isbn"] = node_contents_str(first(raw_parser.pub_id(tag, "isbn")))
+            ref["isbn"] = utils.node_contents_str(
+                utils.first(raw_parser.pub_id(tag, "isbn"))
+            )
 
         if raw_parser.pub_id(tag, "doi"):
-            ref["reference_id"] = node_contents_str(
-                first(raw_parser.pub_id(tag, "doi"))
+            ref["reference_id"] = utils.node_contents_str(
+                utils.first(raw_parser.pub_id(tag, "doi"))
             )
-            ref["doi"] = doi_uri_to_doi(
-                node_contents_str(first(raw_parser.pub_id(tag, "doi")))
+            ref["doi"] = utils.doi_uri_to_doi(
+                utils.node_contents_str(utils.first(raw_parser.pub_id(tag, "doi")))
             )
 
         uri_tag = None
         if raw_parser.ext_link(tag, "uri"):
-            uri_tag = first(raw_parser.ext_link(tag, "uri"))
+            uri_tag = utils.first(raw_parser.ext_link(tag, "uri"))
         elif raw_parser.uri(tag):
-            uri_tag = first(raw_parser.uri(tag))
+            uri_tag = utils.first(raw_parser.uri(tag))
         if uri_tag:
-            set_if_value(ref, "uri", uri_tag.get("xlink:href"))
-            set_if_value(ref, "uri_text", node_contents_str(uri_tag))
+            utils.set_if_value(ref, "uri", uri_tag.get("xlink:href"))
+            utils.set_if_value(ref, "uri_text", utils.node_contents_str(uri_tag))
         # look for a pub-id tag if no uri yet
         if not ref.get("uri"):
             for pub_id_type in ["archive", "accession"]:
                 if raw_parser.pub_id(tag, pub_id_type):
-                    pub_id_tag = first(raw_parser.pub_id(tag, pub_id_type=pub_id_type))
-                    set_if_value(ref, "uri", pub_id_tag.get("xlink:href"))
+                    pub_id_tag = utils.first(
+                        raw_parser.pub_id(tag, pub_id_type=pub_id_type)
+                    )
+                    utils.set_if_value(ref, "uri", pub_id_tag.get("xlink:href"))
                 if ref.get("uri"):
                     break
 
         # accession, could be in either of two tags
-        set_if_value(
+        utils.set_if_value(
             ref,
             "accession",
-            node_contents_str(first(raw_parser.object_id(tag, "art-access-id"))),
+            utils.node_contents_str(
+                utils.first(raw_parser.object_id(tag, "art-access-id"))
+            ),
         )
         if not ref.get("accession"):
-            set_if_value(
+            utils.set_if_value(
                 ref,
                 "accession",
-                node_contents_str(
-                    first(raw_parser.pub_id(tag, pub_id_type="accession"))
+                utils.node_contents_str(
+                    utils.first(raw_parser.pub_id(tag, pub_id_type="accession"))
                 ),
             )
         if not ref.get("accession"):
-            set_if_value(
+            utils.set_if_value(
                 ref,
                 "accession",
-                node_contents_str(first(raw_parser.pub_id(tag, pub_id_type="archive"))),
+                utils.node_contents_str(
+                    utils.first(raw_parser.pub_id(tag, pub_id_type="archive"))
+                ),
             )
 
         if raw_parser.year(tag):
-            set_if_value(ref, "year", node_text(raw_parser.year(tag)))
-            set_if_value(
+            utils.set_if_value(ref, "year", utils.node_text(raw_parser.year(tag)))
+            utils.set_if_value(
                 ref, "year-iso-8601-date", raw_parser.year(tag).get("iso-8601-date")
             )
 
         if raw_parser.date_in_citation(tag):
-            set_if_value(
+            utils.set_if_value(
                 ref,
                 "date-in-citation",
-                node_text(first(raw_parser.date_in_citation(tag))),
+                utils.node_text(utils.first(raw_parser.date_in_citation(tag))),
             )
-            set_if_value(
+            utils.set_if_value(
                 ref,
                 "iso-8601-date",
-                first(raw_parser.date_in_citation(tag)).get("iso-8601-date"),
+                utils.first(raw_parser.date_in_citation(tag)).get("iso-8601-date"),
             )
 
         if raw_parser.patent(tag):
-            set_if_value(ref, "patent", node_text(first(raw_parser.patent(tag))))
-            set_if_value(ref, "country", first(raw_parser.patent(tag)).get("country"))
+            utils.set_if_value(
+                ref, "patent", utils.node_text(utils.first(raw_parser.patent(tag)))
+            )
+            utils.set_if_value(
+                ref, "country", utils.first(raw_parser.patent(tag)).get("country")
+            )
 
-        set_if_value(ref, "source", node_text(first(raw_parser.source(tag))))
-        set_if_value(
-            ref, "elocation-id", node_text(first(raw_parser.elocation_id(tag)))
+        utils.set_if_value(
+            ref, "source", utils.node_text(utils.first(raw_parser.source(tag)))
+        )
+        utils.set_if_value(
+            ref,
+            "elocation-id",
+            utils.node_text(utils.first(raw_parser.elocation_id(tag))),
         )
         if raw_parser.element_citation(tag):
-            copy_attribute(
-                first(raw_parser.element_citation(tag)).attrs, "publication-type", ref
+            utils.copy_attribute(
+                utils.first(raw_parser.element_citation(tag)).attrs,
+                "publication-type",
+                ref,
             )
         if "publication-type" not in ref and raw_parser.mixed_citations(tag):
-            copy_attribute(
-                first(raw_parser.mixed_citations(tag)).attrs, "publication-type", ref
+            utils.copy_attribute(
+                utils.first(raw_parser.mixed_citations(tag)).attrs,
+                "publication-type",
+                ref,
             )
 
         # authors
@@ -1832,46 +1905,52 @@ def refs(soup):
                 author_type = group["person-group-type"]
 
             # Read name or collab tag in the order they are listed
-            for name_or_collab_tag in extract_nodes(
+            for name_or_collab_tag in utils.extract_nodes(
                 group, ["name", "string-name", "collab"]
             ):
                 author = {}
 
                 # Shared tag attribute
-                set_if_value(author, "group-type", author_type)
+                utils.set_if_value(author, "group-type", author_type)
 
                 # name tag attributes
                 if name_or_collab_tag.name in ["name", "string-name"]:
-                    set_if_value(
+                    utils.set_if_value(
                         author,
                         "surname",
-                        node_text(first(raw_parser.surname(name_or_collab_tag))),
+                        utils.node_text(
+                            utils.first(raw_parser.surname(name_or_collab_tag))
+                        ),
                     )
-                    set_if_value(
+                    utils.set_if_value(
                         author,
                         "given-names",
-                        node_text(first(raw_parser.given_names(name_or_collab_tag))),
+                        utils.node_text(
+                            utils.first(raw_parser.given_names(name_or_collab_tag))
+                        ),
                     )
-                    set_if_value(
+                    utils.set_if_value(
                         author,
                         "suffix",
-                        node_text(first(raw_parser.suffix(name_or_collab_tag))),
+                        utils.node_text(
+                            utils.first(raw_parser.suffix(name_or_collab_tag))
+                        ),
                     )
 
                 # collab tag attribute
                 if name_or_collab_tag.name == "collab":
-                    set_if_value(
-                        author, "collab", node_contents_str(name_or_collab_tag)
+                    utils.set_if_value(
+                        author, "collab", utils.node_contents_str(name_or_collab_tag)
                     )
 
                 if len(author) > 0:
                     authors.append(author)
 
             # etal for the person group
-            if first(raw_parser.etal(group)):
+            if utils.first(raw_parser.etal(group)):
                 author = {}
                 author["etal"] = True
-                set_if_value(author, "group-type", author_type)
+                utils.set_if_value(author, "group-type", author_type)
                 authors.append(author)
 
         # Check for collab tag not wrapped in a person-group for backwards compatibility
@@ -1879,8 +1958,10 @@ def refs(soup):
             collab_tags = raw_parser.collab(tag)
             for collab_tag in collab_tags:
                 author = {}
-                set_if_value(author, "group-type", "author")
-                set_if_value(author, "collab", node_contents_str(collab_tag))
+                utils.set_if_value(author, "group-type", "author")
+                utils.set_if_value(
+                    author, "collab", utils.node_contents_str(collab_tag)
+                )
 
                 if len(author) > 0:
                     authors.append(author)
@@ -1888,29 +1969,57 @@ def refs(soup):
         if len(authors) > 0:
             ref["authors"] = authors
 
-        set_if_value(ref, "volume", node_text(first(raw_parser.volume(tag))))
-        set_if_value(ref, "issue", node_text(first(raw_parser.issue(tag))))
-        set_if_value(ref, "fpage", node_text(first(raw_parser.fpage(tag))))
-        set_if_value(ref, "lpage", node_text(first(raw_parser.lpage(tag))))
-        set_if_value(ref, "collab", node_text(first(raw_parser.collab(tag))))
-        set_if_value(
-            ref, "publisher_loc", node_text(first(raw_parser.publisher_loc(tag)))
+        utils.set_if_value(
+            ref, "volume", utils.node_text(utils.first(raw_parser.volume(tag)))
+        )
+        utils.set_if_value(
+            ref, "issue", utils.node_text(utils.first(raw_parser.issue(tag)))
+        )
+        utils.set_if_value(
+            ref, "fpage", utils.node_text(utils.first(raw_parser.fpage(tag)))
         )
-        set_if_value(
-            ref, "publisher_name", node_text(first(raw_parser.publisher_name(tag)))
+        utils.set_if_value(
+            ref, "lpage", utils.node_text(utils.first(raw_parser.lpage(tag)))
         )
-        set_if_value(ref, "edition", node_contents_str(first(raw_parser.edition(tag))))
-        set_if_value(ref, "version", node_contents_str(first(raw_parser.version(tag))))
-        set_if_value(
+        utils.set_if_value(
+            ref, "collab", utils.node_text(utils.first(raw_parser.collab(tag)))
+        )
+        utils.set_if_value(
+            ref,
+            "publisher_loc",
+            utils.node_text(utils.first(raw_parser.publisher_loc(tag))),
+        )
+        utils.set_if_value(
+            ref,
+            "publisher_name",
+            utils.node_text(utils.first(raw_parser.publisher_name(tag))),
+        )
+        utils.set_if_value(
+            ref,
+            "edition",
+            utils.node_contents_str(utils.first(raw_parser.edition(tag))),
+        )
+        utils.set_if_value(
+            ref,
+            "version",
+            utils.node_contents_str(utils.first(raw_parser.version(tag))),
+        )
+        utils.set_if_value(
             ref,
             "chapter-title",
-            node_contents_str(first(raw_parser.chapter_title(tag))),
+            utils.node_contents_str(utils.first(raw_parser.chapter_title(tag))),
+        )
+        utils.set_if_value(
+            ref, "comment", utils.node_text(utils.first(raw_parser.comment(tag)))
         )
-        set_if_value(ref, "comment", node_text(first(raw_parser.comment(tag))))
-        set_if_value(
-            ref, "data-title", node_contents_str(first(raw_parser.data_title(tag)))
+        utils.set_if_value(
+            ref,
+            "data-title",
+            utils.node_contents_str(utils.first(raw_parser.data_title(tag))),
+        )
+        utils.set_if_value(
+            ref, "conf-name", utils.node_text(utils.first(raw_parser.conf_name(tag)))
         )
-        set_if_value(ref, "conf-name", node_text(first(raw_parser.conf_name(tag))))
 
         # If not empty, add position value, append, then increment the position counter
         if len(ref) > 0:
@@ -1932,11 +2041,11 @@ def extract_component_doi(tag, nodenames):
     component_doi = None
 
     if tag.name == "sub-article":
-        component_doi = doi_uri_to_doi(
-            node_text(first(raw_parser.article_id(tag, pub_id_type="doi")))
+        component_doi = utils.doi_uri_to_doi(
+            utils.node_text(utils.first(raw_parser.article_id(tag, pub_id_type="doi")))
         )
     else:
-        object_id_tag = first(raw_parser.object_id(tag, pub_id_type="doi"))
+        object_id_tag = utils.first(raw_parser.object_id(tag, pub_id_type="doi"))
         # Tweak: if it is media and has no object_id_tag then it is not a "component"
         if tag.name == "media" and not object_id_tag:
             component_doi = None
@@ -1946,9 +2055,9 @@ def extract_component_doi(tag, nodenames):
             #   the boxed text does not have a DOI, the figure does have one
             if (
                 object_id_tag
-                and first_parent(object_id_tag, nodenames).name == tag.name
+                and utils.first_parent(object_id_tag, nodenames).name == tag.name
             ):
-                component_doi = doi_uri_to_doi(node_text(object_id_tag))
+                component_doi = utils.doi_uri_to_doi(utils.node_text(object_id_tag))
 
     return component_doi
 
@@ -1985,7 +2094,7 @@ def components(soup):
 
     # Find all tags for all component_types, allows the order
     #  in which they are found to be preserved
-    component_tags = extract_nodes(soup, nodenames)
+    component_tags = utils.extract_nodes(soup, nodenames)
 
     for tag in component_tags:
 
@@ -1998,11 +2107,11 @@ def components(soup):
         component_doi = extract_component_doi(tag, nodenames)
         if component_doi is None:
             continue
-        else:
-            component["doi"] = doi_uri_to_doi(component_doi)
-            component["doi_url"] = doi_to_doi_uri(component["doi"])
 
-        copy_attribute(tag.attrs, "id", component)
+        component["doi"] = utils.doi_uri_to_doi(component_doi)
+        component["doi_url"] = utils.doi_to_doi_uri(component["doi"])
+
+        utils.copy_attribute(tag.attrs, "id", component)
 
         if ctype == "sub-article":
             title_tag = raw_parser.article_title(tag)
@@ -2025,8 +2134,8 @@ def components(soup):
             title_tag = raw_parser.title(tag)
 
         if title_tag:
-            component["title"] = node_text(title_tag)
-            component["full_title"] = node_contents_str(title_tag)
+            component["title"] = utils.node_text(title_tag)
+            component["full_title"] = utils.node_contents_str(title_tag)
 
         if ctype == "boxed-text":
             label_tag = label_tag_inspected(tag, "boxed-text")
@@ -2034,26 +2143,26 @@ def components(soup):
             label_tag = raw_parser.label(tag)
 
         if label_tag:
-            component["label"] = node_text(label_tag)
-            component["full_label"] = node_contents_str(label_tag)
+            component["label"] = utils.node_text(label_tag)
+            component["full_label"] = utils.node_contents_str(label_tag)
 
         if raw_parser.caption(tag):
-            first_paragraph = first(paragraphs(raw_parser.caption(tag)))
+            first_paragraph = utils.first(utils.paragraphs(raw_parser.caption(tag)))
             # fix a problem with the new kitchen sink of caption within caption tag
             if first_paragraph:
                 nested_caption = raw_parser.caption(first_paragraph)
                 if nested_caption:
-                    nested_paragraphs = paragraphs(nested_caption)
-                    first_paragraph = first(nested_paragraphs) or first_paragraph
-            if first_paragraph and not starts_with_doi(first_paragraph):
+                    nested_paragraphs = utils.paragraphs(nested_caption)
+                    first_paragraph = utils.first(nested_paragraphs) or first_paragraph
+            if first_paragraph and not utils.starts_with_doi(first_paragraph):
                 # Remove the supplementary tag from the paragraph if present
                 if raw_parser.supplementary_material(first_paragraph):
-                    first_paragraph = remove_tag_from_tag(
+                    first_paragraph = utils.remove_tag_from_tag(
                         first_paragraph, "supplementary-material"
                     )
-                if node_text(first_paragraph).strip():
-                    component["caption"] = node_text(first_paragraph)
-                    component["full_caption"] = node_contents_str(first_paragraph)
+                if utils.node_text(first_paragraph).strip():
+                    component["caption"] = utils.node_text(first_paragraph)
+                    component["full_caption"] = utils.node_contents_str(first_paragraph)
 
         if raw_parser.permissions(tag):
 
@@ -2061,26 +2170,26 @@ def components(soup):
             for permissions_tag in raw_parser.permissions(tag):
                 permissions_item = {}
                 if raw_parser.copyright_statement(permissions_tag):
-                    permissions_item["copyright_statement"] = node_text(
+                    permissions_item["copyright_statement"] = utils.node_text(
                         raw_parser.copyright_statement(permissions_tag)
                     )
 
                 if raw_parser.copyright_year(permissions_tag):
-                    permissions_item["copyright_year"] = node_text(
+                    permissions_item["copyright_year"] = utils.node_text(
                         raw_parser.copyright_year(permissions_tag)
                     )
 
                 if raw_parser.copyright_holder(permissions_tag):
-                    permissions_item["copyright_holder"] = node_text(
+                    permissions_item["copyright_holder"] = utils.node_text(
                         raw_parser.copyright_holder(permissions_tag)
                     )
 
                 if raw_parser.licence_p(permissions_tag):
-                    permissions_item["license"] = node_text(
-                        first(raw_parser.licence_p(permissions_tag))
+                    permissions_item["license"] = utils.node_text(
+                        utils.first(raw_parser.licence_p(permissions_tag))
                     )
-                    permissions_item["full_license"] = node_contents_str(
-                        first(raw_parser.licence_p(permissions_tag))
+                    permissions_item["full_license"] = utils.node_contents_str(
+                        utils.first(raw_parser.licence_p(permissions_tag))
                     )
 
                 component["permissions"].append(permissions_item)
@@ -2103,12 +2212,12 @@ def components(soup):
             "app",
             "media",
         ]
-        parent_tag = first_parent(tag, parent_nodenames)
+        parent_tag = utils.first_parent(tag, parent_nodenames)
 
         if parent_tag:
 
             # For fig-group we actually want the first fig of the fig-group as the parent
-            acting_parent_tag = component_acting_parent_tag(parent_tag, tag)
+            acting_parent_tag = utils.component_acting_parent_tag(parent_tag, tag)
 
             # Only counts if the acting parent tag has a DOI
             if (
@@ -2118,18 +2227,18 @@ def components(soup):
             ):
 
                 component["parent_type"] = acting_parent_tag.name
-                component["parent_ordinal"] = tag_ordinal(acting_parent_tag)
+                component["parent_ordinal"] = utils.tag_ordinal(acting_parent_tag)
                 component["parent_sibling_ordinal"] = tag_details_sibling_ordinal(
                     acting_parent_tag
                 )
                 component["parent_asset"] = tag_details_asset(acting_parent_tag)
 
             # Look for parent parent, if available
-            parent_parent_tag = first_parent(parent_tag, parent_nodenames)
+            parent_parent_tag = utils.first_parent(parent_tag, parent_nodenames)
 
             if parent_parent_tag:
 
-                acting_parent_tag = component_acting_parent_tag(
+                acting_parent_tag = utils.component_acting_parent_tag(
                     parent_parent_tag, parent_tag
                 )
 
@@ -2139,7 +2248,9 @@ def components(soup):
                     is not None
                 ):
                     component["parent_parent_type"] = acting_parent_tag.name
-                    component["parent_parent_ordinal"] = tag_ordinal(acting_parent_tag)
+                    component["parent_parent_ordinal"] = utils.tag_ordinal(
+                        acting_parent_tag
+                    )
                     component[
                         "parent_parent_sibling_ordinal"
                     ] = tag_details_sibling_ordinal(acting_parent_tag)
@@ -2148,11 +2259,11 @@ def components(soup):
                     )
 
         content = ""
-        for p_tag in extract_nodes(tag, "p"):
+        for p_tag in utils.extract_nodes(tag, "p"):
             if content != "":
                 # Add a space before each new paragraph for now
                 content = content + " "
-            content = content + node_text(p_tag)
+            content = content + utils.node_text(p_tag)
 
         if content != "":
             component["content"] = content
@@ -2162,7 +2273,7 @@ def components(soup):
         if ctype == "media":
             media_tag = tag
         elif ctype == "supplementary-material":
-            media_tag = first(raw_parser.media(tag))
+            media_tag = utils.first(raw_parser.media(tag))
         if media_tag:
             component["mimetype"] = media_tag.get("mimetype")
             component["mime-subtype"] = media_tag.get("mime-subtype")
@@ -2173,7 +2284,7 @@ def components(soup):
             component["position"] = position
 
             # Ordinal is based on all tags of the same type even if they have no DOI
-            component["ordinal"] = tag_ordinal(tag)
+            component["ordinal"] = utils.tag_ordinal(tag)
             component["sibling_ordinal"] = tag_details_sibling_ordinal(tag)
             component["asset"] = tag_details_asset(tag)
             # component['ordinal'] = position_by_type[ctype]
@@ -2218,15 +2329,17 @@ def full_correspondence(soup):
             if raw_parser.email(tag):
                 # Multiple email addresses possible
                 for email_tag in raw_parser.email(tag):
-                    cor[tag["id"]].append(node_contents_str(email_tag))
+                    cor[tag["id"]].append(utils.node_contents_str(email_tag))
             elif raw_parser.phone(tag):
                 # Look for a phone number
-                cor[tag["id"]].append(node_contents_str(first(raw_parser.phone(tag))))
+                cor[tag["id"]].append(
+                    utils.node_contents_str(utils.first(raw_parser.phone(tag)))
+                )
 
     return cor
 
 
-@nullify
+@utils.nullify
 def author_notes(soup):
     """
     Find the fn tags included in author-notes
@@ -2239,12 +2352,12 @@ def author_notes(soup):
         for tag in fn_nodes:
             if "fn-type" in tag.attrs:
                 if tag["fn-type"] != "present-address":
-                    author_notes.append(node_text(tag))
+                    author_notes.append(utils.node_text(tag))
 
     return author_notes
 
 
-@nullify
+@utils.nullify
 def full_author_notes(soup, fntype_filter=None):
     """
     Find the fn tags included in author-notes
@@ -2259,72 +2372,74 @@ def full_author_notes(soup, fntype_filter=None):
     return notes
 
 
-@nullify
+@utils.nullify
 def competing_interests(soup, fntype_filter):
     """
     Find the fn tags included in the competing interest
     """
 
-    competing_interests_section = extract_nodes(
+    competing_interests_section = utils.extract_nodes(
         soup, "fn-group", attr="content-type", value="competing-interest"
     )
     if not competing_interests_section:
         return None
-    fn = extract_nodes(first(competing_interests_section), "fn")
-    interests = footnotes(fn, fntype_filter)
+    fn_nodes = utils.extract_nodes(utils.first(competing_interests_section), "fn")
+    interests = footnotes(fn_nodes, fntype_filter)
 
     return interests
 
 
-@nullify
+@utils.nullify
 def present_addresses(soup):
     notes = []
     fntype_filter = "present-address"
     author_notes_section = raw_parser.author_notes(soup)
     if author_notes_section:
-        fn_nodes = extract_nodes(author_notes_section, "fn")
+        fn_nodes = utils.extract_nodes(author_notes_section, "fn")
         notes = footnotes(fn_nodes, fntype_filter)
     return notes
 
 
-@nullify
+@utils.nullify
 def other_foot_notes(soup):
     notes = []
     fntype_filter = ["fn", "other"]
     author_notes_section = raw_parser.author_notes(soup)
     if author_notes_section:
-        fn_nodes = extract_nodes(author_notes_section, "fn")
+        fn_nodes = utils.extract_nodes(author_notes_section, "fn")
         notes = footnotes(fn_nodes, fntype_filter)
     return notes
 
 
-@nullify
+@utils.nullify
 def author_contributions(soup, fntype_filter):
     """
     Find the fn tags included in the competing interest
     """
 
-    author_contributions_section = extract_nodes(
+    author_contributions_section = utils.extract_nodes(
         soup, "fn-group", attr="content-type", value="author-contribution"
     )
     if not author_contributions_section:
         return None
-    fn = extract_nodes(first(author_contributions_section), "fn")
-    cons = footnotes(fn, fntype_filter)
+    fn_nodes = utils.extract_nodes(utils.first(author_contributions_section), "fn")
+    cons = footnotes(fn_nodes, fntype_filter)
 
     return cons
 
 
-def footnotes(fn, fntype_filter):
+def footnotes(fn_nodes, fntype_filter):
     notes = []
-    for f in fn:
+    for fn_node in fn_nodes:
         try:
-            if fntype_filter is None or f["fn-type"] in fntype_filter:
+            if fntype_filter is None or fn_node["fn-type"] in fntype_filter:
                 notes.append(
                     {
-                        "id": f["id"],
-                        "text": clean_whitespace(node_contents_str(f)),
-                        "fn-type": f["fn-type"],
+                        "id": fn_node["id"],
+                        "text": utils.clean_whitespace(
+                            utils.node_contents_str(fn_node)
+                        ),
+                        "fn-type": fn_node["fn-type"],
                     }
                 )
         except KeyError:
@@ -2333,38 +2448,38 @@ def footnotes(fn, fntype_filter):
     return notes
 
 
-@nullify
+@utils.nullify
 def full_award_groups(soup):
     """
     Find the award-group items and return a list of details
     """
     award_groups = []
 
-    funding_group_section = extract_nodes(soup, "funding-group")
+    funding_group_section = utils.extract_nodes(soup, "funding-group")
     # counter for auto generated id values, if required
     generated_id_counter = 1
-    for fg in funding_group_section:
+    for funding_group in funding_group_section:
 
-        award_group_tags = extract_nodes(fg, "award-group")
+        award_group_tags = utils.extract_nodes(funding_group, "award-group")
 
-        for ag in award_group_tags:
-            if "id" in ag.attrs:
-                ref = ag["id"]
+        for award_group_tag in award_group_tags:
+            if "id" in award_group_tag.attrs:
+                ref = award_group_tag["id"]
             else:
                 # hack: generate and increment an id value none is available
                 ref = "award-group-{id}".format(id=generated_id_counter)
                 generated_id_counter += 1
 
             award_group = {}
-            award_group_id = award_group_award_id(ag)
+            award_group_id = award_group_award_id(award_group_tag)
             if award_group_id is not None:
-                award_group["award-id"] = first(award_group_id)
-            funding_sources = full_award_group_funding_source(ag)
-            source = first(funding_sources)
+                award_group["award-id"] = utils.first(award_group_id)
+            funding_sources = full_award_group_funding_source(award_group_tag)
+            source = utils.first(funding_sources)
             if source is not None:
-                copy_attribute(source, "institution", award_group)
-                copy_attribute(source, "institution-id", award_group, "id")
-                copy_attribute(
+                utils.copy_attribute(source, "institution", award_group)
+                utils.copy_attribute(source, "institution-id", award_group, "id")
+                utils.copy_attribute(
                     source,
                     "institution-id-type",
                     award_group,
@@ -2377,32 +2492,34 @@ def full_award_groups(soup):
     return award_groups
 
 
-@nullify
+@utils.nullify
 def award_groups(soup):
     """
     Find the award-group items and return a list of details
     """
     award_groups = []
 
-    funding_group_section = extract_nodes(soup, "funding-group")
-    for fg in funding_group_section:
+    funding_group_section = utils.extract_nodes(soup, "funding-group")
+    for funding_group in funding_group_section:
 
-        award_group_tags = extract_nodes(fg, "award-group")
+        award_group_tags = utils.extract_nodes(funding_group, "award-group")
 
-        for ag in award_group_tags:
+        for award_group_tag in award_group_tags:
 
             award_group = {}
 
-            award_group["funding_source"] = award_group_funding_source(ag)
-            award_group["recipient"] = award_group_principal_award_recipient(ag)
-            award_group["award_id"] = award_group_award_id(ag)
+            award_group["funding_source"] = award_group_funding_source(award_group_tag)
+            award_group["recipient"] = award_group_principal_award_recipient(
+                award_group_tag
+            )
+            award_group["award_id"] = award_group_award_id(award_group_tag)
 
             award_groups.append(award_group)
 
     return award_groups
 
 
-@nullify
+@utils.nullify
 def award_group_funding_source(tag):
     """
     Given a funding group element
@@ -2410,13 +2527,13 @@ def award_group_funding_source(tag):
     item found in the get_funding_group section
     """
     award_group_funding_source = []
-    funding_source_tags = extract_nodes(tag, "funding-source")
-    for t in funding_source_tags:
-        award_group_funding_source.append(t.text)
+    funding_source_tags = utils.extract_nodes(tag, "funding-source")
+    for funding_source_tag in funding_source_tags:
+        award_group_funding_source.append(funding_source_tag.text)
     return award_group_funding_source
 
 
-@nullify
+@utils.nullify
 def full_award_group_funding_source(tag):
     """
     Given a funding group element
@@ -2424,25 +2541,29 @@ def full_award_group_funding_source(tag):
     item found in the get_funding_group section
     """
     award_group_funding_sources = []
-    funding_source_nodes = extract_nodes(tag, "funding-source")
+    funding_source_nodes = utils.extract_nodes(tag, "funding-source")
     for funding_source_node in funding_source_nodes:
 
         award_group_funding_source = {}
 
-        institution_nodes = extract_nodes(funding_source_node, "institution")
+        institution_nodes = utils.extract_nodes(funding_source_node, "institution")
 
-        institution_node = first(institution_nodes)
+        institution_node = utils.first(institution_nodes)
         if institution_node:
-            award_group_funding_source["institution"] = node_text(institution_node)
+            award_group_funding_source["institution"] = utils.node_text(
+                institution_node
+            )
             if "content-type" in institution_node.attrs:
                 award_group_funding_source["institution-type"] = institution_node[
                     "content-type"
                 ]
 
-        institution_id_nodes = extract_nodes(funding_source_node, "institution-id")
-        institution_id_node = first(institution_id_nodes)
+        institution_id_nodes = utils.extract_nodes(
+            funding_source_node, "institution-id"
+        )
+        institution_id_node = utils.first(institution_id_nodes)
         if institution_id_node:
-            award_group_funding_source["institution-id"] = node_text(
+            award_group_funding_source["institution-id"] = utils.node_text(
                 institution_id_node
             )
             if "institution-id-type" in institution_id_node.attrs:
@@ -2455,35 +2576,43 @@ def full_award_group_funding_source(tag):
     return award_group_funding_sources
 
 
-@nullify
+@utils.nullify
 def award_group_award_id(tag):
     """
     Find the award group award id, one for each
     item found in the get_funding_group section
     """
     award_group_award_id = []
-    award_id_tags = extract_nodes(tag, "award-id")
-    for t in award_id_tags:
-        award_group_award_id.append(t.text)
+    award_id_tags = utils.extract_nodes(tag, "award-id")
+    for award_id_tag in award_id_tags:
+        award_group_award_id.append(award_id_tag.text)
     return award_group_award_id
 
 
-@nullify
+@utils.nullify
 def award_group_principal_award_recipient(tag):
     """
     Find the award group principal award recipient, one for each
     item found in the get_funding_group section
     """
     award_group_principal_award_recipient = []
-    principal_award_recipients = extract_nodes(tag, "principal-award-recipient")
+    principal_award_recipients = utils.extract_nodes(tag, "principal-award-recipient")
 
-    for t in principal_award_recipients:
+    for recipient_tag in principal_award_recipients:
         principal_award_recipient_text = ""
 
-        institution = node_text(first(extract_nodes(t, "institution")))
-        surname = node_text(first(extract_nodes(t, "surname")))
-        given_names = node_text(first(extract_nodes(t, "given-names")))
-        string_name = node_text(first(raw_parser.string_name(t)))
+        institution = utils.node_text(
+            utils.first(utils.extract_nodes(recipient_tag, "institution"))
+        )
+        surname = utils.node_text(
+            utils.first(utils.extract_nodes(recipient_tag, "surname"))
+        )
+        given_names = utils.node_text(
+            utils.first(utils.extract_nodes(recipient_tag, "given-names"))
+        )
+        string_name = utils.node_text(
+            utils.first(raw_parser.string_name(recipient_tag))
+        )
         # Concatenate name and institution values if found
         #  while filtering out excess whitespace
         if given_names:
@@ -2507,11 +2636,11 @@ def object_id_doi(tag, parent_tag_name=None):
     object_id = None
     object_ids = raw_parser.object_id(tag, "doi")
     if object_ids:
-        object_id = first([id_ for id_ in object_ids])
+        object_id = utils.first(object_ids)
     if parent_tag_name and object_id and object_id.parent.name != parent_tag_name:
         object_id = None
     if object_id:
-        doi = node_contents_str(object_id)
+        doi = utils.node_contents_str(object_id)
     return doi
 
 
@@ -2554,7 +2683,7 @@ def title_text(
     )
 
     if title_tag:
-        title = node_contents_str(title_tag)
+        title = utils.node_contents_str(title_tag)
     return title
 
 
@@ -2576,7 +2705,7 @@ def label(tag, parent_tag_name=None):
     label = None
     label_tag = label_tag_inspected(tag, parent_tag_name)
     if label_tag:
-        label = node_contents_str(label_tag)
+        label = utils.node_contents_str(label_tag)
     return label
 
 
@@ -2593,19 +2722,22 @@ def acknowledgements_json(soup):
         return body_block_content_render(raw_parser.acknowledgements(soup))[0].get(
             "content"
         )
-    else:
-        return None
+
+    return None
 
 
 def keywords_json(soup, html_flag=True):
     # Configure the XML to HTML conversion preference for shorthand use below
-    convert = lambda xml_string: xml_to_html(html_flag, xml_string)
+    def convert(xml_string):
+        return xml_to_html(html_flag, xml_string)
+
     return list(map(convert, full_keywords(soup)))
 
 
 def research_organism_json(soup, html_flag=True):
     # Configure the XML to HTML conversion preference for shorthand use below
-    convert = lambda xml_string: xml_to_html(html_flag, xml_string)
+    def convert(xml_string):
+        return xml_to_html(html_flag, xml_string)
 
     do_not_include = ["none", "other"]
     research_organisms = list(
@@ -2620,18 +2752,22 @@ def research_organism_json(soup, html_flag=True):
 def boxed_text_to_image_block(tag):
     "covert boxed-text to an image block containing an inline-graphic"
     tag_block = OrderedDict()
-    image_content = body_block_image_content(first(raw_parser.inline_graphic(tag)))
+    image_content = body_block_image_content(
+        utils.first(raw_parser.inline_graphic(tag))
+    )
     tag_block["type"] = "image"
-    set_if_value(tag_block, "doi", doi_uri_to_doi(object_id_doi(tag, tag.name)))
-    set_if_value(tag_block, "id", tag.get("id"))
-    set_if_value(tag_block, "image", image_content)
+    utils.set_if_value(
+        tag_block, "doi", utils.doi_uri_to_doi(object_id_doi(tag, tag.name))
+    )
+    utils.set_if_value(tag_block, "id", tag.get("id"))
+    utils.set_if_value(tag_block, "image", image_content)
     # render paragraphs into a caption
     p_tags = raw_parser.paragraph(tag)
     caption_content = []
     for p_tag in p_tags:
         if not raw_parser.inline_graphic(p_tag):
             caption_content.append(body_block_content(p_tag))
-    set_if_value(tag_block, "caption", caption_content)
+    utils.set_if_value(tag_block, "caption", caption_content)
     return tag_block
 
 
@@ -2679,12 +2815,12 @@ def body_json(soup, base_url=None):
 def render_raw_body(tag, remove_key_info_box=False, base_url=None):
     body_content = []
     body_tags = body_blocks(tag)
-    for tag in body_tags:
-        if tag.name == "boxed-text":
+    for body_tag in body_tags:
+        if body_tag.name == "boxed-text":
             # Extract the text of the first child tag for comparison, if present
             first_node_text = None
-            if tag.children:
-                first_node_text = node_text(first(list(tag.children)))
+            if body_tag.children:
+                first_node_text = utils.node_text(utils.first(list(body_tag.children)))
 
             if (
                 remove_key_info_box is True
@@ -2694,27 +2830,27 @@ def render_raw_body(tag, remove_key_info_box=False, base_url=None):
                 # Skip this tag
                 continue
 
-            elif raw_parser.inline_graphic(tag):
+            if raw_parser.inline_graphic(body_tag):
                 # edge case where inline-graphic is in the first boxed-text
-                tag_block = boxed_text_to_image_block(tag)
+                tag_block = boxed_text_to_image_block(body_tag)
                 body_content.append(tag_block)
 
-            elif not raw_parser.title(tag) and not raw_parser.label(tag):
+            elif not raw_parser.title(body_tag) and not raw_parser.label(body_tag):
                 # Collapse boxed-text here if it has no title or label
-                for boxed_tag in tag:
+                for boxed_tag in body_tag:
                     tag_blocks = body_block_content_render(boxed_tag, base_url=base_url)
                     for tag_block in tag_blocks:
                         if tag_block != {}:
                             body_content.append(tag_block)
             else:
                 # Add it
-                tag_blocks = body_block_content_render(tag, base_url=base_url)
+                tag_blocks = body_block_content_render(body_tag, base_url=base_url)
                 for tag_block in tag_blocks:
                     if tag_block != {}:
                         body_content.append(tag_block)
         else:
 
-            tag_blocks = body_block_content_render(tag, base_url=base_url)
+            tag_blocks = body_block_content_render(body_tag, base_url=base_url)
             # tag_content = body_block_content_render(tag)
             for tag_block in tag_blocks:
                 if tag_block != {}:
@@ -2769,12 +2905,15 @@ def body_block_content_render(tag, recursive=False, base_url=None):
         "code",
     ]:
         for child_tag in tag:
-            if not (hasattr(child_tag, "name")):
+            if not hasattr(child_tag, "name"):
                 continue
 
             if child_tag.name == "p":
                 # Ignore paragraphs that start with DOI:
-                if node_text(child_tag) and len(remove_doi_paragraph([child_tag])) <= 0:
+                if (
+                    utils.node_text(child_tag)
+                    and len(utils.remove_doi_paragraph([child_tag])) <= 0
+                ):
                     continue
                 for block_content in body_block_paragraph_render(
                     child_tag, base_url=base_url
@@ -2817,12 +2956,12 @@ def body_block_paragraph_render(p_tag, html_flag=True, base_url=None):
     this is separated out so it can be called from more than one place
     """
     # Configure the XML to HTML conversion preference for shorthand use below
-    convert = lambda xml_string: xml_to_html(html_flag, xml_string, base_url)
+    def convert(xml_string):
+        return xml_to_html(html_flag, xml_string, base_url)
 
     block_content_list = []
 
     tag_content_content = []
-    nodenames = body_block_nodenames()
 
     paragraph_content = u""
     for child_tag in p_tag:
@@ -2862,7 +3001,7 @@ def body_block_caption_render(caption_tags, base_url=None):
     caption_content = []
     supplementary_material_tags = []
 
-    for block_tag in remove_doi_paragraph(caption_tags):
+    for block_tag in utils.remove_doi_paragraph(caption_tags):
         # Note then skip p tags with supplementary-material inside
         if raw_parser.supplementary_material(block_tag):
             for supp_tag in raw_parser.supplementary_material(block_tag):
@@ -2894,7 +3033,7 @@ def body_block_paragraph_content(text):
     tag_content = OrderedDict()
     if text and text != "":
         tag_content["type"] = "paragraph"
-        tag_content["text"] = clean_whitespace(text)
+        tag_content["text"] = utils.clean_whitespace(text)
     return tag_content
 
 
@@ -2902,10 +3041,10 @@ def body_block_image_content(tag):
     "format a graphic or inline-graphic into a body block json format"
     image_content = OrderedDict()
     if tag:
-        copy_attribute(tag.attrs, "xlink:href", image_content, "uri")
+        utils.copy_attribute(tag.attrs, "xlink:href", image_content, "uri")
         if "uri" in image_content:
             # todo!! alt
-            set_if_value(image_content, "alt", "")
+            utils.set_if_value(image_content, "alt", "")
     return image_content
 
 
@@ -2921,18 +3060,22 @@ def body_block_title_label_caption(
     """set the title, label and caption values in a consistent way
 
     set_caption: insert a "caption" field
-    prefer_title: when only one value is available, set title rather than label. If False, set label rather than title"""
-    set_if_value(tag_content, "label", rstrip_punctuation(label_value))
-    set_if_value(tag_content, "title", title_value)
+    prefer_title: when only one value is available,
+       set title rather than label. If False, set label rather than title
+    """
+    utils.set_if_value(tag_content, "label", utils.rstrip_punctuation(label_value))
+    utils.set_if_value(tag_content, "title", title_value)
     if set_caption is True and caption_content and len(caption_content) > 0:
         tag_content["caption"] = caption_content
     if prefer_title:
         if "title" not in tag_content and label_value:
-            set_if_value(tag_content, "title", label_value)
+            utils.set_if_value(tag_content, "title", label_value)
             del tag_content["label"]
     if prefer_label:
         if "label" not in tag_content and title_value:
-            set_if_value(tag_content, "label", rstrip_punctuation(title_value))
+            utils.set_if_value(
+                tag_content, "label", utils.rstrip_punctuation(title_value)
+            )
             del tag_content["title"]
 
 
@@ -2941,22 +3084,24 @@ def body_block_attribution(tag):
     attributions = []
     if raw_parser.attrib(tag):
         for attrib_tag in raw_parser.attrib(tag):
-            attributions.append(node_contents_str(attrib_tag))
+            attributions.append(utils.node_contents_str(attrib_tag))
     if raw_parser.permissions(tag):
         # concatenate content from from the permissions tag
         for permissions_tag in raw_parser.permissions(tag):
             attrib_string = ""
             # add the copyright statement if found
-            attrib_string = join_sentences(
+            attrib_string = utils.join_sentences(
                 attrib_string,
-                node_contents_str(raw_parser.copyright_statement(permissions_tag)),
+                utils.node_contents_str(
+                    raw_parser.copyright_statement(permissions_tag)
+                ),
                 ".",
             )
             # add the license paragraphs
             if raw_parser.licence_p(permissions_tag):
                 for licence_p_tag in raw_parser.licence_p(permissions_tag):
-                    attrib_string = join_sentences(
-                        attrib_string, node_contents_str(licence_p_tag), "."
+                    attrib_string = utils.join_sentences(
+                        attrib_string, utils.node_contents_str(licence_p_tag), "."
                     )
             if attrib_string != "":
                 attributions.append(attrib_string)
@@ -2965,26 +3110,31 @@ def body_block_attribution(tag):
 
 def body_block_content(tag, html_flag=True, base_url=None):
     # Configure the XML to HTML conversion preference for shorthand use below
-    convert = lambda xml_string: xml_to_html(html_flag, xml_string, base_url)
+    def convert(xml_string):
+        return xml_to_html(html_flag, xml_string, base_url)
 
     tag_content = OrderedDict()
 
-    if not (hasattr(tag, "name")):
+    if not hasattr(tag, "name"):
         return OrderedDict()
 
     if tag.name == "sec":
         tag_content["type"] = "section"
-        set_if_value(tag_content, "id", tag.get("id"))
-        set_if_value(
+        utils.set_if_value(tag_content, "id", tag.get("id"))
+        utils.set_if_value(
             tag_content, "title", convert(title_text(tag, direct_sibling_only=True))
         )
 
     elif tag.name == "boxed-text":
         tag_content["type"] = "box"
-        set_if_value(tag_content, "doi", doi_uri_to_doi(object_id_doi(tag, tag.name)))
-        set_if_value(tag_content, "id", tag.get("id"))
+        utils.set_if_value(
+            tag_content, "doi", utils.doi_uri_to_doi(object_id_doi(tag, tag.name))
+        )
+        utils.set_if_value(tag_content, "id", tag.get("id"))
 
-        title_parent_tag = first_parent(raw_parser.title(tag), ["boxed-text", "fig"])
+        title_parent_tag = utils.first_parent(
+            raw_parser.title(tag), ["boxed-text", "fig"]
+        )
         if title_parent_tag and title_parent_tag.name == "boxed-text":
             title_value = convert(title_text(tag))
         else:
@@ -3013,10 +3163,12 @@ def body_block_content(tag, html_flag=True, base_url=None):
 
         # Remove unwanted nested tags
         unwanted_tag_names = body_block_nodenames()
-        tag = remove_tag_from_tag(tag, unwanted_tag_names)
+        tag = utils.remove_tag_from_tag(tag, unwanted_tag_names)
 
-        if node_contents_str(tag):
-            tag_content["text"] = convert(clean_whitespace(node_contents_str(tag)))
+        if utils.node_contents_str(tag):
+            tag_content["text"] = convert(
+                utils.clean_whitespace(utils.node_contents_str(tag))
+            )
 
     elif tag.name == "disp-quote":
         if tag.get("content-type") and tag.get("content-type") == "editor-comment":
@@ -3042,10 +3194,10 @@ def body_block_content(tag, html_flag=True, base_url=None):
         asset_tag_content = OrderedDict()
 
         asset_tag_content["type"] = "table"
-        set_if_value(
-            asset_tag_content, "doi", doi_uri_to_doi(object_id_doi(tag, tag.name))
+        utils.set_if_value(
+            asset_tag_content, "doi", utils.doi_uri_to_doi(object_id_doi(tag, tag.name))
         )
-        set_if_value(asset_tag_content, "id", tag.get("id"))
+        utils.set_if_value(asset_tag_content, "id", tag.get("id"))
         title_value = convert(title_text(tag, "caption", tag.name))
         label_value = label(tag, tag.name)
 
@@ -3069,21 +3221,20 @@ def body_block_content(tag, html_flag=True, base_url=None):
         asset_tag_content["tables"] = []
         for table in tables:
             # Add the table tag back for now
-            table_content = "<table>" + node_contents_str(table) + "</table>"
+            table_content = "<table>" + utils.node_contents_str(table) + "</table>"
             asset_tag_content["tables"].append(convert(table_content))
 
         table_wrap_foot = raw_parser.table_wrap_foot(tag)
         for foot_tag in table_wrap_foot:
-            footnotes = []
             for fn_tag in raw_parser.fn(foot_tag):
                 footnote_content = OrderedDict()
                 # Only set id if a label is present
                 if label(fn_tag, fn_tag.name):
-                    set_if_value(footnote_content, "id", fn_tag.get("id"))
-                    set_if_value(
+                    utils.set_if_value(footnote_content, "id", fn_tag.get("id"))
+                    utils.set_if_value(
                         footnote_content,
                         "label",
-                        rstrip_punctuation(label(fn_tag, fn_tag.name)),
+                        utils.rstrip_punctuation(label(fn_tag, fn_tag.name)),
                     )
                 for p_tag in raw_parser.paragraph(fn_tag):
                     if "text" not in footnote_content:
@@ -3117,12 +3268,12 @@ def body_block_content(tag, html_flag=True, base_url=None):
     elif tag.name == "disp-formula":
         tag_content["type"] = "mathml"
 
-        set_if_value(tag_content, "id", tag.get("id"))
-        set_if_value(tag_content, "label", label(tag, tag.name))
+        utils.set_if_value(tag_content, "id", tag.get("id"))
+        utils.set_if_value(tag_content, "label", label(tag, tag.name))
 
-        math_tag = first(raw_parser.math(tag))
+        math_tag = utils.first(raw_parser.math(tag))
         # Add the math tag back for now
-        math_content = "<math>" + node_contents_str(math_tag) + "</math>"
+        math_content = "<math>" + utils.node_contents_str(math_tag) + "</math>"
         tag_content["mathml"] = convert(math_content)
 
     elif tag.name == "fig":
@@ -3132,10 +3283,10 @@ def body_block_content(tag, html_flag=True, base_url=None):
         asset_tag_content = OrderedDict()
 
         asset_tag_content["type"] = "image"
-        set_if_value(
-            asset_tag_content, "doi", doi_uri_to_doi(object_id_doi(tag, tag.name))
+        utils.set_if_value(
+            asset_tag_content, "doi", utils.doi_uri_to_doi(object_id_doi(tag, tag.name))
         )
-        set_if_value(asset_tag_content, "id", tag.get("id"))
+        utils.set_if_value(asset_tag_content, "id", tag.get("id"))
 
         title_value = convert(title_text(tag, u"caption", u"fig"))
         label_value = label(tag, tag.name)
@@ -3157,7 +3308,7 @@ def body_block_content(tag, html_flag=True, base_url=None):
 
         if raw_parser.graphic(tag):
             graphic_tags = raw_parser.graphic(tag)
-            image_content = body_block_image_content(first(graphic_tags))
+            image_content = body_block_image_content(utils.first(graphic_tags))
             if len(image_content) > 0:
                 asset_tag_content["image"] = image_content
 
@@ -3193,10 +3344,10 @@ def body_block_content(tag, html_flag=True, base_url=None):
         asset_tag_content = OrderedDict()
 
         asset_tag_content["type"] = "video"
-        set_if_value(
-            asset_tag_content, "doi", doi_uri_to_doi(object_id_doi(tag, tag.name))
+        utils.set_if_value(
+            asset_tag_content, "doi", utils.doi_uri_to_doi(object_id_doi(tag, tag.name))
         )
-        set_if_value(asset_tag_content, "id", tag.get("id"))
+        utils.set_if_value(asset_tag_content, "id", tag.get("id"))
 
         title_value = convert(title_text(tag, "caption", tag.name))
         label_value = label(tag, tag.name)
@@ -3223,7 +3374,7 @@ def body_block_content(tag, html_flag=True, base_url=None):
             for attrib_string in attributions:
                 asset_tag_content["attribution"].append(convert(attrib_string))
 
-        set_if_value(asset_tag_content, "uri", tag.get("xlink:href"))
+        utils.set_if_value(asset_tag_content, "uri", tag.get("xlink:href"))
         if "uri" in asset_tag_content and asset_tag_content["uri"].endswith(".gif"):
             asset_tag_content["autoplay"] = True
             asset_tag_content["loop"] = True
@@ -3240,7 +3391,7 @@ def body_block_content(tag, html_flag=True, base_url=None):
         tag_content["assets"].append(asset_tag_content)
 
     elif tag.name == "fig-group":
-        for fig_tag in extract_nodes(tag, ["fig", "media"]):
+        for fig_tag in utils.extract_nodes(tag, ["fig", "media"]):
             # Skip any media tags that are not videos
             if fig_tag.name == "media" and fig_tag.get("mimetype") != "video":
                 continue
@@ -3254,8 +3405,10 @@ def body_block_content(tag, html_flag=True, base_url=None):
                     tag_content["assets"].append(asset)
 
     elif tag.name == "supplementary-material":
-        set_if_value(tag_content, "doi", doi_uri_to_doi(object_id_doi(tag, tag.name)))
-        set_if_value(tag_content, "id", tag.get("id"))
+        utils.set_if_value(
+            tag_content, "doi", utils.doi_uri_to_doi(object_id_doi(tag, tag.name))
+        )
+        utils.set_if_value(tag_content, "id", tag.get("id"))
 
         title_value = convert(title_text(tag, "caption", tag.name))
         label_value = label(tag, tag.name)
@@ -3277,7 +3430,7 @@ def body_block_content(tag, html_flag=True, base_url=None):
         )
 
         if raw_parser.media(tag):
-            media_tag = first(raw_parser.media(tag))
+            media_tag = utils.first(raw_parser.media(tag))
             # If a mimetype contains a slash just use it, otherwise concatenate a value
             if media_tag.get("mimetype") and "/" in media_tag.get("mimetype"):
                 tag_content["mediaType"] = media_tag.get("mimetype")
@@ -3286,8 +3439,8 @@ def body_block_content(tag, html_flag=True, base_url=None):
                     media_tag.get("mimetype") + "/" + media_tag.get("mime-subtype")
                 )
 
-            copy_attribute(media_tag.attrs, "xlink:href", tag_content, "uri")
-            copy_attribute(media_tag.attrs, "xlink:href", tag_content, "filename")
+            utils.copy_attribute(media_tag.attrs, "xlink:href", tag_content, "uri")
+            utils.copy_attribute(media_tag.attrs, "xlink:href", tag_content, "filename")
 
     elif tag.name == "list":
         tag_content["type"] = "list"
@@ -3310,18 +3463,22 @@ def body_block_content(tag, html_flag=True, base_url=None):
                         list_item_content = list_item["content"]
                         tag_content["items"].append(list_item_content)
                     else:
-                        tag_content["items"].append(node_contents_str(list_item_tag))
+                        tag_content["items"].append(
+                            utils.node_contents_str(list_item_tag)
+                        )
 
     elif tag.name == "app":
-        set_if_value(tag_content, "id", tag.get("id"))
-        set_if_value(tag_content, "doi", doi_uri_to_doi(object_id_doi(tag, tag.name)))
-        set_if_value(
+        utils.set_if_value(tag_content, "id", tag.get("id"))
+        utils.set_if_value(
+            tag_content, "doi", utils.doi_uri_to_doi(object_id_doi(tag, tag.name))
+        )
+        utils.set_if_value(
             tag_content, "title", convert(title_text(tag, direct_sibling_only=True))
         )
 
     elif tag.name == "code":
         tag_content["type"] = "code"
-        set_if_value(tag_content, "code", node_contents_str(tag))
+        utils.set_if_value(tag_content, "code", utils.node_contents_str(tag))
 
     return tag_content
 
@@ -3339,7 +3496,7 @@ def body_blocks(soup):
     if not soup:
         return body_block_tags
 
-    first_sibling_node = firstnn(soup.find_all())
+    first_sibling_node = utils.firstnn(soup.find_all())
 
     if first_sibling_node is None:
         return body_block_tags
@@ -3357,7 +3514,7 @@ def body_blocks(soup):
 
 def sub_article_doi(tag):
     doi = None
-    article_id_tag = first(raw_parser.article_id(tag, "doi"))
+    article_id_tag = utils.first(raw_parser.article_id(tag, "doi"))
     if article_id_tag:
         doi = article_id_tag.text
     return doi
@@ -3369,9 +3526,11 @@ def decision_letter(soup):
     sub_article = raw_parser.decision_letter(soup)
 
     if sub_article:
-        copy_attribute(sub_article.attrs, "id", sub_article_content)
+        utils.copy_attribute(sub_article.attrs, "id", sub_article_content)
         if sub_article_doi(sub_article):
-            sub_article_content["doi"] = doi_uri_to_doi(sub_article_doi(sub_article))
+            sub_article_content["doi"] = utils.doi_uri_to_doi(
+                sub_article_doi(sub_article)
+            )
         raw_body = raw_parser.article_body(sub_article)
     else:
         raw_body = None
@@ -3381,7 +3540,7 @@ def decision_letter(soup):
         # Description will be the first boxed-text tag
         if raw_parser.boxed_text(raw_body):
             sub_article_content["description"] = []
-            boxed_text_description = first(raw_parser.boxed_text(raw_body))
+            boxed_text_description = utils.first(raw_parser.boxed_text(raw_body))
             tags = body_blocks(boxed_text_description)
             for tag in tags:
                 block_content = body_block_content_render(tag)
@@ -3398,8 +3557,8 @@ def decision_letter(soup):
         body_content_rewritten = elifetools.json_rewrite.rewrite_json(
             "body_json", soup, body_content
         )
-        if len(body_content) > 0:
-            sub_article_content["content"] = body_content
+        if len(body_content_rewritten) > 0:
+            sub_article_content["content"] = body_content_rewritten
 
     return elifetools.json_rewrite.rewrite_json(
         "decision_letter_json", soup, sub_article_content
@@ -3412,9 +3571,11 @@ def author_response(soup):
     sub_article = raw_parser.author_response(soup)
 
     if sub_article:
-        copy_attribute(sub_article.attrs, "id", sub_article_content)
+        utils.copy_attribute(sub_article.attrs, "id", sub_article_content)
         if sub_article_doi(sub_article):
-            sub_article_content["doi"] = doi_uri_to_doi(sub_article_doi(sub_article))
+            sub_article_content["doi"] = utils.doi_uri_to_doi(
+                sub_article_doi(sub_article)
+            )
         raw_body = raw_parser.article_body(sub_article)
     else:
         raw_body = None
@@ -3425,16 +3586,18 @@ def author_response(soup):
         body_content_rewritten = elifetools.json_rewrite.rewrite_json(
             "body_json", soup, body_content
         )
-        if len(body_content) > 0:
-            sub_article_content["content"] = body_content
+        if len(body_content_rewritten) > 0:
+            sub_article_content["content"] = body_content_rewritten
 
     return sub_article_content
 
 
 def render_abstract_json(abstract_tag):
     abstract_json = OrderedDict()
-    set_if_value(abstract_json, "doi", doi_uri_to_doi(object_id_doi(abstract_tag)))
-    for child_tag in remove_doi_paragraph(body_blocks(abstract_tag)):
+    utils.set_if_value(
+        abstract_json, "doi", utils.doi_uri_to_doi(object_id_doi(abstract_tag))
+    )
+    for child_tag in utils.remove_doi_paragraph(body_blocks(abstract_tag)):
         if body_block_content(child_tag) != {}:
             if "content" not in abstract_json:
                 abstract_json["content"] = []
@@ -3466,7 +3629,8 @@ def author_affiliations(author, html_flag=True):
     """compile author affiliations for json output"""
 
     # Configure the XML to HTML conversion preference for shorthand use below
-    convert = lambda xml_string: xml_to_html(html_flag, xml_string)
+    def convert(xml_string):
+        return xml_to_html(html_flag, xml_string)
 
     affilations = []
 
@@ -3517,8 +3681,8 @@ def author_affiliations(author, html_flag=True):
 
     if affilations != []:
         return affilations
-    else:
-        return None
+
+    return None
 
 
 def author_phone_numbers(author, correspondence):
@@ -3531,8 +3695,8 @@ def author_phone_numbers(author, correspondence):
                         phone_numbers.append(phone_number)
     if phone_numbers != []:
         return phone_numbers
-    else:
-        return None
+
+    return None
 
 
 def phone_number_json(phone):
@@ -3570,8 +3734,8 @@ def author_email_addresses(author, correspondence):
 
     if email_addresses != []:
         return email_addresses
-    else:
-        return None
+
+    return None
 
 
 def author_contribution(author, contributions):
@@ -3594,6 +3758,8 @@ def author_contribution(author, contributions):
 def author_competing_interests(author, competing_interests):
     competing_interests_text = None
 
+    label_pattern = re.compile(r"<label>.*</label>")
+
     if "competing-interest" in author.get("references", {}):
         for ref_id in author["references"]["competing-interest"]:
             if competing_interests:
@@ -3608,8 +3774,9 @@ def author_competing_interests(author, competing_interests):
                             .replace("</p>", "")
                         )
                         # Strip labels
-                        p = re.compile("<label>.*</label>")
-                        competing_interests_text = p.sub("", competing_interests_text)
+                        competing_interests_text = label_pattern.sub(
+                            "", competing_interests_text
+                        )
 
     return competing_interests_text
 
@@ -3623,8 +3790,8 @@ def author_equal_contribution(author, equal_contributions_map):
                 equal_contributions.append(equal_contributions_map[ref_id])
     if equal_contributions != []:
         return equal_contributions
-    else:
-        return None
+
+    return None
 
 
 def author_present_address(author, present_address_data):
@@ -3648,8 +3815,8 @@ def author_present_address(author, present_address_data):
                     postal_addresses.append(address)
     if postal_addresses != []:
         return postal_addresses
-    else:
-        return None
+
+    return None
 
 
 def author_foot_notes(author, foot_notes_data):
@@ -3668,8 +3835,8 @@ def author_foot_notes(author, foot_notes_data):
                         foot_notes.append(text)
     if foot_notes != []:
         return foot_notes
-    else:
-        return None
+
+    return None
 
 
 def author_json_details(
@@ -3684,7 +3851,8 @@ def author_json_details(
     html_flag=True,
 ):
     # Configure the XML to HTML conversion preference for shorthand use below
-    convert = lambda xml_string: xml_to_html(html_flag, xml_string)
+    def convert(xml_string):
+        return xml_to_html(html_flag, xml_string)
 
     """add more author json"""
     if author_affiliations(author):
@@ -3743,15 +3911,15 @@ def author_person(
     author_json = OrderedDict()
     author_json["type"] = "person"
     author_name = OrderedDict()
-    author_name["preferred"] = author_preferred_name(
+    author_name["preferred"] = utils.author_preferred_name(
         author.get("surname"), author.get("given-names"), author.get("suffix")
     )
-    author_name["index"] = author_index_name(
+    author_name["index"] = utils.author_index_name(
         author.get("surname"), author.get("given-names"), author.get("suffix")
     )
     author_json["name"] = author_name
     if author.get("orcid"):
-        author_json["orcid"] = orcid_uri_to_orcid(author.get("orcid"))
+        author_json["orcid"] = utils.orcid_uri_to_orcid(author.get("orcid"))
     if author.get("deceased"):
         author_json["deceased"] = True
     if author.get("role"):
@@ -3856,7 +4024,6 @@ def authors_json(soup):
     author_contributions_data = author_contributions(soup, None)
     author_competing_interests_data = competing_interests(soup, None)
     author_correspondence_data = full_correspondence(soup)
-    authors_non_byline_data = authors_non_byline(soup)
     equal_contributions_map = map_equal_contributions(contributors_data)
     present_address_data = present_addresses(soup)
     foot_notes_data = other_foot_notes(soup)
@@ -3985,20 +4152,20 @@ def references_publisher(publisher_name=None, publisher_loc=None):
         publisher["address"] = address
     if len(publisher) > 0:
         return publisher
-    else:
-        return None
+
+    return None
 
 
 def references_pages_range(fpage=None, lpage=None):
-    range = None
+    page_range = None
     if fpage and lpage:
         # use chr(8211) for the hyphen because the schema is requiring it
-        range = fpage.strip() + chr(8211) + lpage.strip()
+        page_range = fpage.strip() + chr(8211) + lpage.strip()
     elif fpage:
-        range = fpage.strip()
+        page_range = fpage.strip()
     elif lpage:
-        range = lpage.strip()
-    return range
+        page_range = lpage.strip()
+    return page_range
 
 
 def references_date(year=None):
@@ -4035,7 +4202,7 @@ def references_authors(ref_authors):
                 author_json = references_author_collab(ref_author)
             else:
                 author_group_type = ref_author.get("group-type") + "s"
-                author_json = references_author_person(ref_author)
+                author_json = utils.references_author_person(ref_author)
 
             if author_group_type:
                 if author_group_type not in all_authors:
@@ -4063,16 +4230,22 @@ def references_json_authors(ref_authors, ref_content):
             "web",
         ]:
             for author_type in ["authors", "authorsEtAl"]:
-                set_if_value(ref_content, author_type, all_authors.get(author_type))
+                utils.set_if_value(
+                    ref_content, author_type, all_authors.get(author_type)
+                )
         elif ref_content.get("type") in ["book", "book-chapter"]:
             for author_type in ["authors", "authorsEtAl", "editors", "editorsEtAl"]:
-                set_if_value(ref_content, author_type, all_authors.get(author_type))
+                utils.set_if_value(
+                    ref_content, author_type, all_authors.get(author_type)
+                )
         elif ref_content.get("type") in ["clinical-trial"]:
             # Always set as authors, once,  then add the authorsType
             for author_type in ["authors", "collaborators", "sponsors"]:
                 if "authorsType" not in ref_content and all_authors.get(author_type):
-                    set_if_value(ref_content, "authors", all_authors.get(author_type))
-                    set_if_value(
+                    utils.set_if_value(
+                        ref_content, "authors", all_authors.get(author_type)
+                    )
+                    utils.set_if_value(
                         ref_content,
                         "authorsEtAl",
                         all_authors.get(author_type + "EtAl"),
@@ -4087,7 +4260,9 @@ def references_json_authors(ref_authors, ref_content):
                 "curators",
                 "curatorsEtAl",
             ]:
-                set_if_value(ref_content, author_type, all_authors.get(author_type))
+                utils.set_if_value(
+                    ref_content, author_type, all_authors.get(author_type)
+                )
         elif ref_content.get("type") in ["patent"]:
             for author_type in [
                 "inventors",
@@ -4095,7 +4270,9 @@ def references_json_authors(ref_authors, ref_content):
                 "assignees",
                 "assigneesEtAl",
             ]:
-                set_if_value(ref_content, author_type, all_authors.get(author_type))
+                utils.set_if_value(
+                    ref_content, author_type, all_authors.get(author_type)
+                )
         elif ref_content.get("type") in ["thesis"]:
             # Convert list to a non-list
             if all_authors.get("authors") and len(all_authors.get("authors")) > 0:
@@ -4106,7 +4283,8 @@ def references_json_authors(ref_authors, ref_content):
 def references_json(soup, html_flag=True):
 
     # Configure the XML to HTML conversion preference for shorthand use below
-    convert = lambda xml_string: xml_to_html(html_flag, xml_string)
+    def convert(xml_string):
+        return xml_to_html(html_flag, xml_string)
 
     references_json = []
     for ref in refs(soup):
@@ -4116,33 +4294,33 @@ def references_json(soup, html_flag=True):
         if ref.get("publication-type") == "book" and (
             "chapter-title" in ref or "full_article_title" in ref
         ):
-            set_if_value(ref_content, "type", "book-chapter")
+            utils.set_if_value(ref_content, "type", "book-chapter")
         elif ref.get("publication-type") == "confproc":
-            set_if_value(ref_content, "type", "conference-proceeding")
+            utils.set_if_value(ref_content, "type", "conference-proceeding")
         elif ref.get("publication-type") == "clinicaltrial":
-            set_if_value(ref_content, "type", "clinical-trial")
+            utils.set_if_value(ref_content, "type", "clinical-trial")
         elif ref.get("publication-type") == "webpage":
-            set_if_value(ref_content, "type", "web")
+            utils.set_if_value(ref_content, "type", "web")
         else:
-            set_if_value(ref_content, "type", ref.get("publication-type"))
+            utils.set_if_value(ref_content, "type", ref.get("publication-type"))
 
-        set_if_value(ref_content, "id", ref.get("id"))
+        utils.set_if_value(ref_content, "id", ref.get("id"))
 
         (year_date, discriminator, year_in_press) = references_date(ref.get("year"))
         if not discriminator:
-            set_if_value(ref_content, "date", ref.get("year-iso-8601-date"))
+            utils.set_if_value(ref_content, "date", ref.get("year-iso-8601-date"))
         if "date" not in ref_content:
-            set_if_value(ref_content, "date", year_date)
-            set_if_value(ref_content, "discriminator", discriminator)
+            utils.set_if_value(ref_content, "date", year_date)
+            utils.set_if_value(ref_content, "discriminator", discriminator)
 
         # accessed
         if ref.get("publication-type") in ["web", "webpage"] and ref.get(
             "iso-8601-date"
         ):
-            set_if_value(ref_content, "accessed", ref.get("iso-8601-date"))
+            utils.set_if_value(ref_content, "accessed", ref.get("iso-8601-date"))
             # Set the date to the year tag value if accessed is set and there is a year
-            set_if_value(ref_content, "date", year_date)
-            set_if_value(ref_content, "discriminator", discriminator)
+            utils.set_if_value(ref_content, "date", year_date)
+            utils.set_if_value(ref_content, "discriminator", discriminator)
 
         # authors and etal
         if ref.get("authors"):
@@ -4155,34 +4333,38 @@ def references_json(soup, html_flag=True):
             "preprint",
             "periodical",
         ]:
-            set_if_value(ref_content, "articleTitle", ref.get("full_article_title"))
+            utils.set_if_value(
+                ref_content, "articleTitle", ref.get("full_article_title")
+            )
         elif ref.get("publication-type") in ["thesis", "clinicaltrial", "other"]:
-            set_if_value(ref_content, "title", ref.get("full_article_title"))
+            utils.set_if_value(ref_content, "title", ref.get("full_article_title"))
         elif ref.get("publication-type") in ["book"]:
-            set_if_value(ref_content, "bookTitle", ref.get("source"))
+            utils.set_if_value(ref_content, "bookTitle", ref.get("source"))
             if "bookTitle" not in ref_content:
-                set_if_value(ref_content, "bookTitle", ref.get("full_article_title"))
+                utils.set_if_value(
+                    ref_content, "bookTitle", ref.get("full_article_title")
+                )
         elif ref.get("publication-type") in ["software", "data"]:
-            set_if_value(ref_content, "title", ref.get("data-title"))
+            utils.set_if_value(ref_content, "title", ref.get("data-title"))
             if "title" not in ref_content:
-                set_if_value(ref_content, "title", ref.get("source"))
+                utils.set_if_value(ref_content, "title", ref.get("source"))
         elif ref.get("publication-type") in ["patent", "web", "webpage"]:
-            set_if_value(ref_content, "title", ref.get("full_article_title"))
+            utils.set_if_value(ref_content, "title", ref.get("full_article_title"))
             if "title" not in ref_content:
-                set_if_value(ref_content, "title", ref.get("comment"))
+                utils.set_if_value(ref_content, "title", ref.get("comment"))
             if "title" not in ref_content:
-                set_if_value(ref_content, "title", ref.get("uri"))
+                utils.set_if_value(ref_content, "title", ref.get("uri"))
         # Finally try to extract from source if a title is not found
         if (
             "title" not in ref_content
             and "articleTitle" not in ref_content
             and "bookTitle" not in ref_content
         ):
-            set_if_value(ref_content, "title", ref.get("source"))
+            utils.set_if_value(ref_content, "title", ref.get("source"))
 
         # conference
         if ref.get("conf-name"):
-            set_if_value(
+            utils.set_if_value(
                 ref_content,
                 "conference",
                 references_publisher(ref.get("conf-name"), None),
@@ -4190,23 +4372,23 @@ def references_json(soup, html_flag=True):
 
         # source
         if ref.get("publication-type") == "journal":
-            set_if_value(ref_content, "journal", ref.get("source"))
+            utils.set_if_value(ref_content, "journal", ref.get("source"))
         elif ref.get("publication-type") == "periodical":
-            set_if_value(ref_content, "periodical", ref.get("source"))
+            utils.set_if_value(ref_content, "periodical", ref.get("source"))
         elif ref.get("publication-type") in ["web", "webpage"]:
-            set_if_value(ref_content, "website", ref.get("source"))
+            utils.set_if_value(ref_content, "website", ref.get("source"))
         elif ref.get("publication-type") in ["patent"]:
-            set_if_value(ref_content, "patentType", ref.get("source"))
+            utils.set_if_value(ref_content, "patentType", ref.get("source"))
         elif ref.get("publication-type") not in ["book"]:
-            set_if_value(ref_content, "source", ref.get("source"))
+            utils.set_if_value(ref_content, "source", ref.get("source"))
 
         # patent details
-        set_if_value(ref_content, "number", ref.get("patent"))
-        set_if_value(ref_content, "country", ref.get("country"))
+        utils.set_if_value(ref_content, "number", ref.get("patent"))
+        utils.set_if_value(ref_content, "country", ref.get("country"))
 
         # publisher
         if ref.get("publisher_name"):
-            set_if_value(
+            utils.set_if_value(
                 ref_content,
                 "publisher",
                 references_publisher(
@@ -4214,27 +4396,29 @@ def references_json(soup, html_flag=True):
                 ),
             )
         elif ref.get("publication-type") in ["software"] and ref.get("source"):
-            set_if_value(
+            utils.set_if_value(
                 ref_content,
                 "publisher",
                 references_publisher(ref.get("source"), ref.get("publisher_loc")),
             )
 
         # volume
-        set_if_value(ref_content, "volume", ref.get("volume"))
+        utils.set_if_value(ref_content, "volume", ref.get("volume"))
 
         # edition
         if ref.get("publication-type") in ["software"]:
-            set_if_value(ref_content, "version", ref.get("version"))
+            utils.set_if_value(ref_content, "version", ref.get("version"))
             if "version" not in ref_content:
-                set_if_value(ref_content, "version", ref.get("edition"))
+                utils.set_if_value(ref_content, "version", ref.get("edition"))
         else:
-            set_if_value(ref_content, "edition", ref.get("edition"))
+            utils.set_if_value(ref_content, "edition", ref.get("edition"))
 
         # chapter-title
-        set_if_value(ref_content, "chapterTitle", ref.get("chapter-title"))
+        utils.set_if_value(ref_content, "chapterTitle", ref.get("chapter-title"))
         if ref_content["type"] == "book-chapter" and "chapterTitle" not in ref_content:
-            set_if_value(ref_content, "chapterTitle", ref.get("full_article_title"))
+            utils.set_if_value(
+                ref_content, "chapterTitle", ref.get("full_article_title")
+            )
 
         # pages
         if ref.get("elocation-id"):
@@ -4279,23 +4463,25 @@ def references_json(soup, html_flag=True):
 
         # dataId
         if ref.get("publication-type") in ["data"]:
-            set_if_value(ref_content, "dataId", ref.get("accession"))
+            utils.set_if_value(ref_content, "dataId", ref.get("accession"))
 
         # doi
         if ref.get("publication-type") not in ["web", "webpage"]:
-            set_if_value(ref_content, "doi", ref.get("doi"))
+            utils.set_if_value(ref_content, "doi", ref.get("doi"))
 
         # pmid
-        set_if_value(ref_content, "pmid", coerce_to_int(ref.get("pmid"), None))
+        utils.set_if_value(
+            ref_content, "pmid", utils.coerce_to_int(ref.get("pmid"), None)
+        )
 
         # isbn
-        set_if_value(ref_content, "isbn", ref.get("isbn"))
+        utils.set_if_value(ref_content, "isbn", ref.get("isbn"))
 
         # uri
-        set_if_value(ref_content, "uri", ref.get("uri"))
+        utils.set_if_value(ref_content, "uri", ref.get("uri"))
         # take the uri_text value if no uri yet
         if "uri" not in ref_content:
-            set_if_value(ref_content, "uri", ref.get("uri_text"))
+            utils.set_if_value(ref_content, "uri", ref.get("uri_text"))
         # next option is to set the uri from the doi value
         if "uri" not in ref_content and ref.get("publication-type") in [
             "confproc",
@@ -4311,7 +4497,7 @@ def references_json(soup, html_flag=True):
 
         # Convert to HTML
         for index in ["title", "articleTitle", "chapterTitle", "bookTitle", "edition"]:
-            set_if_value(ref_content, index, convert(ref_content.get(index)))
+            utils.set_if_value(ref_content, index, convert(ref_content.get(index)))
 
         # Rewrite references data with support to delete a reference too
         ref_content_rewritten = elifetools.json_rewrite.rewrite_json(
@@ -4338,10 +4524,7 @@ def convert_references_json(ref_content, soup=None):
         (ref_content.get("type") == "other")
         or (ref_content.get("type") == "book-chapter" and "editors" not in ref_content)
         or (ref_content.get("type") == "journal" and "articleTitle" not in ref_content)
-        or (
-            ref_content.get("type") in ["book-chapter"]
-            and not "pages" in ref_content
-        )
+        or (ref_content.get("type") == "book-chapter" and "pages" not in ref_content)
         or (ref_content.get("type") == "journal" and "journal" not in ref_content)
         or (
             ref_content.get("type")
@@ -4363,31 +4546,35 @@ def convert_references_json(ref_content, soup=None):
 def references_json_to_unknown(ref_content, soup=None):
     unknown_ref_content = OrderedDict()
     unknown_ref_content["type"] = "unknown"
-    set_if_value(unknown_ref_content, "id", ref_content.get("id"))
-    set_if_value(unknown_ref_content, "date", ref_content.get("date"))
-    set_if_value(unknown_ref_content, "authors", ref_content.get("authors"))
+    utils.set_if_value(unknown_ref_content, "id", ref_content.get("id"))
+    utils.set_if_value(unknown_ref_content, "date", ref_content.get("date"))
+    utils.set_if_value(unknown_ref_content, "authors", ref_content.get("authors"))
     if not unknown_ref_content.get("authors") and ref_content.get("author"):
         unknown_ref_content["authors"] = []
         unknown_ref_content["authors"].append(ref_content.get("author"))
-    set_if_value(unknown_ref_content, "authorsEtAl", ref_content.get("authorsEtAl"))
+    utils.set_if_value(
+        unknown_ref_content, "authorsEtAl", ref_content.get("authorsEtAl")
+    )
 
     # compile details first for use later in title as a default
     details = references_json_unknown_details(ref_content, soup)
 
     # title
-    set_if_value(unknown_ref_content, "title", ref_content.get("title"))
+    utils.set_if_value(unknown_ref_content, "title", ref_content.get("title"))
     if "title" not in unknown_ref_content:
-        set_if_value(unknown_ref_content, "title", ref_content.get("bookTitle"))
+        utils.set_if_value(unknown_ref_content, "title", ref_content.get("bookTitle"))
     if "title" not in unknown_ref_content:
-        set_if_value(unknown_ref_content, "title", ref_content.get("articleTitle"))
+        utils.set_if_value(
+            unknown_ref_content, "title", ref_content.get("articleTitle")
+        )
     if "title" not in unknown_ref_content:
         # Still not title, try to use the details as the title
-        set_if_value(unknown_ref_content, "title", details)
+        utils.set_if_value(unknown_ref_content, "title", details)
 
     # add details
-    set_if_value(unknown_ref_content, "details", details)
+    utils.set_if_value(unknown_ref_content, "details", details)
 
-    set_if_value(unknown_ref_content, "uri", ref_content.get("uri"))
+    utils.set_if_value(unknown_ref_content, "uri", ref_content.get("uri"))
 
     return unknown_ref_content
 
@@ -4406,7 +4593,7 @@ def references_json_unknown_details(ref_content, soup=None):
     if soup:
         # Attempt to find the XML element by id, and convert it to details
         if "id" in ref_content:
-            ref_tag = first(soup.select("ref#" + ref_content["id"]))
+            ref_tag = utils.first(soup.select("ref#" + ref_content["id"]))
             if ref_tag:
                 # Now remove tags that would be already part of the unknown reference by now
                 for remove_tag in [
@@ -4417,22 +4604,22 @@ def references_json_unknown_details(ref_content, soup=None):
                     "fpage",
                     "lpage",
                 ]:
-                    ref_tag = remove_tag_from_tag(ref_tag, remove_tag)
+                    ref_tag = utils.remove_tag_from_tag(ref_tag, remove_tag)
                 # Add the remaining tag content comma separated
-                for tag in first(raw_parser.element_citation(ref_tag)):
-                    if node_text(tag) is not None:
+                for tag in utils.first(raw_parser.element_citation(ref_tag)):
+                    if utils.node_text(tag) is not None:
                         if details != "":
                             details += ", "
-                        details += node_text(tag)
+                        details += utils.node_text(tag)
     if details == "":
         return None
-    else:
-        return details
+
+    return details
 
 
 def ethics_json(soup):
     ethics_json = []
-    ethics_fn_group = first(raw_parser.fn_group(soup, "ethics-information"))
+    ethics_fn_group = utils.first(raw_parser.fn_group(soup, "ethics-information"))
 
     # Part one, find the fn tags in the ethics section
     fn_body_blocks = []
@@ -4472,7 +4659,7 @@ def appendices_json(soup, base_url=None):
     app_tags = []
     back = raw_parser.back(soup)
     if back:
-        app_group = first(raw_parser.app_group(back))
+        app_group = utils.first(raw_parser.app_group(back))
     if app_group:
         app_tags = raw_parser.app(app_group)
     for app_tag in app_tags:
@@ -4497,7 +4684,7 @@ def appendices_json(soup, base_url=None):
         # Then check all first level sections with no title, and fix them by
         #  building the content list again, unwrapping each non-title section
         clean_app_content = []
-        for i, content_block in enumerate(app_content["content"]):
+        for content_block in app_content["content"]:
             if (
                 content_block.get("type")
                 and content_block.get("type") == "section"
@@ -4518,37 +4705,42 @@ def appendices_json(soup, base_url=None):
 
 def dataset_tag_json(tag, html_flag=True):
     # Configure the XML to HTML conversion preference for shorthand use below
-    convert = lambda xml_string: xml_to_html(html_flag, xml_string)
+    def convert(xml_string):
+        return xml_to_html(html_flag, xml_string)
 
     dataset_content = OrderedDict()
 
-    # set_if_value(tag_content, "doi", object_id_doi(tag, tag.name))
-    set_if_value(dataset_content, "id", tag.get("id"))
-    set_if_value(dataset_content, "date", node_text(raw_parser.year(tag)))
+    # utils.set_if_value(tag_content, "doi", object_id_doi(tag, tag.name))
+    utils.set_if_value(dataset_content, "id", tag.get("id"))
+    utils.set_if_value(dataset_content, "date", utils.node_text(raw_parser.year(tag)))
 
     # authors
     dataset_authors = []
-    for contrib_tag in extract_nodes(tag, ["name", "collab"]):
+    for contrib_tag in utils.extract_nodes(tag, ["name", "collab"]):
         dataset_author = OrderedDict()
         if contrib_tag.name == "collab":
             dataset_author["type"] = "group"
-            set_if_value(dataset_author, "name", node_contents_str(contrib_tag))
+            utils.set_if_value(
+                dataset_author, "name", utils.node_contents_str(contrib_tag)
+            )
         elif contrib_tag.name == "name":
             person_details = {}
-            set_if_value(
+            utils.set_if_value(
                 person_details,
                 "surname",
-                first_node_str_contents(contrib_tag, "surname"),
+                utils.first_node_str_contents(contrib_tag, "surname"),
             )
-            set_if_value(
+            utils.set_if_value(
                 person_details,
                 "given-names",
-                first_node_str_contents(contrib_tag, "given-names"),
+                utils.first_node_str_contents(contrib_tag, "given-names"),
             )
-            set_if_value(
-                person_details, "suffix", first_node_str_contents(contrib_tag, "suffix")
+            utils.set_if_value(
+                person_details,
+                "suffix",
+                utils.first_node_str_contents(contrib_tag, "suffix"),
             )
-            dataset_author = references_author_person(person_details)
+            dataset_author = utils.references_author_person(person_details)
         if len(dataset_author) > 0:
             dataset_authors.append(dataset_author)
     if len(dataset_authors) > 0:
@@ -4559,67 +4751,75 @@ def dataset_tag_json(tag, html_flag=True):
         dataset_content["authorsEtAl"] = True
 
     # title
-    set_if_value(
+    utils.set_if_value(
         dataset_content,
         "title",
-        convert(node_contents_str(first(raw_parser.source(tag)))),
+        convert(utils.node_contents_str(utils.first(raw_parser.source(tag)))),
     )
 
     # dataId
-    set_if_value(
+    utils.set_if_value(
         dataset_content,
         "dataId",
-        convert(node_contents_str(first(raw_parser.object_id(tag, "art-access-id")))),
+        convert(
+            utils.node_contents_str(
+                utils.first(raw_parser.object_id(tag, "art-access-id"))
+            )
+        ),
     )
 
     # details
     # prefer the data-title value first, then if not present use the comment tag value
     if raw_parser.data_title(tag):
-        set_if_value(
+        utils.set_if_value(
             dataset_content,
             "details",
-            convert(node_contents_str(first(raw_parser.data_title(tag)))),
+            convert(utils.node_contents_str(utils.first(raw_parser.data_title(tag)))),
         )
     elif raw_parser.comment(tag):
-        set_if_value(
+        utils.set_if_value(
             dataset_content,
             "details",
-            convert(node_contents_str(first(raw_parser.comment(tag)))),
+            convert(utils.node_contents_str(utils.first(raw_parser.comment(tag)))),
         )
 
     # doi
     if raw_parser.pub_id(tag, "doi"):
-        doi_tag = first(raw_parser.pub_id(tag, "doi"))
-        set_if_value(dataset_content, "doi", doi_uri_to_doi(doi_tag.get("xlink:href")))
+        doi_tag = utils.first(raw_parser.pub_id(tag, "doi"))
+        utils.set_if_value(
+            dataset_content, "doi", utils.doi_uri_to_doi(doi_tag.get("xlink:href"))
+        )
         if "doi" not in dataset_content:
             # use the tag value if xlink:href attribute is not present
-            set_if_value(
-                dataset_content, "doi", doi_uri_to_doi(node_contents_str(doi_tag))
+            utils.set_if_value(
+                dataset_content,
+                "doi",
+                utils.doi_uri_to_doi(utils.node_contents_str(doi_tag)),
             )
-        set_if_value(
+        utils.set_if_value(
             dataset_content, "assigningAuthority", doi_tag.get("assigning-authority")
         )
 
     # uri
     if raw_parser.ext_link(tag, "uri"):
-        uri_tag = first(raw_parser.ext_link(tag, "uri"))
-        set_if_value(dataset_content, "uri", uri_tag.get("xlink:href"))
+        uri_tag = utils.first(raw_parser.ext_link(tag, "uri"))
+        utils.set_if_value(dataset_content, "uri", uri_tag.get("xlink:href"))
 
     # uri from pub-id tag
     if "uri" not in dataset_content and raw_parser.pub_id(tag):
-        pub_id_tag = first(raw_parser.pub_id(tag))
+        pub_id_tag = utils.first(raw_parser.pub_id(tag))
         # set uri if it is not a doi tag
         if pub_id_tag.get("pub-id-type") != "doi":
-            set_if_value(dataset_content, "uri", pub_id_tag.get("xlink:href"))
+            utils.set_if_value(dataset_content, "uri", pub_id_tag.get("xlink:href"))
         # set dataId if missing and the pub-id is an accession
         if (
             "dataId" not in dataset_content
             and pub_id_tag.get("pub-id-type") == "accession"
         ):
-            set_if_value(
-                dataset_content, "dataId", convert(node_contents_str(pub_id_tag))
+            utils.set_if_value(
+                dataset_content, "dataId", convert(utils.node_contents_str(pub_id_tag))
             )
-        set_if_value(
+        utils.set_if_value(
             dataset_content, "assigningAuthority", pub_id_tag.get("assigning-authority")
         )
 
@@ -4628,8 +4828,6 @@ def dataset_tag_json(tag, html_flag=True):
 
 def datasets_json(soup, html_flag=True):
     datasets_json = OrderedDict()
-    generated_datasets = []
-    used_datasets = []
     generated_datasets_tags = []
     used_datasets_tags = []
     availabilty_p_tags = []
@@ -4641,7 +4839,9 @@ def datasets_json(soup, html_flag=True):
         # find the section containing datasets by its sec-type value
         for sec_type in ["datasets", "data-availability"]:
             if raw_parser.section(back_tag, sec_type):
-                datasets_section_tag = first(raw_parser.section(back_tag, sec_type))
+                datasets_section_tag = utils.first(
+                    raw_parser.section(back_tag, sec_type)
+                )
                 break
         if datasets_section_tag:
             p_tags = raw_parser.paragraph(datasets_section_tag)
@@ -4664,11 +4864,13 @@ def datasets_json(soup, html_flag=True):
                     used_datasets_tags += raw_parser.element_citation(p_tag)
             else:
                 # Look for paragraphs ending in a certain term optionally with a colon at the end
-                if node_text(p_tag) and node_text(p_tag).rstrip(":").endswith(
-                    "generated"
-                ):
+                if utils.node_text(p_tag) and utils.node_text(p_tag).rstrip(
+                    ":"
+                ).endswith("generated"):
                     dataset_type = "generated"
-                elif node_text(p_tag) and node_text(p_tag).rstrip(":").endswith("used"):
+                elif utils.node_text(p_tag) and utils.node_text(p_tag).rstrip(
+                    ":"
+                ).endswith("used"):
                     dataset_type = "used"
             # If no dataset type found yet, collect the first paragraphs for the data availability
             if not dataset_type:
@@ -4708,14 +4910,14 @@ def poa_supplementary_material_block_content(tag):
         and raw_parser.ext_link(tag)
         and not raw_parser.media(tag)
     ):
-        ext_link_tag = first(raw_parser.ext_link(tag))
+        ext_link_tag = utils.first(raw_parser.ext_link(tag))
         filename = ext_link_tag.get("xlink:href")
 
         if filename and filename.endswith(".zip"):
             tag_content["mediaType"] = "application/zip"
 
-        set_if_value(tag_content, "uri", filename)
-        set_if_value(tag_content, "filename", filename)
+        utils.set_if_value(tag_content, "uri", filename)
+        utils.set_if_value(tag_content, "filename", filename)
 
     return tag_content
 
@@ -4774,7 +4976,11 @@ def supplementary_files_json(soup):
             file["label"] = "All additional files"
             file["caption"] = [
                 {
-                    "text": "Any figure supplements, source code, source data, videos or supplementary files associated with this article are contained within this zip.",
+                    "text": (
+                        "Any figure supplements, source code, source data, videos or "
+                        "supplementary files associated with this article are contained "
+                        "within this zip."
+                    ),
                     "type": "paragraph",
                 }
             ]
@@ -4793,28 +4999,29 @@ def funding_awards_json(soup):
     award_groups = full_award_groups(soup)
     if award_groups:
         for award_group_dict in award_groups:
-            for id, award_group in award_group_dict.items():
+            for award_id, award_group in award_group_dict.items():
                 award_content = OrderedDict()
-                set_if_value(award_content, "id", id)
+                utils.set_if_value(award_content, "id", award_id)
 
                 if award_group.get("institution") or award_group.get("id"):
                     # Set the source
                     source_content = OrderedDict()
-                    set_if_value(
+                    utils.set_if_value(
                         source_content,
                         "funderId",
-                        doi_uri_to_doi(award_group.get("id")),
+                        utils.doi_uri_to_doi(award_group.get("id")),
                     )
                     if award_group.get("institution"):
-                        source_name_content = OrderedDict()
-                        set_if_value(
+                        utils.set_if_value(
                             source_content, "name", [award_group.get("institution")]
                         )
                     award_content["source"] = source_content
 
                 # awardId
                 if award_group.get("award-id") and award_group.get("award-id") != "":
-                    set_if_value(award_content, "awardId", award_group.get("award-id"))
+                    utils.set_if_value(
+                        award_content, "awardId", award_group.get("award-id")
+                    )
 
                 if len(award_content) > 0:
                     awards.append(award_content)
@@ -4822,64 +5029,73 @@ def funding_awards_json(soup):
     # recipients to parse fresh
     award_group_tags = []
     award_recipients = {}
-    funding_group = first(raw_parser.funding_group(soup))
+    funding_group = utils.first(raw_parser.funding_group(soup))
     if funding_group:
         award_group_tags = raw_parser.award_group(funding_group)
     for a_tag in award_group_tags:
-        id = a_tag.get("id")
+        award_id = a_tag.get("id")
         recipient_tags = raw_parser.principal_award_recipient(a_tag)
         if recipient_tags:
             recipients = []
             for recipient_tag in recipient_tags:
-                if len(extract_nodes(recipient_tag, ["name", "institution"])) <= 0:
+                if (
+                    len(utils.extract_nodes(recipient_tag, ["name", "institution"]))
+                    <= 0
+                ):
                     # A loose institution name not surrounded by institution tag
                     recipient_content = OrderedDict()
                     recipient_content["type"] = "group"
-                    set_if_value(
-                        recipient_content, "name", node_contents_str(recipient_tag)
+                    utils.set_if_value(
+                        recipient_content,
+                        "name",
+                        utils.node_contents_str(recipient_tag),
                     )
                     if len(recipient_content) > 0:
                         # add it
                         recipients.append(recipient_content)
                 else:
-                    for contrib_tag in extract_nodes(
+                    for contrib_tag in utils.extract_nodes(
                         recipient_tag, ["name", "institution"]
                     ):
                         recipient_content = OrderedDict()
                         if contrib_tag.name == "institution":
                             recipient_content["type"] = "group"
-                            set_if_value(
+                            utils.set_if_value(
                                 recipient_content,
                                 "name",
-                                node_contents_str(contrib_tag),
+                                utils.node_contents_str(contrib_tag),
                             )
                         elif contrib_tag.name == "name":
                             person_details = {}
-                            set_if_value(
+                            utils.set_if_value(
                                 person_details,
                                 "surname",
-                                first_node_str_contents(contrib_tag, "surname"),
+                                utils.first_node_str_contents(contrib_tag, "surname"),
                             )
-                            set_if_value(
+                            utils.set_if_value(
                                 person_details,
                                 "given-names",
-                                first_node_str_contents(contrib_tag, "given-names"),
+                                utils.first_node_str_contents(
+                                    contrib_tag, "given-names"
+                                ),
                             )
-                            set_if_value(
+                            utils.set_if_value(
                                 person_details,
                                 "suffix",
-                                first_node_str_contents(contrib_tag, "suffix"),
+                                utils.first_node_str_contents(contrib_tag, "suffix"),
+                            )
+                            recipient_content = utils.article_author_person(
+                                person_details
                             )
-                            recipient_content = article_author_person(person_details)
                         if len(recipient_content) > 0:
                             # add it
                             recipients.append(recipient_content)
 
             # Add to the dict for adding to the award data later
             if len(recipients) > 0:
-                if id not in award_recipients:
-                    award_recipients[id] = []
-                award_recipients[id] = recipients
+                if award_id not in award_recipients:
+                    award_recipients[award_id] = []
+                award_recipients[award_id] = recipients
 
     # Add recipient data to the award data
     for award in awards:
diff --git a/elifetools/utils.py b/elifetools/utils.py
index e72062f0..d16847c6 100644
--- a/elifetools/utils.py
+++ b/elifetools/utils.py
@@ -6,26 +6,28 @@
 from bs4 import Comment
 
 
-def subject_slug(subject, stopwords=["and", "of"]):
+def subject_slug(subject, stopwords=None):
     "create a slug for a subject value"
+    if stopwords is None:
+        stopwords = ["and", "of"]
     if not subject:
         return subject
     return slugify(subject, stopwords=stopwords)
 
 
-def first(x):
-    if x is None:
+def first(value):
+    if value is None:
         return None
     "returns the first element of an iterable, swallowing index errors and returning None"
     try:
-        return x[0]
+        return value[0]
     except IndexError:
         return None
 
 
-def firstnn(x):
+def firstnn(value):
     "returns the first non-nil value within given iterable"
-    return first(list(filter(None, x)))
+    return first(list(filter(None, value)))
 
 
 def strip_strings(value):
@@ -34,7 +36,7 @@ def strip_string(value):
             return value.strip()
         return value
 
-    if type(value) == list:
+    if isinstance(value, list):
         return list(map(strip_string, value))
     return strip_string(value)
 
@@ -53,9 +55,9 @@ def strip_punctuation(string):
             string = string.replace(match, replacement)
         return string
 
-    if value == None:
+    if value is None:
         return None
-    if type(value) == list:
+    if isinstance(value, list):
         return [strip_punctuation(v) for v in value]
     return strip_punctuation(value)
 
@@ -75,7 +77,10 @@ def join_sentences(string1, string2, glue="."):
 
 
 def coerce_to_int(val, default=0xDEADBEEF):
-    """Attempts to cast given value to an integer, return the original value if failed or the default if one provided."""
+    """
+    Attempts to cast given value to an integer, return the original value if
+    failed or the default if one provided.
+    """
     try:
         return int(val)
     except (TypeError, ValueError):
@@ -89,7 +94,7 @@ def nullify(function):
 
     def wrapper(*args, **kwargs):
         value = function(*args, **kwargs)
-        if type(value) == list and len(value) == 0:
+        if isinstance(value, list) and len(value) == 0:
             return None
         return value
 
@@ -138,7 +143,7 @@ def date_struct(year, month, day, tz="UTC"):
         return time.strptime("%s-%s-%s %s" % ymdtz, "%Y-%m-%d %Z")
     except (TypeError, ValueError):
         # logger.debug("date failed to convert: %s" % str(ymdtz))
-        pass
+        return None
 
 
 def date_struct_nn(year, month, day, tz="UTC"):
@@ -180,7 +185,7 @@ def date_timestamp(date_struct):
     try:
         return calendar.timegm(date_struct)
     except (TypeError, ValueError):
-        pass
+        return None
 
 
 def paragraphs(tags):
@@ -197,21 +202,15 @@ def convert_testing_doi(doi):
 
 
 def starts_with_doi(tag):
-    if node_text(tag).strip().startswith("DOI:"):
-        return True
-    else:
-        return False
+    return bool(node_text(tag).strip().startswith("DOI:"))
 
 
 def paragraph_is_only_doi(tag):
-    if (
+    return bool(
         node_text(tag).strip().startswith("http://dx.doi.org")
         and " " not in node_text(tag).strip()
         and node_contents_str(tag).startswith('<ext-link ext-link-type="doi"')
-    ):
-        return True
-    else:
-        return False
+    )
 
 
 def doi_uri_to_doi(value):
@@ -290,7 +289,7 @@ def extract_nodes(soup, nodename, attr=None, value=None):
     If an optional attribute and value are given, these are used to filter the results
     further."""
     tags = soup.find_all(nodename)
-    if attr != None and value != None:
+    if attr is not None and value is not None:
         return list(filter(lambda tag: tag.get(attr) == value, tags))
     return list(tags)
 
@@ -322,7 +321,7 @@ def first_parent(tag, nodename):
     Given a beautiful soup tag, look at its parents and return the first
     tag name that matches nodename or the list nodename
     """
-    if nodename is not None and type(nodename) == str:
+    if nodename is not None and isinstance(nodename, str):
         nodename = [nodename]
     if tag and tag.parents:
         return first(list(filter(lambda tag: tag.name in nodename, tag.parents)))
@@ -335,7 +334,6 @@ def tag_ordinal(tag):
     to get the tag ordinal. For example, if it is tag name fig
     and two fig tags are before it, then it is the third fig (3)
     """
-    tag_count = 0
     return len(tag.find_all_previous(tag.name)) + 1
 
 
@@ -344,7 +342,6 @@ def tag_fig_ordinal(tag):
     Meant for finding the position of fig tags with respect to whether
     they are for a main figure or a child figure
     """
-    tag_count = 0
     if "specific-use" not in tag.attrs:
         # Look for tags with no "specific-use" attribute
         return (
@@ -358,6 +355,7 @@ def tag_fig_ordinal(tag):
             )
             + 1
         )
+    return None
 
 
 def tag_sibling_ordinal(tag):
@@ -366,7 +364,6 @@ def tag_sibling_ordinal(tag):
     to get a sibling "local" ordinal value. This is useful in counting
     child figures within a fig-group, for example
     """
-    tag_count = 0
     return len(tag.find_previous_siblings(tag.name)) + 1
 
 
@@ -680,7 +677,7 @@ def list_type_prefix(list_type):
     if list_type:
         if list_type == "simple":
             return "none"
-        elif list_type == "order":
+        if list_type == "order":
             return "number"
         return list_type
     return "none"
diff --git a/elifetools/utils_html.py b/elifetools/utils_html.py
index 9079d35d..1aba9680 100644
--- a/elifetools/utils_html.py
+++ b/elifetools/utils_html.py
@@ -5,7 +5,7 @@
 
 def xml_to_html(html_flag, xml_string, base_url=None):
     "For formatting json output into HTML friendly format"
-    if not xml_string or not html_flag is True:
+    if not xml_string or html_flag is not True:
         return xml_string
     html_string = xml_string
     html_string = remove_comment_tags(html_string)
@@ -120,30 +120,30 @@ def enhance_xlink_href(href):
     return href
 
 
-def replace_simple_tags(s, from_tag="italic", to_tag="i", to_open_tag=None):
+def replace_simple_tags(string, from_tag="italic", to_tag="i", to_open_tag=None):
     """
     Replace tags such as <italic> to <i>
     This does not validate markup
     """
     if to_open_tag:
-        s = s.replace("<" + from_tag + ">", to_open_tag)
+        string = string.replace("<" + from_tag + ">", to_open_tag)
     elif to_tag:
-        s = s.replace("<" + from_tag + ">", "<" + to_tag + ">")
-        s = s.replace("<" + from_tag + "/>", "<" + to_tag + "/>")
+        string = string.replace("<" + from_tag + ">", "<" + to_tag + ">")
+        string = string.replace("<" + from_tag + "/>", "<" + to_tag + "/>")
     else:
-        s = s.replace("<" + from_tag + ">", "")
-        s = s.replace("<" + from_tag + "/>", "")
+        string = string.replace("<" + from_tag + ">", "")
+        string = string.replace("<" + from_tag + "/>", "")
 
     if to_tag:
-        s = s.replace("</" + from_tag + ">", "</" + to_tag + ">")
+        string = string.replace("</" + from_tag + ">", "</" + to_tag + ">")
     else:
-        s = s.replace("</" + from_tag + ">", "")
+        string = string.replace("</" + from_tag + ">", "")
 
-    return s
+    return string
 
 
-def replace_xref_tags(s):
-    for tag_match in re.finditer("<(xref.*?)>", s):
+def replace_xref_tags(string):
+    for tag_match in re.finditer("<(xref.*?)>", string):
         rid_match = re.finditer('rid="(.*)"', tag_match.group())
         if rid_match:
             try:
@@ -152,41 +152,41 @@ def replace_xref_tags(s):
                 rid = all_rid.split(" ")[0]
                 new_tag = '<a href="#' + rid + '">'
                 old_tag = "<" + tag_match.group(1) + ">"
-                s = s.replace(old_tag, new_tag)
+                string = string.replace(old_tag, new_tag)
                 # Replace all close tags even if one open tag gets replaced
-                s = replace_simple_tags(s, "xref", "a")
+                string = replace_simple_tags(string, "xref", "a")
             except StopIteration:
                 pass
 
-    return s
+    return string
 
 
-def replace_related_object_tags(s):
-    for tag_match in re.finditer("<(related-object.*?)>", s):
+def replace_related_object_tags(string):
+    for tag_match in re.finditer("<(related-object.*?)>", string):
         xlink_match = re.finditer('xlink:href="(.*)"', tag_match.group())
         if xlink_match:
             try:
                 xlink = next(xlink_match).group(1)
                 new_tag = '<a href="' + enhance_xlink_href(xlink) + '">'
                 old_tag = "<" + tag_match.group(1) + ">"
-                s = s.replace(old_tag, new_tag)
+                string = string.replace(old_tag, new_tag)
                 # Replace all close tags even if one open tag gets replaced
-                s = replace_simple_tags(s, "related-object", "a")
+                string = replace_simple_tags(string, "related-object", "a")
             except StopIteration:
                 pass
-    return s
+    return string
 
 
-def replace_mathml_tags(s):
-    p = re.compile("<mml:")
-    s = p.sub("<", s)
-    p = re.compile("</mml:")
-    s = p.sub("</", s)
-    return s
+def replace_mathml_tags(string):
+    pattern = re.compile("<mml:")
+    string = pattern.sub("<", string)
+    pattern = re.compile("</mml:")
+    string = pattern.sub("</", string)
+    return string
 
 
-def replace_ext_link_tags(s):
-    for tag_match in re.finditer("<(ext-link.*?)>", s):
+def replace_ext_link_tags(string):
+    for tag_match in re.finditer("<(ext-link.*?)>", string):
         xlink_match = re.finditer('xlink:href="(.*)"', tag_match.group())
         ext_link_type_match = re.finditer('ext-link-type="(.*)"', tag_match.group())
         if xlink_match and ext_link_type_match:
@@ -198,27 +198,27 @@ def replace_ext_link_tags(s):
                 elif ext_link_type.startswith("doi"):
                     new_tag = '<a href="https://doi.org/' + xlink + '">'
                 old_tag = "<" + tag_match.group(1) + ">"
-                s = s.replace(old_tag, new_tag)
+                string = string.replace(old_tag, new_tag)
                 # Replace all close tags even if one open tag gets replaced
-                s = replace_simple_tags(s, "ext-link", "a")
+                string = replace_simple_tags(string, "ext-link", "a")
             except StopIteration:
                 pass
-    return s
+    return string
 
 
-def replace_email_tags(s):
-    for tag_match in re.finditer("<email>(.*?)</email>", s):
+def replace_email_tags(string):
+    for tag_match in re.finditer("<email>(.*?)</email>", string):
         email = tag_match.group(1)
         old_tag = "<email>" + email + "</email>"
         new_tag = '<a href="mailto:' + email + '">' + email + "</a>"
-        s = s.replace(old_tag, new_tag)
-    return s
+        string = string.replace(old_tag, new_tag)
+    return string
 
 
-def replace_inline_graphic_tags(s, base_url=None):
+def replace_inline_graphic_tags(string, base_url=None):
     from_file_extension = [".tif", ".tiff"]
     to_file_extension = ".jpg"
-    for tag_match in re.finditer("<(inline-graphic.*?)>", s):
+    for tag_match in re.finditer("<(inline-graphic.*?)>", string):
         xlink_match = re.finditer('xlink:href="(.*)"', tag_match.group())
         if xlink_match:
             try:
@@ -235,16 +235,16 @@ def replace_inline_graphic_tags(s, base_url=None):
                     xlink = base_url + xlink
                 new_tag = '<img src="' + xlink + '"/>'
                 old_tag = "<" + tag_match.group(1) + ">"
-                s = s.replace(old_tag, new_tag)
+                string = string.replace(old_tag, new_tag)
                 # Replace close tag if present
-                s = s.replace("</inline-graphic>", "")
+                string = string.replace("</inline-graphic>", "")
             except StopIteration:
                 pass
-    return s
+    return string
 
 
-def replace_named_content_tags(s):
-    for tag_match in re.finditer("<(named-content.*?)>", s):
+def replace_named_content_tags(string):
+    for tag_match in re.finditer("<(named-content.*?)>", string):
         content_type_match = re.finditer('content-type="(.*)"', tag_match.group())
         try:
             all_match = next(content_type_match).group(1)
@@ -252,19 +252,19 @@ def replace_named_content_tags(s):
             span_class = all_match.split(" ")[0]
             new_tag = '<span class="' + span_class + '">'
             old_tag = "<" + tag_match.group(1) + ">"
-            s = s.replace(old_tag, new_tag)
+            string = string.replace(old_tag, new_tag)
             # Replace all close tags even if one open tag gets replaced
-            s = replace_simple_tags(s, "named-content", "span")
+            string = replace_simple_tags(string, "named-content", "span")
         except StopIteration:
             pass
-    return s
+    return string
 
 
-def remove_comment_tags(s):
-    for tag_match in re.finditer("<!--(.*?)-->", s):
+def remove_comment_tags(string):
+    for tag_match in re.finditer("<!--(.*?)-->", string):
         old_tag = "<!--" + tag_match.group(1) + "-->"
-        s = s.replace(old_tag, "")
-    return s
+        string = string.replace(old_tag, "")
+    return string
 
 
 def list_tag_name(attributes):
@@ -301,14 +301,14 @@ def list_tag_attributes(attributes):
     return attributes
 
 
-def replace_list_tags(s):
-    for tag_match in re.finditer("<list-item(.*?)>", s):
+def replace_list_tags(string):
+    for tag_match in re.finditer("<list-item(.*?)>", string):
         tag_attributes = tag_match.group(1)
         old_tag = "<list-item%s>" % tag_attributes
         new_tag = "<li%s>" % tag_attributes
-        s = s.replace(old_tag, new_tag)
-        s = s.replace("</list-item>", "</li>")
-    for tag_match in re.finditer("<list(.*?)>", s):
+        string = string.replace(old_tag, new_tag)
+        string = string.replace("</list-item>", "</li>")
+    for tag_match in re.finditer("<list(.*?)>", string):
         tag_attributes = tag_match.group(1)
         old_tag = "<list%s>" % tag_attributes
 
@@ -316,26 +316,29 @@ def replace_list_tags(s):
         new_tag_attributes = list_tag_attributes(tag_attributes)
 
         new_tag = "<%s%s>" % (new_tag_name, new_tag_attributes)
-        s = s.replace(old_tag, new_tag)
-        s = s.replace("</list>", "</%s>" % new_tag_name)
-    return s
+        string = string.replace(old_tag, new_tag)
+        string = string.replace("</list>", "</%s>" % new_tag_name)
+    return string
 
 
-def replace_table_style_author_callout(s):
-    for tag_match in re.finditer('<(td[^>]*style="author-callout-style[^>]*?")/?>', s):
+def replace_table_style_author_callout(string):
+    for tag_match in re.finditer(
+        '<(td[^>]*style="author-callout-style[^>]*?")/?>', string
+    ):
         for style_match in re.finditer('(.*)style="(.*)"(.*)', tag_match.group()):
             tag_start = style_match.group(1)
             class_name = style_match.group(2)
             tag_end = style_match.group(3)
             new_tag = tag_start + 'class="' + class_name + '"' + tag_end
             old_tag = tag_start + 'style="' + class_name + '"' + tag_end
-            s = s.replace(old_tag, new_tag)
-    return s
+            string = string.replace(old_tag, new_tag)
+    return string
 
 
 def references_author_collab(ref_author, html_flag=True):
     # Configure the XML to HTML conversion preference for shorthand use below
-    convert = lambda xml_string: xml_to_html(html_flag, xml_string)
+    def convert(xml_string):
+        return xml_to_html(html_flag, xml_string)
 
     author_json = OrderedDict()
     author_json["type"] = "group"
diff --git a/elifetools/xmlio.py b/elifetools/xmlio.py
index 68e323a8..eb585ef8 100644
--- a/elifetools/xmlio.py
+++ b/elifetools/xmlio.py
@@ -69,10 +69,9 @@ def parse(filename, return_doctype_dict=False, return_processing_instructions=Fa
 
     if return_doctype_dict is True and return_processing_instructions is True:
         return root, doctype_dict, processing_instructions
-    elif return_doctype_dict is True:
+    if return_doctype_dict is True:
         return root, doctype_dict
-    else:
-        return root
+    return root
 
 
 def add_tag_before(tag_name, tag_text, parent_tag, before_tag_name):
@@ -122,16 +121,19 @@ def convert_xlink_href(root, name_map):
 
             if tag.get("{http://www.w3.org/1999/xlink}href"):
 
-                for k, v in name_map.items():
+                for key, value in name_map.items():
                     # Try to match the exact name first, and if not then
                     #  try to match it without the file extension
-                    if tag.get("{http://www.w3.org/1999/xlink}href") == k:
-                        tag.set("{http://www.w3.org/1999/xlink}href", v)
+                    if tag.get("{http://www.w3.org/1999/xlink}href") == key:
+                        tag.set("{http://www.w3.org/1999/xlink}href", value)
                         count += 1
                     elif (
-                        tag.get("{http://www.w3.org/1999/xlink}href") == k.split(".")[0]
+                        tag.get("{http://www.w3.org/1999/xlink}href")
+                        == key.split(".")[0]
                     ):
-                        tag.set("{http://www.w3.org/1999/xlink}href", v.split(".")[0])
+                        tag.set(
+                            "{http://www.w3.org/1999/xlink}href", value.split(".")[0]
+                        )
                         count += 1
     return count
 
@@ -193,14 +195,17 @@ def rewrite_subject_group(root, subjects, subject_group_type, overwrite=True):
     return count
 
 
-def output(root, type="JATS", doctype_dict=None, processing_instructions=None):
+def output(root, output_type="JATS", doctype_dict=None, processing_instructions=None):
 
     if doctype_dict is not None:
         publicId = doctype_dict.get("pubid")
         systemId = doctype_dict.get("system")
         qualifiedName = doctype_dict.get("name")
-    elif type == "JATS":
-        publicId = "-//NLM//DTD JATS (Z39.96) Journal Archiving and Interchange DTD v1.1d3 20150301//EN"
+    elif output_type == "JATS":
+        publicId = (
+            "-//NLM//DTD JATS (Z39.96) Journal Archiving and Interchange "
+            "DTD v1.1d3 20150301//EN"
+        )
         systemId = "JATS-archivearticle1.dtd"
         qualifiedName = "article"
     else:
@@ -299,14 +304,15 @@ def append_minidom_xml_to_elementtree_xml(
             for name, value in node.attributes.items():
                 new_elem.set(name, value)
 
+    new_elem_sub = None
     i = 0
     for child_node in node.childNodes:
         if child_node.nodeName == "#text":
             if not new_elem.text and i <= 0:
                 new_elem.text = child_node.nodeValue
-            elif not new_elem.text and i > 0:
+            elif new_elem_sub is not None and (not new_elem.text and i > 0):
                 new_elem_sub.tail = child_node.nodeValue
-            else:
+            elif new_elem_sub is not None:
                 new_elem_sub.tail = child_node.nodeValue
 
         elif child_node.childNodes is not None:


Test patch:
diff --git a/elifetools/tests/test_utils.py b/elifetools/tests/test_utils.py
index c93bcfc2..c69cfc39 100644
--- a/elifetools/tests/test_utils.py
+++ b/elifetools/tests/test_utils.py
@@ -35,7 +35,7 @@ def test_subject_slug_default(self, value, expected):
     @unpack
     @data(
         (None, None, None),
-        ("this and that", None, "this-and-that"),
+        ("this and that", [], "this-and-that"),
         ("this and that", ["this", "and", "that"], ""),
     )
     def test_subject_slug_stopwords(self, value, stopwords, expected):