views:

56

answers:

3

I'm using a regex to find any URLs and link them accordingly. However, I do not want to linkify any URLs that are already linked so I'm using lookbehind to see if the URL has an href before it. This fails though because variable length quantifiers aren't allowed in lookahead and lookbehind for PHP.

Here's the regex for the match:

/\b(?<!href\s*=\s*[\'\"])((?:http:\/\/|www\.)\S*?)(?=\s|$)/i

What's the best way around this problem?

EDIT:

I have yet to test it, but I think the trick to doing it in a single regex is using conditional expressions within the regex, which is supported by PCRE. It would look something like this:

/(href\s*=\s*[\'\"])?(?(1)^|)((?:http:\/\/|www\.)\w[\w\d\.\/]*)(?=\s|$)/i

The key point is that if the href is captured, the match is immediately thrown out due to the conditional (?(1)^|), which is guaranteed to not match. There's probably something wrong with it. I'll test it out tomorrow.

+1  A: 

I tried doing the same thing the other way round: ensure that the URL doesn't end in ">:

/((?:http:\/\/|www\.)(?:[^"\s]|"[^>]|(*FAIL))*?)(?=\s|$)/i

But for me that looks pretty hacky, I'm sure you can do better.

My second approach is more similar to yours (and thus is more precise):

/href\s*=\s*"[^"]*"(*SKIP)(*FAIL)|((?:http:\/\/|www\.)\S*?)(?=\s|$)/i

If I find an href= I (*SKIP)(*FAIL). This means that I jump to the position the regex engine is at, when it encounters the (*SKIP).

But that's no less hacky and I'm sure there is a better alternative.

nikic
What about `<a href="www.mysite.com" alt="text">`? =)
steven_desu
@steven_desu: That's why I would stick with the second version ;)
nikic
A: 

I don't have a better regex. but if you do not find better regex then I would suggest using two queries for the task. First, find and remove all links and then search for urls. This would be easier and faster possibly. (For, find and replace in one go, you can use something like - http://goo.gl/FJnG).

Satya Prakash
A: 

Finding "every URL that isn't part of a link" is quite difficult negative logic. It may be easier to find every URL, then every URL that's a link, and remove every of the latter from the former list.

As far as finding which URLs are a part of a link, try:

/<a([\s]+[\w="]+)*[\s]+href[\s]*=[\s]*"([\w\s:/.?+&=]+)"([\s]+[\w="]+)*>/i

I tested it with http://regexpal.com/ to be sure. It looks for the <a first, then it allows for any number of parameters, followed by href, followed by any other number of parameters. If it doesn't have the href, it's not a link. If it isn't an <a> tag, it's not a link. Since this is just the list of what we want to remove from the other list (of URLs), I simplified the definition of a URL to [\w\s:/.?+&=]+. As far as generating a list of URLs, you'll want something smarter.

steven_desu