Commit ac49086

Ansari <ping@ansari.wtf>
2026-08-23 18:43:00
initial commit
internal/component.go
@@ -0,0 +1,115 @@
+package internal
+
+import (
+	"fmt"
+	"sort"
+	"strconv"
+	"strings"
+)
+
+type Component struct {
+	ID    string   `yaml:"id"`
+	Type  string   `yaml:"type"`
+	Value string   `yaml:"value,omitempty"`
+	Label string   `yaml:"label,omitempty"`
+	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
+		}
+	}
+	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]
+	}
+	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"
+	}
+	counters[id]++
+	id = fmt.Sprintf("%s-%d", id, counters[id])
+	return id
+}
+
+func getValue(props []xmlProp) string {
+	value, _ := propValue(props, "value")
+
+	listOfKeys := []string{"resistance", "inductance", "frequency", "voltage", "capacitance"}
+
+	for _, key := range listOfKeys {
+		if v, ok := propValue(props, 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)
+
+	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 {
+			continue
+		}
+		pinByIndex[idx] = p.Value
+	}
+
+	indices := make([]int, 0, len(pinByIndex))
+	for idx := range pinByIndex {
+		indices = append(indices, idx)
+	}
+
+	sort.Ints(indices)
+
+
+	var pins []string
+
+	seen := Set()
+	for _, idx := range indices {
+		name := pinByIndex[idx]
+		if seen[name] {
+			//
+			continue
+		}
+		seen[name] = true
+		pins = append(pins, name)
+	}
+
+	return Component{
+		ID:    id,
+		Type:  items[xmlComp.TP],
+		Label: label,
+		Value: value,
+		Pins:  pins,
+	}
+}
internal/loader.go
@@ -0,0 +1,61 @@
+package internal
+
+import (
+	"archive/zip"
+	"encoding/xml"
+	"fmt"
+	"io"
+)
+
+func LoadFile(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)
+	}
+	defer r.Close()
+
+	const docPath = "circuitdiagram/Document.xml"
+
+	var docFile *zip.File
+	for _, f := range r.File {
+		if f.Name == docPath {
+			docFile = f
+			break
+		}
+	}
+	if docFile == nil {
+		return nil, fmt.Errorf("'%s' not found inside archive — not a valid .cddx file", docPath)
+	}
+
+	rc, err := docFile.Open()
+	if err != nil {
+		return nil, fmt.Errorf("failed to open '%s' inside archive: %w", docPath, err)
+	}
+	defer rc.Close()
+
+	data, err := io.ReadAll(rc)
+	if err != nil {
+		return nil, fmt.Errorf("failed to read '%s': %w", docPath, err)
+	}
+
+	var circuit xmlCircuit
+	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/utils.go
@@ -0,0 +1,9 @@
+package internal
+
+func Set(items ...string) map[string]bool {
+	m := make(map[string]bool, len(items))
+	for _, it := range items {
+		m[it] = true
+	}
+	return m
+}
internal/xml.go
@@ -0,0 +1,30 @@
+package internal
+
+// Document.xml file (schema version 1.4, as produced by circuit-diagram.org).
+type xmlCircuit struct {
+	Version    string         `xml:"version,attr"`
+	Adds       []xmlAdd       `xml:"definitions>src>add"`
+	Components []xmlComponent `xml:"elements>c"`
+}
+
+type xmlAdd struct {
+	ID   string `xml:"id,attr"`
+	Item string `xml:"item,attr"`
+}
+
+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"`
+}
+
+type xmlProp struct {
+	Key   string `xml:"k,attr"`
+	Value string `xml:"v,attr"`
+}
+
+type xmlConn struct {
+	ID string `xml:"id,attr"`
+	PT string `xml:"pt,attr"` 
+}
go.mod
@@ -0,0 +1,3 @@
+module cddx2yaml
+
+go 1.26.5
main.go
@@ -0,0 +1,26 @@
+package main
+
+import (
+	"cddx2yaml/internal"
+	"fmt"
+)
+
+func main() {
+	circuit, err := internal.LoadFile("circuit.cddx")
+	if err != nil {
+		fmt.Printf("Error loading file: %v\n", err)
+		return
+	}
+
+	itemByDefID := make(map[string]string)
+	for _, add := range circuit.Adds {
+		itemByDefID[add.ID] = add.Item
+	}
+
+	counters := make(map[string]int)
+	for _, comp := range circuit.Components {
+		component := internal.ConvertToComponent(counters, itemByDefID, comp)
+		fmt.Printf("Component: %+v\n", component)
+	}
+
+}