mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-17 08:14:25 +08:00
code refactor
This commit is contained in:
@@ -0,0 +1,321 @@
|
||||
package sgml
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
)
|
||||
|
||||
const sgmlTagName = "sgml"
|
||||
const sgmlNameFieldName = "SGMLName"
|
||||
const xmlTagName = "xml" // reuse xml tag
|
||||
const xmlNameFieldName = "XMLName" // reuse xml tag
|
||||
|
||||
// sgmlFieldType represents SGML field type
|
||||
type sgmlFieldType byte
|
||||
|
||||
// Transaction template types
|
||||
const (
|
||||
sgmlNotSupportedField sgmlFieldType = 0
|
||||
sgmlTextualField sgmlFieldType = 1
|
||||
sgmlStructField sgmlFieldType = 2
|
||||
sgmlStructSliceField sgmlFieldType = 3
|
||||
)
|
||||
|
||||
// sgmlTypeInfo represents the struct of SGML type reflection info
|
||||
type sgmlTypeInfo struct {
|
||||
supportedFields map[string]*sgmlFieldInfo
|
||||
}
|
||||
|
||||
// sgmlFieldInfo represents the struct of SGML field info
|
||||
type sgmlFieldInfo struct {
|
||||
sgmlFieldName string
|
||||
sgmlFieldType sgmlFieldType
|
||||
structFieldName string
|
||||
}
|
||||
|
||||
type Decoder struct {
|
||||
xmlDecoder *xml.Decoder
|
||||
}
|
||||
|
||||
var sgmlTypeInfoMap sync.Map // map[reflect.Type]*typeInfo
|
||||
|
||||
// Decode unmarshal the specified struct instance and returns whether error occurs
|
||||
func (d *Decoder) Decode(v any) error {
|
||||
value := reflect.ValueOf(v).Elem()
|
||||
finalValue := value
|
||||
finalType := value.Type()
|
||||
|
||||
for finalValue.Kind() == reflect.Pointer {
|
||||
finalValue = value.Elem()
|
||||
finalType = finalValue.Type()
|
||||
}
|
||||
|
||||
rootNameField, exists := finalType.FieldByName(sgmlNameFieldName)
|
||||
|
||||
if !exists {
|
||||
rootNameField, exists = finalType.FieldByName(xmlNameFieldName)
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
rootElementName := rootNameField.Tag.Get(sgmlTagName)
|
||||
|
||||
if rootElementName == "" {
|
||||
rootElementName = rootNameField.Tag.Get(xmlTagName)
|
||||
}
|
||||
|
||||
for {
|
||||
token, err := d.xmlDecoder.RawToken()
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
switch token := token.(type) {
|
||||
case xml.StartElement:
|
||||
if token.Name.Local == rootElementName {
|
||||
return d.unmarshal(value.Elem(), rootElementName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) unmarshal(element reflect.Value, elementName string) error {
|
||||
typeInfo, err := d.getStructTypeInfo(element.Type())
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if typeInfo == nil {
|
||||
return errs.ErrInvalidSGMLFile
|
||||
}
|
||||
|
||||
textualFieldWithoutEndElementNames := make(map[string]bool)
|
||||
textualFieldValues := make(map[string]string)
|
||||
|
||||
hasEndElement := false
|
||||
currentSGMLFieldName := ""
|
||||
|
||||
for {
|
||||
token, err := d.xmlDecoder.RawToken()
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
switch token := token.(type) {
|
||||
case xml.StartElement:
|
||||
if fieldInfo, exists := typeInfo.supportedFields[token.Name.Local]; exists {
|
||||
if fieldInfo.sgmlFieldType == sgmlStructField || fieldInfo.sgmlFieldType == sgmlStructSliceField {
|
||||
field := element.FieldByName(fieldInfo.structFieldName)
|
||||
childElementType := field.Type()
|
||||
childElementKind := field.Kind()
|
||||
var childElement reflect.Value
|
||||
|
||||
if fieldInfo.sgmlFieldType == sgmlStructSliceField {
|
||||
childElementType = childElementType.Elem()
|
||||
childElementKind = childElementType.Kind()
|
||||
}
|
||||
|
||||
if childElementKind == reflect.Pointer {
|
||||
childElement = reflect.New(childElementType.Elem())
|
||||
} else if childElementKind == reflect.Struct {
|
||||
childElement = reflect.New(childElementType)
|
||||
}
|
||||
|
||||
err := d.unmarshal(childElement.Elem(), fieldInfo.sgmlFieldName)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if childElementKind == reflect.Struct {
|
||||
childElement = childElement.Elem()
|
||||
}
|
||||
|
||||
if fieldInfo.sgmlFieldType == sgmlStructField {
|
||||
field.Set(childElement)
|
||||
} else if fieldInfo.sgmlFieldType == sgmlStructSliceField {
|
||||
if field.Len() == 0 {
|
||||
slice := reflect.MakeSlice(reflect.SliceOf(childElement.Type()), 0, 0)
|
||||
field.Set(reflect.Append(slice, childElement))
|
||||
} else {
|
||||
field.Set(reflect.Append(field.Addr().Elem(), childElement))
|
||||
}
|
||||
}
|
||||
} else if fieldInfo.sgmlFieldType == sgmlTextualField {
|
||||
currentSGMLFieldName = token.Name.Local
|
||||
textualFieldWithoutEndElementNames[token.Name.Local] = true
|
||||
}
|
||||
}
|
||||
case xml.EndElement:
|
||||
if fieldInfo, exists := typeInfo.supportedFields[token.Name.Local]; exists {
|
||||
if fieldInfo.sgmlFieldType == sgmlTextualField {
|
||||
delete(textualFieldWithoutEndElementNames, token.Name.Local)
|
||||
}
|
||||
} else if token.Name.Local == elementName {
|
||||
hasEndElement = true
|
||||
break
|
||||
}
|
||||
case xml.CharData:
|
||||
if currentSGMLFieldName != "" {
|
||||
if fieldInfo, exists := typeInfo.supportedFields[currentSGMLFieldName]; exists {
|
||||
if fieldInfo.sgmlFieldType == sgmlTextualField {
|
||||
textualFieldValues[currentSGMLFieldName] = string(token)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
currentSGMLFieldName = ""
|
||||
}
|
||||
|
||||
if hasEndElement {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !hasEndElement {
|
||||
return errs.ErrInvalidSGMLFile
|
||||
}
|
||||
|
||||
for sgmlFieldName, fieldValue := range textualFieldValues {
|
||||
finalValue := d.getActualFieldValue(sgmlFieldName, fieldValue, textualFieldWithoutEndElementNames)
|
||||
fieldInfo, exists := typeInfo.supportedFields[sgmlFieldName]
|
||||
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
|
||||
field := element.FieldByName(fieldInfo.structFieldName)
|
||||
field.SetString(finalValue)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) getStructTypeInfo(reflectType reflect.Type) (*sgmlTypeInfo, error) {
|
||||
if reflectType.Kind() != reflect.Struct {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
typeInfo, exists := sgmlTypeInfoMap.Load(reflectType)
|
||||
|
||||
if exists {
|
||||
return typeInfo.(*sgmlTypeInfo), nil
|
||||
}
|
||||
|
||||
newTypeInfo := &sgmlTypeInfo{
|
||||
supportedFields: make(map[string]*sgmlFieldInfo),
|
||||
}
|
||||
|
||||
for i := 0; i < reflectType.NumField(); i++ {
|
||||
field := reflectType.Field(i)
|
||||
|
||||
if field.Anonymous {
|
||||
fieldType := field.Type
|
||||
|
||||
if fieldType.Kind() == reflect.Struct {
|
||||
fieldSgmlTypeInfo, err := d.getStructTypeInfo(fieldType)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for sgmlFieldName, fieldInfo := range fieldSgmlTypeInfo.supportedFields {
|
||||
newTypeInfo.supportedFields[sgmlFieldName] = fieldInfo
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
} else if !field.IsExported() {
|
||||
continue
|
||||
}
|
||||
|
||||
sgmlFieldName := field.Tag.Get(sgmlTagName)
|
||||
|
||||
if sgmlFieldName == "" {
|
||||
sgmlFieldName = field.Tag.Get(xmlTagName)
|
||||
}
|
||||
|
||||
if sgmlFieldName == "" || field.Name == sgmlNameFieldName || field.Name == xmlNameFieldName {
|
||||
continue
|
||||
}
|
||||
|
||||
sgmlFieldType := sgmlNotSupportedField
|
||||
finalFieldType := field.Type
|
||||
|
||||
for finalFieldType.Kind() == reflect.Pointer {
|
||||
finalFieldType = finalFieldType.Elem()
|
||||
}
|
||||
|
||||
switch finalFieldType.Kind() {
|
||||
case reflect.String:
|
||||
sgmlFieldType = sgmlTextualField
|
||||
case reflect.Struct:
|
||||
sgmlFieldType = sgmlStructField
|
||||
case reflect.Slice:
|
||||
childFinalFieldType := finalFieldType.Elem()
|
||||
|
||||
for childFinalFieldType.Kind() == reflect.Pointer {
|
||||
childFinalFieldType = childFinalFieldType.Elem()
|
||||
}
|
||||
|
||||
if childFinalFieldType.Kind() == reflect.Struct {
|
||||
sgmlFieldType = sgmlStructSliceField
|
||||
}
|
||||
default:
|
||||
sgmlFieldType = sgmlNotSupportedField
|
||||
}
|
||||
|
||||
if sgmlFieldType == sgmlNotSupportedField {
|
||||
return nil, errs.ErrInvalidSGMLFile
|
||||
}
|
||||
|
||||
newTypeInfo.supportedFields[sgmlFieldName] = &sgmlFieldInfo{
|
||||
sgmlFieldName: sgmlFieldName,
|
||||
sgmlFieldType: sgmlFieldType,
|
||||
structFieldName: field.Name,
|
||||
}
|
||||
}
|
||||
|
||||
typeInfo, _ = sgmlTypeInfoMap.LoadOrStore(reflectType, newTypeInfo)
|
||||
|
||||
return typeInfo.(*sgmlTypeInfo), nil
|
||||
}
|
||||
|
||||
func (d *Decoder) getActualFieldValue(fieldName string, fieldValue string, textualFieldWithoutEndElementNames map[string]bool) string {
|
||||
_, notHasEndElement := textualFieldWithoutEndElementNames[fieldName]
|
||||
|
||||
if !notHasEndElement {
|
||||
return fieldValue
|
||||
}
|
||||
|
||||
for i := 0; i < len(fieldValue); i++ {
|
||||
if fieldValue[i] == '\r' || fieldValue[i] == '\n' {
|
||||
return fieldValue[0:i]
|
||||
}
|
||||
}
|
||||
|
||||
return fieldValue
|
||||
}
|
||||
|
||||
// NewDecoder creates a new SGML parser reading from specified io reader
|
||||
func NewDecoder(reader io.Reader) *Decoder {
|
||||
xmlDecoder := xml.NewDecoder(reader)
|
||||
xmlDecoder.CharsetReader = func(charset string, input io.Reader) (io.Reader, error) {
|
||||
return input, nil
|
||||
}
|
||||
|
||||
return &Decoder{
|
||||
xmlDecoder: xmlDecoder,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user