1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
use std::borrow::Cow;
use crate::CaseFoldMap;
use crate::arguments::Arguments;
use crate::element::{Action, AtomicTag, Element, ElementFlag};
use crate::elements::Var;
use crate::entity::{DecodedEntity, EntityEntry, EntityMap, PublishedIter};
use crate::line::{LineTag, LineTags, Mode};
use crate::node::{
AttributeListDefinition, Definition, ElementDefinition, EntityDefinition, LineTagDefinition,
};
use crate::parse::Decoder;
use crate::{Error, ErrorKind};
/// A store of MXP state: elements, entities, and line tags.
#[derive(Debug, Default)]
pub struct State {
elements: CaseFoldMap<'static, Element>,
entities: EntityMap,
line_tags: LineTags,
}
impl Clone for State {
#[inline]
fn clone(&self) -> Self {
Self {
elements: self.elements.clone(),
entities: self.entities.clone(),
line_tags: self.line_tags.clone(),
}
}
#[inline]
fn clone_from(&mut self, source: &Self) {
self.elements.clone_from(&source.elements);
self.entities.clone_from(&source.entities);
self.line_tags.clone_from(&source.line_tags);
}
}
impl State {
/// Constructs a new `State`.
///
/// Unlike `State::default()`, this function populates the state with elements and entities
/// defined by the MXP protocol specification, allocating memory in the process.
///
/// # Examples
///
/// ```
/// let state = mxp::State::with_globals();
/// ```
pub fn with_globals() -> Self {
let mut elements = CaseFoldMap::new();
elements.extend(Element::well_known());
Self {
elements,
entities: EntityMap::with_globals(),
line_tags: LineTags::new(),
}
}
/// Clears the state, removing all elements, entities, and line tags, except for predefined
/// globals.
pub fn clear(&mut self) {
self.elements.clear();
self.entities.clear();
self.line_tags.clear();
}
/// Alias for `self.entities().guard_global(name)`.
/// See [`EntityMap::guard_global`].
pub fn guard_global_entity(&self, name: &str) -> crate::Result<()> {
self.entities.guard_global(name)
}
/// Alias for `self.entities().is_global(name)`.
/// See [`EntityMap::is_global`].
pub fn is_global_entity(&self, name: &str) -> bool {
self.entities.is_global(name)
}
/// Borrows the map of defined MXP entities.
pub fn entities(&self) -> &EntityMap {
&self.entities
}
/// Mutably borrows the map of defined MXP entities.
pub fn entities_mut(&mut self) -> &mut EntityMap {
&mut self.entities
}
/// Alias for `self.entities().get(name)`.
/// See [`EntityMap::get`].
pub fn get_entity(&self, name: &str) -> Option<&str> {
self.entities.get(name)
}
/// Applies a [`<VAR>`] action, using the specified `value` which is the text that was sent by
/// the server in between the opening and closing tag (e.g. `<VAR Hp>value</VAR>`). Note that
/// if [`var.keywords`] contains [`EntityKeyword::Delete`], or if it contains
/// [`EntityKeyword::Remove`] and `value` was the only value in the entity's list, this will set
/// the entity to `None`.
///
/// Returns an error if the name is associated with a global XML entity, since those cannot be
/// changed. Returns `None` if the entity's [`visibility`] is [`EntityVisibility::Private`],
/// because private entities are hidden from the client. Otherwise, returns an `EntityEntry`
/// whose [`value`] is `Some` if the entity was inserted or updated, and `None` if it was
/// removed. As with [`define`], the client can use this to keep track of entity updates,
/// especially if the entity has [`EntityVisibility::Publish`].
///
/// [`<VAR>`]: Var
/// [`var.keywords`]: [`Var::keywords`]
/// [`EntityKeyword::Delete`]: crate::keyword::EntityKeyword::Delete
/// [`EntityKeyword::Remove`]: crate::keyword::EntityKeyword::Remove
/// [`visibility`]: crate::entity::Entity::visibility
/// [`EntityVisibility::Private`]: crate::entity::EntityVisibility::Private
/// [`value`]: EntityEntry::value
/// [`define`]: Self::define
/// [`EntityVisibility::Publish`]: crate::entity::EntityVisibility::Publish
pub fn set_entity<'a, S: AsRef<str>>(
&'a mut self,
var: &Var<S>,
value: &str,
) -> crate::Result<Option<EntityEntry<'a>>> {
let entity = self.entities.define(var.with_value(value))?;
Ok(EntityEntry::new(entity))
}
/// Alias for `self.entities().published()`.
/// See [`EntityMap::published`].
pub fn published_entities(&self) -> PublishedIter<'_> {
self.entities.published()
}
/// Retrieves a tag or element by name. Returns an error if no tag or element is defined by
/// that name, or if the tag or element is not OPEN (see [`Component::is_open`]) and `secure`
/// is false.
pub fn get_component(&self, name: &str, secure: bool) -> crate::Result<Component<'_>> {
let component = if let Some(custom) = self.elements.get(name) {
Component::Element(custom)
} else if let Some(tag) = AtomicTag::well_known(name) {
Component::AtomicTag(tag)
} else {
return Err(Error::new(name, ErrorKind::UnknownElement));
};
if !secure && !component.is_open() {
return Err(Error::new(name, ErrorKind::UnsecuredElement));
}
Ok(component)
}
/// Retrieves the element associated with a line tag for a specified mode, if one exists.
pub fn get_line_tag(&self, mode: Mode) -> Option<LineTag<'_>> {
self.line_tags.get(usize::from(mode.0), &self.elements)
}
/// Returns the number of custom MXP elements that have been stored.
pub fn custom_elements_len(&self) -> usize {
self.elements.len()
}
/// Returns the number of custom MXP entities that have been stored.
/// Alias for `self.entities().len()`.
/// See [`EntityMap::len`].
pub fn custom_entities_len(&self) -> usize {
self.entities.len()
}
/// Decodes the value of an entity.
/// Alias for `self.entities().decode(name)`.
/// See [`EntityMap::decode`].
pub fn decode_entity(&self, name: &str) -> crate::Result<DecodedEntity<'_>> {
self.entities.decode(name)
}
/// Decodes the action of a predefined tag.
pub fn decode_tag<'a>(
&self,
tag: &AtomicTag,
args: &'a Arguments<'a>,
) -> crate::Result<Action<Cow<'a, str>>> {
tag.decode(args, self)
}
/// Handles an MXP definition from the server, which may define an [attribute list], [element],
/// [entity], or [line tag].
///
/// Returns an [`EntityEntry`] if the operation alters the definition of an entity. The client
/// can use this to keep track of entity updates, especially if the entity has
/// [`EntityVisibility::Publish`].
///
/// [attribute list]: https://www.zuggsoft.com/zmud/mxp.htm#ATTLIST
/// [element]: https://www.zuggsoft.com/zmud/mxp.htm#ELEMENT
/// [entity]: https://www.zuggsoft.com/zmud/mxp.htm#ENTITY
/// [line tag]: https://www.zuggsoft.com/zmud/mxp.htm#User-defined%20Line%20Tags
/// [`EntityVisibility::Publish`]: crate::entity::EntityVisibility::Publish
pub fn define<'a>(
&'a mut self,
definition: Definition,
) -> crate::Result<Option<EntityEntry<'a>>> {
match definition {
Definition::AttributeList(def) => self.define_attributes(&def)?,
Definition::Element(def) => self.define_element(def),
Definition::Entity(def) => return self.define_entity(def),
Definition::LineTag(def) => self.define_line_tag(def)?,
}
Ok(None)
}
fn define_attributes(&mut self, definition: &AttributeListDefinition) -> crate::Result<()> {
self.elements
.get_mut(definition.name)
.ok_or_else(|| Error::new(definition.name, ErrorKind::UnknownElementInAttlist))?
.attributes
.append(definition.attributes)
}
fn define_element(&mut self, definition: ElementDefinition) {
let Some(el) = definition.element else {
self.elements.remove(definition.name);
return;
};
if let Some(tag) = el.line_tag {
self.line_tags.set(tag.0.into(), el.name.clone());
}
self.elements.insert(el.name.clone(), el);
}
fn define_entity<'a>(
&'a mut self,
definition: EntityDefinition,
) -> crate::Result<Option<EntityEntry<'a>>> {
let EntityDefinition {
name,
desc,
value,
keywords,
} = definition;
let desc = match desc {
Some(desc) => Some(self.decode_string(desc)?),
None => None,
};
let value = self.decode_string(value)?;
let entity = self.entities.define(EntityDefinition {
name,
desc: desc.as_deref(),
value: &value,
keywords,
})?;
Ok(EntityEntry::new(entity))
}
fn define_line_tag(&mut self, definition: LineTagDefinition) -> crate::Result<()> {
self.line_tags.update(definition)
}
}
impl Decoder for State {
fn get_entity(&self, name: &str) -> Option<&str> {
self.entities.get_entity(name)
}
}
/// This struct is created by [`State::get_component`]. See its documentation for more.
#[derive(Copy, Clone, Debug)]
pub enum Component<'a> {
/// A built-in MXP tag.
AtomicTag(&'static AtomicTag),
/// A user-defined custom tag element.
Element(&'a Element),
}
impl Component<'_> {
/// Returns the name of the component.
///
/// For example, the name of `<SOUND "ouch.wav">` is `"SOUND"`.
pub const fn name(&self) -> &str {
match self {
Self::AtomicTag(tag) => tag.name,
Self::Element(el) => el.name.as_str(),
}
}
/// Returns `true` if the element has no closing tag, e.g. `<BR>`.
pub const fn is_command(&self) -> bool {
match self {
Self::AtomicTag(tag) => tag.action.is_command(),
Self::Element(el) => el.empty,
}
}
/// Returns `true` if the element is in OPEN mode, meaning users can override it.
pub const fn is_open(&self) -> bool {
match self {
Self::AtomicTag(tag) => tag.action.is_open(),
Self::Element(el) => el.open,
}
}
/// Returns the element's flag, if it has one.
pub const fn flag(&self) -> Option<&ElementFlag> {
match self {
Self::AtomicTag(_) => None,
Self::Element(el) => el.flag.as_ref(),
}
}
}