Changed ParseDataText function return values

This commit is contained in:
Alexei Anoshenko 2025-06-25 17:42:32 +03:00
parent 3090a0e94f
commit c3c8b9e858
13 changed files with 609 additions and 545 deletions

View File

@ -2,6 +2,7 @@
* Added support of binding
* Added "binding" argument to CreateViewFromResources, CreateViewFromText, and CreateViewFromObject functions
* Changed ParseDataText function return values
# v0.19.0

View File

@ -164,7 +164,12 @@ func (app *application) postHandler(w http.ResponseWriter, req *http.Request) {
DebugLog(message)
}
if obj := ParseDataText(message); obj != nil {
obj, err := ParseDataText(message)
if err != nil {
ErrorLog(err.Error())
return
}
var session Session = nil
var response chan string = nil
@ -223,7 +228,6 @@ func (app *application) postHandler(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, <-response)
}
}
}
}
func (app *application) socketReader(bridge *wsBridge) {
@ -241,9 +245,13 @@ func (app *application) socketReader(bridge *wsBridge) {
DebugLog("🖥️ -> " + message)
}
if obj := ParseDataText(message); obj != nil {
command := obj.Tag()
switch command {
obj, err := ParseDataText(message)
if err != nil {
ErrorLog(err.Error())
return
}
switch command := obj.Tag(); command {
case "startSession":
answer := ""
if session, answer = app.startSession(obj, events, bridge, nil); session != nil {
@ -295,7 +303,6 @@ func (app *application) socketReader(bridge *wsBridge) {
}
}
}
}
}
func sessionEventHandler(session Session, events chan DataObject, bridge bridge) {

View File

@ -78,6 +78,16 @@ func createBackground(obj DataObject) BackgroundElement {
return result
}
func parseBackgroundText(text string) BackgroundElement {
obj, err := ParseDataText(text)
if err != nil {
ErrorLog(err.Error())
return nil
}
return createBackground(obj)
}
func parseBackgroundValue(value any) []BackgroundElement {
switch value := value.(type) {
@ -96,15 +106,11 @@ func parseBackgroundValue(value any) []BackgroundElement {
} else {
return nil
}
} else if obj := ParseDataText(el.Value()); obj != nil {
if element := createBackground(obj); element != nil {
} else if element := parseBackgroundText(el.Value()); element != nil {
background = append(background, element)
} else {
return nil
}
} else {
return nil
}
}
return background
@ -125,44 +131,34 @@ func parseBackgroundValue(value any) []BackgroundElement {
return background
case string:
if obj := ParseDataText(value); obj != nil {
if element := createBackground(obj); element != nil {
if element := parseBackgroundText(value); element != nil {
return []BackgroundElement{element}
}
}
case []string:
elements := make([]BackgroundElement, 0, len(value))
for _, element := range value {
if obj := ParseDataText(element); obj != nil {
if element := createBackground(obj); element != nil {
for _, text := range value {
if element := parseBackgroundText(text); element != nil {
elements = append(elements, element)
} else {
return nil
}
} else {
return nil
}
}
return elements
case []any:
elements := make([]BackgroundElement, 0, len(value))
for _, element := range value {
switch element := element.(type) {
for _, val := range value {
switch val := val.(type) {
case BackgroundElement:
elements = append(elements, element)
elements = append(elements, val)
case string:
if obj := ParseDataText(element); obj != nil {
if element := createBackground(obj); element != nil {
if element := parseBackgroundText(val); element != nil {
elements = append(elements, element)
} else {
return nil
}
} else {
return nil
}
default:
return nil

View File

@ -610,7 +610,10 @@ func borderSet(properties Properties, tag PropertyName, value any) []PropertyNam
case Left, Right, Top, Bottom:
switch value := value.(type) {
case string:
if obj := ParseDataText(value); obj != nil {
obj, err := ParseDataText(value)
if err != nil {
ErrorLog(err.Error())
} else {
return setSingleBorderObject(tag, obj)
}

385
data.go
View File

@ -1,6 +1,8 @@
package rui
import (
"errors"
"fmt"
"slices"
"strings"
"unicode"
@ -48,6 +50,9 @@ type DataObject interface {
// 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
@ -162,6 +167,28 @@ func (object *dataObject) PropertyByTag(tag string) DataNode {
return nil
}
func (object *dataObject) RemovePropertyByTag(tag string) DataNode {
if object.property != nil {
for i, node := range object.property {
if node.Tag() == tag {
switch i {
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[:i], object.property[i+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
@ -318,54 +345,49 @@ func (node *dataNode) ArrayAsParams() []Params {
return result
}
// ParseDataText - parse text and return DataNode
func ParseDataText(text string) DataObject {
type dataParser struct {
data []rune
size int
pos int
line int
lineStart int
}
if strings.ContainsAny(text, "\r") {
text = strings.ReplaceAll(text, "\r\n", "\n")
text = strings.ReplaceAll(text, "\r", "\n")
}
data := append([]rune(text), rune(0))
pos := 0
size := len(data) - 1
line := 1
lineStart := 0
skipSpaces := func(skipNewLine bool) {
for pos < size {
switch data[pos] {
func (parser *dataParser) skipSpaces(skipNewLine bool) {
for parser.pos < parser.size {
switch parser.data[parser.pos] {
case '\n':
if !skipNewLine {
return
}
line++
lineStart = pos + 1
parser.line++
parser.lineStart = parser.pos + 1
case '/':
if pos+1 < size {
switch data[pos+1] {
if parser.pos+1 < parser.size {
switch parser.data[parser.pos+1] {
case '/':
pos += 2
for pos < size && data[pos] != '\n' {
pos++
parser.pos += 2
for parser.pos < parser.size && parser.data[parser.pos] != '\n' {
parser.pos++
}
pos--
parser.pos--
case '*':
pos += 3
parser.pos += 3
for {
if pos >= size {
if parser.pos >= parser.size {
ErrorLog("Unexpected end of file")
return
}
if data[pos-1] == '*' && data[pos] == '/' {
if parser.data[parser.pos-1] == '*' && parser.data[parser.pos] == '/' {
break
}
if data[pos-1] == '\n' {
line++
lineStart = pos
if parser.data[parser.pos-1] == '\n' {
parser.line++
parser.lineStart = parser.pos
}
pos++
parser.pos++
}
default:
@ -377,75 +399,72 @@ func ParseDataText(text string) DataObject {
// do nothing
default:
if !unicode.IsSpace(data[pos]) {
if !unicode.IsSpace(parser.data[parser.pos]) {
return
}
}
pos++
}
parser.pos++
}
}
parseTag := func() (string, bool) {
skipSpaces(true)
startPos := pos
switch data[pos] {
func (parser *dataParser) parseTag() (string, error) {
parser.skipSpaces(true)
startPos := parser.pos
switch parser.data[parser.pos] {
case '`':
pos++
parser.pos++
startPos++
for data[pos] != '`' {
pos++
if pos >= size {
ErrorLog("Unexpected end of text")
return string(data[startPos:size]), false
for parser.data[parser.pos] != '`' {
parser.pos++
if parser.pos >= parser.size {
return string(parser.data[startPos:parser.size]), errors.New("unexpected end of text")
}
}
str := string(data[startPos:pos])
pos++
return str, true
str := string(parser.data[startPos:parser.pos])
parser.pos++
return str, nil
case '\'', '"':
stopSymbol := data[pos]
pos++
stopSymbol := parser.data[parser.pos]
parser.pos++
startPos++
slash := false
for stopSymbol != data[pos] {
if data[pos] == '\\' {
pos += 2
for stopSymbol != parser.data[parser.pos] {
if parser.data[parser.pos] == '\\' {
parser.pos += 2
slash = true
} else {
pos++
parser.pos++
}
if pos >= size {
ErrorLog("Unexpected end of text")
return string(data[startPos:size]), false
if parser.pos >= parser.size {
return string(parser.data[startPos:parser.size]), errors.New("unexpected end of text")
}
}
if !slash {
str := string(data[startPos:pos])
pos++
skipSpaces(false)
return str, true
str := string(parser.data[startPos:parser.pos])
parser.pos++
parser.skipSpaces(false)
return str, nil
}
buffer := make([]rune, pos-startPos+1)
buffer := make([]rune, parser.pos-startPos+1)
n1 := 0
n2 := startPos
invalidEscape := func() (string, bool) {
str := string(data[startPos:pos])
pos++
ErrorLogF("Invalid escape sequence in \"%s\" (position %d)", str, n2-2-startPos)
return str, false
invalidEscape := func() (string, error) {
str := string(parser.data[startPos:parser.pos])
parser.pos++
return str, fmt.Errorf(`invalid escape sequence in "%s" (position %d)`, str, n2-2-startPos)
}
for n2 < pos {
if data[n2] != '\\' {
buffer[n1] = data[n2]
for n2 < parser.pos {
if parser.data[n2] != '\\' {
buffer[n1] = parser.data[n2]
n2++
} else {
n2 += 2
switch data[n2-1] {
switch parser.data[n2-1] {
case 'n':
buffer[n1] = '\n'
@ -465,12 +484,12 @@ func ParseDataText(text string) DataObject {
buffer[n1] = '\\'
case 'x', 'X':
if n2+2 > pos {
if n2+2 > parser.pos {
return invalidEscape()
}
x := 0
for range 2 {
ch := data[n2]
ch := parser.data[n2]
if ch >= '0' && ch <= '9' {
x = x*16 + int(ch-'0')
} else if ch >= 'a' && ch <= 'f' {
@ -485,12 +504,12 @@ func ParseDataText(text string) DataObject {
buffer[n1] = rune(x)
case 'u', 'U':
if n2+4 > pos {
if n2+4 > parser.pos {
return invalidEscape()
}
x := 0
for range 4 {
ch := data[n2]
ch := parser.data[n2]
if ch >= '0' && ch <= '9' {
x = x*16 + int(ch-'0')
} else if ch >= 'a' && ch <= 'f' {
@ -505,90 +524,84 @@ func ParseDataText(text string) DataObject {
buffer[n1] = rune(x)
default:
str := string(data[startPos:pos])
ErrorLogF("Invalid escape sequence in \"%s\" (position %d)", str, n2-2-startPos)
return str, false
str := string(parser.data[startPos:parser.pos])
return str, fmt.Errorf(`invalid escape sequence in "%s" (position %d)`, str, n2-2-startPos)
}
}
n1++
}
pos++
skipSpaces(false)
return string(buffer[0:n1]), true
parser.pos++
parser.skipSpaces(false)
return string(buffer[0:n1]), nil
}
stopSymbol := func(symbol rune) bool {
return unicode.IsSpace(symbol) ||
slices.Contains([]rune{'=', '{', '}', '[', ']', ',', ' ', '\t', '\n', '\'', '"', '`', '/'}, symbol)
for parser.pos < parser.size && !parser.stopSymbol(parser.data[parser.pos]) {
parser.pos++
}
for pos < size && !stopSymbol(data[pos]) {
pos++
}
endPos := pos
skipSpaces(false)
endPos := parser.pos
parser.skipSpaces(false)
if startPos == endPos {
//ErrorLog("empty tag")
return "", true
}
return string(data[startPos:endPos]), true
return "", nil
}
return string(parser.data[startPos:endPos]), nil
}
var parseObject func(tag string) DataObject
var parseArray func() []DataValue
func (parser *dataParser) stopSymbol(symbol rune) bool {
return unicode.IsSpace(symbol) ||
slices.Contains([]rune{'=', '{', '}', '[', ']', ',', ' ', '\t', '\n', '\'', '"', '`', '/'}, symbol)
}
parseNode := func() DataNode {
func (parser *dataParser) parseNode() (DataNode, error) {
var tag string
var ok bool
var err error
if tag, ok = parseTag(); !ok {
return nil
if tag, err = parser.parseTag(); err != nil {
return nil, err
}
skipSpaces(true)
if data[pos] != '=' {
ErrorLogF("expected '=' after a tag name (line: %d, position: %d)", line, pos-lineStart)
return nil
parser.skipSpaces(true)
if parser.data[parser.pos] != '=' {
return nil, fmt.Errorf("expected '=' after a tag name (line: %d, position: %d)", parser.line, parser.pos-parser.lineStart)
}
pos++
skipSpaces(true)
switch data[pos] {
parser.pos++
parser.skipSpaces(true)
switch parser.data[parser.pos] {
case '[':
node := new(dataNode)
node.tag = tag
if node.array = parseArray(); node.array == nil {
return nil
if node.array, err = parser.parseArray(); err != nil {
return nil, err
}
return node
return node, nil
case '{':
node := new(dataNode)
node.tag = tag
if node.value = parseObject("_"); node.value == nil {
return nil
if node.value, err = parser.parseObject("_"); err != nil {
return nil, err
}
return node
return node, nil
case '}', ']', '=':
ErrorLogF("Expected '[', '{' or a tag name after '=' (line: %d, position: %d)", line, pos-lineStart)
return nil
return nil, fmt.Errorf(`expected '[', '{' or a tag name after '=' (line: %d, position: %d)`, parser.line, parser.pos-parser.lineStart)
default:
var str string
if str, ok = parseTag(); !ok {
return nil
if str, err = parser.parseTag(); err != nil {
return nil, err
}
node := new(dataNode)
node.tag = tag
if data[pos] == '{' {
if node.value = parseObject(str); node.value == nil {
return nil
if parser.data[parser.pos] == '{' {
if node.value, err = parser.parseObject(str); err != nil {
return nil, err
}
} else {
val := new(dataStringValue)
@ -596,91 +609,88 @@ func ParseDataText(text string) DataObject {
node.value = val
}
return node
}
return node, nil
}
}
parseObject = func(tag string) DataObject {
if data[pos] != '{' {
ErrorLogF("Expected '{' (line: %d, position: %d)", line, pos-lineStart)
return nil
func (parser *dataParser) parseObject(tag string) (DataObject, error) {
if parser.data[parser.pos] != '{' {
return nil, fmt.Errorf(`expected '{' (line: %d, position: %d)`, parser.line, parser.pos-parser.lineStart)
}
pos++
parser.pos++
obj := new(dataObject)
obj.tag = tag
obj.property = []DataNode{}
for pos < size {
var node DataNode
skipSpaces(true)
if data[pos] == '}' {
pos++
skipSpaces(false)
return obj
for parser.pos < parser.size {
parser.skipSpaces(true)
if parser.data[parser.pos] == '}' {
parser.pos++
parser.skipSpaces(false)
return obj, nil
}
if node = parseNode(); node == nil {
return nil
node, err := parser.parseNode()
if err != nil {
return nil, err
}
obj.property = append(obj.property, node)
if data[pos] == '}' {
pos++
skipSpaces(true)
return obj
} else if data[pos] != ',' && data[pos] != '\n' {
ErrorLogF(`Expected '}', '\n' or ',' (line: %d, position: %d)`, line, pos-lineStart)
return nil
if parser.data[parser.pos] == '}' {
parser.pos++
parser.skipSpaces(true)
return obj, nil
} else if parser.data[parser.pos] != ',' && parser.data[parser.pos] != '\n' {
return nil, fmt.Errorf(`expected '}', '\n' or ',' (line: %d, position: %d)`, parser.line, parser.pos-parser.lineStart)
}
if data[pos] != '\n' {
pos++
if parser.data[parser.pos] != '\n' {
parser.pos++
}
skipSpaces(true)
for data[pos] == ',' {
pos++
skipSpaces(true)
parser.skipSpaces(true)
for parser.data[parser.pos] == ',' {
parser.pos++
parser.skipSpaces(true)
}
}
ErrorLog("Unexpected end of text")
return nil
}
return nil, errors.New("unexpected end of text")
}
parseArray = func() []DataValue {
pos++
skipSpaces(true)
func (parser *dataParser) parseArray() ([]DataValue, error) {
parser.pos++
parser.skipSpaces(true)
array := []DataValue{}
for pos < size {
var tag string
var ok bool
skipSpaces(true)
for data[pos] == ',' && pos < size {
pos++
skipSpaces(true)
for parser.pos < parser.size {
parser.skipSpaces(true)
for parser.data[parser.pos] == ',' && parser.pos < parser.size {
parser.pos++
parser.skipSpaces(true)
}
if pos >= size {
if parser.pos >= parser.size {
break
}
if data[pos] == ']' {
pos++
skipSpaces(true)
return array
if parser.data[parser.pos] == ']' {
parser.pos++
parser.skipSpaces(true)
return array, nil
}
if tag, ok = parseTag(); !ok {
return nil
tag, err := parser.parseTag()
if err != nil {
return nil, err
}
if data[pos] == '{' {
obj := parseObject(tag)
if obj == nil {
return nil
if parser.data[parser.pos] == '{' {
obj, err := parser.parseObject(tag)
if err != nil {
return nil, err
}
array = append(array, obj)
} else {
@ -689,12 +699,11 @@ func ParseDataText(text string) DataObject {
array = append(array, val)
}
switch data[pos] {
switch parser.data[parser.pos] {
case ']', ',', '\n':
default:
ErrorLogF("Expected ']' or ',' (line: %d, position: %d)", line, pos-lineStart)
return nil
return nil, fmt.Errorf(`expected ']' or ',' (line: %d, position: %d)`, parser.line, parser.pos-parser.lineStart)
}
/*
@ -710,12 +719,28 @@ func ParseDataText(text string) DataObject {
*/
}
ErrorLog("Unexpected end of text")
return nil
return nil, errors.New("unexpected end of text")
}
// ParseDataText - parse text and return DataNode
func ParseDataText(text string) (DataObject, error) {
if strings.ContainsAny(text, "\r") {
text = strings.ReplaceAll(text, "\r\n", "\n")
text = strings.ReplaceAll(text, "\r", "\n")
}
if tag, ok := parseTag(); ok {
return parseObject(tag)
parser := dataParser{
data: append([]rune(text), rune(0)),
pos: 0,
line: 1,
lineStart: 0,
}
return nil
parser.size = len(parser.data) - 1
tag, err := parser.parseTag()
if err != nil {
return nil, err
}
return parser.parseObject(tag)
}

View File

@ -6,10 +6,6 @@ import (
func TestParseDataText(t *testing.T) {
SetErrorLog(func(text string) {
t.Error(text)
})
text := `obj1 {
key1 = val1,
key2=obj2{
@ -27,8 +23,10 @@ func TestParseDataText(t *testing.T) {
key3 = "\n \t \\ \r \" ' \X4F\x4e \U01Ea",` +
"key4=`" + `\n \t \\ \r \" ' \x8F \UF80a` + "`\r}"
obj := ParseDataText(text)
if obj != nil {
obj, err := ParseDataText(text)
if err != nil {
t.Error(err)
} else {
if obj.Tag() != "obj1" {
t.Error(`obj.Tag() != "obj1"`)
}
@ -173,9 +171,6 @@ func TestParseDataText(t *testing.T) {
}
}
SetErrorLog(func(text string) {
})
failText := []string{
" ",
"obj[]",
@ -204,7 +199,7 @@ func TestParseDataText(t *testing.T) {
}
for _, txt := range failText {
if obj := ParseDataText(txt); obj != nil {
if _, err := ParseDataText(txt); err == nil {
t.Errorf("result ParseDataText(\"%s\") must be fail", txt)
}
}

View File

@ -941,6 +941,25 @@ func NewPopup(view View, param Params) Popup {
return popup
}
/*
func CreatePopupFromObject(session Session, object DataObject, binding any) Popup {
node := object.RemovePropertyByTag(string(Content))
if node == nil {
ErrorLog(`"content" property not found`)
return nil
}
switch node.Type() {
case ObjectNode:
case TextNode:
default:
ErrorLog(`Unsupported data of "content" property`)
return nil
}
}
*/
// ShowPopup creates a new Popup and shows it
func ShowPopup(view View, param Params) Popup {
popup := NewPopup(view, param)

View File

@ -266,7 +266,8 @@ func (session *sessionData) setBridge(events chan DataObject, bridge bridge) {
func (session *sessionData) close() {
if session.events != nil {
session.events <- ParseDataText(`session-close{session="` + strconv.Itoa(session.sessionID) + `"}`)
obj, _ := ParseDataText(`session-close{session="` + strconv.Itoa(session.sessionID) + `"}`)
session.events <- obj
}
}

View File

@ -50,8 +50,9 @@ func (resources *resourceManager) scanStringsDir(path string) {
}
func loadStringResources(text string) {
data := ParseDataText(text)
if data == nil {
data, err := ParseDataText(text)
if err != nil {
ErrorLog(err.Error())
return
}

View File

@ -686,8 +686,11 @@ func (theme *theme) addText(themeText string) bool {
theme.init()
}
data := ParseDataText(themeText)
if data == nil || !data.IsObject() || data.Tag() != "theme" {
data, err := ParseDataText(themeText)
if err != nil {
ErrorLog(err.Error())
return false
} else if !data.IsObject() || data.Tag() != "theme" {
return false
}

View File

@ -398,7 +398,10 @@ func valueToTransformProperty(value any) TransformProperty {
}
case string:
if obj := ParseDataText(value); obj != nil {
obj, err := ParseDataText(value)
if err != nil {
ErrorLog(err.Error())
} else {
return parseObject(obj)
}
}

View File

@ -117,14 +117,17 @@ func CreateViewFromObject(session Session, object DataObject, binding any) View
//
// If the function fails, it returns nil and an error message is written to the log.
func CreateViewFromText(session Session, text string, binding ...any) View {
if data := ParseDataText(text); data != nil {
data, err := ParseDataText(text)
if err != nil {
ErrorLog(err.Error())
return nil
}
var b any = nil
if len(binding) > 0 {
b = binding[0]
}
return CreateViewFromObject(session, data, b)
}
return nil
}
// CreateViewFromResources create new View and initialize it by the content of
@ -153,14 +156,20 @@ func CreateViewFromResources(session Session, name string, binding ...any) View
case viewDir:
if data, err := fs.ReadFile(dir + "/" + name); err == nil {
if data := ParseDataText(string(data)); data != nil {
data, err := ParseDataText(string(data))
if err != nil {
ErrorLog(err.Error())
} else {
return CreateViewFromObject(session, data, b)
}
}
default:
if data, err := fs.ReadFile(dir + "/" + viewDir + "/" + name); err == nil {
if data := ParseDataText(string(data)); data != nil {
data, err := ParseDataText(string(data))
if err != nil {
ErrorLog(err.Error())
} else {
return CreateViewFromObject(session, data, b)
}
}
@ -170,7 +179,10 @@ func CreateViewFromResources(session Session, name string, binding ...any) View
if resources.path != "" {
if data, err := os.ReadFile(resources.path + viewDir + "/" + name); err == nil {
if data := ParseDataText(string(data)); data != nil {
data, err := ParseDataText(string(data))
if err != nil {
ErrorLog(err.Error())
} else {
return CreateViewFromObject(session, data, b)
}
}

View File

@ -173,13 +173,11 @@ func (container *viewsContainerData) htmlSubviews(self View, buffer *strings.Bui
}
func viewFromTextValue(text string, session Session) View {
if strings.Contains(text, "{") && strings.Contains(text, "}") {
if data := ParseDataText(text); data != nil {
if data, err := ParseDataText(text); err == nil {
if view := CreateViewFromObject(session, data, nil); view != nil {
return view
}
}
}
return NewTextView(session, Params{Text: text})
}