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.
 
 
 

1149 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. brace := 0
  202. // look for the matching closing bracket
  203. for level := 1; level > 0 && i < len(data); i++ {
  204. switch {
  205. case data[i] == '\n':
  206. textHasNl = true
  207. case data[i-1] == '\\':
  208. continue
  209. case data[i] == '[':
  210. level++
  211. case data[i] == ']':
  212. level--
  213. if level <= 0 {
  214. i-- // compensate for extra i++ in for loop
  215. }
  216. }
  217. }
  218. if i >= len(data) {
  219. return 0
  220. }
  221. txtE := i
  222. i++
  223. // skip any amount of whitespace or newline
  224. // (this is much more lax than original markdown syntax)
  225. for i < len(data) && isspace(data[i]) {
  226. i++
  227. }
  228. switch {
  229. // inline style link
  230. case i < len(data) && data[i] == '(':
  231. // skip initial whitespace
  232. i++
  233. for i < len(data) && isspace(data[i]) {
  234. i++
  235. }
  236. linkB := i
  237. // look for link end: ' " ), check for new opening braces and take this
  238. // into account, this may lead for overshooting and probably will require
  239. // some fine-tuning.
  240. findlinkend:
  241. for i < len(data) {
  242. switch {
  243. case data[i] == '\\':
  244. i += 2
  245. case data[i] == '(':
  246. brace++
  247. i++
  248. case data[i] == ')':
  249. if brace <= 0 {
  250. break findlinkend
  251. }
  252. brace--
  253. i++
  254. case data[i] == '\'' || data[i] == '"':
  255. break findlinkend
  256. default:
  257. i++
  258. }
  259. }
  260. if i >= len(data) {
  261. return 0
  262. }
  263. linkE := i
  264. // look for title end if present
  265. titleB, titleE := 0, 0
  266. if data[i] == '\'' || data[i] == '"' {
  267. i++
  268. titleB = i
  269. findtitleend:
  270. for i < len(data) {
  271. switch {
  272. case data[i] == '\\':
  273. i += 2
  274. case data[i] == ')':
  275. break findtitleend
  276. default:
  277. i++
  278. }
  279. }
  280. if i >= len(data) {
  281. return 0
  282. }
  283. // skip whitespace after title
  284. titleE = i - 1
  285. for titleE > titleB && isspace(data[titleE]) {
  286. titleE--
  287. }
  288. // check for closing quote presence
  289. if data[titleE] != '\'' && data[titleE] != '"' {
  290. titleB, titleE = 0, 0
  291. linkE = i
  292. }
  293. }
  294. // remove whitespace at the end of the link
  295. for linkE > linkB && isspace(data[linkE-1]) {
  296. linkE--
  297. }
  298. // remove optional angle brackets around the link
  299. if data[linkB] == '<' {
  300. linkB++
  301. }
  302. if data[linkE-1] == '>' {
  303. linkE--
  304. }
  305. // build escaped link and title
  306. if linkE > linkB {
  307. link = data[linkB:linkE]
  308. }
  309. if titleE > titleB {
  310. title = data[titleB:titleE]
  311. }
  312. i++
  313. // reference style link
  314. case isReferenceStyleLink(data, i, t):
  315. var id []byte
  316. altContentConsidered := false
  317. // look for the id
  318. i++
  319. linkB := i
  320. for i < len(data) && data[i] != ']' {
  321. i++
  322. }
  323. if i >= len(data) {
  324. return 0
  325. }
  326. linkE := i
  327. // find the reference
  328. if linkB == linkE {
  329. if textHasNl {
  330. var b bytes.Buffer
  331. for j := 1; j < txtE; j++ {
  332. switch {
  333. case data[j] != '\n':
  334. b.WriteByte(data[j])
  335. case data[j-1] != ' ':
  336. b.WriteByte(' ')
  337. }
  338. }
  339. id = b.Bytes()
  340. } else {
  341. id = data[1:txtE]
  342. altContentConsidered = true
  343. }
  344. } else {
  345. id = data[linkB:linkE]
  346. }
  347. // find the reference with matching id
  348. lr, ok := p.getRef(string(id))
  349. if !ok {
  350. return 0
  351. }
  352. // keep link and title from reference
  353. link = lr.link
  354. title = lr.title
  355. if altContentConsidered {
  356. altContent = lr.text
  357. }
  358. i++
  359. // shortcut reference style link or reference or inline footnote
  360. default:
  361. var id []byte
  362. // craft the id
  363. if textHasNl {
  364. var b bytes.Buffer
  365. for j := 1; j < txtE; j++ {
  366. switch {
  367. case data[j] != '\n':
  368. b.WriteByte(data[j])
  369. case data[j-1] != ' ':
  370. b.WriteByte(' ')
  371. }
  372. }
  373. id = b.Bytes()
  374. } else {
  375. if t == linkDeferredFootnote {
  376. id = data[2:txtE] // get rid of the ^
  377. } else {
  378. id = data[1:txtE]
  379. }
  380. }
  381. if t == linkInlineFootnote {
  382. // create a new reference
  383. noteId = len(p.notes) + 1
  384. var fragment []byte
  385. if len(id) > 0 {
  386. if len(id) < 16 {
  387. fragment = make([]byte, len(id))
  388. } else {
  389. fragment = make([]byte, 16)
  390. }
  391. copy(fragment, slugify(id))
  392. } else {
  393. fragment = append([]byte("footnote-"), []byte(strconv.Itoa(noteId))...)
  394. }
  395. ref := &reference{
  396. noteId: noteId,
  397. hasBlock: false,
  398. link: fragment,
  399. title: id,
  400. }
  401. p.notes = append(p.notes, ref)
  402. link = ref.link
  403. title = ref.title
  404. } else {
  405. // find the reference with matching id
  406. lr, ok := p.getRef(string(id))
  407. if !ok {
  408. return 0
  409. }
  410. if t == linkDeferredFootnote {
  411. lr.noteId = len(p.notes) + 1
  412. p.notes = append(p.notes, lr)
  413. }
  414. // keep link and title from reference
  415. link = lr.link
  416. // if inline footnote, title == footnote contents
  417. title = lr.title
  418. noteId = lr.noteId
  419. }
  420. // rewind the whitespace
  421. i = txtE + 1
  422. }
  423. // build content: img alt is escaped, link content is parsed
  424. var content bytes.Buffer
  425. if txtE > 1 {
  426. if t == linkImg {
  427. content.Write(data[1:txtE])
  428. } else {
  429. // links cannot contain other links, so turn off link parsing temporarily
  430. insideLink := p.insideLink
  431. p.insideLink = true
  432. p.inline(&content, data[1:txtE])
  433. p.insideLink = insideLink
  434. }
  435. }
  436. var uLink []byte
  437. if t == linkNormal || t == linkImg {
  438. if len(link) > 0 {
  439. var uLinkBuf bytes.Buffer
  440. unescapeText(&uLinkBuf, link)
  441. uLink = uLinkBuf.Bytes()
  442. }
  443. // links need something to click on and somewhere to go
  444. if len(uLink) == 0 || (t == linkNormal && content.Len() == 0) {
  445. return 0
  446. }
  447. }
  448. // call the relevant rendering function
  449. switch t {
  450. case linkNormal:
  451. if len(altContent) > 0 {
  452. p.r.Link(out, uLink, title, altContent)
  453. } else {
  454. p.r.Link(out, uLink, title, content.Bytes())
  455. }
  456. case linkImg:
  457. outSize := out.Len()
  458. outBytes := out.Bytes()
  459. if outSize > 0 && outBytes[outSize-1] == '!' {
  460. out.Truncate(outSize - 1)
  461. }
  462. p.r.Image(out, uLink, title, content.Bytes())
  463. case linkInlineFootnote:
  464. outSize := out.Len()
  465. outBytes := out.Bytes()
  466. if outSize > 0 && outBytes[outSize-1] == '^' {
  467. out.Truncate(outSize - 1)
  468. }
  469. p.r.FootnoteRef(out, link, noteId)
  470. case linkDeferredFootnote:
  471. p.r.FootnoteRef(out, link, noteId)
  472. default:
  473. return 0
  474. }
  475. return i
  476. }
  477. func (p *parser) inlineHTMLComment(out *bytes.Buffer, data []byte) int {
  478. if len(data) < 5 {
  479. return 0
  480. }
  481. if data[0] != '<' || data[1] != '!' || data[2] != '-' || data[3] != '-' {
  482. return 0
  483. }
  484. i := 5
  485. // scan for an end-of-comment marker, across lines if necessary
  486. for i < len(data) && !(data[i-2] == '-' && data[i-1] == '-' && data[i] == '>') {
  487. i++
  488. }
  489. // no end-of-comment marker
  490. if i >= len(data) {
  491. return 0
  492. }
  493. return i + 1
  494. }
  495. // '<' when tags or autolinks are allowed
  496. func leftAngle(p *parser, out *bytes.Buffer, data []byte, offset int) int {
  497. data = data[offset:]
  498. altype := LINK_TYPE_NOT_AUTOLINK
  499. end := tagLength(data, &altype)
  500. if size := p.inlineHTMLComment(out, data); size > 0 {
  501. end = size
  502. }
  503. if end > 2 {
  504. if altype != LINK_TYPE_NOT_AUTOLINK {
  505. var uLink bytes.Buffer
  506. unescapeText(&uLink, data[1:end+1-2])
  507. if uLink.Len() > 0 {
  508. p.r.AutoLink(out, uLink.Bytes(), altype)
  509. }
  510. } else {
  511. p.r.RawHtmlTag(out, data[:end])
  512. }
  513. }
  514. return end
  515. }
  516. // '\\' backslash escape
  517. var escapeChars = []byte("\\`*_{}[]()#+-.!:|&<>~")
  518. func escape(p *parser, out *bytes.Buffer, data []byte, offset int) int {
  519. data = data[offset:]
  520. if len(data) > 1 {
  521. if bytes.IndexByte(escapeChars, data[1]) < 0 {
  522. return 0
  523. }
  524. p.r.NormalText(out, data[1:2])
  525. }
  526. return 2
  527. }
  528. func unescapeText(ob *bytes.Buffer, src []byte) {
  529. i := 0
  530. for i < len(src) {
  531. org := i
  532. for i < len(src) && src[i] != '\\' {
  533. i++
  534. }
  535. if i > org {
  536. ob.Write(src[org:i])
  537. }
  538. if i+1 >= len(src) {
  539. break
  540. }
  541. ob.WriteByte(src[i+1])
  542. i += 2
  543. }
  544. }
  545. // '&' escaped when it doesn't belong to an entity
  546. // valid entities are assumed to be anything matching &#?[A-Za-z0-9]+;
  547. func entity(p *parser, out *bytes.Buffer, data []byte, offset int) int {
  548. data = data[offset:]
  549. end := 1
  550. if end < len(data) && data[end] == '#' {
  551. end++
  552. }
  553. for end < len(data) && isalnum(data[end]) {
  554. end++
  555. }
  556. if end < len(data) && data[end] == ';' {
  557. end++ // real entity
  558. } else {
  559. return 0 // lone '&'
  560. }
  561. p.r.Entity(out, data[:end])
  562. return end
  563. }
  564. func linkEndsWithEntity(data []byte, linkEnd int) bool {
  565. entityRanges := htmlEntity.FindAllIndex(data[:linkEnd], -1)
  566. return entityRanges != nil && entityRanges[len(entityRanges)-1][1] == linkEnd
  567. }
  568. func autoLink(p *parser, out *bytes.Buffer, data []byte, offset int) int {
  569. // quick check to rule out most false hits on ':'
  570. if p.insideLink || len(data) < offset+3 || data[offset+1] != '/' || data[offset+2] != '/' {
  571. return 0
  572. }
  573. // Now a more expensive check to see if we're not inside an anchor element
  574. anchorStart := offset
  575. offsetFromAnchor := 0
  576. for anchorStart > 0 && data[anchorStart] != '<' {
  577. anchorStart--
  578. offsetFromAnchor++
  579. }
  580. anchorStr := anchorRe.Find(data[anchorStart:])
  581. if anchorStr != nil {
  582. out.Write(anchorStr[offsetFromAnchor:])
  583. return len(anchorStr) - offsetFromAnchor
  584. }
  585. // scan backward for a word boundary
  586. rewind := 0
  587. for offset-rewind > 0 && rewind <= 7 && isletter(data[offset-rewind-1]) {
  588. rewind++
  589. }
  590. if rewind > 6 { // longest supported protocol is "mailto" which has 6 letters
  591. return 0
  592. }
  593. origData := data
  594. data = data[offset-rewind:]
  595. if !isSafeLink(data) {
  596. return 0
  597. }
  598. linkEnd := 0
  599. for linkEnd < len(data) && !isEndOfLink(data[linkEnd]) {
  600. linkEnd++
  601. }
  602. // Skip punctuation at the end of the link
  603. if (data[linkEnd-1] == '.' || data[linkEnd-1] == ',') && data[linkEnd-2] != '\\' {
  604. linkEnd--
  605. }
  606. // But don't skip semicolon if it's a part of escaped entity:
  607. if data[linkEnd-1] == ';' && data[linkEnd-2] != '\\' && !linkEndsWithEntity(data, linkEnd) {
  608. linkEnd--
  609. }
  610. // See if the link finishes with a punctuation sign that can be closed.
  611. var copen byte
  612. switch data[linkEnd-1] {
  613. case '"':
  614. copen = '"'
  615. case '\'':
  616. copen = '\''
  617. case ')':
  618. copen = '('
  619. case ']':
  620. copen = '['
  621. case '}':
  622. copen = '{'
  623. default:
  624. copen = 0
  625. }
  626. if copen != 0 {
  627. bufEnd := offset - rewind + linkEnd - 2
  628. openDelim := 1
  629. /* Try to close the final punctuation sign in this same line;
  630. * if we managed to close it outside of the URL, that means that it's
  631. * not part of the URL. If it closes inside the URL, that means it
  632. * is part of the URL.
  633. *
  634. * Examples:
  635. *
  636. * foo http://www.pokemon.com/Pikachu_(Electric) bar
  637. * => http://www.pokemon.com/Pikachu_(Electric)
  638. *
  639. * foo (http://www.pokemon.com/Pikachu_(Electric)) bar
  640. * => http://www.pokemon.com/Pikachu_(Electric)
  641. *
  642. * foo http://www.pokemon.com/Pikachu_(Electric)) bar
  643. * => http://www.pokemon.com/Pikachu_(Electric))
  644. *
  645. * (foo http://www.pokemon.com/Pikachu_(Electric)) bar
  646. * => foo http://www.pokemon.com/Pikachu_(Electric)
  647. */
  648. for bufEnd >= 0 && origData[bufEnd] != '\n' && openDelim != 0 {
  649. if origData[bufEnd] == data[linkEnd-1] {
  650. openDelim++
  651. }
  652. if origData[bufEnd] == copen {
  653. openDelim--
  654. }
  655. bufEnd--
  656. }
  657. if openDelim == 0 {
  658. linkEnd--
  659. }
  660. }
  661. // we were triggered on the ':', so we need to rewind the output a bit
  662. if out.Len() >= rewind {
  663. out.Truncate(len(out.Bytes()) - rewind)
  664. }
  665. var uLink bytes.Buffer
  666. unescapeText(&uLink, data[:linkEnd])
  667. if uLink.Len() > 0 {
  668. p.r.AutoLink(out, uLink.Bytes(), LINK_TYPE_NORMAL)
  669. }
  670. return linkEnd - rewind
  671. }
  672. func isEndOfLink(char byte) bool {
  673. return isspace(char) || char == '<'
  674. }
  675. var validUris = [][]byte{[]byte("http://"), []byte("https://"), []byte("ftp://"), []byte("mailto://")}
  676. var validPaths = [][]byte{[]byte("/"), []byte("./"), []byte("../")}
  677. func isSafeLink(link []byte) bool {
  678. for _, path := range validPaths {
  679. if len(link) >= len(path) && bytes.Equal(link[:len(path)], path) {
  680. if len(link) == len(path) {
  681. return true
  682. } else if isalnum(link[len(path)]) {
  683. return true
  684. }
  685. }
  686. }
  687. for _, prefix := range validUris {
  688. // TODO: handle unicode here
  689. // case-insensitive prefix test
  690. if len(link) > len(prefix) && bytes.Equal(bytes.ToLower(link[:len(prefix)]), prefix) && isalnum(link[len(prefix)]) {
  691. return true
  692. }
  693. }
  694. return false
  695. }
  696. // return the length of the given tag, or 0 is it's not valid
  697. func tagLength(data []byte, autolink *int) int {
  698. var i, j int
  699. // a valid tag can't be shorter than 3 chars
  700. if len(data) < 3 {
  701. return 0
  702. }
  703. // begins with a '<' optionally followed by '/', followed by letter or number
  704. if data[0] != '<' {
  705. return 0
  706. }
  707. if data[1] == '/' {
  708. i = 2
  709. } else {
  710. i = 1
  711. }
  712. if !isalnum(data[i]) {
  713. return 0
  714. }
  715. // scheme test
  716. *autolink = LINK_TYPE_NOT_AUTOLINK
  717. // try to find the beginning of an URI
  718. for i < len(data) && (isalnum(data[i]) || data[i] == '.' || data[i] == '+' || data[i] == '-') {
  719. i++
  720. }
  721. if i > 1 && i < len(data) && data[i] == '@' {
  722. if j = isMailtoAutoLink(data[i:]); j != 0 {
  723. *autolink = LINK_TYPE_EMAIL
  724. return i + j
  725. }
  726. }
  727. if i > 2 && i < len(data) && data[i] == ':' {
  728. *autolink = LINK_TYPE_NORMAL
  729. i++
  730. }
  731. // complete autolink test: no whitespace or ' or "
  732. switch {
  733. case i >= len(data):
  734. *autolink = LINK_TYPE_NOT_AUTOLINK
  735. case *autolink != 0:
  736. j = i
  737. for i < len(data) {
  738. if data[i] == '\\' {
  739. i += 2
  740. } else if data[i] == '>' || data[i] == '\'' || data[i] == '"' || isspace(data[i]) {
  741. break
  742. } else {
  743. i++
  744. }
  745. }
  746. if i >= len(data) {
  747. return 0
  748. }
  749. if i > j && data[i] == '>' {
  750. return i + 1
  751. }
  752. // one of the forbidden chars has been found
  753. *autolink = LINK_TYPE_NOT_AUTOLINK
  754. }
  755. // look for something looking like a tag end
  756. for i < len(data) && data[i] != '>' {
  757. i++
  758. }
  759. if i >= len(data) {
  760. return 0
  761. }
  762. return i + 1
  763. }
  764. // look for the address part of a mail autolink and '>'
  765. // this is less strict than the original markdown e-mail address matching
  766. func isMailtoAutoLink(data []byte) int {
  767. nb := 0
  768. // address is assumed to be: [-@._a-zA-Z0-9]+ with exactly one '@'
  769. for i := 0; i < len(data); i++ {
  770. if isalnum(data[i]) {
  771. continue
  772. }
  773. switch data[i] {
  774. case '@':
  775. nb++
  776. case '-', '.', '_':
  777. // Do nothing.
  778. case '>':
  779. if nb == 1 {
  780. return i + 1
  781. } else {
  782. return 0
  783. }
  784. default:
  785. return 0
  786. }
  787. }
  788. return 0
  789. }
  790. // look for the next emph char, skipping other constructs
  791. func helperFindEmphChar(data []byte, c byte) int {
  792. i := 0
  793. for i < len(data) {
  794. for i < len(data) && data[i] != c && data[i] != '`' && data[i] != '[' {
  795. i++
  796. }
  797. if i >= len(data) {
  798. return 0
  799. }
  800. // do not count escaped chars
  801. if i != 0 && data[i-1] == '\\' {
  802. i++
  803. continue
  804. }
  805. if data[i] == c {
  806. return i
  807. }
  808. if data[i] == '`' {
  809. // skip a code span
  810. tmpI := 0
  811. i++
  812. for i < len(data) && data[i] != '`' {
  813. if tmpI == 0 && data[i] == c {
  814. tmpI = i
  815. }
  816. i++
  817. }
  818. if i >= len(data) {
  819. return tmpI
  820. }
  821. i++
  822. } else if data[i] == '[' {
  823. // skip a link
  824. tmpI := 0
  825. i++
  826. for i < len(data) && data[i] != ']' {
  827. if tmpI == 0 && data[i] == c {
  828. tmpI = i
  829. }
  830. i++
  831. }
  832. i++
  833. for i < len(data) && (data[i] == ' ' || data[i] == '\n') {
  834. i++
  835. }
  836. if i >= len(data) {
  837. return tmpI
  838. }
  839. if data[i] != '[' && data[i] != '(' { // not a link
  840. if tmpI > 0 {
  841. return tmpI
  842. } else {
  843. continue
  844. }
  845. }
  846. cc := data[i]
  847. i++
  848. for i < len(data) && data[i] != cc {
  849. if tmpI == 0 && data[i] == c {
  850. return i
  851. }
  852. i++
  853. }
  854. if i >= len(data) {
  855. return tmpI
  856. }
  857. i++
  858. }
  859. }
  860. return 0
  861. }
  862. func helperEmphasis(p *parser, out *bytes.Buffer, data []byte, c byte) int {
  863. i := 0
  864. // skip one symbol if coming from emph3
  865. if len(data) > 1 && data[0] == c && data[1] == c {
  866. i = 1
  867. }
  868. for i < len(data) {
  869. length := helperFindEmphChar(data[i:], c)
  870. if length == 0 {
  871. return 0
  872. }
  873. i += length
  874. if i >= len(data) {
  875. return 0
  876. }
  877. if i+1 < len(data) && data[i+1] == c {
  878. i++
  879. continue
  880. }
  881. if data[i] == c && !isspace(data[i-1]) {
  882. if p.flags&EXTENSION_NO_INTRA_EMPHASIS != 0 {
  883. if !(i+1 == len(data) || isspace(data[i+1]) || ispunct(data[i+1])) {
  884. continue
  885. }
  886. }
  887. var work bytes.Buffer
  888. p.inline(&work, data[:i])
  889. p.r.Emphasis(out, work.Bytes())
  890. return i + 1
  891. }
  892. }
  893. return 0
  894. }
  895. func helperDoubleEmphasis(p *parser, out *bytes.Buffer, data []byte, c byte) int {
  896. i := 0
  897. for i < len(data) {
  898. length := helperFindEmphChar(data[i:], c)
  899. if length == 0 {
  900. return 0
  901. }
  902. i += length
  903. if i+1 < len(data) && data[i] == c && data[i+1] == c && i > 0 && !isspace(data[i-1]) {
  904. var work bytes.Buffer
  905. p.inline(&work, data[:i])
  906. if work.Len() > 0 {
  907. // pick the right renderer
  908. if c == '~' {
  909. p.r.StrikeThrough(out, work.Bytes())
  910. } else {
  911. p.r.DoubleEmphasis(out, work.Bytes())
  912. }
  913. }
  914. return i + 2
  915. }
  916. i++
  917. }
  918. return 0
  919. }
  920. func helperTripleEmphasis(p *parser, out *bytes.Buffer, data []byte, offset int, c byte) int {
  921. i := 0
  922. origData := data
  923. data = data[offset:]
  924. for i < len(data) {
  925. length := helperFindEmphChar(data[i:], c)
  926. if length == 0 {
  927. return 0
  928. }
  929. i += length
  930. // skip whitespace preceded symbols
  931. if data[i] != c || isspace(data[i-1]) {
  932. continue
  933. }
  934. switch {
  935. case i+2 < len(data) && data[i+1] == c && data[i+2] == c:
  936. // triple symbol found
  937. var work bytes.Buffer
  938. p.inline(&work, data[:i])
  939. if work.Len() > 0 {
  940. p.r.TripleEmphasis(out, work.Bytes())
  941. }
  942. return i + 3
  943. case (i+1 < len(data) && data[i+1] == c):
  944. // double symbol found, hand over to emph1
  945. length = helperEmphasis(p, out, origData[offset-2:], c)
  946. if length == 0 {
  947. return 0
  948. } else {
  949. return length - 2
  950. }
  951. default:
  952. // single symbol found, hand over to emph2
  953. length = helperDoubleEmphasis(p, out, origData[offset-1:], c)
  954. if length == 0 {
  955. return 0
  956. } else {
  957. return length - 1
  958. }
  959. }
  960. }
  961. return 0
  962. }