Work with rui data files has been redesigned

This commit is contained in:
Alexei Anoshenko 2026-10-18 16:50:24 +03:00
parent d6d243458c
commit 6e703f0692
57 changed files with 2717 additions and 1177 deletions

View File

@ -245,12 +245,16 @@ func parseAnimation(obj DataObject) AnimationProperty {
animation := new(animationData) animation := new(animationData)
animation.init() animation.init()
for node := range obj.Properties() { for tag, node := range obj.Properties() {
tag := PropertyName(node.Tag()) switch node.Type() {
if node.Type() == TextNode { case TextDataValue:
animation.Set(tag, node.Text()) animation.Set(PropertyName(tag), node.String())
} else {
animation.Set(tag, node) case ObjectDataValue:
animation.Set(PropertyName(tag), node.Object())
case ArrayDataValue:
animation.Set(PropertyName(tag), node.Array())
} }
} }
return animation return animation
@ -418,6 +422,47 @@ func normalizeAnimation(tag PropertyName) PropertyName {
} }
func animationSet(properties Properties, tag PropertyName, value any) []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 { switch tag {
case ID: case ID:
if text, ok := value.(string); ok { if text, ok := value.(string); ok {
@ -486,61 +531,16 @@ func animationSet(properties Properties, tag PropertyName, value any) []Property
ErrorLog("[]AnimatedProperty is empty") ErrorLog("[]AnimatedProperty is empty")
} }
case DataNode: case DataObject:
parseObject := func(obj DataObject) (AnimatedProperty, bool) { if prop, ok := parseObject(value); ok {
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
}
switch value.Type() {
case ObjectNode:
if prop, ok := parseObject(value.Object()); ok {
properties.setRaw(tag, []AnimatedProperty{prop}) properties.setRaw(tag, []AnimatedProperty{prop})
return []PropertyName{tag} return []PropertyName{tag}
} }
case ArrayNode: case DataArray:
props := []AnimatedProperty{} props := []AnimatedProperty{}
for val := range value.ArrayElements() { for val := range value.Elements() {
if val.IsObject() { if val.Type() == ObjectDataValue {
if prop, ok := parseObject(val.Object()); ok { if prop, ok := parseObject(val.Object()); ok {
props = append(props, prop) props = append(props, prop)
} }
@ -553,6 +553,17 @@ func animationSet(properties Properties, tag PropertyName, value any) []Property
return []PropertyName{tag} 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: default:
notCompatibleType(tag, value) notCompatibleType(tag, value)
} }
@ -1088,25 +1099,38 @@ func setAnimationProperty(properties Properties, tag PropertyName, value any) bo
return true return true
} }
case DataNode: case DataArray:
animations := []AnimationProperty{} animations := []AnimationProperty{}
result := true result := true
for i := range value.ArraySize() { for val := range value.Elements() {
if obj := value.ArrayElement(i).Object(); obj != nil { if obj := val.Object(); obj != nil {
if anim := parseAnimation(obj); anim.hasAnimatedProperty() { if anim := parseAnimation(obj); anim.hasAnimatedProperty() {
animations = append(animations, anim) animations = append(animations, anim)
} else { } else {
result = false result = false
} }
} else { } else {
notCompatibleType(tag, value.ArrayElement(i)) notCompatibleType(tag, val)
result = false result = false
} }
} }
if result && len(animations) > 0 { if result && len(animations) > 0 {
set(animations) set(animations)
} }
return result 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) notCompatibleType(tag, value)

View File

@ -157,7 +157,7 @@ const (
) )
func (view *viewData) handleTransitionEvents(tag PropertyName, data DataObject) { 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) property := PropertyName(propertyName)
if tag == TransitionEndEvent || tag == TransitionCancelEvent { if tag == TransitionEndEvent || tag == TransitionCancelEvent {
if animation, ok := view.singleTransition[property]; ok { 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) { func (view *viewData) handleAnimationEvents(tag PropertyName, data DataObject) {
if listeners := getOneArgEventListeners[View, string](view, nil, tag); len(listeners) > 0 { if listeners := getOneArgEventListeners[View, string](view, nil, tag); len(listeners) > 0 {
id := "" id := ""
if name, ok := data.PropertyValue("name"); ok { if name, ok := data.PropertyText("name"); ok {
for _, animation := range GetAnimation(view) { for _, animation := range GetAnimation(view) {
if name == animation.animationName() { if name == animation.animationName() {
id, _ = stringProperty(animation, ID, view.Session()) id, _ = stringProperty(animation, ID, view.Session())

View File

@ -272,7 +272,7 @@ func (app *application) sseHandler(w http.ResponseWriter, req *http.Request) {
} }
func getSessionID(obj DataObject) (int, bool) { func getSessionID(obj DataObject) (int, bool) {
sessionText, ok := obj.PropertyValue("session") sessionText, ok := obj.PropertyText("session")
if !ok { if !ok {
ErrorLog(`"session" key not found`) ErrorLog(`"session" key not found`)
return 0, false return 0, false

View File

@ -47,6 +47,10 @@ type backgroundElement struct {
} }
func createBackground(obj DataObject) BackgroundElement { func createBackground(obj DataObject) BackgroundElement {
if obj == nil {
return nil
}
var result BackgroundElement = nil var result BackgroundElement = nil
switch obj.Tag() { switch obj.Tag() {
@ -66,11 +70,9 @@ func createBackground(obj DataObject) BackgroundElement {
return nil return nil
} }
for node := range obj.Properties() { for tag, text := range obj.TextProperties() {
if node.Type() == TextNode { if text != "" {
if value := node.Text(); value != "" { result.Set(PropertyName(tag), text)
result.Set(PropertyName(node.Tag()), value)
}
} }
} }
@ -96,30 +98,47 @@ func parseBackgroundValue(value any) []BackgroundElement {
case []BackgroundElement: case []BackgroundElement:
return value return value
case []DataValue: case DataValue:
background := []BackgroundElement{} switch value.Type() {
for _, el := range value { case TextDataValue:
if el.IsObject() { if element := parseBackgroundText(value.String()); element != nil {
if element := createBackground(el.Object()); 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) background = append(background, element)
} else { } else {
return nil return nil
} }
} else if element := parseBackgroundText(el.Value()); element != nil {
case TextDataValue:
if element := parseBackgroundText(element.String()); element != nil {
background = append(background, element) background = append(background, element)
} else { } else {
return nil return nil
} }
case ArrayDataValue:
return nil
}
} }
return background return background
}
case DataObject:
if element := createBackground(value); element != nil {
return []BackgroundElement{element}
} }
case []DataObject: case []DataObject:
background := []BackgroundElement{} background := make([]BackgroundElement, 0, len(value))
for _, obj := range value { for _, obj := range value {
if element := createBackground(obj); element != nil { if element := createBackground(obj); element != nil {
background = append(background, element) background = append(background, element)
@ -164,7 +183,6 @@ func parseBackgroundValue(value any) []BackgroundElement {
} }
} }
return elements return elements
} }
return nil return nil

View File

@ -205,16 +205,13 @@ func newBorderProperty(value any) BorderProperty {
case BorderProperty: case BorderProperty:
return value return value
case DataNode: case DataValue:
if value.Type() == ObjectNode { if value.Type() == ObjectDataValue {
_ = border.setBorderObject(value.Object()) _ = border.setBorderObject(value.Object())
} else { } else {
return nil return nil
} }
case DataObject:
_ = border.setBorderObject(value)
case ViewBorder: case ViewBorder:
border.properties[Style] = value.Style border.properties[Style] = value.Style
border.properties[Width] = value.Width border.properties[Width] = value.Width
@ -434,17 +431,20 @@ func (border *borderProperty) String() string {
} }
func (border *borderProperty) setBorderObject(obj DataObject) bool { func (border *borderProperty) setBorderObject(obj DataObject) bool {
if obj == nil {
return false
}
result := true result := true
for node := range obj.Properties() { for tag, node := range obj.Properties() {
tag := PropertyName(node.Tag())
switch node.Type() { switch node.Type() {
case TextNode: case TextDataValue:
if borderSet(border, tag, node.Text()) == nil { if borderSet(border, PropertyName(tag), node.String()) == nil {
result = false result = false
} }
case ObjectNode: case ObjectDataValue:
if borderSet(border, tag, node.Object()) == nil { if borderSet(border, PropertyName(tag), node.Object()) == nil {
result = false result = false
} }
@ -535,21 +535,21 @@ func borderSet(properties Properties, tag PropertyName, value any) []PropertyNam
setSingleBorderObject := func(prefix PropertyName, obj DataObject) []PropertyName { setSingleBorderObject := func(prefix PropertyName, obj DataObject) []PropertyName {
result := []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) props := setEnumProperty(properties, prefix+"-style", text, enumProperties[BorderStyle].values)
if props == nil { if props == nil {
return nil return nil
} }
result = append(result, props...) 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) props := setColorProperty(properties, prefix+"-color", text)
if props == nil && len(result) == 0 { if props == nil && len(result) == 0 {
return nil return nil
} }
result = append(result, props...) result = append(result, props...)
} }
if text, ok := obj.PropertyValue("width"); ok { if text, ok := obj.PropertyText("width"); ok {
props := setSizeProperty(properties, prefix+"-width", text) props := setSizeProperty(properties, prefix+"-width", text)
if props == nil && len(result) == 0 { if props == nil && len(result) == 0 {
return nil return nil

View File

@ -265,7 +265,7 @@ func setBoundsProperty(properties Properties, tag PropertyName, value any) []Pro
case DataObject: case DataObject:
bounds := NewBoundsProperty(nil) bounds := NewBoundsProperty(nil)
for _, tag := range []PropertyName{Top, Right, Bottom, Left} { 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) { if !bounds.Set(tag, text) {
notCompatibleType(tag, value) notCompatibleType(tag, value)
return nil return nil

View File

@ -66,7 +66,7 @@ func TestBoundsSet(t *testing.T) {
} }
for _, tag := range []string{"padding-left", "padding-top", "padding-right", "padding-bottom"} { 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 ignoreTestLog = true
obj.SetPropertyValue(tag, "x") obj.SetPropertyValue(tag, "x")
if bounds.setProperty(obj, "padding", session) { if bounds.setProperty(obj, "padding", session) {

View File

@ -113,7 +113,7 @@ func (storage *clientStorageData) RemoveAll() {
func (storage *clientStorageData) handleEvent(command string, data DataObject) { func (storage *clientStorageData) handleEvent(command string, data DataObject) {
request := 0 request := 0
if text, ok := data.PropertyValue("request"); ok { if text, ok := data.PropertyText("request"); ok {
var err error var err error
if request, err = strconv.Atoi(text); err != nil { if request, err = strconv.Atoi(text); err != nil {
ErrorLog(err.Error()) ErrorLog(err.Error())
@ -126,7 +126,7 @@ func (storage *clientStorageData) handleEvent(command string, data DataObject) {
switch command { switch command {
case "storageError": case "storageError":
if text, ok := data.PropertyValue("error"); ok { if text, ok := data.PropertyText("error"); ok {
ErrorLog(text) ErrorLog(text)
} }
@ -137,7 +137,7 @@ func (storage *clientStorageData) handleEvent(command string, data DataObject) {
} }
delete(storage.getResult, request) delete(storage.getResult, request)
text, ok := data.PropertyValue("values") text, ok := data.PropertyText("values")
if !ok { if !ok {
ErrorLog("'values' property not found (command: storageValues)") ErrorLog("'values' property not found (command: storageValues)")
return return

View File

@ -582,7 +582,7 @@ func parseClipShapeProperty(obj DataObject) ClipShapeProperty {
clip := new(insetClipData) clip := new(insetClipData)
clip.init() clip.init()
for _, tag := range []PropertyName{Top, Right, Bottom, Left, Radius, RadiusX, RadiusY} { 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) insetClipDataSet(clip, tag, value)
} }
} }
@ -592,7 +592,7 @@ func parseClipShapeProperty(obj DataObject) ClipShapeProperty {
clip := new(circleClipData) clip := new(circleClipData)
clip.init() clip.init()
for _, tag := range []PropertyName{X, Y, Radius} { 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) circleClipDataSet(clip, tag, value)
} }
} }
@ -602,7 +602,7 @@ func parseClipShapeProperty(obj DataObject) ClipShapeProperty {
clip := new(ellipseClipData) clip := new(ellipseClipData)
clip.init() clip.init()
for _, tag := range []PropertyName{X, Y, RadiusX, RadiusY} { 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) ellipseClipDataSet(clip, tag, value)
} }
} }
@ -611,7 +611,7 @@ func parseClipShapeProperty(obj DataObject) ClipShapeProperty {
case "polygon": case "polygon":
clip := new(polygonClipData) clip := new(polygonClipData)
clip.init() clip.init()
if value, ok := obj.PropertyValue(string(Points)); ok { if value, ok := obj.PropertyText(string(Points)); ok {
polygonClipDataSet(clip, Points, value) polygonClipDataSet(clip, Points, value)
} }
return clip return clip
@ -645,7 +645,7 @@ func setClipShapePropertyProperty(properties Properties, tag PropertyName, value
} }
case DataValue: case DataValue:
if value.IsObject() { if value.Type() == ObjectDataValue {
if clip := parseClipShapeProperty(value.Object()); clip != nil { if clip := parseClipShapeProperty(value.Object()); clip != nil {
properties.setRaw(tag, clip) properties.setRaw(tag, clip)
return []PropertyName{tag} return []PropertyName{tag}

View File

@ -222,20 +222,20 @@ func parseColorPair(obj DataObject) (ColorPair, bool) {
var err error var err error
result := ColorPair{} result := ColorPair{}
for property := range obj.Properties() { for tag, property := range obj.Properties() {
if property.Type() != TextNode { if property.Type() != TextDataValue {
return ColorPair{}, false return ColorPair{}, false
} }
switch property.Tag() { switch tag {
case "light": case "light":
result.Light, err = stringToColor(property.Text()) result.Light, err = stringToColor(property.String())
if err != nil { if err != nil {
return ColorPair{}, false return ColorPair{}, false
} }
case "dark": case "dark":
result.Dark, err = stringToColor(property.Text()) result.Dark, err = stringToColor(property.String())
if err != nil { if err != nil {
return ColorPair{}, false return ColorPair{}, false
} }

View File

@ -159,7 +159,7 @@ func (picker *colorPickerData) htmlProperties(self View, buffer *strings.Builder
func (picker *colorPickerData) handleCommand(self View, command PropertyName, data DataObject) bool { func (picker *colorPickerData) handleCommand(self View, command PropertyName, data DataObject) bool {
switch command { switch command {
case "textChanged": case "textChanged":
if text, ok := data.PropertyValue("text"); ok { if text, ok := data.PropertyText("text"); ok {
if color, ok := StringToColor(text); ok { if color, ok := StringToColor(text); ok {
oldColor := GetColorPickerValue(picker) oldColor := GetColorPickerValue(picker)
picker.setRaw(ColorPickerValue, color) picker.setRaw(ColorPickerValue, color)

View File

@ -36,7 +36,7 @@ func newColumnSeparatorProperty(value any) ColumnSeparatorProperty {
separator := new(columnSeparatorProperty) separator := new(columnSeparatorProperty)
separator.init() separator.init()
for _, tag := range []PropertyName{Style, Width, ColorTag} { 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) propertiesSet(separator, tag, value)
} }
} }

600
data.go
View File

@ -3,387 +3,131 @@ package rui
import ( import (
"errors" "errors"
"fmt" "fmt"
"iter" "os"
"slices" "slices"
"strconv"
"strings" "strings"
"unicode" "unicode"
) )
// DataValue interface of a data node value // DataValueType defines the type of DataNode
type DataValue interface { type DataValueType int
// IsObject returns "true" if data value is an object
IsObject() bool
// Object returns data value as a data object // Constants which are used to describe a value type, see [DataValue]
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]
const ( const (
// TextNode - node is the pair "tag - text value". Syntax: <tag> = <text> // TextDataValue - the DataValue value is a string.
TextNode DataNodeType = 0 // The String() method of DataValue interface is used to access the value.
// ObjectNode - node is the pair "tag - object". Syntax: <tag> = <object name>{...} TextDataValue DataValueType = 0
ObjectNode DataNodeType = 1
// ArrayNode - node is the pair "tag - object". Syntax: <tag> = [...] // ObjectDataValue - the DataValue value is a object.
ArrayNode DataNodeType = 2 // 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 // DataValue interface of a data node value
type DataNode interface { type DataValue interface {
// Tag returns a tag name fmt.Stringer
Tag() string
// Type returns a node type. Possible values are TextNode, ObjectNode and ArrayNode writeToBuffer(buffer *strings.Builder, indent string)
Type() DataNodeType
// Text returns node text // IsObject returns "true" if data value is an object
Text() string 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 Object() DataObject
// ArraySize returns array size if that node type is an array // Array returns data value as a data array if Type() == ArrayDataValue, and nil otherwise.
ArraySize() int Array() DataArray
// 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
} }
/******************************************************************************/ /******************************************************************************/
type dataStringValue struct { type dataStringValue struct {
value string text string
} }
func (value *dataStringValue) Value() string { // NewTextData creates an DataValue object of TextDataValue type.
return value.value //
// 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)
} }
func (value *dataStringValue) IsObject() bool { return data
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 { func (value *dataStringValue) Object() DataObject {
return nil return nil
} }
/******************************************************************************/ func (value *dataStringValue) Array() DataArray {
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 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
}
}
}
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 { type dataParser struct {
data []rune data []rune
size int 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) { func (parser *dataParser) parseTag() (string, error) {
parser.skipSpaces(true) parser.skipSpaces(true)
startPos := parser.pos startPos := parser.pos
@ -593,62 +345,53 @@ func (parser *dataParser) stopSymbol(symbol rune) bool {
slices.Contains([]rune{'=', '{', '}', '[', ']', ',', ' ', '\t', '\n', '\'', '"', '`', '/'}, symbol) slices.Contains([]rune{'=', '{', '}', '[', ']', ',', ' ', '\t', '\n', '\'', '"', '`', '/'}, symbol)
} }
func (parser *dataParser) parseNode() (DataNode, error) { func (parser *dataParser) parseNode() (string, DataValue, error) {
var tag string
var err error
if tag, err = parser.parseTag(); err != nil { tag, err := parser.parseTag()
return nil, err if err != nil {
return "", nil, err
} }
parser.skipSpaces(true) parser.skipSpaces(true)
if parser.data[parser.pos] != '=' { 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.pos++
parser.skipSpaces(true) parser.skipSpaces(true)
switch parser.data[parser.pos] { switch parser.data[parser.pos] {
case '[': case '[':
node := new(dataNode) array, err := parser.parseArray()
node.tag = tag if err != nil {
return "", nil, err
if node.array, err = parser.parseArray(); err != nil {
return nil, err
} }
return node, nil return tag, NewDataArray(array), nil
case '{': case '{':
node := new(dataNode) obj, err := parser.parseObject("_")
node.tag = tag if err != nil {
if node.value, err = parser.parseObject("_"); err != nil { return "", nil, err
return nil, err
} }
return node, nil return tag, obj, nil
case '}', ']', '=': 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: default:
var str string text, err := parser.parseTag()
if str, err = parser.parseTag(); err != nil { if err != nil {
return nil, err return "", nil, err
} }
node := new(dataNode)
node.tag = tag
if parser.data[parser.pos] == '{' { if parser.data[parser.pos] == '{' {
if node.value, err = parser.parseObject(str); err != nil { obj, err := parser.parseObject(text)
return nil, err if err != nil {
return "", nil, err
} }
} else { return tag, obj, nil
val := new(dataStringValue)
val.value = str
node.value = val
} }
return node, nil return tag, NewTextData(text), nil
} }
} }
@ -658,9 +401,7 @@ func (parser *dataParser) parseObject(tag string) (DataObject, error) {
} }
parser.pos++ parser.pos++
obj := new(dataObject) obj := NewDataObject(tag)
obj.tag = tag
obj.property = []DataNode{}
for parser.pos < parser.size { for parser.pos < parser.size {
parser.skipSpaces(true) parser.skipSpaces(true)
@ -670,29 +411,29 @@ func (parser *dataParser) parseObject(tag string) (DataObject, error) {
return obj, nil return obj, nil
} }
node, err := parser.parseNode() property, value, err := parser.parseNode()
if err != nil { if err != nil {
return nil, err return nil, err
} }
obj.SetProperty(property, value)
obj.property = append(obj.property, node) switch parser.data[parser.pos] {
if parser.data[parser.pos] == '}' { case '}':
parser.pos++ parser.pos++
parser.skipSpaces(true) parser.skipSpaces(true)
return obj, nil 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) return nil, fmt.Errorf(`expected '}', '\n' or ',' (line: %d, position: %d)`, parser.line, parser.pos-parser.lineStart)
} }
if parser.data[parser.pos] != '\n' { parser.skipSpacesAndCommas()
parser.pos++
}
parser.skipSpaces(true)
for parser.data[parser.pos] == ',' {
parser.pos++
parser.skipSpaces(true)
}
} }
return nil, errors.New("unexpected end of text") return nil, errors.New("unexpected end of text")
@ -704,23 +445,18 @@ func (parser *dataParser) parseArray() ([]DataValue, error) {
array := []DataValue{} array := []DataValue{}
parser.skipSpacesAndCommas()
for parser.pos < parser.size { for parser.pos < parser.size {
parser.skipSpaces(true) if parser.data[parser.pos] != ']' {
for parser.data[parser.pos] == ',' && parser.pos < parser.size { if parser.data[parser.pos] == '[' {
parser.pos++
parser.skipSpaces(true)
}
if parser.pos >= parser.size { subarray, err := parser.parseArray()
break if err != nil {
} return nil, err
if parser.data[parser.pos] == ']' {
parser.pos++
parser.skipSpaces(true)
return array, nil
} }
array = append(array, NewDataArray(subarray))
} else {
tag, err := parser.parseTag() tag, err := parser.parseTag()
if err != nil { if err != nil {
return nil, err return nil, err
@ -733,35 +469,36 @@ func (parser *dataParser) parseArray() ([]DataValue, error) {
} }
array = append(array, obj) array = append(array, obj)
} else { } else {
val := new(dataStringValue) array = append(array, NewTextData(tag))
val.value = tag }
array = append(array, val) }
} }
switch parser.data[parser.pos] { switch parser.data[parser.pos] {
case ']', ',', '\n': case ']':
parser.pos++
parser.skipSpaces(true)
return array, nil
case '\n':
// do nothing
case ',':
parser.pos++
default: default:
return nil, fmt.Errorf(`expected ']' or ',' (line: %d, position: %d)`, parser.line, parser.pos-parser.lineStart) return nil, fmt.Errorf(`expected ']' or ',' (line: %d, position: %d)`, parser.line, parser.pos-parser.lineStart)
} }
/* parser.skipSpacesAndCommas()
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()
*/
} }
return nil, errors.New("unexpected end of text") 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) { func ParseDataText(text string) (DataObject, error) {
if strings.ContainsAny(text, "\r") { if strings.ContainsAny(text, "\r") {
@ -783,3 +520,16 @@ func ParseDataText(text string) (DataObject, error) {
} }
return parser.parseObject(tag) 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))
}

404
dataArray.go Normal file
View File

@ -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
}

62
dataArray_test.go Normal file
View File

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

View File

@ -127,14 +127,21 @@ func anyToStringArray(value any, dateTimeFormat string) ([]string, bool) {
case []string: case []string:
return value, true return value, true
case []DataValue: case DataValue:
items := make([]string, 0, len(value)) switch value.Type() {
for _, val := range value { case TextDataValue:
if !val.IsObject() { return []string{value.String()}, true
items = append(items, val.Value())
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: case []fmt.Stringer:
items := make([]string, len(value)) items := make([]string, len(value))

1013
dataObject.go Normal file

File diff suppressed because it is too large Load Diff

217
dataObject_test.go Normal file
View File

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

View File

@ -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 + "`")
}
}
*/

View File

@ -30,27 +30,23 @@ func TestParseDataText(t *testing.T) {
if obj.Tag() != "obj1" { if obj.Tag() != "obj1" {
t.Error(`obj.Tag() != "obj1"`) t.Error(`obj.Tag() != "obj1"`)
} }
if !obj.IsObject() { if obj.Type() != ObjectDataValue {
t.Error(`!obj.IsObject()`) t.Error(`obj.Type() != ObjectDataValue`)
} }
if obj.PropertyCount() != 4 { if obj.PropertyCount() != 4 {
t.Error(`obj.PropertyCount() != 4`) t.Error(`obj.PropertyCount() != 4`)
} }
if obj.Property(-1) != nil { if val, ok := obj.PropertyText("key1"); !ok || val != "val1" {
t.Error(`obj.Property(-1) != nil`) t.Errorf(`obj.PropertyText("key1") result: ("%s",%v)`, val, ok)
} }
if val, ok := obj.PropertyValue("key1"); !ok || val != "val1" { if val, ok := obj.PropertyText("key3"); !ok || val != "\n \t \\ \r \" ' \x4F\x4e \u01Ea" {
t.Errorf(`obj.PropertyValue("key1") result: ("%s",%v)`, val, ok) t.Errorf(`obj.PropertyText("key3") result: ("%s",%v)`, val, ok)
} }
if val, ok := obj.PropertyValue("key3"); !ok || val != "\n \t \\ \r \" ' \x4F\x4e \u01Ea" { if val, ok := obj.PropertyText("key4"); !ok || val != `\n \t \\ \r \" ' \x8F \UF80a` {
t.Errorf(`obj.PropertyValue("key3") result: ("%s",%v)`, val, ok) t.Errorf(`obj.PropertyText("key4") 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 o := obj.PropertyObject("key2"); o == nil { if o := obj.PropertyObject("key2"); o == nil {
@ -65,47 +61,41 @@ func TestParseDataText(t *testing.T) {
t.Error(`obj.PropertyObject("key5") != nil`) t.Error(`obj.PropertyObject("key5") != nil`)
} }
if val, ok := obj.PropertyValue("key2"); ok { if val, ok := obj.PropertyText("key2"); ok {
t.Errorf(`obj.PropertyValue("key2") result: ("%s",%v)`, val, ok) t.Errorf(`obj.PropertyText("key2") result: ("%s",%v)`, val, ok)
} }
if val, ok := obj.PropertyValue("key5"); ok { if val, ok := obj.PropertyText("key5"); ok {
t.Errorf(`obj.PropertyValue("key5") result: ("%s",%v)`, val, ok) t.Errorf(`obj.PropertyText("key5") result: ("%s",%v)`, val, ok)
} }
testKey := func(obj DataObject, index int, tag string, nodeType DataNodeType) DataNode { testKey := func(obj DataObject, tag string, nodeType DataValueType) DataValue {
key := obj.Property(index) key := obj.Property(tag)
if key == nil { if key == nil {
t.Errorf(`%s.Property(%d) == nil`, obj.Tag(), index) t.Errorf(`%s.Property(%s) == nil`, obj.Tag(), tag)
} else { } else if key.Type() != nodeType {
if key.Tag() != tag {
t.Errorf(`%s.Property(%d).Tag() != "%s"`, obj.Tag(), index, tag)
}
if key.Type() != nodeType {
switch nodeType { switch nodeType {
case TextNode: case TextDataValue:
t.Errorf(`%s.Property(%d) is not text`, obj.Tag(), index) t.Errorf(`%s.Property(%s) is not text`, obj.Tag(), tag)
case ObjectNode: case ObjectDataValue:
t.Errorf(`%s.Property(%d) is not object`, obj.Tag(), index) t.Errorf(`%s.Property(%s) is not object`, obj.Tag(), tag)
case ArrayNode: case ArrayDataValue:
t.Errorf(`%s.Property(%d) is not array`, obj.Tag(), index) t.Errorf(`%s.Property(%s) is not array`, obj.Tag(), tag)
}
} }
} }
return key return key
} }
if key := testKey(obj, 0, "key1", TextNode); key != nil { if key := testKey(obj, "key1", TextDataValue); key != nil {
if key.Text() != "val1" { if key.String() != "val1" {
t.Error(`key1.Value() != "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() o := key.Object()
if o == nil { if o == nil {
t.Error(`key2.Value().Object() == nil`) t.Error(`key2.Value().Object() == nil`)
@ -116,61 +106,83 @@ func TestParseDataText(t *testing.T) {
type testKeyData struct { type testKeyData struct {
tag string tag string
nodeType DataNodeType nodeType DataValueType
} }
data := []testKeyData{ data := []testKeyData{
{tag: "key2.1", nodeType: ArrayNode}, {tag: "key2.1", nodeType: ArrayDataValue},
{tag: "key 2.2", nodeType: TextNode}, {tag: "key 2.2", nodeType: TextDataValue},
{tag: "key2.3", nodeType: ObjectNode}, {tag: "key2.3", nodeType: ObjectDataValue},
{tag: "key2.4", nodeType: ObjectNode}, {tag: "key2.4", nodeType: ObjectDataValue},
{tag: "key2.5", nodeType: ArrayNode}, {tag: "key2.5", nodeType: ArrayDataValue},
} }
for i, d := range data { for _, d := range data {
testKey(o, i, d.tag, d.nodeType) testKey(o, d.tag, d.nodeType)
} }
} }
} }
node1 := obj.Property(1) node1 := obj.Property("key2")
if node1 == nil { if node1 == nil {
t.Error("obj.Property(1) != nil") t.Error("obj.Property(\"key2\") == nil")
} else if node1.Type() != ObjectNode { } else if node1.Type() != ObjectDataValue {
t.Error("obj.Property(1).Type() != ObjectNode") t.Error("obj.Property(\"key2\").Type() != ObjectNode")
} else if obj := node1.Object(); obj != nil { } else if obj := node1.Object(); obj != nil {
if key := obj.Property(0); key != nil { if key := obj.Property("key2.1"); key != nil {
if key.Type() != ArrayNode { if key.Type() != ArrayDataValue {
t.Error("obj.Property(1).Object().Property(0)..Type() != ArrayNode") t.Error("obj.Property(\"key2\").Object().Property(\"key2.1\").Type() != ArrayNode")
} else { } else {
if key.ArraySize() != 3 { array := key.Array()
t.Error("obj.Property(1).Object().Property(0).ArraySize() != 3") if array == nil {
t.Error("obj.Property(\"key2\").Object().Property(\"key2.1\").Array() == nil")
} else {
if array.Size() != 3 {
t.Error("obj.Property(\"key2\").Object().Property(\"key2.1\").Array().Size() != 3")
} }
if e := key.ArrayElement(0); e == nil { for i, tp := range []DataValueType{TextDataValue, ObjectDataValue, ObjectDataValue} {
t.Error("obj.Property(1).Object().Property(0).ArrayElement(0) == nil") if e := array.Element(i); e == nil {
} else if e.IsObject() { t.Error("obj.Property(\"key2\").Object().Property(\"key2.1\").Array().Element(0) == nil")
t.Error("obj.Property(1).Object().Property(0).ArrayElement(0).IsObject() == true") } else if e.Type() != tp {
t.Errorf("obj.Property(\"key2\").Object().Property(\"key2.1\").Array().Element(0).Type() != %d", tp)
}
} }
if e := key.ArrayElement(2); e == nil { if e := array.Element(3); e != nil {
t.Error("obj.Property(1).Object().Property(0).ArrayElement(2) == nil") t.Error("obj.Property(\"key2\").Object().Property(\"key2.1\").Array().Element(3) != 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() != ""`)
} }
if e := key.ArrayElement(3); e != nil {
t.Error("obj.Property(1).Object().Property(0).ArrayElement(3) != nil")
} }
} }
} }
} else { } 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{ failText := []string{
" ", " ",
"obj[]", "obj[]",

View File

@ -343,7 +343,7 @@ func (picker *datePickerData) htmlProperties(self View, buffer *strings.Builder)
func (picker *datePickerData) handleCommand(self View, command PropertyName, data DataObject) bool { func (picker *datePickerData) handleCommand(self View, command PropertyName, data DataObject) bool {
switch command { switch command {
case "textChanged": 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 { if value, err := time.Parse(dateFormat, text); err == nil {
oldValue := GetDatePickerValue(picker) oldValue := GetDatePickerValue(picker)
picker.setRaw(DatePickerValue, value) picker.setRaw(DatePickerValue, value)

View File

@ -228,7 +228,7 @@ func (event *DragAndDropEvent) init(session Session, data DataObject) {
event.MouseEvent.init(data) event.MouseEvent.init(data)
event.Data = map[string]string{} 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, ";") { for line := range strings.SplitSeq(value, ";") {
if mimeData, data, ok := strings.Cut(line, ":"); ok { if mimeData, data, ok := strings.Cut(line, ":"); ok {
mime, err := base64.StdEncoding.DecodeString(mimeData) 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) 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"} { for i, value := range []string{"undefined", "copy", "move", "copyMove", "link", "copyLink", "linkMove", "all"} {
if value == effect { if value == effect {
event.EffectAllowed = i 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"} { for i, value := range []string{"none", "copy", "move", "", "link"} {
if value == effect { if value == effect {
event.DropEffect = i event.DropEffect = i

View File

@ -188,15 +188,17 @@ func parseIndicesArray(value any) ([]any, bool) {
case string: case string:
return parseIndicesArray(strings.Split(value, ",")) return parseIndicesArray(strings.Split(value, ","))
case []DataValue: case DataValue:
items := make([]string, 0, len(value)) if array := value.Array(); array != nil {
for _, val := range value { items := make([]string, 0, array.Size())
if !val.IsObject() { for val := range array.Elements() {
items = append(items, val.Value()) if val.Type() == TextDataValue {
items = append(items, val.String())
} }
} }
return parseIndicesArray(items) return parseIndicesArray(items)
} }
}
return nil, false 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 { func (list *dropDownListData) handleCommand(self View, command PropertyName, data DataObject) bool {
switch command { switch command {
case "itemSelected": case "itemSelected":
if text, ok := data.PropertyValue("number"); ok { if text, ok := data.PropertyText("number"); ok {
if number, err := strconv.Atoi(text); err == nil { if number, err := strconv.Atoi(text); err == nil {
items := GetDropDownItems(list) items := GetDropDownItems(list)
if GetCurrent(list) != number && number >= 0 && number < len(items) { if GetCurrent(list) != number && number >= 0 && number < len(items) {

View File

@ -387,7 +387,7 @@ func (edit *editViewData) handleCommand(self View, command PropertyName, data Da
switch command { switch command {
case "textChanged": case "textChanged":
oldText := GetText(edit) oldText := GetText(edit)
if text, ok := data.PropertyValue("text"); ok { if text, ok := data.PropertyText("text"); ok {
edit.setRaw(Text, text) edit.setRaw(Text, text)
if text != oldText { if text != oldText {
edit.textChanged(text, oldText) edit.textChanged(text, oldText)

View File

@ -88,24 +88,23 @@ type filePickerData struct {
//loader map[int]func(FileInfo, []byte) //loader map[int]func(FileInfo, []byte)
} }
func dataToFileInfo(node DataValue) FileInfo { func dataToFileInfo(obj DataObject) FileInfo {
var file FileInfo var file FileInfo
if obj := node.Object(); obj != nil { file.Name, _ = obj.PropertyText("name")
file.Name, _ = obj.PropertyValue("name") file.MimeType, _ = obj.PropertyText("mime-type")
file.MimeType, _ = obj.PropertyValue("mime-type")
if size, ok := obj.PropertyValue("size"); ok { if size, ok := obj.PropertyText("size"); ok {
if n, err := strconv.ParseInt(size, 10, 64); err == nil { if n, err := strconv.ParseInt(size, 10, 64); err == nil {
file.Size = n file.Size = n
} }
} }
if value, ok := obj.PropertyValue("last-modified"); ok { if value, ok := obj.PropertyText("last-modified"); ok {
if n, err := strconv.ParseInt(value, 10, 64); err == nil { if n, err := strconv.ParseInt(value, 10, 64); err == nil {
file.LastModified = time.UnixMilli(n) file.LastModified = time.UnixMilli(n)
} }
} }
}
return file return file
} }
@ -273,16 +272,25 @@ func (picker *filePickerData) htmlProperties(self View, buffer *strings.Builder)
} }
func parseFilesTag(data DataObject) []FileInfo { func parseFilesTag(data DataObject) []FileInfo {
if node := data.PropertyByTag("files"); node != nil && node.Type() == ArrayNode { if node := data.Property("files"); node != nil {
count := node.ArraySize() switch node.Type() {
files := make([]FileInfo, count) case ArrayDataValue:
for i := range count { if array := node.Array(); array != nil {
if value := node.ArrayElement(i); value != nil { files := make([]FileInfo, 0, array.Size())
files[i] = dataToFileInfo(value) for value := range array.Elements() {
if obj := value.Object(); obj != nil {
files = append(files, dataToFileInfo(obj))
} }
} }
return files return files
} }
case ObjectDataValue:
if obj := node.Object(); obj != nil {
return []FileInfo{dataToFileInfo(obj)}
}
}
}
return nil return nil
} }

View File

@ -171,13 +171,12 @@ func NewFilterProperty(params Params) FilterProperty {
func newFilterProperty(obj DataObject) FilterProperty { func newFilterProperty(obj DataObject) FilterProperty {
filter := new(filterData) filter := new(filterData)
filter.init() filter.init()
for node := range obj.Properties() { for tag, node := range obj.Properties() {
tag := node.Tag()
switch node.Type() { switch node.Type() {
case TextNode: case TextDataValue:
filter.Set(PropertyName(tag), node.Text()) filter.Set(PropertyName(tag), node.String())
case ObjectNode: case ObjectDataValue:
if tag == string(HueRotate) { if tag == string(HueRotate) {
// TODO // TODO
} else { } 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: case DataValue:
if value.IsObject() { if obj := value.Object(); obj != nil {
if filter := newFilterProperty(value.Object()); filter != nil { if filter := newFilterProperty(obj); filter != nil {
properties.setRaw(tag, filter) properties.setRaw(tag, filter)
return []PropertyName{tag} return []PropertyName{tag}
} }

2
go.mod
View File

@ -1,6 +1,6 @@
module github.com/anoshenko/rui module github.com/anoshenko/rui
go 1.24 go 1.26
require ( require (
github.com/gorilla/websocket v1.5.3 github.com/gorilla/websocket v1.5.3

View File

@ -189,20 +189,22 @@ func setGridCellSize(properties Properties, tag PropertyName, value any) []Prope
return nil return nil
} }
case []DataValue: case DataValue:
count := len(value) array := value.Array()
if count == 0 { if array == nil || array.Size() == 0 {
invalidPropertyValue(tag, value) invalidPropertyValue(tag, value)
return nil return nil
} }
values := make([]string, count)
for i, val := range value { values := make([]string, 0, array.Size())
if val.IsObject() { for val := range array.Elements() {
if val.Type() != TextDataValue {
invalidPropertyValue(tag, value) invalidPropertyValue(tag, value)
return nil return nil
} }
values[i] = val.Value() values = append(values, val.String())
} }
if !setValues(values) { if !setValues(values) {
return nil return nil
} }

View File

@ -101,15 +101,15 @@ func (manager *imageManager) imageLoaded(obj DataObject) {
return return
} }
if url, ok := obj.PropertyValue("url"); ok { if url, ok := obj.PropertyText("url"); ok {
if image, ok := manager.images[url]; ok { if image, ok := manager.images[url]; ok {
image.loadingStatus = ImageReady 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 { if w, err := strconv.ParseFloat(width, 64); err == nil {
image.width = w 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 { if h, err := strconv.ParseFloat(height, 64); err == nil {
image.height = h image.height = h
} }
@ -127,11 +127,11 @@ func (manager *imageManager) imageLoadError(obj DataObject) {
return return
} }
if url, ok := obj.PropertyValue("url"); ok { if url, ok := obj.PropertyText("url"); ok {
if image, ok := manager.images[url]; ok { if image, ok := manager.images[url]; ok {
delete(manager.images, url) delete(manager.images, url)
text, _ := obj.PropertyValue("message") text, _ := obj.PropertyText("message")
image.setLoadingError(text) image.setLoadingError(text)
if image.listener != nil { if image.listener != nil {

View File

@ -315,7 +315,7 @@ func (imageView *imageViewData) handleCommand(self View, command PropertyName, d
case "imageViewLoaded": case "imageViewLoaded":
imageView.naturalWidth = dataFloatProperty(data, "natural-width") imageView.naturalWidth = dataFloatProperty(data, "natural-width")
imageView.naturalHeight = dataFloatProperty(data, "natural-height") 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) { for _, listener := range getNoArgEventListeners[ImageView](imageView, nil, LoadedEvent) {
listener.Run(imageView) listener.Run(imageView)

View File

@ -416,14 +416,12 @@ type KeyEvent struct {
func (event *KeyEvent) init(data DataObject) { func (event *KeyEvent) init(data DataObject) {
getBool := func(tag string) bool { getBool := func(tag string) bool {
if value, ok := data.PropertyValue(tag); ok && value == "1" { value, ok := data.PropertyText(tag)
return true return ok && value == "1"
}
return false
} }
event.Key, _ = data.PropertyValue("key") event.Key, _ = data.PropertyText("key")
code, _ := data.PropertyValue("code") code, _ := data.PropertyText("code")
event.Code = KeyCode(code) event.Code = KeyCode(code)
event.TimeStamp = getTimeStamp(data) event.TimeStamp = getTimeStamp(data)
event.Repeat = getBool("repeat") event.Repeat = getBool("repeat")

View File

@ -354,36 +354,62 @@ func (listView *listViewData) setItems(value any) []PropertyName {
case []string: case []string:
adapter = NewTextListAdapter(value, nil) adapter = NewTextListAdapter(value, nil)
case []DataValue: case DataValue:
switch value.Type() {
case ObjectDataValue:
if view := CreateViewFromObject(session, value.Object(), nil); view != nil {
adapter = NewViewListAdapter([]View{view})
} else {
return nil
}
case TextDataValue:
adapter = NewTextListAdapter([]string{value.String()}, nil)
case ArrayDataValue:
array := value.Array()
if array == nil {
return nil
}
hasObject := false hasObject := false
for _, val := range value { for val := range array.Elements() {
if val.IsObject() { switch val.Type() {
case ObjectDataValue:
hasObject = true hasObject = true
break
case ArrayDataValue:
return nil
} }
} }
if hasObject { if hasObject {
items := make([]View, len(value)) items := make([]View, 0, array.Size())
for i, val := range value { for val := range array.Elements() {
if val.IsObject() { 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 { if view := CreateViewFromObject(session, val.Object(), nil); view != nil {
items[i] = view items = append(items, view)
} else { } else {
return nil return nil
} }
} else {
items[i] = NewTextView(session, Params{Text: val.Value()}) default:
return nil
} }
} }
adapter = NewViewListAdapter(items) adapter = NewViewListAdapter(items)
} else { } else {
items := make([]string, len(value)) items := make([]string, array.Size())
for i, val := range value { for val := range array.Elements() {
items[i] = val.Value() items = append(items, val.String())
} }
adapter = NewTextListAdapter(items, nil) adapter = NewTextListAdapter(items, nil)
} }
}
case []any: case []any:
items := make([]View, len(value)) items := make([]View, len(value))

View File

@ -659,7 +659,9 @@ func (player *mediaPlayerData) setFunc(tag PropertyName, value any) []PropertyNa
} }
func setMediaPlayerSource(properties Properties, value any) []PropertyName { func setMediaPlayerSource(properties Properties, value any) []PropertyName {
switch value := value.(type) { switch value := value.(type) {
case string: case string:
src := MediaSource{Url: value, MimeType: ""} src := MediaSource{Url: value, MimeType: ""}
properties.setRaw(Source, []MediaSource{src}) properties.setRaw(Source, []MediaSource{src})
@ -670,31 +672,56 @@ func setMediaPlayerSource(properties Properties, value any) []PropertyName {
case []MediaSource: case []MediaSource:
properties.setRaw(Source, value) properties.setRaw(Source, value)
case DataObject: case DataValue:
url, ok := value.PropertyValue("src") switch value.Type() {
case ObjectDataValue:
obj := value.Object()
if obj == nil {
invalidPropertyValue(Source, value)
return nil
}
url, ok := obj.PropertyText("src")
if !ok || url == "" { if !ok || url == "" {
invalidPropertyValue(Source, value) invalidPropertyValue(Source, value)
return nil return nil
} }
mimeType, _ := value.PropertyValue("mime-type") mimeType, _ := obj.PropertyText("mime-type")
src := MediaSource{Url: url, MimeType: mimeType} src := MediaSource{Url: url, MimeType: mimeType}
properties.setRaw(Source, []MediaSource{src}) properties.setRaw(Source, []MediaSource{src})
case []DataValue: case ArrayDataValue:
src := []MediaSource{} array := value.Array()
for _, val := range value { if array == nil {
if val.IsObject() { invalidPropertyValue(Source, value)
return nil
}
src := make([]MediaSource, 0, array.Size())
for val := range array.Elements() {
switch val.Type() {
case ObjectDataValue:
obj := val.Object() obj := val.Object()
if url, ok := obj.PropertyValue("src"); ok && url != "" { if obj == nil {
mimeType, _ := obj.PropertyValue("mime-type") 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}) src = append(src, MediaSource{Url: url, MimeType: mimeType})
} else { } else {
invalidPropertyValue(Source, value) invalidPropertyValue(Source, value)
return nil return nil
} }
} else {
src = append(src, MediaSource{Url: val.Value(), MimeType: ""}) case TextDataValue:
src = append(src, MediaSource{Url: val.String(), MimeType: ""})
default:
invalidPropertyValue(Source, value)
return nil
} }
} }
@ -704,6 +731,15 @@ func setMediaPlayerSource(properties Properties, value any) []PropertyName {
} }
properties.setRaw(Source, src) properties.setRaw(Source, src)
case TextDataValue:
src := MediaSource{Url: value.String(), MimeType: ""}
properties.setRaw(Source, []MediaSource{src})
default:
invalidPropertyValue(Source, value)
return nil
}
default: default:
notCompatibleType(Source, value) notCompatibleType(Source, value)
return nil return nil
@ -1019,7 +1055,7 @@ func (player *mediaPlayerData) handleCommand(self View, command PropertyName, da
if value := player.getRaw(command); value != nil { if value := player.getRaw(command); value != nil {
if listeners, ok := value.([]mediaPlayerErrorListener); ok { if listeners, ok := value.([]mediaPlayerErrorListener); ok {
code, _ := dataIntProperty(data, "code") code, _ := dataIntProperty(data, "code")
message, _ := data.PropertyValue("message") message, _ := data.PropertyText("message")
for _, listener := range listeners { for _, listener := range listeners {
listener.Run(player, code, message) listener.Run(player, code, message)
} }

View File

@ -245,7 +245,7 @@ type MouseEvent struct {
} }
func getTimeStamp(data DataObject) uint64 { 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 { if index := strings.Index(value, "."); index > 0 {
value = value[:index] value = value[:index]
} }

View File

@ -286,7 +286,7 @@ func (picker *numberPickerData) htmlProperties(self View, buffer *strings.Builde
func (picker *numberPickerData) handleCommand(self View, command PropertyName, data DataObject) bool { func (picker *numberPickerData) handleCommand(self View, command PropertyName, data DataObject) bool {
switch command { switch command {
case "textChanged": 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 { if value, err := strconv.ParseFloat(text, 32); err == nil {
oldValue := GetNumberPickerValue(picker) oldValue := GetNumberPickerValue(picker)
picker.setRaw(NumberPickerValue, text) picker.setRaw(NumberPickerValue, text)

View File

@ -134,7 +134,7 @@ func setOutlineProperty(properties Properties, value any) []PropertyName {
case DataObject: case DataObject:
outline := NewOutlineProperty(nil) outline := NewOutlineProperty(nil)
for _, tag := range []PropertyName{Style, Width, ColorTag} { 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) outline.Set(tag, text)
} }
} }

View File

@ -177,7 +177,7 @@ func (event *PointerEvent) init(data DataObject) {
event.TiltX = dataFloatProperty(data, "tiltX") event.TiltX = dataFloatProperty(data, "tiltX")
event.TiltY = dataFloatProperty(data, "tiltY") event.TiltY = dataFloatProperty(data, "tiltY")
event.Twist = dataFloatProperty(data, "twist") event.Twist = dataFloatProperty(data, "twist")
value, _ := data.PropertyValue("pointerType") value, _ := data.PropertyText("pointerType")
event.PointerType = value event.PointerType = value
event.IsPrimary = dataBoolProperty(data, "isPrimary") event.IsPrimary = dataBoolProperty(data, "isPrimary")
} }

View File

@ -939,15 +939,15 @@ func (popup *popupData) setButtons(value any) bool {
popupButtonFromObject := func(obj DataObject) popupButton { popupButtonFromObject := func(obj DataObject) popupButton {
var button 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) text, _ = popup.session.resolveConstants(text)
t, _ := enumStringToInt(text, []string{"normal", "default", "cancel"}, true) t, _ := enumStringToInt(text, []string{"normal", "default", "cancel"}, true)
button.buttonType = PopupButtonType(t) button.buttonType = PopupButtonType(t)
} }
if fn, ok := obj.PropertyValue("click"); ok { if fn, ok := obj.PropertyText("click"); ok {
button.onClick = newPopupListenerBinding(fn, true) button.onClick = newPopupListenerBinding(fn, true)
} else if button.buttonType == CancelButton { } else if button.buttonType == CancelButton {
button.onClick = newPopupListener0(popup.Dismiss) button.onClick = newPopupListener0(popup.Dismiss)
@ -984,22 +984,38 @@ func (popup *popupData) setButtons(value any) bool {
case []popupButton: case []popupButton:
popup.setRaw(Buttons, value) popup.setRaw(Buttons, value)
case DataObject: case DataValue:
popup.setRaw(Buttons, []popupButton{popupButtonFromObject(value)}) switch value.Type() {
case ObjectDataValue:
obj := value.Object()
if obj == nil {
return false
}
popup.setRaw(Buttons, []popupButton{popupButtonFromObject(obj)})
case []DataValue: case ArrayDataValue:
buttons := make([]popupButton, 0, len(value)) array := value.Array()
for _, val := range value { if array == nil {
if val.IsObject() { return false
buttons = append(buttons, popupButtonFromObject(val.Object())) }
buttons := make([]popupButton, 0, array.Size())
for val := range array.Elements() {
if obj := val.Object(); obj != nil {
buttons = append(buttons, popupButtonFromObject(obj))
} else { } else {
notCompatibleType(Buttons, val) notCompatibleType(Buttons, val)
return false
} }
} }
if len(buttons) > 0 { if len(buttons) > 0 {
popup.setRaw(Buttons, buttons) popup.setRaw(Buttons, buttons)
} }
default:
return false
}
case []any: case []any:
buttons := make([]popupButton, 0, len(value)) buttons := make([]popupButton, 0, len(value))
for _, val := range value { for _, val := range value {
@ -1570,8 +1586,8 @@ func CreatePopupFromObject(session Session, object DataObject, binding any) Popu
popup.session = session popup.session = session
popup.hotkeys = map[string]func(Popup){} popup.hotkeys = map[string]func(Popup){}
for key, value := range object.ToParams() { for key, value := range object.Properties() {
popup.Set(key, value) popup.Set(PropertyName(key), value)
} }
if binding != nil { if binding != nil {

View File

@ -149,28 +149,33 @@ func (properties *propertyList) writeToBuffer(buffer *strings.Builder,
*/ */
func parseProperties(properties Properties, object DataObject) { func parseProperties(properties Properties, object DataObject) {
for node := range object.Properties() { for tag, node := range object.Properties() {
switch node.Type() { switch node.Type() {
case TextNode: case TextDataValue:
properties.Set(PropertyName(node.Tag()), node.Text()) properties.Set(PropertyName(tag), node.String())
case ObjectNode: case ObjectDataValue:
properties.Set(PropertyName(node.Tag()), node.Object()) properties.Set(PropertyName(tag), node.Object())
case ArrayNode: case ArrayDataValue:
switch node.ArraySize() { array := node.Array()
if array == nil {
return
}
switch array.Size() {
case 0: case 0:
// do nothing // do nothing
case 1: case 1:
if v := node.ArrayElement(0); v.IsObject() { if v := array.Element(0); v.Type() == TextDataValue {
properties.Set(PropertyName(node.Tag()), v.Object()) properties.Set(PropertyName(tag), v.String())
} else { } else {
properties.Set(PropertyName(node.Tag()), v.Value()) properties.Set(PropertyName(tag), v)
} }
default: default:
properties.Set(PropertyName(node.Tag()), node.Array()) properties.Set(PropertyName(tag), array)
} }
} }
} }

View File

@ -978,7 +978,7 @@ func setRadiusProperty(properties Properties, value any) []PropertyName {
radius := NewRadiusProperty(nil) radius := NewRadiusProperty(nil)
for _, tag := range []PropertyName{X, Y, TopLeft, TopRight, BottomLeft, BottomRight, TopLeftX, TopLeftY, for _, tag := range []PropertyName{X, Y, TopLeft, TopRight, BottomLeft, BottomRight, TopLeftX, TopLeftY,
TopRightX, TopRightY, BottomLeftX, BottomLeftY, BottomRightX, BottomRightY} { 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) radius.Set(tag, value)
} }
} }

View File

@ -701,7 +701,7 @@ func (session *sessionData) handleAnswer(command string, data DataObject) bool {
func (session *sessionData) handleRootSize(data DataObject) { func (session *sessionData) handleRootSize(data DataObject) {
getValue := func(tag string) int { 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) float, err := strconv.ParseFloat(value, 64)
if err == nil { if err == nil {
return int(float) return int(float)
@ -722,12 +722,17 @@ func (session *sessionData) handleRootSize(data DataObject) {
} }
func (session *sessionData) handleResize(data DataObject) { func (session *sessionData) handleResize(data DataObject) {
if node := data.PropertyByTag("views"); node != nil && node.Type() == ArrayNode { array := data.PropertyArray("views")
for el := range node.ArrayElements() { if array == nil {
if el.IsObject() { ErrorLog(`Resize event error: invalid "views" property`)
obj := el.Object() return
}
for el := range array.Elements() {
if obj := el.Object(); obj != nil {
getFloat := func(tag string) float64 { getFloat := func(tag string) float64 {
if value, ok := obj.PropertyValue(tag); ok { if value, ok := obj.PropertyText(tag); ok {
f, err := strconv.ParseFloat(value, 64) f, err := strconv.ParseFloat(value, 64)
if err == nil { if err == nil {
return f return f
@ -738,7 +743,8 @@ func (session *sessionData) handleResize(data DataObject) {
} }
return 0 return 0
} }
if viewID, ok := obj.PropertyValue("id"); ok {
if viewID, ok := obj.PropertyText("id"); ok {
if n := strings.IndexRune(viewID, '-'); n > 0 { if n := strings.IndexRune(viewID, '-'); n > 0 {
if view := session.viewByHTMLID(viewID[:n]); view != nil { if view := session.viewByHTMLID(viewID[:n]); view != nil {
view.onItemResize(view, viewID[n+1:], getFloat("x"), getFloat("y"), getFloat("width"), getFloat("height")) view.onItemResize(view, viewID[n+1:], getFloat("x"), getFloat("y"), getFloat("width"), getFloat("height"))
@ -758,39 +764,36 @@ func (session *sessionData) handleResize(data DataObject) {
ErrorLog(`Resize event error: views element is not object`) ErrorLog(`Resize event error: views element is not object`)
} }
} }
} else {
ErrorLog(`Resize event error: invalid "views" property`)
}
} }
func (session *sessionData) handleSessionInfo(params DataObject) { 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") 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 session.userAgent = value
} }
if value, ok := params.PropertyValue("direction"); ok { if value, ok := params.PropertyText("direction"); ok {
if value == "rtl" { if value == "rtl" {
session.textDirection = RightToLeftDirection session.textDirection = RightToLeftDirection
} }
} }
if value, ok := params.PropertyValue("language"); ok { if value, ok := params.PropertyText("language"); ok {
session.language = value session.language = value
} }
if value, ok := params.PropertyValue("languages"); ok { if value, ok := params.PropertyText("languages"); ok {
session.languages = strings.Split(value, ",") 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") 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 { if f, err := strconv.ParseFloat(value, 64); err != nil {
ErrorLog(err.Error()) ErrorLog(err.Error())
} else { } 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") session.lightDark = (value == "1" || value == "true")
} }
} }
@ -818,7 +821,7 @@ func (session *sessionData) handleEvent(command string, data DataObject) {
session.onResume() session.onResume()
case "timer": case "timer":
if text, ok := data.PropertyValue("timerID"); ok { if text, ok := data.PropertyText("timerID"); ok {
timerID, err := strconv.Atoi(text) timerID, err := strconv.Atoi(text)
if err == nil { if err == nil {
if fn, ok := session.timers[timerID]; ok { if fn, ok := session.timers[timerID]; ok {
@ -843,7 +846,7 @@ func (session *sessionData) handleEvent(command string, data DataObject) {
session.handleSessionInfo(data) session.handleSessionInfo(data)
default: default:
if viewID, ok := data.PropertyValue("id"); ok { if viewID, ok := data.PropertyText("id"); ok {
if viewID != "body" { if viewID != "body" {
if view := session.viewByHTMLID(viewID); view != nil { if view := session.viewByHTMLID(viewID); view != nil {
view.handleCommand(view, PropertyName(command), data) view.handleCommand(view, PropertyName(command), data)

View File

@ -289,16 +289,39 @@ func setShadowProperty(properties Properties, tag PropertyName, value any) bool
} }
case DataValue: 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 return false
} }
properties.setRaw(tag, []ShadowProperty{parseShadowProperty(value.Object())})
case []DataValue: case []DataValue:
shadows := []ShadowProperty{} shadows := make([]ShadowProperty, 0, len(value))
for _, data := range value { for _, data := range value {
if data.IsObject() { if obj := data.Object(); obj != nil {
shadows = append(shadows, parseShadowProperty(data.Object())) shadows = append(shadows, parseShadowProperty(obj))
} }
} }
if len(shadows) == 0 { if len(shadows) == 0 {

View File

@ -62,10 +62,8 @@ func loadStringResources(text string) {
table = map[string]string{} table = map[string]string{}
} }
for prop := range obj.Properties() { for tag, text := range obj.TextProperties() {
if prop.Type() == TextNode { table[tag] = text
table[prop.Tag()] = prop.Text()
}
} }
stringResources[lang] = table stringResources[lang] = table
@ -73,9 +71,9 @@ func loadStringResources(text string) {
tag := data.Tag() tag := data.Tag()
if tag == "strings" { if tag == "strings" {
for prop := range data.Properties() { for tag, prop := range data.Properties() {
if prop.Type() == ObjectNode { if prop.Type() == ObjectDataValue {
parseStrings(prop.Object(), prop.Tag()) parseStrings(prop.Object(), tag)
} }
} }

View File

@ -643,11 +643,33 @@ func (table *tableViewData) setFunc(tag PropertyName, value any) []PropertyName
setLineStyle := func() []PropertyName { setLineStyle := func() []PropertyName {
params := []Params{} params := []Params{}
switch value := value.(type) { switch value := value.(type) {
case Params:
params = []Params{value}
case []Params: case []Params:
params = value params = value
case DataNode: case DataValue:
params = value.ArrayAsParams() 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: default:
notCompatibleType(tag, params) notCompatibleType(tag, params)
@ -751,20 +773,18 @@ func (table *tableViewData) setFunc(tag PropertyName, value any) []PropertyName
case DataObject: case DataObject:
params := Params{} params := Params{}
for prop := range value.Properties() { for tag, prop := range value.TextProperties() {
if prop.Type() == TextNode { params[PropertyName(tag)] = prop
params[PropertyName(prop.Tag())] = prop.Text()
}
} }
return table.setFunc(tag, params) return table.setFunc(tag, params)
case DataNode: case DataValue:
switch value.Type() { switch value.Type() {
case ObjectNode: case ObjectDataValue:
return table.setFunc(tag, value.Object()) return table.setFunc(tag, value.Object())
case TextNode: case TextDataValue:
table.setRaw(tag, value.Text()) table.setRaw(tag, value.String())
default: default:
notCompatibleType(tag, value) notCompatibleType(tag, value)

View File

@ -703,7 +703,7 @@ func (tabsLayout *tabsLayoutData) htmlSubviews(self View, buffer *strings.Builde
func (tabsLayout *tabsLayoutData) handleCommand(self View, command PropertyName, data DataObject) bool { func (tabsLayout *tabsLayoutData) handleCommand(self View, command PropertyName, data DataObject) bool {
switch command { switch command {
case "tabClick": case "tabClick":
if numberText, ok := data.PropertyValue("number"); ok { if numberText, ok := data.PropertyText("number"); ok {
if number, err := strconv.Atoi(numberText); err == nil { if number, err := strconv.Atoi(numberText); err == nil {
current := GetCurrent(tabsLayout) current := GetCurrent(tabsLayout)
if current != number { if current != number {
@ -716,7 +716,7 @@ func (tabsLayout *tabsLayoutData) handleCommand(self View, command PropertyName,
return true return true
case "tabCloseClick": case "tabCloseClick":
if numberText, ok := data.PropertyValue("number"); ok { if numberText, ok := data.PropertyText("number"); ok {
if number, err := strconv.Atoi(numberText); err == nil { if number, err := strconv.Atoi(numberText); err == nil {
for _, listener := range getOneArgEventListeners[TabsLayout, int](tabsLayout, nil, TabCloseEvent) { for _, listener := range getOneArgEventListeners[TabsLayout, int](tabsLayout, nil, TabCloseEvent) {
listener.Run(tabsLayout, number) listener.Run(tabsLayout, number)

View File

@ -709,119 +709,92 @@ func (theme *theme) addText(themeText string) bool {
if err != nil { if err != nil {
ErrorLog(err.Error()) ErrorLog(err.Error())
return false return false
} else if !data.IsObject() || data.Tag() != "theme" { } else if data.Tag() != "theme" {
return false return false
} }
objToStyle := func(obj DataObject) ViewStyle { objToStyle := func(obj DataObject) ViewStyle {
params := Params{} params := Params{}
for node := range obj.Properties() { for tag, node := range obj.Properties() {
switch node.Type() { switch node.Type() {
case ArrayNode: case ArrayDataValue:
params[PropertyName(node.Tag())] = node.Array() params[PropertyName(tag)] = node.Array()
case ObjectNode: case ObjectDataValue:
params[PropertyName(node.Tag())] = node.Object() params[PropertyName(tag)] = node.Object()
default: default:
params[PropertyName(node.Tag())] = node.Text() params[PropertyName(tag)] = node.String()
} }
} }
return NewViewStyle(params) return NewViewStyle(params)
} }
for d := range data.Properties() { for tag, d := range data.Properties() {
switch tag := d.Tag(); tag { switch tag {
case "name": case "name":
if d.Type() == TextNode { if d.Type() == TextDataValue {
if text := d.Text(); text != "" { if text := d.String(); text != "" {
theme.name = text theme.name = text
} }
} }
case "constants": case "constants":
if d.Type() == ObjectNode {
if obj := d.Object(); obj != nil { if obj := d.Object(); obj != nil {
for prop := range obj.Properties() { for propTag, text := range obj.TextProperties() {
if prop.Type() == TextNode { theme.constants[propTag] = text
theme.constants[prop.Tag()] = prop.Text()
}
}
} }
} }
case "constants:touch": case "constants:touch":
if d.Type() == ObjectNode {
if obj := d.Object(); obj != nil { if obj := d.Object(); obj != nil {
for prop := range obj.Properties() { for propTag, text := range obj.TextProperties() {
if prop.Type() == TextNode { theme.touchConstants[propTag] = text
theme.touchConstants[prop.Tag()] = prop.Text()
}
}
} }
} }
case "colors": case "colors":
if d.Type() == ObjectNode {
if obj := d.Object(); obj != nil { if obj := d.Object(); obj != nil {
for prop := range obj.Properties() { for propTag, text := range obj.TextProperties() {
if prop.Type() == TextNode { theme.colors[propTag] = text
theme.colors[prop.Tag()] = prop.Text()
}
}
} }
} }
case "colors:dark": case "colors:dark":
if d.Type() == ObjectNode {
if obj := d.Object(); obj != nil { if obj := d.Object(); obj != nil {
for prop := range obj.Properties() { for propTag, text := range obj.TextProperties() {
if prop.Type() == TextNode { theme.darkColors[propTag] = text
theme.darkColors[prop.Tag()] = prop.Text()
}
}
} }
} }
case "images": case "images":
if d.Type() == ObjectNode {
if obj := d.Object(); obj != nil { if obj := d.Object(); obj != nil {
for prop := range obj.Properties() { for propTag, text := range obj.TextProperties() {
if prop.Type() == TextNode { theme.images[propTag] = text
theme.images[prop.Tag()] = prop.Text()
}
}
} }
} }
case "images:dark": case "images:dark":
if d.Type() == ObjectNode {
if obj := d.Object(); obj != nil { if obj := d.Object(); obj != nil {
for prop := range obj.Properties() { for propTag, text := range obj.TextProperties() {
if prop.Type() == TextNode { theme.darkImages[propTag] = text
theme.darkImages[prop.Tag()] = prop.Text()
}
}
} }
} }
case "styles": case "styles":
if d.Type() == ArrayNode { if array := d.Array(); array != nil {
arraySize := d.ArraySize() for element := range array.Elements() {
for k := range arraySize {
if element := d.ArrayElement(k); element != nil && element.IsObject() {
if obj := element.Object(); obj != nil { if obj := element.Object(); obj != nil {
theme.styles[obj.Tag()] = objToStyle(obj) theme.styles[obj.Tag()] = objToStyle(obj)
} }
} }
} }
}
default: default:
if d.Type() == ArrayNode && strings.HasPrefix(tag, "styles:") { if d.Type() == ArrayDataValue && strings.HasPrefix(tag, "styles:") {
if rule, ok := parseMediaRule(tag); ok { if rule, ok := parseMediaRule(tag); ok {
arraySize := d.ArraySize() if array := d.Array(); array != nil {
for k := range arraySize { for element := range array.Elements() {
if element := d.ArrayElement(k); element != nil && element.IsObject() {
if obj := element.Object(); obj != nil { if obj := element.Object(); obj != nil {
rule.styles[obj.Tag()] = objToStyle(obj) rule.styles[obj.Tag()] = objToStyle(obj)
} }

View File

@ -318,7 +318,7 @@ func (picker *timePickerData) htmlProperties(self View, buffer *strings.Builder)
func (picker *timePickerData) handleCommand(self View, command PropertyName, data DataObject) bool { func (picker *timePickerData) handleCommand(self View, command PropertyName, data DataObject) bool {
switch command { switch command {
case "textChanged": case "textChanged":
if text, ok := data.PropertyValue("text"); ok { if text, ok := data.PropertyText("text"); ok {
if value, ok := stringToTime(text); ok { if value, ok := stringToTime(text); ok {
oldValue := GetTimePickerValue(picker) oldValue := GetTimePickerValue(picker)
picker.setRaw(TimePickerValue, value) picker.setRaw(TimePickerValue, value)

View File

@ -154,29 +154,28 @@ func (event *TouchEvent) init(data DataObject) {
event.Touches = []Touch{} event.Touches = []Touch{}
event.TimeStamp = getTimeStamp(data) event.TimeStamp = getTimeStamp(data)
if node := data.PropertyByTag("touches"); node != nil && node.Type() == ArrayNode { if array := data.PropertyArray("touches"); array != nil {
for i := range node.ArraySize() { for element := range array.Elements() {
if element := node.ArrayElement(i); element != nil && element.IsObject() {
if obj := element.Object(); obj != nil { if obj := element.Object(); obj != nil {
var touch Touch touch := Touch{
if value, ok := obj.PropertyValue("identifier"); ok { 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) 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) event.Touches = append(event.Touches, touch)
} }
} }
} }
}
event.CtrlKey = dataBoolProperty(data, "ctrlKey") event.CtrlKey = dataBoolProperty(data, "ctrlKey")
event.ShiftKey = dataBoolProperty(data, "shiftKey") event.ShiftKey = dataBoolProperty(data, "shiftKey")
event.AltKey = dataBoolProperty(data, "altKey") event.AltKey = dataBoolProperty(data, "altKey")

View File

@ -352,9 +352,9 @@ func valueToTransformProperty(value any) TransformProperty {
parseObject := func(obj DataObject) TransformProperty { parseObject := func(obj DataObject) TransformProperty {
transform := NewTransformProperty(nil) transform := NewTransformProperty(nil)
ok := true ok := true
for prop := range obj.Properties() { for tag, prop := range obj.Properties() {
if prop.Type() == TextNode { if prop.Type() == TextDataValue {
if !transform.Set(PropertyName(prop.Tag()), prop.Text()) { if !transform.Set(PropertyName(tag), prop.String()) {
ok = false ok = false
} }
} else { } else {
@ -372,10 +372,7 @@ func valueToTransformProperty(value any) TransformProperty {
case TransformProperty: case TransformProperty:
return value return value
case DataObject: case DataValue:
return parseObject(value)
case DataNode:
if obj := value.Object(); obj != nil { if obj := value.Object(); obj != nil {
return parseObject(obj) return parseObject(obj)
} }

View File

@ -54,7 +54,7 @@ func GetLocalIP() string {
} }
func dataIntProperty(data DataObject, tag string) (int, bool) { 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 { if n, err := strconv.Atoi(value); err == nil {
return n, true return n, true
} }
@ -63,14 +63,14 @@ func dataIntProperty(data DataObject, tag string) (int, bool) {
} }
func dataBoolProperty(data DataObject, tag string) 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 true
} }
return false return false
} }
func dataFloatProperty(data DataObject, tag string) float64 { 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 { if n, err := strconv.ParseFloat(value, 64); err == nil {
return n return n
} }
@ -136,3 +136,54 @@ func writeStrings(buffer *strings.Builder, strs ...string) {
buffer.WriteString(str) 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)
}
}

58
view.go
View File

@ -424,12 +424,11 @@ func (view *viewData) setFunc(tag PropertyName, value any) []PropertyName {
return []PropertyName{Binding} return []PropertyName{Binding}
case changeListeners: case changeListeners:
switch value := value.(type) { if value, ok := value.(DataObject); ok {
case DataObject: for tag, node := range value.Properties() {
for node := range value.Properties() { if node.Type() == TextDataValue {
if node.Type() == TextNode { if text := node.String(); text != "" {
if text := node.Text(); text != "" { view.changeListener[PropertyName(tag)] = newOneArgListenerBinding[View, PropertyName](text)
view.changeListener[PropertyName(node.Tag())] = newOneArgListenerBinding[View, PropertyName](text)
} }
} }
} }
@ -437,11 +436,6 @@ func (view *viewData) setFunc(tag PropertyName, value any) []PropertyName {
view.setRaw(changeListeners, view.changeListener) view.setRaw(changeListeners, view.changeListener)
} }
return []PropertyName{tag} return []PropertyName{tag}
case DataNode:
if value.Type() == ObjectNode {
return view.setFunc(tag, value.Object())
}
} }
return []PropertyName{} return []PropertyName{}
@ -548,11 +542,17 @@ func (view *viewData) setFunc(tag PropertyName, value any) []PropertyName {
} }
} }
case DataObject: 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{} data := map[string]string{}
for node := range value.Properties() { for tag, node := range obj.Properties() {
if node.Type() == TextNode { if node.Type() == TextDataValue {
data[node.Tag()] = node.Text() data[tag] = node.String()
} else { } else {
invalidPropertyValue(DragData, value) invalidPropertyValue(DragData, value)
return nil return nil
@ -563,23 +563,11 @@ func (view *viewData) setFunc(tag PropertyName, value any) []PropertyName {
} else { } else {
view.setRaw(DragData, data) view.setRaw(DragData, data)
} }
return []PropertyName{DragData}
case DataNode: }
switch value.Type() {
case TextNode:
return view.setFunc(DragData, value.Text())
case ObjectNode:
return view.setFunc(DragData, value.Object())
} }
invalidPropertyValue(DragData, value) invalidPropertyValue(DragData, value)
return nil return []PropertyName{}
case DataValue:
if value.IsObject() {
return view.setFunc(DragData, value.Object())
}
return view.setFunc(DragData, value.Value())
default: default:
notCompatibleType(DragData, value) 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")) view.onScroll(view, dataFloatProperty(data, "x"), dataFloatProperty(data, "y"), dataFloatProperty(data, "width"), dataFloatProperty(data, "height"))
case "widthChanged": case "widthChanged":
if value, ok := data.PropertyValue("width"); ok { if value, ok := data.PropertyText("width"); ok {
if width, ok := StringToSizeUnit(value); ok { if width, ok := StringToSizeUnit(value); ok {
self.setRaw(Width, width) self.setRaw(Width, width)
} }
} }
case "heightChanged": case "heightChanged":
if value, ok := data.PropertyValue("height"); ok { if value, ok := data.PropertyText("height"); ok {
if height, ok := StringToSizeUnit(value); ok { if height, ok := StringToSizeUnit(value); ok {
self.setRaw(Height, height) self.setRaw(Height, height)
} }
@ -1221,7 +1209,7 @@ func (view *viewData) handleCommand(self View, command PropertyName, data DataOb
/* /*
case "resize": case "resize":
floatProperty := func(tag string) float64 { 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 { if result, err := strconv.ParseFloat(value, 64); err == nil {
return result return result
} }
@ -1240,7 +1228,7 @@ func (view *viewData) handleCommand(self View, command PropertyName, data DataOb
if listener := view.fileLoader[key]; listener != nil { if listener := view.fileLoader[key]; listener != nil {
delete(view.fileLoader, key) 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 { if index := strings.LastIndex(base64Data, ","); index >= 0 {
base64Data = base64Data[index+1:] base64Data = base64Data[index+1:]
} }
@ -1258,7 +1246,7 @@ func (view *viewData) handleCommand(self View, command PropertyName, data DataOb
file := dataToFileInfo(data) file := dataToFileInfo(data)
key := file.key() 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) ErrorLogF(`Load "%s" file error: %s`, file.Name, error)
} }

View File

@ -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. // 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 { func CreateViewFromObject(session Session, object DataObject, binding any) View {
if session == nil { 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 return nil
} }
tag := object.Tag() tag := object.Tag()
creator, ok := viewCreators()[strings.ToLower(tag)] creator, ok := viewCreators()[strings.ToLower(tag)]
if !ok { if !ok {
ErrorLog(`Unknown view type "` + tag + `"`) ErrorLog(`CreateViewFromObject: unknown view type "` + tag + `"`)
return nil return nil
} }

View File

@ -7,7 +7,6 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"unicode"
) )
// ViewStyle interface of the style of view // ViewStyle interface of the style of view
@ -566,68 +565,8 @@ func viewStyleGet(style Properties, tag PropertyName) any {
return style.getRaw(tag) 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 { 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) { switch value := value.(type) {
case string: case string:
if tag == Text || tag == Title || tag == Summary || isQuotesNeeded(value) { if tag == Text || tag == Title || tag == Summary || isQuotesNeeded(value) {
@ -652,7 +591,7 @@ func propertyValueToString(tag PropertyName, value any, indent string) string {
lead := "[" lead := "["
for _, text := range value { for _, text := range value {
buffer.WriteString(lead) buffer.WriteString(lead)
writeString(buffer, text) writeRuiText(buffer, text, false)
lead = ", " lead = ", "
} }
} else { } else {
@ -660,7 +599,7 @@ func propertyValueToString(tag PropertyName, value any, indent string) string {
for _, text := range value { for _, text := range value {
buffer.WriteString(indent) buffer.WriteString(indent)
buffer.WriteRune('\t') buffer.WriteRune('\t')
writeString(buffer, text) writeRuiText(buffer, text, false)
buffer.WriteString(",\n") buffer.WriteString(",\n")
} }
buffer.WriteString(indent) buffer.WriteString(indent)
@ -683,9 +622,9 @@ func propertyValueToString(tag PropertyName, value any, indent string) string {
for _, key := range keys { for _, key := range keys {
buffer.WriteString(indent) buffer.WriteString(indent)
buffer.WriteRune('\t') buffer.WriteRune('\t')
writeObjectNameString(buffer, key) writeRuiText(buffer, key, true)
buffer.WriteString(" = ") buffer.WriteString(" = ")
writeString(buffer, value[key]) writeRuiText(buffer, value[key], false)
buffer.WriteString(",\n") buffer.WriteString(",\n")
} }
buffer.WriteString(indent) buffer.WriteString(indent)
@ -1006,9 +945,9 @@ func propertyValueToString(tag PropertyName, value any, indent string) string {
for key, listener := range value { for key, listener := range value {
if text, ok := listener.(string); ok && text != "" { if text, ok := listener.(string); ok && text != "" {
buffer.WriteString(indent2) buffer.WriteString(indent2)
writeString(buffer, string(key)) writeRuiText(buffer, string(key), true)
buffer.WriteString(" = ") buffer.WriteString(" = ")
writeString(buffer, text) writeRuiText(buffer, text, false)
buffer.WriteString(",\n") buffer.WriteString(",\n")
} }
} }
@ -1029,7 +968,7 @@ func propertyValueToString(tag PropertyName, value any, indent string) string {
buffer.WriteString("[\n") buffer.WriteString("[\n")
for _, button := range value { for _, button := range value {
buffer.WriteString(lead) buffer.WriteString(lead)
writeString(buffer, button.title) writeRuiText(buffer, button.title, false)
switch button.buttonType { switch button.buttonType {
case CancelButton: case CancelButton:
buffer.WriteString(`, type = cancel`) buffer.WriteString(`, type = cancel`)
@ -1040,7 +979,7 @@ func propertyValueToString(tag PropertyName, value any, indent string) string {
if button.onClick != nil { if button.onClick != nil {
if text, ok := button.onClick.rawListener().(string); ok { if text, ok := button.onClick.rawListener().(string); ok {
buffer.WriteString(`, click = `) buffer.WriteString(`, click = `)
writeString(buffer, text) writeRuiText(buffer, text, true)
} }
} }
buffer.WriteString(" },\n") buffer.WriteString(" },\n")
@ -1062,7 +1001,7 @@ func propertyValueToString(tag PropertyName, value any, indent string) string {
buffer.WriteString("[\n") buffer.WriteString("[\n")
for _, button := range value { for _, button := range value {
buffer.WriteString(lead) buffer.WriteString(lead)
writeString(buffer, button.Title) writeRuiText(buffer, button.Title, false)
switch button.Type { switch button.Type {
case CancelButton: case CancelButton:
buffer.WriteString(`, type = cancel`) buffer.WriteString(`, type = cancel`)
@ -1073,7 +1012,7 @@ func propertyValueToString(tag PropertyName, value any, indent string) string {
if button.Binding != "" { if button.Binding != "" {
buffer.WriteString(`, click = `) buffer.WriteString(`, click = `)
writeString(buffer, button.Binding) writeRuiText(buffer, button.Binding, true)
} }
buffer.WriteString(" },\n") buffer.WriteString(" },\n")
} }

View File

@ -54,23 +54,24 @@ func setTransitionProperty(properties Properties, value any) bool {
} }
return false return false
case DataNode: case DataValue:
switch value.Type() { switch value.Type() {
case ObjectNode: case ObjectDataValue:
if setObject(value.Object()) { if setObject(value.Object()) {
properties.setRaw(Transition, transitions) properties.setRaw(Transition, transitions)
return true return true
} }
return false return false
case ArrayNode: case ArrayDataValue:
for i := range value.ArraySize() { if array := value.Array(); array != nil {
if obj := value.ArrayElement(i).Object(); obj != nil { for element := range array.Elements() {
if obj := element.Object(); obj != nil {
if !setObject(obj) { if !setObject(obj) {
return false return false
} }
} else { } else {
notCompatibleType(Transition, value.ArrayElement(i)) notCompatibleType(Transition, element)
return false return false
} }
} }
@ -81,6 +82,7 @@ func setTransitionProperty(properties Properties, value any) bool {
return true return true
} }
} }
}
notCompatibleType(Transition, value) notCompatibleType(Transition, value)
return false return false
@ -268,12 +270,17 @@ func viewStyleSet(style Properties, tag PropertyName, value any) []PropertyName
case Params: case Params:
style.setRaw(tag, value) style.setRaw(tag, value)
case DataObject: case DataValue:
if params := value.ToParams(); len(params) > 0 { if obj := value.Object(); obj != nil {
if params := obj.ToParams(); len(params) > 0 {
style.setRaw(tag, params) style.setRaw(tag, params)
} else { } else {
style.setRaw(tag, nil) style.setRaw(tag, nil)
} }
} else {
notCompatibleType(tag, value)
return nil
}
default: default:
notCompatibleType(tag, value) notCompatibleType(tag, value)
@ -289,23 +296,16 @@ func viewStyleSet(style Properties, tag PropertyName, value any) []PropertyName
case Params: case Params:
style.setRaw(tag, value) style.setRaw(tag, value)
case DataObject: case DataValue:
if params := value.ToParams(); len(params) > 0 {
style.setRaw(tag, params)
} else {
style.setRaw(tag, nil)
}
case DataNode:
switch value.Type() { switch value.Type() {
case TextNode: case TextDataValue:
if text := value.Text(); text != "" { if text := value.String(); text != "" {
style.setRaw(tag, text) style.setRaw(tag, text)
} else { } else {
style.setRaw(tag, nil) style.setRaw(tag, nil)
} }
case ObjectNode: case ObjectDataValue:
if obj := value.Object(); obj != nil { if obj := value.Object(); obj != nil {
if params := obj.ToParams(); len(params) > 0 { if params := obj.ToParams(); len(params) > 0 {
style.setRaw(tag, params) style.setRaw(tag, params)

View File

@ -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 data, err := ParseDataText(text); err == nil {
if view := CreateViewFromObject(session, data, nil); view != nil { if view := CreateViewFromObject(session, data, nil); view != nil {
return view return view
@ -299,12 +299,12 @@ func (container *viewsContainerData) setContent(value any) bool {
container.views = value container.views = value
case string: case string:
container.views = []View{viewFromTextValue(value, session)} container.views = []View{viewFromTextDataValue(value, session)}
case []string: case []string:
views := []View{} views := []View{}
for _, text := range value { for _, text := range value {
views = append(views, viewFromTextValue(text, session)) views = append(views, viewFromTextDataValue(text, session))
} }
container.views = views container.views = views
@ -316,7 +316,7 @@ func (container *viewsContainerData) setContent(value any) bool {
views = append(views, v) views = append(views, v)
case string: case string:
views = append(views, viewFromTextValue(v, session)) views = append(views, viewFromTextDataValue(v, session))
default: default:
notCompatibleType(Content, value) notCompatibleType(Content, value)
@ -332,19 +332,36 @@ func (container *viewsContainerData) setContent(value any) bool {
return false return false
} }
case []DataValue: case DataArray:
views := []View{} views := []View{}
for _, data := range value { for data := range value.Elements() {
if data.IsObject() { switch data.Type() {
case ObjectDataValue:
if view := CreateViewFromObject(session, data.Object(), nil); view != nil { if view := CreateViewFromObject(session, data.Object(), nil); view != nil {
views = append(views, view) 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 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: default:
notCompatibleType(Content, value) notCompatibleType(Content, value)
return false return false

View File

@ -270,7 +270,7 @@ func (bridge *wasmBridge) htmlPropertyValue(htmlID, name string) string {
} }
func (bridge *wasmBridge) answerReceived(answer DataObject) { 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 id, err := strconv.Atoi(text); err == nil {
if chanel, ok := bridge.answer[id]; ok { if chanel, ok := bridge.answer[id]; ok {
chanel <- answer chanel <- answer

View File

@ -423,7 +423,7 @@ func (bridge *webBridge) canvasTextMetrics(htmlID, font, text string) TextMetric
func (bridge *webBridge) htmlPropertyValue(htmlID, name string) string { func (bridge *webBridge) htmlPropertyValue(htmlID, name string) string {
if data, ok := bridge.remoteValue("getPropertyValue", htmlID, name); ok { 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 return value
} }
} }
@ -431,7 +431,7 @@ func (bridge *webBridge) htmlPropertyValue(htmlID, name string) string {
} }
func (bridge *webBridge) answerReceived(answer DataObject) { 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 id, err := strconv.Atoi(text); err == nil {
if chanel, ok := bridge.answer[id]; ok { if chanel, ok := bridge.answer[id]; ok {
chanel <- answer chanel <- answer