Adapt format.go from the image package Change the only package level state variable to a function parameter Load entire files into memory for performance
554 lines
10 KiB
Go
554 lines
10 KiB
Go
package lsgo
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/xml"
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/google/uuid"
|
|
"gonum.org/v1/gonum/mat"
|
|
)
|
|
|
|
// XMLMarshaler has a pointer to start in order to append multiple attributes to the xml element
|
|
type XMLMarshaler interface {
|
|
MarshalXML(e *xml.Encoder, start *xml.StartElement) error
|
|
}
|
|
|
|
type TranslatedString struct {
|
|
Version uint16
|
|
Value string
|
|
Handle string
|
|
}
|
|
|
|
func (ts TranslatedString) MarshalXML(e *xml.Encoder, start *xml.StartElement) error {
|
|
start.Attr = append(start.Attr,
|
|
xml.Attr{
|
|
Name: xml.Name{Local: "handle"},
|
|
Value: ts.Handle,
|
|
},
|
|
xml.Attr{
|
|
Name: xml.Name{Local: "version"},
|
|
Value: strconv.Itoa(int(ts.Version)),
|
|
},
|
|
)
|
|
return nil
|
|
}
|
|
|
|
type TranslatedFSStringArgument struct {
|
|
String TranslatedFSString
|
|
Key string
|
|
Value string
|
|
}
|
|
|
|
type TranslatedFSString struct {
|
|
TranslatedString
|
|
Arguments []TranslatedFSStringArgument
|
|
}
|
|
|
|
// func (tfs TranslatedFSString) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
|
// start.Attr = append(start.Attr,
|
|
// xml.Attr{
|
|
// Name: xml.Name{Local: "version"},
|
|
// Value: strconv.Itoa(int(tfs.Version)),
|
|
// },
|
|
// xml.Attr{
|
|
// Name: xml.Name{Local: "handle"},
|
|
// Value: tfs.Handle,
|
|
// },
|
|
// xml.Attr{
|
|
// Name: xml.Name{Local: "value"},
|
|
// Value: ts.Value,
|
|
// },
|
|
// )
|
|
// return nil
|
|
// }
|
|
|
|
type Ivec []int
|
|
|
|
func (i Ivec) String() string {
|
|
b := &strings.Builder{}
|
|
for _, v := range i {
|
|
b.WriteString(" ")
|
|
b.WriteString(strconv.Itoa(v))
|
|
}
|
|
return b.String()[1:]
|
|
}
|
|
|
|
type Vec []float64
|
|
|
|
type Mat mat.Dense
|
|
|
|
func (m Mat) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
|
var (
|
|
M = mat.Dense(m)
|
|
v []float64
|
|
)
|
|
rows, cols := M.Dims()
|
|
if rows == cols {
|
|
start.Name.Local = "mat" + strconv.Itoa(rows)
|
|
} else {
|
|
start.Name.Local = "mat" + strconv.Itoa(rows) + "x" + strconv.Itoa(cols)
|
|
}
|
|
e.EncodeToken(start)
|
|
for i := 0; i < rows; i++ {
|
|
v = M.RawRowView(i)
|
|
n := Vec(v)
|
|
e.Encode(n)
|
|
}
|
|
e.EncodeToken(xml.EndElement{Name: start.Name})
|
|
return nil
|
|
}
|
|
|
|
func (v Vec) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
|
var name xml.Name
|
|
for i := 0; i < len(v); i++ {
|
|
switch i {
|
|
case 0:
|
|
name.Local = "x"
|
|
// start.Name = "float1"
|
|
case 1:
|
|
name.Local = "y"
|
|
start.Name.Local = "float2"
|
|
case 2:
|
|
name.Local = "z"
|
|
start.Name.Local = "float3"
|
|
case 3:
|
|
name.Local = "w"
|
|
start.Name.Local = "float4"
|
|
|
|
default:
|
|
return ErrVectorTooBig
|
|
}
|
|
start.Attr = append(start.Attr, xml.Attr{
|
|
Name: name,
|
|
Value: strconv.FormatFloat(v[i], 'f', -1, 32),
|
|
})
|
|
}
|
|
e.EncodeToken(start)
|
|
e.EncodeToken(xml.EndElement{Name: start.Name})
|
|
return nil
|
|
}
|
|
|
|
type DataType int
|
|
|
|
const (
|
|
DTNone DataType = iota
|
|
DTByte
|
|
DTShort
|
|
DTUShort
|
|
DTInt
|
|
DTUInt
|
|
DTFloat
|
|
DTDouble
|
|
DTIVec2
|
|
DTIVec3
|
|
DTIVec4
|
|
DTVec2
|
|
DTVec3
|
|
DTVec4
|
|
DTMat2
|
|
DTMat3
|
|
DTMat3x4
|
|
DTMat4x3
|
|
DTMat4
|
|
DTBool
|
|
DTString
|
|
DTPath
|
|
DTFixedString
|
|
DTLSString
|
|
DTULongLong
|
|
DTScratchBuffer
|
|
// Seems to be unused?
|
|
DTLong
|
|
DTInt8
|
|
DTTranslatedString
|
|
DTWString
|
|
DTLSWString
|
|
DTUUID
|
|
DTInt64
|
|
DTTranslatedFSString
|
|
// Last supported datatype, always keep this one at the end
|
|
DTMax = iota - 1
|
|
)
|
|
|
|
func (dt *DataType) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {
|
|
return xml.Attr{
|
|
Value: dt.String(),
|
|
Name: name,
|
|
}, nil
|
|
}
|
|
|
|
func (dt DataType) String() string {
|
|
switch dt {
|
|
case DTNone:
|
|
return "None"
|
|
case DTByte:
|
|
return "uint8"
|
|
case DTShort:
|
|
return "int16"
|
|
case DTUShort:
|
|
return "uint16"
|
|
case DTInt:
|
|
return "int32"
|
|
case DTUInt:
|
|
return "uint32"
|
|
case DTFloat:
|
|
return "float"
|
|
case DTDouble:
|
|
return "double"
|
|
case DTIVec2:
|
|
return "ivec2"
|
|
case DTIVec3:
|
|
return "ivec3"
|
|
case DTIVec4:
|
|
return "ivec4"
|
|
case DTVec2:
|
|
return "fvec2"
|
|
case DTVec3:
|
|
return "fvec3"
|
|
case DTVec4:
|
|
return "fvec4"
|
|
case DTMat2:
|
|
return "mat2x2"
|
|
case DTMat3:
|
|
return "mat3x3"
|
|
case DTMat3x4:
|
|
return "mat3x4"
|
|
case DTMat4x3:
|
|
return "mat4x3"
|
|
case DTMat4:
|
|
return "mat4x4"
|
|
case DTBool:
|
|
return "bool"
|
|
case DTString:
|
|
return "string"
|
|
case DTPath:
|
|
return "path"
|
|
case DTFixedString:
|
|
return "FixedString"
|
|
case DTLSString:
|
|
return "LSString"
|
|
case DTULongLong:
|
|
return "uint64"
|
|
case DTScratchBuffer:
|
|
return "ScratchBuffer"
|
|
case DTLong:
|
|
return "old_int64"
|
|
case DTInt8:
|
|
return "int8"
|
|
case DTTranslatedString:
|
|
return "TranslatedString"
|
|
case DTWString:
|
|
return "WString"
|
|
case DTLSWString:
|
|
return "LSWString"
|
|
case DTUUID:
|
|
return "guid"
|
|
case DTInt64:
|
|
return "int64"
|
|
case DTTranslatedFSString:
|
|
return "TranslatedFSString"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
type NodeAttribute struct {
|
|
Name string `xml:"id,attr"`
|
|
Type DataType `xml:"type,attr"`
|
|
Value interface{} `xml:"value,attr"`
|
|
}
|
|
|
|
func (na NodeAttribute) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
|
t, _ := na.Type.MarshalXMLAttr(xml.Name{Local: "type"})
|
|
start.Attr = append(start.Attr,
|
|
xml.Attr{
|
|
Name: xml.Name{Local: "id"},
|
|
Value: na.Name,
|
|
},
|
|
t,
|
|
)
|
|
v, MarshalXML2 := na.Value.(XMLMarshaler)
|
|
v1, MarshalXML := na.Value.(xml.Marshaler)
|
|
if MarshalXML2 {
|
|
v.MarshalXML(e, &start)
|
|
}
|
|
if !(MarshalXML || MarshalXML2) {
|
|
start.Attr = append(start.Attr,
|
|
xml.Attr{
|
|
Name: xml.Name{Local: "value"},
|
|
Value: na.String(),
|
|
},
|
|
)
|
|
}
|
|
|
|
e.EncodeToken(start)
|
|
|
|
if MarshalXML {
|
|
e.EncodeElement(v1, xml.StartElement{Name: xml.Name{Local: na.Type.String()}})
|
|
}
|
|
|
|
e.EncodeToken(xml.EndElement{
|
|
Name: start.Name,
|
|
})
|
|
return nil
|
|
}
|
|
|
|
func (na NodeAttribute) String() string {
|
|
switch na.Type {
|
|
case DTScratchBuffer:
|
|
// ScratchBuffer is a special case, as its stored as byte[] and ToString() doesn't really do what we want
|
|
if value, ok := na.Value.([]byte); ok {
|
|
return base64.StdEncoding.EncodeToString(value)
|
|
}
|
|
return fmt.Sprint(na.Value)
|
|
|
|
case DTDouble:
|
|
v := na.Value.(float64)
|
|
if na.Value == 0 {
|
|
na.Value = 0
|
|
}
|
|
return strconv.FormatFloat(v, 'f', -1, 64)
|
|
|
|
case DTFloat:
|
|
v := na.Value.(float32)
|
|
if na.Value == 0 {
|
|
na.Value = 0
|
|
}
|
|
return strconv.FormatFloat(float64(v), 'f', -1, 32)
|
|
|
|
default:
|
|
return fmt.Sprint(na.Value)
|
|
}
|
|
}
|
|
|
|
func (na NodeAttribute) GetRows() (int, error) {
|
|
return na.Type.GetRows()
|
|
}
|
|
|
|
func (dt DataType) GetRows() (int, error) {
|
|
switch dt {
|
|
case DTIVec2, DTIVec3, DTIVec4, DTVec2, DTVec3, DTVec4:
|
|
return 1, nil
|
|
|
|
case DTMat2:
|
|
return 2, nil
|
|
|
|
case DTMat3, DTMat3x4:
|
|
return 3, nil
|
|
|
|
case DTMat4x3, DTMat4:
|
|
return 4, nil
|
|
|
|
default:
|
|
return 0, errors.New("data type does not have rows")
|
|
}
|
|
}
|
|
|
|
func (na NodeAttribute) GetColumns() (int, error) {
|
|
return na.Type.GetColumns()
|
|
}
|
|
|
|
func (dt DataType) GetColumns() (int, error) {
|
|
switch dt {
|
|
case DTIVec2, DTVec2, DTMat2:
|
|
return 2, nil
|
|
|
|
case DTIVec3, DTVec3, DTMat3, DTMat4x3:
|
|
return 3, nil
|
|
|
|
case DTIVec4, DTVec4, DTMat3x4, DTMat4:
|
|
return 4, nil
|
|
|
|
default:
|
|
return 0, errors.New("data type does not have columns")
|
|
}
|
|
}
|
|
|
|
func (na NodeAttribute) IsNumeric() bool {
|
|
switch na.Type {
|
|
case DTByte, DTShort, DTInt, DTUInt, DTFloat, DTDouble, DTULongLong, DTLong, DTInt8:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (na *NodeAttribute) FromString(str string) error {
|
|
if na.IsNumeric() {
|
|
// Workaround: Some XML files use empty strings, instead of "0" for zero values.
|
|
if str == "" {
|
|
str = "0"
|
|
// Handle hexadecimal integers in XML files
|
|
}
|
|
}
|
|
|
|
var err error
|
|
|
|
switch na.Type {
|
|
case DTNone:
|
|
// This is a null type, cannot have a value
|
|
|
|
case DTByte:
|
|
na.Value = []byte(str)
|
|
|
|
case DTShort:
|
|
|
|
na.Value, err = strconv.ParseInt(str, 0, 16)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
case DTUShort:
|
|
na.Value, err = strconv.ParseUint(str, 0, 16)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
case DTInt:
|
|
na.Value, err = strconv.ParseInt(str, 0, 32)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
case DTUInt:
|
|
na.Value, err = strconv.ParseUint(str, 0, 16)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
case DTFloat:
|
|
na.Value, err = strconv.ParseFloat(str, 32)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
case DTDouble:
|
|
na.Value, err = strconv.ParseFloat(str, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
case DTIVec2, DTIVec3, DTIVec4:
|
|
var (
|
|
nums []string
|
|
length int
|
|
)
|
|
|
|
nums = strings.Split(str, ".")
|
|
length, err = na.GetColumns()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if length != len(nums) {
|
|
return fmt.Errorf("a vector of length %d was expected, got %d", length, len(nums))
|
|
}
|
|
|
|
vec := make([]int, length)
|
|
for i, v := range nums {
|
|
var n int64
|
|
n, err = strconv.ParseInt(v, 0, 64)
|
|
vec[i] = int(n)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
na.Value = vec
|
|
|
|
case DTVec2, DTVec3, DTVec4:
|
|
var (
|
|
nums []string
|
|
length int
|
|
)
|
|
nums = strings.Split(str, ".")
|
|
length, err = na.GetColumns()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if length != len(nums) {
|
|
return fmt.Errorf("a vector of length %d was expected, got %d", length, len(nums))
|
|
}
|
|
|
|
vec := make([]float64, length)
|
|
for i, v := range nums {
|
|
vec[i], err = strconv.ParseFloat(v, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
na.Value = vec
|
|
|
|
case DTMat2, DTMat3, DTMat3x4, DTMat4x3, DTMat4:
|
|
// var mat = Matrix.Parse(str);
|
|
// if (mat.cols != na.GetColumns() || mat.rows != na.GetRows()){
|
|
// return errors.New("Invalid column/row count for matrix");
|
|
// }
|
|
// value = mat;
|
|
return errors.New("not implemented")
|
|
|
|
case DTBool:
|
|
na.Value, err = strconv.ParseBool(str)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
case DTString, DTPath, DTFixedString, DTLSString, DTWString, DTLSWString:
|
|
na.Value = str
|
|
|
|
case DTTranslatedString:
|
|
// // We'll only set the value part of the translated string, not the TranslatedStringKey / Handle part
|
|
// // That can be changed separately via attribute.Value.Handle
|
|
// if (value == null)
|
|
// value = new TranslatedString();
|
|
|
|
// ((TranslatedString)value).Value = str;
|
|
|
|
case DTTranslatedFSString:
|
|
// // We'll only set the value part of the translated string, not the TranslatedStringKey / Handle part
|
|
// // That can be changed separately via attribute.Value.Handle
|
|
// if (value == null)
|
|
// value = new TranslatedFSString();
|
|
|
|
// ((TranslatedFSString)value).Value = str;
|
|
|
|
case DTULongLong:
|
|
na.Value, err = strconv.ParseUint(str, 10, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
case DTScratchBuffer:
|
|
na.Value, err = base64.StdEncoding.DecodeString(str)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
case DTLong, DTInt64:
|
|
na.Value, err = strconv.ParseInt(str, 10, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
case DTInt8:
|
|
na.Value, err = strconv.ParseInt(str, 10, 8)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
case DTUUID:
|
|
na.Value, err = uuid.Parse(str)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
default:
|
|
// This should not happen!
|
|
return fmt.Errorf("not implemented for type %v", na.Type)
|
|
}
|
|
return nil
|
|
}
|