Command line client for Write.as https://write.as/apps/cli
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.

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