Commit 2d04062e authored by Erwan Croze's avatar Erwan Croze 👋🏻
Browse files

Revert "Loading Vcard grammar instead of build this"

This reverts commit 34e07ea9.
parent e3ed5fd9
/*
vcard_grammar.hpp
Copyright (C) 2017 Belledonne Communications SARL
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
......@@ -19,10 +19,6 @@
#ifndef vcard_grammar_hpp
#define vcard_grammar_hpp
#include <belr/grammarbuilder.h>
#define VCARD_GRAMMAR "vcard_grammar"
std::shared_ptr<belr::Grammar> loadVcardGrammar();
extern const char *vcard_grammar;
#endif
......@@ -17,7 +17,7 @@
*/
#include "belcard/belcard_params.hpp"
#include "belcard/vcard_grammar.hpp"
using namespace::std;
using namespace::belr;
......@@ -25,7 +25,8 @@ using namespace::belcard;
template <typename T>
shared_ptr<T> BelCardParam::parseParam(const string& rule, const string& input) {
shared_ptr<Grammar> grammar = loadVcardGrammar();
ABNFGrammarBuilder grammar_builder;
shared_ptr<Grammar> grammar = grammar_builder.createFromAbnf((const char*)vcard_grammar, make_shared<CoreRules>());
Parser<shared_ptr<BelCardGeneric>> parser(grammar);
T::setHandlerAndCollectors(&parser);
shared_ptr<BelCardGeneric> ret = parser.parseInput(rule, input, NULL);
......@@ -94,7 +95,8 @@ BelCardLanguageParam::BelCardLanguageParam() : BelCardParam() {
}
shared_ptr<BelCardValueParam> BelCardValueParam::parse(const string& input) {
shared_ptr<Grammar> grammar = loadVcardGrammar();
ABNFGrammarBuilder grammar_builder;
shared_ptr<Grammar> grammar = grammar_builder.createFromAbnf((const char*)vcard_grammar, make_shared<CoreRules>());
Parser<shared_ptr<BelCardGeneric>> parser(grammar);
setHandlerAndCollectors(&parser);
shared_ptr<BelCardGeneric> ret = parser.parseInput("VALUE-param", input, NULL);
......@@ -238,4 +240,4 @@ void BelCardLabelParam::setHandlerAndCollectors(Parser<shared_ptr<BelCardGeneric
BelCardLabelParam::BelCardLabelParam() : BelCardParam() {
setName("LABEL");
}
}
\ No newline at end of file
......@@ -19,7 +19,6 @@
#include "belcard/belcard_parser.hpp"
#include "belcard/belcard.hpp"
#include "belcard/belcard_utils.hpp"
#include "belcard/vcard_grammar.hpp"
#include <iostream>
......@@ -36,7 +35,8 @@ shared_ptr<BelCardParser> BelCardParser::getInstance() {
}
BelCardParser::BelCardParser() {
shared_ptr<Grammar> grammar = loadVcardGrammar();
ABNFGrammarBuilder grammar_builder;
shared_ptr<Grammar> grammar = grammar_builder.createFromAbnf((const char*)vcard_grammar, make_shared<CoreRules>());
_parser = new Parser<shared_ptr<BelCardGeneric>>(grammar);
BelCardList::setHandlerAndCollectors(_parser);
......
This diff is collapsed.
vcard-list = vcard *vcard
vcard = "BEGIN:VCARD" [CR] LF
"VERSION:4.0" [CR] LF
1*property
"END:VCARD" *([CR] LF)
property = (SOURCE / KIND / XML
/ FN / N / NICKNAME / PHOTO / BDAY / ANNIVERSARY / GENDER
/ ADR
/ TEL / EMAIL / IMPP / LANG
/ TZ / GEO
/ TITLE / ROLE / LOGO / ORG / MEMBER / RELATED
/ CATEGORIES / NOTE / PRODID / REV / SOUND / UID / CLIENTPIDMAP / URL
/ KEY
/ FBURL / CALADRURI / CALURI
/ BIRTHPLACE / DEATHPLACE / DEATHDATE
/ X-PROPERTY) [CR] LF
X-PROPERTY = [group "."] X-PROPERTY-name *(";" X-PROPERTY-param) ":" X-PROPERTY-value
X-PROPERTY-name = x-name
X-PROPERTY-param = any-param
X-PROPERTY-value = text
/ 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
SOURCE = [group "."] "SOURCE" *(";" SOURCE-param) ":" SOURCE-value
SOURCE-param = VALUE-param / PID-param / PREF-param / ALTID-param / MEDIATYPE-param / any-param
SOURCE-value = URI
KIND = [group "."] "KIND" *(";" KIND-param) ":" KIND-value
KIND-param = VALUE-param / any-param
KIND-value = "individual" / "group" / "org" / "location" / iana-token / x-name
XML = [group "."] "XML" *(";" XML-param) ":" XML-value
XML-param = VALUE-param / any-param
XML-value = text
FN = [group "."] "FN" *(";" FN-param) ":" FN-value
FN-param = VALUE-param / TYPE-param / LANGUAGE-param / ALTID-param / PID-param / PREF-param / any-param
FN-value = text
N = [group "."] "N" *(";" N-param) ":" N-value
N-param = VALUE-param / SORT-AS-param / LANGUAGE-param / ALTID-param / any-param
N-value = N-fn ";" N-gn ";" N-an ";" N-prefixes ";" N-suffixes
N-fn = component *("," component)
N-gn = component *("," component)
N-an = component *("," component)
N-prefixes = component *("," component)
N-suffixes = component *("," component)
NICKNAME = [group "."] "NICKNAME" *(";" NICKNAME-param) ":" NICKNAME-value
NICKNAME-param = VALUE-param / TYPE-param / LANGUAGE-param / ALTID-param / PID-param / PREF-param / any-param
NICKNAME-value = text-list
BDAY = [group "."] "BDAY" *(";" BDAY-param) ":" BDAY-value
BDAY-param = VALUE-param / LANGUAGE-param / ALTID-param / CALSCALE-param / any-param
BDAY-value = date-and-or-time / text
ANNIVERSARY = [group "."] "ANNIVERSARY" *(";" ANNIVERSARY-param) ":" ANNIVERSARY-value
ANNIVERSARY-param = VALUE-param / ALTID-param / CALSCALE-param / any-param
ANNIVERSARY-value = date-and-or-time / text
GENDER = [group "."] "GENDER" *(";" GENDER-param) ":" GENDER-value
GENDER-param = VALUE-param / any-param
GENDER-value = [sex] [";" text]
sex = "M" / "F" / "O" / "N" / "U"
PHOTO = [group "."] "PHOTO" *(";" PHOTO-param) ":" PHOTO-value
PHOTO-param = VALUE-param / ALTID-param / TYPE-param / MEDIATYPE-param / PREF-param / PID-param / any-param
PHOTO-value = URI
ADR = [group "."] "ADR" *(";" ADR-param) ":" ADR-value
ADR-param = VALUE-param / LABEL-param / LANGUAGE-param / GEO-PARAM-param / TZ-PARAM-param / ALTID-param
/ PID-param / PREF-param / TYPE-param / any-param
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)
TEL = [group "."] "TEL" *(";" TEL-param) ":" TEL-value
TEL-param = VALUE-param / TYPE-param / PID-param / PREF-param / ALTID-param / any-param
TEL-value = URI / text
EMAIL = [group "."] "EMAIL" *(";" EMAIL-param) ":" EMAIL-value
EMAIL-param = VALUE-param / PID-param / PREF-param / TYPE-param / ALTID-param / any-param
EMAIL-value = text
IMPP = [group "."] "IMPP" *(";" IMPP-param) ":" IMPP-value
IMPP-param = VALUE-param / PID-param / PREF-param / TYPE-param / MEDIATYPE-param / ALTID-param / any-param
IMPP-value = URI
LANG = [group "."] "LANG" *(";" LANG-param) ":" LANG-value
LANG-param = VALUE-param / PID-param / PREF-param / ALTID-param / TYPE-param / any-param
LANG-value = Language-Tag
TZ = [group "."] "TZ" *(";" TZ-param) ":" TZ-value
TZ-param = VALUE-param / ALTID-param / PID-param / PREF-param / TYPE-param / MEDIATYPE-param / any-param
TZ-value = text / URI / utc-offset
GEO = [group "."] "GEO" *(";" GEO-param) ":" GEO-value
GEO-param = VALUE-param / PID-param / PREF-param / TYPE-param / MEDIATYPE-param / ALTID-param / any-param
GEO-value = text / URI
TITLE = [group "."] "TITLE" *(";" TITLE-param) ":" TITLE-value
TITLE-param = VALUE-param / LANGUAGE-param / PID-param / PREF-param
/ ALTID-param / TYPE-param / any-param
TITLE-value = text
ROLE = [group "."] "ROLE" *(";" ROLE-param) ":" ROLE-value
ROLE-param = VALUE-param / LANGUAGE-param / PID-param / PREF-param
/ TYPE-param / ALTID-param / any-param
ROLE-value = text
LOGO = [group "."] "LOGO" *(";" LOGO-param) ":" LOGO-value
LOGO-param = VALUE-param / LANGUAGE-param / PID-param / PREF-param
/ TYPE-param / MEDIATYPE-param / ALTID-param / any-param
LOGO-value = URI
ORG = [group "."] "ORG" *(";" ORG-param) ":" ORG-value
ORG-param = VALUE-param / SORT-AS-param / LANGUAGE-param / PID-param
/ PREF-param / ALTID-param / TYPE-param / any-param
ORG-value = component *(";" component)
MEMBER = [group "."] "MEMBER" *(";" MEMBER-param) ":" MEMBER-value
MEMBER-param = VALUE-param / PID-param / PREF-param / ALTID-param
/ MEDIATYPE-param / any-param
MEMBER-value = URI
RELATED = [group "."] "RELATED" *(";" RELATED-param) ":" RELATED-value
RELATED-param = VALUE-param / PID-param / PREF-param / ALTID-param / TYPE-param / any-param
RELATED-value = URI / text
CATEGORIES = [group "."] "CATEGORIES" *(";" CATEGORIES-param) ":" CATEGORIES-value
CATEGORIES-param = VALUE-param / PID-param / PREF-param / TYPE-param / ALTID-param / any-param
CATEGORIES-value = text-list
NOTE = [group "."] "NOTE" *(";" NOTE-param) ":" NOTE-value
NOTE-param = VALUE-param / LANGUAGE-param / PID-param / PREF-param / TYPE-param / ALTID-param / any-param
NOTE-value = text
PRODID = [group "."] "PRODID" *(";" PRODID-param) ":" PRODID-value
PRODID-param = VALUE-param / any-param
PRODID-value = text
REV = [group "."] "REV" *(";" REV-param) ":" REV-value
REV-param = VALUE-param / any-param
REV-value = timestamp
SOUND = [group "."] "SOUND" *(";" SOUND-param) ":" SOUND-value
SOUND-param = VALUE-param / LANGUAGE-param / PID-param / PREF-param
/ TYPE-param / MEDIATYPE-param / ALTID-param / any-param
SOUND-value = URI
UID = [group "."] "UID" *(";" UID-param) ":" UID-value
UID-param = VALUE-param / any-param
UID-value = URI / text
CLIENTPIDMAP = [group "."] "CLIENTPIDMAP" *(";" CLIENTPIDMAP-param) ":" CLIENTPIDMAP-value
CLIENTPIDMAP-param = any-param
CLIENTPIDMAP-value = 1*DIGIT ";" URI
URL = [group "."] "URL" *(";" URL-param) ":" URL-value
URL-param = VALUE-param / PID-param / PREF-param / TYPE-param
/ MEDIATYPE-param / ALTID-param / any-param
URL-value = URI
KEY = [group "."] "KEY" *(";" KEY-param) ":" KEY-value
KEY-param = VALUE-param / ALTID-param / PID-param / PREF-param / TYPE-param / any-param
KEY-value = URI / text
FBURL = [group "."] "FBURL" *(";" FBURL-param) ":" FBURL-value
FBURL-param = VALUE-param / PID-param / PREF-param / TYPE-param
/ MEDIATYPE-param / ALTID-param / any-param
FBURL-value = URI
CALADRURI = [group "."] "CALADRURI" *(";" CALADRURI-param) ":" CALADRURI-value
CALADRURI-param = VALUE-param / PID-param / PREF-param / TYPE-param
/ MEDIATYPE-param / ALTID-param / any-param
CALADRURI-value = URI
CALURI = [group "."] "CALURI" *(";" CALURI-param) ":" CALURI-value
CALURI-param = VALUE-param / PID-param / PREF-param / TYPE-param
/ MEDIATYPE-param / ALTID-param / any-param
CALURI-value = URI
BIRTHPLACE = [group "."] "BIRTHPLACE" *(";" BIRTHPLACE-param) ":" BIRTHPLACE-value
BIRTHPLACE-param = VALUE-param / ALTID-param / LANGUAGE-param / any-param
BIRTHPLACE-value = text / URI
DEATHPLACE = [group "."] "DEATHPLACE" *(";" DEATHPLACE-param) ":" DEATHPLACE-value
DEATHPLACE-param = VALUE-param / ALTID-param / LANGUAGE-param / any-param
DEATHPLACE-value = text / URI
DEATHDATE = [group "."] "DEATHDATE" *(";" DEATHDATE-param) ":" DEATHDATE-value
DEATHDATE-param = VALUE-param / ALTID-param / LANGUAGE-param / any-param
DEATHDATE-value = date-and-or-time / text
group = 1*(ALPHA / DIGIT / "-")
LANGUAGE-param = "LANGUAGE=" LANGUAGE-param-value
LANGUAGE-param-value = Language-Tag
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
PREF-param = "PREF=" PREF-param-value
PREF-param-value = (1*2DIGIT / "100")
ALTID-param = "ALTID=" ALTID-param-value
ALTID-param-value = param-value
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)
GEO-PARAM-param = "GEO=" GEO-PARAM-param
GEO-PARAM-param-value = DQUOTE URI DQUOTE
TZ-PARAM-param = "TZ=" TZ-PARAM-param-value
TZ-PARAM-param-value = (param-value / DQUOTE URI DQUOTE)
LABEL-param = "LABEL=" LABEL-param-value
LABEL-param-value = param-value
any-param = param-name "=" param-value
param-name = (iana-token / x-name)
param-value = param-value-component *("," param-value-component)
param-value-component = *SAFE-CHAR / (DQUOTE *QSAFE-CHAR DQUOTE)
iana-token = 1*(ALPHA / DIGIT / "-")
x-name = "x-" 1*(ALPHA / DIGIT / "-")
COMPONENT-CHAR = "\\" / "\," / "\;" / "\n" / WSP / NON-ASCII / %x21-2B / %x2D-3A / %x3C-5B / %x5D-7E
component = *COMPONENT-CHAR
list-component = component *("," component)
text-list = text *("," text)
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
SAFE-CHAR = WSP / "!" / %x23-2B / %x2D-39 / %x3C-7E / NON-ASCII
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
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]
/ year "-" month
/ "--" month [day]
/ "--" "-" day
date-noreduc = year month day
/ "--" month day
/ "--" "-" day
date-complete = year month day
time = hour [minute [second]] [zone]
/ "-" minute [second]
/ "-" "-" second
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]
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
type-name = reg-name
subtype-name = reg-name
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
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 ":"
pchar = unreserved / pct-encoded / sub-delims / ":" / "@" / "\,"
query = *( pchar / "/" / "?" )
fragment = *( pchar / "/" / "?" )
pct-encoded = "%" HEXDIG HEXDIG
unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
reserved = gen-delims / sub-delims
gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
/ "*" / "+" / "," / ";" / "="
Language-Tag = langtag ; normal language tags
/ privateuse ; private use tag
/ grandfathered ; grandfathered tags
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
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
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment