The Greatest Regex Trick Ever (2014)
rexegg.com
rexegg.com
More broadly, people fear and misunderstand regexes because they have no idea how they work. It becomes much easier if you understand how they map to deterministic finite state machines. Recommended reading: https://swtch.com/~rsc/regexp/regexp1.html
Once you understand how they work, you can basically read a regex left to right and intuitively know all the strings they'd match. There is no such thing as an unmaintainable/illegible basic regex - they're just words with some placeholders in them - it's when you cram in extended functionality (which is basically a programming language where all the keywords are single characters) that shit hits the fan.
The thing is that regular expression are supported as language feature or as standard library in pretty much every language. If you want to build a proper parser, you'll have to jump through a lot more hoops. For instance for C++ I've tried tons of different lexer and parser generators and they all suck for various reasons. (Verbose syntax, uses global variables, C only, lexer not compatible with parser and vice versa,..) Most people seem to end up writing their own parsers from scratch.
The only time I've ever seen parsing done right is with Parsec for Haskell.
I suggest you take alook at Antrl4 for a powerful but easy to use parser plus lexer combo.
https://www.gnu.org/software/bison/manual/
http://flex.sourceforge.net/manual/
Disclaimer: A long time ago in a galaxy, far, far away, I wrote an optimizing Java-to-MIPS compiler (sans GC, so leaky heap) in C++ using Flex/Bison and again in Java using JavaCC.
If you know the shape of your HTML in advance, you're hardly "parsing" it. :)
I've had good success with Jison as a JS parser generator that is performant enough to feel good about using in production.
That's totally a regular language, but I doubt you'll find a legible way to express it.
^(.......)+$((((((6(2|9)8|6(2|9)1|5)(5(2|9)8|5(2|9)1|4)(5(2|9)7 [...]
(Unless you can convert from decimal to unary in regex, too. I doubt you can, regular languages are too weak for that.)
Another point, generally overlooked by the theoretical purists, is that HTML in the wild is rarely correct, and your perfect HTML parser will barf when trying to process it. Regexes on the other hand don't have to care about exact syntax and can cope with horribly mangled data.
It also won't break if the website adds a single attribute or a quirky value.
If you are just scraping a little data, a regex can be a quick and easy way to get what you need and get on with your life.
If you actually care about the html structure, regexes are not sufficient. They are still useful for matching tokens though.
Only exception would be if you need to extract very specific data items which are not hierarchical in nature. Eg. you want to extract only the specified character encoding or something like that.
Your point about the "perfect HTML parser" is kind of missing the point, since regexes does not magically solve the problem of imperfect HTML either. A perfect HTML parser would be one which implement HTML spec fully, including the error recovery rules. These are quite complex, eg, if you reach a <p> then an open <h1> is implicitly closed, but an open <i> is not. Try implementing this logic in a single regex! ("Regexes does not care about perfect syntax" - what does that even mean? A regex match exactly what you tell it to match, just like a parser parses what you program it to parse.)
Your viewpoint is the exact problem I'm complaining about. You think what I am doing is impossible. How can you be so certain? That's the sign of someone who is too tied up in getting something perfect.
In fact, while I've hit many problems in gathering data over those years, I can't think of any problem that has been because of regex limitations. And none of my regexes are overly complicated or rely on obscure extensions. Some sites get redesigned and I have to adjust my code, but those redesigns would break any kind of parsing as the pages got completely redesigned (and the URLs and site structure often change as part of this...)
If you need to gather data from a website, your real problems will be in the networking and reliability side of things.
It is really weird seeing smart people talking about this issue in the no regex works camp.
I think it's probably because they haven't had to grab a few specific data points from websites ever.
The thing is - the alternative is to write a query selector - which is another more suitable domain specific language for making selections - only on the DOM instead of text, I'd just write `$(".question-link").get().map(x => x.href)` to get the hrefs and I know it's __always perfectly safe__. Now that example is trivial, if I only want links where the questions are tagged with C#, I get a much harder problem with Regex, but with query selectors it's still mostly trivial.
So, it's not that it's particularly hard to use regular expressions to solve it, it's just a lot harder than the alternative which is super simple and obviously correct.
/<a[^>]*href="(\/questions\/\d+[^"]+)"/i
But... we can go back and forwards posting examples and find fault in any regex that I post or any selector that you post. It's missing the point. Both methods are at the mercy of web page redesigns. Both methods can be made more robust against certain changes, but cannot survive other changes. You are trying to say that regexes won't work. I am saying that both methods work.> (...) and that they don't change that in a design or in other pages (...)
Well if they change the design of the pages then you will have to rewrite your regex accordingly in order to find the data you need. But if that happens, odds are high that your program, which uses a full featured DOM parser, will have to be rewritten as well in order to handle the modified output of the DOM parser...
I'm very sure that you could invent some website that relied on some hideous deeply recursive complicated structure, and it would be painful to grab data from it. In my experience, those cases are extremely unlikely. If you let these extremely unlikely situations stop you from picking a simple and useful solution for all the other cases, you are indeed getting tied up in trying to be perfect.
What you describe ("sometimes I extract bits of a page with one regex and process it more with further regexes in another function") sounds like a recursive descent parser, which is a very common way to implement a simple and fast parser.
I'm not suggesting you can't parse html with the help of regexes, I'm just stating that you cannot parse html only with a regex: You cannot apply a single regex to the html and get something similar to a DOM out. You need some kind of stack or recursive programming logic in order to extract a recursive or hierarchical data structure from a string.
If the job is to extract all links from a webpage, regexes will do just fine, and will probably be easier to write and understand than alternate approaches. (This is absolutely not a fair comparison of course; you could compare writing a regex engine to writing a html parser. But I digress.)
If the job is to determine whether a given webpage is a member of the set that includes all valid html documents. then a regex is not sufficient.
If the job is to extract a list of syntax tokens from a webpage, a regex is likely fine.
If the job is to assign semantic meaning to every token in that list, a regex just won't work.
Either way, the point is to know what you're doing. Much "parsing" of webpages is not parsing in the formal language sense, and who cares that it isn't because it doesn't need to be.
//a[@class='specified_string']/@href
Yes, you have to understand the syntax XPath to write such expressions, just like you have to know the language of regexes. Or at least be able to google them.The answer to "who cares" is "you", because you're the one who's going to catch hell when your regex failed to capture some hyperlink that utilized some feature of XML that exceeded your test-cases. The one-liner above is guaranteed to Just Work on all valid XML documents, so why even create such a monstrosity?
Everyone knows that Regexes Cannot Parse HTML, and yet people still try it because they think they're smarter than Noam Chompsky. The real truth is that everything looks like a nail to these people, because all they have is a hammer.
There are fault-tolerant HTML parsers like TagSoup that are specifically designed to handle dirty HTML and spit out a valid document object. If you have sources that are malformed badly enough that it's still not working, you can define custom SAX properties to handle them. But a task like that is certainly a best-case effort and the interpretation of such a library is no more or less valid than the interpretation of the browser's parser. It's not a valid document to start with and nothing can make it so.
If you are only parsing values out of a single specific data template, you know it's not going to parse as HTML or XML, you know that it's never going to contain weird values, and you know it's never going to change - then go hog wild. But it's fundamentally a brittle approach that only holds as long as those assumptions do. I've made the mistake of believing some of those about my data and it's bitten me before. And I really question the implicit assertion that "most html parsing" would fall into that exceedingly narrow category. Especially after a couple years of feature creep.
Just keep your logic general and normalize your data. Offer a failover to a fault-tolerant parser in your data layer with a logged warning. This is much more durable and doesn't silently generate invalid tokens or silently fail to capture valid tokens. Regexes simply cannot offer the capability to fail loudly. So once you are no longer actively babysitting your custom regex parser it could have started failing at any time - how would you even know?
If you want to actually touch them as plain objects it's a very straightforward task of implementing a data provider to marshal the objects. In Java this is provided by libraries such as Jackson (JSON) and JAXB (XML). These basically work just like Hibernate.
JSON and XML cover many of the real-world use cases of parsing such CFGs. If you make your data fit into one of those boxes it's very straightforward to validate or marshal them according to those schema protocols. They obviously do have a greater complexity than just writing a regex, but that's kind of the nature of using a more expressive language, and it's by no means an insurmountable increase.
On the other hand, this post has spawned the usual regex thread including a zalgo link and a pointer to a regex that finds prime numbers, so I guess you've done your part to perpetuate regex mythology.
No-one cares about "infinite HTML documents". I don't even think the Chomskyan hierarchy concerns itself with languages with "infinite" productions. All you have to worry about is infinite languages -- i.e., languages with arbitrarily large productions.
There's a key difference between "infinite" and "arbitrarily large": the latter is quantifiable. While indeed to can build a regular expression to match any finite subset of HTML, it can only match HTML documents up to some fixed size. I can always give you a (finite!) document that is one tag deeper that your regex will choke on.
"But recursive parsers have the same issue!" you say. "Their stack will run out of memory at some point!" Yes, but they have a key difference: the amount of stack (memory) they require is bounded by the size of the document. This is not true for a regular expression! In fact, not only would a regular expression to match a given subset of HTML require memory exponentially proportional† to the size of the document, the automata itself would be similarly massive!
I really wish someone came up with and promulgated a concise handy built-in ubiquitious equivalent of regular expressions for, say, PEGs. The closest I've seen are DCGs in Prolog. Would make so many parsing problems more easy to do correctly!
† It's possible I'm wrong about this since it's early morning and I'm basing this off my intuition rather than a proof. The part about the automata itself being exponential w/r/t the size of the document is definitely true though.
Finite state automata do indeed need an exponentially larger number of states compared to a pushdown automata, I made no claim as to efficiency. The point remains - for all practical purposes, you can consider all languages to be regular and using a stack is merely an optimisation.
This is thoroughly wrong. For “all practical purposes”, you won’t expand a non-regular language into a giant regular one with an emulated state.
If you really believe that regular expressions cannot parse any HTML document in reality (eg given that all web browsers in practice limit the nesting depth of HTML) then please present some evidence.
You can build a regular expression to match any HTML document to any fixed depth. Set that to whatever you think “all web browsers” limit HTML nesting to “in practice” – citation very much needed, I don’t believe they do – and voilà! You have produced something absolutely useless and probably several million characters long.
I don’t know what you’re arguing. I don’t think you know what you’re arguing either. It’s pointless to continue talking.
Regarding the BDD approach, it looks like somebody already implements it with excellent performance and memory characteristics: http://www.cs.rutgers.edu/~vinodg/papers/raid2010/raid2010_s...
Of course, the point (if you read back) was not to say that you should use REs for all parsing. Just merely to correct a commonly repeated mistake that 'REs cannot parse HTML'. They can do so just fine.
To someone who knows BRE. I am one of those people. It's ERE and PCRE I do not understand very well.
Sharing solutions to common problems using BRE on HN always seems to trigger (unwarranted) criticism using either of the exact words you mention, or synonyms for them. "Unmaintainable" (by who?). "Illegible" (to who?).
I "maintain" 100's of BRE scripts. They are perfecty legible to me. None of them are so complex I cannot re-write them in a short time. It is the structure of the input that is complex and which takes time to recall.
I also use lex, a common utility found on almost all UNIX derived OS; this article seems to ignore that option. I like to think it's faster than Perl or Python, but I cannot say for sure.
It is. I implemented an assembly language parser with pyparsing. It worked okay but the function call overhead with a combinator-based parser handling both the lexing and grammar was murder. I replaced it with regexes and got a 6x speedup. Not something I would do with a complex grammar though. Native code would obviously blow this away in speed but it is fast enough now.
"Tarzan"|(Tarzan)
You can also include more than one case of what you don't want to match. This one also finds only the cases of Tarzan that don't match the first three patterns: Tarzania|--Tarzan--|"Tarzan"|(Tarzan)
You can even use more complex regexes. This matches all words not in an image tag: <img[^>]+>|(\w+)
And likewise this matches anything not surrounded by <b> tags: <b>[^<]*</b>|([\w\s]+) <img[^>]+>|(\w+)
...has some bugs. The part on the left mistakenly matches `<imgasvaasdf>` and mistakenly misses `<img>`. Better would be: <img\b.*?>|(\w+)Here's the code that generated the regex:
use Regexp::Assemble;
my $ra = Regexp::Assemble->new;
while (<$FH>) {
$csv->parse($_);
next if $. == 1;
my @fields = $csv->fields;
$ra->add($fields[1]);
}
my $suburbs = $ra->as_string;First, their explanation doesn't make sense. They're supposing that there's some determinacy in the order in which a matcher can be expected to examine the different possible matches. But that's provably not the case: if it were, then deterministic and non-determinsitic finite automata would be inequivalent.
But the technique in question does seem to require some determinacy as to which of several alternatives will match against a string. Where does that determinacy come from? The semantics of the alternation operator (the '|') as usually formulated don't specify any preference among alternations. For that reason, POSIX additionally requires that a matcher return the longest possible match (and if there are several such, the leftmost is what must be returned). Where you do find an explicit guarantee concerning which of several different possible ways of matching will be preferred, it's almost certainly because the engine is aiming at POSIX compliance.
Such compliance has a significant cost, though, as it requires the matcher to consider all possible matches (in order to find the largest). For that reason, most regex engines forego strict POSIX compliance and only guarantee that some match will be returned if one exists, not that that match will be the leftmost longest. Some engines offer the option of requesting strict POSIX behavior, but the default will always be to eagerly return the first match encountered (and recall the point above that there provably can't be a guarantee about the order in which matches are encountered, in general).
You should never do this in production code unless you're sure that your matcher is POSIX-compliant.
[ -~]
Unfortunately it doesn't work.
Let's say I wanted to match a string following Tarzan but not "Tarzan", I will try his technique:
("Tarzan"|(Tarzan))\s+and JillOfTheJungle
Unfortunately this matches both: "Tarzan" and JillOfTheJungle
and Tarzan and JillOfTheJungle
Or maybe he meant:> Capture Tarzan but not "Tarzan"
a=/(?:"Tarzan"|(Tarzan))\s+and JillOfTheJungle/;
matched = !!a.exec(x)[1]
Works fine.People often forget to solve the problem they need to solve (match x), and instead work on other things (find a regex to match x).
Completely agree. One common mistake is building a complex regex to match the elements you want to find from a string, when an easier approach is to split the string on a simple regex that matches the things you want to throw away.
"Tarzan"\s+and JillOfTheJungle|(Tarzan\s+and JillOfTheJungle)
Seems to meet your needs?
The regex
"Tarzan"|(Tarzan)
should match the string "Tarzan"
in two ways: first, matching the entire string; and second, matching the substring "Tarzan" in the whole string "\"Tarzan\"". But most regex implementations drop extra overlapping matches. I argue this is incorrect behavior, because it complicates understanding what a regex means - you have to understand the /order/ in which your regular expression matcher interprets your regular expression, which is an implementation detail. I conjecture that a DFA-based regex engine would not be able to exhibit this order-biased behavior, at least not with the standard approach.However, it's interesting that this "bug" turns out to be a "feature" for the case of excluding other behavior. I'm not sure what conclusion to draw from this.
what should the results be, of the regular expression "(aa|aaa)(abbb|bbb)"?
$1 = ?
$2 = ?
- the whole string, grouped as "(aa)(abbb)",
- the whole string, grouped as "(aaa)(bbb)";
- the substring "aabbb", grouped as "(aa)(bbb)".
This was a small regex designed to create multiple answers to see how you resolved the issue, obviously we can engineer regexes that return far more results. So something's got to give. I don't agree with you that regex's innately imply all matches are valid.
Python's regex library, for example, can return multiple matches. It has three functions:
- `re.match`, which checks whether the whole string matches.
- `re.search`, which checks for the first location in the string that matches.
- `re.findall`, which finds "all" non-overlapping matches.
I was simply suggesting that the "non-overlapping" constraint in findall is a "bug", in some sense, because it exposes implementation details of the regex engine.
But, again, given that it is apparently a useful bug, maybe I am wrong. But that leaves open the question what the right spec for regex matching is, anyway.
however, i think that technically, that's not an A* search he implemented, just a breadth-first search. i'm not an expert (i've never even implemented A* search), so i could be mistaken. i'm interested to hear whether other people agree.
I think an even greater Regexp trick is the regular expression that determines primality:
http://stackoverflow.com/questions/3296050/how-does-this-reg...
I for one think the trick described in the article is pretty useful, and might even use it someday (and possibly have already without realizing it).
Finding a bit of code that uses a capture to determine whether a match was found seems like it would easily be confusing/inobvious.
Some pretty clear commenting and it would be ok... maybe.
Also... I wonder how well it would work as part of a larger regex, one that already uses captures (or non-capturing groups)? The examples are all nice, short and sweet... but how often do regex based solutions stay short and sweet? A few maintenance cycles/years and suddenly you've got this funky regex/capture thing that only Bob understands and he's way to busy to talk to you for 5 minutes... and once you change things then Bob suddenly finds time to review your code to complain how you broke it for such a simple change. There goes your bonus you told the wife you were sure to get so you could take her and the kids on vacation. The day after your divorce finalized Bob sends you a fix request to use that improved scheme of yours because the old regex one isn't flexible enough anymore.
I feel like I gain more than I lose by simply excluding regexps from my toolbelt. (It's a personal choice, I'm not saying it'd be the same for others, since their values may differ from mine.)
Write only regexes are trivial once you have tests: when you don't understand one, throw it off and replace with something you do. Tests pass? Here you go.
Whereas having a whole file of many lines of code is much harder to replace when you don't understand what's it all about.
"(\.|[^"])*"
You just won't be able to nest them. You're either in a string or you aren't. You're never in a string inside another string.That makes them pretty maintainable.
Why go to this effort? Because for they're beautiful and a very powerful tool when used for the right problem. Unfortunately they're widely misused (i.e. email validation).
My magnum opus was a program that factored numbers in an arbitrary amount of time. That is _NOT_ something you hammer out in an afternoon, and without commenting everything thoroughly (like what I was doing, where I was in code execution, what state the whole program was in) I would never have made it.
Non-related story time: At first when playing with brainfuck, I wrote my own BF-interpreter that compiled to an IL which was easier to debug (with my own interpreter that could give me stack traces with comments about where I was in memory and what the hell was going on). After a month, I really didn't need to. My brain had gotten used to reasoning about BF-code and I could actually understand what code was doing as long as I knew which kind of compiler/interpreter it was written for.
It takes a little more effort, and one does not get the "look how dense it is"-rush, but I guess the same can be said for all code. There is a point where it breaks down, but there is a range of text-matching/extracting problems where regexes are both useful and - if one makes the effort - maintainable.
regex = "(^[0-9])" //catch the initial digit (group 1)
+ "/" //skip the following slash
+ "([A-Z]+) //capture the identifier (group 2)
...
Regexps are too powerful a tool to ignore. I'd much rather write a simple regex than a whole screenfull of code, especially when the former does the work faster (because regex engines are pretty efficient). def any(x): return x+"*"
def many(x): return x+"+"
def capture(x): return "("+x+")"
def lit(x): return re.escape(x) # shorthand
bol="^"; eol="$"
ident="[A-Za-z_][A-Za-z0-9_]*";
find_function=(bol+any(" ")+lit("def")+many(" ")+
capture(ident)+any(" ")+lit(":")+
any(" ")+eol)
You can handle more or less stuff this way according to how much you and/or your readers like regexp syntaxp. The above is probably further than I'd take it in practice; I'm familiar with the regular expression syntax, but I'd still probably at least use something like the `ident' variable just to keep clutter out of the regexp.(A nice demonstration of this sort of thing is emacs's rx module (see, e.g., http://emacswiki.org/emacs/rx). I couldn't find any good non-emacs documentation about this, nor much that would make sense to people unfamiliar with lisp, but when you're in emacs you can get help on it using C-h f rx RET.)
PRODUCT_ID = "[0-9]{2}[A-Z]{1,5}"
...
CUSTOMER_ID = "[0-9]{2}[A-Z]{1,5}"
...
...
regexp = "(" + PRODUCT_ID + ")" // capture product ID in group 1
+ "something something" // something something
+ "(" + CUSTOMER_ID + ")"; // capture customer ID in group 2
In this example, even though the two constants contain the same regular expresison, they refer to two different concepts. Part of the problem with understanding regexp-based code is connecting parts of the expression with what they mean. Above strategy addresses this.I also like assigning names to capture groups I depend on. So instead of, later in code, asking for e.g. matcher.group(2), I ask for matcher.group(GROUP_CUSTOMER_ID). Makes for a much more readable code.
Speaking of Emacs's rx, it's absolutely amazing and I'm sad that I only discovered it just few days ago :(. Another similar concept, from the other side of "code = data" equality is Common Lisp's (of course it's Lisp again) CL-PPCRE and its internal representation of regular expressions:
* (parse-string "(ab)*")
(:GREEDY-REPETITION 0 NIL (:REGISTER "ab"))
* (parse-string "(a(b))")
(:REGISTER (:SEQUENCE #\a (:REGISTER #\b)))
* (parse-string "(?:abc){3,5}")
(:GREEDY-REPETITION 3 5 (:GROUP "abc"))
You can encode any regexp you want as an S-expression, trading off conciseness for legibility. See http://weitz.de/cl-ppcre/#create-scanner2 for more.Except I'm now having a major case of semantic satiation for the word "Tarzan"...
pcre supports reduction to dfa and also jit, but not only are they not the same thing, they are mutually exclusive. also, ever regexp engine i've seen that supports capturing and backreferences uses not worst-case quadratic-time but actually worst-case exponential-time algorithms, although i'm pretty sure this isn't actually unavoidable.
may i suggest that the next time you think about posting a comment that begins with 'Unimpressive. The author of this article obviously didn't', that you include less than one major technical error per sentence in it.
The script looks elegant, but like the author mentions, doesn't work in a text-editor, so I would consider it the greatest.
(?:(?<!")|(?!Tarzan"))Tarzan