Skip to main content

entrez_rs/parser/
pubmed.rs

1//! Utilities to parse XML results obtained using [EFetch](super::super::eutils::EFetch) from 
2//! [DB::Pubmed](super::super::eutils::DB::Pubmed) database.
3 
4//! # Description
5//! When using [EFetch](super::super::eutils::EFetch), with [DB::Pubmed](super::super::eutils::DB::Pubmed)
6//!  in the [DB](super::super::eutils::DB) field, the resulting XML
7//! is as follows:
8//! ``` xml
9//! <?xml version="1.0" ?>
10//! <!DOCTYPE PubmedArticleSet PUBLIC "-//NLM//DTD PubMedArticle, 1st January 2019//EN" "https://dtd.nlm.nih.gov/ncbi/pubmed/out/pubmed_190101.dtd">
11//! <PubmedArticleSet>
12//!     <PubmedArticle>
13//!      ...
14//!     </PubmedArticle>
15//!     <PubmedArticle>
16//!      ...
17//!     </PubmedArticle>
18//! </PubmedArticleSet>
19//!  ```
20//! The [read](PubmedArticleSet::read) function will parse this XML into a [PubmedArticleSet]
21//! struct.
22//! 
23//! # Example
24//! 
25//!  ```
26//!   use entrez_rs::eutils::{Eutils, EFetch, DB};
27//!   use entrez_rs::parser::pubmed::{PubmedArticleSet};
28//! 
29//!   let xml = EFetch::new(
30//!         DB::Pubmed, 
31//!         vec!["33246200"]) 
32//!         .run().expect("Connection error");
33//! 
34//!   // Use the read function to parse the xml result.
35//!   let parsed = PubmedArticleSet::read(&xml)
36//!                .expect("Parsing error");
37//! 
38//!   assert_eq!(parsed.articles.len(), 1);
39//!   ```
40
41
42use roxmltree::{Node, Document};
43use super::super::errors::{Error};
44
45
46#[derive(Debug, PartialEq)]
47pub struct Reference {
48    pub citation: Option<String>,
49    pub article_id_list: ArticleIdList
50}
51
52#[derive(Debug, PartialEq)]
53pub struct ArticleId {
54    pub id_type: Option<String>,
55    pub id: Option<String>
56}
57
58#[derive(Debug, PartialEq)]
59pub struct ArticleIdList {
60    pub article_ids: Vec<ArticleId>
61}
62
63#[derive(Debug, PartialEq)]
64pub struct ReferenceList {
65    pub references: Vec<Reference>
66}
67
68#[derive(Debug, PartialEq)]
69pub struct PubmedData {
70    pub publication_status: Option<String>,
71    pub article_id_list: Option<ArticleIdList>,
72    pub reference_list: Option<ReferenceList>,
73    pub history: Vec<PubMedPubDate>
74}
75
76#[derive(Debug, PartialEq)]
77pub struct PubMedPubDate {
78    pub pub_status: Option<String>,
79    pub year: Option<String>,
80    pub month: Option<String>,
81    pub day: Option<String>
82}
83
84// MedlineCitation Fields
85#[derive(Debug, PartialEq)]
86pub struct PMID {
87    pub version: Option<String>,
88    pub value: Option<String>
89}
90
91#[derive(Debug, PartialEq)]
92pub struct ISSN {
93    pub issn_type: Option<String>,
94    pub value: Option<String>
95}
96
97#[derive(Debug, PartialEq)]
98pub struct PubDate {
99    pub year: Option<String>,
100    pub month: Option<String>,
101    pub day: Option<String>
102}
103
104#[derive(Debug, PartialEq)]
105pub struct JournalIssue {
106    pub cited_medium: Option<String>,
107    pub volume: Option<String>,
108    pub issue: Option<String>,
109    pub pub_date: Option<PubDate>
110}
111
112#[derive(Debug, PartialEq)]
113pub struct Journal {
114    pub issn: Option<ISSN>,
115    pub journal_issue: Option<JournalIssue>,
116    pub title: Option<String>,
117    pub iso_abbr: Option<String>
118}
119
120#[derive(Debug, PartialEq)]
121pub struct ELocationID {
122    pub eid_type: Option<String>,
123    pub valid_yn: Option<String>,
124    pub value: Option<String>
125}
126
127#[derive(Debug, PartialEq)]
128pub struct AbstractText {
129    pub label: Option<String>,
130    pub nlm_category: Option<String>,
131    pub value: Option<String>
132}
133
134#[derive(Debug, PartialEq)]
135pub struct Abstract {
136    pub text: Vec<AbstractText>
137}
138
139#[derive(Debug, PartialEq)]
140pub struct Article {
141    pub pub_model: Option<String>,
142    pub title: Option<String>,
143    pub journal: Option<Journal>,
144    pub elocation_id: Option<ELocationID>,
145    pub language: Option<String>,
146    pub abstract_text: Option<Abstract>
147}
148
149#[derive(Debug, PartialEq)]
150pub struct MedlineJournalInfo {
151    pub country: Option<String>,
152    pub medline_ta: Option<String>,
153    pub nlm_unique_id: Option<String>,
154    pub issn_linking: Option<String>
155}
156
157#[derive(Debug, PartialEq)]
158pub struct MedlineCitation {
159    pub status: Option<String>,
160    pub owner: Option<String>,
161    pub pmid: Option<PMID>,
162    pub date_revised: Option<PubDate>,
163    pub article: Option<Article>,
164    pub medline_journal_info: Option<MedlineJournalInfo>
165}
166
167#[derive(Debug, PartialEq)]
168pub struct PubmedArticle {
169    pub medline_citation: Option<MedlineCitation>,
170    pub pubmed_data: Option<PubmedData>
171}
172
173#[derive(Debug, PartialEq)]
174pub struct PubmedArticleSet {
175    pub articles: Vec<PubmedArticle>
176}
177
178pub trait FromXMLNode {
179    fn from_node(node: &Node) -> Self;
180}
181
182//PubmedData fields
183impl FromXMLNode for ArticleIdList {
184    fn from_node(node: &Node) -> Self {
185        let mut article_id_list = ArticleIdList {
186            article_ids: Vec::new()
187        };
188        
189        for elem in node
190                         .children()
191                         .filter(|n| n.is_element()) {
192
193            article_id_list
194            .article_ids
195            .push(
196                ArticleId::from_node(&elem)
197            )                    
198
199                        
200        }
201
202        article_id_list
203    }
204}
205impl FromXMLNode for ArticleId {
206    fn from_node(node:&Node) -> Self {
207        ArticleId {
208            id_type: node.attribute("IdType").map(|n| n.to_string()),
209            id: node.text().map(|n| n.to_string())
210                     
211       }
212    } 
213}
214impl FromXMLNode for Reference {
215    fn from_node(node: &Node) -> Self {
216        let mut reference = Reference {
217            citation: None,
218            article_id_list: ArticleIdList {
219                article_ids: Vec::new()
220            }
221        };
222
223        for elem in node.children().filter(|x| x.is_element()) {
224            match elem.tag_name().name() {
225                "Citation" => reference.citation = elem.text().map(|x| x.to_string()),
226                "ArticleIdList"  => reference.article_id_list = ArticleIdList::from_node(&elem),
227                _          => ()
228            }
229        }
230        reference
231    }
232}
233impl FromXMLNode for ReferenceList {
234    fn from_node(node: &Node) -> Self {
235        let mut referene_list = ReferenceList {
236            references: Vec::new()
237        };
238
239        for elem in node
240                        .children()
241                        .filter(|n| n.is_element()) {
242                            referene_list.references.push(
243                                Reference::from_node(&elem)
244                            )
245                        }
246        
247        referene_list
248    }
249}
250impl FromXMLNode for PubMedPubDate {
251    fn from_node(node: &Node) -> Self {
252        let mut pubmed_pub_date = PubMedPubDate {
253            pub_status: node.attribute("PubStatus")
254                             .map(|s| s.to_string()),
255            year: None,
256            month: None,
257            day: None
258        };
259
260        for elem in node
261            .children()
262            .filter(|e| e.is_element()){
263             match elem.tag_name().name() {
264                 "Year"  => pubmed_pub_date.year = elem.text().map(|e| e.to_string()),
265                 "Month" => pubmed_pub_date.month = elem.text().map(|e| e.to_string()),  
266                 "Day"   => pubmed_pub_date.day = elem.text().map(|e| e.to_string()),  
267                 _       => ()              
268             }
269        }
270        pubmed_pub_date
271    }
272}
273impl FromXMLNode for PubmedData {
274    fn from_node(node: &Node) -> Self {
275        let mut pubmed_data = PubmedData {
276            publication_status: None,
277            article_id_list: None,
278            reference_list: None,
279            history: Vec::new()
280        };
281        
282        for elem in node
283            .children()
284            .filter(|n| n.is_element()) {
285            
286            match elem.tag_name().name() {
287                "PublicationStatus" => pubmed_data.publication_status = elem.text().map(|e| e.to_string()),
288                "ArticleIdList"     => pubmed_data.article_id_list = Some(ArticleIdList::from_node(&elem)),
289                "ReferenceList"     => pubmed_data.reference_list  = Some(ReferenceList::from_node(&elem)),
290                "History"           => {
291                    for pub_date in elem.children().filter(|e| e.is_element()) {
292                        pubmed_data.history.push(
293                            PubMedPubDate::from_node(&pub_date)
294                        )
295                    }
296                }
297                _                   => ()
298
299            }
300
301        }
302
303        pubmed_data
304    }
305}
306
307//MedlineCitation fields
308impl FromXMLNode for PMID {
309    fn from_node(node: &Node) -> Self {
310        PMID {
311            version: node.attribute("Version")
312                             .map(|s| s.to_string()),
313            
314            value: node.text().map(|s| s.to_string())
315        }
316    }
317}
318impl FromXMLNode for PubDate {
319    fn from_node(node: &Node) -> Self {
320            let mut date = PubDate {
321                year: None,
322                month: None,
323                day: None
324            };
325
326            for elem in node
327                .children()
328                .filter(|e| e.is_element()) {
329                
330                match elem.tag_name().name() {
331                    "Year" => date.year = elem.text().map(|s| s.to_string()),
332                    "Month" => date.month = elem.text().map(|s| s.to_string()),
333                    "Day" => date.day = elem.text().map(|s| s.to_string()),
334                    _ => ()
335                }
336            }
337           
338            date
339        
340    }
341}
342impl FromXMLNode for ISSN {
343    fn from_node(node: &Node) -> Self {
344        ISSN {
345            issn_type: node.attribute("IssnType")
346                       .map(|n| n.to_string()),
347            value: node.text()
348                   .map(|t| t.to_string())
349        }
350    }
351}
352impl FromXMLNode for JournalIssue {
353    fn from_node(node: &Node) -> Self {
354        let mut journal_issue =  JournalIssue {
355            cited_medium: node.attribute("CitedMedium")
356                          .map(|e| e.to_string()),
357            volume: None,
358            issue:  None,
359            pub_date: None
360        };
361        for elem in node
362                         .children()
363                         .filter(|n| n.is_element()) {
364            
365            match elem.tag_name().name() {
366                "Volume" => {
367                    journal_issue.volume = elem.text().map(|e| e.to_string())
368                },
369                "Issue" => {
370                    journal_issue.issue = elem.text().map(|e| e.to_string()) 
371                },
372                "PubDate" => {
373                    journal_issue.pub_date = Some(PubDate::from_node(&elem))
374                }
375                _ => ()
376            }
377        }
378        journal_issue
379    }
380}
381impl FromXMLNode for Journal {
382    fn from_node(node: &Node) -> Self {
383        let mut journal = Journal {
384            issn: None,
385            journal_issue: None,
386            title: None,
387            iso_abbr: None
388        };
389        
390        for elem in node 
391                 .children()
392                 .filter(|e| e.is_element()) { 
393            match elem.tag_name().name() {
394                "ISSN" => {
395                    journal.issn = Some(ISSN::from_node(&elem))
396                },
397                "JournalIssue" => {
398                    journal.journal_issue = Some(JournalIssue::from_node(&elem))
399                },
400                "Title" => {
401                    journal.title = elem.text()
402                                    .map(|e| e.to_string())
403                },
404                "ISOAbbreviation" => {
405                    journal.iso_abbr = elem.text()
406                                       .map(|e| e.to_string())
407                },
408                _ => ()
409            }
410        }
411
412        journal
413    }
414}
415impl FromXMLNode for ELocationID {
416    fn from_node(node: &Node) -> Self {
417        ELocationID {
418            eid_type: node.attribute("EIdType")
419                      .map(|e| e.to_string()),
420            valid_yn: node.attribute("ValidYN")
421                      .map(|e| e.to_string()),
422            value: node.text().map(|t|t.to_string())
423        }
424    }
425}
426impl FromXMLNode for AbstractText {
427    fn from_node(node: &Node) -> Self {
428        let mut abstract_text = AbstractText {
429            label: node.attribute("Label")
430                   .map(|e| e.to_string()),
431            nlm_category: node.attribute("NlmCategory")
432                   .map(|e| e.to_string()),
433            value: None
434        };
435        
436        let mut txt = Vec::new();
437        for elem in node 
438            .children() {
439            
440            if elem.is_text() {
441                match elem.text() {
442                    Some(t) => txt.push(t),
443                    None => ()
444                }
445                
446            }
447            
448            if elem.is_element() {
449               &txt.push("<");
450               if elem.namespaces().len() > 0 {
451                   for ns in elem.namespaces() {
452                       txt.push(ns.name().unwrap());
453                       txt.push(":");
454                       txt.push(elem.tag_name().name());
455                       txt.push(" xmlns:");
456                       txt.push(ns.name().unwrap());
457                       txt.push("=");
458                       txt.push("\"");
459                       txt.push(ns.uri());
460                       txt.push("\"");
461                   }
462               }
463               else {
464                   &txt.push(elem.tag_name().name());
465               }
466               &txt.push(">");
467                
468                if elem.has_children() {
469                        for c in elem.children() {
470                           
471                            if c.is_element() {
472                           &txt.push("<");
473                           if c.namespaces().len() > 0 {
474                           for ns in c.namespaces() {
475                           txt.push(ns.name().unwrap());
476                           txt.push(":");
477                           txt.push(c.tag_name().name());
478                           txt.push(" xmlns:");
479                           txt.push(ns.name().unwrap());
480                           txt.push("=");
481                           txt.push("\"");
482                           txt.push(ns.uri());
483                           txt.push("\"");
484                        }
485                   }
486                   else {
487                       &txt.push(c.tag_name().name());
488                   }
489                   &txt.push(">");
490                   match c.text() {
491                                    Some(t) => txt.push(t),
492                                    None => ()
493                               }
494                   &txt.push("</");
495                      if c.namespaces().len() > 0 {
496                          for n in c.namespaces() {
497                                  txt.push(n.name().unwrap());
498                                  txt.push(":");
499                          }
500               }
501               &txt.push(c.tag_name().name());
502               &txt.push(">");
503                   }
504                   else {
505                    match c.text() {
506                        Some(t) => &txt.push(&t),
507                        None => &txt.push("")
508                    };
509                }
510                        
511                    
512                    };
513                }// ends here
514                else {
515                    match elem.text() {
516                        Some(t) => &txt.push(&t),
517                        None => &txt.push("")
518                    };
519                }
520                
521               &txt.push("</");
522               if elem.namespaces().len() > 0 {
523                   for ns in elem.namespaces() {
524                           txt.push(ns.name().unwrap());
525                           txt.push(":");
526                   }
527               }
528               &txt.push(elem.tag_name().name());
529               &txt.push(">");
530            }
531            abstract_text.value = Some(txt.concat())
532        }
533        
534        abstract_text
535    }
536}
537impl FromXMLNode for Abstract {
538    fn from_node(node: &Node) -> Self {
539        let mut abs = Abstract {
540            text: Vec::new()
541        };
542
543        for elem in node 
544            .children()
545            .filter(|e| e.is_element()) {
546                match elem.tag_name().name() {
547                    "AbstractText" => abs.text.push(
548                        AbstractText::from_node(&elem)
549                    ),
550                    _ => ()
551                }
552        }
553
554        abs
555        
556    }
557}
558impl FromXMLNode for Article {
559    fn from_node(node: &Node) -> Self {
560        let mut article = Article {
561            pub_model: node.attribute("PubModel")
562                            .map(|a| a.to_string()),
563            title: None,
564            journal: None,
565            elocation_id: None,
566            language: None,
567            abstract_text: None
568            
569      };
570      
571      for elem in node 
572                       .children()
573                       .filter(|e| e.is_element()) {
574          
575          match elem.tag_name().name() {
576              "ArticleTitle" => {
577                  article.title = elem.text() 
578                                  .map(|e| e.to_string())
579              },
580              "Journal" => {
581                  article.journal = Some(Journal::from_node(&elem))
582              },
583              "ELocationID" => {
584                  article.elocation_id = Some(ELocationID::from_node(&elem))
585              },
586              "Language" => {
587                  article.language = elem.text()
588                                         .map(|e| e.to_string())
589              },
590              "Abstract" => {
591                  article.abstract_text = Some(Abstract::from_node(&elem))
592              },
593              _ => ()
594          }
595      }
596      article
597    }
598}
599impl FromXMLNode for MedlineJournalInfo {
600    fn from_node(node: &Node) -> Self {
601        let mut med_journ_info = MedlineJournalInfo {
602            country: None,
603            medline_ta: None,
604            nlm_unique_id: None,
605            issn_linking: None
606        };
607
608        for elem in node 
609            .children()
610            .filter(|e| e.is_element()) {
611                match elem.tag_name().name() {
612                    "Country" => {
613                        med_journ_info
614                        .country = elem.text().map(|t| t.to_string())
615                    },
616                    "MedlineTA" => {
617                        med_journ_info
618                        .medline_ta = elem.text().map(|t| t.to_string())
619                    },
620                    "NlmUniqueID" => {
621                        med_journ_info
622                        .nlm_unique_id = elem.text().map(|t| t.to_string())
623                    },
624                    "ISSNLinking" => {
625                        med_journ_info
626                        .issn_linking = elem.text().map(|t| t.to_string())
627                    },
628                    _ => ()
629
630                }
631            }
632        med_journ_info
633    }
634}
635impl FromXMLNode for MedlineCitation {
636    fn from_node(node: &Node) -> Self {
637        let mut medline_citation = MedlineCitation {
638            status: node.attribute("Status")
639                             .map(|s| s.to_string()),
640            
641            owner: node.attribute("Owner")
642                             .map(|s| s.to_string()),
643            pmid: None,
644
645            date_revised: None,
646
647            article: None,
648
649            medline_journal_info: None
650        };
651
652        for elem in node
653            .children() 
654            .filter(|e| e.is_element()) {
655                match elem.tag_name().name() { 
656                    "PMID" => medline_citation.pmid =  Some(PMID::from_node(&elem)),
657                    "DateRevised" => medline_citation.date_revised = Some(PubDate::from_node(&elem)),
658                    "Article" => medline_citation.article = Some(Article::from_node(&elem)),  
659                    "MedlineJournalInfo" => medline_citation.medline_journal_info = Some(MedlineJournalInfo::from_node(&elem)),                                      
660                    _ => ()
661                }
662        }
663
664        medline_citation
665
666    }
667}
668
669//PubmedArticle
670impl FromXMLNode for PubmedArticle {
671    fn from_node(node: &Node) -> Self {
672        let mut pub_art = PubmedArticle {
673            medline_citation: None,
674            pubmed_data: None
675        };
676        
677        for elem in node
678            .children() 
679            .filter(|e| e.is_element()) {
680                match elem.tag_name().name() { 
681                    "MedlineCitation" => pub_art.medline_citation =  Some(MedlineCitation::from_node(&elem)),
682                    "PubmedData" => pub_art.pubmed_data = Some(PubmedData::from_node(&elem)),
683                    _ => ()
684                }
685        }
686
687        pub_art
688    }
689}
690
691//PubmedArticleSet
692
693impl FromXMLNode for PubmedArticleSet {
694    fn from_node(node: &Node) -> Self {
695        let mut pm_set = PubmedArticleSet {
696            articles: Vec::new()
697        };
698        
699        for elem in node
700            .children() 
701            .filter(|e| e.is_element()) {
702                match elem.tag_name().name() { 
703                    "PubmedArticle" => pm_set.articles.push(
704                        PubmedArticle::from_node(&elem)
705                    ),
706                    _ => ()
707                }
708        }
709
710        pm_set
711    }
712}
713
714impl PubmedArticleSet {
715    pub fn read(xml: &str) -> Result<Self, Error> {
716
717        let pm_parsed = Document::parse(&xml)?;
718        
719        let pm_art_node = &pm_parsed 
720              .descendants()
721              .find(|n| n.has_tag_name("PubmedArticleSet")).unwrap();
722        
723        let res = PubmedArticleSet::from_node(&pm_art_node);
724        
725        Ok(res)
726    }
727}