rakata_formats/twoda/
writer.rs1use std::collections::HashMap;
4use std::io::{Cursor, Write};
5
6use rakata_core::{encode_text, TextEncoding};
7
8use super::{
9 binary::{write_u16, write_u32},
10 validate_twoda, TwoDa, TwoDaBinaryError, TwoDaBinaryOptions, TWODA_MAGIC, TWODA_VERSION_V2B,
11};
12
13#[cfg_attr(
20 feature = "tracing",
21 tracing::instrument(level = "debug", skip(writer, twoda))
22)]
23pub fn write_twoda<W: Write>(writer: &mut W, twoda: &TwoDa) -> Result<(), TwoDaBinaryError> {
24 write_twoda_with_options(writer, twoda, TwoDaBinaryOptions::default())
25}
26
27#[cfg_attr(
46 feature = "tracing",
47 tracing::instrument(level = "debug", skip(writer, twoda, options))
48)]
49pub fn write_twoda_with_options<W: Write>(
50 writer: &mut W,
51 twoda: &TwoDa,
52 options: TwoDaBinaryOptions,
53) -> Result<(), TwoDaBinaryError> {
54 validate_twoda(twoda)?;
55
56 writer.write_all(&TWODA_MAGIC)?;
57 writer.write_all(&TWODA_VERSION_V2B)?;
58 writer.write_all(b"\n")?;
59
60 for (index, header) in twoda.headers.iter().enumerate() {
61 let bytes =
62 encode_with_encoding(header, format!("header[{index}]"), options.text_encoding)?;
63 writer.write_all(&bytes)?;
64 writer.write_all(b"\t")?;
65 }
66 writer.write_all(&[0])?;
67
68 let row_count = u32::try_from(twoda.rows.len())
69 .map_err(|_| TwoDaBinaryError::ValueOverflow("row_count"))?;
70 write_u32(writer, row_count)?;
71
72 for (index, row) in twoda.rows.iter().enumerate() {
73 let bytes = encode_with_encoding(
74 &row.label,
75 format!("row label[{index}]"),
76 options.text_encoding,
77 )?;
78 writer.write_all(&bytes)?;
79 writer.write_all(b"\t")?;
80 }
81
82 let mut unique_values = Vec::<Vec<u8>>::new();
83 let mut offset_by_value = HashMap::<Vec<u8>, u16>::new();
84 let mut cell_offsets = Vec::<u16>::with_capacity(twoda.rows.len() * twoda.headers.len());
85 let mut data_size: usize = 0;
86
87 for (row_index, row) in twoda.rows.iter().enumerate() {
88 for (column_index, value) in row.cells.iter().enumerate() {
89 let mut encoded = encode_with_encoding(
90 value,
91 format!("cell[{row_index}][{column_index}]"),
92 options.text_encoding,
93 )?;
94 encoded.push(0);
95
96 let offset = if let Some(existing) = offset_by_value.get(&encoded) {
97 *existing
98 } else {
99 let offset = u16::try_from(data_size)
100 .map_err(|_| TwoDaBinaryError::ValueOverflow("cell_offset"))?;
101 data_size = data_size
102 .checked_add(encoded.len())
103 .ok_or(TwoDaBinaryError::ValueOverflow("cell_data_size"))?;
104 if data_size > usize::from(u16::MAX) {
105 return Err(TwoDaBinaryError::ValueOverflow("cell_data_size"));
106 }
107 offset_by_value.insert(encoded.clone(), offset);
108 unique_values.push(encoded);
109 offset
110 };
111 cell_offsets.push(offset);
112 }
113 }
114
115 for offset in &cell_offsets {
116 write_u16(writer, *offset)?;
117 }
118 let data_size_u16 =
119 u16::try_from(data_size).map_err(|_| TwoDaBinaryError::ValueOverflow("cell_data_size"))?;
120 write_u16(writer, data_size_u16)?;
121 for value in &unique_values {
122 writer.write_all(value)?;
123 }
124
125 Ok(())
126}
127
128#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(twoda)))]
134pub fn write_twoda_to_vec(twoda: &TwoDa) -> Result<Vec<u8>, TwoDaBinaryError> {
135 write_twoda_to_vec_with_options(twoda, TwoDaBinaryOptions::default())
136}
137
138#[cfg_attr(
145 feature = "tracing",
146 tracing::instrument(level = "debug", skip(twoda, options))
147)]
148pub fn write_twoda_to_vec_with_options(
149 twoda: &TwoDa,
150 options: TwoDaBinaryOptions,
151) -> Result<Vec<u8>, TwoDaBinaryError> {
152 let mut cursor = Cursor::new(Vec::new());
153 write_twoda_with_options(&mut cursor, twoda, options)?;
154 Ok(cursor.into_inner())
155}
156
157fn encode_with_encoding(
158 value: &str,
159 context: String,
160 encoding: TextEncoding,
161) -> Result<Vec<u8>, TwoDaBinaryError> {
162 encode_text(value, encoding)
163 .map_err(|source| TwoDaBinaryError::TextEncoding { context, source })
164}