diff --git a/packtools/sps/formats/pubmed.py b/packtools/sps/formats/pubmed.py index f99b2651d..4965fa135 100644 --- a/packtools/sps/formats/pubmed.py +++ b/packtools/sps/formats/pubmed.py @@ -270,6 +270,7 @@ def pipeline_pubmed(xml_tree, pretty_print=True): xml_pubmed_elocation_pipe(xml_pubmed, xml_tree) xml_pubmed_language_pipe(xml_pubmed, xml_tree) xml_pubmed_author_list(xml_pubmed, xml_tree) + xml_pubmed_group_list(xml_pubmed, xml_tree) xml_pubmed_publication_type(xml_pubmed, xml_tree) xml_pubmed_article_id(xml_pubmed, xml_tree) xml_pubmed_history(xml_pubmed, xml_tree) @@ -290,7 +291,12 @@ def get_authors(xml_tree): def add_first_name(author_reg, author_tag): - author_first_name = author_reg.get("contrib_name", {}).get("given-names") + contrib_name = author_reg.get("contrib_name", {}) + # A DTD do PubMed exige FirstName e LastName juntos (nenhum dos dois é + # opcional isoladamente). Quando o XML fonte só tem um dos dois campos + # (autores sem , por exemplo), duplicamos o valor disponível + # em ambos para gerar um Author válido em vez de descartar o autor. + author_first_name = contrib_name.get("given-names") or contrib_name.get("surname") if author_first_name: first = ET.Element("FirstName") first.text = author_first_name @@ -298,17 +304,34 @@ def add_first_name(author_reg, author_tag): def add_last_name(author_reg, author_tag): - author_last_name = author_reg.get("contrib_name", {}).get("surname") + contrib_name = author_reg.get("contrib_name", {}) + author_last_name = contrib_name.get("surname") or contrib_name.get("given-names") if author_last_name: last = ET.Element("LastName") last.text = author_last_name author_tag.append(last) +def add_suffix(author_reg, author_tag): + author_suffix = author_reg.get("contrib_name", {}).get("suffix") + if author_suffix: + suffix = ET.Element("Suffix") + suffix.text = author_suffix + author_tag.append(suffix) + + +def add_collective_name(author_reg, author_tag): + collab = author_reg.get("collab") + if collab: + collective_name = ET.Element("CollectiveName") + collective_name.text = collab + author_tag.append(collective_name) + + def get_affiliations(author_reg, xml_tree): affiliations = aff.AffiliationExtractor(xml_tree).get_affiliation_dict(subtag=False) affiliation_list = [] - for item in author_reg.get("affs"): + for item in author_reg.get("affs") or []: affiliation_list.append( affiliations.get(item.get("id"), {}).get("institution", {})[0].get("original") ) @@ -337,68 +360,112 @@ def add_orcid(author_reg, author_tag): def xml_pubmed_author_list(xml_pubmed, xml_tree): - authors = list(get_authors(xml_tree)) + # Contribs de contrib-group[@content-type="collab-list"] são os membros + # nomeados de um grupo de autoria (ver xml_pubmed_group_list) e não + # devem aparecer soltos em AuthorList. + authors = [ + author_reg + for author_reg in get_authors(xml_tree) + if author_reg.get("contrib-group-type") != "collab-list" + ] if authors: author_list_tag = ET.Element("AuthorList") for author_reg in authors: author_tag = ET.Element("Author") - add_first_name(author_reg, author_tag) - - # TODO - # add_middle_name(author_reg, author_tag) - # The Author’s full middle name, or initial if the full name is not available. - # Multiple names are allowed in this tag. - # There is no example of using this value in the files. - add_last_name(author_reg, author_tag) + # A DTD do PubMed exige que Author tenha nome pessoal + # (FirstName/MiddleName/LastName/Suffix) OU CollectiveName, + # nunca os dois ao mesmo tempo. + if author_reg.get("collab"): + add_collective_name(author_reg, author_tag) + else: + add_first_name(author_reg, author_tag) - # TODO - # add_suffix(author_reg, author_tag) - # The Author's suffix, if any, e.g. "Jr", "Sr", "II", "IV". Do not include honorific titles, - # e.g. "M.D.", "Ph.D.". - # There is no example of using this value in the files + # TODO + # add_middle_name(author_reg, author_tag) + # The Author’s full middle name, or initial if the full name is not available. + # Multiple names are allowed in this tag. + # There is no example of using this value in the files. - # TODO - # add_collective_name(author_reg, author_tag) - # The name of the authoring committee or organization. The CollectiveName tag should be placed within - # an Author tag. Omit extraneous text like, “on behalf of.” - # Please see the following example: - # - # - # Plastic Surgery Educational Foundation DATA Committee - # - # - # There is no example of using this value in the files + add_last_name(author_reg, author_tag) + add_suffix(author_reg, author_tag) affiliations = get_affiliations(author_reg, xml_tree) add_affiliations(affiliations, author_tag) add_orcid(author_reg, author_tag) author_list_tag.append(author_tag) - # TODO - # add_group_list(author_reg, author_tag) - # Group information should be enclosed in these tags. If an article has one or more Groups, this tag - # must be submitted. Groups should be listed in the same order as in the printed article, and Group name - # format should accurately reflect the article. This tag is Required if the tag Group is present. - # There is no example of using this value in the files + xml_pubmed.append(author_list_tag) - # TODO - # add_group(author_reg, author_tag) - # Information about a single Group must begin with this tag. - # There is no example of using this value in the files - # TODO - # add_group_name(author_reg, author_tag) - # The name of the authoring committee or organization. Omit extraneous text like, “on behalf of.” - # There is no example of using this value in the files +def get_group_members(authors): + return [ + author_reg + for author_reg in authors + if author_reg.get("contrib-group-type") == "collab-list" + ] - # TODO - # add_individual_name(author_reg, author_tag) - # The name of individual members belonging to the authoring committee or organization. - # The name should be tagged with the FirstName, MiddleName, LastName, Suffix, and Affiliation tags. - # There is no example of using this value in the files - xml_pubmed.append(author_list_tag) +def get_group_name(authors): + for author_reg in authors: + if author_reg.get("collab") and author_reg.get("contrib-group-type") != "collab-list": + return author_reg.get("collab") + + +def add_individual_name(member_reg, xml_tree, group_tag): + """ + + Felipe + Esteves + Universidade Federal do Pará (UFPA) + + """ + individual_name_tag = ET.Element("IndividualName") + add_first_name(member_reg, individual_name_tag) + add_last_name(member_reg, individual_name_tag) + add_suffix(member_reg, individual_name_tag) + affiliations = get_affiliations(member_reg, xml_tree) + add_affiliations(affiliations, individual_name_tag) + add_orcid(member_reg, individual_name_tag) + group_tag.append(individual_name_tag) + + +def xml_pubmed_group_list(xml_pubmed, xml_tree): + """ + + + The SciELO Group + + Felipe + Esteves + Universidade Federal do Pará (UFPA) + + + + + Membros de contrib-group[@content-type="collab-list"] (ver + xml_pubmed_author_list), agrupados sob o nome do grupo declarado no + do contrib-group irmão. + """ + authors = list(get_authors(xml_tree)) + members = get_group_members(authors) + if not members: + return + + group_list_tag = ET.Element("GroupList") + group_tag = ET.Element("Group") + + group_name = get_group_name(authors) + if group_name: + group_name_tag = ET.Element("GroupName") + group_name_tag.text = group_name + group_tag.append(group_name_tag) + + for member_reg in members: + add_individual_name(member_reg, xml_tree, group_tag) + + group_list_tag.append(group_tag) + xml_pubmed.append(group_list_tag) def get_publication_type(xml_tree): diff --git a/tests/sps/formats/test_pubmed.py b/tests/sps/formats/test_pubmed.py index a8afad598..15e70e92f 100644 --- a/tests/sps/formats/test_pubmed.py +++ b/tests/sps/formats/test_pubmed.py @@ -16,6 +16,7 @@ xml_pubmed_elocation_pipe, xml_pubmed_language_pipe, xml_pubmed_author_list, + xml_pubmed_group_list, xml_pubmed_publication_type, xml_pubmed_article_id, xml_pubmed_history, @@ -1012,6 +1013,272 @@ def test_xml_pubmed_author_list_without_author(self): self.assertEqual(obtained, expected) + def test_xml_pubmed_author_list_with_suffix(self): + expected = ( + '
' + '' + '' + 'Rogerio' + 'Meneghini' + 'Junior' + 'Some University' + '' + '' + '
' + ) + xml_pubmed = ET.fromstring( + '
' + ) + xml_tree = ET.fromstring( + '
' + '' + '' + '' + '' + '' + 'Meneghini' + 'Rogerio' + 'Junior' + '' + '1' + '' + '' + '' + 'Some University' + '' + '' + '' + '
' + ) + + xml_pubmed_author_list(xml_pubmed, xml_tree) + + obtained = ET.tostring(xml_pubmed, encoding="utf-8").decode("utf-8") + + self.assertEqual(obtained, expected) + + def test_xml_pubmed_author_list_without_surname_duplicates_given_names(self): + # A DTD do PubMed exige FirstName e LastName juntos; quando o XML + # fonte só tem (sem ), duplicamos o valor + # em ambos os campos em vez de gerar um Author inválido. + expected = ( + '
' + '' + '' + 'Viviana Alder' + 'Viviana Alder' + '' + '' + '
' + ) + xml_pubmed = ET.fromstring( + '
' + ) + xml_tree = ET.fromstring( + '
' + '' + '' + '' + '' + '' + 'Viviana Alder' + '' + '' + '' + '' + '' + '
' + ) + + xml_pubmed_author_list(xml_pubmed, xml_tree) + + obtained = ET.tostring(xml_pubmed, encoding="utf-8").decode("utf-8") + + self.assertEqual(obtained, expected) + + def test_xml_pubmed_author_list_with_collective_name(self): + expected = ( + '
' + '' + '' + 'The SciELO Group' + '' + '' + '
' + ) + xml_pubmed = ET.fromstring( + '
' + ) + xml_tree = ET.fromstring( + '
' + '' + '' + '' + '' + 'The SciELO Group' + '1' + '' + '' + '' + '' + '
' + ) + + xml_pubmed_author_list(xml_pubmed, xml_tree) + + obtained = ET.tostring(xml_pubmed, encoding="utf-8").decode("utf-8") + + self.assertEqual(obtained, expected) + + def test_xml_pubmed_author_list_excludes_collab_list_members(self): + expected = ( + '
' + '' + '' + 'The SciELO Group' + '' + '' + '
' + ) + xml_pubmed = ET.fromstring( + '
' + ) + xml_tree = ET.fromstring( + '
' + '' + '' + '' + '' + 'The SciELO Group' + '1' + '' + '' + '' + '' + '' + 'Esteves' + 'Felipe' + '' + '' + '' + '' + '' + '
' + ) + + xml_pubmed_author_list(xml_pubmed, xml_tree) + + obtained = ET.tostring(xml_pubmed, encoding="utf-8").decode("utf-8") + + self.assertEqual(obtained, expected) + + def test_xml_pubmed_group_list(self): + expected = ( + '
' + '' + '' + 'The SciELO Group' + '' + 'Felipe' + 'Esteves' + 'Universidade Federal do Pará (UFPA)' + 'http://orcid.org/0000-0001-0002-0003' + '' + '' + 'Joyce' + 'Souza' + 'Universidade Federal do Pará (UFPA)' + '' + '' + '' + '
' + ) + xml_pubmed = ET.fromstring( + '
' + ) + xml_tree = ET.fromstring( + '
' + '' + '' + '' + '' + 'The SciELO Group' + '1' + '' + '' + '' + '' + '0000-0001-0002-0003' + '' + 'Esteves' + 'Felipe' + '' + '1' + '' + '' + '' + 'Souza' + 'Joyce' + '' + '1' + '' + '' + '' + 'Universidade Federal do Pará (UFPA)' + '' + '' + '' + '
' + ) + + xml_pubmed_group_list(xml_pubmed, xml_tree) + + obtained = ET.tostring(xml_pubmed, encoding="utf-8").decode("utf-8") + + self.assertEqual(obtained, expected) + + def test_xml_pubmed_group_list_without_group(self): + expected = ( + '
' + ) + xml_pubmed = ET.fromstring( + '
' + ) + xml_tree = ET.fromstring( + '
' + '' + '' + '' + '' + '' + 'Meneghini' + 'Rogerio' + '' + '' + '' + '' + '' + '
' + ) + + xml_pubmed_group_list(xml_pubmed, xml_tree) + + obtained = ET.tostring(xml_pubmed, encoding="utf-8").decode("utf-8") + + self.assertEqual(obtained, expected) + def test_xml_pubmed_publication_type(self): # TODO # Originalmente, espera-se que o valor da tag seja Journal Article