本站源代码
No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

1155 líneas
23KB

  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. if p.flags&EXTENSION_JOIN_LINES != 0 {
  142. return 1
  143. }
  144. // should there be a hard line break here?
  145. if p.flags&EXTENSION_HARD_LINE_BREAK == 0 && !precededByTwoSpaces && !precededByBackslash {
  146. return 0
  147. }
  148. if precededByBackslash && eol > 0 {
  149. out.Truncate(eol - 1)
  150. }
  151. p.r.LineBreak(out)
  152. return 1
  153. }
  154. type linkType int
  155. const (
  156. linkNormal linkType = iota
  157. linkImg
  158. linkDeferredFootnote
  159. linkInlineFootnote
  160. )
  161. func isReferenceStyleLink(data []byte, pos int, t linkType) bool {
  162. if t == linkDeferredFootnote {
  163. return false
  164. }
  165. return pos < len(data)-1 && data[pos] == '[' && data[pos+1] != '^'
  166. }
  167. // '[': parse a link or an image or a footnote
  168. func link(p *parser, out *bytes.Buffer, data []byte, offset int) int {
  169. // no links allowed inside regular links, footnote, and deferred footnotes
  170. if p.insideLink && (offset > 0 && data[offset-1] == '[' || len(data)-1 > offset && data[offset+1] == '^') {
  171. return 0
  172. }
  173. var t linkType
  174. switch {
  175. // special case: ![^text] == deferred footnote (that follows something with
  176. // an exclamation point)
  177. case p.flags&EXTENSION_FOOTNOTES != 0 && len(data)-1 > offset && data[offset+1] == '^':
  178. t = linkDeferredFootnote
  179. // ![alt] == image
  180. case offset > 0 && data[offset-1] == '!':
  181. t = linkImg
  182. // ^[text] == inline footnote
  183. // [^refId] == deferred footnote
  184. case p.flags&EXTENSION_FOOTNOTES != 0:
  185. if offset > 0 && data[offset-1] == '^' {
  186. t = linkInlineFootnote
  187. } else if len(data)-1 > offset && data[offset+1] == '^' {
  188. t = linkDeferredFootnote
  189. }
  190. // [text] == regular link
  191. default:
  192. t = linkNormal
  193. }
  194. data = data[offset:]
  195. var (
  196. i = 1
  197. noteId int
  198. title, link, altContent []byte
  199. textHasNl = false
  200. )
  201. if t == linkDeferredFootnote {
  202. i++
  203. }
  204. brace := 0
  205. // look for the matching closing bracket
  206. for level := 1; level > 0 && i < len(data); i++ {
  207. switch {
  208. case data[i] == '\n':
  209. textHasNl = true
  210. case data[i-1] == '\\':
  211. continue
  212. case data[i] == '[':
  213. level++
  214. case data[i] == ']':
  215. level--
  216. if level <= 0 {
  217. i-- // compensate for extra i++ in for loop
  218. }
  219. }
  220. }
  221. if i >= len(data) {
  222. return 0
  223. }
  224. txtE := i
  225. i++
  226. // skip any amount of whitespace or newline
  227. // (this is much more lax than original markdown syntax)
  228. for i < len(data) && isspace(data[i]) {
  229. i++
  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. p.notesRecord[string(ref.link)] = struct{}{}
  406. link = ref.link
  407. title = ref.title
  408. } else {
  409. // find the reference with matching id
  410. lr, ok := p.getRef(string(id))
  411. if !ok {
  412. return 0
  413. }
  414. if t == linkDeferredFootnote && !p.isFootnote(lr) {
  415. lr.noteId = len(p.notes) + 1
  416. p.notes = append(p.notes, lr)
  417. p.notesRecord[string(lr.link)] = struct{}{}
  418. }
  419. // keep link and title from reference
  420. link = lr.link
  421. // if inline footnote, title == footnote contents
  422. title = lr.title
  423. noteId = lr.noteId
  424. }
  425. // rewind the whitespace
  426. i = txtE + 1
  427. }
  428. // build content: img alt is escaped, link content is parsed
  429. var content bytes.Buffer
  430. if txtE > 1 {
  431. if t == linkImg {
  432. content.Write(data[1:txtE])
  433. } else {
  434. // links cannot contain other links, so turn off link parsing temporarily
  435. insideLink := p.insideLink
  436. p.insideLink = true
  437. p.inline(&content, data[1:txtE])
  438. p.insideLink = insideLink
  439. }
  440. }
  441. var uLink []byte
  442. if t == linkNormal || t == linkImg {
  443. if len(link) > 0 {
  444. var uLinkBuf bytes.Buffer
  445. unescapeText(&uLinkBuf, link)
  446. uLink = uLinkBuf.Bytes()
  447. }
  448. // links need something to click on and somewhere to go
  449. if len(uLink) == 0 || (t == linkNormal && content.Len() == 0) {
  450. return 0
  451. }
  452. }
  453. // call the relevant rendering function
  454. switch t {
  455. case linkNormal:
  456. if len(altContent) > 0 {
  457. p.r.Link(out, uLink, title, altContent)
  458. } else {
  459. p.r.Link(out, uLink, title, content.Bytes())
  460. }
  461. case linkImg:
  462. outSize := out.Len()
  463. outBytes := out.Bytes()
  464. if outSize > 0 && outBytes[outSize-1] == '!' {
  465. out.Truncate(outSize - 1)
  466. }
  467. p.r.Image(out, uLink, title, content.Bytes())
  468. case linkInlineFootnote:
  469. outSize := out.Len()
  470. outBytes := out.Bytes()
  471. if outSize > 0 && outBytes[outSize-1] == '^' {
  472. out.Truncate(outSize - 1)
  473. }
  474. p.r.FootnoteRef(out, link, noteId)
  475. case linkDeferredFootnote:
  476. p.r.FootnoteRef(out, link, noteId)
  477. default:
  478. return 0
  479. }
  480. return i
  481. }
  482. func (p *parser) inlineHTMLComment(out *bytes.Buffer, data []byte) int {
  483. if len(data) < 5 {
  484. return 0
  485. }
  486. if data[0] != '<' || data[1] != '!' || data[2] != '-' || data[3] != '-' {
  487. return 0
  488. }
  489. i := 5
  490. // scan for an end-of-comment marker, across lines if necessary
  491. for i < len(data) && !(data[i-2] == '-' && data[i-1] == '-' && data[i] == '>') {
  492. i++
  493. }
  494. // no end-of-comment marker
  495. if i >= len(data) {
  496. return 0
  497. }
  498. return i + 1
  499. }
  500. // '<' when tags or autolinks are allowed
  501. func leftAngle(p *parser, out *bytes.Buffer, data []byte, offset int) int {
  502. data = data[offset:]
  503. altype := LINK_TYPE_NOT_AUTOLINK
  504. end := tagLength(data, &altype)
  505. if size := p.inlineHTMLComment(out, data); size > 0 {
  506. end = size
  507. }
  508. if end > 2 {
  509. if altype != LINK_TYPE_NOT_AUTOLINK {
  510. var uLink bytes.Buffer
  511. unescapeText(&uLink, data[1:end+1-2])
  512. if uLink.Len() > 0 {
  513. p.r.AutoLink(out, uLink.Bytes(), altype)
  514. }
  515. } else {
  516. p.r.RawHtmlTag(out, data[:end])
  517. }
  518. }
  519. return end
  520. }
  521. // '\\' backslash escape
  522. var escapeChars = []byte("\\`*_{}[]()#+-.!:|&<>~")
  523. func escape(p *parser, out *bytes.Buffer, data []byte, offset int) int {
  524. data = data[offset:]
  525. if len(data) > 1 {
  526. if bytes.IndexByte(escapeChars, data[1]) < 0 {
  527. return 0
  528. }
  529. p.r.NormalText(out, data[1:2])
  530. }
  531. return 2
  532. }
  533. func unescapeText(ob *bytes.Buffer, src []byte) {
  534. i := 0
  535. for i < len(src) {
  536. org := i
  537. for i < len(src) && src[i] != '\\' {
  538. i++
  539. }
  540. if i > org {
  541. ob.Write(src[org:i])
  542. }
  543. if i+1 >= len(src) {
  544. break
  545. }
  546. ob.WriteByte(src[i+1])
  547. i += 2
  548. }
  549. }
  550. // '&' escaped when it doesn't belong to an entity
  551. // valid entities are assumed to be anything matching &#?[A-Za-z0-9]+;
  552. func entity(p *parser, out *bytes.Buffer, data []byte, offset int) int {
  553. data = data[offset:]
  554. end := 1
  555. if end < len(data) && data[end] == '#' {
  556. end++
  557. }
  558. for end < len(data) && isalnum(data[end]) {
  559. end++
  560. }
  561. if end < len(data) && data[end] == ';' {
  562. end++ // real entity
  563. } else {
  564. return 0 // lone '&'
  565. }
  566. p.r.Entity(out, data[:end])
  567. return end
  568. }
  569. func linkEndsWithEntity(data []byte, linkEnd int) bool {
  570. entityRanges := htmlEntity.FindAllIndex(data[:linkEnd], -1)
  571. return entityRanges != nil && entityRanges[len(entityRanges)-1][1] == linkEnd
  572. }
  573. func autoLink(p *parser, out *bytes.Buffer, data []byte, offset int) int {
  574. // quick check to rule out most false hits on ':'
  575. if p.insideLink || len(data) < offset+3 || data[offset+1] != '/' || data[offset+2] != '/' {
  576. return 0
  577. }
  578. // Now a more expensive check to see if we're not inside an anchor element
  579. anchorStart := offset
  580. offsetFromAnchor := 0
  581. for anchorStart > 0 && data[anchorStart] != '<' {
  582. anchorStart--
  583. offsetFromAnchor++
  584. }
  585. anchorStr := anchorRe.Find(data[anchorStart:])
  586. if anchorStr != nil {
  587. out.Write(anchorStr[offsetFromAnchor:])
  588. return len(anchorStr) - offsetFromAnchor
  589. }
  590. // scan backward for a word boundary
  591. rewind := 0
  592. for offset-rewind > 0 && rewind <= 7 && isletter(data[offset-rewind-1]) {
  593. rewind++
  594. }
  595. if rewind > 6 { // longest supported protocol is "mailto" which has 6 letters
  596. return 0
  597. }
  598. origData := data
  599. data = data[offset-rewind:]
  600. if !isSafeLink(data) {
  601. return 0
  602. }
  603. linkEnd := 0
  604. for linkEnd < len(data) && !isEndOfLink(data[linkEnd]) {
  605. linkEnd++
  606. }
  607. // Skip punctuation at the end of the link
  608. if (data[linkEnd-1] == '.' || data[linkEnd-1] == ',') && data[linkEnd-2] != '\\' {
  609. linkEnd--
  610. }
  611. // But don't skip semicolon if it's a part of escaped entity:
  612. if data[linkEnd-1] == ';' && data[linkEnd-2] != '\\' && !linkEndsWithEntity(data, linkEnd) {
  613. linkEnd--
  614. }
  615. // See if the link finishes with a punctuation sign that can be closed.
  616. var copen byte
  617. switch data[linkEnd-1] {
  618. case '"':
  619. copen = '"'
  620. case '\'':
  621. copen = '\''
  622. case ')':
  623. copen = '('
  624. case ']':
  625. copen = '['
  626. case '}':
  627. copen = '{'
  628. default:
  629. copen = 0
  630. }
  631. if copen != 0 {
  632. bufEnd := offset - rewind + linkEnd - 2
  633. openDelim := 1
  634. /* Try to close the final punctuation sign in this same line;
  635. * if we managed to close it outside of the URL, that means that it's
  636. * not part of the URL. If it closes inside the URL, that means it
  637. * is part of the URL.
  638. *
  639. * Examples:
  640. *
  641. * foo http://www.pokemon.com/Pikachu_(Electric) bar
  642. * => http://www.pokemon.com/Pikachu_(Electric)
  643. *
  644. * foo (http://www.pokemon.com/Pikachu_(Electric)) bar
  645. * => http://www.pokemon.com/Pikachu_(Electric)
  646. *
  647. * foo http://www.pokemon.com/Pikachu_(Electric)) bar
  648. * => http://www.pokemon.com/Pikachu_(Electric))
  649. *
  650. * (foo http://www.pokemon.com/Pikachu_(Electric)) bar
  651. * => foo http://www.pokemon.com/Pikachu_(Electric)
  652. */
  653. for bufEnd >= 0 && origData[bufEnd] != '\n' && openDelim != 0 {
  654. if origData[bufEnd] == data[linkEnd-1] {
  655. openDelim++
  656. }
  657. if origData[bufEnd] == copen {
  658. openDelim--
  659. }
  660. bufEnd--
  661. }
  662. if openDelim == 0 {
  663. linkEnd--
  664. }
  665. }
  666. // we were triggered on the ':', so we need to rewind the output a bit
  667. if out.Len() >= rewind {
  668. out.Truncate(len(out.Bytes()) - rewind)
  669. }
  670. var uLink bytes.Buffer
  671. unescapeText(&uLink, data[:linkEnd])
  672. if uLink.Len() > 0 {
  673. p.r.AutoLink(out, uLink.Bytes(), LINK_TYPE_NORMAL)
  674. }
  675. return linkEnd - rewind
  676. }
  677. func isEndOfLink(char byte) bool {
  678. return isspace(char) || char == '<'
  679. }
  680. var validUris = [][]byte{[]byte("http://"), []byte("https://"), []byte("ftp://"), []byte("mailto://")}
  681. var validPaths = [][]byte{[]byte("/"), []byte("./"), []byte("../")}
  682. func isSafeLink(link []byte) bool {
  683. for _, path := range validPaths {
  684. if len(link) >= len(path) && bytes.Equal(link[:len(path)], path) {
  685. if len(link) == len(path) {
  686. return true
  687. } else if isalnum(link[len(path)]) {
  688. return true
  689. }
  690. }
  691. }
  692. for _, prefix := range validUris {
  693. // TODO: handle unicode here
  694. // case-insensitive prefix test
  695. if len(link) > len(prefix) && bytes.Equal(bytes.ToLower(link[:len(prefix)]), prefix) && isalnum(link[len(prefix)]) {
  696. return true
  697. }
  698. }
  699. return false
  700. }
  701. // return the length of the given tag, or 0 is it's not valid
  702. func tagLength(data []byte, autolink *int) int {
  703. var i, j int
  704. // a valid tag can't be shorter than 3 chars
  705. if len(data) < 3 {
  706. return 0
  707. }
  708. // begins with a '<' optionally followed by '/', followed by letter or number
  709. if data[0] != '<' {
  710. return 0
  711. }
  712. if data[1] == '/' {
  713. i = 2
  714. } else {
  715. i = 1
  716. }
  717. if !isalnum(data[i]) {
  718. return 0
  719. }
  720. // scheme test
  721. *autolink = LINK_TYPE_NOT_AUTOLINK
  722. // try to find the beginning of an URI
  723. for i < len(data) && (isalnum(data[i]) || data[i] == '.' || data[i] == '+' || data[i] == '-') {
  724. i++
  725. }
  726. if i > 1 && i < len(data) && data[i] == '@' {
  727. if j = isMailtoAutoLink(data[i:]); j != 0 {
  728. *autolink = LINK_TYPE_EMAIL
  729. return i + j
  730. }
  731. }
  732. if i > 2 && i < len(data) && data[i] == ':' {
  733. *autolink = LINK_TYPE_NORMAL
  734. i++
  735. }
  736. // complete autolink test: no whitespace or ' or "
  737. switch {
  738. case i >= len(data):
  739. *autolink = LINK_TYPE_NOT_AUTOLINK
  740. case *autolink != 0:
  741. j = i
  742. for i < len(data) {
  743. if data[i] == '\\' {
  744. i += 2
  745. } else if data[i] == '>' || data[i] == '\'' || data[i] == '"' || isspace(data[i]) {
  746. break
  747. } else {
  748. i++
  749. }
  750. }
  751. if i >= len(data) {
  752. return 0
  753. }
  754. if i > j && data[i] == '>' {
  755. return i + 1
  756. }
  757. // one of the forbidden chars has been found
  758. *autolink = LINK_TYPE_NOT_AUTOLINK
  759. }
  760. // look for something looking like a tag end
  761. for i < len(data) && data[i] != '>' {
  762. i++
  763. }
  764. if i >= len(data) {
  765. return 0
  766. }
  767. return i + 1
  768. }
  769. // look for the address part of a mail autolink and '>'
  770. // this is less strict than the original markdown e-mail address matching
  771. func isMailtoAutoLink(data []byte) int {
  772. nb := 0
  773. // address is assumed to be: [-@._a-zA-Z0-9]+ with exactly one '@'
  774. for i := 0; i < len(data); i++ {
  775. if isalnum(data[i]) {
  776. continue
  777. }
  778. switch data[i] {
  779. case '@':
  780. nb++
  781. case '-', '.', '_':
  782. // Do nothing.
  783. case '>':
  784. if nb == 1 {
  785. return i + 1
  786. } else {
  787. return 0
  788. }
  789. default:
  790. return 0
  791. }
  792. }
  793. return 0
  794. }
  795. // look for the next emph char, skipping other constructs
  796. func helperFindEmphChar(data []byte, c byte) int {
  797. i := 0
  798. for i < len(data) {
  799. for i < len(data) && data[i] != c && data[i] != '`' && data[i] != '[' {
  800. i++
  801. }
  802. if i >= len(data) {
  803. return 0
  804. }
  805. // do not count escaped chars
  806. if i != 0 && data[i-1] == '\\' {
  807. i++
  808. continue
  809. }
  810. if data[i] == c {
  811. return i
  812. }
  813. if data[i] == '`' {
  814. // skip a code span
  815. tmpI := 0
  816. i++
  817. for i < len(data) && data[i] != '`' {
  818. if tmpI == 0 && data[i] == c {
  819. tmpI = i
  820. }
  821. i++
  822. }
  823. if i >= len(data) {
  824. return tmpI
  825. }
  826. i++
  827. } else if data[i] == '[' {
  828. // skip a link
  829. tmpI := 0
  830. i++
  831. for i < len(data) && data[i] != ']' {
  832. if tmpI == 0 && data[i] == c {
  833. tmpI = i
  834. }
  835. i++
  836. }
  837. i++
  838. for i < len(data) && (data[i] == ' ' || data[i] == '\n') {
  839. i++
  840. }
  841. if i >= len(data) {
  842. return tmpI
  843. }
  844. if data[i] != '[' && data[i] != '(' { // not a link
  845. if tmpI > 0 {
  846. return tmpI
  847. } else {
  848. continue
  849. }
  850. }
  851. cc := data[i]
  852. i++
  853. for i < len(data) && data[i] != cc {
  854. if tmpI == 0 && data[i] == c {
  855. return i
  856. }
  857. i++
  858. }
  859. if i >= len(data) {
  860. return tmpI
  861. }
  862. i++
  863. }
  864. }
  865. return 0
  866. }
  867. func helperEmphasis(p *parser, out *bytes.Buffer, data []byte, c byte) int {
  868. i := 0
  869. // skip one symbol if coming from emph3
  870. if len(data) > 1 && data[0] == c && data[1] == c {
  871. i = 1
  872. }
  873. for i < len(data) {
  874. length := helperFindEmphChar(data[i:], c)
  875. if length == 0 {
  876. return 0
  877. }
  878. i += length
  879. if i >= len(data) {
  880. return 0
  881. }
  882. if i+1 < len(data) && data[i+1] == c {
  883. i++
  884. continue
  885. }
  886. if data[i] == c && !isspace(data[i-1]) {
  887. if p.flags&EXTENSION_NO_INTRA_EMPHASIS != 0 {
  888. if !(i+1 == len(data) || isspace(data[i+1]) || ispunct(data[i+1])) {
  889. continue
  890. }
  891. }
  892. var work bytes.Buffer
  893. p.inline(&work, data[:i])
  894. p.r.Emphasis(out, work.Bytes())
  895. return i + 1
  896. }
  897. }
  898. return 0
  899. }
  900. func helperDoubleEmphasis(p *parser, out *bytes.Buffer, data []byte, c byte) int {
  901. i := 0
  902. for i < len(data) {
  903. length := helperFindEmphChar(data[i:], c)
  904. if length == 0 {
  905. return 0
  906. }
  907. i += length
  908. if i+1 < len(data) && data[i] == c && data[i+1] == c && i > 0 && !isspace(data[i-1]) {
  909. var work bytes.Buffer
  910. p.inline(&work, data[:i])
  911. if work.Len() > 0 {
  912. // pick the right renderer
  913. if c == '~' {
  914. p.r.StrikeThrough(out, work.Bytes())
  915. } else {
  916. p.r.DoubleEmphasis(out, work.Bytes())
  917. }
  918. }
  919. return i + 2
  920. }
  921. i++
  922. }
  923. return 0
  924. }
  925. func helperTripleEmphasis(p *parser, out *bytes.Buffer, data []byte, offset int, c byte) int {
  926. i := 0
  927. origData := data
  928. data = data[offset:]
  929. for i < len(data) {
  930. length := helperFindEmphChar(data[i:], c)
  931. if length == 0 {
  932. return 0
  933. }
  934. i += length
  935. // skip whitespace preceded symbols
  936. if data[i] != c || isspace(data[i-1]) {
  937. continue
  938. }
  939. switch {
  940. case i+2 < len(data) && data[i+1] == c && data[i+2] == c:
  941. // triple symbol found
  942. var work bytes.Buffer
  943. p.inline(&work, data[:i])
  944. if work.Len() > 0 {
  945. p.r.TripleEmphasis(out, work.Bytes())
  946. }
  947. return i + 3
  948. case (i+1 < len(data) && data[i+1] == c):
  949. // double symbol found, hand over to emph1
  950. length = helperEmphasis(p, out, origData[offset-2:], c)
  951. if length == 0 {
  952. return 0
  953. } else {
  954. return length - 2
  955. }
  956. default:
  957. // single symbol found, hand over to emph2
  958. length = helperDoubleEmphasis(p, out, origData[offset-1:], c)
  959. if length == 0 {
  960. return 0
  961. } else {
  962. return length - 1
  963. }
  964. }
  965. }
  966. return 0
  967. }
上海开阖软件有限公司 沪ICP备12045867号-1