oxjsonld/to_rdf.rs
1use crate::context::{
2 JsonLdLoadDocumentOptions, JsonLdProcessingMode, JsonLdRemoteDocument, JsonLdTermDefinition,
3};
4use crate::error::{JsonLdParseError, JsonLdSyntaxError};
5use crate::expansion::{JsonLdEvent, JsonLdExpansionConverter, JsonLdValue};
6use crate::{JsonLdProfile, JsonLdProfileSet};
7#[cfg(feature = "async-tokio")]
8use json_event_parser::TokioAsyncReaderJsonParser;
9use json_event_parser::{JsonEvent, ReaderJsonParser, SliceJsonParser};
10use oxiri::{Iri, IriParseError};
11use oxrdf::vocab::{rdf, xsd};
12use oxrdf::{BlankNode, GraphName, Literal, NamedNode, NamedNodeRef, NamedOrBlankNode, Quad};
13use std::error::Error;
14use std::fmt::Write;
15use std::io::Read;
16use std::panic::{RefUnwindSafe, UnwindSafe};
17use std::str;
18#[cfg(feature = "async-tokio")]
19use tokio::io::AsyncRead;
20
21/// A [JSON-LD](https://www.w3.org/TR/json-ld/) parser.
22///
23/// The parser is a work in progress.
24/// Only JSON-LD 1.0 is supported at the moment. JSON-LD 1.1 is not supported yet.
25///
26/// The parser supports two modes:
27/// - regular JSON-LD parsing that needs to buffer the full file into memory.
28/// - [Streaming JSON-LD](https://www.w3.org/TR/json-ld11-streaming/) that can avoid buffering in a few cases.
29/// To enable it call the [`with_profile(JsonLdProfile::Streaming)`](JsonLdParser::with_profile) method.
30///
31/// Count the number of people:
32/// ```
33/// use oxjsonld::JsonLdParser;
34/// use oxrdf::vocab::rdf;
35/// use oxrdf::NamedNodeRef;
36///
37/// let file = br#"{
38/// "@context": {"schema": "http://schema.org/"},
39/// "@graph": [
40/// {
41/// "@type": "schema:Person",
42/// "@id": "http://example.com/foo",
43/// "schema:name": "Foo"
44/// },
45/// {
46/// "@type": "schema:Person",
47/// "schema:name": "Bar"
48/// }
49/// ]
50/// }"#;
51///
52/// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
53/// let mut count = 0;
54/// for quad in JsonLdParser::new().for_reader(file.as_ref()) {
55/// let quad = quad?;
56/// if quad.predicate == rdf::TYPE && quad.object == schema_person.into() {
57/// count += 1;
58/// }
59/// }
60/// assert_eq!(2, count);
61/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
62/// ```
63#[derive(Default, Clone)]
64#[must_use]
65pub struct JsonLdParser {
66 lenient: bool,
67 profile: JsonLdProfileSet,
68 base: Option<Iri<String>>,
69}
70
71impl JsonLdParser {
72 /// Builds a new [`JsonLdParser`].
73 #[inline]
74 pub fn new() -> Self {
75 Self::default()
76 }
77
78 /// Assumes the file is valid to make parsing faster.
79 ///
80 /// It will skip some validations.
81 ///
82 /// Note that if the file is actually not valid, broken RDF might be emitted by the parser.
83 #[inline]
84 pub fn lenient(mut self) -> Self {
85 self.lenient = true;
86 self
87 }
88
89 /// Assume the given profile(s) during parsing.
90 ///
91 /// If you set the [Streaming JSON-LD](https://www.w3.org/TR/json-ld11-streaming/) profile ([`JsonLdProfile::Streaming`]),
92 /// the parser will skip some buffering to make parsing faster and memory consumption lower.
93 ///
94 /// ```
95 /// use oxjsonld::{JsonLdParser, JsonLdProfile};
96 /// use oxrdf::vocab::rdf;
97 /// use oxrdf::NamedNodeRef;
98 ///
99 /// let file = br#"{
100 /// "@context": {"schema": "http://schema.org/"},
101 /// "@graph": [
102 /// {
103 /// "@type": "schema:Person",
104 /// "@id": "http://example.com/foo",
105 /// "schema:name": "Foo"
106 /// }
107 /// ]
108 /// }"#;
109 ///
110 /// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
111 /// let mut count = 0;
112 /// for quad in JsonLdParser::new()
113 /// .with_profile(JsonLdProfile::Streaming)
114 /// .for_slice(file)
115 /// {
116 /// let quad = quad?;
117 /// if quad.predicate == rdf::TYPE && quad.object == schema_person.into() {
118 /// count += 1;
119 /// }
120 /// }
121 /// assert_eq!(1, count);
122 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
123 /// ```
124 #[inline]
125 pub fn with_profile(mut self, profile: impl Into<JsonLdProfileSet>) -> Self {
126 self.profile = profile.into();
127 self
128 }
129
130 /// Base IRI to use when expanding the document.
131 ///
132 /// It corresponds to the [`base` option from the algorithm specification](https://www.w3.org/TR/json-ld-api/#dom-jsonldoptions-base).
133 #[inline]
134 pub fn with_base_iri(mut self, base_iri: impl Into<String>) -> Result<Self, IriParseError> {
135 self.base = Some(Iri::parse(base_iri.into())?);
136 Ok(self)
137 }
138
139 /// Parses a JSON-LD file from a [`Read`] implementation.
140 ///
141 /// Count the number of people:
142 /// ```
143 /// use oxjsonld::JsonLdParser;
144 /// use oxrdf::vocab::rdf;
145 /// use oxrdf::NamedNodeRef;
146 ///
147 /// let file = br#"{
148 /// "@context": {"schema": "http://schema.org/"},
149 /// "@graph": [
150 /// {
151 /// "@type": "schema:Person",
152 /// "@id": "http://example.com/foo",
153 /// "schema:name": "Foo"
154 /// },
155 /// {
156 /// "@type": "schema:Person",
157 /// "schema:name": "Bar"
158 /// }
159 /// ]
160 /// }"#;
161 ///
162 /// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
163 /// let mut count = 0;
164 /// for quad in JsonLdParser::new().for_reader(file.as_ref()) {
165 /// let quad = quad?;
166 /// if quad.predicate == rdf::TYPE && quad.object == schema_person.into() {
167 /// count += 1;
168 /// }
169 /// }
170 /// assert_eq!(2, count);
171 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
172 /// ```
173 pub fn for_reader<R: Read>(self, reader: R) -> ReaderJsonLdParser<R> {
174 ReaderJsonLdParser {
175 results: Vec::new(),
176 errors: Vec::new(),
177 inner: self.into_inner(),
178 json_parser: ReaderJsonParser::new(reader),
179 }
180 }
181
182 /// Parses a JSON-LD file from a [`AsyncRead`] implementation.
183 ///
184 /// Count the number of people:
185 /// ```
186 /// # #[tokio::main(flavor = "current_thread")]
187 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
188 /// use oxjsonld::JsonLdParser;
189 /// use oxrdf::vocab::rdf;
190 /// use oxrdf::NamedNodeRef;
191 ///
192 /// let file = br#"{
193 /// "@context": {"schema": "http://schema.org/"},
194 /// "@graph": [
195 /// {
196 /// "@type": "schema:Person",
197 /// "@id": "http://example.com/foo",
198 /// "schema:name": "Foo"
199 /// },
200 /// {
201 /// "@type": "schema:Person",
202 /// "schema:name": "Bar"
203 /// }
204 /// ]
205 /// }"#;
206 ///
207 /// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
208 /// let mut count = 0;
209 /// let mut parser = JsonLdParser::new().for_tokio_async_reader(file.as_ref());
210 /// while let Some(quad) = parser.next().await {
211 /// let quad = quad?;
212 /// if quad.predicate == rdf::TYPE && quad.object == schema_person.into() {
213 /// count += 1;
214 /// }
215 /// }
216 /// assert_eq!(2, count);
217 /// # Ok(())
218 /// # }
219 /// ```
220 #[cfg(feature = "async-tokio")]
221 pub fn for_tokio_async_reader<R: AsyncRead + Unpin>(
222 self,
223 reader: R,
224 ) -> TokioAsyncReaderJsonLdParser<R> {
225 TokioAsyncReaderJsonLdParser {
226 results: Vec::new(),
227 errors: Vec::new(),
228 inner: self.into_inner(),
229 json_parser: TokioAsyncReaderJsonParser::new(reader),
230 }
231 }
232
233 /// Parses a JSON-LD file from a byte slice.
234 ///
235 /// Count the number of people:
236 /// ```
237 /// use oxjsonld::JsonLdParser;
238 /// use oxrdf::vocab::rdf;
239 /// use oxrdf::NamedNodeRef;
240 ///
241 /// let file = br#"{
242 /// "@context": {"schema": "http://schema.org/"},
243 /// "@graph": [
244 /// {
245 /// "@type": "schema:Person",
246 /// "@id": "http://example.com/foo",
247 /// "schema:name": "Foo"
248 /// },
249 /// {
250 /// "@type": "schema:Person",
251 /// "schema:name": "Bar"
252 /// }
253 /// ]
254 /// }"#;
255 ///
256 /// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
257 /// let mut count = 0;
258 /// for quad in JsonLdParser::new().for_slice(file) {
259 /// let quad = quad?;
260 /// if quad.predicate == rdf::TYPE && quad.object == schema_person.into() {
261 /// count += 1;
262 /// }
263 /// }
264 /// assert_eq!(2, count);
265 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
266 /// ```
267 pub fn for_slice(self, slice: &[u8]) -> SliceJsonLdParser<'_> {
268 SliceJsonLdParser {
269 results: Vec::new(),
270 errors: Vec::new(),
271 inner: self.into_inner(),
272 json_parser: SliceJsonParser::new(slice),
273 }
274 }
275
276 fn into_inner(self) -> InternalJsonLdParser {
277 InternalJsonLdParser {
278 expansion: JsonLdExpansionConverter::new(
279 self.base,
280 self.profile.contains(JsonLdProfile::Streaming),
281 self.lenient,
282 JsonLdProcessingMode::JsonLd1_0,
283 ),
284 expended_events: Vec::new(),
285 to_rdf: JsonLdToRdfConverter {
286 state: vec![JsonLdToRdfState::Graph(Some(GraphName::DefaultGraph))],
287 lenient: self.lenient,
288 },
289 json_error: false,
290 }
291 }
292}
293
294/// Parses a JSON-LD file from a [`Read`] implementation.
295///
296/// Can be built using [`JsonLdParser::for_reader`].
297///
298/// Count the number of people:
299/// ```
300/// use oxjsonld::JsonLdParser;
301/// use oxrdf::vocab::rdf;
302/// use oxrdf::NamedNodeRef;
303///
304/// let file = br#"{
305/// "@context": {"schema": "http://schema.org/"},
306/// "@graph": [
307/// {
308/// "@type": "schema:Person",
309/// "@id": "http://example.com/foo",
310/// "schema:name": "Foo"
311/// },
312/// {
313/// "@type": "schema:Person",
314/// "schema:name": "Bar"
315/// }
316/// ]
317/// }"#;
318///
319/// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
320/// let mut count = 0;
321/// for quad in JsonLdParser::new().for_reader(file.as_ref()) {
322/// let quad = quad?;
323/// if quad.predicate == rdf::TYPE && quad.object == schema_person.into() {
324/// count += 1;
325/// }
326/// }
327/// assert_eq!(2, count);
328/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
329/// ```
330#[must_use]
331pub struct ReaderJsonLdParser<R: Read> {
332 results: Vec<Quad>,
333 errors: Vec<JsonLdSyntaxError>,
334 inner: InternalJsonLdParser,
335 json_parser: ReaderJsonParser<R>,
336}
337
338impl<R: Read> Iterator for ReaderJsonLdParser<R> {
339 type Item = Result<Quad, JsonLdParseError>;
340
341 fn next(&mut self) -> Option<Self::Item> {
342 loop {
343 if let Some(error) = self.errors.pop() {
344 return Some(Err(error.into()));
345 } else if let Some(quad) = self.results.pop() {
346 return Some(Ok(quad));
347 } else if self.inner.is_end() {
348 return None;
349 }
350 let step = self.parse_step();
351 if let Err(e) = step {
352 return Some(Err(e));
353 }
354 // We make sure to have data in the right order
355 self.results.reverse();
356 self.errors.reverse();
357 }
358 }
359}
360
361impl<R: Read> ReaderJsonLdParser<R> {
362 /// Allows to set a callback to load remote document and contexts
363 ///
364 /// The first argument is the document URL.
365 ///
366 /// It corresponds to the [`documentLoader` option from the algorithm specification](https://www.w3.org/TR/json-ld11-api/#dom-jsonldoptions-documentloader).
367 ///
368 /// See [`LoadDocumentCallback` API documentation](https://www.w3.org/TR/json-ld-api/#loaddocumentcallback) for more details
369 ///
370 /// ```
371 /// use oxjsonld::{JsonLdParser, JsonLdRemoteDocument};
372 /// use oxrdf::vocab::rdf;
373 /// use oxrdf::NamedNodeRef;
374 ///
375 /// let file = br#"{
376 /// "@context": "file://context.jsonld",
377 /// "@type": "schema:Person",
378 /// "@id": "http://example.com/foo",
379 /// "schema:name": "Foo"
380 /// }"#;
381 ///
382 /// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
383 /// let mut count = 0;
384 /// for quad in JsonLdParser::new()
385 /// .for_reader(file.as_ref())
386 /// .with_load_document_callback(|url, _options| {
387 /// assert_eq!(url, "file://context.jsonld");
388 /// Ok(JsonLdRemoteDocument {
389 /// document: br#"{"@context":{"schema": "http://schema.org/"}}"#.to_vec(),
390 /// document_url: "file://context.jsonld".into(),
391 /// })
392 /// })
393 /// {
394 /// let quad = quad?;
395 /// if quad.predicate == rdf::TYPE && quad.object == schema_person.into() {
396 /// count += 1;
397 /// }
398 /// }
399 /// assert_eq!(1, count);
400 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
401 /// ```
402 pub fn with_load_document_callback(
403 mut self,
404 callback: impl Fn(
405 &str,
406 &JsonLdLoadDocumentOptions,
407 ) -> Result<JsonLdRemoteDocument, Box<dyn Error + Send + Sync>>
408 + Send
409 + Sync
410 + UnwindSafe
411 + RefUnwindSafe
412 + 'static,
413 ) -> Self {
414 self.inner.expansion = self.inner.expansion.with_load_document_callback(callback);
415 self
416 }
417
418 /// The list of IRI prefixes considered at the current step of the parsing.
419 ///
420 /// This method returns (prefix name, prefix value) tuples.
421 /// It is empty at the beginning of the parsing and gets updated when prefixes are encountered.
422 /// It should be full at the end of the parsing (but if a prefix is overridden, only the latest version will be returned).
423 ///
424 /// ```
425 /// use oxjsonld::JsonLdParser;
426 ///
427 /// let file = br#"{
428 /// "@context": {"schema": "http://schema.org/", "@base": "http://example.com/"},
429 /// "@type": "schema:Person",
430 /// "@id": "foo",
431 /// "schema:name": "Foo"
432 /// }"#;
433 ///
434 /// let mut parser = JsonLdParser::new().for_reader(file.as_ref());
435 /// assert_eq!(parser.prefixes().collect::<Vec<_>>(), []); // No prefix at the beginning
436 ///
437 /// parser.next().unwrap()?; // We read the first quad
438 /// assert_eq!(
439 /// parser.prefixes().collect::<Vec<_>>(),
440 /// [("schema", "http://schema.org/")]
441 /// ); // There are now prefixes
442 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
443 /// ```
444 pub fn prefixes(&self) -> JsonLdPrefixesIter<'_> {
445 self.inner.prefixes()
446 }
447
448 /// The base IRI considered at the current step of the parsing.
449 ///
450 /// ```
451 /// use oxjsonld::JsonLdParser;
452 ///
453 /// let file = br#"{
454 /// "@context": {"schema": "http://schema.org/", "@base": "http://example.com/"},
455 /// "@type": "schema:Person",
456 /// "@id": "foo",
457 /// "schema:name": "Foo"
458 /// }"#;
459 ///
460 /// let mut parser = JsonLdParser::new().for_reader(file.as_ref());
461 /// assert!(parser.base_iri().is_none()); // No base at the beginning because none has been given to the parser.
462 ///
463 /// parser.next().unwrap()?; // We read the first quad
464 /// assert_eq!(parser.base_iri(), Some("http://example.com/")); // There is now a base IRI.
465 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
466 /// ```
467 pub fn base_iri(&self) -> Option<&str> {
468 self.inner.base_iri()
469 }
470
471 fn parse_step(&mut self) -> Result<(), JsonLdParseError> {
472 let event = self.json_parser.parse_next().map_err(|e| {
473 self.inner.json_error = true;
474 e
475 })?;
476 self.inner
477 .parse_event(event, &mut self.results, &mut self.errors);
478 Ok(())
479 }
480}
481
482/// Parses a JSON-LD file from a [`AsyncRead`] implementation.
483///
484/// Can be built using [`JsonLdParser::for_tokio_async_reader`].
485///
486/// Count the number of people:
487/// ```
488/// # #[tokio::main(flavor = "current_thread")]
489/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
490/// use oxjsonld::JsonLdParser;
491/// use oxrdf::vocab::rdf;
492/// use oxrdf::NamedNodeRef;
493///
494/// let file = br#"{
495/// "@context": {"schema": "http://schema.org/"},
496/// "@graph": [
497/// {
498/// "@type": "schema:Person",
499/// "@id": "http://example.com/foo",
500/// "schema:name": "Foo"
501/// },
502/// {
503/// "@type": "schema:Person",
504/// "schema:name": "Bar"
505/// }
506/// ]
507/// }"#;
508///
509/// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
510/// let mut count = 0;
511/// let mut parser = JsonLdParser::new().for_tokio_async_reader(file.as_ref());
512/// while let Some(quad) = parser.next().await {
513/// let quad = quad?;
514/// if quad.predicate == rdf::TYPE && quad.object == schema_person.into() {
515/// count += 1;
516/// }
517/// }
518/// assert_eq!(2, count);
519/// # Ok(())
520/// # }
521/// ```
522#[cfg(feature = "async-tokio")]
523#[must_use]
524pub struct TokioAsyncReaderJsonLdParser<R: AsyncRead + Unpin> {
525 results: Vec<Quad>,
526 errors: Vec<JsonLdSyntaxError>,
527 inner: InternalJsonLdParser,
528 json_parser: TokioAsyncReaderJsonParser<R>,
529}
530
531#[cfg(feature = "async-tokio")]
532impl<R: AsyncRead + Unpin> TokioAsyncReaderJsonLdParser<R> {
533 /// Reads the next quad or returns `None` if the file is finished.
534 pub async fn next(&mut self) -> Option<Result<Quad, JsonLdParseError>> {
535 loop {
536 if let Some(error) = self.errors.pop() {
537 return Some(Err(error.into()));
538 } else if let Some(quad) = self.results.pop() {
539 return Some(Ok(quad));
540 } else if self.inner.is_end() {
541 return None;
542 }
543 if let Err(e) = self.parse_step().await {
544 return Some(Err(e));
545 }
546 // We make sure to have data in the right order
547 self.results.reverse();
548 self.errors.reverse();
549 }
550 }
551
552 /// The list of IRI prefixes considered at the current step of the parsing.
553 ///
554 /// This method returns (prefix name, prefix value) tuples.
555 /// It is empty at the beginning of the parsing and gets updated when prefixes are encountered.
556 /// It should be full at the end of the parsing (but if a prefix is overridden, only the latest version will be returned).
557 ///
558 /// ```
559 /// # #[tokio::main(flavor = "current_thread")]
560 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
561 /// use oxjsonld::JsonLdParser;
562 ///
563 /// let file = br#"{
564 /// "@context": {"schema": "http://schema.org/", "@base": "http://example.com/"},
565 /// "@type": "schema:Person",
566 /// "@id": "foo",
567 /// "schema:name": "Foo"
568 /// }"#;
569 ///
570 /// let mut parser = JsonLdParser::new().for_tokio_async_reader(file.as_ref());
571 /// assert_eq!(parser.prefixes().collect::<Vec<_>>(), []); // No prefix at the beginning
572 ///
573 /// parser.next().await.unwrap()?; // We read the first quad
574 /// assert_eq!(
575 /// parser.prefixes().collect::<Vec<_>>(),
576 /// [("schema", "http://schema.org/")]
577 /// ); // There are now prefixes
578 /// # Ok(())
579 /// # }
580 /// ```
581 pub fn prefixes(&self) -> JsonLdPrefixesIter<'_> {
582 self.inner.prefixes()
583 }
584
585 /// The base IRI considered at the current step of the parsing.
586 ///
587 /// ```
588 /// # #[tokio::main(flavor = "current_thread")]
589 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
590 /// use oxjsonld::JsonLdParser;
591 ///
592 /// let file = br#"{
593 /// "@context": {"schema": "http://schema.org/", "@base": "http://example.com/"},
594 /// "@type": "schema:Person",
595 /// "@id": "foo",
596 /// "schema:name": "Foo"
597 /// }"#;
598 ///
599 /// let mut parser = JsonLdParser::new().for_tokio_async_reader(file.as_ref());
600 /// assert!(parser.base_iri().is_none()); // No base at the beginning because none has been given to the parser.
601 ///
602 /// parser.next().await.unwrap()?; // We read the first quad
603 /// assert_eq!(parser.base_iri(), Some("http://example.com/")); // There is now a base IRI.
604 /// # Ok(())
605 /// # }
606 /// ```
607 pub fn base_iri(&self) -> Option<&str> {
608 self.inner.base_iri()
609 }
610
611 async fn parse_step(&mut self) -> Result<(), JsonLdParseError> {
612 let event = self.json_parser.parse_next().await.map_err(|e| {
613 self.inner.json_error = true;
614 e
615 })?;
616 self.inner
617 .parse_event(event, &mut self.results, &mut self.errors);
618 Ok(())
619 }
620}
621
622/// Parses a JSON-LD file from a byte slice.
623///
624/// Can be built using [`JsonLdParser::for_slice`].
625///
626/// Count the number of people:
627/// ```
628/// use oxjsonld::JsonLdParser;
629/// use oxrdf::vocab::rdf;
630/// use oxrdf::NamedNodeRef;
631///
632/// let file = br#"{
633/// "@context": {"schema": "http://schema.org/"},
634/// "@graph": [
635/// {
636/// "@type": "schema:Person",
637/// "@id": "http://example.com/foo",
638/// "schema:name": "Foo"
639/// },
640/// {
641/// "@type": "schema:Person",
642/// "schema:name": "Bar"
643/// }
644/// ]
645/// }"#;
646///
647/// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
648/// let mut count = 0;
649/// for quad in JsonLdParser::new().for_slice(file) {
650/// let quad = quad?;
651/// if quad.predicate == rdf::TYPE && quad.object == schema_person.into() {
652/// count += 1;
653/// }
654/// }
655/// assert_eq!(2, count);
656/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
657/// ```
658#[must_use]
659pub struct SliceJsonLdParser<'a> {
660 results: Vec<Quad>,
661 errors: Vec<JsonLdSyntaxError>,
662 inner: InternalJsonLdParser,
663 json_parser: SliceJsonParser<'a>,
664}
665
666impl Iterator for SliceJsonLdParser<'_> {
667 type Item = Result<Quad, JsonLdSyntaxError>;
668
669 fn next(&mut self) -> Option<Self::Item> {
670 loop {
671 if let Some(error) = self.errors.pop() {
672 return Some(Err(error));
673 } else if let Some(quad) = self.results.pop() {
674 return Some(Ok(quad));
675 } else if self.inner.is_end() {
676 return None;
677 }
678 if let Err(e) = self.parse_step() {
679 // I/O errors cannot happen
680 return Some(Err(e));
681 }
682 // We make sure to have data in the right order
683 self.results.reverse();
684 self.errors.reverse();
685 }
686 }
687}
688
689impl SliceJsonLdParser<'_> {
690 /// The list of IRI prefixes considered at the current step of the parsing.
691 ///
692 /// This method returns (prefix name, prefix value) tuples.
693 /// It is empty at the beginning of the parsing and gets updated when prefixes are encountered.
694 /// It should be full at the end of the parsing (but if a prefix is overridden, only the latest version will be returned).
695 ///
696 /// ```
697 /// use oxjsonld::JsonLdParser;
698 ///
699 /// let file = br#"{
700 /// "@context": {"schema": "http://schema.org/", "@base": "http://example.com/"},
701 /// "@type": "schema:Person",
702 /// "@id": "foo",
703 /// "schema:name": "Foo"
704 /// }"#;
705 ///
706 /// let mut parser = JsonLdParser::new().for_slice(file);
707 /// assert_eq!(parser.prefixes().collect::<Vec<_>>(), []); // No prefix at the beginning
708 ///
709 /// parser.next().unwrap()?; // We read the first quad
710 /// assert_eq!(
711 /// parser.prefixes().collect::<Vec<_>>(),
712 /// [("schema", "http://schema.org/")]
713 /// ); // There are now prefixes
714 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
715 /// ```
716 pub fn prefixes(&self) -> JsonLdPrefixesIter<'_> {
717 self.inner.prefixes()
718 }
719
720 /// The base IRI considered at the current step of the parsing.
721 ///
722 /// ```
723 /// use oxjsonld::JsonLdParser;
724 ///
725 /// let file = br#"{
726 /// "@context": {"schema": "http://schema.org/", "@base": "http://example.com/"},
727 /// "@type": "schema:Person",
728 /// "@id": "foo",
729 /// "schema:name": "Foo"
730 /// }"#;
731 ///
732 /// let mut parser = JsonLdParser::new().for_slice(file);
733 /// assert!(parser.base_iri().is_none()); // No base at the beginning because none has been given to the parser.
734 ///
735 /// parser.next().unwrap()?; // We read the first quad
736 /// assert_eq!(parser.base_iri(), Some("http://example.com/")); // There is now a base IRI.
737 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
738 /// ```
739 pub fn base_iri(&self) -> Option<&str> {
740 self.inner.base_iri()
741 }
742
743 fn parse_step(&mut self) -> Result<(), JsonLdSyntaxError> {
744 let event = self.json_parser.parse_next().map_err(|e| {
745 self.inner.json_error = true;
746 e
747 })?;
748 self.inner
749 .parse_event(event, &mut self.results, &mut self.errors);
750 Ok(())
751 }
752}
753
754/// Iterator on the file prefixes.
755///
756/// See [`ReaderJsonLdParser::prefixes`].
757pub struct JsonLdPrefixesIter<'a> {
758 term_definitions: std::collections::hash_map::Iter<'a, String, JsonLdTermDefinition>,
759 lenient: bool,
760}
761
762impl<'a> Iterator for JsonLdPrefixesIter<'a> {
763 type Item = (&'a str, &'a str);
764
765 #[inline]
766 fn next(&mut self) -> Option<Self::Item> {
767 loop {
768 let (prefix, term_definition) = self.term_definitions.next()?;
769 if term_definition.prefix_flag {
770 if let Some(Some(mapping)) = &term_definition.iri_mapping {
771 if self.lenient || Iri::parse(mapping.as_str()).is_ok() {
772 return Some((prefix, mapping));
773 }
774 }
775 }
776 }
777 }
778
779 #[inline]
780 fn size_hint(&self) -> (usize, Option<usize>) {
781 (0, self.term_definitions.size_hint().1)
782 }
783}
784
785struct InternalJsonLdParser {
786 expansion: JsonLdExpansionConverter,
787 expended_events: Vec<JsonLdEvent>,
788 to_rdf: JsonLdToRdfConverter,
789 json_error: bool,
790}
791
792impl InternalJsonLdParser {
793 fn parse_event(
794 &mut self,
795 event: JsonEvent<'_>,
796 results: &mut Vec<Quad>,
797 errors: &mut Vec<JsonLdSyntaxError>,
798 ) {
799 self.expansion
800 .convert_event(event, &mut self.expended_events, errors);
801 for event in self.expended_events.drain(..) {
802 self.to_rdf.convert_event(event, results);
803 }
804 }
805
806 fn is_end(&self) -> bool {
807 self.json_error || self.expansion.is_end()
808 }
809
810 fn base_iri(&self) -> Option<&str> {
811 Some(self.expansion.context().base_iri.as_ref()?.as_str())
812 }
813
814 fn prefixes(&self) -> JsonLdPrefixesIter<'_> {
815 JsonLdPrefixesIter {
816 term_definitions: self.expansion.context().term_definitions.iter(),
817 lenient: self.to_rdf.lenient,
818 }
819 }
820}
821
822enum JsonLdToRdfState {
823 StartObject {
824 types: Vec<NamedOrBlankNode>,
825 /// Events before the @id event
826 buffer: Vec<JsonLdEvent>,
827 /// Nesting level of objects, useful during buffering
828 nesting: usize,
829 },
830 Object(Option<NamedOrBlankNode>),
831 Property {
832 id: Option<NamedNode>,
833 reverse: bool,
834 },
835 List(Option<NamedOrBlankNode>),
836 Graph(Option<GraphName>),
837}
838
839struct JsonLdToRdfConverter {
840 state: Vec<JsonLdToRdfState>,
841 lenient: bool,
842}
843
844impl JsonLdToRdfConverter {
845 fn convert_event(&mut self, event: JsonLdEvent, results: &mut Vec<Quad>) {
846 #[allow(clippy::expect_used)]
847 let state = self.state.pop().expect("Empty stack");
848 match state {
849 JsonLdToRdfState::StartObject {
850 types,
851 mut buffer,
852 nesting,
853 } => {
854 match event {
855 JsonLdEvent::Id(id) => {
856 if nesting > 0 {
857 buffer.push(JsonLdEvent::Id(id));
858 self.state.push(JsonLdToRdfState::StartObject {
859 types,
860 buffer,
861 nesting,
862 });
863 } else {
864 let id = self.convert_named_or_blank_node(id);
865 self.emit_quads_for_new_object(id.as_ref(), types, results);
866 self.state.push(JsonLdToRdfState::Object(id));
867 for event in buffer {
868 self.convert_event(event, results);
869 }
870 }
871 }
872 JsonLdEvent::EndObject => {
873 if nesting > 0 {
874 buffer.push(JsonLdEvent::EndObject);
875 self.state.push(JsonLdToRdfState::StartObject {
876 types,
877 buffer,
878 nesting: nesting - 1,
879 });
880 } else {
881 let id = Some(BlankNode::default().into());
882 self.emit_quads_for_new_object(id.as_ref(), types, results);
883 if !buffer.is_empty() {
884 self.state.push(JsonLdToRdfState::Object(id));
885 for event in buffer {
886 self.convert_event(event, results);
887 }
888 // We properly end after playing the buffer
889 self.convert_event(JsonLdEvent::EndObject, results);
890 }
891 }
892 }
893 JsonLdEvent::StartObject { .. } => {
894 buffer.push(event);
895 self.state.push(JsonLdToRdfState::StartObject {
896 types,
897 buffer,
898 nesting: nesting + 1,
899 });
900 }
901 _ => {
902 buffer.push(event);
903 self.state.push(JsonLdToRdfState::StartObject {
904 types,
905 buffer,
906 nesting,
907 });
908 }
909 }
910 }
911 JsonLdToRdfState::Object(id) => match event {
912 JsonLdEvent::Id(_) => {
913 unreachable!("Should have buffered before @id")
914 }
915 JsonLdEvent::EndObject => (),
916 JsonLdEvent::StartProperty { name, reverse } => {
917 self.state.push(JsonLdToRdfState::Object(id));
918 self.state.push(JsonLdToRdfState::Property {
919 id: if self.has_defined_last_predicate() {
920 self.convert_named_node(name)
921 } else {
922 None // We do not want to emit if one of the parent property is not emitted
923 },
924 reverse,
925 });
926 }
927 JsonLdEvent::StartGraph => {
928 let graph_name = id.clone().map(Into::into);
929 self.state.push(JsonLdToRdfState::Object(id));
930 self.state.push(JsonLdToRdfState::Graph(graph_name));
931 }
932 JsonLdEvent::StartObject { .. }
933 | JsonLdEvent::Value { .. }
934 | JsonLdEvent::EndProperty
935 | JsonLdEvent::EndGraph
936 | JsonLdEvent::StartList
937 | JsonLdEvent::EndList
938 | JsonLdEvent::StartSet
939 | JsonLdEvent::EndSet => unreachable!(),
940 },
941 JsonLdToRdfState::Property { .. } => match event {
942 JsonLdEvent::StartObject { types } => {
943 self.state.push(state);
944 self.state.push(JsonLdToRdfState::StartObject {
945 types: types
946 .into_iter()
947 .filter_map(|t| self.convert_named_or_blank_node(t))
948 .collect(),
949 buffer: Vec::new(),
950 nesting: 0,
951 });
952 }
953 JsonLdEvent::Value {
954 value,
955 r#type,
956 language,
957 } => {
958 self.state.push(state);
959 self.emit_quad_for_new_literal(
960 self.convert_literal(value, language, r#type),
961 results,
962 )
963 }
964 JsonLdEvent::EndProperty => (),
965 JsonLdEvent::StartList => {
966 self.state.push(state);
967 self.state.push(JsonLdToRdfState::List(None));
968 }
969 JsonLdEvent::StartSet | JsonLdEvent::EndSet => {
970 self.state.push(state);
971 }
972 JsonLdEvent::StartProperty { .. }
973 | JsonLdEvent::Id(_)
974 | JsonLdEvent::EndObject
975 | JsonLdEvent::StartGraph
976 | JsonLdEvent::EndGraph
977 | JsonLdEvent::EndList => unreachable!(),
978 },
979 JsonLdToRdfState::List(current_node) => match event {
980 JsonLdEvent::StartObject { types } => {
981 self.add_new_list_node_state(current_node, results);
982 self.state.push(JsonLdToRdfState::StartObject {
983 types: types
984 .into_iter()
985 .filter_map(|t| self.convert_named_or_blank_node(t))
986 .collect(),
987 buffer: Vec::new(),
988 nesting: 0,
989 })
990 }
991 JsonLdEvent::Value {
992 value,
993 r#type,
994 language,
995 } => {
996 self.add_new_list_node_state(current_node, results);
997 self.emit_quad_for_new_literal(
998 self.convert_literal(value, language, r#type),
999 results,
1000 )
1001 }
1002 JsonLdEvent::StartList => {
1003 self.add_new_list_node_state(current_node, results);
1004 self.state.push(JsonLdToRdfState::List(None));
1005 }
1006 JsonLdEvent::EndList => {
1007 if let Some(previous_node) = current_node {
1008 if let Some(graph_name) = self.last_graph_name() {
1009 results.push(Quad::new(
1010 previous_node,
1011 rdf::REST,
1012 rdf::NIL.into_owned(),
1013 graph_name.clone(),
1014 ));
1015 }
1016 } else {
1017 self.emit_quads_for_new_object(
1018 Some(&rdf::NIL.into_owned().into()),
1019 Vec::new(),
1020 results,
1021 )
1022 }
1023 }
1024 JsonLdEvent::StartSet | JsonLdEvent::EndSet => {
1025 // TODO: this is bad
1026 self.state.push(JsonLdToRdfState::List(current_node));
1027 }
1028 JsonLdEvent::EndObject
1029 | JsonLdEvent::StartProperty { .. }
1030 | JsonLdEvent::EndProperty
1031 | JsonLdEvent::Id(_)
1032 | JsonLdEvent::StartGraph
1033 | JsonLdEvent::EndGraph => unreachable!(),
1034 },
1035 JsonLdToRdfState::Graph(_) => match event {
1036 JsonLdEvent::StartObject { types } => {
1037 self.state.push(state);
1038 self.state.push(JsonLdToRdfState::StartObject {
1039 types: types
1040 .into_iter()
1041 .filter_map(|t| self.convert_named_or_blank_node(t))
1042 .collect(),
1043 buffer: Vec::new(),
1044 nesting: 0,
1045 });
1046 }
1047 JsonLdEvent::Value { .. } => {
1048 self.state.push(state);
1049 }
1050 JsonLdEvent::EndGraph => (),
1051 JsonLdEvent::StartGraph
1052 | JsonLdEvent::StartProperty { .. }
1053 | JsonLdEvent::EndProperty
1054 | JsonLdEvent::Id(_)
1055 | JsonLdEvent::EndObject
1056 | JsonLdEvent::StartList
1057 | JsonLdEvent::EndList
1058 | JsonLdEvent::StartSet
1059 | JsonLdEvent::EndSet => unreachable!(),
1060 },
1061 }
1062 }
1063
1064 fn emit_quads_for_new_object(
1065 &self,
1066 id: Option<&NamedOrBlankNode>,
1067 types: Vec<NamedOrBlankNode>,
1068 results: &mut Vec<Quad>,
1069 ) {
1070 let Some(id) = id else {
1071 return;
1072 };
1073 let Some(graph_name) = self.last_graph_name() else {
1074 return;
1075 };
1076 if let (Some(subject), Some((predicate, reverse))) =
1077 (self.last_subject(), self.last_predicate())
1078 {
1079 results.push(if reverse {
1080 Quad::new(id.clone(), predicate, subject.clone(), graph_name.clone())
1081 } else {
1082 Quad::new(subject.clone(), predicate, id.clone(), graph_name.clone())
1083 })
1084 }
1085 for t in types {
1086 results.push(Quad::new(id.clone(), rdf::TYPE, t, graph_name.clone()))
1087 }
1088 }
1089
1090 fn emit_quad_for_new_literal(&self, literal: Option<Literal>, results: &mut Vec<Quad>) {
1091 let Some(literal) = literal else {
1092 return;
1093 };
1094 let Some(graph_name) = self.last_graph_name() else {
1095 return;
1096 };
1097 let Some(subject) = self.last_subject() else {
1098 return;
1099 };
1100 let Some((predicate, reverse)) = self.last_predicate() else {
1101 return;
1102 };
1103 if reverse {
1104 return;
1105 }
1106 results.push(Quad::new(
1107 subject.clone(),
1108 predicate,
1109 literal,
1110 graph_name.clone(),
1111 ))
1112 }
1113
1114 fn add_new_list_node_state(
1115 &mut self,
1116 current_node: Option<NamedOrBlankNode>,
1117 results: &mut Vec<Quad>,
1118 ) {
1119 let new_node = BlankNode::default();
1120 if let Some(previous_node) = current_node {
1121 if let Some(graph_name) = self.last_graph_name() {
1122 results.push(Quad::new(
1123 previous_node,
1124 rdf::REST,
1125 new_node.clone(),
1126 graph_name.clone(),
1127 ));
1128 }
1129 } else {
1130 self.emit_quads_for_new_object(Some(&new_node.clone().into()), Vec::new(), results)
1131 }
1132 self.state
1133 .push(JsonLdToRdfState::List(Some(new_node.into())));
1134 }
1135
1136 fn convert_named_or_blank_node(&self, value: String) -> Option<NamedOrBlankNode> {
1137 Some(if let Some(bnode_id) = value.strip_prefix("_:") {
1138 if self.lenient {
1139 Some(BlankNode::new_unchecked(bnode_id))
1140 } else {
1141 BlankNode::new(bnode_id).ok()
1142 }?
1143 .into()
1144 } else {
1145 self.convert_named_node(value)?.into()
1146 })
1147 }
1148
1149 fn convert_named_node(&self, value: String) -> Option<NamedNode> {
1150 if self.lenient {
1151 Some(NamedNode::new_unchecked(value))
1152 } else {
1153 NamedNode::new(&value).ok()
1154 }
1155 }
1156
1157 fn convert_literal(
1158 &self,
1159 value: JsonLdValue,
1160 language: Option<String>,
1161 r#type: Option<String>,
1162 ) -> Option<Literal> {
1163 let r#type = if let Some(t) = r#type {
1164 Some(self.convert_named_node(t)?)
1165 } else {
1166 None
1167 };
1168 Some(match value {
1169 JsonLdValue::String(value) => {
1170 if let Some(language) = language {
1171 if r#type.is_some_and(|t| t != rdf::LANG_STRING) {
1172 return None; // Expansion already returns an error
1173 }
1174 if self.lenient {
1175 Literal::new_language_tagged_literal_unchecked(value, language)
1176 } else {
1177 Literal::new_language_tagged_literal(value, &language).ok()?
1178 }
1179 } else if let Some(datatype) = r#type {
1180 Literal::new_typed_literal(value, datatype)
1181 } else {
1182 Literal::new_simple_literal(value)
1183 }
1184 }
1185 JsonLdValue::Number(value) => {
1186 if language.is_some() {
1187 return None; // Expansion already returns an error
1188 }
1189 let value = canonicalize_json_number(
1190 &value,
1191 r#type.as_ref().is_some_and(|t| *t == xsd::DOUBLE),
1192 )
1193 .unwrap_or(RdfJsonNumber::Double(value));
1194 match value {
1195 RdfJsonNumber::Integer(value) => Literal::new_typed_literal(
1196 value,
1197 r#type.unwrap_or_else(|| xsd::INTEGER.into()),
1198 ),
1199 RdfJsonNumber::Double(value) => Literal::new_typed_literal(
1200 value,
1201 r#type.unwrap_or_else(|| xsd::DOUBLE.into()),
1202 ),
1203 }
1204 }
1205 JsonLdValue::Boolean(value) => {
1206 if language.is_some() {
1207 return None; // Expansion already returns an error
1208 }
1209 Literal::new_typed_literal(
1210 if value { "true" } else { "false" },
1211 r#type.unwrap_or_else(|| xsd::BOOLEAN.into()),
1212 )
1213 }
1214 })
1215 }
1216
1217 fn last_subject(&self) -> Option<&NamedOrBlankNode> {
1218 for state in self.state.iter().rev() {
1219 match state {
1220 JsonLdToRdfState::Object(id) => {
1221 return id.as_ref();
1222 }
1223 JsonLdToRdfState::StartObject { .. } => {
1224 unreachable!()
1225 }
1226 JsonLdToRdfState::Property { .. } => (),
1227 JsonLdToRdfState::List(id) => return id.as_ref(),
1228 JsonLdToRdfState::Graph(_) => {
1229 return None;
1230 }
1231 }
1232 }
1233 None
1234 }
1235
1236 fn last_predicate(&self) -> Option<(NamedNodeRef<'_>, bool)> {
1237 for state in self.state.iter().rev() {
1238 match state {
1239 JsonLdToRdfState::Property { id, reverse } => {
1240 return Some((id.as_ref()?.as_ref(), *reverse));
1241 }
1242 JsonLdToRdfState::StartObject { .. } | JsonLdToRdfState::Object(_) => (),
1243 JsonLdToRdfState::List(_) => return Some((rdf::FIRST, false)),
1244 JsonLdToRdfState::Graph(_) => {
1245 return None;
1246 }
1247 }
1248 }
1249 None
1250 }
1251
1252 fn has_defined_last_predicate(&self) -> bool {
1253 for state in self.state.iter().rev() {
1254 if let JsonLdToRdfState::Property { id, .. } = state {
1255 return id.is_some();
1256 }
1257 }
1258 true
1259 }
1260
1261 fn last_graph_name(&self) -> Option<&GraphName> {
1262 for state in self.state.iter().rev() {
1263 match state {
1264 JsonLdToRdfState::Graph(graph) => {
1265 return graph.as_ref();
1266 }
1267 JsonLdToRdfState::StartObject { .. }
1268 | JsonLdToRdfState::Object(_)
1269 | JsonLdToRdfState::Property { .. }
1270 | JsonLdToRdfState::List(_) => (),
1271 }
1272 }
1273 None
1274 }
1275}
1276
1277#[derive(Eq, PartialEq, Debug, Clone)]
1278enum RdfJsonNumber {
1279 Integer(String),
1280 Double(String),
1281}
1282
1283/// Canonicalizes the JSON number to xsd:double canonical form.
1284fn canonicalize_json_number(value: &str, always_double: bool) -> Option<RdfJsonNumber> {
1285 // We parse
1286 let (value, is_negative) = if let Some(value) = value.strip_prefix('-') {
1287 (value, true)
1288 } else if let Some(value) = value.strip_prefix('+') {
1289 (value, false)
1290 } else {
1291 (value, false)
1292 };
1293 let (value, exp) = value.split_once(['e', 'E']).unwrap_or((value, "0"));
1294 let (mut integer_part, mut decimal_part) = value.split_once('.').unwrap_or((value, ""));
1295 let mut exp = exp.parse::<i64>().ok()?;
1296
1297 // We normalize
1298 // We trim the zeros
1299 while let Some(c) = integer_part.strip_prefix('0') {
1300 integer_part = c;
1301 }
1302 while let Some(c) = decimal_part.strip_suffix('0') {
1303 decimal_part = c;
1304 }
1305 if decimal_part.is_empty() {
1306 while let Some(c) = integer_part.strip_suffix('0') {
1307 integer_part = c;
1308 exp = exp.checked_add(1)?;
1309 }
1310 }
1311 if integer_part.is_empty() {
1312 while let Some(c) = decimal_part.strip_prefix('0') {
1313 decimal_part = c;
1314 exp = exp.checked_sub(1)?;
1315 }
1316 }
1317
1318 // We set the exponent in the 0.XXXEYYY form
1319 let exp_change = i64::try_from(integer_part.len()).ok()?;
1320 exp = exp.checked_add(exp_change)?;
1321
1322 // We handle the zero case
1323 if integer_part.is_empty() && decimal_part.is_empty() {
1324 integer_part = "0";
1325 exp = 1;
1326 }
1327
1328 // We serialize
1329 let mut buffer = String::with_capacity(value.len());
1330 if is_negative {
1331 buffer.push('-');
1332 }
1333 let digits_count = i64::try_from(integer_part.len() + decimal_part.len()).ok()?;
1334 Some(if !always_double && exp >= digits_count && exp < 21 {
1335 buffer.push_str(integer_part);
1336 buffer.push_str(decimal_part);
1337 buffer.extend((0..(exp - digits_count)).map(|_| '0'));
1338 RdfJsonNumber::Integer(buffer)
1339 } else {
1340 let mut all_digits = integer_part.chars().chain(decimal_part.chars());
1341 buffer.push(all_digits.next()?);
1342 buffer.push('.');
1343 if digits_count == 1 {
1344 buffer.push('0');
1345 } else {
1346 buffer.extend(all_digits);
1347 }
1348 write!(&mut buffer, "E{}", exp.checked_sub(1)?).ok()?;
1349 RdfJsonNumber::Double(buffer)
1350 })
1351}
1352
1353#[cfg(test)]
1354mod tests {
1355 use super::*;
1356
1357 #[test]
1358 fn test_canonicalize_json_number() {
1359 assert_eq!(
1360 canonicalize_json_number("12", false),
1361 Some(RdfJsonNumber::Integer("12".into()))
1362 );
1363 assert_eq!(
1364 canonicalize_json_number("-12", false),
1365 Some(RdfJsonNumber::Integer("-12".into()))
1366 );
1367 assert_eq!(
1368 canonicalize_json_number("1", true),
1369 Some(RdfJsonNumber::Double("1.0E0".into()))
1370 );
1371 assert_eq!(
1372 canonicalize_json_number("1", true),
1373 Some(RdfJsonNumber::Double("1.0E0".into()))
1374 );
1375 assert_eq!(
1376 canonicalize_json_number("+1", true),
1377 Some(RdfJsonNumber::Double("1.0E0".into()))
1378 );
1379 assert_eq!(
1380 canonicalize_json_number("-1", true),
1381 Some(RdfJsonNumber::Double("-1.0E0".into()))
1382 );
1383 assert_eq!(
1384 canonicalize_json_number("12", true),
1385 Some(RdfJsonNumber::Double("1.2E1".into()))
1386 );
1387 assert_eq!(
1388 canonicalize_json_number("-12", true),
1389 Some(RdfJsonNumber::Double("-1.2E1".into()))
1390 );
1391 assert_eq!(
1392 canonicalize_json_number("12.3456E3", false),
1393 Some(RdfJsonNumber::Double("1.23456E4".into()))
1394 );
1395 assert_eq!(
1396 canonicalize_json_number("12.3456e3", false),
1397 Some(RdfJsonNumber::Double("1.23456E4".into()))
1398 );
1399 assert_eq!(
1400 canonicalize_json_number("-12.3456E3", false),
1401 Some(RdfJsonNumber::Double("-1.23456E4".into()))
1402 );
1403 assert_eq!(
1404 canonicalize_json_number("12.34E-3", false),
1405 Some(RdfJsonNumber::Double("1.234E-2".into()))
1406 );
1407 assert_eq!(
1408 canonicalize_json_number("12.340E-3", false),
1409 Some(RdfJsonNumber::Double("1.234E-2".into()))
1410 );
1411 assert_eq!(
1412 canonicalize_json_number("0.01234E-1", false),
1413 Some(RdfJsonNumber::Double("1.234E-3".into()))
1414 );
1415 assert_eq!(
1416 canonicalize_json_number("1.0", false),
1417 Some(RdfJsonNumber::Integer("1".into()))
1418 );
1419 assert_eq!(
1420 canonicalize_json_number("1.0E0", false),
1421 Some(RdfJsonNumber::Integer("1".into()))
1422 );
1423 assert_eq!(
1424 canonicalize_json_number("0.01E2", false),
1425 Some(RdfJsonNumber::Integer("1".into()))
1426 );
1427 assert_eq!(
1428 canonicalize_json_number("1E2", false),
1429 Some(RdfJsonNumber::Integer("100".into()))
1430 );
1431 assert_eq!(
1432 canonicalize_json_number("1E21", false),
1433 Some(RdfJsonNumber::Double("1.0E21".into()))
1434 );
1435 assert_eq!(
1436 canonicalize_json_number("0", false),
1437 Some(RdfJsonNumber::Integer("0".into()))
1438 );
1439 assert_eq!(
1440 canonicalize_json_number("0", true),
1441 Some(RdfJsonNumber::Double("0.0E0".into()))
1442 );
1443 assert_eq!(
1444 canonicalize_json_number("-0", true),
1445 Some(RdfJsonNumber::Double("-0.0E0".into()))
1446 );
1447 assert_eq!(
1448 canonicalize_json_number("0E-10", true),
1449 Some(RdfJsonNumber::Double("0.0E0".into()))
1450 );
1451 }
1452}