Repository: acl-org/acl-anthology

Issue:
Correction
author correction

removing  line 1056:  <author><first>Isabelle</first><last>Augenstein</last></author>

A corrected PDF is also attched here
[CoAStaL_EmoContext (5).pdf](https://github.com/acl-org/acl-anthology/files/3296956/CoAStaL_EmoContext.5.pdf)




Discussion:

Resolving patch:
diff --git a/bin/add_attachment.py b/bin/add_attachment.py
--- a/bin/add_attachment.py
+++ b/bin/add_attachment.py
@@ -35,59 +35,79 @@
 import sys
 import tempfile
 
-from add_revision import maybe_copy
+from anthology.utils import build_anthology_id, deconstruct_anthology_id
 
 import lxml.etree as ET
 import urllib.request
 
 def main(args):
 
+    print(f'Processing attachment for {args.anthology_id}', file=sys.stderr)
+
     if args.path.startswith('http'):
         _, input_file_path = tempfile.mkstemp()
         try:
-            print('Downloading file from {}'.format(args.path), file=sys.stderr)
+            print('-> Downloading file from {}'.format(args.path), file=sys.stderr)
             with urllib.request.urlopen(args.path) as url, open(input_file_path, mode='wb') as input_file_fh:
                 input_file_fh.write(url.read())
         except ssl.SSLError:
-            print('An SSL error was encountered in downloading the files.', file=sys.stderr)
+            print('-> FATAL: An SSL error was encountered in downloading the files.', file=sys.stderr)
             sys.exit(1)
     else:
         input_file_path = args.path
 
+    collection_id, volume_id, paper_id = deconstruct_anthology_id(args.anthology_id)
+    paper_extension = args.path.split('.')[-1]
+
+    if paper_extension not in ['pdf', 'pptx']:
+        print('-> FATAL: unknown file extension {paper_extension}', file=sys.stderr)
+        sys.exit(1)
+
+    attachment_file_name = f'{args.anthology_id}.{args.type.capitalize()}.{paper_extension}'
+
     # Update XML
-    xml_file = os.path.join(os.path.dirname(sys.argv[0]), '..', 'data', 'xml', f'{volume_id}.xml')
+    xml_file = os.path.join(os.path.dirname(sys.argv[0]), '..', 'data', 'xml', f'{collection_id}.xml')
     tree = ET.parse(xml_file)
+    # add newline to end-of-file if not present
     if not tree.getroot().tail: tree.getroot().tail = '\n'
-    for paper in tree.getroot().findall('paper'):
-        if paper.attrib['id'] == paper_num:
+    paper = tree.getroot().find(f"./volume[@id='{volume_id}']/paper[@id='{paper_id}']")
+    if paper is not None:
+        # Check if attachment already exists
+        for attachment in paper.findall('attachment'):
+            if attachment.text == attachment_file_name:
+                print(f'-> attachment {attachment_file_name} already exists in the XML', file=sys.stderr)
+                break
+        else:
             attachment = ET.Element('attachment')
             attachment.attrib['type'] = args.type
-            attachment.text = file_name
-            attachment.tail = '\n  '  # newline and one level of indent
-            paper.append(attachment)
-            print('Adding attachment node to XML', file=sys.stderr)
+            attachment.text = attachment_file_name
+
+            # Set tails to maintain proper indentation
+            paper[-1].tail += '  '
+            attachment.tail = '\n    '  # newline and two levels of indent
 
+            paper.append(attachment)
             tree.write(xml_file, encoding="UTF-8", xml_declaration=True)
-            break
+            print(f'-> added attachment {attachment_file_name} to the XML', file=sys.stderr)
+
     else:
-        print(f'Fatal: paper ID {paper_id} not found in the Anthology', file=sys.stderr)
+        print(f'-> FATAL: paper not found in the Anthology', file=sys.stderr)
         sys.exit(1)
 
-    volume_letter, year, paper_num = [args.paper_id[0], args.paper_id[1:3], args.paper_id[4:]]
-    volume_id = '{}{}'.format(volume_letter, year)
-    ext = args.path.split('.')[-1]
-
-    file_name = f'{args.paper_id}.{args.type.capitalize()}.{ext}'
-    output_dir = os.path.join(args.attachment_root, volume_letter, volume_id)
-
     # Make sure directory exists
+    output_dir = os.path.join(args.attachment_root, collection_id[0], collection_id)
     if not os.path.exists(output_dir):
-        print('No such directory "{}"'.format(output_dir), file=sys.stderr)
-        sys.exit(1)
+        print(f'-> Creating directory {output_dir}', file=sys.stderr)
+        os.makedirs(output_dir)
 
     # Copy file
-    dest_path = os.path.join(output_dir, file_name)
-    maybe_copy(input_file_path, dest_path, do=True)
+    dest_path = os.path.join(output_dir, attachment_file_name)
+    if os.path.exists(dest_path):
+        print(f'-> target file {dest_path} already in place, refusing to overwrite', file=sys.stderr)
+    else:
+        shutil.copy(input_file_path, dest_path)
+        os.chmod(dest_path, 0o644)
+        print(f'-> copied {input_file_path} to {dest_path} and fixed perms', file=sys.stderr)
 
     # Clean up
     if args.path.startswith('http'):
@@ -96,9 +116,9 @@ def main(args):
 
 if __name__ == '__main__':
     parser = argparse.ArgumentParser()
-    parser.add_argument('paper_id', help='The Antholgoy paper ID (e.g., P18-1001)')
+    parser.add_argument('anthology_id', help='The Anthology ID (e.g., P18-1001)')
     parser.add_argument('path', type=str, help='Path to the attachment (can be URL)')
-    parser.add_argument('type', type=str, choices='poster presentation note software'.split(), help='Attachment type')
+    parser.add_argument('type', type=str, choices='poster presentation note software supplementary'.split(), help='Attachment type')
     parser.add_argument('--attachment-root', '-d', default=os.path.join(os.environ['HOME'], 'anthology-files/attachments'),
                         help='Anthology web directory root.')
     args = parser.parse_args()
diff --git a/bin/add_revision.py b/bin/add_revision.py
--- a/bin/add_revision.py
+++ b/bin/add_revision.py
@@ -44,23 +44,34 @@
 import sys
 import tempfile
 
+from anthology.utils import deconstruct_anthology_id
+from anthology.data import ANTHOLOGY_URL
+
+import lxml.etree as ET
 import urllib.request
 
+
 def maybe_copy(file_from, file_to, do=False):
     if do:
-        print('Copying from {} -> {}'.format(file_from, file_to), file=sys.stderr)
+        print('-> Copying from {} -> {}'.format(file_from, file_to), file=sys.stderr)
         shutil.copy(file_from, file_to)
         os.chmod(file_to, 0o644)
     else:
-        print('DRY RUN: Copying from {} -> {}'.format(file_from, file_to), file=sys.stderr)
+        print('-> DRY RUN: Copying from {} -> {}'.format(file_from, file_to), file=sys.stderr)
+
 
 def main(args):
 
+    change_type = 'erratum' if args.erratum else 'revision'
+    change_letter = 'e' if args.erratum else 'v'
+
+    print(f'Processing {change_type} to {args.anthology_id}...')
+
     # TODO: make sure path exists, or download URL to temp file
     if args.path.startswith('http'):
         _, input_file_path = tempfile.mkstemp()
         try:
-            print('Downloading file from {}'.format(args.path), file=sys.stderr)
+            print(f'-> Downloading file from {args.path}', file=sys.stderr)
             with urllib.request.urlopen(args.path) as url, open(input_file_path, mode='wb') as input_file_fh:
                 input_file_fh.write(url.read())
         except ssl.SSLError:
@@ -69,35 +80,71 @@ def main(args):
     else:
         input_file_path = args.path
 
-    volume_letter, year, paper_num = [args.paper_id[0], args.paper_id[1:3], args.paper_id[4:]]
-    volume_id = '{}{}'.format(volume_letter, year)
+    collection_id, volume_id, paper_id = deconstruct_anthology_id(args.anthology_id)
+    paper_extension = args.path.split('.')[-1]
 
-    file_prefix = os.path.join(args.anthology_dir, volume_letter, volume_id, args.paper_id)
-    output_dir = os.path.dirname(file_prefix)
+    # The new version
+    revno = None
 
-    # Sanity checks
-    if not os.path.exists(output_dir):
-        print('No such directory "{}"'.format(output_dir), file=sys.stderr)
+    # Update XML
+    xml_file = os.path.join(os.path.dirname(sys.argv[0]), '..', 'data', 'xml', f'{collection_id}.xml')
+    tree = ET.parse(xml_file)
+    paper = tree.getroot().find(f"./volume[@id='{volume_id}']/paper[@id='{paper_id}']")
+    if paper is not None:
+        revisions = paper.findall(change_type)
+        revno = 1 if args.erratum else 2
+        for revision in revisions:
+            revno = int(revision.attrib['id']) + 1
+
+        if args.do:
+            revision = ET.Element(change_type)
+            revision.attrib['id'] = str(revno)
+            revision.text = f'{args.anthology_id}{change_letter}{revno}'
+
+            # Set tails to maintain proper indentation
+            paper[-1].tail += '  '
+            revision.tail = '\n    '  # newline and two levels of indent
+
+            paper.append(revision)
+
+            tree.write(xml_file, encoding="UTF-8", xml_declaration=True)
+            print(f'-> Added {change_type} node "{revision.text}" to XML', file=sys.stderr)
+
+    else:
+        print(f'-> FATAL: paper ID {args.anthology_id} not found in the Anthology', file=sys.stderr)
         sys.exit(1)
 
-    # Look for existing versions (looking for pattern {paper_id}{letter}{num})
-    letter = 'e' if args.erratum else 'v'
+    output_dir = os.path.join(args.anthology_dir, 'pdf', collection_id[0], collection_id)
 
-    existing_revisions = list(filter(lambda f: f.startswith('{}{}'.format(args.paper_id, letter)), os.listdir(output_dir)))
-    new_version = len(existing_revisions) + 1
+    # Make sure directory exists
+    if not os.path.exists(output_dir):
+        print(f'-> Creating directory {output_dir}', file=sys.stderr)
+        os.makedirs(output_dir)
 
-    revised_file_generic_path = '{}.pdf'.format(file_prefix, new_version)
+    canonical_path = os.path.join(output_dir, f'{args.anthology_id}.pdf')
 
-    if not args.erratum and new_version == 1:
+    if not args.erratum and revno == 2:
         # There are no versioned files the first time around, so create the first one
-        revised_file_v1_path = '{}{}1.pdf'.format(file_prefix, letter)
-        maybe_copy(revised_file_generic_path, revised_file_v1_path, args.do)
-        maybe_copy(input_file_path, revised_file_generic_path, args.do)
-        new_version = 2
+        # (essentially backing up the original version)
+        revised_file_v1_path = os.path.join(output_dir, f'{args.anthology_id}{change_letter}1.pdf')
+
+        current_version = ANTHOLOGY_URL.format(args.anthology_id)
+        if args.do:
+            try:
+                print(f'-> Downloading file from {args.path} to {revised_file_v1_path}', file=sys.stderr)
+                with urllib.request.urlopen(current_version) as url, open(revised_file_v1_path, mode='wb') as fh:
+                    fh.write(url.read())
+            except ssl.SSLError:
+                print(f'-> FATAL: An SSL error was encountered in downloading {args.path}.', file=sys.stderr)
+                sys.exit(1)
+        else:
+            print(f'-> DRY RUN: Downlading file from {args.path} to {revised_file_v1_path}', file=sys.stderr)
+
 
-    revised_file_versioned_path = '{}{}{}.pdf'.format(file_prefix, letter, new_version)
+    revised_file_versioned_path = os.path.join(output_dir, f'{args.anthology_id}{change_letter}{revno}.pdf')
 
     maybe_copy(input_file_path, revised_file_versioned_path, args.do)
+    maybe_copy(input_file_path, canonical_path, args.do)
 
     if args.path.startswith('http'):
         os.remove(input_file_path)
@@ -105,12 +152,12 @@ def main(args):
 
 if __name__ == '__main__':
     parser = argparse.ArgumentParser()
-    parser.add_argument('paper_id', help='The paper ID to revise (e.g., P18-1001)')
+    parser.add_argument('anthology_id', help='The Anthology paper ID to revise (e.g., P18-1001)')
     parser.add_argument('path', type=str, help='Path to the revised paper ID (can be URL)')
     parser.add_argument('explanation', help='Brief description of the changes.')
     parser.add_argument('--erratum', '-e', action='store_true', help='This is an erratum instead of a revision.')
     parser.add_argument('--do', '-x', action='store_true', default=False, help='Actually do the copying')
-    parser.add_argument('--anthology-dir', default=os.path.join(os.environ['HOME'], 'anthology-files/pdf'),
+    parser.add_argument('--anthology-dir', default=os.path.join(os.environ['HOME'], 'anthology-files'),
                         help='Anthology web directory root.')
     args = parser.parse_args()
 
diff --git a/bin/anthology/utils.py b/bin/anthology/utils.py
--- a/bin/anthology/utils.py
+++ b/bin/anthology/utils.py
@@ -46,6 +46,32 @@ def to_volume_id(anthology_id):
     return anthology_id[:5]
 
 
+def build_anthology_id(collection_id, volume_id, paper_id):
+    """
+    Transforms collection id, volume id, and paper id to a width-padded
+    Anthology ID. e.g., ('P18', '1', '1') -> P18-1001.
+    """
+    if collection_id.startswith('W') or collection_id == 'C69':
+        return '{}-{:02d}{:02d}'.format(collection_id, int(volume_id), int(paper_id))
+    else:
+        return '{}-{:02d}{:02d}'.format(collection_id, int(volume_id), int(paper_id))
+
+
+def deconstruct_anthology_id(anthology_id):
+    """
+    Transforms an Anthology id into its constituent collection id, voulume id, and paper id
+    parts. e.g,
+
+        P18-1007 -> ('P18', '1', '7')
+        W18-6310 -> ('W18', '63', '10')
+    """
+    collection_id, rest = anthology_id.split('-')
+    if collection_id.startswith('W') or collection_id.startswith('C69'):
+        return (collection_id, str(int(rest[0:2])), str(int(rest[2:])))
+    else:
+        return (collection_id, str(int(rest[0:1])), str(int(rest[1:])))
+
+
 def stringify_children(node):
     """Returns the full content of a node, including tags.
 


Test patch: