You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

1134 lines
22 KiB

  1. //
  2. // Blackfriday Markdown Processor
  3. // Available at http://github.com/russross/blackfriday
  4. //
  5. // Copyright © 2011 Russ Ross <russ@russross.com>.
  6. // Distributed under the Simplified BSD License.
  7. // See README.md for details.
  8. //
  9. //
  10. // Functions to parse inline elements.
  11. //
  12. package blackfriday
  13. import (
  14. "bytes"
  15. "regexp"
  16. "strconv"
  17. )
  18. var (
  19. urlRe = `((https?|ftp):\/\/|\/)[-A-Za-z0-9+&@#\/%?=~_|!:,.;\(\)]+`
  20. anchorRe = regexp.MustCompile(`^(<a\shref="` + urlRe + `"(\stitle="[^"<>]+")?\s?>` + urlRe + `<\/a>)`)
  21. )
  22. // Functions to parse text within a block
  23. // Each function returns the number of chars taken care of
  24. // data is the complete block being rendered
  25. // offset is the number of valid chars before the current cursor
  26. func (p *parser) inline(out *bytes.Buffer, data []byte) {
  27. // this is called recursively: enforce a maximum depth
  28. if p.nesting >= p.maxNesting {
  29. return
  30. }
  31. p.nesting++
  32. i, end := 0, 0
  33. for i < len(data) {
  34. // copy inactive chars into the output
  35. for end < len(data) && p.inlineCallback[data[end]] == nil {
  36. end++
  37. }
  38. p.r.NormalText(out, data[i:end])
  39. if end >= len(data) {
  40. break
  41. }
  42. i = end
  43. // call the trigger
  44. handler := p.inlineCallback[data[end]]
  45. if consumed := handler(p, out, data, i); consumed == 0 {
  46. // no action from the callback; buffer the byte for later
  47. end = i + 1
  48. } else {
  49. // skip past whatever the callback used
  50. i += consumed
  51. end = i
  52. }
  53. }
  54. p.nesting--
  55. }
  56. // single and double emphasis parsing
  57. func emphasis(p *parser, out *bytes.Buffer, data []byte, offset int) int {
  58. data = data[offset:]
  59. c := data[0]
  60. ret := 0
  61. if len(data) > 2 && data[1] != c {
  62. // whitespace cannot follow an opening emphasis;
  63. // strikethrough only takes two characters '~~'
  64. if c == '~' || isspace(data[1]) {
  65. return 0
  66. }
  67. if ret = helperEmphasis(p, out, data[1:], c); ret == 0 {
  68. return 0
  69. }
  70. return ret + 1
  71. }
  72. if len(data) > 3 && data[1] == c && data[2] != c {
  73. if isspace(data[2]) {
  74. return 0
  75. }
  76. if ret = helperDoubleEmphasis(p, out, data[2:], c); ret == 0 {
  77. return 0
  78. }
  79. return ret + 2
  80. }
  81. if len(data) > 4 && data[1] == c && data[2] == c && data[3] != c {
  82. if c == '~' || isspace(data[3]) {
  83. return 0
  84. }
  85. if ret = helperTripleEmphasis(p, out, data, 3, c); ret == 0 {
  86. return 0
  87. }
  88. return ret + 3
  89. }
  90. return 0
  91. }
  92. func codeSpan(p *parser, out *bytes.Buffer, data []byte, offset int) int {
  93. data = data[offset:]
  94. nb := 0
  95. // count the number of backticks in the delimiter
  96. for nb < len(data) && data[nb] == '`' {
  97. nb++
  98. }
  99. // find the next delimiter
  100. i, end := 0, 0
  101. for end = nb; end < len(data) && i < nb; end++ {
  102. if data[end] == '`' {
  103. i++
  104. } else {
  105. i = 0
  106. }
  107. }
  108. // no matching delimiter?
  109. if i < nb && end >= len(data) {
  110. return 0
  111. }
  112. // trim outside whitespace
  113. fBegin := nb
  114. for fBegin < end && data[fBegin] == ' ' {
  115. fBegin++
  116. }
  117. fEnd := end - nb
  118. for fEnd > fBegin && data[fEnd-1] == ' ' {
  119. fEnd--
  120. }
  121. // render the code span
  122. if fBegin != fEnd {
  123. p.r.CodeSpan(out, data[fBegin:fEnd])
  124. }
  125. return end
  126. }
  127. // newline preceded by two spaces becomes <br>
  128. // newline without two spaces works when EXTENSION_HARD_LINE_BREAK is enabled
  129. func lineBreak(p *parser, out *bytes.Buffer, data []byte, offset int) int {
  130. // remove trailing spaces from out
  131. outBytes := out.Bytes()
  132. end := len(outBytes)
  133. eol := end
  134. for eol > 0 && outBytes[eol-1] == ' ' {
  135. eol--
  136. }
  137. out.Truncate(eol)
  138. precededByTwoSpaces := offset >= 2 && data[offset-2] == ' ' && data[offset-1] == ' '
  139. precededByBackslash := offset >= 1 && data[offset-1] == '\\' // see http://spec.commonmark.org/0.18/#example-527
  140. precededByBackslash = precededByBackslash && p.flags&EXTENSION_BACKSLASH_LINE_BREAK != 0
  141. // should there be a hard line break here?
  142. if p.flags&EXTENSION_HARD_LINE_BREAK == 0 && !precededByTwoSpaces && !precededByBackslash {
  143. return 0
  144. }
  145. if precededByBackslash && eol > 0 {
  146. out.Truncate(eol - 1)
  147. }
  148. p.r.LineBreak(out)
  149. return 1
  150. }
  151. type linkType int
  152. const (
  153. linkNormal linkType = iota
  154. linkImg
  155. linkDeferredFootnote
  156. linkInlineFootnote
  157. )
  158. func isReferenceStyleLink(data []byte, pos int, t linkType) bool {
  159. if t == linkDeferredFootnote {
  160. return false
  161. }
  162. return pos < len(data)-1 && data[pos] == '[' && data[pos+1] != '^'
  163. }
  164. // '[': parse a link or an image or a footnote
  165. func link(p *parser, out *bytes.Buffer, data []byte, offset int) int {
  166. // no links allowed inside regular links, footnote, and deferred footnotes
  167. if p.insideLink && (offset > 0 && data[offset-1] == '[' || len(data)-1 > offset && data[offset+1] == '^') {
  168. return 0
  169. }
  170. var t linkType
  171. switch {
  172. // special case: ![^text] == deferred footnote (that follows something with
  173. // an exclamation point)
  174. case p.flags&EXTENSION_FOOTNOTES != 0 && len(data)-1 > offset && data[offset+1] == '^':
  175. t = linkDeferredFootnote
  176. // ![alt] == image
  177. case offset > 0 && data[offset-1] == '!':
  178. t = linkImg
  179. // ^[text] == inline footnote
  180. // [^refId] == deferred footnote
  181. case p.flags&EXTENSION_FOOTNOTES != 0:
  182. if offset > 0 && data[offset-1] == '^' {
  183. t = linkInlineFootnote
  184. } else if len(data)-1 > offset && data[offset+1] == '^' {
  185. t = linkDeferredFootnote
  186. }
  187. // [text] == regular link
  188. default:
  189. t = linkNormal
  190. }
  191. data = data[offset:]
  192. var (
  193. i = 1
  194. noteId int
  195. title, link, altContent []byte
  196. textHasNl = false
  197. )
  198. if t == linkDeferredFootnote {
  199. i++
  200. }
  201. // look for the matching closing bracket
  202. for level := 1; level > 0 && i < len(data); i++ {
  203. switch {
  204. case data[i] == '\n':
  205. textHasNl = true
  206. case data[i-1] == '\\':
  207. continue
  208. case data[i] == '[':
  209. level++
  210. case data[i] == ']':
  211. level--
  212. if level <= 0 {
  213. i-- // compensate for extra i++ in for loop
  214. }
  215. }
  216. }
  217. if i >= len(data) {
  218. return 0
  219. }
  220. txtE := i
  221. i++
  222. // skip any amount of whitespace or newline
  223. // (this is much more lax than original markdown syntax)
  224. for i < len(data) && isspace(data[i]) {
  225. i++
  226. }
  227. // inline style link
  228. switch {
  229. case i < len(data) && data[i] == '(':
  230. // skip initial whitespace
  231. i++
  232. for i < len(data) && isspace(data[i]) {
  233. i++
  234. }
  235. linkB := i
  236. // look for link end: ' " )
  237. findlinkend:
  238. for i < len(data) {
  239. switch {
  240. case data[i] == '\\':
  241. i += 2
  242. case data[i] == ')' || data[i] == '\'' || data[i] == '"':
  243. break findlinkend
  244. default:
  245. i++
  246. }
  247. }
  248. if i >= len(data) {
  249. return 0
  250. }
  251. linkE := i
  252. // look for title end if present
  253. titleB, titleE := 0, 0
  254. if data[i] == '\'' || data[i] == '"' {
  255. i++
  256. titleB = i
  257. findtitleend:
  258. for i < len(data) {
  259. switch {
  260. case data[i] == '\\':
  261. i += 2
  262. case data[i] == ')':
  263. break findtitleend
  264. default:
  265. i++
  266. }
  267. }
  268. if i >= len(data) {
  269. return 0
  270. }
  271. // skip whitespace after title
  272. titleE = i - 1
  273. for titleE > titleB && isspace(data[titleE]) {
  274. titleE--
  275. }
  276. // check for closing quote presence
  277. if data[titleE] != '\'' && data[titleE] != '"' {
  278. titleB, titleE = 0, 0
  279. linkE = i
  280. }
  281. }
  282. // remove whitespace at the end of the link
  283. for linkE > linkB && isspace(data[linkE-1]) {
  284. linkE--
  285. }
  286. // remove optional angle brackets around the link
  287. if data[linkB] == '<' {
  288. linkB++
  289. }
  290. if data[linkE-1] == '>' {
  291. linkE--
  292. }
  293. // build escaped link and title
  294. if linkE > linkB {
  295. link = data[linkB:linkE]
  296. }
  297. if titleE > titleB {
  298. title = data[titleB:titleE]
  299. }
  300. i++
  301. // reference style link
  302. case isReferenceStyleLink(data, i, t):
  303. var id []byte
  304. altContentConsidered := false
  305. // look for the id
  306. i++
  307. linkB := i
  308. for i < len(data) && data[i] != ']' {
  309. i++
  310. }
  311. if i >= len(data) {
  312. return 0
  313. }
  314. linkE := i
  315. // find the reference
  316. if linkB == linkE {
  317. if textHasNl {
  318. var b bytes.Buffer
  319. for j := 1; j < txtE; j++ {
  320. switch {
  321. case data[j] != '\n':
  322. b.WriteByte(data[j])
  323. case data[j-1] != ' ':
  324. b.WriteByte(' ')
  325. }
  326. }
  327. id = b.Bytes()
  328. } else {
  329. id = data[1:txtE]
  330. altContentConsidered = true
  331. }
  332. } else {
  333. id = data[linkB:linkE]
  334. }
  335. // find the reference with matching id
  336. lr, ok := p.getRef(string(id))
  337. if !ok {
  338. return 0
  339. }
  340. // keep link and title from reference
  341. link = lr.link
  342. title = lr.title
  343. if altContentConsidered {
  344. altContent = lr.text
  345. }
  346. i++
  347. // shortcut reference style link or reference or inline footnote
  348. default:
  349. var id []byte
  350. // craft the id
  351. if textHasNl {
  352. var b bytes.Buffer
  353. for j := 1; j < txtE; j++ {
  354. switch {
  355. case data[j] != '\n':
  356. b.WriteByte(data[j])
  357. case data[j-1] != ' ':
  358. b.WriteByte(' ')
  359. }
  360. }
  361. id = b.Bytes()
  362. } else {
  363. if t == linkDeferredFootnote {
  364. id = data[2:txtE] // get rid of the ^
  365. } else {
  366. id = data[1:txtE]
  367. }
  368. }
  369. if t == linkInlineFootnote {
  370. // create a new reference
  371. noteId = len(p.notes) + 1
  372. var fragment []byte
  373. if len(id) > 0 {
  374. if len(id) < 16 {
  375. fragment = make([]byte, len(id))
  376. } else {
  377. fragment = make([]byte, 16)
  378. }
  379. copy(fragment, slugify(id))
  380. } else {
  381. fragment = append([]byte("footnote-"), []byte(strconv.Itoa(noteId))...)
  382. }
  383. ref := &reference{
  384. noteId: noteId,
  385. hasBlock: false,
  386. link: fragment,
  387. title: id,
  388. }
  389. p.notes = append(p.notes, ref)
  390. link = ref.link
  391. title = ref.title
  392. } else {
  393. // find the reference with matching id
  394. lr, ok := p.getRef(string(id))
  395. if !ok {
  396. return 0
  397. }
  398. if t == linkDeferredFootnote {
  399. lr.noteId = len(p.notes) + 1
  400. p.notes = append(p.notes, lr)
  401. }
  402. // keep link and title from reference
  403. link = lr.link
  404. // if inline footnote, title == footnote contents
  405. title = lr.title
  406. noteId = lr.noteId
  407. }
  408. // rewind the whitespace
  409. i = txtE + 1
  410. }
  411. // build content: img alt is escaped, link content is parsed
  412. var content bytes.Buffer
  413. if txtE > 1 {
  414. if t == linkImg {
  415. content.Write(data[1:txtE])
  416. } else {
  417. // links cannot contain other links, so turn off link parsing temporarily
  418. insideLink := p.insideLink
  419. p.insideLink = true
  420. p.inline(&content, data[1:txtE])
  421. p.insideLink = insideLink
  422. }
  423. }
  424. var uLink []byte
  425. if t == linkNormal || t == linkImg {
  426. if len(link) > 0 {
  427. var uLinkBuf bytes.Buffer
  428. unescapeText(&uLinkBuf, link)
  429. uLink = uLinkBuf.Bytes()
  430. }
  431. // links need something to click on and somewhere to go
  432. if len(uLink) == 0 || (t == linkNormal && content.Len() == 0) {
  433. return 0
  434. }
  435. }
  436. // call the relevant rendering function
  437. switch t {
  438. case linkNormal:
  439. if len(altContent) > 0 {
  440. p.r.Link(out, uLink, title, altContent)
  441. } else {
  442. p.r.Link(out, uLink, title, content.Bytes())
  443. }
  444. case linkImg:
  445. outSize := out.Len()
  446. outBytes := out.Bytes()
  447. if outSize > 0 && outBytes[outSize-1] == '!' {
  448. out.Truncate(outSize - 1)
  449. }
  450. p.r.Image(out, uLink, title, content.Bytes())
  451. case linkInlineFootnote:
  452. outSize := out.Len()
  453. outBytes := out.Bytes()
  454. if outSize > 0 && outBytes[outSize-1] == '^' {
  455. out.Truncate(outSize - 1)
  456. }
  457. p.r.FootnoteRef(out, link, noteId)
  458. case linkDeferredFootnote:
  459. p.r.FootnoteRef(out, link, noteId)
  460. default:
  461. return 0
  462. }
  463. return i
  464. }
  465. func (p *parser) inlineHtmlComment(out *bytes.Buffer, data []byte) int {
  466. if len(data) < 5 {
  467. return 0
  468. }
  469. if data[0] != '<' || data[1] != '!' || data[2] != '-' || data[3] != '-' {
  470. return 0
  471. }
  472. i := 5
  473. // scan for an end-of-comment marker, across lines if necessary
  474. for i < len(data) && !(data[i-2] == '-' && data[i-1] == '-' && data[i] == '>') {
  475. i++
  476. }
  477. // no end-of-comment marker
  478. if i >= len(data) {
  479. return 0
  480. }
  481. return i + 1
  482. }
  483. // '<' when tags or autolinks are allowed
  484. func leftAngle(p *parser, out *bytes.Buffer, data []byte, offset int) int {
  485. data = data[offset:]
  486. altype := LINK_TYPE_NOT_AUTOLINK
  487. end := tagLength(data, &altype)
  488. if size := p.inlineHtmlComment(out, data); size > 0 {
  489. end = size
  490. }
  491. if end > 2 {
  492. if altype != LINK_TYPE_NOT_AUTOLINK {
  493. var uLink bytes.Buffer
  494. unescapeText(&uLink, data[1:end+1-2])
  495. if uLink.Len() > 0 {
  496. p.r.AutoLink(out, uLink.Bytes(), altype)
  497. }
  498. } else {
  499. p.r.RawHtmlTag(out, data[:end])
  500. }
  501. }
  502. return end
  503. }
  504. // '\\' backslash escape
  505. var escapeChars = []byte("\\`*_{}[]()#+-.!:|&<>~")
  506. func escape(p *parser, out *bytes.Buffer, data []byte, offset int) int {
  507. data = data[offset:]
  508. if len(data) > 1 {
  509. if bytes.IndexByte(escapeChars, data[1]) < 0 {
  510. return 0
  511. }
  512. p.r.NormalText(out, data[1:2])
  513. }
  514. return 2
  515. }
  516. func unescapeText(ob *bytes.Buffer, src []byte) {
  517. i := 0
  518. for i < len(src) {
  519. org := i
  520. for i < len(src) && src[i] != '\\' {
  521. i++
  522. }
  523. if i > org {
  524. ob.Write(src[org:i])
  525. }
  526. if i+1 >= len(src) {
  527. break
  528. }
  529. ob.WriteByte(src[i+1])
  530. i += 2
  531. }
  532. }
  533. // '&' escaped when it doesn't belong to an entity
  534. // valid entities are assumed to be anything matching &#?[A-Za-z0-9]+;
  535. func entity(p *parser, out *bytes.Buffer, data []byte, offset int) int {
  536. data = data[offset:]
  537. end := 1
  538. if end < len(data) && data[end] == '#' {
  539. end++
  540. }
  541. for end < len(data) && isalnum(data[end]) {
  542. end++
  543. }
  544. if end < len(data) && data[end] == ';' {
  545. end++ // real entity
  546. } else {
  547. return 0 // lone '&'
  548. }
  549. p.r.Entity(out, data[:end])
  550. return end
  551. }
  552. func linkEndsWithEntity(data []byte, linkEnd int) bool {
  553. entityRanges := htmlEntity.FindAllIndex(data[:linkEnd], -1)
  554. return entityRanges != nil && entityRanges[len(entityRanges)-1][1] == linkEnd
  555. }
  556. func autoLink(p *parser, out *bytes.Buffer, data []byte, offset int) int {
  557. // quick check to rule out most false hits on ':'
  558. if p.insideLink || len(data) < offset+3 || data[offset+1] != '/' || data[offset+2] != '/' {
  559. return 0
  560. }
  561. // Now a more expensive check to see if we're not inside an anchor element
  562. anchorStart := offset
  563. offsetFromAnchor := 0
  564. for anchorStart > 0 && data[anchorStart] != '<' {
  565. anchorStart--
  566. offsetFromAnchor++
  567. }
  568. anchorStr := anchorRe.Find(data[anchorStart:])
  569. if anchorStr != nil {
  570. out.Write(anchorStr[offsetFromAnchor:])
  571. return len(anchorStr) - offsetFromAnchor
  572. }
  573. // scan backward for a word boundary
  574. rewind := 0
  575. for offset-rewind > 0 && rewind <= 7 && isletter(data[offset-rewind-1]) {
  576. rewind++
  577. }
  578. if rewind > 6 { // longest supported protocol is "mailto" which has 6 letters
  579. return 0
  580. }
  581. origData := data
  582. data = data[offset-rewind:]
  583. if !isSafeLink(data) {
  584. return 0
  585. }
  586. linkEnd := 0
  587. for linkEnd < len(data) && !isEndOfLink(data[linkEnd]) {
  588. linkEnd++
  589. }
  590. // Skip punctuation at the end of the link
  591. if (data[linkEnd-1] == '.' || data[linkEnd-1] == ',') && data[linkEnd-2] != '\\' {
  592. linkEnd--
  593. }
  594. // But don't skip semicolon if it's a part of escaped entity:
  595. if data[linkEnd-1] == ';' && data[linkEnd-2] != '\\' && !linkEndsWithEntity(data, linkEnd) {
  596. linkEnd--
  597. }
  598. // See if the link finishes with a punctuation sign that can be closed.
  599. var copen byte
  600. switch data[linkEnd-1] {
  601. case '"':
  602. copen = '"'
  603. case '\'':
  604. copen = '\''
  605. case ')':
  606. copen = '('
  607. case ']':
  608. copen = '['
  609. case '}':
  610. copen = '{'
  611. default:
  612. copen = 0
  613. }
  614. if copen != 0 {
  615. bufEnd := offset - rewind + linkEnd - 2
  616. openDelim := 1
  617. /* Try to close the final punctuation sign in this same line;
  618. * if we managed to close it outside of the URL, that means that it's
  619. * not part of the URL. If it closes inside the URL, that means it
  620. * is part of the URL.
  621. *
  622. * Examples:
  623. *
  624. * foo http://www.pokemon.com/Pikachu_(Electric) bar
  625. * => http://www.pokemon.com/Pikachu_(Electric)
  626. *
  627. * foo (http://www.pokemon.com/Pikachu_(Electric)) bar
  628. * => http://www.pokemon.com/Pikachu_(Electric)
  629. *
  630. * foo http://www.pokemon.com/Pikachu_(Electric)) bar
  631. * => http://www.pokemon.com/Pikachu_(Electric))
  632. *
  633. * (foo http://www.pokemon.com/Pikachu_(Electric)) bar
  634. * => foo http://www.pokemon.com/Pikachu_(Electric)
  635. */
  636. for bufEnd >= 0 && origData[bufEnd] != '\n' && openDelim != 0 {
  637. if origData[bufEnd] == data[linkEnd-1] {
  638. openDelim++
  639. }
  640. if origData[bufEnd] == copen {
  641. openDelim--
  642. }
  643. bufEnd--
  644. }
  645. if openDelim == 0 {
  646. linkEnd--
  647. }
  648. }
  649. // we were triggered on the ':', so we need to rewind the output a bit
  650. if out.Len() >= rewind {
  651. out.Truncate(len(out.Bytes()) - rewind)
  652. }
  653. var uLink bytes.Buffer
  654. unescapeText(&uLink, data[:linkEnd])
  655. if uLink.Len() > 0 {
  656. p.r.AutoLink(out, uLink.Bytes(), LINK_TYPE_NORMAL)
  657. }
  658. return linkEnd - rewind
  659. }
  660. func isEndOfLink(char byte) bool {
  661. return isspace(char) || char == '<'
  662. }
  663. var validUris = [][]byte{[]byte("http://"), []byte("https://"), []byte("ftp://"), []byte("mailto://")}
  664. var validPaths = [][]byte{[]byte("/"), []byte("./"), []byte("../")}
  665. func isSafeLink(link []byte) bool {
  666. for _, path := range validPaths {
  667. if len(link) >= len(path) && bytes.Equal(link[:len(path)], path) {
  668. if len(link) == len(path) {
  669. return true
  670. } else if isalnum(link[len(path)]) {
  671. return true
  672. }
  673. }
  674. }
  675. for _, prefix := range validUris {
  676. // TODO: handle unicode here
  677. // case-insensitive prefix test
  678. if len(link) > len(prefix) && bytes.Equal(bytes.ToLower(link[:len(prefix)]), prefix) && isalnum(link[len(prefix)]) {
  679. return true
  680. }
  681. }
  682. return false
  683. }
  684. // return the length of the given tag, or 0 is it's not valid
  685. func tagLength(data []byte, autolink *int) int {
  686. var i, j int
  687. // a valid tag can't be shorter than 3 chars
  688. if len(data) < 3 {
  689. return 0
  690. }
  691. // begins with a '<' optionally followed by '/', followed by letter or number
  692. if data[0] != '<' {
  693. return 0
  694. }
  695. if data[1] == '/' {
  696. i = 2
  697. } else {
  698. i = 1
  699. }
  700. if !isalnum(data[i]) {
  701. return 0
  702. }
  703. // scheme test
  704. *autolink = LINK_TYPE_NOT_AUTOLINK
  705. // try to find the beginning of an URI
  706. for i < len(data) && (isalnum(data[i]) || data[i] == '.' || data[i] == '+' || data[i] == '-') {
  707. i++
  708. }
  709. if i > 1 && i < len(data) && data[i] == '@' {
  710. if j = isMailtoAutoLink(data[i:]); j != 0 {
  711. *autolink = LINK_TYPE_EMAIL
  712. return i + j
  713. }
  714. }
  715. if i > 2 && i < len(data) && data[i] == ':' {
  716. *autolink = LINK_TYPE_NORMAL
  717. i++
  718. }
  719. // complete autolink test: no whitespace or ' or "
  720. switch {
  721. case i >= len(data):
  722. *autolink = LINK_TYPE_NOT_AUTOLINK
  723. case *autolink != 0:
  724. j = i
  725. for i < len(data) {
  726. if data[i] == '\\' {
  727. i += 2
  728. } else if data[i] == '>' || data[i] == '\'' || data[i] == '"' || isspace(data[i]) {
  729. break
  730. } else {
  731. i++
  732. }
  733. }
  734. if i >= len(data) {
  735. return 0
  736. }
  737. if i > j && data[i] == '>' {
  738. return i + 1
  739. }
  740. // one of the forbidden chars has been found
  741. *autolink = LINK_TYPE_NOT_AUTOLINK
  742. }
  743. // look for something looking like a tag end
  744. for i < len(data) && data[i] != '>' {
  745. i++
  746. }
  747. if i >= len(data) {
  748. return 0
  749. }
  750. return i + 1
  751. }
  752. // look for the address part of a mail autolink and '>'
  753. // this is less strict than the original markdown e-mail address matching
  754. func isMailtoAutoLink(data []byte) int {
  755. nb := 0
  756. // address is assumed to be: [-@._a-zA-Z0-9]+ with exactly one '@'
  757. for i := 0; i < len(data); i++ {
  758. if isalnum(data[i]) {
  759. continue
  760. }
  761. switch data[i] {
  762. case '@':
  763. nb++
  764. case '-', '.', '_':
  765. break
  766. case '>':
  767. if nb == 1 {
  768. return i + 1
  769. } else {
  770. return 0
  771. }
  772. default:
  773. return 0
  774. }
  775. }
  776. return 0
  777. }
  778. // look for the next emph char, skipping other constructs
  779. func helperFindEmphChar(data []byte, c byte) int {
  780. i := 0
  781. for i < len(data) {
  782. for i < len(data) && data[i] != c && data[i] != '`' && data[i] != '[' {
  783. i++
  784. }
  785. if i >= len(data) {
  786. return 0
  787. }
  788. // do not count escaped chars
  789. if i != 0 && data[i-1] == '\\' {
  790. i++
  791. continue
  792. }
  793. if data[i] == c {
  794. return i
  795. }
  796. if data[i] == '`' {
  797. // skip a code span
  798. tmpI := 0
  799. i++
  800. for i < len(data) && data[i] != '`' {
  801. if tmpI == 0 && data[i] == c {
  802. tmpI = i
  803. }
  804. i++
  805. }
  806. if i >= len(data) {
  807. return tmpI
  808. }
  809. i++
  810. } else if data[i] == '[' {
  811. // skip a link
  812. tmpI := 0
  813. i++
  814. for i < len(data) && data[i] != ']' {
  815. if tmpI == 0 && data[i] == c {
  816. tmpI = i
  817. }
  818. i++
  819. }
  820. i++
  821. for i < len(data) && (data[i] == ' ' || data[i] == '\n') {
  822. i++
  823. }
  824. if i >= len(data) {
  825. return tmpI
  826. }
  827. if data[i] != '[' && data[i] != '(' { // not a link
  828. if tmpI > 0 {
  829. return tmpI
  830. } else {
  831. continue
  832. }
  833. }
  834. cc := data[i]
  835. i++
  836. for i < len(data) && data[i] != cc {
  837. if tmpI == 0 && data[i] == c {
  838. return i
  839. }
  840. i++
  841. }
  842. if i >= len(data) {
  843. return tmpI
  844. }
  845. i++
  846. }
  847. }
  848. return 0
  849. }
  850. func helperEmphasis(p *parser, out *bytes.Buffer, data []byte, c byte) int {
  851. i := 0
  852. // skip one symbol if coming from emph3
  853. if len(data) > 1 && data[0] == c && data[1] == c {
  854. i = 1
  855. }
  856. for i < len(data) {
  857. length := helperFindEmphChar(data[i:], c)
  858. if length == 0 {
  859. return 0
  860. }
  861. i += length
  862. if i >= len(data) {
  863. return 0
  864. }
  865. if i+1 < len(data) && data[i+1] == c {
  866. i++
  867. continue
  868. }
  869. if data[i] == c && !isspace(data[i-1]) {
  870. if p.flags&EXTENSION_NO_INTRA_EMPHASIS != 0 {
  871. if !(i+1 == len(data) || isspace(data[i+1]) || ispunct(data[i+1])) {
  872. continue
  873. }
  874. }
  875. var work bytes.Buffer
  876. p.inline(&work, data[:i])
  877. p.r.Emphasis(out, work.Bytes())
  878. return i + 1
  879. }
  880. }
  881. return 0
  882. }
  883. func helperDoubleEmphasis(p *parser, out *bytes.Buffer, data []byte, c byte) int {
  884. i := 0
  885. for i < len(data) {
  886. length := helperFindEmphChar(data[i:], c)
  887. if length == 0 {
  888. return 0
  889. }
  890. i += length
  891. if i+1 < len(data) && data[i] == c && data[i+1] == c && i > 0 && !isspace(data[i-1]) {
  892. var work bytes.Buffer
  893. p.inline(&work, data[:i])
  894. if work.Len() > 0 {
  895. // pick the right renderer
  896. if c == '~' {
  897. p.r.StrikeThrough(out, work.Bytes())
  898. } else {
  899. p.r.DoubleEmphasis(out, work.Bytes())
  900. }
  901. }
  902. return i + 2
  903. }
  904. i++
  905. }
  906. return 0
  907. }
  908. func helperTripleEmphasis(p *parser, out *bytes.Buffer, data []byte, offset int, c byte) int {
  909. i := 0
  910. origData := data
  911. data = data[offset:]
  912. for i < len(data) {
  913. length := helperFindEmphChar(data[i:], c)
  914. if length == 0 {
  915. return 0
  916. }
  917. i += length
  918. // skip whitespace preceded symbols
  919. if data[i] != c || isspace(data[i-1]) {
  920. continue
  921. }
  922. switch {
  923. case i+2 < len(data) && data[i+1] == c && data[i+2] == c:
  924. // triple symbol found
  925. var work bytes.Buffer
  926. p.inline(&work, data[:i])
  927. if work.Len() > 0 {
  928. p.r.TripleEmphasis(out, work.Bytes())
  929. }
  930. return i + 3
  931. case (i+1 < len(data) && data[i+1] == c):
  932. // double symbol found, hand over to emph1
  933. length = helperEmphasis(p, out, origData[offset-2:], c)
  934. if length == 0 {
  935. return 0
  936. } else {
  937. return length - 2
  938. }
  939. default:
  940. // single symbol found, hand over to emph2
  941. length = helperDoubleEmphasis(p, out, origData[offset-1:], c)
  942. if length == 0 {
  943. return 0
  944. } else {
  945. return length - 1
  946. }
  947. }
  948. }
  949. return 0
  950. }