...
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.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() {
|
||||
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 {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module elements
|
||||
|
||||
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
|
||||
// THIS ALLOWS FOR EASY ADOPTIONS TO DIFFERENT REALITIES
|
||||
@@ -19,7 +19,7 @@ fn (mut paragraph Paragraph) paragraph_parse() ! {
|
||||
mut llast := paragraph.children.last()
|
||||
mut char_ := parser.char_current()
|
||||
|
||||
// console.print_debug("[[[${char_}]]]")
|
||||
//console.print_debug("[[[${char_}]]]")
|
||||
|
||||
// char == '' means end of file
|
||||
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)!
|
||||
}
|
||||
|
||||
mut pathlist:=path.list()!
|
||||
mut pathlist:=path.list(regex: [r'.*\.md$'],recursive:true)!
|
||||
for mut mypatho2 in pathlist.paths{
|
||||
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())}'
|
||||
mut mydesto:=pathlib.get_file(path:mydest,create:true)!
|
||||
|
||||
println(path.path)
|
||||
mut mymd:=markdownparser.new(path:path.path)!
|
||||
mut myfm:=mymd.frontmatter2()!
|
||||
if ! args.visible{
|
||||
myfm.args["draft"]= 'true'
|
||||
}
|
||||
println(2)
|
||||
// mut myfm:=mymd.frontmatter2()!
|
||||
// if ! args.visible{
|
||||
// myfm.args["draft"]= 'true'
|
||||
// }
|
||||
//println(myfm)
|
||||
//println(mymd.markdown()!)
|
||||
mydesto.write(mymd.markdown()!)!
|
||||
// mydesto.write(mymd.markdown()!)!
|
||||
//exit(0)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user