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

BlockProcessor.cs 42 kB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790
  1. //
  2. // MarkdownDeep - http://www.toptensoftware.com/markdowndeep
  3. // Copyright (C) 2010-2011 Topten Software
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this product except in
  6. // compliance with the License. You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software distributed under the License is
  11. // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and limitations under the License.
  13. //
  14. using System;
  15. using System.Collections.Generic;
  16. using System.Linq;
  17. using System.Text;
  18. namespace MarkdownDeep
  19. {
  20. public class BlockProcessor : StringScanner
  21. {
  22. #region Enums
  23. internal enum MarkdownInHtmlMode
  24. {
  25. NA, // No markdown attribute on the tag
  26. Block, // markdown=1 or markdown=block
  27. Span, // markdown=1 or markdown=span
  28. Deep, // markdown=deep - recursive block mode
  29. Off, // Markdown="something else"
  30. }
  31. #endregion
  32. public BlockProcessor(Markdown m, bool MarkdownInHtml)
  33. {
  34. m_markdown = m;
  35. m_bMarkdownInHtml = MarkdownInHtml;
  36. m_parentType = BlockType.Blank;
  37. }
  38. internal BlockProcessor(Markdown m, bool MarkdownInHtml, BlockType parentType)
  39. {
  40. m_markdown = m;
  41. m_bMarkdownInHtml = MarkdownInHtml;
  42. m_parentType = parentType;
  43. }
  44. internal List<Block> Process(string str)
  45. {
  46. return ScanLines(str);
  47. }
  48. internal List<Block> ScanLines(string str)
  49. {
  50. // Reset string scanner
  51. Reset(str);
  52. return ScanLines();
  53. }
  54. internal List<Block> ScanLines(string str, int start, int len)
  55. {
  56. Reset(str, start, len);
  57. return ScanLines();
  58. }
  59. internal bool StartTable(TableSpec spec, List<Block> lines)
  60. {
  61. // Mustn't have more than 1 preceeding line
  62. if (lines.Count > 1)
  63. return false;
  64. // Rewind, parse the header row then fast forward back to current pos
  65. if (lines.Count == 1)
  66. {
  67. int savepos = Position;
  68. Position = lines[0].LineStart;
  69. spec.Headers = spec.ParseRow(this);
  70. if (spec.Headers == null)
  71. return false;
  72. Position = savepos;
  73. lines.Clear();
  74. }
  75. // Parse all rows
  76. while (true)
  77. {
  78. int savepos = Position;
  79. var row=spec.ParseRow(this);
  80. if (row!=null)
  81. {
  82. spec.Rows.Add(row);
  83. continue;
  84. }
  85. Position = savepos;
  86. break;
  87. }
  88. return true;
  89. }
  90. internal List<Block> ScanLines()
  91. {
  92. // The final set of blocks will be collected here
  93. var blocks = new List<Block>();
  94. // The current paragraph/list/codeblock etc will be accumulated here
  95. // before being collapsed into a block and store in above `blocks` list
  96. var lines = new List<Block>();
  97. // Add all blocks
  98. BlockType PrevBlockType = BlockType.unsafe_html;
  99. while (!Eof)
  100. {
  101. // Remember if the previous line was blank
  102. bool bPreviousBlank = PrevBlockType == BlockType.Blank;
  103. // Get the next block
  104. var b = EvaluateLine();
  105. PrevBlockType = b.BlockType;
  106. // For dd blocks, we need to know if it was preceeded by a blank line
  107. // so store that fact as the block's data.
  108. if (b.BlockType == BlockType.dd)
  109. {
  110. b.Data = bPreviousBlank;
  111. }
  112. // SetExt header?
  113. if (b.BlockType == BlockType.post_h1 || b.BlockType == BlockType.post_h2)
  114. {
  115. if (lines.Count > 0)
  116. {
  117. // Remove the previous line and collapse the current paragraph
  118. var prevline = lines.Pop();
  119. CollapseLines(blocks, lines);
  120. // If previous line was blank,
  121. if (prevline.BlockType != BlockType.Blank)
  122. {
  123. // Convert the previous line to a heading and add to block list
  124. prevline.RevertToPlain();
  125. prevline.BlockType = b.BlockType == BlockType.post_h1 ? BlockType.h1 : BlockType.h2;
  126. blocks.Add(prevline);
  127. continue;
  128. }
  129. }
  130. // Couldn't apply setext header to a previous line
  131. if (b.BlockType == BlockType.post_h1)
  132. {
  133. // `===` gets converted to normal paragraph
  134. b.RevertToPlain();
  135. lines.Add(b);
  136. }
  137. else
  138. {
  139. // `---` gets converted to hr
  140. if (b.ContentLen >= 3)
  141. {
  142. b.BlockType = BlockType.hr;
  143. blocks.Add(b);
  144. }
  145. else
  146. {
  147. b.RevertToPlain();
  148. lines.Add(b);
  149. }
  150. }
  151. continue;
  152. }
  153. // Work out the current paragraph type
  154. BlockType currentBlockType = lines.Count > 0 ? lines[0].BlockType : BlockType.Blank;
  155. // Starting a table?
  156. if (b.BlockType == BlockType.table_spec)
  157. {
  158. // Get the table spec, save position
  159. TableSpec spec = (TableSpec)b.Data;
  160. int savepos = Position;
  161. if (!StartTable(spec, lines))
  162. {
  163. // Not a table, revert the tablespec row to plain,
  164. // fast forward back to where we were up to and continue
  165. // on as if nothing happened
  166. Position = savepos;
  167. b.RevertToPlain();
  168. }
  169. else
  170. {
  171. blocks.Add(b);
  172. continue;
  173. }
  174. }
  175. // Process this line
  176. switch (b.BlockType)
  177. {
  178. case BlockType.Blank:
  179. switch (currentBlockType)
  180. {
  181. case BlockType.Blank:
  182. FreeBlock(b);
  183. break;
  184. case BlockType.p:
  185. CollapseLines(blocks, lines);
  186. FreeBlock(b);
  187. break;
  188. case BlockType.quote:
  189. case BlockType.ol_li:
  190. case BlockType.ul_li:
  191. case BlockType.dd:
  192. case BlockType.footnote:
  193. case BlockType.indent:
  194. lines.Add(b);
  195. break;
  196. default:
  197. System.Diagnostics.Debug.Assert(false);
  198. break;
  199. }
  200. break;
  201. case BlockType.p:
  202. switch (currentBlockType)
  203. {
  204. case BlockType.Blank:
  205. case BlockType.p:
  206. lines.Add(b);
  207. break;
  208. case BlockType.quote:
  209. case BlockType.ol_li:
  210. case BlockType.ul_li:
  211. case BlockType.dd:
  212. case BlockType.footnote:
  213. var prevline = lines.Last();
  214. if (prevline.BlockType == BlockType.Blank)
  215. {
  216. CollapseLines(blocks, lines);
  217. lines.Add(b);
  218. }
  219. else
  220. {
  221. lines.Add(b);
  222. }
  223. break;
  224. case BlockType.indent:
  225. CollapseLines(blocks, lines);
  226. lines.Add(b);
  227. break;
  228. default:
  229. System.Diagnostics.Debug.Assert(false);
  230. break;
  231. }
  232. break;
  233. case BlockType.indent:
  234. switch (currentBlockType)
  235. {
  236. case BlockType.Blank:
  237. // Start a code block
  238. lines.Add(b);
  239. break;
  240. case BlockType.p:
  241. case BlockType.quote:
  242. var prevline = lines.Last();
  243. if (prevline.BlockType == BlockType.Blank)
  244. {
  245. // Start a code block after a paragraph
  246. CollapseLines(blocks, lines);
  247. lines.Add(b);
  248. }
  249. else
  250. {
  251. // indented line in paragraph, just continue it
  252. b.RevertToPlain();
  253. lines.Add(b);
  254. }
  255. break;
  256. case BlockType.ol_li:
  257. case BlockType.ul_li:
  258. case BlockType.dd:
  259. case BlockType.footnote:
  260. case BlockType.indent:
  261. lines.Add(b);
  262. break;
  263. default:
  264. System.Diagnostics.Debug.Assert(false);
  265. break;
  266. }
  267. break;
  268. case BlockType.quote:
  269. if (currentBlockType != BlockType.quote)
  270. {
  271. CollapseLines(blocks, lines);
  272. }
  273. lines.Add(b);
  274. break;
  275. case BlockType.ol_li:
  276. case BlockType.ul_li:
  277. switch (currentBlockType)
  278. {
  279. case BlockType.Blank:
  280. lines.Add(b);
  281. break;
  282. case BlockType.p:
  283. case BlockType.quote:
  284. var prevline = lines.Last();
  285. if (prevline.BlockType == BlockType.Blank || m_parentType==BlockType.ol_li || m_parentType==BlockType.ul_li || m_parentType==BlockType.dd)
  286. {
  287. // List starting after blank line after paragraph or quote
  288. CollapseLines(blocks, lines);
  289. lines.Add(b);
  290. }
  291. else
  292. {
  293. // List's can't start in middle of a paragraph
  294. b.RevertToPlain();
  295. lines.Add(b);
  296. }
  297. break;
  298. case BlockType.ol_li:
  299. case BlockType.ul_li:
  300. if (b.BlockType!=BlockType.ol_li && b.BlockType!=BlockType.ul_li)
  301. {
  302. CollapseLines(blocks, lines);
  303. }
  304. lines.Add(b);
  305. break;
  306. case BlockType.dd:
  307. case BlockType.footnote:
  308. if (b.BlockType != currentBlockType)
  309. {
  310. CollapseLines(blocks, lines);
  311. }
  312. lines.Add(b);
  313. break;
  314. case BlockType.indent:
  315. // List after code block
  316. CollapseLines(blocks, lines);
  317. lines.Add(b);
  318. break;
  319. }
  320. break;
  321. case BlockType.dd:
  322. case BlockType.footnote:
  323. switch (currentBlockType)
  324. {
  325. case BlockType.Blank:
  326. case BlockType.p:
  327. case BlockType.dd:
  328. case BlockType.footnote:
  329. CollapseLines(blocks, lines);
  330. lines.Add(b);
  331. break;
  332. default:
  333. b.RevertToPlain();
  334. lines.Add(b);
  335. break;
  336. }
  337. break;
  338. default:
  339. CollapseLines(blocks, lines);
  340. blocks.Add(b);
  341. break;
  342. }
  343. }
  344. CollapseLines(blocks, lines);
  345. if (m_markdown.ExtraMode)
  346. {
  347. BuildDefinitionLists(blocks);
  348. }
  349. return blocks;
  350. }
  351. internal Block CreateBlock()
  352. {
  353. return m_markdown.CreateBlock();
  354. }
  355. internal void FreeBlock(Block b)
  356. {
  357. m_markdown.FreeBlock(b);
  358. }
  359. internal void FreeBlocks(List<Block> blocks)
  360. {
  361. foreach (var b in blocks)
  362. FreeBlock(b);
  363. blocks.Clear();
  364. }
  365. internal string RenderLines(List<Block> lines)
  366. {
  367. StringBuilder b = m_markdown.GetStringBuilder();
  368. foreach (var l in lines)
  369. {
  370. b.Append(l.Buf, l.ContentStart, l.ContentLen);
  371. b.Append('\n');
  372. }
  373. return b.ToString();
  374. }
  375. internal void CollapseLines(List<Block> blocks, List<Block> lines)
  376. {
  377. // Remove trailing blank lines
  378. while (lines.Count>0 && lines.Last().BlockType == BlockType.Blank)
  379. {
  380. FreeBlock(lines.Pop());
  381. }
  382. // Quit if empty
  383. if (lines.Count == 0)
  384. {
  385. return;
  386. }
  387. // What sort of block?
  388. switch (lines[0].BlockType)
  389. {
  390. case BlockType.p:
  391. {
  392. // Collapse all lines into a single paragraph
  393. var para = CreateBlock();
  394. para.BlockType = BlockType.p;
  395. para.Buf = lines[0].Buf;
  396. para.ContentStart = lines[0].ContentStart;
  397. para.ContentEnd = lines.Last().ContentEnd;
  398. blocks.Add(para);
  399. FreeBlocks(lines);
  400. break;
  401. }
  402. case BlockType.quote:
  403. {
  404. // Create a new quote block
  405. var quote = new Block(BlockType.quote);
  406. quote.Children = new BlockProcessor(m_markdown, m_bMarkdownInHtml, BlockType.quote).Process(RenderLines(lines));
  407. FreeBlocks(lines);
  408. blocks.Add(quote);
  409. break;
  410. }
  411. case BlockType.ol_li:
  412. case BlockType.ul_li:
  413. blocks.Add(BuildList(lines));
  414. break;
  415. case BlockType.dd:
  416. if (blocks.Count > 0)
  417. {
  418. var prev=blocks[blocks.Count-1];
  419. switch (prev.BlockType)
  420. {
  421. case BlockType.p:
  422. prev.BlockType = BlockType.dt;
  423. break;
  424. case BlockType.dd:
  425. break;
  426. default:
  427. var wrapper = CreateBlock();
  428. wrapper.BlockType = BlockType.dt;
  429. wrapper.Children = new List<Block>();
  430. wrapper.Children.Add(prev);
  431. blocks.Pop();
  432. blocks.Add(wrapper);
  433. break;
  434. }
  435. }
  436. blocks.Add(BuildDefinition(lines));
  437. break;
  438. case BlockType.footnote:
  439. m_markdown.AddFootnote(BuildFootnote(lines));
  440. break;
  441. case BlockType.indent:
  442. {
  443. var codeblock = new Block(BlockType.codeblock);
  444. /*
  445. if (m_markdown.FormatCodeBlockAttributes != null)
  446. {
  447. // Does the line first line look like a syntax specifier
  448. var firstline = lines[0].Content;
  449. if (firstline.StartsWith("{{") && firstline.EndsWith("}}"))
  450. {
  451. codeblock.data = firstline.Substring(2, firstline.Length - 4);
  452. lines.RemoveAt(0);
  453. }
  454. }
  455. */
  456. codeblock.Children = new List<Block>();
  457. codeblock.Children.AddRange(lines);
  458. blocks.Add(codeblock);
  459. lines.Clear();
  460. break;
  461. }
  462. }
  463. }
  464. Block EvaluateLine()
  465. {
  466. // Create a block
  467. Block b=CreateBlock();
  468. // Store line start
  469. b.LineStart=Position;
  470. b.Buf=Input;
  471. // Scan the line
  472. b.ContentStart = Position;
  473. b.ContentLen = -1;
  474. b.BlockType=EvaluateLine(b);
  475. // If end of line not returned, do it automatically
  476. if (b.ContentLen < 0)
  477. {
  478. // Move to end of line
  479. SkipToEol();
  480. b.ContentLen = Position - b.ContentStart;
  481. }
  482. // Setup line length
  483. b.LineLen=Position-b.LineStart;
  484. // Next line
  485. SkipEol();
  486. // Create block
  487. return b;
  488. }
  489. BlockType EvaluateLine(Block b)
  490. {
  491. // Empty line?
  492. if (Eol)
  493. return BlockType.Blank;
  494. // Save start of line position
  495. int line_start= Position;
  496. // ## Heading ##
  497. char ch=Current;
  498. if (ch == '#')
  499. {
  500. // Work out heading level
  501. int level = 1;
  502. SkipForward(1);
  503. while (Current == '#')
  504. {
  505. level++;
  506. SkipForward(1);
  507. }
  508. // Limit of 6
  509. if (level > 6)
  510. level = 6;
  511. // Skip any whitespace
  512. SkipLinespace();
  513. // Save start position
  514. b.ContentStart = Position;
  515. // Jump to end
  516. SkipToEol();
  517. // In extra mode, check for a trailing HTML ID
  518. if (m_markdown.ExtraMode && !m_markdown.SafeMode)
  519. {
  520. int end=Position;
  521. string strID = Utils.StripHtmlID(Input, b.ContentStart, ref end);
  522. if (strID!=null)
  523. {
  524. b.Data = strID;
  525. Position = end;
  526. }
  527. }
  528. // Rewind over trailing hashes
  529. while (Position>b.ContentStart && CharAtOffset(-1) == '#')
  530. {
  531. SkipForward(-1);
  532. }
  533. // Rewind over trailing spaces
  534. while (Position>b.ContentStart && char.IsWhiteSpace(CharAtOffset(-1)))
  535. {
  536. SkipForward(-1);
  537. }
  538. // Create the heading block
  539. b.ContentEnd = Position;
  540. SkipToEol();
  541. return BlockType.h1 + (level - 1);
  542. }
  543. // Check for entire line as - or = for setext h1 and h2
  544. if (ch=='-' || ch=='=')
  545. {
  546. // Skip all matching characters
  547. char chType = ch;
  548. while (Current==chType)
  549. {
  550. SkipForward(1);
  551. }
  552. // Trailing whitespace allowed
  553. SkipLinespace();
  554. // If not at eol, must have found something other than setext header
  555. if (Eol)
  556. {
  557. return chType == '=' ? BlockType.post_h1 : BlockType.post_h2;
  558. }
  559. Position = line_start;
  560. }
  561. // MarkdownExtra Table row indicator?
  562. if (m_markdown.ExtraMode)
  563. {
  564. TableSpec spec = TableSpec.Parse(this);
  565. if (spec!=null)
  566. {
  567. b.Data = spec;
  568. return BlockType.table_spec;
  569. }
  570. Position = line_start;
  571. }
  572. // Fenced code blocks?
  573. if((m_markdown.ExtraMode && (ch == '~' || ch=='`')) || (m_markdown.GitHubCodeBlocks && (ch=='`')))
  574. {
  575. if (ProcessFencedCodeBlock(b))
  576. return b.BlockType;
  577. // Rewind
  578. Position = line_start;
  579. }
  580. // Scan the leading whitespace, remembering how many spaces and where the first tab is
  581. int tabPos = -1;
  582. int leadingSpaces = 0;
  583. while (!Eol)
  584. {
  585. if (Current == ' ')
  586. {
  587. if (tabPos < 0)
  588. leadingSpaces++;
  589. }
  590. else if (Current == '\t')
  591. {
  592. if (tabPos < 0)
  593. tabPos = Position;
  594. }
  595. else
  596. {
  597. // Something else, get out
  598. break;
  599. }
  600. SkipForward(1);
  601. }
  602. // Blank line?
  603. if (Eol)
  604. {
  605. b.ContentEnd = b.ContentStart;
  606. return BlockType.Blank;
  607. }
  608. // 4 leading spaces?
  609. if (leadingSpaces >= 4)
  610. {
  611. b.ContentStart = line_start + 4;
  612. return BlockType.indent;
  613. }
  614. // Tab in the first 4 characters?
  615. if (tabPos >= 0 && tabPos - line_start<4)
  616. {
  617. b.ContentStart = tabPos + 1;
  618. return BlockType.indent;
  619. }
  620. // Treat start of line as after leading whitespace
  621. b.ContentStart = Position;
  622. // Get the next character
  623. ch = Current;
  624. // Html block?
  625. if (ch == '<')
  626. {
  627. // Scan html block
  628. if (ScanHtml(b))
  629. return b.BlockType;
  630. // Rewind
  631. Position = b.ContentStart;
  632. }
  633. // Block quotes start with '>' and have one space or one tab following
  634. if (ch == '>')
  635. {
  636. // Block quote followed by space
  637. if (IsLineSpace(CharAtOffset(1)))
  638. {
  639. // Skip it and create quote block
  640. SkipForward(2);
  641. b.ContentStart = Position;
  642. return BlockType.quote;
  643. }
  644. SkipForward(1);
  645. b.ContentStart = Position;
  646. return BlockType.quote;
  647. }
  648. // Horizontal rule - a line consisting of 3 or more '-', '_' or '*' with optional spaces and nothing else
  649. if (ch == '-' || ch == '_' || ch == '*')
  650. {
  651. int count = 0;
  652. while (!Eol)
  653. {
  654. char chType = Current;
  655. if (Current == ch)
  656. {
  657. count++;
  658. SkipForward(1);
  659. continue;
  660. }
  661. if (IsLineSpace(Current))
  662. {
  663. SkipForward(1);
  664. continue;
  665. }
  666. break;
  667. }
  668. if (Eol && count >= 3)
  669. {
  670. if (m_markdown.UserBreaks)
  671. return BlockType.user_break;
  672. else
  673. return BlockType.hr;
  674. }
  675. // Rewind
  676. Position = b.ContentStart;
  677. }
  678. // Abbreviation definition?
  679. if (m_markdown.ExtraMode && ch == '*' && CharAtOffset(1) == '[')
  680. {
  681. SkipForward(2);
  682. SkipLinespace();
  683. Mark();
  684. while (!Eol && Current != ']')
  685. {
  686. SkipForward(1);
  687. }
  688. var abbr = Extract().Trim();
  689. if (Current == ']' && CharAtOffset(1) == ':' && !string.IsNullOrEmpty(abbr))
  690. {
  691. SkipForward(2);
  692. SkipLinespace();
  693. Mark();
  694. SkipToEol();
  695. var title = Extract();
  696. m_markdown.AddAbbreviation(abbr, title);
  697. return BlockType.Blank;
  698. }
  699. Position = b.ContentStart;
  700. }
  701. // Unordered list
  702. if ((ch == '*' || ch == '+' || ch == '-') && IsLineSpace(CharAtOffset(1)))
  703. {
  704. // Skip it
  705. SkipForward(1);
  706. SkipLinespace();
  707. b.ContentStart = Position;
  708. return BlockType.ul_li;
  709. }
  710. // Definition
  711. if (ch == ':' && m_markdown.ExtraMode && IsLineSpace(CharAtOffset(1)))
  712. {
  713. SkipForward(1);
  714. SkipLinespace();
  715. b.ContentStart = Position;
  716. return BlockType.dd;
  717. }
  718. // Ordered list
  719. if (char.IsDigit(ch))
  720. {
  721. // Ordered list? A line starting with one or more digits, followed by a '.' and a space or tab
  722. // Skip all digits
  723. SkipForward(1);
  724. while (char.IsDigit(Current))
  725. SkipForward(1);
  726. if (SkipChar('.') && SkipLinespace())
  727. {
  728. b.ContentStart = Position;
  729. return BlockType.ol_li;
  730. }
  731. Position=b.ContentStart;
  732. }
  733. // Reference link definition?
  734. if (ch == '[')
  735. {
  736. // Footnote definition?
  737. if (m_markdown.ExtraMode && CharAtOffset(1) == '^')
  738. {
  739. var savepos = Position;
  740. SkipForward(2);
  741. string id;
  742. if (SkipFootnoteID(out id) && SkipChar(']') && SkipChar(':'))
  743. {
  744. SkipLinespace();
  745. b.ContentStart = Position;
  746. b.Data = id;
  747. return BlockType.footnote;
  748. }
  749. Position = savepos;
  750. }
  751. // Parse a link definition
  752. LinkDefinition l = LinkDefinition.ParseLinkDefinition(this, m_markdown.ExtraMode);
  753. if (l!=null)
  754. {
  755. m_markdown.AddLinkDefinition(l);
  756. return BlockType.Blank;
  757. }
  758. }
  759. // DocNet '@' extensions
  760. if(ch == '@' && m_markdown.DocNetMode)
  761. {
  762. if(HandleDocNetExtension(b))
  763. {
  764. return b.BlockType;
  765. }
  766. // Not valid, Rewind
  767. Position = b.ContentStart;
  768. }
  769. // Nothing special
  770. return BlockType.p;
  771. }
  772. internal MarkdownInHtmlMode GetMarkdownMode(HtmlTag tag)
  773. {
  774. // Get the markdown attribute
  775. string strMarkdownMode;
  776. if (!m_markdown.ExtraMode || !tag.attributes.TryGetValue("markdown", out strMarkdownMode))
  777. {
  778. if (m_bMarkdownInHtml)
  779. return MarkdownInHtmlMode.Deep;
  780. else
  781. return MarkdownInHtmlMode.NA;
  782. }
  783. // Remove it
  784. tag.attributes.Remove("markdown");
  785. // Parse mode
  786. if (strMarkdownMode == "1")
  787. return (tag.Flags & HtmlTagFlags.ContentAsSpan)!=0 ? MarkdownInHtmlMode.Span : MarkdownInHtmlMode.Block;
  788. if (strMarkdownMode == "block")
  789. return MarkdownInHtmlMode.Block;
  790. if (strMarkdownMode == "deep")
  791. return MarkdownInHtmlMode.Deep;
  792. if (strMarkdownMode == "span")
  793. return MarkdownInHtmlMode.Span;
  794. return MarkdownInHtmlMode.Off;
  795. }
  796. internal bool ProcessMarkdownEnabledHtml(Block b, HtmlTag openingTag, MarkdownInHtmlMode mode)
  797. {
  798. // Current position is just after the opening tag
  799. // Scan until we find matching closing tag
  800. int inner_pos = Position;
  801. int depth = 1;
  802. bool bHasUnsafeContent = false;
  803. while (!Eof)
  804. {
  805. // Find next angle bracket
  806. if (!Find('<'))
  807. break;
  808. // Is it a html tag?
  809. int tagpos = Position;
  810. HtmlTag tag = HtmlTag.Parse(this);
  811. if (tag == null)
  812. {
  813. // Nope, skip it
  814. SkipForward(1);
  815. continue;
  816. }
  817. // In markdown off mode, we need to check for unsafe tags
  818. if (m_markdown.SafeMode && mode == MarkdownInHtmlMode.Off && !bHasUnsafeContent)
  819. {
  820. if (!tag.IsSafe())
  821. bHasUnsafeContent = true;
  822. }
  823. // Ignore self closing tags
  824. if (tag.closed)
  825. continue;
  826. // Same tag?
  827. if (tag.name == openingTag.name)
  828. {
  829. if (tag.closing)
  830. {
  831. depth--;
  832. if (depth == 0)
  833. {
  834. // End of tag?
  835. SkipLinespace();
  836. SkipEol();
  837. b.BlockType = BlockType.HtmlTag;
  838. b.Data = openingTag;
  839. b.ContentEnd = Position;
  840. switch (mode)
  841. {
  842. case MarkdownInHtmlMode.Span:
  843. {
  844. Block span = this.CreateBlock();
  845. span.Buf = Input;
  846. span.BlockType = BlockType.span;
  847. span.ContentStart = inner_pos;
  848. span.ContentLen = tagpos - inner_pos;
  849. b.Children = new List<Block>();
  850. b.Children.Add(span);
  851. break;
  852. }
  853. case MarkdownInHtmlMode.Block:
  854. case MarkdownInHtmlMode.Deep:
  855. {
  856. // Scan the internal content
  857. var bp = new BlockProcessor(m_markdown, mode == MarkdownInHtmlMode.Deep);
  858. b.Children = bp.ScanLines(Input, inner_pos, tagpos - inner_pos);
  859. break;
  860. }
  861. case MarkdownInHtmlMode.Off:
  862. {
  863. if (bHasUnsafeContent)
  864. {
  865. b.BlockType = BlockType.unsafe_html;
  866. b.ContentEnd = Position;
  867. }
  868. else
  869. {
  870. Block span = this.CreateBlock();
  871. span.Buf = Input;
  872. span.BlockType = BlockType.html;
  873. span.ContentStart = inner_pos;
  874. span.ContentLen = tagpos - inner_pos;
  875. b.Children = new List<Block>();
  876. b.Children.Add(span);
  877. }
  878. break;
  879. }
  880. }
  881. return true;
  882. }
  883. }
  884. else
  885. {
  886. depth++;
  887. }
  888. }
  889. }
  890. // Missing closing tag(s).
  891. return false;
  892. }
  893. // Scan from the current position to the end of the html section
  894. internal bool ScanHtml(Block b)
  895. {
  896. // Remember start of html
  897. int posStartPiece = this.Position;
  898. // Parse a HTML tag
  899. HtmlTag openingTag = HtmlTag.Parse(this);
  900. if (openingTag == null)
  901. return false;
  902. // Closing tag?
  903. if (openingTag.closing)
  904. return false;
  905. // Safe mode?
  906. bool bHasUnsafeContent = m_markdown.SafeMode && !openingTag.IsSafe();
  907. HtmlTagFlags flags = openingTag.Flags;
  908. // Is it a block level tag?
  909. if ((flags & HtmlTagFlags.Block) == 0)
  910. return false;
  911. // Closed tag, hr or comment?
  912. if ((flags & HtmlTagFlags.NoClosing) != 0 || openingTag.closed)
  913. {
  914. SkipLinespace();
  915. SkipEol();
  916. b.ContentEnd = Position;
  917. b.BlockType = bHasUnsafeContent ? BlockType.unsafe_html : BlockType.html;
  918. return true;
  919. }
  920. // Can it also be an inline tag?
  921. if ((flags & HtmlTagFlags.Inline) != 0)
  922. {
  923. // Yes, opening tag must be on a line by itself
  924. SkipLinespace();
  925. if (!Eol)
  926. return false;
  927. }
  928. // Head block extraction?
  929. bool bHeadBlock = m_markdown.ExtractHeadBlocks && string.Compare(openingTag.name, "head", true) == 0;
  930. int headStart = this.Position;
  931. // Work out the markdown mode for this element
  932. if (!bHeadBlock && m_markdown.ExtraMode)
  933. {
  934. MarkdownInHtmlMode MarkdownMode = this.GetMarkdownMode(openingTag);
  935. if (MarkdownMode != MarkdownInHtmlMode.NA)
  936. {
  937. return this.ProcessMarkdownEnabledHtml(b, openingTag, MarkdownMode);
  938. }
  939. }
  940. List<Block> childBlocks = null;
  941. // Now capture everything up to the closing tag and put it all in a single HTML block
  942. int depth = 1;
  943. while (!Eof)
  944. {
  945. // Find next angle bracket
  946. if (!Find('<'))
  947. break;
  948. // Save position of current tag
  949. int posStartCurrentTag = Position;
  950. // Is it a html tag?
  951. HtmlTag tag = HtmlTag.Parse(this);
  952. if (tag == null)
  953. {
  954. // Nope, skip it
  955. SkipForward(1);
  956. continue;
  957. }
  958. // Safe mode checks
  959. if (m_markdown.SafeMode && !tag.IsSafe())
  960. bHasUnsafeContent = true;
  961. // Ignore self closing tags
  962. if (tag.closed)
  963. continue;
  964. // Markdown enabled content?
  965. if (!bHeadBlock && !tag.closing && m_markdown.ExtraMode && !bHasUnsafeContent)
  966. {
  967. MarkdownInHtmlMode MarkdownMode = this.GetMarkdownMode(tag);
  968. if (MarkdownMode != MarkdownInHtmlMode.NA)
  969. {
  970. Block markdownBlock = this.CreateBlock();
  971. if (this.ProcessMarkdownEnabledHtml(markdownBlock, tag, MarkdownMode))
  972. {
  973. if (childBlocks==null)
  974. {
  975. childBlocks = new List<Block>();
  976. }
  977. // Create a block for everything before the markdown tag
  978. if (posStartCurrentTag > posStartPiece)
  979. {
  980. Block htmlBlock = this.CreateBlock();
  981. htmlBlock.Buf = Input;
  982. htmlBlock.BlockType = BlockType.html;
  983. htmlBlock.ContentStart = posStartPiece;
  984. htmlBlock.ContentLen = posStartCurrentTag - posStartPiece;
  985. childBlocks.Add(htmlBlock);
  986. }
  987. // Add the markdown enabled child block
  988. childBlocks.Add(markdownBlock);
  989. // Remember start of the next piece
  990. posStartPiece = Position;
  991. continue;
  992. }
  993. else
  994. {
  995. this.FreeBlock(markdownBlock);
  996. }
  997. }
  998. }
  999. // Same tag?
  1000. if (tag.name == openingTag.name)
  1001. {
  1002. if (tag.closing)
  1003. {
  1004. depth--;
  1005. if (depth == 0)
  1006. {
  1007. // End of tag?
  1008. SkipLinespace();
  1009. SkipEol();
  1010. // If anything unsafe detected, just encode the whole block
  1011. if (bHasUnsafeContent)
  1012. {
  1013. b.BlockType = BlockType.unsafe_html;
  1014. b.ContentEnd = Position;
  1015. return true;
  1016. }
  1017. // Did we create any child blocks
  1018. if (childBlocks != null)
  1019. {
  1020. // Create a block for the remainder
  1021. if (Position > posStartPiece)
  1022. {
  1023. Block htmlBlock = this.CreateBlock();
  1024. htmlBlock.Buf = Input;
  1025. htmlBlock.BlockType = BlockType.html;
  1026. htmlBlock.ContentStart = posStartPiece;
  1027. htmlBlock.ContentLen = Position - posStartPiece;
  1028. childBlocks.Add(htmlBlock);
  1029. }
  1030. // Return a composite block
  1031. b.BlockType = BlockType.Composite;
  1032. b.ContentEnd = Position;
  1033. b.Children = childBlocks;
  1034. return true;
  1035. }
  1036. // Extract the head block content
  1037. if (bHeadBlock)
  1038. {
  1039. var content = this.Substring(headStart, posStartCurrentTag - headStart);
  1040. m_markdown.HeadBlockContent = (m_markdown.HeadBlockContent ?? "") + content.Trim() + "\n";
  1041. b.BlockType = BlockType.html;
  1042. b.ContentStart = Position;
  1043. b.ContentEnd = Position;
  1044. b.LineStart = Position;
  1045. return true;
  1046. }
  1047. // Straight html block
  1048. b.BlockType = BlockType.html;
  1049. b.ContentEnd = Position;
  1050. return true;
  1051. }
  1052. }
  1053. else
  1054. {
  1055. depth++;
  1056. }
  1057. }
  1058. }
  1059. // Rewind to just after the tag
  1060. return false;
  1061. }
  1062. /// <summary>
  1063. /// Handles the docnet extension, starting with '@'. This can be:
  1064. /// * @fa-
  1065. /// * @alert
  1066. /// @end
  1067. /// * @tabs
  1068. /// @tabsend
  1069. /// </summary>
  1070. /// <param name="b">The b.</param>
  1071. /// <returns>true if extension was correctly handled, false otherwise (error)</returns>
  1072. private bool HandleDocNetExtension(Block b)
  1073. {
  1074. var initialStart = this.Position;
  1075. if(DoesMatch("@fa-"))
  1076. {
  1077. return HandleFontAwesomeExtension(b);
  1078. }
  1079. // first match @tabs, and then @tab, as both are handled by this processor.
  1080. if(DoesMatch("@tabs"))
  1081. {
  1082. return HandleTabsExtension(b);
  1083. }
  1084. if(DoesMatch("@tab"))
  1085. {
  1086. return HandleTabForTabsExtension(b);
  1087. }
  1088. if(DoesMatch("@alert"))
  1089. {
  1090. return HandleAlertExtension(b);
  1091. }
  1092. return false;
  1093. }
  1094. /// <summary>
  1095. /// Handles the alert extension:
  1096. /// @alert type
  1097. /// text
  1098. /// @end
  1099. ///
  1100. /// where text can be anything and has to be handled further.
  1101. /// type is: danger, warning, info or neutral.
  1102. /// </summary>
  1103. /// <param name="b">The b.</param>
  1104. /// <returns></returns>
  1105. private bool HandleAlertExtension(Block b)
  1106. {
  1107. // skip '@alert'
  1108. if(!SkipString("@alert"))
  1109. {
  1110. return false;
  1111. }
  1112. SkipLinespace();
  1113. var alertType = string.Empty;
  1114. if(!SkipIdentifier(ref alertType))
  1115. {
  1116. return false;
  1117. }
  1118. SkipToNextLine();
  1119. int startContent = this.Position;
  1120. // find @end.
  1121. if(!Find("@end"))
  1122. {
  1123. return false;
  1124. }
  1125. // Character before must be a eol char
  1126. if(!IsLineEnd(CharAtOffset(-1)))
  1127. {
  1128. return false;
  1129. }
  1130. int endContent = Position;
  1131. // skip @end
  1132. SkipString("@end");
  1133. SkipLinespace();
  1134. if(!Eol)
  1135. {
  1136. return false;
  1137. }
  1138. // Remove the trailing line end
  1139. endContent = UnskipCRLFBeforePos(endContent);
  1140. b.BlockType = BlockType.alert;
  1141. b.Data = alertType.ToLowerInvariant();
  1142. // scan the content, as it can contain markdown statements.
  1143. var contentProcessor = new BlockProcessor(m_markdown, m_markdown.MarkdownInHtml);
  1144. b.Children = contentProcessor.ScanLines(Input, startContent, endContent - startContent);
  1145. return true;
  1146. }
  1147. private bool HandleTabsExtension(Block b)
  1148. {
  1149. // skip '@tabs'
  1150. if(!SkipString("@tabs"))
  1151. {
  1152. return false;
  1153. }
  1154. // ignore what's specified behind @tabs
  1155. SkipToNextLine();
  1156. int startContent = this.Position;
  1157. // find @end.
  1158. if(!Find("@endtabs"))
  1159. {
  1160. return false;
  1161. }
  1162. // Character before must be a eol char
  1163. if(!IsLineEnd(CharAtOffset(-1)))
  1164. {
  1165. return false;
  1166. }
  1167. int endContent = Position;
  1168. // skip @end
  1169. SkipString("@endtabs");
  1170. SkipLinespace();
  1171. if(!Eol)
  1172. {
  1173. return false;
  1174. }
  1175. // Remove the trailing line end
  1176. endContent = UnskipCRLFBeforePos(endContent);
  1177. b.BlockType = BlockType.tabs;
  1178. // scan the content, as it can contain markdown statements.
  1179. var contentProcessor = new BlockProcessor(m_markdown, m_markdown.MarkdownInHtml);
  1180. var scanLines = contentProcessor.ScanLines(this.Input, startContent, endContent - startContent);
  1181. // check whether the content is solely tab blocks. If not, we ignore this tabs specification.
  1182. if(scanLines.Any(x=>x.BlockType != BlockType.tab))
  1183. {
  1184. return false;
  1185. }
  1186. b.Children = scanLines;
  1187. return true;
  1188. }
  1189. /// <summary>
  1190. /// Handles the tab for tabs extension. This is a docnet extension and it handles:
  1191. /// @tab tab head text
  1192. /// tab content
  1193. /// @end
  1194. /// </summary>
  1195. /// <param name="b">The current block.</param>
  1196. /// <returns></returns>
  1197. private bool HandleTabForTabsExtension(Block b)
  1198. {
  1199. // skip '@tab'
  1200. if(!SkipString("@tab"))
  1201. {
  1202. return false;
  1203. }
  1204. SkipLinespace();
  1205. var tabHeaderTextStart = this.Position;
  1206. // skip to eol, then grab the content between positions.
  1207. SkipToEol();
  1208. var tabHeaderText = this.Input.Substring(tabHeaderTextStart, this.Position - tabHeaderTextStart);
  1209. SkipToNextLine();
  1210. int startContent = this.Position;
  1211. // find @end.
  1212. if(!Find("@end"))
  1213. {
  1214. return false;
  1215. }
  1216. // Character before must be a eol char
  1217. if(!IsLineEnd(CharAtOffset(-1)))
  1218. {
  1219. return false;
  1220. }
  1221. int endContent = Position;
  1222. // skip @end
  1223. SkipString("@end");
  1224. SkipLinespace();
  1225. if(!Eol)
  1226. {
  1227. return false;
  1228. }
  1229. // Remove the trailing line end
  1230. endContent = UnskipCRLFBeforePos(endContent);
  1231. b.BlockType = BlockType.tab;
  1232. b.Data = tabHeaderText;
  1233. // scan the content, as it can contain markdown statements.
  1234. var contentProcessor = new BlockProcessor(m_markdown, m_markdown.MarkdownInHtml);
  1235. b.Children = contentProcessor.ScanLines(Input, startContent, endContent - startContent);
  1236. return true;
  1237. }
  1238. /// <summary>
  1239. /// Handles the font awesome extension, which is available in DocNet mode. FontAwesome extension uses @fa-iconname, where iconname is the name of the fontawesome icon.
  1240. /// Called when '@fa-' has been seen. Current position is on 'f' of 'fa-'.
  1241. /// </summary>
  1242. /// <param name="b">The b.</param>
  1243. /// <returns></returns>
  1244. private bool HandleFontAwesomeExtension(Block b)
  1245. {
  1246. string iconName = string.Empty;
  1247. int newPosition = this.Position;
  1248. if(!Utils.SkipFontAwesome(this.Input, this.Position, out newPosition, out iconName))
  1249. {
  1250. return false;
  1251. }
  1252. this.Position = newPosition;
  1253. b.BlockType = BlockType.font_awesome;
  1254. b.Data = iconName;
  1255. return true;
  1256. }
  1257. /*
  1258. * Spacing
  1259. *
  1260. * 1-3 spaces - Promote to indented if more spaces than original item
  1261. *
  1262. */
  1263. /*
  1264. * BuildList - build a single <ol> or <ul> list
  1265. */
  1266. private Block BuildList(List<Block> lines)
  1267. {
  1268. // What sort of list are we dealing with
  1269. BlockType listType = lines[0].BlockType;
  1270. System.Diagnostics.Debug.Assert(listType == BlockType.ul_li || listType == BlockType.ol_li);
  1271. // Preprocess
  1272. // 1. Collapse all plain lines (ie: handle hardwrapped lines)
  1273. // 2. Promote any unindented lines that have more leading space
  1274. // than the original list item to indented, including leading
  1275. // special chars
  1276. int leadingSpace = lines[0].LeadingSpaces;
  1277. for (int i = 1; i < lines.Count; i++)
  1278. {
  1279. // Join plain paragraphs
  1280. if ((lines[i].BlockType == BlockType.p) &&
  1281. (lines[i - 1].BlockType == BlockType.p || lines[i - 1].BlockType == BlockType.ul_li || lines[i - 1].BlockType==BlockType.ol_li))
  1282. {
  1283. lines[i - 1].ContentEnd = lines[i].ContentEnd;
  1284. FreeBlock(lines[i]);
  1285. lines.RemoveAt(i);
  1286. i--;
  1287. continue;
  1288. }
  1289. if (lines[i].BlockType != BlockType.indent && lines[i].BlockType != BlockType.Blank)
  1290. {
  1291. int thisLeadingSpace = lines[i].LeadingSpaces;
  1292. if (thisLeadingSpace > leadingSpace)
  1293. {
  1294. // Change line to indented, including original leading chars
  1295. // (eg: '* ', '>', '1.' etc...)
  1296. lines[i].BlockType = BlockType.indent;
  1297. int saveend = lines[i].ContentEnd;
  1298. lines[i].ContentStart = lines[i].LineStart + thisLeadingSpace;
  1299. lines[i].ContentEnd = saveend;
  1300. }
  1301. }
  1302. }
  1303. // Create the wrapping list item
  1304. var List = new Block(listType == BlockType.ul_li ? BlockType.ul : BlockType.ol);
  1305. List.Children = new List<Block>();
  1306. // Process all lines in the range
  1307. for (int i = 0; i < lines.Count; i++)
  1308. {
  1309. System.Diagnostics.Debug.Assert(lines[i].BlockType == BlockType.ul_li || lines[i].BlockType==BlockType.ol_li);
  1310. // Find start of item, including leading blanks
  1311. int start_of_li = i;
  1312. while (start_of_li > 0 && lines[start_of_li - 1].BlockType == BlockType.Blank)
  1313. start_of_li--;
  1314. // Find end of the item, including trailing blanks
  1315. int end_of_li = i;
  1316. while (end_of_li < lines.Count - 1 && lines[end_of_li + 1].BlockType != BlockType.ul_li && lines[end_of_li + 1].BlockType != BlockType.ol_li)
  1317. end_of_li++;
  1318. // Is this a simple or complex list item?
  1319. if (start_of_li == end_of_li)
  1320. {
  1321. // It's a simple, single line item item
  1322. System.Diagnostics.Debug.Assert(start_of_li == i);
  1323. List.Children.Add(CreateBlock().CopyFrom(lines[i]));
  1324. }
  1325. else
  1326. {
  1327. // Build a new string containing all child items
  1328. bool bAnyBlanks = false;
  1329. StringBuilder sb = m_markdown.GetStringBuilder();
  1330. for (int j = start_of_li; j <= end_of_li; j++)
  1331. {
  1332. var l = lines[j];
  1333. sb.Append(l.Buf, l.ContentStart, l.ContentLen);
  1334. sb.Append('\n');
  1335. if (lines[j].BlockType == BlockType.Blank)
  1336. {
  1337. bAnyBlanks = true;
  1338. }
  1339. }
  1340. // Create the item and process child blocks
  1341. var item = new Block(BlockType.li);
  1342. item.Children = new BlockProcessor(m_markdown, m_bMarkdownInHtml, listType).Process(sb.ToString());
  1343. // If no blank lines, change all contained paragraphs to plain text
  1344. if (!bAnyBlanks)
  1345. {
  1346. foreach (var child in item.Children)
  1347. {
  1348. if (child.BlockType == BlockType.p)
  1349. {
  1350. child.BlockType = BlockType.span;
  1351. }
  1352. }
  1353. }
  1354. // Add the complex item
  1355. List.Children.Add(item);
  1356. }
  1357. // Continue processing from end of li
  1358. i = end_of_li;
  1359. }
  1360. FreeBlocks(lines);
  1361. lines.Clear();
  1362. // Continue processing after this item
  1363. return List;
  1364. }
  1365. /*
  1366. * BuildDefinition - build a single <dd> item
  1367. */
  1368. private Block BuildDefinition(List<Block> lines)
  1369. {
  1370. // Collapse all plain lines (ie: handle hardwrapped lines)
  1371. for (int i = 1; i < lines.Count; i++)
  1372. {
  1373. // Join plain paragraphs
  1374. if ((lines[i].BlockType == BlockType.p) &&
  1375. (lines[i - 1].BlockType == BlockType.p || lines[i - 1].BlockType == BlockType.dd))
  1376. {
  1377. lines[i - 1].ContentEnd = lines[i].ContentEnd;
  1378. FreeBlock(lines[i]);
  1379. lines.RemoveAt(i);
  1380. i--;
  1381. continue;
  1382. }
  1383. }
  1384. // Single line definition
  1385. bool bPreceededByBlank=(bool)lines[0].Data;
  1386. if (lines.Count==1 && !bPreceededByBlank)
  1387. {
  1388. var ret=lines[0];
  1389. lines.Clear();
  1390. return ret;
  1391. }
  1392. // Build a new string containing all child items
  1393. StringBuilder sb = m_markdown.GetStringBuilder();
  1394. for (int i = 0; i < lines.Count; i++)
  1395. {
  1396. var l = lines[i];
  1397. sb.Append(l.Buf, l.ContentStart, l.ContentLen);
  1398. sb.Append('\n');
  1399. }
  1400. // Create the item and process child blocks
  1401. var item = this.CreateBlock();
  1402. item.BlockType = BlockType.dd;
  1403. item.Children = new BlockProcessor(m_markdown, m_bMarkdownInHtml, BlockType.dd).Process(sb.ToString());
  1404. FreeBlocks(lines);
  1405. lines.Clear();
  1406. // Continue processing after this item
  1407. return item;
  1408. }
  1409. void BuildDefinitionLists(List<Block> blocks)
  1410. {
  1411. Block currentList = null;
  1412. for (int i = 0; i < blocks.Count; i++)
  1413. {
  1414. switch (blocks[i].BlockType)
  1415. {
  1416. case BlockType.dt:
  1417. case BlockType.dd:
  1418. if (currentList==null)
  1419. {
  1420. currentList=CreateBlock();
  1421. currentList.BlockType=BlockType.dl;
  1422. currentList.Children=new List<Block>();
  1423. blocks.Insert(i, currentList);
  1424. i++;
  1425. }
  1426. currentList.Children.Add(blocks[i]);
  1427. blocks.RemoveAt(i);
  1428. i--;
  1429. break;
  1430. default:
  1431. currentList = null;
  1432. break;
  1433. }
  1434. }
  1435. }
  1436. private Block BuildFootnote(List<Block> lines)
  1437. {
  1438. // Collapse all plain lines (ie: handle hardwrapped lines)
  1439. for (int i = 1; i < lines.Count; i++)
  1440. {
  1441. // Join plain paragraphs
  1442. if ((lines[i].BlockType == BlockType.p) &&
  1443. (lines[i - 1].BlockType == BlockType.p || lines[i - 1].BlockType == BlockType.footnote))
  1444. {
  1445. lines[i - 1].ContentEnd = lines[i].ContentEnd;
  1446. FreeBlock(lines[i]);
  1447. lines.RemoveAt(i);
  1448. i--;
  1449. continue;
  1450. }
  1451. }
  1452. // Build a new string containing all child items
  1453. StringBuilder sb = m_markdown.GetStringBuilder();
  1454. for (int i = 0; i < lines.Count; i++)
  1455. {
  1456. var l = lines[i];
  1457. sb.Append(l.Buf, l.ContentStart, l.ContentLen);
  1458. sb.Append('\n');
  1459. }
  1460. // Create the item and process child blocks
  1461. var item = this.CreateBlock();
  1462. item.BlockType = BlockType.footnote;
  1463. item.Data = lines[0].Data;
  1464. item.Children = new BlockProcessor(m_markdown, m_bMarkdownInHtml, BlockType.footnote).Process(sb.ToString());
  1465. FreeBlocks(lines);
  1466. lines.Clear();
  1467. // Continue processing after this item
  1468. return item;
  1469. }
  1470. bool ProcessFencedCodeBlock(Block b)
  1471. {
  1472. char delim = Current;
  1473. // Extract the fence
  1474. Mark();
  1475. while (Current == delim)
  1476. SkipForward(1);
  1477. string strFence = Extract();
  1478. // Must be at least 3 long
  1479. if (strFence.Length < 3)
  1480. return false;
  1481. if(m_markdown.GitHubCodeBlocks)
  1482. {
  1483. // check whether a name has been specified after the start ```. If so we'll store that into 'Data'.
  1484. var languageName = string.Empty;
  1485. // allow space between first fence and name
  1486. SkipLinespace();
  1487. SkipIdentifier(ref languageName);
  1488. b.Data = string.IsNullOrWhiteSpace(languageName) ? "nohighlight" : languageName;
  1489. // skip linespace to EOL
  1490. SkipLinespace();
  1491. }
  1492. else
  1493. {
  1494. // Rest of line must be blank
  1495. SkipLinespace();
  1496. if(!Eol)
  1497. return false;
  1498. }
  1499. // Skip the eol and remember start of code
  1500. SkipEol();
  1501. int startCode = Position;
  1502. // Find the end fence
  1503. if (!Find(strFence))
  1504. return false;
  1505. // Character before must be a eol char
  1506. if (!IsLineEnd(CharAtOffset(-1)))
  1507. return false;
  1508. int endCode = Position;
  1509. // Skip the fence
  1510. SkipForward(strFence.Length);
  1511. // Whitespace allowed at end
  1512. SkipLinespace();
  1513. if (!Eol)
  1514. return false;
  1515. // Create the code block
  1516. b.BlockType = BlockType.codeblock;
  1517. b.Children = new List<Block>();
  1518. // Remove the trailing line end
  1519. if (Input[endCode - 1] == '\r' && Input[endCode - 2] == '\n')
  1520. endCode -= 2;
  1521. else if (Input[endCode - 1] == '\n' && Input[endCode - 2] == '\r')
  1522. endCode -= 2;
  1523. else
  1524. endCode--;
  1525. // Create the child block with the entire content
  1526. var child = CreateBlock();
  1527. child.BlockType = BlockType.indent;
  1528. child.Buf = Input;
  1529. child.ContentStart = startCode;
  1530. child.ContentEnd = endCode;
  1531. b.Children.Add(child);
  1532. return true;
  1533. }
  1534. Markdown m_markdown;
  1535. BlockType m_parentType;
  1536. bool m_bMarkdownInHtml;
  1537. }
  1538. }