Commit ac49086
2026-08-23 18:43:00
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)
+ }
+
+}