oxjsonld/from_rdf.rs
1#[cfg(feature = "async-tokio")]
2use json_event_parser::TokioAsyncWriterJsonSerializer;
3use json_event_parser::{JsonEvent, WriterJsonSerializer};
4use oxiri::{Iri, IriParseError};
5use oxrdf::vocab::xsd;
6use oxrdf::{
7 GraphName, GraphNameRef, NamedNode, NamedOrBlankNodeRef, QuadRef, Subject, SubjectRef, TermRef,
8};
9use std::borrow::Cow;
10use std::collections::{BTreeMap, BTreeSet};
11use std::io;
12use std::io::Write;
13#[cfg(feature = "async-tokio")]
14use tokio::io::AsyncWrite;
15
16/// A [JSON-LD](https://www.w3.org/TR/json-ld/) serializer.
17///
18/// Returns [Streaming JSON-LD](https://www.w3.org/TR/json-ld11-streaming/).
19///
20/// It does not implement exactly the [RDF as JSON-LD Algorithm](https://www.w3.org/TR/json-ld-api/#serialize-rdf-as-json-ld-algorithm)
21/// to be a streaming serializer but aims at being close to it.
22/// Features like `@json` and `@list` generation are not implemented.
23///
24/// ```
25/// use oxrdf::{GraphNameRef, LiteralRef, NamedNodeRef, QuadRef};
26/// use oxrdf::vocab::rdf;
27/// use oxjsonld::JsonLdSerializer;
28///
29/// let mut serializer = JsonLdSerializer::new().with_prefix("schema", "http://schema.org/")?.for_writer(Vec::new());
30/// serializer.serialize_quad(QuadRef::new(
31/// NamedNodeRef::new("http://example.com#me")?,
32/// rdf::TYPE,
33/// NamedNodeRef::new("http://schema.org/Person")?,
34/// GraphNameRef::DefaultGraph
35/// ))?;
36/// serializer.serialize_quad(QuadRef::new(
37/// NamedNodeRef::new("http://example.com#me")?,
38/// NamedNodeRef::new("http://schema.org/name")?,
39/// LiteralRef::new_language_tagged_literal_unchecked("Foo Bar", "en"),
40/// GraphNameRef::DefaultGraph
41/// ))?;
42/// assert_eq!(
43/// b"{\"@context\":{\"schema\":\"http://schema.org/\"},\"@graph\":[{\"@id\":\"http://example.com#me\",\"http://www.w3.org/1999/02/22-rdf-syntax-ns#type\":[{\"@id\":\"http://schema.org/Person\"}],\"http://schema.org/name\":[{\"@language\":\"en\",\"@value\":\"Foo Bar\"}]}]}",
44/// serializer.finish()?.as_slice()
45/// );
46/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
47/// ```
48#[derive(Default, Clone)]
49#[must_use]
50pub struct JsonLdSerializer {
51 prefixes: BTreeMap<String, String>,
52 base_iri: Option<Iri<String>>,
53}
54
55impl JsonLdSerializer {
56 /// Builds a new [`JsonLdSerializer`].
57 #[inline]
58 pub fn new() -> Self {
59 Self {
60 prefixes: BTreeMap::new(),
61 base_iri: None,
62 }
63 }
64
65 #[inline]
66 pub fn with_prefix(
67 mut self,
68 prefix_name: impl Into<String>,
69 prefix_iri: impl Into<String>,
70 ) -> Result<Self, IriParseError> {
71 self.prefixes.insert(
72 prefix_name.into(),
73 Iri::parse(prefix_iri.into())?.into_inner(),
74 );
75 Ok(self)
76 }
77
78 /// Allows to set the base IRI for serialization.
79 ///
80 /// Corresponds to the [`base` option from the algorithm specification](https://www.w3.org/TR/json-ld-api/#dom-jsonldoptions-base).
81 /// ```
82 /// use oxrdf::{GraphNameRef, NamedNodeRef, QuadRef};
83 /// use oxjsonld::JsonLdSerializer;
84 ///
85 /// let mut serializer = JsonLdSerializer::new()
86 /// .with_base_iri("http://example.com")?
87 /// .with_prefix("ex", "http://example.com/ns#")?
88 /// .for_writer(Vec::new());
89 /// serializer.serialize_quad(QuadRef::new(
90 /// NamedNodeRef::new("http://example.com#me")?,
91 /// NamedNodeRef::new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")?,
92 /// NamedNodeRef::new("http://example.com/ns#Person")?,
93 /// GraphNameRef::DefaultGraph
94 /// ))?;
95 /// serializer.serialize_quad(QuadRef::new(
96 /// NamedNodeRef::new("http://example.com#me")?,
97 /// NamedNodeRef::new("http://example.com/ns#parent")?,
98 /// NamedNodeRef::new("http://example.com#other")?,
99 /// GraphNameRef::DefaultGraph
100 /// ))?;
101 /// assert_eq!(
102 /// b"{\"@context\":{\"@base\":\"http://example.com\",\"ex\":\"http://example.com/ns#\"},\"@graph\":[{\"@id\":\"#me\",\"http://www.w3.org/1999/02/22-rdf-syntax-ns#type\":[{\"@id\":\"/ns#Person\"}],\"http://example.com/ns#parent\":[{\"@id\":\"#other\"}]}]}",
103 /// serializer.finish()?.as_slice()
104 /// );
105 /// # Result::<_,Box<dyn std::error::Error>>::Ok(())
106 /// ```
107 #[inline]
108 pub fn with_base_iri(mut self, base_iri: impl Into<String>) -> Result<Self, IriParseError> {
109 self.base_iri = Some(Iri::parse(base_iri.into())?);
110 Ok(self)
111 }
112
113 /// Serializes a JSON-LD file to a [`Write`] implementation.
114 ///
115 /// This writer does unbuffered writes.
116 ///
117 /// ```
118 /// use oxrdf::{GraphNameRef, LiteralRef, NamedNodeRef, QuadRef};
119 /// use oxrdf::vocab::rdf;
120 /// use oxjsonld::JsonLdSerializer;
121 ///
122 /// let mut serializer = JsonLdSerializer::new().with_prefix("schema", "http://schema.org/")?.for_writer(Vec::new());
123 /// serializer.serialize_quad(QuadRef::new(
124 /// NamedNodeRef::new("http://example.com#me")?,
125 /// rdf::TYPE,
126 /// NamedNodeRef::new("http://schema.org/Person")?,
127 /// GraphNameRef::DefaultGraph
128 /// ))?;
129 /// serializer.serialize_quad(QuadRef::new(
130 /// NamedNodeRef::new("http://example.com#me")?,
131 /// NamedNodeRef::new("http://schema.org/name")?,
132 /// LiteralRef::new_language_tagged_literal_unchecked("Foo Bar", "en"),
133 /// GraphNameRef::DefaultGraph
134 /// ))?;
135 /// assert_eq!(
136 /// b"{\"@context\":{\"schema\":\"http://schema.org/\"},\"@graph\":[{\"@id\":\"http://example.com#me\",\"http://www.w3.org/1999/02/22-rdf-syntax-ns#type\":[{\"@id\":\"http://schema.org/Person\"}],\"http://schema.org/name\":[{\"@language\":\"en\",\"@value\":\"Foo Bar\"}]}]}",
137 /// serializer.finish()?.as_slice()
138 /// );
139 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
140 /// ```
141 #[allow(clippy::unused_self)]
142 pub fn for_writer<W: Write>(self, writer: W) -> WriterJsonLdSerializer<W> {
143 WriterJsonLdSerializer {
144 writer: WriterJsonSerializer::new(writer),
145 inner: self.inner_writer(),
146 }
147 }
148
149 /// Serializes a JSON-LD file to a [`AsyncWrite`] implementation.
150 ///
151 /// This writer does unbuffered writes.
152 ///
153 /// ```
154 /// # #[tokio::main(flavor = "current_thread")]
155 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
156 /// use oxrdf::{NamedNodeRef, QuadRef, LiteralRef, GraphNameRef};
157 /// use oxrdf::vocab::rdf;
158 /// use oxjsonld::JsonLdSerializer;
159 ///
160 /// let mut serializer = JsonLdSerializer::new().with_prefix("schema", "http://schema.org/")?.for_tokio_async_writer(Vec::new());
161 /// serializer.serialize_quad(QuadRef::new(
162 /// NamedNodeRef::new("http://example.com#me")?,
163 /// rdf::TYPE,
164 /// NamedNodeRef::new("http://schema.org/Person")?,
165 /// GraphNameRef::DefaultGraph
166 /// )).await?;
167 /// serializer.serialize_quad(QuadRef::new(
168 /// NamedNodeRef::new("http://example.com#me")?,
169 /// NamedNodeRef::new("http://schema.org/name")?,
170 /// LiteralRef::new_language_tagged_literal_unchecked("Foo Bar", "en"),
171 /// GraphNameRef::DefaultGraph
172 /// )).await?;
173 /// assert_eq!(
174 /// b"{\"@context\":{\"schema\":\"http://schema.org/\"},\"@graph\":[{\"@id\":\"http://example.com#me\",\"http://www.w3.org/1999/02/22-rdf-syntax-ns#type\":[{\"@id\":\"http://schema.org/Person\"}],\"http://schema.org/name\":[{\"@language\":\"en\",\"@value\":\"Foo Bar\"}]}]}",
175 /// serializer.finish().await?.as_slice()
176 /// );
177 /// # Ok(())
178 /// # }
179 /// ```
180 #[allow(clippy::unused_self)]
181 #[cfg(feature = "async-tokio")]
182 pub fn for_tokio_async_writer<W: AsyncWrite + Unpin>(
183 self,
184 writer: W,
185 ) -> TokioAsyncWriterJsonLdSerializer<W> {
186 TokioAsyncWriterJsonLdSerializer {
187 writer: TokioAsyncWriterJsonSerializer::new(writer),
188 inner: self.inner_writer(),
189 }
190 }
191
192 fn inner_writer(self) -> InnerJsonLdWriter {
193 InnerJsonLdWriter {
194 started: false,
195 current_graph_name: None,
196 current_subject: None,
197 current_predicate: None,
198 emitted_predicates: BTreeSet::new(),
199 prefixes: self.prefixes,
200 base_iri: self.base_iri,
201 }
202 }
203}
204
205/// Serializes a JSON-LD file to a [`Write`] implementation.
206///
207/// Can be built using [`JsonLdSerializer::for_writer`].
208///
209/// ```
210/// use oxrdf::{GraphNameRef, LiteralRef, NamedNodeRef, QuadRef};
211/// use oxrdf::vocab::rdf;
212/// use oxjsonld::JsonLdSerializer;
213///
214/// let mut serializer = JsonLdSerializer::new().with_prefix("schema", "http://schema.org/")?.for_writer(Vec::new());
215/// serializer.serialize_quad(QuadRef::new(
216/// NamedNodeRef::new("http://example.com#me")?,
217/// rdf::TYPE,
218/// NamedNodeRef::new("http://schema.org/Person")?,
219/// GraphNameRef::DefaultGraph
220/// ))?;
221/// serializer.serialize_quad(QuadRef::new(
222/// NamedNodeRef::new("http://example.com#me")?,
223/// NamedNodeRef::new("http://schema.org/name")?,
224/// LiteralRef::new_language_tagged_literal_unchecked("Foo Bar", "en"),
225/// GraphNameRef::DefaultGraph
226/// ))?;
227/// assert_eq!(
228/// b"{\"@context\":{\"schema\":\"http://schema.org/\"},\"@graph\":[{\"@id\":\"http://example.com#me\",\"http://www.w3.org/1999/02/22-rdf-syntax-ns#type\":[{\"@id\":\"http://schema.org/Person\"}],\"http://schema.org/name\":[{\"@language\":\"en\",\"@value\":\"Foo Bar\"}]}]}",
229/// serializer.finish()?.as_slice()
230/// );
231/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
232/// ```
233#[must_use]
234pub struct WriterJsonLdSerializer<W: Write> {
235 writer: WriterJsonSerializer<W>,
236 inner: InnerJsonLdWriter,
237}
238
239impl<W: Write> WriterJsonLdSerializer<W> {
240 /// Serializes an extra quad.
241 pub fn serialize_quad<'a>(&mut self, t: impl Into<QuadRef<'a>>) -> io::Result<()> {
242 let mut buffer = Vec::new();
243 self.inner.serialize_quad(t, &mut buffer)?;
244 self.flush_buffer(&mut buffer)
245 }
246
247 /// Ends the write process and returns the underlying [`Write`].
248 pub fn finish(mut self) -> io::Result<W> {
249 let mut buffer = Vec::new();
250 self.inner.finish(&mut buffer);
251 self.flush_buffer(&mut buffer)?;
252 self.writer.finish()
253 }
254
255 fn flush_buffer(&mut self, buffer: &mut Vec<JsonEvent<'_>>) -> io::Result<()> {
256 for event in buffer.drain(0..) {
257 self.writer.serialize_event(event)?;
258 }
259 Ok(())
260 }
261}
262
263/// Serializes a JSON-LD file to a [`AsyncWrite`] implementation.
264///
265/// Can be built using [`JsonLdSerializer::for_tokio_async_writer`].
266///
267/// ```
268/// # #[tokio::main(flavor = "current_thread")]
269/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
270/// use oxrdf::{NamedNodeRef, QuadRef, LiteralRef, GraphNameRef};
271/// use oxrdf::vocab::rdf;
272/// use oxjsonld::JsonLdSerializer;
273///
274/// let mut serializer = JsonLdSerializer::new().with_prefix("schema", "http://schema.org/")?.for_tokio_async_writer(Vec::new());
275/// serializer.serialize_quad(QuadRef::new(
276/// NamedNodeRef::new("http://example.com#me")?,
277/// rdf::TYPE,
278/// NamedNodeRef::new("http://schema.org/Person")?,
279/// GraphNameRef::DefaultGraph
280/// )).await?;
281/// serializer.serialize_quad(QuadRef::new(
282/// NamedNodeRef::new("http://example.com#me")?,
283/// NamedNodeRef::new("http://schema.org/name")?,
284/// LiteralRef::new_language_tagged_literal_unchecked("Foo Bar", "en"),
285/// GraphNameRef::DefaultGraph
286/// )).await?;
287/// assert_eq!(
288/// b"{\"@context\":{\"schema\":\"http://schema.org/\"},\"@graph\":[{\"@id\":\"http://example.com#me\",\"http://www.w3.org/1999/02/22-rdf-syntax-ns#type\":[{\"@id\":\"http://schema.org/Person\"}],\"http://schema.org/name\":[{\"@language\":\"en\",\"@value\":\"Foo Bar\"}]}]}",
289/// serializer.finish().await?.as_slice()
290/// );
291/// # Ok(())
292/// # }
293/// ```
294#[cfg(feature = "async-tokio")]
295#[must_use]
296pub struct TokioAsyncWriterJsonLdSerializer<W: AsyncWrite + Unpin> {
297 writer: TokioAsyncWriterJsonSerializer<W>,
298 inner: InnerJsonLdWriter,
299}
300
301#[cfg(feature = "async-tokio")]
302impl<W: AsyncWrite + Unpin> TokioAsyncWriterJsonLdSerializer<W> {
303 /// Serializes an extra quad.
304 pub async fn serialize_quad<'a>(&mut self, t: impl Into<QuadRef<'a>>) -> io::Result<()> {
305 let mut buffer = Vec::new();
306 self.inner.serialize_quad(t, &mut buffer)?;
307 self.flush_buffer(&mut buffer).await
308 }
309
310 /// Ends the write process and returns the underlying [`Write`].
311 pub async fn finish(mut self) -> io::Result<W> {
312 let mut buffer = Vec::new();
313 self.inner.finish(&mut buffer);
314 self.flush_buffer(&mut buffer).await?;
315 self.writer.finish()
316 }
317
318 async fn flush_buffer(&mut self, buffer: &mut Vec<JsonEvent<'_>>) -> io::Result<()> {
319 for event in buffer.drain(0..) {
320 self.writer.serialize_event(event).await?;
321 }
322 Ok(())
323 }
324}
325
326pub struct InnerJsonLdWriter {
327 started: bool,
328 current_graph_name: Option<GraphName>,
329 current_subject: Option<Subject>,
330 current_predicate: Option<NamedNode>,
331 emitted_predicates: BTreeSet<String>,
332 prefixes: BTreeMap<String, String>,
333 base_iri: Option<Iri<String>>,
334}
335
336impl InnerJsonLdWriter {
337 fn serialize_quad<'a>(
338 &mut self,
339 quad: impl Into<QuadRef<'a>>,
340 output: &mut Vec<JsonEvent<'a>>,
341 ) -> io::Result<()> {
342 if !self.started {
343 self.serialize_start(output);
344 self.started = true;
345 }
346
347 let quad = quad.into();
348 if self
349 .current_graph_name
350 .as_ref()
351 .is_some_and(|graph_name| graph_name.as_ref() != quad.graph_name)
352 {
353 output.push(JsonEvent::EndArray);
354 output.push(JsonEvent::EndObject);
355 if self
356 .current_graph_name
357 .as_ref()
358 .is_some_and(|g| !g.is_default_graph())
359 {
360 output.push(JsonEvent::EndArray);
361 output.push(JsonEvent::EndObject);
362 }
363 self.current_graph_name = None;
364 self.current_subject = None;
365 self.current_predicate = None;
366 self.emitted_predicates.clear();
367 } else if self
368 .current_subject
369 .as_ref()
370 .is_some_and(|subject| subject.as_ref() != quad.subject)
371 || self
372 .current_predicate
373 .as_ref()
374 .is_some_and(|predicate| predicate.as_ref() != quad.predicate)
375 && self.emitted_predicates.contains(quad.predicate.as_str())
376 {
377 output.push(JsonEvent::EndArray);
378 output.push(JsonEvent::EndObject);
379 self.current_subject = None;
380 self.emitted_predicates.clear();
381 self.current_predicate = None;
382 } else if self
383 .current_predicate
384 .as_ref()
385 .is_some_and(|predicate| predicate.as_ref() != quad.predicate)
386 {
387 output.push(JsonEvent::EndArray);
388 if let Some(current_predicate) = self.current_predicate.take() {
389 self.emitted_predicates
390 .insert(current_predicate.into_string());
391 }
392 }
393
394 if self.current_graph_name.is_none() {
395 if !quad.graph_name.is_default_graph() {
396 // We open a new graph name
397 output.push(JsonEvent::StartObject);
398 output.push(JsonEvent::ObjectKey("@id".into()));
399 output.push(JsonEvent::String(self.id_value(match quad.graph_name {
400 GraphNameRef::NamedNode(iri) => iri.into(),
401 GraphNameRef::BlankNode(bnode) => bnode.into(),
402 GraphNameRef::DefaultGraph => unreachable!(),
403 })));
404 output.push(JsonEvent::ObjectKey("@graph".into()));
405 output.push(JsonEvent::StartArray);
406 }
407 self.current_graph_name = Some(quad.graph_name.into_owned());
408 }
409
410 // We open a new subject block if useful (ie. new subject or already used predicate)
411 if self.current_subject.is_none() {
412 output.push(JsonEvent::StartObject);
413 output.push(JsonEvent::ObjectKey("@id".into()));
414 #[allow(clippy::match_wildcard_for_single_variants, unreachable_patterns)]
415 output.push(JsonEvent::String(self.id_value(match quad.subject {
416 SubjectRef::NamedNode(iri) => iri.into(),
417 SubjectRef::BlankNode(bnode) => bnode.into(),
418 _ => {
419 return Err(io::Error::new(
420 io::ErrorKind::InvalidInput,
421 "JSON-LD does not support RDF-star yet",
422 ))
423 }
424 })));
425 self.current_subject = Some(quad.subject.into_owned());
426 }
427
428 // We open a predicate key
429 if self.current_predicate.is_none() {
430 output.push(JsonEvent::ObjectKey(
431 // TODO: use @type
432 quad.predicate.as_str().into(), // TODO: prefixes including @vocab
433 ));
434 output.push(JsonEvent::StartArray);
435 self.current_predicate = Some(quad.predicate.into_owned());
436 }
437
438 self.serialize_term(quad.object, output)
439 }
440
441 fn serialize_start(&self, output: &mut Vec<JsonEvent<'_>>) {
442 if self.base_iri.is_some() || !self.prefixes.is_empty() {
443 output.push(JsonEvent::StartObject);
444 output.push(JsonEvent::ObjectKey("@context".into()));
445 output.push(JsonEvent::StartObject);
446 if let Some(base_iri) = &self.base_iri {
447 output.push(JsonEvent::ObjectKey("@base".into()));
448 output.push(JsonEvent::String(base_iri.to_string().into()));
449 }
450 for (prefix_name, prefix_iri) in &self.prefixes {
451 output.push(JsonEvent::ObjectKey(if prefix_name.is_empty() {
452 "@vocab".into()
453 } else {
454 prefix_name.clone().into()
455 }));
456 output.push(JsonEvent::String(prefix_iri.clone().into()));
457 }
458 output.push(JsonEvent::EndObject);
459 output.push(JsonEvent::ObjectKey("@graph".into()));
460 }
461 output.push(JsonEvent::StartArray);
462 }
463
464 fn serialize_term<'a>(
465 &self,
466 term: TermRef<'a>,
467 output: &mut Vec<JsonEvent<'a>>,
468 ) -> io::Result<()> {
469 output.push(JsonEvent::StartObject);
470 #[allow(clippy::match_wildcard_for_single_variants, unreachable_patterns)]
471 match term {
472 TermRef::NamedNode(iri) => {
473 output.push(JsonEvent::ObjectKey("@id".into()));
474 output.push(JsonEvent::String(self.id_value(iri.into())));
475 }
476 TermRef::BlankNode(bnode) => {
477 output.push(JsonEvent::ObjectKey("@id".into()));
478 output.push(JsonEvent::String(self.id_value(bnode.into())));
479 }
480 TermRef::Literal(literal) => {
481 if let Some(language) = literal.language() {
482 output.push(JsonEvent::ObjectKey("@language".into()));
483 output.push(JsonEvent::String(language.into()));
484 } else if literal.datatype() != xsd::STRING {
485 output.push(JsonEvent::ObjectKey("@type".into()));
486 output.push(JsonEvent::String(Self::type_value(
487 literal.datatype().into(),
488 )));
489 }
490 output.push(JsonEvent::ObjectKey("@value".into()));
491 output.push(JsonEvent::String(literal.value().into()));
492 }
493 _ => {
494 return Err(io::Error::new(
495 io::ErrorKind::InvalidInput,
496 "JSON-LD does not support RDF-star yet",
497 ))
498 }
499 }
500 output.push(JsonEvent::EndObject);
501 Ok(())
502 }
503
504 fn id_value<'a>(&self, id: NamedOrBlankNodeRef<'a>) -> Cow<'a, str> {
505 match id {
506 NamedOrBlankNodeRef::NamedNode(iri) => {
507 if let Some(base_iri) = &self.base_iri {
508 if let Ok(relative) = base_iri.relativize(&Iri::parse_unchecked(iri.as_str())) {
509 let relative = relative.into_inner();
510 // We check the relative IRI is not considered as absolute by IRI expansion
511 if !relative.split_once(':').is_some_and(|(prefix, suffix)| {
512 prefix == "_" || suffix.starts_with("//")
513 }) {
514 return relative.into();
515 }
516 }
517 }
518 iri.as_str().into()
519 }
520 NamedOrBlankNodeRef::BlankNode(bnode) => bnode.to_string().into(),
521 }
522 }
523
524 fn type_value(id: NamedOrBlankNodeRef<'_>) -> Cow<'_, str> {
525 match id {
526 NamedOrBlankNodeRef::NamedNode(iri) => iri.as_str().into(),
527 NamedOrBlankNodeRef::BlankNode(bnode) => bnode.to_string().into(),
528 }
529 }
530
531 fn finish(&mut self, output: &mut Vec<JsonEvent<'static>>) {
532 if !self.started {
533 self.serialize_start(output);
534 }
535 if self.current_predicate.is_some() {
536 output.push(JsonEvent::EndArray)
537 }
538 if self.current_subject.is_some() {
539 output.push(JsonEvent::EndObject)
540 }
541 if self
542 .current_graph_name
543 .as_ref()
544 .is_some_and(|g| !g.is_default_graph())
545 {
546 output.push(JsonEvent::EndArray);
547 output.push(JsonEvent::EndObject)
548 }
549 output.push(JsonEvent::EndArray);
550 if self.base_iri.is_some() || !self.prefixes.is_empty() {
551 output.push(JsonEvent::EndObject);
552 }
553 }
554}