use crate::types::{
BlockLocation, SfcCustomBlock, SfcDescriptor, SfcError, SfcParseOptions, SfcScriptBlock,
SfcStyleBlock, SfcTemplateBlock,
};
use memchr::memchr;
use std::borrow::Cow;
use super::block::{parse_block_fast, tag_name_eq};
const TAG_TEMPLATE: &[u8] = b"template";
const TAG_SCRIPT: &[u8] = b"script";
const TAG_STYLE: &[u8] = b"style";
pub fn parse_sfc<'a>(
source: &'a str,
options: SfcParseOptions,
) -> Result<SfcDescriptor<'a>, SfcError> {
let mut descriptor = SfcDescriptor {
filename: Cow::Owned(options.filename.into()),
source: Cow::Borrowed(source),
..Default::default()
};
let bytes = source.as_bytes();
let len = bytes.len();
let mut pos = 0;
let mut line = 1;
let mut column = 1;
while pos < len {
while pos < len {
let c = bytes[pos];
if c == b' ' || c == b'\t' || c == b'\r' {
pos += 1;
column += 1;
} else if c == b'\n' {
pos += 1;
line += 1;
column = 1;
} else {
break;
}
}
if pos >= len {
break;
}
if bytes[pos] != b'<' {
if let Some(next_lt) = memchr(b'<', &bytes[pos..]) {
for &b in &bytes[pos..pos + next_lt] {
if b == b'\n' {
line += 1;
column = 1;
} else {
column += 1;
}
}
pos += next_lt;
} else {
break;
}
}
if pos >= len {
break;
}
if let Some(block_result) = parse_block_fast(bytes, source, pos, line) {
let (tag_name, attrs, content, content_start, content_end, end_pos, end_line, end_col) =
block_result;
let loc = BlockLocation {
start: content_start,
end: content_end,
tag_start: pos,
tag_end: end_pos,
start_line: line,
start_column: column,
end_line,
end_column: end_col,
};
if tag_name_eq(tag_name, TAG_TEMPLATE) {
if descriptor.template.is_some() {
return Err(SfcError {
message: "SFC can only contain one <template> block".into(),
code: Some("DUPLICATE_TEMPLATE".into()),
loc: Some(loc.clone()),
});
}
descriptor.template = Some(SfcTemplateBlock {
content,
loc,
lang: attrs.get("lang").cloned(),
src: attrs.get("src").cloned(),
attrs,
});
} else if tag_name_eq(tag_name, TAG_SCRIPT) {
let is_setup = attrs.contains_key("setup");
let script_block = SfcScriptBlock {
content,
loc,
lang: attrs.get("lang").cloned(),
src: attrs.get("src").cloned(),
setup: is_setup,
attrs,
bindings: None,
};
if is_setup {
if descriptor.script_setup.is_some() {
return Err(SfcError {
message: "SFC can only contain one <script setup> block".into(),
code: Some("DUPLICATE_SCRIPT_SETUP".into()),
loc: Some(script_block.loc),
});
}
descriptor.script_setup = Some(script_block);
} else {
if descriptor.script.is_some() {
return Err(SfcError {
message: "SFC can only contain one <script> block".into(),
code: Some("DUPLICATE_SCRIPT".into()),
loc: Some(script_block.loc),
});
}
descriptor.script = Some(script_block);
}
} else if tag_name_eq(tag_name, TAG_STYLE) {
let scoped = attrs.contains_key("scoped");
let module = if attrs.contains_key("module") {
Some(
attrs
.get("module")
.filter(|v| !v.is_empty())
.cloned()
.unwrap_or_else(|| Cow::Borrowed("$style")),
)
} else {
None
};
descriptor.styles.push(SfcStyleBlock {
content,
loc,
lang: attrs.get("lang").cloned(),
src: attrs.get("src").cloned(),
scoped,
module,
attrs,
});
} else {
let tag_str = unsafe { std::str::from_utf8_unchecked(tag_name) };
descriptor.custom_blocks.push(SfcCustomBlock {
block_type: Cow::Borrowed(tag_str),
content,
loc,
attrs,
});
}
pos = end_pos;
line = end_line;
column = end_col;
} else {
pos += 1;
column += 1;
}
}
Ok(descriptor)
}