From e33baee08e4bc51b37255d9e3047fa3598e2fd8b Mon Sep 17 00:00:00 2001 From: Yogesh Date: Thu, 20 Nov 2025 19:12:36 +0530 Subject: [PATCH] wsscan: document final params --- proto/wsscan/attributedelement.go | 115 ++++++++ proto/wsscan/attributedelement_test.go | 251 +++++++++++++++++ proto/wsscan/compressionqualityfactor.go | 44 +++ proto/wsscan/compressionqualityfactor_test.go | 197 ++++++++++++++ proto/wsscan/contenttype.go | 38 +++ proto/wsscan/contenttype_test.go | 189 +++++++++++++ proto/wsscan/exposure.go | 70 +++++ proto/wsscan/exposure_test.go | 207 ++++++++++++++ proto/wsscan/filmscanmodeelement.go | 42 +++ proto/wsscan/filmscanmodeelement_test.go | 194 ++++++++++++++ proto/wsscan/formatelement.go | 45 ++++ proto/wsscan/formatelement_test.go | 253 ++++++++++++++++++ proto/wsscan/imagestotransfer.go | 45 ++++ proto/wsscan/imagestotransfer_test.go | 249 +++++++++++++++++ proto/wsscan/inputsource.go | 37 +++ proto/wsscan/inputsource_test.go | 199 ++++++++++++++ 16 files changed, 2175 insertions(+) create mode 100644 proto/wsscan/attributedelement.go create mode 100644 proto/wsscan/attributedelement_test.go create mode 100644 proto/wsscan/compressionqualityfactor.go create mode 100644 proto/wsscan/compressionqualityfactor_test.go create mode 100644 proto/wsscan/contenttype.go create mode 100644 proto/wsscan/contenttype_test.go create mode 100644 proto/wsscan/exposure.go create mode 100644 proto/wsscan/exposure_test.go create mode 100644 proto/wsscan/filmscanmodeelement.go create mode 100644 proto/wsscan/filmscanmodeelement_test.go create mode 100644 proto/wsscan/formatelement.go create mode 100644 proto/wsscan/formatelement_test.go create mode 100644 proto/wsscan/imagestotransfer.go create mode 100644 proto/wsscan/imagestotransfer_test.go create mode 100644 proto/wsscan/inputsource.go create mode 100644 proto/wsscan/inputsource_test.go diff --git a/proto/wsscan/attributedelement.go b/proto/wsscan/attributedelement.go new file mode 100644 index 0000000..21ecbdd --- /dev/null +++ b/proto/wsscan/attributedelement.go @@ -0,0 +1,115 @@ +// MFP - Multi-Function Printers and scanners toolkit +// WS-Scan core protocol +// +// Copyright (C) 2024 and up by Yogesh Singla (yogeshsingla481@gmail.com) +// See LICENSE for license terms and conditions +// +// AttributedElement: reusable type for elements with +// text value and optional wscn:MustHonor, wscn:Override, wscn:UsedDefault attributes + +package wsscan + +import ( + "fmt" + + "github.com/OpenPrinting/go-mfp/util/optional" + "github.com/OpenPrinting/go-mfp/util/xmldoc" +) + +// AttributedElement holds a value and optional wscn:MustHonor, wscn:Override, +// and wscn:UsedDefault attributes. +// +// The attributes are xs:string but must be boolean values: "0", "1", "false", or "true" +// (case-insensitive, whitespace ignored). +// +// This type is generic and can be used for elements like +// that have these attributes along with text content. +type AttributedElement[T any] struct { + Value T + MustHonor optional.Val[BooleanElement] + Override optional.Val[BooleanElement] + UsedDefault optional.Val[BooleanElement] +} + +// decodeAttributedElement fills the struct from an XML element. +// +// decodeValue is a function that decodes the value type T from a string. +func decodeAttributedElement[T any]( + root xmldoc.Element, + decodeValue func(string) (T, error), +) (AttributedElement[T], error) { + var elem AttributedElement[T] + + // Decode the value from text content + var err error + elem.Value, err = decodeValue(root.Text) + if err != nil { + return elem, err + } + + // Decode optional attributes with validation + if attr, found := root.AttrByName(NsWSCN + ":MustHonor"); found { + mustHonor := BooleanElement(attr.Value) + if err := mustHonor.Validate(); err != nil { + return elem, xmldoc.XMLErrWrap(root, fmt.Errorf("mustHonor: %w", err)) + } + elem.MustHonor = optional.New(mustHonor) + } + if attr, found := root.AttrByName(NsWSCN + ":Override"); found { + override := BooleanElement(attr.Value) + if err := override.Validate(); err != nil { + return elem, xmldoc.XMLErrWrap(root, fmt.Errorf("override: %w", err)) + } + elem.Override = optional.New(override) + } + if attr, found := root.AttrByName(NsWSCN + ":UsedDefault"); found { + usedDefault := BooleanElement(attr.Value) + if err := usedDefault.Validate(); err != nil { + return elem, xmldoc.XMLErrWrap(root, fmt.Errorf("usedDefault: %w", err)) + } + elem.UsedDefault = optional.New(usedDefault) + } + + return elem, nil +} + +// toXML creates an XML element from the struct. +// +// name is the XML element name (e.g., "wscn:Rotation"). +// valueToString converts the value type T to its string representation. +func (a AttributedElement[T]) toXML( + name string, + valueToString func(T) string, +) xmldoc.Element { + elm := xmldoc.Element{ + Name: name, + Text: valueToString(a.Value), + } + + // Add optional attributes if present + attrs := make([]xmldoc.Attr, 0, 3) + if mustHonor := optional.Get(a.MustHonor); mustHonor != "" { + attrs = append(attrs, xmldoc.Attr{ + Name: NsWSCN + ":MustHonor", + Value: string(mustHonor), + }) + } + if override := optional.Get(a.Override); override != "" { + attrs = append(attrs, xmldoc.Attr{ + Name: NsWSCN + ":Override", + Value: string(override), + }) + } + if usedDefault := optional.Get(a.UsedDefault); usedDefault != "" { + attrs = append(attrs, xmldoc.Attr{ + Name: NsWSCN + ":UsedDefault", + Value: string(usedDefault), + }) + } + + if len(attrs) > 0 { + elm.Attrs = attrs + } + + return elm +} diff --git a/proto/wsscan/attributedelement_test.go b/proto/wsscan/attributedelement_test.go new file mode 100644 index 0000000..6fe7a85 --- /dev/null +++ b/proto/wsscan/attributedelement_test.go @@ -0,0 +1,251 @@ +// MFP - Multi-Function Printers and scanners toolkit +// WS-Scan core protocol +// +// Copyright (C) 2024 and up by Yogesh Singla (yogeshsingla481@gmail.com) +// See LICENSE for license terms and conditions +// +// Test for AttributedElement + +package wsscan + +import ( + "reflect" + "testing" + + "github.com/OpenPrinting/go-mfp/util/optional" + "github.com/OpenPrinting/go-mfp/util/xmldoc" +) + +func TestAttributedElement_RoundTrip(t *testing.T) { + orig := AttributedElement[RotationValue]{ + Value: Rotation90, + MustHonor: optional.New(BooleanElement("true")), + Override: optional.New(BooleanElement("false")), + UsedDefault: optional.New(BooleanElement("true")), + } + + elm := orig.toXML(NsWSCN+":Rotation", func(rv RotationValue) string { + return rv.String() + }) + + if elm.Name != NsWSCN+":Rotation" { + t.Errorf("expected element name '%s', got '%s'", NsWSCN+":Rotation", elm.Name) + } + if elm.Text != "90" { + t.Errorf("expected text '90', got '%s'", elm.Text) + } + if len(elm.Attrs) != 3 { + t.Errorf("expected 3 attributes, got %d", len(elm.Attrs)) + } + + // Check attributes + attrsMap := make(map[string]string) + for _, attr := range elm.Attrs { + attrsMap[attr.Name] = attr.Value + } + if attrsMap[NsWSCN+":MustHonor"] != "true" { + t.Errorf("expected MustHonor='true', got '%s'", attrsMap[NsWSCN+":MustHonor"]) + } + if attrsMap[NsWSCN+":Override"] != "false" { + t.Errorf("expected Override='false', got '%s'", attrsMap[NsWSCN+":Override"]) + } + if attrsMap[NsWSCN+":UsedDefault"] != "true" { + t.Errorf("expected UsedDefault='true', got '%s'", attrsMap[NsWSCN+":UsedDefault"]) + } + + // Decode back + decoded, err := decodeAttributedElement(elm, func(s string) (RotationValue, error) { + return DecodeRotationValue(s), nil + }) + if err != nil { + t.Fatalf("decode returned error: %v", err) + } + if decoded.Value != orig.Value { + t.Errorf("expected value %v, got %v", orig.Value, decoded.Value) + } + if !reflect.DeepEqual(orig.MustHonor, decoded.MustHonor) { + t.Errorf("expected MustHonor %+v, got %+v", orig.MustHonor, decoded.MustHonor) + } + if !reflect.DeepEqual(orig.Override, decoded.Override) { + t.Errorf("expected Override %+v, got %+v", orig.Override, decoded.Override) + } + if !reflect.DeepEqual(orig.UsedDefault, decoded.UsedDefault) { + t.Errorf("expected UsedDefault %+v, got %+v", orig.UsedDefault, decoded.UsedDefault) + } +} + +func TestAttributedElement_NoAttributes(t *testing.T) { + orig := AttributedElement[RotationValue]{ + Value: Rotation180, + } + + elm := orig.toXML(NsWSCN+":Rotation", func(rv RotationValue) string { + return rv.String() + }) + + if len(elm.Attrs) != 0 { + t.Errorf("expected no attributes, got %+v", elm.Attrs) + } + + decoded, err := decodeAttributedElement(elm, func(s string) (RotationValue, error) { + return DecodeRotationValue(s), nil + }) + if err != nil { + t.Fatalf("decode returned error: %v", err) + } + if decoded.Value != orig.Value { + t.Errorf("expected value %v, got %v", orig.Value, decoded.Value) + } +} + +func TestAttributedElement_StringValue(t *testing.T) { + orig := AttributedElement[string]{ + Value: "some-value", + MustHonor: optional.New(BooleanElement("1")), + } + + elm := orig.toXML(NsWSCN+":SomeElement", func(s string) string { + return s + }) + + if elm.Text != "some-value" { + t.Errorf("expected text 'some-value', got '%s'", elm.Text) + } + if len(elm.Attrs) != 1 { + t.Errorf("expected 1 attribute, got %d", len(elm.Attrs)) + } + + decoded, err := decodeAttributedElement(elm, func(s string) (string, error) { + return s, nil + }) + if err != nil { + t.Fatalf("decode returned error: %v", err) + } + if decoded.Value != orig.Value { + t.Errorf("expected value %v, got %v", orig.Value, decoded.Value) + } +} + +func TestAttributedElement_FromXML(t *testing.T) { + // Create XML element manually + root := xmldoc.Element{ + Name: NsWSCN + ":Rotation", + Text: "270", + Attrs: []xmldoc.Attr{ + {Name: NsWSCN + ":MustHonor", Value: "false"}, + {Name: NsWSCN + ":Override", Value: "true"}, + }, + } + + decoded, err := decodeAttributedElement(root, func(s string) (RotationValue, error) { + val := DecodeRotationValue(s) + if val == UnknownRotationValue { + return val, xmldoc.XMLErrWrap(root, nil) + } + return val, nil + }) + if err != nil { + t.Fatalf("decode returned error: %v", err) + } + + if decoded.Value != Rotation270 { + t.Errorf("expected Rotation270, got %v", decoded.Value) + } + if mustHonor := optional.Get(decoded.MustHonor); string(mustHonor) != "false" { + t.Errorf("expected MustHonor='false', got '%s'", mustHonor) + } + if override := optional.Get(decoded.Override); string(override) != "true" { + t.Errorf("expected Override='true', got '%s'", override) + } + if usedDefault := optional.Get(decoded.UsedDefault); usedDefault != "" { + t.Errorf("expected empty UsedDefault, got '%s'", usedDefault) + } +} + +func TestAttributedElement_InvalidBooleanAttributes(t *testing.T) { + tests := []struct { + name string + attr string + value string + wantErr bool + }{ + { + name: "valid true", + attr: "MustHonor", + value: "true", + wantErr: false, + }, + { + name: "valid false", + attr: "MustHonor", + value: "false", + wantErr: false, + }, + { + name: "valid 1", + attr: "MustHonor", + value: "1", + wantErr: false, + }, + { + name: "valid 0", + attr: "MustHonor", + value: "0", + wantErr: false, + }, + { + name: "invalid value", + attr: "MustHonor", + value: "invalid", + wantErr: true, + }, + { + name: "invalid empty", + attr: "MustHonor", + value: "", + wantErr: true, + }, + { + name: "invalid Override", + attr: "Override", + value: "yes", + wantErr: true, + }, + { + name: "invalid UsedDefault", + attr: "UsedDefault", + value: "maybe", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + root := xmldoc.Element{ + Name: NsWSCN + ":Rotation", + Text: "90", + Attrs: []xmldoc.Attr{ + {Name: NsWSCN + ":" + tt.attr, Value: tt.value}, + }, + } + + _, err := decodeAttributedElement(root, func(s string) (RotationValue, error) { + val := DecodeRotationValue(s) + if val == UnknownRotationValue { + return val, xmldoc.XMLErrWrap(root, nil) + } + return val, nil + }) + + if tt.wantErr { + if err == nil { + t.Errorf("expected error for %s='%s', got nil", tt.attr, tt.value) + } + } else { + if err != nil { + t.Errorf("unexpected error for %s='%s': %v", tt.attr, tt.value, err) + } + } + }) + } +} diff --git a/proto/wsscan/compressionqualityfactor.go b/proto/wsscan/compressionqualityfactor.go new file mode 100644 index 0000000..a43bd5b --- /dev/null +++ b/proto/wsscan/compressionqualityfactor.go @@ -0,0 +1,44 @@ +// MFP - Multi-Function Printers and scanners toolkit +// WS-Scan core protocol +// +// Copyright (C) 2024 and up by Yogesh Singla (yogeshsingla481@gmail.com) +// See LICENSE for license terms and conditions +// +// CompressionQualityFactor element + +package wsscan + +import ( + "fmt" + "strconv" + + "github.com/OpenPrinting/go-mfp/util/xmldoc" +) + +// CompressionQualityFactor represents the optional +// element that specifies an idealized integer amount of image quality, +// on a scale from 0 through 100. +// +// It includes optional wscn:MustHonor, wscn:Override, and wscn:UsedDefault +// attributes (all xs:string, but should be boolean values: 0, false, 1, or true). +type CompressionQualityFactor = AttributedElement[int] + +// decodeCompressionQualityFactor decodes [CompressionQualityFactor] from the XML tree. +func decodeCompressionQualityFactor(root xmldoc.Element) ( + CompressionQualityFactor, error) { + return decodeAttributedElement(root, func(s string) (int, error) { + val, err := strconv.Atoi(s) + if err != nil { + return 0, fmt.Errorf("invalid integer: %q", s) + } + if val < 0 || val > 100 { + return 0, fmt.Errorf("value out of range [0-100]: %d", val) + } + return val, nil + }) +} + +// toXMLCompressionQualityFactor generates XML tree for the [CompressionQualityFactor]. +func toXMLCompressionQualityFactor(cqf CompressionQualityFactor, name string) xmldoc.Element { + return cqf.toXML(name, strconv.Itoa) +} diff --git a/proto/wsscan/compressionqualityfactor_test.go b/proto/wsscan/compressionqualityfactor_test.go new file mode 100644 index 0000000..45fb824 --- /dev/null +++ b/proto/wsscan/compressionqualityfactor_test.go @@ -0,0 +1,197 @@ +// MFP - Multi-Function Printers and scanners toolkit +// WS-Scan core protocol +// +// Copyright (C) 2024 and up by Yogesh Singla (yogeshsingla481@gmail.com) +// See LICENSE for license terms and conditions +// +// Test for CompressionQualityFactor + +package wsscan + +import ( + "reflect" + "testing" + + "github.com/OpenPrinting/go-mfp/util/optional" + "github.com/OpenPrinting/go-mfp/util/xmldoc" +) + +func TestCompressionQualityFactor_RoundTrip(t *testing.T) { + orig := CompressionQualityFactor{ + Value: 85, + MustHonor: optional.New(BooleanElement("true")), + Override: optional.New(BooleanElement("false")), + UsedDefault: optional.New(BooleanElement("1")), + } + + elm := toXMLCompressionQualityFactor(orig, NsWSCN+":CompressionQualityFactor") + + if elm.Name != NsWSCN+":CompressionQualityFactor" { + t.Errorf("expected element name '%s', got '%s'", + NsWSCN+":CompressionQualityFactor", elm.Name) + } + if elm.Text != "85" { + t.Errorf("expected text '85', got '%s'", elm.Text) + } + if len(elm.Attrs) != 3 { + t.Errorf("expected 3 attributes, got %d", len(elm.Attrs)) + } + + // Check attributes + attrsMap := make(map[string]string) + for _, attr := range elm.Attrs { + attrsMap[attr.Name] = attr.Value + } + if attrsMap[NsWSCN+":MustHonor"] != "true" { + t.Errorf("expected MustHonor='true', got '%s'", attrsMap[NsWSCN+":MustHonor"]) + } + if attrsMap[NsWSCN+":Override"] != "false" { + t.Errorf("expected Override='false', got '%s'", attrsMap[NsWSCN+":Override"]) + } + if attrsMap[NsWSCN+":UsedDefault"] != "1" { + t.Errorf("expected UsedDefault='1', got '%s'", attrsMap[NsWSCN+":UsedDefault"]) + } + + // Decode back + decoded, err := decodeCompressionQualityFactor(elm) + if err != nil { + t.Fatalf("decode returned error: %v", err) + } + if decoded.Value != orig.Value { + t.Errorf("expected value %v, got %v", orig.Value, decoded.Value) + } + if !reflect.DeepEqual(orig.MustHonor, decoded.MustHonor) { + t.Errorf("expected MustHonor %+v, got %+v", orig.MustHonor, decoded.MustHonor) + } + if !reflect.DeepEqual(orig.Override, decoded.Override) { + t.Errorf("expected Override %+v, got %+v", orig.Override, decoded.Override) + } + if !reflect.DeepEqual(orig.UsedDefault, decoded.UsedDefault) { + t.Errorf("expected UsedDefault %+v, got %+v", orig.UsedDefault, decoded.UsedDefault) + } +} + +func TestCompressionQualityFactor_NoAttributes(t *testing.T) { + orig := CompressionQualityFactor{ + Value: 50, + } + + elm := toXMLCompressionQualityFactor(orig, NsWSCN+":CompressionQualityFactor") + + if len(elm.Attrs) != 0 { + t.Errorf("expected no attributes, got %+v", elm.Attrs) + } + if elm.Text != "50" { + t.Errorf("expected text '50', got '%s'", elm.Text) + } + + decoded, err := decodeCompressionQualityFactor(elm) + if err != nil { + t.Fatalf("decode returned error: %v", err) + } + if decoded.Value != orig.Value { + t.Errorf("expected value %v, got %v", orig.Value, decoded.Value) + } +} + +func TestCompressionQualityFactor_Validation(t *testing.T) { + tests := []struct { + name string + text string + wantErr bool + wantVal int + }{ + { + name: "valid minimum", + text: "0", + wantErr: false, + wantVal: 0, + }, + { + name: "valid maximum", + text: "100", + wantErr: false, + wantVal: 100, + }, + { + name: "valid middle", + text: "50", + wantErr: false, + wantVal: 50, + }, + { + name: "invalid negative", + text: "-1", + wantErr: true, + }, + { + name: "invalid too large", + text: "101", + wantErr: true, + }, + { + name: "invalid not a number", + text: "abc", + wantErr: true, + }, + { + name: "invalid empty", + text: "", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + root := xmldoc.Element{ + Name: NsWSCN + ":CompressionQualityFactor", + Text: tt.text, + } + + decoded, err := decodeCompressionQualityFactor(root) + if tt.wantErr { + if err == nil { + t.Errorf("expected error, got nil") + } + } else { + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if decoded.Value != tt.wantVal { + t.Errorf("expected value %d, got %d", tt.wantVal, decoded.Value) + } + } + }) + } +} + +func TestCompressionQualityFactor_FromXML(t *testing.T) { + // Create XML element manually with all attributes + root := xmldoc.Element{ + Name: NsWSCN + ":CompressionQualityFactor", + Text: "75", + Attrs: []xmldoc.Attr{ + {Name: NsWSCN + ":MustHonor", Value: "0"}, + {Name: NsWSCN + ":Override", Value: "1"}, + {Name: NsWSCN + ":UsedDefault", Value: "false"}, + }, + } + + decoded, err := decodeCompressionQualityFactor(root) + if err != nil { + t.Fatalf("decode returned error: %v", err) + } + + if decoded.Value != 75 { + t.Errorf("expected value 75, got %v", decoded.Value) + } + if mustHonor := optional.Get(decoded.MustHonor); string(mustHonor) != "0" { + t.Errorf("expected MustHonor='0', got '%s'", mustHonor) + } + if override := optional.Get(decoded.Override); string(override) != "1" { + t.Errorf("expected Override='1', got '%s'", override) + } + if usedDefault := optional.Get(decoded.UsedDefault); string(usedDefault) != "false" { + t.Errorf("expected UsedDefault='false', got '%s'", usedDefault) + } +} diff --git a/proto/wsscan/contenttype.go b/proto/wsscan/contenttype.go new file mode 100644 index 0000000..eec0712 --- /dev/null +++ b/proto/wsscan/contenttype.go @@ -0,0 +1,38 @@ +// MFP - Multi-Function Printers and scanners toolkit +// WS-Scan core protocol +// +// Copyright (C) 2024 and up by Yogesh Singla (yogeshsingla481@gmail.com) +// See LICENSE for license terms and conditions +// +// ContentType element + +package wsscan + +import ( + "github.com/OpenPrinting/go-mfp/util/xmldoc" +) + +// ContentType represents the optional element +// that specifies the document content type. +// +// Standard values are: "Auto", "Text", "Photo", "Halftone", "Mixed". +// Values can be extended or subset, so any string value is accepted. +// +// It includes optional wscn:MustHonor, wscn:Override, and wscn:UsedDefault +// attributes (all xs:string, but should be boolean values: 0, false, 1, or true). +type ContentType = AttributedElement[string] + +// decodeContentType decodes [ContentType] from the XML tree. +func decodeContentType(root xmldoc.Element) (ContentType, error) { + return decodeAttributedElement(root, func(s string) (string, error) { + // Accept any string value as values can be extended/subset + return s, nil + }) +} + +// toXMLContentType generates XML tree for the [ContentType]. +func toXMLContentType(ct ContentType, name string) xmldoc.Element { + return ct.toXML(name, func(s string) string { + return s + }) +} diff --git a/proto/wsscan/contenttype_test.go b/proto/wsscan/contenttype_test.go new file mode 100644 index 0000000..5948729 --- /dev/null +++ b/proto/wsscan/contenttype_test.go @@ -0,0 +1,189 @@ +// MFP - Multi-Function Printers and scanners toolkit +// WS-Scan core protocol +// +// Copyright (C) 2024 and up by Yogesh Singla (yogeshsingla481@gmail.com) +// See LICENSE for license terms and conditions +// +// Test for ContentType + +package wsscan + +import ( + "reflect" + "testing" + + "github.com/OpenPrinting/go-mfp/util/optional" + "github.com/OpenPrinting/go-mfp/util/xmldoc" +) + +func TestContentType_RoundTrip(t *testing.T) { + orig := ContentType{ + Value: "Auto", + MustHonor: optional.New(BooleanElement("true")), + Override: optional.New(BooleanElement("false")), + UsedDefault: optional.New(BooleanElement("1")), + } + + elm := toXMLContentType(orig, NsWSCN+":ContentType") + + if elm.Name != NsWSCN+":ContentType" { + t.Errorf("expected element name '%s', got '%s'", + NsWSCN+":ContentType", elm.Name) + } + if elm.Text != "Auto" { + t.Errorf("expected text 'Auto', got '%s'", elm.Text) + } + if len(elm.Attrs) != 3 { + t.Errorf("expected 3 attributes, got %d", len(elm.Attrs)) + } + + // Check attributes + attrsMap := make(map[string]string) + for _, attr := range elm.Attrs { + attrsMap[attr.Name] = attr.Value + } + if attrsMap[NsWSCN+":MustHonor"] != "true" { + t.Errorf("expected MustHonor='true', got '%s'", attrsMap[NsWSCN+":MustHonor"]) + } + if attrsMap[NsWSCN+":Override"] != "false" { + t.Errorf("expected Override='false', got '%s'", attrsMap[NsWSCN+":Override"]) + } + if attrsMap[NsWSCN+":UsedDefault"] != "1" { + t.Errorf("expected UsedDefault='1', got '%s'", attrsMap[NsWSCN+":UsedDefault"]) + } + + // Decode back + decoded, err := decodeContentType(elm) + if err != nil { + t.Fatalf("decode returned error: %v", err) + } + if decoded.Value != orig.Value { + t.Errorf("expected value %v, got %v", orig.Value, decoded.Value) + } + if !reflect.DeepEqual(orig.MustHonor, decoded.MustHonor) { + t.Errorf("expected MustHonor %+v, got %+v", orig.MustHonor, decoded.MustHonor) + } + if !reflect.DeepEqual(orig.Override, decoded.Override) { + t.Errorf("expected Override %+v, got %+v", orig.Override, decoded.Override) + } + if !reflect.DeepEqual(orig.UsedDefault, decoded.UsedDefault) { + t.Errorf("expected UsedDefault %+v, got %+v", orig.UsedDefault, decoded.UsedDefault) + } +} + +func TestContentType_NoAttributes(t *testing.T) { + orig := ContentType{ + Value: "Text", + } + + elm := toXMLContentType(orig, NsWSCN+":ContentType") + + if len(elm.Attrs) != 0 { + t.Errorf("expected no attributes, got %+v", elm.Attrs) + } + if elm.Text != "Text" { + t.Errorf("expected text 'Text', got '%s'", elm.Text) + } + + decoded, err := decodeContentType(elm) + if err != nil { + t.Fatalf("decode returned error: %v", err) + } + if decoded.Value != orig.Value { + t.Errorf("expected value %v, got %v", orig.Value, decoded.Value) + } +} + +func TestContentType_StandardValues(t *testing.T) { + standardValues := []string{"Auto", "Text", "Photo", "Halftone", "Mixed"} + + for _, val := range standardValues { + t.Run(val, func(t *testing.T) { + orig := ContentType{ + Value: val, + } + + elm := toXMLContentType(orig, NsWSCN+":ContentType") + if elm.Text != val { + t.Errorf("expected text '%s', got '%s'", val, elm.Text) + } + + decoded, err := decodeContentType(elm) + if err != nil { + t.Fatalf("decode returned error: %v", err) + } + if decoded.Value != val { + t.Errorf("expected value %s, got %s", val, decoded.Value) + } + }) + } +} + +func TestContentType_ExtendedValues(t *testing.T) { + // Test that extended values are accepted (as per spec: "You can both extend and subset values") + extendedValues := []string{"CustomType1", "AnotherType", "ExtendedValue"} + + for _, val := range extendedValues { + t.Run(val, func(t *testing.T) { + orig := ContentType{ + Value: val, + } + + elm := toXMLContentType(orig, NsWSCN+":ContentType") + decoded, err := decodeContentType(elm) + if err != nil { + t.Fatalf("decode returned error for extended value '%s': %v", val, err) + } + if decoded.Value != val { + t.Errorf("expected value %s, got %s", val, decoded.Value) + } + }) + } +} + +func TestContentType_FromXML(t *testing.T) { + // Create XML element manually with all attributes + root := xmldoc.Element{ + Name: NsWSCN + ":ContentType", + Text: "Photo", + Attrs: []xmldoc.Attr{ + {Name: NsWSCN + ":MustHonor", Value: "0"}, + {Name: NsWSCN + ":Override", Value: "1"}, + {Name: NsWSCN + ":UsedDefault", Value: "false"}, + }, + } + + decoded, err := decodeContentType(root) + if err != nil { + t.Fatalf("decode returned error: %v", err) + } + + if decoded.Value != "Photo" { + t.Errorf("expected value 'Photo', got '%s'", decoded.Value) + } + if mustHonor := optional.Get(decoded.MustHonor); string(mustHonor) != "0" { + t.Errorf("expected MustHonor='0', got '%s'", mustHonor) + } + if override := optional.Get(decoded.Override); string(override) != "1" { + t.Errorf("expected Override='1', got '%s'", override) + } + if usedDefault := optional.Get(decoded.UsedDefault); string(usedDefault) != "false" { + t.Errorf("expected UsedDefault='false', got '%s'", usedDefault) + } +} + +func TestContentType_InvalidBooleanAttributes(t *testing.T) { + // Test that invalid boolean values in attributes are rejected + root := xmldoc.Element{ + Name: NsWSCN + ":ContentType", + Text: "Text", + Attrs: []xmldoc.Attr{ + {Name: NsWSCN + ":MustHonor", Value: "invalid"}, + }, + } + + _, err := decodeContentType(root) + if err == nil { + t.Errorf("expected error for invalid MustHonor value 'invalid', got nil") + } +} diff --git a/proto/wsscan/exposure.go b/proto/wsscan/exposure.go new file mode 100644 index 0000000..3307dea --- /dev/null +++ b/proto/wsscan/exposure.go @@ -0,0 +1,70 @@ +// MFP - Multi-Function Printers and scanners toolkit +// WS-Scan core protocol +// +// Copyright (C) 2024 and up by Yogesh Singla (yogeshsingla481@gmail.com) +// See LICENSE for license terms and conditions +// +// Exposure element + +package wsscan + +import ( + "fmt" + + "github.com/OpenPrinting/go-mfp/util/optional" + "github.com/OpenPrinting/go-mfp/util/xmldoc" +) + +// Exposure represents the optional element +// that specifies the exposure settings of the document. +// +// It includes an optional wscn:MustHonor attribute (xs:string, +// but should be a boolean value: 0, false, 1, or true). +// +// The element contains child elements that define the exposure settings. +type Exposure struct { + MustHonor optional.Val[BooleanElement] + Children []xmldoc.Element +} + +// decodeExposure decodes [Exposure] from the XML tree. +func decodeExposure(root xmldoc.Element) (Exposure, error) { + var exp Exposure + + // Decode optional MustHonor attribute with validation + if attr, found := root.AttrByName(NsWSCN + ":MustHonor"); found { + mustHonor := BooleanElement(attr.Value) + if err := mustHonor.Validate(); err != nil { + return exp, xmldoc.XMLErrWrap(root, fmt.Errorf("mustHonor: %w", err)) + } + exp.MustHonor = optional.New(mustHonor) + } + + // Copy child elements + if root.Children != nil { + exp.Children = make([]xmldoc.Element, len(root.Children)) + copy(exp.Children, root.Children) + } + + return exp, nil +} + +// toXMLExposure generates XML tree for the [Exposure]. +func toXMLExposure(exp Exposure, name string) xmldoc.Element { + elm := xmldoc.Element{ + Name: name, + Children: exp.Children, + } + + // Add optional MustHonor attribute if present + if mustHonor := optional.Get(exp.MustHonor); mustHonor != "" { + elm.Attrs = []xmldoc.Attr{ + { + Name: NsWSCN + ":MustHonor", + Value: string(mustHonor), + }, + } + } + + return elm +} diff --git a/proto/wsscan/exposure_test.go b/proto/wsscan/exposure_test.go new file mode 100644 index 0000000..70dd332 --- /dev/null +++ b/proto/wsscan/exposure_test.go @@ -0,0 +1,207 @@ +// MFP - Multi-Function Printers and scanners toolkit +// WS-Scan core protocol +// +// Copyright (C) 2024 and up by Yogesh Singla (yogeshsingla481@gmail.com) +// See LICENSE for license terms and conditions +// +// Test for Exposure + +package wsscan + +import ( + "reflect" + "testing" + + "github.com/OpenPrinting/go-mfp/util/optional" + "github.com/OpenPrinting/go-mfp/util/xmldoc" +) + +func TestExposure_RoundTrip(t *testing.T) { + orig := Exposure{ + MustHonor: optional.New(BooleanElement("true")), + Children: []xmldoc.Element{ + {Name: NsWSCN + ":SomeChild", Text: "value1"}, + {Name: NsWSCN + ":AnotherChild", Text: "value2"}, + }, + } + + elm := toXMLExposure(orig, NsWSCN+":Exposure") + + if elm.Name != NsWSCN+":Exposure" { + t.Errorf("expected element name '%s', got '%s'", + NsWSCN+":Exposure", elm.Name) + } + if len(elm.Children) != 2 { + t.Errorf("expected 2 children, got %d", len(elm.Children)) + } + if len(elm.Attrs) != 1 { + t.Errorf("expected 1 attribute, got %d", len(elm.Attrs)) + } + + // Check MustHonor attribute + if elm.Attrs[0].Name != NsWSCN+":MustHonor" { + t.Errorf("expected attribute name '%s', got '%s'", + NsWSCN+":MustHonor", elm.Attrs[0].Name) + } + if elm.Attrs[0].Value != "true" { + t.Errorf("expected MustHonor='true', got '%s'", elm.Attrs[0].Value) + } + + // Check children + if elm.Children[0].Name != NsWSCN+":SomeChild" { + t.Errorf("expected first child name '%s', got '%s'", + NsWSCN+":SomeChild", elm.Children[0].Name) + } + if elm.Children[1].Name != NsWSCN+":AnotherChild" { + t.Errorf("expected second child name '%s', got '%s'", + NsWSCN+":AnotherChild", elm.Children[1].Name) + } + + // Decode back + decoded, err := decodeExposure(elm) + if err != nil { + t.Fatalf("decode returned error: %v", err) + } + if !reflect.DeepEqual(orig.MustHonor, decoded.MustHonor) { + t.Errorf("expected MustHonor %+v, got %+v", orig.MustHonor, decoded.MustHonor) + } + if len(decoded.Children) != len(orig.Children) { + t.Errorf("expected %d children, got %d", len(orig.Children), len(decoded.Children)) + } + for i := range orig.Children { + if !orig.Children[i].Equal(decoded.Children[i]) { + t.Errorf("child %d mismatch: expected %+v, got %+v", + i, orig.Children[i], decoded.Children[i]) + } + } +} + +func TestExposure_NoAttributes(t *testing.T) { + orig := Exposure{ + Children: []xmldoc.Element{ + {Name: NsWSCN + ":Child", Text: "value"}, + }, + } + + elm := toXMLExposure(orig, NsWSCN+":Exposure") + + if len(elm.Attrs) != 0 { + t.Errorf("expected no attributes, got %+v", elm.Attrs) + } + if len(elm.Children) != 1 { + t.Errorf("expected 1 child, got %d", len(elm.Children)) + } + + decoded, err := decodeExposure(elm) + if err != nil { + t.Fatalf("decode returned error: %v", err) + } + if decoded.MustHonor != nil { + t.Errorf("expected empty MustHonor, got %+v", decoded.MustHonor) + } + if len(decoded.Children) != 1 { + t.Errorf("expected 1 child, got %d", len(decoded.Children)) + } +} + +func TestExposure_NoChildren(t *testing.T) { + orig := Exposure{ + MustHonor: optional.New(BooleanElement("false")), + Children: nil, + } + + elm := toXMLExposure(orig, NsWSCN+":Exposure") + + if len(elm.Children) != 0 { + t.Errorf("expected no children, got %d", len(elm.Children)) + } + if len(elm.Attrs) != 1 { + t.Errorf("expected 1 attribute, got %d", len(elm.Attrs)) + } + + decoded, err := decodeExposure(elm) + if err != nil { + t.Fatalf("decode returned error: %v", err) + } + if mustHonor := optional.Get(decoded.MustHonor); string(mustHonor) != "false" { + t.Errorf("expected MustHonor='false', got '%s'", mustHonor) + } + if len(decoded.Children) != 0 { + t.Errorf("expected no children, got %d", len(decoded.Children)) + } +} + +func TestExposure_FromXML(t *testing.T) { + // Create XML element manually with MustHonor and children + root := xmldoc.Element{ + Name: NsWSCN + ":Exposure", + Attrs: []xmldoc.Attr{ + {Name: NsWSCN + ":MustHonor", Value: "1"}, + }, + Children: []xmldoc.Element{ + {Name: NsWSCN + ":ExposureSettings", Text: "auto"}, + {Name: NsWSCN + ":Brightness", Text: "50"}, + }, + } + + decoded, err := decodeExposure(root) + if err != nil { + t.Fatalf("decode returned error: %v", err) + } + + if mustHonor := optional.Get(decoded.MustHonor); string(mustHonor) != "1" { + t.Errorf("expected MustHonor='1', got '%s'", mustHonor) + } + if len(decoded.Children) != 2 { + t.Errorf("expected 2 children, got %d", len(decoded.Children)) + } + if decoded.Children[0].Name != NsWSCN+":ExposureSettings" { + t.Errorf("expected first child name '%s', got '%s'", + NsWSCN+":ExposureSettings", decoded.Children[0].Name) + } + if decoded.Children[1].Name != NsWSCN+":Brightness" { + t.Errorf("expected second child name '%s', got '%s'", + NsWSCN+":Brightness", decoded.Children[1].Name) + } +} + +func TestExposure_InvalidBooleanAttribute(t *testing.T) { + // Test that invalid boolean value in MustHonor attribute is rejected + root := xmldoc.Element{ + Name: NsWSCN + ":Exposure", + Attrs: []xmldoc.Attr{ + {Name: NsWSCN + ":MustHonor", Value: "invalid"}, + }, + Children: []xmldoc.Element{ + {Name: NsWSCN + ":Child", Text: "value"}, + }, + } + + _, err := decodeExposure(root) + if err == nil { + t.Errorf("expected error for invalid MustHonor value 'invalid', got nil") + } +} + +func TestExposure_ValidBooleanValues(t *testing.T) { + validValues := []string{"0", "1", "false", "true", "False", "True"} + + for _, val := range validValues { + t.Run(val, func(t *testing.T) { + root := xmldoc.Element{ + Name: NsWSCN + ":Exposure", + Attrs: []xmldoc.Attr{ + {Name: NsWSCN + ":MustHonor", Value: val}, + }, + } + + decoded, err := decodeExposure(root) + if err != nil { + t.Errorf("unexpected error for valid value '%s': %v", val, err) + } + if mustHonor := optional.Get(decoded.MustHonor); string(mustHonor) == "" { + t.Errorf("expected MustHonor to be set for value '%s'", val) + } + }) + } +} diff --git a/proto/wsscan/filmscanmodeelement.go b/proto/wsscan/filmscanmodeelement.go new file mode 100644 index 0000000..11141e3 --- /dev/null +++ b/proto/wsscan/filmscanmodeelement.go @@ -0,0 +1,42 @@ +// MFP - Multi-Function Printers and scanners toolkit +// WS-Scan core protocol +// +// Copyright (C) 2024 and up by Yogesh Singla (yogeshsingla481@gmail.com) +// See LICENSE for license terms and conditions +// +// FilmScanMode element (not to be confused with FilmScanMode enum type) + +package wsscan + +import ( + "github.com/OpenPrinting/go-mfp/util/xmldoc" +) + +// FilmScanModeElement represents the optional element +// that specifies the exposure type of the film to be scanned. +// +// Standard values include: "NotApplicable", "ColorSlideFilm", +// "ColorNegativeFilm", "BlackandWhiteNegativeFilm". +// Values can be extended or subset, so any string value is accepted. +// +// It includes optional wscn:MustHonor, wscn:Override, and wscn:UsedDefault +// attributes (all xs:string, but should be boolean values: 0, false, 1, or true). +// +// Note: This is different from the [FilmScanMode] enum type which is used +// in FilmScanModesSupported lists. +type FilmScanModeElement = AttributedElement[string] + +// decodeFilmScanModeElement decodes [FilmScanModeElement] from the XML tree. +func decodeFilmScanModeElement(root xmldoc.Element) (FilmScanModeElement, error) { + return decodeAttributedElement(root, func(s string) (string, error) { + // Accept any string value as values can be extended/subset + return s, nil + }) +} + +// toXMLFilmScanModeElement generates XML tree for the [FilmScanModeElement]. +func toXMLFilmScanModeElement(fsm FilmScanModeElement, name string) xmldoc.Element { + return fsm.toXML(name, func(s string) string { + return s + }) +} diff --git a/proto/wsscan/filmscanmodeelement_test.go b/proto/wsscan/filmscanmodeelement_test.go new file mode 100644 index 0000000..947f4d2 --- /dev/null +++ b/proto/wsscan/filmscanmodeelement_test.go @@ -0,0 +1,194 @@ +// MFP - Multi-Function Printers and scanners toolkit +// WS-Scan core protocol +// +// Copyright (C) 2024 and up by Yogesh Singla (yogeshsingla481@gmail.com) +// See LICENSE for license terms and conditions +// +// Test for FilmScanModeElement + +package wsscan + +import ( + "reflect" + "testing" + + "github.com/OpenPrinting/go-mfp/util/optional" + "github.com/OpenPrinting/go-mfp/util/xmldoc" +) + +func TestFilmScanModeElement_RoundTrip(t *testing.T) { + orig := FilmScanModeElement{ + Value: "ColorSlideFilm", + MustHonor: optional.New(BooleanElement("true")), + Override: optional.New(BooleanElement("false")), + UsedDefault: optional.New(BooleanElement("1")), + } + + elm := toXMLFilmScanModeElement(orig, NsWSCN+":FilmScanMode") + + if elm.Name != NsWSCN+":FilmScanMode" { + t.Errorf("expected element name '%s', got '%s'", + NsWSCN+":FilmScanMode", elm.Name) + } + if elm.Text != "ColorSlideFilm" { + t.Errorf("expected text 'ColorSlideFilm', got '%s'", elm.Text) + } + if len(elm.Attrs) != 3 { + t.Errorf("expected 3 attributes, got %d", len(elm.Attrs)) + } + + // Check attributes + attrsMap := make(map[string]string) + for _, attr := range elm.Attrs { + attrsMap[attr.Name] = attr.Value + } + if attrsMap[NsWSCN+":MustHonor"] != "true" { + t.Errorf("expected MustHonor='true', got '%s'", attrsMap[NsWSCN+":MustHonor"]) + } + if attrsMap[NsWSCN+":Override"] != "false" { + t.Errorf("expected Override='false', got '%s'", attrsMap[NsWSCN+":Override"]) + } + if attrsMap[NsWSCN+":UsedDefault"] != "1" { + t.Errorf("expected UsedDefault='1', got '%s'", attrsMap[NsWSCN+":UsedDefault"]) + } + + // Decode back + decoded, err := decodeFilmScanModeElement(elm) + if err != nil { + t.Fatalf("decode returned error: %v", err) + } + if decoded.Value != orig.Value { + t.Errorf("expected value %v, got %v", orig.Value, decoded.Value) + } + if !reflect.DeepEqual(orig.MustHonor, decoded.MustHonor) { + t.Errorf("expected MustHonor %+v, got %+v", orig.MustHonor, decoded.MustHonor) + } + if !reflect.DeepEqual(orig.Override, decoded.Override) { + t.Errorf("expected Override %+v, got %+v", orig.Override, decoded.Override) + } + if !reflect.DeepEqual(orig.UsedDefault, decoded.UsedDefault) { + t.Errorf("expected UsedDefault %+v, got %+v", orig.UsedDefault, decoded.UsedDefault) + } +} + +func TestFilmScanModeElement_NoAttributes(t *testing.T) { + orig := FilmScanModeElement{ + Value: "NotApplicable", + } + + elm := toXMLFilmScanModeElement(orig, NsWSCN+":FilmScanMode") + + if len(elm.Attrs) != 0 { + t.Errorf("expected no attributes, got %+v", elm.Attrs) + } + if elm.Text != "NotApplicable" { + t.Errorf("expected text 'NotApplicable', got '%s'", elm.Text) + } + + decoded, err := decodeFilmScanModeElement(elm) + if err != nil { + t.Fatalf("decode returned error: %v", err) + } + if decoded.Value != orig.Value { + t.Errorf("expected value %v, got %v", orig.Value, decoded.Value) + } +} + +func TestFilmScanModeElement_StandardValues(t *testing.T) { + standardValues := []string{ + "NotApplicable", + "ColorSlideFilm", + "ColorNegativeFilm", + "BlackandWhiteNegativeFilm", + } + + for _, val := range standardValues { + t.Run(val, func(t *testing.T) { + orig := FilmScanModeElement{ + Value: val, + } + + elm := toXMLFilmScanModeElement(orig, NsWSCN+":FilmScanMode") + if elm.Text != val { + t.Errorf("expected text '%s', got '%s'", val, elm.Text) + } + + decoded, err := decodeFilmScanModeElement(elm) + if err != nil { + t.Fatalf("decode returned error: %v", err) + } + if decoded.Value != val { + t.Errorf("expected value %s, got %s", val, decoded.Value) + } + }) + } +} + +func TestFilmScanModeElement_ExtendedValues(t *testing.T) { + // Test that extended values are accepted (as per spec: "You can both extend and subset values") + extendedValues := []string{"CustomFilmType", "AnotherType", "ExtendedValue"} + + for _, val := range extendedValues { + t.Run(val, func(t *testing.T) { + orig := FilmScanModeElement{ + Value: val, + } + + elm := toXMLFilmScanModeElement(orig, NsWSCN+":FilmScanMode") + decoded, err := decodeFilmScanModeElement(elm) + if err != nil { + t.Fatalf("decode returned error for extended value '%s': %v", val, err) + } + if decoded.Value != val { + t.Errorf("expected value %s, got %s", val, decoded.Value) + } + }) + } +} + +func TestFilmScanModeElement_FromXML(t *testing.T) { + // Create XML element manually with all attributes + root := xmldoc.Element{ + Name: NsWSCN + ":FilmScanMode", + Text: "ColorNegativeFilm", + Attrs: []xmldoc.Attr{ + {Name: NsWSCN + ":MustHonor", Value: "0"}, + {Name: NsWSCN + ":Override", Value: "1"}, + {Name: NsWSCN + ":UsedDefault", Value: "false"}, + }, + } + + decoded, err := decodeFilmScanModeElement(root) + if err != nil { + t.Fatalf("decode returned error: %v", err) + } + + if decoded.Value != "ColorNegativeFilm" { + t.Errorf("expected value 'ColorNegativeFilm', got '%s'", decoded.Value) + } + if mustHonor := optional.Get(decoded.MustHonor); string(mustHonor) != "0" { + t.Errorf("expected MustHonor='0', got '%s'", mustHonor) + } + if override := optional.Get(decoded.Override); string(override) != "1" { + t.Errorf("expected Override='1', got '%s'", override) + } + if usedDefault := optional.Get(decoded.UsedDefault); string(usedDefault) != "false" { + t.Errorf("expected UsedDefault='false', got '%s'", usedDefault) + } +} + +func TestFilmScanModeElement_InvalidBooleanAttributes(t *testing.T) { + // Test that invalid boolean values in attributes are rejected + root := xmldoc.Element{ + Name: NsWSCN + ":FilmScanMode", + Text: "ColorSlideFilm", + Attrs: []xmldoc.Attr{ + {Name: NsWSCN + ":MustHonor", Value: "invalid"}, + }, + } + + _, err := decodeFilmScanModeElement(root) + if err == nil { + t.Errorf("expected error for invalid MustHonor value 'invalid', got nil") + } +} diff --git a/proto/wsscan/formatelement.go b/proto/wsscan/formatelement.go new file mode 100644 index 0000000..d925f3c --- /dev/null +++ b/proto/wsscan/formatelement.go @@ -0,0 +1,45 @@ +// MFP - Multi-Function Printers and scanners toolkit +// WS-Scan core protocol +// +// Copyright (C) 2024 and up by Yogesh Singla (yogeshsingla481@gmail.com) +// See LICENSE for license terms and conditions +// +// Format element (not to be confused with FormatValue enum type) + +package wsscan + +import ( + "github.com/OpenPrinting/go-mfp/util/xmldoc" +) + +// FormatElement represents the optional element +// that indicates a single file format and compression type supported by the scanner. +// +// Standard values include: "dib", "exif", "jbig", "jfif", "jpeg2k", "pdf-a", "png", +// "tiff-single-uncompressed", "tiff-single-g4", "tiff-single-g3mh", +// "tiff-single-jpeg-tn2", "tiff-multi-uncompressed", "tiff-multi-g4", +// "tiff-multi-g3mh", "tiff-multi-jpeg-tn2", "xps". +// Vendor-defined values are also allowed. +// +// It includes optional wscn:Override and wscn:UsedDefault attributes +// (all xs:string, but should be boolean values: 0, false, 1, or true). +// Note: This element does NOT have a MustHonor attribute. +// +// Note: This is different from the [FormatValue] enum type which is used +// in FormatsSupported lists. +type FormatElement = AttributedElement[string] + +// decodeFormatElement decodes [FormatElement] from the XML tree. +func decodeFormatElement(root xmldoc.Element) (FormatElement, error) { + return decodeAttributedElement(root, func(s string) (string, error) { + // Accept any string value as vendor-defined values are allowed + return s, nil + }) +} + +// toXMLFormatElement generates XML tree for the [FormatElement]. +func toXMLFormatElement(f FormatElement, name string) xmldoc.Element { + return f.toXML(name, func(s string) string { + return s + }) +} diff --git a/proto/wsscan/formatelement_test.go b/proto/wsscan/formatelement_test.go new file mode 100644 index 0000000..c6ed4c6 --- /dev/null +++ b/proto/wsscan/formatelement_test.go @@ -0,0 +1,253 @@ +// MFP - Multi-Function Printers and scanners toolkit +// WS-Scan core protocol +// +// Copyright (C) 2024 and up by Yogesh Singla (yogeshsingla481@gmail.com) +// See LICENSE for license terms and conditions +// +// Test for FormatElement + +package wsscan + +import ( + "reflect" + "testing" + + "github.com/OpenPrinting/go-mfp/util/optional" + "github.com/OpenPrinting/go-mfp/util/xmldoc" +) + +func TestFormatElement_RoundTrip(t *testing.T) { + orig := FormatElement{ + Value: "png", + Override: optional.New(BooleanElement("false")), + UsedDefault: optional.New(BooleanElement("1")), + } + + elm := toXMLFormatElement(orig, NsWSCN+":Format") + + if elm.Name != NsWSCN+":Format" { + t.Errorf("expected element name '%s', got '%s'", + NsWSCN+":Format", elm.Name) + } + if elm.Text != "png" { + t.Errorf("expected text 'png', got '%s'", elm.Text) + } + if len(elm.Attrs) != 2 { + t.Errorf("expected 2 attributes, got %d", len(elm.Attrs)) + } + + // Check attributes + attrsMap := make(map[string]string) + for _, attr := range elm.Attrs { + attrsMap[attr.Name] = attr.Value + } + if attrsMap[NsWSCN+":Override"] != "false" { + t.Errorf("expected Override='false', got '%s'", attrsMap[NsWSCN+":Override"]) + } + if attrsMap[NsWSCN+":UsedDefault"] != "1" { + t.Errorf("expected UsedDefault='1', got '%s'", attrsMap[NsWSCN+":UsedDefault"]) + } + // MustHonor should not be present + if _, found := attrsMap[NsWSCN+":MustHonor"]; found { + t.Errorf("expected MustHonor to not be present, but it was found") + } + + // Decode back + decoded, err := decodeFormatElement(elm) + if err != nil { + t.Fatalf("decode returned error: %v", err) + } + if decoded.Value != orig.Value { + t.Errorf("expected value %v, got %v", orig.Value, decoded.Value) + } + if !reflect.DeepEqual(orig.Override, decoded.Override) { + t.Errorf("expected Override %+v, got %+v", orig.Override, decoded.Override) + } + if !reflect.DeepEqual(orig.UsedDefault, decoded.UsedDefault) { + t.Errorf("expected UsedDefault %+v, got %+v", orig.UsedDefault, decoded.UsedDefault) + } +} + +func TestFormatElement_NoAttributes(t *testing.T) { + orig := FormatElement{ + Value: "jpeg2k", + } + + elm := toXMLFormatElement(orig, NsWSCN+":Format") + + if len(elm.Attrs) != 0 { + t.Errorf("expected no attributes, got %+v", elm.Attrs) + } + if elm.Text != "jpeg2k" { + t.Errorf("expected text 'jpeg2k', got '%s'", elm.Text) + } + + decoded, err := decodeFormatElement(elm) + if err != nil { + t.Fatalf("decode returned error: %v", err) + } + if decoded.Value != orig.Value { + t.Errorf("expected value %v, got %v", orig.Value, decoded.Value) + } +} + +func TestFormatElement_StandardValues(t *testing.T) { + standardValues := []string{ + "dib", + "exif", + "jbig", + "jfif", + "jpeg2k", + "pdf-a", + "png", + "tiff-single-uncompressed", + "tiff-single-g4", + "tiff-single-g3mh", + "tiff-single-jpeg-tn2", + "tiff-multi-uncompressed", + "tiff-multi-g4", + "tiff-multi-g3mh", + "tiff-multi-jpeg-tn2", + "xps", + } + + for _, val := range standardValues { + t.Run(val, func(t *testing.T) { + orig := FormatElement{ + Value: val, + } + + elm := toXMLFormatElement(orig, NsWSCN+":Format") + if elm.Text != val { + t.Errorf("expected text '%s', got '%s'", val, elm.Text) + } + + decoded, err := decodeFormatElement(elm) + if err != nil { + t.Fatalf("decode returned error: %v", err) + } + if decoded.Value != val { + t.Errorf("expected value %s, got %s", val, decoded.Value) + } + }) + } +} + +func TestFormatElement_VendorDefinedValues(t *testing.T) { + // Test that vendor-defined values are accepted + vendorValues := []string{"vendor-format-1", "custom-format", "extended-value"} + + for _, val := range vendorValues { + t.Run(val, func(t *testing.T) { + orig := FormatElement{ + Value: val, + } + + elm := toXMLFormatElement(orig, NsWSCN+":Format") + decoded, err := decodeFormatElement(elm) + if err != nil { + t.Fatalf("decode returned error for vendor-defined value '%s': %v", val, err) + } + if decoded.Value != val { + t.Errorf("expected value %s, got %s", val, decoded.Value) + } + }) + } +} + +func TestFormatElement_FromXML(t *testing.T) { + // Create XML element manually with Override and UsedDefault (no MustHonor) + root := xmldoc.Element{ + Name: NsWSCN + ":Format", + Text: "tiff-multi-g4", + Attrs: []xmldoc.Attr{ + {Name: NsWSCN + ":Override", Value: "0"}, + {Name: NsWSCN + ":UsedDefault", Value: "true"}, + }, + } + + decoded, err := decodeFormatElement(root) + if err != nil { + t.Fatalf("decode returned error: %v", err) + } + + if decoded.Value != "tiff-multi-g4" { + t.Errorf("expected value 'tiff-multi-g4', got '%s'", decoded.Value) + } + if override := optional.Get(decoded.Override); string(override) != "0" { + t.Errorf("expected Override='0', got '%s'", override) + } + if usedDefault := optional.Get(decoded.UsedDefault); string(usedDefault) != "true" { + t.Errorf("expected UsedDefault='true', got '%s'", usedDefault) + } + // MustHonor should not be set + if decoded.MustHonor != nil { + t.Errorf("expected MustHonor to be nil, got %+v", decoded.MustHonor) + } +} + +func TestFormatElement_InvalidBooleanAttributes(t *testing.T) { + // Test that invalid boolean values in attributes are rejected + root := xmldoc.Element{ + Name: NsWSCN + ":Format", + Text: "png", + Attrs: []xmldoc.Attr{ + {Name: NsWSCN + ":Override", Value: "invalid"}, + }, + } + + _, err := decodeFormatElement(root) + if err == nil { + t.Errorf("expected error for invalid Override value 'invalid', got nil") + } +} + +func TestFormatElement_OnlyOverride(t *testing.T) { + // Test with only Override attribute + orig := FormatElement{ + Value: "pdf-a", + Override: optional.New(BooleanElement("1")), + } + + elm := toXMLFormatElement(orig, NsWSCN+":Format") + + if len(elm.Attrs) != 1 { + t.Errorf("expected 1 attribute, got %d", len(elm.Attrs)) + } + if elm.Attrs[0].Name != NsWSCN+":Override" { + t.Errorf("expected Override attribute, got %s", elm.Attrs[0].Name) + } + + decoded, err := decodeFormatElement(elm) + if err != nil { + t.Fatalf("decode returned error: %v", err) + } + if decoded.Value != "pdf-a" { + t.Errorf("expected value 'pdf-a', got '%s'", decoded.Value) + } +} + +func TestFormatElement_OnlyUsedDefault(t *testing.T) { + // Test with only UsedDefault attribute + orig := FormatElement{ + Value: "exif", + UsedDefault: optional.New(BooleanElement("false")), + } + + elm := toXMLFormatElement(orig, NsWSCN+":Format") + + if len(elm.Attrs) != 1 { + t.Errorf("expected 1 attribute, got %d", len(elm.Attrs)) + } + if elm.Attrs[0].Name != NsWSCN+":UsedDefault" { + t.Errorf("expected UsedDefault attribute, got %s", elm.Attrs[0].Name) + } + + decoded, err := decodeFormatElement(elm) + if err != nil { + t.Fatalf("decode returned error: %v", err) + } + if decoded.Value != "exif" { + t.Errorf("expected value 'exif', got '%s'", decoded.Value) + } +} diff --git a/proto/wsscan/imagestotransfer.go b/proto/wsscan/imagestotransfer.go new file mode 100644 index 0000000..e0af974 --- /dev/null +++ b/proto/wsscan/imagestotransfer.go @@ -0,0 +1,45 @@ +// MFP - Multi-Function Printers and scanners toolkit +// WS-Scan core protocol +// +// Copyright (C) 2024 and up by Yogesh Singla (yogeshsingla481@gmail.com) +// See LICENSE for license terms and conditions +// +// ImagesToTransfer element + +package wsscan + +import ( + "fmt" + "strconv" + + "github.com/OpenPrinting/go-mfp/util/xmldoc" +) + +// ImagesToTransfer represents the optional element +// that specifies the number of images to scan for the current job. +// +// The value must be an integer in the range from 0 through 2147483648. +// +// It includes optional wscn:MustHonor, wscn:Override, and wscn:UsedDefault +// attributes (all xs:string, but should be boolean values: 0, false, 1, or true). +type ImagesToTransfer = AttributedElement[int] + +// decodeImagesToTransfer decodes [ImagesToTransfer] from the XML tree. +func decodeImagesToTransfer(root xmldoc.Element) ( + ImagesToTransfer, error) { + return decodeAttributedElement(root, func(s string) (int, error) { + val, err := strconv.Atoi(s) + if err != nil { + return 0, fmt.Errorf("invalid integer: %q", s) + } + if val < 0 || val > 2147483648 { + return 0, fmt.Errorf("value out of range [0-2147483648]: %d", val) + } + return val, nil + }) +} + +// toXMLImagesToTransfer generates XML tree for the [ImagesToTransfer]. +func toXMLImagesToTransfer(itt ImagesToTransfer, name string) xmldoc.Element { + return itt.toXML(name, strconv.Itoa) +} diff --git a/proto/wsscan/imagestotransfer_test.go b/proto/wsscan/imagestotransfer_test.go new file mode 100644 index 0000000..6217890 --- /dev/null +++ b/proto/wsscan/imagestotransfer_test.go @@ -0,0 +1,249 @@ +// MFP - Multi-Function Printers and scanners toolkit +// WS-Scan core protocol +// +// Copyright (C) 2024 and up by Yogesh Singla (yogeshsingla481@gmail.com) +// See LICENSE for license terms and conditions +// +// Test for ImagesToTransfer + +package wsscan + +import ( + "reflect" + "testing" + + "github.com/OpenPrinting/go-mfp/util/optional" + "github.com/OpenPrinting/go-mfp/util/xmldoc" +) + +func TestImagesToTransfer_RoundTrip(t *testing.T) { + orig := ImagesToTransfer{ + Value: 10, + MustHonor: optional.New(BooleanElement("true")), + Override: optional.New(BooleanElement("false")), + UsedDefault: optional.New(BooleanElement("1")), + } + + elm := toXMLImagesToTransfer(orig, NsWSCN+":ImagesToTransfer") + + if elm.Name != NsWSCN+":ImagesToTransfer" { + t.Errorf("expected element name '%s', got '%s'", + NsWSCN+":ImagesToTransfer", elm.Name) + } + if elm.Text != "10" { + t.Errorf("expected text '10', got '%s'", elm.Text) + } + if len(elm.Attrs) != 3 { + t.Errorf("expected 3 attributes, got %d", len(elm.Attrs)) + } + + // Check attributes + attrsMap := make(map[string]string) + for _, attr := range elm.Attrs { + attrsMap[attr.Name] = attr.Value + } + if attrsMap[NsWSCN+":MustHonor"] != "true" { + t.Errorf("expected MustHonor='true', got '%s'", attrsMap[NsWSCN+":MustHonor"]) + } + if attrsMap[NsWSCN+":Override"] != "false" { + t.Errorf("expected Override='false', got '%s'", attrsMap[NsWSCN+":Override"]) + } + if attrsMap[NsWSCN+":UsedDefault"] != "1" { + t.Errorf("expected UsedDefault='1', got '%s'", attrsMap[NsWSCN+":UsedDefault"]) + } + + // Decode back + decoded, err := decodeImagesToTransfer(elm) + if err != nil { + t.Fatalf("decode returned error: %v", err) + } + if decoded.Value != orig.Value { + t.Errorf("expected value %v, got %v", orig.Value, decoded.Value) + } + if !reflect.DeepEqual(orig.MustHonor, decoded.MustHonor) { + t.Errorf("expected MustHonor %+v, got %+v", orig.MustHonor, decoded.MustHonor) + } + if !reflect.DeepEqual(orig.Override, decoded.Override) { + t.Errorf("expected Override %+v, got %+v", orig.Override, decoded.Override) + } + if !reflect.DeepEqual(orig.UsedDefault, decoded.UsedDefault) { + t.Errorf("expected UsedDefault %+v, got %+v", orig.UsedDefault, decoded.UsedDefault) + } +} + +func TestImagesToTransfer_NoAttributes(t *testing.T) { + orig := ImagesToTransfer{ + Value: 5, + } + + elm := toXMLImagesToTransfer(orig, NsWSCN+":ImagesToTransfer") + + if len(elm.Attrs) != 0 { + t.Errorf("expected no attributes, got %+v", elm.Attrs) + } + if elm.Text != "5" { + t.Errorf("expected text '5', got '%s'", elm.Text) + } + + decoded, err := decodeImagesToTransfer(elm) + if err != nil { + t.Fatalf("decode returned error: %v", err) + } + if decoded.Value != orig.Value { + t.Errorf("expected value %v, got %v", orig.Value, decoded.Value) + } +} + +func TestImagesToTransfer_Validation(t *testing.T) { + tests := []struct { + name string + text string + wantErr bool + wantVal int + }{ + { + name: "valid minimum", + text: "0", + wantErr: false, + wantVal: 0, + }, + { + name: "valid maximum", + text: "2147483648", + wantErr: false, + wantVal: 2147483648, + }, + { + name: "valid middle", + text: "100", + wantErr: false, + wantVal: 100, + }, + { + name: "valid large value", + text: "1000000", + wantErr: false, + wantVal: 1000000, + }, + { + name: "invalid negative", + text: "-1", + wantErr: true, + }, + { + name: "invalid too large", + text: "2147483649", + wantErr: true, + }, + { + name: "invalid not a number", + text: "abc", + wantErr: true, + }, + { + name: "invalid empty", + text: "", + wantErr: true, + }, + { + name: "invalid float", + text: "10.5", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + root := xmldoc.Element{ + Name: NsWSCN + ":ImagesToTransfer", + Text: tt.text, + } + + decoded, err := decodeImagesToTransfer(root) + if tt.wantErr { + if err == nil { + t.Errorf("expected error, got nil") + } + } else { + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if decoded.Value != tt.wantVal { + t.Errorf("expected value %d, got %d", tt.wantVal, decoded.Value) + } + } + }) + } +} + +func TestImagesToTransfer_FromXML(t *testing.T) { + // Create XML element manually with all attributes + root := xmldoc.Element{ + Name: NsWSCN + ":ImagesToTransfer", + Text: "25", + Attrs: []xmldoc.Attr{ + {Name: NsWSCN + ":MustHonor", Value: "0"}, + {Name: NsWSCN + ":Override", Value: "1"}, + {Name: NsWSCN + ":UsedDefault", Value: "false"}, + }, + } + + decoded, err := decodeImagesToTransfer(root) + if err != nil { + t.Fatalf("decode returned error: %v", err) + } + + if decoded.Value != 25 { + t.Errorf("expected value 25, got %v", decoded.Value) + } + if mustHonor := optional.Get(decoded.MustHonor); string(mustHonor) != "0" { + t.Errorf("expected MustHonor='0', got '%s'", mustHonor) + } + if override := optional.Get(decoded.Override); string(override) != "1" { + t.Errorf("expected Override='1', got '%s'", override) + } + if usedDefault := optional.Get(decoded.UsedDefault); string(usedDefault) != "false" { + t.Errorf("expected UsedDefault='false', got '%s'", usedDefault) + } +} + +func TestImagesToTransfer_EdgeCases(t *testing.T) { + tests := []struct { + name string + value int + }{ + { + name: "zero", + value: 0, + }, + { + name: "one", + value: 1, + }, + { + name: "maximum", + value: 2147483648, + }, + { + name: "near maximum", + value: 2147483647, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + orig := ImagesToTransfer{ + Value: tt.value, + } + + elm := toXMLImagesToTransfer(orig, NsWSCN+":ImagesToTransfer") + decoded, err := decodeImagesToTransfer(elm) + if err != nil { + t.Fatalf("decode returned error for value %d: %v", tt.value, err) + } + if decoded.Value != tt.value { + t.Errorf("expected value %d, got %d", tt.value, decoded.Value) + } + }) + } +} diff --git a/proto/wsscan/inputsource.go b/proto/wsscan/inputsource.go new file mode 100644 index 0000000..b348b10 --- /dev/null +++ b/proto/wsscan/inputsource.go @@ -0,0 +1,37 @@ +// MFP - Multi-Function Printers and scanners toolkit +// WS-Scan core protocol +// +// Copyright (C) 2024 and up by Yogesh Singla (yogeshsingla481@gmail.com) +// See LICENSE for license terms and conditions +// +// InputSource element + +package wsscan + +import ( + "github.com/OpenPrinting/go-mfp/util/xmldoc" +) + +// InputSource represents the optional element +// that specifies the source of the original document on the scanning device. +// +// Standard values are: "ADF", "ADFDuplex", "Film", "Platen". +// +// It includes optional wscn:MustHonor, wscn:Override, and wscn:UsedDefault +// attributes (all xs:string, but should be boolean values: 0, false, 1, or true). +type InputSource = AttributedElement[string] + +// decodeInputSource decodes [InputSource] from the XML tree. +func decodeInputSource(root xmldoc.Element) (InputSource, error) { + return decodeAttributedElement(root, func(s string) (string, error) { + // Accept any string value + return s, nil + }) +} + +// toXMLInputSource generates XML tree for the [InputSource]. +func toXMLInputSource(is InputSource, name string) xmldoc.Element { + return is.toXML(name, func(s string) string { + return s + }) +} diff --git a/proto/wsscan/inputsource_test.go b/proto/wsscan/inputsource_test.go new file mode 100644 index 0000000..1de3921 --- /dev/null +++ b/proto/wsscan/inputsource_test.go @@ -0,0 +1,199 @@ +// MFP - Multi-Function Printers and scanners toolkit +// WS-Scan core protocol +// +// Copyright (C) 2024 and up by Yogesh Singla (yogeshsingla481@gmail.com) +// See LICENSE for license terms and conditions +// +// Test for InputSource + +package wsscan + +import ( + "reflect" + "testing" + + "github.com/OpenPrinting/go-mfp/util/optional" + "github.com/OpenPrinting/go-mfp/util/xmldoc" +) + +func TestInputSource_RoundTrip(t *testing.T) { + orig := InputSource{ + Value: "ADF", + MustHonor: optional.New(BooleanElement("true")), + Override: optional.New(BooleanElement("false")), + UsedDefault: optional.New(BooleanElement("1")), + } + + elm := toXMLInputSource(orig, NsWSCN+":InputSource") + + if elm.Name != NsWSCN+":InputSource" { + t.Errorf("expected element name '%s', got '%s'", + NsWSCN+":InputSource", elm.Name) + } + if elm.Text != "ADF" { + t.Errorf("expected text 'ADF', got '%s'", elm.Text) + } + if len(elm.Attrs) != 3 { + t.Errorf("expected 3 attributes, got %d", len(elm.Attrs)) + } + + // Check attributes + attrsMap := make(map[string]string) + for _, attr := range elm.Attrs { + attrsMap[attr.Name] = attr.Value + } + if attrsMap[NsWSCN+":MustHonor"] != "true" { + t.Errorf("expected MustHonor='true', got '%s'", attrsMap[NsWSCN+":MustHonor"]) + } + if attrsMap[NsWSCN+":Override"] != "false" { + t.Errorf("expected Override='false', got '%s'", attrsMap[NsWSCN+":Override"]) + } + if attrsMap[NsWSCN+":UsedDefault"] != "1" { + t.Errorf("expected UsedDefault='1', got '%s'", attrsMap[NsWSCN+":UsedDefault"]) + } + + // Decode back + decoded, err := decodeInputSource(elm) + if err != nil { + t.Fatalf("decode returned error: %v", err) + } + if decoded.Value != orig.Value { + t.Errorf("expected value %v, got %v", orig.Value, decoded.Value) + } + if !reflect.DeepEqual(orig.MustHonor, decoded.MustHonor) { + t.Errorf("expected MustHonor %+v, got %+v", orig.MustHonor, decoded.MustHonor) + } + if !reflect.DeepEqual(orig.Override, decoded.Override) { + t.Errorf("expected Override %+v, got %+v", orig.Override, decoded.Override) + } + if !reflect.DeepEqual(orig.UsedDefault, decoded.UsedDefault) { + t.Errorf("expected UsedDefault %+v, got %+v", orig.UsedDefault, decoded.UsedDefault) + } +} + +func TestInputSource_NoAttributes(t *testing.T) { + orig := InputSource{ + Value: "Platen", + } + + elm := toXMLInputSource(orig, NsWSCN+":InputSource") + + if len(elm.Attrs) != 0 { + t.Errorf("expected no attributes, got %+v", elm.Attrs) + } + if elm.Text != "Platen" { + t.Errorf("expected text 'Platen', got '%s'", elm.Text) + } + + decoded, err := decodeInputSource(elm) + if err != nil { + t.Fatalf("decode returned error: %v", err) + } + if decoded.Value != orig.Value { + t.Errorf("expected value %v, got %v", orig.Value, decoded.Value) + } +} + +func TestInputSource_StandardValues(t *testing.T) { + standardValues := []string{ + "ADF", + "ADFDuplex", + "Film", + "Platen", + } + + for _, val := range standardValues { + t.Run(val, func(t *testing.T) { + orig := InputSource{ + Value: val, + } + + elm := toXMLInputSource(orig, NsWSCN+":InputSource") + if elm.Text != val { + t.Errorf("expected text '%s', got '%s'", val, elm.Text) + } + + decoded, err := decodeInputSource(elm) + if err != nil { + t.Fatalf("decode returned error: %v", err) + } + if decoded.Value != val { + t.Errorf("expected value %s, got %s", val, decoded.Value) + } + }) + } +} + +func TestInputSource_FromXML(t *testing.T) { + // Create XML element manually with all attributes + root := xmldoc.Element{ + Name: NsWSCN + ":InputSource", + Text: "ADFDuplex", + Attrs: []xmldoc.Attr{ + {Name: NsWSCN + ":MustHonor", Value: "0"}, + {Name: NsWSCN + ":Override", Value: "1"}, + {Name: NsWSCN + ":UsedDefault", Value: "false"}, + }, + } + + decoded, err := decodeInputSource(root) + if err != nil { + t.Fatalf("decode returned error: %v", err) + } + + if decoded.Value != "ADFDuplex" { + t.Errorf("expected value 'ADFDuplex', got '%s'", decoded.Value) + } + if mustHonor := optional.Get(decoded.MustHonor); string(mustHonor) != "0" { + t.Errorf("expected MustHonor='0', got '%s'", mustHonor) + } + if override := optional.Get(decoded.Override); string(override) != "1" { + t.Errorf("expected Override='1', got '%s'", override) + } + if usedDefault := optional.Get(decoded.UsedDefault); string(usedDefault) != "false" { + t.Errorf("expected UsedDefault='false', got '%s'", usedDefault) + } +} + +func TestInputSource_InvalidBooleanAttributes(t *testing.T) { + // Test that invalid boolean values in attributes are rejected + root := xmldoc.Element{ + Name: NsWSCN + ":InputSource", + Text: "ADF", + Attrs: []xmldoc.Attr{ + {Name: NsWSCN + ":MustHonor", Value: "invalid"}, + }, + } + + _, err := decodeInputSource(root) + if err == nil { + t.Errorf("expected error for invalid MustHonor value 'invalid', got nil") + } +} + +func TestInputSource_AllStandardValuesWithAttributes(t *testing.T) { + standardValues := []string{"ADF", "ADFDuplex", "Film", "Platen"} + + for _, val := range standardValues { + t.Run(val, func(t *testing.T) { + orig := InputSource{ + Value: val, + MustHonor: optional.New(BooleanElement("1")), + Override: optional.New(BooleanElement("0")), + UsedDefault: optional.New(BooleanElement("true")), + } + + elm := toXMLInputSource(orig, NsWSCN+":InputSource") + decoded, err := decodeInputSource(elm) + if err != nil { + t.Fatalf("decode returned error for value '%s': %v", val, err) + } + if decoded.Value != val { + t.Errorf("expected value %s, got %s", val, decoded.Value) + } + if len(elm.Attrs) != 3 { + t.Errorf("expected 3 attributes for value '%s', got %d", val, len(elm.Attrs)) + } + }) + } +}