diff --git a/animation.go b/animation.go index 9133413..87f6778 100644 --- a/animation.go +++ b/animation.go @@ -245,12 +245,16 @@ func parseAnimation(obj DataObject) AnimationProperty { animation := new(animationData) animation.init() - for node := range obj.Properties() { - tag := PropertyName(node.Tag()) - if node.Type() == TextNode { - animation.Set(tag, node.Text()) - } else { - animation.Set(tag, node) + for tag, node := range obj.Properties() { + switch node.Type() { + case TextDataValue: + animation.Set(PropertyName(tag), node.String()) + + case ObjectDataValue: + animation.Set(PropertyName(tag), node.Object()) + + case ArrayDataValue: + animation.Set(PropertyName(tag), node.Array()) } } return animation @@ -418,6 +422,47 @@ func normalizeAnimation(tag PropertyName) PropertyName { } func animationSet(properties Properties, tag PropertyName, value any) []PropertyName { + + parseObject := func(obj DataObject) (AnimatedProperty, bool) { + result := AnimatedProperty{} + for tag, value := range obj.Properties() { + if value.Type() == TextDataValue { + propTag := strings.ToLower(tag) + switch propTag { + case "from", "0", "0%": + result.From = value.String() + + case "to", "100", "100%": + result.To = value.String() + + default: + tagLen := len(propTag) + if tagLen > 0 && propTag[tagLen-1] == '%' { + propTag = propTag[:tagLen-1] + } + n, err := strconv.Atoi(propTag) + if err != nil { + ErrorLog(err.Error()) + } else if n < 0 || n > 100 { + ErrorLogF(`key-frame "%d" is out of range`, n) + } else { + if result.KeyFrames == nil { + result.KeyFrames = map[int]any{n: value.String()} + } else { + result.KeyFrames[n] = value.String() + } + } + } + } + } + + if len(obj.Tag()) > 0 { + result.Tag = PropertyName(obj.Tag()) + } + + return result, result.From != nil && result.To != nil + } + switch tag { case ID: if text, ok := value.(string); ok { @@ -486,72 +531,38 @@ func animationSet(properties Properties, tag PropertyName, value any) []Property ErrorLog("[]AnimatedProperty is empty") } - case DataNode: - parseObject := func(obj DataObject) (AnimatedProperty, bool) { - result := AnimatedProperty{} - for node := range obj.Properties() { - if node.Type() == TextNode { - propTag := strings.ToLower(node.Tag()) - switch propTag { - case "from", "0", "0%": - result.From = node.Text() - - case "to", "100", "100%": - result.To = node.Text() - - default: - tagLen := len(propTag) - if tagLen > 0 && propTag[tagLen-1] == '%' { - propTag = propTag[:tagLen-1] - } - n, err := strconv.Atoi(propTag) - if err != nil { - ErrorLog(err.Error()) - } else if n < 0 || n > 100 { - ErrorLogF(`key-frame "%d" is out of range`, n) - } else { - if result.KeyFrames == nil { - result.KeyFrames = map[int]any{n: node.Text()} - } else { - result.KeyFrames[n] = node.Text() - } - } - } - } - } - - if len(obj.Tag()) > 0 { - result.Tag = PropertyName(obj.Tag()) - } - - if result.From != nil && result.To != nil { - return result, true - } - return result, false + case DataObject: + if prop, ok := parseObject(value); ok { + properties.setRaw(tag, []AnimatedProperty{prop}) + return []PropertyName{tag} } - switch value.Type() { - case ObjectNode: - if prop, ok := parseObject(value.Object()); ok { - properties.setRaw(tag, []AnimatedProperty{prop}) - return []PropertyName{tag} - } - - case ArrayNode: - props := []AnimatedProperty{} - for val := range value.ArrayElements() { - if val.IsObject() { - if prop, ok := parseObject(val.Object()); ok { - props = append(props, prop) - } - } else { - notCompatibleType(tag, val) + case DataArray: + props := []AnimatedProperty{} + for val := range value.Elements() { + if val.Type() == ObjectDataValue { + if prop, ok := parseObject(val.Object()); ok { + props = append(props, prop) } + } else { + notCompatibleType(tag, val) } - if len(props) > 0 { - properties.setRaw(tag, props) - return []PropertyName{tag} - } + } + if len(props) > 0 { + properties.setRaw(tag, props) + return []PropertyName{tag} + } + + case DataValue: + switch value.Type() { + case TextDataValue: + return animationSet(properties, tag, value.String()) + + case ObjectDataValue: + return animationSet(properties, tag, value.Object()) + + case ArrayDataValue: + return animationSet(properties, tag, value.Array()) default: notCompatibleType(tag, value) @@ -1088,25 +1099,38 @@ func setAnimationProperty(properties Properties, tag PropertyName, value any) bo return true } - case DataNode: + case DataArray: animations := []AnimationProperty{} result := true - for i := range value.ArraySize() { - if obj := value.ArrayElement(i).Object(); obj != nil { + for val := range value.Elements() { + if obj := val.Object(); obj != nil { if anim := parseAnimation(obj); anim.hasAnimatedProperty() { animations = append(animations, anim) } else { result = false } } else { - notCompatibleType(tag, value.ArrayElement(i)) + notCompatibleType(tag, val) result = false } } + if result && len(animations) > 0 { set(animations) } return result + + case DataValue: + switch value.Type() { + case TextDataValue: + return setAnimationProperty(properties, tag, value.String()) + + case ObjectDataValue: + return setAnimationProperty(properties, tag, value.Object()) + + case ArrayDataValue: + return setAnimationProperty(properties, tag, value.Array()) + } } notCompatibleType(tag, value) diff --git a/animationEvents.go b/animationEvents.go index a5f00ac..82bc8cc 100644 --- a/animationEvents.go +++ b/animationEvents.go @@ -157,7 +157,7 @@ const ( ) func (view *viewData) handleTransitionEvents(tag PropertyName, data DataObject) { - if propertyName, ok := data.PropertyValue("property"); ok { + if propertyName, ok := data.PropertyText("property"); ok { property := PropertyName(propertyName) if tag == TransitionEndEvent || tag == TransitionCancelEvent { if animation, ok := view.singleTransition[property]; ok { @@ -177,7 +177,7 @@ func (view *viewData) handleTransitionEvents(tag PropertyName, data DataObject) func (view *viewData) handleAnimationEvents(tag PropertyName, data DataObject) { if listeners := getOneArgEventListeners[View, string](view, nil, tag); len(listeners) > 0 { id := "" - if name, ok := data.PropertyValue("name"); ok { + if name, ok := data.PropertyText("name"); ok { for _, animation := range GetAnimation(view) { if name == animation.animationName() { id, _ = stringProperty(animation, ID, view.Session()) diff --git a/appServer.go b/appServer.go index 9d88859..fd6c439 100644 --- a/appServer.go +++ b/appServer.go @@ -272,7 +272,7 @@ func (app *application) sseHandler(w http.ResponseWriter, req *http.Request) { } func getSessionID(obj DataObject) (int, bool) { - sessionText, ok := obj.PropertyValue("session") + sessionText, ok := obj.PropertyText("session") if !ok { ErrorLog(`"session" key not found`) return 0, false diff --git a/background.go b/background.go index 65917fe..1e01eb3 100644 --- a/background.go +++ b/background.go @@ -47,6 +47,10 @@ type backgroundElement struct { } func createBackground(obj DataObject) BackgroundElement { + if obj == nil { + return nil + } + var result BackgroundElement = nil switch obj.Tag() { @@ -66,11 +70,9 @@ func createBackground(obj DataObject) BackgroundElement { return nil } - for node := range obj.Properties() { - if node.Type() == TextNode { - if value := node.Text(); value != "" { - result.Set(PropertyName(node.Tag()), value) - } + for tag, text := range obj.TextProperties() { + if text != "" { + result.Set(PropertyName(tag), text) } } @@ -96,30 +98,47 @@ func parseBackgroundValue(value any) []BackgroundElement { case []BackgroundElement: return value - case []DataValue: - background := []BackgroundElement{} - for _, el := range value { - if el.IsObject() { - if element := createBackground(el.Object()); element != nil { - background = append(background, element) - } else { - return nil - } - } else if element := parseBackgroundText(el.Value()); element != nil { - background = append(background, element) - } else { - return nil + case DataValue: + switch value.Type() { + case TextDataValue: + if element := parseBackgroundText(value.String()); element != nil { + return []BackgroundElement{element} } - } - return background - case DataObject: - if element := createBackground(value); element != nil { - return []BackgroundElement{element} + case ObjectDataValue: + if element := createBackground(value.Object()); element != nil { + return []BackgroundElement{element} + } + + case ArrayDataValue: + if array := value.Array(); array != nil { + background := make([]BackgroundElement, 0, array.Size()) + for element := range array.Elements() { + switch element.Type() { + case ObjectDataValue: + if element := createBackground(element.Object()); element != nil { + background = append(background, element) + } else { + return nil + } + + case TextDataValue: + if element := parseBackgroundText(element.String()); element != nil { + background = append(background, element) + } else { + return nil + } + + case ArrayDataValue: + return nil + } + } + return background + } } case []DataObject: - background := []BackgroundElement{} + background := make([]BackgroundElement, 0, len(value)) for _, obj := range value { if element := createBackground(obj); element != nil { background = append(background, element) @@ -164,7 +183,6 @@ func parseBackgroundValue(value any) []BackgroundElement { } } return elements - } return nil diff --git a/border.go b/border.go index 27f2deb..c5c320c 100644 --- a/border.go +++ b/border.go @@ -205,16 +205,13 @@ func newBorderProperty(value any) BorderProperty { case BorderProperty: return value - case DataNode: - if value.Type() == ObjectNode { + case DataValue: + if value.Type() == ObjectDataValue { _ = border.setBorderObject(value.Object()) } else { return nil } - case DataObject: - _ = border.setBorderObject(value) - case ViewBorder: border.properties[Style] = value.Style border.properties[Width] = value.Width @@ -434,17 +431,20 @@ func (border *borderProperty) String() string { } func (border *borderProperty) setBorderObject(obj DataObject) bool { + if obj == nil { + return false + } + result := true - for node := range obj.Properties() { - tag := PropertyName(node.Tag()) + for tag, node := range obj.Properties() { switch node.Type() { - case TextNode: - if borderSet(border, tag, node.Text()) == nil { + case TextDataValue: + if borderSet(border, PropertyName(tag), node.String()) == nil { result = false } - case ObjectNode: - if borderSet(border, tag, node.Object()) == nil { + case ObjectDataValue: + if borderSet(border, PropertyName(tag), node.Object()) == nil { result = false } @@ -535,21 +535,21 @@ func borderSet(properties Properties, tag PropertyName, value any) []PropertyNam setSingleBorderObject := func(prefix PropertyName, obj DataObject) []PropertyName { result := []PropertyName{} - if text, ok := obj.PropertyValue(string(Style)); ok { + if text, ok := obj.PropertyText(string(Style)); ok { props := setEnumProperty(properties, prefix+"-style", text, enumProperties[BorderStyle].values) if props == nil { return nil } result = append(result, props...) } - if text, ok := obj.PropertyValue(string(ColorTag)); ok { + if text, ok := obj.PropertyText(string(ColorTag)); ok { props := setColorProperty(properties, prefix+"-color", text) if props == nil && len(result) == 0 { return nil } result = append(result, props...) } - if text, ok := obj.PropertyValue("width"); ok { + if text, ok := obj.PropertyText("width"); ok { props := setSizeProperty(properties, prefix+"-width", text) if props == nil && len(result) == 0 { return nil diff --git a/bounds.go b/bounds.go index e1af8cf..2fe5b38 100644 --- a/bounds.go +++ b/bounds.go @@ -265,7 +265,7 @@ func setBoundsProperty(properties Properties, tag PropertyName, value any) []Pro case DataObject: bounds := NewBoundsProperty(nil) for _, tag := range []PropertyName{Top, Right, Bottom, Left} { - if text, ok := value.PropertyValue(string(tag)); ok { + if text, ok := value.PropertyText(string(tag)); ok { if !bounds.Set(tag, text) { notCompatibleType(tag, value) return nil diff --git a/bounds_test.go b/bounds_test.go index 19ffcef..db6273a 100644 --- a/bounds_test.go +++ b/bounds_test.go @@ -66,7 +66,7 @@ func TestBoundsSet(t *testing.T) { } for _, tag := range []string{"padding-left", "padding-top", "padding-right", "padding-bottom"} { - if old, ok := obj.PropertyValue(tag); ok { + if old, ok := obj.PropertyText(tag); ok { ignoreTestLog = true obj.SetPropertyValue(tag, "x") if bounds.setProperty(obj, "padding", session) { diff --git a/clientStorage.go b/clientStorage.go index 4c78b31..87e9f5e 100644 --- a/clientStorage.go +++ b/clientStorage.go @@ -113,7 +113,7 @@ func (storage *clientStorageData) RemoveAll() { func (storage *clientStorageData) handleEvent(command string, data DataObject) { request := 0 - if text, ok := data.PropertyValue("request"); ok { + if text, ok := data.PropertyText("request"); ok { var err error if request, err = strconv.Atoi(text); err != nil { ErrorLog(err.Error()) @@ -126,7 +126,7 @@ func (storage *clientStorageData) handleEvent(command string, data DataObject) { switch command { case "storageError": - if text, ok := data.PropertyValue("error"); ok { + if text, ok := data.PropertyText("error"); ok { ErrorLog(text) } @@ -137,7 +137,7 @@ func (storage *clientStorageData) handleEvent(command string, data DataObject) { } delete(storage.getResult, request) - text, ok := data.PropertyValue("values") + text, ok := data.PropertyText("values") if !ok { ErrorLog("'values' property not found (command: storageValues)") return diff --git a/clipShape.go b/clipShape.go index 00fe64b..409c7d0 100644 --- a/clipShape.go +++ b/clipShape.go @@ -582,7 +582,7 @@ func parseClipShapeProperty(obj DataObject) ClipShapeProperty { clip := new(insetClipData) clip.init() for _, tag := range []PropertyName{Top, Right, Bottom, Left, Radius, RadiusX, RadiusY} { - if value, ok := obj.PropertyValue(string(tag)); ok { + if value, ok := obj.PropertyText(string(tag)); ok { insetClipDataSet(clip, tag, value) } } @@ -592,7 +592,7 @@ func parseClipShapeProperty(obj DataObject) ClipShapeProperty { clip := new(circleClipData) clip.init() for _, tag := range []PropertyName{X, Y, Radius} { - if value, ok := obj.PropertyValue(string(tag)); ok { + if value, ok := obj.PropertyText(string(tag)); ok { circleClipDataSet(clip, tag, value) } } @@ -602,7 +602,7 @@ func parseClipShapeProperty(obj DataObject) ClipShapeProperty { clip := new(ellipseClipData) clip.init() for _, tag := range []PropertyName{X, Y, RadiusX, RadiusY} { - if value, ok := obj.PropertyValue(string(tag)); ok { + if value, ok := obj.PropertyText(string(tag)); ok { ellipseClipDataSet(clip, tag, value) } } @@ -611,7 +611,7 @@ func parseClipShapeProperty(obj DataObject) ClipShapeProperty { case "polygon": clip := new(polygonClipData) clip.init() - if value, ok := obj.PropertyValue(string(Points)); ok { + if value, ok := obj.PropertyText(string(Points)); ok { polygonClipDataSet(clip, Points, value) } return clip @@ -645,7 +645,7 @@ func setClipShapePropertyProperty(properties Properties, tag PropertyName, value } case DataValue: - if value.IsObject() { + if value.Type() == ObjectDataValue { if clip := parseClipShapeProperty(value.Object()); clip != nil { properties.setRaw(tag, clip) return []PropertyName{tag} diff --git a/color.go b/color.go index dcb1a44..4f50458 100644 --- a/color.go +++ b/color.go @@ -222,20 +222,20 @@ func parseColorPair(obj DataObject) (ColorPair, bool) { var err error result := ColorPair{} - for property := range obj.Properties() { - if property.Type() != TextNode { + for tag, property := range obj.Properties() { + if property.Type() != TextDataValue { return ColorPair{}, false } - switch property.Tag() { + switch tag { case "light": - result.Light, err = stringToColor(property.Text()) + result.Light, err = stringToColor(property.String()) if err != nil { return ColorPair{}, false } case "dark": - result.Dark, err = stringToColor(property.Text()) + result.Dark, err = stringToColor(property.String()) if err != nil { return ColorPair{}, false } diff --git a/colorPicker.go b/colorPicker.go index 29629fc..63b27a5 100644 --- a/colorPicker.go +++ b/colorPicker.go @@ -159,7 +159,7 @@ func (picker *colorPickerData) htmlProperties(self View, buffer *strings.Builder func (picker *colorPickerData) handleCommand(self View, command PropertyName, data DataObject) bool { switch command { case "textChanged": - if text, ok := data.PropertyValue("text"); ok { + if text, ok := data.PropertyText("text"); ok { if color, ok := StringToColor(text); ok { oldColor := GetColorPickerValue(picker) picker.setRaw(ColorPickerValue, color) diff --git a/columnSeparator.go b/columnSeparator.go index 5ed3a12..bdda7be 100644 --- a/columnSeparator.go +++ b/columnSeparator.go @@ -36,7 +36,7 @@ func newColumnSeparatorProperty(value any) ColumnSeparatorProperty { separator := new(columnSeparatorProperty) separator.init() for _, tag := range []PropertyName{Style, Width, ColorTag} { - if val, ok := value.PropertyValue(string(tag)); ok && val != "" { + if val, ok := value.PropertyText(string(tag)); ok && val != "" { propertiesSet(separator, tag, value) } } diff --git a/data.go b/data.go index b46db41..c9dbe1b 100644 --- a/data.go +++ b/data.go @@ -3,387 +3,131 @@ package rui import ( "errors" "fmt" - "iter" + "os" "slices" + "strconv" "strings" "unicode" ) -// DataValue interface of a data node value -type DataValue interface { - // IsObject returns "true" if data value is an object - IsObject() bool +// DataValueType defines the type of DataNode +type DataValueType int - // Object returns data value as a data object - Object() DataObject - - // Value returns value as a string - Value() string -} - -// DataObject interface of a data object -type DataObject interface { - DataValue - - // Tag returns data object tag - Tag() string - - // Properties() returns an iterator to access the properties - Properties() iter.Seq[DataNode] - - // PropertyCount returns properties count - PropertyCount() int - - // Property returns a data node corresponding to a property with specific index - Property(index int) DataNode - - // PropertyByTag returns a data node corresponding to a property tag - PropertyByTag(tag string) DataNode - - // PropertyValue returns a string value of a property with a specific tag - PropertyValue(tag string) (string, bool) - - // PropertyObject returns an object value of a property with a specific tag - PropertyObject(tag string) DataObject - - // SetPropertyValue sets a string value of a property with a specific tag - SetPropertyValue(tag, value string) - - // SetPropertyObject sets an object value of a property with a specific tag - SetPropertyObject(tag string, object DataObject) - - // ToParams create a params(map) representation of a data object - ToParams() Params - - // PropertyByTag removes a data node corresponding to a property tag and returns it - RemovePropertyByTag(tag string) DataNode -} - -// DataNodeType defines the type of DataNode -type DataNodeType int - -// Constants which are used to describe a node type, see [DataNode] +// Constants which are used to describe a value type, see [DataValue] const ( - // TextNode - node is the pair "tag - text value". Syntax: = - TextNode DataNodeType = 0 - // ObjectNode - node is the pair "tag - object". Syntax: = {...} - ObjectNode DataNodeType = 1 - // ArrayNode - node is the pair "tag - object". Syntax: = [...] - ArrayNode DataNodeType = 2 + // TextDataValue - the DataValue value is a string. + // The String() method of DataValue interface is used to access the value. + TextDataValue DataValueType = 0 + + // ObjectDataValue - the DataValue value is a object. + // The Object() method of DataValue interface is used to access the object. + ObjectDataValue DataValueType = 1 + + // ArrayDataNode - the DataValue value is an array of DataValue. + // The Array() method of DataValue interface is used to access the array. + ArrayDataValue DataValueType = 2 ) -// DataNode interface of a data node -type DataNode interface { - // Tag returns a tag name - Tag() string +// DataValue interface of a data node value +type DataValue interface { + fmt.Stringer - // Type returns a node type. Possible values are TextNode, ObjectNode and ArrayNode - Type() DataNodeType + writeToBuffer(buffer *strings.Builder, indent string) - // Text returns node text - Text() string + // IsObject returns "true" if data value is an object + Type() DataValueType - // Object returns node as object if that node type is an object + // Object returns data value as a data object if Type() == ObjectDataValue, and nil otherwise. Object() DataObject - // ArraySize returns array size if that node type is an array - ArraySize() int - - // ArrayElement returns a value of an array if that node type is an array - ArrayElement(index int) DataValue - - // ArrayElements returns an array of objects if that node is an array - Array() []DataValue - - // ArrayElements returns an iterator to access the array elements of objects if that node is an array - ArrayElements() iter.Seq[DataValue] - - // ArrayAsParams returns an array of a params(map) if that node is an array - ArrayAsParams() []Params + // Array returns data value as a data array if Type() == ArrayDataValue, and nil otherwise. + Array() DataArray } /******************************************************************************/ type dataStringValue struct { - value string + text string } -func (value *dataStringValue) Value() string { - return value.value +// NewTextData creates an DataValue object of TextDataValue type. +// +// The argument is converted to a string using the following functions: +// - int: strconv.FormatInt(value, 10) +// - uint: strconv.FormatUint(value, 10) +// - bool: fmt.Sprintf("%t", value) +// - float: fmt.Sprintf("%g", value) +func NewTextData[T string | int | int8 | int16 | int32 | int64 | + uint | uint8 | uint16 | uint32 | uint64 | bool | float32 | float64](value T) DataValue { + + data := new(dataStringValue) + switch value := any(value).(type) { + case string: + data.text = value + + case int: + data.text = strconv.FormatInt(int64(value), 10) + + case int8: + data.text = strconv.FormatInt(int64(value), 10) + + case int16: + data.text = strconv.FormatInt(int64(value), 10) + + case int32: + data.text = strconv.FormatInt(int64(value), 10) + + case int64: + data.text = strconv.FormatInt(value, 10) + + case uint: + data.text = strconv.FormatUint(uint64(value), 10) + + case uint8: + data.text = strconv.FormatUint(uint64(value), 10) + + case uint16: + data.text = strconv.FormatUint(uint64(value), 10) + + case uint32: + data.text = strconv.FormatUint(uint64(value), 10) + + case uint64: + data.text = strconv.FormatUint(uint64(value), 10) + + case bool: + data.text = fmt.Sprintf("%t", value) + + case float32: + data.text = fmt.Sprintf("%g", float64(value)) + + case float64: + data.text = fmt.Sprintf("%g", value) + } + + return data } -func (value *dataStringValue) IsObject() bool { - return false +func (value *dataStringValue) String() string { + return value.text +} + +func (value *dataStringValue) writeToBuffer(buffer *strings.Builder, indent string) { + writeRuiText(buffer, value.text, false) +} + +func (value *dataStringValue) Type() DataValueType { + return TextDataValue } func (value *dataStringValue) Object() DataObject { return nil } -/******************************************************************************/ -type dataObject struct { - tag string - property []DataNode -} - -// NewDataObject create new DataObject with the tag and empty property list -func NewDataObject(tag string) DataObject { - obj := new(dataObject) - obj.tag = tag - obj.property = []DataNode{} - return obj -} - -func (object *dataObject) Value() string { - return "" -} - -func (object *dataObject) IsObject() bool { - return true -} - -func (object *dataObject) Object() DataObject { - return object -} - -func (object *dataObject) Tag() string { - return object.tag -} - -func (object *dataObject) Properties() iter.Seq[DataNode] { - return func(yield func(DataNode) bool) { - for _, node := range object.property { - if !yield(node) { - return - } - } - } -} - -func (object *dataObject) PropertyCount() int { - if object.property != nil { - return len(object.property) - } - return 0 -} - -func (object *dataObject) Property(index int) DataNode { - if object.property == nil || index < 0 || index >= len(object.property) { - return nil - } - return object.property[index] -} - -func (object *dataObject) PropertyByTag(tag string) DataNode { - if object.property != nil { - for _, node := range object.property { - if node.Tag() == tag { - return node - } - } - } +func (value *dataStringValue) Array() DataArray { return nil } -func (object *dataObject) RemovePropertyByTag(tag string) DataNode { - if object.property != nil { - index := slices.IndexFunc(object.property, func(node DataNode) bool { - return node.Tag() == tag - }) - - if index >= 0 { - node := object.property[index] - - switch index { - case 0: - object.property = object.property[1:] - - case len(object.property) - 1: - object.property = object.property[:len(object.property)-1] - - default: - object.property = append(object.property[:index], object.property[index+1:]...) - } - - return node - } - } - return nil -} - -func (object *dataObject) PropertyValue(tag string) (string, bool) { - if node := object.PropertyByTag(tag); node != nil && node.Type() == TextNode { - return node.Text(), true - } - return "", false -} - -func (object *dataObject) PropertyObject(tag string) DataObject { - if node := object.PropertyByTag(tag); node != nil && node.Type() == ObjectNode { - return node.Object() - } - return nil -} - -func (object *dataObject) setNode(node DataNode) { - if len(object.property) == 0 { - object.property = []DataNode{node} - } else { - tag := node.Tag() - for i, p := range object.property { - if p.Tag() == tag { - object.property[i] = node - return - } - } - - object.property = append(object.property, node) - } -} - -// SetPropertyValue - set a string property with tag by value -func (object *dataObject) SetPropertyValue(tag, value string) { - val := new(dataStringValue) - val.value = value - node := new(dataNode) - node.tag = tag - node.value = val - object.setNode(node) -} - -// SetPropertyObject - set a property with tag by object -func (object *dataObject) SetPropertyObject(tag string, obj DataObject) { - if obj != nil { - node := new(dataNode) - node.tag = tag - node.value = obj - object.setNode(node) - } else { - object.RemovePropertyByTag(tag) - } -} - -func (object *dataObject) ToParams() Params { - params := Params{} - for _, node := range object.property { - switch node.Type() { - case TextNode: - if text := node.Text(); text != "" { - params[PropertyName(node.Tag())] = text - } - - case ObjectNode: - if obj := node.Object(); obj != nil { - params[PropertyName(node.Tag())] = node.Object() - } - - case ArrayNode: - array := []any{} - for i := range node.ArraySize() { - if data := node.ArrayElement(i); data != nil { - if data.IsObject() { - if obj := data.Object(); obj != nil { - array = append(array, obj) - } - } else if text := data.Value(); text != "" { - array = append(array, text) - } - } - } - if len(array) > 0 { - params[PropertyName(node.Tag())] = array - } - } - } - - return params -} - /******************************************************************************/ -type dataNode struct { - tag string - value DataValue - array []DataValue -} - -func (node *dataNode) Tag() string { - return node.tag -} - -func (node *dataNode) Type() DataNodeType { - if node.array != nil { - return ArrayNode - } - if node.value.IsObject() { - return ObjectNode - } - return TextNode -} - -func (node *dataNode) Text() string { - if node.value != nil { - return node.value.Value() - } - return "" -} - -func (node *dataNode) Object() DataObject { - if node.value != nil { - return node.value.Object() - } - return nil -} - -func (node *dataNode) ArraySize() int { - if node.array != nil { - return len(node.array) - } - return 0 -} - -func (node *dataNode) ArrayElement(index int) DataValue { - if node.array != nil && index >= 0 && index < len(node.array) { - return node.array[index] - } - return nil -} - -func (node *dataNode) Array() []DataValue { - if node.array != nil { - return slices.Clone(node.array) - } - return []DataValue{node.value} -} - -func (node *dataNode) ArrayElements() iter.Seq[DataValue] { - return func(yield func(DataValue) bool) { - if node.array != nil { - for _, element := range node.array { - if !yield(element) { - return - } - } - } else { - yield(node.value) - } - } -} - -func (node *dataNode) ArrayAsParams() []Params { - result := []Params{} - if node.array != nil { - for _, data := range node.array { - if data.IsObject() { - if obj := data.Object(); obj != nil { - if params := obj.ToParams(); len(params) > 0 { - result = append(result, params) - } - } - } - } - } - return result -} - type dataParser struct { data []rune size int @@ -446,6 +190,14 @@ func (parser *dataParser) skipSpaces(skipNewLine bool) { } } +func (parser *dataParser) skipSpacesAndCommas() { + parser.skipSpaces(true) + for parser.data[parser.pos] == ',' && parser.pos < parser.size { + parser.pos++ + parser.skipSpaces(true) + } +} + func (parser *dataParser) parseTag() (string, error) { parser.skipSpaces(true) startPos := parser.pos @@ -593,62 +345,53 @@ func (parser *dataParser) stopSymbol(symbol rune) bool { slices.Contains([]rune{'=', '{', '}', '[', ']', ',', ' ', '\t', '\n', '\'', '"', '`', '/'}, symbol) } -func (parser *dataParser) parseNode() (DataNode, error) { - var tag string - var err error +func (parser *dataParser) parseNode() (string, DataValue, error) { - if tag, err = parser.parseTag(); err != nil { - return nil, err + tag, err := parser.parseTag() + if err != nil { + return "", nil, err } parser.skipSpaces(true) if parser.data[parser.pos] != '=' { - return nil, fmt.Errorf("expected '=' after a tag name (line: %d, position: %d)", parser.line, parser.pos-parser.lineStart) + return "", nil, fmt.Errorf("expected '=' after a tag name (line: %d, position: %d)", parser.line, parser.pos-parser.lineStart) } parser.pos++ parser.skipSpaces(true) switch parser.data[parser.pos] { case '[': - node := new(dataNode) - node.tag = tag - - if node.array, err = parser.parseArray(); err != nil { - return nil, err + array, err := parser.parseArray() + if err != nil { + return "", nil, err } - return node, nil + return tag, NewDataArray(array), nil case '{': - node := new(dataNode) - node.tag = tag - if node.value, err = parser.parseObject("_"); err != nil { - return nil, err + obj, err := parser.parseObject("_") + if err != nil { + return "", nil, err } - return node, nil + return tag, obj, nil case '}', ']', '=': - return nil, fmt.Errorf(`expected '[', '{' or a tag name after '=' (line: %d, position: %d)`, parser.line, parser.pos-parser.lineStart) + return "", nil, fmt.Errorf(`expected '[', '{' or a tag name after '=' (line: %d, position: %d)`, parser.line, parser.pos-parser.lineStart) default: - var str string - if str, err = parser.parseTag(); err != nil { - return nil, err + text, err := parser.parseTag() + if err != nil { + return "", nil, err } - node := new(dataNode) - node.tag = tag - if parser.data[parser.pos] == '{' { - if node.value, err = parser.parseObject(str); err != nil { - return nil, err + obj, err := parser.parseObject(text) + if err != nil { + return "", nil, err } - } else { - val := new(dataStringValue) - val.value = str - node.value = val + return tag, obj, nil } - return node, nil + return tag, NewTextData(text), nil } } @@ -658,9 +401,7 @@ func (parser *dataParser) parseObject(tag string) (DataObject, error) { } parser.pos++ - obj := new(dataObject) - obj.tag = tag - obj.property = []DataNode{} + obj := NewDataObject(tag) for parser.pos < parser.size { parser.skipSpaces(true) @@ -670,29 +411,29 @@ func (parser *dataParser) parseObject(tag string) (DataObject, error) { return obj, nil } - node, err := parser.parseNode() + property, value, err := parser.parseNode() if err != nil { return nil, err } + obj.SetProperty(property, value) - obj.property = append(obj.property, node) - if parser.data[parser.pos] == '}' { + switch parser.data[parser.pos] { + case '}': parser.pos++ parser.skipSpaces(true) return obj, nil - } else if parser.data[parser.pos] != ',' && parser.data[parser.pos] != '\n' { + + case ',': + parser.pos++ + + case '\n': + // do nothing + + default: return nil, fmt.Errorf(`expected '}', '\n' or ',' (line: %d, position: %d)`, parser.line, parser.pos-parser.lineStart) } - if parser.data[parser.pos] != '\n' { - parser.pos++ - } - - parser.skipSpaces(true) - for parser.data[parser.pos] == ',' { - parser.pos++ - parser.skipSpaces(true) - } + parser.skipSpacesAndCommas() } return nil, errors.New("unexpected end of text") @@ -704,64 +445,60 @@ func (parser *dataParser) parseArray() ([]DataValue, error) { array := []DataValue{} + parser.skipSpacesAndCommas() for parser.pos < parser.size { - parser.skipSpaces(true) - for parser.data[parser.pos] == ',' && parser.pos < parser.size { - parser.pos++ - parser.skipSpaces(true) - } + if parser.data[parser.pos] != ']' { + if parser.data[parser.pos] == '[' { - if parser.pos >= parser.size { - break - } + subarray, err := parser.parseArray() + if err != nil { + return nil, err + } + array = append(array, NewDataArray(subarray)) - if parser.data[parser.pos] == ']' { - parser.pos++ - parser.skipSpaces(true) - return array, nil - } + } else { + tag, err := parser.parseTag() + if err != nil { + return nil, err + } - tag, err := parser.parseTag() - if err != nil { - return nil, err - } - - if parser.data[parser.pos] == '{' { - obj, err := parser.parseObject(tag) - if err != nil { - return nil, err + if parser.data[parser.pos] == '{' { + obj, err := parser.parseObject(tag) + if err != nil { + return nil, err + } + array = append(array, obj) + } else { + array = append(array, NewTextData(tag)) + } } - array = append(array, obj) - } else { - val := new(dataStringValue) - val.value = tag - array = append(array, val) } switch parser.data[parser.pos] { - case ']', ',', '\n': + case ']': + parser.pos++ + parser.skipSpaces(true) + return array, nil + + case '\n': + // do nothing + + case ',': + parser.pos++ default: return nil, fmt.Errorf(`expected ']' or ',' (line: %d, position: %d)`, parser.line, parser.pos-parser.lineStart) } - /* - if data[pos] == ']' { - pos++ - skipSpaces() - return array, nil - } else if data[pos] != ',' { - return nil, fmt.Errorf("Expected ']' or ',' (line: %d, position: %d)", line, pos-lineStart) - } - pos++ - skipSpaces() - */ + parser.skipSpacesAndCommas() } return nil, errors.New("unexpected end of text") } -// ParseDataText - parse text and return DataNode +// ParseDataText parses a text. +// If successful, it returns a DataObject and nil as the second element; +// otherwise, it returns nil and an error. func ParseDataText(text string) (DataObject, error) { if strings.ContainsAny(text, "\r") { @@ -783,3 +520,16 @@ func ParseDataText(text string) (DataObject, error) { } return parser.parseObject(tag) } + +// ReadDataObject reads a text file from the specified path, parses it. +// If successful, it returns a DataObject and nil as the second element; +// otherwise, it returns nil and an error. +func ReadDataObject(path string) (DataObject, error) { + data, err := os.ReadFile(path) + if err != nil { + debugLog(err.Error()) + return nil, err + } + + return ParseDataText(string(data)) +} diff --git a/dataArray.go b/dataArray.go new file mode 100644 index 0000000..37bedbc --- /dev/null +++ b/dataArray.go @@ -0,0 +1,404 @@ +package rui + +import ( + "iter" + "slices" + "strings" +) + +// DataValue interface of a data node value +type DataArray interface { + DataValue + + // Size returns returns the size of the array. + Size() int + + // Element returns a value of an array element. + Element(index int) DataValue + + // Slice returns an all element as a slice. The returned slice is a copy of the data. + // Modifying the slice does not affect the internal structure of the DataArray. + Slice() []DataValue + + // Elements returns an iterator to access the array elements + Elements() iter.Seq[DataValue] + + // ElementsWithIndices returns an iterator to access the array elements with itheir indices + ElementsWithIndices() iter.Seq2[int, DataValue] + + // TextElements returns an iterator for accessing the text elements of the array. + // Elements of type DataArray and DataObject are skipped. + TextElements() iter.Seq[string] + + // Append adds the specified elements to the end of the array. + // + // All nil elements are skipped (not added to the array). + Append(values ...DataValue) + + // Append adds the specified elements to the end of the array. + AppendText(values ...string) + + // Append adds the specified elements to the end of the array. + AppendInt(values ...int) + + // Append adds the specified elements to the end of the array. + AppendFloat(values ...float64) + + // Append adds the specified elements to the end of the array. + AppendArray(array DataArray) + + // Insert inserts the specified elements into the array starting at the element with index. + // + // All nil elements are skipped (not added to the array). + // + // If index < 0 then the elements are inserted at the beginning of the array. + // + // If the index is greater than the array size, the elements are inserted at the end of the array. + Insert(index int, values ...DataValue) + + // Insert inserts the specified elements into the array starting at the element with index. + // + // If index < 0 then the elements are inserted at the beginning of the array. + // + // If the index is greater than the array size, the elements are inserted at the end of the array. + InsertText(index int, values ...string) + + // Insert inserts the specified elements into the array starting at the element with index. + // + // If index < 0 then the elements are inserted at the beginning of the array. + // + // If the index is greater than the array size, the elements are inserted at the end of the array. + InsertInt(index int, values ...int) + + // Insert inserts the specified elements into the array starting at the element with index. + // + // If index < 0 then the elements are inserted at the beginning of the array. + // + // If the index is greater than the array size, the elements are inserted at the end of the array. + InsertFloat(index int, values ...float64) + + // Remove removes the element at the specified index from the array and returns it. It returns nil if the index is out of bounds. + Remove(index int) DataValue + + // RemoveN removes count elements from the array starting at the specified index and returns them. + // It returns empty array if the index is out of bounds. + RemoveN(index int, count int) []DataValue + + // SingleLineString returns how the result of the String() method will be formatted. + // + // If result is true, then all array elements are output on a single line (separated by ", "). + // + // If result is false, then each array element is output on a new line. + SingleLineString() bool + + // SetSingleLineString Determines how the result of the String() method is formatted. + // + // If argument is true, then all array elements are output on a single line (separated by ", "). + // + // If argument is false, then each array element is output on a new line. + SetSingleLineString(flag bool) +} + +type dataArrayDataValue struct { + array []DataValue + singleLineString bool +} + +// NewDataArray creates an DataValue object of ArrayDataValue type. +// +// When creating the object, all nil elements are skipped (not added to the array). +func NewDataArray(array []DataValue) DataArray { + data := new(dataArrayDataValue) + + if array == nil { + data.array = []DataValue{} + } else { + data.array = make([]DataValue, 0, len(array)) + for _, element := range array { + if element != nil { + data.array = append(data.array, element) + } + } + } + + return data +} + +// NewDataArrayOf creates an DataValue object of ArrayDataValue type. +// +// When creating the object, all nil elements are skipped (not added to the array). +func NewDataArrayOf(array ...DataValue) DataArray { + data := new(dataArrayDataValue) + + data.array = make([]DataValue, 0, len(array)) + for _, element := range array { + if element != nil { + data.array = append(data.array, element) + } + } + + return data +} + +// NewTexDataArray creates a DataArray object (an DataValue object of ArrayDataValue type). +// +// All elements of the argument are converted to DataValue using the NewDataText function. +func NewTextDataArray[T string | int | int8 | int16 | int32 | int64 | + uint | uint8 | uint16 | uint32 | uint64 | bool | float32 | float64](array []T) DataArray { + data := new(dataArrayDataValue) + + if array == nil { + data.array = []DataValue{} + } else { + data.array = make([]DataValue, 0, len(array)) + for _, element := range array { + data.array = append(data.array, NewTextData(element)) + } + } + + if len(data.array) < 10 { + data.singleLineString = true + } + + return data +} + +// NewTextDataArrayOf creates a DataArray object (an DataValue object of ArrayDataValue type). +// +// All arguments are converted to DataValue using the NewDataText function. +func NewTextDataArrayOf[T string | int | int8 | int16 | int32 | int64 | + uint | uint8 | uint16 | uint32 | uint64 | bool | float32 | float64](values ...T) DataArray { + data := new(dataArrayDataValue) + + data.array = make([]DataValue, 0, len(values)) + for _, element := range values { + data.array = append(data.array, NewTextData(element)) + } + + if len(data.array) < 10 { + data.singleLineString = true + } + + return data +} + +func (value *dataArrayDataValue) String() string { + buffer := allocStringBuilder() + defer freeStringBuilder(buffer) + + value.writeToBuffer(buffer, "") + + return buffer.String() +} + +func (value *dataArrayDataValue) writeToBuffer(buffer *strings.Builder, indent string) { + + if len(value.array) == 0 { + buffer.WriteString("[]") + return + } + + indent2 := indent + "\t" + + if value.singleLineString { + + lead := "[" + for _, element := range value.array { + buffer.WriteString(lead) + element.writeToBuffer(buffer, indent2) + lead = ", " + } + + } else { + + buffer.WriteString("[\n") + for _, element := range value.array { + buffer.WriteString(indent2) + element.writeToBuffer(buffer, indent2) + buffer.WriteString(",\n") + } + + buffer.WriteString(indent) + } + + buffer.WriteString("]") +} + +func (value *dataArrayDataValue) Type() DataValueType { + return ArrayDataValue +} + +func (value *dataArrayDataValue) Object() DataObject { + return nil +} + +func (value *dataArrayDataValue) Array() DataArray { + return value +} + +func (value *dataArrayDataValue) Size() int { + return len(value.array) +} + +func (value *dataArrayDataValue) Element(index int) DataValue { + if index >= 0 && index < len(value.array) { + return value.array[index] + } + return nil +} + +func (value *dataArrayDataValue) Slice() []DataValue { + return slices.Clone(value.array) +} + +func (value *dataArrayDataValue) Elements() iter.Seq[DataValue] { + return func(yield func(DataValue) bool) { + for _, element := range value.array { + if !yield(element) { + return + } + } + } +} + +func (value *dataArrayDataValue) TextElements() iter.Seq[string] { + return func(yield func(string) bool) { + for _, element := range value.array { + if element.Type() == TextDataValue { + if !yield(element.String()) { + return + } + } + } + } +} + +func (value *dataArrayDataValue) ElementsWithIndices() iter.Seq2[int, DataValue] { + return func(yield func(int, DataValue) bool) { + for i, element := range value.array { + if !yield(i, element) { + return + } + } + } +} + +func (value *dataArrayDataValue) Append(data ...DataValue) { + for _, d := range data { + if d != nil { + value.array = append(value.array, d) + } + } +} + +func (value *dataArrayDataValue) AppendText(values ...string) { + for _, v := range values { + value.array = append(value.array, NewTextData(v)) + } +} + +func (value *dataArrayDataValue) AppendInt(values ...int) { + for _, v := range values { + value.array = append(value.array, NewTextData(v)) + } +} + +func (value *dataArrayDataValue) AppendFloat(values ...float64) { + for _, v := range values { + value.array = append(value.array, NewTextData(v)) + } +} + +func (value *dataArrayDataValue) AppendArray(array DataArray) { + if array != nil { + for d := range array.Elements() { + value.array = append(value.array, d) + } + } +} + +func (value *dataArrayDataValue) Insert(index int, data ...DataValue) { + if index >= len(value.array) { + value.Append(data...) + return + } + + if index < 0 { + index = 0 + } + value.array = slices.Insert(value.array, index, data...) +} + +func (value *dataArrayDataValue) InsertText(index int, values ...string) { + data := make([]DataValue, len(values)) + for i, v := range values { + data[i] = NewTextData(v) + } + value.Insert(index, data...) +} + +func (value *dataArrayDataValue) InsertInt(index int, values ...int) { + data := make([]DataValue, len(values)) + for i, v := range values { + data[i] = NewTextData(v) + } + value.Insert(index, data...) +} + +func (value *dataArrayDataValue) InsertFloat(index int, values ...float64) { + data := make([]DataValue, len(values)) + for i, v := range values { + data[i] = NewTextData(v) + } + value.Insert(index, data...) +} + +func (value *dataArrayDataValue) Remove(index int) DataValue { + size := len(value.array) + if index < 0 || index >= size { + return nil + } + + result := value.array[index] + switch index { + case 0: + value.array = value.array[1:] + + case size - 1: + value.array = value.array[:index] + + default: + value.array = append(value.array[:index], value.array[index+1:]...) + } + return result +} + +func (value *dataArrayDataValue) RemoveN(index int, count int) []DataValue { + size := len(value.array) + if index < 0 || index >= size { + return nil + } + + if index+count >= size { + result := value.array[index:] + value.array = value.array[:index] + return result + } + + result := value.array[index : index+count] + if index == 0 { + value.array = value.array[count:] + } else { + value.array = append(value.array[:index], value.array[index+count:]...) + } + + return result +} + +func (value *dataArrayDataValue) SetSingleLineString(flag bool) { + value.singleLineString = true +} + +func (value *dataArrayDataValue) SingleLineString() bool { + return value.singleLineString +} diff --git a/dataArray_test.go b/dataArray_test.go new file mode 100644 index 0000000..03e9a41 --- /dev/null +++ b/dataArray_test.go @@ -0,0 +1,62 @@ +package rui + +import "testing" + +func TestDataArray(t *testing.T) { + array := NewTextDataArrayOf(0, 1, 2, 3, 4, 5) + array.SetSingleLineString(true) + + if array.String() != "[0, 1, 2, 3, 4, 5]" { + t.Error(`NewTextDataArrayOf(0, 1, 2, 3, 4, 5) fail. Result: ` + array.String()) + } + + array.AppendInt(6, 7) + if array.String() != "[0, 1, 2, 3, 4, 5, 6, 7]" { + t.Error(`array.AppendInt(6, 7) fail. Result: ` + array.String()) + } + + array.InsertInt(0, 8, 9) + if array.String() != "[8, 9, 0, 1, 2, 3, 4, 5, 6, 7]" { + t.Error(`array.InsertInt(0, 8, 9) fail. Result: ` + array.String()) + } + + array.InsertInt(2, 10, 11, 12) + if array.String() != "[8, 9, 10, 11, 12, 0, 1, 2, 3, 4, 5, 6, 7]" { + t.Error(`array.InsertInt(2, 10, 11, 12) fail. Result: ` + array.String()) + } + + array.InsertInt(100, 13, 14) + if array.String() != "[8, 9, 10, 11, 12, 0, 1, 2, 3, 4, 5, 6, 7, 13, 14]" { + t.Error(`array.InsertInt(100, 13, 14) fail. Result: ` + array.String()) + } + + array.Remove(0) + if array.String() != "[9, 10, 11, 12, 0, 1, 2, 3, 4, 5, 6, 7, 13, 14]" { + t.Error(`array.Remove(0) fail. Result: ` + array.String()) + } + + array.Remove(1) + if array.String() != "[9, 11, 12, 0, 1, 2, 3, 4, 5, 6, 7, 13, 14]" { + t.Error(`array.Remove(1) fail. Result: ` + array.String()) + } + + array.Remove(12) + if array.String() != "[9, 11, 12, 0, 1, 2, 3, 4, 5, 6, 7, 13]" { + t.Error(`array.Remove(12) fail. Result: ` + array.String()) + } + + array.RemoveN(0, 2) + if array.String() != "[12, 0, 1, 2, 3, 4, 5, 6, 7, 13]" { + t.Error(`array.RemoveN(0, 2) fail. Result: ` + array.String()) + } + + array.RemoveN(1, 3) + if array.String() != "[12, 3, 4, 5, 6, 7, 13]" { + t.Error(`array.RemoveN(1, 3) fail. Result: ` + array.String()) + } + + array.RemoveN(5, 12) + if array.String() != "[12, 3, 4, 5, 6]" { + t.Error(`array.RemoveN(5, 12) fail. Result: ` + array.String()) + } +} diff --git a/dataList.go b/dataList.go index c631459..6f58d10 100644 --- a/dataList.go +++ b/dataList.go @@ -127,14 +127,21 @@ func anyToStringArray(value any, dateTimeFormat string) ([]string, bool) { case []string: return value, true - case []DataValue: - items := make([]string, 0, len(value)) - for _, val := range value { - if !val.IsObject() { - items = append(items, val.Value()) + case DataValue: + switch value.Type() { + case TextDataValue: + return []string{value.String()}, true + + case ArrayDataValue: + array := value.Array() + items := make([]string, 0, array.Size()) + for val := range array.Elements() { + if val.Type() == TextDataValue { + items = append(items, val.String()) + } } + return items, true } - return items, true case []fmt.Stringer: items := make([]string, len(value)) diff --git a/dataObject.go b/dataObject.go new file mode 100644 index 0000000..4c47d76 --- /dev/null +++ b/dataObject.go @@ -0,0 +1,1013 @@ +package rui + +import ( + "fmt" + "iter" + "os" + "reflect" + "slices" + "strconv" + "strings" +) + +// DataObject interface of a data object +type DataObject interface { + DataValue + + // Tag returns data object tag + Tag() string + + // Properties() returns an iterator to access the properties + Properties() iter.Seq2[string, DataValue] + + // TextProperties() returns an iterator to access the text properties. Objects and Arrays are skipped. + TextProperties() iter.Seq2[string, string] + + // PropertyCount returns properties count + PropertyCount() int + + // PropertyTags() returns an iterator to access the property tags + PropertyTags() iter.Seq[string] + + // SetSortedTags specifies whether properties will be sorted by tag + SetSortedTags(sorted bool) + + // Property returns a data node corresponding to the specified chain of tags. + // If such a property does not exist, then nil is returned. + // + // A chain of tags is specified by the "tag" and "tags" arguments. + // For example + // + // p := object.Property("tag1", "tag2", "tag2") + // + // equivalent to the following code + // + // var p DataValue + // p = nil + // if p1 := object.Property("tag1"); p1 != nil { + // if o1 := p1.Object(); o1 != nil { + // if p2 := object.Property("tag2"); p2 != nil { + // if o2 := p2.Object(); o2 != nil { + // p = object.Property("tag3") + // } + // } + // } + // } + Property(tag string, tags ...string) DataValue + + // PropertyValue returns a string value of a property with the specified chain of tags (see Property method). + PropertyText(tag string, tags ...string) (string, bool) + + // PropertyObject returns an object value of a property with the specified chain of tags (see Property method). + // If such a property does not exist or the value is not a DataObject, then nil is returned. + PropertyObject(tag string, tags ...string) DataObject + + // PropertyObject returns an array value of a property with the specified chain of tags (see Property method). + // If such a property does not exist or the value is not a DataArray, then nil is returned. + PropertyArray(tag string, tags ...string) DataArray + + // SetProperty sets a value of a property with a specific tag + SetProperty(tag string, value DataValue) + + // SetPropertyText sets a string value of a property with a specific tag + SetPropertyText(tag, value string) + + // PropertyByTag removes a data node corresponding to a property tag and returns it + RemoveProperty(tag string) DataValue + + // ToParams create a params(map) representation of a data object + ToParams() Params + + // ToStruct fills the struct fields with property values of the DataObject. + // A pointer to the struct to be filled is passed as an argument. + ToStruct(data any) + + // WriteToFile converts the data object to text and write it to the named file, creating it if necessary. + // If the file does not exist, WriteToFile creates it with permissions perm (before umask); + // otherwise WriteFile truncates it before writing, without changing permissions. + WriteToFile(path string, perm os.FileMode) error +} + +type dataObjectProperty struct { + tag string + value DataValue +} + +type dataObject struct { + tag string + sorted bool + property []dataObjectProperty +} + +func (value *dataObject) String() string { + buffer := allocStringBuilder() + defer freeStringBuilder(buffer) + + value.writeToBuffer(buffer, "") + + return buffer.String() +} + +func (object *dataObject) writeToBuffer(buffer *strings.Builder, indent string) { + + if len(object.tag) > 0 { + writeRuiText(buffer, object.tag, true) + } + + if len(object.property) == 0 { + buffer.WriteString("{}") + return + } + + indent2 := indent + "\t" + buffer.WriteString("{\n") + + for _, property := range object.property { + buffer.WriteString(indent2) + writeRuiText(buffer, property.tag, true) + buffer.WriteString(" = ") + property.value.writeToBuffer(buffer, indent2) + buffer.WriteString(",\n") + } + + buffer.WriteString(indent) + buffer.WriteString("}") + +} + +func (object *dataObject) Type() DataValueType { + return ObjectDataValue +} + +func (object *dataObject) Object() DataObject { + return object +} + +func (value *dataObject) Array() DataArray { + return nil +} + +func (object *dataObject) Tag() string { + return object.tag +} + +func (object *dataObject) Properties() iter.Seq2[string, DataValue] { + return func(yield func(string, DataValue) bool) { + for _, property := range object.property { + if !yield(property.tag, property.value) { + return + } + } + } +} + +func (object *dataObject) TextProperties() iter.Seq2[string, string] { + return func(yield func(string, string) bool) { + for _, property := range object.property { + if property.value.Type() == TextDataValue { + if !yield(property.tag, property.value.String()) { + return + } + } + } + } +} + +func (object *dataObject) PropertyCount() int { + return len(object.property) +} + +func (object *dataObject) PropertyTags() iter.Seq[string] { + return func(yield func(string) bool) { + for _, property := range object.property { + if !yield(property.tag) { + return + } + } + } +} + +func (object *dataObject) SetSortedTags(sorted bool) { + object.sorted = sorted + if sorted { + slices.SortFunc(object.property, func(e1, e2 dataObjectProperty) int { + return strings.Compare(e1.tag, e2.tag) + }) + } +} + +func (object *dataObject) indexByTag(tag string) int { + if object.property != nil { + return slices.IndexFunc(object.property, func(property dataObjectProperty) bool { + return property.tag == tag + }) + } + return -1 +} + +func (object *dataObject) Property(tag string, tags ...string) DataValue { + if index := object.indexByTag(tag); index >= 0 { + property := object.property[index].value + if len(tags) > 0 { + for _, t := range tags { + obj := property.Object() + if obj == nil { + return nil + } + + property = obj.Property(t) + if property == nil { + return nil + } + } + } + return property + + } + return nil +} + +func (object *dataObject) RemoveProperty(tag string) DataValue { + if object.property != nil { + if index := object.indexByTag(tag); index >= 0 { + node := object.property[index] + + switch index { + case 0: + object.property = object.property[1:] + + case len(object.property) - 1: + object.property = object.property[:len(object.property)-1] + + default: + object.property = append(object.property[:index], object.property[index+1:]...) + } + + return node.value + } + } + return nil +} + +func (object *dataObject) PropertyText(tag string, tags ...string) (string, bool) { + if node := object.Property(tag, tag); node != nil && node.Type() == TextDataValue { + return node.String(), true + } + return "", false +} + +func (object *dataObject) PropertyObject(tag string, tags ...string) DataObject { + if node := object.Property(tag); node != nil { + return node.Object() + } + return nil +} + +func (object *dataObject) PropertyArray(tag string, tags ...string) DataArray { + if node := object.Property(tag); node != nil { + return node.Array() + } + return nil +} + +func (object *dataObject) SetProperty(tag string, value DataValue) { + if value == nil { + object.RemoveProperty(tag) + return + } + + if index := object.indexByTag(tag); index >= 0 { + object.property[index].value = value + } else { + object.property = append(object.property, dataObjectProperty{ + tag: tag, + value: value, + }) + + if object.sorted { + slices.SortFunc(object.property, func(e1, e2 dataObjectProperty) int { + return strings.Compare(e1.tag, e2.tag) + }) + } + } +} + +// SetPropertyValue - set a string property with tag by value +func (object *dataObject) SetPropertyText(tag, text string) { + object.SetProperty(tag, NewTextData(text)) +} + +func (object *dataObject) ToParams() Params { + params := Params{} + for _, node := range object.property { + switch node.value.Type() { + case TextDataValue: + if text := node.value.String(); text != "" { + params[PropertyName(node.tag)] = text + } + + case ObjectDataValue: + if obj := node.value.Object(); obj != nil { + params[PropertyName(node.tag)] = obj + } + + case ArrayDataValue: + if nodeArray := node.value.Array(); nodeArray != nil { + array := make([]any, 0, nodeArray.Size()) + for data := range nodeArray.Elements() { + if data != nil { + switch data.Type() { + case TextDataValue: + if text := data.String(); text != "" { + array = append(array, text) + } + + case ArrayDataValue: + if dataArray := data.Array(); dataArray != nil && dataArray.Size() > 0 { + array = append(array, dataArray) + } + + default: + array = append(array, data) + } + } + } + + if len(array) > 0 { + params[PropertyName(node.tag)] = array + } + } + } + } + + return params +} + +func copyToReflectSlice(array DataArray, value reflect.Value) { + val := reflect.New(value.Type()) + val = val.Elem() + + switch val.Type().Elem().Kind() { + case reflect.Pointer: + for data := range array.Elements() { + s := reflect.New(val.Type().Elem()).Elem() + copyToReflectValue(data, s) + val = reflect.Append(val, s) + } + + case reflect.String: + for text := range array.TextElements() { + val = reflect.Append(val, reflect.ValueOf(text)) + } + + case reflect.Int: + for text := range array.TextElements() { + if n, err := strconv.ParseInt(text, 10, 64); err == nil { + val = reflect.Append(val, reflect.ValueOf(int(n))) + } + } + + case reflect.Int8: + for text := range array.TextElements() { + if n, err := strconv.ParseInt(text, 10, 8); err == nil { + val = reflect.Append(val, reflect.ValueOf(int8(n))) + } + } + + case reflect.Int16: + for text := range array.TextElements() { + if n, err := strconv.ParseInt(text, 10, 16); err == nil { + val = reflect.Append(val, reflect.ValueOf(int16(n))) + } + } + + case reflect.Int32: + for text := range array.TextElements() { + if n, err := strconv.ParseInt(text, 10, 32); err == nil { + val = reflect.Append(val, reflect.ValueOf(int32(n))) + } + } + + case reflect.Int64: + for text := range array.TextElements() { + if n, err := strconv.ParseInt(text, 10, 64); err == nil { + val = reflect.Append(val, reflect.ValueOf(n)) + } + } + + case reflect.Uint: + for text := range array.TextElements() { + if n, err := strconv.ParseUint(text, 10, 64); err == nil { + val = reflect.Append(val, reflect.ValueOf(uint(n))) + } + } + + case reflect.Uint8: + for text := range array.TextElements() { + if n, err := strconv.ParseUint(text, 10, 8); err == nil { + val = reflect.Append(val, reflect.ValueOf(uint8(n))) + } + } + + case reflect.Uint16: + for text := range array.TextElements() { + if n, err := strconv.ParseUint(text, 10, 16); err == nil { + val = reflect.Append(val, reflect.ValueOf(uint16(n))) + } + } + + case reflect.Uint32: + for text := range array.TextElements() { + if n, err := strconv.ParseUint(text, 10, 32); err == nil { + val = reflect.Append(val, reflect.ValueOf(uint32(n))) + } + } + + case reflect.Uint64: + for text := range array.TextElements() { + if n, err := strconv.ParseUint(text, 10, 64); err == nil { + val = reflect.Append(val, reflect.ValueOf(n)) + } + } + + case reflect.Float32: + for text := range array.TextElements() { + if n, err := strconv.ParseFloat(text, 32); err == nil { + val = reflect.Append(val, reflect.ValueOf(float32(n))) + } + } + + case reflect.Float64: + for text := range array.TextElements() { + if n, err := strconv.ParseFloat(text, 64); err == nil { + val = reflect.Append(val, reflect.ValueOf(n)) + } + } + + case reflect.Bool: + for text := range array.TextElements() { + switch text { + case "true", "1": + val = reflect.Append(val, reflect.ValueOf(true)) + + case "false", "0": + val = reflect.Append(val, reflect.ValueOf(false)) + } + } + + case reflect.Struct: + for data := range array.Elements() { + if obj := data.Object(); obj != nil { + s := reflect.New(val.Type().Elem()) + if s.Kind() == reflect.Pointer { + s = s.Elem() + } + copyToReflectStruct(obj, s) + val = reflect.Append(val, s) + } + } + + case reflect.Slice: + for data := range array.Elements() { + if array := data.Array(); array != nil { + s := reflect.New(val.Type().Elem()) + if s.Kind() == reflect.Pointer { + s = s.Elem() + } + copyToReflectSlice(array, s) + val = reflect.Append(val, s) + } + } + + case reflect.Array: + for data := range array.Elements() { + if array := data.Array(); array != nil { + s := reflect.New(val.Type().Elem()) + if s.Kind() == reflect.Pointer { + s = s.Elem() + } + copyToReflectArray(array, s) + val = reflect.Append(val, s) + } + } + + case reflect.Map: + for data := range array.Elements() { + if obj := data.Object(); obj != nil { + s := reflect.MakeMap(val.Type().Elem()) + copyToReflectMap(obj, s) + val = reflect.Append(val, s) + } + } + + default: + ErrorLog("unsupported slice element type") + } + + value.Set(val) +} + +func copyToReflectArray(array DataArray, val reflect.Value) { + i := 0 + maxSize := val.Len() + + switch val.Type().Elem().Kind() { + case reflect.Pointer: + for data := range array.Elements() { + if i >= maxSize { + break + } + value := val.Index(i) + copyToReflectValue(data, value) + i++ + } + + case reflect.String: + for text := range array.TextElements() { + if i >= maxSize { + break + } + value := val.Index(i) + value.SetString(text) + i++ + } + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + for text := range array.TextElements() { + if n, err := strconv.ParseInt(text, 10, 64); err == nil { + if i >= maxSize { + break + } + value := val.Index(i) + value.SetInt(n) + i++ + } + } + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + for text := range array.TextElements() { + if n, err := strconv.ParseUint(text, 10, 64); err == nil { + if i >= maxSize { + break + } + value := val.Index(i) + value.SetUint(n) + i++ + } + } + + case reflect.Float32, reflect.Float64: + for text := range array.TextElements() { + if n, err := strconv.ParseFloat(text, 64); err == nil { + if i >= maxSize { + break + } + value := val.Index(i) + value.SetFloat(n) + i++ + } + } + + case reflect.Bool: + for text := range array.TextElements() { + if i >= maxSize { + break + } + + switch text { + case "true", "1": + value := val.Index(i) + value.SetBool(true) + i++ + + case "false", "0": + value := val.Index(i) + value.SetBool(false) + i++ + } + } + + case reflect.Struct: + for data := range array.Elements() { + if obj := data.Object(); obj != nil { + if i >= maxSize { + break + } + value := val.Index(i) + copyToReflectStruct(obj, value) + i++ + } + } + + case reflect.Map: + for data := range array.Elements() { + if obj := data.Object(); obj != nil { + if i >= maxSize { + break + } + value := val.Index(i) + copyToReflectMap(obj, value) + i++ + } + } + + case reflect.Slice: + for data := range array.Elements() { + if array := data.Array(); array != nil { + if i >= maxSize { + break + } + value := val.Index(i) + copyToReflectSlice(array, value) + i++ + } + } + + case reflect.Array: + for data := range array.Elements() { + if array := data.Array(); array != nil { + if i >= maxSize { + break + } + value := val.Index(i) + copyToReflectArray(array, value) + i++ + } + } + + default: + ErrorLog("unsupported array element type") + } +} + +func copyToReflectMap(object DataObject, val reflect.Value) { + + mapValue := reflect.MakeMap(val.Type()) + + for tag, data := range object.Properties() { + key := reflect.New(mapValue.Type().Key()) + if key.Kind() == reflect.Pointer { + key = key.Elem() + } + + switch key.Kind() { + case reflect.String: + key.SetString(tag) + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if n, err := strconv.ParseInt(tag, 10, 64); err == nil { + key.SetInt(n) + } + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if n, err := strconv.ParseUint(tag, 10, 64); err == nil { + key.SetUint(n) + } + + case reflect.Float32, reflect.Float64: + if n, err := strconv.ParseFloat(tag, 64); err == nil { + key.SetFloat(n) + } + + case reflect.Bool: + switch tag { + case "true", "1": + key.SetBool(true) + + case "false", "0": + key.SetBool(false) + } + + default: + ErrorLog("unsupported map key type") + return + } + + value := reflect.New(mapValue.Type().Elem()).Elem() + copyToReflectValue(data, value) + mapValue.SetMapIndex(key, value) + } + + val.Set(mapValue) +} + +func copyToReflectValue(data DataValue, value reflect.Value) { + if value.Kind() == reflect.Pointer { + v := reflect.New(value.Type().Elem()) + value.Set(v) + value = v.Elem() + } + + switch value.Kind() { + case reflect.Struct: + if obj := data.Object(); obj != nil { + copyToReflectStruct(obj, value) + } + + case reflect.String: + if data.Type() == TextDataValue { + value.SetString(data.String()) + } + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if data.Type() == TextDataValue { + if n, err := strconv.ParseInt(data.String(), 10, 64); err == nil { + value.SetInt(n) + } + } + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if data.Type() == TextDataValue { + if n, err := strconv.ParseUint(data.String(), 10, 64); err == nil { + value.SetUint(n) + } + } + + case reflect.Float32, reflect.Float64: + if data.Type() == TextDataValue { + if n, err := strconv.ParseFloat(data.String(), 64); err == nil { + value.SetFloat(n) + } + } + + case reflect.Bool: + if data.Type() == TextDataValue { + switch data.String() { + case "true", "1": + value.SetBool(true) + + case "false", "0": + value.SetBool(false) + } + } + + case reflect.Slice: + if array := data.Array(); array != nil { + copyToReflectSlice(array, value) + } + + case reflect.Array: + if array := data.Array(); array != nil { + copyToReflectArray(array, value) + } + + case reflect.Map: + if obj := data.Object(); obj != nil { + copyToReflectMap(obj, value) + } + + default: + ErrorLog("unsupported element type") + } +} + +func copyToReflectStruct(object DataObject, val reflect.Value) { + for i := range val.NumField() { + value := val.Field(i) + field := val.Type().Field(i) + fieldName := field.Name + if tag := field.Tag.Get("rui"); tag != "" { + params := parseFieldTag(tag) + if name, ok := params["id"]; ok && name != "" { + fieldName = name + } + } + + if value.CanSet() { + if data := object.Property(fieldName); data != nil { + copyToReflectValue(data, value) + } + } else { + DebugLogF("field %s can not be set", fieldName) + } + } +} + +func (object *dataObject) ToStruct(data any) { + if data != nil { + + value := reflect.ValueOf(data) + if value.Kind() == reflect.Pointer { + value = value.Elem() + } + + if value.Kind() == reflect.Struct { + copyToReflectStruct(object, value) + } + } +} + +func (object *dataObject) WriteToFile(path string, perm os.FileMode) error { + text := object.String() + return os.WriteFile(path, []byte(text), perm) +} + +func reflectArrayToDataValue(value reflect.Value) DataValue { + count := value.Len() + if count == 0 { + ErrorLog("empty array") + return nil + } + + kind := value.Type().Elem().Kind() + if kind == reflect.Pointer { + kind = value.Type().Elem().Elem().Kind() + } + + switch kind { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.String, reflect.Float32, reflect.Float64, reflect.Bool: + + array := make([]DataValue, 0, count) + for i := range count { + if e := reflectValueToDataValue(value.Index(i)); e != nil { + array = append(array, e) + } + } + + result := NewDataArray(array) + if count < 10 { + result.SetSingleLineString(true) + } + + return result + + case reflect.Struct, reflect.Array, reflect.Slice: + + array := make([]DataValue, 0, count) + for i := range count { + if e := reflectValueToDataValue(value.Index(i)); e != nil { + array = append(array, e) + } + } + return NewDataArray(array) + } + + ErrorLog("unsupported array element type") + return nil +} + +func reflectMapToDataValue(value reflect.Value) DataValue { + keys := value.MapKeys() + + count := len(keys) + if count == 0 { + return nil + } + + obj := new(dataObject) + obj.tag = "_" + obj.property = make([]dataObjectProperty, 0, count) + + for _, key := range keys { + tag := "" + switch key.Kind() { + case reflect.String: + tag = key.String() + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + tag = strconv.FormatInt(key.Int(), 10) + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + tag = strconv.FormatUint(key.Uint(), 10) + + case reflect.Float32, reflect.Float64: + tag = fmt.Sprintf("%g", key.Float()) + + case reflect.Bool: + tag = fmt.Sprintf("%t", key.Bool()) + + default: + ErrorLog("unsupported map key type") + return nil + } + + if tag != "" { + if data := reflectValueToDataValue(value.MapIndex(key)); data != nil { + obj.property = append(obj.property, dataObjectProperty{tag: tag, value: data}) + } + } else { + DebugLog("empty map key") + } + } + + if len(obj.property) == 0 { + DebugLog("empty map") + return nil + } + + return obj +} + +func reflectValueToDataValue(value reflect.Value) DataValue { + for value.Kind() == reflect.Pointer && !value.IsNil() { + value = value.Elem() + } + + switch value.Kind() { + case reflect.Struct: + return reflectStructToDataObject(value) + + case reflect.String: + return NewTextData(value.String()) + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return NewTextData(value.Int()) + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return NewTextData(value.Uint()) + + case reflect.Float32, reflect.Float64: + return NewTextData(value.Float()) + + case reflect.Bool: + return NewTextData(value.Bool()) + + case reflect.Slice, reflect.Array: + return reflectArrayToDataValue(value) + + case reflect.Map: + return reflectMapToDataValue(value) + } + + ErrorLog("unsupported element type") + return nil +} + +func parseFieldTag(tag string) map[string]string { + result := map[string]string{} + for line := range strings.SplitSeq(tag, ";") { + pair := strings.SplitN(line, "=", 2) + if len(pair) == 2 { + key := strings.Trim(pair[0], " \t") + value := strings.Trim(pair[1], " \t") + if key != "" && value != "" { + result[key] = value + } + } + } + return result +} + +func reflectStructToDataObject(val reflect.Value) DataObject { + obj := new(dataObject) + obj.tag = val.Type().Name() + obj.property = []dataObjectProperty{} + + for field, value := range val.Fields() { + if data := reflectValueToDataValue(value); data != nil { + fieldName := field.Name + if tag := field.Tag.Get("rui"); tag != "" { + params := parseFieldTag(tag) + if name, ok := params["id"]; ok && name != "" { + fieldName = name + } + } + obj.property = append(obj.property, dataObjectProperty{tag: fieldName, value: data}) + } + } + + return obj +} + +// NewDataObject create new DataObject with the tag and empty property list +func NewDataObject(tag string) DataObject { + obj := new(dataObject) + obj.tag = tag + obj.property = []dataObjectProperty{} + return obj +} + +// NewDataObjectFromStruct converts a struct to DataObject. +// +// A struct or a pointer to a struct is passed as a function argument. +// If other types of data are passed, the function returns nil. +// +// Depending on the type, the following transformation of structure fields is performed: +// - string, int, int8...int64, uint, uint8...uint64, float32, float64, and bool are converted into DataText; +// - struct and map are converted into DataObject; +// - array and slice are converted into DataArray. +// +// The names of the corresponding structure fields are used as names. +// A name can be redefined using a tag `rui:"id=your_name"`. Example: +// +// type A struct { +// X int `rui:"id=y"` +// } +func NewDataObjectFromStruct(data any) DataObject { + if data == nil { + return nil + } + + value := reflect.ValueOf(data) + if value.Kind() == reflect.Pointer { + value = value.Elem() + } + + if value.Kind() == reflect.Struct { + return reflectStructToDataObject(value) + } + + return nil +} diff --git a/dataObject_test.go b/dataObject_test.go new file mode 100644 index 0000000..caeecc6 --- /dev/null +++ b/dataObject_test.go @@ -0,0 +1,217 @@ +package rui + +import "testing" + +type TestDataXY struct { + X float32 + Y float64 +} +type TestDataObjectStruct struct { + Items []string `rui:"id=items"` + Items2 [2]string + Int_a []int `rui:"id=intArray"` + Int_a2 [3]int + Int8_a []int8 + Int16_a []int16 + Int32_a []int32 + Int64_a []int64 + Uint_a []uint + Uint8_a []uint8 + Uint16_a []uint16 + Uint32_a []uint32 + Uint64_a []uint64 + Float32_a []float32 + Float32_a2 [3]float32 + Float64_a []float64 + Bool_a []bool + Bool_a2 [2]bool + Text string `rui:"id=text"` + Int int `rui:"id=int"` + Int8 int `rui:"id=int8"` + Int16 int `rui:"id=int16"` + Int32 int `rui:"id=int32"` + Int64 int `rui:"id=int64"` + Uint uint `rui:"id=uint"` + Uint8 uint `rui:"id=uint8"` + Uint16 uint `rui:"id=uint16"` + Uint32 uint `rui:"id=uint32"` + Uint64 uint `rui:"id=uint64"` + Float32 float32 + Float64 float64 + Flag bool + B TestDataXY + Struct_a []TestDataXY + Struct_a2 [2]TestDataXY + Matrix [][]int + Matrix2 [2][]int + Matrix3 [][3]int + Matrix4 [2][3]int + Map map[string]int +} + +func TestDataObject(t *testing.T) { + + data := TestDataObjectStruct{ + Items: []string{"Item 1", "Item2"}, + Items2: [2]string{"Item 3", "Item4"}, + Int_a: []int{-1, 2, 3}, + Int_a2: [3]int{0, 1, 2}, + Int8_a: []int8{4, 5, 6}, + Int16_a: []int16{7, 8, 9}, + Int32_a: []int32{10, 11, 12}, + Int64_a: []int64{13, 14, 15}, + Uint_a: []uint{1, 2, 3}, + Uint8_a: []uint8{4, 5, 6}, + Uint16_a: []uint16{7, 8, 9}, + Uint32_a: []uint32{10, 11, 12}, + Uint64_a: []uint64{13, 14, 15}, + Float32_a: []float32{1.5, 2, 3}, + Float32_a2: [3]float32{2.5, 3.5, 4}, + Float64_a: []float64{1, 2.5, 3}, + Bool_a: []bool{false, true}, + Bool_a2: [2]bool{true, false}, + Text: "text", + Int: -1, + Int8: 0, + Int16: 1, + Int32: 2, + Int64: 3, + Uint: 5, + Uint8: 6, + Uint16: 7, + Uint32: 8, + Uint64: 9, + Float32: 10.5, + Float64: 11.5, + Flag: true, + B: TestDataXY{X: 4, Y: 5.5}, + Struct_a: []TestDataXY{{X: 1, Y: 2}, {X: 3, Y: 4}}, + Struct_a2: [2]TestDataXY{{X: 5, Y: 6}, {X: 7, Y: 8}}, + Matrix: [][]int{ + {1, 2, 3}, + {4, 5}, + }, + Matrix2: [2][]int{ + {21, 22, 23}, + {24, 25}, + }, + Matrix3: [][3]int{ + {31, 32, 33}, + {34, 35, 36}, + }, + Matrix4: [2][3]int{ + {41, 42, 43}, + {44, 45, 46}, + }, + Map: map[string]int{ + "key 1": 12, + "key2": 25, + }, + } + + obj := NewDataObjectFromStruct(&data) + if obj == nil { + t.Error(`NewDataObjectFromStruct fail`) + } + + expected := `TestDataObjectStruct{ + items = ["Item 1", Item2], + Items2 = ["Item 3", Item4], + intArray = [-1, 2, 3], + Int_a2 = [0, 1, 2], + Int8_a = [4, 5, 6], + Int16_a = [7, 8, 9], + Int32_a = [10, 11, 12], + Int64_a = [13, 14, 15], + Uint_a = [1, 2, 3], + Uint8_a = [4, 5, 6], + Uint16_a = [7, 8, 9], + Uint32_a = [10, 11, 12], + Uint64_a = [13, 14, 15], + Float32_a = [1.5, 2, 3], + Float32_a2 = [2.5, 3.5, 4], + Float64_a = [1, 2.5, 3], + Bool_a = [false, true], + Bool_a2 = [true, false], + text = text, + int = -1, + int8 = 0, + int16 = 1, + int32 = 2, + int64 = 3, + uint = 5, + uint8 = 6, + uint16 = 7, + uint32 = 8, + uint64 = 9, + Float32 = 10.5, + Float64 = 11.5, + Flag = true, + B = TestDataXY{ + X = 4, + Y = 5.5, + }, + Struct_a = [ + TestDataXY{ + X = 1, + Y = 2, + }, + TestDataXY{ + X = 3, + Y = 4, + }, + ], + Struct_a2 = [ + TestDataXY{ + X = 5, + Y = 6, + }, + TestDataXY{ + X = 7, + Y = 8, + }, + ], + Matrix = [ + [1, 2, 3], + [4, 5], + ], + Matrix2 = [ + [21, 22, 23], + [24, 25], + ], + Matrix3 = [ + [31, 32, 33], + [34, 35, 36], + ], + Matrix4 = [ + [41, 42, 43], + [44, 45, 46], + ], + Map = _{ + "key 1" = 12, + key2 = 25, + }, +}` + + if mapObject := obj.PropertyObject("Map"); mapObject != nil { + mapObject.SetSortedTags(true) + } + if str := obj.String(); str != expected { + t.Error("NewDataObjectFromStruct result is fail:\n" + str) + return + } + + var s TestDataObjectStruct + obj.ToStruct(&s) + + obj2 := NewDataObjectFromStruct(s) + + if mapObject := obj2.PropertyObject("Map"); mapObject != nil { + mapObject.SetSortedTags(true) + } + + if str := obj2.String(); str != expected { + t.Error("CopyToStruct result is fail:\n" + str) + } + +} diff --git a/dataWriter_test.go b/dataWriter_test.go deleted file mode 100644 index 1207f4a..0000000 --- a/dataWriter_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package rui - -/* -import ( - "testing" -) - -func TestDataWriter(t *testing.T) { - w := NewDataWriter() - w.StartObject("root") - w.WriteStringKey("key1", "text") - w.WriteStringKey("key2", "text 2") - w.WriteStringKey("key 3", "text4") - w.WriteStringsKey("key4", []string{"text4.1", "text4.2", "text4.3"}, '|') - w.WriteStringsKey("key5", []string{"text5.1", "text5.2", "text5.3"}, ',') - w.WriteColorKey("color", Color(0x7FD18243)) - w.WriteColorsKey("colors", []Color{Color(0x7FD18243), Color(0xFF817263)}, ',') - w.WriteIntKey("int", 43) - w.WriteIntsKey("ints", []int{111, 222, 333}, '|') - - w.StartObjectKey("obj", "xxx") - w.WriteSizeUnitKey("size", Px(16)) - w.WriteSizeUnitsKey("sizes", []SizeUnit{Px(8), Percent(100)}, ',') - w.StartArray("array") - w.WriteStringToArray("text") - w.WriteColorToArray(Color(0x23456789)) - w.WriteIntToArray(1) - w.WriteSizeUnitToArray(Inch(2)) - w.FinishArray() - w.WriteBoundsKey("bounds1", Bounds{Px(8), Px(8), Px(8), Px(8)}) - w.WriteBoundsKey("bounds2", Bounds{Px(8), Pt(12), Mm(4.5), Inch(1.2)}) - w.FinishObject() // xxx - - w.FinishObject() // root - - text := w.String() - expected := `root { - key1 = text, - key2 = "text 2", - "key 3" = text4, - key4 = text4.1|text4.2|text4.3, - key5 = "text5.1,text5.2,text5.3", - color = #7FD18243, - colors = "#7FD18243,#FF817263", - int = 43, - ints = 111|222|333, - obj = xxx { - size = 16px, - sizes = "8px,100%", - array = [ - text, - #23456789, - 1, - 2in - ], - bounds1 = 8px, - bounds2 = "8px,12pt,4.5mm,1.2in" - } -}` - - if text != expected { - t.Error("DataWriter test fail. Result:\n`" + text + "`\nExpected:\n`" + expected + "`") - } - -} -*/ diff --git a/data_test.go b/data_test.go index ed26ba3..8b58c76 100644 --- a/data_test.go +++ b/data_test.go @@ -30,27 +30,23 @@ func TestParseDataText(t *testing.T) { if obj.Tag() != "obj1" { t.Error(`obj.Tag() != "obj1"`) } - if !obj.IsObject() { - t.Error(`!obj.IsObject()`) + if obj.Type() != ObjectDataValue { + t.Error(`obj.Type() != ObjectDataValue`) } if obj.PropertyCount() != 4 { t.Error(`obj.PropertyCount() != 4`) } - if obj.Property(-1) != nil { - t.Error(`obj.Property(-1) != nil`) + if val, ok := obj.PropertyText("key1"); !ok || val != "val1" { + t.Errorf(`obj.PropertyText("key1") result: ("%s",%v)`, val, ok) } - if val, ok := obj.PropertyValue("key1"); !ok || val != "val1" { - t.Errorf(`obj.PropertyValue("key1") result: ("%s",%v)`, val, ok) + if val, ok := obj.PropertyText("key3"); !ok || val != "\n \t \\ \r \" ' \x4F\x4e \u01Ea" { + t.Errorf(`obj.PropertyText("key3") result: ("%s",%v)`, val, ok) } - if val, ok := obj.PropertyValue("key3"); !ok || val != "\n \t \\ \r \" ' \x4F\x4e \u01Ea" { - t.Errorf(`obj.PropertyValue("key3") result: ("%s",%v)`, val, ok) - } - - if val, ok := obj.PropertyValue("key4"); !ok || val != `\n \t \\ \r \" ' \x8F \UF80a` { - t.Errorf(`obj.PropertyValue("key4") result: ("%s",%v)`, val, ok) + if val, ok := obj.PropertyText("key4"); !ok || val != `\n \t \\ \r \" ' \x8F \UF80a` { + t.Errorf(`obj.PropertyText("key4") result: ("%s",%v)`, val, ok) } if o := obj.PropertyObject("key2"); o == nil { @@ -65,47 +61,41 @@ func TestParseDataText(t *testing.T) { t.Error(`obj.PropertyObject("key5") != nil`) } - if val, ok := obj.PropertyValue("key2"); ok { - t.Errorf(`obj.PropertyValue("key2") result: ("%s",%v)`, val, ok) + if val, ok := obj.PropertyText("key2"); ok { + t.Errorf(`obj.PropertyText("key2") result: ("%s",%v)`, val, ok) } - if val, ok := obj.PropertyValue("key5"); ok { - t.Errorf(`obj.PropertyValue("key5") result: ("%s",%v)`, val, ok) + if val, ok := obj.PropertyText("key5"); ok { + t.Errorf(`obj.PropertyText("key5") result: ("%s",%v)`, val, ok) } - testKey := func(obj DataObject, index int, tag string, nodeType DataNodeType) DataNode { - key := obj.Property(index) + testKey := func(obj DataObject, tag string, nodeType DataValueType) DataValue { + key := obj.Property(tag) if key == nil { - t.Errorf(`%s.Property(%d) == nil`, obj.Tag(), index) - } else { - if key.Tag() != tag { - t.Errorf(`%s.Property(%d).Tag() != "%s"`, obj.Tag(), index, tag) - } + t.Errorf(`%s.Property(%s) == nil`, obj.Tag(), tag) + } else if key.Type() != nodeType { + switch nodeType { + case TextDataValue: + t.Errorf(`%s.Property(%s) is not text`, obj.Tag(), tag) - if key.Type() != nodeType { - switch nodeType { - case TextNode: - t.Errorf(`%s.Property(%d) is not text`, obj.Tag(), index) + case ObjectDataValue: + t.Errorf(`%s.Property(%s) is not object`, obj.Tag(), tag) - case ObjectNode: - t.Errorf(`%s.Property(%d) is not object`, obj.Tag(), index) - - case ArrayNode: - t.Errorf(`%s.Property(%d) is not array`, obj.Tag(), index) - } + case ArrayDataValue: + t.Errorf(`%s.Property(%s) is not array`, obj.Tag(), tag) } } return key } - if key := testKey(obj, 0, "key1", TextNode); key != nil { - if key.Text() != "val1" { + if key := testKey(obj, "key1", TextDataValue); key != nil { + if key.String() != "val1" { t.Error(`key1.Value() != "val1"`) } } - if key := testKey(obj, 1, "key2", ObjectNode); key != nil { + if key := testKey(obj, "key2", ObjectDataValue); key != nil { o := key.Object() if o == nil { t.Error(`key2.Value().Object() == nil`) @@ -116,61 +106,83 @@ func TestParseDataText(t *testing.T) { type testKeyData struct { tag string - nodeType DataNodeType + nodeType DataValueType } data := []testKeyData{ - {tag: "key2.1", nodeType: ArrayNode}, - {tag: "key 2.2", nodeType: TextNode}, - {tag: "key2.3", nodeType: ObjectNode}, - {tag: "key2.4", nodeType: ObjectNode}, - {tag: "key2.5", nodeType: ArrayNode}, + {tag: "key2.1", nodeType: ArrayDataValue}, + {tag: "key 2.2", nodeType: TextDataValue}, + {tag: "key2.3", nodeType: ObjectDataValue}, + {tag: "key2.4", nodeType: ObjectDataValue}, + {tag: "key2.5", nodeType: ArrayDataValue}, } - for i, d := range data { - testKey(o, i, d.tag, d.nodeType) + for _, d := range data { + testKey(o, d.tag, d.nodeType) } } } - node1 := obj.Property(1) + node1 := obj.Property("key2") if node1 == nil { - t.Error("obj.Property(1) != nil") - } else if node1.Type() != ObjectNode { - t.Error("obj.Property(1).Type() != ObjectNode") + t.Error("obj.Property(\"key2\") == nil") + } else if node1.Type() != ObjectDataValue { + t.Error("obj.Property(\"key2\").Type() != ObjectNode") } else if obj := node1.Object(); obj != nil { - if key := obj.Property(0); key != nil { - if key.Type() != ArrayNode { - t.Error("obj.Property(1).Object().Property(0)..Type() != ArrayNode") + if key := obj.Property("key2.1"); key != nil { + if key.Type() != ArrayDataValue { + t.Error("obj.Property(\"key2\").Object().Property(\"key2.1\").Type() != ArrayNode") } else { - if key.ArraySize() != 3 { - t.Error("obj.Property(1).Object().Property(0).ArraySize() != 3") - } + array := key.Array() + if array == nil { + t.Error("obj.Property(\"key2\").Object().Property(\"key2.1\").Array() == nil") + } else { - if e := key.ArrayElement(0); e == nil { - t.Error("obj.Property(1).Object().Property(0).ArrayElement(0) == nil") - } else if e.IsObject() { - t.Error("obj.Property(1).Object().Property(0).ArrayElement(0).IsObject() == true") - } + if array.Size() != 3 { + t.Error("obj.Property(\"key2\").Object().Property(\"key2.1\").Array().Size() != 3") + } - if e := key.ArrayElement(2); e == nil { - t.Error("obj.Property(1).Object().Property(0).ArrayElement(2) == nil") - } else if !e.IsObject() { - t.Error("obj.Property(1).Object().Property(0).ArrayElement(2).IsObject() == false") - } else if e.Value() != "" { - t.Error(`obj.Property(1).Object().Property(0).ArrayElement(2).Value() != ""`) - } + for i, tp := range []DataValueType{TextDataValue, ObjectDataValue, ObjectDataValue} { + if e := array.Element(i); e == nil { + t.Error("obj.Property(\"key2\").Object().Property(\"key2.1\").Array().Element(0) == nil") + } else if e.Type() != tp { + t.Errorf("obj.Property(\"key2\").Object().Property(\"key2.1\").Array().Element(0).Type() != %d", tp) + } + } - if e := key.ArrayElement(3); e != nil { - t.Error("obj.Property(1).Object().Property(0).ArrayElement(3) != nil") + if e := array.Element(3); e != nil { + t.Error("obj.Property(\"key2\").Object().Property(\"key2.1\").Array().Element(3) != nil") + } } } } } else { - t.Error("obj.Property(1).Object() == nil") + t.Error("obj.Property(\"key 2.2\").Object() == nil") } } + expected := `obj1{ + key1 = val1, + key2 = obj2{ + key2.1 = [ + val2.1, + obj2.2{}, + obj2.3{}, + ], + "key 2.2" = "val 2.2", + key2.3 = _{}, + key2.4 = obj2.3{ + text = " ", + }, + key2.5 = [], + }, + key3 = "\n \t \\ \r \" ' ON Ǫ", + key4 = "\\n \\t \\\\ \\r \\\" ' \\x8F \\UF80a", +}` + if expected != obj.String() { + t.Error("obj.String() result fail") + } + failText := []string{ " ", "obj[]", diff --git a/datePicker.go b/datePicker.go index 25a5377..4fc10bf 100644 --- a/datePicker.go +++ b/datePicker.go @@ -343,7 +343,7 @@ func (picker *datePickerData) htmlProperties(self View, buffer *strings.Builder) func (picker *datePickerData) handleCommand(self View, command PropertyName, data DataObject) bool { switch command { case "textChanged": - if text, ok := data.PropertyValue("text"); ok { + if text, ok := data.PropertyText("text"); ok { if value, err := time.Parse(dateFormat, text); err == nil { oldValue := GetDatePickerValue(picker) picker.setRaw(DatePickerValue, value) diff --git a/dragAndDrop.go b/dragAndDrop.go index 355292f..44223d4 100644 --- a/dragAndDrop.go +++ b/dragAndDrop.go @@ -228,7 +228,7 @@ func (event *DragAndDropEvent) init(session Session, data DataObject) { event.MouseEvent.init(data) event.Data = map[string]string{} - if value, ok := data.PropertyValue("data"); ok { + if value, ok := data.PropertyText("data"); ok { for line := range strings.SplitSeq(value, ";") { if mimeData, data, ok := strings.Cut(line, ":"); ok { mime, err := base64.StdEncoding.DecodeString(mimeData) @@ -246,11 +246,11 @@ func (event *DragAndDropEvent) init(session Session, data DataObject) { } } - if targetId, ok := data.PropertyValue("target"); ok { + if targetId, ok := data.PropertyText("target"); ok { event.Target = session.viewByHTMLID(targetId) } - if effect, ok := data.PropertyValue("effect-allowed"); ok { + if effect, ok := data.PropertyText("effect-allowed"); ok { for i, value := range []string{"undefined", "copy", "move", "copyMove", "link", "copyLink", "linkMove", "all"} { if value == effect { event.EffectAllowed = i @@ -259,7 +259,7 @@ func (event *DragAndDropEvent) init(session Session, data DataObject) { } } - if effect, ok := data.PropertyValue("drop-effect"); ok && effect != "" { + if effect, ok := data.PropertyText("drop-effect"); ok && effect != "" { for i, value := range []string{"none", "copy", "move", "", "link"} { if value == effect { event.DropEffect = i diff --git a/dropDownList.go b/dropDownList.go index 3a1d00a..c16e663 100644 --- a/dropDownList.go +++ b/dropDownList.go @@ -188,14 +188,16 @@ func parseIndicesArray(value any) ([]any, bool) { case string: return parseIndicesArray(strings.Split(value, ",")) - case []DataValue: - items := make([]string, 0, len(value)) - for _, val := range value { - if !val.IsObject() { - items = append(items, val.Value()) + case DataValue: + if array := value.Array(); array != nil { + items := make([]string, 0, array.Size()) + for val := range array.Elements() { + if val.Type() == TextDataValue { + items = append(items, val.String()) + } } + return parseIndicesArray(items) } - return parseIndicesArray(items) } return nil, false @@ -242,7 +244,7 @@ func (list *dropDownListData) htmlProperties(self View, buffer *strings.Builder) func (list *dropDownListData) handleCommand(self View, command PropertyName, data DataObject) bool { switch command { case "itemSelected": - if text, ok := data.PropertyValue("number"); ok { + if text, ok := data.PropertyText("number"); ok { if number, err := strconv.Atoi(text); err == nil { items := GetDropDownItems(list) if GetCurrent(list) != number && number >= 0 && number < len(items) { diff --git a/editView.go b/editView.go index 5f99947..076bb60 100644 --- a/editView.go +++ b/editView.go @@ -387,7 +387,7 @@ func (edit *editViewData) handleCommand(self View, command PropertyName, data Da switch command { case "textChanged": oldText := GetText(edit) - if text, ok := data.PropertyValue("text"); ok { + if text, ok := data.PropertyText("text"); ok { edit.setRaw(Text, text) if text != oldText { edit.textChanged(text, oldText) diff --git a/filePicker.go b/filePicker.go index dcbbbb2..7c1cc8f 100644 --- a/filePicker.go +++ b/filePicker.go @@ -88,24 +88,23 @@ type filePickerData struct { //loader map[int]func(FileInfo, []byte) } -func dataToFileInfo(node DataValue) FileInfo { +func dataToFileInfo(obj DataObject) FileInfo { var file FileInfo - if obj := node.Object(); obj != nil { - file.Name, _ = obj.PropertyValue("name") - file.MimeType, _ = obj.PropertyValue("mime-type") + file.Name, _ = obj.PropertyText("name") + file.MimeType, _ = obj.PropertyText("mime-type") - if size, ok := obj.PropertyValue("size"); ok { - if n, err := strconv.ParseInt(size, 10, 64); err == nil { - file.Size = n - } - } - - if value, ok := obj.PropertyValue("last-modified"); ok { - if n, err := strconv.ParseInt(value, 10, 64); err == nil { - file.LastModified = time.UnixMilli(n) - } + if size, ok := obj.PropertyText("size"); ok { + if n, err := strconv.ParseInt(size, 10, 64); err == nil { + file.Size = n } } + + if value, ok := obj.PropertyText("last-modified"); ok { + if n, err := strconv.ParseInt(value, 10, 64); err == nil { + file.LastModified = time.UnixMilli(n) + } + } + return file } @@ -273,15 +272,24 @@ func (picker *filePickerData) htmlProperties(self View, buffer *strings.Builder) } func parseFilesTag(data DataObject) []FileInfo { - if node := data.PropertyByTag("files"); node != nil && node.Type() == ArrayNode { - count := node.ArraySize() - files := make([]FileInfo, count) - for i := range count { - if value := node.ArrayElement(i); value != nil { - files[i] = dataToFileInfo(value) + if node := data.Property("files"); node != nil { + switch node.Type() { + case ArrayDataValue: + if array := node.Array(); array != nil { + files := make([]FileInfo, 0, array.Size()) + for value := range array.Elements() { + if obj := value.Object(); obj != nil { + files = append(files, dataToFileInfo(obj)) + } + } + return files + } + + case ObjectDataValue: + if obj := node.Object(); obj != nil { + return []FileInfo{dataToFileInfo(obj)} } } - return files } return nil } diff --git a/filter.go b/filter.go index 814b594..ea093ec 100644 --- a/filter.go +++ b/filter.go @@ -171,13 +171,12 @@ func NewFilterProperty(params Params) FilterProperty { func newFilterProperty(obj DataObject) FilterProperty { filter := new(filterData) filter.init() - for node := range obj.Properties() { - tag := node.Tag() + for tag, node := range obj.Properties() { switch node.Type() { - case TextNode: - filter.Set(PropertyName(tag), node.Text()) + case TextDataValue: + filter.Set(PropertyName(tag), node.String()) - case ObjectNode: + case ObjectDataValue: if tag == string(HueRotate) { // TODO } else { @@ -286,15 +285,9 @@ func setFilterProperty(properties Properties, tag PropertyName, value any) []Pro } } - case DataObject: - if filter := newFilterProperty(value); filter != nil { - properties.setRaw(tag, filter) - return []PropertyName{tag} - } - case DataValue: - if value.IsObject() { - if filter := newFilterProperty(value.Object()); filter != nil { + if obj := value.Object(); obj != nil { + if filter := newFilterProperty(obj); filter != nil { properties.setRaw(tag, filter) return []PropertyName{tag} } diff --git a/go.mod b/go.mod index 7cb971d..7a51c85 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/anoshenko/rui -go 1.24 +go 1.26 require ( github.com/gorilla/websocket v1.5.3 diff --git a/gridLayout.go b/gridLayout.go index 45efee8..a1de0a2 100644 --- a/gridLayout.go +++ b/gridLayout.go @@ -189,20 +189,22 @@ func setGridCellSize(properties Properties, tag PropertyName, value any) []Prope return nil } - case []DataValue: - count := len(value) - if count == 0 { + case DataValue: + array := value.Array() + if array == nil || array.Size() == 0 { invalidPropertyValue(tag, value) return nil } - values := make([]string, count) - for i, val := range value { - if val.IsObject() { + + values := make([]string, 0, array.Size()) + for val := range array.Elements() { + if val.Type() != TextDataValue { invalidPropertyValue(tag, value) return nil } - values[i] = val.Value() + values = append(values, val.String()) } + if !setValues(values) { return nil } diff --git a/image.go b/image.go index 44b80d8..4425db2 100644 --- a/image.go +++ b/image.go @@ -101,15 +101,15 @@ func (manager *imageManager) imageLoaded(obj DataObject) { return } - if url, ok := obj.PropertyValue("url"); ok { + if url, ok := obj.PropertyText("url"); ok { if image, ok := manager.images[url]; ok { image.loadingStatus = ImageReady - if width, ok := obj.PropertyValue("width"); ok { + if width, ok := obj.PropertyText("width"); ok { if w, err := strconv.ParseFloat(width, 64); err == nil { image.width = w } } - if height, ok := obj.PropertyValue("height"); ok { + if height, ok := obj.PropertyText("height"); ok { if h, err := strconv.ParseFloat(height, 64); err == nil { image.height = h } @@ -127,11 +127,11 @@ func (manager *imageManager) imageLoadError(obj DataObject) { return } - if url, ok := obj.PropertyValue("url"); ok { + if url, ok := obj.PropertyText("url"); ok { if image, ok := manager.images[url]; ok { delete(manager.images, url) - text, _ := obj.PropertyValue("message") + text, _ := obj.PropertyText("message") image.setLoadingError(text) if image.listener != nil { diff --git a/imageView.go b/imageView.go index a2c85cb..64ef66c 100644 --- a/imageView.go +++ b/imageView.go @@ -315,7 +315,7 @@ func (imageView *imageViewData) handleCommand(self View, command PropertyName, d case "imageViewLoaded": imageView.naturalWidth = dataFloatProperty(data, "natural-width") imageView.naturalHeight = dataFloatProperty(data, "natural-height") - imageView.currentSrc, _ = data.PropertyValue("current-src") + imageView.currentSrc, _ = data.PropertyText("current-src") for _, listener := range getNoArgEventListeners[ImageView](imageView, nil, LoadedEvent) { listener.Run(imageView) diff --git a/keyEvents.go b/keyEvents.go index 247bca7..c803f13 100644 --- a/keyEvents.go +++ b/keyEvents.go @@ -416,14 +416,12 @@ type KeyEvent struct { func (event *KeyEvent) init(data DataObject) { getBool := func(tag string) bool { - if value, ok := data.PropertyValue(tag); ok && value == "1" { - return true - } - return false + value, ok := data.PropertyText(tag) + return ok && value == "1" } - event.Key, _ = data.PropertyValue("key") - code, _ := data.PropertyValue("code") + event.Key, _ = data.PropertyText("key") + code, _ := data.PropertyText("code") event.Code = KeyCode(code) event.TimeStamp = getTimeStamp(data) event.Repeat = getBool("repeat") diff --git a/listView.go b/listView.go index d9aaf66..6977462 100644 --- a/listView.go +++ b/listView.go @@ -354,35 +354,61 @@ func (listView *listViewData) setItems(value any) []PropertyName { case []string: adapter = NewTextListAdapter(value, nil) - case []DataValue: - hasObject := false - for _, val := range value { - if val.IsObject() { - hasObject = true - break + case DataValue: + switch value.Type() { + case ObjectDataValue: + if view := CreateViewFromObject(session, value.Object(), nil); view != nil { + adapter = NewViewListAdapter([]View{view}) + } else { + return nil } - } - if hasObject { - items := make([]View, len(value)) - for i, val := range value { - if val.IsObject() { - if view := CreateViewFromObject(session, val.Object(), nil); view != nil { - items[i] = view - } else { - return nil - } - } else { - items[i] = NewTextView(session, Params{Text: val.Value()}) + case TextDataValue: + adapter = NewTextListAdapter([]string{value.String()}, nil) + + case ArrayDataValue: + array := value.Array() + if array == nil { + return nil + } + + hasObject := false + for val := range array.Elements() { + switch val.Type() { + case ObjectDataValue: + hasObject = true + + case ArrayDataValue: + return nil } } - adapter = NewViewListAdapter(items) - } else { - items := make([]string, len(value)) - for i, val := range value { - items[i] = val.Value() + + if hasObject { + items := make([]View, 0, array.Size()) + for val := range array.Elements() { + switch val.Type() { + case TextDataValue: + items = append(items, NewTextView(session, Params{Text: val.String()})) + + case ObjectDataValue: + if view := CreateViewFromObject(session, val.Object(), nil); view != nil { + items = append(items, view) + } else { + return nil + } + + default: + return nil + } + } + adapter = NewViewListAdapter(items) + } else { + items := make([]string, array.Size()) + for val := range array.Elements() { + items = append(items, val.String()) + } + adapter = NewTextListAdapter(items, nil) } - adapter = NewTextListAdapter(items, nil) } case []any: diff --git a/mediaPlayer.go b/mediaPlayer.go index e80deea..a95f7bd 100644 --- a/mediaPlayer.go +++ b/mediaPlayer.go @@ -659,7 +659,9 @@ func (player *mediaPlayerData) setFunc(tag PropertyName, value any) []PropertyNa } func setMediaPlayerSource(properties Properties, value any) []PropertyName { + switch value := value.(type) { + case string: src := MediaSource{Url: value, MimeType: ""} properties.setRaw(Source, []MediaSource{src}) @@ -670,39 +672,73 @@ func setMediaPlayerSource(properties Properties, value any) []PropertyName { case []MediaSource: properties.setRaw(Source, value) - case DataObject: - url, ok := value.PropertyValue("src") - if !ok || url == "" { - invalidPropertyValue(Source, value) - return nil - } + case DataValue: + switch value.Type() { + case ObjectDataValue: + obj := value.Object() + if obj == nil { + invalidPropertyValue(Source, value) + return nil + } - mimeType, _ := value.PropertyValue("mime-type") - src := MediaSource{Url: url, MimeType: mimeType} - properties.setRaw(Source, []MediaSource{src}) + url, ok := obj.PropertyText("src") + if !ok || url == "" { + invalidPropertyValue(Source, value) + return nil + } - case []DataValue: - src := []MediaSource{} - for _, val := range value { - if val.IsObject() { - obj := val.Object() - if url, ok := obj.PropertyValue("src"); ok && url != "" { - mimeType, _ := obj.PropertyValue("mime-type") - src = append(src, MediaSource{Url: url, MimeType: mimeType}) - } else { + mimeType, _ := obj.PropertyText("mime-type") + src := MediaSource{Url: url, MimeType: mimeType} + properties.setRaw(Source, []MediaSource{src}) + + case ArrayDataValue: + array := value.Array() + if array == nil { + invalidPropertyValue(Source, value) + return nil + } + + src := make([]MediaSource, 0, array.Size()) + for val := range array.Elements() { + switch val.Type() { + case ObjectDataValue: + obj := val.Object() + if obj == nil { + invalidPropertyValue(Source, value) + return nil + } + + if url, ok := obj.PropertyText("src"); ok && url != "" { + mimeType, _ := obj.PropertyText("mime-type") + src = append(src, MediaSource{Url: url, MimeType: mimeType}) + } else { + invalidPropertyValue(Source, value) + return nil + } + + case TextDataValue: + src = append(src, MediaSource{Url: val.String(), MimeType: ""}) + + default: invalidPropertyValue(Source, value) return nil } - } else { - src = append(src, MediaSource{Url: val.Value(), MimeType: ""}) } - } - if len(src) == 0 { + if len(src) == 0 { + invalidPropertyValue(Source, value) + return nil + } + properties.setRaw(Source, src) + + case TextDataValue: + src := MediaSource{Url: value.String(), MimeType: ""} + properties.setRaw(Source, []MediaSource{src}) + + default: invalidPropertyValue(Source, value) return nil } - properties.setRaw(Source, src) default: notCompatibleType(Source, value) @@ -1019,7 +1055,7 @@ func (player *mediaPlayerData) handleCommand(self View, command PropertyName, da if value := player.getRaw(command); value != nil { if listeners, ok := value.([]mediaPlayerErrorListener); ok { code, _ := dataIntProperty(data, "code") - message, _ := data.PropertyValue("message") + message, _ := data.PropertyText("message") for _, listener := range listeners { listener.Run(player, code, message) } diff --git a/mouseEvents.go b/mouseEvents.go index 6d53e42..e9a9b1a 100644 --- a/mouseEvents.go +++ b/mouseEvents.go @@ -245,7 +245,7 @@ type MouseEvent struct { } func getTimeStamp(data DataObject) uint64 { - if value, ok := data.PropertyValue("timeStamp"); ok { + if value, ok := data.PropertyText("timeStamp"); ok { if index := strings.Index(value, "."); index > 0 { value = value[:index] } diff --git a/numberPicker.go b/numberPicker.go index 812d80d..e57362b 100644 --- a/numberPicker.go +++ b/numberPicker.go @@ -286,7 +286,7 @@ func (picker *numberPickerData) htmlProperties(self View, buffer *strings.Builde func (picker *numberPickerData) handleCommand(self View, command PropertyName, data DataObject) bool { switch command { case "textChanged": - if text, ok := data.PropertyValue("text"); ok { + if text, ok := data.PropertyText("text"); ok { if value, err := strconv.ParseFloat(text, 32); err == nil { oldValue := GetNumberPickerValue(picker) picker.setRaw(NumberPickerValue, text) diff --git a/outline.go b/outline.go index 8ad8736..2066f76 100644 --- a/outline.go +++ b/outline.go @@ -134,7 +134,7 @@ func setOutlineProperty(properties Properties, value any) []PropertyName { case DataObject: outline := NewOutlineProperty(nil) for _, tag := range []PropertyName{Style, Width, ColorTag} { - if text, ok := value.PropertyValue(string(tag)); ok && text != "" { + if text, ok := value.PropertyText(string(tag)); ok && text != "" { outline.Set(tag, text) } } diff --git a/pointerEvents.go b/pointerEvents.go index 1621f8f..ceb9c19 100644 --- a/pointerEvents.go +++ b/pointerEvents.go @@ -177,7 +177,7 @@ func (event *PointerEvent) init(data DataObject) { event.TiltX = dataFloatProperty(data, "tiltX") event.TiltY = dataFloatProperty(data, "tiltY") event.Twist = dataFloatProperty(data, "twist") - value, _ := data.PropertyValue("pointerType") + value, _ := data.PropertyText("pointerType") event.PointerType = value event.IsPrimary = dataBoolProperty(data, "isPrimary") } diff --git a/popup.go b/popup.go index eb6289f..a25a4b2 100644 --- a/popup.go +++ b/popup.go @@ -939,15 +939,15 @@ func (popup *popupData) setButtons(value any) bool { popupButtonFromObject := func(obj DataObject) popupButton { var button popupButton - button.title, _ = obj.PropertyValue(string(Title)) + button.title, _ = obj.PropertyText(string(Title)) - if text, ok := obj.PropertyValue("type"); ok { + if text, ok := obj.PropertyText("type"); ok { text, _ = popup.session.resolveConstants(text) t, _ := enumStringToInt(text, []string{"normal", "default", "cancel"}, true) button.buttonType = PopupButtonType(t) } - if fn, ok := obj.PropertyValue("click"); ok { + if fn, ok := obj.PropertyText("click"); ok { button.onClick = newPopupListenerBinding(fn, true) } else if button.buttonType == CancelButton { button.onClick = newPopupListener0(popup.Dismiss) @@ -984,20 +984,36 @@ func (popup *popupData) setButtons(value any) bool { case []popupButton: popup.setRaw(Buttons, value) - case DataObject: - popup.setRaw(Buttons, []popupButton{popupButtonFromObject(value)}) - - case []DataValue: - buttons := make([]popupButton, 0, len(value)) - for _, val := range value { - if val.IsObject() { - buttons = append(buttons, popupButtonFromObject(val.Object())) - } else { - notCompatibleType(Buttons, val) + case DataValue: + switch value.Type() { + case ObjectDataValue: + obj := value.Object() + if obj == nil { + return false } - } - if len(buttons) > 0 { - popup.setRaw(Buttons, buttons) + popup.setRaw(Buttons, []popupButton{popupButtonFromObject(obj)}) + + case ArrayDataValue: + array := value.Array() + if array == nil { + return false + } + + buttons := make([]popupButton, 0, array.Size()) + for val := range array.Elements() { + if obj := val.Object(); obj != nil { + buttons = append(buttons, popupButtonFromObject(obj)) + } else { + notCompatibleType(Buttons, val) + return false + } + } + if len(buttons) > 0 { + popup.setRaw(Buttons, buttons) + } + + default: + return false } case []any: @@ -1570,8 +1586,8 @@ func CreatePopupFromObject(session Session, object DataObject, binding any) Popu popup.session = session popup.hotkeys = map[string]func(Popup){} - for key, value := range object.ToParams() { - popup.Set(key, value) + for key, value := range object.Properties() { + popup.Set(PropertyName(key), value) } if binding != nil { diff --git a/properties.go b/properties.go index 1463008..9b147d7 100644 --- a/properties.go +++ b/properties.go @@ -149,28 +149,33 @@ func (properties *propertyList) writeToBuffer(buffer *strings.Builder, */ func parseProperties(properties Properties, object DataObject) { - for node := range object.Properties() { + for tag, node := range object.Properties() { switch node.Type() { - case TextNode: - properties.Set(PropertyName(node.Tag()), node.Text()) + case TextDataValue: + properties.Set(PropertyName(tag), node.String()) - case ObjectNode: - properties.Set(PropertyName(node.Tag()), node.Object()) + case ObjectDataValue: + properties.Set(PropertyName(tag), node.Object()) - case ArrayNode: - switch node.ArraySize() { + case ArrayDataValue: + array := node.Array() + if array == nil { + return + } + + switch array.Size() { case 0: // do nothing case 1: - if v := node.ArrayElement(0); v.IsObject() { - properties.Set(PropertyName(node.Tag()), v.Object()) + if v := array.Element(0); v.Type() == TextDataValue { + properties.Set(PropertyName(tag), v.String()) } else { - properties.Set(PropertyName(node.Tag()), v.Value()) + properties.Set(PropertyName(tag), v) } default: - properties.Set(PropertyName(node.Tag()), node.Array()) + properties.Set(PropertyName(tag), array) } } } diff --git a/radius.go b/radius.go index 56a1fdc..27dddb6 100644 --- a/radius.go +++ b/radius.go @@ -978,7 +978,7 @@ func setRadiusProperty(properties Properties, value any) []PropertyName { radius := NewRadiusProperty(nil) for _, tag := range []PropertyName{X, Y, TopLeft, TopRight, BottomLeft, BottomRight, TopLeftX, TopLeftY, TopRightX, TopRightY, BottomLeftX, BottomLeftY, BottomRightX, BottomRightY} { - if value, ok := value.PropertyValue(string(tag)); ok { + if value, ok := value.PropertyText(string(tag)); ok { radius.Set(tag, value) } } diff --git a/session.go b/session.go index 021b469..9bc1f74 100644 --- a/session.go +++ b/session.go @@ -701,7 +701,7 @@ func (session *sessionData) handleAnswer(command string, data DataObject) bool { func (session *sessionData) handleRootSize(data DataObject) { getValue := func(tag string) int { - if value, ok := data.PropertyValue(tag); ok { + if value, ok := data.PropertyText(tag); ok { float, err := strconv.ParseFloat(value, 64) if err == nil { return int(float) @@ -722,75 +722,78 @@ func (session *sessionData) handleRootSize(data DataObject) { } func (session *sessionData) handleResize(data DataObject) { - if node := data.PropertyByTag("views"); node != nil && node.Type() == ArrayNode { - for el := range node.ArrayElements() { - if el.IsObject() { - obj := el.Object() - getFloat := func(tag string) float64 { - if value, ok := obj.PropertyValue(tag); ok { - f, err := strconv.ParseFloat(value, 64) - if err == nil { - return f - } - ErrorLog(`Resize event error: ` + err.Error()) - } else { - ErrorLogF(`Resize event error: the property "%s" not found`, tag) - } - return 0 - } - if viewID, ok := obj.PropertyValue("id"); ok { - if n := strings.IndexRune(viewID, '-'); n > 0 { - if view := session.viewByHTMLID(viewID[:n]); view != nil { - view.onItemResize(view, viewID[n+1:], getFloat("x"), getFloat("y"), getFloat("width"), getFloat("height")) - } else { - DebugLogF(`View with id == %s not found`, viewID[:n]) - } - } else if view := session.viewByHTMLID(viewID); view != nil { - view.onResize(view, getFloat("x"), getFloat("y"), getFloat("width"), getFloat("height")) - view.setScroll(getFloat("scroll-x"), getFloat("scroll-y"), getFloat("scroll-width"), getFloat("scroll-height")) - } else { - DebugLogF(`View with id == %s not found`, viewID) + array := data.PropertyArray("views") + if array == nil { + ErrorLog(`Resize event error: invalid "views" property`) + return + } + + for el := range array.Elements() { + if obj := el.Object(); obj != nil { + + getFloat := func(tag string) float64 { + if value, ok := obj.PropertyText(tag); ok { + f, err := strconv.ParseFloat(value, 64) + if err == nil { + return f } + ErrorLog(`Resize event error: ` + err.Error()) } else { - ErrorLog(`"id" property not found`) + ErrorLogF(`Resize event error: the property "%s" not found`, tag) + } + return 0 + } + + if viewID, ok := obj.PropertyText("id"); ok { + if n := strings.IndexRune(viewID, '-'); n > 0 { + if view := session.viewByHTMLID(viewID[:n]); view != nil { + view.onItemResize(view, viewID[n+1:], getFloat("x"), getFloat("y"), getFloat("width"), getFloat("height")) + } else { + DebugLogF(`View with id == %s not found`, viewID[:n]) + } + } else if view := session.viewByHTMLID(viewID); view != nil { + view.onResize(view, getFloat("x"), getFloat("y"), getFloat("width"), getFloat("height")) + view.setScroll(getFloat("scroll-x"), getFloat("scroll-y"), getFloat("scroll-width"), getFloat("scroll-height")) + } else { + DebugLogF(`View with id == %s not found`, viewID) } } else { - ErrorLog(`Resize event error: views element is not object`) + ErrorLog(`"id" property not found`) } + } else { + ErrorLog(`Resize event error: views element is not object`) } - } else { - ErrorLog(`Resize event error: invalid "views" property`) } } func (session *sessionData) handleSessionInfo(params DataObject) { - if value, ok := params.PropertyValue("touch"); ok { + if value, ok := params.PropertyText("touch"); ok { session.touchScreen = (value == "1" || value == "true") } - if value, ok := params.PropertyValue("user-agent"); ok { + if value, ok := params.PropertyText("user-agent"); ok { session.userAgent = value } - if value, ok := params.PropertyValue("direction"); ok { + if value, ok := params.PropertyText("direction"); ok { if value == "rtl" { session.textDirection = RightToLeftDirection } } - if value, ok := params.PropertyValue("language"); ok { + if value, ok := params.PropertyText("language"); ok { session.language = value } - if value, ok := params.PropertyValue("languages"); ok { + if value, ok := params.PropertyText("languages"); ok { session.languages = strings.Split(value, ",") } - if value, ok := params.PropertyValue("dark"); ok { + if value, ok := params.PropertyText("dark"); ok { session.darkTheme = (value == "1" || value == "true") } - if value, ok := params.PropertyValue("pixel-ratio"); ok { + if value, ok := params.PropertyText("pixel-ratio"); ok { if f, err := strconv.ParseFloat(value, 64); err != nil { ErrorLog(err.Error()) } else { @@ -798,7 +801,7 @@ func (session *sessionData) handleSessionInfo(params DataObject) { } } - if value, ok := params.PropertyValue("light-dark"); ok { + if value, ok := params.PropertyText("light-dark"); ok { session.lightDark = (value == "1" || value == "true") } } @@ -818,7 +821,7 @@ func (session *sessionData) handleEvent(command string, data DataObject) { session.onResume() case "timer": - if text, ok := data.PropertyValue("timerID"); ok { + if text, ok := data.PropertyText("timerID"); ok { timerID, err := strconv.Atoi(text) if err == nil { if fn, ok := session.timers[timerID]; ok { @@ -843,7 +846,7 @@ func (session *sessionData) handleEvent(command string, data DataObject) { session.handleSessionInfo(data) default: - if viewID, ok := data.PropertyValue("id"); ok { + if viewID, ok := data.PropertyText("id"); ok { if viewID != "body" { if view := session.viewByHTMLID(viewID); view != nil { view.handleCommand(view, PropertyName(command), data) diff --git a/shadow.go b/shadow.go index 36841f3..0a4965a 100644 --- a/shadow.go +++ b/shadow.go @@ -289,16 +289,39 @@ func setShadowProperty(properties Properties, tag PropertyName, value any) bool } case DataValue: - if !value.IsObject() { + switch value.Type() { + case ObjectDataValue: + obj := value.Object() + if obj == nil { + return false + } + properties.setRaw(tag, []ShadowProperty{parseShadowProperty(obj)}) + + case ArrayDataValue: + array := value.Array() + if array == nil { + return false + } + shadows := make([]ShadowProperty, 0, array.Size()) + for data := range array.Elements() { + if obj := data.Object(); obj != nil { + shadows = append(shadows, parseShadowProperty(obj)) + } + } + if len(shadows) == 0 { + return false + } + properties.setRaw(tag, shadows) + + default: return false } - properties.setRaw(tag, []ShadowProperty{parseShadowProperty(value.Object())}) case []DataValue: - shadows := []ShadowProperty{} + shadows := make([]ShadowProperty, 0, len(value)) for _, data := range value { - if data.IsObject() { - shadows = append(shadows, parseShadowProperty(data.Object())) + if obj := data.Object(); obj != nil { + shadows = append(shadows, parseShadowProperty(obj)) } } if len(shadows) == 0 { diff --git a/strings.go b/strings.go index fa023cb..58c6de2 100644 --- a/strings.go +++ b/strings.go @@ -62,10 +62,8 @@ func loadStringResources(text string) { table = map[string]string{} } - for prop := range obj.Properties() { - if prop.Type() == TextNode { - table[prop.Tag()] = prop.Text() - } + for tag, text := range obj.TextProperties() { + table[tag] = text } stringResources[lang] = table @@ -73,9 +71,9 @@ func loadStringResources(text string) { tag := data.Tag() if tag == "strings" { - for prop := range data.Properties() { - if prop.Type() == ObjectNode { - parseStrings(prop.Object(), prop.Tag()) + for tag, prop := range data.Properties() { + if prop.Type() == ObjectDataValue { + parseStrings(prop.Object(), tag) } } diff --git a/tableView.go b/tableView.go index ef2fe87..42e6eec 100644 --- a/tableView.go +++ b/tableView.go @@ -643,11 +643,33 @@ func (table *tableViewData) setFunc(tag PropertyName, value any) []PropertyName setLineStyle := func() []PropertyName { params := []Params{} switch value := value.(type) { + case Params: + params = []Params{value} + case []Params: params = value - case DataNode: - params = value.ArrayAsParams() + case DataValue: + switch value.Type() { + case ObjectDataValue: + if obj := value.Object(); obj != nil { + if objParams := obj.ToParams(); len(objParams) > 0 { + params = []Params{objParams} + } + } + + case ArrayDataValue: + if array := value.Array(); array != nil { + params = make([]Params, 0, array.Size()) + for element := range array.Elements() { + if obj := element.Object(); obj != nil { + if objParams := obj.ToParams(); len(objParams) > 0 { + params = append(params, objParams) + } + } + } + } + } default: notCompatibleType(tag, params) @@ -751,20 +773,18 @@ func (table *tableViewData) setFunc(tag PropertyName, value any) []PropertyName case DataObject: params := Params{} - for prop := range value.Properties() { - if prop.Type() == TextNode { - params[PropertyName(prop.Tag())] = prop.Text() - } + for tag, prop := range value.TextProperties() { + params[PropertyName(tag)] = prop } return table.setFunc(tag, params) - case DataNode: + case DataValue: switch value.Type() { - case ObjectNode: + case ObjectDataValue: return table.setFunc(tag, value.Object()) - case TextNode: - table.setRaw(tag, value.Text()) + case TextDataValue: + table.setRaw(tag, value.String()) default: notCompatibleType(tag, value) diff --git a/tabsLayout.go b/tabsLayout.go index e567449..3d0db3e 100644 --- a/tabsLayout.go +++ b/tabsLayout.go @@ -703,7 +703,7 @@ func (tabsLayout *tabsLayoutData) htmlSubviews(self View, buffer *strings.Builde func (tabsLayout *tabsLayoutData) handleCommand(self View, command PropertyName, data DataObject) bool { switch command { case "tabClick": - if numberText, ok := data.PropertyValue("number"); ok { + if numberText, ok := data.PropertyText("number"); ok { if number, err := strconv.Atoi(numberText); err == nil { current := GetCurrent(tabsLayout) if current != number { @@ -716,7 +716,7 @@ func (tabsLayout *tabsLayoutData) handleCommand(self View, command PropertyName, return true case "tabCloseClick": - if numberText, ok := data.PropertyValue("number"); ok { + if numberText, ok := data.PropertyText("number"); ok { if number, err := strconv.Atoi(numberText); err == nil { for _, listener := range getOneArgEventListeners[TabsLayout, int](tabsLayout, nil, TabCloseEvent) { listener.Run(tabsLayout, number) diff --git a/theme.go b/theme.go index e6e7249..b0e7f2e 100644 --- a/theme.go +++ b/theme.go @@ -709,119 +709,92 @@ func (theme *theme) addText(themeText string) bool { if err != nil { ErrorLog(err.Error()) return false - } else if !data.IsObject() || data.Tag() != "theme" { + } else if data.Tag() != "theme" { return false } objToStyle := func(obj DataObject) ViewStyle { params := Params{} - for node := range obj.Properties() { + for tag, node := range obj.Properties() { switch node.Type() { - case ArrayNode: - params[PropertyName(node.Tag())] = node.Array() + case ArrayDataValue: + params[PropertyName(tag)] = node.Array() - case ObjectNode: - params[PropertyName(node.Tag())] = node.Object() + case ObjectDataValue: + params[PropertyName(tag)] = node.Object() default: - params[PropertyName(node.Tag())] = node.Text() + params[PropertyName(tag)] = node.String() } } return NewViewStyle(params) } - for d := range data.Properties() { - switch tag := d.Tag(); tag { + for tag, d := range data.Properties() { + switch tag { case "name": - if d.Type() == TextNode { - if text := d.Text(); text != "" { + if d.Type() == TextDataValue { + if text := d.String(); text != "" { theme.name = text } } + case "constants": - if d.Type() == ObjectNode { - if obj := d.Object(); obj != nil { - for prop := range obj.Properties() { - if prop.Type() == TextNode { - theme.constants[prop.Tag()] = prop.Text() - } - } + if obj := d.Object(); obj != nil { + for propTag, text := range obj.TextProperties() { + theme.constants[propTag] = text } } case "constants:touch": - if d.Type() == ObjectNode { - if obj := d.Object(); obj != nil { - for prop := range obj.Properties() { - if prop.Type() == TextNode { - theme.touchConstants[prop.Tag()] = prop.Text() - } - } + if obj := d.Object(); obj != nil { + for propTag, text := range obj.TextProperties() { + theme.touchConstants[propTag] = text } } case "colors": - if d.Type() == ObjectNode { - if obj := d.Object(); obj != nil { - for prop := range obj.Properties() { - if prop.Type() == TextNode { - theme.colors[prop.Tag()] = prop.Text() - } - } + if obj := d.Object(); obj != nil { + for propTag, text := range obj.TextProperties() { + theme.colors[propTag] = text } } case "colors:dark": - if d.Type() == ObjectNode { - if obj := d.Object(); obj != nil { - for prop := range obj.Properties() { - if prop.Type() == TextNode { - theme.darkColors[prop.Tag()] = prop.Text() - } - } + if obj := d.Object(); obj != nil { + for propTag, text := range obj.TextProperties() { + theme.darkColors[propTag] = text } } case "images": - if d.Type() == ObjectNode { - if obj := d.Object(); obj != nil { - for prop := range obj.Properties() { - if prop.Type() == TextNode { - theme.images[prop.Tag()] = prop.Text() - } - } + if obj := d.Object(); obj != nil { + for propTag, text := range obj.TextProperties() { + theme.images[propTag] = text } } case "images:dark": - if d.Type() == ObjectNode { - if obj := d.Object(); obj != nil { - for prop := range obj.Properties() { - if prop.Type() == TextNode { - theme.darkImages[prop.Tag()] = prop.Text() - } - } + if obj := d.Object(); obj != nil { + for propTag, text := range obj.TextProperties() { + theme.darkImages[propTag] = text } } case "styles": - if d.Type() == ArrayNode { - arraySize := d.ArraySize() - for k := range arraySize { - if element := d.ArrayElement(k); element != nil && element.IsObject() { - if obj := element.Object(); obj != nil { - theme.styles[obj.Tag()] = objToStyle(obj) - } + if array := d.Array(); array != nil { + for element := range array.Elements() { + if obj := element.Object(); obj != nil { + theme.styles[obj.Tag()] = objToStyle(obj) } } } default: - if d.Type() == ArrayNode && strings.HasPrefix(tag, "styles:") { + if d.Type() == ArrayDataValue && strings.HasPrefix(tag, "styles:") { if rule, ok := parseMediaRule(tag); ok { - arraySize := d.ArraySize() - for k := range arraySize { - if element := d.ArrayElement(k); element != nil && element.IsObject() { + if array := d.Array(); array != nil { + for element := range array.Elements() { if obj := element.Object(); obj != nil { rule.styles[obj.Tag()] = objToStyle(obj) } diff --git a/timePicker.go b/timePicker.go index 43df1a8..98b1326 100644 --- a/timePicker.go +++ b/timePicker.go @@ -318,7 +318,7 @@ func (picker *timePickerData) htmlProperties(self View, buffer *strings.Builder) func (picker *timePickerData) handleCommand(self View, command PropertyName, data DataObject) bool { switch command { case "textChanged": - if text, ok := data.PropertyValue("text"); ok { + if text, ok := data.PropertyText("text"); ok { if value, ok := stringToTime(text); ok { oldValue := GetTimePickerValue(picker) picker.setRaw(TimePickerValue, value) diff --git a/touchEvents.go b/touchEvents.go index 4a1a73e..6e07687 100644 --- a/touchEvents.go +++ b/touchEvents.go @@ -154,26 +154,25 @@ func (event *TouchEvent) init(data DataObject) { event.Touches = []Touch{} event.TimeStamp = getTimeStamp(data) - if node := data.PropertyByTag("touches"); node != nil && node.Type() == ArrayNode { - for i := range node.ArraySize() { - if element := node.ArrayElement(i); element != nil && element.IsObject() { - if obj := element.Object(); obj != nil { - var touch Touch - if value, ok := obj.PropertyValue("identifier"); ok { - touch.Identifier, _ = strconv.Atoi(value) - } - touch.X = dataFloatProperty(obj, "x") - touch.Y = dataFloatProperty(obj, "y") - touch.ClientX = dataFloatProperty(obj, "clientX") - touch.ClientY = dataFloatProperty(obj, "clientY") - touch.ScreenX = dataFloatProperty(obj, "screenX") - touch.ScreenY = dataFloatProperty(obj, "screenY") - touch.RadiusX = dataFloatProperty(obj, "radiusX") - touch.RadiusY = dataFloatProperty(obj, "radiusY") - touch.RotationAngle = dataFloatProperty(obj, "rotationAngle") - touch.Force = dataFloatProperty(obj, "force") - event.Touches = append(event.Touches, touch) + if array := data.PropertyArray("touches"); array != nil { + for element := range array.Elements() { + if obj := element.Object(); obj != nil { + touch := Touch{ + X: dataFloatProperty(obj, "x"), + Y: dataFloatProperty(obj, "y"), + ClientX: dataFloatProperty(obj, "clientX"), + ClientY: dataFloatProperty(obj, "clientY"), + ScreenX: dataFloatProperty(obj, "screenX"), + ScreenY: dataFloatProperty(obj, "screenY"), + RadiusX: dataFloatProperty(obj, "radiusX"), + RadiusY: dataFloatProperty(obj, "radiusY"), + RotationAngle: dataFloatProperty(obj, "rotationAngle"), + Force: dataFloatProperty(obj, "force"), } + if value, ok := obj.PropertyText("identifier"); ok { + touch.Identifier, _ = strconv.Atoi(value) + } + event.Touches = append(event.Touches, touch) } } } diff --git a/transform.go b/transform.go index df94657..b2e9466 100644 --- a/transform.go +++ b/transform.go @@ -352,9 +352,9 @@ func valueToTransformProperty(value any) TransformProperty { parseObject := func(obj DataObject) TransformProperty { transform := NewTransformProperty(nil) ok := true - for prop := range obj.Properties() { - if prop.Type() == TextNode { - if !transform.Set(PropertyName(prop.Tag()), prop.Text()) { + for tag, prop := range obj.Properties() { + if prop.Type() == TextDataValue { + if !transform.Set(PropertyName(tag), prop.String()) { ok = false } } else { @@ -372,10 +372,7 @@ func valueToTransformProperty(value any) TransformProperty { case TransformProperty: return value - case DataObject: - return parseObject(value) - - case DataNode: + case DataValue: if obj := value.Object(); obj != nil { return parseObject(obj) } diff --git a/utils.go b/utils.go index afccea0..99ff0d9 100644 --- a/utils.go +++ b/utils.go @@ -54,7 +54,7 @@ func GetLocalIP() string { } func dataIntProperty(data DataObject, tag string) (int, bool) { - if value, ok := data.PropertyValue(tag); ok { + if value, ok := data.PropertyText(tag); ok { if n, err := strconv.Atoi(value); err == nil { return n, true } @@ -63,14 +63,14 @@ func dataIntProperty(data DataObject, tag string) (int, bool) { } func dataBoolProperty(data DataObject, tag string) bool { - if value, ok := data.PropertyValue(tag); ok && value == "1" { + if value, ok := data.PropertyText(tag); ok && value == "1" { return true } return false } func dataFloatProperty(data DataObject, tag string) float64 { - if value, ok := data.PropertyValue(tag); ok { + if value, ok := data.PropertyText(tag); ok { if n, err := strconv.ParseFloat(value, 64); err == nil { return n } @@ -136,3 +136,54 @@ func writeStrings(buffer *strings.Builder, strs ...string) { buffer.WriteString(str) } } + +func isQuotesNeeded(text string) bool { + switch len(text) { + case 0: + return true + + case 1: + simple := (text[0] >= '0' && text[0] <= '9') || (text[0] >= 'A' && text[0] <= 'Z') || + (text[0] >= 'a' && text[0] <= 'z') || text[0] == '_' + return !simple + } + + for _, ch := range text { + if (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || + ch == '+' || ch == '-' || ch == '@' || ch == '/' || ch == '_' || ch == '.' || + ch == ':' || ch == '#' || ch == '%' || ch == 'π' || ch == '°' { + } else { + return true + } + } + + return false +} + +func replaceEscapeSymbols(text string) string { + replace := []struct{ old, new string }{ + {old: "\\", new: `\\`}, + {old: "\t", new: `\t`}, + {old: "\r", new: `\r`}, + {old: "\n", new: `\n`}, + {old: "\"", new: `\"`}, + } + for _, s := range replace { + text = strings.ReplaceAll(text, s.old, s.new) + } + return text +} + +func writeRuiText(buffer *strings.Builder, text string, objectName bool) { + if isQuotesNeeded(text) || (objectName && strings.Contains(text, "/")) { + if strings.ContainsAny(text, "\\\t\r\n\"") { + text = replaceEscapeSymbols(text) + } + + buffer.WriteRune('"') + buffer.WriteString(text) + buffer.WriteRune('"') + } else { + buffer.WriteString(text) + } +} diff --git a/view.go b/view.go index fa7d052..fd27178 100644 --- a/view.go +++ b/view.go @@ -424,12 +424,11 @@ func (view *viewData) setFunc(tag PropertyName, value any) []PropertyName { return []PropertyName{Binding} case changeListeners: - switch value := value.(type) { - case DataObject: - for node := range value.Properties() { - if node.Type() == TextNode { - if text := node.Text(); text != "" { - view.changeListener[PropertyName(node.Tag())] = newOneArgListenerBinding[View, PropertyName](text) + if value, ok := value.(DataObject); ok { + for tag, node := range value.Properties() { + if node.Type() == TextDataValue { + if text := node.String(); text != "" { + view.changeListener[PropertyName(tag)] = newOneArgListenerBinding[View, PropertyName](text) } } } @@ -437,11 +436,6 @@ func (view *viewData) setFunc(tag PropertyName, value any) []PropertyName { view.setRaw(changeListeners, view.changeListener) } return []PropertyName{tag} - - case DataNode: - if value.Type() == ObjectNode { - return view.setFunc(tag, value.Object()) - } } return []PropertyName{} @@ -548,38 +542,32 @@ func (view *viewData) setFunc(tag PropertyName, value any) []PropertyName { } } - case DataObject: - data := map[string]string{} - for node := range value.Properties() { - if node.Type() == TextNode { - data[node.Tag()] = node.Text() - } else { - invalidPropertyValue(DragData, value) - return nil + case DataValue: + switch value.Type() { + case TextDataValue: + return view.setFunc(DragData, value.String()) + + case ObjectDataValue: + if obj := value.Object(); obj != nil { + data := map[string]string{} + for tag, node := range obj.Properties() { + if node.Type() == TextDataValue { + data[tag] = node.String() + } else { + invalidPropertyValue(DragData, value) + return nil + } + } + if len(data) == 0 { + view.setRaw(DragData, nil) + } else { + view.setRaw(DragData, data) + } + return []PropertyName{DragData} } } - if len(data) == 0 { - view.setRaw(DragData, nil) - } else { - view.setRaw(DragData, data) - } - - case DataNode: - switch value.Type() { - case TextNode: - return view.setFunc(DragData, value.Text()) - - case ObjectNode: - return view.setFunc(DragData, value.Object()) - } invalidPropertyValue(DragData, value) - return nil - - case DataValue: - if value.IsObject() { - return view.setFunc(DragData, value.Object()) - } - return view.setFunc(DragData, value.Value()) + return []PropertyName{} default: notCompatibleType(DragData, value) @@ -1205,14 +1193,14 @@ func (view *viewData) handleCommand(self View, command PropertyName, data DataOb view.onScroll(view, dataFloatProperty(data, "x"), dataFloatProperty(data, "y"), dataFloatProperty(data, "width"), dataFloatProperty(data, "height")) case "widthChanged": - if value, ok := data.PropertyValue("width"); ok { + if value, ok := data.PropertyText("width"); ok { if width, ok := StringToSizeUnit(value); ok { self.setRaw(Width, width) } } case "heightChanged": - if value, ok := data.PropertyValue("height"); ok { + if value, ok := data.PropertyText("height"); ok { if height, ok := StringToSizeUnit(value); ok { self.setRaw(Height, height) } @@ -1221,7 +1209,7 @@ func (view *viewData) handleCommand(self View, command PropertyName, data DataOb /* case "resize": floatProperty := func(tag string) float64 { - if value, ok := data.PropertyValue(tag); ok { + if value, ok := data.PropertyText(tag); ok { if result, err := strconv.ParseFloat(value, 64); err == nil { return result } @@ -1240,7 +1228,7 @@ func (view *viewData) handleCommand(self View, command PropertyName, data DataOb if listener := view.fileLoader[key]; listener != nil { delete(view.fileLoader, key) - if base64Data, ok := data.PropertyValue("data"); ok { + if base64Data, ok := data.PropertyText("data"); ok { if index := strings.LastIndex(base64Data, ","); index >= 0 { base64Data = base64Data[index+1:] } @@ -1258,7 +1246,7 @@ func (view *viewData) handleCommand(self View, command PropertyName, data DataOb file := dataToFileInfo(data) key := file.key() - if error, ok := data.PropertyValue("error"); ok { + if error, ok := data.PropertyText("error"); ok { ErrorLogF(`Load "%s" file error: %s`, file.Name, error) } diff --git a/viewFactory.go b/viewFactory.go index 8a79ea4..8a11e3b 100644 --- a/viewFactory.go +++ b/viewFactory.go @@ -78,14 +78,19 @@ func RegisterViewCreator(tag string, creator func(Session) View) bool { // If the function fails, it returns nil and an error message is written to the log. func CreateViewFromObject(session Session, object DataObject, binding any) View { if session == nil { - ErrorLog(`Session must not be nil`) + ErrorLog(`CreateViewFromObject: session must not be nil`) + return nil + } + + if object == nil { + ErrorLog(`CreateViewFromObject: object must not be nil`) return nil } tag := object.Tag() creator, ok := viewCreators()[strings.ToLower(tag)] if !ok { - ErrorLog(`Unknown view type "` + tag + `"`) + ErrorLog(`CreateViewFromObject: unknown view type "` + tag + `"`) return nil } diff --git a/viewStyle.go b/viewStyle.go index b5121b2..52d16ff 100644 --- a/viewStyle.go +++ b/viewStyle.go @@ -7,7 +7,6 @@ import ( "strconv" "strings" "time" - "unicode" ) // ViewStyle interface of the style of view @@ -566,68 +565,8 @@ func viewStyleGet(style Properties, tag PropertyName) any { return style.getRaw(tag) } -func isQuotesNeeded(text string) bool { - if len(text) == 1 { - simple := (text[0] >= '0' && text[0] <= '9') || (text[0] >= 'A' && text[0] <= 'Z') || (text[0] >= 'a' && text[0] <= 'z') - return !simple - } else { - for _, ch := range text { - if (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || - ch == '+' || ch == '-' || ch == '@' || ch == '/' || ch == '_' || ch == '.' || - ch == ':' || ch == '#' || ch == '%' || ch == 'π' || ch == '°' { - } else { - return true - } - } - } - return false -} - -func isQuotesNeededForObjectName(name string) bool { - for _, char := range name { - if unicode.IsSpace(char) || - slices.Contains([]rune{'=', '{', '}', '[', ']', ',', ' ', '\t', '\n', '\'', '"', '`', '/'}, char) { - return true - } - } - return false -} - -func replaceEscapeSymbols(text string) string { - replace := []struct{ old, new string }{ - {old: "\\", new: `\\`}, - {old: "\t", new: `\t`}, - {old: "\r", new: `\r`}, - {old: "\n", new: `\n`}, - {old: "\"", new: `\"`}, - } - for _, s := range replace { - text = strings.ReplaceAll(text, s.old, s.new) - } - return text -} - func propertyValueToString(tag PropertyName, value any, indent string) string { - writeString := func(buffer *strings.Builder, text string) string { - if isQuotesNeeded(text) { - writeStrings(buffer, `"`, replaceEscapeSymbols(text), `"`) - } else { - buffer.WriteString(text) - } - return text - } - - writeObjectNameString := func(buffer *strings.Builder, text string) string { - textEscaped := replaceEscapeSymbols(text) - if isQuotesNeededForObjectName(textEscaped) { - writeStrings(buffer, `"`, textEscaped, `"`) - } else { - buffer.WriteString(text) - } - return text - } - switch value := value.(type) { case string: if tag == Text || tag == Title || tag == Summary || isQuotesNeeded(value) { @@ -652,7 +591,7 @@ func propertyValueToString(tag PropertyName, value any, indent string) string { lead := "[" for _, text := range value { buffer.WriteString(lead) - writeString(buffer, text) + writeRuiText(buffer, text, false) lead = ", " } } else { @@ -660,7 +599,7 @@ func propertyValueToString(tag PropertyName, value any, indent string) string { for _, text := range value { buffer.WriteString(indent) buffer.WriteRune('\t') - writeString(buffer, text) + writeRuiText(buffer, text, false) buffer.WriteString(",\n") } buffer.WriteString(indent) @@ -683,9 +622,9 @@ func propertyValueToString(tag PropertyName, value any, indent string) string { for _, key := range keys { buffer.WriteString(indent) buffer.WriteRune('\t') - writeObjectNameString(buffer, key) + writeRuiText(buffer, key, true) buffer.WriteString(" = ") - writeString(buffer, value[key]) + writeRuiText(buffer, value[key], false) buffer.WriteString(",\n") } buffer.WriteString(indent) @@ -1006,9 +945,9 @@ func propertyValueToString(tag PropertyName, value any, indent string) string { for key, listener := range value { if text, ok := listener.(string); ok && text != "" { buffer.WriteString(indent2) - writeString(buffer, string(key)) + writeRuiText(buffer, string(key), true) buffer.WriteString(" = ") - writeString(buffer, text) + writeRuiText(buffer, text, false) buffer.WriteString(",\n") } } @@ -1029,7 +968,7 @@ func propertyValueToString(tag PropertyName, value any, indent string) string { buffer.WriteString("[\n") for _, button := range value { buffer.WriteString(lead) - writeString(buffer, button.title) + writeRuiText(buffer, button.title, false) switch button.buttonType { case CancelButton: buffer.WriteString(`, type = cancel`) @@ -1040,7 +979,7 @@ func propertyValueToString(tag PropertyName, value any, indent string) string { if button.onClick != nil { if text, ok := button.onClick.rawListener().(string); ok { buffer.WriteString(`, click = `) - writeString(buffer, text) + writeRuiText(buffer, text, true) } } buffer.WriteString(" },\n") @@ -1062,7 +1001,7 @@ func propertyValueToString(tag PropertyName, value any, indent string) string { buffer.WriteString("[\n") for _, button := range value { buffer.WriteString(lead) - writeString(buffer, button.Title) + writeRuiText(buffer, button.Title, false) switch button.Type { case CancelButton: buffer.WriteString(`, type = cancel`) @@ -1073,7 +1012,7 @@ func propertyValueToString(tag PropertyName, value any, indent string) string { if button.Binding != "" { buffer.WriteString(`, click = `) - writeString(buffer, button.Binding) + writeRuiText(buffer, button.Binding, true) } buffer.WriteString(" },\n") } diff --git a/viewStyleSet.go b/viewStyleSet.go index 02a0fb6..5eda756 100644 --- a/viewStyleSet.go +++ b/viewStyleSet.go @@ -54,31 +54,33 @@ func setTransitionProperty(properties Properties, value any) bool { } return false - case DataNode: + case DataValue: switch value.Type() { - case ObjectNode: + case ObjectDataValue: if setObject(value.Object()) { properties.setRaw(Transition, transitions) return true } return false - case ArrayNode: - for i := range value.ArraySize() { - if obj := value.ArrayElement(i).Object(); obj != nil { - if !setObject(obj) { + case ArrayDataValue: + if array := value.Array(); array != nil { + for element := range array.Elements() { + if obj := element.Object(); obj != nil { + if !setObject(obj) { + return false + } + } else { + notCompatibleType(Transition, element) return false } - } else { - notCompatibleType(Transition, value.ArrayElement(i)) - return false } + if len(transitions) == 0 { + transitions = nil + } + properties.setRaw(Transition, transitions) + return true } - if len(transitions) == 0 { - transitions = nil - } - properties.setRaw(Transition, transitions) - return true } } @@ -268,11 +270,16 @@ func viewStyleSet(style Properties, tag PropertyName, value any) []PropertyName case Params: style.setRaw(tag, value) - case DataObject: - if params := value.ToParams(); len(params) > 0 { - style.setRaw(tag, params) + case DataValue: + if obj := value.Object(); obj != nil { + if params := obj.ToParams(); len(params) > 0 { + style.setRaw(tag, params) + } else { + style.setRaw(tag, nil) + } } else { - style.setRaw(tag, nil) + notCompatibleType(tag, value) + return nil } default: @@ -289,23 +296,16 @@ func viewStyleSet(style Properties, tag PropertyName, value any) []PropertyName case Params: style.setRaw(tag, value) - case DataObject: - if params := value.ToParams(); len(params) > 0 { - style.setRaw(tag, params) - } else { - style.setRaw(tag, nil) - } - - case DataNode: + case DataValue: switch value.Type() { - case TextNode: - if text := value.Text(); text != "" { + case TextDataValue: + if text := value.String(); text != "" { style.setRaw(tag, text) } else { style.setRaw(tag, nil) } - case ObjectNode: + case ObjectDataValue: if obj := value.Object(); obj != nil { if params := obj.ToParams(); len(params) > 0 { style.setRaw(tag, params) diff --git a/viewsContainer.go b/viewsContainer.go index 0137cf1..c3afb4e 100644 --- a/viewsContainer.go +++ b/viewsContainer.go @@ -219,7 +219,7 @@ func (container *viewsContainerData) htmlSubviews(self View, buffer *strings.Bui } } -func viewFromTextValue(text string, session Session) View { +func viewFromTextDataValue(text string, session Session) View { if data, err := ParseDataText(text); err == nil { if view := CreateViewFromObject(session, data, nil); view != nil { return view @@ -299,12 +299,12 @@ func (container *viewsContainerData) setContent(value any) bool { container.views = value case string: - container.views = []View{viewFromTextValue(value, session)} + container.views = []View{viewFromTextDataValue(value, session)} case []string: views := []View{} for _, text := range value { - views = append(views, viewFromTextValue(text, session)) + views = append(views, viewFromTextDataValue(text, session)) } container.views = views @@ -316,7 +316,7 @@ func (container *viewsContainerData) setContent(value any) bool { views = append(views, v) case string: - views = append(views, viewFromTextValue(v, session)) + views = append(views, viewFromTextDataValue(v, session)) default: notCompatibleType(Content, value) @@ -332,19 +332,36 @@ func (container *viewsContainerData) setContent(value any) bool { return false } - case []DataValue: + case DataArray: views := []View{} - for _, data := range value { - if data.IsObject() { + for data := range value.Elements() { + switch data.Type() { + case ObjectDataValue: if view := CreateViewFromObject(session, data.Object(), nil); view != nil { views = append(views, view) } - } else { - views = append(views, viewFromTextValue(data.Value(), session)) + + case TextDataValue: + views = append(views, viewFromTextDataValue(data.String(), session)) + + case ArrayDataValue: + // not supported } } container.views = views + case DataValue: + switch value.Type() { + case ObjectDataValue: + return container.setContent(value.Object()) + + case TextDataValue: + return container.setContent(value.String()) + + case ArrayDataValue: + return container.setContent(value.Array()) + } + default: notCompatibleType(Content, value) return false diff --git a/wasmBridge.go b/wasmBridge.go index 80e40ee..67d1617 100644 --- a/wasmBridge.go +++ b/wasmBridge.go @@ -270,7 +270,7 @@ func (bridge *wasmBridge) htmlPropertyValue(htmlID, name string) string { } func (bridge *wasmBridge) answerReceived(answer DataObject) { - if text, ok := answer.PropertyValue("answerID"); ok { + if text, ok := answer.PropertyText("answerID"); ok { if id, err := strconv.Atoi(text); err == nil { if chanel, ok := bridge.answer[id]; ok { chanel <- answer diff --git a/webBridge.go b/webBridge.go index be9f147..de6efeb 100644 --- a/webBridge.go +++ b/webBridge.go @@ -423,7 +423,7 @@ func (bridge *webBridge) canvasTextMetrics(htmlID, font, text string) TextMetric func (bridge *webBridge) htmlPropertyValue(htmlID, name string) string { if data, ok := bridge.remoteValue("getPropertyValue", htmlID, name); ok { - if value, ok := data.PropertyValue("value"); ok { + if value, ok := data.PropertyText("value"); ok { return value } } @@ -431,7 +431,7 @@ func (bridge *webBridge) htmlPropertyValue(htmlID, name string) string { } func (bridge *webBridge) answerReceived(answer DataObject) { - if text, ok := answer.PropertyValue("answerID"); ok { + if text, ok := answer.PropertyText("answerID"); ok { if id, err := strconv.Atoi(text); err == nil { if chanel, ok := bridge.answer[id]; ok { chanel <- answer