Skip to main content

rdf_syntax/
blankid.rs

1use std::borrow::{Borrow, ToOwned};
2use std::fmt;
3
4use str_newtype::StrNewType;
5
6use crate::RdfDisplay;
7
8#[macro_export]
9macro_rules! blankid {
10	($input:literal) => {
11		const {
12			match $crate::BlankId::from_str($input) {
13				Ok(b) => b,
14				Err(_) => panic!("invalid blank identifier"),
15			}
16		}
17	};
18}
19
20/// Blank node identifier.
21///
22/// A blank node identifier is a string matching
23/// the `BLANK_NODE_LABEL` production in the following [EBNF](http://www.w3.org/TR/REC-xml/#sec-notation) grammar:
24///
25/// ```ebnf
26/// [141s] BLANK_NODE_LABEL ::= '_:' (PN_CHARS_U | [0-9]) ((PN_CHARS | '.')* PN_CHARS)?
27/// [157s] PN_CHARS_BASE    ::= [A-Z] | [a-z] | [#x00C0-#x00D6] | [#x00D8-#x00F6] | [#x00F8-#x02FF] | [#x0370-#x037D] | [#x037F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF]
28/// [158s] PN_CHARS_U       ::= PN_CHARS_BASE | '_' | ':'
29/// [160s] PN_CHARS         ::= PN_CHARS_U | '-' | [0-9] | #x00B7 | [#x0300-#x036F] | [#x203F-#x2040]
30/// ```
31#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, StrNewType)]
32#[newtype(owned(BlankIdBuf, derive(PartialEq, Eq, PartialOrd, Ord, Hash)))]
33#[cfg_attr(feature = "serde", newtype(serde))]
34pub struct BlankId(str);
35
36impl BlankId {
37	/// Checks that `input` is a valid blank node identifier, in its UTF-8
38	/// byte representation.
39	pub const fn validate_bytes(input: &[u8]) -> bool {
40		let mut i = 0;
41
42		if !matches!(
43			utf8_decode::try_decode_char(input, &mut i),
44			Ok(Some(('_', _)))
45		) {
46			return false;
47		}
48
49		if !matches!(
50			utf8_decode::try_decode_char(input, &mut i),
51			Ok(Some((':', _)))
52		) {
53			return false;
54		}
55
56		if !matches!(
57			utf8_decode::try_decode_char(input, &mut i),
58			Ok(Some((c, _))) if c.is_ascii_digit() || is_pn_char_u(c)
59		) {
60			return false;
61		}
62
63		loop {
64			match utf8_decode::try_decode_char(input, &mut i) {
65				Ok(Some((c, _))) if is_pn_char(c) => (),
66				Ok(None) => break true,
67				_ => break false,
68			}
69		}
70	}
71
72	/// Checks that `input` is a valid blank node identifier.
73	pub const fn validate_str(input: &str) -> bool {
74		Self::validate_bytes(input.as_bytes())
75	}
76
77	/// Returns the suffix part (after `_:`) of the blank node identifier.
78	#[inline(always)]
79	pub fn suffix(&self) -> &str {
80		&self.0[2..]
81	}
82}
83
84impl RdfDisplay for BlankId {
85	fn rdf_fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
86		f.write_str(self.as_str())
87	}
88}
89
90impl PartialEq<str> for BlankId {
91	#[inline(always)]
92	fn eq(&self, other: &str) -> bool {
93		self.0 == *other
94	}
95}
96
97impl BlankIdBuf {
98	/// Creates a blank node identifier using the given `u8` as suffix.
99	#[inline(always)]
100	pub fn from_u8(i: u8) -> Self {
101		unsafe { Self::new_unchecked(format!("_:{i}")) }
102	}
103
104	/// Creates a blank node identifier using the given `u16` as suffix.
105	#[inline(always)]
106	pub fn from_u16(i: u16) -> Self {
107		unsafe { Self::new_unchecked(format!("_:{i}")) }
108	}
109
110	/// Creates a blank node identifier using the given `u32` as suffix.
111	#[inline(always)]
112	pub fn from_u32(i: u32) -> Self {
113		unsafe { Self::new_unchecked(format!("_:{i}")) }
114	}
115
116	/// Creates a blank node identifier using the given `u64` as suffix.
117	#[inline(always)]
118	pub fn from_u64(i: u64) -> Self {
119		unsafe { Self::new_unchecked(format!("_:{i}")) }
120	}
121
122	/// Creates a blank node identifier using the given suffix.
123	#[inline(always)]
124	pub fn from_suffix(suffix: &str) -> Result<Self, InvalidBlankId<String>> {
125		Self::new(format!("_:{suffix}"))
126	}
127}
128
129impl Borrow<str> for BlankIdBuf {
130	#[inline(always)]
131	fn borrow(&self) -> &str {
132		self.0.as_str()
133	}
134}
135
136impl Borrow<BlankId> for &BlankIdBuf {
137	#[inline(always)]
138	fn borrow(&self) -> &BlankId {
139		self.as_blank_id()
140	}
141}
142
143impl Borrow<str> for &BlankIdBuf {
144	#[inline(always)]
145	fn borrow(&self) -> &str {
146		self.0.as_str()
147	}
148}
149
150impl<'a> From<&'a BlankIdBuf> for &'a BlankId {
151	#[inline(always)]
152	fn from(b: &'a BlankIdBuf) -> Self {
153		b.as_ref()
154	}
155}
156
157impl RdfDisplay for BlankIdBuf {
158	fn rdf_fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
159		self.as_blank_id().rdf_fmt(f)
160	}
161}
162
163const fn is_pn_char_base(c: char) -> bool {
164	matches!(c, 'A'..='Z' | 'a'..='z' | '\u{00c0}'..='\u{00d6}' | '\u{00d8}'..='\u{00f6}' | '\u{00f8}'..='\u{02ff}' | '\u{0370}'..='\u{037d}' | '\u{037f}'..='\u{1fff}' | '\u{200c}'..='\u{200d}' | '\u{2070}'..='\u{218f}' | '\u{2c00}'..='\u{2fef}' | '\u{3001}'..='\u{d7ff}' | '\u{f900}'..='\u{fdcf}' | '\u{fdf0}'..='\u{fffd}' | '\u{10000}'..='\u{effff}')
165}
166
167const fn is_pn_char_u(c: char) -> bool {
168	is_pn_char_base(c) || matches!(c, '_' | ':')
169}
170
171const fn is_pn_char(c: char) -> bool {
172	is_pn_char_u(c)
173		|| matches!(c, '-' | '0'..='9' | '\u{00b7}' | '\u{0300}'..='\u{036f}' | '\u{203f}'..='\u{2040}')
174}