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