Просмотр исходного кода

Merge pull request #14121 from overleaf/bg-best-allow-underscore-in-hyperref-labels

allow underscore in hyperref labels

GitOrigin-RevId: c4e0cfcdef92dec959dceba9a7ae55920812fdb0
Brian Gough 3 лет назад
Родитель
Сommit
1ef6c70258

+ 70 - 0
services/web/frontend/js/features/source-editor/languages/latex/linter/latex-linter.worker.js

@@ -322,6 +322,54 @@ const read1filename = function (TokeniseResult, k) {
   }
 }
 
+const readOptionalLabel = function (TokeniseResult, k) {
+  // read a label my_label:text..
+  const Tokens = TokeniseResult.tokens
+  const text = TokeniseResult.text
+
+  const params = Tokens[k + 1]
+
+  // Quick check for arguments like [label]
+  if (params && params[1] === 'Text') {
+    const paramNum = text.substring(params[2], params[3])
+    if (paramNum.match(/^(\[[^\]]*\])*\s*$/)) {
+      return k + 1 // got it
+    }
+  }
+
+  let label = ''
+  let j, tok
+  for (j = k + 1, tok; (tok = Tokens[j]); j++) {
+    if (tok[1] === '{') {
+      // unclosed label
+      break
+    } else if (tok[1] === 'Text') {
+      const str = text.substring(tok[2], tok[3])
+      label = label + str
+      if (str.match(/\]/)) {
+        // breaking due to ]
+        break
+      }
+    } else if (tok[1] === '_') {
+      label = label + tok[1]
+    } else {
+      break // breaking due to unrecognised token
+    }
+  }
+
+  if (label.length === 0) {
+    return null
+  } else if (label.length > 0 && /^\[[^\]]*\]\s*$/.test(label)) {
+    // make sure the label is of the form [label]
+    return j - 1 // advance past these tokens
+  } else {
+    // invalid label
+    const e = new Error('Invalid label')
+    e.pos = j + 1
+    return e
+  }
+}
+
 const readOptionalParams = function (TokeniseResult, k) {
   // read an optional parameter [N] where N is a number, used
   // for \newcommand{\foo}[2]... meaning 2 parameters
@@ -956,6 +1004,28 @@ const InterpretTokens = function (TokeniseResult, ErrorReporter) {
           i = newPos
         }
         nextGroupMathMode = false
+      } else if (seq === 'hyperref') {
+        // try to read any optional params [LABEL].... allowing for
+        // underscores, advance if found
+        let newPos = readOptionalLabel(TokeniseResult, i)
+        if (newPos instanceof Error) {
+          TokenErrorFromTo(
+            Tokens[i + 1],
+            Tokens[newPos.pos],
+            'invalid hyperref label'
+          )
+          i = newPos.pos
+        } else {
+          i = newPos
+        }
+        // try to read parameter {....}, advance if found
+        newPos = readDefinition(TokeniseResult, i)
+        if (newPos === null) {
+          /* do nothing */
+        } else {
+          i = newPos
+        }
+        nextGroupMathMode = false
       } else if (seq === 'resizebox') {
         // try to read any optional params [BAR]...., advance if found
         let newPos = readOptionalGeneric(TokeniseResult, i)

+ 34 - 0
services/web/test/frontend/features/source-editor/languages/latex/latex-linter.test.ts

@@ -410,6 +410,40 @@ describe('LatexLinter', function () {
     assert.equal(errors.length, 0)
   })
 
+  it('should accept a plain hyperref command', function () {
+    const { errors } = Parse('\\hyperref{http://www.overleaf.com/}')
+    assert.equal(errors.length, 0)
+  })
+
+  it('should accept a hyperref command with underscores in the url ', function () {
+    const { errors } = Parse('\\hyperref{http://www.overleaf.com/my_page.html}')
+    assert.equal(errors.length, 0)
+  })
+
+  it('should accept a hyperref command with category, name and text arguments ', function () {
+    const { errors } = Parse(
+      '\\hyperref{http://www.overleaf.com/}{category}{name}{text}'
+    )
+    assert.equal(errors.length, 0)
+  })
+
+  it('should accept an underscore in a hyperref label', function () {
+    const { errors } = Parse('\\hyperref[foo_bar]{foo bar}')
+    assert.equal(errors.length, 0)
+  })
+
+  it('should reject a $ in a hyperref label', function () {
+    const { errors } = Parse('\\hyperref[foo$bar]{foo bar}')
+    assert.equal(errors.length, 1)
+  })
+
+  it('should reject an unclosed hyperref label', function () {
+    const { errors } = Parse('\\hyperref[foo_bar{foo bar}')
+    assert.equal(errors.length, 2)
+    assert.equal(errors[0].text, 'invalid hyperref label')
+    assert.equal(errors[1].text, 'unexpected close group }')
+  })
+
   // %novalidate
   // %begin novalidate
   // %end novalidate