react-native-nitro-markdown 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +276 -0
  2. package/android/CMakeLists.txt +40 -0
  3. package/android/build.gradle +92 -0
  4. package/android/gradle.properties +5 -0
  5. package/android/src/main/AndroidManifest.xml +4 -0
  6. package/android/src/main/cpp/cpp-adapter.cpp +7 -0
  7. package/android/src/main/java/com/nitromarkdown/NitroMarkdownPackage.kt +23 -0
  8. package/cpp/CMakeLists.txt +46 -0
  9. package/cpp/bindings/HybridMarkdownParser.cpp +112 -0
  10. package/cpp/bindings/HybridMarkdownParser.hpp +28 -0
  11. package/cpp/core/MD4CParser.cpp +442 -0
  12. package/cpp/core/MD4CParser.hpp +21 -0
  13. package/cpp/core/MarkdownTypes.hpp +119 -0
  14. package/cpp/md4c/md4c.c +6492 -0
  15. package/cpp/md4c/md4c.h +407 -0
  16. package/ios/NitroMarkdown-Bridging-Header.h +14 -0
  17. package/lib/commonjs/Markdown.nitro.js +6 -0
  18. package/lib/commonjs/Markdown.nitro.js.map +1 -0
  19. package/lib/commonjs/MarkdownJS.nitro.js +114 -0
  20. package/lib/commonjs/MarkdownJS.nitro.js.map +1 -0
  21. package/lib/commonjs/index.js +19 -0
  22. package/lib/commonjs/index.js.map +1 -0
  23. package/lib/module/Markdown.nitro.js +4 -0
  24. package/lib/module/Markdown.nitro.js.map +1 -0
  25. package/lib/module/MarkdownJS.nitro.js +107 -0
  26. package/lib/module/MarkdownJS.nitro.js.map +1 -0
  27. package/lib/module/index.js +13 -0
  28. package/lib/module/index.js.map +1 -0
  29. package/lib/typescript/Markdown.nitro.d.ts +13 -0
  30. package/lib/typescript/Markdown.nitro.d.ts.map +1 -0
  31. package/lib/typescript/MarkdownJS.nitro.d.ts +33 -0
  32. package/lib/typescript/MarkdownJS.nitro.d.ts.map +1 -0
  33. package/lib/typescript/index.d.ts +22 -0
  34. package/lib/typescript/index.d.ts.map +1 -0
  35. package/nitro.json +16 -0
  36. package/nitrogen/generated/.gitattributes +1 -0
  37. package/nitrogen/generated/android/NitroMarkdown+autolinking.cmake +81 -0
  38. package/nitrogen/generated/android/NitroMarkdown+autolinking.gradle +27 -0
  39. package/nitrogen/generated/android/NitroMarkdownOnLoad.cpp +44 -0
  40. package/nitrogen/generated/android/NitroMarkdownOnLoad.hpp +25 -0
  41. package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/nitromarkdown/NitroMarkdownOnLoad.kt +35 -0
  42. package/nitrogen/generated/ios/NitroMarkdown+autolinking.rb +60 -0
  43. package/nitrogen/generated/ios/NitroMarkdown-Swift-Cxx-Bridge.cpp +17 -0
  44. package/nitrogen/generated/ios/NitroMarkdown-Swift-Cxx-Bridge.hpp +27 -0
  45. package/nitrogen/generated/ios/NitroMarkdown-Swift-Cxx-Umbrella.hpp +38 -0
  46. package/nitrogen/generated/ios/NitroMarkdownAutolinking.mm +35 -0
  47. package/nitrogen/generated/ios/NitroMarkdownAutolinking.swift +12 -0
  48. package/nitrogen/generated/shared/c++/HybridMarkdownParserSpec.cpp +22 -0
  49. package/nitrogen/generated/shared/c++/HybridMarkdownParserSpec.hpp +65 -0
  50. package/nitrogen/generated/shared/c++/ParserOptions.hpp +79 -0
  51. package/package.json +101 -0
  52. package/react-native-nitro-markdown.podspec +42 -0
  53. package/src/Markdown.nitro.ts +12 -0
  54. package/src/MarkdownJS.nitro.ts +113 -0
  55. package/src/index.ts +65 -0
