196 lines
No EOL
5.5 KiB
Go
196 lines
No EOL
5.5 KiB
Go
package parser
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"strings"
|
|
"path/filepath"
|
|
)
|
|
|
|
// THE HYLIA PARSER
|
|
|
|
type Element struct {
|
|
Name string
|
|
Content string
|
|
FilePath string
|
|
NestedElements []Element
|
|
Variables map[string]string
|
|
}
|
|
|
|
func ParseFile(filename string) ([]Element, map[string]string, error) {
|
|
// Hey, does the file we're trying to parse actually exist?
|
|
data, err := ioutil.ReadFile(filename)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
elements, variables, err := parseElements(string(data), filename)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failed to parse elements: %w", err)
|
|
}
|
|
|
|
importedElements := []Element{}
|
|
importedVariables := map[string]string{}
|
|
|
|
for _, element := range elements {
|
|
if element.Name == "import" {
|
|
importPath := extractAttributeValue("import", "src")
|
|
if importPath == "" {
|
|
return nil, nil, fmt.Errorf("Import is missing a source ('src' attribute)")
|
|
}
|
|
|
|
importFullPath := resolvePath(filename, importPath)
|
|
|
|
childElements, childVariables, err := ParseFile(importFullPath)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failed to parse elements: %w", err)
|
|
}
|
|
|
|
importedElements = append(importedElements, childElements...)
|
|
for k, v := range childVariables {
|
|
importedVariables[k] = v
|
|
}
|
|
}
|
|
}
|
|
|
|
for k, v := range importedVariables {
|
|
if _, exists := variables[k]; !exists {
|
|
variables[k] = v
|
|
}
|
|
}
|
|
|
|
elements = append(importedElements, elements...)
|
|
|
|
return elements, variables, nil
|
|
|
|
}
|
|
|
|
func resolvePath(basePath, relativePath string) string {
|
|
dir := filepath.Dir(basePath)
|
|
return filepath.Join(dir, relativePath)
|
|
}
|
|
|
|
func parseElements(content, filename string) ([]Element, map[string]string, error) {
|
|
elements := []Element{}
|
|
variables := make(map[string]string)
|
|
classes := make(map[string]Element)
|
|
|
|
lines := strings.Split(content, "\n")
|
|
var currentElement *Element
|
|
var nestedContent strings.Builder
|
|
|
|
for _, line := range lines {
|
|
line = strings.TrimSpace(line)
|
|
if strings.HasPrefix(line, "<var ") {
|
|
name := extractAttributeValue(line, "name")
|
|
if name == "" {
|
|
return nil, nil, errors.New("Variable is missing a name ('name' attribute)")
|
|
}
|
|
valueStart := strings.Index(line, ">")
|
|
valueEnd := strings.Index(line, "</var>")
|
|
if valueStart == -1 || valueEnd == -1 {
|
|
return nil, nil, errors.New("invalid variable structure")
|
|
}
|
|
value := strings.TrimSpace(line[valueStart+1 : valueEnd])
|
|
variables[name] = value
|
|
|
|
} else if strings.HasPrefix(line, "<element") {
|
|
if currentElement != nil {
|
|
currentElement.Content = nestedContent.String()
|
|
parsedNested, nestedVars, _ := parseElements(nestedContent.String(), filename)
|
|
currentElement.NestedElements = parsedNested
|
|
mergeVariables(variables, nestedVars)
|
|
elements = append(elements, *currentElement)
|
|
nestedContent.Reset()
|
|
}
|
|
name := extractAttributeValue(line, "name")
|
|
if name == "" {
|
|
return nil, nil, errors.New("Element is missing a name ('name' attribute)")
|
|
}
|
|
currentElement = &Element{Name: name, FilePath: filename}
|
|
} else if strings.HasPrefix(line, "</element>") {
|
|
if currentElement != nil {
|
|
currentElement.Content = nestedContent.String()
|
|
parsedNested, nestedVars, _ := parseElements(nestedContent.String(), filename)
|
|
currentElement.NestedElements = parsedNested
|
|
mergeVariables(variables, nestedVars)
|
|
elements = append(elements, *currentElement)
|
|
nestedContent.Reset()
|
|
currentElement = nil
|
|
}
|
|
} else if strings.HasPrefix(line, "<class") {
|
|
if currentElement != nil {
|
|
currentElement.Content = nestedContent.String()
|
|
parsedNested, nestedVars, _ := parseElements(nestedContent.String(), filename)
|
|
currentElement.NestedElements = parsedNested
|
|
mergeVariables(variables, nestedVars)
|
|
elements = append(elements, *currentElement)
|
|
nestedContent.Reset()
|
|
currentElement = nil
|
|
}
|
|
|
|
name := extractAttributeValue(line, "name")
|
|
if name == "" {
|
|
return nil, nil, errors.New("class is missing a name ('name' attribute)")
|
|
}
|
|
|
|
currentElement = &Element{Name: name, FilePath: filename}
|
|
} else if strings.HasPrefix(line, "</class>") {
|
|
if currentElement != nil {
|
|
currentElement.Content = nestedContent.String()
|
|
parsedNested, nestedVars, _ := parseElements(nestedContent.String(), filename)
|
|
currentElement.NestedElements = parsedNested
|
|
mergeVariables(variables, nestedVars)
|
|
if classes == nil {
|
|
classes = make(map[string]Element)
|
|
}
|
|
classes[currentElement.Name] = *currentElement
|
|
|
|
nestedContent.Reset()
|
|
currentElement = nil
|
|
}
|
|
|
|
} else {
|
|
|
|
if currentElement != nil {
|
|
nestedContent.WriteString(line + "\n")
|
|
}
|
|
}
|
|
}
|
|
return elements, variables, nil
|
|
}
|
|
|
|
func mergeVariables(target, source map[string]string) {
|
|
for key, value := range source {
|
|
target[key] = value
|
|
}
|
|
}
|
|
|
|
// Extracts an attribute
|
|
func extractAttributeValue(tag, attribute string) string {
|
|
prefix := fmt.Sprintf(`%s="`, attribute)
|
|
start := strings.Index(tag, prefix)
|
|
if start == -1 {
|
|
return ""
|
|
}
|
|
start += len(prefix)
|
|
end := strings.Index(tag[start:], `"`)
|
|
if end == -1 {
|
|
return ""
|
|
}
|
|
return tag[start : start+end]
|
|
}
|
|
|
|
// Extracts an <element>
|
|
func extractElementContent(tag, content string) (string, error) {
|
|
start := strings.Index(content, tag)
|
|
if start == -1 {
|
|
return "", errors.New("element tag not found in content")
|
|
}
|
|
end := strings.Index(content[start:], "</element>")
|
|
if end == -1 {
|
|
return "", errors.New("missing end tag of element")
|
|
}
|
|
return content[start+len(tag) : start+end], nil
|
|
} |