...
This commit is contained in:
29
examples/webtools/mdbook_markdown/content/cybercity.md
Normal file
29
examples/webtools/mdbook_markdown/content/cybercity.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 10
|
||||||
|
title: 'Dunia CyberCity'
|
||||||
|
description: 'Co-create the Future'
|
||||||
|
---
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
We are building a 700,000 m2 Regenerative Startup Cyber City
|
||||||
|
|
||||||
|
- 100% co-owned
|
||||||
|
- regenerative
|
||||||
|
- autonomous zone
|
||||||
|
|
||||||
|
a city for startups and its creators
|
||||||
|
|
||||||
|
- build a system for augmented collective intelligence
|
||||||
|
- operate business wise from a digital freezone
|
||||||
|
- (co)own assets (shares, digital currencies) safely and privately
|
||||||
|
|
||||||
|
|
||||||
|
## More Info
|
||||||
|
|
||||||
|
> see [https://friends.threefold.info/cybercity](https://friends.threefold.info/cybercity)
|
||||||
|
|
||||||
|
- login:```planet```
|
||||||
|
- passwd:```first```
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
|
||||||
|
|
||||||
// import freeflowuniverse.herolib.core.texttools
|
// import freeflowuniverse.herolib.core.texttools
|
||||||
import freeflowuniverse.herolib.ui.console
|
import freeflowuniverse.herolib.ui.console
|
||||||
|
|||||||
27
examples/webtools/vmarkdown/markdown_example_v.vsh
Executable file
27
examples/webtools/vmarkdown/markdown_example_v.vsh
Executable file
@@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||||
|
|
||||||
|
// import freeflowuniverse.herolib.core.texttools
|
||||||
|
import freeflowuniverse.herolib.ui.console
|
||||||
|
import log
|
||||||
|
import os
|
||||||
|
import markdown
|
||||||
|
import mlib
|
||||||
|
|
||||||
|
path2:="${os.home_dir()}/code/github/freeflowuniverse/herolib/examples/webtools/mdbook_markdown/content/links.md"
|
||||||
|
path1:="${os.home_dir()}/code/github/freeflowuniverse/herolib/examples/webtools/mdbook_markdown/content/test.md"
|
||||||
|
|
||||||
|
text := os.read_file(path1)!
|
||||||
|
|
||||||
|
// Example 1: Using the built-in plaintext renderer
|
||||||
|
println('=== PLAINTEXT RENDERING ===')
|
||||||
|
println(markdown.to_plain(text))
|
||||||
|
println('')
|
||||||
|
|
||||||
|
// Example 2: Using our custom structure renderer to show markdown structure
|
||||||
|
println('=== STRUCTURE RENDERING ===')
|
||||||
|
println(mlib.to_structure(text))
|
||||||
|
|
||||||
|
// // Example 3: Using a simple markdown example to demonstrate structure
|
||||||
|
// println('\n=== STRUCTURE OF A SIMPLE MARKDOWN EXAMPLE ===')
|
||||||
|
// simple_md := '# Heading 1\n\nThis is a paragraph with **bold** and *italic* text.\n\n- List item 1\n- List item 2\n\n```v\nfn main() {\n\tprintln("Hello, world!")\n}\n```\n\n[Link to V language](https://vlang.io)'
|
||||||
|
// println(markdown.to_structure(simple_md))
|
||||||
95
examples/webtools/vmarkdown/markdown_parser.vsh
Executable file
95
examples/webtools/vmarkdown/markdown_parser.vsh
Executable file
@@ -0,0 +1,95 @@
|
|||||||
|
#!/usr/bin/env -S v -n -w -gc none run
|
||||||
|
|
||||||
|
import mlib2
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Sample markdown text
|
||||||
|
text := '# Heading 1
|
||||||
|
|
||||||
|
This is a paragraph with **bold** and *italic* text.
|
||||||
|
|
||||||
|
## Heading 2
|
||||||
|
|
||||||
|
- List item 1
|
||||||
|
- List item 2
|
||||||
|
- Nested item
|
||||||
|
- List item 3
|
||||||
|
|
||||||
|
```v
|
||||||
|
fn main() {
|
||||||
|
println("Hello, world!")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> This is a blockquote
|
||||||
|
> with multiple lines
|
||||||
|
|
||||||
|
| Column 1 | Column 2 | Column 3 |
|
||||||
|
|----------|:--------:|---------:|
|
||||||
|
| Left | Center | Right |
|
||||||
|
| Cell 1 | Cell 2 | Cell 3 |
|
||||||
|
|
||||||
|
[Link to V language](https://vlang.io)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Footnote reference[^1]
|
||||||
|
|
||||||
|
[^1]: This is a footnote.
|
||||||
|
'
|
||||||
|
|
||||||
|
// Example 1: Using the plain text renderer
|
||||||
|
println('=== PLAINTEXT RENDERING ===')
|
||||||
|
println(mlib2.to_plain(text))
|
||||||
|
println('')
|
||||||
|
|
||||||
|
// Example 2: Using the structure renderer to show markdown structure
|
||||||
|
println('=== STRUCTURE RENDERING ===')
|
||||||
|
println(mlib2.to_structure(text))
|
||||||
|
|
||||||
|
// Example 3: Using the navigator to find specific elements
|
||||||
|
println('\n=== NAVIGATION EXAMPLE ===')
|
||||||
|
|
||||||
|
// Parse the markdown text
|
||||||
|
doc := mlib2.parse(text)
|
||||||
|
|
||||||
|
// Create a navigator
|
||||||
|
mut nav := mlib2.new_navigator(doc)
|
||||||
|
|
||||||
|
// Find all headings
|
||||||
|
headings := nav.find_all_by_type(.heading)
|
||||||
|
println('Found ${headings.len} headings:')
|
||||||
|
for heading in headings {
|
||||||
|
level := heading.attributes['level']
|
||||||
|
println(' ${'#'.repeat(level.int())} ${heading.content}')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all code blocks
|
||||||
|
code_blocks := nav.find_all_by_type(.code_block)
|
||||||
|
println('\nFound ${code_blocks.len} code blocks:')
|
||||||
|
for block in code_blocks {
|
||||||
|
language := block.attributes['language']
|
||||||
|
println(' Language: ${language}')
|
||||||
|
println(' Content length: ${block.content.len} characters')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all list items
|
||||||
|
list_items := nav.find_all_by_type(.list_item)
|
||||||
|
println('\nFound ${list_items.len} list items:')
|
||||||
|
for item in list_items {
|
||||||
|
println(' - ${item.content}')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find content containing specific text
|
||||||
|
if element := nav.find_by_content('blockquote') {
|
||||||
|
println('\nFound element containing "blockquote":')
|
||||||
|
println(' Type: ${element.typ}')
|
||||||
|
println(' Content: ${element.content}')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all footnotes
|
||||||
|
println('\nFootnotes:')
|
||||||
|
for id, footnote in nav.footnotes() {
|
||||||
|
println(' [^${id}]: ${footnote.content}')
|
||||||
|
}
|
||||||
|
}
|
||||||
194
examples/webtools/vmarkdown/mlib/structure_renderer.v
Normal file
194
examples/webtools/vmarkdown/mlib/structure_renderer.v
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
module mlib
|
||||||
|
|
||||||
|
import markdown
|
||||||
|
|
||||||
|
import strings
|
||||||
|
|
||||||
|
// Helper functions to extract information from C structs
|
||||||
|
fn get_md_attribute_string(attr C.MD_ATTRIBUTE) ?string {
|
||||||
|
unsafe {
|
||||||
|
if attr.text == nil || attr.size == 0 {
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
return attr.text.vstring_with_len(int(attr.size))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_heading_level(detail voidptr) int {
|
||||||
|
unsafe {
|
||||||
|
h_detail := &C.MD_BLOCK_H_DETAIL(detail)
|
||||||
|
return int(h_detail.level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_code_language(detail voidptr) ?string {
|
||||||
|
unsafe {
|
||||||
|
code_detail := &C.MD_BLOCK_CODE_DETAIL(detail)
|
||||||
|
return get_md_attribute_string(code_detail.lang)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_ul_details(detail voidptr) (bool, string) {
|
||||||
|
unsafe {
|
||||||
|
ul_detail := &C.MD_BLOCK_UL_DETAIL(detail)
|
||||||
|
is_tight := ul_detail.is_tight != 0
|
||||||
|
mark := ul_detail.mark.ascii_str()
|
||||||
|
return is_tight, mark
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_ol_details(detail voidptr) (int, bool) {
|
||||||
|
unsafe {
|
||||||
|
ol_detail := &C.MD_BLOCK_OL_DETAIL(detail)
|
||||||
|
start := int(ol_detail.start)
|
||||||
|
is_tight := ol_detail.is_tight != 0
|
||||||
|
return start, is_tight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_link_details(detail voidptr) (?string, ?string) {
|
||||||
|
unsafe {
|
||||||
|
a_detail := &C.MD_SPAN_A_DETAIL(detail)
|
||||||
|
href := get_md_attribute_string(a_detail.href)
|
||||||
|
title := get_md_attribute_string(a_detail.title)
|
||||||
|
return href, title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_image_details(detail voidptr) (?string, ?string) {
|
||||||
|
unsafe {
|
||||||
|
img_detail := &C.MD_SPAN_IMG_DETAIL(detail)
|
||||||
|
src := get_md_attribute_string(img_detail.src)
|
||||||
|
title := get_md_attribute_string(img_detail.title)
|
||||||
|
return src, title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_wikilink_target(detail voidptr) ?string {
|
||||||
|
unsafe {
|
||||||
|
wl_detail := &C.MD_SPAN_WIKILINK_DETAIL(detail)
|
||||||
|
return get_md_attribute_string(wl_detail.target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StructureRenderer is a custom renderer that outputs the structure of a markdown document
|
||||||
|
pub struct StructureRenderer {
|
||||||
|
mut:
|
||||||
|
writer strings.Builder = strings.new_builder(200)
|
||||||
|
indent int // Track indentation level for nested elements
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut sr StructureRenderer) str() string {
|
||||||
|
return sr.writer.str()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut sr StructureRenderer) enter_block(typ markdown.MD_BLOCKTYPE, detail voidptr) ? {
|
||||||
|
// Add indentation based on current level
|
||||||
|
sr.writer.write_string(strings.repeat(` `, sr.indent * 2))
|
||||||
|
|
||||||
|
// Output the block type
|
||||||
|
sr.writer.write_string('BLOCK[${typ}]: ')
|
||||||
|
|
||||||
|
// Add specific details based on block type
|
||||||
|
match typ {
|
||||||
|
.md_block_h {
|
||||||
|
level := get_heading_level(detail)
|
||||||
|
sr.writer.write_string('Level ${level}')
|
||||||
|
}
|
||||||
|
.md_block_code {
|
||||||
|
if lang := get_code_language(detail) {
|
||||||
|
sr.writer.write_string('Language: ${lang}')
|
||||||
|
} else {
|
||||||
|
sr.writer.write_string('No language specified')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.md_block_ul {
|
||||||
|
is_tight, mark := get_ul_details(detail)
|
||||||
|
sr.writer.write_string('Tight: ${is_tight}, Mark: ${mark}')
|
||||||
|
}
|
||||||
|
.md_block_ol {
|
||||||
|
start, is_tight := get_ol_details(detail)
|
||||||
|
sr.writer.write_string('Start: ${start}, Tight: ${is_tight}')
|
||||||
|
}
|
||||||
|
else {}
|
||||||
|
}
|
||||||
|
|
||||||
|
sr.writer.write_u8(`\n`)
|
||||||
|
sr.indent++
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut sr StructureRenderer) leave_block(typ markdown.MD_BLOCKTYPE, _ voidptr) ? {
|
||||||
|
sr.indent--
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut sr StructureRenderer) enter_span(typ markdown.MD_SPANTYPE, detail voidptr) ? {
|
||||||
|
// Add indentation based on current level
|
||||||
|
sr.writer.write_string(strings.repeat(` `, sr.indent * 2))
|
||||||
|
|
||||||
|
// Output the span type
|
||||||
|
sr.writer.write_string('SPAN[${typ}]: ')
|
||||||
|
|
||||||
|
// Add specific details based on span type
|
||||||
|
match typ {
|
||||||
|
.md_span_a {
|
||||||
|
href, title := get_link_details(detail)
|
||||||
|
if href != none {
|
||||||
|
sr.writer.write_string('Link: ${href}')
|
||||||
|
}
|
||||||
|
if title != none {
|
||||||
|
sr.writer.write_string(', Title: ${title}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.md_span_img {
|
||||||
|
src, title := get_image_details(detail)
|
||||||
|
if src != none {
|
||||||
|
sr.writer.write_string('Source: ${src}')
|
||||||
|
}
|
||||||
|
if title != none {
|
||||||
|
sr.writer.write_string(', Title: ${title}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.md_span_wikilink {
|
||||||
|
if target := get_wikilink_target(detail) {
|
||||||
|
sr.writer.write_string('Target: ${target}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {}
|
||||||
|
}
|
||||||
|
|
||||||
|
sr.writer.write_u8(`\n`)
|
||||||
|
sr.indent++
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut sr StructureRenderer) leave_span(typ markdown.MD_SPANTYPE, _ voidptr) ? {
|
||||||
|
sr.indent--
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut sr StructureRenderer) text(typ markdown.MD_TEXTTYPE, text string) ? {
|
||||||
|
if text.trim_space() == '' {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add indentation based on current level
|
||||||
|
sr.writer.write_string(strings.repeat(` `, sr.indent * 2))
|
||||||
|
|
||||||
|
// Output the text type
|
||||||
|
sr.writer.write_string('TEXT[${typ}]: ')
|
||||||
|
|
||||||
|
// Add the text content (truncate if too long)
|
||||||
|
content := if text.len > 50 { text[..50] + '...' } else { text }
|
||||||
|
sr.writer.write_string(content.replace('\n', '\\n'))
|
||||||
|
|
||||||
|
sr.writer.write_u8(`\n`)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut sr StructureRenderer) debug_log(msg string) {
|
||||||
|
println(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// to_structure renders a markdown string and returns its structure
|
||||||
|
pub fn to_structure(input string) string {
|
||||||
|
mut structure_renderer := StructureRenderer{}
|
||||||
|
out := markdown.render(input, mut structure_renderer) or { '' }
|
||||||
|
return out
|
||||||
|
}
|
||||||
133
examples/webtools/vmarkdown/mlib2/README.md
Normal file
133
examples/webtools/vmarkdown/mlib2/README.md
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
# V Markdown Parser
|
||||||
|
|
||||||
|
A pure V implementation of a Markdown parser that supports extended Markdown syntax and provides an easy way to navigate through the document structure.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Parses Markdown text into a structured representation
|
||||||
|
- Supports both basic and extended Markdown syntax
|
||||||
|
- Provides an easy way to navigate through the document structure
|
||||||
|
- Includes renderers for different output formats
|
||||||
|
- No external dependencies
|
||||||
|
|
||||||
|
## Supported Markdown Syntax
|
||||||
|
|
||||||
|
- Headings (# to ######)
|
||||||
|
- Paragraphs
|
||||||
|
- Blockquotes
|
||||||
|
- Lists (ordered and unordered)
|
||||||
|
- Task lists
|
||||||
|
- Code blocks (fenced with language support)
|
||||||
|
- Tables with alignment
|
||||||
|
- Horizontal rules
|
||||||
|
- Footnotes
|
||||||
|
- Basic text elements (currently as plain text, with planned support for inline formatting)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Parsing Markdown
|
||||||
|
|
||||||
|
```v
|
||||||
|
import mlib2
|
||||||
|
|
||||||
|
// Parse Markdown text
|
||||||
|
md_text := '# Hello World\n\nThis is a paragraph.'
|
||||||
|
doc := mlib2.parse(md_text)
|
||||||
|
|
||||||
|
// Access the document structure
|
||||||
|
root := doc.root
|
||||||
|
for child in root.children {
|
||||||
|
println(child.typ)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Navigating the Document
|
||||||
|
|
||||||
|
```v
|
||||||
|
import mlib2
|
||||||
|
|
||||||
|
// Parse Markdown text
|
||||||
|
md_text := '# Hello World\n\nThis is a paragraph.'
|
||||||
|
doc := mlib2.parse(md_text)
|
||||||
|
|
||||||
|
// Create a navigator
|
||||||
|
mut nav := mlib2.new_navigator(doc)
|
||||||
|
|
||||||
|
// Find elements by type
|
||||||
|
headings := nav.find_all_by_type(.heading)
|
||||||
|
for heading in headings {
|
||||||
|
level := heading.attributes['level']
|
||||||
|
println('Heading level ${level}: ${heading.content}')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find elements by content
|
||||||
|
if para := nav.find_by_content('paragraph') {
|
||||||
|
println('Found paragraph: ${para.content}')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigate through the document
|
||||||
|
if first_heading := nav.find_by_type(.heading) {
|
||||||
|
println('First heading: ${first_heading.content}')
|
||||||
|
|
||||||
|
// Move to next sibling
|
||||||
|
if next := nav.next_sibling() {
|
||||||
|
println('Next element after heading: ${next.typ}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rendering the Document
|
||||||
|
|
||||||
|
```v
|
||||||
|
import mlib2
|
||||||
|
|
||||||
|
// Parse Markdown text
|
||||||
|
md_text := '# Hello World\n\nThis is a paragraph.'
|
||||||
|
|
||||||
|
// Render as structure (for debugging)
|
||||||
|
structure := mlib2.to_structure(md_text)
|
||||||
|
println(structure)
|
||||||
|
|
||||||
|
// Render as plain text
|
||||||
|
plain_text := mlib2.to_plain(md_text)
|
||||||
|
println(plain_text)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Element Types
|
||||||
|
|
||||||
|
The parser recognizes the following element types:
|
||||||
|
|
||||||
|
- `document`: The root element of the document
|
||||||
|
- `heading`: A heading element (h1-h6)
|
||||||
|
- `paragraph`: A paragraph of text
|
||||||
|
- `blockquote`: A blockquote
|
||||||
|
- `code_block`: A code block
|
||||||
|
- `list`: A list (ordered or unordered)
|
||||||
|
- `list_item`: An item in a list
|
||||||
|
- `task_list_item`: A task list item with checkbox
|
||||||
|
- `table`: A table
|
||||||
|
- `table_row`: A row in a table
|
||||||
|
- `table_cell`: A cell in a table
|
||||||
|
- `horizontal_rule`: A horizontal rule
|
||||||
|
- `footnote`: A footnote definition
|
||||||
|
- `footnote_ref`: A reference to a footnote
|
||||||
|
- `text`: A text element
|
||||||
|
- `link`, `image`, `emphasis`, `strong`, `strikethrough`, `inline_code`: Inline formatting elements (planned for future implementation)
|
||||||
|
|
||||||
|
## Element Structure
|
||||||
|
|
||||||
|
Each Markdown element has the following properties:
|
||||||
|
|
||||||
|
- `typ`: The type of the element
|
||||||
|
- `content`: The text content of the element
|
||||||
|
- `children`: Child elements
|
||||||
|
- `attributes`: Additional attributes specific to the element type
|
||||||
|
- `line_number`: The line number where the element starts in the source
|
||||||
|
- `column`: The column number where the element starts in the source
|
||||||
|
|
||||||
|
## Future Improvements
|
||||||
|
|
||||||
|
- Implement parsing of inline elements (bold, italic, links, etc.)
|
||||||
|
- Add HTML renderer
|
||||||
|
- Support for more extended Markdown syntax
|
||||||
|
- Performance optimizations
|
||||||
143
examples/webtools/vmarkdown/mlib2/example.v
Normal file
143
examples/webtools/vmarkdown/mlib2/example.v
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
module mlib2
|
||||||
|
|
||||||
|
// This file contains examples of how to use the Markdown parser
|
||||||
|
|
||||||
|
// Example of parsing and navigating a markdown document
|
||||||
|
pub fn example_navigation() {
|
||||||
|
md_text := '# Heading 1
|
||||||
|
|
||||||
|
This is a paragraph with **bold** and *italic* text.
|
||||||
|
|
||||||
|
## Heading 2
|
||||||
|
|
||||||
|
- List item 1
|
||||||
|
- List item 2
|
||||||
|
- Nested item
|
||||||
|
- List item 3
|
||||||
|
|
||||||
|
```v
|
||||||
|
fn main() {
|
||||||
|
println("Hello, world!")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> This is a blockquote
|
||||||
|
> with multiple lines
|
||||||
|
|
||||||
|
| Column 1 | Column 2 | Column 3 |
|
||||||
|
|----------|:--------:|---------:|
|
||||||
|
| Left | Center | Right |
|
||||||
|
| Cell 1 | Cell 2 | Cell 3 |
|
||||||
|
|
||||||
|
[Link to V language](https://vlang.io)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Footnote reference[^1]
|
||||||
|
|
||||||
|
[^1]: This is a footnote.
|
||||||
|
'
|
||||||
|
|
||||||
|
// Parse the markdown text
|
||||||
|
doc := parse(md_text)
|
||||||
|
|
||||||
|
// Create a navigator
|
||||||
|
mut nav := new_navigator(doc)
|
||||||
|
|
||||||
|
// Find all headings
|
||||||
|
headings := nav.find_all_by_type(.heading)
|
||||||
|
println('Found ${headings.len} headings:')
|
||||||
|
for heading in headings {
|
||||||
|
level := heading.attributes['level']
|
||||||
|
println(' ${'#'.repeat(level.int())} ${heading.content}')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the first code block
|
||||||
|
if code_block := nav.find_by_type(.code_block) {
|
||||||
|
language := code_block.attributes['language']
|
||||||
|
println('\nFound code block in language: ${language}')
|
||||||
|
println('```${language}\n${code_block.content}```')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all list items
|
||||||
|
list_items := nav.find_all_by_type(.list_item)
|
||||||
|
println('\nFound ${list_items.len} list items:')
|
||||||
|
for item in list_items {
|
||||||
|
println(' - ${item.content}')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find content containing specific text
|
||||||
|
if element := nav.find_by_content('blockquote') {
|
||||||
|
println('\nFound element containing "blockquote":')
|
||||||
|
println(' Type: ${element.typ}')
|
||||||
|
println(' Content: ${element.content}')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find table cells
|
||||||
|
table_cells := nav.find_all_by_type(.table_cell)
|
||||||
|
println('\nFound ${table_cells.len} table cells:')
|
||||||
|
for cell in table_cells {
|
||||||
|
alignment := cell.attributes['align'] or { 'left' }
|
||||||
|
is_header := cell.attributes['is_header'] or { 'false' }
|
||||||
|
println(' Cell: "${cell.content}" (align: ${alignment}, header: ${is_header})')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find footnotes
|
||||||
|
println('\nFootnotes:')
|
||||||
|
for id, footnote in nav.footnotes() {
|
||||||
|
println(' [^${id}]: ${footnote.content}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example of rendering a markdown document
|
||||||
|
pub fn example_rendering() {
|
||||||
|
md_text := '# Heading 1
|
||||||
|
|
||||||
|
This is a paragraph with **bold** and *italic* text.
|
||||||
|
|
||||||
|
## Heading 2
|
||||||
|
|
||||||
|
- List item 1
|
||||||
|
- List item 2
|
||||||
|
- Nested item
|
||||||
|
- List item 3
|
||||||
|
|
||||||
|
```v
|
||||||
|
fn main() {
|
||||||
|
println("Hello, world!")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> This is a blockquote
|
||||||
|
> with multiple lines
|
||||||
|
'
|
||||||
|
|
||||||
|
// Parse the markdown text
|
||||||
|
doc := parse(md_text)
|
||||||
|
|
||||||
|
// Render as structure
|
||||||
|
mut structure_renderer := new_structure_renderer()
|
||||||
|
structure := structure_renderer.render(doc)
|
||||||
|
println('=== STRUCTURE RENDERING ===')
|
||||||
|
println(structure)
|
||||||
|
|
||||||
|
// Render as plain text
|
||||||
|
mut plain_text_renderer := new_plain_text_renderer()
|
||||||
|
plain_text := plain_text_renderer.render(doc)
|
||||||
|
println('=== PLAIN TEXT RENDERING ===')
|
||||||
|
println(plain_text)
|
||||||
|
|
||||||
|
// Using convenience functions
|
||||||
|
println('=== USING CONVENIENCE FUNCTIONS ===')
|
||||||
|
println(to_structure(md_text))
|
||||||
|
println(to_plain(md_text))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main function to run the examples
|
||||||
|
pub fn main() {
|
||||||
|
println('=== NAVIGATION EXAMPLE ===')
|
||||||
|
example_navigation()
|
||||||
|
|
||||||
|
println('\n=== RENDERING EXAMPLE ===')
|
||||||
|
example_rendering()
|
||||||
|
}
|
||||||
70
examples/webtools/vmarkdown/mlib2/markdown.v
Normal file
70
examples/webtools/vmarkdown/mlib2/markdown.v
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
module mlib2
|
||||||
|
|
||||||
|
// MarkdownElement represents a single element in a markdown document
|
||||||
|
pub struct MarkdownElement {
|
||||||
|
pub:
|
||||||
|
typ ElementType
|
||||||
|
content string
|
||||||
|
children []&MarkdownElement
|
||||||
|
attributes map[string]string
|
||||||
|
line_number int
|
||||||
|
column int
|
||||||
|
}
|
||||||
|
|
||||||
|
// ElementType represents the type of a markdown element
|
||||||
|
pub enum ElementType {
|
||||||
|
document
|
||||||
|
heading
|
||||||
|
paragraph
|
||||||
|
blockquote
|
||||||
|
code_block
|
||||||
|
list
|
||||||
|
list_item
|
||||||
|
table
|
||||||
|
table_row
|
||||||
|
table_cell
|
||||||
|
horizontal_rule
|
||||||
|
link
|
||||||
|
image
|
||||||
|
emphasis
|
||||||
|
strong
|
||||||
|
strikethrough
|
||||||
|
inline_code
|
||||||
|
html
|
||||||
|
text
|
||||||
|
footnote
|
||||||
|
footnote_ref
|
||||||
|
task_list_item
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkdownDocument represents a parsed markdown document
|
||||||
|
pub struct MarkdownDocument {
|
||||||
|
pub mut:
|
||||||
|
root &MarkdownElement
|
||||||
|
footnotes map[string]&MarkdownElement
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new markdown document
|
||||||
|
pub fn new_document() MarkdownDocument {
|
||||||
|
root := &MarkdownElement{
|
||||||
|
typ: .document
|
||||||
|
content: ''
|
||||||
|
children: []
|
||||||
|
}
|
||||||
|
return MarkdownDocument{
|
||||||
|
root: root
|
||||||
|
footnotes: map[string]&MarkdownElement{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses markdown text and returns a MarkdownDocument
|
||||||
|
pub fn parse(text string) MarkdownDocument {
|
||||||
|
mut parser := Parser{
|
||||||
|
text: text
|
||||||
|
pos: 0
|
||||||
|
line: 1
|
||||||
|
column: 1
|
||||||
|
}
|
||||||
|
return parser.parse()
|
||||||
|
}
|
||||||
|
|
||||||
269
examples/webtools/vmarkdown/mlib2/navigator.v
Normal file
269
examples/webtools/vmarkdown/mlib2/navigator.v
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
module mlib2
|
||||||
|
|
||||||
|
// Navigator provides an easy way to navigate through the document structure
|
||||||
|
@[heap]
|
||||||
|
pub struct Navigator {
|
||||||
|
pub:
|
||||||
|
doc MarkdownDocument
|
||||||
|
pub mut:
|
||||||
|
current_element &MarkdownElement
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new navigator for a markdown document
|
||||||
|
pub fn new_navigator(doc MarkdownDocument) Navigator {
|
||||||
|
return Navigator{
|
||||||
|
doc: doc
|
||||||
|
current_element: doc.root
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the navigator to the root element
|
||||||
|
pub fn (mut n Navigator) reset() {
|
||||||
|
n.current_element = n.doc.root
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find an element by type
|
||||||
|
pub fn (mut n Navigator) find_by_type(typ ElementType) ?&MarkdownElement {
|
||||||
|
return n.find_by_type_from(n.doc.root, typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find an element by type starting from a specific element
|
||||||
|
fn (mut n Navigator) find_by_type_from(element &MarkdownElement, typ ElementType) ?&MarkdownElement {
|
||||||
|
if element.typ == typ {
|
||||||
|
n.current_element = element
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
|
||||||
|
for child in element.children {
|
||||||
|
if child.typ == typ {
|
||||||
|
n.current_element = child
|
||||||
|
return child
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := n.find_by_type_from(child, typ) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all elements by type
|
||||||
|
pub fn (mut n Navigator) find_all_by_type(typ ElementType) []&MarkdownElement {
|
||||||
|
return n.find_all_by_type_from(n.doc.root, typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all elements by type starting from a specific element
|
||||||
|
fn (mut n Navigator) find_all_by_type_from(element &MarkdownElement, typ ElementType) []&MarkdownElement {
|
||||||
|
mut results := []&MarkdownElement{}
|
||||||
|
|
||||||
|
if element.typ == typ {
|
||||||
|
results << element
|
||||||
|
}
|
||||||
|
|
||||||
|
for child in element.children {
|
||||||
|
if child.typ == typ {
|
||||||
|
results << child
|
||||||
|
}
|
||||||
|
|
||||||
|
results << n.find_all_by_type_from(child, typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find an element by content
|
||||||
|
pub fn (mut n Navigator) find_by_content(text string) ?&MarkdownElement {
|
||||||
|
return n.find_by_content_from(n.doc.root, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find an element by content starting from a specific element
|
||||||
|
fn (mut n Navigator) find_by_content_from(element &MarkdownElement, text string) ?&MarkdownElement {
|
||||||
|
if element.content.contains(text) {
|
||||||
|
n.current_element = element
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
|
||||||
|
for child in element.children {
|
||||||
|
if child.content.contains(text) {
|
||||||
|
n.current_element = child
|
||||||
|
return child
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := n.find_by_content_from(child, text) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all elements by content
|
||||||
|
pub fn (mut n Navigator) find_all_by_content(text string) []&MarkdownElement {
|
||||||
|
return n.find_all_by_content_from(n.doc.root, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all elements by content starting from a specific element
|
||||||
|
fn (mut n Navigator) find_all_by_content_from(element &MarkdownElement, text string) []&MarkdownElement {
|
||||||
|
mut results := []&MarkdownElement{}
|
||||||
|
|
||||||
|
if element.content.contains(text) {
|
||||||
|
results << element
|
||||||
|
}
|
||||||
|
|
||||||
|
for child in element.children {
|
||||||
|
if child.content.contains(text) {
|
||||||
|
results << child
|
||||||
|
}
|
||||||
|
|
||||||
|
results << n.find_all_by_content_from(child, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find an element by attribute
|
||||||
|
pub fn (mut n Navigator) find_by_attribute(key string, value string) ?&MarkdownElement {
|
||||||
|
return n.find_by_attribute_from(n.doc.root, key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find an element by attribute starting from a specific element
|
||||||
|
fn (mut n Navigator) find_by_attribute_from(element &MarkdownElement, key string, value string) ?&MarkdownElement {
|
||||||
|
if element.attributes[key] == value {
|
||||||
|
n.current_element = element
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
|
||||||
|
for child in element.children {
|
||||||
|
if child.attributes[key] == value {
|
||||||
|
n.current_element = child
|
||||||
|
return child
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := n.find_by_attribute_from(child, key, value) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all elements by attribute
|
||||||
|
pub fn (mut n Navigator) find_all_by_attribute(key string, value string) []&MarkdownElement {
|
||||||
|
return n.find_all_by_attribute_from(n.doc.root, key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all elements by attribute starting from a specific element
|
||||||
|
fn (mut n Navigator) find_all_by_attribute_from(element &MarkdownElement, key string, value string) []&MarkdownElement {
|
||||||
|
mut results := []&MarkdownElement{}
|
||||||
|
|
||||||
|
if element.attributes[key] == value {
|
||||||
|
results << element
|
||||||
|
}
|
||||||
|
|
||||||
|
for child in element.children {
|
||||||
|
if child.attributes[key] == value {
|
||||||
|
results << child
|
||||||
|
}
|
||||||
|
|
||||||
|
results << n.find_all_by_attribute_from(child, key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the parent of an element
|
||||||
|
pub fn (mut n Navigator) find_parent(target &MarkdownElement) ?&MarkdownElement {
|
||||||
|
return n.find_parent_from(n.doc.root, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the parent of an element starting from a specific element
|
||||||
|
fn (mut n Navigator) find_parent_from(root &MarkdownElement, target &MarkdownElement) ?&MarkdownElement {
|
||||||
|
for child in root.children {
|
||||||
|
if child == target {
|
||||||
|
n.current_element = root
|
||||||
|
return root
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := n.find_parent_from(child, target) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the parent of the current element
|
||||||
|
pub fn (mut n Navigator) parent() ?&MarkdownElement {
|
||||||
|
return n.find_parent(n.current_element)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the next sibling of the current element
|
||||||
|
pub fn (mut n Navigator) next_sibling() ?&MarkdownElement {
|
||||||
|
parent := n.parent() or { return none }
|
||||||
|
|
||||||
|
mut found := false
|
||||||
|
for child in parent.children {
|
||||||
|
if found {
|
||||||
|
n.current_element = child
|
||||||
|
return child
|
||||||
|
}
|
||||||
|
|
||||||
|
if child == n.current_element {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the previous sibling of the current element
|
||||||
|
pub fn (mut n Navigator) prev_sibling() ?&MarkdownElement {
|
||||||
|
parent := n.parent() or { return none }
|
||||||
|
|
||||||
|
mut prev := &MarkdownElement(0)
|
||||||
|
for child in parent.children {
|
||||||
|
if child == n.current_element && prev != 0 {
|
||||||
|
n.current_element = prev
|
||||||
|
return prev
|
||||||
|
}
|
||||||
|
|
||||||
|
prev = child
|
||||||
|
}
|
||||||
|
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the first child of the current element
|
||||||
|
pub fn (mut n Navigator) first_child() ?&MarkdownElement {
|
||||||
|
if n.current_element.children.len == 0 {
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
|
||||||
|
n.current_element = n.current_element.children[0]
|
||||||
|
return n.current_element
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the last child of the current element
|
||||||
|
pub fn (mut n Navigator) last_child() ?&MarkdownElement {
|
||||||
|
if n.current_element.children.len == 0 {
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
|
||||||
|
n.current_element = n.current_element.children[n.current_element.children.len - 1]
|
||||||
|
return n.current_element
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all footnotes in the document
|
||||||
|
pub fn (n Navigator) footnotes() map[string]&MarkdownElement {
|
||||||
|
return n.doc.footnotes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a footnote by identifier
|
||||||
|
pub fn (n Navigator) footnote(id string) ?&MarkdownElement {
|
||||||
|
if id in n.doc.footnotes {
|
||||||
|
return n.doc.footnotes[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
return none
|
||||||
|
}
|
||||||
31
examples/webtools/vmarkdown/mlib2/parser_block.v
Normal file
31
examples/webtools/vmarkdown/mlib2/parser_block.v
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
module mlib2
|
||||||
|
|
||||||
|
// Parse a block-level element
|
||||||
|
fn (mut p Parser) parse_block() ?&MarkdownElement {
|
||||||
|
// Skip whitespace at the beginning of a line
|
||||||
|
p.skip_whitespace()
|
||||||
|
|
||||||
|
// Check for end of input
|
||||||
|
if p.pos >= p.text.len {
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for different block types
|
||||||
|
if p.text[p.pos] == `#` {
|
||||||
|
return p.parse_heading()
|
||||||
|
} else if p.text[p.pos] == `>` {
|
||||||
|
return p.parse_blockquote()
|
||||||
|
} else if p.text[p.pos] == `-` && p.peek(1) == `-` && p.peek(2) == `-` {
|
||||||
|
return p.parse_horizontal_rule()
|
||||||
|
} else if p.text[p.pos] == '`' && p.peek(1) == '`' && p.peek(2) == '`' {
|
||||||
|
return p.parse_fenced_code_block()
|
||||||
|
} else if p.is_list_start() {
|
||||||
|
return p.parse_list()
|
||||||
|
} else if p.is_table_start() {
|
||||||
|
return p.parse_table()
|
||||||
|
} else if p.is_footnote_definition() {
|
||||||
|
return p.parse_footnote_definition()
|
||||||
|
} else {
|
||||||
|
return p.parse_paragraph()
|
||||||
|
}
|
||||||
|
}
|
||||||
97
examples/webtools/vmarkdown/mlib2/parser_blockquote.v
Normal file
97
examples/webtools/vmarkdown/mlib2/parser_blockquote.v
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
module mlib2
|
||||||
|
|
||||||
|
// Parse a blockquote element
|
||||||
|
fn (mut p Parser) parse_blockquote() ?&MarkdownElement {
|
||||||
|
start_pos := p.pos
|
||||||
|
start_line := p.line
|
||||||
|
start_column := p.column
|
||||||
|
|
||||||
|
// Skip the > character
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
|
||||||
|
// Skip whitespace after >
|
||||||
|
p.skip_whitespace()
|
||||||
|
|
||||||
|
mut content := ''
|
||||||
|
mut lines := []string{}
|
||||||
|
|
||||||
|
// Read the first line
|
||||||
|
for p.pos < p.text.len && p.text[p.pos] != `\n` {
|
||||||
|
content += p.text[p.pos].ascii_str()
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
}
|
||||||
|
lines << content
|
||||||
|
|
||||||
|
// Skip the newline
|
||||||
|
if p.pos < p.text.len && p.text[p.pos] == `\n` {
|
||||||
|
p.pos++
|
||||||
|
p.line++
|
||||||
|
p.column = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read additional lines of the blockquote
|
||||||
|
for p.pos < p.text.len {
|
||||||
|
// Check if the line starts with >
|
||||||
|
if p.text[p.pos] == `>` {
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
p.skip_whitespace()
|
||||||
|
|
||||||
|
mut line := ''
|
||||||
|
for p.pos < p.text.len && p.text[p.pos] != `\n` {
|
||||||
|
line += p.text[p.pos].ascii_str()
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
}
|
||||||
|
lines << line
|
||||||
|
|
||||||
|
// Skip the newline
|
||||||
|
if p.pos < p.text.len && p.text[p.pos] == `\n` {
|
||||||
|
p.pos++
|
||||||
|
p.line++
|
||||||
|
p.column = 1
|
||||||
|
}
|
||||||
|
} else if p.text[p.pos] == `\n` {
|
||||||
|
// Empty line - could be a continuation or the end of the blockquote
|
||||||
|
p.pos++
|
||||||
|
p.line++
|
||||||
|
p.column = 1
|
||||||
|
|
||||||
|
// Check if the next line is part of the blockquote
|
||||||
|
if p.pos < p.text.len && p.text[p.pos] == `>` {
|
||||||
|
lines << ''
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Not a blockquote line, end of blockquote
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join the lines with newlines
|
||||||
|
content = lines.join('\n')
|
||||||
|
|
||||||
|
// Create the blockquote element
|
||||||
|
mut blockquote := &MarkdownElement{
|
||||||
|
typ: .blockquote
|
||||||
|
content: content
|
||||||
|
line_number: start_line
|
||||||
|
column: start_column
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse nested blocks within the blockquote
|
||||||
|
mut nested_parser := Parser{
|
||||||
|
text: content
|
||||||
|
pos: 0
|
||||||
|
line: start_line
|
||||||
|
column: start_column
|
||||||
|
}
|
||||||
|
|
||||||
|
nested_doc := nested_parser.parse()
|
||||||
|
blockquote.children = nested_doc.root.children
|
||||||
|
|
||||||
|
return blockquote
|
||||||
|
}
|
||||||
117
examples/webtools/vmarkdown/mlib2/parser_fenced_code_block.v
Normal file
117
examples/webtools/vmarkdown/mlib2/parser_fenced_code_block.v
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
module mlib2
|
||||||
|
|
||||||
|
// Parse a fenced code block element
|
||||||
|
fn (mut p Parser) parse_fenced_code_block() ?&MarkdownElement {
|
||||||
|
start_pos := p.pos
|
||||||
|
start_line := p.line
|
||||||
|
start_column := p.column
|
||||||
|
|
||||||
|
// Check for opening fence (``` or ~~~)
|
||||||
|
fence_char := p.text[p.pos]
|
||||||
|
if fence_char != '`' && fence_char != '~' {
|
||||||
|
p.pos = start_pos
|
||||||
|
p.line = start_line
|
||||||
|
p.column = start_column
|
||||||
|
return p.parse_paragraph()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count fence characters
|
||||||
|
mut fence_len := 0
|
||||||
|
for p.pos < p.text.len && p.text[p.pos] == fence_char {
|
||||||
|
fence_len++
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must have at least 3 characters
|
||||||
|
if fence_len < 3 {
|
||||||
|
p.pos = start_pos
|
||||||
|
p.line = start_line
|
||||||
|
p.column = start_column
|
||||||
|
return p.parse_paragraph()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read language identifier
|
||||||
|
mut language := ''
|
||||||
|
for p.pos < p.text.len && p.text[p.pos] != `\n` {
|
||||||
|
language += p.text[p.pos].ascii_str()
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
}
|
||||||
|
language = language.trim_space()
|
||||||
|
|
||||||
|
// Skip the newline
|
||||||
|
if p.pos < p.text.len && p.text[p.pos] == `\n` {
|
||||||
|
p.pos++
|
||||||
|
p.line++
|
||||||
|
p.column = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read code content until closing fence
|
||||||
|
mut content := ''
|
||||||
|
mut found_closing_fence := false
|
||||||
|
|
||||||
|
for p.pos < p.text.len {
|
||||||
|
// Check for closing fence
|
||||||
|
if p.text[p.pos] == fence_char {
|
||||||
|
mut i := p.pos
|
||||||
|
mut count := 0
|
||||||
|
|
||||||
|
// Count fence characters
|
||||||
|
for i < p.text.len && p.text[i] == fence_char {
|
||||||
|
count++
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's a valid closing fence
|
||||||
|
if count >= fence_len {
|
||||||
|
// Skip to end of line
|
||||||
|
for i < p.text.len && p.text[i] != `\n` {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update position
|
||||||
|
p.pos = i
|
||||||
|
if p.pos < p.text.len && p.text[p.pos] == `\n` {
|
||||||
|
p.pos++
|
||||||
|
p.line++
|
||||||
|
p.column = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
found_closing_fence = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add character to content
|
||||||
|
content += p.text[p.pos].ascii_str()
|
||||||
|
|
||||||
|
// Move to next character
|
||||||
|
if p.text[p.pos] == `\n` {
|
||||||
|
p.line++
|
||||||
|
p.column = 1
|
||||||
|
} else {
|
||||||
|
p.column++
|
||||||
|
}
|
||||||
|
p.pos++
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no closing fence was found, treat as paragraph
|
||||||
|
if !found_closing_fence {
|
||||||
|
p.pos = start_pos
|
||||||
|
p.line = start_line
|
||||||
|
p.column = start_column
|
||||||
|
return p.parse_paragraph()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the code block element
|
||||||
|
return &MarkdownElement{
|
||||||
|
typ: .code_block
|
||||||
|
content: content
|
||||||
|
line_number: start_line
|
||||||
|
column: start_column
|
||||||
|
attributes: {
|
||||||
|
'language': language
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
140
examples/webtools/vmarkdown/mlib2/parser_footnote_definition.v
Normal file
140
examples/webtools/vmarkdown/mlib2/parser_footnote_definition.v
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
module mlib2
|
||||||
|
|
||||||
|
// Parse a footnote definition
|
||||||
|
fn (mut p Parser) parse_footnote_definition() ?&MarkdownElement {
|
||||||
|
start_pos := p.pos
|
||||||
|
start_line := p.line
|
||||||
|
start_column := p.column
|
||||||
|
|
||||||
|
// Skip the [ character
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
|
||||||
|
// Skip the ^ character
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
|
||||||
|
// Read the footnote identifier
|
||||||
|
mut identifier := ''
|
||||||
|
for p.pos < p.text.len && p.text[p.pos] != `]` {
|
||||||
|
identifier += p.text[p.pos].ascii_str()
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip the ] character
|
||||||
|
if p.pos < p.text.len && p.text[p.pos] == `]` {
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
} else {
|
||||||
|
p.pos = start_pos
|
||||||
|
p.line = start_line
|
||||||
|
p.column = start_column
|
||||||
|
return p.parse_paragraph()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip the : character
|
||||||
|
if p.pos < p.text.len && p.text[p.pos] == `:` {
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
} else {
|
||||||
|
p.pos = start_pos
|
||||||
|
p.line = start_line
|
||||||
|
p.column = start_column
|
||||||
|
return p.parse_paragraph()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip whitespace
|
||||||
|
p.skip_whitespace()
|
||||||
|
|
||||||
|
// Read the footnote content
|
||||||
|
mut content := ''
|
||||||
|
mut lines := []string{}
|
||||||
|
|
||||||
|
// Read the first line
|
||||||
|
for p.pos < p.text.len && p.text[p.pos] != `\n` {
|
||||||
|
content += p.text[p.pos].ascii_str()
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
}
|
||||||
|
lines << content
|
||||||
|
|
||||||
|
// Skip the newline
|
||||||
|
if p.pos < p.text.len && p.text[p.pos] == `\n` {
|
||||||
|
p.pos++
|
||||||
|
p.line++
|
||||||
|
p.column = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read additional lines of the footnote
|
||||||
|
for p.pos < p.text.len {
|
||||||
|
// Check if the line is indented (part of the current footnote)
|
||||||
|
if p.text[p.pos] == ` ` || p.text[p.pos] == `\t` {
|
||||||
|
// Count indentation
|
||||||
|
mut indent := 0
|
||||||
|
for p.pos < p.text.len && (p.text[p.pos] == ` ` || p.text[p.pos] == `\t`) {
|
||||||
|
indent++
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
}
|
||||||
|
|
||||||
|
// If indented enough, it's part of the current footnote
|
||||||
|
if indent >= 2 {
|
||||||
|
mut line := ''
|
||||||
|
for p.pos < p.text.len && p.text[p.pos] != `\n` {
|
||||||
|
line += p.text[p.pos].ascii_str()
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
}
|
||||||
|
lines << line
|
||||||
|
|
||||||
|
// Skip the newline
|
||||||
|
if p.pos < p.text.len && p.text[p.pos] == `\n` {
|
||||||
|
p.pos++
|
||||||
|
p.line++
|
||||||
|
p.column = 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Not indented enough, end of footnote
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else if p.text[p.pos] == `\n` {
|
||||||
|
// Empty line - could be a continuation or the end of the footnote
|
||||||
|
p.pos++
|
||||||
|
p.line++
|
||||||
|
p.column = 1
|
||||||
|
|
||||||
|
// Check if the next line is indented
|
||||||
|
if p.pos < p.text.len && (p.text[p.pos] == ` ` || p.text[p.pos] == `\t`) {
|
||||||
|
lines << ''
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Not an indented line, end of footnote
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join the lines with newlines
|
||||||
|
content = lines.join('\n')
|
||||||
|
|
||||||
|
// Create the footnote element
|
||||||
|
mut footnote := &MarkdownElement{
|
||||||
|
typ: .footnote
|
||||||
|
content: content
|
||||||
|
line_number: start_line
|
||||||
|
column: start_column
|
||||||
|
attributes: {
|
||||||
|
'identifier': identifier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse inline elements within the footnote
|
||||||
|
footnote.children = p.parse_inline(content)
|
||||||
|
|
||||||
|
// Add the footnote to the document
|
||||||
|
p.doc.footnotes[identifier] = footnote
|
||||||
|
|
||||||
|
return footnote
|
||||||
|
}
|
||||||
64
examples/webtools/vmarkdown/mlib2/parser_heading.v
Normal file
64
examples/webtools/vmarkdown/mlib2/parser_heading.v
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
module mlib2
|
||||||
|
|
||||||
|
// Parse a heading element
|
||||||
|
fn (mut p Parser) parse_heading() ?&MarkdownElement {
|
||||||
|
start_pos := p.pos
|
||||||
|
start_line := p.line
|
||||||
|
start_column := p.column
|
||||||
|
|
||||||
|
// Count the number of # characters
|
||||||
|
mut level := 0
|
||||||
|
for p.pos < p.text.len && p.text[p.pos] == `#` && level < 6 {
|
||||||
|
level++
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be followed by a space
|
||||||
|
if p.pos >= p.text.len || (p.text[p.pos] != ` ` && p.text[p.pos] != `\t`) {
|
||||||
|
p.pos = start_pos
|
||||||
|
p.line = start_line
|
||||||
|
p.column = start_column
|
||||||
|
return p.parse_paragraph()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip whitespace after #
|
||||||
|
p.skip_whitespace()
|
||||||
|
|
||||||
|
// Read the heading text until end of line
|
||||||
|
mut content := ''
|
||||||
|
for p.pos < p.text.len && p.text[p.pos] != `\n` {
|
||||||
|
content += p.text[p.pos].ascii_str()
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip the newline
|
||||||
|
if p.pos < p.text.len && p.text[p.pos] == `\n` {
|
||||||
|
p.pos++
|
||||||
|
p.line++
|
||||||
|
p.column = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim trailing whitespace and optional closing #s
|
||||||
|
content = content.trim_right()
|
||||||
|
for content.ends_with('#') {
|
||||||
|
content = content.trim_right('#').trim_right()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the heading element
|
||||||
|
mut heading := &MarkdownElement{
|
||||||
|
typ: .heading
|
||||||
|
content: content
|
||||||
|
line_number: start_line
|
||||||
|
column: start_column
|
||||||
|
attributes: {
|
||||||
|
'level': level.str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse inline elements within the heading
|
||||||
|
heading.children = p.parse_inline(content)
|
||||||
|
|
||||||
|
return heading
|
||||||
|
}
|
||||||
119
examples/webtools/vmarkdown/mlib2/parser_helpers.v
Normal file
119
examples/webtools/vmarkdown/mlib2/parser_helpers.v
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
module mlib2
|
||||||
|
|
||||||
|
// Helper function to peek ahead in the text
|
||||||
|
fn (p Parser) peek(offset int) byte {
|
||||||
|
if p.pos + offset >= p.text.len {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return p.text[p.pos + offset]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip whitespace characters
|
||||||
|
fn (mut p Parser) skip_whitespace() {
|
||||||
|
for p.pos < p.text.len && (p.text[p.pos] == ` ` || p.text[p.pos] == `\t`) {
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if current position is the start of a list
|
||||||
|
fn (p Parser) is_list_start() bool {
|
||||||
|
if p.pos >= p.text.len {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unordered list: *, -, +
|
||||||
|
if (p.text[p.pos] == `*` || p.text[p.pos] == `-` || p.text[p.pos] == `+`) &&
|
||||||
|
(p.peek(1) == ` ` || p.peek(1) == `\t`) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ordered list: 1., 2., etc.
|
||||||
|
if p.pos + 2 < p.text.len && p.text[p.pos].is_digit() {
|
||||||
|
mut i := p.pos + 1
|
||||||
|
for i < p.text.len && p.text[i].is_digit() {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if i < p.text.len && p.text[i] == `.` && i + 1 < p.text.len && (p.text[i + 1] == ` ` || p.text[i + 1] == `\t`) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Task list: - [ ], - [x], etc.
|
||||||
|
if p.pos + 4 < p.text.len &&
|
||||||
|
(p.text[p.pos] == `-` || p.text[p.pos] == `*` || p.text[p.pos] == `+`) &&
|
||||||
|
p.text[p.pos + 1] == ` ` && p.text[p.pos + 2] == `[` &&
|
||||||
|
(p.text[p.pos + 3] == ` ` || p.text[p.pos + 3] == `x` || p.text[p.pos + 3] == `X`) &&
|
||||||
|
p.text[p.pos + 4] == `]` {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if current position is the start of a table
|
||||||
|
fn (p Parser) is_table_start() bool {
|
||||||
|
if p.pos >= p.text.len || p.text[p.pos] != `|` {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for a pipe character at the beginning of the line
|
||||||
|
// and check if there's at least one more pipe in the line
|
||||||
|
mut has_second_pipe := false
|
||||||
|
mut i := p.pos + 1
|
||||||
|
for i < p.text.len && p.text[i] != `\n` {
|
||||||
|
if p.text[i] == `|` {
|
||||||
|
has_second_pipe = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
if !has_second_pipe {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the next line has a header separator (---|---|...)
|
||||||
|
mut next_line_start := i + 1
|
||||||
|
if next_line_start >= p.text.len {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip whitespace at the beginning of the next line
|
||||||
|
for next_line_start < p.text.len && (p.text[next_line_start] == ` ` || p.text[next_line_start] == `\t`) {
|
||||||
|
next_line_start++
|
||||||
|
}
|
||||||
|
|
||||||
|
if next_line_start >= p.text.len || p.text[next_line_start] != `|` {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for pattern like |---|---|...
|
||||||
|
mut has_separator := false
|
||||||
|
mut j := next_line_start + 1
|
||||||
|
for j < p.text.len && p.text[j] != `\n` {
|
||||||
|
if p.text[j] == `-` {
|
||||||
|
has_separator = true
|
||||||
|
} else if p.text[j] == `|` {
|
||||||
|
// Reset for next column
|
||||||
|
has_separator = false
|
||||||
|
} else if p.text[j] != `:` && p.text[j] != ` ` && p.text[j] != `\t` {
|
||||||
|
// Only allow :, space, or tab besides - and |
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if current position is a footnote definition
|
||||||
|
fn (p Parser) is_footnote_definition() bool {
|
||||||
|
if p.pos + 3 >= p.text.len {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for pattern like [^id]:
|
||||||
|
return p.text[p.pos] == `[` && p.text[p.pos + 1] == `^` &&
|
||||||
|
p.text[p.pos + 2] != `]` && p.text.index_after(']:', p.pos + 2) > p.pos + 2
|
||||||
|
}
|
||||||
58
examples/webtools/vmarkdown/mlib2/parser_horizontal_rule.v
Normal file
58
examples/webtools/vmarkdown/mlib2/parser_horizontal_rule.v
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
module mlib2
|
||||||
|
|
||||||
|
// Parse a horizontal rule element
|
||||||
|
fn (mut p Parser) parse_horizontal_rule() ?&MarkdownElement {
|
||||||
|
start_pos := p.pos
|
||||||
|
start_line := p.line
|
||||||
|
start_column := p.column
|
||||||
|
|
||||||
|
// Check for at least 3 of the same character (-, *, _)
|
||||||
|
char := p.text[p.pos]
|
||||||
|
if char != `-` && char != `*` && char != `_` {
|
||||||
|
p.pos = start_pos
|
||||||
|
p.line = start_line
|
||||||
|
p.column = start_column
|
||||||
|
return p.parse_paragraph()
|
||||||
|
}
|
||||||
|
|
||||||
|
mut count := 0
|
||||||
|
for p.pos < p.text.len && p.text[p.pos] == char {
|
||||||
|
count++
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must have at least 3 characters
|
||||||
|
if count < 3 {
|
||||||
|
p.pos = start_pos
|
||||||
|
p.line = start_line
|
||||||
|
p.column = start_column
|
||||||
|
return p.parse_paragraph()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip whitespace
|
||||||
|
p.skip_whitespace()
|
||||||
|
|
||||||
|
// Must be at end of line
|
||||||
|
if p.pos < p.text.len && p.text[p.pos] != `\n` {
|
||||||
|
p.pos = start_pos
|
||||||
|
p.line = start_line
|
||||||
|
p.column = start_column
|
||||||
|
return p.parse_paragraph()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip the newline
|
||||||
|
if p.pos < p.text.len && p.text[p.pos] == `\n` {
|
||||||
|
p.pos++
|
||||||
|
p.line++
|
||||||
|
p.column = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the horizontal rule element
|
||||||
|
return &MarkdownElement{
|
||||||
|
typ: .horizontal_rule
|
||||||
|
content: ''
|
||||||
|
line_number: start_line
|
||||||
|
column: start_column
|
||||||
|
}
|
||||||
|
}
|
||||||
22
examples/webtools/vmarkdown/mlib2/parser_inline.v
Normal file
22
examples/webtools/vmarkdown/mlib2/parser_inline.v
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
module mlib2
|
||||||
|
|
||||||
|
// Parse inline elements within a block
|
||||||
|
fn (mut p Parser) parse_inline(text string) []&MarkdownElement {
|
||||||
|
mut elements := []&MarkdownElement{}
|
||||||
|
|
||||||
|
// Simple implementation for now - just create a text element
|
||||||
|
if text.trim_space() != '' {
|
||||||
|
elements << &MarkdownElement{
|
||||||
|
typ: .text
|
||||||
|
content: text
|
||||||
|
line_number: 0
|
||||||
|
column: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Implement parsing of inline elements like bold, italic, links, etc.
|
||||||
|
// This would involve scanning the text for markers like *, _, **, __, [, !, etc.
|
||||||
|
// and creating appropriate elements for each.
|
||||||
|
|
||||||
|
return elements
|
||||||
|
}
|
||||||
96
examples/webtools/vmarkdown/mlib2/parser_list.v
Normal file
96
examples/webtools/vmarkdown/mlib2/parser_list.v
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
module mlib2
|
||||||
|
|
||||||
|
// Parse a list element
|
||||||
|
fn (mut p Parser) parse_list() ?&MarkdownElement {
|
||||||
|
start_pos := p.pos
|
||||||
|
start_line := p.line
|
||||||
|
start_column := p.column
|
||||||
|
|
||||||
|
// Determine list type (ordered or unordered)
|
||||||
|
mut is_ordered := false
|
||||||
|
mut start_number := 1
|
||||||
|
mut marker := ''
|
||||||
|
|
||||||
|
if p.text[p.pos].is_digit() {
|
||||||
|
// Ordered list
|
||||||
|
is_ordered = true
|
||||||
|
|
||||||
|
// Parse start number
|
||||||
|
mut num_str := ''
|
||||||
|
for p.pos < p.text.len && p.text[p.pos].is_digit() {
|
||||||
|
num_str += p.text[p.pos].ascii_str()
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
}
|
||||||
|
|
||||||
|
start_number = num_str.int()
|
||||||
|
|
||||||
|
// Must be followed by a period
|
||||||
|
if p.pos >= p.text.len || p.text[p.pos] != `.` {
|
||||||
|
p.pos = start_pos
|
||||||
|
p.line = start_line
|
||||||
|
p.column = start_column
|
||||||
|
return p.parse_paragraph()
|
||||||
|
}
|
||||||
|
|
||||||
|
marker = '.'
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
} else {
|
||||||
|
// Unordered list
|
||||||
|
marker = p.text[p.pos].ascii_str()
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be followed by whitespace
|
||||||
|
if p.pos >= p.text.len || (p.text[p.pos] != ` ` && p.text[p.pos] != `\t`) {
|
||||||
|
p.pos = start_pos
|
||||||
|
p.line = start_line
|
||||||
|
p.column = start_column
|
||||||
|
return p.parse_paragraph()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the list element
|
||||||
|
mut list := &MarkdownElement{
|
||||||
|
typ: .list
|
||||||
|
content: ''
|
||||||
|
line_number: start_line
|
||||||
|
column: start_column
|
||||||
|
attributes: {
|
||||||
|
'ordered': is_ordered.str()
|
||||||
|
'start': start_number.str()
|
||||||
|
'marker': marker
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse list items
|
||||||
|
for {
|
||||||
|
// Parse list item
|
||||||
|
if item := p.parse_list_item(is_ordered, marker) {
|
||||||
|
list.children << item
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we're at the end of the list
|
||||||
|
p.skip_whitespace()
|
||||||
|
|
||||||
|
if p.pos >= p.text.len {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for next list item
|
||||||
|
if is_ordered {
|
||||||
|
if !p.text[p.pos].is_digit() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if p.text[p.pos] != marker[0] {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
117
examples/webtools/vmarkdown/mlib2/parser_list_item.v
Normal file
117
examples/webtools/vmarkdown/mlib2/parser_list_item.v
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
module mlib2
|
||||||
|
|
||||||
|
// Parse a list item
|
||||||
|
fn (mut p Parser) parse_list_item(is_ordered bool, marker string) ?&MarkdownElement {
|
||||||
|
start_pos := p.pos
|
||||||
|
start_line := p.line
|
||||||
|
start_column := p.column
|
||||||
|
|
||||||
|
// Skip whitespace
|
||||||
|
p.skip_whitespace()
|
||||||
|
|
||||||
|
// Check for task list item
|
||||||
|
mut is_task := false
|
||||||
|
mut is_completed := false
|
||||||
|
|
||||||
|
if p.pos + 3 < p.text.len && p.text[p.pos] == `[` &&
|
||||||
|
(p.text[p.pos + 1] == ` ` || p.text[p.pos + 1] == `x` || p.text[p.pos + 1] == `X`) &&
|
||||||
|
p.text[p.pos + 2] == `]` && (p.text[p.pos + 3] == ` ` || p.text[p.pos + 3] == `\t`) {
|
||||||
|
is_task = true
|
||||||
|
is_completed = p.text[p.pos + 1] == `x` || p.text[p.pos + 1] == `X`
|
||||||
|
p.pos += 3
|
||||||
|
p.column += 3
|
||||||
|
p.skip_whitespace()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read item content until end of line or next list item
|
||||||
|
mut content := ''
|
||||||
|
mut lines := []string{}
|
||||||
|
|
||||||
|
// Read the first line
|
||||||
|
for p.pos < p.text.len && p.text[p.pos] != `\n` {
|
||||||
|
content += p.text[p.pos].ascii_str()
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
}
|
||||||
|
lines << content
|
||||||
|
|
||||||
|
// Skip the newline
|
||||||
|
if p.pos < p.text.len && p.text[p.pos] == `\n` {
|
||||||
|
p.pos++
|
||||||
|
p.line++
|
||||||
|
p.column = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read additional lines of the list item
|
||||||
|
for p.pos < p.text.len {
|
||||||
|
// Check if the line is indented (part of the current item)
|
||||||
|
if p.text[p.pos] == ` ` || p.text[p.pos] == `\t` {
|
||||||
|
// Count indentation
|
||||||
|
mut indent := 0
|
||||||
|
for p.pos < p.text.len && (p.text[p.pos] == ` ` || p.text[p.pos] == `\t`) {
|
||||||
|
indent++
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
}
|
||||||
|
|
||||||
|
// If indented enough, it's part of the current item
|
||||||
|
if indent >= 2 {
|
||||||
|
mut line := ''
|
||||||
|
for p.pos < p.text.len && p.text[p.pos] != `\n` {
|
||||||
|
line += p.text[p.pos].ascii_str()
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
}
|
||||||
|
lines << line
|
||||||
|
|
||||||
|
// Skip the newline
|
||||||
|
if p.pos < p.text.len && p.text[p.pos] == `\n` {
|
||||||
|
p.pos++
|
||||||
|
p.line++
|
||||||
|
p.column = 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Not indented enough, end of list item
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else if p.text[p.pos] == `\n` {
|
||||||
|
// Empty line - could be a continuation or the end of the list item
|
||||||
|
p.pos++
|
||||||
|
p.line++
|
||||||
|
p.column = 1
|
||||||
|
|
||||||
|
// Check if the next line is indented
|
||||||
|
if p.pos < p.text.len && (p.text[p.pos] == ` ` || p.text[p.pos] == `\t`) {
|
||||||
|
lines << ''
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Not an indented line, end of list item
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join the lines with newlines
|
||||||
|
content = lines.join('\n')
|
||||||
|
|
||||||
|
// Create the list item element
|
||||||
|
mut item := &MarkdownElement{
|
||||||
|
typ: if is_task { .task_list_item } else { .list_item }
|
||||||
|
content: content
|
||||||
|
line_number: start_line
|
||||||
|
column: start_column
|
||||||
|
attributes: if is_task {
|
||||||
|
{
|
||||||
|
'completed': is_completed.str()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
map[string]string{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse inline elements within the list item
|
||||||
|
item.children = p.parse_inline(content)
|
||||||
|
|
||||||
|
return item
|
||||||
|
}
|
||||||
49
examples/webtools/vmarkdown/mlib2/parser_main.v
Normal file
49
examples/webtools/vmarkdown/mlib2/parser_main.v
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
module mlib2
|
||||||
|
|
||||||
|
// Parser is responsible for parsing markdown text
|
||||||
|
struct Parser {
|
||||||
|
mut:
|
||||||
|
text string
|
||||||
|
pos int
|
||||||
|
line int
|
||||||
|
column int
|
||||||
|
doc MarkdownDocument
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main parsing function
|
||||||
|
fn (mut p Parser) parse() MarkdownDocument {
|
||||||
|
p.doc = new_document()
|
||||||
|
|
||||||
|
// Parse blocks until end of input
|
||||||
|
for p.pos < p.text.len {
|
||||||
|
element := p.parse_block() or { break }
|
||||||
|
p.doc.root.children << element
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process footnotes
|
||||||
|
p.process_footnotes()
|
||||||
|
|
||||||
|
return p.doc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process footnotes and add them to the document
|
||||||
|
fn (mut p Parser) process_footnotes() {
|
||||||
|
// Nothing to do if no footnotes
|
||||||
|
if p.doc.footnotes.len == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a horizontal rule before footnotes
|
||||||
|
hr := &MarkdownElement{
|
||||||
|
typ: .horizontal_rule
|
||||||
|
content: ''
|
||||||
|
line_number: p.line
|
||||||
|
column: p.column
|
||||||
|
}
|
||||||
|
p.doc.root.children << hr
|
||||||
|
|
||||||
|
// Add footnotes section
|
||||||
|
for key, footnote in p.doc.footnotes {
|
||||||
|
p.doc.root.children << footnote
|
||||||
|
}
|
||||||
|
}
|
||||||
77
examples/webtools/vmarkdown/mlib2/parser_paragraph.v
Normal file
77
examples/webtools/vmarkdown/mlib2/parser_paragraph.v
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
module mlib2
|
||||||
|
|
||||||
|
// Parse a paragraph element
|
||||||
|
fn (mut p Parser) parse_paragraph() ?&MarkdownElement {
|
||||||
|
start_pos := p.pos
|
||||||
|
start_line := p.line
|
||||||
|
start_column := p.column
|
||||||
|
|
||||||
|
mut content := ''
|
||||||
|
mut lines := []string{}
|
||||||
|
|
||||||
|
// Read the first line
|
||||||
|
for p.pos < p.text.len && p.text[p.pos] != `\n` {
|
||||||
|
content += p.text[p.pos].ascii_str()
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
}
|
||||||
|
lines << content
|
||||||
|
|
||||||
|
// Skip the newline
|
||||||
|
if p.pos < p.text.len && p.text[p.pos] == `\n` {
|
||||||
|
p.pos++
|
||||||
|
p.line++
|
||||||
|
p.column = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read additional lines of the paragraph
|
||||||
|
for p.pos < p.text.len {
|
||||||
|
// Check if the line is empty (end of paragraph)
|
||||||
|
if p.text[p.pos] == `\n` {
|
||||||
|
p.pos++
|
||||||
|
p.line++
|
||||||
|
p.column = 1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the line starts with a block element
|
||||||
|
if p.text[p.pos] == `#` || p.text[p.pos] == `>` ||
|
||||||
|
(p.text[p.pos] == `-` && p.peek(1) == `-` && p.peek(2) == `-`) ||
|
||||||
|
(p.text[p.pos] == `\`` && p.peek(1) == `\`` && p.peek(2) == `\``) ||
|
||||||
|
p.is_list_start() || p.is_table_start() || p.is_footnote_definition() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the line
|
||||||
|
mut line := ''
|
||||||
|
for p.pos < p.text.len && p.text[p.pos] != `\n` {
|
||||||
|
line += p.text[p.pos].ascii_str()
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
}
|
||||||
|
lines << line
|
||||||
|
|
||||||
|
// Skip the newline
|
||||||
|
if p.pos < p.text.len && p.text[p.pos] == `\n` {
|
||||||
|
p.pos++
|
||||||
|
p.line++
|
||||||
|
p.column = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join the lines with spaces
|
||||||
|
content = lines.join(' ')
|
||||||
|
|
||||||
|
// Create the paragraph element
|
||||||
|
mut paragraph := &MarkdownElement{
|
||||||
|
typ: .paragraph
|
||||||
|
content: content
|
||||||
|
line_number: start_line
|
||||||
|
column: start_column
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse inline elements within the paragraph
|
||||||
|
paragraph.children = p.parse_inline(content)
|
||||||
|
|
||||||
|
return paragraph
|
||||||
|
}
|
||||||
225
examples/webtools/vmarkdown/mlib2/parser_table.v
Normal file
225
examples/webtools/vmarkdown/mlib2/parser_table.v
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
module mlib2
|
||||||
|
|
||||||
|
// Parse a table element
|
||||||
|
fn (mut p Parser) parse_table() ?&MarkdownElement {
|
||||||
|
start_pos := p.pos
|
||||||
|
start_line := p.line
|
||||||
|
start_column := p.column
|
||||||
|
|
||||||
|
// Create the table element
|
||||||
|
mut table := &MarkdownElement{
|
||||||
|
typ: .table
|
||||||
|
content: ''
|
||||||
|
line_number: start_line
|
||||||
|
column: start_column
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse header row
|
||||||
|
mut header_row := &MarkdownElement{
|
||||||
|
typ: .table_row
|
||||||
|
content: ''
|
||||||
|
line_number: p.line
|
||||||
|
column: p.column
|
||||||
|
attributes: {
|
||||||
|
'is_header': 'true'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip initial pipe if present
|
||||||
|
if p.text[p.pos] == `|` {
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse header cells
|
||||||
|
for p.pos < p.text.len && p.text[p.pos] != `\n` {
|
||||||
|
// Parse cell content
|
||||||
|
mut cell_content := ''
|
||||||
|
for p.pos < p.text.len && p.text[p.pos] != `|` && p.text[p.pos] != `\n` {
|
||||||
|
cell_content += p.text[p.pos].ascii_str()
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create cell element
|
||||||
|
cell := &MarkdownElement{
|
||||||
|
typ: .table_cell
|
||||||
|
content: cell_content.trim_space()
|
||||||
|
line_number: p.line
|
||||||
|
column: p.column - cell_content.len
|
||||||
|
attributes: {
|
||||||
|
'is_header': 'true'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add cell to row
|
||||||
|
header_row.children << cell
|
||||||
|
|
||||||
|
// Skip pipe
|
||||||
|
if p.pos < p.text.len && p.text[p.pos] == `|` {
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip newline
|
||||||
|
if p.pos < p.text.len && p.text[p.pos] == `\n` {
|
||||||
|
p.pos++
|
||||||
|
p.line++
|
||||||
|
p.column = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add header row to table
|
||||||
|
table.children << header_row
|
||||||
|
|
||||||
|
// Parse separator row (---|---|...)
|
||||||
|
// Skip initial pipe if present
|
||||||
|
if p.pos < p.text.len && p.text[p.pos] == `|` {
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse alignment information
|
||||||
|
mut alignments := []string{}
|
||||||
|
|
||||||
|
for p.pos < p.text.len && p.text[p.pos] != `\n` {
|
||||||
|
// Skip whitespace
|
||||||
|
for p.pos < p.text.len && (p.text[p.pos] == ` ` || p.text[p.pos] == `\t`) {
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check alignment
|
||||||
|
mut left_colon := false
|
||||||
|
mut right_colon := false
|
||||||
|
|
||||||
|
if p.pos < p.text.len && p.text[p.pos] == `:` {
|
||||||
|
left_colon = true
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip dashes
|
||||||
|
for p.pos < p.text.len && p.text[p.pos] == `-` {
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.pos < p.text.len && p.text[p.pos] == `:` {
|
||||||
|
right_colon = true
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine alignment
|
||||||
|
mut alignment := 'left' // default
|
||||||
|
if left_colon && right_colon {
|
||||||
|
alignment = 'center'
|
||||||
|
} else if right_colon {
|
||||||
|
alignment = 'right'
|
||||||
|
}
|
||||||
|
|
||||||
|
alignments << alignment
|
||||||
|
|
||||||
|
// Skip whitespace
|
||||||
|
for p.pos < p.text.len && (p.text[p.pos] == ` ` || p.text[p.pos] == `\t`) {
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip pipe
|
||||||
|
if p.pos < p.text.len && p.text[p.pos] == `|` {
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip newline
|
||||||
|
if p.pos < p.text.len && p.text[p.pos] == `\n` {
|
||||||
|
p.pos++
|
||||||
|
p.line++
|
||||||
|
p.column = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set alignment for header cells
|
||||||
|
for i, cell in header_row.children {
|
||||||
|
if i < alignments.len {
|
||||||
|
cell.attributes['align'] = alignments[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse data rows
|
||||||
|
for p.pos < p.text.len && p.text[p.pos] != `\n` {
|
||||||
|
// Create row element
|
||||||
|
mut row := &MarkdownElement{
|
||||||
|
typ: .table_row
|
||||||
|
content: ''
|
||||||
|
line_number: p.line
|
||||||
|
column: p.column
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip initial pipe if present
|
||||||
|
if p.text[p.pos] == `|` {
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse cells
|
||||||
|
mut cell_index := 0
|
||||||
|
for p.pos < p.text.len && p.text[p.pos] != `\n` {
|
||||||
|
// Parse cell content
|
||||||
|
mut cell_content := ''
|
||||||
|
for p.pos < p.text.len && p.text[p.pos] != `|` && p.text[p.pos] != `\n` {
|
||||||
|
cell_content += p.text[p.pos].ascii_str()
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create cell element
|
||||||
|
mut cell := &MarkdownElement{
|
||||||
|
typ: .table_cell
|
||||||
|
content: cell_content.trim_space()
|
||||||
|
line_number: p.line
|
||||||
|
column: p.column - cell_content.len
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set alignment
|
||||||
|
if cell_index < alignments.len {
|
||||||
|
cell.attributes['align'] = alignments[cell_index]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add cell to row
|
||||||
|
row.children << cell
|
||||||
|
cell_index++
|
||||||
|
|
||||||
|
// Skip pipe
|
||||||
|
if p.pos < p.text.len && p.text[p.pos] == `|` {
|
||||||
|
p.pos++
|
||||||
|
p.column++
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add row to table
|
||||||
|
table.children << row
|
||||||
|
|
||||||
|
// Skip newline
|
||||||
|
if p.pos < p.text.len && p.text[p.pos] == `\n` {
|
||||||
|
p.pos++
|
||||||
|
p.line++
|
||||||
|
p.column = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we're at the end of the table
|
||||||
|
if p.pos >= p.text.len || p.text[p.pos] != `|` {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return table
|
||||||
|
}
|
||||||
169
examples/webtools/vmarkdown/mlib2/renderer.v
Normal file
169
examples/webtools/vmarkdown/mlib2/renderer.v
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
module mlib2
|
||||||
|
|
||||||
|
// Renderer is the interface for all renderers
|
||||||
|
pub interface Renderer {
|
||||||
|
render(doc MarkdownDocument) string
|
||||||
|
}
|
||||||
|
|
||||||
|
// StructureRenderer renders a markdown document as a structure
|
||||||
|
pub struct StructureRenderer {
|
||||||
|
indent string = ' '
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new structure renderer
|
||||||
|
pub fn new_structure_renderer() StructureRenderer {
|
||||||
|
return StructureRenderer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render a markdown document as a structure
|
||||||
|
pub fn (r StructureRenderer) render(doc MarkdownDocument) string {
|
||||||
|
return r.render_element(doc.root, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render an element as a structure
|
||||||
|
fn (r StructureRenderer) render_element(element &MarkdownElement, level int) string {
|
||||||
|
mut result := r.indent.repeat(level) + '${element.typ}'
|
||||||
|
|
||||||
|
if element.content.len > 0 {
|
||||||
|
// Truncate long content
|
||||||
|
mut content := element.content
|
||||||
|
if content.len > 50 {
|
||||||
|
content = content[0..47] + '...'
|
||||||
|
}
|
||||||
|
// Escape newlines
|
||||||
|
content = content.replace('\n', '\\n')
|
||||||
|
result += ': "${content}"'
|
||||||
|
}
|
||||||
|
|
||||||
|
if element.attributes.len > 0 {
|
||||||
|
result += ' {'
|
||||||
|
mut first := true
|
||||||
|
for key, value in element.attributes {
|
||||||
|
if !first {
|
||||||
|
result += ', '
|
||||||
|
}
|
||||||
|
result += '${key}: "${value}"'
|
||||||
|
first = false
|
||||||
|
}
|
||||||
|
result += '}'
|
||||||
|
}
|
||||||
|
|
||||||
|
result += '\n'
|
||||||
|
|
||||||
|
for child in element.children {
|
||||||
|
result += r.render_element(child, level + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// PlainTextRenderer renders a markdown document as plain text
|
||||||
|
pub struct PlainTextRenderer {}
|
||||||
|
|
||||||
|
// Creates a new plain text renderer
|
||||||
|
pub fn new_plain_text_renderer() PlainTextRenderer {
|
||||||
|
return PlainTextRenderer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render a markdown document as plain text
|
||||||
|
pub fn (r PlainTextRenderer) render(doc MarkdownDocument) string {
|
||||||
|
return r.render_element(doc.root)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render an element as plain text
|
||||||
|
fn (r PlainTextRenderer) render_element(element &MarkdownElement) string {
|
||||||
|
mut result := ''
|
||||||
|
|
||||||
|
match element.typ {
|
||||||
|
.document {
|
||||||
|
for child in element.children {
|
||||||
|
result += r.render_element(child)
|
||||||
|
if child.typ != .horizontal_rule {
|
||||||
|
result += '\n\n'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Trim trailing newlines
|
||||||
|
result = result.trim_right('\n')
|
||||||
|
}
|
||||||
|
.heading {
|
||||||
|
level := element.attributes['level'].int()
|
||||||
|
result += '#'.repeat(level) + ' ' + element.content
|
||||||
|
}
|
||||||
|
.paragraph {
|
||||||
|
result += element.content
|
||||||
|
}
|
||||||
|
.blockquote {
|
||||||
|
lines := element.content.split('\n')
|
||||||
|
for line in lines {
|
||||||
|
result += '> ' + line + '\n'
|
||||||
|
}
|
||||||
|
result = result.trim_right('\n')
|
||||||
|
}
|
||||||
|
.code_block {
|
||||||
|
language := element.attributes['language']
|
||||||
|
result += '```${language}\n'
|
||||||
|
result += element.content
|
||||||
|
result += '```'
|
||||||
|
}
|
||||||
|
.list {
|
||||||
|
is_ordered := element.attributes['ordered'] == 'true'
|
||||||
|
start_number := element.attributes['start'].int()
|
||||||
|
|
||||||
|
mut i := start_number
|
||||||
|
for child in element.children {
|
||||||
|
if is_ordered {
|
||||||
|
result += '${i}. '
|
||||||
|
i++
|
||||||
|
} else {
|
||||||
|
result += '- '
|
||||||
|
}
|
||||||
|
result += r.render_element(child) + '\n'
|
||||||
|
}
|
||||||
|
result = result.trim_right('\n')
|
||||||
|
}
|
||||||
|
.list_item, .task_list_item {
|
||||||
|
if element.typ == .task_list_item {
|
||||||
|
is_completed := element.attributes['completed'] == 'true'
|
||||||
|
if is_completed {
|
||||||
|
result += '[x] '
|
||||||
|
} else {
|
||||||
|
result += '[ ] '
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result += element.content
|
||||||
|
}
|
||||||
|
.table {
|
||||||
|
// TODO: Implement table rendering
|
||||||
|
result += '[Table with ${element.children.len} rows]'
|
||||||
|
}
|
||||||
|
.horizontal_rule {
|
||||||
|
result += '---'
|
||||||
|
}
|
||||||
|
.footnote {
|
||||||
|
identifier := element.attributes['identifier']
|
||||||
|
result += '[^${identifier}]: ${element.content}'
|
||||||
|
}
|
||||||
|
.text {
|
||||||
|
result += element.content
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result += element.content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenience function to render markdown text as a structure
|
||||||
|
pub fn to_structure(text string) string {
|
||||||
|
doc := parse(text)
|
||||||
|
mut renderer := new_structure_renderer()
|
||||||
|
return renderer.render(doc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenience function to render markdown text as plain text
|
||||||
|
pub fn to_plain(text string) string {
|
||||||
|
doc := parse(text)
|
||||||
|
mut renderer := new_plain_text_renderer()
|
||||||
|
return renderer.render(doc)
|
||||||
|
}
|
||||||
@@ -37,7 +37,7 @@ fn (mut self DocBase) parent_doc() &Doc {
|
|||||||
|
|
||||||
fn (mut self DocBase) remove_empty_children() {
|
fn (mut self DocBase) remove_empty_children() {
|
||||||
self.children = self.children.filter(!(it.content == '' && it.children.len == 0
|
self.children = self.children.filter(!(it.content == '' && it.children.len == 0
|
||||||
&& it.type_name in ['text', 'empty']))
|
&& it.type_name in ['paragraph','text', 'empty']))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (mut self DocBase) process() !int {
|
pub fn (mut self DocBase) process() !int {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
module elements
|
module elements
|
||||||
|
|
||||||
import freeflowuniverse.herolib.core.texttools
|
import freeflowuniverse.herolib.core.texttools
|
||||||
// import freeflowuniverse.herolib.ui.console
|
import freeflowuniverse.herolib.ui.console
|
||||||
|
|
||||||
// DO NOT CHANGE THE WAY HOW THIS WORKS, THIS HAS BEEN DONE AS A STATEFUL PARSER BY DESIGN
|
// DO NOT CHANGE THE WAY HOW THIS WORKS, THIS HAS BEEN DONE AS A STATEFUL PARSER BY DESIGN
|
||||||
// THIS ALLOWS FOR EASY ADOPTIONS TO DIFFERENT REALITIES
|
// THIS ALLOWS FOR EASY ADOPTIONS TO DIFFERENT REALITIES
|
||||||
@@ -19,7 +19,7 @@ fn (mut paragraph Paragraph) paragraph_parse() ! {
|
|||||||
mut llast := paragraph.children.last()
|
mut llast := paragraph.children.last()
|
||||||
mut char_ := parser.char_current()
|
mut char_ := parser.char_current()
|
||||||
|
|
||||||
// console.print_debug("[[[${char_}]]]")
|
//console.print_debug("[[[${char_}]]]")
|
||||||
|
|
||||||
// char == '' means end of file
|
// char == '' means end of file
|
||||||
if mut llast is Def {
|
if mut llast is Def {
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ fn (mut site DocSite) process_md(mut path pathlib.Path, args MyImport)!{
|
|||||||
mypatho_img.copy(dest:dest,rsync:false)!
|
mypatho_img.copy(dest:dest,rsync:false)!
|
||||||
}
|
}
|
||||||
|
|
||||||
mut pathlist:=path.list()!
|
mut pathlist:=path.list(regex: [r'.*\.md$'],recursive:true)!
|
||||||
for mut mypatho2 in pathlist.paths{
|
for mut mypatho2 in pathlist.paths{
|
||||||
site.process_md(mut mypatho2,args)!
|
site.process_md(mut mypatho2,args)!
|
||||||
}
|
}
|
||||||
@@ -195,14 +195,16 @@ fn (mut site DocSite) process_md(mut path pathlib.Path, args MyImport)!{
|
|||||||
mydest:='${site.path_build.path}/docs/${args.dest}/${texttools.name_fix(path.name())}'
|
mydest:='${site.path_build.path}/docs/${args.dest}/${texttools.name_fix(path.name())}'
|
||||||
mut mydesto:=pathlib.get_file(path:mydest,create:true)!
|
mut mydesto:=pathlib.get_file(path:mydest,create:true)!
|
||||||
|
|
||||||
|
println(path.path)
|
||||||
mut mymd:=markdownparser.new(path:path.path)!
|
mut mymd:=markdownparser.new(path:path.path)!
|
||||||
mut myfm:=mymd.frontmatter2()!
|
println(2)
|
||||||
if ! args.visible{
|
// mut myfm:=mymd.frontmatter2()!
|
||||||
myfm.args["draft"]= 'true'
|
// if ! args.visible{
|
||||||
}
|
// myfm.args["draft"]= 'true'
|
||||||
|
// }
|
||||||
//println(myfm)
|
//println(myfm)
|
||||||
//println(mymd.markdown()!)
|
//println(mymd.markdown()!)
|
||||||
mydesto.write(mymd.markdown()!)!
|
// mydesto.write(mymd.markdown()!)!
|
||||||
//exit(0)
|
//exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user