We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Volume links are 404
The "URL" field on volume pages is 404, e.g., https://www.aclweb.org/anthology/2020.acl-main.

![image](https://user-images.githubusercontent.com/455256/93599234-cccd5300-f98b-11ea-838c-fd6a0a13c3de.png)

We need to either (a) update `.htaccess` to recognize and redirect volume links or (b) update the URL to https://www.aclweb.org/anthology/volumes/2020.acl-main

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `bin/anthology/papers.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 #
3 # Copyright 2019 Marcel Bollmann <marcel@bollmann.me>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 #     http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 
17 import iso639
18 import logging as log
19 from .utils import (
20     build_anthology_id,
21     parse_element,
22     infer_attachment_url,
23     remove_extra_whitespace,
24     is_journal,
25     is_volume_id,
26 )
27 from . import data
28 
29 # For BibTeX export
30 from .formatter import bibtex_encode, bibtex_make_entry
31 
32 
33 class Paper:
34     def __init__(self, paper_id, ingest_date, volume, formatter):
35         self.parent_volume = volume
36         self.formatter = formatter
37         self._id = paper_id
38         self._ingest_date = ingest_date
39         self._bibkey = False
40         self.is_volume = paper_id == "0"
41 
42         # initialize metadata with keys inherited from volume
43         self.attrib = {}
44         for key, value in volume.attrib.items():
45             # Only inherit 'editor' for frontmatter
46             if (key == "editor" and not self.is_volume) or key in (
47                 "collection_id",
48                 "booktitle",
49                 "id",
50                 "meta_data",
51                 "meta_journal_title",
52                 "meta_volume",
53                 "meta_issue",
54                 "sigs",
55                 "venues",
56                 "meta_date",
57                 "url",
58                 "pdf",
59             ):
60                 continue
61 
62             self.attrib[key] = value
63 
64     def from_xml(xml_element, *args):
65         ingest_date = xml_element.get("ingest-date", data.UNKNOWN_INGEST_DATE)
66 
67         # Default to paper ID "0" (for front matter)
68         paper = Paper(xml_element.get("id", "0"), ingest_date, *args)
69 
70         # Set values from parsing the XML element (overwriting
71         # and changing some initialized from the volume metadata)
72         for key, value in parse_element(xml_element).items():
73             if key == "author" and "editor" in paper.attrib:
74                 del paper.attrib["editor"]
75             paper.attrib[key] = value
76 
77         # Frontmatter title is the volume 'booktitle'
78         if paper.is_volume:
79             paper.attrib["xml_title"] = paper.attrib["xml_booktitle"]
80             paper.attrib["xml_title"].tag = "title"
81 
82         # Remove booktitle for frontmatter and journals
83         if paper.is_volume or is_journal(paper.full_id):
84             del paper.attrib["xml_booktitle"]
85 
86         # Expand URLs with paper ID
87         for tag in ("revision", "erratum"):
88             if tag in paper.attrib:
89                 for item in paper.attrib[tag]:
90                     if not item["url"].startswith(paper.full_id):
91                         log.error(
92                             "{} must begin with paper ID '{}', but is '{}'".format(
93                                 tag, paper.full_id, item["url"]
94                             )
95                         )
96                     item["url"] = data.ANTHOLOGY_PDF.format(item["url"])
97 
98         if "attachment" in paper.attrib:
99             for item in paper.attrib["attachment"]:
100                 item["url"] = infer_attachment_url(item["url"], paper.full_id)
101 
102         paper.attrib["title"] = paper.get_title("plain")
103         paper.attrib["booktitle"] = paper.get_booktitle("plain")
104 
105         if "editor" in paper.attrib:
106             if paper.is_volume:
107                 if "author" in paper.attrib:
108                     log.warn(
109                         "Paper {} has both <editor> and <author>; ignoring <author>".format(
110                             paper.full_id
111                         )
112                     )
113                 # Proceedings editors are considered authors for their front matter
114                 paper.attrib["author"] = paper.attrib["editor"]
115                 del paper.attrib["editor"]
116             else:
117                 log.warn(
118                     "Paper {} has <editor> but is not a proceedings volume; ignoring <editor>".format(
119                         paper.full_id
120                     )
121                 )
122         if "pages" in paper.attrib:
123             if paper.attrib["pages"] is not None:
124                 paper._interpret_pages()
125             else:
126                 del paper.attrib["pages"]
127 
128         if "author" in paper.attrib:
129             paper.attrib["author_string"] = ", ".join(
130                 [x[0].full for x in paper.attrib["author"]]
131             )
132 
133         paper.attrib["thumbnail"] = data.ANTHOLOGY_THUMBNAIL.format(paper.full_id)
134 
135         return paper
136 
137     def _interpret_pages(self):
138         """Splits up 'pages' field into first and last page, if possible.
139 
140         This is used for metadata in the generated HTML."""
141         for s in ("--", "-", "–"):
142             if self.attrib["pages"].count(s) == 1:
143                 self.attrib["page_first"], self.attrib["page_last"] = self.attrib[
144                     "pages"
145                 ].split(s)
146                 self.attrib["pages"] = self.attrib["pages"].replace(s, "–")
147                 return
148 
149     @property
150     def ingest_date(self):
151         """Inherit publication date from parent, but self overrides. May be undefined."""
152         if self._ingest_date:
153             return self._ingest_date
154         if self.parent_volume:
155             return self.parent_volume.ingest_date
156         return data.UNKNOWN_INGEST_DATE
157 
158     @property
159     def collection_id(self):
160         return self.parent_volume.collection_id
161 
162     @property
163     def volume_id(self):
164         return self.parent_volume.volume_id
165 
166     @property
167     def paper_id(self):
168         return self._id
169 
170     @property
171     def full_id(self):
172         return self.anthology_id
173 
174     @property
175     def anthology_id(self):
176         return build_anthology_id(self.collection_id, self.volume_id, self.paper_id)
177 
178     @property
179     def bibkey(self):
180         if not self._bibkey:
181             self._bibkey = self.full_id  # fallback
182         return self._bibkey
183 
184     @bibkey.setter
185     def bibkey(self, value):
186         self._bibkey = value
187 
188     @property
189     def bibtype(self):
190         if is_journal(self.full_id):
191             return "article"
192         elif self.is_volume:
193             return "proceedings"
194         else:
195             return "inproceedings"
196 
197     @property
198     def parent_volume_id(self):
199         if self.parent_volume is not None:
200             return self.parent_volume.full_id
201         return None
202 
203     @property
204     def has_abstract(self):
205         return "xml_abstract" in self.attrib
206 
207     @property
208     def isbn(self):
209         return self.attrib.get("isbn", None)
210 
211     @property
212     def language(self):
213         """Returns the ISO-639 language code, if present"""
214         return self.attrib.get("language", None)
215 
216     def get(self, name, default=None):
217         try:
218             return self.attrib[name]
219         except KeyError:
220             return default
221 
222     def get_title(self, form="xml"):
223         """Returns the paper title, optionally formatting it.
224 
225         Accepted formats:
226           - xml:   Include any contained XML tags unchanged
227           - plain: Strip all XML tags, returning only plain text
228           - html:  Convert XML tags into valid HTML tags
229           - latex: Convert XML tags into LaTeX commands
230         """
231         return self.formatter(self.get("xml_title"), form)
232 
233     def get_abstract(self, form="xml"):
234         """Returns the abstract, optionally formatting it.
235 
236         See `get_title()` for details.
237         """
238         return self.formatter(self.get("xml_abstract"), form, allow_url=True)
239 
240     def get_booktitle(self, form="xml", default=""):
241         """Returns the booktitle, optionally formatting it.
242 
243         See `get_title()` for details.
244         """
245         if "xml_booktitle" in self.attrib:
246             return self.formatter(self.get("xml_booktitle"), form)
247         elif self.parent_volume is not None:
248             return self.parent_volume.get("title")
249         else:
250             return default
251 
252     def as_bibtex(self, concise=False):
253         """Return the BibTeX entry for this paper."""
254         # Build BibTeX entry
255         bibkey = self.bibkey
256         bibtype = self.bibtype
257         entries = [("title", self.get_title(form="latex"))]
258         for people in ("author", "editor"):
259             if people in self.attrib:
260                 entries.append(
261                     (people, "  and  ".join(p.as_bibtex() for p, _ in self.get(people)))
262                 )
263         if is_journal(self.full_id):
264             entries.append(
265                 ("journal", bibtex_encode(self.parent_volume.get("meta_journal_title")))
266             )
267             journal_volume = self.parent_volume.get(
268                 "meta_volume", self.parent_volume.get("volume")
269             )
270             if journal_volume:
271                 entries.append(("volume", journal_volume))
272             journal_issue = self.parent_volume.get(
273                 "meta_issue", self.parent_volume.get("issue")
274             )
275             if journal_issue:
276                 entries.append(("number", journal_issue))
277         else:
278             # not is_journal(self.full_id)
279             if "xml_booktitle" in self.attrib:
280                 entries.append(("booktitle", self.get_booktitle(form="latex")))
281             elif bibtype != "proceedings":
282                 entries.append(("booktitle", self.parent_volume.get_title(form="latex")))
283         for entry in ("month", "year", "address", "publisher", "note"):
284             if self.get(entry) is not None:
285                 entries.append((entry, bibtex_encode(self.get(entry))))
286         for entry in ("url", "doi"):
287             if entry in self.attrib:
288                 # don't want latex escapes such as
289                 # doi = "10.1162/coli{\_}a{\_}00008",
290                 entries.append((entry, self.get(entry)))
291         if "pages" in self.attrib:
292             entries.append(("pages", self.get("pages").replace("–", "--")))
293         if "xml_abstract" in self.attrib and not concise:
294             entries.append(("abstract", self.get_abstract(form="latex")))
295         if self.language:
296             entries.append(("language", iso639.languages.get(part3=self.language).name))
297         if self.isbn:
298             entries.append(("ISBN", self.isbn))
299 
300         # Serialize it
301         return bibtex_make_entry(bibkey, bibtype, entries)
302 
303     def as_dict(self):
304         value = self.attrib
305         value["paper_id"] = self.paper_id
306         value["parent_volume_id"] = self.parent_volume_id
307         value["bibkey"] = self.bibkey
308         value["bibtype"] = self.bibtype
309         return value
310 
311     def items(self):
312         return self.attrib.items()
313
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/bin/anthology/papers.py b/bin/anthology/papers.py
--- a/bin/anthology/papers.py
+++ b/bin/anthology/papers.py
@@ -19,6 +19,7 @@
 from .utils import (
     build_anthology_id,
     parse_element,
+    infer_url,
     infer_attachment_url,
     remove_extra_whitespace,
     is_journal,
@@ -79,6 +80,10 @@
             paper.attrib["xml_title"] = paper.attrib["xml_booktitle"]
             paper.attrib["xml_title"].tag = "title"
 
+        # Create URL field if not present. But see https://github.com/acl-org/acl-anthology/issues/997.
+        if "url" not in paper.attrib:
+            paper.attrib["url"] = infer_url(paper.full_id)
+
         # Remove booktitle for frontmatter and journals
         if paper.is_volume or is_journal(paper.full_id):
             del paper.attrib["xml_booktitle"]

{"golden_diff": "diff --git a/bin/anthology/papers.py b/bin/anthology/papers.py\n--- a/bin/anthology/papers.py\n+++ b/bin/anthology/papers.py\n@@ -19,6 +19,7 @@\n from .utils import (\n     build_anthology_id,\n     parse_element,\n+    infer_url,\n     infer_attachment_url,\n     remove_extra_whitespace,\n     is_journal,\n@@ -79,6 +80,10 @@\n             paper.attrib[\"xml_title\"] = paper.attrib[\"xml_booktitle\"]\n             paper.attrib[\"xml_title\"].tag = \"title\"\n \n+        # Create URL field if not present. But see https://github.com/acl-org/acl-anthology/issues/997.\n+        if \"url\" not in paper.attrib:\n+            paper.attrib[\"url\"] = infer_url(paper.full_id)\n+\n         # Remove booktitle for frontmatter and journals\n         if paper.is_volume or is_journal(paper.full_id):\n             del paper.attrib[\"xml_booktitle\"]\n", "issue": "Volume links are 404\nThe \"URL\" field on volume pages is 404, e.g., https://www.aclweb.org/anthology/2020.acl-main.\r\n\r\n![image](https://user-images.githubusercontent.com/455256/93599234-cccd5300-f98b-11ea-838c-fd6a0a13c3de.png)\r\n\r\nWe need to either (a) update `.htaccess` to recognize and redirect volume links or (b) update the URL to https://www.aclweb.org/anthology/volumes/2020.acl-main\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2019 Marcel Bollmann <marcel@bollmann.me>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport iso639\nimport logging as log\nfrom .utils import (\n    build_anthology_id,\n    parse_element,\n    infer_attachment_url,\n    remove_extra_whitespace,\n    is_journal,\n    is_volume_id,\n)\nfrom . import data\n\n# For BibTeX export\nfrom .formatter import bibtex_encode, bibtex_make_entry\n\n\nclass Paper:\n    def __init__(self, paper_id, ingest_date, volume, formatter):\n        self.parent_volume = volume\n        self.formatter = formatter\n        self._id = paper_id\n        self._ingest_date = ingest_date\n        self._bibkey = False\n        self.is_volume = paper_id == \"0\"\n\n        # initialize metadata with keys inherited from volume\n        self.attrib = {}\n        for key, value in volume.attrib.items():\n            # Only inherit 'editor' for frontmatter\n            if (key == \"editor\" and not self.is_volume) or key in (\n                \"collection_id\",\n                \"booktitle\",\n                \"id\",\n                \"meta_data\",\n                \"meta_journal_title\",\n                \"meta_volume\",\n                \"meta_issue\",\n                \"sigs\",\n                \"venues\",\n                \"meta_date\",\n                \"url\",\n                \"pdf\",\n            ):\n                continue\n\n            self.attrib[key] = value\n\n    def from_xml(xml_element, *args):\n        ingest_date = xml_element.get(\"ingest-date\", data.UNKNOWN_INGEST_DATE)\n\n        # Default to paper ID \"0\" (for front matter)\n        paper = Paper(xml_element.get(\"id\", \"0\"), ingest_date, *args)\n\n        # Set values from parsing the XML element (overwriting\n        # and changing some initialized from the volume metadata)\n        for key, value in parse_element(xml_element).items():\n            if key == \"author\" and \"editor\" in paper.attrib:\n                del paper.attrib[\"editor\"]\n            paper.attrib[key] = value\n\n        # Frontmatter title is the volume 'booktitle'\n        if paper.is_volume:\n            paper.attrib[\"xml_title\"] = paper.attrib[\"xml_booktitle\"]\n            paper.attrib[\"xml_title\"].tag = \"title\"\n\n        # Remove booktitle for frontmatter and journals\n        if paper.is_volume or is_journal(paper.full_id):\n            del paper.attrib[\"xml_booktitle\"]\n\n        # Expand URLs with paper ID\n        for tag in (\"revision\", \"erratum\"):\n            if tag in paper.attrib:\n                for item in paper.attrib[tag]:\n                    if not item[\"url\"].startswith(paper.full_id):\n                        log.error(\n                            \"{} must begin with paper ID '{}', but is '{}'\".format(\n                                tag, paper.full_id, item[\"url\"]\n                            )\n                        )\n                    item[\"url\"] = data.ANTHOLOGY_PDF.format(item[\"url\"])\n\n        if \"attachment\" in paper.attrib:\n            for item in paper.attrib[\"attachment\"]:\n                item[\"url\"] = infer_attachment_url(item[\"url\"], paper.full_id)\n\n        paper.attrib[\"title\"] = paper.get_title(\"plain\")\n        paper.attrib[\"booktitle\"] = paper.get_booktitle(\"plain\")\n\n        if \"editor\" in paper.attrib:\n            if paper.is_volume:\n                if \"author\" in paper.attrib:\n                    log.warn(\n                        \"Paper {} has both <editor> and <author>; ignoring <author>\".format(\n                            paper.full_id\n                        )\n                    )\n                # Proceedings editors are considered authors for their front matter\n                paper.attrib[\"author\"] = paper.attrib[\"editor\"]\n                del paper.attrib[\"editor\"]\n            else:\n                log.warn(\n                    \"Paper {} has <editor> but is not a proceedings volume; ignoring <editor>\".format(\n                        paper.full_id\n                    )\n                )\n        if \"pages\" in paper.attrib:\n            if paper.attrib[\"pages\"] is not None:\n                paper._interpret_pages()\n            else:\n                del paper.attrib[\"pages\"]\n\n        if \"author\" in paper.attrib:\n            paper.attrib[\"author_string\"] = \", \".join(\n                [x[0].full for x in paper.attrib[\"author\"]]\n            )\n\n        paper.attrib[\"thumbnail\"] = data.ANTHOLOGY_THUMBNAIL.format(paper.full_id)\n\n        return paper\n\n    def _interpret_pages(self):\n        \"\"\"Splits up 'pages' field into first and last page, if possible.\n\n        This is used for metadata in the generated HTML.\"\"\"\n        for s in (\"--\", \"-\", \"\u2013\"):\n            if self.attrib[\"pages\"].count(s) == 1:\n                self.attrib[\"page_first\"], self.attrib[\"page_last\"] = self.attrib[\n                    \"pages\"\n                ].split(s)\n                self.attrib[\"pages\"] = self.attrib[\"pages\"].replace(s, \"\u2013\")\n                return\n\n    @property\n    def ingest_date(self):\n        \"\"\"Inherit publication date from parent, but self overrides. May be undefined.\"\"\"\n        if self._ingest_date:\n            return self._ingest_date\n        if self.parent_volume:\n            return self.parent_volume.ingest_date\n        return data.UNKNOWN_INGEST_DATE\n\n    @property\n    def collection_id(self):\n        return self.parent_volume.collection_id\n\n    @property\n    def volume_id(self):\n        return self.parent_volume.volume_id\n\n    @property\n    def paper_id(self):\n        return self._id\n\n    @property\n    def full_id(self):\n        return self.anthology_id\n\n    @property\n    def anthology_id(self):\n        return build_anthology_id(self.collection_id, self.volume_id, self.paper_id)\n\n    @property\n    def bibkey(self):\n        if not self._bibkey:\n            self._bibkey = self.full_id  # fallback\n        return self._bibkey\n\n    @bibkey.setter\n    def bibkey(self, value):\n        self._bibkey = value\n\n    @property\n    def bibtype(self):\n        if is_journal(self.full_id):\n            return \"article\"\n        elif self.is_volume:\n            return \"proceedings\"\n        else:\n            return \"inproceedings\"\n\n    @property\n    def parent_volume_id(self):\n        if self.parent_volume is not None:\n            return self.parent_volume.full_id\n        return None\n\n    @property\n    def has_abstract(self):\n        return \"xml_abstract\" in self.attrib\n\n    @property\n    def isbn(self):\n        return self.attrib.get(\"isbn\", None)\n\n    @property\n    def language(self):\n        \"\"\"Returns the ISO-639 language code, if present\"\"\"\n        return self.attrib.get(\"language\", None)\n\n    def get(self, name, default=None):\n        try:\n            return self.attrib[name]\n        except KeyError:\n            return default\n\n    def get_title(self, form=\"xml\"):\n        \"\"\"Returns the paper title, optionally formatting it.\n\n        Accepted formats:\n          - xml:   Include any contained XML tags unchanged\n          - plain: Strip all XML tags, returning only plain text\n          - html:  Convert XML tags into valid HTML tags\n          - latex: Convert XML tags into LaTeX commands\n        \"\"\"\n        return self.formatter(self.get(\"xml_title\"), form)\n\n    def get_abstract(self, form=\"xml\"):\n        \"\"\"Returns the abstract, optionally formatting it.\n\n        See `get_title()` for details.\n        \"\"\"\n        return self.formatter(self.get(\"xml_abstract\"), form, allow_url=True)\n\n    def get_booktitle(self, form=\"xml\", default=\"\"):\n        \"\"\"Returns the booktitle, optionally formatting it.\n\n        See `get_title()` for details.\n        \"\"\"\n        if \"xml_booktitle\" in self.attrib:\n            return self.formatter(self.get(\"xml_booktitle\"), form)\n        elif self.parent_volume is not None:\n            return self.parent_volume.get(\"title\")\n        else:\n            return default\n\n    def as_bibtex(self, concise=False):\n        \"\"\"Return the BibTeX entry for this paper.\"\"\"\n        # Build BibTeX entry\n        bibkey = self.bibkey\n        bibtype = self.bibtype\n        entries = [(\"title\", self.get_title(form=\"latex\"))]\n        for people in (\"author\", \"editor\"):\n            if people in self.attrib:\n                entries.append(\n                    (people, \"  and  \".join(p.as_bibtex() for p, _ in self.get(people)))\n                )\n        if is_journal(self.full_id):\n            entries.append(\n                (\"journal\", bibtex_encode(self.parent_volume.get(\"meta_journal_title\")))\n            )\n            journal_volume = self.parent_volume.get(\n                \"meta_volume\", self.parent_volume.get(\"volume\")\n            )\n            if journal_volume:\n                entries.append((\"volume\", journal_volume))\n            journal_issue = self.parent_volume.get(\n                \"meta_issue\", self.parent_volume.get(\"issue\")\n            )\n            if journal_issue:\n                entries.append((\"number\", journal_issue))\n        else:\n            # not is_journal(self.full_id)\n            if \"xml_booktitle\" in self.attrib:\n                entries.append((\"booktitle\", self.get_booktitle(form=\"latex\")))\n            elif bibtype != \"proceedings\":\n                entries.append((\"booktitle\", self.parent_volume.get_title(form=\"latex\")))\n        for entry in (\"month\", \"year\", \"address\", \"publisher\", \"note\"):\n            if self.get(entry) is not None:\n                entries.append((entry, bibtex_encode(self.get(entry))))\n        for entry in (\"url\", \"doi\"):\n            if entry in self.attrib:\n                # don't want latex escapes such as\n                # doi = \"10.1162/coli{\\_}a{\\_}00008\",\n                entries.append((entry, self.get(entry)))\n        if \"pages\" in self.attrib:\n            entries.append((\"pages\", self.get(\"pages\").replace(\"\u2013\", \"--\")))\n        if \"xml_abstract\" in self.attrib and not concise:\n            entries.append((\"abstract\", self.get_abstract(form=\"latex\")))\n        if self.language:\n            entries.append((\"language\", iso639.languages.get(part3=self.language).name))\n        if self.isbn:\n            entries.append((\"ISBN\", self.isbn))\n\n        # Serialize it\n        return bibtex_make_entry(bibkey, bibtype, entries)\n\n    def as_dict(self):\n        value = self.attrib\n        value[\"paper_id\"] = self.paper_id\n        value[\"parent_volume_id\"] = self.parent_volume_id\n        value[\"bibkey\"] = self.bibkey\n        value[\"bibtype\"] = self.bibtype\n        return value\n\n    def items(self):\n        return self.attrib.items()\n", "path": "bin/anthology/papers.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2019 Marcel Bollmann <marcel@bollmann.me>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport iso639\nimport logging as log\nfrom .utils import (\n    build_anthology_id,\n    parse_element,\n    infer_url,\n    infer_attachment_url,\n    remove_extra_whitespace,\n    is_journal,\n    is_volume_id,\n)\nfrom . import data\n\n# For BibTeX export\nfrom .formatter import bibtex_encode, bibtex_make_entry\n\n\nclass Paper:\n    def __init__(self, paper_id, ingest_date, volume, formatter):\n        self.parent_volume = volume\n        self.formatter = formatter\n        self._id = paper_id\n        self._ingest_date = ingest_date\n        self._bibkey = False\n        self.is_volume = paper_id == \"0\"\n\n        # initialize metadata with keys inherited from volume\n        self.attrib = {}\n        for key, value in volume.attrib.items():\n            # Only inherit 'editor' for frontmatter\n            if (key == \"editor\" and not self.is_volume) or key in (\n                \"collection_id\",\n                \"booktitle\",\n                \"id\",\n                \"meta_data\",\n                \"meta_journal_title\",\n                \"meta_volume\",\n                \"meta_issue\",\n                \"sigs\",\n                \"venues\",\n                \"meta_date\",\n                \"url\",\n                \"pdf\",\n            ):\n                continue\n\n            self.attrib[key] = value\n\n    def from_xml(xml_element, *args):\n        ingest_date = xml_element.get(\"ingest-date\", data.UNKNOWN_INGEST_DATE)\n\n        # Default to paper ID \"0\" (for front matter)\n        paper = Paper(xml_element.get(\"id\", \"0\"), ingest_date, *args)\n\n        # Set values from parsing the XML element (overwriting\n        # and changing some initialized from the volume metadata)\n        for key, value in parse_element(xml_element).items():\n            if key == \"author\" and \"editor\" in paper.attrib:\n                del paper.attrib[\"editor\"]\n            paper.attrib[key] = value\n\n        # Frontmatter title is the volume 'booktitle'\n        if paper.is_volume:\n            paper.attrib[\"xml_title\"] = paper.attrib[\"xml_booktitle\"]\n            paper.attrib[\"xml_title\"].tag = \"title\"\n\n        # Create URL field if not present. But see https://github.com/acl-org/acl-anthology/issues/997.\n        if \"url\" not in paper.attrib:\n            paper.attrib[\"url\"] = infer_url(paper.full_id)\n\n        # Remove booktitle for frontmatter and journals\n        if paper.is_volume or is_journal(paper.full_id):\n            del paper.attrib[\"xml_booktitle\"]\n\n        # Expand URLs with paper ID\n        for tag in (\"revision\", \"erratum\"):\n            if tag in paper.attrib:\n                for item in paper.attrib[tag]:\n                    if not item[\"url\"].startswith(paper.full_id):\n                        log.error(\n                            \"{} must begin with paper ID '{}', but is '{}'\".format(\n                                tag, paper.full_id, item[\"url\"]\n                            )\n                        )\n                    item[\"url\"] = data.ANTHOLOGY_PDF.format(item[\"url\"])\n\n        if \"attachment\" in paper.attrib:\n            for item in paper.attrib[\"attachment\"]:\n                item[\"url\"] = infer_attachment_url(item[\"url\"], paper.full_id)\n\n        paper.attrib[\"title\"] = paper.get_title(\"plain\")\n        paper.attrib[\"booktitle\"] = paper.get_booktitle(\"plain\")\n\n        if \"editor\" in paper.attrib:\n            if paper.is_volume:\n                if \"author\" in paper.attrib:\n                    log.warn(\n                        \"Paper {} has both <editor> and <author>; ignoring <author>\".format(\n                            paper.full_id\n                        )\n                    )\n                # Proceedings editors are considered authors for their front matter\n                paper.attrib[\"author\"] = paper.attrib[\"editor\"]\n                del paper.attrib[\"editor\"]\n            else:\n                log.warn(\n                    \"Paper {} has <editor> but is not a proceedings volume; ignoring <editor>\".format(\n                        paper.full_id\n                    )\n                )\n        if \"pages\" in paper.attrib:\n            if paper.attrib[\"pages\"] is not None:\n                paper._interpret_pages()\n            else:\n                del paper.attrib[\"pages\"]\n\n        if \"author\" in paper.attrib:\n            paper.attrib[\"author_string\"] = \", \".join(\n                [x[0].full for x in paper.attrib[\"author\"]]\n            )\n\n        paper.attrib[\"thumbnail\"] = data.ANTHOLOGY_THUMBNAIL.format(paper.full_id)\n\n        return paper\n\n    def _interpret_pages(self):\n        \"\"\"Splits up 'pages' field into first and last page, if possible.\n\n        This is used for metadata in the generated HTML.\"\"\"\n        for s in (\"--\", \"-\", \"\u2013\"):\n            if self.attrib[\"pages\"].count(s) == 1:\n                self.attrib[\"page_first\"], self.attrib[\"page_last\"] = self.attrib[\n                    \"pages\"\n                ].split(s)\n                self.attrib[\"pages\"] = self.attrib[\"pages\"].replace(s, \"\u2013\")\n                return\n\n    @property\n    def ingest_date(self):\n        \"\"\"Inherit publication date from parent, but self overrides. May be undefined.\"\"\"\n        if self._ingest_date:\n            return self._ingest_date\n        if self.parent_volume:\n            return self.parent_volume.ingest_date\n        return data.UNKNOWN_INGEST_DATE\n\n    @property\n    def collection_id(self):\n        return self.parent_volume.collection_id\n\n    @property\n    def volume_id(self):\n        return self.parent_volume.volume_id\n\n    @property\n    def paper_id(self):\n        return self._id\n\n    @property\n    def full_id(self):\n        return self.anthology_id\n\n    @property\n    def anthology_id(self):\n        return build_anthology_id(self.collection_id, self.volume_id, self.paper_id)\n\n    @property\n    def bibkey(self):\n        if not self._bibkey:\n            self._bibkey = self.full_id  # fallback\n        return self._bibkey\n\n    @bibkey.setter\n    def bibkey(self, value):\n        self._bibkey = value\n\n    @property\n    def bibtype(self):\n        if is_journal(self.full_id):\n            return \"article\"\n        elif self.is_volume:\n            return \"proceedings\"\n        else:\n            return \"inproceedings\"\n\n    @property\n    def parent_volume_id(self):\n        if self.parent_volume is not None:\n            return self.parent_volume.full_id\n        return None\n\n    @property\n    def has_abstract(self):\n        return \"xml_abstract\" in self.attrib\n\n    @property\n    def isbn(self):\n        return self.attrib.get(\"isbn\", None)\n\n    @property\n    def language(self):\n        \"\"\"Returns the ISO-639 language code, if present\"\"\"\n        return self.attrib.get(\"language\", None)\n\n    def get(self, name, default=None):\n        try:\n            return self.attrib[name]\n        except KeyError:\n            return default\n\n    def get_title(self, form=\"xml\"):\n        \"\"\"Returns the paper title, optionally formatting it.\n\n        Accepted formats:\n          - xml:   Include any contained XML tags unchanged\n          - plain: Strip all XML tags, returning only plain text\n          - html:  Convert XML tags into valid HTML tags\n          - latex: Convert XML tags into LaTeX commands\n        \"\"\"\n        return self.formatter(self.get(\"xml_title\"), form)\n\n    def get_abstract(self, form=\"xml\"):\n        \"\"\"Returns the abstract, optionally formatting it.\n\n        See `get_title()` for details.\n        \"\"\"\n        return self.formatter(self.get(\"xml_abstract\"), form, allow_url=True)\n\n    def get_booktitle(self, form=\"xml\", default=\"\"):\n        \"\"\"Returns the booktitle, optionally formatting it.\n\n        See `get_title()` for details.\n        \"\"\"\n        if \"xml_booktitle\" in self.attrib:\n            return self.formatter(self.get(\"xml_booktitle\"), form)\n        elif self.parent_volume is not None:\n            return self.parent_volume.get(\"title\")\n        else:\n            return default\n\n    def as_bibtex(self, concise=False):\n        \"\"\"Return the BibTeX entry for this paper.\"\"\"\n        # Build BibTeX entry\n        bibkey = self.bibkey\n        bibtype = self.bibtype\n        entries = [(\"title\", self.get_title(form=\"latex\"))]\n        for people in (\"author\", \"editor\"):\n            if people in self.attrib:\n                entries.append(\n                    (people, \"  and  \".join(p.as_bibtex() for p, _ in self.get(people)))\n                )\n        if is_journal(self.full_id):\n            entries.append(\n                (\"journal\", bibtex_encode(self.parent_volume.get(\"meta_journal_title\")))\n            )\n            journal_volume = self.parent_volume.get(\n                \"meta_volume\", self.parent_volume.get(\"volume\")\n            )\n            if journal_volume:\n                entries.append((\"volume\", journal_volume))\n            journal_issue = self.parent_volume.get(\n                \"meta_issue\", self.parent_volume.get(\"issue\")\n            )\n            if journal_issue:\n                entries.append((\"number\", journal_issue))\n        else:\n            # not is_journal(self.full_id)\n            if \"xml_booktitle\" in self.attrib:\n                entries.append((\"booktitle\", self.get_booktitle(form=\"latex\")))\n            elif bibtype != \"proceedings\":\n                entries.append((\"booktitle\", self.parent_volume.get_title(form=\"latex\")))\n        for entry in (\"month\", \"year\", \"address\", \"publisher\", \"note\"):\n            if self.get(entry) is not None:\n                entries.append((entry, bibtex_encode(self.get(entry))))\n        for entry in (\"url\", \"doi\"):\n            if entry in self.attrib:\n                # don't want latex escapes such as\n                # doi = \"10.1162/coli{\\_}a{\\_}00008\",\n                entries.append((entry, self.get(entry)))\n        if \"pages\" in self.attrib:\n            entries.append((\"pages\", self.get(\"pages\").replace(\"\u2013\", \"--\")))\n        if \"xml_abstract\" in self.attrib and not concise:\n            entries.append((\"abstract\", self.get_abstract(form=\"latex\")))\n        if self.language:\n            entries.append((\"language\", iso639.languages.get(part3=self.language).name))\n        if self.isbn:\n            entries.append((\"ISBN\", self.isbn))\n\n        # Serialize it\n        return bibtex_make_entry(bibkey, bibtype, entries)\n\n    def as_dict(self):\n        value = self.attrib\n        value[\"paper_id\"] = self.paper_id\n        value[\"parent_volume_id\"] = self.parent_volume_id\n        value[\"bibkey\"] = self.bibkey\n        value[\"bibtype\"] = self.bibtype\n        return value\n\n    def items(self):\n        return self.attrib.items()\n", "path": "bin/anthology/papers.py"}]}