vcard_grammar.cpp 19 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
/*
	vcard_grammar.cpp
	Copyright (C) 2015  Belledonne Communications SARL

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "belcard/vcard_grammar.hpp"

21 22
const char *vcard_grammar = R"==GRAMMAR==(
vcard-list = vcard *vcard
Sylvain Berfini's avatar
Sylvain Berfini committed
23

24 25
vcard = "BEGIN:VCARD" [CR] LF
		"VERSION:4.0" [CR] LF
26
		1*property
27 28
		"END:VCARD" *([CR] LF)
property = (SOURCE / KIND / XML
29 30
			/ FN / N / NICKNAME / PHOTO / BDAY / ANNIVERSARY / GENDER
			/ ADR
31 32 33 34 35 36
			/ TEL / EMAIL / IMPP / LANG
			/ TZ / GEO
			/ TITLE / ROLE / LOGO / ORG / MEMBER / RELATED
			/ CATEGORIES / NOTE / PRODID / REV / SOUND / UID / CLIENTPIDMAP / URL
			/ KEY
			/ FBURL / CALADRURI / CALURI
37
			/ BIRTHPLACE / DEATHPLACE / DEATHDATE
38
			/ X-PROPERTY) [CR] LF
39

40
X-PROPERTY = [group "."] X-PROPERTY-name *(";" X-PROPERTY-param) ":" X-PROPERTY-value
41
X-PROPERTY-name = x-name
42
X-PROPERTY-param = any-param
43
X-PROPERTY-value = text
44 45 46 47 48 49 50 51 52 53 54 55
		/ text-list
		/ date-list
		/ time-list
		/ date-time-list
		/ date-and-or-time-list
		/ timestamp-list
		/ boolean
		/ integer-list
		/ float-list
		/ URI
		/ utc-offset
		/ Language-Tag
56

57
SOURCE = [group "."] "SOURCE" *(";" SOURCE-param) ":" SOURCE-value
58
SOURCE-param = VALUE-param / PID-param / PREF-param / ALTID-param / MEDIATYPE-param / any-param
59 60
SOURCE-value = URI

61
KIND = [group "."] "KIND" *(";" KIND-param) ":" KIND-value
62 63 64
KIND-param = VALUE-param / any-param
KIND-value = "individual" / "group" / "org" / "location" / iana-token / x-name

65
XML = [group "."] "XML" *(";" XML-param) ":" XML-value
66
XML-param = VALUE-param / any-param
67 68
XML-value = text

69
FN = [group "."] "FN" *(";" FN-param) ":" FN-value
70
FN-param = VALUE-param / TYPE-param / LANGUAGE-param / ALTID-param / PID-param / PREF-param / any-param
Sylvain Berfini's avatar
Sylvain Berfini committed
71 72
FN-value = text

73
N = [group "."] "N" *(";" N-param) ":" N-value
74
N-param = VALUE-param / SORT-AS-param / LANGUAGE-param / ALTID-param / any-param
75 76 77 78
N-value    = N-fn ";" N-gn ";" N-an ";" N-prefixes ";" N-suffixes
N-fn       = component *("," component)
N-gn       = component *("," component)
N-an       = component *("," component)
79 80
N-prefixes = component *("," component)
N-suffixes = component *("," component)
81

82
NICKNAME = [group "."] "NICKNAME" *(";" NICKNAME-param) ":" NICKNAME-value
83
NICKNAME-param = VALUE-param / TYPE-param / LANGUAGE-param / ALTID-param / PID-param / PREF-param / any-param
84
NICKNAME-value = text-list
85

86
BDAY = [group "."] "BDAY" *(";" BDAY-param) ":" BDAY-value
87
BDAY-param = VALUE-param / LANGUAGE-param / ALTID-param / CALSCALE-param / any-param
88
BDAY-value = date-and-or-time / text
89 90
)==GRAMMAR=="
R"==GRAMMAR==(
91
ANNIVERSARY = [group "."] "ANNIVERSARY" *(";" ANNIVERSARY-param) ":" ANNIVERSARY-value
92
ANNIVERSARY-param = VALUE-param / ALTID-param / CALSCALE-param / any-param
93 94
ANNIVERSARY-value = date-and-or-time / text

95
GENDER = [group "."] "GENDER" *(";" GENDER-param) ":" GENDER-value
96
GENDER-param = VALUE-param / any-param
97 98 99
GENDER-value = [sex] [";" text]
sex = "M" / "F" / "O" / "N" / "U"

100
PHOTO = [group "."] "PHOTO" *(";" PHOTO-param) ":" PHOTO-value
101 102 103
PHOTO-param = VALUE-param / ALTID-param / TYPE-param / MEDIATYPE-param / PREF-param / PID-param / any-param
PHOTO-value = URI

104
ADR = [group "."] "ADR" *(";" ADR-param) ":" ADR-value
105 106
ADR-param = VALUE-param / LABEL-param / LANGUAGE-param / GEO-PARAM-param / TZ-PARAM-param / ALTID-param
			/ PID-param / PREF-param / TYPE-param / any-param
107 108 109 110 111 112 113 114 115 116 117
ADR-value = ADR-pobox  ";" ADR-ext      ";"
			ADR-street ";" ADR-locality ";"
			ADR-region ";" ADR-code     ";"
			ADR-country
ADR-pobox    = component *("," component)
ADR-ext      = component *("," component)
ADR-street   = component *("," component)
ADR-locality = component *("," component)
ADR-region   = component *("," component)
ADR-code     = component *("," component)
ADR-country  = component *("," component)
Sylvain Berfini's avatar
Sylvain Berfini committed
118

119
TEL = [group "."] "TEL" *(";" TEL-param) ":" TEL-value
120
TEL-param = VALUE-param / TYPE-param / PID-param / PREF-param / ALTID-param / any-param
Sylvain Berfini's avatar
Sylvain Berfini committed
121
TEL-value = URI / text
122

123
EMAIL = [group "."] "EMAIL" *(";" EMAIL-param) ":" EMAIL-value
124
EMAIL-param = VALUE-param / PID-param / PREF-param / TYPE-param / ALTID-param / any-param
125 126
EMAIL-value = text

127
IMPP = [group "."] "IMPP" *(";" IMPP-param) ":" IMPP-value
128
IMPP-param = VALUE-param / PID-param / PREF-param / TYPE-param / MEDIATYPE-param / ALTID-param / any-param
129
IMPP-value = URI
130

131
LANG = [group "."] "LANG" *(";" LANG-param) ":" LANG-value
132
LANG-param = VALUE-param / PID-param / PREF-param / ALTID-param / TYPE-param / any-param
133
LANG-value = Language-Tag
134

135
TZ = [group "."] "TZ" *(";" TZ-param) ":" TZ-value
136
TZ-param = VALUE-param / ALTID-param / PID-param / PREF-param / TYPE-param / MEDIATYPE-param / any-param
137 138
TZ-value = text / URI / utc-offset

139
GEO = [group "."] "GEO" *(";" GEO-param) ":" GEO-value
140
GEO-param = VALUE-param / PID-param / PREF-param / TYPE-param / MEDIATYPE-param / ALTID-param / any-param
141
GEO-value = text / URI
142

143
TITLE = [group "."] "TITLE" *(";" TITLE-param) ":" TITLE-value
144
TITLE-param = VALUE-param / LANGUAGE-param / PID-param / PREF-param
145
			/ ALTID-param / TYPE-param / any-param
146 147
TITLE-value = text

148
ROLE = [group "."] "ROLE" *(";" ROLE-param) ":" ROLE-value
149
ROLE-param = VALUE-param / LANGUAGE-param / PID-param / PREF-param
150
			/ TYPE-param / ALTID-param / any-param
151 152
ROLE-value = text

153
LOGO = [group "."] "LOGO" *(";" LOGO-param) ":" LOGO-value
154
LOGO-param = VALUE-param / LANGUAGE-param / PID-param / PREF-param
155
			/ TYPE-param / MEDIATYPE-param / ALTID-param / any-param
156
LOGO-value = URI
157 158
)==GRAMMAR=="
R"==GRAMMAR==(
159
ORG = [group "."] "ORG" *(";" ORG-param) ":" ORG-value
160
ORG-param = VALUE-param / SORT-AS-param / LANGUAGE-param / PID-param
161
			/ PREF-param / ALTID-param / TYPE-param / any-param
162 163
ORG-value = component *(";" component)

164
MEMBER = [group "."] "MEMBER" *(";" MEMBER-param) ":" MEMBER-value
165
MEMBER-param = VALUE-param / PID-param / PREF-param / ALTID-param
166
			/ MEDIATYPE-param / any-param
167 168
MEMBER-value = URI

169
RELATED = [group "."] "RELATED" *(";" RELATED-param) ":" RELATED-value
170
RELATED-param = VALUE-param / PID-param / PREF-param / ALTID-param / TYPE-param / any-param
171 172
RELATED-value = URI / text

173
CATEGORIES = [group "."] "CATEGORIES" *(";" CATEGORIES-param) ":" CATEGORIES-value
174
CATEGORIES-param = VALUE-param / PID-param / PREF-param / TYPE-param / ALTID-param / any-param
175 176
CATEGORIES-value = text-list

177
NOTE = [group "."] "NOTE" *(";" NOTE-param) ":" NOTE-value
178
NOTE-param = VALUE-param / LANGUAGE-param / PID-param / PREF-param / TYPE-param / ALTID-param / any-param
179 180
NOTE-value = text

181
PRODID = [group "."] "PRODID" *(";" PRODID-param) ":" PRODID-value
182
PRODID-param = VALUE-param / any-param
183 184
PRODID-value = text

185
REV = [group "."] "REV" *(";" REV-param) ":" REV-value
186
REV-param = VALUE-param / any-param
187 188
REV-value = timestamp

189
SOUND = [group "."] "SOUND" *(";" SOUND-param) ":" SOUND-value
190 191
SOUND-param = VALUE-param / LANGUAGE-param / PID-param / PREF-param 
			/ TYPE-param / MEDIATYPE-param / ALTID-param / any-param
192 193
SOUND-value = URI

194
UID = [group "."] "UID" *(";" UID-param) ":" UID-value
195
UID-param = VALUE-param / any-param
196 197
UID-value = URI / text

198
CLIENTPIDMAP = [group "."] "CLIENTPIDMAP" *(";" CLIENTPIDMAP-param) ":" CLIENTPIDMAP-value
199
CLIENTPIDMAP-param = any-param
200 201
CLIENTPIDMAP-value = 1*DIGIT ";" URI

202
URL = [group "."] "URL" *(";" URL-param) ":" URL-value
203
URL-param = VALUE-param / PID-param / PREF-param / TYPE-param
204
			/ MEDIATYPE-param / ALTID-param / any-param
205 206
URL-value = URI

207
KEY = [group "."] "KEY" *(";" KEY-param) ":" KEY-value
208
KEY-param = VALUE-param / ALTID-param / PID-param / PREF-param / TYPE-param / any-param
209 210
KEY-value = URI / text

211
FBURL = [group "."] "FBURL" *(";" FBURL-param) ":" FBURL-value
212
FBURL-param = VALUE-param / PID-param / PREF-param / TYPE-param
213
			/ MEDIATYPE-param / ALTID-param / any-param
214 215
FBURL-value = URI

216
CALADRURI = [group "."] "CALADRURI" *(";" CALADRURI-param) ":" CALADRURI-value
217
CALADRURI-param = VALUE-param / PID-param / PREF-param / TYPE-param
218
				/ MEDIATYPE-param / ALTID-param / any-param
219 220
CALADRURI-value = URI

221
CALURI = [group "."] "CALURI" *(";" CALURI-param) ":" CALURI-value
222
CALURI-param = VALUE-param / PID-param / PREF-param / TYPE-param
223
			/ MEDIATYPE-param / ALTID-param / any-param
224
CALURI-value = URI
225 226
)==GRAMMAR=="
R"==GRAMMAR==(
227
BIRTHPLACE = [group "."] "BIRTHPLACE" *(";" BIRTHPLACE-param) ":" BIRTHPLACE-value
228 229 230
BIRTHPLACE-param = VALUE-param / ALTID-param / LANGUAGE-param / any-param
BIRTHPLACE-value = text / URI

231
DEATHPLACE = [group "."] "DEATHPLACE" *(";" DEATHPLACE-param) ":" DEATHPLACE-value
232 233 234
DEATHPLACE-param = VALUE-param / ALTID-param / LANGUAGE-param / any-param
DEATHPLACE-value = text / URI

235
DEATHDATE = [group "."] "DEATHDATE" *(";" DEATHDATE-param) ":" DEATHDATE-value
236 237
DEATHDATE-param = VALUE-param / ALTID-param / LANGUAGE-param / any-param
DEATHDATE-value = date-and-or-time / text
238

239
group = 1*(ALPHA / DIGIT / "-")
Sylvain Berfini's avatar
Sylvain Berfini committed
240

241 242
LANGUAGE-param = "LANGUAGE=" LANGUAGE-param-value
LANGUAGE-param-value = Language-Tag
243

244 245 246 247 248 249 250 251 252 253 254 255 256 257
VALUE-param = "VALUE=" VALUE-param-value
VALUE-param-value = "text"
					/ "uri"
					/ "date"
					/ "time"
					/ "date-time"
					/ "date-and-or-time"
					/ "timestamp"
					/ "boolean"
					/ "integer"
					/ "float"
					/ "utc-offset"
					/ "language-tag"
					/ x-name
258

259 260
PREF-param = "PREF=" PREF-param-value
PREF-param-value = (1*2DIGIT / "100")
261

262 263
ALTID-param = "ALTID=" ALTID-param-value
ALTID-param-value = param-value
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279

PID-param = "PID=" PID-param-value *("," PID-param-value)
PID-param-value = 1*DIGIT ["." 1*DIGIT]

TYPE-param = "TYPE=" TYPE-param-value *("," TYPE-param-value)
TYPE-param-value = "work" / "home" / type-param-tel / type-param-related / iana-token / x-name

MEDIATYPE-param = "MEDIATYPE=" MEDIATYPE-param-value
MEDIATYPE-param-value = type-name "/" subtype-name *( ";" any-param )

CALSCALE-param = "CALSCALE=" CALSCALE-param-value
CALSCALE-param-value = "gregorian" / iana-token / x-name

SORT-AS-param = "SORT-AS=" SORT-AS-param-value
SORT-AS-param-value = param-value *("," param-value)

280 281
GEO-PARAM-param = "GEO=" GEO-PARAM-param
GEO-PARAM-param-value = DQUOTE URI DQUOTE
282

283 284
TZ-PARAM-param = "TZ=" TZ-PARAM-param-value
TZ-PARAM-param-value = (param-value / DQUOTE URI DQUOTE)
285

286 287
LABEL-param = "LABEL=" LABEL-param-value
LABEL-param-value = param-value
288

289 290 291
any-param  = param-name "=" param-value
param-name = (iana-token / x-name)
param-value = param-value-component *("," param-value-component)
292
param-value-component = *SAFE-CHAR /  (DQUOTE *QSAFE-CHAR DQUOTE)
293 294
)==GRAMMAR=="
R"==GRAMMAR==(
Sylvain Berfini's avatar
Sylvain Berfini committed
295 296 297
iana-token = 1*(ALPHA / DIGIT / "-")
x-name = "x-" 1*(ALPHA / DIGIT / "-")

298 299 300 301
COMPONENT-CHAR = "\\" / "\," / "\;" / "\n" / WSP / NON-ASCII / %x21-2B / %x2D-3A / %x3C-5B / %x5D-7E
component = *COMPONENT-CHAR
list-component = component *("," component)

302
text-list = text *("," text)
Sylvain Berfini's avatar
Sylvain Berfini committed
303 304 305 306
text = *TEXT-CHAR
TEXT-CHAR = "\\" / "\," / "\n" / WSP / NON-ASCII / %x21-2B / %x2D-5B / %x5D-7E
NON-ASCII = UTF8-2 / UTF8-3 / UTF8-4
QSAFE-CHAR = WSP / "!" / %x23-7E / NON-ASCII
307
SAFE-CHAR = WSP / "!" / %x23-2B / %x2D-39 / %x3C-7E / NON-ASCII
Sylvain Berfini's avatar
Sylvain Berfini committed
308 309 310 311 312 313
VALUE-CHAR = WSP / VCHAR / NON-ASCII
UTF8-1      = %x00-7F
UTF8-2      = %xC2-DF UTF8-tail
UTF8-3      = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) / %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
UTF8-4      = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) / %xF4 %x80-8F 2( UTF8-tail )
UTF8-tail   = %x80-BF
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338

date-list             = date             *("," date)
time-list             = time             *("," time)
date-time-list        = date-time        *("," date-time)
date-and-or-time-list = date-and-or-time *("," date-and-or-time)
timestamp-list        = timestamp        *("," timestamp)
integer-list          = integer          *("," integer)
float-list            = float            *("," float)

boolean = "TRUE" / "FALSE"
integer = [sign] 1*DIGIT
float   = [sign] 1*DIGIT ["." 1*DIGIT]

sign = "+" / "-"

year   = 4DIGIT  ; 0000-9999
month  = 2DIGIT  ; 01-12
day    = 2DIGIT  ; 01-28/29/30/31 depending on month and leap year
hour   = 2DIGIT  ; 00-23
minute = 2DIGIT  ; 00-59
second = 2DIGIT  ; 00-58/59/60 depending on leap second
zone   = utc-designator / utc-offset
utc-designator = %x5A  ; uppercase "Z"

date          = year    [month  day]
339 340 341
			  / year "-" month
			  / "--"     month [day]
			  / "--"      "-"   day
342
date-noreduc  = year     month  day
343 344
			  / "--"     month  day
			  / "--"      "-"   day
345 346 347
date-complete = year     month  day

time          = hour [minute [second]] [zone]
348 349
			  /  "-"  minute [second]
			  /  "-"   "-"    second
350 351 352 353 354 355 356 357 358
time-notrunc  = hour [minute [second]] [zone]
time-complete = hour  minute  second   [zone]
time-designator = %x54  ; uppercase "T"
date-time = date-noreduc  time-designator time-notrunc
timestamp = date-complete time-designator time-complete

date-and-or-time = date-time / date / time-designator time

utc-offset = sign hour [minute]
359 360
)==GRAMMAR=="
R"==GRAMMAR==(
361 362 363 364 365 366 367 368 369
type-param-related = related-type-value *("," related-type-value)
related-type-value = "contact" / "acquaintance" / "friend" / "met"
					/ "co-worker" / "colleague" / "co-resident"
					/ "neighbor" / "child" / "parent"
					/ "sibling" / "spouse" / "kin" / "muse"
					/ "crush" / "date" / "sweetheart" / "me"
					/ "agent" / "emergency"
type-param-tel = "text" / "voice" / "fax" / "cell" / "video"
				/ "pager" / "textphone" / iana-token / x-name
370

371 372 373
type-name = reg-name
subtype-name = reg-name

374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428
URI           = scheme ":" hier-part [ "?" query ] [ "#" fragment ]

hier-part     = "//" authority path-abempty
				/ path-absolute
				/ path-rootless
				/ path-empty

URI-reference = URI / relative-ref

absolute-URI  = scheme ":" hier-part [ "?" query ]

relative-ref  = relative-part [ "?" query ] [ "#" fragment ]

relative-part = "//" authority path-abempty
				/ path-absolute
				/ path-noscheme
				/ path-empty

scheme        = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )

authority     = [ userinfo "@" ] host [ ":" port ]
userinfo      = *( unreserved / pct-encoded / sub-delims / ":" )
host          = IP-literal / IPv4address / reg-name
port          = *DIGIT

IP-literal    = "[" ( IPv6address / IPvFuture  ) "]"

IPvFuture     = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )

IPv6address   =                            6( h16 ":" ) ls32
				/                       "::" 5( h16 ":" ) ls32
				/ [               h16 ] "::" 4( h16 ":" ) ls32
				/ [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
				/ [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
				/ [ *3( h16 ":" ) h16 ] "::"    h16 ":"   ls32
				/ [ *4( h16 ":" ) h16 ] "::"              ls32
				/ [ *5( h16 ":" ) h16 ] "::"              h16
				/ [ *6( h16 ":" ) h16 ] "::"

h16           = 1*4HEXDIG
ls32          = ( h16 ":" h16 ) / IPv4address
IPv4address   = dec-octet "." dec-octet "." dec-octet "." dec-octet
dec-octet     = DIGIT                 ; 0-9
				/ %x31-39 DIGIT         ; 10-99
				/ "1" 2DIGIT            ; 100-199
				/ "2" %x30-34 DIGIT     ; 200-249
				/ "25" %x30-35          ; 250-255

reg-name      = *( unreserved / pct-encoded / sub-delims )

path          = path-abempty    ; begins with "/" or is empty
				/ path-absolute   ; begins with "/" but not "//"
				/ path-noscheme   ; begins with a non-colon segment
				/ path-rootless   ; begins with a segment
				/ path-empty      ; zero characters
429 430
)==GRAMMAR=="
R"==GRAMMAR==(
431 432 433 434 435 436 437 438 439 440 441
path-abempty  = *( "/" segment )
path-absolute = "/" [ segment-nz *( "/" segment ) ]
path-noscheme = segment-nz-nc *( "/" segment )
path-rootless = segment-nz *( "/" segment )
path-empty    = [pchar]

segment       = *pchar
segment-nz    = 1*pchar
segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )
				; non-zero-length segment without any colon ":"

442
pchar         = unreserved / pct-encoded / sub-delims / ":" / "@" / "\,"
443 444 445 446 447 448 449 450 451 452 453 454

query         = *( pchar / "/" / "?" )

fragment      = *( pchar / "/" / "?" )

pct-encoded   = "%" HEXDIG HEXDIG

unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
reserved      = gen-delims / sub-delims
gen-delims    = ":" / "/" / "?" / "#" / "[" / "]" / "@"
sub-delims    = "!" / "$" / "&" / "'" / "(" / ")"
				/ "*" / "+" / "," / ";" / "="
455

456 457 458
Language-Tag  = langtag             ; normal language tags
			 / privateuse            ; private use tag
			 / grandfathered         ; grandfathered tags
459

460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494
langtag       = language
				["-" script]
				["-" region]
				*("-" variant)
				*("-" extension)
				["-" privateuse]

language      = 2*3ALPHA              ; shortest ISO 639 code
				["-" extlang]         ; sometimes followed by extended language subtags
				/ 4ALPHA              ; or reserved for future use
				/ 5*8ALPHA            ; or registered language subtag

extlang       = 3ALPHA                ; selected ISO 639 codes
				*2("-" 3ALPHA)        ; permanently reserved

script        = 4ALPHA                ; ISO 15924 code

region        = 2ALPHA                ; ISO 3166-1 code
				/ 3DIGIT              ; UN M.49 code

variant       = 5*8alphanum         ; registered variants
				/ (DIGIT 3alphanum)

extension     = singleton 1*("-" (2*8alphanum))

singleton     = DIGIT                 ; 0 - 9
				/ %x41-57             ; A - W
				/ %x59-5A             ; Y - Z
				/ %x61-77             ; a - w
				/ %x79-7A             ; y - z

privateuse    = "x" 1*("-" (1*8alphanum))

grandfathered = irregular             ; non-redundant tags registered
				/ regular             ; during the RFC 3066 era
495 496
)==GRAMMAR=="
R"==GRAMMAR==(
497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525
irregular     = "en-GB-oed"           ; irregular tags do not match
				/ "i-ami"             ; the 'langtag' production and
				/ "i-bnn"             ; would not otherwise be
				/ "i-default"         ; considered 'well-formed'
				/ "i-enochian"        ; These tags are all valid,
				/ "i-hak"             ; but most are deprecated
				/ "i-klingon"         ; in favor of more modern
				/ "i-lux"             ; subtags or subtag
				/ "i-mingo"           ; combination
				/ "i-navajo"
				/ "i-pwn"
				/ "i-tao"
				/ "i-tay"
				/ "i-tsu"
				/ "sgn-BE-FR"
				/ "sgn-BE-NL"
				/ "sgn-CH-DE"

regular       = "art-lojban"          ; these tags match the 'langtag'
				/ "cel-gaulish"       ; production, but their subtags
				/ "no-bok"            ; are not extended language
				/ "no-nyn"            ; or variant subtags: their meaning
				/ "zh-guoyu"          ; is defined by their registration
				/ "zh-hakka"          ; and all of these are deprecated
				/ "zh-min"            ; in favor of a more modern
				/ "zh-min-nan"        ; subtag or sequence of subtags
				/ "zh-xiang"

alphanum      = (ALPHA / DIGIT)       ; letters and numbers
526
)==GRAMMAR==";