use std::ops::Range;
use super::index::XPathNodeKind;
use super::NodeId;
pub const RTF_BASE: NodeId = 1_usize << (usize::BITS - 2);
const RTF_LOCAL_BITS: u32 = 32;
const RTF_LOCAL_MASK: NodeId = (1_usize << RTF_LOCAL_BITS) - 1;
const RTF_INDEX_MASK: NodeId = !(RTF_BASE | super::context::SYNTHETIC_TEXT_BASE | RTF_LOCAL_MASK);
#[inline(always)]
pub fn is_rtf_id(id: NodeId) -> bool {
id & RTF_BASE != 0 && id & super::context::SYNTHETIC_TEXT_BASE == 0
}
#[inline(always)]
pub fn decode_rtf_id(id: NodeId) -> (usize, NodeId) {
let local = id & RTF_LOCAL_MASK;
let rtf_i = (id & RTF_INDEX_MASK) >> RTF_LOCAL_BITS;
(rtf_i, local)
}
#[inline(always)]
pub fn encode_rtf_id(rtf_i: usize, local: NodeId) -> NodeId {
debug_assert!(local <= RTF_LOCAL_MASK,
"RTF local id {local} exceeds 32-bit budget");
debug_assert!(rtf_i <= (RTF_INDEX_MASK >> RTF_LOCAL_BITS),
"RTF index {rtf_i} exceeds 30-bit budget");
RTF_BASE | (rtf_i << RTF_LOCAL_BITS) | local
}
#[derive(Debug)]
pub struct RtfNode {
pub kind: RtfNodeKind,
pub parent: Option<NodeId>,
pub children: Vec<NodeId>,
pub attr_start: NodeId,
pub attr_end: NodeId,
pub ns_start: NodeId,
pub ns_end: NodeId,
}
#[derive(Debug)]
pub enum RtfNodeKind {
Document,
Element {
name: Box<str>,
local_name: Box<str>,
prefix: Option<Box<str>>,
namespace_uri: Box<str>,
},
Attribute {
name: Box<str>,
local_name: Box<str>,
prefix: Option<Box<str>>,
namespace_uri: Box<str>,
value: Box<str>,
},
Text(Box<str>),
Comment(Box<str>),
PI { target: Box<str>, data: Box<str> },
Namespace { prefix: Option<Box<str>>, uri: Box<str> },
}
#[derive(Debug)]
pub struct RtfIndex {
pub nodes: Vec<RtfNode>,
pub host_index: usize,
}
impl RtfIndex {
pub fn children(&self, local_id: NodeId) -> &[NodeId] {
&self.nodes[local_id].children
}
pub fn parent(&self, local_id: NodeId) -> Option<NodeId> {
self.nodes[local_id].parent
}
pub fn attr_range(&self, local_id: NodeId) -> Range<NodeId> {
let n = &self.nodes[local_id];
n.attr_start..n.attr_end
}
pub fn ns_range(&self, local_id: NodeId) -> Range<NodeId> {
let n = &self.nodes[local_id];
n.ns_start..n.ns_end
}
pub fn kind(&self, local_id: NodeId) -> XPathNodeKind {
match self.nodes[local_id].kind {
RtfNodeKind::Document => XPathNodeKind::Document,
RtfNodeKind::Element { .. } => XPathNodeKind::Element,
RtfNodeKind::Attribute { .. } => XPathNodeKind::Attribute,
RtfNodeKind::Text(_) => XPathNodeKind::Text,
RtfNodeKind::Comment(_) => XPathNodeKind::Comment,
RtfNodeKind::PI { .. } => XPathNodeKind::PI,
RtfNodeKind::Namespace { .. } => XPathNodeKind::Namespace,
}
}
pub fn pi_target(&self, local_id: NodeId) -> &str {
match &self.nodes[local_id].kind {
RtfNodeKind::PI { target, .. } => target,
_ => "",
}
}
pub fn node_name(&self, local_id: NodeId) -> &str {
match &self.nodes[local_id].kind {
RtfNodeKind::Element { name, .. }
| RtfNodeKind::Attribute { name, .. } => name,
RtfNodeKind::PI { target, .. } => target,
RtfNodeKind::Namespace { prefix, .. } =>
prefix.as_deref().unwrap_or(""),
_ => "",
}
}
pub fn local_name(&self, local_id: NodeId) -> &str {
match &self.nodes[local_id].kind {
RtfNodeKind::Element { local_name, .. }
| RtfNodeKind::Attribute { local_name, .. } => local_name,
RtfNodeKind::PI { target, .. } => target,
RtfNodeKind::Namespace { prefix, .. } =>
prefix.as_deref().unwrap_or(""),
_ => "",
}
}
pub fn namespace_uri(&self, local_id: NodeId) -> &str {
match &self.nodes[local_id].kind {
RtfNodeKind::Element { namespace_uri, .. }
| RtfNodeKind::Attribute { namespace_uri, .. } => namespace_uri,
_ => "",
}
}
pub fn namespace_prefix(&self, local_id: NodeId) -> Option<&str> {
match &self.nodes[local_id].kind {
RtfNodeKind::Element { prefix, .. }
| RtfNodeKind::Attribute { prefix, .. } => prefix.as_deref(),
_ => None,
}
}
pub fn string_value(&self, local_id: NodeId) -> String {
match &self.nodes[local_id].kind {
RtfNodeKind::Text(s) => s.to_string(),
RtfNodeKind::Comment(s) => s.to_string(),
RtfNodeKind::PI { data, .. } => data.to_string(),
RtfNodeKind::Attribute { value, .. } => value.to_string(),
RtfNodeKind::Namespace { uri, .. } => uri.to_string(),
RtfNodeKind::Element { .. } | RtfNodeKind::Document => {
let mut out = String::new();
self.append_text(local_id, &mut out);
out
}
}
}
fn append_text(&self, local_id: NodeId, out: &mut String) {
for &child in &self.nodes[local_id].children {
let child_local = child & RTF_LOCAL_MASK;
match &self.nodes[child_local].kind {
RtfNodeKind::Text(s) => out.push_str(s),
RtfNodeKind::Element { .. } | RtfNodeKind::Document =>
self.append_text(child_local, out),
_ => {}
}
}
}
}
pub struct RtfBuilder {
pub(crate) host_index: usize,
pub(crate) nodes: Vec<RtfNode>,
pub typed_nodes: Vec<(NodeId, Box<(String, String)>)>,
}
impl RtfBuilder {
#[allow(dead_code)] pub(crate) fn new(host_index: usize) -> Self {
Self { host_index, nodes: Vec::new(), typed_nodes: Vec::new() }
}
#[inline]
fn glob(&self, local: NodeId) -> NodeId {
encode_rtf_id(self.host_index, local)
}
fn push(&mut self, kind: RtfNodeKind, parent: Option<NodeId>) -> NodeId {
let local_id = self.nodes.len();
self.nodes.push(RtfNode {
kind,
parent,
children: Vec::new(),
attr_start: 0, attr_end: 0,
ns_start: 0, ns_end: 0,
});
self.glob(local_id)
}
fn local_of(global: NodeId) -> NodeId {
global & RTF_LOCAL_MASK
}
pub fn add_document(&mut self) -> NodeId {
assert!(self.nodes.is_empty(),
"RtfBuilder::add_document must be the first call");
self.push(RtfNodeKind::Document, None)
}
pub fn add_element(
&mut self, parent: NodeId,
qname: &str, namespace_uri: &str, prefix: Option<&str>,
) -> NodeId {
let (local_name, _) = qname.rsplit_once(':')
.map(|(_, l)| (l, true))
.unwrap_or((qname, false));
let id = self.push(RtfNodeKind::Element {
name: qname.into(),
local_name: local_name.into(),
prefix: prefix.map(Into::into),
namespace_uri: namespace_uri.into(),
}, Some(parent));
let parent_local = Self::local_of(parent);
self.nodes[parent_local].children.push(id);
id
}
pub fn add_text(&mut self, parent: NodeId, content: &str) -> NodeId {
let id = self.push(RtfNodeKind::Text(content.into()), Some(parent));
let parent_local = Self::local_of(parent);
self.nodes[parent_local].children.push(id);
id
}
pub fn add_comment(&mut self, parent: NodeId, content: &str) -> NodeId {
let id = self.push(RtfNodeKind::Comment(content.into()), Some(parent));
let parent_local = Self::local_of(parent);
self.nodes[parent_local].children.push(id);
id
}
pub fn add_pi(&mut self, parent: NodeId, target: &str, data: &str) -> NodeId {
let id = self.push(RtfNodeKind::PI {
target: target.into(), data: data.into(),
}, Some(parent));
let parent_local = Self::local_of(parent);
self.nodes[parent_local].children.push(id);
id
}
pub fn start_attrs(&mut self, elem_global: NodeId) {
let elem_local = Self::local_of(elem_global);
let start_local = self.nodes.len();
self.nodes[elem_local].attr_start = self.glob(start_local);
self.nodes[elem_local].attr_end = self.glob(start_local);
}
pub fn add_attribute(
&mut self, elem_global: NodeId,
qname: &str, namespace_uri: &str, prefix: Option<&str>,
value: &str,
) -> NodeId {
let (local_name, _) = qname.rsplit_once(':')
.map(|(_, l)| (l, true))
.unwrap_or((qname, false));
let id = self.push(RtfNodeKind::Attribute {
name: qname.into(),
local_name: local_name.into(),
prefix: prefix.map(Into::into),
namespace_uri: namespace_uri.into(),
value: value.into(),
}, Some(elem_global));
let elem_local = Self::local_of(elem_global);
let new_end_local = Self::local_of(id) + 1;
self.nodes[elem_local].attr_end = self.glob(new_end_local);
id
}
pub fn start_ns(&mut self, elem_global: NodeId) {
let elem_local = Self::local_of(elem_global);
let start_local = self.nodes.len();
self.nodes[elem_local].ns_start = self.glob(start_local);
self.nodes[elem_local].ns_end = self.glob(start_local);
}
pub fn add_namespace_node(
&mut self, elem_global: NodeId,
prefix: Option<&str>, uri: &str,
) -> NodeId {
let prefix = prefix.filter(|p| !p.is_empty());
let id = self.push(
RtfNodeKind::Namespace {
prefix: prefix.map(Into::into),
uri: uri.into(),
},
Some(elem_global),
);
let elem_local = Self::local_of(elem_global);
let new_end_local = Self::local_of(id) + 1;
self.nodes[elem_local].ns_end = self.glob(new_end_local);
id
}
pub fn build(self) -> RtfIndex {
RtfIndex { nodes: self.nodes, host_index: self.host_index }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rtf_id_round_trips() {
let id = encode_rtf_id(7, 42);
assert!(is_rtf_id(id));
assert_eq!(decode_rtf_id(id), (7, 42));
}
#[test]
fn rtf_marker_is_disjoint_from_synthetic() {
let s = super::super::context::SYNTHETIC_TEXT_BASE;
let r = encode_rtf_id(0, 0);
assert!(!is_rtf_id(s),
"synthetic ids must not look like RTF ids");
assert!(r & s == 0,
"RTF and synthetic markers must be distinct bits");
}
}