react-native-fast-html-parser 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.
- package/FastHtmlParser.podspec +36 -0
- package/LICENSE +20 -0
- package/README.md +435 -0
- package/android/CMakeLists.txt +31 -0
- package/android/build.gradle +121 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/cpp-adapter.cpp +11 -0
- package/android/src/main/java/com/margelo/nitro/fasthtmlparser/FastHtmlParserPackage.kt +22 -0
- package/android/src/main/jniLibs/arm64-v8a/libhtml_2_json.so +0 -0
- package/android/src/main/jniLibs/armeabi-v7a/libhtml_2_json.so +0 -0
- package/android/src/main/jniLibs/x86/libhtml_2_json.so +0 -0
- package/android/src/main/jniLibs/x86_64/libhtml_2_json.so +0 -0
- package/cpp/HybridFastHtmlParser.cpp +178 -0
- package/cpp/HybridFastHtmlParser.hpp +224 -0
- package/ios/libs/libhtml_2_json.xcframework/Info.plist +44 -0
- package/ios/libs/libhtml_2_json.xcframework/ios-arm64/libhtml_2_json.a +0 -0
- package/ios/libs/libhtml_2_json.xcframework/ios-arm64_x86_64-simulator/libhtml_2_json.a +0 -0
- package/lib/module/FastHtmlParser.nitro.js +4 -0
- package/lib/module/FastHtmlParser.nitro.js.map +1 -0
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/parser.js +6 -0
- package/lib/module/parser.js.map +1 -0
- package/lib/module/parser.native.js +8 -0
- package/lib/module/parser.native.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/FastHtmlParser.nitro.d.ts +81 -0
- package/lib/typescript/src/FastHtmlParser.nitro.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +3 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/parser.d.ts +3 -0
- package/lib/typescript/src/parser.d.ts.map +1 -0
- package/lib/typescript/src/parser.native.d.ts +3 -0
- package/lib/typescript/src/parser.native.d.ts.map +1 -0
- package/nitro.json +23 -0
- package/nitrogen/generated/android/fasthtmlparser+autolinking.cmake +88 -0
- package/nitrogen/generated/android/fasthtmlparser+autolinking.gradle +27 -0
- package/nitrogen/generated/android/fasthtmlparserOnLoad.cpp +49 -0
- package/nitrogen/generated/android/fasthtmlparserOnLoad.hpp +34 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/fasthtmlparser/fasthtmlparserOnLoad.kt +35 -0
- package/nitrogen/generated/ios/FastHtmlParser+autolinking.rb +62 -0
- package/nitrogen/generated/ios/FastHtmlParser-Swift-Cxx-Bridge.cpp +17 -0
- package/nitrogen/generated/ios/FastHtmlParser-Swift-Cxx-Bridge.hpp +27 -0
- package/nitrogen/generated/ios/FastHtmlParser-Swift-Cxx-Umbrella.hpp +38 -0
- package/nitrogen/generated/ios/FastHtmlParserAutolinking.mm +35 -0
- package/nitrogen/generated/ios/FastHtmlParserAutolinking.swift +16 -0
- package/nitrogen/generated/shared/c++/HybridContentBlockSpec.cpp +39 -0
- package/nitrogen/generated/shared/c++/HybridContentBlockSpec.hpp +96 -0
- package/nitrogen/generated/shared/c++/HybridDefinitionItemSpec.cpp +24 -0
- package/nitrogen/generated/shared/c++/HybridDefinitionItemSpec.hpp +68 -0
- package/nitrogen/generated/shared/c++/HybridFastHtmlParserSpec.cpp +21 -0
- package/nitrogen/generated/shared/c++/HybridFastHtmlParserSpec.hpp +67 -0
- package/nitrogen/generated/shared/c++/HybridInlineNodeSpec.cpp +25 -0
- package/nitrogen/generated/shared/c++/HybridInlineNodeSpec.hpp +70 -0
- package/nitrogen/generated/shared/c++/HybridListItemSpec.cpp +24 -0
- package/nitrogen/generated/shared/c++/HybridListItemSpec.hpp +71 -0
- package/nitrogen/generated/shared/c++/HybridParsedArticleSpec.cpp +22 -0
- package/nitrogen/generated/shared/c++/HybridParsedArticleSpec.hpp +66 -0
- package/nitrogen/generated/shared/c++/HybridTableCellSpec.cpp +22 -0
- package/nitrogen/generated/shared/c++/HybridTableCellSpec.hpp +66 -0
- package/nitrogen/generated/shared/c++/HybridTableRowSpec.cpp +22 -0
- package/nitrogen/generated/shared/c++/HybridTableRowSpec.hpp +66 -0
- package/package.json +153 -0
- package/scripts/postinstall.js +234 -0
- package/src/FastHtmlParser.nitro.ts +86 -0
- package/src/index.tsx +12 -0
- package/src/parser.native.tsx +9 -0
- package/src/parser.tsx +7 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "FastHtmlParser"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = package["homepage"]
|
|
10
|
+
s.license = package["license"]
|
|
11
|
+
s.authors = package["author"]
|
|
12
|
+
|
|
13
|
+
s.platforms = { :ios => min_ios_version_supported }
|
|
14
|
+
s.source = { :git => "https://github.com/abhishekce17/react-native-fast-html-parser.git", :tag => "#{s.version}" }
|
|
15
|
+
|
|
16
|
+
s.source_files = [
|
|
17
|
+
"ios/**/*.{m,mm}",
|
|
18
|
+
"cpp/**/*.{hpp,cpp}",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
s.vendored_frameworks = 'ios/libs/libhtml_2_json.xcframework'
|
|
22
|
+
|
|
23
|
+
s.dependency 'React-jsi'
|
|
24
|
+
s.dependency 'React-callinvoker'
|
|
25
|
+
|
|
26
|
+
load 'nitrogen/generated/ios/FastHtmlParser+autolinking.rb'
|
|
27
|
+
add_nitrogen_files(s)
|
|
28
|
+
|
|
29
|
+
install_modules_dependencies(s)
|
|
30
|
+
|
|
31
|
+
# Force C++17 standard to prevent Fabric compiler standard mismatches
|
|
32
|
+
s.attributes_hash["pod_target_xcconfig"] ||= {}
|
|
33
|
+
s.attributes_hash["pod_target_xcconfig"]["CLANG_CXX_LANGUAGE_STANDARD"] = "c++17"
|
|
34
|
+
s.attributes_hash["user_target_xcconfig"] ||= {}
|
|
35
|
+
s.attributes_hash["user_target_xcconfig"]["CLANG_CXX_LANGUAGE_STANDARD"] = "c++17"
|
|
36
|
+
end
|
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 abhishekce17
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
# react-native-fast-html-parser
|
|
2
|
+
|
|
3
|
+
A high-performance, zero-copy HTML-to-structured-JSON parser library for React Native. Powered by a lightning-fast Rust core engine (`html_2_json`) and integrated via direct C++ JSI using Margelo Nitro Modules.
|
|
4
|
+
|
|
5
|
+
It achieves pure zero-copy shared-memory access on-device. This avoids standard string/JSON serialization overhead and Hermes garbage collection pauses during render loops.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Blazing Fast**: Directly parses HTML in CPU memory space using Rust's ecosystem.
|
|
12
|
+
- **Zero-Copy JSI Access**: Access elements recursively on-demand. Properties are retrieved directly from the native C++ pointer space without copying whole objects.
|
|
13
|
+
- **No JSON Intermediates**: Avoids standard CPU intensive parsing overhead of `JSON.parse` or bridge string serialization.
|
|
14
|
+
- **Type-safe AST**: Comprehensive typescript interfaces representing paragraph, headings, lists, tables, code blocks, figures, and rich media.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
Run this command inside your React Native application root directory:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install react-native-fast-html-parser
|
|
24
|
+
```
|
|
25
|
+
or if you use Yarn:
|
|
26
|
+
```bash
|
|
27
|
+
yarn add react-native-fast-html-parser
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### iOS Setup
|
|
31
|
+
Install pod dependencies:
|
|
32
|
+
```bash
|
|
33
|
+
cd ios && pod install
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Build & Run
|
|
37
|
+
Since this package contains native C++ and precompiled Rust libraries, you must rebuild the application:
|
|
38
|
+
```bash
|
|
39
|
+
npx react-native run-ios
|
|
40
|
+
# or
|
|
41
|
+
npx react-native run-android
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Basic Usage
|
|
47
|
+
|
|
48
|
+
Import the `parseHTML` function to parse raw HTML and safely read properties by checking the `block.type` first:
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { parseHTML } from 'react-native-fast-html-parser';
|
|
52
|
+
|
|
53
|
+
const html = "<h1>Title</h1><p>This is a <b>bold</b> word.</p>";
|
|
54
|
+
const article = parseHTML(html);
|
|
55
|
+
|
|
56
|
+
if (article) {
|
|
57
|
+
console.log("Total Blocks parsed:", article.length); // 2
|
|
58
|
+
|
|
59
|
+
// Fetch block by index (calls C++ FFI directly)
|
|
60
|
+
const block = article.getBlock(0);
|
|
61
|
+
|
|
62
|
+
if (block) {
|
|
63
|
+
console.log("Block Type:", block.type); // "Heading"
|
|
64
|
+
|
|
65
|
+
// 1. Safe Property Querying via Block Type
|
|
66
|
+
if (block.type === 'Heading') {
|
|
67
|
+
console.log("Heading Level:", block.level); // 1 (Safe to read)
|
|
68
|
+
} else if (block.type === 'CodeBlock') {
|
|
69
|
+
console.log("Code language:", block.language); // Safe to read
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 2. Safe Default Fallbacks
|
|
73
|
+
// If you read a property that does not belong to the block type,
|
|
74
|
+
// it returns a safe default fallback instead of returning undefined or crashing:
|
|
75
|
+
console.log("Alt Text (non-applicable):", block.alt); // "" (empty string)
|
|
76
|
+
|
|
77
|
+
// 3. Read inline child nodes
|
|
78
|
+
const inline = block.getChild(0);
|
|
79
|
+
if (inline) {
|
|
80
|
+
console.log("Inline Type:", inline.type); // "Text"
|
|
81
|
+
console.log("Text Content:", inline.text); // "Title"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Block Type Mapping & Properties
|
|
90
|
+
|
|
91
|
+
Here is the quick reference of which properties/methods are valid for each `block.type`:
|
|
92
|
+
|
|
93
|
+
| `block.type` | Applicable Fields & Methods | Description |
|
|
94
|
+
| :--- | :--- | :--- |
|
|
95
|
+
| **`Paragraph`** | `childCount`, `getChild(index)` | Text block containing inline formatting nodes |
|
|
96
|
+
| **`Heading`** | `level` (1-6), `childCount`, `getChild(index)` | Section headers containing formatted inline text |
|
|
97
|
+
| **`Image`** / **`Figure`** | `url`, `alt`, `caption` | Images and figure elements with alt text and captions |
|
|
98
|
+
| **`CodeBlock`** | `code`, `language` | Syntax-highlighted code blocks |
|
|
99
|
+
| **`List`** | `ordered` (boolean), `itemCount`, `getItem(index)` | Ordered (`ol`) or Unordered (`ul`) list elements |
|
|
100
|
+
| **`Table`** | `rowCount`, `getRow(index)` | Tables containing rows and cells |
|
|
101
|
+
| **`Quote`** | `childCount`, `getQuoteChild(index)` | Blockquotes containing nested block elements |
|
|
102
|
+
| **`DefinitionList`** | `itemCount`, `getDefItem(index)` | List of term/definition pairs (`dl`/`dt`/`dd`) |
|
|
103
|
+
| **`Video`** | `src`, `poster`, `caption` | Native video components with source, cover, and captions |
|
|
104
|
+
| **`Audio`** | `src`, `caption` | Native audio players with captions |
|
|
105
|
+
| **`Embed`** | `src`, `title`, `caption` | Generic iframe or external embeds |
|
|
106
|
+
| **`Separator`** | *None* | A horizontal rule element (`<hr>`) |
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Handling Each Block Type (Code Examples)
|
|
111
|
+
|
|
112
|
+
Here is how you handle and query properties for each specific block type returned by `article.getBlock(index)`:
|
|
113
|
+
|
|
114
|
+
### 1. Paragraph & Heading
|
|
115
|
+
Paragraphs and Headings contain child inline formatting nodes:
|
|
116
|
+
```typescript
|
|
117
|
+
if (block.type === 'Paragraph') {
|
|
118
|
+
for (let i = 0; i < block.childCount; i++) {
|
|
119
|
+
const inline = block.getChild(i);
|
|
120
|
+
if (inline) {
|
|
121
|
+
console.log(`Text: ${inline.text}, Formatting: ${inline.type}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (block.type === 'Heading') {
|
|
127
|
+
console.log("Heading Level:", block.level); // 1-6
|
|
128
|
+
const firstInlineChild = block.getChild(0);
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 2. Image, Figure & Media
|
|
133
|
+
```typescript
|
|
134
|
+
if (block.type === 'Image' || block.type === 'Figure') {
|
|
135
|
+
console.log("URL:", block.url);
|
|
136
|
+
console.log("Alt Text:", block.alt);
|
|
137
|
+
console.log("Caption:", block.caption);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (block.type === 'Video') {
|
|
141
|
+
console.log("Video source:", block.src);
|
|
142
|
+
console.log("Video cover poster:", block.poster);
|
|
143
|
+
console.log("Caption:", block.caption);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (block.type === 'Audio') {
|
|
147
|
+
console.log("Audio source:", block.src);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (block.type === 'Embed') {
|
|
151
|
+
console.log("Iframe source url:", block.src);
|
|
152
|
+
console.log("Iframe title:", block.title);
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### 3. CodeBlock
|
|
157
|
+
```typescript
|
|
158
|
+
if (block.type === 'CodeBlock') {
|
|
159
|
+
console.log("Raw Code:", block.code);
|
|
160
|
+
console.log("Language:", block.language); // e.g. "typescript", "rust"
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### 4. List (Ordered / Unordered)
|
|
165
|
+
```typescript
|
|
166
|
+
if (block.type === 'List') {
|
|
167
|
+
console.log("Is ordered list?", block.ordered);
|
|
168
|
+
|
|
169
|
+
for (let i = 0; i < block.itemCount; i++) {
|
|
170
|
+
const item = block.getItem(i);
|
|
171
|
+
if (item) {
|
|
172
|
+
// 1. Read inline children of list item
|
|
173
|
+
for (let j = 0; j < item.childCount; j++) {
|
|
174
|
+
const inline = item.getChild(j);
|
|
175
|
+
console.log(inline?.text);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// 2. Read nested lists (for multi-level nested lists)
|
|
179
|
+
for (let k = 0; k < item.nestedCount; k++) {
|
|
180
|
+
const nestedBlock = item.getNested(k);
|
|
181
|
+
console.log("Nested block type:", nestedBlock?.type); // "List"
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### 5. Table
|
|
189
|
+
```typescript
|
|
190
|
+
if (block.type === 'Table') {
|
|
191
|
+
for (let r = 0; r < block.rowCount; r++) {
|
|
192
|
+
const row = block.getRow(r);
|
|
193
|
+
if (row) {
|
|
194
|
+
for (let c = 0; c < row.cellCount; c++) {
|
|
195
|
+
const cell = row.getCell(c);
|
|
196
|
+
if (cell) {
|
|
197
|
+
// Read cell contents
|
|
198
|
+
const inlineChild = cell.getChild(0);
|
|
199
|
+
console.log(`Cell [${r}, ${c}]:`, inlineChild?.text);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### 6. Quote (Blockquote)
|
|
208
|
+
```typescript
|
|
209
|
+
if (block.type === 'Quote') {
|
|
210
|
+
// Quote blocks contain sub-blocks (like nested Paragraphs)
|
|
211
|
+
for (let i = 0; i < block.childCount; i++) {
|
|
212
|
+
const quoteChildBlock = block.getQuoteChild(i);
|
|
213
|
+
if (quoteChildBlock) {
|
|
214
|
+
console.log("Nested quote block type:", quoteChildBlock.type); // e.g., "Paragraph"
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### 7. DefinitionList
|
|
221
|
+
```typescript
|
|
222
|
+
if (block.type === 'DefinitionList') {
|
|
223
|
+
for (let i = 0; i < block.itemCount; i++) {
|
|
224
|
+
const defItem = block.getDefItem(i);
|
|
225
|
+
if (defItem) {
|
|
226
|
+
// Read term inlines (<dt>)
|
|
227
|
+
const termInline = defItem.getTerm(0);
|
|
228
|
+
|
|
229
|
+
// Read definition inlines (<dd>)
|
|
230
|
+
const defInline = defItem.getDef(0);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### 8. Separator
|
|
237
|
+
```typescript
|
|
238
|
+
if (block.type === 'Separator') {
|
|
239
|
+
console.log("Renders horizontal rule line (<hr>)");
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Rendering Components (Traversing the AST)
|
|
246
|
+
|
|
247
|
+
Here is a complete, production-grade React Native component showing how to traverse the AST recursively to map HTML content to native layout blocks:
|
|
248
|
+
|
|
249
|
+
```tsx
|
|
250
|
+
import React from 'react';
|
|
251
|
+
import { View, Text, StyleSheet, ScrollView } from 'react-native';
|
|
252
|
+
import { parseHTML, type ContentBlock, type InlineNode } from 'react-native-fast-html-parser';
|
|
253
|
+
|
|
254
|
+
// Helper to render formatting inlines (bold, italic, links)
|
|
255
|
+
function RenderInline({ node }: { node: InlineNode }): React.ReactElement {
|
|
256
|
+
if (node.type === 'Text') {
|
|
257
|
+
return <Text>{node.text}</Text>;
|
|
258
|
+
}
|
|
259
|
+
if (node.type === 'Bold') {
|
|
260
|
+
return <Text style={styles.bold}>{node.text}</Text>;
|
|
261
|
+
}
|
|
262
|
+
if (node.type === 'Italic') {
|
|
263
|
+
return <Text style={styles.italic}>{node.text}</Text>;
|
|
264
|
+
}
|
|
265
|
+
if (node.type === 'Link') {
|
|
266
|
+
return <Text style={styles.link} onPress={() => console.log('Open URL:', node.url)}>{node.text}</Text>;
|
|
267
|
+
}
|
|
268
|
+
return <Text>{node.text}</Text>;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Helper to render block-level structures
|
|
272
|
+
function RenderBlock({ block }: { block: ContentBlock }): React.ReactElement | null {
|
|
273
|
+
switch (block.type) {
|
|
274
|
+
case 'Heading': {
|
|
275
|
+
const children = [];
|
|
276
|
+
for (let i = 0; i < block.childCount; i++) {
|
|
277
|
+
const child = block.getChild(i);
|
|
278
|
+
if (child) children.push(<RenderInline key={i} node={child} />);
|
|
279
|
+
}
|
|
280
|
+
const isH1 = block.level === 1;
|
|
281
|
+
return <Text style={isH1 ? styles.h1 : styles.h2}>{children}</Text>;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
case 'Paragraph': {
|
|
285
|
+
const children = [];
|
|
286
|
+
for (let i = 0; i < block.childCount; i++) {
|
|
287
|
+
const child = block.getChild(i);
|
|
288
|
+
if (child) children.push(<RenderInline key={i} node={child} />);
|
|
289
|
+
}
|
|
290
|
+
return <Text style={styles.paragraph}>{children}</Text>;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
case 'CodeBlock': {
|
|
294
|
+
return (
|
|
295
|
+
<View style={styles.codeContainer}>
|
|
296
|
+
<Text style={styles.codeLanguage}>{block.language || 'code'}</Text>
|
|
297
|
+
<Text style={styles.codeText}>{block.code}</Text>
|
|
298
|
+
</View>
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
case 'Quote': {
|
|
303
|
+
const quoteChildren = [];
|
|
304
|
+
for (let i = 0; i < block.childCount; i++) {
|
|
305
|
+
const quoteBlock = block.getQuoteChild(i);
|
|
306
|
+
if (quoteBlock) {
|
|
307
|
+
quoteChildren.push(<RenderBlock key={i} block={quoteBlock} />);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return <View style={styles.quoteBorder}>{quoteChildren}</View>;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
default:
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Main Renderer Component
|
|
319
|
+
export default function HtmlContentRenderer({ html }: { html: string }) {
|
|
320
|
+
const article = React.useMemo(() => parseHTML(html), [html]);
|
|
321
|
+
|
|
322
|
+
if (!article) {
|
|
323
|
+
return <Text>Error parsing content</Text>;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const blocks = [];
|
|
327
|
+
for (let i = 0; i < article.length; i++) {
|
|
328
|
+
const block = article.getBlock(i);
|
|
329
|
+
if (block) {
|
|
330
|
+
blocks.push(<RenderBlock key={i} block={block} />);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return <ScrollView style={styles.container}>{blocks}</ScrollView>;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const styles = StyleSheet.create({
|
|
338
|
+
container: { flex: 1, padding: 16, backgroundColor: '#ffffff' },
|
|
339
|
+
paragraph: { fontSize: 16, lineHeight: 24, color: '#333333', marginVertical: 8 },
|
|
340
|
+
h1: { fontSize: 24, fontWeight: 'bold', marginVertical: 12, color: '#111111' },
|
|
341
|
+
h2: { fontSize: 20, fontWeight: 'bold', marginVertical: 10, color: '#222222' },
|
|
342
|
+
bold: { fontWeight: 'bold' },
|
|
343
|
+
italic: { fontStyle: 'italic' },
|
|
344
|
+
link: { color: '#0066cc', textDecorationLine: 'underline' },
|
|
345
|
+
codeContainer: { backgroundColor: '#f5f5f5', padding: 12, borderRadius: 8, marginVertical: 8 },
|
|
346
|
+
codeLanguage: { fontSize: 11, color: '#888888', textTransform: 'uppercase', marginBottom: 4 },
|
|
347
|
+
codeText: { fontFamily: 'Courier', fontSize: 14, color: '#333333' },
|
|
348
|
+
quoteBorder: { borderLeftWidth: 4, borderLeftColor: '#cccccc', paddingLeft: 12, marginVertical: 8, fontStyle: 'italic' }
|
|
349
|
+
});
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## AST Type Reference
|
|
355
|
+
|
|
356
|
+
All exposed interfaces are fully registered with JSI and typechecked:
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
export interface InlineNode {
|
|
360
|
+
readonly type: 'Text' | 'Bold' | 'Italic' | 'Link' | 'Strikethrough' | 'Underline' | 'Code';
|
|
361
|
+
readonly text: string;
|
|
362
|
+
readonly url: string;
|
|
363
|
+
readonly childCount: number;
|
|
364
|
+
getChild(index: number): InlineNode | null;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export interface ListItem {
|
|
368
|
+
readonly childCount: number;
|
|
369
|
+
getChild(index: number): InlineNode | null;
|
|
370
|
+
readonly nestedCount: number;
|
|
371
|
+
getNested(index: number): ContentBlock | null;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export interface TableCell {
|
|
375
|
+
readonly childCount: number;
|
|
376
|
+
getChild(index: number): InlineNode | null;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export interface TableRow {
|
|
380
|
+
readonly cellCount: number;
|
|
381
|
+
getCell(index: number): TableCell | null;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export interface DefinitionItem {
|
|
385
|
+
readonly termCount: number;
|
|
386
|
+
getTerm(index: number): InlineNode | null;
|
|
387
|
+
readonly defCount: number;
|
|
388
|
+
getDef(index: number): InlineNode | null;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export interface ContentBlock {
|
|
392
|
+
readonly type: 'Paragraph' | 'Heading' | 'List' | 'Table' | 'Quote' | 'CodeBlock' | 'Media' | 'DefinitionList' | 'Embed';
|
|
393
|
+
readonly level: number; // For Heading elements (1-6)
|
|
394
|
+
readonly url: string; // For Image/Media nodes
|
|
395
|
+
readonly alt: string; // For Image/Media nodes
|
|
396
|
+
readonly caption: string; // For Figures
|
|
397
|
+
readonly code: string; // For Code blocks
|
|
398
|
+
readonly language: string; // For Code blocks
|
|
399
|
+
readonly src: string; // For Video/Audio/Embed source url
|
|
400
|
+
readonly poster: string; // For Video poster url
|
|
401
|
+
readonly title: string; // For Embed elements
|
|
402
|
+
|
|
403
|
+
// Children elements
|
|
404
|
+
readonly childCount: number;
|
|
405
|
+
getChild(index: number): InlineNode | null;
|
|
406
|
+
getQuoteChild(index: number): ContentBlock | null;
|
|
407
|
+
|
|
408
|
+
// List properties
|
|
409
|
+
readonly ordered: boolean;
|
|
410
|
+
readonly itemCount: number;
|
|
411
|
+
getItem(index: number): ListItem | null;
|
|
412
|
+
|
|
413
|
+
// Table properties
|
|
414
|
+
readonly rowCount: number;
|
|
415
|
+
getRow(index: number): TableRow | null;
|
|
416
|
+
|
|
417
|
+
// DefinitionList properties
|
|
418
|
+
getDefItem(index: number): DefinitionItem | null;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export interface ParsedArticle {
|
|
422
|
+
readonly length: number;
|
|
423
|
+
getBlock(index: number): ContentBlock | null;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
export interface FastHtmlParser {
|
|
427
|
+
parse(html: string): ParsedArticle | null;
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## License
|
|
434
|
+
|
|
435
|
+
MIT © [abhishekce17](https://github.com/abhishekce17)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
project(fasthtmlparser)
|
|
2
|
+
cmake_minimum_required(VERSION 3.9.0)
|
|
3
|
+
|
|
4
|
+
set(PACKAGE_NAME fasthtmlparser)
|
|
5
|
+
set(CMAKE_VERBOSE_MAKEFILE ON)
|
|
6
|
+
set(CMAKE_CXX_STANDARD 20)
|
|
7
|
+
|
|
8
|
+
# Define C++ library and compile all sources
|
|
9
|
+
add_library(${PACKAGE_NAME} SHARED
|
|
10
|
+
src/main/cpp/cpp-adapter.cpp
|
|
11
|
+
../cpp/HybridFastHtmlParser.cpp
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
# Add Nitrogen specs :)
|
|
15
|
+
include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/fasthtmlparser+autolinking.cmake)
|
|
16
|
+
|
|
17
|
+
# Set up local includes
|
|
18
|
+
include_directories("src/main/cpp" "../cpp")
|
|
19
|
+
|
|
20
|
+
# Tell the linker where to search for the precompiled Rust shared library for this target
|
|
21
|
+
target_link_directories(${PACKAGE_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI})
|
|
22
|
+
|
|
23
|
+
find_library(LOG_LIB log)
|
|
24
|
+
|
|
25
|
+
# Link all libraries together
|
|
26
|
+
target_link_libraries(
|
|
27
|
+
${PACKAGE_NAME}
|
|
28
|
+
${LOG_LIB}
|
|
29
|
+
android # <-- Android core
|
|
30
|
+
html_2_json # <-- Link precompiled Rust library by name directly
|
|
31
|
+
)
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.FastHtmlParser = [
|
|
3
|
+
kotlinVersion: "2.0.21",
|
|
4
|
+
minSdkVersion: 24,
|
|
5
|
+
compileSdkVersion: 36
|
|
6
|
+
]
|
|
7
|
+
|
|
8
|
+
ext.getExtOrDefault = { prop ->
|
|
9
|
+
if (rootProject.ext.has(prop)) {
|
|
10
|
+
return rootProject.ext.get(prop)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return FastHtmlParser[prop]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
repositories {
|
|
17
|
+
google()
|
|
18
|
+
mavenCentral()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
dependencies {
|
|
22
|
+
classpath "com.android.tools.build:gradle:8.7.2"
|
|
23
|
+
// noinspection DifferentKotlinGradleVersion
|
|
24
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
def reactNativeArchitectures() {
|
|
29
|
+
def value = rootProject.getProperties().get("reactNativeArchitectures")
|
|
30
|
+
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
apply plugin: "com.android.library"
|
|
37
|
+
apply plugin: "kotlin-android"
|
|
38
|
+
apply from: '../nitrogen/generated/android/fasthtmlparser+autolinking.gradle'
|
|
39
|
+
|
|
40
|
+
apply plugin: "com.facebook.react"
|
|
41
|
+
|
|
42
|
+
android {
|
|
43
|
+
namespace "com.margelo.nitro.fasthtmlparser"
|
|
44
|
+
|
|
45
|
+
compileSdkVersion getExtOrDefault("compileSdkVersion")
|
|
46
|
+
|
|
47
|
+
defaultConfig {
|
|
48
|
+
minSdkVersion getExtOrDefault("minSdkVersion")
|
|
49
|
+
|
|
50
|
+
externalNativeBuild {
|
|
51
|
+
cmake {
|
|
52
|
+
cppFlags "-frtti -fexceptions -Wall -fstack-protector-all"
|
|
53
|
+
arguments "-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
|
|
54
|
+
abiFilters (*reactNativeArchitectures())
|
|
55
|
+
|
|
56
|
+
buildTypes {
|
|
57
|
+
debug {
|
|
58
|
+
cppFlags "-O1 -g"
|
|
59
|
+
}
|
|
60
|
+
release {
|
|
61
|
+
cppFlags "-O2"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
externalNativeBuild {
|
|
69
|
+
cmake {
|
|
70
|
+
path "CMakeLists.txt"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
packaging {
|
|
75
|
+
resources {
|
|
76
|
+
excludes += [
|
|
77
|
+
"META-INF",
|
|
78
|
+
"META-INF/**"
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
jniLibs {
|
|
83
|
+
excludes += [
|
|
84
|
+
"**/libc++_shared.so",
|
|
85
|
+
"**/libfbjni.so",
|
|
86
|
+
"**/libjsi.so",
|
|
87
|
+
"**/libfolly_json.so",
|
|
88
|
+
"**/libfolly_runtime.so",
|
|
89
|
+
"**/libglog.so",
|
|
90
|
+
"**/libhermes.so",
|
|
91
|
+
"**/libhermes-executor-debug.so",
|
|
92
|
+
"**/libhermes_executor.so",
|
|
93
|
+
"**/libreactnative.so",
|
|
94
|
+
"**/libreactnativejni.so",
|
|
95
|
+
"**/libturbomodulejsijni.so",
|
|
96
|
+
"**/libreact_nativemodule_core.so",
|
|
97
|
+
"**/libjscexecutor.so"
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
sourceSets {
|
|
103
|
+
main {
|
|
104
|
+
jniLibs.srcDirs = ["src/main/jniLibs"]
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
buildFeatures {
|
|
109
|
+
prefab true
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
compileOptions {
|
|
113
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
114
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
dependencies {
|
|
119
|
+
implementation "com.facebook.react:react-android"
|
|
120
|
+
implementation project(":react-native-nitro-modules")
|
|
121
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
package com.margelo.nitro.fasthtmlparser
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.BaseReactPackage
|
|
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 FastHtmlParserPackage : BaseReactPackage() {
|
|
9
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
|
|
10
|
+
return null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
|
|
14
|
+
return ReactModuleInfoProvider { HashMap() }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
companion object {
|
|
18
|
+
init {
|
|
19
|
+
System.loadLibrary("fasthtmlparser")
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|