Packages
html_to_markdown
3.0.1
3.9.0
3.8.3
3.8.2
3.8.1
3.8.0
3.8.0-rc.2
3.8.0-rc.1
3.7.2
3.7.1
3.7.0
3.6.21
3.6.20
3.6.19
3.6.18
3.6.17
3.6.16
3.6.15
3.6.14
3.6.13
3.6.11
3.6.10
3.6.9
3.6.8
3.6.3
3.6.1
3.6.0
3.6.0-rc.25
3.6.0-rc.23
3.6.0-rc.22
3.6.0-rc.21
3.6.0-rc.20
3.6.0-rc.17
3.6.0-rc.16
3.6.0-rc.14
3.6.0-rc.13
3.6.0-rc.12
3.6.0-rc.11
3.6.0-rc.10
3.6.0-rc.9
3.6.0-rc.7
3.6.0-rc.3
3.6.0-rc.2
3.6.0-rc.1
3.5.7
3.5.5
3.5.3
3.5.2
3.5.1
3.5.0
3.5.0-rc.1
3.4.0
3.4.0-rc.45
3.4.0-rc.44
3.4.0-rc.42
3.4.0-rc.41
3.4.0-rc.40
3.4.0-rc.25
3.4.0-rc.24
3.4.0-rc.23
3.4.0-rc.21
3.4.0-rc.20
3.4.0-rc.18
3.4.0-rc.16
3.4.0-rc.15
3.4.0-rc.14
3.4.0-rc.13
3.4.0-rc.12
3.4.0-rc.11
3.4.0-rc.9
3.4.0-rc.8
3.4.0-rc.2
3.4.0-rc.1
3.3.2
3.2.4
3.2.2
3.2.1
3.2.0
3.1.0
3.0.2
3.0.1
3.0.0
2.30.0
2.29.0
2.28.6
2.28.5
2.28.4
2.28.3
2.28.2
2.28.1
2.28.0
2.27.3
2.27.2
2.27.1
2.27.0
2.26.3
2.26.2
2.26.1
2.25.1
2.25.0
2.24.6
2.24.5
2.24.4
2.24.3
2.24.1
2.23.4
2.23.3
2.23.2
2.23.1
2.23.0
2.22.5
2.22.2
2.22.1
2.22.0
2.21.1
2.20.0
2.19.8
2.19.7
2.19.6
2.19.5
2.19.4
2.19.3
2.19.2
2.19.1
2.19.0
2.18.0
2.16.1
2.16.0
2.15.0
2.14.11
2.14.10
2.14.9
2.14.8
2.14.7
2.14.6
2.14.3
2.14.2
2.14.1
2.14.0
2.13.0
2.12.1
2.12.0
2.11.4
2.11.3
2.11.1
2.10.1
2.9.2
2.9.1
2.9.0
2.8.3
High-performance HTML to Markdown converter
Current section
Files
Jump to
Current section
Files
native/html_to_markdown_elixir/src/lib.rs
#![allow(clippy::let_unit_value, deprecated)]
use html_to_markdown_rs::metadata::{
DocumentMetadata, HeaderMetadata, HtmlMetadata, ImageMetadata, LinkMetadata, StructuredData,
};
mod options;
mod types;
use options::{
INVALID_OPTION_ERROR, decode_options_term,
take_invalid_option_message,
};
use types::{
ConversionResultTerm, DocumentMetadataTerm, ExtendedMetadataTerm, ExtractTableTerm, GridCellTerm,
HeaderMetadataTerm, ImageMetadataTerm, InlineImageTerm, LinkMetadataTerm,
StructuredDataTerm, WarningTerm,
};
use rustler::{Encoder, Env, Error, NifResult, Term};
rustler::init!(
"Elixir.HtmlToMarkdown.Native",
[
convert
],
load = on_load
);
fn on_load(_env: Env, _info: Term) -> bool {
true
}
mod atoms {
rustler::atoms! {
ok,
error,
invalid_option,
conversion_failed,
atx,
atx_closed,
underlined,
spaces,
tabs,
normalized,
strict,
minimal,
standard,
aggressive,
backslash,
indented,
backticks,
tildes,
img_data_uri,
svg_element,
}
}
#[rustler::nif(schedule = "DirtyCpu")]
fn convert<'a>(env: Env<'a>, html: String, options_term: Term<'a>) -> NifResult<Term<'a>> {
let options = match decode_options_term(options_term) {
Ok(options) => options,
Err(err) => return handle_invalid_option_error(env, err),
};
match html_to_markdown_rs::convert(&html, Some(options.clone())) {
Ok(result) => {
let tables: Vec<ExtractTableTerm> = result
.tables
.into_iter()
.map(|t| {
let cells: Vec<GridCellTerm> = t
.grid
.cells
.into_iter()
.map(|c| GridCellTerm {
content: c.content,
row: c.row,
col: c.col,
row_span: c.row_span,
col_span: c.col_span,
is_header: c.is_header,
})
.collect();
ExtractTableTerm {
grid: types::TableGridTerm {
rows: t.grid.rows,
cols: t.grid.cols,
cells,
},
markdown: t.markdown,
}
})
.collect();
let warnings: Vec<WarningTerm> = result
.warnings
.into_iter()
.map(|w| {
let kind = match w.kind {
html_to_markdown_rs::WarningKind::ImageExtractionFailed => {
"image_extraction_failed"
}
html_to_markdown_rs::WarningKind::EncodingFallback => "encoding_fallback",
html_to_markdown_rs::WarningKind::TruncatedInput => "truncated_input",
html_to_markdown_rs::WarningKind::MalformedHtml => "malformed_html",
html_to_markdown_rs::WarningKind::SanitizationApplied => {
"sanitization_applied"
}
};
WarningTerm {
message: w.message,
kind: kind.to_string(),
}
})
.collect();
let document = result.document.and_then(|doc| {
serde_json::to_string(&doc).ok()
});
let images: Vec<InlineImageTerm> = result
.images
.into_iter()
.map(|img| InlineImageTerm {
data: img.data,
format: img.format.to_string(),
filename: img.filename,
description: img.description,
dimensions: img.dimensions,
source: img.source.to_string(),
attributes: img.attributes.into_iter().collect(),
})
.collect();
let term = ConversionResultTerm {
content: result.content,
document,
metadata: build_metadata(result.metadata),
tables,
images,
warnings,
};
Ok((atoms::ok(), term).encode(env))
}
Err(err) => Ok((atoms::error(), err.to_string()).encode(env)),
}
}
fn build_metadata(metadata: HtmlMetadata) -> ExtendedMetadataTerm {
ExtendedMetadataTerm {
document: build_document_metadata(metadata.document),
headers: metadata.headers.into_iter().map(build_header_metadata).collect(),
links: metadata.links.into_iter().map(build_link_metadata).collect(),
images: metadata.images.into_iter().map(build_image_metadata).collect(),
structured_data: metadata
.structured_data
.into_iter()
.map(build_structured_data)
.collect(),
}
}
fn build_document_metadata(metadata: DocumentMetadata) -> DocumentMetadataTerm {
DocumentMetadataTerm {
title: metadata.title,
description: metadata.description,
keywords: metadata.keywords,
author: metadata.author,
canonical_url: metadata.canonical_url,
base_href: metadata.base_href,
language: metadata.language,
text_direction: metadata.text_direction.map(|td| td.to_string()),
open_graph: metadata.open_graph.into_iter().collect(),
twitter_card: metadata.twitter_card.into_iter().collect(),
meta_tags: metadata.meta_tags.into_iter().collect(),
}
}
fn build_header_metadata(metadata: HeaderMetadata) -> HeaderMetadataTerm {
HeaderMetadataTerm {
level: metadata.level,
text: metadata.text,
id: metadata.id,
depth: metadata.depth as u64,
html_offset: metadata.html_offset as u64,
}
}
fn build_link_metadata(metadata: LinkMetadata) -> LinkMetadataTerm {
LinkMetadataTerm {
href: metadata.href,
text: metadata.text,
title: metadata.title,
link_type: metadata.link_type.to_string(),
rel: metadata.rel,
attributes: metadata.attributes.into_iter().collect(),
}
}
fn build_image_metadata(metadata: ImageMetadata) -> ImageMetadataTerm {
ImageMetadataTerm {
src: metadata.src,
alt: metadata.alt,
title: metadata.title,
dimensions: metadata.dimensions,
image_type: metadata.image_type.to_string(),
attributes: metadata.attributes.into_iter().collect(),
}
}
fn build_structured_data(metadata: StructuredData) -> StructuredDataTerm {
StructuredDataTerm {
data_type: metadata.data_type.to_string(),
raw_json: metadata.raw_json,
schema_type: metadata.schema_type,
}
}
fn handle_invalid_option_error<'a>(env: Env<'a>, err: Error) -> NifResult<Term<'a>> {
match err {
Error::Atom(atom) if atom == INVALID_OPTION_ERROR => {
let reason = take_invalid_option_message().unwrap_or_else(|| "invalid option".to_string());
Ok((atoms::error(), reason).encode(env))
}
other => Err(other),
}
}