///|
const IFC_GUID_ALPHABET : String = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_$"
///|
pub fn ifc_guid_alphabet() -> String {
IFC_GUID_ALPHABET
}
///|
fn guid_is_valid_character(character : Char) -> Bool {
IFC_GUID_ALPHABET.contains_char(character)
}
///|
fn guid_checksum(value : String) -> Int {
let mut checksum = 17
for character in value {
checksum = (checksum * 31 + character.to_int()) % 1000003
}
checksum
}
///|
/// Validate the lexical constraints of an IFC compressed GUID.
pub fn validate_ifc_guid(value : String) -> GuidReport {
let trimmed = value.trim()
let mut valid = trimmed.length() == 22
let mut decoded = 0
for character in trimmed {
if guid_is_valid_character(character) {
decoded += 1
} else {
valid = false
}
}
let message = if trimmed.length() != 22 {
"IFC compressed GUID must contain 22 characters"
} else if decoded != 22 {
"IFC compressed GUID contains an unsupported character"
} else {
"valid lexical IFC compressed GUID"
}
{
value,
valid,
decoded_bytes: if valid {
16
} else {
0
},
normalized: trimmed.to_owned(),
message,
}
}
///|
pub fn ifc_guid_is_valid(value : String) -> Bool {
validate_ifc_guid(value).valid
}
///|
pub fn ifc_guid_fingerprint(value : String) -> String {
let report = validate_ifc_guid(value)
if !report.valid {
"invalid"
} else {
let hash = guid_checksum(report.normalized)
"guid-" + hash.to_string()
}
}
///|
pub fn GuidReport::headline(self : GuidReport) -> String {
self.normalized + " valid=" + self.valid.to_string() + " " + self.message
}
///|
pub fn GuidReport::is_canonical(self : GuidReport) -> Bool {
self.valid && self.value == self.normalized
}
///|
pub fn GuidReport::to_json(self : GuidReport) -> String {
"{\"value\":\"" +
self.value +
"\",\"normalized\":\"" +
self.normalized +
"\",\"valid\":" +
self.valid.to_string() +
",\"decoded_bytes\":" +
self.decoded_bytes.to_string() +
",\"fingerprint\":\"" +
ifc_guid_fingerprint(self.value) +
"\"}"
}
///|
pub fn model_guid_reports(model : Model) -> Array[GuidReport] {
model
.products()
.fold(init=Array::new(), (result, product) => {
match product.global_id {
Some(value) => result.push(validate_ifc_guid(value))
None => ()
}
result
})
}
///|
pub fn invalid_model_guids(model : Model) -> Array[GuidReport] {
model_guid_reports(model).filter(report => !report.valid)
}
///|
pub fn duplicate_model_guids(model : Model) -> Array[String] {
let counts : Map[String, Int] = Map([])
for report in model_guid_reports(model) {
if report.valid {
counts.update_or_default(report.normalized, 1, count => count + 1)
}
}
let result : Array[String] = []
for value, count in counts {
if count > 1 {
result.push(value)
}
}
result
}
///|
pub fn guid_quality_score(model : Model) -> Int {
let reports = model_guid_reports(model)
if reports.length() == 0 {
return 0
}
let valid = reports.filter(report => report.valid).length()
valid * 100 / reports.length()
}
///|
pub fn guid_quality_summary(model : Model) -> Array[String] {
let reports = model_guid_reports(model)
[
"checked=" + reports.length().to_string(),
"valid=" + reports.filter(report => report.valid).length().to_string(),
"invalid=" + invalid_model_guids(model).length().to_string(),
"duplicates=" + duplicate_model_guids(model).length().to_string(),
"score=" + guid_quality_score(model).to_string(),
]
}
///|
pub fn ifc_guid_from_product(model : Model, product_id : Int) -> GuidReport? {
for product in model.products() {
if product.id == product_id {
return product.global_id.map(value => validate_ifc_guid(value))
}
}
None
}
///|
pub fn normalize_ifc_guid(value : String) -> String {
validate_ifc_guid(value).normalized
}
///|
pub fn guid_fingerprint_map(model : Model) -> Map[String, Int] {
model_guid_reports(model).fold(init=Map([]), (result, report) => {
if report.valid {
result.update_or_default(ifc_guid_fingerprint(report.value), 1, count => {
count + 1
})
}
result
})
}