diff --git a/tamer/src/ir/xir.rs b/tamer/src/ir/xir.rs index afc2ea21..975b61f8 100644 --- a/tamer/src/ir/xir.rs +++ b/tamer/src/ir/xir.rs @@ -385,12 +385,22 @@ pub enum Token { /// close!). Close(Option>, Span), - /// Element attribute name + /// Element attribute name. AttrName(QName, Span), - /// Element attribute value + /// Element attribute value. AttrValue(AttrValue, Span), + /// A portion of an element attribute value. + /// + /// This allows for concatenating values into an attribute value without + /// having to copy values. + /// The last fragment must be a [`Token::AttrValue`]. + /// + /// This is intended for writing to a token stream and may not be + /// emitted by readers or supported by [XIR Tree](self::tree). + AttrValueFragment(AttrValue, Span), + /// Comment node. Comment(Text, Span), diff --git a/tamer/src/ir/xir/writer.rs b/tamer/src/ir/xir/writer.rs index 502332cd..270e3788 100644 --- a/tamer/src/ir/xir/writer.rs +++ b/tamer/src/ir/xir/writer.rs @@ -89,8 +89,10 @@ pub enum WriterState { NodeExpected, /// A node is currently being output and has not yet been closed. NodeOpen, - /// Cursor is position adjacent to an attribute name within an element. + /// Cursor is adjacent to an attribute name within an element. AttrNameAdjacent, + /// Cursor is adjacent to an attribute fragment within an element. + AttrFragmentAdjacent, } impl Default for WriterState { @@ -217,6 +219,35 @@ impl XmlWriter for Token { Ok(S::NodeOpen) } + ( + Self::AttrValue(AttrValue::Escaped(value), _), + S::AttrFragmentAdjacent, + ) => { + sink.write(value.lookup_str().as_bytes())?; + sink.write(b"\"")?; + + Ok(S::NodeOpen) + } + + ( + Self::AttrValueFragment(AttrValue::Escaped(value), _), + S::AttrNameAdjacent, + ) => { + sink.write(b"=\"")?; + sink.write(value.lookup_str().as_bytes())?; + + Ok(S::AttrFragmentAdjacent) + } + + ( + Self::AttrValueFragment(AttrValue::Escaped(value), _), + S::AttrFragmentAdjacent, + ) => { + sink.write(value.lookup_str().as_bytes())?; + + Ok(S::AttrFragmentAdjacent) + } + // Unescaped not yet supported, but you could use CData. ( Self::Text(Text::Escaped(text), _), @@ -436,6 +467,24 @@ mod test { Ok(()) } + #[test] + fn writes_escaped_attr_value_consisting_of_fragments() -> TestResult { + let value_left = AttrValue::::Escaped("left ".intern()); + let value_right = AttrValue::::Escaped("right".intern()); + + let result = vec![ + Token::AttrValueFragment(value_left, *S), + Token::AttrValue(value_right, *S), + ] + .into_iter() + .write_new(WriterState::AttrNameAdjacent)?; + + assert_eq!(result.0, br#"="left right""#); + assert_eq!(result.1, WriterState::NodeOpen); + + Ok(()) + } + #[test] fn writes_escaped_text() -> TestResult { // Just to be sure it's not trying to escape when we say it