Java regex to check if last line has unescaped %

Viewed 128

I need for Java a check of the last line. I have a String which will be processed further by TeX compiler. There the very first % in a line is treated as comment and all the rest of the line is 'not seen' by the compiler.

But the very first % is little bit tricky because it could be escaped by an \ so a \% should be ignored.

So basically I want to check if the very last line ends with a comment or not?

e.g.

(a) last line % now with comment
(b) last line with escaped \%  and not treated
(c) last line withouth any special chars
(d) last line terminates with %
(e) last line terminates with escaped \%
(f) beginning % but even escaped \% is ignored

For a check I need to have positive: a, d and f while the others should be ignored.

As requested my approach up till now:

[^\n]*$

tests the very last line. Fine but now I don't even know how to match in the very last line just a %. I would have exepected that a (%)? would match only if % is available but with even (c) is positive since it matches the last line.

Could anybody help how to filter only the % I'm looking for?

2 Answers

You can use

(?<!\\)(?:\\{2})*%[^\\%\r\n]*(?:\\[\w\W][^\\%\r\n]*)*\z

See the regex demo.

Details

  • (?<!\\) - no \ allowed immediately to the left of the current location
  • (?:\\{2})* - any zero or more double backslashes (this and the preceding pattern are necessary to avoid matching a % that is preceded with an escaping backslash, you can't just use (?<!\\))
  • % - a % char
  • [^\\%\r\n]* - zero or more chars other than \, % and CR and LF line ending symbols
  • (?:\\[\w\W][^\\%\r\n]*)* - zero or more occurrences of
    • \\[\w\W] - any escaped char, \\ matches \ and [\w\W] matches any char (it can be replaced with . if you add (?s) DOTALL inline embedded flag option at the start of the pattern)
    • [^\\%\r\n]* - any zero or more chars other than \, % and CR and LF line ending symbols.

In Java, use the pattern like

String text = "(a) last line % now with comment\n(b) last line with escaped \\%  and not treated\n(c) last line withouth any special chars\n(d) last line terminates with %\n(e) last line terminates with escaped \\%\n(f) beginning % but even escaped \\% is ignored";
Pattern p = Pattern.compile("(?<!\\\\)(?:\\\\{2})*%[^\\\\%\r\n]*(?:\\\\[\\w\\W][^\\\\%\r\n]*)*\\z");
Matcher m = p.matcher(text);
if (m.find()) {
  System.out.println("Match found!");
}
// => Match found!

See the Java demo.

You can use negative lookbehind to acheive what you want.

Demo: https://regex101.com/r/oRqrhv/1

Pattern: (?<!\\)%


Update1: To ensure only we match the last line, atomic group can be used.

Pattern: (?>[\s\S]*\n).*(?<!\\)%

Detail: [\s\S]*\n will match all the characters till the last \n. Atomic group, (?>..) will prevent the engine from backtracking.

Successful match: https://regex101.com/r/oRqrhv/2
Failed Match: https://regex101.com/r/oRqrhv/4


Note: If there is a empty new line at the end of the input text, like https://regex101.com/r/oRqrhv/3, it will not match. If this needs to be matched, then we need to use negative lookahead.
Pattern: (?>[\s\S]*\n(?<!$)).*(?<!\\)%. (?<!$) ensures \n is not immediately followed by end of string.

Related