rui_orig/data.go

725 lines
14 KiB
Go

package rui
import (
"strings"
"unicode"
)
// DataValue interface of a data node value
type DataValue interface {
// IsObject returns "true" if data value is an object
IsObject() bool
// Object returns data value as a data object
Object() DataObject
// Value returns value as a string
Value() string
}
// DataObject interface of a data object
type DataObject interface {
DataValue
// Tag returns data object tag
Tag() string
// 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
}
// Constants which are used to describe a node type, see [DataNode]
const (
// TextNode - node is the pair "tag - text value". Syntax: <tag> = <text>
TextNode = 0
// ObjectNode - node is the pair "tag - object". Syntax: <tag> = <object name>{...}
ObjectNode = 1
// ArrayNode - node is the pair "tag - object". Syntax: <tag> = [...]
ArrayNode = 2
)
// DataNode interface of a data node
type DataNode interface {
// Tag returns a tag name
Tag() string
// Type returns a node type. Possible values are TextNode, ObjectNode and ArrayNode
Type() int
// Text returns node text
Text() string
// Object returns node as object if that node type is an object
Object() DataObject
// ArraySize returns array size if that node type is an array
ArraySize() int
// ArrayElement returns a value of an array if that node type is an array
ArrayElement(index int) DataValue
// ArrayElements returns an array of objects if that node is an array
ArrayElements() []DataValue
// ArrayAsParams returns an array of a params(map) if that node is an array
ArrayAsParams() []Params
}
/******************************************************************************/
type dataStringValue struct {
value string
}
func (value *dataStringValue) Value() string {
return value.value
}
func (value *dataStringValue) IsObject() bool {
return false
}
func (value *dataStringValue) Object() DataObject {
return nil
}
/******************************************************************************/
type dataObject struct {
tag string
property []DataNode
}
// NewDataObject create new DataObject with the tag and empty property list
func NewDataObject(tag string) DataObject {
obj := new(dataObject)
obj.tag = tag
obj.property = []DataNode{}
return obj
}
func (object *dataObject) Value() string {
return ""
}
func (object *dataObject) IsObject() bool {
return true
}
func (object *dataObject) Object() DataObject {
return object
}
func (object *dataObject) Tag() string {
return object.tag
}
func (object *dataObject) PropertyCount() int {
if object.property != nil {
return len(object.property)
}
return 0
}
func (object *dataObject) Property(index int) DataNode {
if object.property == nil || index < 0 || index >= len(object.property) {
return nil
}
return object.property[index]
}
func (object *dataObject) PropertyByTag(tag string) DataNode {
if object.property != nil {
for _, node := range object.property {
if node.Tag() == tag {
return node
}
}
}
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) {
node := new(dataNode)
node.tag = tag
node.value = obj
object.setNode(node)
}
func (object *dataObject) ToParams() Params {
params := Params{}
for _, node := range object.property {
switch node.Type() {
case TextNode:
if text := node.Text(); text != "" {
params[node.Tag()] = text
}
case ObjectNode:
if obj := node.Object(); obj != nil {
params[node.Tag()] = node.Object()
}
case ArrayNode:
array := []any{}
for i := 0; i < node.ArraySize(); i++ {
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[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() int {
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) ArrayElements() []DataValue {
if node.array != nil {
return node.array
}
return []DataValue{}
}
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
}
// ParseDataText - parse text and return DataNode
func ParseDataText(text string) DataObject {
if strings.ContainsAny(text, "\r") {
text = strings.Replace(text, "\r\n", "\n", -1)
text = strings.Replace(text, "\r", "\n", -1)
}
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++
}
}
parseTag := func() (string, bool) {
skipSpaces(true)
startPos := pos
if data[pos] == '`' {
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
} else if data[pos] == '\'' || data[pos] == '"' {
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 i := 0; i < 2; i++ {
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 i := 0; i < 4; i++ {
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 {
if unicode.IsSpace(symbol) {
return true
}
for _, sym := range []rune{'=', '{', '}', '[', ']', ',', ' ', '\t', '\n', '\'', '"', '`', '/'} {
if sym == symbol {
return true
}
}
return false
}
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
}
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
}