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#[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 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 pub const fn validate_str(input: &str) -> bool {
74 Self::validate_bytes(input.as_bytes())
75 }
76
77 #[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 #[inline(always)]
100 pub fn from_u8(i: u8) -> Self {
101 unsafe { Self::new_unchecked(format!("_:{i}")) }
102 }
103
104 #[inline(always)]
106 pub fn from_u16(i: u16) -> Self {
107 unsafe { Self::new_unchecked(format!("_:{i}")) }
108 }
109
110 #[inline(always)]
112 pub fn from_u32(i: u32) -> Self {
113 unsafe { Self::new_unchecked(format!("_:{i}")) }
114 }
115
116 #[inline(always)]
118 pub fn from_u64(i: u64) -> Self {
119 unsafe { Self::new_unchecked(format!("_:{i}")) }
120 }
121
122 #[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}