1use std::collections::HashMap;
5use std::io::Write;
6
7use crate::{AbxError, Attribute, AttributeValue, Event, InternedStr, MAGIC, Result};
8use crate::{
9 CMD_ATTRIBUTE, CMD_CDSECT, CMD_COMMENT, CMD_DOCDECL, CMD_END_DOCUMENT, CMD_END_TAG,
10 CMD_ENTITY_REF, CMD_IGNORABLE_WHITESPACE, CMD_PROCESSING_INSTRUCTION, CMD_START_DOCUMENT,
11 CMD_START_TAG, CMD_TEXT,
12};
13use crate::{
14 INTERNED_NEW, TYPE_BOOLEAN_FALSE, TYPE_BOOLEAN_TRUE, TYPE_BYTES_BASE64, TYPE_BYTES_HEX,
15 TYPE_DOUBLE, TYPE_FLOAT, TYPE_INT, TYPE_INT_HEX, TYPE_LONG, TYPE_LONG_HEX, TYPE_NULL,
16 TYPE_STRING, TYPE_STRING_INTERNED,
17};
18
19const LINEAR_SCAN_LIMIT: usize = 32;
30
31const MAX_UNSIGNED_SHORT: usize = 65_535;
36
37struct InternedPool {
38 names: Vec<InternedStr>,
39 index: Option<HashMap<InternedStr, u16>>,
40}
41
42impl InternedPool {
43 fn new() -> Self {
44 InternedPool {
45 names: Vec::new(),
46 index: None,
47 }
48 }
49
50 fn find(&self, s: &InternedStr) -> Option<u16> {
51 match &self.index {
52 Some(index) => index.get(s).copied(),
53 None => self.names.iter().position(|n| n == s).map(|i| i as u16),
54 }
55 }
56
57 fn write(&mut self, out: &mut impl Write, s: &InternedStr) -> Result<()> {
58 if let Some(idx) = self.find(s) {
59 out.write_all(&idx.to_be_bytes())?;
60 } else {
61 out.write_all(&INTERNED_NEW.to_be_bytes())?;
62 write_utf(out, s)?;
63
64 if self.names.len() < INTERNED_NEW as usize {
69 let idx = self.names.len() as u16;
70 self.names.push(s.clone());
71 if let Some(index) = &mut self.index {
72 index.insert(s.clone(), idx);
73 } else if self.names.len() > LINEAR_SCAN_LIMIT {
74 self.index = Some(self.names.iter().cloned().zip(0u16..).collect());
75 }
76 }
77 }
78 Ok(())
79 }
80}
81
82fn write_utf(out: &mut impl Write, s: &str) -> Result<()> {
87 let bytes = s.as_bytes();
88 if bytes.len() > MAX_UNSIGNED_SHORT {
89 return Err(AbxError::ValueTooLong {
90 len: bytes.len(),
91 max: MAX_UNSIGNED_SHORT,
92 });
93 }
94 out.write_all(&(bytes.len() as u16).to_be_bytes())?;
95 out.write_all(bytes)?;
96 Ok(())
97}
98
99fn write_bytes_blob(out: &mut impl Write, bytes: &[u8]) -> Result<()> {
103 if bytes.len() > MAX_UNSIGNED_SHORT {
104 return Err(AbxError::ValueTooLong {
105 len: bytes.len(),
106 max: MAX_UNSIGNED_SHORT,
107 });
108 }
109 out.write_all(&(bytes.len() as u16).to_be_bytes())?;
110 out.write_all(bytes)?;
111 Ok(())
112}
113
114pub struct AbxWriter<W: Write> {
119 writer: W,
120 pool: InternedPool,
121}
122
123impl<W: Write> AbxWriter<W> {
124 pub fn new(mut writer: W) -> Result<Self> {
126 writer.write_all(&MAGIC)?;
127 Ok(AbxWriter {
128 writer,
129 pool: InternedPool::new(),
130 })
131 }
132
133 pub fn write_event(&mut self, ev: &Event) -> Result<()> {
135 match ev {
136 Event::StartDocument => self.writer.write_all(&[CMD_START_DOCUMENT | TYPE_NULL])?,
137 Event::EndDocument => self.writer.write_all(&[CMD_END_DOCUMENT | TYPE_NULL])?,
138 Event::StartTag { name, attributes } => {
139 self.writer
140 .write_all(&[TYPE_STRING_INTERNED | CMD_START_TAG])?;
141 self.pool.write(&mut self.writer, name)?;
142 for attr in attributes {
143 self.write_attribute(attr)?;
144 }
145 }
146 Event::EndTag { name } => {
147 self.writer
148 .write_all(&[TYPE_STRING_INTERNED | CMD_END_TAG])?;
149 self.pool.write(&mut self.writer, name)?;
150 }
151 Event::Text(s) => self.write_text_token(CMD_TEXT, s)?,
152 Event::CdataSection(s) => self.write_text_token(CMD_CDSECT, s)?,
153 Event::Comment(s) => self.write_text_token(CMD_COMMENT, s)?,
154 Event::ProcessingInstruction(s) => {
155 self.write_text_token(CMD_PROCESSING_INSTRUCTION, s)?
156 }
157 Event::EntityReference(s) => self.write_text_token(CMD_ENTITY_REF, s)?,
158 Event::IgnorableWhitespace(s) => self.write_text_token(CMD_IGNORABLE_WHITESPACE, s)?,
159 Event::DocDecl(s) => self.write_text_token(CMD_DOCDECL, s)?,
160 }
161 Ok(())
162 }
163
164 fn write_text_token(&mut self, cmd: u8, s: &str) -> Result<()> {
171 self.writer.write_all(&[TYPE_STRING | cmd])?;
172 write_utf(&mut self.writer, s)?;
173 Ok(())
174 }
175
176 fn write_attribute(&mut self, attr: &Attribute) -> Result<()> {
180 let type_nibble = match &attr.value {
181 AttributeValue::Null => TYPE_NULL,
182 AttributeValue::String(_) => TYPE_STRING,
183 AttributeValue::BytesHex(_) => TYPE_BYTES_HEX,
184 AttributeValue::BytesBase64(_) => TYPE_BYTES_BASE64,
185 AttributeValue::Int(_) => TYPE_INT,
186 AttributeValue::IntHex(_) => TYPE_INT_HEX,
187 AttributeValue::Long(_) => TYPE_LONG,
188 AttributeValue::LongHex(_) => TYPE_LONG_HEX,
189 AttributeValue::Float(_) => TYPE_FLOAT,
190 AttributeValue::Double(_) => TYPE_DOUBLE,
191 AttributeValue::Boolean(true) => TYPE_BOOLEAN_TRUE,
192 AttributeValue::Boolean(false) => TYPE_BOOLEAN_FALSE,
193 };
194 self.writer.write_all(&[type_nibble | CMD_ATTRIBUTE])?;
195 self.pool.write(&mut self.writer, &attr.name)?;
196 match &attr.value {
197 AttributeValue::Null | AttributeValue::Boolean(_) => {}
198 AttributeValue::String(s) => write_utf(&mut self.writer, s)?,
199 AttributeValue::BytesHex(b) | AttributeValue::BytesBase64(b) => {
200 write_bytes_blob(&mut self.writer, b)?
201 }
202 AttributeValue::Int(v) => self.writer.write_all(&v.to_be_bytes())?,
203 AttributeValue::IntHex(v) => self.writer.write_all(&v.to_be_bytes())?,
204 AttributeValue::Long(v) => self.writer.write_all(&v.to_be_bytes())?,
205 AttributeValue::LongHex(v) => self.writer.write_all(&v.to_be_bytes())?,
206 AttributeValue::Float(v) => self.writer.write_all(&v.to_be_bytes())?,
207 AttributeValue::Double(v) => self.writer.write_all(&v.to_be_bytes())?,
208 }
209 Ok(())
210 }
211
212 pub fn into_inner(self) -> W {
214 self.writer
215 }
216}