1465aec67SAndreas Gohr<?php 2465aec67SAndreas Gohr 3465aec67SAndreas Gohrnamespace dokuwiki\test\Parsing\ParserMode; 4465aec67SAndreas Gohr 5465aec67SAndreas Gohruse dokuwiki\Parsing\ParserMode\Externallink; 6465aec67SAndreas Gohruse dokuwiki\Parsing\ParserMode\Internallink; 7465aec67SAndreas Gohr 8465aec67SAndreas Gohr/** 9465aec67SAndreas Gohr * Tests for the {@see Externallink} parser mode. 10465aec67SAndreas Gohr * 11465aec67SAndreas Gohr * Covers the classic DokuWiki autolink behavior (bare URLs, www./ftp. shortcuts, IPv4/IPv6, 12465aec67SAndreas Gohr * scheme allow-listing), the Markdown angle-bracket autolink form (CommonMark §6.5), and the 13465aec67SAndreas Gohr * GFM autolink extension trim step (paren balancing, trailing entity-ref decoding). 14465aec67SAndreas Gohr * 15465aec67SAndreas Gohr * @group parser_links 16465aec67SAndreas Gohr */ 17465aec67SAndreas Gohrclass ExternallinkTest extends ParserTestBase 18465aec67SAndreas Gohr{ 19465aec67SAndreas Gohr public function setUp(): void 20465aec67SAndreas Gohr { 21465aec67SAndreas Gohr parent::setUp(); 22*47a02a10SAndreas Gohr $this->setSyntax('md'); 23465aec67SAndreas Gohr } 24465aec67SAndreas Gohr 25465aec67SAndreas Gohr // ----- basic bare-URL autolink ----- 26465aec67SAndreas Gohr 27465aec67SAndreas Gohr function testSimple() { 28465aec67SAndreas Gohr $this->P->addMode('externallink', new Externallink()); 29465aec67SAndreas Gohr $this->P->parse("Foo http://www.google.com Bar"); 30465aec67SAndreas Gohr $calls = [ 31465aec67SAndreas Gohr ['document_start', []], 32465aec67SAndreas Gohr ['p_open', []], 33465aec67SAndreas Gohr ['cdata', ["\n" . 'Foo ']], 34465aec67SAndreas Gohr ['externallink', ['http://www.google.com', null]], 35465aec67SAndreas Gohr ['cdata', [' Bar']], 36465aec67SAndreas Gohr ['p_close', []], 37465aec67SAndreas Gohr ['document_end', []], 38465aec67SAndreas Gohr ]; 39465aec67SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 40465aec67SAndreas Gohr } 41465aec67SAndreas Gohr 42465aec67SAndreas Gohr function testCase() { 43465aec67SAndreas Gohr $this->P->addMode('externallink', new Externallink()); 44465aec67SAndreas Gohr $this->P->parse("Foo HTTP://WWW.GOOGLE.COM Bar"); 45465aec67SAndreas Gohr $calls = [ 46465aec67SAndreas Gohr ['document_start', []], 47465aec67SAndreas Gohr ['p_open', []], 48465aec67SAndreas Gohr ['cdata', ["\n" . 'Foo ']], 49465aec67SAndreas Gohr ['externallink', ['HTTP://WWW.GOOGLE.COM', null]], 50465aec67SAndreas Gohr ['cdata', [' Bar']], 51465aec67SAndreas Gohr ['p_close', []], 52465aec67SAndreas Gohr ['document_end', []], 53465aec67SAndreas Gohr ]; 54465aec67SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 55465aec67SAndreas Gohr } 56465aec67SAndreas Gohr 57465aec67SAndreas Gohr function testIPv4() { 58465aec67SAndreas Gohr $this->P->addMode('externallink', new Externallink()); 59465aec67SAndreas Gohr $this->P->parse("Foo http://123.123.3.21/foo Bar"); 60465aec67SAndreas Gohr $calls = [ 61465aec67SAndreas Gohr ['document_start', []], 62465aec67SAndreas Gohr ['p_open', []], 63465aec67SAndreas Gohr ['cdata', ["\n" . 'Foo ']], 64465aec67SAndreas Gohr ['externallink', ['http://123.123.3.21/foo', null]], 65465aec67SAndreas Gohr ['cdata', [' Bar']], 66465aec67SAndreas Gohr ['p_close', []], 67465aec67SAndreas Gohr ['document_end', []], 68465aec67SAndreas Gohr ]; 69465aec67SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 70465aec67SAndreas Gohr } 71465aec67SAndreas Gohr 72465aec67SAndreas Gohr function testIPv6() { 73465aec67SAndreas Gohr $this->P->addMode('externallink', new Externallink()); 74465aec67SAndreas Gohr $this->P->parse("Foo http://[3ffe:2a00:100:7031::1]/foo Bar"); 75465aec67SAndreas Gohr $calls = [ 76465aec67SAndreas Gohr ['document_start', []], 77465aec67SAndreas Gohr ['p_open', []], 78465aec67SAndreas Gohr ['cdata', ["\n" . 'Foo ']], 79465aec67SAndreas Gohr ['externallink', ['http://[3ffe:2a00:100:7031::1]/foo', null]], 80465aec67SAndreas Gohr ['cdata', [' Bar']], 81465aec67SAndreas Gohr ['p_close', []], 82465aec67SAndreas Gohr ['document_end', []], 83465aec67SAndreas Gohr ]; 84465aec67SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 85465aec67SAndreas Gohr } 86465aec67SAndreas Gohr 87465aec67SAndreas Gohr function testMulti() { 88465aec67SAndreas Gohr $this->teardown(); 89465aec67SAndreas Gohr 90465aec67SAndreas Gohr $links = [ 91465aec67SAndreas Gohr 'http://www.google.com', 92465aec67SAndreas Gohr 'HTTP://WWW.GOOGLE.COM', 93465aec67SAndreas Gohr 'http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html', 94465aec67SAndreas Gohr 'http://[1080:0:0:0:8:800:200C:417A]/index.html', 95465aec67SAndreas Gohr 'http://[3ffe:2a00:100:7031::1]', 96465aec67SAndreas Gohr 'http://[1080::8:800:200C:417A]/foo', 97465aec67SAndreas Gohr 'http://[::192.9.5.5]/ipng', 98465aec67SAndreas Gohr 'http://[::FFFF:129.144.52.38]:80/index.html', 99465aec67SAndreas Gohr 'http://[2010:836B:4179::836B:4179]', 100465aec67SAndreas Gohr ]; 101465aec67SAndreas Gohr $titles = [false, null, 'foo bar']; 102465aec67SAndreas Gohr foreach ($links as $link) { 103465aec67SAndreas Gohr foreach ($titles as $title) { 104465aec67SAndreas Gohr if ($title === false) { 105465aec67SAndreas Gohr $source = $link; 106465aec67SAndreas Gohr $name = null; 107465aec67SAndreas Gohr } elseif ($title === null) { 108465aec67SAndreas Gohr $source = "[[$link]]"; 109465aec67SAndreas Gohr $name = null; 110465aec67SAndreas Gohr } else { 111465aec67SAndreas Gohr $source = "[[$link|$title]]"; 112465aec67SAndreas Gohr $name = $title; 113465aec67SAndreas Gohr } 114465aec67SAndreas Gohr $this->setup(); 115465aec67SAndreas Gohr $this->P->addMode('internallink', new Internallink()); 116465aec67SAndreas Gohr $this->P->addMode('externallink', new Externallink()); 117465aec67SAndreas Gohr $this->P->parse("Foo $source Bar"); 118465aec67SAndreas Gohr $calls = [ 119465aec67SAndreas Gohr ['document_start', []], 120465aec67SAndreas Gohr ['p_open', []], 121465aec67SAndreas Gohr ['cdata', ["\n" . 'Foo ']], 122465aec67SAndreas Gohr ['externallink', [$link, $name]], 123465aec67SAndreas Gohr ['cdata', [' Bar']], 124465aec67SAndreas Gohr ['p_close', []], 125465aec67SAndreas Gohr ['document_end', []], 126465aec67SAndreas Gohr ]; 127465aec67SAndreas Gohr $this->assertCalls($calls, $this->H->calls, $source); 128465aec67SAndreas Gohr $this->teardown(); 129465aec67SAndreas Gohr } 130465aec67SAndreas Gohr } 131465aec67SAndreas Gohr 132465aec67SAndreas Gohr $this->setup(); 133465aec67SAndreas Gohr } 134465aec67SAndreas Gohr 135465aec67SAndreas Gohr function testJavascriptScheme() { 136465aec67SAndreas Gohr $this->P->addMode('externallink', new Externallink()); 137465aec67SAndreas Gohr $this->P->parse("Foo javascript:alert('XSS'); Bar"); 138465aec67SAndreas Gohr $calls = [ 139465aec67SAndreas Gohr ['document_start', []], 140465aec67SAndreas Gohr ['p_open', []], 141465aec67SAndreas Gohr ['cdata', ["\nFoo javascript:alert('XSS'); Bar"]], 142465aec67SAndreas Gohr ['p_close', []], 143465aec67SAndreas Gohr ['document_end', []], 144465aec67SAndreas Gohr ]; 145465aec67SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 146465aec67SAndreas Gohr } 147465aec67SAndreas Gohr 148465aec67SAndreas Gohr // ----- www. / ftp. shortcuts ----- 149465aec67SAndreas Gohr 150465aec67SAndreas Gohr function testWWWLink() { 151465aec67SAndreas Gohr $this->P->addMode('externallink', new Externallink()); 152465aec67SAndreas Gohr $this->P->parse("Foo www.google.com Bar"); 153465aec67SAndreas Gohr $calls = [ 154465aec67SAndreas Gohr ['document_start', []], 155465aec67SAndreas Gohr ['p_open', []], 156465aec67SAndreas Gohr ['cdata', ["\n" . 'Foo ']], 157465aec67SAndreas Gohr ['externallink', ['http://www.google.com', 'www.google.com']], 158465aec67SAndreas Gohr ['cdata', [' Bar']], 159465aec67SAndreas Gohr ['p_close', []], 160465aec67SAndreas Gohr ['document_end', []], 161465aec67SAndreas Gohr ]; 162465aec67SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 163465aec67SAndreas Gohr } 164465aec67SAndreas Gohr 165465aec67SAndreas Gohr function testWWWLinkStartOfLine() { 166465aec67SAndreas Gohr // Regression test for issue #2399 167465aec67SAndreas Gohr $calls = [ 168465aec67SAndreas Gohr ['document_start', []], 169465aec67SAndreas Gohr ['p_open', []], 170465aec67SAndreas Gohr ['externallink', ['http://www.google.com', 'www.google.com']], 171465aec67SAndreas Gohr ['cdata', [' Bar']], 172465aec67SAndreas Gohr ['p_close', []], 173465aec67SAndreas Gohr ['document_end', []], 174465aec67SAndreas Gohr ]; 175465aec67SAndreas Gohr $instructions = p_get_instructions("www.google.com Bar"); 176465aec67SAndreas Gohr $this->assertCalls($calls, $instructions); 177465aec67SAndreas Gohr } 178465aec67SAndreas Gohr 179465aec67SAndreas Gohr function testWWWLinkInRoundBrackets() { 180465aec67SAndreas Gohr $this->P->addMode('externallink', new Externallink()); 181465aec67SAndreas Gohr $this->P->parse("Foo (www.google.com) Bar"); 182465aec67SAndreas Gohr $calls = [ 183465aec67SAndreas Gohr ['document_start', []], 184465aec67SAndreas Gohr ['p_open', []], 185465aec67SAndreas Gohr ['cdata', ["\n" . 'Foo (']], 186465aec67SAndreas Gohr ['externallink', ['http://www.google.com', 'www.google.com']], 187465aec67SAndreas Gohr ['cdata', [') Bar']], 188465aec67SAndreas Gohr ['p_close', []], 189465aec67SAndreas Gohr ['document_end', []], 190465aec67SAndreas Gohr ]; 191465aec67SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 192465aec67SAndreas Gohr } 193465aec67SAndreas Gohr 194465aec67SAndreas Gohr function testWWWLinkInPath() { 195465aec67SAndreas Gohr $this->P->addMode('externallink', new Externallink()); 196465aec67SAndreas Gohr // See issue #936. Should NOT generate a link! 197465aec67SAndreas Gohr $this->P->parse("Foo /home/subdir/www/www.something.de/somedir/ Bar"); 198465aec67SAndreas Gohr $calls = [ 199465aec67SAndreas Gohr ['document_start', []], 200465aec67SAndreas Gohr ['p_open', []], 201465aec67SAndreas Gohr ['cdata', ["\n" . 'Foo /home/subdir/www/www.something.de/somedir/ Bar']], 202465aec67SAndreas Gohr ['p_close', []], 203465aec67SAndreas Gohr ['document_end', []], 204465aec67SAndreas Gohr ]; 205465aec67SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 206465aec67SAndreas Gohr } 207465aec67SAndreas Gohr 208465aec67SAndreas Gohr function testWWWLinkFollowingPath() { 209465aec67SAndreas Gohr $this->P->addMode('externallink', new Externallink()); 210465aec67SAndreas Gohr $this->P->parse("Foo /home/subdir/www/ www.something.de/somedir/ Bar"); 211465aec67SAndreas Gohr $calls = [ 212465aec67SAndreas Gohr ['document_start', []], 213465aec67SAndreas Gohr ['p_open', []], 214465aec67SAndreas Gohr ['cdata', ["\n" . 'Foo /home/subdir/www/ ']], 215465aec67SAndreas Gohr ['externallink', ['http://www.something.de/somedir/', 'www.something.de/somedir/']], 216465aec67SAndreas Gohr ['cdata', [' Bar']], 217465aec67SAndreas Gohr ['p_close', []], 218465aec67SAndreas Gohr ['document_end', []], 219465aec67SAndreas Gohr ]; 220465aec67SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 221465aec67SAndreas Gohr } 222465aec67SAndreas Gohr 223465aec67SAndreas Gohr function testFTPLink() { 224465aec67SAndreas Gohr $this->P->addMode('externallink', new Externallink()); 225465aec67SAndreas Gohr $this->P->parse("Foo ftp.sunsite.com Bar"); 226465aec67SAndreas Gohr $calls = [ 227465aec67SAndreas Gohr ['document_start', []], 228465aec67SAndreas Gohr ['p_open', []], 229465aec67SAndreas Gohr ['cdata', ["\n" . 'Foo ']], 230465aec67SAndreas Gohr ['externallink', ['ftp://ftp.sunsite.com', 'ftp.sunsite.com']], 231465aec67SAndreas Gohr ['cdata', [' Bar']], 232465aec67SAndreas Gohr ['p_close', []], 233465aec67SAndreas Gohr ['document_end', []], 234465aec67SAndreas Gohr ]; 235465aec67SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 236465aec67SAndreas Gohr } 237465aec67SAndreas Gohr 238465aec67SAndreas Gohr function testFTPLinkInPath() { 239465aec67SAndreas Gohr $this->P->addMode('externallink', new Externallink()); 240465aec67SAndreas Gohr // See issue #936. Should NOT generate a link! 241465aec67SAndreas Gohr $this->P->parse("Foo /home/subdir/www/ftp.something.de/somedir/ Bar"); 242465aec67SAndreas Gohr $calls = [ 243465aec67SAndreas Gohr ['document_start', []], 244465aec67SAndreas Gohr ['p_open', []], 245465aec67SAndreas Gohr ['cdata', ["\n" . 'Foo /home/subdir/www/ftp.something.de/somedir/ Bar']], 246465aec67SAndreas Gohr ['p_close', []], 247465aec67SAndreas Gohr ['document_end', []], 248465aec67SAndreas Gohr ]; 249465aec67SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 250465aec67SAndreas Gohr } 251465aec67SAndreas Gohr 252465aec67SAndreas Gohr function testFTPLinkFollowingPath() { 253465aec67SAndreas Gohr $this->P->addMode('externallink', new Externallink()); 254465aec67SAndreas Gohr $this->P->parse("Foo /home/subdir/www/ ftp.something.de/somedir/ Bar"); 255465aec67SAndreas Gohr $calls = [ 256465aec67SAndreas Gohr ['document_start', []], 257465aec67SAndreas Gohr ['p_open', []], 258465aec67SAndreas Gohr ['cdata', ["\n" . 'Foo /home/subdir/www/ ']], 259465aec67SAndreas Gohr ['externallink', ['ftp://ftp.something.de/somedir/', 'ftp.something.de/somedir/']], 260465aec67SAndreas Gohr ['cdata', [' Bar']], 261465aec67SAndreas Gohr ['p_close', []], 262465aec67SAndreas Gohr ['document_end', []], 263465aec67SAndreas Gohr ]; 264465aec67SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 265465aec67SAndreas Gohr } 266465aec67SAndreas Gohr 267465aec67SAndreas Gohr // ----- Markdown angle-bracket autolinks (§6.5) ----- 268465aec67SAndreas Gohr 269465aec67SAndreas Gohr function testAngleBracketAutolink() { 270465aec67SAndreas Gohr $this->P->addMode('externallink', new Externallink()); 271465aec67SAndreas Gohr $this->P->parse("Foo <http://www.google.com> Bar"); 272465aec67SAndreas Gohr $calls = [ 273465aec67SAndreas Gohr ['document_start', []], 274465aec67SAndreas Gohr ['p_open', []], 275465aec67SAndreas Gohr ['cdata', ["\n" . 'Foo ']], 276465aec67SAndreas Gohr ['externallink', ['http://www.google.com', 'http://www.google.com']], 277465aec67SAndreas Gohr ['cdata', [' Bar']], 278465aec67SAndreas Gohr ['p_close', []], 279465aec67SAndreas Gohr ['document_end', []], 280465aec67SAndreas Gohr ]; 281465aec67SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 282465aec67SAndreas Gohr } 283465aec67SAndreas Gohr 284465aec67SAndreas Gohr function testAngleBracketDisqualifiedByInternalWhitespace() { 285465aec67SAndreas Gohr $this->P->addMode('externallink', new Externallink()); 286465aec67SAndreas Gohr $this->P->parse("Foo <http://www.google.com bim> Bar"); 287465aec67SAndreas Gohr // Internal whitespace disqualifies the autolink. The whole envelope is consumed as cdata so the 288465aec67SAndreas Gohr // bare-URL detector cannot pick up http://www.google.com inside and leave dangling brackets. 289465aec67SAndreas Gohr $calls = [ 290465aec67SAndreas Gohr ['document_start', []], 291465aec67SAndreas Gohr ['p_open', []], 292465aec67SAndreas Gohr ['cdata', ["\nFoo <http://www.google.com bim> Bar"]], 293465aec67SAndreas Gohr ['p_close', []], 294465aec67SAndreas Gohr ['document_end', []], 295465aec67SAndreas Gohr ]; 296465aec67SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 297465aec67SAndreas Gohr } 298465aec67SAndreas Gohr 299465aec67SAndreas Gohr function testAngleBracketDisqualifiedByLeadingWhitespace() { 300465aec67SAndreas Gohr $this->P->addMode('externallink', new Externallink()); 301465aec67SAndreas Gohr $this->P->parse("Foo < http://www.google.com > Bar"); 302465aec67SAndreas Gohr $calls = [ 303465aec67SAndreas Gohr ['document_start', []], 304465aec67SAndreas Gohr ['p_open', []], 305465aec67SAndreas Gohr ['cdata', ["\nFoo < http://www.google.com > Bar"]], 306465aec67SAndreas Gohr ['p_close', []], 307465aec67SAndreas Gohr ['document_end', []], 308465aec67SAndreas Gohr ]; 309465aec67SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 310465aec67SAndreas Gohr } 311465aec67SAndreas Gohr 312465aec67SAndreas Gohr function testAngleBracketUnregisteredScheme() { 313465aec67SAndreas Gohr $this->P->addMode('externallink', new Externallink()); 314465aec67SAndreas Gohr // mailto is not in the default conf/scheme.conf allow-list, so no per-scheme angle pattern is built 315465aec67SAndreas Gohr // for it. The brackets fall through to cdata, matching DokuWiki's bare-URL scheme policy. 316465aec67SAndreas Gohr $this->P->parse("Foo <mailto:foo@example.com> Bar"); 317465aec67SAndreas Gohr $calls = [ 318465aec67SAndreas Gohr ['document_start', []], 319465aec67SAndreas Gohr ['p_open', []], 320465aec67SAndreas Gohr ['cdata', ["\nFoo <mailto:foo@example.com> Bar"]], 321465aec67SAndreas Gohr ['p_close', []], 322465aec67SAndreas Gohr ['document_end', []], 323465aec67SAndreas Gohr ]; 324465aec67SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 325465aec67SAndreas Gohr } 326465aec67SAndreas Gohr 327465aec67SAndreas Gohr function testAngleBracketInactiveInDwMode() { 328*47a02a10SAndreas Gohr $this->setSyntax('dw'); 329465aec67SAndreas Gohr $this->P->addMode('externallink', new Externallink()); 330465aec67SAndreas Gohr // In DW-only syntax, angle-bracket processing is intentionally not active. The bare-URL pattern still 331465aec67SAndreas Gohr // picks up the URL inside and the angle brackets fall through as literal text — matches the pre-merge 332465aec67SAndreas Gohr // behavior of DokuWiki's Externallink mode. 333465aec67SAndreas Gohr $this->P->parse("Foo <http://www.google.com> Bar"); 334465aec67SAndreas Gohr $calls = [ 335465aec67SAndreas Gohr ['document_start', []], 336465aec67SAndreas Gohr ['p_open', []], 337465aec67SAndreas Gohr ['cdata', ["\n" . 'Foo <']], 338465aec67SAndreas Gohr ['externallink', ['http://www.google.com', null]], 339465aec67SAndreas Gohr ['cdata', ['> Bar']], 340465aec67SAndreas Gohr ['p_close', []], 341465aec67SAndreas Gohr ['document_end', []], 342465aec67SAndreas Gohr ]; 343465aec67SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 344465aec67SAndreas Gohr } 345465aec67SAndreas Gohr 346465aec67SAndreas Gohr // ----- GFM autolink extension: paren balancing ----- 347465aec67SAndreas Gohr 348465aec67SAndreas Gohr function testBalancedParensInUrl() { 349465aec67SAndreas Gohr $this->P->addMode('externallink', new Externallink()); 350465aec67SAndreas Gohr $this->P->parse('See www.example.com/path(foo) end'); 351465aec67SAndreas Gohr $calls = [ 352465aec67SAndreas Gohr ['document_start', []], 353465aec67SAndreas Gohr ['p_open', []], 354465aec67SAndreas Gohr ['cdata', ["\nSee "]], 355465aec67SAndreas Gohr ['externallink', ['http://www.example.com/path(foo)', 'www.example.com/path(foo)']], 356465aec67SAndreas Gohr ['cdata', [' end']], 357465aec67SAndreas Gohr ['p_close', []], 358465aec67SAndreas Gohr ['document_end', []], 359465aec67SAndreas Gohr ]; 360465aec67SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 361465aec67SAndreas Gohr } 362465aec67SAndreas Gohr 363465aec67SAndreas Gohr function testTrailingUnbalancedParenExcluded() { 364465aec67SAndreas Gohr $this->P->addMode('externallink', new Externallink()); 365465aec67SAndreas Gohr $this->P->parse('See (www.example.com/path(foo)) end'); 366465aec67SAndreas Gohr $calls = [ 367465aec67SAndreas Gohr ['document_start', []], 368465aec67SAndreas Gohr ['p_open', []], 369465aec67SAndreas Gohr ['cdata', ["\nSee ("]], 370465aec67SAndreas Gohr ['externallink', ['http://www.example.com/path(foo)', 'www.example.com/path(foo)']], 371465aec67SAndreas Gohr ['cdata', [') end']], 372465aec67SAndreas Gohr ['p_close', []], 373465aec67SAndreas Gohr ['document_end', []], 374465aec67SAndreas Gohr ]; 375465aec67SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 376465aec67SAndreas Gohr } 377465aec67SAndreas Gohr 378465aec67SAndreas Gohr function testMultipleTrailingParensTrimmedUntilBalanced() { 379465aec67SAndreas Gohr $this->P->addMode('externallink', new Externallink()); 380465aec67SAndreas Gohr // Inner `(foo)` is balanced and stays inside the URL; the two unbalanced trailing `)` are peeled off. 381465aec67SAndreas Gohr $this->P->parse('See www.example.com/path(foo))) end'); 382465aec67SAndreas Gohr $calls = [ 383465aec67SAndreas Gohr ['document_start', []], 384465aec67SAndreas Gohr ['p_open', []], 385465aec67SAndreas Gohr ['cdata', ["\nSee "]], 386465aec67SAndreas Gohr ['externallink', ['http://www.example.com/path(foo)', 'www.example.com/path(foo)']], 387465aec67SAndreas Gohr ['cdata', [')) end']], 388465aec67SAndreas Gohr ['p_close', []], 389465aec67SAndreas Gohr ['document_end', []], 390465aec67SAndreas Gohr ]; 391465aec67SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 392465aec67SAndreas Gohr } 393465aec67SAndreas Gohr 394465aec67SAndreas Gohr function testParenInsideUrlNoTrailing() { 395465aec67SAndreas Gohr $this->P->addMode('externallink', new Externallink()); 396465aec67SAndreas Gohr $this->P->parse('See www.example.com/search?q=(business))+ok end'); 397465aec67SAndreas Gohr $calls = [ 398465aec67SAndreas Gohr ['document_start', []], 399465aec67SAndreas Gohr ['p_open', []], 400465aec67SAndreas Gohr ['cdata', ["\nSee "]], 401465aec67SAndreas Gohr ['externallink', [ 402465aec67SAndreas Gohr 'http://www.example.com/search?q=(business))+ok', 403465aec67SAndreas Gohr 'www.example.com/search?q=(business))+ok' 404465aec67SAndreas Gohr ]], 405465aec67SAndreas Gohr ['cdata', [' end']], 406465aec67SAndreas Gohr ['p_close', []], 407465aec67SAndreas Gohr ['document_end', []], 408465aec67SAndreas Gohr ]; 409465aec67SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 410465aec67SAndreas Gohr } 411465aec67SAndreas Gohr 412465aec67SAndreas Gohr // ----- GFM autolink extension: trailing entity references ----- 413465aec67SAndreas Gohr 414465aec67SAndreas Gohr function testTrailingValidEntityDecodedToUnicode() { 415465aec67SAndreas Gohr $this->P->addMode('externallink', new Externallink()); 416465aec67SAndreas Gohr $this->P->parse('See http://example.com/© end'); 417465aec67SAndreas Gohr $calls = [ 418465aec67SAndreas Gohr ['document_start', []], 419465aec67SAndreas Gohr ['p_open', []], 420465aec67SAndreas Gohr ['cdata', ["\nSee "]], 421465aec67SAndreas Gohr ['externallink', ['http://example.com/', null]], 422465aec67SAndreas Gohr ['cdata', ['© end']], 423465aec67SAndreas Gohr ['p_close', []], 424465aec67SAndreas Gohr ['document_end', []], 425465aec67SAndreas Gohr ]; 426465aec67SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 427465aec67SAndreas Gohr } 428465aec67SAndreas Gohr 429465aec67SAndreas Gohr function testTrailingUnknownEntityRoundTripsLiterally() { 430465aec67SAndreas Gohr $this->P->addMode('externallink', new Externallink()); 431465aec67SAndreas Gohr $this->P->parse('See http://example.com/&hl; end'); 432465aec67SAndreas Gohr $calls = [ 433465aec67SAndreas Gohr ['document_start', []], 434465aec67SAndreas Gohr ['p_open', []], 435465aec67SAndreas Gohr ['cdata', ["\nSee "]], 436465aec67SAndreas Gohr ['externallink', ['http://example.com/', null]], 437465aec67SAndreas Gohr ['cdata', ['&hl; end']], 438465aec67SAndreas Gohr ['p_close', []], 439465aec67SAndreas Gohr ['document_end', []], 440465aec67SAndreas Gohr ]; 441465aec67SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 442465aec67SAndreas Gohr } 443465aec67SAndreas Gohr 444465aec67SAndreas Gohr function testTrailingNumericEntityDecoded() { 445465aec67SAndreas Gohr $this->P->addMode('externallink', new Externallink()); 446465aec67SAndreas Gohr $this->P->parse('See http://example.com/A end'); 447465aec67SAndreas Gohr $calls = [ 448465aec67SAndreas Gohr ['document_start', []], 449465aec67SAndreas Gohr ['p_open', []], 450465aec67SAndreas Gohr ['cdata', ["\nSee "]], 451465aec67SAndreas Gohr ['externallink', ['http://example.com/', null]], 452465aec67SAndreas Gohr ['cdata', ['A end']], 453465aec67SAndreas Gohr ['p_close', []], 454465aec67SAndreas Gohr ['document_end', []], 455465aec67SAndreas Gohr ]; 456465aec67SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 457465aec67SAndreas Gohr } 458465aec67SAndreas Gohr 459465aec67SAndreas Gohr function testNonTrailingEntityStaysInsideUrl() { 460465aec67SAndreas Gohr $this->P->addMode('externallink', new Externallink()); 461465aec67SAndreas Gohr $this->P->parse('See http://example.com/©more end'); 462465aec67SAndreas Gohr $calls = [ 463465aec67SAndreas Gohr ['document_start', []], 464465aec67SAndreas Gohr ['p_open', []], 465465aec67SAndreas Gohr ['cdata', ["\nSee "]], 466465aec67SAndreas Gohr ['externallink', ['http://example.com/©more', null]], 467465aec67SAndreas Gohr ['cdata', [' end']], 468465aec67SAndreas Gohr ['p_close', []], 469465aec67SAndreas Gohr ['document_end', []], 470465aec67SAndreas Gohr ]; 471465aec67SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 472465aec67SAndreas Gohr } 473465aec67SAndreas Gohr 474465aec67SAndreas Gohr function testMixtureParenThenEntityPeelsBoth() { 475465aec67SAndreas Gohr $this->P->addMode('externallink', new Externallink()); 476465aec67SAndreas Gohr $this->P->parse('See (http://example.com/path)© end'); 477465aec67SAndreas Gohr $calls = [ 478465aec67SAndreas Gohr ['document_start', []], 479465aec67SAndreas Gohr ['p_open', []], 480465aec67SAndreas Gohr ['cdata', ["\nSee ("]], 481465aec67SAndreas Gohr ['externallink', ['http://example.com/path', null]], 482465aec67SAndreas Gohr ['cdata', [')© end']], 483465aec67SAndreas Gohr ['p_close', []], 484465aec67SAndreas Gohr ['document_end', []], 485465aec67SAndreas Gohr ]; 486465aec67SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 487465aec67SAndreas Gohr } 488465aec67SAndreas Gohr 489465aec67SAndreas Gohr function testMixtureMultipleEntitiesAndParens() { 490465aec67SAndreas Gohr $this->P->addMode('externallink', new Externallink()); 491465aec67SAndreas Gohr $this->P->parse('See http://example.com/)©)&hl; end'); 492465aec67SAndreas Gohr $calls = [ 493465aec67SAndreas Gohr ['document_start', []], 494465aec67SAndreas Gohr ['p_open', []], 495465aec67SAndreas Gohr ['cdata', ["\nSee "]], 496465aec67SAndreas Gohr ['externallink', ['http://example.com/', null]], 497465aec67SAndreas Gohr ['cdata', [')©)&hl; end']], 498465aec67SAndreas Gohr ['p_close', []], 499465aec67SAndreas Gohr ['document_end', []], 500465aec67SAndreas Gohr ]; 501465aec67SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 502465aec67SAndreas Gohr } 503465aec67SAndreas Gohr} 504