Skip to content
8 changes: 8 additions & 0 deletions openreview/arr/process/camera_ready_preprocess.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
async function process(client, edit, invitation) {
client.throwErrors = true;
const { invitations } = await client.getInvitations({ id: invitation.invitations[0] });
const metaInvitation = invitations[0];
const script = metaInvitation.content.revision_preprocess_script.value;
Comment thread
haroldrubio marked this conversation as resolved.
eval(`var process = ${script}`);
await process(client, edit, invitation);
}
64 changes: 64 additions & 0 deletions openreview/arr/process/camera_ready_preprocess_content.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
async function process(client, edit, invitation) {
client.throwErrors = true;

const texRules = [
{ name: 'TeX environment', regex: /\\begin\{[^}\n]+\}[\s\S]*?\\end\{[^}\n]+\}/ },
{ name: 'TeX command', regex: /\\[A-Za-z]+(?:\*)?\b/ },
{ name: 'TeX escaped symbol', regex: /\\[%&#_$^{}]/ },
{ name: 'TeX display math', regex: /(^|[^\\])\$\$[\s\S]+?\$\$/ },
{ name: 'TeX inline math', regex: /(^|[^\\])\$(?:[^$\n]|\\\$)+\$/ },
{ name: 'TeX math \\(...\\)', regex: /\\\([\s\S]+?\\\)/ },
Comment thread
haroldrubio marked this conversation as resolved.
{ name: 'TeX math \\[...\\]', regex: /\\\[[\s\S]+?\\\]/ }
];

const markdownRules = [
{ name: 'Markdown link', regex: /\[[^\]\n]+\]\([^)]+\)/ },
{ name: 'Markdown autolink', regex: /<https?:\/\/[^>\s]+>/i },
{ name: 'Markdown fenced code', regex: /```[\s\S]+?```/ },
{ name: 'Markdown code span', regex: /`[^`\n]+`/ },
{ name: 'Markdown emphasis (*)', regex: /(^|[^\w])\*{1,3}[^*\n]+\*{1,3}(?=$|[^\w])/ },
{ name: 'Markdown emphasis (_)', regex: /(^|[^\w])_{1,2}[^_\n]+_{1,2}(?=$|[^\w])/ }
];

const normalize = (value) => typeof value === 'string'
? value.replace(/\r\n?/g, '\n').trim()
: '';

const findViolation = (text, rules) => {
for (const rule of rules) {
const match = text.match(rule.regex);
if (match) {
return {
rule: rule.name,
snippet: match[0].replace(/\s+/g, ' ').trim().slice(0, 80)
};
}
}
return null;
};

for (const field of ['title', 'abstract']) {
const value = normalize(edit.note?.content?.[field]?.value);
if (!value) {
continue;
}

const texViolation = findViolation(value, texRules);
if (texViolation) {
return Promise.reject(new OpenReviewError({
name: 'Error',
message: `${field} cannot contain TeX markup (${texViolation.rule}). Detected: ${texViolation.snippet}`
}));
}

const markdownViolation = findViolation(value, markdownRules);
if (markdownViolation) {
return Promise.reject(new OpenReviewError({
name: 'Error',
message: `${field} cannot contain Markdown markup (${markdownViolation.rule}). Detected: ${markdownViolation.snippet}`
}));
}
}

return Promise.resolve();
}
128 changes: 127 additions & 1 deletion tests/test_arr_venue_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@
arr_ac_max_load_task,
arr_sac_max_load_task
)

ARR_PROCESS_DIR = os.path.join(os.path.dirname(__file__), '../openreview/arr/process')

def read_arr_process(process_filename):
with open(os.path.join(ARR_PROCESS_DIR, process_filename)) as process_file:
return process_file.read()

# API2 template from ICML
class TestARRVenueV2():

Expand Down Expand Up @@ -7460,8 +7467,127 @@ def test_commitment_venue(self, client, test_client, openreview_client, helpers)

notes = pc_client_v2.get_notes(forum=submssion3.id, domain='aclweb.org/ACL/ARR/2023/August')
assert len(notes) == 5 # submission + review + 3 reports

commitment_submissions = openreview_client.get_notes(invitation='aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment/-/Submission', sort='number:asc')

venue = openreview.helpers.get_conference(client, request_form_note.forum)
venue.submission_revision_stage = openreview.stages.SubmissionRevisionStage(
name='Camera_Ready_Revision',
due_date=due_date,
exp_date=due_date + datetime.timedelta(days=1),
only_accepted=False
)
venue.create_submission_revision_stage()
helpers.await_queue_edit(openreview_client, f'aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment/-/Camera_Ready_Revision-0-1', count=1)

camera_ready_submission = commitment_submissions[0]
camera_ready_invitation = openreview_client.get_invitation('aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment/Submission1/-/Camera_Ready_Revision')
Comment thread
haroldrubio marked this conversation as resolved.
Outdated

openreview_client.post_invitation_edit(
invitations=venue.get_meta_invitation_id(),
readers=['aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment'],
writers=['aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment'],
signatures=['aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment'],
replacement=False,
invitation=openreview.api.Invitation(
id=f'aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment/-/Camera_Ready_Revision',
content={
'revision_preprocess_script': {
'value': read_arr_process('camera_ready_preprocess_content.js')
}
},
edit={
'invitation': {
'preprocess': read_arr_process('camera_ready_preprocess.js')
}
}
)
)
helpers.await_queue_edit(openreview_client, f'aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment/-/Camera_Ready_Revision-0-1', count=2)
camera_ready_parent_invitation = openreview_client.get_invitation(f'aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment/-/Camera_Ready_Revision')
camera_ready_invitation = openreview_client.get_invitation(f'aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment/Submission1/-/Camera_Ready_Revision')

assert camera_ready_parent_invitation
assert 'revision_preprocess_script' in camera_ready_parent_invitation.content
assert 'title' in camera_ready_parent_invitation.content['revision_preprocess_script']['value']
assert 'Markdown link' in camera_ready_parent_invitation.content['revision_preprocess_script']['value']

assert camera_ready_invitation
assert camera_ready_invitation.preprocess
assert 'revision_preprocess_script' in camera_ready_invitation.preprocess
assert 'client.getInvitations' in camera_ready_invitation.preprocess

def build_camera_ready_revision_content(submission, title=None, abstract=None):
content = {
'title': {'value': title if title is not None else submission.content['title']['value']},
'abstract': {'value': abstract if abstract is not None else submission.content['abstract']['value']}
}

for field in ['authors', 'authorids', 'keywords', 'paper_link', 'supplementary_material']:
if field in submission.content:
content[field] = {'value': submission.content[field]['value']}

return content

def post_camera_ready_revision(title=None, abstract=None):
return test_client.post_note_edit(
invitation='aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment/Submission1/-/Camera_Ready_Revision',
signatures=['aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment/Submission1/Authors'],
note=openreview.api.Note(
content=build_camera_ready_revision_content(
camera_ready_submission,
title=title,
abstract=abstract
)
)
)

invalid_cases = [
('title', 'Title with inline math $x$', r'cannot contain TeX markup'),
('title', r'Title with \textit{TeX} command', r'cannot contain TeX markup'),
('abstract', 'Abstract with $$x + y$$ display math.', r'cannot contain TeX markup'),
('abstract', r'Abstract with \begin{equation}a=b\end{equation} environment.', r'cannot contain TeX markup'),
('abstract', r'Abstract with \(x + y\) math delimiters.', r'cannot contain TeX markup'),
('title', '**Bold** markdown title', r'cannot contain Markdown markup'),
('title', '_Italic_ markdown title', r'cannot contain Markdown markup'),
('abstract', 'Abstract with [Appendix](https://example.com) markdown link.', r'cannot contain Markdown markup'),
('abstract', 'Abstract with `inline code` markers.', r'cannot contain Markdown markup'),
('abstract', 'Abstract with <https://example.com> markdown autolink.', r'cannot contain Markdown markup')
]

for field, value, error_match in invalid_cases:
with pytest.raises(openreview.OpenReviewException, match=error_match):
post_camera_ready_revision(**{field: value})

valid_cases = [
{
'title': '$100K or 100 Days for Better Evaluation',
'abstract': camera_ready_submission.content['abstract']['value']
},
{
'title': 'Wojood^{Relations} Without Delimited Math',
'abstract': camera_ready_submission.content['abstract']['value']
},
{
'title': camera_ready_submission.content['title']['value'],
'abstract': 'We compare [CLS] tokens against plain baselines with ordinary brackets.'
},
{
'title': camera_ready_submission.content['title']['value'],
'abstract': 'A* search and PLAYER* variants remain plain prose here.'
},
{
'title': 'Camera-ready title with (parentheses) and [brackets]',
'abstract': 'A plain URL like https://example.com is allowed because it is not Markdown syntax.'
}
]

for case in valid_cases:
revision_edit = post_camera_ready_revision(title=case['title'], abstract=case['abstract'])
helpers.await_queue_edit(openreview_client, edit_id=revision_edit['id'])
updated_submission = openreview_client.get_notes(id=camera_ready_submission.id)[0]
assert updated_submission.content['title']['value'] == case['title']
assert updated_submission.content['abstract']['value'] == case['abstract']

venue.invitation_builder.expire_invitation('aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment/Senior_Area_Chairs/-/Submission_Group')
venue.invitation_builder.expire_invitation('aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment/Area_Chairs/-/Submission_Group')
venue.invitation_builder.expire_invitation('aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment/Reviewers/-/Submission_Group')
Expand Down