Last updated

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.

{"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 afteros_.

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

{"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"]}}}
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

{"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"]}}}
browser=""
browser_version=""
os="os_Android"
os_version=""
device_type="fo_FeaturePhone"
device_manufacturer=""
device_model=""

4.Source Code

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()
}