# WURFL Parsing WURFL (Wireless Universal Resource FiLe) is used by MediaMath to understand information about devices, browser, OS’s, and their capabilities. The following section discusses how to work with the various features that contain WURFL data. ## 1. Combine targeted and untargeted fields from the contextual data • Combine targeted and untargeted array to one array. • Sort resulting array by string length and pick the largest string for each dim. ```java {"24":{"1":{"targeted":["br_Firefox"],"untargeted":["br_Chrome Mobile:ve_67.0.3396"]}}} targeted_and_untargeted = ["br_Chrome Mobile:ve_67.0.3396", "br_Firefox"] -> for dimension "24" we will pick the largest string as follows "br_Chrome Mobile:ve_67.0.3396" entire output: browser="br_ChromeMobile" browser_version="br_ChromeMobile:ve_67.0.3396" os="" os_version="" device_type="" device_manufacturer="" device_model="" ``` ## 2. WURFL features extraction All features have default values if empty string (""). It will always generate 7 features as below: ### 2.1 `browser` Sets the name of the browser. It looks for identifier `br_` and reads the browser name until the first ":" char. It will fallback to an empty string if there are no more chars after `br_`. ### 2.2 `browser_version` Sets the version of the browser. It looks for the identifier `br_` and `ve_` and reads the entire string. It will fallback to an empty string if there are no more chars after `ve_`. ### 2.3 `os` Sets the name of the operating system. It looks for the identifier `os_` and reads the os name until the first ":" char. It will fallback to an empty string if there are no more chars after`os_`. ### 2.4 `os_version` Sets the version of the operating system. It looks for the identifier `os_` and `ve_` and reads the entire string. It will fallback to an empty string if there are no more chars after `ve_`. ### 2.5 `device_manufacturer` Sets the manufacturer.It looks for the identifier `ma_` and reads the os name until the first ":" char. It will fallback to an empty string if there are no more chars after `ma_`. ### 2.6 `device_model` Sets the manufacturer and model. It looks for the identifier `ma_` and `mo_` and reads the entire string. It will fallback to an empty string if there are no more chars after `mo_`. ### 2.7 `device_type` Sets the type of the device and may return any of these values: Desktop, App, Tablet, Smartphone, Feature Phone, Smart-TV, Robot, Other non-Mobile, Other Mobile.It look for the identifier `fo_` and reads the entire string. It will fallback to an empty string if there are no more chars after `fo_`. ## 3. Example Feature extraction ### 3.1 Feature extraction Example 1 ```json {"24":{"1":{"targeted":[],"untargeted":["br_Chrome Mobile:ve_67.0.3396"]}},"25":{"1":{"targeted":[],"untargeted":["os_Android:ve_8.1.0"]}},"29":{"1":{"targeted":[],"untargeted":["ma_Generic:mo_Android 2.0"]}},"26":{"1":{"targeted":[],"untargeted":["fo_Feature Phone"]}}} ``` ```ini browser="br_ChromeMobile" browser_version="br_ChromeMobile:ve_67.0.3396" os="os_Android" os_version="os_Android:ve_8.1.0" device_type="fo_FeaturePhone" device_manufacturer="ma_Generic" device_model="ma_Generic:mo_Android2.0" ``` ### 3.2 Feature extraction Example 2 ```json {"24":{"1":{"targeted":[],"untargeted":["br_"]}},"25":{"1":{"targeted":[],"untargeted":["os_Android:ve_"]}},"29":{"1":{"targeted":[],"untargeted":["ma_:mo_"]}},"26":{"1":{"targeted":[],"untargeted":["fo_Feature Phone"]}}} ``` ```ini browser="" browser_version="" os="os_Android" os_version="" device_type="fo_FeaturePhone" device_manufacturer="" device_model="" ``` ## 4.Source Code ```go package main import ( "fmt" "sort" "strings" "encoding/json" ) type Num1 struct { Targeted []string `json:"targeted"` Untargeted []string `json:"untargeted"` } type wurfulData struct { Num24 struct { Dim Num1 `json:"1"` } `json:"24"` Num25 struct { Dim Num1 `json:"1"` } `json:"25"` Num26 struct { Dim Num1 `json:"1"` } `json:"26"` Num29 struct { Dim Num1 `json:"1"` } `json:"29"` } func extractDim(dim *Num1, dimID string, appendTo *[]string) { type conf struct { lookup []string logbrainNames []string } var dimLookUpMap = map[string]conf{ "24": conf{[]string{"br_", "ve_"}, []string{"browser", "browser_version"}}, "25": conf{[]string{"os_", "ve_"}, []string{"os", "os_version"}}, "29": conf{[]string{"ma_", "mo_"}, []string{"device_manufacturer", "device_model"}}, "26": conf{[]string{"fo_"}, []string{"device_type"}}, } dim.Targeted = append(dim.Targeted, dim.Untargeted...) sort.Slice(dim.Targeted, func(i, j int) bool { return len(dim.Targeted[i]) > len(dim.Targeted[j]) }) cf := dimLookUpMap[dimID] wurflFeatures := make(map[string]string) for _, featureName := range cf.logbrainNames { wurflFeatures[featureName] = "" } // at the moment we only support dim_id = 24,25,26,29 if len(cf.lookup) <= 0 { return } if len(dim.Targeted) <= 0 { for featureName, featureVal := range wurflFeatures { *appendTo = append(*appendTo,featureName+"^"+featureVal) } return } stringWithValue := strings.Replace(dim.Targeted[0], " ", "", -1) // handles device_type if len(cf.lookup) == 1 && strings.Contains(dim.Targeted[0], cf.lookup[0]) && !strings.HasSuffix(dim.Targeted[0], "_") { wurflFeatures[cf.logbrainNames[0]] = stringWithValue } // handles the browser, os, device_manufacturer if len(cf.lookup) == 2 && strings.Contains(stringWithValue, cf.lookup[0]) { splitted := strings.Split(stringWithValue, ":") if !strings.HasSuffix(splitted[0], "_") { wurflFeatures[cf.logbrainNames[0]] = splitted[0] } if len(splitted) == 2 && !strings.HasSuffix(stringWithValue, "_") { wurflFeatures[cf.logbrainNames[1]] = stringWithValue } } for featureName, featureVal := range wurflFeatures { *appendTo = append(*appendTo,featureName+"^"+featureVal) } } func getWurfulFeatureValues(raw []byte) []string { var wd wurfulData json.Unmarshal(raw, &wd) var res []string extractDim(&wd.Num24.Dim, "24", &res) extractDim(&wd.Num25.Dim, "25", &res) extractDim(&wd.Num26.Dim, "26", &res) extractDim(&wd.Num29.Dim, "29", &res) return res } func testEq(a, b []string) bool { if a == nil && b == nil { return true } if a == nil || b == nil { return false } if len(a) != len(b) { return false } for i := range a { found := false for j := range b { if a[i] == b[j] { found = true break } } if !found { return false } } return true } func example1() { rawWurflData := `{"24":{"1":{"targeted":["br_Firefox"],"untargeted":["br_Chrome Mobile:ve_67.0.3396"]}}}` wurflFeatureValues := getWurfulFeatureValues([]byte(rawWurflData)) fmt.Println(wurflFeatureValues) } func example2() { rawWurflData := `{}` wurflFeatureValues := getWurfulFeatureValues([]byte(rawWurflData)) fmt.Println(wurflFeatureValues) } func example3() { rawWurflData := `{"24":{"1":{"targeted":[],"untargeted":["br_Chrome Mobile:ve_67.0.3396"]}},"25":{"1":{"targeted":[],"untargeted":["os_Android:ve_8.1.0"]}},"29":{"1":{"targeted":[],"untargeted":["ma_Generic:mo_Android 2.0"]}},"26":{"1":{"targeted":[],"untargeted":["fo_Feature Phone"]}}}` wurflFeatureValues := getWurfulFeatureValues([]byte(rawWurflData)) fmt.Println(wurflFeatureValues) } func example4() { rawWurflData := `{"24":{"1":{"targeted":[],"untargeted":["br_"]}},"25":{"1":{"targeted":[],"untargeted":["os_Android:ve_"]}},"29":{"1":{"targeted":[],"untargeted":["ma_:mo_"]}},"26":{"1":{"targeted":[],"untargeted":["fo_Feature Phone"]}}}` wurflFeatureValues := getWurfulFeatureValues([]byte(rawWurflData)) fmt.Println(wurflFeatureValues) } func main() { example1() example2() example3() example4() } ```