Pumbedの論文データをもとに業績リストを作成する

#はじめに
研究をしていると、業績リストの提出を求められることがあります。業績の中心は何と言っても論文ですが、業績リストのフォーマットがその都度異なったりするのです。

特に、新しい順に番号を振って業績を書かなくてはならない場合が多く、少し前に作った業績リストに新しく業績を付け替えるには、番号を振り直さなくてはなりません。

5件10件なら手作業でも良いですが、だんだん大変になってきます。

今回必要となった形式は、
著書名（全員）・論文題目・雑誌名・巻号・頁（初めから最終頁まで記入）・発行年（西暦）
という形式です。また、新しいものから古いものに遡って、番号をつける必要があります。

以前作ったxml形式のpubmedデータを取り扱うための3つのクラスを使って解決したいと思います。またpubmedidのリストから、全xml形式のデータを入手しているものとします。データの入手にについての記事は[こちら](https://qiita.com/yoho/items/64f3cd0317416121ccc1)にあります。

##ページ番号に関する問題
古い論文にはおおよそ、volume(巻)、issue(号)と開始ページー終了ページのデータがあります。号についてはデータがあったりなかったりします。

また古い論文についてはページ数に省略表記が見られる場合が多いです。例えば12340-51とかです。これは開始ページ12340から終了ページ12351まで、を表します。業績リストでこの省略をそのままにするのは良く無い、とする考え方が支配的です(多分)。

ですので12340-51を12340-12351に変換するようにします。ちなみにページ番号は1年を通じた通し番号になっているケースがほとんどです。

以下のメソッドで、ページ番号のフォーマットを変更します。

```py
def correctedPageNumber(pageNumberString):
    if pageNumberString is None:
        return None

    #そもそもハイフンがなければ、そのまま返す。
    if not "-" in pageNumberString:
        return pageNumberString
    #ハイフンまでとハイフンまでの後の文字数が同じなら修正の必要なし。そのまま返す。
    list = pageNumberString.split('-')
    if len(list[0]) == len(list[1]):
        return pageNumberString
    
    #足りない桁数を付け足す。
    if len(list[0])>len(list[1]):
        str = list[0][:len(list[0])-len(list[1])] + list[1]
        #print(pageNumberString +" changed to: " + list[0] + "-" + str)
        return list[0] + "-" + str
    return pageNumberString

```


最近の論文は電子ジャーナル化が進んでいて巻号とページについてはデータがないものがあります。この場合、巻号とページに代わって、piiが指定されています。piiはpublisher item identifierの略のようです。piiデータがある場合はそちらを使い、無い場合は巻号とページを出力することにします。

少し前は、ページ番号はないのか？とか聞かれることがありましたが最近すっかりなくなりましたのでpiiが書いてあればOKでしょう。

##論文タイトルについての問題
古い論文にはなかったと思いますが、最近の論文では論文タイトルにあるイタリックなどの修飾がhtmlタグとして含まれていることがあるようです。

例えば、論文タイトル中で遺伝子名が\<i>と<\i>に囲まれているようなケースです。

先に作成したPubmedArticleクラスでは、タイトル情報の取得に使うメソッドは以下のようになっていました。

```py
def title(self):
    element = self.xml.find('MedlineCitation/Article/ArticleTitle')
    if element is not None
        return element.text
    else:
        return None
```
このコードだと、最初のhtmlタグの直前までのみが返されてしまいます。

問題のタイトル部分は、xml形式だと思って解釈すると、例えば以下のようになります。

```
<ArticleTitle>
    text_1
    <i>italic_data</i>
    text_2
    <sub>subscript_data</sub>
    text_3
</ArticleTitle>
```
これを踏まえて、取り出すことにします。
\<ArticleTitle>エレメントをelementとすると、element.textで取得できるのはtext_1の部分です。これ以降の部分は\<ArticleTitle>エレメントのsubelementsを取得して、そのtextおよびtailとして取得します。

最初のsubelementのtextはitalic_dataで、tailはtext_2です。

subelementsはlist(element)で取得できます。コードとしては次のようになりました。htmlタグを残したいときもあるかもしれないので、フラグ(shouldPreserveTag)を作って動作を分けるようにしました。

```py
    def title(self,shouldPreserveTag=True):
        element = self.xml.find('MedlineCitation/Article/ArticleTitle')
        if element is not None:
            #中身にhtmlタグが含まれている時がある。
            titleStr = ""
            titleStr += element.text
            subelements = list(element)
            for subelement in subelements:
                if shouldPreserveTag:
                    titleStr += "<" + subelement.tag + ">" + subelement.text + "</" + subelement.tag + ">" + subelement.tail
                else:
                    titleStr += subelement.text + subelement.tail
            return titleStr
        else:
            return None
```

##クラスメソッドの追加

前回作ったクラスメソッドに、ページ番号、volume、issue、piiを返すメソッドがなかったので追加します。

```py
#17 ページ番号
    def page(self,):
        element = self.xml.find('MedlineCitation/Article/Pagination/MedlinePgn')
        if element is not None:
            return element.text
        else:
            return None

#18 volume
    def volume(self,):
        element = self.xml.find('MedlineCitation/Article/Journal/JournalIssue/Volume')
        if element is not None:
            return element.text
        else:
            return None

#19 issue
    def issue(self,):
        element = self.xml.find('MedlineCitation/Article/Journal/JournalIssue/Issue')
        if element is not None:
            return element.text
        else:
            return None

#20 pii
    def pii(self,):
        for element in self.xml.findall('MedlineCitation/Article/ELocationID'):
            if element.attrib['EIdType'] == 'pii':
                return element.text
        return None

```

##雑誌名の変更
雑誌名は、例えばpubmedデータではJournal of bacteriologyですが、これをJournal of Bacteriologyに変更できるようにしたいと思います。ただしJournal Of Bacteriologyとはしたくありません。OfのOが大文字だとちょっとかっこ悪く見えます。

小文字にしたくないのはとりあず"in","of","for","and","the"です。将来的には追加することになるとおもいます。

変更があった場合は、何を何に変更したのか、print()で出力することにします。

またメソッドを呼ぶときにupperCase=Trueとした時のみ、大文字への変換をすることにしました。

```py
#8 ジャーナル名: str
    def journal(self,upperCase=False):
        element = self.xml.find('MedlineCitation/Article/Journal/Title')
        if element is not None:
            if upperCase:
                changedJournalName = []
                whiteList = ["in","of","for","and","the"]
                journalNameWords = element.text.split()
                flag = False
                for word in journalNameWords:
                    if not word in whiteList and word[0] in "abcdefghjjklmnopqrstuvwxyz": 
                        changedJournalName.append(word.title())
                        flag = True
                    else:
                        changedJournalName.append(word)
                a = " ".join(changedJournalName)
                if flag:
                    print(element.text + " changed to \n" + a + "\n\n")
                return a
            else:
                return element.text
        else:
            return None

```


##オブジェクトのソート用クラスメソッドの追加 
論文を出版年月でソートする必要があるので、ソート用の特殊メソッド\_\_lt\_\_()をPubmedArticleクラスに記述することにします。

このメソッドの中では、出版年が同じ場合は、出版月を比較します。

出版月の記載方式はしっかり定まっていないようで、05であったりMayであったりするようです。辞書を作って、数字に変換します。辞書に無い場合は、その年最初の出版とすることにします(辞書へのアクセスをget()で行い、この時デフォルト値0を指定します)。

```py
#21並べ替えのための特殊メソッド
    def __lt__(self, other):
        if self.year() ==  other.year():
            dictionary = {
                    "1":1,"2":2,"3":3,"4":4,"5":5,"6":6,"7":7,"8":8,"9":9,"10":10,"11":11,"12":12,
                    "01":1,"02":2,"03":3,"04":4,"05":5,"06":6,"07":7,"08":8,"09":9,
                "Jan":1,"Feb":2,"Mar":3,"Apr":4,"May":5,"Jun":6,"Jul":7,"Aug":8,"Sep":9,"Oct":10,"Nov":11,"Dec":12
            }
            return dictionary.get(self.month(),0) < dictionary.get(other.month(),0)
        else:
            return int(self.year()) < int(other.year())

```

##データの整形用メソッド
piiがあればpiiを出力(巻、号、ページは出力しない)。
号(issume)がない場合は、巻+ページ番号、ある場合は巻(号)ページ番号とする。
doiがある場合は最後に付け足すことにする。

```py
def stringDataFromArtcicle(article):
    str = ""
    authorStrings = []
    for author in article.humanAuthors:
        authorStrings.append( author.foreName() + " " + author.lastName() )
    str += ", ".join(authorStrings)+". " +article.title() + " " + article.journal() + ". "

    if article.pii() is None:
        #volume (issue) pageとするか
        #volume pageとするかの二択
        if article.volume() is None or article.page() is None:
            str += "Either or both of volume number and page number is provided!"
        elif article.issue() is not None:
            str += "Volume " + article.volume() + "(" + article.issue() + ") " + "p" + correctedPageNumber(article.page())+ "(" + article.year() + ")" 
        else:
            str += "Volume " + article.volume() + " " + "p" + correctedPageNumber(article.page())+"(" + article.year() + ")" 
    else:
        str += article.pii() + " (" + article.year() + ")"

    if article.doi() is not None:
        str += " doi:" + article.doi()
    str += "\n"
    return str
```


これでデータを整形する準備はできました。xmlデータはファイルとして持っています([前の記事](https://qiita.com/yoho/items/64f3cd0317416121ccc1)を参照)。


```py
import xml.etree.ElementTree as ET

test_data = open("/Users/yoho/myPublications.xml", "r")
contents = test_data.read()
root = ET.fromstring(contents)
allArticles = root.findall('PubmedArticle')

#出版年で並び替えたリストを作成
publications = []
for pubmedArticleElement in allArticles:
    p = PubmedArticle(pubmedArticleElement)
    publications.append(p)
publications.sort(reverse=True)

#論文データを整形。連番をつける。
result = ""
counter1 = 1
for p in publications:
    result += str(counter1) + " " +  stringDataFromArtcicle(p)
    counter1 += 1

print(result)
```
これで書き出せました。
#終わりに
これで論文リストが簡単に作れるようになりました。ただしやっぱりタイトル中の単語を必要に応じて斜体にしたり、著者名にアンダーラインを引いたりとか、手作業がたくさん発生してしまいますね。html形式で出力するようにすればよかったかもしれませんが、斜体にするべき箇所は、特に古い論文ではpubmedデータに含まれておらず、どうしても手作業ということになってしまいます。