sourceafButter::QualityValues.fan


** Parses a 'Str' of HTTP qvalues as per HTTP 1.1 Spec / 
** [rfc2616-sec14.3]`http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3`. Provides some 
** useful accessor methods; like [#accepts(Str name)]`QualityValues.accepts` which returns 'true' only if the 
** name exists AND has a qvalue greater than 0.0.
**
**   syntax: fantom
**   QualityValues("audio/*; q=0.2, audio/basic")
** 
** @see `http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3`
class QualityValues {
    
    private Str:Float map
    
    private new make(Str:Float qvalues) {
        this.map = qvalues
    }
    
    ** Parses a HTTP header value into a 'name:qvalue' map.
    ** Values are comma delimited with an optional q value.
    ** 
    ** Throws 'ParseErr' if the header Str is invalid.
    ** 
    **   syntax: fantom
    **   QualityValues("audio/*; q=0.2, audio/basic")
    static new fromStr(Str? header := null, Bool checked := true) {
        qvalues := Utils.makeMap(Str#, Float#)
        
        if (header == null)
            return QualityValues(qvalues)

        try {
            header.split(',').each |val| {
                vals := val.split(';')
                
                if (vals.size == 1) {
                    qvalues[vals[0]] = 1.0f
                    return
                }
                
                if (vals.size == 2) {
                    qval := vals[1]
                    if (!qval.lower.startsWith("q="))
                        throw ParseErr("'$qval' should start with 'q='")
                    qnum := qval[2..-1].toFloat
                    if (qnum < 0.0f || qnum > 1.0f)
                        throw ParseErr("'$qnum' should be 0.0 >= q <= 1.0")
                    qvalues[vals[0]] = qnum
                    return
                }
                
                throw ParseErr("'$val' contains too many ';'")
            }

            return QualityValues(qvalues)
            
        } catch (ParseErr pe) {
            if (checked) throw pe
            return null
        }
    }

    ** Returns a joined-up Str of qvalues that may be set in a HTTP header. The names are sorted by 
    ** qvalue. Example:
    **
    **   audio/*; q=0.2, audio/basic
    override Str toStr() {
        map.keys.sortr |q1, q2| { map[q1] <=> map[q2] }.join(", ") |q| { map[q] == 1.0f ? "$q" : "$q;q=" + map[q].toLocale("0.###") }
    }

    ** Returns the qvalue associated with 'name'. Defaults to '0' if 'name' is not mapped.
    ** 
    ** Wildcards are *not* honoured but 'name' is case-insensitive.
    @Operator
    Float get(Str name) {
        map.get(name, 0f)
    }
    
    ** Sets the given quality value. 
    **  
    ** Wildcards are *not* honoured but 'name' is case-insensitive.
    @Operator
    This set(Str name, Float qval) {
        if (qval < 0.0f || qval > 1.0f)
            throw ArgErr("'$qval' should be 0.0 >= q <= 1.0")
        map[name] = qval
        return this
    }

    ** Returns 'true' if 'name' was supplied in the header.
    ** 
    ** This method matches against '*' wildcards.
    Bool contains(Str name) {
        map.any |qval, mime| {
            Regex.glob(mime).matches(name)
        }
    }

    ** Returns 'true' if the name was supplied in the header AND has a qvalue > 0.0
    ** 
    ** This method matches against '*' wildcards.
    Bool accepts(Str name) {
        map.any |qval, mime| {
            Regex.glob(mime).matches(name) && qval > 0f
        }
    }
    
    ** Returns the number of values given in the header
    Int size() {
        map.size
    }

    ** Returns 'size() == 0'
    Bool isEmpty() {
        map.isEmpty
    }
    
    ** Clears the qvalues
    Void clear() {
        map.clear
    }
    
    @NoDoc @Deprecated { msg="Use 'qvalues' instead" } 
    Str:Float toMap() {
        map.dup
    }
    
    ** Returns a dup of the internal 'name:qvalue' map.
    ** 
    ** Use 'get()' and 'set()' to modify qvalues.
    Str:Float qvalues() {
        map.dup
    }
}