package/README.md ADDED
@@ -0,0 +1,276 @@
1
+ <p align="center">
2
+ <img src="./apps/example/assets/icon.png" alt="react-native-nitro-markdown logo" width="150" height="150" />
3
+ </p>
4
+
5
+ # react-native-nitro-markdown 🚀
6
+
7
+ > The fastest Markdown parser for React Native. Period.
8
+
9
+ [![npm version](https://img.shields.io/npm/v/react-native-nitro-markdown?style=flat-square)](https://www.npmjs.com/package/react-native-nitro-markdown)
10
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
11
+ [![Nitro Modules](https://img.shields.io/badge/Powered%20by-Nitro%20Modules-blueviolet?style=flat-square)](https://nitro.margelo.com)
12
+
13
+ **react-native-nitro-markdown** is a high-performance Markdown parser built on **[md4c](https://github.com/mity/md4c)** (C++) and **[Nitro Modules](https://nitro.margelo.com)**. It parses complex Markdown, GFM, and LaTeX Math into a structured AST **synchronously** via JSI, bypassing the React Native Bridge entirely.
14
+
15
+ ---
16
+
17
+ ## ⚡ Why Nitro? (Benchmarks)
18
+
19
+ We benchmarked this library against the most popular JavaScript parsers on a real mobile device (iPhone 15 Pro, Release Mode) using a heavy **237KB** Markdown document.
20
+
21
+ | Parser | Time (ms) | Speedup | Frame Drops (60fps) |
22
+ | :-------------------------- | :--------- | :---------------- | :-------------------- |
23
+ | **🚀 Nitro Markdown (C++)** | **~29 ms** | **1x (Baseline)** | **~1 frame** (Smooth) |
24
+ | 📋 CommonMark (JS) | ~82 ms | 2.8x slower | ~5 frames (Jank) |
25
+ | 🏗️ Markdown-It (JS) | ~118 ms | 4.0x slower | ~7 frames (Jank) |
26
+ | 💨 Marked (JS) | ~400 ms | 13.5x slower | ~24 frames (Freeze) |
27
+
28
+ > **Takeaway:** JavaScript parsers trigger Garbage Collection pauses. Nitro uses C++ to parse efficiently with zero-copy overhead, keeping your UI thread responsive.
29
+
30
+ ---
31
+
32
+ ## 📦 Installation
33
+
34
+ Choose your preferred package manager to install the package and its core dependency (`react-native-nitro-modules`).
35
+
36
+ ### **1. Install Dependencies**
37
+
38
+ **npm**
39
+
40
+ ```bash
41
+ npm install react-native-nitro-markdown react-native-nitro-modules
42
+ ```
43
+
44
+ **Yarn**
45
+
46
+ ```bash
47
+ yarn add react-native-nitro-markdown react-native-nitro-modules
48
+ ```
49
+
50
+ **Bun**
51
+
52
+ ```bash
53
+ bun add react-native-nitro-markdown react-native-nitro-modules
54
+ ```
55
+
56
+ **pnpm**
57
+
58
+ ```bash
59
+ pnpm add react-native-nitro-markdown react-native-nitro-modules
60
+ ```
61
+
62
+ ### **2. Install Native Pods (iOS)**
63
+
64
+ **Standard**
65
+
66
+ ```bash
67
+ cd ios && pod install
68
+ ```
69
+
70
+ ### **3. Expo Users**
71
+
72
+ If you are using Expo, you must run a **Prebuild** (Development Build) because this package contains native C++ code.
73
+
74
+ ```bash
75
+ npx expo install react-native-nitro-markdown react-native-nitro-modules
76
+ npx expo prebuild
77
+ ```
78
+
79
+ ---
80
+
81
+ ## 💻 Usage
82
+
83
+ ### Basic Parsing
84
+
85
+ The parsing is synchronous and instant. It returns a fully typed JSON AST.
86
+
87
+ ```typescript
88
+ import { parseMarkdown } from "react-native-nitro-markdown";
89
+
90
+ const markdown = `
91
+ # Hello World
92
+ This is **bold** text and a [link](https://github.com).
93
+ `;
94
+
95
+ const ast = parseMarkdown(markdown);
96
+ console.log(ast);
97
+ // Output: { type: "document", children: [...] }
98
+ ```
99
+
100
+ ### Advanced Options (GFM & Math)
101
+
102
+ Enable GitHub Flavored Markdown (Tables, TaskLists) or LaTeX Math support.
103
+
104
+ ```typescript
105
+ import { parseMarkdown } from "react-native-nitro-markdown";
106
+
107
+ const ast = parseMarkdown(markdown, {
108
+ gfm: true, // Tables, Strikethrough, Autolinks, TaskLists
109
+ math: true, // $E=mc^2$ and $$block$$
110
+ wiki: true, // [[WikiLinks]]
111
+ });
112
+ ```
113
+
114
+ ---
115
+
116
+ ## 🎨 Rendering
117
+
118
+ This library is a **Parser Only**. It gives you the raw data (AST) so you can render it with native components (`<Text>`, `<View>`) for maximum performance.
119
+
120
+ Here is a simple recursive renderer example:
121
+
122
+ ```tsx
123
+ import React from "react";
124
+ import { Text, View, StyleSheet } from "react-native";
125
+ import { parseMarkdown, type MarkdownNode } from "react-native-nitro-markdown";
126
+
127
+ export function MarkdownView({ content }: { content: string }) {
128
+ const ast = parseMarkdown(content, { gfm: true });
129
+
130
+ return (
131
+ <View style={styles.container}>
132
+ <Renderer node={ast} />
133
+ </View>
134
+ );
135
+ }
136
+
137
+ function Renderer({ node }: { node: MarkdownNode }) {
138
+ switch (node.type) {
139
+ case "document":
140
+ return (
141
+ <View>
142
+ {node.children?.map((child, i) => (
143
+ <Renderer key={i} node={child} />
144
+ ))}
145
+ </View>
146
+ );
147
+
148
+ case "heading":
149
+ return (
150
+ <Text style={styles.h1}>
151
+ {node.children?.map((c, i) => (
152
+ <Renderer key={i} node={c} />
153
+ ))}
154
+ </Text>
155
+ );
156
+
157
+ case "paragraph":
158
+ return (
159
+ <Text style={styles.p}>
160
+ {node.children?.map((child, i) => (
161
+ <Renderer key={i} node={child} />
162
+ ))}
163
+ </Text>
164
+ );
165
+
166
+ case "text":
167
+ return <Text>{node.content}</Text>;
168
+
169
+ case "bold":
170
+ return (
171
+ <Text style={styles.bold}>
172
+ {node.children?.map((c, i) => (
173
+ <Renderer key={i} node={c} />
174
+ ))}
175
+ </Text>
176
+ );
177
+
178
+ // Handle 'table', 'code_block', 'math_inline' etc...
179
+ default:
180
+ return null;
181
+ }
182
+ }
183
+
184
+ const styles = StyleSheet.create({
185
+ container: { padding: 16 },
186
+ h1: { fontSize: 24, fontWeight: "bold", marginVertical: 8 },
187
+ p: { fontSize: 16, lineHeight: 24, marginBottom: 8 },
188
+ bold: { fontWeight: "700" },
189
+ });
190
+ ```
191
+
192
+ ---
193
+
194
+ ## 📐 AST Structure
195
+
196
+ The parser returns a `MarkdownNode` tree. The Types are fully exported for TypeScript support.
197
+
198
+ ```typescript
199
+ export interface MarkdownNode {
200
+ type: NodeType;
201
+ // Content for Text/Code/Math
202
+ content?: string;
203
+ // Hierarchy
204
+ children?: MarkdownNode[];
205
+ // Metadata
206
+ level?: number; // Headings (1-6)
207
+ href?: string; // Links
208
+ checked?: boolean; // Task Lists
209
+ language?: string; // Code Blocks
210
+ // Table Props
211
+ align?: "left" | "center" | "right";
212
+ isHeader?: boolean;
213
+ }
214
+
215
+ export type NodeType =
216
+ | "document"
217
+ | "paragraph"
218
+ | "text"
219
+ | "heading"
220
+ | "bold"
221
+ | "italic"
222
+ | "strikethrough"
223
+ | "link"
224
+ | "image"
225
+ | "code_inline"
226
+ | "code_block"
227
+ | "blockquote"
228
+ | "list"
229
+ | "list_item"
230
+ | "task_list_item"
231
+ | "table"
232
+ | "table_row"
233
+ | "table_cell"
234
+ | "math_inline"
235
+ | "math_block";
236
+ ```
237
+
238
+ ---
239
+
240
+ ## 🧮 LaTeX Math Support
241
+
242
+ We parse math delimiters (`$` and `$$`) natively using the `MD_FLAG_LATEXMATHSPANS` flag in `md4c`.
243
+
244
+ To render the math, you should use a library like `react-native-math-view` inside your renderer:
245
+
246
+ ```tsx
247
+ // Inside your switch(node.type)
248
+ case 'math_inline':
249
+ return <MathView math={node.content} style={styles.math} />;
250
+ case 'math_block':
251
+ return <MathView math={node.content} style={styles.mathBlock} />;
252
+ ```
253
+
254
+ ## 📊 Package Size
255
+
256
+ | Metric | Size |
257
+ | :----- | :--- |
258
+ | **Packed (tarball)** | ~75 kB |
259
+ | **Unpacked** | ~325 kB |
260
+ | **Total files** | 55 |
261
+
262
+ > The package includes the [md4c](https://github.com/mity/md4c) C source code (~244 kB) which is compiled natively on iOS and Android. This is a one-time cost that enables the high-performance parsing.
263
+
264
+ ---
265
+
266
+ ## 🤝 Contributing
267
+
268
+ See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
269
+
270
+ ## 📄 License
271
+
272
+ MIT
273
+
274
+ ---
275
+
276
+ Built with ❤️ using [Nitro Modules](https://nitro.margelo.com) and [md4c](https://github.com/mity/md4c).
@@ -0,0 +1,40 @@
1
+ cmake_minimum_required(VERSION 3.22.1)
2
+ project(NitroMarkdown)
3
+
4
+ set(CMAKE_CXX_STANDARD 20)
5
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
6
+ set(CMAKE_C_STANDARD 11)
7
+ set(CMAKE_C_STANDARD_REQUIRED ON)
8
+
9
+ # Define the path to our C++ sources
10
+ set(CPP_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../cpp")
11
+
12
+ # Collect source files
13
+ file(GLOB MD4C_SOURCES "${CPP_ROOT}/md4c/*.c")
14
+ file(GLOB CORE_SOURCES "${CPP_ROOT}/core/*.cpp")
15
+ file(GLOB BINDING_SOURCES "${CPP_ROOT}/bindings/*.cpp")
16
+
17
+ # Create the shared library with our sources
18
+ add_library(${PROJECT_NAME} SHARED
19
+ ${MD4C_SOURCES}
20
+ ${CORE_SOURCES}
21
+ ${BINDING_SOURCES}
22
+ # JNI adapter
23
+ src/main/cpp/cpp-adapter.cpp
24
+ )
25
+
26
+ # Include directories
27
+ target_include_directories(${PROJECT_NAME} PRIVATE
28
+ "${CPP_ROOT}/md4c"
29
+ "${CPP_ROOT}/core"
30
+ "${CPP_ROOT}/bindings"
31
+ )
32
+
33
+ # Preprocessor definitions
34
+ target_compile_definitions(${PROJECT_NAME} PRIVATE
35
+ MD4C_USE_UTF8=1
36
+ )
37
+
38
+ # Include Nitro autolinking (adds nitrogen sources, definitions, and links)
39
+ include(${CMAKE_CURRENT_SOURCE_DIR}/../nitrogen/generated/android/NitroMarkdown+autolinking.cmake)
40
+
@@ -0,0 +1,92 @@
1
+ buildscript {
2
+ repositories {
3
+ google()
4
+ mavenCentral()
5
+ }
6
+
7
+ dependencies {
8
+ classpath "com.android.tools.build:gradle:8.10.1"
9
+ }
10
+ }
11
+
12
+ def reactNativeArchitectures() {
13
+ def value = rootProject.getProperties().get("reactNativeArchitectures")
14
+ return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
15
+ }
16
+
17
+ def isNewArchitectureEnabled() {
18
+ return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
19
+ }
20
+
21
+ apply plugin: "com.android.library"
22
+ apply plugin: 'org.jetbrains.kotlin.android'
23
+ apply from: '../nitrogen/generated/android/NitroMarkdown+autolinking.gradle'
24
+
25
+ if (isNewArchitectureEnabled()) {
26
+ apply plugin: "com.facebook.react"
27
+ }
28
+
29
+ def getExtOrDefault(name) {
30
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["NitroMarkdown_" + name]
31
+ }
32
+
33
+ def getExtOrIntegerDefault(name) {
34
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["NitroMarkdown_" + name]).toInteger()
35
+ }
36
+
37
+ android {
38
+ namespace "com.nitromarkdown"
39
+
40
+ ndkVersion getExtOrDefault("ndkVersion")
41
+ compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
42
+
43
+ defaultConfig {
44
+ minSdkVersion getExtOrIntegerDefault("minSdkVersion")
45
+ targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
46
+ buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
47
+
48
+ externalNativeBuild {
49
+ cmake {
50
+ cppFlags "-frtti -fexceptions -Wall -Wextra -fstack-protector-all"
51
+ arguments "-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
52
+ abiFilters (*reactNativeArchitectures())
53
+ }
54
+ }
55
+ }
56
+
57
+ externalNativeBuild {
58
+ cmake {
59
+ path "CMakeLists.txt"
60
+ }
61
+ }
62
+
63
+ buildFeatures {
64
+ buildConfig true
65
+ prefab true
66
+ }
67
+
68
+ compileOptions {
69
+ sourceCompatibility JavaVersion.VERSION_1_8
70
+ targetCompatibility JavaVersion.VERSION_1_8
71
+ }
72
+
73
+ sourceSets {
74
+ main {
75
+ java.srcDirs += ["src/main/java"]
76
+ if (isNewArchitectureEnabled()) {
77
+ java.srcDirs += ["${project.buildDir}/generated/source/codegen/java"]
78
+ }
79
+ }
80
+ }
81
+ }
82
+
83
+ repositories {
84
+ mavenCentral()
85
+ google()
86
+ }
87
+
88
+ dependencies {
89
+ //noinspection GradleDynamicVersion
90
+ implementation "com.facebook.react:react-native:+"
91
+ implementation project(":react-native-nitro-modules")
92
+ }
@@ -0,0 +1,5 @@
1
+ NitroMarkdown_compileSdkVersion=34
2
+ NitroMarkdown_minSdkVersion=24
3
+ NitroMarkdown_targetSdkVersion=34
4
+ NitroMarkdown_ndkVersion=27.1.12297006
5
+
@@ -0,0 +1,4 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
+ package="com.nitromarkdown">
3
+ </manifest>
4
+
@@ -0,0 +1,7 @@
1
+ #include <jni.h>
2
+ #include "NitroMarkdownOnLoad.hpp"
3
+
4
+ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
5
+ return margelo::nitro::NitroMarkdown::initialize(vm);
6
+ }
7
+
@@ -0,0 +1,23 @@
1
+ package com.nitromarkdown
2
+
3
+ import com.facebook.react.TurboReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.module.model.ReactModuleInfoProvider
7
+
8
+ class NitroMarkdownPackage : TurboReactPackage() {
9
+ companion object {
10
+ init {
11
+ System.loadLibrary("NitroMarkdown")
12
+ }
13
+ }
14
+
15
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
16
+ return null
17
+ }
18
+
19
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
20
+ return ReactModuleInfoProvider { emptyMap() }
21
+ }
22
+ }
23
+
@@ -0,0 +1,46 @@
1
+ cmake_minimum_required(VERSION 3.22.1)
2
+ project(MD4CParserTest)
3
+
4
+ set(CMAKE_CXX_STANDARD 20)
5
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
6
+ set(CMAKE_C_STANDARD 11)
7
+ set(CMAKE_C_STANDARD_REQUIRED ON)
8
+
9
+ # Define the path to our C++ sources
10
+ set(CPP_ROOT "${CMAKE_CURRENT_SOURCE_DIR}")
11
+
12
+ # Collect source files
13
+ file(GLOB MD4C_SOURCES "${CPP_ROOT}/md4c/*.c")
14
+ file(GLOB CORE_SOURCES "${CPP_ROOT}/core/*.cpp")
15
+ # Exclude the test file from the library sources
16
+ list(FILTER CORE_SOURCES EXCLUDE REGEX ".*Test\.cpp$")
17
+
18
+ # Create the core library
19
+ add_library(MD4CCore STATIC
20
+ ${MD4C_SOURCES}
21
+ ${CORE_SOURCES}
22
+ )
23
+
24
+ # Include directories for the library
25
+ target_include_directories(MD4CCore PUBLIC
26
+ "${CPP_ROOT}/md4c"
27
+ "${CPP_ROOT}/core"
28
+ )
29
+
30
+ # Preprocessor definitions
31
+ target_compile_definitions(MD4CCore PRIVATE
32
+ MD4C_USE_UTF8=1
33
+ )
34
+
35
+ # Create the test executable
36
+ add_executable(MD4CParserTest
37
+ core/MD4CParserTest.cpp
38
+ )
39
+
40
+ # Link the test executable to the core library
41
+ target_link_libraries(MD4CParserTest PRIVATE MD4CCore)
42
+
43
+ # Include directories for the test
44
+ target_include_directories(MD4CParserTest PRIVATE
45
+ "${CPP_ROOT}/core"
46
+ )
@@ -0,0 +1,112 @@
1
+ #include "HybridMarkdownParser.hpp"
2
+ #include <sstream>
3
+ #include <iomanip>
4
+
5
+ namespace margelo::nitro::NitroMarkdown {
6
+
7
+ std::string HybridMarkdownParser::parse(const std::string& text) {
8
+ InternalParserOptions opts;
9
+ opts.gfm = true;
10
+ opts.math = true;
11
+
12
+ auto ast = parser_->parse(text, opts);
13
+ return nodeToJson(ast);
14
+ }
15
+
16
+ std::string HybridMarkdownParser::parseWithOptions(const std::string& text, const ParserOptions& options) {
17
+ InternalParserOptions internalOpts;
18
+ internalOpts.gfm = options.gfm.value_or(true);
19
+ internalOpts.math = options.math.value_or(true);
20
+
21
+ auto ast = parser_->parse(text, internalOpts);
22
+ return nodeToJson(ast);
23
+ }
24
+
25
+ static std::string escapeJson(const std::string& s) {
26
+ std::ostringstream o;
27
+ for (char c : s) {
28
+ switch (c) {
29
+ case '"': o << "\\\""; break;
30
+ case '\\': o << "\\\\"; break;
31
+ case '\b': o << "\\b"; break;
32
+ case '\f': o << "\\f"; break;
33
+ case '\n': o << "\\n"; break;
34
+ case '\r': o << "\\r"; break;
35
+ case '\t': o << "\\t"; break;
36
+ default:
37
+ if ('\x00' <= c && c <= '\x1f') {
38
+ o << "\\u" << std::hex << std::setw(4) << std::setfill('0') << (int)(unsigned char)c;
39
+ } else {
40
+ o << c;
41
+ }
42
+ }
43
+ }
44
+ return o.str();
45
+ }
46
+
47
+ std::string HybridMarkdownParser::nodeToJson(const std::shared_ptr<InternalMarkdownNode>& node) {
48
+ std::ostringstream json;
49
+ json << "{";
50
+ json << "\"type\":\"" << ::NitroMarkdown::nodeTypeToString(node->type) << "\"";
51
+
52
+ if (node->content.has_value()) {
53
+ json << ",\"content\":\"" << escapeJson(node->content.value()) << "\"";
54
+ }
55
+
56
+ if (node->level.has_value()) {
57
+ json << ",\"level\":" << node->level.value();
58
+ }
59
+
60
+ if (node->href.has_value()) {
61
+ json << ",\"href\":\"" << escapeJson(node->href.value()) << "\"";
62
+ }
63
+
64
+ if (node->title.has_value()) {
65
+ json << ",\"title\":\"" << escapeJson(node->title.value()) << "\"";
66
+ }
67
+
68
+ if (node->alt.has_value()) {
69
+ json << ",\"alt\":\"" << escapeJson(node->alt.value()) << "\"";
70
+ }
71
+
72
+ if (node->language.has_value()) {
73
+ json << ",\"language\":\"" << escapeJson(node->language.value()) << "\"";
74
+ }
75
+
76
+ if (node->ordered.has_value()) {
77
+ json << ",\"ordered\":" << (node->ordered.value() ? "true" : "false");
78
+ }
79
+
80
+ if (node->start.has_value()) {
81
+ json << ",\"start\":" << node->start.value();
82
+ }
83
+
84
+ if (node->checked.has_value()) {
85
+ json << ",\"checked\":" << (node->checked.value() ? "true" : "false");
86
+ }
87
+
88
+ if (node->isHeader.has_value()) {
89
+ json << ",\"isHeader\":" << (node->isHeader.value() ? "true" : "false");
90
+ }
91
+
92
+ if (node->align.has_value()) {
93
+ std::string alignStr = ::NitroMarkdown::textAlignToString(node->align.value());
94
+ if (!alignStr.empty()) {
95
+ json << ",\"align\":\"" << alignStr << "\"";
96
+ }
97
+ }
98
+
99
+ if (!node->children.empty()) {
100
+ json << ",\"children\":[";
101
+ for (size_t i = 0; i < node->children.size(); ++i) {
102
+ if (i > 0) json << ",";
103
+ json << nodeToJson(node->children[i]);
104
+ }
105
+ json << "]";
106
+ }
107
+
108
+ json << "}";
109
+ return json.str();
110
+ }
111
+
112
+ } // namespace margelo::nitro::NitroMarkdown
@@ -0,0 +1,28 @@
1
+ #pragma once
2
+
3
+ #include "HybridMarkdownParserSpec.hpp"
4
+ #include "../core/MD4CParser.hpp"
5
+ #include <memory>
6
+
7
+ namespace margelo::nitro::NitroMarkdown {
8
+
9
+ using InternalMarkdownNode = ::NitroMarkdown::MarkdownNode;
10
+ using InternalParserOptions = ::NitroMarkdown::ParserOptions;
11
+
12
+ class HybridMarkdownParser : public HybridMarkdownParserSpec {
13
+ public:
14
+ HybridMarkdownParser() : HybridObject(TAG), HybridMarkdownParserSpec() {
15
+ parser_ = std::make_unique<::NitroMarkdown::MD4CParser>();
16
+ }
17
+
18
+ ~HybridMarkdownParser() override = default;
19
+
20
+ std::string parse(const std::string& text) override;
21
+ std::string parseWithOptions(const std::string& text, const ParserOptions& options) override;
22
+
23
+ private:
24
+ std::unique_ptr<::NitroMarkdown::MD4CParser> parser_;
25
+ std::string nodeToJson(const std::shared_ptr<InternalMarkdownNode>& node);
26
+ };
27
+
28
+ } // namespace margelo::nitro::NitroMarkdown