Commit 385d856

Ansari <ping@ansari.wtf>
2026-08-24 06:32:49
restructuring
1 parent ac49086
internal/cleanup.go
@@ -0,0 +1,20 @@
+package internal
+
+import "strings"
+
+func cleanupCircuit(c *xmlCircuit) {
+	for i := range c.Components {
+		c.Components[i].DefinitionID = strings.Trim(c.Components[i].DefinitionID, "{}")
+		c.Components[i].Props = nonemptyProps(c.Components[i].Props)
+	}
+}
+
+func nonemptyProps(props []xmlProp) []xmlProp {
+	var out []xmlProp
+	for _, p := range props {
+		if p.Value != "" {
+			out = append(out, p)
+		}
+	}
+	return out
+}
internal/component.go
@@ -7,6 +7,12 @@ import (
 	"strings"
 )
 
+type Circuit struct {
+	Version               string      `yaml:"cddx2yamlVersion"`
+	CircuitDiagramVersion string      `yaml:"circuitDiagramVersion"`
+	Components            []Component `yaml:"components"`
+}
+
 type Component struct {
 	ID    string   `yaml:"id"`
 	Type  string   `yaml:"type"`
@@ -15,70 +21,87 @@ type Component struct {
 	Pins  []string `yaml:"pins,omitempty"`
 }
 
-func propValue(props []xmlProp, key string) (string, bool) {
-	for _, prop := range props {
-		if prop.Key == key {
-			return prop.Value, true
-		}
+type typeCatalog map[string]string
+
+type idCounters map[string]int
+
+func documentFromXML(c *xmlCircuit) *Circuit {
+	types := make(typeCatalog, len(c.Adds))
+	for _, add := range c.Adds {
+		types[add.ID] = add.Item
 	}
-	return "", false
-}
 
-func getLabel(items map[string]string, xmlComp xmlComponent) string {
-	props := xmlComp.Props
-	TP := xmlComp.TP
-	label, hasHeader := propValue(props, "header")
-	if label == "" || !hasHeader {
-		label = items[TP]
+	counters := make(idCounters)
+	components := make([]Component, 0, len(c.Components))
+	for _, xc := range c.Components {
+		components = append(components, xc.toComponent(types, counters))
+	}
+	return &Circuit{
+		Version:               "0.1",
+		CircuitDiagramVersion: c.Version,
+		Components:            components,
 	}
-	return label
 }
 
-func getId(items map[string]string, counters map[string]int, xmlComp xmlComponent) string {
-	id := strings.ToUpper(items[xmlComp.TP][:3])
-	if items[xmlComp.TP] == "microcontroller" {
-		id = "IC"
+func (types typeCatalog) name(definitionID string) string {
+	return types[definitionID]
+}
+
+func (counters idCounters) next(prefix string) string {
+	counters[prefix]++
+	return fmt.Sprintf("%s-%d", prefix, counters[prefix])
+}
+
+func idPrefix(typeName string) string {
+	if typeName == "microcontroller" {
+		return "IC"
+	}
+	if typeName == "" {
+		return "UNK"
 	}
-	counters[id]++
-	id = fmt.Sprintf("%s-%d", id, counters[id])
-	return id
+	if len(typeName) < 3 {
+		return strings.ToUpper(typeName)
+	}
+	return strings.ToUpper(typeName[:3])
 }
 
-func getValue(props []xmlProp) string {
-	value, _ := propValue(props, "value")
+func (c xmlComponent) toComponent(types typeCatalog, counters idCounters) Component {
+	typeName := types.name(c.DefinitionID)
+	label := c.label(types)
+	if typeName == label {
+		label = ""
+	}
+	return Component{
+		ID:    counters.next(idPrefix(typeName)),
+		Type:  typeName,
+		Label: label, 
+		Value: c.electricalValue(),
+		Pins:  c.pins(),
+	}
+}
 
-	listOfKeys := []string{"resistance", "inductance", "frequency", "voltage", "capacitance"}
+func (c xmlComponent) label(types typeCatalog) string {
+	if v, ok := c.prop("header"); ok && v != "" {
+		return v
+	}
+	return types.name(c.DefinitionID)
+}
 
-	for _, key := range listOfKeys {
-		if v, ok := propValue(props, key); ok {
+func (c xmlComponent) electricalValue() string {
+	value, _ := c.prop("value")
+	for _, key := range []string{"resistance", "inductance", "frequency", "voltage", "capacitance"} {
+		if v, ok := c.prop(key); ok {
 			value = v
 		}
 	}
 	return value
 }
 
-func ConvertToComponent(counters map[string]int, items map[string]string, xmlComp xmlComponent) Component {
-
-	label := getLabel(items, xmlComp)
-	id := getId(items, counters, xmlComp)
-	value := getValue(xmlComp.Props)
-
+func (c xmlComponent) pins() []string {
 	pinByIndex := make(map[int]string)
-	for _, p := range xmlComp.Props {
-		if !strings.HasPrefix(p.Key, "p") && !strings.HasPrefix(p.Key, "#") {
-			continue
-		}
-		var idx int
-		err := error(nil)
-		switch {
-		case strings.HasPrefix(p.Key, "p"):
-			idx, err = strconv.Atoi(strings.TrimPrefix(p.Key, "p"))
-		case strings.HasPrefix(p.Key, "#"):
-			idx, err = strconv.Atoi(strings.TrimPrefix(p.Key, "#"))
-		default:
-			continue
-		}
-		if err != nil {
+	for _, p := range c.Props {
+		idx, ok := pinIndexFromPropKey(p.Key)
+		if !ok {
 			continue
 		}
 		pinByIndex[idx] = p.Value
@@ -88,28 +111,32 @@ func ConvertToComponent(counters map[string]int, items map[string]string, xmlCom
 	for idx := range pinByIndex {
 		indices = append(indices, idx)
 	}
-
 	sort.Ints(indices)
 
-
 	var pins []string
-
-	seen := Set()
+	seen := make(map[string]bool)
 	for _, idx := range indices {
 		name := pinByIndex[idx]
 		if seen[name] {
-			//
 			continue
 		}
 		seen[name] = true
 		pins = append(pins, name)
 	}
+	return pins
+}
 
-	return Component{
-		ID:    id,
-		Type:  items[xmlComp.TP],
-		Label: label,
-		Value: value,
-		Pins:  pins,
+func pinIndexFromPropKey(key string) (int, bool) {
+	for _, prefix := range []string{"p", "#"} {
+		rest, ok := strings.CutPrefix(key, prefix)
+		if !ok {
+			continue
+		}
+		idx, err := strconv.Atoi(rest)
+		if err != nil {
+			continue
+		}
+		return idx, true
 	}
+	return 0, false
 }
internal/loader.go
@@ -7,7 +7,16 @@ import (
 	"io"
 )
 
-func LoadFile(path string) (*xmlCircuit, error) {
+func LoadFile(path string) (*Circuit, error) {
+	circuit, err := loadXML(path)
+	if err != nil {
+		return nil, err
+	}
+	cleanupCircuit(circuit)
+	return documentFromXML(circuit), nil
+}
+
+func loadXML(path string) (*xmlCircuit, error) {
 	r, err := zip.OpenReader(path)
 	if err != nil {
 		return nil, fmt.Errorf("failed to open .cddx as a zip archive: %w", err)
@@ -42,20 +51,5 @@ func LoadFile(path string) (*xmlCircuit, error) {
 	if err := xml.Unmarshal(data, &circuit); err != nil {
 		return nil, fmt.Errorf("failed to parse '%s': %w", docPath, err)
 	}
-
-	for i := range circuit.Components{
-
-		var newProps []xmlProp
-		
-		circuit.Components[i].TP = circuit.Components[i].TP[1 : len(circuit.Components[i].TP)-1] // remove the curly braces
-		for _, prop := range circuit.Components[i].Props {
-			if prop.Value != "" {
-				newProps = append(newProps, prop)
-			}
-		}
-		circuit.Components[i].Props = newProps
-	}
-
-
 	return &circuit, nil
 }
internal/xml.go
@@ -1,6 +1,6 @@
 package internal
 
-// Document.xml file (schema version 1.4, as produced by circuit-diagram.org).
+// Document.xml (schema version 1.4, as produced by circuit-diagram.org).
 type xmlCircuit struct {
 	Version    string         `xml:"version,attr"`
 	Adds       []xmlAdd       `xml:"definitions>src>add"`
@@ -13,10 +13,10 @@ type xmlAdd struct {
 }
 
 type xmlComponent struct {
-	ID    string    `xml:"id,attr"`
-	TP    string    `xml:"tp,attr"` // references xmlAdd.ID, formatted as "{N}"
-	Props []xmlProp `xml:"prs>p"`
-	Conns []xmlConn `xml:"cns>cn"`
+	ID           string    `xml:"id,attr"`
+	DefinitionID string    `xml:"tp,attr"` // references xmlAdd.ID, formatted as "{N}"
+	Props        []xmlProp `xml:"prs>p"`
+	Conns        []xmlConn `xml:"cns>cn"`
 }
 
 type xmlProp struct {
@@ -25,6 +25,15 @@ type xmlProp struct {
 }
 
 type xmlConn struct {
-	ID string `xml:"id,attr"`
-	PT string `xml:"pt,attr"` 
+	ID  string `xml:"id,attr"`
+	Pin string `xml:"pt,attr"`
+}
+
+func (c xmlComponent) prop(key string) (string, bool) {
+	for _, p := range c.Props {
+		if p.Key == key {
+			return p.Value, true
+		}
+	}
+	return "", false
 }
main.go
@@ -1,26 +1,41 @@
 package main
 
 import (
-	"cddx2yaml/internal"
 	"fmt"
+	"os"
+
+	"cddx2yaml/internal"
+
+	"gopkg.in/yaml.v3"
 )
 
 func main() {
-	circuit, err := internal.LoadFile("circuit.cddx")
+	path, outputPath := "circuit.cddx", "circuit.yaml"
+
+	if len(os.Args) > 1 {
+		path = os.Args[1]
+	}
+	if len(os.Args) > 2 {
+		outputPath = os.Args[2]
+	}
+	circuit, err := internal.LoadFile(path)
 	if err != nil {
-		fmt.Printf("Error loading file: %v\n", err)
-		return
+		fmt.Fprintf(os.Stderr, "Error loading file: %v\n", err)
+		os.Exit(1)
 	}
 
-	itemByDefID := make(map[string]string)
-	for _, add := range circuit.Adds {
-		itemByDefID[add.ID] = add.Item
+	outputFile, err := os.Create(outputPath)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Error creating output file: %v\n", err)
+		os.Exit(1)
 	}
+	defer outputFile.Close()
 
-	counters := make(map[string]int)
-	for _, comp := range circuit.Components {
-		component := internal.ConvertToComponent(counters, itemByDefID, comp)
-		fmt.Printf("Component: %+v\n", component)
+	encFile := yaml.NewEncoder(outputFile)
+	encFile.SetIndent(2)
+	if err := encFile.Encode(circuit); err != nil {
+		fmt.Fprintf(os.Stderr, "Error encoding YAML to file: %v\n", err)
+		os.Exit(1)
 	}
 
 }