{"record":{"id":"e45e4dee847c2fa8","repo":"languagetool-org/languagetool","slug":"only-backward-references-in-match-elements-are-pos","errorCode":null,"errorMessage":"Only backward references in match elements are possible, tried to specify token ${refNumber}\nLine: ${pLocator.getLineNumber()}, column: ${pLocator.getColumnNumber()}.","messagePattern":"Only backward references in match elements are possible, tried to specify token (.+?)\nLine: (.+?), column: (.+?)\\.","errorType":"exception","errorClass":"SAXException","httpStatus":null,"severity":"error","filePath":"languagetool-core/src/main/java/org/languagetool/rules/patterns/XMLRuleHandler.java","lineNumber":433,"sourceCode":"      elements.append(refNumber);\n    }\n  }\n\n  private void checkNumber(Attributes attrs) throws SAXException {\n    if (StringTools.isEmpty(attrs.getValue(\"no\"))) {\n      throw new SAXException(\"References cannot be empty: \" + \"\\n Line: \"\n          + pLocator.getLineNumber() + \", column: \"\n          + pLocator.getColumnNumber() + \".\");\n    } else if (Integer.parseInt(attrs.getValue(\"no\")) < 1 && regex.length() == 0) {\n      throw new SAXException(\"References must be larger than 0: \"\n          + attrs.getValue(\"no\") + \"\\n Line: \" + pLocator.getLineNumber()\n          + \", column: \" + pLocator.getColumnNumber() + \".\");\n    }\n  }\n\n  private void checkRefNumber(int refNumber) throws SAXException {\n    if (refNumber > patternTokens.size()) {\n      throw new SAXException(\"Only backward references in match elements are possible, tried to specify token \"\n          + refNumber + \"\\n\" + \"Line: \" + pLocator.getLineNumber()\n          + \", column: \" + pLocator.getColumnNumber() + \".\");\n    }\n  }\n\n  protected void setExceptions(Attributes attrs) {\n    inException = true;\n    exceptions = new StringBuilder();\n    resetException();\n\n    exceptionStringNegation = YES.equals(attrs.getValue(NEGATE));\n    exceptionValidNext = \"next\".equals(attrs.getValue(SCOPE));\n    exceptionValidPrev = \"previous\".equals(attrs.getValue(SCOPE));\n    exceptionStringInflected = YES.equals(attrs.getValue(INFLECTED));\n\n    if (attrs.getValue(POSTAG) != null) {\n      exceptionPosToken = StringInterner.intern(attrs.getValue(POSTAG));\n      exceptionPosRegExp = YES.equals(attrs.getValue(POSTAG_REGEXP));","sourceCodeStart":415,"sourceCodeEnd":451,"githubUrl":"https://github.com/languagetool-org/languagetool/blob/2e990059ce67d5e2a0f7f7ca5d31160c6709df4b/languagetool-core/src/main/java/org/languagetool/rules/patterns/XMLRuleHandler.java#L415-L451","documentation":"XMLRuleHandler.checkRefNumber throws this SAXException when a <match no='N'> reference points to a token at or after the current pattern position. References in match elements may only be backward (to earlier tokens), because the token being matched cannot reference itself or a future token. The error names the offending token number and the XML location.","triggerScenarios":"Parsing rule XML where the 'no' attribute exceeds patternTokens.size() at parse time, e.g. <match no=\"2\"/> placed as the first token, or referencing a token not yet defined in the pattern.","commonSituations":"Reordering tokens after writing matches and forgetting to renumber references; authors trying forward references; rules copied with offsets that no longer line up.","solutions":["Lower the 'no' value so it refers to an earlier token in the pattern","Move the <match> element after the token it references, or restructure the rule","Re-count token positions (1-based, counting only preceding pattern tokens) after editing the rule"],"exampleFix":"// before (first token cannot reference token 2)\n<token>foo</token><match no=\"2\"/>\n// after\n<token>foo</token><match no=\"1\"/>","handlingStrategy":"validation","validationCode":"// While building rules programmatically, track current token position\nint currentPosition = /* index of token being defined */;\nint refNumber = parseNo(matchElement);\nif (refNumber > currentPosition) {\n  throw new IllegalArgumentException(\"Forward reference no=\" + refNumber + \" at token \" + currentPosition);\n}","typeGuard":"boolean isBackwardReference(int refNumber, int tokensDefinedSoFar) {\n  return refNumber >= 1 && refNumber <= tokensDefinedSoFar;\n}","tryCatchPattern":"try {\n  loader.getRules(is, filename);\n} catch (SAXException e) {\n  if (e.getMessage().startsWith(\"Only backward references\")) {\n    log.error(\"Renumber match references; they may only point to earlier tokens\", e);\n  } else throw e;\n}","preventionTips":["Only reference tokens defined earlier in the pattern","Renumber all <match no> values whenever tokens are reordered or inserted","Add a custom lint that checks reference positions during rule CI"],"tags":["languagetool","xml-rules","validation","reference"],"backgroundTag":"invalid-argument-value","analyzedSha":"2e990059ce67d5e2a0f7f7ca5d31160c6709df4b","analyzedAt":"2026-09-06T09:20:17.015Z","contentChangedAt":"2026-09-06T09:20:17.015Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}