#[cfg(any(feature = "alloc", feature = "std"))]
use derive_more::{TryUnwrap, Unwrap};
use super::*;
#[cfg(any(feature = "alloc", feature = "std"))]
use crate::error::MaxDepthExceededError;
#[cfg(any(feature = "alloc", feature = "std"))]
use crate::vtt::Timestamp;
#[cfg(any(feature = "alloc", feature = "std"))]
use std::vec::Vec;
#[cfg(any(feature = "alloc", feature = "std"))]
mod sealed {
use super::*;
pub trait Sealed {}
impl Sealed for Vec<super::Node<'_>> {}
impl Sealed for &[super::Node<'_>] {}
impl<const N: usize> Sealed for [super::Node<'_>; N] {}
impl Sealed for super::Node<'_> {}
}
#[cfg(any(feature = "alloc", feature = "std"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
pub trait Nodes<'a>: sealed::Sealed {
fn as_nodes(&self) -> &[Node<'a>];
}
#[derive(Debug, Clone, PartialEq, Eq, IsVariant, Unwrap, TryUnwrap)]
#[unwrap(ref, ref_mut)]
#[try_unwrap(ref, ref_mut)]
#[cfg(any(feature = "alloc", feature = "std"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
pub enum Node<'a> {
Text(CueStr<'a>),
Timestamp(Timestamp),
Tag(TagNode<'a>),
}
#[cfg(any(feature = "alloc", feature = "std"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
const _: () = {
impl<'a> Nodes<'a> for Vec<Node<'a>> {
#[cfg_attr(not(tarpaulin), inline(always))]
fn as_nodes(&self) -> &[Node<'a>] {
self.as_slice()
}
}
impl<'a> Nodes<'a> for &'a [Node<'a>] {
#[cfg_attr(not(tarpaulin), inline(always))]
fn as_nodes(&self) -> &[Node<'a>] {
self
}
}
impl<'a, const N: usize> Nodes<'a> for [Node<'a>; N] {
#[cfg_attr(not(tarpaulin), inline(always))]
fn as_nodes(&self) -> &[Node<'a>] {
self.as_slice()
}
}
impl<'a> Nodes<'a> for Node<'a> {
#[cfg_attr(not(tarpaulin), inline(always))]
fn as_nodes(&self) -> &[Node<'a>] {
core::slice::from_ref(self)
}
}
impl<'a> AsRef<[Node<'a>]> for Node<'a> {
#[cfg_attr(not(tarpaulin), inline(always))]
fn as_ref(&self) -> &[Node<'a>] {
core::slice::from_ref(self)
}
}
impl<'a> AsMut<[Node<'a>]> for Node<'a> {
#[cfg_attr(not(tarpaulin), inline(always))]
fn as_mut(&mut self) -> &mut [Node<'a>] {
core::slice::from_mut(self)
}
}
impl AsRef<Self> for Node<'_> {
#[cfg_attr(not(tarpaulin), inline(always))]
fn as_ref(&self) -> &Self {
self
}
}
impl AsMut<Self> for Node<'_> {
#[cfg_attr(not(tarpaulin), inline(always))]
fn as_mut(&mut self) -> &mut Self {
self
}
}
impl fmt::Display for Node<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Node::Text(s) => fmt::Display::fmt(s, f),
Node::Timestamp(ts) => write!(f, "<{}>", ts.encode().as_str()),
Node::Tag(tag) => fmt::Display::fmt(tag, f),
}
}
}
};
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg(any(feature = "alloc", feature = "std"))]
pub struct TagNode<'a, C = Vec<Node<'a>>> {
tag: Tag,
classes: &'a str,
annotation: Option<Annotation<'a>>,
children: C,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg(not(any(feature = "alloc", feature = "std")))]
pub struct TagNode<'a, C> {
tag: Tag,
classes: &'a str,
annotation: Option<Annotation<'a>>,
children: C,
}
#[derive(Debug, Clone)]
pub struct Classes<'a> {
rest: &'a str,
}
impl<'a> Classes<'a> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(raw: &'a str) -> Self {
Self { rest: raw }
}
}
impl<'a> Iterator for Classes<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
loop {
if self.rest.is_empty() {
return None;
}
match self.rest.find('.') {
Some(idx) => {
let (class, rest) = self.rest.split_at(idx);
self.rest = &rest[1..];
if !class.is_empty() {
return Some(class);
}
}
None => return Some(core::mem::take(&mut self.rest)),
}
}
}
}
impl core::iter::FusedIterator for Classes<'_> {}
impl<'a, C> AsRef<[TagNode<'a, C>]> for TagNode<'a, C> {
#[cfg_attr(not(tarpaulin), inline(always))]
fn as_ref(&self) -> &[TagNode<'a, C>] {
core::slice::from_ref(self)
}
}
impl<'a, C> AsMut<[TagNode<'a, C>]> for TagNode<'a, C> {
#[cfg_attr(not(tarpaulin), inline(always))]
fn as_mut(&mut self) -> &mut [TagNode<'a, C>] {
core::slice::from_mut(self)
}
}
impl<C> AsRef<Self> for TagNode<'_, C> {
#[cfg_attr(not(tarpaulin), inline(always))]
fn as_ref(&self) -> &Self {
self
}
}
impl<C> AsMut<Self> for TagNode<'_, C> {
#[cfg_attr(not(tarpaulin), inline(always))]
fn as_mut(&mut self) -> &mut Self {
self
}
}
impl<'a, C> TagNode<'a, C> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn tag(&self) -> Tag {
self.tag
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn with_tag(mut self, tag: Tag) -> Self {
self.tag = tag;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn set_tag(&mut self, tag: Tag) -> &mut Self {
self.tag = tag;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn classes(&self) -> Classes<'a> {
Classes::new(self.classes)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn classes_raw(&self) -> &'a str {
self.classes
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn with_classes(mut self, classes: &'a str) -> Self {
self.classes = classes;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn set_classes(&mut self, classes: &'a str) -> &mut Self {
self.classes = classes;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn annotation(&self) -> Option<&Annotation<'a>> {
self.annotation.as_ref()
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn with_annotation(mut self, annotation: Option<Annotation<'a>>) -> Self {
self.annotation = annotation;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn set_annotation(&mut self, annotation: Option<Annotation<'a>>) -> &mut Self {
self.annotation = annotation;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn declared_language(&self) -> Option<&str> {
match self.tag {
Tag::Lang => Some(match &self.annotation {
Some(language) => language.normalize(),
None => "",
}),
_ => None,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn children(&self) -> &C {
&self.children
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn children_mut(&mut self) -> &mut C {
&mut self.children
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn with_children<D>(self, children: D) -> TagNode<'a, D> {
TagNode {
tag: self.tag,
classes: self.classes,
annotation: self.annotation,
children,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn set_children(&mut self, children: C) -> &mut Self {
self.children = children;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn into_children(self) -> C {
self.children
}
}
#[cfg(any(feature = "alloc", feature = "std"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
impl TagNode<'_> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(tag: Tag) -> Self {
Self {
tag,
classes: "",
annotation: None,
children: Vec::new(),
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn with_vec_capacity(tag: Tag, cap: usize) -> Self {
Self {
tag,
classes: "",
annotation: None,
children: Vec::with_capacity(cap),
}
}
}
#[cfg(any(feature = "alloc", feature = "std"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
impl<'a, C: AsRef<[Node<'a>]>> fmt::Display for TagNode<'a, C> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "<{}", self.tag)?;
if !self.classes.is_empty() {
write!(f, ".{}", self.classes)?;
}
if let Some(annotation) = &self.annotation {
write!(f, " {}", annotation.as_raw())?;
}
f.write_str(">")?;
for child in self.children.as_ref() {
write!(f, "{}", child)?;
}
write!(f, "</{}>", self.tag)
}
}
#[cfg(any(feature = "alloc", feature = "std"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
pub const DEFAULT_MAX_DEPTH: usize = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case", default))]
#[cfg(any(feature = "alloc", feature = "std"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
pub struct Options {
max_depth: usize,
}
#[cfg(any(feature = "alloc", feature = "std"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
impl Options {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new() -> Self {
Self {
max_depth: DEFAULT_MAX_DEPTH,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn max_depth(&self) -> usize {
self.max_depth
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn with_max_depth(mut self, max_depth: usize) -> Self {
self.max_depth = max_depth;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_max_depth(&mut self, max_depth: usize) -> &mut Self {
self.max_depth = max_depth;
self
}
}
#[cfg(any(feature = "alloc", feature = "std"))]
impl Default for Options {
#[cfg_attr(not(tarpaulin), inline(always))]
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg(any(feature = "alloc", feature = "std"))]
pub struct CueText<'a, C = Vec<Node<'a>>> {
children: C,
_marker: core::marker::PhantomData<&'a ()>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg(not(any(feature = "alloc", feature = "std")))]
pub struct CueText<'a, C> {
children: C,
_marker: core::marker::PhantomData<&'a ()>,
}
impl<C> CueText<'_, C> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(children: C) -> Self {
Self {
children,
_marker: core::marker::PhantomData,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn children(&self) -> &C {
&self.children
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn children_mut(&mut self) -> &mut C {
&mut self.children
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn into_children(self) -> C {
self.children
}
}
#[cfg(any(feature = "alloc", feature = "std"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
impl<'a, C> CueText<'a, C> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn children_slice(&self) -> &[Node<'a>]
where
C: AsRef<[Node<'a>]>,
{
self.children.as_ref()
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn children_slice_mut(&mut self) -> &mut [Node<'a>]
where
C: AsMut<[Node<'a>]>,
{
self.children.as_mut()
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn nodes_with_language(&self) -> NodesWithLanguage<'_, 'a>
where
C: AsRef<[Node<'a>]>,
{
NodesWithLanguage::new(self.children.as_ref())
}
}
#[derive(Debug, Clone)]
#[cfg(any(feature = "alloc", feature = "std"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
pub struct NodesWithLanguage<'t, 'a> {
stack: Vec<(core::slice::Iter<'t, Node<'a>>, Option<&'t str>)>,
}
#[cfg(any(feature = "alloc", feature = "std"))]
impl<'t, 'a> NodesWithLanguage<'t, 'a> {
fn new(nodes: &'t [Node<'a>]) -> Self {
Self {
stack: std::vec![(nodes.iter(), None)],
}
}
}
#[cfg(any(feature = "alloc", feature = "std"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
impl<'t, 'a> Iterator for NodesWithLanguage<'t, 'a> {
type Item = (&'t Node<'a>, Option<&'t str>);
fn next(&mut self) -> Option<Self::Item> {
loop {
let (siblings, language) = self.stack.last_mut()?;
let language = *language;
match siblings.next() {
Some(node @ Node::Tag(tag)) => {
let inner = tag.declared_language().or(language);
self.stack.push((tag.children().iter(), inner));
return Some((node, inner));
}
Some(node) => return Some((node, language)),
None => {
self.stack.pop();
}
}
}
}
}
#[cfg(any(feature = "alloc", feature = "std"))]
impl core::iter::FusedIterator for NodesWithLanguage<'_, '_> {}
#[cfg(any(feature = "alloc", feature = "std"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
impl<'a> CueText<'a> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn parse(input: &'a str) -> Self {
Self::parse_with(input, Options::new())
}
pub fn parse_with(input: &'a str, options: Options) -> Self {
Self::build(input, options.max_depth(), false).0
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn try_parse(input: &'a str) -> Result<Self, MaxDepthExceededError> {
Self::try_parse_with(input, Options::new())
}
pub fn try_parse_with(input: &'a str, options: Options) -> Result<Self, MaxDepthExceededError> {
match Self::build(input, options.max_depth(), true) {
(tree, false) => Ok(tree),
(_, true) => Err(MaxDepthExceededError::new(options.max_depth())),
}
}
fn build(input: &'a str, max_depth: usize, stop_when_exceeded: bool) -> (Self, bool) {
let mut builder = Builder::new(max_depth);
let mut exceeded = false;
for token in CueParser::new(input) {
match token {
CueToken::Text(text) => builder.push_leaf(Node::Text(text)),
CueToken::Timestamp(ts) => builder.push_leaf(Node::Timestamp(ts)),
CueToken::StartTag {
tag,
classes,
annotation,
} => {
if tag == Tag::RubyText && builder.current_tag() != Some(Tag::Ruby) {
continue;
}
if !builder.open(tag, classes, annotation) {
exceeded = true;
if stop_when_exceeded {
break;
}
}
}
CueToken::EndTag(tag) => {
match builder.current_tag() {
Some(current) if current == tag => builder.close_current(),
Some(Tag::RubyText) if tag == Tag::Ruby => {
builder.close_current();
builder.close_current();
}
_ => {}
}
}
}
}
(Self::new(builder.finish()), exceeded)
}
}
#[cfg(any(feature = "alloc", feature = "std"))]
struct Builder<'a> {
root: Vec<Node<'a>>,
stack: Vec<TagNode<'a>>,
over: Vec<Tag>,
max_depth: usize,
}
#[cfg(any(feature = "alloc", feature = "std"))]
impl<'a> Builder<'a> {
fn new(max_depth: usize) -> Self {
Self {
root: Vec::new(),
stack: Vec::new(),
over: Vec::new(),
max_depth,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
fn current_tag(&self) -> Option<Tag> {
match self.over.last() {
Some(tag) => Some(*tag),
None => self.stack.last().map(|node| node.tag()),
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
fn push_leaf(&mut self, node: Node<'a>) {
match self.stack.last_mut() {
Some(parent) => parent.children_mut().push(node),
None => self.root.push(node),
}
}
fn open(&mut self, tag: Tag, classes: &'a str, annotation: Option<Annotation<'a>>) -> bool {
if self.over.is_empty() && self.stack.len() < self.max_depth {
self.stack.push(
TagNode::new(tag)
.with_classes(classes)
.with_annotation(annotation),
);
true
} else {
self.over.push(tag);
false
}
}
fn close_current(&mut self) {
if self.over.pop().is_some() {
return;
}
if let Some(node) = self.stack.pop() {
self.push_leaf(Node::Tag(node));
}
}
fn finish(mut self) -> Vec<Node<'a>> {
while let Some(node) = self.stack.pop() {
let completed = Node::Tag(node);
match self.stack.last_mut() {
Some(parent) => parent.children_mut().push(completed),
None => self.root.push(completed),
}
}
self.root
}
}
#[cfg(any(feature = "alloc", feature = "std"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
impl<'a, C: Nodes<'a>> fmt::Display for CueText<'a, C> {
#[cfg_attr(not(tarpaulin), inline(always))]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for child in self.children.as_nodes() {
write!(f, "{}", child)?;
}
Ok(())
}
}
#[cfg(test)]
#[cfg(any(feature = "alloc", feature = "std"))]
mod tests {
use super::*;
#[test]
fn tag_node_as_slice() {
let node = TagNode::new(Tag::Bold);
let slice: &[TagNode<'_>] = node.as_ref();
assert_eq!(slice.len(), 1);
assert_eq!(slice[0].tag(), Tag::Bold);
}
#[test]
fn tag_node_as_mut_slice() {
let mut node = TagNode::new(Tag::Italic);
let slice: &mut [TagNode<'_>] = node.as_mut();
assert_eq!(slice.len(), 1);
assert_eq!(slice[0].tag(), Tag::Italic);
slice[0].set_tag(Tag::Underline);
assert_eq!(node.tag(), Tag::Underline);
}
#[test]
fn tag_node_as_ref() {
let node = TagNode::new(Tag::Class);
let r: &TagNode<'_> = node.as_ref();
assert_eq!(r.tag(), Tag::Class);
}
#[test]
fn tag_node_as_mut() {
let mut node = TagNode::new(Tag::Lang);
let r: &mut TagNode<'_> = node.as_mut();
assert_eq!(r.tag(), Tag::Lang);
r.set_tag(Tag::Voice);
assert_eq!(node.tag(), Tag::Voice);
}
}