<p>The <a href="http://doi.org/" rel="noreferrer">DOI</a> system places basically no useful limitations on what constitutes <a href="http://doi.org/handbook_2000/enumeration.html#2.2" rel="noreferrer">a reasonable identifier</a>. However, being able to pull DOIs out of PDFs, web pages, etc. is quite useful for citation information, etc.</p>

<p>Is there a reliable way to identify a DOI in a block of text without assuming the 'doi:' prefix? (any language acceptable, regexes preferred, and avoiding false positives a must)</p>

[{'answer_id': 29639, 'author': 'Silas Snider', 'author_id': 2933, 'author_profile': 'https://Stackoverflow.com/users/2933', 'pm_score': 1, 'selected': False, 'text': '<p>The following regex should do the job (Perl regex syntax):</p>\n\n<pre><code>/(10\\.\\d+\\/\\d+)/\n</code></pre>\n\n<p>You could do some additional sanity checking by opening the urls</p>\n\n<pre><code>http://hdl.handle.net/&lt;doi&gt;\n</code></pre>\n\n<p>and</p>\n\n<pre><code>http://dx.doi.org/&lt;doi&gt;\n</code></pre>\n\n<p>where  is the candidate doi,</p>\n\n<p>and testing that you a) get a 200 OK http status, and b) the returned page is not the "DOI not found" page for the service.</p>\n'}, {'answer_id': 29764, 'author': 'Kai', 'author_id': 2963, 'author_profile': 'https://Stackoverflow.com/users/2963', 'pm_score': 4, 'selected': False, 'text': '<p>@Silas The sanity checking is a good idea. However, the regex doesn\'t cover all DOIs. The first element must (currently) be 10, and the second element must (currently) be numeric, but the third element is barely restricted at all:</p>\n\n<blockquote>\n  <p>"Legal characters are the legal graphic characters of Unicode. This specifically excludes the control character ranges 0x00-0x1F and 0x80-0x9F..."  </p>\n</blockquote>\n\n<p>and that\'s where the real problem lies. In practice, I\'ve never seen whitespace used, but the spec specifically allows for it. Basically, there doesn\'t seem to be a sensible way of detecting the <em>end</em> of a DOI.</p>\n'}, {'answer_id': 1876427, 'author': 'rgcb', 'author_id': 8178, 'author_profile': 'https://Stackoverflow.com/users/8178', 'pm_score': 2, 'selected': False, 'text': '<p>I\'m sure it\'s not super-helpful for the OP at this point, but I figured I\'d post what I am trying in case anyone else like me stumbles upon this:</p>\n\n<pre><code>(10.(\\d)+/(\\S)+)\n</code></pre>\n\n<p>This matches: "10 dot number slash anything-not-whitespace" </p>\n\n<p>But for my use (scraping HTML), this was finding false-positives, so I had to match the above, plus get rid of quotes and greater-than/less-than:</p>\n\n<pre><code>(10.(\\d)+/([^(\\s\\&gt;\\"\\&lt;)])+)\n</code></pre>\n\n<p>I\'m still testing these out, but I\'m feeling hopeful thus far.</p>\n'}, {'answer_id': 10300246, 'author': 'Alix Axel', 'author_id': 89771, 'author_profile': 'https://Stackoverflow.com/users/89771', 'pm_score': 2, 'selected': False, 'text': '<p>Here is my go at it:</p>\n\n<pre><code>(10[.][0-9]{4,}[^\\s"/&lt;&gt;]*/[^\\s"&lt;&gt;]+)\n</code></pre>\n\n<p>And a couple of valid edge cases where this doesn\'t fail, but others seem to do:</p>\n\n<ul>\n<li><a href="http://dx.doi.org/10.1007/978-3-642-28108-2_19" rel="nofollow noreferrer"><code>10.1007/978-3-642-28108-2_19</code></a></li>\n<li><code>10.1007.10/978-3-642-28108-2_19</code> (fictitious example, see <a href="https://stackoverflow.com/a/1876427/89771">@Ju9OR comment</a>)</li>\n<li><a href="http://dx.doi.org/10.1016/S0735-1097%2898%2900347-7" rel="nofollow noreferrer"><code>10.1016/S0735-1097(98)00347-7</code></a></li>\n<li><a href="http://dx.doi.org/10.1579/0044-7447%282006%2935%5B89:RDUICP%5D2.0.CO;2" rel="nofollow noreferrer"><code>10.1579/0044-7447(2006)35\\[89:RDUICP\\]2.0.CO;2</code></a></li>\n</ul>\n\n<p>Also, correctly discards some falsy (X|HT)ML stuff like:</p>\n\n<ul>\n<li><code>&lt;geo coords="10.4515260,51.1656910"&gt;&lt;/geo&gt;</code></li>\n</ul>\n'}, {'answer_id': 10324802, 'author': 'Alix Axel', 'author_id': 89771, 'author_profile': 'https://Stackoverflow.com/users/89771', 'pm_score': 7, 'selected': True, 'text': '<p>Ok, I\'m currently extracting thousands of DOIs from free form text (XML) and I realized that <a href="https://stackoverflow.com/a/10300246/89771">my previous approach</a> had a few problems, namely regarding encoded entities and trailing punctuation, so I went on reading <a href="http://www.doi.org/doi_handbook/2_Numbering.html" rel="nofollow noreferrer">the specification</a> and this is the best I could come with.</p>\n<hr />\n<blockquote>\n<p>The DOI prefix shall be composed of a directory indicator followed by\na registrant code. These two components shall be separated by a full\nstop (period).</p>\n<p>The directory indicator shall be &quot;10&quot;. The directory indicator\ndistinguishes the entire set of character strings (prefix and suffix)\nas digital object identifiers within the resolution system.</p>\n</blockquote>\n<p>Easy enough, the initial <code>\\b</code> prevents us from &quot;matching&quot; a &quot;DOI&quot; that doesn\'t start with <code>10.</code>:</p>\n<pre><code>$pattern = \'\\b(10[.]\';\n</code></pre>\n<hr />\n<blockquote>\n<p>The second element of the DOI prefix shall be the registrant code. The\nregistrant code is a unique string assigned to a registrant.</p>\n</blockquote>\n<p>Also, all assigned registrant code are numeric, and at least 4 digits long, so:</p>\n<pre><code>$pattern = \'\\b(10[.][0-9]{4,}\';\n</code></pre>\n<hr />\n<blockquote>\n<p>The registrant code may be further divided into sub-elements for\nadministrative convenience if desired. Each sub-element of the\nregistrant code shall be preceded by a full stop.</p>\n</blockquote>\n<p><code>$pattern = \'\\b(10[.][0-9]{4,}(?:[.][0-9]+)*\';</code></p>\n<hr />\n<blockquote>\n<p>The DOI syntax shall be made up of a DOI prefix and a DOI suffix\nseparated by a forward slash.</p>\n</blockquote>\n<p>However, this isn\'t absolutely necessary, section 2.2.3 states that uncommon suffix systems may use other conventions (such as <code>10.1000.123456</code> instead of <code>10.1000/123456</code>), but lets cut some slack.</p>\n<p><code>$pattern = \'\\b(10[.][0-9]{4,}(?:[.][0-9]+)*/\';</code></p>\n<hr />\n<blockquote>\n<p>The DOI name is case-insensitive and can incorporate any printable\ncharacters from the legal graphic characters of Unicode. The DOI\nsuffix shall consist of a character string of any length chosen by the\nregistrant. Each suffix shall be unique to the prefix element that\nprecedes it. The unique suffix can be a sequential number, or it might\nincorporate an identifier generated from or based on another system.</p>\n</blockquote>\n<p>Now this is where it gets trickier, from all the DOIs I have processed, I saw the following characters (besides <code>[0-9a-zA-Z]</code> of course) in their <strong>suffixes</strong>: <code>.-()/:-</code> -- so, while it doesn\'t exist, the DOI <code>10.1016.12.31/nature.S0735-1097(98)2000/12/31/34:7-7</code> is completely plausible.</p>\n<p>The logical choice would be to use <code>\\S</code> or the <code>[[:graph:]]</code> PCRE POSIX class, so lets do that:</p>\n<p><code>$pattern = \'\\b(10[.][0-9]{4,}(?:[.][0-9]+)*/\\S+\'; // or</code></p>\n<p><code>$pattern = \'\\b(10[.][0-9]{4,}(?:[.][0-9]+)*/[[:graph:]]+\';</code></p>\n<hr />\n<p>Now we have a difficult problem, the <code>[[:graph:]]</code> class is a super-set of the <code>[[:punct:]]</code> class, which includes characters easily found in free text or any markup language: <code>&quot;\'&amp;&lt;&gt;</code> among others.</p>\n<p>Lets just filter the markup ones for now using a negative lookahead:</p>\n<p><code>$pattern = \'\\b(10[.][0-9]{4,}(?:[.][0-9]+)*/(?:(?![&quot;&amp;\\\'&lt;&gt;])\\S)+\'; // or</code></p>\n<p><code>$pattern = \'\\b(10[.][0-9]{4,}(?:[.][0-9]+)*/(?:(?![&quot;&amp;\\\'&lt;&gt;])[[:graph:]])+\';</code></p>\n<hr />\n<p>The above should cover encoded entities (<code>&amp;</code>), attribute quotes (<code>[&quot;\']</code>) and open / close tags (<code>[&lt;&gt;]</code>).</p>\n<p>Unlike markup languages, free text usually doesn\'t employ punctuation characters unless they are bounded by at least one space <em><strong>or</strong></em> placed at the end of a sentence, for instance:</p>\n<blockquote>\n<p>This is a long DOI:\n<code>10.1016.12.31/nature.S0735-1097(98)2000/12/31/34:7-7</code><strong>!!!</strong></p>\n</blockquote>\n<p>The solution here is to close our capture group and assert another word boundary:</p>\n<p><code>$pattern = \'\\b(10[.][0-9]{4,}(?:[.][0-9]+)*/(?:(?![&quot;&amp;\\\'&lt;&gt;])\\S)+)\\b\'; // or</code></p>\n<p><code>$pattern = \'\\b(10[.][0-9]{4,}(?:[.][0-9]+)*/(?:(?![&quot;&amp;\\\'&lt;&gt;])[[:graph:]])+)\\b\';</code></p>\n<p>And <em>voilá</em>, <a href="http://regexpal.com/?flags=g&amp;regex=%5Cb(10%5B.%5D%5B0-9%5D%7B4%2C%7D(%3F%3A%5B.%5D%5B0-9%5D%2B)*%2F(%3F%3A(%3F!%5B%22%26%5C%27%3C%3E%5D)%5CS)%2B)%5Cb&amp;input=This%20is%20a%20short%20DOI%3A%2010.1000%2F123456.%0AThis%20is%20NOT%20a%20DOI%3A%204210.1000%2F123456.%0AThis%20is%20a%20long%20DOI%3A%2010.1016.12.31%2Fnature.S0735-1097(98)2000%2F12%2F31%2F34%3A7-7!!!%0A%0A10.1007%2F978-3-642-28108-2_19%0A10.1007.10%2F978-3-642-28108-2_19%20(fictitious%20example%2C%20see%20%40Ju9OR%20comment)%0A10.1016%2FS0735-1097(98)00347-7%0A10.1579%2F0044-7447(2006)35%5C%5B89%3ARDUICP%5C%5D2.0.CO%3B2%0A%0AAlso%2C%20correctly%20discards%20some%20falsy%20(X%7CHT)ML%20stuff%20like%3A%0A%0A%3Cgeo%20coords%3D%2210.4515260%2C51.1656910%22%3E%3C%2Fgeo%3E" rel="nofollow noreferrer">here is a demo</a>.</p>\n'}, {'answer_id': 24246270, 'author': 'hobwell', 'author_id': 1781344, 'author_profile': 'https://Stackoverflow.com/users/1781344', 'pm_score': 2, 'selected': False, 'text': "<p>This is a really old and answered question, but here's another potential substitute.</p>\n<p><code>\\b10\\.(\\d+\\.*)+[\\/](([^\\s\\.])+\\.*)+\\b</code></p>\n<p>This assumes that white space is not part of the DOI.</p>\n<p>Haven't tested this for false positives, but it seems to be able to find all the edge cases mentioned in this page.</p>\n"}, {'answer_id': 48524047, 'author': 'Katrin Leinweber', 'author_id': 4341322, 'author_profile': 'https://Stackoverflow.com/users/4341322', 'pm_score': 4, 'selected': False, 'text': '<p><a href="https://www.crossref.org/blog/dois-and-matching-regular-expressions/" rel="noreferrer">CrossRef has a recommendation</a>, that they tested successfully on 99.3% of DOIs (known to them):</p>\n\n<pre><code>/^10.\\d{4,9}/[-._;()/:A-Z0-9]+$/i\n</code></pre>\n'}]
['https://Stackoverflow.com/questions/27910', 'https://Stackoverflow.com', 'https://Stackoverflow.com/users/2963/']