From c3c8b9e858ca20afe3c60d63dc8c1d009a43ae4e Mon Sep 17 00:00:00 2001 From: Alexei Anoshenko <2277098+anoshenko@users.noreply.github.com> Date: Wed, 25 Jun 2025 17:42:32 +0300 Subject: [PATCH] Changed ParseDataText function return values --- CHANGELOG.md | 1 + appServer.go | 201 ++++++------ background.go | 48 ++- border.go | 5 +- data.go | 805 ++++++++++++++++++++++++---------------------- data_test.go | 15 +- popup.go | 19 ++ session.go | 3 +- strings.go | 5 +- theme.go | 7 +- transform.go | 5 +- viewFactory.go | 32 +- viewsContainer.go | 8 +- 13 files changed, 609 insertions(+), 545 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91903e6..29c1e64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/appServer.go b/appServer.go index 8a2db52..ee71774 100644 --- a/appServer.go +++ b/appServer.go @@ -164,64 +164,68 @@ func (app *application) postHandler(w http.ResponseWriter, req *http.Request) { DebugLog(message) } - if obj := ParseDataText(message); obj != nil { - var session Session = nil - var response chan string = nil + obj, err := ParseDataText(message) + if err != nil { + ErrorLog(err.Error()) + return + } - if cookie, err := req.Cookie("session"); err == nil { - sessionID, err := strconv.Atoi(cookie.Value) - if err != nil { - ErrorLog(err.Error()) - } else if info, ok := app.sessions[sessionID]; ok && info.response != nil { - response = info.response - session = info.session - } - } - - command := obj.Tag() - startSession := false - - if session == nil || command == "startSession" { - events := make(chan DataObject, 1024) - bridge := createHttpBridge(req) - response = bridge.response - answer := "" - session, answer = app.startSession(obj, events, bridge, response) - - bridge.writeMessage(answer) - session.onStart() - if command == "session-resume" { - session.onResume() - } - bridge.sendResponse() - - setSessionIDCookie(w, session.ID()) - startSession = true - - go sessionEventHandler(session, events, bridge) - } - - if !startSession { - switch command { - case "nop": - session.sendResponse() - - case "session-close": - session.onFinish() - session.App().removeSession(session.ID()) - return - - default: - if !session.handleAnswer(command, obj) { - session.addToEventsQueue(obj) - } + var session Session = nil + var response chan string = nil + + if cookie, err := req.Cookie("session"); err == nil { + sessionID, err := strconv.Atoi(cookie.Value) + if err != nil { + ErrorLog(err.Error()) + } else if info, ok := app.sessions[sessionID]; ok && info.response != nil { + response = info.response + session = info.session + } + } + + command := obj.Tag() + startSession := false + + if session == nil || command == "startSession" { + events := make(chan DataObject, 1024) + bridge := createHttpBridge(req) + response = bridge.response + answer := "" + session, answer = app.startSession(obj, events, bridge, response) + + bridge.writeMessage(answer) + session.onStart() + if command == "session-resume" { + session.onResume() + } + bridge.sendResponse() + + setSessionIDCookie(w, session.ID()) + startSession = true + + go sessionEventHandler(session, events, bridge) + } + + if !startSession { + switch command { + case "nop": + session.sendResponse() + + case "session-close": + session.onFinish() + session.App().removeSession(session.ID()) + return + + default: + if !session.handleAnswer(command, obj) { + session.addToEventsQueue(obj) } } + } + io.WriteString(w, <-response) + for len(response) > 0 { io.WriteString(w, <-response) - for len(response) > 0 { - io.WriteString(w, <-response) - } } } } @@ -241,58 +245,61 @@ func (app *application) socketReader(bridge *wsBridge) { DebugLog("🖥️ -> " + message) } - if obj := ParseDataText(message); obj != nil { - command := obj.Tag() - switch command { - case "startSession": - answer := "" + 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 { + if !bridge.writeMessage(answer) { + return + } + session.onStart() + go sessionEventHandler(session, events, bridge) + } + + case "reconnect": + session = nil + if sessionText, ok := obj.PropertyValue("session"); ok { + if sessionID, err := strconv.Atoi(sessionText); err == nil { + if info, ok := app.sessions[sessionID]; ok { + session = info.session + session.setBridge(events, bridge) + + go sessionEventHandler(session, events, bridge) + session.onReconnect() + } else { + DebugLogF("Session #%d not exists", sessionID) + } + } else { + ErrorLog(`strconv.Atoi(sessionText) error: ` + err.Error()) + } + } else { + ErrorLog(`"session" key not found`) + } + + if session == nil { + /* answer := "" if session, answer = app.startSession(obj, events, bridge, nil); session != nil { if !bridge.writeMessage(answer) { return } session.onStart() go sessionEventHandler(session, events, bridge) + bridge.writeMessage("restartSession();") } + */ + bridge.writeMessage("reloadPage();") + return + } - case "reconnect": - session = nil - if sessionText, ok := obj.PropertyValue("session"); ok { - if sessionID, err := strconv.Atoi(sessionText); err == nil { - if info, ok := app.sessions[sessionID]; ok { - session = info.session - session.setBridge(events, bridge) - - go sessionEventHandler(session, events, bridge) - session.onReconnect() - } else { - DebugLogF("Session #%d not exists", sessionID) - } - } else { - ErrorLog(`strconv.Atoi(sessionText) error: ` + err.Error()) - } - } else { - ErrorLog(`"session" key not found`) - } - - if session == nil { - /* answer := "" - if session, answer = app.startSession(obj, events, bridge, nil); session != nil { - if !bridge.writeMessage(answer) { - return - } - session.onStart() - go sessionEventHandler(session, events, bridge) - bridge.writeMessage("restartSession();") - } - */ - bridge.writeMessage("reloadPage();") - return - } - - default: - if !session.handleAnswer(command, obj) { - events <- obj - } + default: + if !session.handleAnswer(command, obj) { + events <- obj } } } diff --git a/background.go b/background.go index d55d12d..920064b 100644 --- a/background.go +++ b/background.go @@ -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,12 +106,8 @@ func parseBackgroundValue(value any) []BackgroundElement { } else { return nil } - } else if obj := ParseDataText(el.Value()); obj != nil { - if element := createBackground(obj); element != nil { - background = append(background, element) - } else { - return nil - } + } else if element := parseBackgroundText(el.Value()); element != nil { + background = append(background, element) } else { return nil } @@ -125,21 +131,15 @@ func parseBackgroundValue(value any) []BackgroundElement { return background case string: - if obj := ParseDataText(value); obj != nil { - if element := createBackground(obj); element != nil { - return []BackgroundElement{element} - } + 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 { - elements = append(elements, element) - } else { - return nil - } + for _, text := range value { + if element := parseBackgroundText(text); element != nil { + elements = append(elements, element) } else { return nil } @@ -148,18 +148,14 @@ func parseBackgroundValue(value any) []BackgroundElement { 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 { - elements = append(elements, element) - } else { - return nil - } + if element := parseBackgroundText(val); element != nil { + elements = append(elements, element) } else { return nil } diff --git a/border.go b/border.go index 45d0db6..ff07794 100644 --- a/border.go +++ b/border.go @@ -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) } diff --git a/data.go b/data.go index c3cc88f..edc6998 100644 --- a/data.go +++ b/data.go @@ -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,404 +345,402 @@ func (node *dataNode) ArrayAsParams() []Params { return result } +type dataParser struct { + data []rune + size int + pos int + line int + lineStart int +} + +func (parser *dataParser) skipSpaces(skipNewLine bool) { + for parser.pos < parser.size { + switch parser.data[parser.pos] { + case '\n': + if !skipNewLine { + return + } + parser.line++ + parser.lineStart = parser.pos + 1 + + case '/': + if parser.pos+1 < parser.size { + switch parser.data[parser.pos+1] { + case '/': + parser.pos += 2 + for parser.pos < parser.size && parser.data[parser.pos] != '\n' { + parser.pos++ + } + parser.pos-- + + case '*': + parser.pos += 3 + for { + if parser.pos >= parser.size { + ErrorLog("Unexpected end of file") + return + } + if parser.data[parser.pos-1] == '*' && parser.data[parser.pos] == '/' { + break + } + if parser.data[parser.pos-1] == '\n' { + parser.line++ + parser.lineStart = parser.pos + } + parser.pos++ + } + + default: + return + } + } + + case ' ', '\t': + // do nothing + + default: + if !unicode.IsSpace(parser.data[parser.pos]) { + return + } + } + parser.pos++ + } +} + +func (parser *dataParser) parseTag() (string, error) { + parser.skipSpaces(true) + startPos := parser.pos + switch parser.data[parser.pos] { + case '`': + parser.pos++ + startPos++ + 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(parser.data[startPos:parser.pos]) + parser.pos++ + return str, nil + + case '\'', '"': + stopSymbol := parser.data[parser.pos] + parser.pos++ + startPos++ + slash := false + for stopSymbol != parser.data[parser.pos] { + if parser.data[parser.pos] == '\\' { + parser.pos += 2 + slash = true + } else { + parser.pos++ + } + if parser.pos >= parser.size { + return string(parser.data[startPos:parser.size]), errors.New("unexpected end of text") + } + } + + if !slash { + str := string(parser.data[startPos:parser.pos]) + parser.pos++ + parser.skipSpaces(false) + return str, nil + } + + buffer := make([]rune, parser.pos-startPos+1) + n1 := 0 + n2 := startPos + + 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 < parser.pos { + if parser.data[n2] != '\\' { + buffer[n1] = parser.data[n2] + n2++ + } else { + n2 += 2 + switch parser.data[n2-1] { + case 'n': + buffer[n1] = '\n' + + case 'r': + buffer[n1] = '\r' + + case 't': + buffer[n1] = '\t' + + case '"': + buffer[n1] = '"' + + case '\'': + buffer[n1] = '\'' + + case '\\': + buffer[n1] = '\\' + + case 'x', 'X': + if n2+2 > parser.pos { + return invalidEscape() + } + x := 0 + for range 2 { + ch := parser.data[n2] + if ch >= '0' && ch <= '9' { + x = x*16 + int(ch-'0') + } else if ch >= 'a' && ch <= 'f' { + x = x*16 + int(ch-'a'+10) + } else if ch >= 'A' && ch <= 'F' { + x = x*16 + int(ch-'A'+10) + } else { + return invalidEscape() + } + n2++ + } + buffer[n1] = rune(x) + + case 'u', 'U': + if n2+4 > parser.pos { + return invalidEscape() + } + x := 0 + for range 4 { + ch := parser.data[n2] + if ch >= '0' && ch <= '9' { + x = x*16 + int(ch-'0') + } else if ch >= 'a' && ch <= 'f' { + x = x*16 + int(ch-'a'+10) + } else if ch >= 'A' && ch <= 'F' { + x = x*16 + int(ch-'A'+10) + } else { + return invalidEscape() + } + n2++ + } + buffer[n1] = rune(x) + + default: + str := string(parser.data[startPos:parser.pos]) + return str, fmt.Errorf(`invalid escape sequence in "%s" (position %d)`, str, n2-2-startPos) + } + } + n1++ + } + + parser.pos++ + parser.skipSpaces(false) + return string(buffer[0:n1]), nil + } + + for parser.pos < parser.size && !parser.stopSymbol(parser.data[parser.pos]) { + parser.pos++ + } + + endPos := parser.pos + parser.skipSpaces(false) + if startPos == endPos { + //ErrorLog("empty tag") + return "", nil + } + return string(parser.data[startPos:endPos]), nil +} + +func (parser *dataParser) stopSymbol(symbol rune) bool { + return unicode.IsSpace(symbol) || + slices.Contains([]rune{'=', '{', '}', '[', ']', ',', ' ', '\t', '\n', '\'', '"', '`', '/'}, symbol) +} + +func (parser *dataParser) parseNode() (DataNode, error) { + var tag string + var err error + + if tag, err = parser.parseTag(); err != nil { + return nil, err + } + + parser.skipSpaces(true) + if parser.data[parser.pos] != '=' { + return nil, fmt.Errorf("expected '=' after a tag name (line: %d, position: %d)", parser.line, parser.pos-parser.lineStart) + } + + parser.pos++ + parser.skipSpaces(true) + switch parser.data[parser.pos] { + case '[': + node := new(dataNode) + node.tag = tag + + if node.array, err = parser.parseArray(); err != nil { + return nil, err + } + return node, nil + + case '{': + node := new(dataNode) + node.tag = tag + if node.value, err = parser.parseObject("_"); err != nil { + return nil, err + } + return node, nil + + case '}', ']', '=': + return nil, fmt.Errorf(`expected '[', '{' or a tag name after '=' (line: %d, position: %d)`, parser.line, parser.pos-parser.lineStart) + + default: + var str string + if str, err = parser.parseTag(); err != nil { + return nil, err + } + + node := new(dataNode) + node.tag = tag + + if parser.data[parser.pos] == '{' { + if node.value, err = parser.parseObject(str); err != nil { + return nil, err + } + } else { + val := new(dataStringValue) + val.value = str + node.value = val + } + + return node, 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) + } + parser.pos++ + + obj := new(dataObject) + obj.tag = tag + obj.property = []DataNode{} + + for parser.pos < parser.size { + parser.skipSpaces(true) + if parser.data[parser.pos] == '}' { + parser.pos++ + parser.skipSpaces(false) + return obj, nil + } + + node, err := parser.parseNode() + if err != nil { + return nil, err + } + + obj.property = append(obj.property, node) + 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 parser.data[parser.pos] != '\n' { + parser.pos++ + } + + parser.skipSpaces(true) + for parser.data[parser.pos] == ',' { + parser.pos++ + parser.skipSpaces(true) + } + } + + return nil, errors.New("unexpected end of text") +} + +func (parser *dataParser) parseArray() ([]DataValue, error) { + parser.pos++ + parser.skipSpaces(true) + + array := []DataValue{} + + for parser.pos < parser.size { + parser.skipSpaces(true) + for parser.data[parser.pos] == ',' && parser.pos < parser.size { + parser.pos++ + parser.skipSpaces(true) + } + + if parser.pos >= parser.size { + break + } + + if parser.data[parser.pos] == ']' { + parser.pos++ + parser.skipSpaces(true) + return array, nil + } + + tag, err := parser.parseTag() + if err != nil { + return nil, err + } + + if parser.data[parser.pos] == '{' { + obj, err := parser.parseObject(tag) + if err != nil { + return nil, err + } + array = append(array, obj) + } else { + val := new(dataStringValue) + val.value = tag + array = append(array, val) + } + + switch parser.data[parser.pos] { + case ']', ',', '\n': + + default: + return nil, fmt.Errorf(`expected ']' or ',' (line: %d, position: %d)`, parser.line, parser.pos-parser.lineStart) + } + + /* + if data[pos] == ']' { + pos++ + skipSpaces() + return array, nil + } else if data[pos] != ',' { + return nil, fmt.Errorf("Expected ']' or ',' (line: %d, position: %d)", line, pos-lineStart) + } + pos++ + skipSpaces() + */ + } + + return nil, errors.New("unexpected end of text") +} + // ParseDataText - parse text and return DataNode -func ParseDataText(text string) DataObject { +func ParseDataText(text string) (DataObject, error) { 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] { - case '\n': - if !skipNewLine { - return - } - line++ - lineStart = pos + 1 - - case '/': - if pos+1 < size { - switch data[pos+1] { - case '/': - pos += 2 - for pos < size && data[pos] != '\n' { - pos++ - } - pos-- - - case '*': - pos += 3 - for { - if pos >= size { - ErrorLog("Unexpected end of file") - return - } - if data[pos-1] == '*' && data[pos] == '/' { - break - } - if data[pos-1] == '\n' { - line++ - lineStart = pos - } - pos++ - } - - default: - return - } - } - - case ' ', '\t': - // do nothing - - default: - if !unicode.IsSpace(data[pos]) { - return - } - } - pos++ - } + parser := dataParser{ + data: append([]rune(text), rune(0)), + pos: 0, + line: 1, + lineStart: 0, } + parser.size = len(parser.data) - 1 - parseTag := func() (string, bool) { - skipSpaces(true) - startPos := pos - switch data[pos] { - case '`': - pos++ - startPos++ - for data[pos] != '`' { - pos++ - if pos >= size { - ErrorLog("Unexpected end of text") - return string(data[startPos:size]), false - } - } - str := string(data[startPos:pos]) - pos++ - return str, true - - case '\'', '"': - stopSymbol := data[pos] - pos++ - startPos++ - slash := false - for stopSymbol != data[pos] { - if data[pos] == '\\' { - pos += 2 - slash = true - } else { - pos++ - } - if pos >= size { - ErrorLog("Unexpected end of text") - return string(data[startPos:size]), false - } - } - - if !slash { - str := string(data[startPos:pos]) - pos++ - skipSpaces(false) - return str, true - } - - buffer := make([]rune, 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 - } - - for n2 < pos { - if data[n2] != '\\' { - buffer[n1] = data[n2] - n2++ - } else { - n2 += 2 - switch data[n2-1] { - case 'n': - buffer[n1] = '\n' - - case 'r': - buffer[n1] = '\r' - - case 't': - buffer[n1] = '\t' - - case '"': - buffer[n1] = '"' - - case '\'': - buffer[n1] = '\'' - - case '\\': - buffer[n1] = '\\' - - case 'x', 'X': - if n2+2 > pos { - return invalidEscape() - } - x := 0 - for range 2 { - ch := data[n2] - if ch >= '0' && ch <= '9' { - x = x*16 + int(ch-'0') - } else if ch >= 'a' && ch <= 'f' { - x = x*16 + int(ch-'a'+10) - } else if ch >= 'A' && ch <= 'F' { - x = x*16 + int(ch-'A'+10) - } else { - return invalidEscape() - } - n2++ - } - buffer[n1] = rune(x) - - case 'u', 'U': - if n2+4 > pos { - return invalidEscape() - } - x := 0 - for range 4 { - ch := data[n2] - if ch >= '0' && ch <= '9' { - x = x*16 + int(ch-'0') - } else if ch >= 'a' && ch <= 'f' { - x = x*16 + int(ch-'a'+10) - } else if ch >= 'A' && ch <= 'F' { - x = x*16 + int(ch-'A'+10) - } else { - return invalidEscape() - } - n2++ - } - 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 - } - } - n1++ - } - - pos++ - skipSpaces(false) - return string(buffer[0:n1]), true - } - - stopSymbol := func(symbol rune) bool { - return unicode.IsSpace(symbol) || - slices.Contains([]rune{'=', '{', '}', '[', ']', ',', ' ', '\t', '\n', '\'', '"', '`', '/'}, symbol) - } - - for pos < size && !stopSymbol(data[pos]) { - pos++ - } - - endPos := pos - skipSpaces(false) - if startPos == endPos { - //ErrorLog("empty tag") - return "", true - } - return string(data[startPos:endPos]), true + tag, err := parser.parseTag() + if err != nil { + return nil, err } - - var parseObject func(tag string) DataObject - var parseArray func() []DataValue - - parseNode := func() DataNode { - var tag string - var ok bool - - if tag, ok = parseTag(); !ok { - return nil - } - - skipSpaces(true) - if data[pos] != '=' { - ErrorLogF("expected '=' after a tag name (line: %d, position: %d)", line, pos-lineStart) - return nil - } - - pos++ - skipSpaces(true) - switch data[pos] { - case '[': - node := new(dataNode) - node.tag = tag - - if node.array = parseArray(); node.array == nil { - return nil - } - return node - - case '{': - node := new(dataNode) - node.tag = tag - if node.value = parseObject("_"); node.value == nil { - return nil - } - return node - - case '}', ']', '=': - ErrorLogF("Expected '[', '{' or a tag name after '=' (line: %d, position: %d)", line, pos-lineStart) - return nil - - default: - var str string - if str, ok = parseTag(); !ok { - return nil - } - - node := new(dataNode) - node.tag = tag - - if data[pos] == '{' { - if node.value = parseObject(str); node.value == nil { - return nil - } - } else { - val := new(dataStringValue) - val.value = str - node.value = val - } - - return node - } - } - - parseObject = func(tag string) DataObject { - if data[pos] != '{' { - ErrorLogF("Expected '{' (line: %d, position: %d)", line, pos-lineStart) - return nil - } - 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 - } - - if node = parseNode(); node == nil { - return nil - } - 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 data[pos] != '\n' { - pos++ - } - skipSpaces(true) - for data[pos] == ',' { - pos++ - skipSpaces(true) - } - } - - ErrorLog("Unexpected end of text") - return nil - } - - parseArray = func() []DataValue { - pos++ - skipSpaces(true) - - array := []DataValue{} - - for pos < size { - var tag string - var ok bool - - skipSpaces(true) - for data[pos] == ',' && pos < size { - pos++ - skipSpaces(true) - } - - if pos >= size { - break - } - - if data[pos] == ']' { - pos++ - skipSpaces(true) - return array - } - - if tag, ok = parseTag(); !ok { - return nil - } - - if data[pos] == '{' { - obj := parseObject(tag) - if obj == nil { - return nil - } - array = append(array, obj) - } else { - val := new(dataStringValue) - val.value = tag - array = append(array, val) - } - - switch data[pos] { - case ']', ',', '\n': - - default: - ErrorLogF("Expected ']' or ',' (line: %d, position: %d)", line, pos-lineStart) - return nil - } - - /* - 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() - */ - } - - ErrorLog("Unexpected end of text") - return nil - } - - if tag, ok := parseTag(); ok { - return parseObject(tag) - } - return nil + return parser.parseObject(tag) } diff --git a/data_test.go b/data_test.go index 2ab257a..ed26ba3 100644 --- a/data_test.go +++ b/data_test.go @@ -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) } } diff --git a/popup.go b/popup.go index 560b519..ea18c14 100644 --- a/popup.go +++ b/popup.go @@ -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) diff --git a/session.go b/session.go index 54662b1..5c92551 100644 --- a/session.go +++ b/session.go @@ -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 } } diff --git a/strings.go b/strings.go index 5b2bf26..090ecd9 100644 --- a/strings.go +++ b/strings.go @@ -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 } diff --git a/theme.go b/theme.go index de11522..1e425a8 100644 --- a/theme.go +++ b/theme.go @@ -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 } diff --git a/transform.go b/transform.go index 6a0c600..cb5c10c 100644 --- a/transform.go +++ b/transform.go @@ -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) } } diff --git a/viewFactory.go b/viewFactory.go index a4e7d68..2017123 100644 --- a/viewFactory.go +++ b/viewFactory.go @@ -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 { - var b any = nil - if len(binding) > 0 { - b = binding[0] - } - return CreateViewFromObject(session, data, b) + data, err := ParseDataText(text) + if err != nil { + ErrorLog(err.Error()) + return nil } - return nil + + var b any = nil + if len(binding) > 0 { + b = binding[0] + } + return CreateViewFromObject(session, data, b) } // 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) } } diff --git a/viewsContainer.go b/viewsContainer.go index 3d6d870..d5050f1 100644 --- a/viewsContainer.go +++ b/viewsContainer.go @@ -173,11 +173,9 @@ 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 view := CreateViewFromObject(session, data, nil); view != nil { - return view - } + if data, err := ParseDataText(text); err == nil { + if view := CreateViewFromObject(session, data, nil); view != nil { + return view } } return NewTextView(session, Params{Text: text})